@sanity/cli 7.8.0 → 7.10.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.
Files changed (69) hide show
  1. package/README.md +132 -0
  2. package/dist/actions/auth/login/getProvider.js +7 -2
  3. package/dist/actions/auth/login/getProvider.js.map +1 -1
  4. package/dist/actions/dataset/create.js +2 -2
  5. package/dist/actions/dataset/create.js.map +1 -1
  6. package/dist/actions/deploy/createUserApplication.js +8 -5
  7. package/dist/actions/deploy/createUserApplication.js.map +1 -1
  8. package/dist/actions/deploy/deployApp.js +43 -20
  9. package/dist/actions/deploy/deployApp.js.map +1 -1
  10. package/dist/actions/deploy/deployChecks.js +9 -8
  11. package/dist/actions/deploy/deployChecks.js.map +1 -1
  12. package/dist/actions/deploy/deployStudio.js +12 -9
  13. package/dist/actions/deploy/deployStudio.js.map +1 -1
  14. package/dist/actions/deploy/resolveDeployTarget.js +3 -2
  15. package/dist/actions/deploy/resolveDeployTarget.js.map +1 -1
  16. package/dist/actions/deploy/urlUtils.js +0 -4
  17. package/dist/actions/deploy/urlUtils.js.map +1 -1
  18. package/dist/actions/documents/types.js.map +1 -1
  19. package/dist/actions/documents/validate.js +2 -1
  20. package/dist/actions/documents/validate.js.map +1 -1
  21. package/dist/actions/manifest/extractCoreAppManifest.js +6 -1
  22. package/dist/actions/manifest/extractCoreAppManifest.js.map +1 -1
  23. package/dist/actions/organizations/getOrganization.js +7 -3
  24. package/dist/actions/organizations/getOrganization.js.map +1 -1
  25. package/dist/actions/organizations/validateOrganizationSlug.js +20 -0
  26. package/dist/actions/organizations/validateOrganizationSlug.js.map +1 -0
  27. package/dist/actions/undeploy/adapters.js +3 -3
  28. package/dist/actions/undeploy/adapters.js.map +1 -1
  29. package/dist/actions/undeploy/runUndeploy.js +20 -6
  30. package/dist/actions/undeploy/runUndeploy.js.map +1 -1
  31. package/dist/actions/undeploy/undeployPlan.js +18 -3
  32. package/dist/actions/undeploy/undeployPlan.js.map +1 -1
  33. package/dist/commands/documents/validate.js +18 -17
  34. package/dist/commands/documents/validate.js.map +1 -1
  35. package/dist/commands/hooks/create.js +4 -0
  36. package/dist/commands/hooks/create.js.map +1 -1
  37. package/dist/commands/hooks/delete.js +5 -0
  38. package/dist/commands/hooks/delete.js.map +1 -1
  39. package/dist/commands/hooks/logs.js +5 -0
  40. package/dist/commands/hooks/logs.js.map +1 -1
  41. package/dist/commands/install.js +1 -1
  42. package/dist/commands/install.js.map +1 -1
  43. package/dist/commands/organizations/create.js +79 -0
  44. package/dist/commands/organizations/create.js.map +1 -0
  45. package/dist/commands/organizations/delete.js +100 -0
  46. package/dist/commands/organizations/delete.js.map +1 -0
  47. package/dist/commands/organizations/get.js +48 -0
  48. package/dist/commands/organizations/get.js.map +1 -0
  49. package/dist/commands/organizations/list.js +60 -0
  50. package/dist/commands/organizations/list.js.map +1 -0
  51. package/dist/commands/organizations/update.js +115 -0
  52. package/dist/commands/organizations/update.js.map +1 -0
  53. package/dist/commands/projects/create.js +2 -1
  54. package/dist/commands/projects/create.js.map +1 -1
  55. package/dist/commands/undeploy.js +13 -1
  56. package/dist/commands/undeploy.js.map +1 -1
  57. package/dist/prompts/promptForOrganizationName.js +2 -1
  58. package/dist/prompts/promptForOrganizationName.js.map +1 -1
  59. package/dist/server/devServer.js.map +1 -1
  60. package/dist/services/organizations.js +45 -2
  61. package/dist/services/organizations.js.map +1 -1
  62. package/dist/services/userApplications.js.map +1 -1
  63. package/dist/topicAliases.js +7 -0
  64. package/dist/topicAliases.js.map +1 -1
  65. package/dist/util/organizationAliases.js +12 -0
  66. package/dist/util/organizationAliases.js.map +1 -0
  67. package/oclif.config.js +1 -0
  68. package/oclif.manifest.json +1342 -1094
  69. package/package.json +27 -27
@@ -1,5 +1,6 @@
1
1
  import { exitCodes, getLocalPackageVersion } from '@sanity/cli-core';
2
2
  import { getErrorMessage } from '@sanity/cli-core/errors';
3
+ import { getCoreAppUrl } from '@sanity/cli-core/util';
3
4
  import { spinner } from '@sanity/cli-core/ux';
4
5
  import { checkBuiltOutput } from '@sanity/workbench-cli/deploy';
5
6
  import { resolveAppIdIssue } from '../../util/appId.js';
