Skip to content

Commit

Permalink
feat(cli): --quickstart flag for ejecting server schemas (#5797)
Browse files Browse the repository at this point in the history
* feat: builder flag

* feat: rename function

* feat: rename functions

* feat: undo bad change

* feat: rename flag

* feat: add helper

* feat: edit puntuation

* feat: remove flag help, add doc block

* feat: replace enum with a dynamic import

* feat: added --config flag

* feat: minor fixes

* feat: cleanup duplicate line

* feat: remote redundant await

* feat: telemetry

* fix: types path

* chore: lint

* feat: adjustments for new api

* feat: fix missing dataset

* fix: no use var before declaration

* chore: remove test data

* feat: add prettier as dep

* chore: pnpm i

* feat: pr fixes + consume signed url

* chore: remove console error

* feat: update comment

* feat: more explicit trace log

* feat: preview support

* chore: update deps

* fix: typo

* feat: rename func

* feat: account for root

* feat: cleanup serializer

* feat: cleanup serializer

* feat: changes post review

* feat: slide sort
  • Loading branch information
RostiMelk committed Mar 4, 2024
1 parent 75ac3cf commit 174a616
Show file tree
Hide file tree
Showing 8 changed files with 414 additions and 30 deletions.
1 change: 1 addition & 0 deletions packages/@sanity/cli/package.json
Expand Up @@ -122,6 +122,7 @@
"p-filter": "^2.1.0",
"p-timeout": "^4.0.0",
"preferred-pm": "^3.0.3",
"prettier": "^3.1.0",
"promise-props-recursive": "^2.0.2",
"recast": "^0.22.0",
"resolve-from": "^5.0.0",
Expand Down
9 changes: 9 additions & 0 deletions packages/@sanity/cli/src/__telemetry__/init.telemetry.ts
Expand Up @@ -10,6 +10,14 @@ interface LoginStep {
alreadyLoggedIn?: boolean
}

interface FetchJourneyConfigStep {
step: 'fetchJourneyConfig'
projectId: string
datasetName: string
displayName: string
isFirstProject: boolean
}

interface CreateOrSelectProjectStep {
step: 'createOrSelectProject'
projectId: string
Expand Down Expand Up @@ -68,6 +76,7 @@ interface SelectPackageManagerStep {
type InitStepResult =
| StartStep
| LoginStep
| FetchJourneyConfigStep
| CreateOrSelectProjectStep
| CreateOrSelectDatasetStep
| UseDetectedFrameworkStep
Expand Down
Expand Up @@ -6,6 +6,7 @@ import {debug} from '../../debug'
import {studioDependencies} from '../../studioDependencies'
import {type CliCommandContext} from '../../types'
import {copy} from '../../util/copy'
import {getAndWriteJourneySchemaWorker} from '../../util/journeyConfig'
import {resolveLatestVersions} from '../../util/resolveLatestVersions'
import {createCliConfig} from './createCliConfig'
import {createPackageManifest} from './createPackageManifest'
Expand All @@ -16,6 +17,11 @@ import templates from './templates'
export interface BootstrapOptions {
packageName: string
templateName: string
/**
* Used for initializing a project from a server schema that is saved in the Journey API
* @beta
*/
schemaUrl?: string
outputPath: string
useTypeScript: boolean
variables: GenerateConfigOptions['variables']
Expand All @@ -40,7 +46,12 @@ export async function bootstrapTemplate(

// Copy template files
debug('Copying files from template "%s" to "%s"', templateName, outputPath)
let spinner = output.spinner('Bootstrapping files from template').start()
let spinner = output
.spinner(
opts.schemaUrl ? 'Extracting your Sanity configuration' : 'Bootstrapping files from template',
)
.start()

await copy(sourceDir, outputPath, {
rename: useTypeScript ? toTypeScriptPath : undefined,
})
Expand All @@ -50,6 +61,17 @@ export async function bootstrapTemplate(
await fs.copyFile(path.join(sharedDir, 'tsconfig.json'), path.join(outputPath, 'tsconfig.json'))
}

// If we have a schemaUrl, get the schema and write it to disk
// At this point the selected template should already have been forced to "clean"
if (opts.schemaUrl) {
debug('Fetching and writing remote schema "%s"', opts.schemaUrl)
await getAndWriteJourneySchemaWorker({
schemasPath: path.join(outputPath, 'schemaTypes'),
useTypeScript,
schemaUrl: opts.schemaUrl,
})
}

spinner.succeed()

// Merge global and template-specific plugins and dependencies
Expand Down
114 changes: 85 additions & 29 deletions packages/@sanity/cli/src/actions/init-project/initProject.ts
Expand Up @@ -38,6 +38,7 @@ import {getProjectDefaults, type ProjectDefaults} from '../../util/getProjectDef
import {getUserConfig} from '../../util/getUserConfig'
import {isCommandGroup} from '../../util/isCommandGroup'
import {isInteractive} from '../../util/isInteractive'
import {fetchJourneyConfig} from '../../util/journeyConfig'
import {login, type LoginFlags} from '../login/login'
import {createProject} from '../project/createProject'
import {type BootstrapOptions, bootstrapTemplate} from './bootstrapTemplate'
Expand Down Expand Up @@ -66,8 +67,17 @@ import {
// eslint-disable-next-line no-process-env
const isCI = process.env.CI

/**
* @deprecated - No longer used
*/
export interface InitOptions {
template: string
// /**
// * Used for initializing a project from a server schema that is saved in the Journey API
// * This will override the `template` option.
// * @beta
// */
// journeyProjectId?: string
outputDir: string
name: string
displayName: string
Expand Down Expand Up @@ -235,7 +245,11 @@ export default async function initSanity(
}

const usingBareOrEnv = cliFlags.bare || cliFlags.env
print(`You're setting up a new project!`)
print(
cliFlags.quickstart
? "You're ejecting a remote Sanity project!"
: `You're setting up a new project!`,
)
print(`We'll make sure you have an account with Sanity.io. ${usingBareOrEnv ? '' : `Then we'll`}`)
if (!usingBareOrEnv) {
print('install an open-source JS content editor that connects to')
Expand All @@ -260,39 +274,13 @@ export default async function initSanity(
}

const flags = await prepareFlags()

// We're authenticated, now lets select or create a project
debug('Prompting user to select or create a project')
const {
projectId,
displayName,
isFirstProject,
userAction: getOrCreateUserAction,
} = await getOrCreateProject()
trace.log({step: 'createOrSelectProject', projectId, selectedOption: getOrCreateUserAction})
const {projectId, displayName, isFirstProject, datasetName, schemaUrl} = await getProjectDetails()

const sluggedName = deburr(displayName.toLowerCase())
.replace(/\s+/g, '-')
.replace(/[^a-z0-9-]/g, '')

debug(`Project with name ${displayName} selected`)

// Now let's pick or create a dataset
debug('Prompting user to select or create a dataset')
const {datasetName, userAction: getOrCreateDatasetUserAction} = await getOrCreateDataset({
projectId,
displayName,
dataset: flags.dataset,
aclMode: flags.visibility,
defaultConfig: flags['dataset-default'],
})
trace.log({
step: 'createOrSelectDataset',
selectedOption: getOrCreateDatasetUserAction,
datasetName,
visibility: flags.visibility as 'private' | 'public',
})
debug(`Dataset with name ${datasetName} selected`)

// If user doesn't want to output any template code
if (bareOutput) {
print(`\n${chalk.green('Success!')} Below are your project details:\n`)
Expand Down Expand Up @@ -542,6 +530,7 @@ export default async function initSanity(
outputPath,
packageName: sluggedName,
templateName,
schemaUrl,
useTypeScript,
variables: {
dataset: datasetName,
Expand Down Expand Up @@ -656,6 +645,57 @@ export default async function initSanity(
print('datasets and collaborators safe and snug.')
}

async function getProjectDetails(): Promise<{
projectId: string
datasetName: string
displayName: string
isFirstProject: boolean
schemaUrl?: string
}> {
// If we're doing a quickstart, we don't need to prompt for project details
if (flags.quickstart) {
debug('Fetching project details from Journey API')
const data = await fetchJourneyConfig(apiClient, flags.quickstart)
trace.log({
step: 'fetchJourneyConfig',
projectId: data.projectId,
datasetName: data.datasetName,
displayName: data.displayName,
isFirstProject: data.isFirstProject,
})
return data
}

debug('Prompting user to select or create a project')
const project = await getOrCreateProject()
debug(`Project with name ${project.displayName} selected`)

// Now let's pick or create a dataset
debug('Prompting user to select or create a dataset')
const dataset = await getOrCreateDataset({
projectId: project.projectId,
displayName: project.displayName,
dataset: flags.dataset,
aclMode: flags.visibility,
defaultConfig: flags['dataset-default'],
})
debug(`Dataset with name ${dataset.datasetName} selected`)

trace.log({
step: 'createOrSelectDataset',
selectedOption: dataset.userAction,
datasetName: dataset.datasetName,
visibility: flags.visibility as 'private' | 'public',
})

return {
projectId: project.projectId,
displayName: project.displayName,
isFirstProject: project.isFirstProject,
datasetName: dataset.datasetName,
}
}

// eslint-disable-next-line complexity
async function getOrCreateProject(): Promise<{
projectId: string
Expand Down Expand Up @@ -923,6 +963,12 @@ export default async function initSanity(
}

function selectProjectTemplate() {
// Make sure the --quickstart and --template are not used together
// Force template to clean if --quickstart is used
if (flags.quickstart) {
return 'clean'
}

const defaultTemplate = unattended || flags.template ? flags.template || 'clean' : null
if (defaultTemplate) {
return defaultTemplate
Expand Down Expand Up @@ -996,6 +1042,16 @@ export default async function initSanity(
)
}

if (
cliFlags.quickstart &&
(cliFlags.project || cliFlags.dataset || cliFlags.visibility || cliFlags.template)
) {
const disallowed = ['project', 'dataset', 'visibility', 'template']
const usedDisallowed = disallowed.filter((flag) => cliFlags[flag as keyof InitFlags])
const usedDisallowedStr = usedDisallowed.map((flag) => `--${flag}`).join(', ')
throw new Error(`\`--quickstart\` cannot be combined with ${usedDisallowedStr}`)
}

if (createProjectName === true) {
throw new Error('Please specify a project name (`--create-project <name>`)')
}
Expand Down
10 changes: 10 additions & 0 deletions packages/@sanity/cli/src/commands/init/initCommand.ts
Expand Up @@ -52,8 +52,18 @@ export interface InitFlags {
project?: string
dataset?: string
template?: string

visibility?: string
typescript?: boolean
/**
* Used for initializing a project from a server schema that is saved in the Journey API
* Overrides `project` option.
* Overrides `dataset` option.
* Overrides `template` option.
* Overrides `visibility` option.
* @beta
*/
quickstart?: string
bare?: boolean
env?: boolean | string
git?: boolean | string
Expand Down

0 comments on commit 174a616

Please sign in to comment.