diff --git a/packages/@sanity/cli/src/actions/init-project/bootstrapTemplate.ts b/packages/@sanity/cli/src/actions/init-project/bootstrapTemplate.ts index 186d5a19ef0..1c14b8f0614 100644 --- a/packages/@sanity/cli/src/actions/init-project/bootstrapTemplate.ts +++ b/packages/@sanity/cli/src/actions/init-project/bootstrapTemplate.ts @@ -127,6 +127,7 @@ export async function bootstrapTemplate( const cliConfig = await createCliConfig({ projectId: variables.projectId, dataset: variables.dataset, + autoUpdates: variables.autoUpdates, }) // Write non-template files to disc diff --git a/packages/@sanity/cli/src/actions/init-project/createCliConfig.ts b/packages/@sanity/cli/src/actions/init-project/createCliConfig.ts index 4b83847ce4b..abd1e5c8c92 100644 --- a/packages/@sanity/cli/src/actions/init-project/createCliConfig.ts +++ b/packages/@sanity/cli/src/actions/init-project/createCliConfig.ts @@ -9,19 +9,26 @@ export default defineCliConfig({ api: { projectId: '%projectId%', dataset: '%dataset%' - } + }, + /** + * Enable auto-updates for studios. + * Learn more at https://www.sanity.io/docs/cli#auto-updates + */ + autoUpdates: __BOOL__autoUpdates__, }) ` export interface GenerateCliConfigOptions { projectId: string dataset: string + autoUpdates: boolean } export function createCliConfig(options: GenerateCliConfigOptions): string { const variables = options const template = defaultTemplate.trimStart() const ast = parse(template, {parser}) + traverse(ast, { StringLiteral: { enter({node}) { @@ -29,13 +36,35 @@ export function createCliConfig(options: GenerateCliConfigOptions): string { if (!value.startsWith('%') || !value.endsWith('%')) { return } - const variableName = value.slice(1, -1) as keyof GenerateCliConfigOptions if (!(variableName in variables)) { throw new Error(`Template variable '${value}' not defined`) } - - node.value = variables[variableName] || '' + const newValue = variables[variableName] + /* + * although there are valid non-strings in our config, + * they're not in StringLiteral nodes, so assume undefined + */ + node.value = typeof newValue === 'string' ? newValue : '' + }, + }, + Identifier: { + enter(path) { + if (!path.node.name.startsWith('__BOOL__')) { + return + } + const variableName = path.node.name.replace( + /^__BOOL__(.+?)__$/, + '$1', + ) as keyof GenerateCliConfigOptions + if (!(variableName in variables)) { + throw new Error(`Template variable '${variableName}' not defined`) + } + const value = variables[variableName] + if (typeof value !== 'boolean') { + throw new Error(`Expected boolean value for '${variableName}'`) + } + path.replaceWith({type: 'BooleanLiteral', value}) }, }, }) diff --git a/packages/@sanity/cli/src/actions/init-project/createStudioConfig.ts b/packages/@sanity/cli/src/actions/init-project/createStudioConfig.ts index 6aa2b8f8ecb..c5d29795ec6 100644 --- a/packages/@sanity/cli/src/actions/init-project/createStudioConfig.ts +++ b/packages/@sanity/cli/src/actions/init-project/createStudioConfig.ts @@ -34,6 +34,7 @@ export interface GenerateConfigOptions { variables: { projectId: string dataset: string + autoUpdates: boolean projectName?: string sourceName?: string sourceTitle?: string @@ -60,8 +61,12 @@ export function createStudioConfig(options: GenerateConfigOptions): string { if (!(variableName in variables)) { throw new Error(`Template variable '${value}' not defined`) } - - node.value = variables[variableName] || '' + const newValue = variables[variableName] + /* + * although there are valid non-strings in our config, + * they're not in this template, so assume undefined + */ + node.value = typeof newValue === 'string' ? newValue : '' }, }, }) diff --git a/packages/@sanity/cli/src/actions/init-project/initProject.ts b/packages/@sanity/cli/src/actions/init-project/initProject.ts index 9e785da777e..fe87d5eb5dd 100644 --- a/packages/@sanity/cli/src/actions/init-project/initProject.ts +++ b/packages/@sanity/cli/src/actions/init-project/initProject.ts @@ -567,6 +567,12 @@ export default async function initSanity( trace.log({step: 'useTypeScript', selectedOption: useTypeScript ? 'yes' : 'no'}) } + // we enable auto-updates by default, but allow users to specify otherwise + let autoUpdates = true + if (typeof cliFlags['auto-updates'] === 'boolean') { + autoUpdates = cliFlags['auto-updates'] + } + // Build a full set of resolved options const templateOptions: BootstrapOptions = { outputPath, @@ -575,6 +581,7 @@ export default async function initSanity( schemaUrl, useTypeScript, variables: { + autoUpdates, dataset: datasetName, projectId, projectName: displayName || answers.projectName, diff --git a/packages/@sanity/cli/src/commands/init/initCommand.ts b/packages/@sanity/cli/src/commands/init/initCommand.ts index 2863f626b38..394dc71e8e2 100644 --- a/packages/@sanity/cli/src/commands/init/initCommand.ts +++ b/packages/@sanity/cli/src/commands/init/initCommand.ts @@ -27,6 +27,7 @@ Options --coupon Optionally select a coupon for a new project (cannot be used with --project-plan) --no-typescript Do not use TypeScript for template files --package-manager Specify which package manager to use [allowed: ${allowedPackageManagersString}] + --no-auto-updates Disable auto updates of studio versions Examples # Initialize a new project, prompt for required information along the way @@ -60,6 +61,7 @@ export interface InitFlags { 'visibility'?: string 'typescript'?: boolean + 'auto-updates'?: boolean /** * Used for initializing a project from a server schema that is saved in the Journey API * Overrides `project` option. diff --git a/packages/@sanity/cli/test/init.test.ts b/packages/@sanity/cli/test/init.test.ts new file mode 100644 index 00000000000..98b47f57383 --- /dev/null +++ b/packages/@sanity/cli/test/init.test.ts @@ -0,0 +1,94 @@ +import fs from 'node:fs/promises' +import path from 'node:path' + +import {describe, expect} from '@jest/globals' + +import templates from '../src/actions/init-project/templates' +import {describeCliTest, testConcurrent} from './shared/describe' +import {baseTestPath, cliProjectId, getTestRunArgs, runSanityCmdCommand} from './shared/environment' + +describeCliTest('CLI: `sanity init v3`', () => { + describe.each(Object.keys(templates))('for template %s', (template) => { + testConcurrent('adds autoUpdates: true to cli config', async () => { + const version = 'v3' + const testRunArgs = getTestRunArgs(version) + const outpath = `test-template-${template}-${version}` + + await runSanityCmdCommand(version, [ + 'init', + '--y', + '--project', + cliProjectId, + '--dataset', + testRunArgs.dataset, + '--template', + template, + '--output-path', + `${baseTestPath}/${outpath}`, + '--package-manager', + 'manual', + ]) + + const cliConfig = await fs.readFile( + path.join(baseTestPath, outpath, 'sanity.cli.ts'), + 'utf-8', + ) + + expect(cliConfig).toContain(`projectId: '${cliProjectId}'`) + expect(cliConfig).toContain(`dataset: '${testRunArgs.dataset}'`) + expect(cliConfig).toContain(`autoUpdates: true`) + }) + }) + + testConcurrent('adds autoUpdates: true to cli config for javascript projects', async () => { + const version = 'v3' + const testRunArgs = getTestRunArgs(version) + const outpath = `test-template-${version}` + + await runSanityCmdCommand(version, [ + 'init', + '--y', + '--project', + cliProjectId, + '--dataset', + testRunArgs.dataset, + '--output-path', + `${baseTestPath}/${outpath}`, + '--package-manager', + 'manual', + '--no-typescript', + ]) + + const cliConfig = await fs.readFile(path.join(baseTestPath, outpath, 'sanity.cli.js'), 'utf-8') + + expect(cliConfig).toContain(`projectId: '${cliProjectId}'`) + expect(cliConfig).toContain(`dataset: '${testRunArgs.dataset}'`) + expect(cliConfig).toContain(`autoUpdates: true`) + }) + + testConcurrent('adds autoUpdates: false to cli config if flag provided', async () => { + const version = 'v3' + const testRunArgs = getTestRunArgs(version) + const outpath = `test-template-${version}` + + await runSanityCmdCommand(version, [ + 'init', + '--y', + '--project', + cliProjectId, + '--dataset', + testRunArgs.dataset, + '--output-path', + `${baseTestPath}/${outpath}`, + '--package-manager', + 'manual', + '--no-auto-updates', + ]) + + const cliConfig = await fs.readFile(path.join(baseTestPath, outpath, 'sanity.cli.ts'), 'utf-8') + + expect(cliConfig).toContain(`projectId: '${cliProjectId}'`) + expect(cliConfig).toContain(`dataset: '${testRunArgs.dataset}'`) + expect(cliConfig).toContain(`autoUpdates: false`) + }) +}) diff --git a/packages/sanity/src/_internal/cli/commands/build/buildCommand.ts b/packages/sanity/src/_internal/cli/commands/build/buildCommand.ts index 143bfbf98ca..e45cf70dc96 100644 --- a/packages/sanity/src/_internal/cli/commands/build/buildCommand.ts +++ b/packages/sanity/src/_internal/cli/commands/build/buildCommand.ts @@ -4,7 +4,7 @@ import {BuildSanityStudioCommandFlags} from '../../actions/build/buildAction' const helpText = ` Options --source-maps Enable source maps for built bundles (increases size of bundle) - --auto-updates Enable auto updates of studio versions + --auto-updates / --no-auto-updates Enable/disable auto updates of studio versions --no-minify Skip minifying built JavaScript (speeds up build, increases size of bundle) -y, --yes Unattended mode, answers "yes" to any "yes/no" prompt and otherwise uses defaults diff --git a/packages/sanity/src/_internal/cli/commands/deploy/deployCommand.ts b/packages/sanity/src/_internal/cli/commands/deploy/deployCommand.ts index 1ace736bcf5..a4b8bc18c84 100644 --- a/packages/sanity/src/_internal/cli/commands/deploy/deployCommand.ts +++ b/packages/sanity/src/_internal/cli/commands/deploy/deployCommand.ts @@ -9,7 +9,7 @@ import {type DeployStudioActionFlags} from '../../actions/deploy/deployAction' const helpText = ` Options --source-maps Enable source maps for built bundles (increases size of bundle) - --auto-updates Enable auto updates of studio versions + --auto-updates / --no-auto-updates Enable/disable auto updates of studio versions --no-minify Skip minifying built JavaScript (speeds up build, increases size of bundle) --no-build Don't build the studio prior to deploy, instead deploying the version currently in \`dist/\`