@@ -9,7 +10,6 @@ import { getAutoUpdateIssueMessage, getAutoUpdateMigrationHint, resolveAutoUpdat
9
10
  import { checkDir } from './checkDir.js';
10
11
  import { deployDebug } from './deployDebug.js';
11
12
  import { resolveAppDeployTarget, resolveStudioDeployTarget, resolveWorkbenchApp, resolveWorkbenchStudio } from './resolveDeployTarget.js';
12
- import { getCoreAppUrl } from './urlUtils.js';
13
13
  export async function checkPackageVersion(reporter, { moduleName, workDir }) {
14
14
  const version = await getLocalPackageVersion(moduleName, workDir);
15
15
  reporter.report(version ? {
@@ -123,7 +123,7 @@ export async function checkBuild(reporter, { build, skipReason, successMessage }
123
123
  {
124
124
  const { application } = resolution;
125
125
  const title = application.title ?? application.appHost;
126
- const url = getCoreAppUrl(application.organizationId, application.id);
126
+ const url = application.url ?? getCoreAppUrl(application.organizationId, application.id);
127
127
  return {
128
128
  message: `Deploys to existing application "${title}" at ${url}`,
129
129
  status: 'pass',
@@ -222,7 +222,7 @@ export function describeAppTargetError(err, organizationId) {
222
222
  }
223
223
  });
224
224
  }
225
- /** Same contract as {@link describeAppTarget}, for the studio verdicts. */ export function describeStudioTarget(resolution, { isExternal, title }) {
225
+ /** Same contract as {@link describeAppTarget}, for the studio verdicts. */ export function describeStudioTarget(resolution, { isExternal, isWorkbench, title }) {
226
226
  const studioUrl = (host)=>isExternal ? host : `https://${host}.sanity.studio`;
227
227
  switch(resolution.type){
228
228
  case 'blocked':
@@ -234,7 +234,7 @@ export function describeAppTargetError(err, organizationId) {
234
234
  }
235
235
  case 'found':
236
236
  {
237
- const url = studioUrl(resolution.application.appHost);
237
+ const url = resolution.application.url ?? studioUrl(resolution.application.appHost);
238
238
  return {
239
239
  message: `Deploys to existing studio ${url}`,
240
240
  status: 'pass',
@@ -267,10 +267,11 @@ export function describeAppTargetError(err, organizationId) {
267
267
  }
268
268
  case 'would-create':
269
269
  {
270
- const url = studioUrl(resolution.appHost);
270
+ // A workbench studio's URL needs the application id, which only exists after the create.
271
+ const url = isWorkbench ? null : studioUrl(resolution.appHost);
271
272
  const titled = title ? ` titled "${title}"` : '';
272
273
  return {
273
- message: isExternal ? `Would register external studio at ${resolution.appHost}${titled}` : `Would create studio hostname ${url}${titled} (name availability is checked on deploy)`,
274
+ message: isExternal ? `Would register external studio at ${resolution.appHost}${titled}` : `Would create studio hostname ${url ?? resolution.appHost}${titled} (name availability is checked on deploy)`,
274
275
  status: 'pass',
275
276
  // `title || null`, not `?? null`, so target.title tracks the same
276
277
  // truthiness the message's `titled` suffix uses (an empty title is no title)
@@ -287,8 +288,7 @@ export function describeAppTargetError(err, organizationId) {
287
288
  /**
288
289
  * Reports the studio deploy target as a check and returns the resolved target;
289
290
  * `isWorkbenchApp` selects the backend. Both modes run it — a real deploy uses
290
- * the returned target to reject a bad `appId` before building and to report the
291
- * studio URL from the resolved slug.
291
+ * it to reject a bad `appId` before building.
292
292
  */ export async function checkStudioTarget(reporter, options) {
293
293
  const { title } = options;
294
294
  const resolve = options.isWorkbenchApp ? resolveWorkbenchStudio({
@@ -304,6 +304,7 @@ export function describeAppTargetError(err, organizationId) {
304
304
  work: async ()=>{
305
305
  const check = describeStudioTarget(await resolve, {
306
306
  isExternal,
307
+ isWorkbench: !!options.isWorkbenchApp,
307
308
  title
308
309
  });
309
310
  reporter.report(check);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/deployChecks.ts"],"sourcesContent":["import {type CliConfig, exitCodes, getLocalPackageVersion} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {checkBuiltOutput, type DeployedExpose} from '@sanity/workbench-cli/deploy'\n\nimport {resolveAppIdIssue} from '../../util/appId.js'\nimport {type Check, type CheckReporter, runStep} from '../../util/checks.js'\nimport {APP_ID_NOT_FOUND_IN_ORGANIZATION} from '../../util/errorMessages.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 resolveWorkbenchApp,\n resolveWorkbenchStudio,\n type StudioDeployTargetResolution,\n} from './resolveDeployTarget.js'\nimport {type DeployFlags} from './types.js'\nimport {getCoreAppUrl} from './urlUtils.js'\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 /** Whether the deploy creates a new application/studio or updates an existing one. */\n action: 'create' | 'update'\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 /**\n * Slug the deploy creates the application at. Omitted on redeploys, and in a\n * dry run when no slug is configured (it's generated on deploy).\n */\n slug?: string\n}\n\nexport interface DeployCheck extends Check {\n /** Set on the config check with its summary both reporters read. */\n config?: string\n /** Set on the exposes check with the workbench exposes both reporters read. */\n exposes?: DeployedExpose[]\n /** Set on the singleton check when the app declares the flag explicitly. */\n isSingleton?: boolean\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\nexport type DeployCheckReporter = CheckReporter<DeployCheck>\n\nexport async function checkPackageVersion(\n reporter: DeployCheckReporter,\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: DeployCheckReporter,\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(\n reporter: DeployCheckReporter,\n {cliConfig}: {cliConfig: CliConfig},\n): 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: DeployCheckReporter,\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(reporter, {\n debug: deployDebug,\n formatError: (err) => `Build failed: ${getErrorMessage(err)}`,\n name: 'build',\n solution: 'Fix the build error above, then retry',\n work: async () => {\n await build()\n reporter.report({message: successMessage, status: 'pass'})\n },\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: DeployCheckReporter\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 {slug, title}: {slug?: string; 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: {\n action: 'update',\n applicationId: application.id,\n title: application.title ?? null,\n url,\n },\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 {\n message: `Would create a new application \"${title}\"${slug ? ` with slug \"${slug}\"` : ''}`,\n status: 'pass',\n target: {\n action: 'create',\n applicationId: null,\n ...(slug ? {slug} : {}),\n title,\n url: null,\n },\n }\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\n/**\n * Reports the app deploy target as a check and returns the resolved target;\n * `isWorkbenchApp` selects the backend. Both modes run it — a real deploy uses\n * it to reject a bad `appId` before building.\n */\nexport async function checkAppTarget(\n reporter: DeployCheckReporter,\n options:\n | {appId: string | undefined; isWorkbenchApp: true; slug?: string; title?: string}\n | {\n appId: string | undefined\n isWorkbenchApp?: false\n organizationId: string | undefined\n title?: string\n },\n): Promise<DeployTarget | null> {\n const {title} = options\n if (options.isWorkbenchApp) {\n const {appId, slug} = options\n return runStep(reporter, {\n debug: deployDebug,\n name: 'target',\n work: async () => {\n const check = describeAppTarget(await resolveWorkbenchApp({appId}), {slug, title})\n reporter.report(check)\n return check.target ?? null\n },\n })\n }\n\n const {appId, organizationId} = options\n return runStep(reporter, {\n debug: deployDebug,\n formatError: (err) => describeAppTargetError(err, organizationId),\n name: 'target',\n work: async () => {\n const check = describeAppTarget(await resolveAppDeployTarget({appId, organizationId}), {\n title,\n })\n reporter.report(check)\n return check.target ?? null\n },\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 action: 'update',\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 // `title || null`, not `?? null`, so target.title tracks the same\n // truthiness the message's `titled` suffix uses (an empty title is no title)\n target: {action: 'create', applicationId: null, title: title || null, url},\n }\n }\n }\n}\n\n/**\n * Reports the studio deploy target as a check and returns the resolved target;\n * `isWorkbenchApp` selects the backend. Both modes run it — a real deploy uses\n * the returned target to reject a bad `appId` before building and to report the\n * studio URL from the resolved slug.\n */\nexport async function checkStudioTarget(\n reporter: DeployCheckReporter,\n options:\n | {\n appId: string | undefined\n isExternal: boolean\n isWorkbenchApp?: false\n projectId: string | undefined\n studioHost: string | undefined\n title: string | undefined\n urlFlag: string | undefined\n }\n | {\n appId: string | undefined\n isWorkbenchApp: true\n studioHost: string | undefined\n title?: string\n },\n): Promise<DeployTarget | null> {\n const {title} = options\n const resolve = options.isWorkbenchApp\n ? resolveWorkbenchStudio({appId: options.appId, studioHost: options.studioHost})\n : resolveStudioDeployTarget(options)\n // Workbench studios always deploy to Sanity hosting, never an external URL.\n const isExternal = options.isWorkbenchApp ? false : options.isExternal\n\n return runStep(reporter, {\n debug: deployDebug,\n formatError: (err) => `Failed to resolve deploy target: ${getErrorMessage(err)}`,\n name: 'target',\n work: async () => {\n const check = describeStudioTarget(await resolve, {isExternal, title})\n reporter.report(check)\n return check.target ?? null\n },\n })\n}\n"],"names":["exitCodes","getLocalPackageVersion","getErrorMessage","spinner","checkBuiltOutput","resolveAppIdIssue","runStep","APP_ID_NOT_FOUND_IN_ORGANIZATION","getAutoUpdateIssueMessage","getAutoUpdateMigrationHint","resolveAutoUpdates","checkDir","deployDebug","resolveAppDeployTarget","resolveStudioDeployTarget","resolveWorkbenchApp","resolveWorkbenchStudio","getCoreAppUrl","checkPackageVersion","reporter","moduleName","workDir","version","report","message","status","solution","checkAutoUpdates","cliConfig","flags","enabled","issue","type","autoUpdates","checkAppId","checkBuild","build","skipReason","successMessage","debug","formatError","err","name","work","verifyOutputDir","isWorkbenchApp","sourceDir","spin","start","verifyBuild","succeed","fail","describeAppTarget","resolution","slug","title","application","appHost","url","organizationId","id","target","action","applicationId","exitCode","USAGE_ERROR","existing","length","describeAppTargetError","statusCode","checkAppTarget","options","appId","check","describeStudioTarget","isExternal","studioUrl","host","reason","titled","checkStudioTarget","resolve","studioHost"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/deploy/deployChecks.ts"],"sourcesContent":["import {type CliConfig, exitCodes, getLocalPackageVersion} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {getCoreAppUrl} from '@sanity/cli-core/util'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {checkBuiltOutput, type DeployedExpose} from '@sanity/workbench-cli/deploy'\n\nimport {resolveAppIdIssue} from '../../util/appId.js'\nimport {type Check, type CheckReporter, runStep} from '../../util/checks.js'\nimport {APP_ID_NOT_FOUND_IN_ORGANIZATION} from '../../util/errorMessages.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 resolveWorkbenchApp,\n resolveWorkbenchStudio,\n type StudioDeployTargetResolution,\n} from './resolveDeployTarget.js'\nimport {type DeployFlags} from './types.js'\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 /** Whether the deploy creates a new application/studio or updates an existing one. */\n action: 'create' | 'update'\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 /**\n * Slug the deploy creates the application at. Omitted on redeploys, and in a\n * dry run when no slug is configured (it's generated on deploy).\n */\n slug?: string\n}\n\nexport interface DeployCheck extends Check {\n /** Set on the config check with its summary both reporters read. */\n config?: string\n /** Set on the exposes check with the workbench exposes both reporters read. */\n exposes?: DeployedExpose[]\n /** Set on the singleton check when the app declares the flag explicitly. */\n isSingleton?: boolean\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\nexport type DeployCheckReporter = CheckReporter<DeployCheck>\n\nexport async function checkPackageVersion(\n reporter: DeployCheckReporter,\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: DeployCheckReporter,\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(\n reporter: DeployCheckReporter,\n {cliConfig}: {cliConfig: CliConfig},\n): 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: DeployCheckReporter,\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(reporter, {\n debug: deployDebug,\n formatError: (err) => `Build failed: ${getErrorMessage(err)}`,\n name: 'build',\n solution: 'Fix the build error above, then retry',\n work: async () => {\n await build()\n reporter.report({message: successMessage, status: 'pass'})\n },\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: DeployCheckReporter\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 {slug, title}: {slug?: string; 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 = application.url ?? getCoreAppUrl(application.organizationId, application.id)\n return {\n message: `Deploys to existing application \"${title}\" at ${url}`,\n status: 'pass',\n target: {\n action: 'update',\n applicationId: application.id,\n title: application.title ?? null,\n url,\n },\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 {\n message: `Would create a new application \"${title}\"${slug ? ` with slug \"${slug}\"` : ''}`,\n status: 'pass',\n target: {\n action: 'create',\n applicationId: null,\n ...(slug ? {slug} : {}),\n title,\n url: null,\n },\n }\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\n/**\n * Reports the app deploy target as a check and returns the resolved target;\n * `isWorkbenchApp` selects the backend. Both modes run it — a real deploy uses\n * it to reject a bad `appId` before building.\n */\nexport async function checkAppTarget(\n reporter: DeployCheckReporter,\n options:\n | {appId: string | undefined; isWorkbenchApp: true; slug?: string; title?: string}\n | {\n appId: string | undefined\n isWorkbenchApp?: false\n organizationId: string | undefined\n title?: string\n },\n): Promise<DeployTarget | null> {\n const {title} = options\n if (options.isWorkbenchApp) {\n const {appId, slug} = options\n return runStep(reporter, {\n debug: deployDebug,\n name: 'target',\n work: async () => {\n const check = describeAppTarget(await resolveWorkbenchApp({appId}), {slug, title})\n reporter.report(check)\n return check.target ?? null\n },\n })\n }\n\n const {appId, organizationId} = options\n return runStep(reporter, {\n debug: deployDebug,\n formatError: (err) => describeAppTargetError(err, organizationId),\n name: 'target',\n work: async () => {\n const check = describeAppTarget(await resolveAppDeployTarget({appId, organizationId}), {\n title,\n })\n reporter.report(check)\n return check.target ?? null\n },\n })\n}\n\n/** Same contract as {@link describeAppTarget}, for the studio verdicts. */\nexport function describeStudioTarget(\n resolution: StudioDeployTargetResolution,\n {isExternal, isWorkbench, title}: {isExternal: boolean; isWorkbench?: 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 = resolution.application.url ?? studioUrl(resolution.application.appHost)\n return {\n message: `Deploys to existing studio ${url}`,\n status: 'pass',\n target: {\n action: 'update',\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 // A workbench studio's URL needs the application id, which only exists after the create.\n const url = isWorkbench ? null : 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 ?? resolution.appHost}${titled} (name availability is checked on deploy)`,\n status: 'pass',\n // `title || null`, not `?? null`, so target.title tracks the same\n // truthiness the message's `titled` suffix uses (an empty title is no title)\n target: {action: 'create', applicationId: null, title: title || null, url},\n }\n }\n }\n}\n\n/**\n * Reports the studio deploy target as a check and returns the resolved target;\n * `isWorkbenchApp` selects the backend. Both modes run it — a real deploy uses\n * it to reject a bad `appId` before building.\n */\nexport async function checkStudioTarget(\n reporter: DeployCheckReporter,\n options:\n | {\n appId: string | undefined\n isExternal: boolean\n isWorkbenchApp?: false\n projectId: string | undefined\n studioHost: string | undefined\n title: string | undefined\n urlFlag: string | undefined\n }\n | {\n appId: string | undefined\n isWorkbenchApp: true\n studioHost: string | undefined\n title?: string\n },\n): Promise<DeployTarget | null> {\n const {title} = options\n const resolve = options.isWorkbenchApp\n ? resolveWorkbenchStudio({appId: options.appId, studioHost: options.studioHost})\n : resolveStudioDeployTarget(options)\n // Workbench studios always deploy to Sanity hosting, never an external URL.\n const isExternal = options.isWorkbenchApp ? false : options.isExternal\n\n return runStep(reporter, {\n debug: deployDebug,\n formatError: (err) => `Failed to resolve deploy target: ${getErrorMessage(err)}`,\n name: 'target',\n work: async () => {\n const check = describeStudioTarget(await resolve, {\n isExternal,\n isWorkbench: !!options.isWorkbenchApp,\n title,\n })\n reporter.report(check)\n return check.target ?? null\n },\n })\n}\n"],"names":["exitCodes","getLocalPackageVersion","getErrorMessage","getCoreAppUrl","spinner","checkBuiltOutput","resolveAppIdIssue","runStep","APP_ID_NOT_FOUND_IN_ORGANIZATION","getAutoUpdateIssueMessage","getAutoUpdateMigrationHint","resolveAutoUpdates","checkDir","deployDebug","resolveAppDeployTarget","resolveStudioDeployTarget","resolveWorkbenchApp","resolveWorkbenchStudio","checkPackageVersion","reporter","moduleName","workDir","version","report","message","status","solution","checkAutoUpdates","cliConfig","flags","enabled","issue","type","autoUpdates","checkAppId","checkBuild","build","skipReason","successMessage","debug","formatError","err","name","work","verifyOutputDir","isWorkbenchApp","sourceDir","spin","start","verifyBuild","succeed","fail","describeAppTarget","resolution","slug","title","application","appHost","url","organizationId","id","target","action","applicationId","exitCode","USAGE_ERROR","existing","length","describeAppTargetError","statusCode","checkAppTarget","options","appId","check","describeStudioTarget","isExternal","isWorkbench","studioUrl","host","reason","titled","checkStudioTarget","resolve","studioHost"],"mappings":"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"}
@@ -4,7 +4,7 @@ import { createGzip } from 'node:zlib';
4
4
  import { formatSchemaValidation, SchemaExtractionError } from '@sanity/cli-build/_internal/extract';
5
5
  import { exitCodes } from '@sanity/cli-core';
6
6
  import { spinner } from '@sanity/cli-core/ux';
7
- import { buildExposes, deployStudio as deployWorkbenchStudio, getWorkbench } from '@sanity/workbench-cli/deploy';
7
+ import { buildExposes, deployStudio as deployWorkbenchStudio, getApplicationUrl, getWorkbench } from '@sanity/workbench-cli/deploy';
8
8
  import { pack } from 'tar-fs';
9
9
  import { createDeployment } from '../../services/userApplications.js';
10
10
  import { getAppId } from '../../util/appId.js';
@@ -66,7 +66,6 @@ export function deployStudio(options) {
66
66
  // resolve/create on user-applications, unchanged.
67
67
  let application = null;
68
68
  let studioCreated = false;
69
- let studioTarget = null;
70
69
  if (workbench && !isExternal) {
71
70
  reporter.report(organizationId ? {
72
71
  message: `Organization: ${organizationId}`,
@@ -76,9 +75,7 @@ export function deployStudio(options) {
76
75
  solution: 'Add `app.organizationId` to sanity.cli.ts',
77
76
  status: 'fail'
78
77
  });
79
- // Both modes, so a bad appId fails before the build; its resolved URL feeds
80
- // the deploy result.
81
- studioTarget = await checkStudioTarget(reporter, {
78
+ await checkStudioTarget(reporter, {
82
79
  appId,
83
80
  isWorkbenchApp: true,
84
81
  studioHost: cliConfig.studioHost,
@@ -145,10 +142,16 @@ export function deployStudio(options) {
145
142
  version,
146
143
  workspaces: toWorkspaces(studioManifest)
147
144
  });
145
+ const url = getApplicationUrl({
146
+ id: applicationId,
147
+ organizationId,
148
+ type: 'studio'
149
+ });
148
150
  logWorkbenchStudioDeployed({
149
151
  applicationId,
150
152
  cliConfig,
151
- output
153
+ output,
154
+ url
152
155
  });
153
156
  return {
154
157
  applicationType: 'studio',
@@ -160,7 +163,7 @@ export function deployStudio(options) {
160
163
  action: appId ? 'update' : 'create',
161
164
  applicationId,
162
165
  title: appTitle,
163
- url: studioTarget?.url ?? null
166
+ url
164
167
  }
165
168
  };
166
169
  }
@@ -332,8 +335,8 @@ function toWorkspaces(manifest) {
332
335
  title: workspace.title
333
336
  }));
334
337
  }
335
- /** Renders the workbench studio's deploy result; the appId hint shows only when none is configured. */ function logWorkbenchStudioDeployed({ applicationId, cliConfig, output }) {
336
- output.log(`\nSuccess! Studio deployed`);
338
+ /** Renders the workbench studio's deploy result; the appId hint shows only when none is configured. */ function logWorkbenchStudioDeployed({ applicationId, cliConfig, output, url }) {
339
+ output.log(`\nSuccess! Studio deployed to ${styleText('cyan', url)}`);
337
340
  if (getAppId(cliConfig)) return;
338
341
  output.log(`\nAdd ${styleText('cyan', `appId: '${applicationId}'`)}`);
339
342
  output.log(`to the \`deployment\` section in sanity.cli.js or sanity.cli.ts`);
@@ -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 {\n type BrettWorkspace,\n buildExposes,\n deployStudio as deployWorkbenchStudio,\n getWorkbench,\n} 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_ORGANIZATION_ID, 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 checkStudioTarget,\n type DeployCheckReporter,\n type DeployTarget,\n verifyOutputDir,\n} from './deployChecks.js'\nimport {deployDebug} from './deployDebug.js'\nimport {listDeploymentFiles, reportExposes} 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: DeployCheckReporter,\n): Promise<DeployResult | void> {\n const {cliConfig, flags, output, sourceDir} = options\n const workDir = options.projectRoot.directory\n const isExternal = !!flags.external\n const workbench = getWorkbench(cliConfig)\n const isWorkbenchApp = workbench !== null\n const projectId = cliConfig.api?.projectId\n const organizationId = cliConfig.app?.organizationId\n const appId = getAppId(cliConfig)\n const dryRun = !!flags['dry-run']\n\n // A federated app deploys through Sanity's build/hosting pipeline, which\n // --external skips — fail before doing any other work.\n if (isExternal && isWorkbenchApp) {\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 appTitle = workbench\n ? flags.title?.trim() || cliConfig.app?.title?.trim() || workbench.name\n : ''\n\n const isAutoUpdating = checkAutoUpdates(reporter, {cliConfig, flags})\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 // Workbench studios deploy to Brett (which needs the org); plain studios\n // resolve/create on user-applications, unchanged.\n let application: UserApplication | null = null\n let studioCreated = false\n let studioTarget: DeployTarget | null = null\n if (workbench && !isExternal) {\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 // Both modes, so a bad appId fails before the build; its resolved URL feeds\n // the deploy result.\n studioTarget = await checkStudioTarget(reporter, {\n appId,\n isWorkbenchApp: true,\n studioHost: cliConfig.studioHost,\n title: appTitle,\n })\n } else {\n ;({application, created: studioCreated} = await resolveStudioApplication(options, {\n dryRun,\n reporter,\n }))\n }\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 // Report the exposes deploying with the studio, both modes. External studios\n // host their own bundle, so nothing registers.\n const exposes = workbench && !isExternal ? reportExposes(reporter, workbench) : []\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 version means the deploy target was never resolved.\n if (!version) return\n\n const studioManifest = await uploadStudioSchema(options, {isExternal})\n // Workbench studios deploy to Brett; plain studios use user-applications.\n if (workbench && !isExternal && organizationId) {\n const {applicationId} = await deployWorkbenchStudio({\n appId,\n interfaces: buildExposes(workbench, {\n appName: workbench.name,\n appTitle,\n exposesAppView: true,\n version,\n }),\n isAutoUpdating,\n organizationId,\n output,\n projectId,\n sourceDir,\n studioHost: cliConfig.studioHost,\n title: appTitle,\n version,\n workspaces: toWorkspaces(studioManifest),\n })\n logWorkbenchStudioDeployed({applicationId, cliConfig, output})\n return {\n applicationType: 'studio',\n applicationVersion: version,\n ...(exposes.length > 0 ? {exposes} : {}),\n target: {\n action: appId ? 'update' : 'create',\n applicationId,\n title: appTitle,\n url: studioTarget?.url ?? null,\n },\n }\n }\n\n if (!application) return\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: {\n action: studioCreated ? 'create' : 'update',\n applicationId: application.id,\n title: application.title ?? null,\n url: location,\n },\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: DeployCheckReporter},\n): Promise<{application: UserApplication | null; created: boolean}> {\n const {cliConfig, flags, output} = options\n const isExternal = !!flags.external\n const appId = getAppId(cliConfig)\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,\n isExternal,\n projectId: cliConfig.api?.projectId,\n studioHost: cliConfig.studioHost,\n title,\n urlFlag: flags.url,\n })\n return {application: null, created: false}\n }\n\n const projectId = cliConfig.api?.projectId ?? ''\n // `created` is true when a configured-but-unregistered host was just registered.\n const {application, created} = await findUserApplicationForStudio({\n appId,\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 const registered = await createStudioUserApplication({\n projectId,\n title,\n urlType: isExternal ? 'external' : 'internal',\n })\n deployDebug('Created user application', registered)\n return {application: registered, created: true}\n }\n\n deployDebug('Found user application', application)\n return {application, created}\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 toWorkspaces(manifest: StudioManifest | null): BrettWorkspace[] {\n return (manifest?.workspaces ?? []).map((workspace) => ({\n basePath: workspace.basePath,\n dataset: workspace.dataset,\n icon: workspace.icon,\n name: workspace.name,\n projectId: workspace.projectId,\n subtitle: workspace.subtitle,\n title: workspace.title,\n }))\n}\n\n/** Renders the workbench studio's deploy result; the appId hint shows only when none is configured. */\nfunction logWorkbenchStudioDeployed({\n applicationId,\n cliConfig,\n output,\n}: {\n applicationId: string\n cliConfig: DeployAppOptions['cliConfig']\n output: DeployAppOptions['output']\n}): void {\n output.log(`\\nSuccess! Studio deployed`)\n if (getAppId(cliConfig)) return\n\n output.log(`\\nAdd ${styleText('cyan', `appId: '${applicationId}'`)}`)\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}\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","buildExposes","deployStudio","deployWorkbenchStudio","getWorkbench","pack","createDeployment","getAppId","NO_ORGANIZATION_ID","NO_PROJECT_ID","buildStudio","createStudioUserApplication","checkAutoUpdates","checkBuild","checkPackageVersion","checkStudioTarget","verifyOutputDir","deployDebug","listDeploymentFiles","reportExposes","runDeploy","deployStudioSchemasAndManifests","findUserApplicationForStudio","STUDIO_PACKAGE","options","listFiles","flags","projectRoot","sourceDir","external","Promise","resolve","directory","run","runStudioDeployment","type","reporter","cliConfig","output","workDir","isExternal","workbench","isWorkbenchApp","projectId","api","organizationId","app","appId","dryRun","report","exitCode","USAGE_ERROR","message","solution","status","appTitle","title","trim","name","isAutoUpdating","version","moduleName","application","studioCreated","studioTarget","studioHost","created","resolveStudioApplication","build","autoUpdatesEnabled","calledFromDeploy","outDir","skipReason","studioBuildSkipReason","successMessage","exposes","studioManifest","uploadStudioSchema","applicationId","interfaces","appName","exposesAppView","workspaces","toWorkspaces","logWorkbenchStudioDeployed","applicationType","applicationVersion","length","target","action","url","location","shipStudioDeployment","id","undefined","urlFlag","unattended","yes","log","registered","urlType","configPath","path","outPath","schemaRequired","verbose","error","validation","exit","tarball","entries","pipe","spin","start","isApp","manifest","fail","succeed","named","example","map","workspace","basePath","dataset","icon","subtitle"],"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 {\n type BrettWorkspace,\n buildExposes,\n deployStudio as deployWorkbenchStudio,\n getApplicationUrl,\n getWorkbench,\n} 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_ORGANIZATION_ID, 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 checkStudioTarget,\n type DeployCheckReporter,\n verifyOutputDir,\n} from './deployChecks.js'\nimport {deployDebug} from './deployDebug.js'\nimport {listDeploymentFiles, reportExposes} 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: DeployCheckReporter,\n): Promise<DeployResult | void> {\n const {cliConfig, flags, output, sourceDir} = options\n const workDir = options.projectRoot.directory\n const isExternal = !!flags.external\n const workbench = getWorkbench(cliConfig)\n const isWorkbenchApp = workbench !== null\n const projectId = cliConfig.api?.projectId\n const organizationId = cliConfig.app?.organizationId\n const appId = getAppId(cliConfig)\n const dryRun = !!flags['dry-run']\n\n // A federated app deploys through Sanity's build/hosting pipeline, which\n // --external skips — fail before doing any other work.\n if (isExternal && isWorkbenchApp) {\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 appTitle = workbench\n ? flags.title?.trim() || cliConfig.app?.title?.trim() || workbench.name\n : ''\n\n const isAutoUpdating = checkAutoUpdates(reporter, {cliConfig, flags})\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 // Workbench studios deploy to Brett (which needs the org); plain studios\n // resolve/create on user-applications, unchanged.\n let application: UserApplication | null = null\n let studioCreated = false\n if (workbench && !isExternal) {\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 await checkStudioTarget(reporter, {\n appId,\n isWorkbenchApp: true,\n studioHost: cliConfig.studioHost,\n title: appTitle,\n })\n } else {\n ;({application, created: studioCreated} = await resolveStudioApplication(options, {\n dryRun,\n reporter,\n }))\n }\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 // Report the exposes deploying with the studio, both modes. External studios\n // host their own bundle, so nothing registers.\n const exposes = workbench && !isExternal ? reportExposes(reporter, workbench) : []\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 version means the deploy target was never resolved.\n if (!version) return\n\n const studioManifest = await uploadStudioSchema(options, {isExternal})\n // Workbench studios deploy to Brett; plain studios use user-applications.\n if (workbench && !isExternal && organizationId) {\n const {applicationId} = await deployWorkbenchStudio({\n appId,\n interfaces: buildExposes(workbench, {\n appName: workbench.name,\n appTitle,\n exposesAppView: true,\n version,\n }),\n isAutoUpdating,\n organizationId,\n output,\n projectId,\n sourceDir,\n studioHost: cliConfig.studioHost,\n title: appTitle,\n version,\n workspaces: toWorkspaces(studioManifest),\n })\n const url = getApplicationUrl({id: applicationId, organizationId, type: 'studio'})\n logWorkbenchStudioDeployed({applicationId, cliConfig, output, url})\n return {\n applicationType: 'studio',\n applicationVersion: version,\n ...(exposes.length > 0 ? {exposes} : {}),\n target: {\n action: appId ? 'update' : 'create',\n applicationId,\n title: appTitle,\n url,\n },\n }\n }\n\n if (!application) return\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: {\n action: studioCreated ? 'create' : 'update',\n applicationId: application.id,\n title: application.title ?? null,\n url: location,\n },\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: DeployCheckReporter},\n): Promise<{application: UserApplication | null; created: boolean}> {\n const {cliConfig, flags, output} = options\n const isExternal = !!flags.external\n const appId = getAppId(cliConfig)\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,\n isExternal,\n projectId: cliConfig.api?.projectId,\n studioHost: cliConfig.studioHost,\n title,\n urlFlag: flags.url,\n })\n return {application: null, created: false}\n }\n\n const projectId = cliConfig.api?.projectId ?? ''\n // `created` is true when a configured-but-unregistered host was just registered.\n const {application, created} = await findUserApplicationForStudio({\n appId,\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 const registered = await createStudioUserApplication({\n projectId,\n title,\n urlType: isExternal ? 'external' : 'internal',\n })\n deployDebug('Created user application', registered)\n return {application: registered, created: true}\n }\n\n deployDebug('Found user application', application)\n return {application, created}\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 toWorkspaces(manifest: StudioManifest | null): BrettWorkspace[] {\n return (manifest?.workspaces ?? []).map((workspace) => ({\n basePath: workspace.basePath,\n dataset: workspace.dataset,\n icon: workspace.icon,\n name: workspace.name,\n projectId: workspace.projectId,\n subtitle: workspace.subtitle,\n title: workspace.title,\n }))\n}\n\n/** Renders the workbench studio's deploy result; the appId hint shows only when none is configured. */\nfunction logWorkbenchStudioDeployed({\n applicationId,\n cliConfig,\n output,\n url,\n}: {\n applicationId: string\n cliConfig: DeployAppOptions['cliConfig']\n output: DeployAppOptions['output']\n url: string\n}): void {\n output.log(`\\nSuccess! Studio deployed to ${styleText('cyan', url)}`)\n if (getAppId(cliConfig)) return\n\n output.log(`\\nAdd ${styleText('cyan', `appId: '${applicationId}'`)}`)\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}\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","buildExposes","deployStudio","deployWorkbenchStudio","getApplicationUrl","getWorkbench","pack","createDeployment","getAppId","NO_ORGANIZATION_ID","NO_PROJECT_ID","buildStudio","createStudioUserApplication","checkAutoUpdates","checkBuild","checkPackageVersion","checkStudioTarget","verifyOutputDir","deployDebug","listDeploymentFiles","reportExposes","runDeploy","deployStudioSchemasAndManifests","findUserApplicationForStudio","STUDIO_PACKAGE","options","listFiles","flags","projectRoot","sourceDir","external","Promise","resolve","directory","run","runStudioDeployment","type","reporter","cliConfig","output","workDir","isExternal","workbench","isWorkbenchApp","projectId","api","organizationId","app","appId","dryRun","report","exitCode","USAGE_ERROR","message","solution","status","appTitle","title","trim","name","isAutoUpdating","version","moduleName","application","studioCreated","studioHost","created","resolveStudioApplication","build","autoUpdatesEnabled","calledFromDeploy","outDir","skipReason","studioBuildSkipReason","successMessage","exposes","studioManifest","uploadStudioSchema","applicationId","interfaces","appName","exposesAppView","workspaces","toWorkspaces","url","id","logWorkbenchStudioDeployed","applicationType","applicationVersion","length","target","action","location","shipStudioDeployment","undefined","urlFlag","unattended","yes","log","registered","urlType","configPath","path","outPath","schemaRequired","verbose","error","validation","exit","tarball","entries","pipe","spin","start","isApp","manifest","fail","succeed","named","example","map","workspace","basePath","dataset","icon","subtitle"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { getApplication } from '@sanity/workbench-cli/deploy';
1
+ import { getApplication, getApplicationUrl } from '@sanity/workbench-cli/deploy';
2
2
  import { getUserApplication, getUserApplications } from '../../services/userApplications.js';
3
3
  import { APP_ID_NOT_FOUND_IN_ORGANIZATION } from '../../util/errorMessages.js';
4
4
  import { normalizeUrl, validateUrl } from './urlUtils.js';
@@ -170,7 +170,8 @@ async function resolveAppById(appId) {
170
170
  appHost: application.slug ?? '',
171
171
  id: application.id,
172
172
  organizationId: application.organizationId,
173
- title: application.title
173
+ title: application.title,
174
+ url: getApplicationUrl(application)
174
175
  },
175
176
  type: 'found'
176
177
  } : {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/resolveDeployTarget.ts"],"sourcesContent":["import {getApplication} from '@sanity/workbench-cli/deploy'\n\nimport {\n getUserApplication,\n getUserApplications,\n type UserApplication,\n type UserApplicationResolved,\n} from '../../services/userApplications.js'\nimport {APP_ID_NOT_FOUND_IN_ORGANIZATION} from '../../util/errorMessages.js'\nimport {normalizeUrl, validateUrl} from './urlUtils.js'\n\n/** The application fields a deploy-target verdict carries for reporting. */\ninterface DeployTargetApp {\n appHost: string\n id: string\n title: string | null\n}\n\n/** A coreApp verdict also carries the organization, for the dashboard URL. */\ninterface DeployTargetCoreApp extends DeployTargetApp {\n organizationId: string\n}\n\n/**\n * The read-only outcome of resolving where a deploy would go.\n *\n * - `found` — the deploy targets this existing 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 *\n * The user-applications resolvers carry the full {@link UserApplication} (the\n * real deploy needs it); the workbench resolvers and the report only need\n * {@link DeployTargetApp}, so `App` defaults to that widened shape.\n */\ntype CommonDeployTargetResolution<App = DeployTargetApp> =\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<App = DeployTargetApp> =\n | CommonDeployTargetResolution<App>\n | {appHost: string; type: 'would-create'}\n\nexport type AppDeployTargetResolution<App = DeployTargetCoreApp> =\n | CommonDeployTargetResolution<App>\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<UserApplication>> {\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<UserApplicationResolved>> {\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\n/**\n * The dry-run counterpart to a workbench app's create-on-deploy: a configured\n * `appId` is looked up read-only, otherwise a coreApp would be created.\n * @internal\n */\nexport async function resolveWorkbenchApp({\n appId,\n}: {\n appId: string | undefined\n}): Promise<AppDeployTargetResolution> {\n return appId ? resolveAppById(appId) : {type: 'would-create'}\n}\n\n/**\n * The studio counterpart to {@link resolveWorkbenchApp}: a configured\n * `studioHost` would create that hostname, and without one a deploy would prompt.\n *\n * @internal\n */\nexport async function resolveWorkbenchStudio({\n appId,\n studioHost,\n}: {\n appId: string | undefined\n studioHost: string | undefined\n}): Promise<StudioDeployTargetResolution> {\n if (appId) return resolveAppById(appId)\n if (studioHost) return {appHost: studioHost, type: 'would-create'}\n return {existing: [], type: 'needs-input'}\n}\n\nasync function resolveAppById(\n appId: string,\n): Promise<CommonDeployTargetResolution<DeployTargetCoreApp>> {\n const application = await getApplication(appId)\n return application\n ? {\n application: {\n appHost: application.slug ?? '',\n id: application.id,\n organizationId: application.organizationId,\n title: application.title,\n },\n type: 'found',\n }\n : {message: APP_ID_NOT_FOUND_IN_ORGANIZATION, reason: 'app-not-found', type: 'invalid'}\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":["getApplication","getUserApplication","getUserApplications","APP_ID_NOT_FOUND_IN_ORGANIZATION","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","resolveWorkbenchApp","resolveAppById","resolveWorkbenchStudio","slug","id","title","urlType","applications","filter","normalized","hostname","replace","includes","test"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/deploy/resolveDeployTarget.ts"],"sourcesContent":["import {getApplication, getApplicationUrl} from '@sanity/workbench-cli/deploy'\n\nimport {\n getUserApplication,\n getUserApplications,\n type UserApplication,\n type UserApplicationResolved,\n} from '../../services/userApplications.js'\nimport {APP_ID_NOT_FOUND_IN_ORGANIZATION} from '../../util/errorMessages.js'\nimport {normalizeUrl, validateUrl} from './urlUtils.js'\n\n/** The application fields a deploy-target verdict carries for reporting. */\ninterface DeployTargetApp {\n appHost: string\n id: string\n title: string | null\n\n url?: string\n}\n\n/** A coreApp verdict also carries the organization, for the dashboard URL. */\ninterface DeployTargetCoreApp extends DeployTargetApp {\n organizationId: string\n}\n\n/**\n * The read-only outcome of resolving where a deploy would go.\n *\n * - `found` — the deploy targets this existing 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 *\n * The user-applications resolvers carry the full {@link UserApplication} (the\n * real deploy needs it); the workbench resolvers and the report only need\n * {@link DeployTargetApp}, so `App` defaults to that widened shape.\n */\ntype CommonDeployTargetResolution<App = DeployTargetApp> =\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<App = DeployTargetApp> =\n | CommonDeployTargetResolution<App>\n | {appHost: string; type: 'would-create'}\n\nexport type AppDeployTargetResolution<App = DeployTargetCoreApp> =\n | CommonDeployTargetResolution<App>\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<UserApplication>> {\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<UserApplicationResolved>> {\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\n/**\n * The dry-run counterpart to a workbench app's create-on-deploy: a configured\n * `appId` is looked up read-only, otherwise a coreApp would be created.\n * @internal\n */\nexport async function resolveWorkbenchApp({\n appId,\n}: {\n appId: string | undefined\n}): Promise<AppDeployTargetResolution> {\n return appId ? resolveAppById(appId) : {type: 'would-create'}\n}\n\n/**\n * The studio counterpart to {@link resolveWorkbenchApp}: a configured\n * `studioHost` would create that hostname, and without one a deploy would prompt.\n *\n * @internal\n */\nexport async function resolveWorkbenchStudio({\n appId,\n studioHost,\n}: {\n appId: string | undefined\n studioHost: string | undefined\n}): Promise<StudioDeployTargetResolution> {\n if (appId) return resolveAppById(appId)\n if (studioHost) return {appHost: studioHost, type: 'would-create'}\n return {existing: [], type: 'needs-input'}\n}\n\nasync function resolveAppById(\n appId: string,\n): Promise<CommonDeployTargetResolution<DeployTargetCoreApp>> {\n const application = await getApplication(appId)\n return application\n ? {\n application: {\n appHost: application.slug ?? '',\n id: application.id,\n organizationId: application.organizationId,\n title: application.title,\n url: getApplicationUrl(application),\n },\n type: 'found',\n }\n : {message: APP_ID_NOT_FOUND_IN_ORGANIZATION, reason: 'app-not-found', type: 'invalid'}\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":["getApplication","getApplicationUrl","getUserApplication","getUserApplications","APP_ID_NOT_FOUND_IN_ORGANIZATION","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","resolveWorkbenchApp","resolveAppById","resolveWorkbenchStudio","slug","id","title","urlType","applications","filter","normalized","hostname","replace","includes","test"],"mappings":"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"}
@@ -1,7 +1,3 @@
1
- import { getSanityUrl } from '@sanity/cli-core';
2
- export function getCoreAppUrl(organizationId, appId) {
3
- return getSanityUrl(`/@${organizationId}/application/${appId}`);
4
- }
5
1
  /**
6
2
  * Validates that the given string is a valid http or https URL.
7
3
  * Returns `true` if valid, or an error message string if invalid.
@@ -1 +1 @@
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"}
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 +1 @@
1
- {"version":3,"sources":["../../../src/actions/documents/types.ts"],"sourcesContent":["import {type Output} from '@sanity/cli-core'\nimport {type ClientConfig} from '@sanity/client'\nimport {type ValidationMarker} from '@sanity/types'\nimport {type WorkerChannel, type WorkerChannelReceiver} from '@sanity/worker-channels'\n\nimport {type ValidateDocumentsCommand} from '../../commands/documents/validate.js'\n\nexport type Level = ValidationMarker['level']\n\n/** @internal */\nexport interface ValidateDocumentsWorkerData {\n workDir: string\n\n clientConfig?: Partial<ClientConfig>\n dataset?: string\n level?: ValidationMarker['level']\n maxCustomValidationConcurrency?: number\n maxFetchConcurrency?: number\n ndjsonFilePath?: string\n projectId?: string\n studioHost?: string\n workspace?: string\n}\n\n/** @internal */\nexport type ValidationWorkerChannel = WorkerChannel.Definition<{\n exportFinished: WorkerChannel.Event<{totalDocumentsToValidate: number}>\n exportProgress: WorkerChannel.Stream<{documentCount: number; downloadedCount: number}>\n loadedDocumentCount: WorkerChannel.Event<{documentCount: number}>\n loadedReferenceIntegrity: WorkerChannel.Event\n loadedWorkspace: WorkerChannel.Event<{\n basePath: string\n dataset: string\n name: string\n projectId: string\n }>\n validation: WorkerChannel.Stream<{\n documentId: string\n documentType: string\n intentUrl?: string\n level: ValidationMarker['level']\n markers: ValidationMarker[]\n revision: string\n validatedCount: number\n }>\n}>\n\n/**\n * Combines the package's receiver API with a `dispose()` method that\n * unsubscribes from worker messages AND terminates the worker thread.\n */\nexport interface ValidationReceiver {\n dispose: () => Promise<number>\n event: WorkerChannelReceiver<ValidationWorkerChannel>['event']\n stream: WorkerChannelReceiver<ValidationWorkerChannel>['stream']\n}\n\nexport type BuiltInValidationReporter = (options: {\n flags: ValidateDocumentsCommand['flags']\n output: Output\n worker: ValidationReceiver\n}) => Promise<Level>\n"],"names":[],"mappings":"AAyDA,WAIoB"}
1
+ {"version":3,"sources":["../../../src/actions/documents/types.ts"],"sourcesContent":["import {type Output} from '@sanity/cli-core/types'\nimport {type ClientConfig} from '@sanity/client'\nimport {type ValidationMarker} from '@sanity/types'\nimport {type WorkerChannel, type WorkerChannelReceiver} from '@sanity/worker-channels'\n\nimport {type ValidateDocumentsCommand} from '../../commands/documents/validate.js'\n\nexport type Level = ValidationMarker['level']\n\n/** @internal */\nexport interface ValidateDocumentsWorkerData {\n workDir: string\n\n clientConfig?: Partial<ClientConfig>\n dataset?: string\n level?: ValidationMarker['level']\n maxCustomValidationConcurrency?: number\n maxFetchConcurrency?: number\n ndjsonFilePath?: string\n projectId?: string\n studioHost?: string\n workspace?: string\n}\n\n/** @internal */\nexport type ValidationWorkerChannel = WorkerChannel.Definition<{\n exportFinished: WorkerChannel.Event<{totalDocumentsToValidate: number}>\n exportProgress: WorkerChannel.Stream<{documentCount: number; downloadedCount: number}>\n loadedDocumentCount: WorkerChannel.Event<{documentCount: number}>\n loadedReferenceIntegrity: WorkerChannel.Event\n loadedWorkspace: WorkerChannel.Event<{\n basePath: string\n dataset: string\n name: string\n projectId: string\n }>\n validation: WorkerChannel.Stream<{\n documentId: string\n documentType: string\n intentUrl?: string\n level: ValidationMarker['level']\n markers: ValidationMarker[]\n revision: string\n validatedCount: number\n }>\n}>\n\n/**\n * Combines the package's receiver API with a `dispose()` method that\n * unsubscribes from worker messages AND terminates the worker thread.\n */\nexport interface ValidationReceiver {\n dispose: () => Promise<number>\n event: WorkerChannelReceiver<ValidationWorkerChannel>['event']\n stream: WorkerChannelReceiver<ValidationWorkerChannel>['stream']\n}\n\nexport type BuiltInValidationReporter = (options: {\n flags: ValidateDocumentsCommand['flags']\n output: Output\n worker: ValidationReceiver\n}) => Promise<Level>\n"],"names":[],"mappings":"AAyDA,WAIoB"}
@@ -1,4 +1,5 @@
1
- import { createStudioWorker, getGlobalCliClient } from '@sanity/cli-core';
1
+ import { getGlobalCliClient } from '@sanity/cli-core/apiClient';
2
+ import { createStudioWorker } from '@sanity/cli-core/tasks';
2
3
  import { WorkerChannelReceiver } from '@sanity/worker-channels';
3
4
  import { DOCUMENTS_API_VERSION } from './constants.js';
4
5
  const defaultReporter = ({ dispose, stream })=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/documents/validate.ts"],"sourcesContent":["import {createStudioWorker, getGlobalCliClient} from '@sanity/cli-core'\nimport {type ClientConfig} from '@sanity/client'\nimport {type ValidationMarker} from '@sanity/types'\nimport {WorkerChannelReceiver} from '@sanity/worker-channels'\n\nimport {DOCUMENTS_API_VERSION} from './constants.js'\nimport {\n Level,\n type ValidateDocumentsWorkerData,\n type ValidationReceiver,\n type ValidationWorkerChannel,\n} from './types.js'\n\ninterface ValidateDocumentsOptions<TReturn = unknown> {\n dataset?: string // override\n level?: Level\n maxCustomValidationConcurrency?: number\n maxFetchConcurrency?: number\n ndjsonFilePath?: string\n projectId?: string // override\n reporter?: (worker: ValidationReceiver) => TReturn\n studioHost?: string\n workDir?: string\n workspace?: string\n}\n\ninterface DocumentValidationResult {\n documentId: string\n documentType: string\n level: ValidationMarker['level']\n markers: ValidationMarker[]\n revision: string\n}\n\nconst defaultReporter = ({dispose, stream}: ValidationReceiver) => {\n async function* createValidationGenerator() {\n for await (const {documentId, documentType, level, markers, revision} of stream.validation()) {\n const result: DocumentValidationResult = {\n documentId,\n documentType,\n level,\n markers,\n revision,\n }\n\n yield result\n }\n\n await dispose()\n }\n\n return createValidationGenerator()\n}\n\nexport function validateDocuments<TReturn>(\n options: Required<Pick<ValidateDocumentsOptions<TReturn>, 'reporter'>> &\n ValidateDocumentsOptions<TReturn>,\n): Promise<TReturn>\nexport function validateDocuments(\n options: ValidateDocumentsOptions,\n): Promise<AsyncIterable<DocumentValidationResult>>\nexport async function validateDocuments(options: ValidateDocumentsOptions): Promise<unknown> {\n const {\n dataset,\n level,\n maxCustomValidationConcurrency,\n maxFetchConcurrency,\n ndjsonFilePath,\n projectId,\n reporter = defaultReporter,\n workDir = process.cwd(),\n workspace,\n } = options\n\n const apiClient = await getGlobalCliClient({\n apiVersion: DOCUMENTS_API_VERSION,\n requireUser: true,\n })\n\n const clientConfig: ClientConfig = {\n ...apiClient.config(),\n // we set this explictly to true because we pass in a token via the\n // `clientConfiguration` object and also mock a browser environment in\n // this worker which triggers the browser warning\n ignoreBrowserTokenWarning: true,\n // Removing from object so config can be serialized\n // before sent to validation worker\n requester: undefined,\n // we set this explictly to true because the default client configuration\n // from the CLI comes configured with `useProjectHostname: false` when\n // `requireProject` is set to false\n useProjectHostname: true,\n }\n\n const worker = createStudioWorker(new URL('validateDocuments.worker.js', import.meta.url), {\n name: 'validateDocuments',\n studioRootPath: workDir,\n workerData: {\n // removes props in the config that make this object fail to serialize\n clientConfig: structuredClone(clientConfig),\n dataset,\n level,\n maxCustomValidationConcurrency,\n maxFetchConcurrency,\n ndjsonFilePath,\n projectId,\n studioHost: options.studioHost,\n workDir,\n workspace,\n } satisfies ValidateDocumentsWorkerData,\n })\n\n const receiver = WorkerChannelReceiver.from<ValidationWorkerChannel>(worker)\n const validationReceiver: ValidationReceiver = {\n dispose: async () => {\n receiver.unsubscribe()\n return worker.terminate()\n },\n event: receiver.event,\n stream: receiver.stream,\n }\n\n return reporter(validationReceiver)\n}\n"],"names":["createStudioWorker","getGlobalCliClient","WorkerChannelReceiver","DOCUMENTS_API_VERSION","defaultReporter","dispose","stream","createValidationGenerator","documentId","documentType","level","markers","revision","validation","result","validateDocuments","options","dataset","maxCustomValidationConcurrency","maxFetchConcurrency","ndjsonFilePath","projectId","reporter","workDir","process","cwd","workspace","apiClient","apiVersion","requireUser","clientConfig","config","ignoreBrowserTokenWarning","requester","undefined","useProjectHostname","worker","URL","url","name","studioRootPath","workerData","structuredClone","studioHost","receiver","from","validationReceiver","unsubscribe","terminate","event"],"mappings":"AAAA,SAAQA,kBAAkB,EAAEC,kBAAkB,QAAO,mBAAkB;AAGvE,SAAQC,qBAAqB,QAAO,0BAAyB;AAE7D,SAAQC,qBAAqB,QAAO,iBAAgB;AA6BpD,MAAMC,kBAAkB,CAAC,EAACC,OAAO,EAAEC,MAAM,EAAqB;IAC5D,gBAAgBC;QACd,WAAW,MAAM,EAACC,UAAU,EAAEC,YAAY,EAAEC,KAAK,EAAEC,OAAO,EAAEC,QAAQ,EAAC,IAAIN,OAAOO,UAAU,GAAI;YAC5F,MAAMC,SAAmC;gBACvCN;gBACAC;gBACAC;gBACAC;gBACAC;YACF;YAEA,MAAME;QACR;QAEA,MAAMT;IACR;IAEA,OAAOE;AACT;AASA,OAAO,eAAeQ,kBAAkBC,OAAiC;IACvE,MAAM,EACJC,OAAO,EACPP,KAAK,EACLQ,8BAA8B,EAC9BC,mBAAmB,EACnBC,cAAc,EACdC,SAAS,EACTC,WAAWlB,eAAe,EAC1BmB,UAAUC,QAAQC,GAAG,EAAE,EACvBC,SAAS,EACV,GAAGV;IAEJ,MAAMW,YAAY,MAAM1B,mBAAmB;QACzC2B,YAAYzB;QACZ0B,aAAa;IACf;IAEA,MAAMC,eAA6B;QACjC,GAAGH,UAAUI,MAAM,EAAE;QACrB,mEAAmE;QACnE,sEAAsE;QACtE,iDAAiD;QACjDC,2BAA2B;QAC3B,mDAAmD;QACnD,mCAAmC;QACnCC,WAAWC;QACX,yEAAyE;QACzE,sEAAsE;QACtE,mCAAmC;QACnCC,oBAAoB;IACtB;IAEA,MAAMC,SAASpC,mBAAmB,IAAIqC,IAAI,+BAA+B,YAAYC,GAAG,GAAG;QACzFC,MAAM;QACNC,gBAAgBjB;QAChBkB,YAAY;YACV,sEAAsE;YACtEX,cAAcY,gBAAgBZ;YAC9Bb;YACAP;YACAQ;YACAC;YACAC;YACAC;YACAsB,YAAY3B,QAAQ2B,UAAU;YAC9BpB;YACAG;QACF;IACF;IAEA,MAAMkB,WAAW1C,sBAAsB2C,IAAI,CAA0BT;IACrE,MAAMU,qBAAyC;QAC7CzC,SAAS;YACPuC,SAASG,WAAW;YACpB,OAAOX,OAAOY,SAAS;QACzB;QACAC,OAAOL,SAASK,KAAK;QACrB3C,QAAQsC,SAAStC,MAAM;IACzB;IAEA,OAAOgB,SAASwB;AAClB"}
1
+ {"version":3,"sources":["../../../src/actions/documents/validate.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core/apiClient'\nimport {createStudioWorker} from '@sanity/cli-core/tasks'\nimport {type ClientConfig} from '@sanity/client'\nimport {type ValidationMarker} from '@sanity/types'\nimport {WorkerChannelReceiver} from '@sanity/worker-channels'\n\nimport {DOCUMENTS_API_VERSION} from './constants.js'\nimport {\n Level,\n type ValidateDocumentsWorkerData,\n type ValidationReceiver,\n type ValidationWorkerChannel,\n} from './types.js'\n\ninterface ValidateDocumentsOptions<TReturn = unknown> {\n dataset?: string // override\n level?: Level\n maxCustomValidationConcurrency?: number\n maxFetchConcurrency?: number\n ndjsonFilePath?: string\n projectId?: string // override\n reporter?: (worker: ValidationReceiver) => TReturn\n studioHost?: string\n workDir?: string\n workspace?: string\n}\n\ninterface DocumentValidationResult {\n documentId: string\n documentType: string\n level: ValidationMarker['level']\n markers: ValidationMarker[]\n revision: string\n}\n\nconst defaultReporter = ({dispose, stream}: ValidationReceiver) => {\n async function* createValidationGenerator() {\n for await (const {documentId, documentType, level, markers, revision} of stream.validation()) {\n const result: DocumentValidationResult = {\n documentId,\n documentType,\n level,\n markers,\n revision,\n }\n\n yield result\n }\n\n await dispose()\n }\n\n return createValidationGenerator()\n}\n\nexport function validateDocuments<TReturn>(\n options: Required<Pick<ValidateDocumentsOptions<TReturn>, 'reporter'>> &\n ValidateDocumentsOptions<TReturn>,\n): Promise<TReturn>\nexport function validateDocuments(\n options: ValidateDocumentsOptions,\n): Promise<AsyncIterable<DocumentValidationResult>>\nexport async function validateDocuments(options: ValidateDocumentsOptions): Promise<unknown> {\n const {\n dataset,\n level,\n maxCustomValidationConcurrency,\n maxFetchConcurrency,\n ndjsonFilePath,\n projectId,\n reporter = defaultReporter,\n workDir = process.cwd(),\n workspace,\n } = options\n\n const apiClient = await getGlobalCliClient({\n apiVersion: DOCUMENTS_API_VERSION,\n requireUser: true,\n })\n\n const clientConfig: ClientConfig = {\n ...apiClient.config(),\n // we set this explictly to true because we pass in a token via the\n // `clientConfiguration` object and also mock a browser environment in\n // this worker which triggers the browser warning\n ignoreBrowserTokenWarning: true,\n // Removing from object so config can be serialized\n // before sent to validation worker\n requester: undefined,\n // we set this explictly to true because the default client configuration\n // from the CLI comes configured with `useProjectHostname: false` when\n // `requireProject` is set to false\n useProjectHostname: true,\n }\n\n const worker = createStudioWorker(new URL('validateDocuments.worker.js', import.meta.url), {\n name: 'validateDocuments',\n studioRootPath: workDir,\n workerData: {\n // removes props in the config that make this object fail to serialize\n clientConfig: structuredClone(clientConfig),\n dataset,\n level,\n maxCustomValidationConcurrency,\n maxFetchConcurrency,\n ndjsonFilePath,\n projectId,\n studioHost: options.studioHost,\n workDir,\n workspace,\n } satisfies ValidateDocumentsWorkerData,\n })\n\n const receiver = WorkerChannelReceiver.from<ValidationWorkerChannel>(worker)\n const validationReceiver: ValidationReceiver = {\n dispose: async () => {\n receiver.unsubscribe()\n return worker.terminate()\n },\n event: receiver.event,\n stream: receiver.stream,\n }\n\n return reporter(validationReceiver)\n}\n"],"names":["getGlobalCliClient","createStudioWorker","WorkerChannelReceiver","DOCUMENTS_API_VERSION","defaultReporter","dispose","stream","createValidationGenerator","documentId","documentType","level","markers","revision","validation","result","validateDocuments","options","dataset","maxCustomValidationConcurrency","maxFetchConcurrency","ndjsonFilePath","projectId","reporter","workDir","process","cwd","workspace","apiClient","apiVersion","requireUser","clientConfig","config","ignoreBrowserTokenWarning","requester","undefined","useProjectHostname","worker","URL","url","name","studioRootPath","workerData","structuredClone","studioHost","receiver","from","validationReceiver","unsubscribe","terminate","event"],"mappings":"AAAA,SAAQA,kBAAkB,QAAO,6BAA4B;AAC7D,SAAQC,kBAAkB,QAAO,yBAAwB;AAGzD,SAAQC,qBAAqB,QAAO,0BAAyB;AAE7D,SAAQC,qBAAqB,QAAO,iBAAgB;AA6BpD,MAAMC,kBAAkB,CAAC,EAACC,OAAO,EAAEC,MAAM,EAAqB;IAC5D,gBAAgBC;QACd,WAAW,MAAM,EAACC,UAAU,EAAEC,YAAY,EAAEC,KAAK,EAAEC,OAAO,EAAEC,QAAQ,EAAC,IAAIN,OAAOO,UAAU,GAAI;YAC5F,MAAMC,SAAmC;gBACvCN;gBACAC;gBACAC;gBACAC;gBACAC;YACF;YAEA,MAAME;QACR;QAEA,MAAMT;IACR;IAEA,OAAOE;AACT;AASA,OAAO,eAAeQ,kBAAkBC,OAAiC;IACvE,MAAM,EACJC,OAAO,EACPP,KAAK,EACLQ,8BAA8B,EAC9BC,mBAAmB,EACnBC,cAAc,EACdC,SAAS,EACTC,WAAWlB,eAAe,EAC1BmB,UAAUC,QAAQC,GAAG,EAAE,EACvBC,SAAS,EACV,GAAGV;IAEJ,MAAMW,YAAY,MAAM3B,mBAAmB;QACzC4B,YAAYzB;QACZ0B,aAAa;IACf;IAEA,MAAMC,eAA6B;QACjC,GAAGH,UAAUI,MAAM,EAAE;QACrB,mEAAmE;QACnE,sEAAsE;QACtE,iDAAiD;QACjDC,2BAA2B;QAC3B,mDAAmD;QACnD,mCAAmC;QACnCC,WAAWC;QACX,yEAAyE;QACzE,sEAAsE;QACtE,mCAAmC;QACnCC,oBAAoB;IACtB;IAEA,MAAMC,SAASnC,mBAAmB,IAAIoC,IAAI,+BAA+B,YAAYC,GAAG,GAAG;QACzFC,MAAM;QACNC,gBAAgBjB;QAChBkB,YAAY;YACV,sEAAsE;YACtEX,cAAcY,gBAAgBZ;YAC9Bb;YACAP;YACAQ;YACAC;YACAC;YACAC;YACAsB,YAAY3B,QAAQ2B,UAAU;YAC9BpB;YACAG;QACF;IACF;IAEA,MAAMkB,WAAW1C,sBAAsB2C,IAAI,CAA0BT;IACrE,MAAMU,qBAAyC;QAC7CzC,SAAS;YACPuC,SAASG,WAAW;YACpB,OAAOX,OAAOY,SAAS;QACzB;QACAC,OAAOL,SAASK,KAAK;QACrB3C,QAAQsC,SAAStC,MAAM;IACzB;IAEA,OAAOgB,SAASwB;AAClB"}
@@ -3,6 +3,7 @@ import { relative, resolve } from 'node:path';
3
3
  import { doImport, getCliConfigUncached } from '@sanity/cli-core';
4
4
  import { getErrorMessage } from '@sanity/cli-core/errors';
5
5
  import { spinner } from '@sanity/cli-core/ux';
6
+ import { isWorkbenchApp } from '@sanity/workbench-cli';
6
7
  import { coreAppManifestSchema } from './types.js';
7
8
  /**
8
9
  * The title change a deploy would sync from the manifest to the user
@@ -77,6 +78,7 @@ const sanitizeIconPath = new URL('sanitizeIcon.js', import.meta.url).href;
77
78
  spin.info('Manifest creation skipped: no icon or title found in app configuration');
78
79
  return undefined;
79
80
  }
81
+ const slug = isWorkbenchApp(app) ? app.slug : undefined;
80
82
  const manifest = coreAppManifestSchema.parse({
81
83
  version: '1',
82
84
  ...icon ? {
@@ -90,7 +92,10 @@ const sanitizeIconPath = new URL('sanitizeIcon.js', import.meta.url).href;
90
92
  } : {},
91
93
  ...app.priority === undefined ? {} : {
92
94
  priority: app.priority
93
- }
95
+ },
96
+ ...slug ? {
97
+ slug
98
+ } : {}
94
99
  });
95
100
  spin.succeed(`Extracted manifest`);
96
101
  return manifest;
@@ -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 {getErrorMessage} from '@sanity/cli-core/errors'\nimport {spinner} from '@sanity/cli-core/ux'\n\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","getErrorMessage","spinner","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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"}
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 {getErrorMessage} from '@sanity/cli-core/errors'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {isWorkbenchApp} from '@sanity/workbench-cli'\n\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 slug = isWorkbenchApp(app) ? app.slug : undefined\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 ...(slug ? {slug} : {}),\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","getErrorMessage","spinner","isWorkbenchApp","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","slug","parse","version","group","priority","succeed","fail"],"mappings":"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"}