@sanity/cli 7.6.0 → 7.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/deployStudio.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {styleText} from 'node:util'\nimport {createGzip, type Gzip} from 'node:zlib'\n\nimport {formatSchemaValidation, SchemaExtractionError} from '@sanity/cli-build/_internal/extract'\nimport {exitCodes} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {getWorkbench} from '@sanity/workbench-cli/deploy'\nimport {type StudioManifest} from 'sanity'\nimport {pack} from 'tar-fs'\n\nimport {createDeployment, type UserApplication} from '../../services/userApplications.js'\nimport {getAppId} from '../../util/appId.js'\nimport {NO_PROJECT_ID} from '../../util/errorMessages.js'\nimport {buildStudio} from '../build/buildStudio.js'\nimport {createStudioUserApplication} from './createUserApplication.js'\nimport {\n checkAutoUpdates,\n checkBuild,\n checkPackageVersion,\n type CheckReporter,\n checkStudioTarget,\n verifyOutputDir,\n} from './deployChecks.js'\nimport {deployDebug} from './deployDebug.js'\nimport {listDeploymentFiles} from './deploymentPlan.js'\nimport {runDeploy} from './deployRunner.js'\nimport {deployStudioSchemasAndManifests} from './deployStudioSchemasAndManifests.js'\nimport {findUserApplicationForStudio} from './findUserApplication.js'\nimport {type DeployAppOptions} from './types.js'\n\nexport function deployStudio(options: DeployAppOptions): Promise<void> {\n return runDeploy(options, {\n listFiles: ({flags, projectRoot, sourceDir}) =>\n flags.external ? Promise.resolve([]) : listDeploymentFiles(sourceDir, projectRoot.directory),\n run: runStudioDeployment,\n type: 'studio',\n })\n}\n\n/** Validates the deploy, extracts and uploads the schema, and ships the build. */\nasync function runStudioDeployment(\n options: DeployAppOptions,\n reporter: CheckReporter,\n): Promise<void> {\n const {cliConfig, flags, output, sourceDir} = options\n const workDir = options.projectRoot.directory\n const isExternal = !!flags.external\n const isWorkbenchApp = getWorkbench(cliConfig) !== null\n const projectId = cliConfig.api?.projectId\n const dryRun = !!flags['dry-run']\n\n const isAutoUpdating = checkAutoUpdates(reporter, {cliConfig, flags})\n\n if (isExternal && isWorkbenchApp) {\n // A federated app deploys through Sanity's build/hosting pipeline, which\n // --external skips.\n reporter.report({\n exitCode: exitCodes.USAGE_ERROR,\n message: 'Deploying a federated application to an external host is not yet supported',\n solution: 'Remove the --external flag to deploy to Sanity hosting',\n status: 'fail',\n })\n }\n\n const version = await checkPackageVersion(reporter, {\n moduleName: 'sanity',\n workDir,\n })\n\n reporter.report(\n projectId\n ? {message: `Project: ${projectId}`, status: 'pass'}\n : {\n message: NO_PROJECT_ID,\n solution: 'Add `api.projectId` to sanity.cli.ts',\n status: 'fail',\n },\n )\n\n const application = await resolveStudioApplication(options, {dryRun, reporter})\n\n await checkBuild(reporter, {\n build: () =>\n buildStudio({\n autoUpdatesEnabled: isAutoUpdating,\n calledFromDeploy: true,\n cliConfig,\n flags,\n outDir: sourceDir,\n output,\n workDir,\n }),\n skipReason: studioBuildSkipReason({build: flags.build, isExternal}),\n successMessage: 'Studio built',\n })\n\n if (!isExternal) {\n await verifyOutputDir({isWorkbenchApp, reporter, sourceDir})\n }\n\n // Dry run stops here — everything below mutates.\n if (dryRun) return\n\n // A real deploy has already exited if anything failed; landing here without a\n // resolved application or version means the deploy target was never resolved.\n if (!application || !version) return\n\n const studioManifest = await uploadStudioSchema(options, {isExternal})\n await shipStudioDeployment({\n application,\n isAutoUpdating,\n isExternal,\n options,\n studioManifest,\n version,\n })\n}\n\n/**\n * Finds the application a real deploy targets, registering a studio host when\n * none is configured. A dry run resolves and reports the target read-only instead.\n */\nasync function resolveStudioApplication(\n options: DeployAppOptions,\n {dryRun, reporter}: {dryRun: boolean; reporter: CheckReporter},\n): Promise<UserApplication | null> {\n const {cliConfig, flags, output} = options\n const isExternal = !!flags.external\n // Sets the title on a newly registered studio; blank falls back to undefined\n const title = flags.title?.trim() || undefined\n\n if (dryRun) {\n await checkStudioTarget(reporter, {\n appId: getAppId(cliConfig),\n isExternal,\n projectId: cliConfig.api?.projectId,\n studioHost: cliConfig.studioHost,\n title,\n urlFlag: flags.url,\n })\n return null\n }\n\n const projectId = cliConfig.api?.projectId ?? ''\n let application = await findUserApplicationForStudio({\n appId: getAppId(cliConfig),\n isExternal,\n output,\n projectId,\n studioHost: cliConfig.studioHost,\n title,\n unattended: !!flags.yes,\n urlFlag: flags.url,\n })\n\n if (!application) {\n if (isExternal) {\n output.log('Your project has not been registered with an external studio URL.')\n output.log('Please enter the full URL where your studio is hosted.')\n } else {\n output.log('Your project has not been assigned a studio hostname.')\n output.log('To deploy your Sanity Studio to our hosted sanity.studio service,')\n output.log('you will need one. Please enter the subdomain you want to use.')\n }\n\n application = await createStudioUserApplication({\n projectId,\n title,\n urlType: isExternal ? 'external' : 'internal',\n })\n deployDebug('Created user application', application)\n }\n\n deployDebug('Found user application', application)\n return application\n}\n\n/** Extracts the studio schema and manifest and uploads them to the schema store. */\nasync function uploadStudioSchema(\n options: DeployAppOptions,\n {isExternal}: {isExternal: boolean},\n): Promise<StudioManifest | null> {\n const {cliConfig, flags, output, projectRoot, sourceDir} = options\n\n let studioManifest: StudioManifest | null = null\n try {\n studioManifest = await deployStudioSchemasAndManifests({\n configPath: projectRoot.path,\n isExternal,\n outPath: `${sourceDir}/static`,\n projectId: cliConfig.api?.projectId ?? '',\n schemaRequired: flags['schema-required'],\n verbose: flags.verbose,\n workDir: projectRoot.directory,\n })\n } catch (error) {\n deployDebug('Error deploying studio schemas and manifests', error)\n if (error instanceof SchemaExtractionError) {\n output.error(formatSchemaValidation(error.validation || []), {exit: 1})\n }\n output.error(`Error deploying studio schemas and manifests: ${error}`, {exit: 1})\n }\n\n if (!studioManifest) {\n output.error('Failed to generate studio manifest. Please check your schemas and manifests.', {\n exit: 1,\n })\n }\n\n return studioManifest\n}\n\nasync function shipStudioDeployment({\n application,\n isAutoUpdating,\n isExternal,\n options,\n studioManifest,\n version,\n}: {\n application: UserApplication\n isAutoUpdating: boolean\n isExternal: boolean\n options: DeployAppOptions\n studioManifest: StudioManifest | null\n version: string\n}): Promise<void> {\n const {cliConfig, output, sourceDir} = options\n\n let tarball: Gzip | undefined\n if (!isExternal) {\n tarball = pack(dirname(sourceDir), {entries: [basename(sourceDir)]}).pipe(createGzip())\n }\n\n const spin = spinner(isExternal ? 'Registering studio' : 'Deploying to sanity.studio').start()\n let location: string\n try {\n ;({location} = await createDeployment({\n applicationId: application.id,\n isApp: false,\n isAutoUpdating,\n manifest: studioManifest,\n projectId: cliConfig.api?.projectId,\n tarball,\n version,\n }))\n } catch (error) {\n spin.fail()\n throw error\n }\n spin.succeed()\n\n output.log(\n isExternal\n ? `\\nSuccess! Studio registered`\n : `\\nSuccess! Studio deployed to ${styleText('cyan', location)}`,\n )\n\n if (getAppId(cliConfig)) return\n\n const example = `Example:\nexport default defineCliConfig({\n //…\n deployment: {\n ${styleText('cyan', `appId: '${application.id}'`)},\n },\n //…\n})`\n output.log(`\\nAdd ${styleText('cyan', `appId: '${application.id}'`)}`)\n output.log(`to the \\`deployment\\` section in sanity.cli.js or sanity.cli.ts`)\n output.log(`to avoid prompting for application id on next deploy.`)\n output.log(`\\n${example}`)\n}\n\nfunction studioBuildSkipReason({build, isExternal}: {build: boolean; isExternal: boolean}) {\n if (isExternal) return 'Build skipped for externally hosted studios'\n if (!build) return 'Build skipped (--no-build) — validating existing output directory'\n return\n}\n"],"names":["basename","dirname","styleText","createGzip","formatSchemaValidation","SchemaExtractionError","exitCodes","spinner","getWorkbench","pack","createDeployment","getAppId","NO_PROJECT_ID","buildStudio","createStudioUserApplication","checkAutoUpdates","checkBuild","checkPackageVersion","checkStudioTarget","verifyOutputDir","deployDebug","listDeploymentFiles","runDeploy","deployStudioSchemasAndManifests","findUserApplicationForStudio","deployStudio","options","listFiles","flags","projectRoot","sourceDir","external","Promise","resolve","directory","run","runStudioDeployment","type","reporter","cliConfig","output","workDir","isExternal","isWorkbenchApp","projectId","api","dryRun","isAutoUpdating","report","exitCode","USAGE_ERROR","message","solution","status","version","moduleName","application","resolveStudioApplication","build","autoUpdatesEnabled","calledFromDeploy","outDir","skipReason","studioBuildSkipReason","successMessage","studioManifest","uploadStudioSchema","shipStudioDeployment","title","trim","undefined","appId","studioHost","urlFlag","url","unattended","yes","log","urlType","configPath","path","outPath","schemaRequired","verbose","error","validation","exit","tarball","entries","pipe","spin","start","location","applicationId","id","isApp","manifest","fail","succeed","example"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/deploy/deployStudio.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {styleText} from 'node:util'\nimport {createGzip, type Gzip} from 'node:zlib'\n\nimport {formatSchemaValidation, SchemaExtractionError} from '@sanity/cli-build/_internal/extract'\nimport {exitCodes} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {getWorkbench} from '@sanity/workbench-cli/deploy'\nimport {type StudioManifest} from 'sanity'\nimport {pack} from 'tar-fs'\n\nimport {createDeployment, type UserApplication} from '../../services/userApplications.js'\nimport {getAppId} from '../../util/appId.js'\nimport {NO_PROJECT_ID} from '../../util/errorMessages.js'\nimport {buildStudio} from '../build/buildStudio.js'\nimport {createStudioUserApplication} from './createUserApplication.js'\nimport {\n checkAutoUpdates,\n checkBuild,\n checkPackageVersion,\n type CheckReporter,\n checkStudioTarget,\n verifyOutputDir,\n} from './deployChecks.js'\nimport {deployDebug} from './deployDebug.js'\nimport {listDeploymentFiles} from './deploymentPlan.js'\nimport {type DeployResult, runDeploy} from './deployRunner.js'\nimport {deployStudioSchemasAndManifests} from './deployStudioSchemasAndManifests.js'\nimport {findUserApplicationForStudio} from './findUserApplication.js'\nimport {type DeployAppOptions} from './types.js'\n\nconst STUDIO_PACKAGE = 'sanity'\n\nexport function deployStudio(options: DeployAppOptions): Promise<void> {\n return runDeploy(options, {\n listFiles: ({flags, projectRoot, sourceDir}) =>\n flags.external ? Promise.resolve([]) : listDeploymentFiles(sourceDir, projectRoot.directory),\n run: runStudioDeployment,\n type: 'studio',\n })\n}\n\n/** Validates the deploy, extracts and uploads the schema, and ships the build. */\nasync function runStudioDeployment(\n options: DeployAppOptions,\n reporter: CheckReporter,\n): Promise<DeployResult | void> {\n const {cliConfig, flags, output, sourceDir} = options\n const workDir = options.projectRoot.directory\n const isExternal = !!flags.external\n const isWorkbenchApp = getWorkbench(cliConfig) !== null\n const projectId = cliConfig.api?.projectId\n const dryRun = !!flags['dry-run']\n\n const isAutoUpdating = checkAutoUpdates(reporter, {cliConfig, flags})\n\n if (isExternal && isWorkbenchApp) {\n // A federated app deploys through Sanity's build/hosting pipeline, which\n // --external skips.\n reporter.report({\n exitCode: exitCodes.USAGE_ERROR,\n message: 'Deploying a federated application to an external host is not yet supported',\n solution: 'Remove the --external flag to deploy to Sanity hosting',\n status: 'fail',\n })\n }\n\n const version = await checkPackageVersion(reporter, {\n moduleName: STUDIO_PACKAGE,\n workDir,\n })\n\n reporter.report(\n projectId\n ? {message: `Project: ${projectId}`, status: 'pass'}\n : {\n message: NO_PROJECT_ID,\n solution: 'Add `api.projectId` to sanity.cli.ts',\n status: 'fail',\n },\n )\n\n const application = await resolveStudioApplication(options, {dryRun, reporter})\n\n await checkBuild(reporter, {\n build: () =>\n buildStudio({\n autoUpdatesEnabled: isAutoUpdating,\n calledFromDeploy: true,\n cliConfig,\n flags,\n outDir: sourceDir,\n output,\n workDir,\n }),\n skipReason: studioBuildSkipReason({build: flags.build, isExternal}),\n successMessage: 'Studio built',\n })\n\n if (!isExternal) {\n await verifyOutputDir({isWorkbenchApp, reporter, sourceDir})\n }\n\n // Dry run stops here — everything below mutates.\n if (dryRun) return\n\n // A real deploy has already exited if anything failed; landing here without a\n // resolved application or version means the deploy target was never resolved.\n if (!application || !version) return\n\n const studioManifest = await uploadStudioSchema(options, {isExternal})\n const location = await shipStudioDeployment({\n application,\n isAutoUpdating,\n isExternal,\n options,\n studioManifest,\n version,\n })\n\n return {\n applicationType: 'studio',\n applicationVersion: version,\n target: {applicationId: application.id, title: application.title ?? null, url: location},\n }\n}\n\n/**\n * Finds the application a real deploy targets, registering a studio host when\n * none is configured. A dry run resolves and reports the target read-only instead.\n */\nasync function resolveStudioApplication(\n options: DeployAppOptions,\n {dryRun, reporter}: {dryRun: boolean; reporter: CheckReporter},\n): Promise<UserApplication | null> {\n const {cliConfig, flags, output} = options\n const isExternal = !!flags.external\n // Sets the title on a newly registered studio; blank falls back to undefined\n const title = flags.title?.trim() || undefined\n\n if (dryRun) {\n await checkStudioTarget(reporter, {\n appId: getAppId(cliConfig),\n isExternal,\n projectId: cliConfig.api?.projectId,\n studioHost: cliConfig.studioHost,\n title,\n urlFlag: flags.url,\n })\n return null\n }\n\n const projectId = cliConfig.api?.projectId ?? ''\n let application = await findUserApplicationForStudio({\n appId: getAppId(cliConfig),\n isExternal,\n output,\n projectId,\n studioHost: cliConfig.studioHost,\n title,\n unattended: !!flags.yes,\n urlFlag: flags.url,\n })\n\n if (!application) {\n if (isExternal) {\n output.log('Your project has not been registered with an external studio URL.')\n output.log('Please enter the full URL where your studio is hosted.')\n } else {\n output.log('Your project has not been assigned a studio hostname.')\n output.log('To deploy your Sanity Studio to our hosted sanity.studio service,')\n output.log('you will need one. Please enter the subdomain you want to use.')\n }\n\n application = await createStudioUserApplication({\n projectId,\n title,\n urlType: isExternal ? 'external' : 'internal',\n })\n deployDebug('Created user application', application)\n }\n\n deployDebug('Found user application', application)\n return application\n}\n\n/** Extracts the studio schema and manifest and uploads them to the schema store. */\nasync function uploadStudioSchema(\n options: DeployAppOptions,\n {isExternal}: {isExternal: boolean},\n): Promise<StudioManifest | null> {\n const {cliConfig, flags, output, projectRoot, sourceDir} = options\n\n let studioManifest: StudioManifest | null = null\n try {\n studioManifest = await deployStudioSchemasAndManifests(\n {\n configPath: projectRoot.path,\n isExternal,\n outPath: `${sourceDir}/static`,\n projectId: cliConfig.api?.projectId ?? '',\n schemaRequired: flags['schema-required'],\n verbose: flags.verbose,\n workDir: projectRoot.directory,\n },\n output,\n )\n } catch (error) {\n deployDebug('Error deploying studio schemas and manifests', error)\n if (error instanceof SchemaExtractionError) {\n output.error(formatSchemaValidation(error.validation || []), {exit: 1})\n }\n output.error(`Error deploying studio schemas and manifests: ${error}`, {exit: 1})\n }\n\n if (!studioManifest) {\n output.error('Failed to generate studio manifest. Please check your schemas and manifests.', {\n exit: 1,\n })\n }\n\n return studioManifest\n}\n\nasync function shipStudioDeployment({\n application,\n isAutoUpdating,\n isExternal,\n options,\n studioManifest,\n version,\n}: {\n application: UserApplication\n isAutoUpdating: boolean\n isExternal: boolean\n options: DeployAppOptions\n studioManifest: StudioManifest | null\n version: string\n}): Promise<string> {\n const {cliConfig, output, sourceDir} = options\n\n let tarball: Gzip | undefined\n if (!isExternal) {\n tarball = pack(dirname(sourceDir), {entries: [basename(sourceDir)]}).pipe(createGzip())\n }\n\n const spin = spinner(isExternal ? 'Registering studio' : 'Deploying to sanity.studio').start()\n let location: string\n try {\n ;({location} = await createDeployment({\n applicationId: application.id,\n isApp: false,\n isAutoUpdating,\n manifest: studioManifest,\n projectId: cliConfig.api?.projectId,\n tarball,\n version,\n }))\n } catch (error) {\n spin.fail()\n throw error\n }\n spin.succeed()\n\n const named = application.title ? ` — \"${application.title}\"` : ''\n output.log(\n isExternal\n ? `\\nSuccess! Studio registered${named}`\n : `\\nSuccess! Studio deployed to ${styleText('cyan', location)}${named}`,\n )\n\n if (getAppId(cliConfig)) return location\n\n const example = `Example:\nexport default defineCliConfig({\n //…\n deployment: {\n ${styleText('cyan', `appId: '${application.id}'`)},\n },\n //…\n})`\n output.log(`\\nAdd ${styleText('cyan', `appId: '${application.id}'`)}`)\n output.log(`to the \\`deployment\\` section in sanity.cli.js or sanity.cli.ts`)\n output.log(`to avoid prompting for application id on next deploy.`)\n output.log(`\\n${example}`)\n\n return location\n}\n\nfunction studioBuildSkipReason({build, isExternal}: {build: boolean; isExternal: boolean}) {\n if (isExternal) return 'Build skipped for externally hosted studios'\n if (!build) return 'Build skipped (--no-build) — validating existing output directory'\n return\n}\n"],"names":["basename","dirname","styleText","createGzip","formatSchemaValidation","SchemaExtractionError","exitCodes","spinner","getWorkbench","pack","createDeployment","getAppId","NO_PROJECT_ID","buildStudio","createStudioUserApplication","checkAutoUpdates","checkBuild","checkPackageVersion","checkStudioTarget","verifyOutputDir","deployDebug","listDeploymentFiles","runDeploy","deployStudioSchemasAndManifests","findUserApplicationForStudio","STUDIO_PACKAGE","deployStudio","options","listFiles","flags","projectRoot","sourceDir","external","Promise","resolve","directory","run","runStudioDeployment","type","reporter","cliConfig","output","workDir","isExternal","isWorkbenchApp","projectId","api","dryRun","isAutoUpdating","report","exitCode","USAGE_ERROR","message","solution","status","version","moduleName","application","resolveStudioApplication","build","autoUpdatesEnabled","calledFromDeploy","outDir","skipReason","studioBuildSkipReason","successMessage","studioManifest","uploadStudioSchema","location","shipStudioDeployment","applicationType","applicationVersion","target","applicationId","id","title","url","trim","undefined","appId","studioHost","urlFlag","unattended","yes","log","urlType","configPath","path","outPath","schemaRequired","verbose","error","validation","exit","tarball","entries","pipe","spin","start","isApp","manifest","fail","succeed","named","example"],"mappings":"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"}
@@ -1,5 +1,4 @@
1
1
  import { styleText } from 'node:util';
2
- import { ux } from '@oclif/core/ux';
3
2
  import { SchemaDeploy, SchemaExtractionError } from '@sanity/cli-build/_internal/extract';
4
3
  import { getCliTelemetry, studioWorkerTask, subdebug } from '@sanity/cli-core';
5
4
  const debug = subdebug('deployStudioSchemasAndManifests');
@@ -7,7 +6,7 @@ const debug = subdebug('deployStudioSchemasAndManifests');
7
6
  * 1. Extracts the create manifest in dist/static (automatically deployed with studio)
8
7
  * 2. Deploys the schemas to /schemas endpoint
9
8
  * 3. Creates a studio manifest, uploads it to user application and lexicon
10
- */ export async function deployStudioSchemasAndManifests(options) {
9
+ */ export async function deployStudioSchemasAndManifests(options, output) {
11
10
  const { configPath, isExternal, outPath, projectId, schemaRequired, verbose, workDir } = options;
12
11
  const trace = getCliTelemetry().trace(SchemaDeploy, {
13
12
  // If the studio is externally hosted, we don't need to extract the manifest
@@ -43,7 +42,7 @@ const debug = subdebug('deployStudioSchemasAndManifests');
43
42
  throw new SchemaExtractionError(result.error, result.validation);
44
43
  }
45
44
  trace.complete();
46
- ux.stdout(`${styleText('gray', '↳ List deployed schemas with:')} ${styleText('cyan', 'sanity schema list')}`);
45
+ output.log(`${styleText('gray', '↳ List deployed schemas with:')} ${styleText('cyan', 'sanity schema list')}`);
47
46
  return result.studioManifest;
48
47
  } catch (err) {
49
48
  trace.error(err);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/deployStudioSchemasAndManifests.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {ux} from '@oclif/core/ux'\nimport {SchemaDeploy, SchemaExtractionError} from '@sanity/cli-build/_internal/extract'\nimport {getCliTelemetry, studioWorkerTask, subdebug} from '@sanity/cli-core'\nimport {type SchemaValidationProblemGroup} from '@sanity/types'\nimport {type StudioManifest} from 'sanity'\n\nimport {type DeployStudioSchemasAndManifestsWorkerData} from './types.js'\n\ntype DeployStudioSchemasAndManifestsWorkerMessage =\n | {\n error: string\n type: 'error'\n validation?: SchemaValidationProblemGroup[]\n }\n | {\n studioManifest: StudioManifest | null\n type: 'success'\n }\n\nconst debug = subdebug('deployStudioSchemasAndManifests')\n\n/**\n * 1. Extracts the create manifest in dist/static (automatically deployed with studio)\n * 2. Deploys the schemas to /schemas endpoint\n * 3. Creates a studio manifest, uploads it to user application and lexicon\n */\nexport async function deployStudioSchemasAndManifests(\n options: DeployStudioSchemasAndManifestsWorkerData,\n): Promise<StudioManifest | null> {\n const {configPath, isExternal, outPath, projectId, schemaRequired, verbose, workDir} = options\n\n const trace = getCliTelemetry().trace(SchemaDeploy, {\n // If the studio is externally hosted, we don't need to extract the manifest\n extractManifest: !isExternal,\n manifestDir: outPath,\n schemaRequired,\n })\n\n try {\n trace.start()\n const result = await studioWorkerTask<DeployStudioSchemasAndManifestsWorkerMessage>(\n new URL('deployStudioSchemasAndManifests.worker.js', import.meta.url),\n {\n env: {\n ...process.env,\n // Workers don't inherit TTY state — propagate color support from parent\n ...(process.stdout.isTTY && !process.env.NO_COLOR ? {FORCE_COLOR: '1'} : {}),\n },\n name: 'deployStudioSchemasAndManifests',\n studioRootPath: workDir,\n workerData: {\n configPath,\n isExternal,\n outPath,\n projectId,\n schemaRequired,\n verbose,\n workDir,\n } satisfies DeployStudioSchemasAndManifestsWorkerData,\n },\n )\n\n debug('Result %o', result)\n\n // If the schema is required, we throw an error\n if (result.type === 'error') {\n throw new SchemaExtractionError(result.error, result.validation)\n }\n\n trace.complete()\n ux.stdout(\n `${styleText('gray', '↳ List deployed schemas with:')} ${styleText('cyan', 'sanity schema list')}`,\n )\n return result.studioManifest\n } catch (err) {\n trace.error(err)\n throw err\n }\n}\n"],"names":["styleText","ux","SchemaDeploy","SchemaExtractionError","getCliTelemetry","studioWorkerTask","subdebug","debug","deployStudioSchemasAndManifests","options","configPath","isExternal","outPath","projectId","schemaRequired","verbose","workDir","trace","extractManifest","manifestDir","start","result","URL","url","env","process","stdout","isTTY","NO_COLOR","FORCE_COLOR","name","studioRootPath","workerData","type","error","validation","complete","studioManifest","err"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,EAAE,QAAO,iBAAgB;AACjC,SAAQC,YAAY,EAAEC,qBAAqB,QAAO,sCAAqC;AACvF,SAAQC,eAAe,EAAEC,gBAAgB,EAAEC,QAAQ,QAAO,mBAAkB;AAiB5E,MAAMC,QAAQD,SAAS;AAEvB;;;;CAIC,GACD,OAAO,eAAeE,gCACpBC,OAAkD;IAElD,MAAM,EAACC,UAAU,EAAEC,UAAU,EAAEC,OAAO,EAAEC,SAAS,EAAEC,cAAc,EAAEC,OAAO,EAAEC,OAAO,EAAC,GAAGP;IAEvF,MAAMQ,QAAQb,kBAAkBa,KAAK,CAACf,cAAc;QAClD,4EAA4E;QAC5EgB,iBAAiB,CAACP;QAClBQ,aAAaP;QACbE;IACF;IAEA,IAAI;QACFG,MAAMG,KAAK;QACX,MAAMC,SAAS,MAAMhB,iBACnB,IAAIiB,IAAI,6CAA6C,YAAYC,GAAG,GACpE;YACEC,KAAK;gBACH,GAAGC,QAAQD,GAAG;gBACd,wEAAwE;gBACxE,GAAIC,QAAQC,MAAM,CAACC,KAAK,IAAI,CAACF,QAAQD,GAAG,CAACI,QAAQ,GAAG;oBAACC,aAAa;gBAAG,IAAI,CAAC,CAAC;YAC7E;YACAC,MAAM;YACNC,gBAAgBf;YAChBgB,YAAY;gBACVtB;gBACAC;gBACAC;gBACAC;gBACAC;gBACAC;gBACAC;YACF;QACF;QAGFT,MAAM,aAAac;QAEnB,+CAA+C;QAC/C,IAAIA,OAAOY,IAAI,KAAK,SAAS;YAC3B,MAAM,IAAI9B,sBAAsBkB,OAAOa,KAAK,EAAEb,OAAOc,UAAU;QACjE;QAEAlB,MAAMmB,QAAQ;QACdnC,GAAGyB,MAAM,CACP,GAAG1B,UAAU,QAAQ,iCAAiC,CAAC,EAAEA,UAAU,QAAQ,uBAAuB;QAEpG,OAAOqB,OAAOgB,cAAc;IAC9B,EAAE,OAAOC,KAAK;QACZrB,MAAMiB,KAAK,CAACI;QACZ,MAAMA;IACR;AACF"}
1
+ {"version":3,"sources":["../../../src/actions/deploy/deployStudioSchemasAndManifests.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {SchemaDeploy, SchemaExtractionError} from '@sanity/cli-build/_internal/extract'\nimport {getCliTelemetry, type Output, studioWorkerTask, subdebug} from '@sanity/cli-core'\nimport {type SchemaValidationProblemGroup} from '@sanity/types'\nimport {type StudioManifest} from 'sanity'\n\nimport {type DeployStudioSchemasAndManifestsWorkerData} from './types.js'\n\ntype DeployStudioSchemasAndManifestsWorkerMessage =\n | {\n error: string\n type: 'error'\n validation?: SchemaValidationProblemGroup[]\n }\n | {\n studioManifest: StudioManifest | null\n type: 'success'\n }\n\nconst debug = subdebug('deployStudioSchemasAndManifests')\n\n/**\n * 1. Extracts the create manifest in dist/static (automatically deployed with studio)\n * 2. Deploys the schemas to /schemas endpoint\n * 3. Creates a studio manifest, uploads it to user application and lexicon\n */\nexport async function deployStudioSchemasAndManifests(\n options: DeployStudioSchemasAndManifestsWorkerData,\n output: Output,\n): Promise<StudioManifest | null> {\n const {configPath, isExternal, outPath, projectId, schemaRequired, verbose, workDir} = options\n\n const trace = getCliTelemetry().trace(SchemaDeploy, {\n // If the studio is externally hosted, we don't need to extract the manifest\n extractManifest: !isExternal,\n manifestDir: outPath,\n schemaRequired,\n })\n\n try {\n trace.start()\n const result = await studioWorkerTask<DeployStudioSchemasAndManifestsWorkerMessage>(\n new URL('deployStudioSchemasAndManifests.worker.js', import.meta.url),\n {\n env: {\n ...process.env,\n // Workers don't inherit TTY state — propagate color support from parent\n ...(process.stdout.isTTY && !process.env.NO_COLOR ? {FORCE_COLOR: '1'} : {}),\n },\n name: 'deployStudioSchemasAndManifests',\n studioRootPath: workDir,\n workerData: {\n configPath,\n isExternal,\n outPath,\n projectId,\n schemaRequired,\n verbose,\n workDir,\n } satisfies DeployStudioSchemasAndManifestsWorkerData,\n },\n )\n\n debug('Result %o', result)\n\n // If the schema is required, we throw an error\n if (result.type === 'error') {\n throw new SchemaExtractionError(result.error, result.validation)\n }\n\n trace.complete()\n output.log(\n `${styleText('gray', '↳ List deployed schemas with:')} ${styleText('cyan', 'sanity schema list')}`,\n )\n return result.studioManifest\n } catch (err) {\n trace.error(err)\n throw err\n }\n}\n"],"names":["styleText","SchemaDeploy","SchemaExtractionError","getCliTelemetry","studioWorkerTask","subdebug","debug","deployStudioSchemasAndManifests","options","output","configPath","isExternal","outPath","projectId","schemaRequired","verbose","workDir","trace","extractManifest","manifestDir","start","result","URL","url","env","process","stdout","isTTY","NO_COLOR","FORCE_COLOR","name","studioRootPath","workerData","type","error","validation","complete","log","studioManifest","err"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,YAAY,EAAEC,qBAAqB,QAAO,sCAAqC;AACvF,SAAQC,eAAe,EAAeC,gBAAgB,EAAEC,QAAQ,QAAO,mBAAkB;AAiBzF,MAAMC,QAAQD,SAAS;AAEvB;;;;CAIC,GACD,OAAO,eAAeE,gCACpBC,OAAkD,EAClDC,MAAc;IAEd,MAAM,EAACC,UAAU,EAAEC,UAAU,EAAEC,OAAO,EAAEC,SAAS,EAAEC,cAAc,EAAEC,OAAO,EAAEC,OAAO,EAAC,GAAGR;IAEvF,MAAMS,QAAQd,kBAAkBc,KAAK,CAAChB,cAAc;QAClD,4EAA4E;QAC5EiB,iBAAiB,CAACP;QAClBQ,aAAaP;QACbE;IACF;IAEA,IAAI;QACFG,MAAMG,KAAK;QACX,MAAMC,SAAS,MAAMjB,iBACnB,IAAIkB,IAAI,6CAA6C,YAAYC,GAAG,GACpE;YACEC,KAAK;gBACH,GAAGC,QAAQD,GAAG;gBACd,wEAAwE;gBACxE,GAAIC,QAAQC,MAAM,CAACC,KAAK,IAAI,CAACF,QAAQD,GAAG,CAACI,QAAQ,GAAG;oBAACC,aAAa;gBAAG,IAAI,CAAC,CAAC;YAC7E;YACAC,MAAM;YACNC,gBAAgBf;YAChBgB,YAAY;gBACVtB;gBACAC;gBACAC;gBACAC;gBACAC;gBACAC;gBACAC;YACF;QACF;QAGFV,MAAM,aAAae;QAEnB,+CAA+C;QAC/C,IAAIA,OAAOY,IAAI,KAAK,SAAS;YAC3B,MAAM,IAAI/B,sBAAsBmB,OAAOa,KAAK,EAAEb,OAAOc,UAAU;QACjE;QAEAlB,MAAMmB,QAAQ;QACd3B,OAAO4B,GAAG,CACR,GAAGrC,UAAU,QAAQ,iCAAiC,CAAC,EAAEA,UAAU,QAAQ,uBAAuB;QAEpG,OAAOqB,OAAOiB,cAAc;IAC9B,EAAE,OAAOC,KAAK;QACZtB,MAAMiB,KAAK,CAACK;QACZ,MAAMA;IACR;AACF"}
@@ -2,6 +2,7 @@ import { readdir, stat } from 'node:fs/promises';
2
2
  import { join, relative, sep } from 'node:path';
3
3
  import { styleText } from 'node:util';
4
4
  import { logSymbols } from '@sanity/cli-core/ux';
5
+ import { pluralize } from '../../util/pluralize.js';
5
6
  /**
6
7
  * Lists the files a deploy would pack from `sourceDir`, as paths relative to
7
8
  * `fromDir`. A missing directory yields an empty list rather than throwing.
@@ -31,11 +32,38 @@ import { logSymbols } from '@sanity/cli-core/ux';
31
32
  })));
32
33
  return files.toSorted((a, b)=>a.path.localeCompare(b.path));
33
34
  }
35
+ export function isDeployable(plan) {
36
+ return plan.checks.every((check)=>check.status !== 'fail');
37
+ }
38
+ function totalBytes(files) {
39
+ return files.reduce((sum, file)=>sum + file.size, 0);
40
+ }
41
+ /**
42
+ * A problem-focused, machine-readable projection of the plan: blocking problems
43
+ * mapped to their fix, warnings as messages. Derived from the same checks the
44
+ * human report renders (its pass/skip lines are informational and omitted here).
45
+ */ export function deploymentPlanToJson(plan) {
46
+ const errors = {};
47
+ const warnings = [];
48
+ for (const check of plan.checks){
49
+ if (check.status === 'fail') errors[check.message] = check.solution ?? null;
50
+ else if (check.status === 'warn') warnings.push(check.message);
51
+ }
52
+ return {
53
+ applicationType: plan.type,
54
+ applicationVersion: plan.version,
55
+ errors,
56
+ files: plan.files,
57
+ isDeployable: isDeployable(plan),
58
+ target: plan.target,
59
+ totalBytes: totalBytes(plan.files),
60
+ warnings
61
+ };
62
+ }
34
63
  export function renderDeploymentPlan(plan, output) {
35
64
  const label = plan.type === 'coreApp' ? 'application' : 'studio';
36
65
  const problems = plan.checks.filter((check)=>check.status === 'fail');
37
66
  const warnings = plan.checks.filter((check)=>check.status === 'warn');
38
- const totalBytes = plan.files.reduce((sum, file)=>sum + file.size, 0);
39
67
  output.log('\nDry run — no changes made.\n');
40
68
  // Only pass/skip here; problems and warnings render below with their fixes.
41
69
  for (const check of plan.checks){
@@ -43,12 +71,12 @@ export function renderDeploymentPlan(plan, output) {
43
71
  output.log(` ${statusIcon(check.status)} ${check.message}`);
44
72
  }
45
73
  }
46
- output.log(problems.length === 0 ? styleText('green', `\nThis ${label} can be deployed.`) : styleText('red', `\nThis ${label} can't be deployed.`));
74
+ output.log(isDeployable(plan) ? styleText('green', `\nThis ${label} can be deployed.`) : styleText('red', `\nThis ${label} can't be deployed.`));
47
75
  renderIssues(output, 'Problems to fix:', problems);
48
76
  renderIssues(output, 'Warnings:', warnings);
49
77
  // A blocked deploy uploads nothing, so only list files for a deployable plan.
50
- if (problems.length === 0 && plan.files.length > 0) {
51
- output.log(`\nFiles to deploy (${formatMB(totalBytes)}):`);
78
+ if (isDeployable(plan) && plan.files.length > 0) {
79
+ output.log(`\nFiles to deploy (${plan.files.length} ${pluralize('file', plan.files.length)}, ${formatMB(totalBytes(plan.files))}):`);
52
80
  for (const file of plan.files){
53
81
  output.log(` ${file.path} (${formatMB(file.size)})`);
54
82
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/deploymentPlan.ts"],"sourcesContent":["import {readdir, stat} from 'node:fs/promises'\nimport {join, relative, sep} from 'node:path'\nimport {styleText} from 'node:util'\n\nimport {type Output} from '@sanity/cli-core'\nimport {logSymbols} from '@sanity/cli-core/ux'\n\nimport {type DeployCheck} from './deployChecks.js'\n\nexport interface DeploymentFile {\n /** Path relative to the project root, POSIX-style. */\n path: string\n size: number\n}\n\n/** What a `--dry-run` deploy would do: the real deploy sequence with every mutation gated off. */\nexport interface DeploymentPlan {\n checks: DeployCheck[]\n files: DeploymentFile[]\n type: 'coreApp' | 'studio'\n}\n\n/**\n * Lists the files a deploy would pack from `sourceDir`, as paths relative to\n * `fromDir`. A missing directory yields an empty list rather than throwing.\n */\nexport async function listDeploymentFiles(\n sourceDir: string,\n fromDir: string,\n): Promise<DeploymentFile[]> {\n const walk = async (dir: string): Promise<string[]> => {\n let entries\n try {\n entries = await readdir(dir, {withFileTypes: true})\n } catch {\n return []\n }\n const nested = await Promise.all(\n entries.map((entry) => {\n const full = join(dir, entry.name)\n return entry.isDirectory() ? walk(full) : Promise.resolve([full])\n }),\n )\n return nested.flat()\n }\n\n const absolute = await walk(sourceDir)\n const files = await Promise.all(\n absolute.map(async (file) => ({\n // Deploy paths are POSIX-style regardless of the host OS (Windows gives `\\`).\n path: relative(fromDir, file).split(sep).join('/'),\n size: (await stat(file)).size,\n })),\n )\n return files.toSorted((a, b) => a.path.localeCompare(b.path))\n}\n\nexport function renderDeploymentPlan(plan: DeploymentPlan, output: Output): void {\n const label = plan.type === 'coreApp' ? 'application' : 'studio'\n const problems = plan.checks.filter((check) => check.status === 'fail')\n const warnings = plan.checks.filter((check) => check.status === 'warn')\n const totalBytes = plan.files.reduce((sum, file) => sum + file.size, 0)\n\n output.log('\\nDry run — no changes made.\\n')\n\n // Only pass/skip here; problems and warnings render below with their fixes.\n for (const check of plan.checks) {\n if (check.status === 'pass' || check.status === 'skip') {\n output.log(` ${statusIcon(check.status)} ${check.message}`)\n }\n }\n\n output.log(\n problems.length === 0\n ? styleText('green', `\\nThis ${label} can be deployed.`)\n : styleText('red', `\\nThis ${label} can't be deployed.`),\n )\n\n renderIssues(output, 'Problems to fix:', problems)\n renderIssues(output, 'Warnings:', warnings)\n\n // A blocked deploy uploads nothing, so only list files for a deployable plan.\n if (problems.length === 0 && plan.files.length > 0) {\n output.log(`\\nFiles to deploy (${formatMB(totalBytes)}):`)\n for (const file of plan.files) {\n output.log(` ${file.path} (${formatMB(file.size)})`)\n }\n }\n}\n\nfunction renderIssues(output: Output, title: string, checks: DeployCheck[]): void {\n if (checks.length === 0) return\n\n output.log(`\\n${title}`)\n for (const check of checks) {\n const fix = check.solution ? `: ${check.solution}` : ''\n output.log(` ${statusIcon(check.status)} ${check.message}${fix}`)\n }\n}\n\nfunction formatMB(bytes: number): string {\n return `${(bytes / 1024 / 1024).toFixed(2)} MB`\n}\n\nfunction statusIcon(status: DeployCheck['status']): string {\n switch (status) {\n case 'fail': {\n return logSymbols.error\n }\n case 'skip': {\n return logSymbols.info\n }\n case 'warn': {\n return logSymbols.warning\n }\n default: {\n return logSymbols.success\n }\n }\n}\n"],"names":["readdir","stat","join","relative","sep","styleText","logSymbols","listDeploymentFiles","sourceDir","fromDir","walk","dir","entries","withFileTypes","nested","Promise","all","map","entry","full","name","isDirectory","resolve","flat","absolute","files","file","path","split","size","toSorted","a","b","localeCompare","renderDeploymentPlan","plan","output","label","type","problems","checks","filter","check","status","warnings","totalBytes","reduce","sum","log","statusIcon","message","length","renderIssues","formatMB","title","fix","solution","bytes","toFixed","error","info","warning","success"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/deploy/deploymentPlan.ts"],"sourcesContent":["import {readdir, stat} from 'node:fs/promises'\nimport {join, relative, sep} from 'node:path'\nimport {styleText} from 'node:util'\n\nimport {type Output} from '@sanity/cli-core'\nimport {logSymbols} from '@sanity/cli-core/ux'\n\nimport {pluralize} from '../../util/pluralize.js'\nimport {type DeployCheck, type DeployTarget} from './deployChecks.js'\n\nexport interface DeploymentFile {\n /** Path relative to the project root, POSIX-style. */\n path: string\n size: number\n}\n\n/** What a `--dry-run` deploy would do: the real deploy sequence with every mutation gated off. */\nexport interface DeploymentPlan {\n checks: DeployCheck[]\n files: DeploymentFile[]\n /** The resolved deploy target; `null` when the checks can't determine one. */\n target: DeployTarget | null\n type: 'coreApp' | 'studio'\n /** Installed framework version the deploy would use; `null` when not found. */\n version: string | null\n}\n\n/**\n * Lists the files a deploy would pack from `sourceDir`, as paths relative to\n * `fromDir`. A missing directory yields an empty list rather than throwing.\n */\nexport async function listDeploymentFiles(\n sourceDir: string,\n fromDir: string,\n): Promise<DeploymentFile[]> {\n const walk = async (dir: string): Promise<string[]> => {\n let entries\n try {\n entries = await readdir(dir, {withFileTypes: true})\n } catch {\n return []\n }\n const nested = await Promise.all(\n entries.map((entry) => {\n const full = join(dir, entry.name)\n return entry.isDirectory() ? walk(full) : Promise.resolve([full])\n }),\n )\n return nested.flat()\n }\n\n const absolute = await walk(sourceDir)\n const files = await Promise.all(\n absolute.map(async (file) => ({\n // Deploy paths are POSIX-style regardless of the host OS (Windows gives `\\`).\n path: relative(fromDir, file).split(sep).join('/'),\n size: (await stat(file)).size,\n })),\n )\n return files.toSorted((a, b) => a.path.localeCompare(b.path))\n}\n\nexport function isDeployable(plan: DeploymentPlan): boolean {\n return plan.checks.every((check) => check.status !== 'fail')\n}\n\nfunction totalBytes(files: DeploymentFile[]): number {\n return files.reduce((sum, file) => sum + file.size, 0)\n}\n\n/**\n * A problem-focused, machine-readable projection of the plan: blocking problems\n * mapped to their fix, warnings as messages. Derived from the same checks the\n * human report renders (its pass/skip lines are informational and omitted here).\n */\nexport function deploymentPlanToJson(plan: DeploymentPlan): {\n applicationType: DeploymentPlan['type']\n applicationVersion: string | null\n errors: Record<string, string | null>\n files: DeploymentFile[]\n isDeployable: boolean\n target: DeployTarget | null\n totalBytes: number\n warnings: string[]\n} {\n const errors: Record<string, string | null> = {}\n const warnings: string[] = []\n for (const check of plan.checks) {\n if (check.status === 'fail') errors[check.message] = check.solution ?? null\n else if (check.status === 'warn') warnings.push(check.message)\n }\n\n return {\n applicationType: plan.type,\n applicationVersion: plan.version,\n errors,\n files: plan.files,\n isDeployable: isDeployable(plan),\n target: plan.target,\n totalBytes: totalBytes(plan.files),\n warnings,\n }\n}\n\nexport function renderDeploymentPlan(plan: DeploymentPlan, output: Output): void {\n const label = plan.type === 'coreApp' ? 'application' : 'studio'\n const problems = plan.checks.filter((check) => check.status === 'fail')\n const warnings = plan.checks.filter((check) => check.status === 'warn')\n\n output.log('\\nDry run — no changes made.\\n')\n\n // Only pass/skip here; problems and warnings render below with their fixes.\n for (const check of plan.checks) {\n if (check.status === 'pass' || check.status === 'skip') {\n output.log(` ${statusIcon(check.status)} ${check.message}`)\n }\n }\n\n output.log(\n isDeployable(plan)\n ? styleText('green', `\\nThis ${label} can be deployed.`)\n : styleText('red', `\\nThis ${label} can't be deployed.`),\n )\n\n renderIssues(output, 'Problems to fix:', problems)\n renderIssues(output, 'Warnings:', warnings)\n\n // A blocked deploy uploads nothing, so only list files for a deployable plan.\n if (isDeployable(plan) && plan.files.length > 0) {\n output.log(\n `\\nFiles to deploy (${plan.files.length} ${pluralize('file', plan.files.length)}, ${formatMB(totalBytes(plan.files))}):`,\n )\n for (const file of plan.files) {\n output.log(` ${file.path} (${formatMB(file.size)})`)\n }\n }\n}\n\nfunction renderIssues(output: Output, title: string, checks: DeployCheck[]): void {\n if (checks.length === 0) return\n\n output.log(`\\n${title}`)\n for (const check of checks) {\n const fix = check.solution ? `: ${check.solution}` : ''\n output.log(` ${statusIcon(check.status)} ${check.message}${fix}`)\n }\n}\n\nfunction formatMB(bytes: number): string {\n return `${(bytes / 1024 / 1024).toFixed(2)} MB`\n}\n\nfunction statusIcon(status: DeployCheck['status']): string {\n switch (status) {\n case 'fail': {\n return logSymbols.error\n }\n case 'skip': {\n return logSymbols.info\n }\n case 'warn': {\n return logSymbols.warning\n }\n default: {\n return logSymbols.success\n }\n }\n}\n"],"names":["readdir","stat","join","relative","sep","styleText","logSymbols","pluralize","listDeploymentFiles","sourceDir","fromDir","walk","dir","entries","withFileTypes","nested","Promise","all","map","entry","full","name","isDirectory","resolve","flat","absolute","files","file","path","split","size","toSorted","a","b","localeCompare","isDeployable","plan","checks","every","check","status","totalBytes","reduce","sum","deploymentPlanToJson","errors","warnings","message","solution","push","applicationType","type","applicationVersion","version","target","renderDeploymentPlan","output","label","problems","filter","log","statusIcon","renderIssues","length","formatMB","title","fix","bytes","toFixed","error","info","warning","success"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/findUserApplication.ts"],"sourcesContent":["/**\n * Finds (or creates) the user application a deploy targets — the interactive\n * adapter that turns the resolveDeployTarget verdicts into prompts, creation,\n * and exits. Dry runs consume the same verdicts read-only (see deployChecks).\n */\n\nimport {type CliConfig, type Output} from '@sanity/cli-core'\nimport {select, Separator, spinner} from '@sanity/cli-core/ux'\n\nimport {createUserApplication, type UserApplication} from '../../services/userApplications.js'\nimport {getAppId} from '../../util/appId.js'\nimport {getErrorMessage} from '../../util/getErrorMessage.js'\nimport {\n createFailFastReporter,\n describeAppTarget,\n describeAppTargetError,\n describeStudioTarget,\n} from './deployChecks.js'\nimport {deployDebug} from './deployDebug.js'\nimport {resolveAppDeployTarget, resolveStudioDeployTarget} from './resolveDeployTarget.js'\n\ninterface FindUserApplicationOptions {\n cliConfig: CliConfig\n organizationId: string\n output: Output\n\n title?: string\n unattended?: boolean\n}\n\nexport async function findUserApplication(\n options: FindUserApplicationOptions,\n): Promise<UserApplication | null> {\n const {cliConfig, organizationId, output, title, unattended = false} = options\n const spin = spinner('Checking application info...').start()\n\n let resolution\n try {\n resolution = await resolveAppDeployTarget({appId: getAppId(cliConfig), organizationId})\n deployDebug('Resolved app deploy target', resolution)\n } catch (error) {\n spin.clear()\n deployDebug('Error finding user application for app', error)\n output.error(describeAppTargetError(error, organizationId), {exit: 1})\n return null\n }\n\n if (resolution.type === 'found') {\n spin.succeed()\n return resolution.application\n }\n\n // null tells the caller to create. Unattended runs can only create with a\n // --title; picking among existing apps (needs-input) always needs a prompt.\n if (resolution.type === 'would-create' && (!unattended || title)) {\n spin.info('No application ID configured')\n return null\n }\n if (resolution.type === 'needs-input' && !unattended) {\n spin.info('No application ID configured')\n return promptForExistingApp(resolution.existing)\n }\n\n spin.clear()\n // 'blocked' diagnoses as a skip (its root cause fails an earlier check), so it\n // needs an explicit exit here to not fall through to application creation\n if (resolution.type === 'blocked') {\n output.error(resolution.message, {exit: 1})\n return null\n }\n createFailFastReporter(output).report(describeAppTarget(resolution))\n return null\n}\n\ninterface FindUserApplicationForStudioOptions {\n isExternal: boolean\n output: Output\n projectId: string\n\n appId?: string\n studioHost?: string\n title?: string\n unattended?: boolean\n urlFlag?: string\n}\n\nexport async function findUserApplicationForStudio(\n options: FindUserApplicationForStudioOptions,\n): Promise<UserApplication | null> {\n const {\n appId,\n isExternal,\n output,\n projectId,\n studioHost,\n title,\n unattended = false,\n urlFlag,\n } = options\n const urlType = isExternal ? 'external' : 'internal'\n const spin = spinner('Checking project info').start()\n\n let resolution\n try {\n resolution = await resolveStudioDeployTarget({\n appId,\n isExternal,\n projectId,\n studioHost,\n urlFlag,\n })\n deployDebug('Resolved studio deploy target', resolution)\n } catch (error) {\n spin.fail()\n deployDebug('Error finding user application', error)\n output.error(`Failed to resolve deploy target: ${getErrorMessage(error)}`, {exit: 1})\n return null\n }\n\n if (resolution.type === 'found') {\n spin.succeed()\n return resolution.application\n }\n\n // The configured host isn't registered yet — a deploy registers it without prompting\n if (resolution.type === 'would-create') {\n spin.succeed()\n return createFromConfiguredHost({\n appHost: resolution.appHost,\n output,\n projectId,\n title,\n urlType,\n })\n }\n\n if (resolution.type === 'needs-input' && !unattended) {\n spin.succeed()\n // Nothing to select from — the caller prompts for a brand new host\n if (resolution.existing.length === 0) return null\n return promptForExistingStudio({existing: resolution.existing, urlType})\n }\n\n spin.fail()\n // 'blocked' diagnoses as a skip (its root cause fails an earlier check), so it\n // needs an explicit exit here\n if (resolution.type === 'blocked') {\n output.error(resolution.message, {exit: 1})\n return null\n }\n createFailFastReporter(output).report(describeStudioTarget(resolution, {isExternal}))\n return null\n}\n\n/**\n * The host is configured (studioHost or --url) but not registered yet:\n * a deploy registers it without prompting.\n */\nasync function createFromConfiguredHost({\n appHost,\n output,\n projectId,\n title,\n urlType,\n}: {\n appHost: string\n output: Output\n projectId: string\n title?: string\n urlType: 'external' | 'internal'\n}): Promise<UserApplication | null> {\n if (urlType === 'external') {\n output.log('Your project has not been registered with an external studio URL.')\n output.log(`Registering ${appHost}`)\n } else {\n output.log('Your project has not been assigned a studio hostname.')\n output.log(`Creating https://${appHost}.sanity.studio`)\n }\n output.log('')\n\n const spin = spinner(\n urlType === 'external' ? 'Registering external studio' : 'Creating studio hostname',\n ).start()\n\n try {\n const response = await createUserApplication({\n appType: 'studio',\n body: {appHost, title, type: 'studio', urlType},\n projectId,\n })\n spin.succeed()\n return response\n } catch (e) {\n spin.fail()\n // if the name is taken, it should return a 409 so we relay to the user\n if ([402, 409].includes(e?.statusCode)) {\n output.error(e?.response?.body?.message || 'Bad request', {exit: 1})\n return null\n }\n // otherwise, it's a fatal error\n deployDebug('Error creating user application from config', e)\n output.error(\n `Error creating user application from config: ${e instanceof Error ? e.message : e}`,\n {exit: 1},\n )\n return null\n }\n}\n\nasync function promptForExistingApp(existing: UserApplication[]): Promise<UserApplication | null> {\n const choices = existing.map((app) => ({name: app.title ?? app.appHost, value: app.appHost}))\n\n const selected = await select({\n choices: [\n {name: 'New application deployment', value: 'NEW_APP'},\n new Separator(' ════ Existing applications: ════ '),\n ...choices,\n ],\n loop: false,\n message: 'Would you like to create a new application deployment, or deploy to an existing one?',\n pageSize: 10,\n })\n\n if (selected === 'NEW_APP') {\n return null\n }\n\n return existing.find((app) => app.appHost === selected)!\n}\n\nasync function promptForExistingStudio({\n existing,\n urlType,\n}: {\n existing: UserApplication[]\n urlType: 'external' | 'internal'\n}): Promise<UserApplication | null> {\n const newLabel =\n urlType === 'external' ? 'Register new external studio URL' : 'Create new studio hostname'\n const selectMessage =\n urlType === 'external'\n ? 'Select existing external studio, or register a new one'\n : 'Select existing studio hostname, or create a new one'\n\n const choices = existing.map((app) => ({name: app.title ?? app.appHost, value: app.appHost}))\n\n const selected = await select({\n choices: [{name: newLabel, value: 'NEW_STUDIO'}, new Separator(), ...choices],\n message: selectMessage,\n })\n\n if (selected === 'NEW_STUDIO') {\n return null\n }\n\n return existing.find((app) => app.appHost === selected)!\n}\n"],"names":["select","Separator","spinner","createUserApplication","getAppId","getErrorMessage","createFailFastReporter","describeAppTarget","describeAppTargetError","describeStudioTarget","deployDebug","resolveAppDeployTarget","resolveStudioDeployTarget","findUserApplication","options","cliConfig","organizationId","output","title","unattended","spin","start","resolution","appId","error","clear","exit","type","succeed","application","info","promptForExistingApp","existing","message","report","findUserApplicationForStudio","isExternal","projectId","studioHost","urlFlag","urlType","fail","createFromConfiguredHost","appHost","length","promptForExistingStudio","log","response","appType","body","e","includes","statusCode","Error","choices","map","app","name","value","selected","loop","pageSize","find","newLabel","selectMessage"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/deploy/findUserApplication.ts"],"sourcesContent":["/**\n * Finds (or creates) the user application a deploy targets — the interactive\n * adapter that turns the resolveDeployTarget verdicts into prompts, creation,\n * and exits. Dry runs consume the same verdicts read-only (see deployChecks).\n */\n\nimport {type CliConfig, type Output} from '@sanity/cli-core'\nimport {select, Separator, spinner} from '@sanity/cli-core/ux'\n\nimport {\n createUserApplication,\n type UserApplication,\n type UserApplicationResolved,\n} from '../../services/userApplications.js'\nimport {getAppId} from '../../util/appId.js'\nimport {getErrorMessage} from '../../util/getErrorMessage.js'\nimport {\n createFailFastReporter,\n describeAppTarget,\n describeAppTargetError,\n describeStudioTarget,\n} from './deployChecks.js'\nimport {deployDebug} from './deployDebug.js'\nimport {resolveAppDeployTarget, resolveStudioDeployTarget} from './resolveDeployTarget.js'\n\ninterface FindUserApplicationOptions {\n cliConfig: CliConfig\n organizationId: string\n output: Output\n\n title?: string\n unattended?: boolean\n}\n\nexport async function findUserApplication(\n options: FindUserApplicationOptions,\n): Promise<UserApplicationResolved | null> {\n const {cliConfig, organizationId, output, title, unattended = false} = options\n const spin = spinner('Checking application info...').start()\n\n let resolution\n try {\n resolution = await resolveAppDeployTarget({appId: getAppId(cliConfig), organizationId})\n deployDebug('Resolved app deploy target', resolution)\n } catch (error) {\n spin.clear()\n deployDebug('Error finding user application for app', error)\n output.error(describeAppTargetError(error, organizationId), {exit: 1})\n return null\n }\n\n if (resolution.type === 'found') {\n spin.succeed()\n return resolution.application\n }\n\n // null tells the caller to create. Unattended runs can only create with a\n // --title; picking among existing apps (needs-input) always needs a prompt.\n if (resolution.type === 'would-create' && (!unattended || title)) {\n spin.info('No application ID configured')\n return null\n }\n if (resolution.type === 'needs-input' && !unattended) {\n spin.info('No application ID configured')\n return promptForExistingApp(resolution.existing)\n }\n\n spin.clear()\n // 'blocked' diagnoses as a skip (its root cause fails an earlier check), so it\n // needs an explicit exit here to not fall through to application creation\n if (resolution.type === 'blocked') {\n output.error(resolution.message, {exit: 1})\n return null\n }\n createFailFastReporter(output).report(describeAppTarget(resolution))\n return null\n}\n\ninterface FindUserApplicationForStudioOptions {\n isExternal: boolean\n output: Output\n projectId: string\n\n appId?: string\n studioHost?: string\n title?: string\n unattended?: boolean\n urlFlag?: string\n}\n\nexport async function findUserApplicationForStudio(\n options: FindUserApplicationForStudioOptions,\n): Promise<UserApplication | null> {\n const {\n appId,\n isExternal,\n output,\n projectId,\n studioHost,\n title,\n unattended = false,\n urlFlag,\n } = options\n const urlType = isExternal ? 'external' : 'internal'\n const spin = spinner('Checking project info').start()\n\n let resolution\n try {\n resolution = await resolveStudioDeployTarget({\n appId,\n isExternal,\n projectId,\n studioHost,\n urlFlag,\n })\n deployDebug('Resolved studio deploy target', resolution)\n } catch (error) {\n spin.fail()\n deployDebug('Error finding user application', error)\n output.error(`Failed to resolve deploy target: ${getErrorMessage(error)}`, {exit: 1})\n return null\n }\n\n if (resolution.type === 'found') {\n spin.succeed()\n return resolution.application\n }\n\n // The configured host isn't registered yet — a deploy registers it without prompting\n if (resolution.type === 'would-create') {\n spin.succeed()\n return createFromConfiguredHost({\n appHost: resolution.appHost,\n output,\n projectId,\n title,\n urlType,\n })\n }\n\n if (resolution.type === 'needs-input' && !unattended) {\n spin.succeed()\n // Nothing to select from — the caller prompts for a brand new host\n if (resolution.existing.length === 0) return null\n return promptForExistingStudio({existing: resolution.existing, urlType})\n }\n\n spin.fail()\n // 'blocked' diagnoses as a skip (its root cause fails an earlier check), so it\n // needs an explicit exit here\n if (resolution.type === 'blocked') {\n output.error(resolution.message, {exit: 1})\n return null\n }\n createFailFastReporter(output).report(describeStudioTarget(resolution, {isExternal}))\n return null\n}\n\n/**\n * The host is configured (studioHost or --url) but not registered yet:\n * a deploy registers it without prompting.\n */\nasync function createFromConfiguredHost({\n appHost,\n output,\n projectId,\n title,\n urlType,\n}: {\n appHost: string\n output: Output\n projectId: string\n title?: string\n urlType: 'external' | 'internal'\n}): Promise<UserApplication | null> {\n if (urlType === 'external') {\n output.log('Your project has not been registered with an external studio URL.')\n output.log(`Registering ${appHost}`)\n } else {\n output.log('Your project has not been assigned a studio hostname.')\n output.log(`Creating https://${appHost}.sanity.studio`)\n }\n output.log('')\n\n const spin = spinner(\n urlType === 'external' ? 'Registering external studio' : 'Creating studio hostname',\n ).start()\n\n try {\n const response = await createUserApplication({\n appType: 'studio',\n body: {appHost, title, type: 'studio', urlType},\n projectId,\n })\n spin.succeed()\n return response\n } catch (e) {\n spin.fail()\n // if the name is taken, it should return a 409 so we relay to the user\n if ([402, 409].includes(e?.statusCode)) {\n output.error(e?.response?.body?.message || 'Bad request', {exit: 1})\n return null\n }\n // otherwise, it's a fatal error\n deployDebug('Error creating user application from config', e)\n output.error(\n `Error creating user application from config: ${e instanceof Error ? e.message : e}`,\n {exit: 1},\n )\n return null\n }\n}\n\nasync function promptForExistingApp(\n existing: UserApplicationResolved[],\n): Promise<UserApplicationResolved | null> {\n const choices = existing.map((app) => ({name: app.title ?? app.appHost, value: app.appHost}))\n\n const selected = await select({\n choices: [\n {name: 'New application deployment', value: 'NEW_APP'},\n new Separator(' ════ Existing applications: ════ '),\n ...choices,\n ],\n loop: false,\n message: 'Would you like to create a new application deployment, or deploy to an existing one?',\n pageSize: 10,\n })\n\n if (selected === 'NEW_APP') {\n return null\n }\n\n return existing.find((app) => app.appHost === selected)!\n}\n\nasync function promptForExistingStudio({\n existing,\n urlType,\n}: {\n existing: UserApplication[]\n urlType: 'external' | 'internal'\n}): Promise<UserApplication | null> {\n const newLabel =\n urlType === 'external' ? 'Register new external studio URL' : 'Create new studio hostname'\n const selectMessage =\n urlType === 'external'\n ? 'Select existing external studio, or register a new one'\n : 'Select existing studio hostname, or create a new one'\n\n const choices = existing.map((app) => ({name: app.title ?? app.appHost, value: app.appHost}))\n\n const selected = await select({\n choices: [{name: newLabel, value: 'NEW_STUDIO'}, new Separator(), ...choices],\n message: selectMessage,\n })\n\n if (selected === 'NEW_STUDIO') {\n return null\n }\n\n return existing.find((app) => app.appHost === selected)!\n}\n"],"names":["select","Separator","spinner","createUserApplication","getAppId","getErrorMessage","createFailFastReporter","describeAppTarget","describeAppTargetError","describeStudioTarget","deployDebug","resolveAppDeployTarget","resolveStudioDeployTarget","findUserApplication","options","cliConfig","organizationId","output","title","unattended","spin","start","resolution","appId","error","clear","exit","type","succeed","application","info","promptForExistingApp","existing","message","report","findUserApplicationForStudio","isExternal","projectId","studioHost","urlFlag","urlType","fail","createFromConfiguredHost","appHost","length","promptForExistingStudio","log","response","appType","body","e","includes","statusCode","Error","choices","map","app","name","value","selected","loop","pageSize","find","newLabel","selectMessage"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/resolveDeployTarget.ts"],"sourcesContent":["import {\n getUserApplication,\n getUserApplications,\n type UserApplication,\n} from '../../services/userApplications.js'\nimport {normalizeUrl, validateUrl} from './urlUtils.js'\n\n/**\n * The read-only outcome of resolving where a deploy would go.\n *\n * - `found` — the deploy targets this existing user application\n * - `would-create` — nothing registered yet; a deploy would create it without prompting\n * - `needs-input` — config doesn't determine a target; a deploy would prompt\n * (`existing` lists the applications a prompt would offer)\n * - `invalid` — the configured target can never resolve (bad host, unknown appId)\n * - `blocked` — resolution requires config that's missing (projectId/organizationId)\n *\n * Transport errors (network, permissions) throw — they're not verdicts.\n */\ntype CommonDeployTargetResolution =\n | {application: UserApplication; type: 'found'}\n | {existing: UserApplication[]; type: 'needs-input'}\n | {message: string; reason: 'app-not-found' | 'invalid-host'; type: 'invalid'}\n | {message: string; type: 'blocked'}\n\nexport type StudioDeployTargetResolution =\n | CommonDeployTargetResolution\n | {appHost: string; type: 'would-create'}\n\nexport type AppDeployTargetResolution = CommonDeployTargetResolution | {type: 'would-create'}\n\n/**\n * Owns the studio deploy-target rules: the --url flag over studioHost config,\n * appId over appHost precedence, external URL normalization and validation.\n * Both the real deploy and the dry run consume these verdicts.\n *\n * @internal\n */\nexport async function resolveStudioDeployTarget(options: {\n appId: string | undefined\n isExternal: boolean\n projectId: string | undefined\n studioHost: string | undefined\n urlFlag: string | undefined\n}): Promise<StudioDeployTargetResolution> {\n const {appId, isExternal, projectId, studioHost, urlFlag} = options\n\n // appId wins over host config (and undeploy resolves it the same way): a\n // configured appId deploys even when studioHost is stale or invalid, so it's\n // resolved before any host validation.\n if (appId) {\n if (!projectId) {\n return {message: 'api.projectId is missing', type: 'blocked'}\n }\n const application = await getUserApplication({appId, isSdkApp: false, projectId})\n if (application) {\n return {application, type: 'found'}\n }\n return {\n message: `Cannot find app with app ID ${appId}`,\n reason: 'app-not-found',\n type: 'invalid',\n }\n }\n\n const {error: hostError, host: resolvedHost} = resolveAppHost({\n isExternal,\n studioHost,\n url: urlFlag,\n })\n if (hostError) {\n return {message: hostError, reason: 'invalid-host', type: 'invalid'}\n }\n\n // A host from config hasn't passed through the --url validation yet\n let appHost = resolvedHost\n if (appHost && isExternal) {\n appHost = normalizeUrl(appHost)\n const validation = validateUrl(appHost)\n if (validation !== true) {\n return {message: validation, reason: 'invalid-host', type: 'invalid'}\n }\n }\n\n if (appHost) {\n if (!projectId) {\n return {message: 'api.projectId is missing', type: 'blocked'}\n }\n const application = await getUserApplication({appHost, isSdkApp: false, projectId})\n if (application) {\n return {application, type: 'found'}\n }\n return {appHost, type: 'would-create'}\n }\n\n // Neither appId nor host configured — a deploy would prompt.\n // Without a project there is nothing to list; a deploy would still prompt.\n const existing = projectId ? await listStudioApplications(projectId, isExternal) : []\n return {existing, type: 'needs-input'}\n}\n\n/**\n * Owns the app deploy-target rules: appId lookup, falling back to listing the\n * organization's applications. Both the real deploy and the dry run consume\n * these verdicts.\n *\n * @internal\n */\nexport async function resolveAppDeployTarget(options: {\n appId: string | undefined\n organizationId: string | undefined\n}): Promise<AppDeployTargetResolution> {\n const {appId, organizationId} = options\n\n if (appId) {\n const application = await getUserApplication({appId, isSdkApp: true})\n if (application) {\n return {application, type: 'found'}\n }\n return {\n message: `Cannot find app with app ID ${appId}`,\n reason: 'app-not-found',\n type: 'invalid',\n }\n }\n\n if (!organizationId) {\n return {message: 'app.organizationId is missing', type: 'blocked'}\n }\n\n const existing = await getUserApplications({appType: 'coreApp', organizationId})\n if (existing?.length) {\n return {existing, type: 'needs-input'}\n }\n\n return {type: 'would-create'}\n}\n\nasync function listStudioApplications(\n projectId: string,\n isExternal: boolean,\n): Promise<UserApplication[]> {\n const urlType = isExternal ? 'external' : 'internal'\n const applications = await getUserApplications({appType: 'studio', projectId})\n // External deploys should only see external studios and vice versa\n return applications?.filter((application) => application.urlType === urlType) ?? []\n}\n\nfunction resolveAppHost({\n isExternal,\n studioHost,\n url,\n}: {\n isExternal: boolean\n studioHost: string | undefined\n url: string | undefined\n}): {error?: string; host?: string} {\n if (!url) {\n return {host: studioHost}\n }\n\n if (isExternal) {\n const normalized = normalizeUrl(url)\n const validation = validateUrl(normalized)\n if (validation !== true) {\n return {error: validation}\n }\n return {host: normalized}\n }\n\n // For internal deploys, strip protocol prefix and .sanity.studio suffix if present\n const hostname = url.replace(/^https?:\\/\\//i, '').replace(/\\.sanity\\.studio\\/?$/i, '')\n\n // If the result still looks like a URL (contains dots), the user likely meant --external\n if (hostname.includes('.')) {\n return {\n error: `\"${hostname}\" does not look like a sanity.studio hostname. Did you mean to use --external?`,\n }\n }\n\n // Validate hostname characters (alphanumeric and hyphens only)\n if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i.test(hostname)) {\n return {\n error: `Invalid studio hostname \"${hostname}\". Hostnames can only contain letters, numbers, and hyphens.`,\n }\n }\n\n return {host: hostname}\n}\n"],"names":["getUserApplication","getUserApplications","normalizeUrl","validateUrl","resolveStudioDeployTarget","options","appId","isExternal","projectId","studioHost","urlFlag","message","type","application","isSdkApp","reason","error","hostError","host","resolvedHost","resolveAppHost","url","appHost","validation","existing","listStudioApplications","resolveAppDeployTarget","organizationId","appType","length","urlType","applications","filter","normalized","hostname","replace","includes","test"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/deploy/resolveDeployTarget.ts"],"sourcesContent":["import {\n getUserApplication,\n getUserApplications,\n type UserApplication,\n type UserApplicationResolved,\n} from '../../services/userApplications.js'\nimport {normalizeUrl, validateUrl} from './urlUtils.js'\n\n/**\n * The read-only outcome of resolving where a deploy would go.\n *\n * - `found` — the deploy targets this existing user application\n * - `would-create` — nothing registered yet; a deploy would create it without prompting\n * - `needs-input` — config doesn't determine a target; a deploy would prompt\n * (`existing` lists the applications a prompt would offer)\n * - `invalid` — the configured target can never resolve (bad host, unknown appId)\n * - `blocked` — resolution requires config that's missing (projectId/organizationId)\n *\n * Transport errors (network, permissions) throw — they're not verdicts.\n */\ntype CommonDeployTargetResolution<App extends UserApplication = UserApplication> =\n | {application: App; type: 'found'}\n | {existing: App[]; type: 'needs-input'}\n | {message: string; reason: 'app-not-found' | 'invalid-host'; type: 'invalid'}\n | {message: string; type: 'blocked'}\n\nexport type StudioDeployTargetResolution =\n | CommonDeployTargetResolution\n | {appHost: string; type: 'would-create'}\n\nexport type AppDeployTargetResolution =\n | CommonDeployTargetResolution<UserApplicationResolved>\n | {type: 'would-create'}\n\n/**\n * Owns the studio deploy-target rules: the --url flag over studioHost config,\n * appId over appHost precedence, external URL normalization and validation.\n * Both the real deploy and the dry run consume these verdicts.\n *\n * @internal\n */\nexport async function resolveStudioDeployTarget(options: {\n appId: string | undefined\n isExternal: boolean\n projectId: string | undefined\n studioHost: string | undefined\n urlFlag: string | undefined\n}): Promise<StudioDeployTargetResolution> {\n const {appId, isExternal, projectId, studioHost, urlFlag} = options\n\n // appId wins over host config (and undeploy resolves it the same way): a\n // configured appId deploys even when studioHost is stale or invalid, so it's\n // resolved before any host validation.\n if (appId) {\n if (!projectId) {\n return {message: 'api.projectId is missing', type: 'blocked'}\n }\n const application = await getUserApplication({appId, isSdkApp: false, projectId})\n if (application) {\n return {application, type: 'found'}\n }\n return {\n message: `Cannot find app with app ID ${appId}`,\n reason: 'app-not-found',\n type: 'invalid',\n }\n }\n\n const {error: hostError, host: resolvedHost} = resolveAppHost({\n isExternal,\n studioHost,\n url: urlFlag,\n })\n if (hostError) {\n return {message: hostError, reason: 'invalid-host', type: 'invalid'}\n }\n\n // A host from config hasn't passed through the --url validation yet\n let appHost = resolvedHost\n if (appHost && isExternal) {\n appHost = normalizeUrl(appHost)\n const validation = validateUrl(appHost)\n if (validation !== true) {\n return {message: validation, reason: 'invalid-host', type: 'invalid'}\n }\n }\n\n if (appHost) {\n if (!projectId) {\n return {message: 'api.projectId is missing', type: 'blocked'}\n }\n const application = await getUserApplication({appHost, isSdkApp: false, projectId})\n if (application) {\n return {application, type: 'found'}\n }\n return {appHost, type: 'would-create'}\n }\n\n // Neither appId nor host configured — a deploy would prompt.\n // Without a project there is nothing to list; a deploy would still prompt.\n const existing = projectId ? await listStudioApplications(projectId, isExternal) : []\n return {existing, type: 'needs-input'}\n}\n\n/**\n * Owns the app deploy-target rules: appId lookup, falling back to listing the\n * organization's applications. Both the real deploy and the dry run consume\n * these verdicts.\n *\n * @internal\n */\nexport async function resolveAppDeployTarget(options: {\n appId: string | undefined\n organizationId: string | undefined\n}): Promise<AppDeployTargetResolution> {\n const {appId, organizationId} = options\n\n if (appId) {\n const application = await getUserApplication({appId, isSdkApp: true})\n if (application) {\n return {application, type: 'found'}\n }\n return {\n message: `Cannot find app with app ID ${appId}`,\n reason: 'app-not-found',\n type: 'invalid',\n }\n }\n\n if (!organizationId) {\n return {message: 'app.organizationId is missing', type: 'blocked'}\n }\n\n const existing = await getUserApplications({appType: 'coreApp', organizationId})\n if (existing?.length) {\n return {existing, type: 'needs-input'}\n }\n\n return {type: 'would-create'}\n}\n\nasync function listStudioApplications(\n projectId: string,\n isExternal: boolean,\n): Promise<UserApplication[]> {\n const urlType = isExternal ? 'external' : 'internal'\n const applications = await getUserApplications({appType: 'studio', projectId})\n // External deploys should only see external studios and vice versa\n return applications?.filter((application) => application.urlType === urlType) ?? []\n}\n\nfunction resolveAppHost({\n isExternal,\n studioHost,\n url,\n}: {\n isExternal: boolean\n studioHost: string | undefined\n url: string | undefined\n}): {error?: string; host?: string} {\n if (!url) {\n return {host: studioHost}\n }\n\n if (isExternal) {\n const normalized = normalizeUrl(url)\n const validation = validateUrl(normalized)\n if (validation !== true) {\n return {error: validation}\n }\n return {host: normalized}\n }\n\n // For internal deploys, strip protocol prefix and .sanity.studio suffix if present\n const hostname = url.replace(/^https?:\\/\\//i, '').replace(/\\.sanity\\.studio\\/?$/i, '')\n\n // If the result still looks like a URL (contains dots), the user likely meant --external\n if (hostname.includes('.')) {\n return {\n error: `\"${hostname}\" does not look like a sanity.studio hostname. Did you mean to use --external?`,\n }\n }\n\n // Validate hostname characters (alphanumeric and hyphens only)\n if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i.test(hostname)) {\n return {\n error: `Invalid studio hostname \"${hostname}\". Hostnames can only contain letters, numbers, and hyphens.`,\n }\n }\n\n return {host: hostname}\n}\n"],"names":["getUserApplication","getUserApplications","normalizeUrl","validateUrl","resolveStudioDeployTarget","options","appId","isExternal","projectId","studioHost","urlFlag","message","type","application","isSdkApp","reason","error","hostError","host","resolvedHost","resolveAppHost","url","appHost","validation","existing","listStudioApplications","resolveAppDeployTarget","organizationId","appType","length","urlType","applications","filter","normalized","hostname","replace","includes","test"],"mappings":"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"}
@@ -1,3 +1,7 @@
1
+ import { getSanityUrl } from '@sanity/cli-core';
2
+ export function getCoreAppUrl(organizationId, appId) {
3
+ return getSanityUrl(`/@${organizationId}/application/${appId}`);
4
+ }
1
5
  /**
2
6
  * Validates that the given string is a valid http or https URL.
3
7
  * Returns `true` if valid, or an error message string if invalid.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/urlUtils.ts"],"sourcesContent":["/**\n * Validates that the given string is a valid http or https URL.\n * Returns `true` if valid, or an error message string if invalid.\n */\nexport function validateUrl(url: string): string | true {\n try {\n const parsed = new URL(url)\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return 'URL must use http or https protocol'\n }\n return true\n } catch {\n return 'Invalid URL. Please enter a valid http or https URL'\n }\n}\n\n/**\n * Normalizes a URL by removing trailing slashes.\n */\nexport function normalizeUrl(url: string): string {\n return url.replace(/\\/+$/, '')\n}\n"],"names":["validateUrl","url","parsed","URL","protocol","normalizeUrl","replace"],"mappings":"AAAA;;;CAGC,GACD,OAAO,SAASA,YAAYC,GAAW;IACrC,IAAI;QACF,MAAMC,SAAS,IAAIC,IAAIF;QACvB,IAAIC,OAAOE,QAAQ,KAAK,WAAWF,OAAOE,QAAQ,KAAK,UAAU;YAC/D,OAAO;QACT;QACA,OAAO;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA;;CAEC,GACD,OAAO,SAASC,aAAaJ,GAAW;IACtC,OAAOA,IAAIK,OAAO,CAAC,QAAQ;AAC7B"}
1
+ {"version":3,"sources":["../../../src/actions/deploy/urlUtils.ts"],"sourcesContent":["import {getSanityUrl} from '@sanity/cli-core'\n\nexport function getCoreAppUrl(organizationId: string, appId: string): string {\n return getSanityUrl(`/@${organizationId}/application/${appId}`)\n}\n\n/**\n * Validates that the given string is a valid http or https URL.\n * Returns `true` if valid, or an error message string if invalid.\n */\nexport function validateUrl(url: string): string | true {\n try {\n const parsed = new URL(url)\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return 'URL must use http or https protocol'\n }\n return true\n } catch {\n return 'Invalid URL. Please enter a valid http or https URL'\n }\n}\n\n/**\n * Normalizes a URL by removing trailing slashes.\n */\nexport function normalizeUrl(url: string): string {\n return url.replace(/\\/+$/, '')\n}\n"],"names":["getSanityUrl","getCoreAppUrl","organizationId","appId","validateUrl","url","parsed","URL","protocol","normalizeUrl","replace"],"mappings":"AAAA,SAAQA,YAAY,QAAO,mBAAkB;AAE7C,OAAO,SAASC,cAAcC,cAAsB,EAAEC,KAAa;IACjE,OAAOH,aAAa,CAAC,EAAE,EAAEE,eAAe,aAAa,EAAEC,OAAO;AAChE;AAEA;;;CAGC,GACD,OAAO,SAASC,YAAYC,GAAW;IACrC,IAAI;QACF,MAAMC,SAAS,IAAIC,IAAIF;QACvB,IAAIC,OAAOE,QAAQ,KAAK,WAAWF,OAAOE,QAAQ,KAAK,UAAU;YAC/D,OAAO;QACT;QACA,OAAO;IACT,EAAE,OAAM;QACN,OAAO;IACT;AACF;AAEA;;CAEC,GACD,OAAO,SAASC,aAAaJ,GAAW;IACtC,OAAOA,IAAIK,OAAO,CAAC,QAAQ;AAC7B"}
@@ -74,7 +74,7 @@ const sanitizeIconPath = new URL('sanitizeIcon.js', import.meta.url).href;
74
74
  icon = await readIconFromPath(workDir, app.icon);
75
75
  }
76
76
  if (!icon && !app.title) {
77
- spin.succeed('Manifest creation skipped: no icon or title found in app configuration');
77
+ spin.info('Manifest creation skipped: no icon or title found in app configuration');
78
78
  return undefined;
79
79
  }
80
80
  const manifest = coreAppManifestSchema.parse({
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/manifest/extractCoreAppManifest.ts"],"sourcesContent":["import {readFile} from 'node:fs/promises'\nimport {relative, resolve} from 'node:path'\n\nimport {doImport, getCliConfigUncached} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\n\nimport {getErrorMessage} from '../../util/getErrorMessage.js'\nimport {type sanitizeIcon as sanitizeIconFn} from './sanitizeIcon.js'\nimport {type CoreAppManifest, coreAppManifestSchema} from './types.js'\n\ninterface ExtractCoreAppManifestOptions {\n workDir: string\n}\n\n/**\n * The title change a deploy would sync from the manifest to the user\n * application, or null when the titles already match (or none is set).\n *\n * @internal\n */\nexport function resolveTitleUpdate(\n manifest: CoreAppManifest | undefined,\n application: {title: string | null},\n): {from: string | null; to: string} | null {\n if (manifest?.title === undefined || manifest.title === application.title) {\n return null\n }\n return {from: application.title, to: manifest.title}\n}\n\nconst sanitizeIconPath = new URL('sanitizeIcon.js', import.meta.url).href\n\n/**\n * Lazy-load {@link sanitizeIconFn} so `isomorphic-dompurify` (and its jsdom\n * dependency) stays out of the CLI's eager import graph. The studio manifest\n * resolver lazy-loads its icon machinery for the same reason; this path runs in\n * the main process (not the manifest worker), so only an app deploy that\n * actually has an icon pays the cost.\n */\nasync function lazySanitizeIcon(): Promise<typeof sanitizeIconFn> {\n const mod = await doImport(sanitizeIconPath)\n return mod.sanitizeIcon\n}\n\n/**\n * Resolves app.icon from config (a file path) to an SVG string for the manifest.\n * The manifest expects the SVG string inline, not a path.\n *\n * The file is sanitized through the same allowlist as the studio manifest's\n * icon resolver (see {@link lazySanitizeIcon}) so both manifest paths inline the\n * same trusted subset of SVG markup.\n */\nasync function readIconFromPath(workDir: string, iconPath: string): Promise<string> {\n const resolvedPath = resolve(workDir, iconPath)\n const pathRelativeToWorkDir = relative(workDir, resolvedPath)\n if (pathRelativeToWorkDir.startsWith('..')) {\n throw new Error(\n `Icon path \"${iconPath}\" resolves outside the project directory and is not allowed.`,\n )\n }\n\n let content: string\n try {\n content = await readFile(resolvedPath, 'utf8')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new Error(\n `Could not read icon file at \"${iconPath}\" (resolved: ${resolvedPath}): ${message}`,\n {cause: err},\n )\n }\n\n const trimmed = content.trim()\n if (!/<svg[\\s>]/i.test(trimmed)) {\n throw new Error(\n `Icon file at \"${iconPath}\" does not contain an SVG element. App manifest icons must be SVG files.`,\n )\n }\n\n const sanitizeIcon = await lazySanitizeIcon()\n return sanitizeIcon(trimmed)\n}\n\n/**\n * Unlike studio manifest extraction, skips schema/tool parsing. The config's\n * `app.icon` is a file path; its content is read and inlined in the manifest.\n */\nexport async function extractCoreAppManifest(\n options: ExtractCoreAppManifestOptions,\n): Promise<CoreAppManifest | undefined> {\n const {workDir} = options\n const {app} = await getCliConfigUncached(workDir)\n if (!app) {\n return undefined\n }\n\n const spin = spinner('Extracting manifest').start()\n\n try {\n let icon: string | undefined\n if (app.icon) {\n icon = await readIconFromPath(workDir, app.icon)\n }\n\n if (!icon && !app.title) {\n spin.succeed('Manifest creation skipped: no icon or title found in app configuration')\n return undefined\n }\n\n const manifest: CoreAppManifest = coreAppManifestSchema.parse({\n version: '1',\n ...(icon ? {icon} : {}),\n ...(app.title ? {title: app.title} : {}),\n ...(app.group ? {group: app.group} : {}),\n ...(app.priority === undefined ? {} : {priority: app.priority}),\n })\n\n spin.succeed(`Extracted manifest`)\n\n return manifest\n } catch (err) {\n const message = getErrorMessage(err)\n spin.fail(message)\n throw err\n }\n}\n"],"names":["readFile","relative","resolve","doImport","getCliConfigUncached","spinner","getErrorMessage","coreAppManifestSchema","resolveTitleUpdate","manifest","application","title","undefined","from","to","sanitizeIconPath","URL","url","href","lazySanitizeIcon","mod","sanitizeIcon","readIconFromPath","workDir","iconPath","resolvedPath","pathRelativeToWorkDir","startsWith","Error","content","err","message","String","cause","trimmed","trim","test","extractCoreAppManifest","options","app","spin","start","icon","succeed","parse","version","group","priority","fail"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/manifest/extractCoreAppManifest.ts"],"sourcesContent":["import {readFile} from 'node:fs/promises'\nimport {relative, resolve} from 'node:path'\n\nimport {doImport, getCliConfigUncached} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\n\nimport {getErrorMessage} from '../../util/getErrorMessage.js'\nimport {type sanitizeIcon as sanitizeIconFn} from './sanitizeIcon.js'\nimport {type CoreAppManifest, coreAppManifestSchema} from './types.js'\n\ninterface ExtractCoreAppManifestOptions {\n workDir: string\n}\n\n/**\n * The title change a deploy would sync from the manifest to the user\n * application, or null when the titles already match (or none is set).\n *\n * @internal\n */\nexport function resolveTitleUpdate(\n manifest: CoreAppManifest | undefined,\n application: {title: string | null},\n): {from: string | null; to: string} | null {\n if (manifest?.title === undefined || manifest.title === application.title) {\n return null\n }\n return {from: application.title, to: manifest.title}\n}\n\nconst sanitizeIconPath = new URL('sanitizeIcon.js', import.meta.url).href\n\n/**\n * Lazy-load {@link sanitizeIconFn} so `isomorphic-dompurify` (and its jsdom\n * dependency) stays out of the CLI's eager import graph. The studio manifest\n * resolver lazy-loads its icon machinery for the same reason; this path runs in\n * the main process (not the manifest worker), so only an app deploy that\n * actually has an icon pays the cost.\n */\nasync function lazySanitizeIcon(): Promise<typeof sanitizeIconFn> {\n const mod = await doImport(sanitizeIconPath)\n return mod.sanitizeIcon\n}\n\n/**\n * Resolves app.icon from config (a file path) to an SVG string for the manifest.\n * The manifest expects the SVG string inline, not a path.\n *\n * The file is sanitized through the same allowlist as the studio manifest's\n * icon resolver (see {@link lazySanitizeIcon}) so both manifest paths inline the\n * same trusted subset of SVG markup.\n */\nasync function readIconFromPath(workDir: string, iconPath: string): Promise<string> {\n const resolvedPath = resolve(workDir, iconPath)\n const pathRelativeToWorkDir = relative(workDir, resolvedPath)\n if (pathRelativeToWorkDir.startsWith('..')) {\n throw new Error(\n `Icon path \"${iconPath}\" resolves outside the project directory and is not allowed.`,\n )\n }\n\n let content: string\n try {\n content = await readFile(resolvedPath, 'utf8')\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n throw new Error(\n `Could not read icon file at \"${iconPath}\" (resolved: ${resolvedPath}): ${message}`,\n {cause: err},\n )\n }\n\n const trimmed = content.trim()\n if (!/<svg[\\s>]/i.test(trimmed)) {\n throw new Error(\n `Icon file at \"${iconPath}\" does not contain an SVG element. App manifest icons must be SVG files.`,\n )\n }\n\n const sanitizeIcon = await lazySanitizeIcon()\n return sanitizeIcon(trimmed)\n}\n\n/**\n * Unlike studio manifest extraction, skips schema/tool parsing. The config's\n * `app.icon` is a file path; its content is read and inlined in the manifest.\n */\nexport async function extractCoreAppManifest(\n options: ExtractCoreAppManifestOptions,\n): Promise<CoreAppManifest | undefined> {\n const {workDir} = options\n const {app} = await getCliConfigUncached(workDir)\n if (!app) {\n return undefined\n }\n\n const spin = spinner('Extracting manifest').start()\n\n try {\n let icon: string | undefined\n if (app.icon) {\n icon = await readIconFromPath(workDir, app.icon)\n }\n\n if (!icon && !app.title) {\n spin.info('Manifest creation skipped: no icon or title found in app configuration')\n return undefined\n }\n\n const manifest: CoreAppManifest = coreAppManifestSchema.parse({\n version: '1',\n ...(icon ? {icon} : {}),\n ...(app.title ? {title: app.title} : {}),\n ...(app.group ? {group: app.group} : {}),\n ...(app.priority === undefined ? {} : {priority: app.priority}),\n })\n\n spin.succeed(`Extracted manifest`)\n\n return manifest\n } catch (err) {\n const message = getErrorMessage(err)\n spin.fail(message)\n throw err\n }\n}\n"],"names":["readFile","relative","resolve","doImport","getCliConfigUncached","spinner","getErrorMessage","coreAppManifestSchema","resolveTitleUpdate","manifest","application","title","undefined","from","to","sanitizeIconPath","URL","url","href","lazySanitizeIcon","mod","sanitizeIcon","readIconFromPath","workDir","iconPath","resolvedPath","pathRelativeToWorkDir","startsWith","Error","content","err","message","String","cause","trimmed","trim","test","extractCoreAppManifest","options","app","spin","start","icon","info","parse","version","group","priority","succeed","fail"],"mappings":"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"}
@@ -56,6 +56,11 @@ export class DeployCommand extends SanityCommand {
56
56
  'build'
57
57
  ]
58
58
  }),
59
+ json: Flags.boolean({
60
+ char: 'j',
61
+ default: false,
62
+ description: 'Output the result as JSON'
63
+ }),
59
64
  minify: Flags.boolean({
60
65
  allowNo: true,
61
66
  default: true,
@@ -98,7 +103,6 @@ export class DeployCommand extends SanityCommand {
98
103
  if (relativeOutput[0] !== '.') {
99
104
  relativeOutput = `./${relativeOutput}`;
100
105
  }
101
- // Unattended runs (--yes or a non-interactive terminal) and dry runs skip the overwrite prompt
102
106
  if (!this.isUnattended() && !flags['dry-run']) {
103
107
  const isEmpty = await dirIsEmptyOrNonExistent(sourceDir);
104
108
  const shouldProceed = isEmpty || await confirm({
@@ -111,11 +115,10 @@ export class DeployCommand extends SanityCommand {
111
115
  });
112
116
  }
113
117
  }
114
- this.output.log(`Building to ${relativeOutput}\n`);
118
+ // Keep --json's stdout clean for the payload
119
+ if (!flags.json) this.output.log(`Building to ${relativeOutput}\n`);
115
120
  }
116
- // Unattended runs (--yes or a non-interactive terminal) and dry runs deploy
117
- // without any downstream prompts — application resolution and the build
118
- // (buildApp/buildStudio) otherwise stop for prerelease/version choices.
121
+ // Force yes downstream: build/app resolution otherwise prompts for prerelease/version choices
119
122
  const deployFlags = this.isUnattended() || flags['dry-run'] ? {
120
123
  ...flags,
121
124
  yes: true
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/deploy.ts"],"sourcesContent":["import path from 'node:path'\n\nimport {Args, Flags} from '@oclif/core'\nimport {SanityCommand} from '@sanity/cli-core'\nimport {confirm} from '@sanity/cli-core/ux'\n\nimport {deployApp} from '../actions/deploy/deployApp.js'\nimport {deployDebug} from '../actions/deploy/deployDebug.js'\nimport {deployStudio} from '../actions/deploy/deployStudio.js'\nimport {determineIsApp} from '../util/determineIsApp.js'\nimport {dirIsEmptyOrNonExistent} from '../util/dirIsEmptyOrNonExistent.js'\n\nexport class DeployCommand extends SanityCommand<typeof DeployCommand> {\n static override args = {\n sourceDir: Args.directory({\n description: 'Source directory',\n }),\n }\n\n static override description = 'Builds and deploys Sanity Studio or application to Sanity hosting'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Build and deploy the studio to Sanity hosting',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --no-minify --source-maps',\n description: 'Deploys non-minified build with source maps',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --schema-required',\n description:\n 'Fail fast on schema store fails - for when other services rely on the stored schema',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --external',\n description: 'Register an externally hosted studio (studioHost contains full URL)',\n },\n ]\n\n static override flags = {\n 'auto-updates': Flags.boolean({\n allowNo: true,\n deprecated: true,\n description: 'Automatically update the studio to the latest version',\n }),\n build: Flags.boolean({\n allowNo: true,\n default: true,\n description:\n 'Build the studio before deploying (use --no-build to deploy existing `dist/` output)',\n }),\n 'dry-run': Flags.boolean({\n default: false,\n description: 'Report what would be deployed without uploading or creating anything',\n }),\n external: Flags.boolean({\n default: false,\n description: 'Register an externally hosted studio',\n exclusive: ['source-maps', 'minify', 'build'],\n }),\n minify: Flags.boolean({\n allowNo: true,\n default: true,\n description: 'Minify built JavaScript (use --no-minify to skip for faster builds)',\n }),\n 'schema-required': Flags.boolean({\n default: false,\n description: 'Fail if schema deployment fails',\n }),\n 'source-maps': Flags.boolean({\n default: false,\n description: 'Enable source maps for built bundles (increases size of bundle)',\n }),\n title: Flags.string({\n description:\n 'Title for a newly created application or studio. For apps it also skips the interactive title prompt, enabling unattended creation',\n }),\n url: Flags.string({\n description:\n 'Studio URL for deployment. For external studios, the full URL. For hosted studios, the hostname (e.g. \"my-studio\" or \"my-studio.sanity.studio\")',\n }),\n verbose: Flags.boolean({\n default: false,\n description: 'Enable verbose logging',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description:\n 'Unattended mode, answers \"yes\" to any \"yes/no\" prompt and otherwise uses defaults',\n }),\n }\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(DeployCommand)\n\n const cliConfig = await this.getCliConfig()\n const projectRoot = await this.getProjectRoot()\n\n const isApp = determineIsApp(cliConfig)\n\n const defaultOutputDir = path.resolve(path.join(projectRoot.directory, 'dist'))\n const sourceDir = path.resolve(process.cwd(), this.args.sourceDir || defaultOutputDir)\n\n // Skip the directory check if the studio is externally hosted\n if (this.args.sourceDir && this.args.sourceDir !== 'dist' && !flags.external) {\n let relativeOutput = path.relative(process.cwd(), sourceDir)\n if (relativeOutput[0] !== '.') {\n relativeOutput = `./${relativeOutput}`\n }\n\n // Unattended runs (--yes or a non-interactive terminal) and dry runs skip the overwrite prompt\n if (!this.isUnattended() && !flags['dry-run']) {\n const isEmpty = await dirIsEmptyOrNonExistent(sourceDir)\n const shouldProceed =\n isEmpty ||\n (await confirm({\n default: false,\n message: `\"${relativeOutput}\" is not empty, do you want to proceed?`,\n }))\n\n if (!shouldProceed) {\n this.output.error('Cancelled.', {exit: 1})\n }\n }\n\n this.output.log(`Building to ${relativeOutput}\\n`)\n }\n\n // Unattended runs (--yes or a non-interactive terminal) and dry runs deploy\n // without any downstream prompts — application resolution and the build\n // (buildApp/buildStudio) otherwise stop for prerelease/version choices.\n const deployFlags = this.isUnattended() || flags['dry-run'] ? {...flags, yes: true} : flags\n\n if (isApp) {\n deployDebug('Deploying app')\n await deployApp({\n cliConfig,\n flags: deployFlags,\n output: this.output,\n projectRoot,\n sourceDir,\n })\n } else {\n deployDebug('Deploying studio')\n await deployStudio({\n cliConfig,\n flags: deployFlags,\n output: this.output,\n projectRoot,\n sourceDir,\n })\n }\n }\n}\n"],"names":["path","Args","Flags","SanityCommand","confirm","deployApp","deployDebug","deployStudio","determineIsApp","dirIsEmptyOrNonExistent","DeployCommand","args","sourceDir","directory","description","examples","command","flags","boolean","allowNo","deprecated","build","default","external","exclusive","minify","title","string","url","verbose","yes","char","run","parse","cliConfig","getCliConfig","projectRoot","getProjectRoot","isApp","defaultOutputDir","resolve","join","process","cwd","relativeOutput","relative","isUnattended","isEmpty","shouldProceed","message","output","error","exit","log","deployFlags"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/commands/deploy.ts"],"sourcesContent":["import path from 'node:path'\n\nimport {Args, Flags} from '@oclif/core'\nimport {SanityCommand} from '@sanity/cli-core'\nimport {confirm} from '@sanity/cli-core/ux'\n\nimport {deployApp} from '../actions/deploy/deployApp.js'\nimport {deployDebug} from '../actions/deploy/deployDebug.js'\nimport {deployStudio} from '../actions/deploy/deployStudio.js'\nimport {determineIsApp} from '../util/determineIsApp.js'\nimport {dirIsEmptyOrNonExistent} from '../util/dirIsEmptyOrNonExistent.js'\n\nexport class DeployCommand extends SanityCommand<typeof DeployCommand> {\n static override args = {\n sourceDir: Args.directory({\n description: 'Source directory',\n }),\n }\n\n static override description = 'Builds and deploys Sanity Studio or application to Sanity hosting'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Build and deploy the studio to Sanity hosting',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --no-minify --source-maps',\n description: 'Deploys non-minified build with source maps',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --schema-required',\n description:\n 'Fail fast on schema store fails - for when other services rely on the stored schema',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --external',\n description: 'Register an externally hosted studio (studioHost contains full URL)',\n },\n ]\n\n static override flags = {\n 'auto-updates': Flags.boolean({\n allowNo: true,\n deprecated: true,\n description: 'Automatically update the studio to the latest version',\n }),\n build: Flags.boolean({\n allowNo: true,\n default: true,\n description:\n 'Build the studio before deploying (use --no-build to deploy existing `dist/` output)',\n }),\n 'dry-run': Flags.boolean({\n default: false,\n description: 'Report what would be deployed without uploading or creating anything',\n }),\n external: Flags.boolean({\n default: false,\n description: 'Register an externally hosted studio',\n exclusive: ['source-maps', 'minify', 'build'],\n }),\n json: Flags.boolean({\n char: 'j',\n default: false,\n description: 'Output the result as JSON',\n }),\n minify: Flags.boolean({\n allowNo: true,\n default: true,\n description: 'Minify built JavaScript (use --no-minify to skip for faster builds)',\n }),\n 'schema-required': Flags.boolean({\n default: false,\n description: 'Fail if schema deployment fails',\n }),\n 'source-maps': Flags.boolean({\n default: false,\n description: 'Enable source maps for built bundles (increases size of bundle)',\n }),\n title: Flags.string({\n description:\n 'Title for a newly created application or studio. For apps it also skips the interactive title prompt, enabling unattended creation',\n }),\n url: Flags.string({\n description:\n 'Studio URL for deployment. For external studios, the full URL. For hosted studios, the hostname (e.g. \"my-studio\" or \"my-studio.sanity.studio\")',\n }),\n verbose: Flags.boolean({\n default: false,\n description: 'Enable verbose logging',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description:\n 'Unattended mode, answers \"yes\" to any \"yes/no\" prompt and otherwise uses defaults',\n }),\n }\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(DeployCommand)\n\n const cliConfig = await this.getCliConfig()\n const projectRoot = await this.getProjectRoot()\n\n const isApp = determineIsApp(cliConfig)\n\n const defaultOutputDir = path.resolve(path.join(projectRoot.directory, 'dist'))\n const sourceDir = path.resolve(process.cwd(), this.args.sourceDir || defaultOutputDir)\n\n // Skip the directory check if the studio is externally hosted\n if (this.args.sourceDir && this.args.sourceDir !== 'dist' && !flags.external) {\n let relativeOutput = path.relative(process.cwd(), sourceDir)\n if (relativeOutput[0] !== '.') {\n relativeOutput = `./${relativeOutput}`\n }\n\n if (!this.isUnattended() && !flags['dry-run']) {\n const isEmpty = await dirIsEmptyOrNonExistent(sourceDir)\n const shouldProceed =\n isEmpty ||\n (await confirm({\n default: false,\n message: `\"${relativeOutput}\" is not empty, do you want to proceed?`,\n }))\n\n if (!shouldProceed) {\n this.output.error('Cancelled.', {exit: 1})\n }\n }\n\n // Keep --json's stdout clean for the payload\n if (!flags.json) this.output.log(`Building to ${relativeOutput}\\n`)\n }\n\n // Force yes downstream: build/app resolution otherwise prompts for prerelease/version choices\n const deployFlags = this.isUnattended() || flags['dry-run'] ? {...flags, yes: true} : flags\n\n if (isApp) {\n deployDebug('Deploying app')\n await deployApp({\n cliConfig,\n flags: deployFlags,\n output: this.output,\n projectRoot,\n sourceDir,\n })\n } else {\n deployDebug('Deploying studio')\n await deployStudio({\n cliConfig,\n flags: deployFlags,\n output: this.output,\n projectRoot,\n sourceDir,\n })\n }\n }\n}\n"],"names":["path","Args","Flags","SanityCommand","confirm","deployApp","deployDebug","deployStudio","determineIsApp","dirIsEmptyOrNonExistent","DeployCommand","args","sourceDir","directory","description","examples","command","flags","boolean","allowNo","deprecated","build","default","external","exclusive","json","char","minify","title","string","url","verbose","yes","run","parse","cliConfig","getCliConfig","projectRoot","getProjectRoot","isApp","defaultOutputDir","resolve","join","process","cwd","relativeOutput","relative","isUnattended","isEmpty","shouldProceed","message","output","error","exit","log","deployFlags"],"mappings":"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"}