@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.
package/README.md CHANGED
@@ -1449,20 +1449,24 @@ Builds and deploys Sanity Studio or application to Sanity hosting
1449
1449
 
1450
1450
  ```
1451
1451
  USAGE
1452
- $ sanity deploy [SOURCEDIR] [--auto-updates] [--external | --source-maps | --minify | --build]
1453
- [--schema-required] [--url <value>] [--verbose] [-y]
1452
+ $ sanity deploy [SOURCEDIR] [--auto-updates] [--dry-run] [--external | --source-maps | --minify | --build]
1453
+ [-j] [--schema-required] [--title <value>] [--url <value>] [--verbose] [-y]
1454
1454
 
1455
1455
  ARGUMENTS
1456
1456
  [SOURCEDIR] Source directory
1457
1457
 
1458
1458
  FLAGS
1459
+ -j, --json Output the result as JSON
1459
1460
  -y, --yes Unattended mode, answers "yes" to any "yes/no" prompt and otherwise uses defaults
1460
1461
  --[no-]auto-updates Automatically update the studio to the latest version
1461
1462
  --[no-]build Build the studio before deploying (use --no-build to deploy existing `dist/` output)
1463
+ --dry-run Report what would be deployed without uploading or creating anything
1462
1464
  --external Register an externally hosted studio
1463
1465
  --[no-]minify Minify built JavaScript (use --no-minify to skip for faster builds)
1464
1466
  --schema-required Fail if schema deployment fails
1465
1467
  --source-maps Enable source maps for built bundles (increases size of bundle)
1468
+ --title=<value> Title for a newly created application or studio. For apps it also skips the interactive title
1469
+ prompt, enabling unattended creation
1466
1470
  --url=<value> Studio URL for deployment. For external studios, the full URL. For hosted studios, the
1467
1471
  hostname (e.g. "my-studio" or "my-studio.sanity.studio")
1468
1472
  --verbose Enable verbose logging
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/createUserApplication.ts"],"sourcesContent":["import {CLIError} from '@oclif/core/errors'\nimport {input, spinner} from '@sanity/cli-core/ux'\nimport {customAlphabet} from 'nanoid'\n\nimport {\n createUserApplication as createUserApplicationRequest,\n type UserApplication,\n} from '../../services/userApplications.js'\nimport {NO_ORGANIZATION_ID} from '../../util/errorMessages.js'\nimport {deployDebug} from './deployDebug.js'\nimport {normalizeUrl, validateUrl} from './urlUtils.js'\n\n// TODO: replace with `Promise.withResolvers()` once it lands in node 22\nfunction promiseWithResolvers<T>() {\n let resolve!: (t: T) => void\n let reject!: (err: unknown) => void\n const promise = new Promise<T>((res, rej) => {\n resolve = res\n reject = rej\n })\n return {promise, reject, resolve}\n}\n\ninterface CreateStudioUserApplicationOptions {\n projectId: string\n\n title?: string\n urlType?: 'external' | 'internal'\n}\n\nexport async function createStudioUserApplication(options: CreateStudioUserApplicationOptions) {\n const {projectId, title, urlType = 'internal'} = options\n const {promise, resolve} = promiseWithResolvers<UserApplication>()\n\n const isExternal = urlType === 'external'\n\n await input({\n message: isExternal ? 'Studio URL (https://...):' : 'Studio hostname (<value>.sanity.studio):',\n // if a string is returned here, it is relayed to the user and prompt allows\n // the user to try again until this function returns true\n validate: async (inp: string) => {\n let appHost: string\n\n if (isExternal) {\n const normalized = normalizeUrl(inp)\n const validation = validateUrl(normalized)\n if (validation !== true) {\n return validation\n }\n appHost = normalized\n } else {\n appHost = inp.replace(/\\.sanity\\.studio$/i, '')\n }\n\n try {\n const response = await createUserApplicationRequest({\n appType: 'studio',\n body: {appHost, title, type: 'studio', urlType},\n projectId,\n })\n resolve(response)\n return true\n } catch (e) {\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 return e?.response?.body?.message || 'Bad request' // just in case\n }\n\n deployDebug('Error creating user application', e)\n // otherwise, it's a fatal error\n throw new CLIError('Error creating user application', {exit: 1})\n }\n },\n })\n\n return await promise\n}\n\nexport async function createUserApplication(\n organizationId?: string,\n title?: string,\n): Promise<UserApplication> {\n if (!organizationId) {\n throw new Error(NO_ORGANIZATION_ID)\n }\n\n const resolvedTitle =\n title ??\n (await input({\n message: 'Enter a title for your application:',\n validate: (value: string) => value.length > 0 || 'Title is required',\n }))\n\n return tryCreateApp(resolvedTitle, organizationId)\n}\n\n// appHosts have some restrictions (no uppercase, must start with a letter)\nconst generateId = () => {\n const letters = 'abcdefghijklmnopqrstuvwxyz'\n const firstChar = customAlphabet(letters, 1)()\n const rest = customAlphabet('abcdefghijklmnopqrstuvwxyz0123456789', 11)()\n return `${firstChar}${rest}`\n}\n\nconst tryCreateApp = async (title: string, organizationId: string) => {\n // we will likely prepend this with an org ID or other parameter in the future\n const appHost = generateId()\n\n const spin = spinner('Creating application').start()\n\n try {\n const response = await createUserApplicationRequest({\n appType: 'coreApp',\n body: {appHost, title, type: 'coreApp', urlType: 'internal'},\n organizationId,\n })\n\n spin.succeed()\n return response\n } catch (e) {\n // if the name is taken, generate a new one and try again\n if ([402, 409].includes(e?.statusCode)) {\n deployDebug('App host taken, retrying with new host')\n return tryCreateApp(title, organizationId)\n }\n\n spin.fail()\n\n deployDebug('Error creating core application', e)\n throw e\n }\n}\n"],"names":["CLIError","input","spinner","customAlphabet","createUserApplication","createUserApplicationRequest","NO_ORGANIZATION_ID","deployDebug","normalizeUrl","validateUrl","promiseWithResolvers","resolve","reject","promise","Promise","res","rej","createStudioUserApplication","options","projectId","title","urlType","isExternal","message","validate","inp","appHost","normalized","validation","replace","response","appType","body","type","e","includes","statusCode","exit","organizationId","Error","resolvedTitle","value","length","tryCreateApp","generateId","letters","firstChar","rest","spin","start","succeed","fail"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/deploy/createUserApplication.ts"],"sourcesContent":["import {CLIError} from '@oclif/core/errors'\nimport {input, spinner} from '@sanity/cli-core/ux'\nimport {customAlphabet} from 'nanoid'\n\nimport {\n createUserApplication as createUserApplicationRequest,\n type UserApplication,\n type UserApplicationResolved,\n} from '../../services/userApplications.js'\nimport {NO_ORGANIZATION_ID} from '../../util/errorMessages.js'\nimport {deployDebug} from './deployDebug.js'\nimport {normalizeUrl, validateUrl} from './urlUtils.js'\n\n// TODO: replace with `Promise.withResolvers()` once it lands in node 22\nfunction promiseWithResolvers<T>() {\n let resolve!: (t: T) => void\n let reject!: (err: unknown) => void\n const promise = new Promise<T>((res, rej) => {\n resolve = res\n reject = rej\n })\n return {promise, reject, resolve}\n}\n\ninterface CreateStudioUserApplicationOptions {\n projectId: string\n\n title?: string\n urlType?: 'external' | 'internal'\n}\n\nexport async function createStudioUserApplication(options: CreateStudioUserApplicationOptions) {\n const {projectId, title, urlType = 'internal'} = options\n const {promise, resolve} = promiseWithResolvers<UserApplication>()\n\n const isExternal = urlType === 'external'\n\n await input({\n message: isExternal ? 'Studio URL (https://...):' : 'Studio hostname (<value>.sanity.studio):',\n // if a string is returned here, it is relayed to the user and prompt allows\n // the user to try again until this function returns true\n validate: async (inp: string) => {\n let appHost: string\n\n if (isExternal) {\n const normalized = normalizeUrl(inp)\n const validation = validateUrl(normalized)\n if (validation !== true) {\n return validation\n }\n appHost = normalized\n } else {\n appHost = inp.replace(/\\.sanity\\.studio$/i, '')\n }\n\n try {\n const response = await createUserApplicationRequest({\n appType: 'studio',\n body: {appHost, title, type: 'studio', urlType},\n projectId,\n })\n resolve(response)\n return true\n } catch (e) {\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 return e?.response?.body?.message || 'Bad request' // just in case\n }\n\n deployDebug('Error creating user application', e)\n // otherwise, it's a fatal error\n throw new CLIError('Error creating user application', {exit: 1})\n }\n },\n })\n\n return await promise\n}\n\nexport async function createUserApplication(\n organizationId?: string,\n title?: string,\n): Promise<UserApplicationResolved> {\n if (!organizationId) {\n throw new Error(NO_ORGANIZATION_ID)\n }\n\n const resolvedTitle =\n title ??\n (await input({\n message: 'Enter a title for your application:',\n validate: (value: string) => value.length > 0 || 'Title is required',\n }))\n\n return tryCreateApp(resolvedTitle, organizationId)\n}\n\n// appHosts have some restrictions (no uppercase, must start with a letter)\nconst generateId = () => {\n const letters = 'abcdefghijklmnopqrstuvwxyz'\n const firstChar = customAlphabet(letters, 1)()\n const rest = customAlphabet('abcdefghijklmnopqrstuvwxyz0123456789', 11)()\n return `${firstChar}${rest}`\n}\n\nconst tryCreateApp = async (title: string, organizationId: string) => {\n // we will likely prepend this with an org ID or other parameter in the future\n const appHost = generateId()\n\n const spin = spinner('Creating application').start()\n\n try {\n const response = await createUserApplicationRequest({\n appType: 'coreApp',\n body: {appHost, title, type: 'coreApp', urlType: 'internal'},\n organizationId,\n })\n\n spin.succeed()\n return response\n } catch (e) {\n // if the name is taken, generate a new one and try again\n if ([402, 409].includes(e?.statusCode)) {\n deployDebug('App host taken, retrying with new host')\n return tryCreateApp(title, organizationId)\n }\n\n spin.fail()\n\n deployDebug('Error creating core application', e)\n throw e\n }\n}\n"],"names":["CLIError","input","spinner","customAlphabet","createUserApplication","createUserApplicationRequest","NO_ORGANIZATION_ID","deployDebug","normalizeUrl","validateUrl","promiseWithResolvers","resolve","reject","promise","Promise","res","rej","createStudioUserApplication","options","projectId","title","urlType","isExternal","message","validate","inp","appHost","normalized","validation","replace","response","appType","body","type","e","includes","statusCode","exit","organizationId","Error","resolvedTitle","value","length","tryCreateApp","generateId","letters","firstChar","rest","spin","start","succeed","fail"],"mappings":"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"}
@@ -17,6 +17,8 @@ import { deployDebug } from './deployDebug.js';
17
17
  import { listDeploymentFiles } from './deploymentPlan.js';
18
18
  import { runDeploy } from './deployRunner.js';
19
19
  import { findUserApplication } from './findUserApplication.js';
20
+ import { getCoreAppUrl } from './urlUtils.js';
21
+ const APP_PACKAGE = '@sanity/sdk-react';
20
22
  export function deployApp(options) {
21
23
  return runDeploy(options, {
22
24
  listFiles: ({ projectRoot, sourceDir })=>listDeploymentFiles(sourceDir, projectRoot.directory),
@@ -57,7 +59,7 @@ export function deployApp(options) {
57
59
  let version = null;
58
60
  if (deployApplication) {
59
61
  version = await checkPackageVersion(reporter, {
60
- moduleName: '@sanity/sdk-react',
62
+ moduleName: APP_PACKAGE,
61
63
  workDir
62
64
  });
63
65
  } else if (deploySingletonInstallationConfig) {
@@ -151,8 +153,18 @@ export function deployApp(options) {
151
153
  version
152
154
  });
153
155
  }
154
- // A config-only singleton has no application to ship.
155
- if (!deployApplication) return;
156
+ // A config-only singleton ships no application, only its installation config.
157
+ if (!deployApplication) {
158
+ if (installationId && version) {
159
+ return {
160
+ applicationType: 'coreApp',
161
+ applicationVersion: version,
162
+ installationId,
163
+ target: null
164
+ };
165
+ }
166
+ return;
167
+ }
156
168
  // A real deploy has already exited if anything failed; landing here without a
157
169
  // resolved application or version means the deploy target was never resolved.
158
170
  if (!application || !version) return;
@@ -173,6 +185,15 @@ export function deployApp(options) {
173
185
  cliConfig,
174
186
  output
175
187
  });
188
+ return {
189
+ applicationType: 'coreApp',
190
+ applicationVersion: version,
191
+ target: {
192
+ applicationId: application.id,
193
+ title: application.title ?? null,
194
+ url: getCoreAppUrl(application.organizationId, application.id)
195
+ }
196
+ };
176
197
  }
177
198
  /**
178
199
  * Finds the application a real deploy targets, creating one when none is
@@ -253,8 +274,10 @@ async function shipAppDeployment({ application, isAutoUpdating, manifest, source
253
274
  }
254
275
  spin.succeed();
255
276
  }
256
- function logAppDeployed({ application, cliConfig, output }) {
257
- output.log(`\n🚀 ${styleText('bold', 'Success!')} Application deployed`);
277
+ export function logAppDeployed({ application, cliConfig, output }) {
278
+ const url = getCoreAppUrl(application.organizationId, application.id);
279
+ const named = application.title ? ` — "${application.title}"` : '';
280
+ output.log(`\nSuccess! Application deployed to ${styleText('cyan', url)}${named}`);
258
281
  if (getAppId(cliConfig)) return;
259
282
  output.log(`\n════ ${styleText('bold', 'Next step:')} ════`);
260
283
  output.log(styleText('bold', '\nAdd the deployment.appId to your sanity.cli.js or sanity.cli.ts file:'));
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/deployApp.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {styleText} from 'node:util'\nimport {createGzip} from 'node:zlib'\n\nimport {exitCodes} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {\n deployInstallationConfig,\n getWorkbench,\n resolveInstallationId,\n summarizeInstallationConfig,\n} from '@sanity/workbench-cli/deploy'\nimport {pack} from 'tar-fs'\n\nimport {\n createDeployment,\n updateUserApplication,\n type UserApplication,\n} from '../../services/userApplications.js'\nimport {getAppId} from '../../util/appId.js'\nimport {EXTERNAL_APP_NOT_SUPPORTED, NO_ORGANIZATION_ID} from '../../util/errorMessages.js'\nimport {getErrorMessage} from '../../util/getErrorMessage.js'\nimport {buildApp} from '../build/buildApp.js'\nimport {extractCoreAppManifest, resolveTitleUpdate} from '../manifest/extractCoreAppManifest.js'\nimport {type CoreAppManifest} from '../manifest/types.js'\nimport {createUserApplication} from './createUserApplication.js'\nimport {\n checkAppId,\n checkAppTarget,\n checkAutoUpdates,\n checkBuild,\n checkPackageVersion,\n type CheckReporter,\n verifyOutputDir,\n} from './deployChecks.js'\nimport {deployDebug} from './deployDebug.js'\nimport {listDeploymentFiles} from './deploymentPlan.js'\nimport {runDeploy} from './deployRunner.js'\nimport {findUserApplication} from './findUserApplication.js'\nimport {type DeployAppOptions} from './types.js'\n\nexport function deployApp(options: DeployAppOptions): Promise<void> {\n return runDeploy(options, {\n listFiles: ({projectRoot, sourceDir}) => listDeploymentFiles(sourceDir, projectRoot.directory),\n run: runAppDeployment,\n type: 'coreApp',\n })\n}\n\n/** Validates the deploy, syncs the title from the manifest, and ships the build. */\nasync function runAppDeployment(options: DeployAppOptions, reporter: CheckReporter): Promise<void> {\n const {cliConfig, flags, output, sourceDir} = options\n const workDir = options.projectRoot.directory\n const organizationId = cliConfig.app?.organizationId\n const workbench = getWorkbench(cliConfig)\n const dryRun = !!flags['dry-run']\n\n // A singleton (the Media Library) persists its config instead of hosting an\n // application; anything with interfaces still ships one.\n const deploySingletonInstallationConfig = workbench?.deploySingletonInstallationConfig ?? false\n const deployApplication = !workbench || workbench.hasInterfaces\n\n // A federated app with no entry, view or service would ship a remote with\n // nothing to load — reported first so it fails before any prompt or API call.\n if (workbench) {\n try {\n workbench.assertDeployable()\n } catch (err) {\n reporter.report({\n exitCode: exitCodes.USAGE_ERROR,\n message: getErrorMessage(err),\n solution: 'Declare at least one entry, view, or service in the app',\n status: 'fail',\n })\n }\n }\n\n const isAutoUpdating = checkAutoUpdates(reporter, {cliConfig, flags})\n\n // An application ships the SDK runtime; a media-library config stamps its\n // `sanity` version instead.\n let version: string | null = null\n if (deployApplication) {\n version = await checkPackageVersion(reporter, {moduleName: '@sanity/sdk-react', workDir})\n } else if (deploySingletonInstallationConfig) {\n version = await checkPackageVersion(reporter, {moduleName: 'sanity', workDir})\n }\n\n reporter.report(\n organizationId\n ? {message: `Organization: ${organizationId}`, status: 'pass'}\n : {\n message: NO_ORGANIZATION_ID,\n solution: 'Add `app.organizationId` to sanity.cli.ts',\n status: 'fail',\n },\n )\n\n checkAppId(reporter, {cliConfig})\n\n let application: UserApplication | null = null\n if (flags.external) {\n reporter.report({\n message: EXTERNAL_APP_NOT_SUPPORTED,\n solution: 'Remove the --external flag — apps deploy to Sanity hosting',\n status: 'fail',\n })\n } else if (deployApplication) {\n application = await resolveAppApplication(options, {dryRun, reporter})\n }\n\n await checkBuild(reporter, {\n build: () =>\n buildApp({\n autoUpdatesEnabled: isAutoUpdating,\n calledFromDeploy: true,\n cliConfig,\n flags,\n outDir: sourceDir,\n output,\n workDir,\n }),\n skipReason: flags.build\n ? undefined\n : 'Build skipped (--no-build) — validating existing output directory',\n successMessage: 'App built',\n })\n\n await verifyOutputDir({isWorkbenchApp: workbench !== null, reporter, sourceDir})\n\n // Manifests aren't strictly essential, so a failure warns and continues\n let manifest: CoreAppManifest | undefined\n try {\n manifest = await extractCoreAppManifest({workDir})\n } catch (err) {\n deployDebug('Error extracting app manifest', err)\n reporter.report({\n message: `Error extracting app manifest: ${getErrorMessage(err)}`,\n status: 'warn',\n })\n }\n\n // Resolve the installation in both modes so the report — dry-run and real —\n // shows whether the config is deployable; a missing one fails the deploy here.\n let installationId: string | undefined\n const configAppType = workbench?.installationConfig?.appType\n if (\n deploySingletonInstallationConfig &&\n organizationId &&\n workbench?.installationConfig &&\n configAppType\n ) {\n installationId = await resolveInstallationId({appType: configAppType, organizationId})\n reporter.report(\n installationId\n ? {message: summarizeInstallationConfig(workbench.installationConfig), status: 'pass'}\n : {\n exitCode: exitCodes.USAGE_ERROR,\n message: `No active \"${configAppType}\" installation for organization \"${organizationId}\"`,\n solution: 'Install the Media Library for the organization before deploying its config',\n status: 'fail',\n },\n )\n }\n\n // Dry run stops here — everything below mutates.\n if (dryRun) return\n\n if (installationId && version && configAppType) {\n await deployInstallationConfig({\n appType: configAppType,\n installationId,\n output,\n sourceDir,\n version,\n })\n }\n\n // A config-only singleton has no application to ship.\n if (!deployApplication) 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 application = await syncApplicationTitle({application, manifest, output})\n\n await shipAppDeployment({application, isAutoUpdating, manifest, sourceDir, version})\n\n logAppDeployed({application, cliConfig, output})\n}\n\n/**\n * Finds the application a real deploy targets, creating one when none is\n * configured. A dry run resolves and reports the target read-only instead.\n */\nasync function resolveAppApplication(\n options: DeployAppOptions,\n {dryRun, reporter}: {dryRun: boolean; reporter: CheckReporter},\n): Promise<UserApplication | null> {\n const {cliConfig, flags, output} = options\n const organizationId = cliConfig.app?.organizationId ?? ''\n // Create name from --title or `app.title` config; blank falls back to the prompt\n const title = flags.title?.trim() || cliConfig.app?.title?.trim() || undefined\n\n if (dryRun) {\n await checkAppTarget(reporter, {appId: getAppId(cliConfig), organizationId, title})\n return null\n }\n\n let application = await findUserApplication({\n cliConfig,\n organizationId,\n output,\n title,\n unattended: !!flags.yes,\n })\n deployDebug('User application found', application)\n\n if (!application) {\n deployDebug('No user application found. Creating a new one')\n application = await createUserApplication(organizationId, title)\n deployDebug('User application created', application)\n }\n\n return application\n}\n\n/** Syncs the application title from the manifest when it has changed. */\nasync function syncApplicationTitle({\n application,\n manifest,\n output,\n}: {\n application: UserApplication\n manifest: CoreAppManifest | undefined\n output: DeployAppOptions['output']\n}): Promise<UserApplication> {\n const titleUpdate = resolveTitleUpdate(manifest, application)\n if (!titleUpdate) return application\n\n deployDebug('Updating application title from manifest', titleUpdate)\n output.log(\n titleUpdate.from\n ? `Updating title from \"${titleUpdate.from}\" to \"${titleUpdate.to}\"`\n : `Setting application title to \"${titleUpdate.to}\"`,\n )\n const spin = spinner('Updating application title').start()\n try {\n const updated = await updateUserApplication({\n applicationId: application.id,\n appType: 'coreApp',\n body: {title: titleUpdate.to},\n })\n spin.succeed()\n return updated\n } catch (err) {\n spin.fail()\n const message = getErrorMessage(err)\n deployDebug('Error updating application title', {message})\n output.warn(`Error updating application title: ${message}`)\n return application\n }\n}\n\nasync function shipAppDeployment({\n application,\n isAutoUpdating,\n manifest,\n sourceDir,\n version,\n}: {\n application: UserApplication\n isAutoUpdating: boolean\n manifest: CoreAppManifest | undefined\n sourceDir: string\n version: string\n}): Promise<void> {\n const tarball = pack(dirname(sourceDir), {entries: [basename(sourceDir)]}).pipe(createGzip())\n\n const spin = spinner('Deploying...').start()\n try {\n await createDeployment({\n applicationId: application.id,\n isApp: true,\n isAutoUpdating,\n manifest,\n tarball,\n version,\n })\n } catch (error) {\n spin.clear()\n throw error\n }\n spin.succeed()\n}\n\nfunction logAppDeployed({\n application,\n cliConfig,\n output,\n}: {\n application: UserApplication\n cliConfig: DeployAppOptions['cliConfig']\n output: DeployAppOptions['output']\n}): void {\n output.log(`\\n🚀 ${styleText('bold', 'Success!')} Application deployed`)\n\n if (getAppId(cliConfig)) return\n\n output.log(`\\n════ ${styleText('bold', 'Next step:')} ════`)\n output.log(\n styleText('bold', '\\nAdd the deployment.appId to your sanity.cli.js or sanity.cli.ts file:'),\n )\n output.log(`\n${styleText(\n 'dim',\n `app: {\n // your application config here…\n}`,\n)},\n${styleText(\n ['bold', 'green'],\n `deployment: {\n appId: '${application.id}',\n}\\n`,\n)}`)\n}\n"],"names":["basename","dirname","styleText","createGzip","exitCodes","spinner","deployInstallationConfig","getWorkbench","resolveInstallationId","summarizeInstallationConfig","pack","createDeployment","updateUserApplication","getAppId","EXTERNAL_APP_NOT_SUPPORTED","NO_ORGANIZATION_ID","getErrorMessage","buildApp","extractCoreAppManifest","resolveTitleUpdate","createUserApplication","checkAppId","checkAppTarget","checkAutoUpdates","checkBuild","checkPackageVersion","verifyOutputDir","deployDebug","listDeploymentFiles","runDeploy","findUserApplication","deployApp","options","listFiles","projectRoot","sourceDir","directory","run","runAppDeployment","type","reporter","cliConfig","flags","output","workDir","organizationId","app","workbench","dryRun","deploySingletonInstallationConfig","deployApplication","hasInterfaces","assertDeployable","err","report","exitCode","USAGE_ERROR","message","solution","status","isAutoUpdating","version","moduleName","application","external","resolveAppApplication","build","autoUpdatesEnabled","calledFromDeploy","outDir","skipReason","undefined","successMessage","isWorkbenchApp","manifest","installationId","configAppType","installationConfig","appType","syncApplicationTitle","shipAppDeployment","logAppDeployed","title","trim","appId","unattended","yes","titleUpdate","log","from","to","spin","start","updated","applicationId","id","body","succeed","fail","warn","tarball","entries","pipe","isApp","error","clear"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/deploy/deployApp.ts"],"sourcesContent":["import {basename, dirname} from 'node:path'\nimport {styleText} from 'node:util'\nimport {createGzip} from 'node:zlib'\n\nimport {exitCodes} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {\n deployInstallationConfig,\n getWorkbench,\n resolveInstallationId,\n summarizeInstallationConfig,\n} from '@sanity/workbench-cli/deploy'\nimport {pack} from 'tar-fs'\n\nimport {\n createDeployment,\n updateUserApplication,\n type UserApplication,\n type UserApplicationResolved,\n} from '../../services/userApplications.js'\nimport {getAppId} from '../../util/appId.js'\nimport {EXTERNAL_APP_NOT_SUPPORTED, NO_ORGANIZATION_ID} from '../../util/errorMessages.js'\nimport {getErrorMessage} from '../../util/getErrorMessage.js'\nimport {buildApp} from '../build/buildApp.js'\nimport {extractCoreAppManifest, resolveTitleUpdate} from '../manifest/extractCoreAppManifest.js'\nimport {type CoreAppManifest} from '../manifest/types.js'\nimport {createUserApplication} from './createUserApplication.js'\nimport {\n checkAppId,\n checkAppTarget,\n checkAutoUpdates,\n checkBuild,\n checkPackageVersion,\n type CheckReporter,\n verifyOutputDir,\n} from './deployChecks.js'\nimport {deployDebug} from './deployDebug.js'\nimport {listDeploymentFiles} from './deploymentPlan.js'\nimport {type DeployResult, runDeploy} from './deployRunner.js'\nimport {findUserApplication} from './findUserApplication.js'\nimport {type DeployAppOptions} from './types.js'\nimport {getCoreAppUrl} from './urlUtils.js'\n\nconst APP_PACKAGE = '@sanity/sdk-react'\n\nexport function deployApp(options: DeployAppOptions): Promise<void> {\n return runDeploy(options, {\n listFiles: ({projectRoot, sourceDir}) => listDeploymentFiles(sourceDir, projectRoot.directory),\n run: runAppDeployment,\n type: 'coreApp',\n })\n}\n\n/** Validates the deploy, syncs the title from the manifest, and ships the build. */\nasync function runAppDeployment(\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 organizationId = cliConfig.app?.organizationId\n const workbench = getWorkbench(cliConfig)\n const dryRun = !!flags['dry-run']\n\n // A singleton (the Media Library) persists its config instead of hosting an\n // application; anything with interfaces still ships one.\n const deploySingletonInstallationConfig = workbench?.deploySingletonInstallationConfig ?? false\n const deployApplication = !workbench || workbench.hasInterfaces\n\n // A federated app with no entry, view or service would ship a remote with\n // nothing to load — reported first so it fails before any prompt or API call.\n if (workbench) {\n try {\n workbench.assertDeployable()\n } catch (err) {\n reporter.report({\n exitCode: exitCodes.USAGE_ERROR,\n message: getErrorMessage(err),\n solution: 'Declare at least one entry, view, or service in the app',\n status: 'fail',\n })\n }\n }\n\n const isAutoUpdating = checkAutoUpdates(reporter, {cliConfig, flags})\n\n // An application ships the SDK runtime; a media-library config stamps its\n // `sanity` version instead.\n let version: string | null = null\n if (deployApplication) {\n version = await checkPackageVersion(reporter, {moduleName: APP_PACKAGE, workDir})\n } else if (deploySingletonInstallationConfig) {\n version = await checkPackageVersion(reporter, {moduleName: 'sanity', workDir})\n }\n\n reporter.report(\n organizationId\n ? {message: `Organization: ${organizationId}`, status: 'pass'}\n : {\n message: NO_ORGANIZATION_ID,\n solution: 'Add `app.organizationId` to sanity.cli.ts',\n status: 'fail',\n },\n )\n\n checkAppId(reporter, {cliConfig})\n\n let application: UserApplicationResolved | null = null\n if (flags.external) {\n reporter.report({\n message: EXTERNAL_APP_NOT_SUPPORTED,\n solution: 'Remove the --external flag — apps deploy to Sanity hosting',\n status: 'fail',\n })\n } else if (deployApplication) {\n application = await resolveAppApplication(options, {dryRun, reporter})\n }\n\n await checkBuild(reporter, {\n build: () =>\n buildApp({\n autoUpdatesEnabled: isAutoUpdating,\n calledFromDeploy: true,\n cliConfig,\n flags,\n outDir: sourceDir,\n output,\n workDir,\n }),\n skipReason: flags.build\n ? undefined\n : 'Build skipped (--no-build) — validating existing output directory',\n successMessage: 'App built',\n })\n\n await verifyOutputDir({isWorkbenchApp: workbench !== null, reporter, sourceDir})\n\n // Manifests aren't strictly essential, so a failure warns and continues\n let manifest: CoreAppManifest | undefined\n try {\n manifest = await extractCoreAppManifest({workDir})\n } catch (err) {\n deployDebug('Error extracting app manifest', err)\n reporter.report({\n message: `Error extracting app manifest: ${getErrorMessage(err)}`,\n status: 'warn',\n })\n }\n\n // Resolve the installation in both modes so the report — dry-run and real —\n // shows whether the config is deployable; a missing one fails the deploy here.\n let installationId: string | undefined\n const configAppType = workbench?.installationConfig?.appType\n if (\n deploySingletonInstallationConfig &&\n organizationId &&\n workbench?.installationConfig &&\n configAppType\n ) {\n installationId = await resolveInstallationId({appType: configAppType, organizationId})\n reporter.report(\n installationId\n ? {message: summarizeInstallationConfig(workbench.installationConfig), status: 'pass'}\n : {\n exitCode: exitCodes.USAGE_ERROR,\n message: `No active \"${configAppType}\" installation for organization \"${organizationId}\"`,\n solution: 'Install the Media Library for the organization before deploying its config',\n status: 'fail',\n },\n )\n }\n\n // Dry run stops here — everything below mutates.\n if (dryRun) return\n\n if (installationId && version && configAppType) {\n await deployInstallationConfig({\n appType: configAppType,\n installationId,\n output,\n sourceDir,\n version,\n })\n }\n\n // A config-only singleton ships no application, only its installation config.\n if (!deployApplication) {\n if (installationId && version) {\n return {applicationType: 'coreApp', applicationVersion: version, installationId, target: null}\n }\n return\n }\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 application = await syncApplicationTitle({application, manifest, output})\n\n await shipAppDeployment({application, isAutoUpdating, manifest, sourceDir, version})\n\n logAppDeployed({application, cliConfig, output})\n\n return {\n applicationType: 'coreApp',\n applicationVersion: version,\n target: {\n applicationId: application.id,\n title: application.title ?? null,\n url: getCoreAppUrl(application.organizationId, application.id),\n },\n }\n}\n\n/**\n * Finds the application a real deploy targets, creating one when none is\n * configured. A dry run resolves and reports the target read-only instead.\n */\nasync function resolveAppApplication(\n options: DeployAppOptions,\n {dryRun, reporter}: {dryRun: boolean; reporter: CheckReporter},\n): Promise<UserApplicationResolved | null> {\n const {cliConfig, flags, output} = options\n const organizationId = cliConfig.app?.organizationId ?? ''\n // Create name from --title or `app.title` config; blank falls back to the prompt\n const title = flags.title?.trim() || cliConfig.app?.title?.trim() || undefined\n\n if (dryRun) {\n await checkAppTarget(reporter, {appId: getAppId(cliConfig), organizationId, title})\n return null\n }\n\n let application = await findUserApplication({\n cliConfig,\n organizationId,\n output,\n title,\n unattended: !!flags.yes,\n })\n deployDebug('User application found', application)\n\n if (!application) {\n deployDebug('No user application found. Creating a new one')\n application = await createUserApplication(organizationId, title)\n deployDebug('User application created', application)\n }\n\n return application\n}\n\n/** Syncs the application title from the manifest when it has changed. */\nasync function syncApplicationTitle({\n application,\n manifest,\n output,\n}: {\n application: UserApplicationResolved\n manifest: CoreAppManifest | undefined\n output: DeployAppOptions['output']\n}): Promise<UserApplicationResolved> {\n const titleUpdate = resolveTitleUpdate(manifest, application)\n if (!titleUpdate) return application\n\n deployDebug('Updating application title from manifest', titleUpdate)\n output.log(\n titleUpdate.from\n ? `Updating title from \"${titleUpdate.from}\" to \"${titleUpdate.to}\"`\n : `Setting application title to \"${titleUpdate.to}\"`,\n )\n const spin = spinner('Updating application title').start()\n try {\n const updated = await updateUserApplication({\n applicationId: application.id,\n appType: 'coreApp',\n body: {title: titleUpdate.to},\n })\n spin.succeed()\n return updated\n } catch (err) {\n spin.fail()\n const message = getErrorMessage(err)\n deployDebug('Error updating application title', {message})\n output.warn(`Error updating application title: ${message}`)\n return application\n }\n}\n\nasync function shipAppDeployment({\n application,\n isAutoUpdating,\n manifest,\n sourceDir,\n version,\n}: {\n application: UserApplication\n isAutoUpdating: boolean\n manifest: CoreAppManifest | undefined\n sourceDir: string\n version: string\n}): Promise<void> {\n const tarball = pack(dirname(sourceDir), {entries: [basename(sourceDir)]}).pipe(createGzip())\n\n const spin = spinner('Deploying...').start()\n try {\n await createDeployment({\n applicationId: application.id,\n isApp: true,\n isAutoUpdating,\n manifest,\n tarball,\n version,\n })\n } catch (error) {\n spin.clear()\n throw error\n }\n spin.succeed()\n}\n\nexport function logAppDeployed({\n application,\n cliConfig,\n output,\n}: {\n application: UserApplicationResolved\n cliConfig: DeployAppOptions['cliConfig']\n output: DeployAppOptions['output']\n}): void {\n const url = getCoreAppUrl(application.organizationId, application.id)\n const named = application.title ? ` — \"${application.title}\"` : ''\n output.log(`\\nSuccess! Application deployed to ${styleText('cyan', url)}${named}`)\n\n if (getAppId(cliConfig)) return\n\n output.log(`\\n════ ${styleText('bold', 'Next step:')} ════`)\n output.log(\n styleText('bold', '\\nAdd the deployment.appId to your sanity.cli.js or sanity.cli.ts file:'),\n )\n output.log(`\n${styleText(\n 'dim',\n `app: {\n // your application config here…\n}`,\n)},\n${styleText(\n ['bold', 'green'],\n `deployment: {\n appId: '${application.id}',\n}\\n`,\n)}`)\n}\n"],"names":["basename","dirname","styleText","createGzip","exitCodes","spinner","deployInstallationConfig","getWorkbench","resolveInstallationId","summarizeInstallationConfig","pack","createDeployment","updateUserApplication","getAppId","EXTERNAL_APP_NOT_SUPPORTED","NO_ORGANIZATION_ID","getErrorMessage","buildApp","extractCoreAppManifest","resolveTitleUpdate","createUserApplication","checkAppId","checkAppTarget","checkAutoUpdates","checkBuild","checkPackageVersion","verifyOutputDir","deployDebug","listDeploymentFiles","runDeploy","findUserApplication","getCoreAppUrl","APP_PACKAGE","deployApp","options","listFiles","projectRoot","sourceDir","directory","run","runAppDeployment","type","reporter","cliConfig","flags","output","workDir","organizationId","app","workbench","dryRun","deploySingletonInstallationConfig","deployApplication","hasInterfaces","assertDeployable","err","report","exitCode","USAGE_ERROR","message","solution","status","isAutoUpdating","version","moduleName","application","external","resolveAppApplication","build","autoUpdatesEnabled","calledFromDeploy","outDir","skipReason","undefined","successMessage","isWorkbenchApp","manifest","installationId","configAppType","installationConfig","appType","applicationType","applicationVersion","target","syncApplicationTitle","shipAppDeployment","logAppDeployed","applicationId","id","title","url","trim","appId","unattended","yes","titleUpdate","log","from","to","spin","start","updated","body","succeed","fail","warn","tarball","entries","pipe","isApp","error","clear","named"],"mappings":"AAAA,SAAQA,QAAQ,EAAEC,OAAO,QAAO,YAAW;AAC3C,SAAQC,SAAS,QAAO,YAAW;AACnC,SAAQC,UAAU,QAAO,YAAW;AAEpC,SAAQC,SAAS,QAAO,mBAAkB;AAC1C,SAAQC,OAAO,QAAO,sBAAqB;AAC3C,SACEC,wBAAwB,EACxBC,YAAY,EACZC,qBAAqB,EACrBC,2BAA2B,QACtB,+BAA8B;AACrC,SAAQC,IAAI,QAAO,SAAQ;AAE3B,SACEC,gBAAgB,EAChBC,qBAAqB,QAGhB,qCAAoC;AAC3C,SAAQC,QAAQ,QAAO,sBAAqB;AAC5C,SAAQC,0BAA0B,EAAEC,kBAAkB,QAAO,8BAA6B;AAC1F,SAAQC,eAAe,QAAO,gCAA+B;AAC7D,SAAQC,QAAQ,QAAO,uBAAsB;AAC7C,SAAQC,sBAAsB,EAAEC,kBAAkB,QAAO,wCAAuC;AAEhG,SAAQC,qBAAqB,QAAO,6BAA4B;AAChE,SACEC,UAAU,EACVC,cAAc,EACdC,gBAAgB,EAChBC,UAAU,EACVC,mBAAmB,EAEnBC,eAAe,QACV,oBAAmB;AAC1B,SAAQC,WAAW,QAAO,mBAAkB;AAC5C,SAAQC,mBAAmB,QAAO,sBAAqB;AACvD,SAA2BC,SAAS,QAAO,oBAAmB;AAC9D,SAAQC,mBAAmB,QAAO,2BAA0B;AAE5D,SAAQC,aAAa,QAAO,gBAAe;AAE3C,MAAMC,cAAc;AAEpB,OAAO,SAASC,UAAUC,OAAyB;IACjD,OAAOL,UAAUK,SAAS;QACxBC,WAAW,CAAC,EAACC,WAAW,EAAEC,SAAS,EAAC,GAAKT,oBAAoBS,WAAWD,YAAYE,SAAS;QAC7FC,KAAKC;QACLC,MAAM;IACR;AACF;AAEA,kFAAkF,GAClF,eAAeD,iBACbN,OAAyB,EACzBQ,QAAuB;IAEvB,MAAM,EAACC,SAAS,EAAEC,KAAK,EAAEC,MAAM,EAAER,SAAS,EAAC,GAAGH;IAC9C,MAAMY,UAAUZ,QAAQE,WAAW,CAACE,SAAS;IAC7C,MAAMS,iBAAiBJ,UAAUK,GAAG,EAAED;IACtC,MAAME,YAAY1C,aAAaoC;IAC/B,MAAMO,SAAS,CAAC,CAACN,KAAK,CAAC,UAAU;IAEjC,4EAA4E;IAC5E,yDAAyD;IACzD,MAAMO,oCAAoCF,WAAWE,qCAAqC;IAC1F,MAAMC,oBAAoB,CAACH,aAAaA,UAAUI,aAAa;IAE/D,0EAA0E;IAC1E,8EAA8E;IAC9E,IAAIJ,WAAW;QACb,IAAI;YACFA,UAAUK,gBAAgB;QAC5B,EAAE,OAAOC,KAAK;YACZb,SAASc,MAAM,CAAC;gBACdC,UAAUrD,UAAUsD,WAAW;gBAC/BC,SAAS3C,gBAAgBuC;gBACzBK,UAAU;gBACVC,QAAQ;YACV;QACF;IACF;IAEA,MAAMC,iBAAiBvC,iBAAiBmB,UAAU;QAACC;QAAWC;IAAK;IAEnE,0EAA0E;IAC1E,4BAA4B;IAC5B,IAAImB,UAAyB;IAC7B,IAAIX,mBAAmB;QACrBW,UAAU,MAAMtC,oBAAoBiB,UAAU;YAACsB,YAAYhC;YAAac;QAAO;IACjF,OAAO,IAAIK,mCAAmC;QAC5CY,UAAU,MAAMtC,oBAAoBiB,UAAU;YAACsB,YAAY;YAAUlB;QAAO;IAC9E;IAEAJ,SAASc,MAAM,CACbT,iBACI;QAACY,SAAS,CAAC,cAAc,EAAEZ,gBAAgB;QAAEc,QAAQ;IAAM,IAC3D;QACEF,SAAS5C;QACT6C,UAAU;QACVC,QAAQ;IACV;IAGNxC,WAAWqB,UAAU;QAACC;IAAS;IAE/B,IAAIsB,cAA8C;IAClD,IAAIrB,MAAMsB,QAAQ,EAAE;QAClBxB,SAASc,MAAM,CAAC;YACdG,SAAS7C;YACT8C,UAAU;YACVC,QAAQ;QACV;IACF,OAAO,IAAIT,mBAAmB;QAC5Ba,cAAc,MAAME,sBAAsBjC,SAAS;YAACgB;YAAQR;QAAQ;IACtE;IAEA,MAAMlB,WAAWkB,UAAU;QACzB0B,OAAO,IACLnD,SAAS;gBACPoD,oBAAoBP;gBACpBQ,kBAAkB;gBAClB3B;gBACAC;gBACA2B,QAAQlC;gBACRQ;gBACAC;YACF;QACF0B,YAAY5B,MAAMwB,KAAK,GACnBK,YACA;QACJC,gBAAgB;IAClB;IAEA,MAAMhD,gBAAgB;QAACiD,gBAAgB1B,cAAc;QAAMP;QAAUL;IAAS;IAE9E,wEAAwE;IACxE,IAAIuC;IACJ,IAAI;QACFA,WAAW,MAAM1D,uBAAuB;YAAC4B;QAAO;IAClD,EAAE,OAAOS,KAAK;QACZ5B,YAAY,iCAAiC4B;QAC7Cb,SAASc,MAAM,CAAC;YACdG,SAAS,CAAC,+BAA+B,EAAE3C,gBAAgBuC,MAAM;YACjEM,QAAQ;QACV;IACF;IAEA,4EAA4E;IAC5E,+EAA+E;IAC/E,IAAIgB;IACJ,MAAMC,gBAAgB7B,WAAW8B,oBAAoBC;IACrD,IACE7B,qCACAJ,kBACAE,WAAW8B,sBACXD,eACA;QACAD,iBAAiB,MAAMrE,sBAAsB;YAACwE,SAASF;YAAe/B;QAAc;QACpFL,SAASc,MAAM,CACbqB,iBACI;YAAClB,SAASlD,4BAA4BwC,UAAU8B,kBAAkB;YAAGlB,QAAQ;QAAM,IACnF;YACEJ,UAAUrD,UAAUsD,WAAW;YAC/BC,SAAS,CAAC,WAAW,EAAEmB,cAAc,iCAAiC,EAAE/B,eAAe,CAAC,CAAC;YACzFa,UAAU;YACVC,QAAQ;QACV;IAER;IAEA,iDAAiD;IACjD,IAAIX,QAAQ;IAEZ,IAAI2B,kBAAkBd,WAAWe,eAAe;QAC9C,MAAMxE,yBAAyB;YAC7B0E,SAASF;YACTD;YACAhC;YACAR;YACA0B;QACF;IACF;IAEA,8EAA8E;IAC9E,IAAI,CAACX,mBAAmB;QACtB,IAAIyB,kBAAkBd,SAAS;YAC7B,OAAO;gBAACkB,iBAAiB;gBAAWC,oBAAoBnB;gBAASc;gBAAgBM,QAAQ;YAAI;QAC/F;QACA;IACF;IAEA,8EAA8E;IAC9E,8EAA8E;IAC9E,IAAI,CAAClB,eAAe,CAACF,SAAS;IAE9BE,cAAc,MAAMmB,qBAAqB;QAACnB;QAAaW;QAAU/B;IAAM;IAEvE,MAAMwC,kBAAkB;QAACpB;QAAaH;QAAgBc;QAAUvC;QAAW0B;IAAO;IAElFuB,eAAe;QAACrB;QAAatB;QAAWE;IAAM;IAE9C,OAAO;QACLoC,iBAAiB;QACjBC,oBAAoBnB;QACpBoB,QAAQ;YACNI,eAAetB,YAAYuB,EAAE;YAC7BC,OAAOxB,YAAYwB,KAAK,IAAI;YAC5BC,KAAK3D,cAAckC,YAAYlB,cAAc,EAAEkB,YAAYuB,EAAE;QAC/D;IACF;AACF;AAEA;;;CAGC,GACD,eAAerB,sBACbjC,OAAyB,EACzB,EAACgB,MAAM,EAAER,QAAQ,EAA6C;IAE9D,MAAM,EAACC,SAAS,EAAEC,KAAK,EAAEC,MAAM,EAAC,GAAGX;IACnC,MAAMa,iBAAiBJ,UAAUK,GAAG,EAAED,kBAAkB;IACxD,iFAAiF;IACjF,MAAM0C,QAAQ7C,MAAM6C,KAAK,EAAEE,UAAUhD,UAAUK,GAAG,EAAEyC,OAAOE,UAAUlB;IAErE,IAAIvB,QAAQ;QACV,MAAM5B,eAAeoB,UAAU;YAACkD,OAAO/E,SAAS8B;YAAYI;YAAgB0C;QAAK;QACjF,OAAO;IACT;IAEA,IAAIxB,cAAc,MAAMnC,oBAAoB;QAC1Ca;QACAI;QACAF;QACA4C;QACAI,YAAY,CAAC,CAACjD,MAAMkD,GAAG;IACzB;IACAnE,YAAY,0BAA0BsC;IAEtC,IAAI,CAACA,aAAa;QAChBtC,YAAY;QACZsC,cAAc,MAAM7C,sBAAsB2B,gBAAgB0C;QAC1D9D,YAAY,4BAA4BsC;IAC1C;IAEA,OAAOA;AACT;AAEA,uEAAuE,GACvE,eAAemB,qBAAqB,EAClCnB,WAAW,EACXW,QAAQ,EACR/B,MAAM,EAKP;IACC,MAAMkD,cAAc5E,mBAAmByD,UAAUX;IACjD,IAAI,CAAC8B,aAAa,OAAO9B;IAEzBtC,YAAY,4CAA4CoE;IACxDlD,OAAOmD,GAAG,CACRD,YAAYE,IAAI,GACZ,CAAC,qBAAqB,EAAEF,YAAYE,IAAI,CAAC,MAAM,EAAEF,YAAYG,EAAE,CAAC,CAAC,CAAC,GAClE,CAAC,8BAA8B,EAAEH,YAAYG,EAAE,CAAC,CAAC,CAAC;IAExD,MAAMC,OAAO9F,QAAQ,8BAA8B+F,KAAK;IACxD,IAAI;QACF,MAAMC,UAAU,MAAMzF,sBAAsB;YAC1C2E,eAAetB,YAAYuB,EAAE;YAC7BR,SAAS;YACTsB,MAAM;gBAACb,OAAOM,YAAYG,EAAE;YAAA;QAC9B;QACAC,KAAKI,OAAO;QACZ,OAAOF;IACT,EAAE,OAAO9C,KAAK;QACZ4C,KAAKK,IAAI;QACT,MAAM7C,UAAU3C,gBAAgBuC;QAChC5B,YAAY,oCAAoC;YAACgC;QAAO;QACxDd,OAAO4D,IAAI,CAAC,CAAC,kCAAkC,EAAE9C,SAAS;QAC1D,OAAOM;IACT;AACF;AAEA,eAAeoB,kBAAkB,EAC/BpB,WAAW,EACXH,cAAc,EACdc,QAAQ,EACRvC,SAAS,EACT0B,OAAO,EAOR;IACC,MAAM2C,UAAUhG,KAAKT,QAAQoC,YAAY;QAACsE,SAAS;YAAC3G,SAASqC;SAAW;IAAA,GAAGuE,IAAI,CAACzG;IAEhF,MAAMgG,OAAO9F,QAAQ,gBAAgB+F,KAAK;IAC1C,IAAI;QACF,MAAMzF,iBAAiB;YACrB4E,eAAetB,YAAYuB,EAAE;YAC7BqB,OAAO;YACP/C;YACAc;YACA8B;YACA3C;QACF;IACF,EAAE,OAAO+C,OAAO;QACdX,KAAKY,KAAK;QACV,MAAMD;IACR;IACAX,KAAKI,OAAO;AACd;AAEA,OAAO,SAASjB,eAAe,EAC7BrB,WAAW,EACXtB,SAAS,EACTE,MAAM,EAKP;IACC,MAAM6C,MAAM3D,cAAckC,YAAYlB,cAAc,EAAEkB,YAAYuB,EAAE;IACpE,MAAMwB,QAAQ/C,YAAYwB,KAAK,GAAG,CAAC,IAAI,EAAExB,YAAYwB,KAAK,CAAC,CAAC,CAAC,GAAG;IAChE5C,OAAOmD,GAAG,CAAC,CAAC,mCAAmC,EAAE9F,UAAU,QAAQwF,OAAOsB,OAAO;IAEjF,IAAInG,SAAS8B,YAAY;IAEzBE,OAAOmD,GAAG,CAAC,CAAC,OAAO,EAAE9F,UAAU,QAAQ,cAAc,KAAK,CAAC;IAC3D2C,OAAOmD,GAAG,CACR9F,UAAU,QAAQ;IAEpB2C,OAAOmD,GAAG,CAAC,CAAC;AACd,EAAE9F,UACA,OACA,CAAC;;CAEF,CAAC,EACA;AACF,EAAEA,UACA;QAAC;QAAQ;KAAQ,EACjB,CAAC;UACO,EAAE+D,YAAYuB,EAAE,CAAC;GACxB,CAAC,GACD;AACH"}
@@ -8,6 +8,7 @@ import { getAutoUpdateIssueMessage, getAutoUpdateMigrationHint, resolveAutoUpdat
8
8
  import { checkDir } from './checkDir.js';
9
9
  import { deployDebug } from './deployDebug.js';
10
10
  import { resolveAppDeployTarget, resolveStudioDeployTarget } from './resolveDeployTarget.js';
11
+ import { getCoreAppUrl } from './urlUtils.js';
11
12
  export function createFailFastReporter(output) {
12
13
  return {
13
14
  report (check) {
@@ -55,7 +56,8 @@ export async function checkPackageVersion(reporter, { moduleName, workDir }) {
55
56
  const version = await getLocalPackageVersion(moduleName, workDir);
56
57
  reporter.report(version ? {
57
58
  message: `Using ${moduleName} ${version}`,
58
- status: 'pass'
59
+ status: 'pass',
60
+ version
59
61
  } : {
60
62
  message: `Failed to find installed ${moduleName} version`,
61
63
  solution: `Install ${moduleName} in this project`,
@@ -156,9 +158,16 @@ export async function checkBuild(reporter, { build, skipReason, successMessage }
156
158
  case 'found':
157
159
  {
158
160
  const { application } = resolution;
161
+ const title = application.title ?? application.appHost;
162
+ const url = getCoreAppUrl(application.organizationId, application.id);
159
163
  return {
160
- message: `Deploys to existing application "${application.title ?? application.appHost}"`,
161
- status: 'pass'
164
+ message: `Deploys to existing application "${title}" at ${url}`,
165
+ status: 'pass',
166
+ target: {
167
+ applicationId: application.id,
168
+ title: application.title ?? null,
169
+ url
170
+ }
162
171
  };
163
172
  }
164
173
  case 'invalid':
@@ -219,9 +228,15 @@ export async function checkAppTarget(reporter, { appId, organizationId, title })
219
228
  }
220
229
  case 'found':
221
230
  {
231
+ const url = studioUrl(resolution.application.appHost);
222
232
  return {
223
- message: `Deploys to existing studio ${studioUrl(resolution.application.appHost)}`,
224
- status: 'pass'
233
+ message: `Deploys to existing studio ${url}`,
234
+ status: 'pass',
235
+ target: {
236
+ applicationId: resolution.application.id,
237
+ title: resolution.application.title ?? null,
238
+ url
239
+ }
225
240
  };
226
241
  }
227
242
  case 'invalid':
@@ -245,10 +260,16 @@ export async function checkAppTarget(reporter, { appId, organizationId, title })
245
260
  }
246
261
  case 'would-create':
247
262
  {
263
+ const url = studioUrl(resolution.appHost);
248
264
  const titled = title ? ` titled "${title}"` : '';
249
265
  return {
250
- message: isExternal ? `Would register external studio at ${resolution.appHost}${titled}` : `Would create studio hostname ${studioUrl(resolution.appHost)}${titled} (name availability is checked on deploy)`,
251
- status: 'pass'
266
+ message: isExternal ? `Would register external studio at ${resolution.appHost}${titled}` : `Would create studio hostname ${url}${titled} (name availability is checked on deploy)`,
267
+ status: 'pass',
268
+ target: {
269
+ applicationId: null,
270
+ title: null,
271
+ url
272
+ }
252
273
  };
253
274
  }
254
275
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/deployChecks.ts"],"sourcesContent":["import {type CliConfig, exitCodes, getLocalPackageVersion, type Output} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {checkBuiltOutput} from '@sanity/workbench-cli/deploy'\n\nimport {resolveAppIdIssue} from '../../util/appId.js'\nimport {APP_ID_NOT_FOUND_IN_ORGANIZATION} from '../../util/errorMessages.js'\nimport {getErrorMessage} from '../../util/getErrorMessage.js'\nimport {\n getAutoUpdateIssueMessage,\n getAutoUpdateMigrationHint,\n resolveAutoUpdates,\n} from '../build/shouldAutoUpdate.js'\nimport {checkDir} from './checkDir.js'\nimport {deployDebug} from './deployDebug.js'\nimport {\n type AppDeployTargetResolution,\n resolveAppDeployTarget,\n resolveStudioDeployTarget,\n type StudioDeployTargetResolution,\n} from './resolveDeployTarget.js'\nimport {type DeployFlags} from './types.js'\n\ntype DeployCheckStatus = 'fail' | 'pass' | 'skip' | 'warn'\n\nexport interface DeployCheck {\n message: string\n status: DeployCheckStatus\n\n /** Exit code a real deploy uses when this check fails; defaults to 1 */\n exitCode?: number\n /** Actionable fix, shown under a failing or warning check */\n solution?: string\n}\n\n/**\n * Where deploy steps send their check outcomes — and the only place the deploy\n * mode lives. A real deploy fails fast: a `fail` prints and exits immediately,\n * which aborts the sequence. A dry run collects every outcome and never exits.\n * Steps just call `report`; they never know which mode is running.\n */\nexport interface CheckReporter {\n report(check: DeployCheck): void\n}\n\nexport function createFailFastReporter(output: Output): CheckReporter {\n return {\n report(check) {\n // Fixes surface in both modes: appended after the message here, and in the\n // dry-run report, so the problem and its fix never drift apart.\n const text = check.solution ? `${check.message}: ${check.solution}` : check.message\n if (check.status === 'fail') {\n output.error(text, {exit: check.exitCode ?? 1})\n } else if (check.status === 'warn') {\n output.warn(text)\n }\n },\n }\n}\n\nexport function createCollectingReporter(): CheckReporter & {results: DeployCheck[]} {\n const results: DeployCheck[] = []\n return {\n report(check) {\n results.push(check)\n },\n results,\n }\n}\n\n/**\n * Runs a fallible step and turns a throw into a `fail` check. In a real deploy\n * that fail exits (aborting the run); in a dry run it's recorded and `null`\n * comes back so the caller can skip the rest of the step. `name` labels the\n * step in debug logs.\n */\nexport async function runStep<T>(\n reporter: CheckReporter,\n name: string,\n work: () => Promise<T>,\n formatError: (err: unknown) => string = getErrorMessage,\n solution?: string,\n): Promise<T | null> {\n try {\n return await work()\n } catch (err) {\n deployDebug(`${name} step failed`, err)\n reporter.report({message: formatError(err), solution, status: 'fail'})\n return null\n }\n}\n\nexport async function checkPackageVersion(\n reporter: CheckReporter,\n {moduleName, workDir}: {moduleName: string; workDir: string},\n): Promise<string | null> {\n const version = await getLocalPackageVersion(moduleName, workDir)\n reporter.report(\n version\n ? {message: `Using ${moduleName} ${version}`, status: 'pass'}\n : {\n message: `Failed to find installed ${moduleName} version`,\n solution: `Install ${moduleName} in this project`,\n status: 'fail',\n },\n )\n return version\n}\n\nexport function checkAutoUpdates(\n reporter: CheckReporter,\n {cliConfig, flags}: {cliConfig: CliConfig; flags: DeployFlags},\n): boolean {\n const {enabled, issue} = resolveAutoUpdates({cliConfig, flags})\n\n if (issue) {\n reporter.report({\n message: getAutoUpdateIssueMessage(issue),\n // A config conflict makes a real deploy refuse to run; deprecations only warn\n status: issue.type === 'conflicting-config' ? 'fail' : 'warn',\n })\n\n // The deprecated top-level config also gets the styled migration edit, the\n // same second warning the build/dev path prints through shouldAutoUpdate.\n if (issue.type === 'deprecated-config') {\n reporter.report({\n message: getAutoUpdateMigrationHint(cliConfig.autoUpdates),\n status: 'warn',\n })\n }\n }\n\n return enabled\n}\n\n/**\n * The dry-run form of the `app.id` config check a real deploy runs in\n * `findUserApplication`: a conflict fails (both `app.id` and `deployment.appId`\n * set), the deprecated config alone warns.\n */\nexport function checkAppId(reporter: CheckReporter, {cliConfig}: {cliConfig: CliConfig}): void {\n const issue = resolveAppIdIssue(cliConfig)\n if (issue === 'conflicting-config') {\n reporter.report({\n message: 'Both `app.id` (deprecated) and `deployment.appId` are set',\n solution: 'Remove `app.id` from sanity.cli.ts',\n status: 'fail',\n })\n } else if (issue === 'deprecated-config') {\n reporter.report({\n message: 'The `app.id` config is deprecated',\n solution: 'Move it to `deployment.appId` in sanity.cli.ts',\n status: 'warn',\n })\n }\n}\n\nexport async function checkBuild(\n reporter: CheckReporter,\n {\n build,\n skipReason,\n successMessage,\n }: {build: () => Promise<void>; skipReason: string | undefined; successMessage: string},\n): Promise<void> {\n if (skipReason) {\n reporter.report({message: skipReason, status: 'skip'})\n return\n }\n\n await runStep(\n reporter,\n 'build',\n async () => {\n await build()\n reporter.report({message: successMessage, status: 'pass'})\n },\n (err) => `Build failed: ${getErrorMessage(err)}`,\n 'Fix the build error above, then retry',\n )\n}\n\n/**\n * The deploy directory must exist and hold the right build output: a federation\n * remote for a workbench app, an `index.html` SPA otherwise.\n */\nexport async function verifyOutputDir({\n isWorkbenchApp,\n reporter,\n sourceDir,\n}: {\n isWorkbenchApp: boolean\n reporter: CheckReporter\n sourceDir: string\n}): Promise<void> {\n const spin = spinner('Verifying local content...').start()\n const verifyBuild = isWorkbenchApp ? checkBuiltOutput : checkDir\n try {\n await verifyBuild(sourceDir)\n spin.succeed()\n } catch (err) {\n spin.fail()\n deployDebug('Error checking directory', err)\n reporter.report({\n message: getErrorMessage(err),\n solution: 'Run the build first, or check the output directory',\n status: 'fail',\n })\n }\n}\n\n/**\n * The single diagnosis for each app deploy-target verdict, shared by the\n * dry-run report and the real deploy's unattended error paths so message,\n * fix, and exit code can't drift between the two.\n */\nexport function describeAppTarget(\n resolution: AppDeployTargetResolution,\n {title}: {title?: string} = {},\n): DeployCheck {\n switch (resolution.type) {\n case 'blocked': {\n return {message: `Deploy target not resolved — ${resolution.message}`, status: 'skip'}\n }\n case 'found': {\n const {application} = resolution\n return {\n message: `Deploys to existing application \"${application.title ?? application.appHost}\"`,\n status: 'pass',\n }\n }\n case 'invalid': {\n return {\n message: APP_ID_NOT_FOUND_IN_ORGANIZATION,\n solution: 'Check `deployment.appId` matches an app in your organization',\n status: 'fail',\n }\n }\n case 'needs-input': {\n return {\n exitCode: exitCodes.USAGE_ERROR,\n message: `No \\`deployment.appId\\` configured (${resolution.existing.length} existing ${resolution.existing.length === 1 ? 'application' : 'applications'} to choose from)`,\n solution: 'Add `deployment.appId` to sanity.cli.ts',\n status: 'fail',\n }\n }\n // Without --title, creating an app needs a prompt no unattended run can answer\n case 'would-create': {\n if (title) {\n return {message: `Would create a new application \"${title}\"`, status: 'pass'}\n }\n return {\n exitCode: exitCodes.USAGE_ERROR,\n message: 'No application to deploy to — creating one needs a title',\n solution:\n 'Pass `--title \"<name>\"` or set `app.title` in sanity.cli.ts to create one, or set `deployment.appId` to deploy to an existing app',\n status: 'fail',\n }\n }\n }\n}\n\nexport function describeAppTargetError(err: unknown, organizationId: string | undefined): string {\n return (err as {statusCode?: number})?.statusCode === 403\n ? `You don’t have permission to view applications for the configured organization ID (\"${organizationId}\"). Verify the organization ID, or ask your organization’s admin for access.`\n : `Failed to resolve deploy target: ${getErrorMessage(err)}`\n}\n\nexport async function checkAppTarget(\n reporter: CheckReporter,\n {\n appId,\n organizationId,\n title,\n }: {appId: string | undefined; organizationId: string | undefined; title?: string},\n): Promise<void> {\n await runStep(\n reporter,\n 'target',\n async () =>\n reporter.report(\n describeAppTarget(await resolveAppDeployTarget({appId, organizationId}), {title}),\n ),\n (err) => describeAppTargetError(err, organizationId),\n )\n}\n\n/** Same contract as {@link describeAppTarget}, for the studio verdicts. */\nexport function describeStudioTarget(\n resolution: StudioDeployTargetResolution,\n {isExternal, title}: {isExternal: boolean; title?: string},\n): DeployCheck {\n const studioUrl = (host: string) => (isExternal ? host : `https://${host}.sanity.studio`)\n\n switch (resolution.type) {\n case 'blocked': {\n return {message: `Deploy target not resolved — ${resolution.message}`, status: 'skip'}\n }\n case 'found': {\n return {\n message: `Deploys to existing studio ${studioUrl(resolution.application.appHost)}`,\n status: 'pass',\n }\n }\n case 'invalid': {\n return {\n // A bad host is a usage error; other invalid targets exit 1\n exitCode: resolution.reason === 'invalid-host' ? exitCodes.USAGE_ERROR : 1,\n message: resolution.message,\n solution: 'Check `studioHost` and `deployment.appId` in sanity.cli.ts',\n status: 'fail',\n }\n }\n case 'needs-input': {\n return {\n exitCode: exitCodes.USAGE_ERROR,\n message: isExternal ? 'No external studio URL configured' : 'No studio hostname configured',\n solution: isExternal\n ? 'Set `studioHost` in sanity.cli.ts, or pass the full URL with --url'\n : 'Set `studioHost` in sanity.cli.ts, or pass a hostname with --url',\n status: 'fail',\n }\n }\n case 'would-create': {\n const titled = title ? ` titled \"${title}\"` : ''\n return {\n message: isExternal\n ? `Would register external studio at ${resolution.appHost}${titled}`\n : `Would create studio hostname ${studioUrl(resolution.appHost)}${titled} (name availability is checked on deploy)`,\n status: 'pass',\n }\n }\n }\n}\n\nexport async function checkStudioTarget(\n reporter: CheckReporter,\n options: {\n appId: string | undefined\n isExternal: boolean\n projectId: string | undefined\n studioHost: string | undefined\n title: string | undefined\n urlFlag: string | undefined\n },\n): Promise<void> {\n await runStep(\n reporter,\n 'target',\n async () =>\n reporter.report(\n describeStudioTarget(await resolveStudioDeployTarget(options), {\n isExternal: options.isExternal,\n title: options.title,\n }),\n ),\n (err) => `Failed to resolve deploy target: ${getErrorMessage(err)}`,\n )\n}\n"],"names":["exitCodes","getLocalPackageVersion","spinner","checkBuiltOutput","resolveAppIdIssue","APP_ID_NOT_FOUND_IN_ORGANIZATION","getErrorMessage","getAutoUpdateIssueMessage","getAutoUpdateMigrationHint","resolveAutoUpdates","checkDir","deployDebug","resolveAppDeployTarget","resolveStudioDeployTarget","createFailFastReporter","output","report","check","text","solution","message","status","error","exit","exitCode","warn","createCollectingReporter","results","push","runStep","reporter","name","work","formatError","err","checkPackageVersion","moduleName","workDir","version","checkAutoUpdates","cliConfig","flags","enabled","issue","type","autoUpdates","checkAppId","checkBuild","build","skipReason","successMessage","verifyOutputDir","isWorkbenchApp","sourceDir","spin","start","verifyBuild","succeed","fail","describeAppTarget","resolution","title","application","appHost","USAGE_ERROR","existing","length","describeAppTargetError","organizationId","statusCode","checkAppTarget","appId","describeStudioTarget","isExternal","studioUrl","host","reason","titled","checkStudioTarget","options"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/deploy/deployChecks.ts"],"sourcesContent":["import {type CliConfig, exitCodes, getLocalPackageVersion, type Output} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {checkBuiltOutput} from '@sanity/workbench-cli/deploy'\n\nimport {resolveAppIdIssue} from '../../util/appId.js'\nimport {APP_ID_NOT_FOUND_IN_ORGANIZATION} from '../../util/errorMessages.js'\nimport {getErrorMessage} from '../../util/getErrorMessage.js'\nimport {\n getAutoUpdateIssueMessage,\n getAutoUpdateMigrationHint,\n resolveAutoUpdates,\n} from '../build/shouldAutoUpdate.js'\nimport {checkDir} from './checkDir.js'\nimport {deployDebug} from './deployDebug.js'\nimport {\n type AppDeployTargetResolution,\n resolveAppDeployTarget,\n resolveStudioDeployTarget,\n type StudioDeployTargetResolution,\n} from './resolveDeployTarget.js'\nimport {type DeployFlags} from './types.js'\nimport {getCoreAppUrl} from './urlUtils.js'\n\ntype DeployCheckStatus = 'fail' | 'pass' | 'skip' | 'warn'\n\n/**\n * Where a deploy resolves to, computed once from the deploy-target verdict. The\n * dry-run report and its JSON both read this, so the human and machine outputs\n * can't drift.\n */\nexport interface DeployTarget {\n /** The application the deploy targets; `null` when a deploy would create one. */\n applicationId: string | null\n /** The application's title; `null` when it has none (or isn't created yet). */\n title: string | null\n /** Where the deployed studio/app is reachable; `null` when it can't be resolved yet. */\n url: string | null\n}\n\nexport interface DeployCheck {\n message: string\n status: DeployCheckStatus\n\n /** Exit code a real deploy uses when this check fails; defaults to 1 */\n exitCode?: number\n /** Actionable fix, shown under a failing or warning check */\n solution?: string\n /** Set on the deploy-target check with the resolved target both reporters read. */\n target?: DeployTarget\n /** Set on the package-version check with the version both reporters read. */\n version?: string\n}\n\n/**\n * Where deploy steps send their check outcomes — and the only place the deploy\n * mode lives. A real deploy fails fast: a `fail` prints and exits immediately,\n * which aborts the sequence. A dry run collects every outcome and never exits.\n * Steps just call `report`; they never know which mode is running.\n */\nexport interface CheckReporter {\n report(check: DeployCheck): void\n}\n\nexport function createFailFastReporter(output: Output): CheckReporter {\n return {\n report(check) {\n // Fixes surface in both modes: appended after the message here, and in the\n // dry-run report, so the problem and its fix never drift apart.\n const text = check.solution ? `${check.message}: ${check.solution}` : check.message\n if (check.status === 'fail') {\n output.error(text, {exit: check.exitCode ?? 1})\n } else if (check.status === 'warn') {\n output.warn(text)\n }\n },\n }\n}\n\nexport function createCollectingReporter(): CheckReporter & {results: DeployCheck[]} {\n const results: DeployCheck[] = []\n return {\n report(check) {\n results.push(check)\n },\n results,\n }\n}\n\n/**\n * Runs a fallible step and turns a throw into a `fail` check. In a real deploy\n * that fail exits (aborting the run); in a dry run it's recorded and `null`\n * comes back so the caller can skip the rest of the step. `name` labels the\n * step in debug logs.\n */\nexport async function runStep<T>(\n reporter: CheckReporter,\n name: string,\n work: () => Promise<T>,\n formatError: (err: unknown) => string = getErrorMessage,\n solution?: string,\n): Promise<T | null> {\n try {\n return await work()\n } catch (err) {\n deployDebug(`${name} step failed`, err)\n reporter.report({message: formatError(err), solution, status: 'fail'})\n return null\n }\n}\n\nexport async function checkPackageVersion(\n reporter: CheckReporter,\n {moduleName, workDir}: {moduleName: string; workDir: string},\n): Promise<string | null> {\n const version = await getLocalPackageVersion(moduleName, workDir)\n reporter.report(\n version\n ? {message: `Using ${moduleName} ${version}`, status: 'pass', version}\n : {\n message: `Failed to find installed ${moduleName} version`,\n solution: `Install ${moduleName} in this project`,\n status: 'fail',\n },\n )\n return version\n}\n\nexport function checkAutoUpdates(\n reporter: CheckReporter,\n {cliConfig, flags}: {cliConfig: CliConfig; flags: DeployFlags},\n): boolean {\n const {enabled, issue} = resolveAutoUpdates({cliConfig, flags})\n\n if (issue) {\n reporter.report({\n message: getAutoUpdateIssueMessage(issue),\n // A config conflict makes a real deploy refuse to run; deprecations only warn\n status: issue.type === 'conflicting-config' ? 'fail' : 'warn',\n })\n\n // The deprecated top-level config also gets the styled migration edit, the\n // same second warning the build/dev path prints through shouldAutoUpdate.\n if (issue.type === 'deprecated-config') {\n reporter.report({\n message: getAutoUpdateMigrationHint(cliConfig.autoUpdates),\n status: 'warn',\n })\n }\n }\n\n return enabled\n}\n\n/**\n * The dry-run form of the `app.id` config check a real deploy runs in\n * `findUserApplication`: a conflict fails (both `app.id` and `deployment.appId`\n * set), the deprecated config alone warns.\n */\nexport function checkAppId(reporter: CheckReporter, {cliConfig}: {cliConfig: CliConfig}): void {\n const issue = resolveAppIdIssue(cliConfig)\n if (issue === 'conflicting-config') {\n reporter.report({\n message: 'Both `app.id` (deprecated) and `deployment.appId` are set',\n solution: 'Remove `app.id` from sanity.cli.ts',\n status: 'fail',\n })\n } else if (issue === 'deprecated-config') {\n reporter.report({\n message: 'The `app.id` config is deprecated',\n solution: 'Move it to `deployment.appId` in sanity.cli.ts',\n status: 'warn',\n })\n }\n}\n\nexport async function checkBuild(\n reporter: CheckReporter,\n {\n build,\n skipReason,\n successMessage,\n }: {build: () => Promise<void>; skipReason: string | undefined; successMessage: string},\n): Promise<void> {\n if (skipReason) {\n reporter.report({message: skipReason, status: 'skip'})\n return\n }\n\n await runStep(\n reporter,\n 'build',\n async () => {\n await build()\n reporter.report({message: successMessage, status: 'pass'})\n },\n (err) => `Build failed: ${getErrorMessage(err)}`,\n 'Fix the build error above, then retry',\n )\n}\n\n/**\n * The deploy directory must exist and hold the right build output: a federation\n * remote for a workbench app, an `index.html` SPA otherwise.\n */\nexport async function verifyOutputDir({\n isWorkbenchApp,\n reporter,\n sourceDir,\n}: {\n isWorkbenchApp: boolean\n reporter: CheckReporter\n sourceDir: string\n}): Promise<void> {\n const spin = spinner('Verifying local content...').start()\n const verifyBuild = isWorkbenchApp ? checkBuiltOutput : checkDir\n try {\n await verifyBuild(sourceDir)\n spin.succeed()\n } catch (err) {\n spin.fail()\n deployDebug('Error checking directory', err)\n reporter.report({\n message: getErrorMessage(err),\n solution: 'Run the build first, or check the output directory',\n status: 'fail',\n })\n }\n}\n\n/**\n * The single diagnosis for each app deploy-target verdict, shared by the\n * dry-run report and the real deploy's unattended error paths so message,\n * fix, and exit code can't drift between the two.\n */\nexport function describeAppTarget(\n resolution: AppDeployTargetResolution,\n {title}: {title?: string} = {},\n): DeployCheck {\n switch (resolution.type) {\n case 'blocked': {\n return {message: `Deploy target not resolved — ${resolution.message}`, status: 'skip'}\n }\n case 'found': {\n const {application} = resolution\n const title = application.title ?? application.appHost\n const url = getCoreAppUrl(application.organizationId, application.id)\n return {\n message: `Deploys to existing application \"${title}\" at ${url}`,\n status: 'pass',\n target: {applicationId: application.id, title: application.title ?? null, url},\n }\n }\n case 'invalid': {\n return {\n message: APP_ID_NOT_FOUND_IN_ORGANIZATION,\n solution: 'Check `deployment.appId` matches an app in your organization',\n status: 'fail',\n }\n }\n case 'needs-input': {\n return {\n exitCode: exitCodes.USAGE_ERROR,\n message: `No \\`deployment.appId\\` configured (${resolution.existing.length} existing ${resolution.existing.length === 1 ? 'application' : 'applications'} to choose from)`,\n solution: 'Add `deployment.appId` to sanity.cli.ts',\n status: 'fail',\n }\n }\n // Without --title, creating an app needs a prompt no unattended run can answer\n case 'would-create': {\n if (title) {\n return {message: `Would create a new application \"${title}\"`, status: 'pass'}\n }\n return {\n exitCode: exitCodes.USAGE_ERROR,\n message: 'No application to deploy to — creating one needs a title',\n solution:\n 'Pass `--title \"<name>\"` or set `app.title` in sanity.cli.ts to create one, or set `deployment.appId` to deploy to an existing app',\n status: 'fail',\n }\n }\n }\n}\n\nexport function describeAppTargetError(err: unknown, organizationId: string | undefined): string {\n return (err as {statusCode?: number})?.statusCode === 403\n ? `You don’t have permission to view applications for the configured organization ID (\"${organizationId}\"). Verify the organization ID, or ask your organization’s admin for access.`\n : `Failed to resolve deploy target: ${getErrorMessage(err)}`\n}\n\nexport async function checkAppTarget(\n reporter: CheckReporter,\n {\n appId,\n organizationId,\n title,\n }: {appId: string | undefined; organizationId: string | undefined; title?: string},\n): Promise<void> {\n await runStep(\n reporter,\n 'target',\n async () =>\n reporter.report(\n describeAppTarget(await resolveAppDeployTarget({appId, organizationId}), {title}),\n ),\n (err) => describeAppTargetError(err, organizationId),\n )\n}\n\n/** Same contract as {@link describeAppTarget}, for the studio verdicts. */\nexport function describeStudioTarget(\n resolution: StudioDeployTargetResolution,\n {isExternal, title}: {isExternal: boolean; title?: string},\n): DeployCheck {\n const studioUrl = (host: string) => (isExternal ? host : `https://${host}.sanity.studio`)\n\n switch (resolution.type) {\n case 'blocked': {\n return {message: `Deploy target not resolved — ${resolution.message}`, status: 'skip'}\n }\n case 'found': {\n const url = studioUrl(resolution.application.appHost)\n return {\n message: `Deploys to existing studio ${url}`,\n status: 'pass',\n target: {\n applicationId: resolution.application.id,\n title: resolution.application.title ?? null,\n url,\n },\n }\n }\n case 'invalid': {\n return {\n // A bad host is a usage error; other invalid targets exit 1\n exitCode: resolution.reason === 'invalid-host' ? exitCodes.USAGE_ERROR : 1,\n message: resolution.message,\n solution: 'Check `studioHost` and `deployment.appId` in sanity.cli.ts',\n status: 'fail',\n }\n }\n case 'needs-input': {\n return {\n exitCode: exitCodes.USAGE_ERROR,\n message: isExternal ? 'No external studio URL configured' : 'No studio hostname configured',\n solution: isExternal\n ? 'Set `studioHost` in sanity.cli.ts, or pass the full URL with --url'\n : 'Set `studioHost` in sanity.cli.ts, or pass a hostname with --url',\n status: 'fail',\n }\n }\n case 'would-create': {\n const url = studioUrl(resolution.appHost)\n const titled = title ? ` titled \"${title}\"` : ''\n return {\n message: isExternal\n ? `Would register external studio at ${resolution.appHost}${titled}`\n : `Would create studio hostname ${url}${titled} (name availability is checked on deploy)`,\n status: 'pass',\n target: {applicationId: null, title: null, url},\n }\n }\n }\n}\n\nexport async function checkStudioTarget(\n reporter: CheckReporter,\n options: {\n appId: string | undefined\n isExternal: boolean\n projectId: string | undefined\n studioHost: string | undefined\n title: string | undefined\n urlFlag: string | undefined\n },\n): Promise<void> {\n await runStep(\n reporter,\n 'target',\n async () =>\n reporter.report(\n describeStudioTarget(await resolveStudioDeployTarget(options), {\n isExternal: options.isExternal,\n title: options.title,\n }),\n ),\n (err) => `Failed to resolve deploy target: ${getErrorMessage(err)}`,\n )\n}\n"],"names":["exitCodes","getLocalPackageVersion","spinner","checkBuiltOutput","resolveAppIdIssue","APP_ID_NOT_FOUND_IN_ORGANIZATION","getErrorMessage","getAutoUpdateIssueMessage","getAutoUpdateMigrationHint","resolveAutoUpdates","checkDir","deployDebug","resolveAppDeployTarget","resolveStudioDeployTarget","getCoreAppUrl","createFailFastReporter","output","report","check","text","solution","message","status","error","exit","exitCode","warn","createCollectingReporter","results","push","runStep","reporter","name","work","formatError","err","checkPackageVersion","moduleName","workDir","version","checkAutoUpdates","cliConfig","flags","enabled","issue","type","autoUpdates","checkAppId","checkBuild","build","skipReason","successMessage","verifyOutputDir","isWorkbenchApp","sourceDir","spin","start","verifyBuild","succeed","fail","describeAppTarget","resolution","title","application","appHost","url","organizationId","id","target","applicationId","USAGE_ERROR","existing","length","describeAppTargetError","statusCode","checkAppTarget","appId","describeStudioTarget","isExternal","studioUrl","host","reason","titled","checkStudioTarget","options"],"mappings":"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"}
@@ -1,37 +1,63 @@
1
+ import { format } from 'node:util';
1
2
  import { CLIError } from '@oclif/core/errors';
2
3
  import { createCollectingReporter, createFailFastReporter } from './deployChecks.js';
3
4
  import { deployDebug } from './deployDebug.js';
4
- import { renderDeploymentPlan } from './deploymentPlan.js';
5
+ import { deploymentPlanToJson, isDeployable, renderDeploymentPlan } from './deploymentPlan.js';
5
6
  /**
6
- * Runs a deploy in the mode the flags select. A real deploy fails fast and
7
- * mutates; `--dry-run` drives the same `run` sequence read-only and renders a
8
- * plan. The mode lives only in the reporter, so the two can't drift.
7
+ * Runs a deploy in the mode the flags select: a real deploy fails fast and
8
+ * mutates, `--dry-run` drives the same `run` sequence read-only and renders a
9
+ * plan, and `--json` emits the same information as machine-readable JSON.
9
10
  */ export async function runDeploy(options, spec) {
10
11
  const { output } = options;
11
- if (options.flags['dry-run']) {
12
- const reporter = createCollectingReporter();
13
- await spec.run(options, reporter);
14
- const failed = reporter.results.find((check)=>check.status === 'fail');
15
- const plan = {
16
- checks: reporter.results,
17
- // A blocked deploy uploads nothing, so only enumerate files for a deployable plan.
18
- files: failed ? [] : await spec.listFiles(options),
19
- type: spec.type
20
- };
21
- renderDeploymentPlan(plan, output);
22
- // Exit like a real (fail-fast) deploy would on the first failing check, so a
23
- // script gating on the exit code sees the same status.
24
- if (failed) output.error('Deploy blocked by failing checks.', {
25
- exit: failed.exitCode ?? 1
26
- });
27
- return;
28
- }
12
+ const json = !!options.flags.json;
13
+ // The JSON payload owns stdout, so the run's progress logs go to stderr; only
14
+ // the final JSON.stringify writes to stdout. Spinners are already on stderr.
15
+ const runOptions = json ? {
16
+ ...options,
17
+ output: {
18
+ ...output,
19
+ log: (message = '', ...args)=>void process.stderr.write(`${format(message, ...args)}\n`)
20
+ }
21
+ } : options;
29
22
  try {
30
- await spec.run(options, createFailFastReporter(output));
23
+ if (options.flags['dry-run']) {
24
+ const plan = await collectPlan(runOptions, spec);
25
+ if (json) output.log(JSON.stringify(deploymentPlanToJson(plan), null, 2));
26
+ else renderDeploymentPlan(plan, output);
27
+ exitIfBlocked(plan, output);
28
+ return;
29
+ }
30
+ const result = await spec.run(runOptions, createFailFastReporter(runOptions.output));
31
+ if (json && result) output.log(JSON.stringify({
32
+ deployed: true,
33
+ ...result
34
+ }, null, 2));
31
35
  } catch (error) {
36
+ // Failures signal via exit code and stderr, like every other command — no JSON on stdout.
32
37
  normalizeDeployError(error, output, spec.type);
33
38
  }
34
39
  }
40
+ /** Runs the step sequence read-only and gathers the plan a dry run reports. */ async function collectPlan(options, spec) {
41
+ const reporter = createCollectingReporter();
42
+ await spec.run(options, reporter);
43
+ const plan = {
44
+ checks: reporter.results,
45
+ files: [],
46
+ target: reporter.results.find((check)=>check.target)?.target ?? null,
47
+ type: spec.type,
48
+ version: reporter.results.find((check)=>check.version)?.version ?? null
49
+ };
50
+ // A blocked deploy uploads nothing, so only enumerate files for a deployable plan.
51
+ if (isDeployable(plan)) plan.files = await spec.listFiles(options);
52
+ return plan;
53
+ }
54
+ /** Exits like a real (fail-fast) deploy would, on the first failing check's exit code. */ function exitIfBlocked(plan, output) {
55
+ if (isDeployable(plan)) return;
56
+ const failed = plan.checks.find((check)=>check.status === 'fail');
57
+ output.error('Deploy blocked by failing checks.', {
58
+ exit: failed?.exitCode ?? 1
59
+ });
60
+ }
35
61
  function normalizeDeployError(error, output, type) {
36
62
  const noun = type === 'coreApp' ? 'application' : 'studio';
37
63
  // Ctrl+C on an interactive prompt isn't a real failure
@@ -41,13 +67,8 @@ function normalizeDeployError(error, output, type) {
41
67
  });
42
68
  return;
43
69
  }
44
- // A failed check already carries its own exit code; keep it
45
- if (error instanceof CLIError) {
46
- output.error(error.message, {
47
- exit: error.oclif?.exit ?? 1
48
- });
49
- return;
50
- }
70
+ // A failed check already carries its own message and exit code; rethrow untouched
71
+ if (error instanceof CLIError) throw error;
51
72
  deployDebug(`Error deploying ${noun}`, error);
52
73
  output.error(`Error deploying ${noun}: ${error}`, {
53
74
  exit: 1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/deployRunner.ts"],"sourcesContent":["import {CLIError} from '@oclif/core/errors'\nimport {type Output} from '@sanity/cli-core'\n\nimport {\n type CheckReporter,\n createCollectingReporter,\n createFailFastReporter,\n} from './deployChecks.js'\nimport {deployDebug} from './deployDebug.js'\nimport {type DeploymentFile, type DeploymentPlan, renderDeploymentPlan} from './deploymentPlan.js'\nimport {type DeployAppOptions} from './types.js'\n\n/**\n * The parts of a deploy that differ between core apps and studios. The shared\n * sequence — mode selection, error handling, the dry-run plan — lives in `runDeploy`.\n */\nexport interface DeploySpec {\n /** Files a real deploy would upload, listed only for the dry-run plan. */\n listFiles: (options: DeployAppOptions) => Promise<DeploymentFile[]>\n /** The step sequence; every step reports through `reporter`. */\n run: (options: DeployAppOptions, reporter: CheckReporter) => Promise<void>\n type: 'coreApp' | 'studio'\n}\n\n/**\n * Runs a deploy in the mode the flags select. A real deploy fails fast and\n * mutates; `--dry-run` drives the same `run` sequence read-only and renders a\n * plan. The mode lives only in the reporter, so the two can't drift.\n */\nexport async function runDeploy(options: DeployAppOptions, spec: DeploySpec): Promise<void> {\n const {output} = options\n\n if (options.flags['dry-run']) {\n const reporter = createCollectingReporter()\n await spec.run(options, reporter)\n const failed = reporter.results.find((check) => check.status === 'fail')\n const plan: DeploymentPlan = {\n checks: reporter.results,\n // A blocked deploy uploads nothing, so only enumerate files for a deployable plan.\n files: failed ? [] : await spec.listFiles(options),\n type: spec.type,\n }\n renderDeploymentPlan(plan, output)\n // Exit like a real (fail-fast) deploy would on the first failing check, so a\n // script gating on the exit code sees the same status.\n if (failed) output.error('Deploy blocked by failing checks.', {exit: failed.exitCode ?? 1})\n return\n }\n\n try {\n await spec.run(options, createFailFastReporter(output))\n } catch (error) {\n normalizeDeployError(error, output, spec.type)\n }\n}\n\nfunction normalizeDeployError(error: unknown, output: Output, type: 'coreApp' | 'studio'): void {\n const noun = type === 'coreApp' ? 'application' : 'studio'\n\n // Ctrl+C on an interactive prompt isn't a real failure\n if (error instanceof Error && error.name === 'ExitPromptError') {\n output.error('Deployment cancelled by user', {exit: 1})\n return\n }\n // A failed check already carries its own exit code; keep it\n if (error instanceof CLIError) {\n output.error(error.message, {exit: error.oclif?.exit ?? 1})\n return\n }\n deployDebug(`Error deploying ${noun}`, error)\n output.error(`Error deploying ${noun}: ${error}`, {exit: 1})\n}\n"],"names":["CLIError","createCollectingReporter","createFailFastReporter","deployDebug","renderDeploymentPlan","runDeploy","options","spec","output","flags","reporter","run","failed","results","find","check","status","plan","checks","files","listFiles","type","error","exit","exitCode","normalizeDeployError","noun","Error","name","message","oclif"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,qBAAoB;AAG3C,SAEEC,wBAAwB,EACxBC,sBAAsB,QACjB,oBAAmB;AAC1B,SAAQC,WAAW,QAAO,mBAAkB;AAC5C,SAAkDC,oBAAoB,QAAO,sBAAqB;AAelG;;;;CAIC,GACD,OAAO,eAAeC,UAAUC,OAAyB,EAAEC,IAAgB;IACzE,MAAM,EAACC,MAAM,EAAC,GAAGF;IAEjB,IAAIA,QAAQG,KAAK,CAAC,UAAU,EAAE;QAC5B,MAAMC,WAAWT;QACjB,MAAMM,KAAKI,GAAG,CAACL,SAASI;QACxB,MAAME,SAASF,SAASG,OAAO,CAACC,IAAI,CAAC,CAACC,QAAUA,MAAMC,MAAM,KAAK;QACjE,MAAMC,OAAuB;YAC3BC,QAAQR,SAASG,OAAO;YACxB,mFAAmF;YACnFM,OAAOP,SAAS,EAAE,GAAG,MAAML,KAAKa,SAAS,CAACd;YAC1Ce,MAAMd,KAAKc,IAAI;QACjB;QACAjB,qBAAqBa,MAAMT;QAC3B,6EAA6E;QAC7E,uDAAuD;QACvD,IAAII,QAAQJ,OAAOc,KAAK,CAAC,qCAAqC;YAACC,MAAMX,OAAOY,QAAQ,IAAI;QAAC;QACzF;IACF;IAEA,IAAI;QACF,MAAMjB,KAAKI,GAAG,CAACL,SAASJ,uBAAuBM;IACjD,EAAE,OAAOc,OAAO;QACdG,qBAAqBH,OAAOd,QAAQD,KAAKc,IAAI;IAC/C;AACF;AAEA,SAASI,qBAAqBH,KAAc,EAAEd,MAAc,EAAEa,IAA0B;IACtF,MAAMK,OAAOL,SAAS,YAAY,gBAAgB;IAElD,uDAAuD;IACvD,IAAIC,iBAAiBK,SAASL,MAAMM,IAAI,KAAK,mBAAmB;QAC9DpB,OAAOc,KAAK,CAAC,gCAAgC;YAACC,MAAM;QAAC;QACrD;IACF;IACA,4DAA4D;IAC5D,IAAID,iBAAiBtB,UAAU;QAC7BQ,OAAOc,KAAK,CAACA,MAAMO,OAAO,EAAE;YAACN,MAAMD,MAAMQ,KAAK,EAAEP,QAAQ;QAAC;QACzD;IACF;IACApB,YAAY,CAAC,gBAAgB,EAAEuB,MAAM,EAAEJ;IACvCd,OAAOc,KAAK,CAAC,CAAC,gBAAgB,EAAEI,KAAK,EAAE,EAAEJ,OAAO,EAAE;QAACC,MAAM;IAAC;AAC5D"}
1
+ {"version":3,"sources":["../../../src/actions/deploy/deployRunner.ts"],"sourcesContent":["import {format} from 'node:util'\n\nimport {CLIError} from '@oclif/core/errors'\nimport {type Output} from '@sanity/cli-core'\n\nimport {\n type CheckReporter,\n createCollectingReporter,\n createFailFastReporter,\n type DeployTarget,\n} from './deployChecks.js'\nimport {deployDebug} from './deployDebug.js'\nimport {\n type DeploymentFile,\n type DeploymentPlan,\n deploymentPlanToJson,\n isDeployable,\n renderDeploymentPlan,\n} from './deploymentPlan.js'\nimport {type DeployAppOptions} from './types.js'\n\n/** What a real deploy produced — the payload `--json` reports. */\nexport interface DeployResult {\n applicationType: 'coreApp' | 'studio'\n /** Installed framework version the deploy used (`sanity` or `@sanity/sdk-react`). */\n applicationVersion: string\n /**\n * The deployed application/studio, in the same shape the dry-run plan reports\n * so the two modes can't drift; `null` for a config-only singleton deploy.\n */\n target: DeployTarget | null\n\n /** Set when a media-library singleton deployed its installation config. */\n installationId?: string\n}\n\n/**\n * The parts of a deploy that differ between core apps and studios. The shared\n * sequence — mode selection, error handling, the dry-run plan, `--json` — lives\n * in `runDeploy`.\n */\nexport interface DeploySpec {\n /** Files a real deploy would upload, listed only for the dry-run plan. */\n listFiles: (options: DeployAppOptions) => Promise<DeploymentFile[]>\n /** The step sequence; every step reports through `reporter`. */\n run: (options: DeployAppOptions, reporter: CheckReporter) => Promise<DeployResult | void>\n type: 'coreApp' | 'studio'\n}\n\n/**\n * Runs a deploy in the mode the flags select: a real deploy fails fast and\n * mutates, `--dry-run` drives the same `run` sequence read-only and renders a\n * plan, and `--json` emits the same information as machine-readable JSON.\n */\nexport async function runDeploy(options: DeployAppOptions, spec: DeploySpec): Promise<void> {\n const {output} = options\n const json = !!options.flags.json\n\n // The JSON payload owns stdout, so the run's progress logs go to stderr; only\n // the final JSON.stringify writes to stdout. Spinners are already on stderr.\n const runOptions = json\n ? {\n ...options,\n output: {\n ...output,\n log: (message = '', ...args: unknown[]) =>\n void process.stderr.write(`${format(message, ...args)}\\n`),\n },\n }\n : options\n\n try {\n if (options.flags['dry-run']) {\n const plan = await collectPlan(runOptions, spec)\n if (json) output.log(JSON.stringify(deploymentPlanToJson(plan), null, 2))\n else renderDeploymentPlan(plan, output)\n exitIfBlocked(plan, output)\n return\n }\n\n const result = await spec.run(runOptions, createFailFastReporter(runOptions.output))\n if (json && result) output.log(JSON.stringify({deployed: true, ...result}, null, 2))\n } catch (error) {\n // Failures signal via exit code and stderr, like every other command — no JSON on stdout.\n normalizeDeployError(error, output, spec.type)\n }\n}\n\n/** Runs the step sequence read-only and gathers the plan a dry run reports. */\nasync function collectPlan(options: DeployAppOptions, spec: DeploySpec): Promise<DeploymentPlan> {\n const reporter = createCollectingReporter()\n await spec.run(options, reporter)\n const plan: DeploymentPlan = {\n checks: reporter.results,\n files: [],\n target: reporter.results.find((check) => check.target)?.target ?? null,\n type: spec.type,\n version: reporter.results.find((check) => check.version)?.version ?? null,\n }\n // A blocked deploy uploads nothing, so only enumerate files for a deployable plan.\n if (isDeployable(plan)) plan.files = await spec.listFiles(options)\n return plan\n}\n\n/** Exits like a real (fail-fast) deploy would, on the first failing check's exit code. */\nfunction exitIfBlocked(plan: DeploymentPlan, output: Output): void {\n if (isDeployable(plan)) return\n const failed = plan.checks.find((check) => check.status === 'fail')\n output.error('Deploy blocked by failing checks.', {exit: failed?.exitCode ?? 1})\n}\n\nfunction normalizeDeployError(error: unknown, output: Output, type: 'coreApp' | 'studio'): void {\n const noun = type === 'coreApp' ? 'application' : 'studio'\n\n // Ctrl+C on an interactive prompt isn't a real failure\n if (error instanceof Error && error.name === 'ExitPromptError') {\n output.error('Deployment cancelled by user', {exit: 1})\n return\n }\n // A failed check already carries its own message and exit code; rethrow untouched\n if (error instanceof CLIError) throw error\n deployDebug(`Error deploying ${noun}`, error)\n output.error(`Error deploying ${noun}: ${error}`, {exit: 1})\n}\n"],"names":["format","CLIError","createCollectingReporter","createFailFastReporter","deployDebug","deploymentPlanToJson","isDeployable","renderDeploymentPlan","runDeploy","options","spec","output","json","flags","runOptions","log","message","args","process","stderr","write","plan","collectPlan","JSON","stringify","exitIfBlocked","result","run","deployed","error","normalizeDeployError","type","reporter","checks","results","files","target","find","check","version","listFiles","failed","status","exit","exitCode","noun","Error","name"],"mappings":"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"}
@@ -17,6 +17,7 @@ import { listDeploymentFiles } from './deploymentPlan.js';
17
17
  import { runDeploy } from './deployRunner.js';
18
18
  import { deployStudioSchemasAndManifests } from './deployStudioSchemasAndManifests.js';
19
19
  import { findUserApplicationForStudio } from './findUserApplication.js';
20
+ const STUDIO_PACKAGE = 'sanity';
20
21
  export function deployStudio(options) {
21
22
  return runDeploy(options, {
22
23
  listFiles: ({ flags, projectRoot, sourceDir })=>flags.external ? Promise.resolve([]) : listDeploymentFiles(sourceDir, projectRoot.directory),
@@ -46,7 +47,7 @@ export function deployStudio(options) {
46
47
  });
47
48
  }
48
49
  const version = await checkPackageVersion(reporter, {
49
- moduleName: 'sanity',
50
+ moduleName: STUDIO_PACKAGE,
50
51
  workDir
51
52
  });
52
53
  reporter.report(projectId ? {
@@ -92,7 +93,7 @@ export function deployStudio(options) {
92
93
  const studioManifest = await uploadStudioSchema(options, {
93
94
  isExternal
94
95
  });
95
- await shipStudioDeployment({
96
+ const location = await shipStudioDeployment({
96
97
  application,
97
98
  isAutoUpdating,
98
99
  isExternal,
@@ -100,6 +101,15 @@ export function deployStudio(options) {
100
101
  studioManifest,
101
102
  version
102
103
  });
104
+ return {
105
+ applicationType: 'studio',
106
+ applicationVersion: version,
107
+ target: {
108
+ applicationId: application.id,
109
+ title: application.title ?? null,
110
+ url: location
111
+ }
112
+ };
103
113
  }
104
114
  /**
105
115
  * Finds the application a real deploy targets, registering a studio host when
@@ -162,7 +172,7 @@ export function deployStudio(options) {
162
172
  schemaRequired: flags['schema-required'],
163
173
  verbose: flags.verbose,
164
174
  workDir: projectRoot.directory
165
- });
175
+ }, output);
166
176
  } catch (error) {
167
177
  deployDebug('Error deploying studio schemas and manifests', error);
168
178
  if (error instanceof SchemaExtractionError) {
@@ -209,8 +219,9 @@ async function shipStudioDeployment({ application, isAutoUpdating, isExternal, o
209
219
  throw error;
210
220
  }
211
221
  spin.succeed();
212
- output.log(isExternal ? `\nSuccess! Studio registered` : `\nSuccess! Studio deployed to ${styleText('cyan', location)}`);
213
- if (getAppId(cliConfig)) return;
222
+ const named = application.title ? ` "${application.title}"` : '';
223
+ output.log(isExternal ? `\nSuccess! Studio registered${named}` : `\nSuccess! Studio deployed to ${styleText('cyan', location)}${named}`);
224
+ if (getAppId(cliConfig)) return location;
214
225
  const example = `Example:
215
226
  export default defineCliConfig({
216
227
  //…
@@ -223,6 +234,7 @@ export default defineCliConfig({
223
234
  output.log(`to the \`deployment\` section in sanity.cli.js or sanity.cli.ts`);
224
235
  output.log(`to avoid prompting for application id on next deploy.`);
225
236
  output.log(`\n${example}`);
237
+ return location;
226
238
  }
227
239
  function studioBuildSkipReason({ build, isExternal }) {
228
240
  if (isExternal) return 'Build skipped for externally hosted studios';