@sanity/cli 7.8.0 → 7.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/deploy/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"}
@@ -1,7 +1,7 @@
1
+ import { getCoreAppUrl } from '@sanity/cli-core/util';
1
2
  import { deleteUserApplication, getUserApplication } from '../../services/userApplications.js';
2
3
  import { getAppId } from '../../util/appId.js';
3
4
  import { NO_PROJECT_ID } from '../../util/errorMessages.js';
4
- import { getCoreAppUrl } from '../deploy/urlUtils.js';
5
5
  export function createAppUndeployAdapter(cliConfig) {
6
6
  return {
7
7
  async resolveTarget () {
@@ -77,11 +77,11 @@ function toUndeployTarget(application, type) {
77
77
  return {
78
78
  activeDeployment: application.activeDeployment ? {
79
79
  deployedAt: application.activeDeployment.deployedAt,
80
- deployedBy: application.activeDeployment.deployedBy,
81
- version: application.activeDeployment.version
80
+ deployedBy: application.activeDeployment.deployedBy
82
81
  } : null,
83
82
  appHost: application.appHost ?? null,
84
83
  createdAt: application.createdAt ?? null,
84
+ deletes: 'application',
85
85
  id: application.id,
86
86
  organizationId: application.organizationId ?? null,
87
87
  projectId: application.projectId ?? null,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/undeploy/adapters.ts"],"sourcesContent":["import {type CliConfig} from '@sanity/cli-core'\n\nimport {\n deleteUserApplication,\n getUserApplication,\n type UserApplication,\n} from '../../services/userApplications.js'\nimport {getAppId} from '../../util/appId.js'\nimport {NO_PROJECT_ID} from '../../util/errorMessages.js'\nimport {getCoreAppUrl} from '../deploy/urlUtils.js'\nimport {type UndeployAdapter} from './runUndeploy.js'\nimport {type UndeployTarget} from './undeployPlan.js'\n\nexport function createAppUndeployAdapter(cliConfig: CliConfig): UndeployAdapter {\n return {\n async resolveTarget() {\n const appId = getAppId(cliConfig)\n if (!appId) {\n return {\n message: 'No `deployment.appId` configured',\n solution: 'Add `deployment.appId` to sanity.cli.ts',\n type: 'none',\n }\n }\n\n const application = await getUserApplication({appId, isSdkApp: true})\n if (!application) {\n return {message: 'Application with the given ID does not exist', type: 'none'}\n }\n\n return {target: toUndeployTarget(application, 'coreApp'), type: 'found'}\n },\n type: 'coreApp',\n undeploy: ({id}) => deleteUserApplication({applicationId: id, appType: 'coreApp'}),\n }\n}\n\nexport function createStudioUndeployAdapter(cliConfig: CliConfig): UndeployAdapter {\n return {\n async resolveTarget() {\n const appId = cliConfig.deployment?.appId\n const studioHost = cliConfig.studioHost\n if (!appId && !studioHost) {\n return {\n message: 'No studio hostname configured',\n solution: 'Set `studioHost` in sanity.cli.ts',\n type: 'none',\n }\n }\n\n const projectId = cliConfig.api?.projectId\n if (!projectId) throw new Error(NO_PROJECT_ID)\n\n const application = await getUserApplication({\n appHost: studioHost,\n appId,\n isSdkApp: false,\n projectId,\n })\n if (!application) {\n return {\n message:\n 'Your project has not been assigned an app ID or a studio hostname, or the `appId` or `studioHost` provided does not exist',\n type: 'none',\n }\n }\n\n return {target: toUndeployTarget(application, 'studio'), type: 'found'}\n },\n type: 'studio',\n undeploy: ({id}) => deleteUserApplication({applicationId: id, appType: 'studio'}),\n }\n}\n\nfunction toUndeployTarget(\n application: UserApplication,\n type: UndeployTarget['type'],\n): UndeployTarget {\n return {\n activeDeployment: application.activeDeployment\n ? {\n deployedAt: application.activeDeployment.deployedAt,\n deployedBy: application.activeDeployment.deployedBy,\n version: application.activeDeployment.version,\n }\n : null,\n appHost: application.appHost ?? null,\n createdAt: application.createdAt ?? null,\n id: application.id,\n organizationId: application.organizationId ?? null,\n projectId: application.projectId ?? null,\n title: application.title ?? null,\n type,\n url: resolveTargetUrl(application, type),\n }\n}\n\nfunction resolveTargetUrl(\n application: UserApplication,\n type: UndeployTarget['type'],\n): string | null {\n if (type === 'coreApp') {\n return application.organizationId\n ? getCoreAppUrl(application.organizationId, application.id)\n : null\n }\n if (!application.appHost) return null\n return application.urlType === 'external'\n ? application.appHost\n : `https://${application.appHost}.sanity.studio`\n}\n"],"names":["deleteUserApplication","getUserApplication","getAppId","NO_PROJECT_ID","getCoreAppUrl","createAppUndeployAdapter","cliConfig","resolveTarget","appId","message","solution","type","application","isSdkApp","target","toUndeployTarget","undeploy","id","applicationId","appType","createStudioUndeployAdapter","deployment","studioHost","projectId","api","Error","appHost","activeDeployment","deployedAt","deployedBy","version","createdAt","organizationId","title","url","resolveTargetUrl","urlType"],"mappings":"AAEA,SACEA,qBAAqB,EACrBC,kBAAkB,QAEb,qCAAoC;AAC3C,SAAQC,QAAQ,QAAO,sBAAqB;AAC5C,SAAQC,aAAa,QAAO,8BAA6B;AACzD,SAAQC,aAAa,QAAO,wBAAuB;AAInD,OAAO,SAASC,yBAAyBC,SAAoB;IAC3D,OAAO;QACL,MAAMC;YACJ,MAAMC,QAAQN,SAASI;YACvB,IAAI,CAACE,OAAO;gBACV,OAAO;oBACLC,SAAS;oBACTC,UAAU;oBACVC,MAAM;gBACR;YACF;YAEA,MAAMC,cAAc,MAAMX,mBAAmB;gBAACO;gBAAOK,UAAU;YAAI;YACnE,IAAI,CAACD,aAAa;gBAChB,OAAO;oBAACH,SAAS;oBAAgDE,MAAM;gBAAM;YAC/E;YAEA,OAAO;gBAACG,QAAQC,iBAAiBH,aAAa;gBAAYD,MAAM;YAAO;QACzE;QACAA,MAAM;QACNK,UAAU,CAAC,EAACC,EAAE,EAAC,GAAKjB,sBAAsB;gBAACkB,eAAeD;gBAAIE,SAAS;YAAS;IAClF;AACF;AAEA,OAAO,SAASC,4BAA4Bd,SAAoB;IAC9D,OAAO;QACL,MAAMC;YACJ,MAAMC,QAAQF,UAAUe,UAAU,EAAEb;YACpC,MAAMc,aAAahB,UAAUgB,UAAU;YACvC,IAAI,CAACd,SAAS,CAACc,YAAY;gBACzB,OAAO;oBACLb,SAAS;oBACTC,UAAU;oBACVC,MAAM;gBACR;YACF;YAEA,MAAMY,YAAYjB,UAAUkB,GAAG,EAAED;YACjC,IAAI,CAACA,WAAW,MAAM,IAAIE,MAAMtB;YAEhC,MAAMS,cAAc,MAAMX,mBAAmB;gBAC3CyB,SAASJ;gBACTd;gBACAK,UAAU;gBACVU;YACF;YACA,IAAI,CAACX,aAAa;gBAChB,OAAO;oBACLH,SACE;oBACFE,MAAM;gBACR;YACF;YAEA,OAAO;gBAACG,QAAQC,iBAAiBH,aAAa;gBAAWD,MAAM;YAAO;QACxE;QACAA,MAAM;QACNK,UAAU,CAAC,EAACC,EAAE,EAAC,GAAKjB,sBAAsB;gBAACkB,eAAeD;gBAAIE,SAAS;YAAQ;IACjF;AACF;AAEA,SAASJ,iBACPH,WAA4B,EAC5BD,IAA4B;IAE5B,OAAO;QACLgB,kBAAkBf,YAAYe,gBAAgB,GAC1C;YACEC,YAAYhB,YAAYe,gBAAgB,CAACC,UAAU;YACnDC,YAAYjB,YAAYe,gBAAgB,CAACE,UAAU;YACnDC,SAASlB,YAAYe,gBAAgB,CAACG,OAAO;QAC/C,IACA;QACJJ,SAASd,YAAYc,OAAO,IAAI;QAChCK,WAAWnB,YAAYmB,SAAS,IAAI;QACpCd,IAAIL,YAAYK,EAAE;QAClBe,gBAAgBpB,YAAYoB,cAAc,IAAI;QAC9CT,WAAWX,YAAYW,SAAS,IAAI;QACpCU,OAAOrB,YAAYqB,KAAK,IAAI;QAC5BtB;QACAuB,KAAKC,iBAAiBvB,aAAaD;IACrC;AACF;AAEA,SAASwB,iBACPvB,WAA4B,EAC5BD,IAA4B;IAE5B,IAAIA,SAAS,WAAW;QACtB,OAAOC,YAAYoB,cAAc,GAC7B5B,cAAcQ,YAAYoB,cAAc,EAAEpB,YAAYK,EAAE,IACxD;IACN;IACA,IAAI,CAACL,YAAYc,OAAO,EAAE,OAAO;IACjC,OAAOd,YAAYwB,OAAO,KAAK,aAC3BxB,YAAYc,OAAO,GACnB,CAAC,QAAQ,EAAEd,YAAYc,OAAO,CAAC,cAAc,CAAC;AACpD"}
1
+ {"version":3,"sources":["../../../src/actions/undeploy/adapters.ts"],"sourcesContent":["import {type CliConfig} from '@sanity/cli-core'\nimport {\n type UndeployAdapter,\n type UndeployApplicationTarget,\n type UndeployTarget,\n} from '@sanity/cli-core/undeploy'\nimport {getCoreAppUrl} from '@sanity/cli-core/util'\n\nimport {\n deleteUserApplication,\n getUserApplication,\n type UserApplication,\n} from '../../services/userApplications.js'\nimport {getAppId} from '../../util/appId.js'\nimport {NO_PROJECT_ID} from '../../util/errorMessages.js'\n\nexport function createAppUndeployAdapter(\n cliConfig: CliConfig,\n): UndeployAdapter<UndeployApplicationTarget> {\n return {\n async resolveTarget() {\n const appId = getAppId(cliConfig)\n if (!appId) {\n return {\n message: 'No `deployment.appId` configured',\n solution: 'Add `deployment.appId` to sanity.cli.ts',\n type: 'none',\n }\n }\n\n const application = await getUserApplication({appId, isSdkApp: true})\n if (!application) {\n return {message: 'Application with the given ID does not exist', type: 'none'}\n }\n\n return {target: toUndeployTarget(application, 'coreApp'), type: 'found'}\n },\n type: 'coreApp',\n undeploy: ({id}) => deleteUserApplication({applicationId: id, appType: 'coreApp'}),\n }\n}\n\nexport function createStudioUndeployAdapter(\n cliConfig: CliConfig,\n): UndeployAdapter<UndeployApplicationTarget> {\n return {\n async resolveTarget() {\n const appId = cliConfig.deployment?.appId\n const studioHost = cliConfig.studioHost\n if (!appId && !studioHost) {\n return {\n message: 'No studio hostname configured',\n solution: 'Set `studioHost` in sanity.cli.ts',\n type: 'none',\n }\n }\n\n const projectId = cliConfig.api?.projectId\n if (!projectId) throw new Error(NO_PROJECT_ID)\n\n const application = await getUserApplication({\n appHost: studioHost,\n appId,\n isSdkApp: false,\n projectId,\n })\n if (!application) {\n return {\n message:\n 'Your project has not been assigned an app ID or a studio hostname, or the `appId` or `studioHost` provided does not exist',\n type: 'none',\n }\n }\n\n return {target: toUndeployTarget(application, 'studio'), type: 'found'}\n },\n type: 'studio',\n undeploy: ({id}) => deleteUserApplication({applicationId: id, appType: 'studio'}),\n }\n}\n\nfunction toUndeployTarget(\n application: UserApplication,\n type: UndeployTarget['type'],\n): UndeployApplicationTarget {\n return {\n activeDeployment: application.activeDeployment\n ? {\n deployedAt: application.activeDeployment.deployedAt,\n deployedBy: application.activeDeployment.deployedBy,\n }\n : null,\n appHost: application.appHost ?? null,\n createdAt: application.createdAt ?? null,\n deletes: 'application',\n id: application.id,\n organizationId: application.organizationId ?? null,\n projectId: application.projectId ?? null,\n title: application.title ?? null,\n type,\n url: resolveTargetUrl(application, type),\n }\n}\n\nfunction resolveTargetUrl(\n application: UserApplication,\n type: UndeployTarget['type'],\n): string | null {\n if (type === 'coreApp') {\n return application.organizationId\n ? getCoreAppUrl(application.organizationId, application.id)\n : null\n }\n if (!application.appHost) return null\n return application.urlType === 'external'\n ? application.appHost\n : `https://${application.appHost}.sanity.studio`\n}\n"],"names":["getCoreAppUrl","deleteUserApplication","getUserApplication","getAppId","NO_PROJECT_ID","createAppUndeployAdapter","cliConfig","resolveTarget","appId","message","solution","type","application","isSdkApp","target","toUndeployTarget","undeploy","id","applicationId","appType","createStudioUndeployAdapter","deployment","studioHost","projectId","api","Error","appHost","activeDeployment","deployedAt","deployedBy","createdAt","deletes","organizationId","title","url","resolveTargetUrl","urlType"],"mappings":"AAMA,SAAQA,aAAa,QAAO,wBAAuB;AAEnD,SACEC,qBAAqB,EACrBC,kBAAkB,QAEb,qCAAoC;AAC3C,SAAQC,QAAQ,QAAO,sBAAqB;AAC5C,SAAQC,aAAa,QAAO,8BAA6B;AAEzD,OAAO,SAASC,yBACdC,SAAoB;IAEpB,OAAO;QACL,MAAMC;YACJ,MAAMC,QAAQL,SAASG;YACvB,IAAI,CAACE,OAAO;gBACV,OAAO;oBACLC,SAAS;oBACTC,UAAU;oBACVC,MAAM;gBACR;YACF;YAEA,MAAMC,cAAc,MAAMV,mBAAmB;gBAACM;gBAAOK,UAAU;YAAI;YACnE,IAAI,CAACD,aAAa;gBAChB,OAAO;oBAACH,SAAS;oBAAgDE,MAAM;gBAAM;YAC/E;YAEA,OAAO;gBAACG,QAAQC,iBAAiBH,aAAa;gBAAYD,MAAM;YAAO;QACzE;QACAA,MAAM;QACNK,UAAU,CAAC,EAACC,EAAE,EAAC,GAAKhB,sBAAsB;gBAACiB,eAAeD;gBAAIE,SAAS;YAAS;IAClF;AACF;AAEA,OAAO,SAASC,4BACdd,SAAoB;IAEpB,OAAO;QACL,MAAMC;YACJ,MAAMC,QAAQF,UAAUe,UAAU,EAAEb;YACpC,MAAMc,aAAahB,UAAUgB,UAAU;YACvC,IAAI,CAACd,SAAS,CAACc,YAAY;gBACzB,OAAO;oBACLb,SAAS;oBACTC,UAAU;oBACVC,MAAM;gBACR;YACF;YAEA,MAAMY,YAAYjB,UAAUkB,GAAG,EAAED;YACjC,IAAI,CAACA,WAAW,MAAM,IAAIE,MAAMrB;YAEhC,MAAMQ,cAAc,MAAMV,mBAAmB;gBAC3CwB,SAASJ;gBACTd;gBACAK,UAAU;gBACVU;YACF;YACA,IAAI,CAACX,aAAa;gBAChB,OAAO;oBACLH,SACE;oBACFE,MAAM;gBACR;YACF;YAEA,OAAO;gBAACG,QAAQC,iBAAiBH,aAAa;gBAAWD,MAAM;YAAO;QACxE;QACAA,MAAM;QACNK,UAAU,CAAC,EAACC,EAAE,EAAC,GAAKhB,sBAAsB;gBAACiB,eAAeD;gBAAIE,SAAS;YAAQ;IACjF;AACF;AAEA,SAASJ,iBACPH,WAA4B,EAC5BD,IAA4B;IAE5B,OAAO;QACLgB,kBAAkBf,YAAYe,gBAAgB,GAC1C;YACEC,YAAYhB,YAAYe,gBAAgB,CAACC,UAAU;YACnDC,YAAYjB,YAAYe,gBAAgB,CAACE,UAAU;QACrD,IACA;QACJH,SAASd,YAAYc,OAAO,IAAI;QAChCI,WAAWlB,YAAYkB,SAAS,IAAI;QACpCC,SAAS;QACTd,IAAIL,YAAYK,EAAE;QAClBe,gBAAgBpB,YAAYoB,cAAc,IAAI;QAC9CT,WAAWX,YAAYW,SAAS,IAAI;QACpCU,OAAOrB,YAAYqB,KAAK,IAAI;QAC5BtB;QACAuB,KAAKC,iBAAiBvB,aAAaD;IACrC;AACF;AAEA,SAASwB,iBACPvB,WAA4B,EAC5BD,IAA4B;IAE5B,IAAIA,SAAS,WAAW;QACtB,OAAOC,YAAYoB,cAAc,GAC7BhC,cAAcY,YAAYoB,cAAc,EAAEpB,YAAYK,EAAE,IACxD;IACN;IACA,IAAI,CAACL,YAAYc,OAAO,EAAE,OAAO;IACjC,OAAOd,YAAYwB,OAAO,KAAK,aAC3BxB,YAAYc,OAAO,GACnB,CAAC,QAAQ,EAAEd,YAAYc,OAAO,CAAC,cAAc,CAAC;AACpD"}
@@ -5,7 +5,7 @@ import { getErrorMessage } from '@sanity/cli-core/errors';
5
5
  import { confirm, spinner } from '@sanity/cli-core/ux';
6
6
  import { createCollectingReporter, createFailFastReporter } from '../../util/checks.js';
7
7
  import { toStderrOutput } from '../../util/toStderrOutput.js';
8
- import { describeUndeployTarget, renderUndeployPlan, undeployPlanToJson } from './undeployPlan.js';
8
+ import { describeUndeployTarget, renderUndeployPlan, undeployLabel, undeployPlanToJson } from './undeployPlan.js';
9
9
  const undeployDebug = subdebug('undeploy');
10
10
  /**
11
11
  * Runs an undeploy in the mode the flags select: a real undeploy fails fast,
@@ -15,7 +15,8 @@ const undeployDebug = subdebug('undeploy');
15
15
  */ export async function runUndeploy(options, adapter) {
16
16
  const { flags, output } = options;
17
17
  const json = !!flags.json;
18
- const emitJson = (payload)=>output.log(JSON.stringify(payload, null, 2));
18
+ // The report-only `summary` lines never enter the payload
19
+ const emitJson = (payload)=>output.log(JSON.stringify(payload, (key, value)=>key === 'summary' ? undefined : value, 2));
19
20
  // The JSON payload owns stdout, so the run's progress logs go to stderr; only
20
21
  // the final JSON.stringify writes to stdout.
21
22
  const runOptions = json ? {
@@ -82,15 +83,20 @@ const undeployDebug = subdebug('undeploy');
82
83
  });
83
84
  if (!shouldUndeploy) return undefined;
84
85
  }
85
- const spin = spinner(`Undeploying ${adapter.type === 'coreApp' ? 'application' : 'studio'}`).start();
86
+ const label = undeployLabel(target, adapter.type);
87
+ const spin = spinner(`Undeploying ${label}`).start();
86
88
  try {
87
89
  await adapter.undeploy(target);
88
90
  } catch (error) {
89
91
  spin.fail();
90
- throw error;
92
+ undeployDebug(`Error undeploying ${label}`, error);
93
+ // Labeled here, where the target is known — `normalizeFailure` only sees the adapter type.
94
+ throw new CLIError(`Error undeploying ${label}: ${getErrorMessage(error)}`, {
95
+ exit: 1
96
+ });
91
97
  }
92
98
  spin.succeed();
93
- printUndeployScheduled(target, output);
99
+ printUndeployed(target, output);
94
100
  return {
95
101
  application: target,
96
102
  undeployed: true
@@ -118,6 +124,10 @@ const undeployDebug = subdebug('undeploy');
118
124
  return resolution;
119
125
  }
120
126
  function confirmUndeployMessage(target) {
127
+ if (target.deletes === 'config') {
128
+ return `This will delete the deployed config for ${styleText('yellow', target.title ?? 'this app')}.
129
+ Are you ${styleText('red', 'sure')} you want to undeploy?`;
130
+ }
121
131
  if (target.type === 'coreApp') {
122
132
  return `This will undeploy the following application:
123
133
 
@@ -131,7 +141,11 @@ Are you ${styleText('red', 'sure')} you want to undeploy?`;
131
141
  return `This will undeploy ${styleText('yellow', target.url ?? target.id)} and make it unavailable for your users.
132
142
  Are you ${styleText('red', 'sure')} you want to undeploy?`;
133
143
  }
134
- function printUndeployScheduled(target, output) {
144
+ function printUndeployed(target, output) {
145
+ if (target.deletes === 'config') {
146
+ output.log(`\n${styleText('bold', 'Config deleted.')}`);
147
+ return;
148
+ }
135
149
  if (target.type === 'coreApp') {
136
150
  output.log(`\n${styleText('bold', '⏱️ Application undeploy scheduled.')} It might be a few minutes until ${target.title ? styleText('italic', `'${target.title}'`) : 'your application'} is unavailable.`);
137
151
  output.log(`\n${styleText('bold', 'Remember to remove `deployment.appId` from your application configuration')} to avoid errors when redeploying.`);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/undeploy/runUndeploy.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {CLIError} from '@oclif/core/errors'\nimport {type Output, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {confirm, spinner} from '@sanity/cli-core/ux'\n\nimport {type UndeployCommand} from '../../commands/undeploy.js'\nimport {\n type CheckReporter,\n createCollectingReporter,\n createFailFastReporter,\n} from '../../util/checks.js'\nimport {toStderrOutput} from '../../util/toStderrOutput.js'\nimport {\n describeUndeployTarget,\n renderUndeployPlan,\n type UndeployPlan,\n undeployPlanToJson,\n type UndeployTarget,\n type UndeployTargetResolution,\n} from './undeployPlan.js'\n\nconst undeployDebug = subdebug('undeploy')\n\ntype UndeployFlags = UndeployCommand['flags']\n\nexport interface UndeployOptions {\n flags: UndeployFlags\n output: Output\n}\n\n/**\n * The parts of an undeploy that differ per application backend. The shared\n * sequence — mode selection, target reporting, confirmation, error handling —\n * lives in `runUndeploy`; adapters only resolve and delete.\n */\nexport interface UndeployAdapter {\n /** Resolves what an undeploy would delete; read-only. */\n resolveTarget(): Promise<UndeployTargetResolution>\n type: 'coreApp' | 'studio'\n /** Deletes the target — the only mutating step, never run on a dry run. */\n undeploy(target: UndeployTarget): Promise<void>\n}\n\n/** What a real undeploy produced — the payload `--json` reports. */\ntype UndeployResult =\n | {application: UndeployTarget; undeployed: true}\n | {reason: string; undeployed: false}\n\n/**\n * Runs an undeploy in the mode the flags select: a real undeploy fails fast,\n * confirms, and deletes; `--dry-run` drives the same target resolution\n * read-only and renders a plan instead. `--json` emits the same information as\n * machine-readable JSON.\n */\nexport async function runUndeploy(\n options: UndeployOptions,\n adapter: UndeployAdapter,\n): Promise<void> {\n const {flags, output} = options\n const json = !!flags.json\n const emitJson = (payload: unknown) => output.log(JSON.stringify(payload, null, 2))\n\n // The JSON payload owns stdout, so the run's progress logs go to stderr; only\n // the final JSON.stringify writes to stdout.\n const runOptions = json ? {...options, output: toStderrOutput(output)} : options\n\n try {\n if (flags['dry-run']) {\n const reporter = createCollectingReporter()\n const resolution = await resolveTarget(adapter, reporter)\n const plan: UndeployPlan = {\n checks: reporter.results,\n reason: resolution?.type === 'none' ? resolution.message : null,\n target: resolution?.type === 'found' ? resolution.target : null,\n type: adapter.type,\n }\n if (json) emitJson(undeployPlanToJson(plan))\n else renderUndeployPlan(plan, output)\n // A blocked plan exits like a real (fail-fast) undeploy would.\n const failed = plan.checks.find((check) => check.status === 'fail')\n if (failed) output.error('Undeploy blocked by failing checks.', {exit: failed.exitCode ?? 1})\n return\n }\n\n const result = await undeployApp(runOptions, adapter)\n if (json && result) emitJson(result)\n } catch (error) {\n const failure = normalizeFailure(error, adapter.type)\n // A blocked dry run reaches this catch too (its exit throws) and already\n // printed its plan, so only a real undeploy adds the {undeployed: false} envelope.\n if (json && !flags['dry-run']) {\n emitJson({error: {message: failure.message}, undeployed: false})\n }\n output.error(failure.message, {exit: failure.exit})\n }\n}\n\n/** The real run: resolve the target, confirm, delete, report. */\nasync function undeployApp(\n options: UndeployOptions,\n adapter: UndeployAdapter,\n): Promise<UndeployResult | undefined> {\n const {flags, output} = options\n\n const resolution = await resolveTarget(adapter, createFailFastReporter(output))\n // A resolve failure never lands here: the fail-fast reporter already exited.\n if (!resolution) return undefined\n if (resolution.type === 'none') {\n output.log(`${resolution.message}.`)\n if (resolution.solution) output.log(`${resolution.solution}.`)\n output.log('Nothing to undeploy.')\n return {reason: resolution.message, undeployed: false}\n }\n\n const {target} = resolution\n if (!flags.yes) {\n const shouldUndeploy = await confirm({\n default: false,\n message: confirmUndeployMessage(target),\n })\n if (!shouldUndeploy) return undefined\n }\n\n const spin = spinner(\n `Undeploying ${adapter.type === 'coreApp' ? 'application' : 'studio'}`,\n ).start()\n try {\n await adapter.undeploy(target)\n } catch (error) {\n spin.fail()\n throw error\n }\n spin.succeed()\n\n printUndeployScheduled(target, output)\n return {application: target, undeployed: true}\n}\n\n/**\n * Resolves the undeploy target and reports the verdict as a check, shared by\n * both modes so a real run and a dry run can't diverge.\n */\nasync function resolveTarget(\n adapter: UndeployAdapter,\n reporter: CheckReporter,\n): Promise<UndeployTargetResolution | null> {\n const spin = spinner('Checking application info').start()\n\n let resolution: UndeployTargetResolution\n try {\n resolution = await adapter.resolveTarget()\n } catch (error) {\n spin.fail()\n undeployDebug('Failed to resolve undeploy target', error)\n reporter.report({\n message: `Failed to resolve undeploy target: ${getErrorMessage(error)}`,\n status: 'fail',\n })\n return null\n }\n\n spin[resolution.type === 'found' ? 'succeed' : 'fail']()\n reporter.report(describeUndeployTarget(resolution))\n return resolution\n}\n\nfunction confirmUndeployMessage(target: UndeployTarget): string {\n if (target.type === 'coreApp') {\n return `This will undeploy the following application:\n\n Title: ${styleText('yellow', target.title || '(untitled application)')}\n ID: ${styleText('yellow', target.id)}\n\nThe application will no longer be available for any of your users if you proceed.\n\nAre you ${styleText('red', 'sure')} you want to undeploy?`\n }\n\n return `This will undeploy ${styleText('yellow', target.url ?? target.id)} and make it unavailable for your users.\nAre you ${styleText('red', 'sure')} you want to undeploy?`\n}\n\nfunction printUndeployScheduled(target: UndeployTarget, output: Output): void {\n if (target.type === 'coreApp') {\n output.log(\n `\\n${styleText('bold', '⏱️ Application undeploy scheduled.')} It might be a few minutes until ${\n target.title ? styleText('italic', `'${target.title}'`) : 'your application'\n } is unavailable.`,\n )\n output.log(\n `\\n${styleText('bold', 'Remember to remove `deployment.appId` from your application configuration')} to avoid errors when redeploying.`,\n )\n return\n }\n\n output.log(\n `\\nStudio undeploy scheduled. It might be a few minutes until ${target.url ?? 'your studio'} is unavailable.`,\n )\n}\n\n/** The one failure diagnosis both the stderr message and the `--json` envelope read. */\nfunction normalizeFailure(\n error: unknown,\n type: UndeployAdapter['type'],\n): {exit: number; message: string} {\n // Ctrl+C on the confirmation prompt isn't a real failure\n if (error instanceof Error && error.name === 'ExitPromptError') {\n return {exit: 1, message: 'Undeploy cancelled by user'}\n }\n // A failed check already carries its own message and exit code\n if (error instanceof CLIError) {\n return {exit: error.oclif?.exit ?? 1, message: error.message}\n }\n const label = type === 'coreApp' ? 'application' : 'studio'\n undeployDebug(`Error undeploying ${label}`, error)\n return {exit: 1, message: `Error undeploying ${label}: ${getErrorMessage(error)}`}\n}\n"],"names":["styleText","CLIError","subdebug","getErrorMessage","confirm","spinner","createCollectingReporter","createFailFastReporter","toStderrOutput","describeUndeployTarget","renderUndeployPlan","undeployPlanToJson","undeployDebug","runUndeploy","options","adapter","flags","output","json","emitJson","payload","log","JSON","stringify","runOptions","reporter","resolution","resolveTarget","plan","checks","results","reason","type","message","target","failed","find","check","status","error","exit","exitCode","result","undeployApp","failure","normalizeFailure","undeployed","undefined","solution","yes","shouldUndeploy","default","confirmUndeployMessage","spin","start","undeploy","fail","succeed","printUndeployScheduled","application","report","title","id","url","Error","name","oclif","label"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/undeploy/runUndeploy.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {CLIError} from '@oclif/core/errors'\nimport {type Output, subdebug} from '@sanity/cli-core'\nimport {getErrorMessage} from '@sanity/cli-core/errors'\nimport {\n type UndeployAdapter,\n type UndeployTarget,\n type UndeployTargetResolution,\n} from '@sanity/cli-core/undeploy'\nimport {confirm, spinner} from '@sanity/cli-core/ux'\n\nimport {type UndeployCommand} from '../../commands/undeploy.js'\nimport {\n type CheckReporter,\n createCollectingReporter,\n createFailFastReporter,\n} from '../../util/checks.js'\nimport {toStderrOutput} from '../../util/toStderrOutput.js'\nimport {\n describeUndeployTarget,\n renderUndeployPlan,\n undeployLabel,\n type UndeployPlan,\n undeployPlanToJson,\n} from './undeployPlan.js'\n\nconst undeployDebug = subdebug('undeploy')\n\ntype UndeployFlags = UndeployCommand['flags']\n\nexport interface UndeployOptions {\n flags: UndeployFlags\n output: Output\n}\n\n/** What a real undeploy produced — the payload `--json` reports. */\ntype UndeployResult =\n | {application: UndeployTarget; undeployed: true}\n | {reason: string; undeployed: false}\n\n/**\n * Runs an undeploy in the mode the flags select: a real undeploy fails fast,\n * confirms, and deletes; `--dry-run` drives the same target resolution\n * read-only and renders a plan instead. `--json` emits the same information as\n * machine-readable JSON.\n */\nexport async function runUndeploy(\n options: UndeployOptions,\n adapter: UndeployAdapter,\n): Promise<void> {\n const {flags, output} = options\n const json = !!flags.json\n // The report-only `summary` lines never enter the payload\n const emitJson = (payload: unknown) =>\n output.log(JSON.stringify(payload, (key, value) => (key === 'summary' ? undefined : value), 2))\n\n // The JSON payload owns stdout, so the run's progress logs go to stderr; only\n // the final JSON.stringify writes to stdout.\n const runOptions = json ? {...options, output: toStderrOutput(output)} : options\n\n try {\n if (flags['dry-run']) {\n const reporter = createCollectingReporter()\n const resolution = await resolveTarget(adapter, reporter)\n const plan: UndeployPlan = {\n checks: reporter.results,\n reason: resolution?.type === 'none' ? resolution.message : null,\n target: resolution?.type === 'found' ? resolution.target : null,\n type: adapter.type,\n }\n if (json) emitJson(undeployPlanToJson(plan))\n else renderUndeployPlan(plan, output)\n // A blocked plan exits like a real (fail-fast) undeploy would.\n const failed = plan.checks.find((check) => check.status === 'fail')\n if (failed) output.error('Undeploy blocked by failing checks.', {exit: failed.exitCode ?? 1})\n return\n }\n\n const result = await undeployApp(runOptions, adapter)\n if (json && result) emitJson(result)\n } catch (error) {\n const failure = normalizeFailure(error, adapter.type)\n // A blocked dry run reaches this catch too (its exit throws) and already\n // printed its plan, so only a real undeploy adds the {undeployed: false} envelope.\n if (json && !flags['dry-run']) {\n emitJson({error: {message: failure.message}, undeployed: false})\n }\n output.error(failure.message, {exit: failure.exit})\n }\n}\n\n/** The real run: resolve the target, confirm, delete, report. */\nasync function undeployApp(\n options: UndeployOptions,\n adapter: UndeployAdapter,\n): Promise<UndeployResult | undefined> {\n const {flags, output} = options\n\n const resolution = await resolveTarget(adapter, createFailFastReporter(output))\n // A resolve failure never lands here: the fail-fast reporter already exited.\n if (!resolution) return undefined\n if (resolution.type === 'none') {\n output.log(`${resolution.message}.`)\n if (resolution.solution) output.log(`${resolution.solution}.`)\n output.log('Nothing to undeploy.')\n return {reason: resolution.message, undeployed: false}\n }\n\n const {target} = resolution\n if (!flags.yes) {\n const shouldUndeploy = await confirm({\n default: false,\n message: confirmUndeployMessage(target),\n })\n if (!shouldUndeploy) return undefined\n }\n\n const label = undeployLabel(target, adapter.type)\n const spin = spinner(`Undeploying ${label}`).start()\n try {\n await adapter.undeploy(target)\n } catch (error) {\n spin.fail()\n undeployDebug(`Error undeploying ${label}`, error)\n // Labeled here, where the target is known — `normalizeFailure` only sees the adapter type.\n throw new CLIError(`Error undeploying ${label}: ${getErrorMessage(error)}`, {exit: 1})\n }\n spin.succeed()\n\n printUndeployed(target, output)\n return {application: target, undeployed: true}\n}\n\n/**\n * Resolves the undeploy target and reports the verdict as a check, shared by\n * both modes so a real run and a dry run can't diverge.\n */\nasync function resolveTarget(\n adapter: UndeployAdapter,\n reporter: CheckReporter,\n): Promise<UndeployTargetResolution | null> {\n const spin = spinner('Checking application info').start()\n\n let resolution: UndeployTargetResolution\n try {\n resolution = await adapter.resolveTarget()\n } catch (error) {\n spin.fail()\n undeployDebug('Failed to resolve undeploy target', error)\n reporter.report({\n message: `Failed to resolve undeploy target: ${getErrorMessage(error)}`,\n status: 'fail',\n })\n return null\n }\n\n spin[resolution.type === 'found' ? 'succeed' : 'fail']()\n reporter.report(describeUndeployTarget(resolution))\n return resolution\n}\n\nfunction confirmUndeployMessage(target: UndeployTarget): string {\n if (target.deletes === 'config') {\n return `This will delete the deployed config for ${styleText(\n 'yellow',\n target.title ?? 'this app',\n )}.\nAre you ${styleText('red', 'sure')} you want to undeploy?`\n }\n\n if (target.type === 'coreApp') {\n return `This will undeploy the following application:\n\n Title: ${styleText('yellow', target.title || '(untitled application)')}\n ID: ${styleText('yellow', target.id)}\n\nThe application will no longer be available for any of your users if you proceed.\n\nAre you ${styleText('red', 'sure')} you want to undeploy?`\n }\n\n return `This will undeploy ${styleText('yellow', target.url ?? target.id)} and make it unavailable for your users.\nAre you ${styleText('red', 'sure')} you want to undeploy?`\n}\n\nfunction printUndeployed(target: UndeployTarget, output: Output): void {\n if (target.deletes === 'config') {\n output.log(`\\n${styleText('bold', 'Config deleted.')}`)\n return\n }\n\n if (target.type === 'coreApp') {\n output.log(\n `\\n${styleText('bold', '⏱️ Application undeploy scheduled.')} It might be a few minutes until ${\n target.title ? styleText('italic', `'${target.title}'`) : 'your application'\n } is unavailable.`,\n )\n output.log(\n `\\n${styleText('bold', 'Remember to remove `deployment.appId` from your application configuration')} to avoid errors when redeploying.`,\n )\n return\n }\n\n output.log(\n `\\nStudio undeploy scheduled. It might be a few minutes until ${target.url ?? 'your studio'} is unavailable.`,\n )\n}\n\n/** The one failure diagnosis both the stderr message and the `--json` envelope read. */\nfunction normalizeFailure(\n error: unknown,\n type: UndeployAdapter['type'],\n): {exit: number; message: string} {\n // Ctrl+C on the confirmation prompt isn't a real failure\n if (error instanceof Error && error.name === 'ExitPromptError') {\n return {exit: 1, message: 'Undeploy cancelled by user'}\n }\n // A failed check already carries its own message and exit code\n if (error instanceof CLIError) {\n return {exit: error.oclif?.exit ?? 1, message: error.message}\n }\n const label = type === 'coreApp' ? 'application' : 'studio'\n undeployDebug(`Error undeploying ${label}`, error)\n return {exit: 1, message: `Error undeploying ${label}: ${getErrorMessage(error)}`}\n}\n"],"names":["styleText","CLIError","subdebug","getErrorMessage","confirm","spinner","createCollectingReporter","createFailFastReporter","toStderrOutput","describeUndeployTarget","renderUndeployPlan","undeployLabel","undeployPlanToJson","undeployDebug","runUndeploy","options","adapter","flags","output","json","emitJson","payload","log","JSON","stringify","key","value","undefined","runOptions","reporter","resolution","resolveTarget","plan","checks","results","reason","type","message","target","failed","find","check","status","error","exit","exitCode","result","undeployApp","failure","normalizeFailure","undeployed","solution","yes","shouldUndeploy","default","confirmUndeployMessage","label","spin","start","undeploy","fail","succeed","printUndeployed","application","report","deletes","title","id","url","Error","name","oclif"],"mappings":"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"}
@@ -3,6 +3,12 @@ import { checkStatusIcon, renderIssues } from '../../util/checks.js';
3
3
  export function canUndeploy(plan) {
4
4
  return plan.target !== null && plan.checks.every((check)=>check.status !== 'fail');
5
5
  }
6
+ /** What an undeploy deletes, phrased for the human output. */ export function undeployLabel(target, type) {
7
+ if (target?.deletes === 'config') {
8
+ return `installation config${target.title ? ` for "${target.title}"` : ''}`;
9
+ }
10
+ return type === 'coreApp' ? 'application' : 'studio';
11
+ }
6
12
  /**
7
13
  * A machine-readable projection of the plan: blocking problems mapped to their
8
14
  * fix, warnings as messages. Derived from the same checks and target the human
@@ -34,6 +40,12 @@ export function canUndeploy(plan) {
34
40
  };
35
41
  }
36
42
  const { target } = resolution;
43
+ if (target.deletes === 'config') {
44
+ return {
45
+ message: target.title ? `Undeploys the installation config for "${target.title}"` : 'Undeploys the installation config',
46
+ status: 'pass'
47
+ };
48
+ }
37
49
  if (target.type === 'studio') {
38
50
  return {
39
51
  message: `Undeploys studio ${target.url ?? target.id}`,
@@ -62,8 +74,8 @@ export function renderUndeployPlan(plan, output) {
62
74
  if (plan.target) renderTarget(plan.target, output);
63
75
  if (!canUndeploy(plan)) {
64
76
  if (problems.length > 0) {
65
- const label = plan.type === 'coreApp' ? 'Application' : 'Studio';
66
- output.log(styleText('red', `\n${checkStatusIcon('fail')} ${label} can not be undeployed.`));
77
+ const label = undeployLabel(plan.target, plan.type);
78
+ output.log(styleText('red', `\n${checkStatusIcon('fail')} ${label[0].toUpperCase()}${label.slice(1)} can not be undeployed.`));
67
79
  } else {
68
80
  output.log('\nNothing to undeploy.');
69
81
  }
@@ -94,11 +106,14 @@ function renderTarget(target, output) {
94
106
  for (const [key, value] of rows){
95
107
  if (value) output.log(` ${key.padEnd(8)} ${styleText('yellow', value)}`);
96
108
  }
109
+ // Adapter-authored lines about what gets deleted (interfaces, config snapshots, …)
110
+ for (const entry of target.summary ?? []){
111
+ for (const line of entry.split('\n'))output.log(` ${line}`);
112
+ }
97
113
  }
98
114
  function formatDeployment(deployment) {
99
115
  if (!deployment) return null;
100
116
  const parts = [
101
- deployment.version ? `version ${deployment.version}` : null,
102
117
  deployment.deployedAt ? `at ${deployment.deployedAt}` : null,
103
118
  deployment.deployedBy ? `by ${deployment.deployedBy}` : null
104
119
  ].filter(Boolean);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/actions/undeploy/undeployPlan.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {type Output} from '@sanity/cli-core'\n\nimport {type Check, checkStatusIcon, renderIssues} from '../../util/checks.js'\n\n/**\n * What an undeploy deletes, resolved once and read by every report — the\n * dry-run plan, the `--json` payloads, and the real run's confirmation prompt —\n * so the human and machine outputs can't drift.\n */\nexport interface UndeployTarget {\n /** Details of the deployment currently being served; `null` when none is live. */\n activeDeployment: {deployedAt: string; deployedBy: string; version: string} | null\n /** Hostname the application is served from. */\n appHost: string | null\n createdAt: string | null\n /** The application an undeploy deletes, along with all its deployments. */\n id: string\n organizationId: string | null\n projectId: string | null\n title: string | null\n type: 'coreApp' | 'studio'\n /** Where the deployed studio/app is currently reachable. */\n url: string | null\n}\n\nexport type UndeployTargetResolution =\n | {message: string; solution?: string; type: 'none'}\n | {target: UndeployTarget; type: 'found'}\n\n/** What a `--dry-run` undeploy would do: the real undeploy sequence with the deletion gated off. */\nexport interface UndeployPlan {\n checks: Check[]\n /** Why there is nothing to undeploy; `null` when a target resolved. */\n reason: string | null\n /** What would be deleted; `null` when there is nothing to undeploy. */\n target: UndeployTarget | null\n type: 'coreApp' | 'studio'\n}\n\nexport function canUndeploy(plan: UndeployPlan): boolean {\n return plan.target !== null && plan.checks.every((check) => check.status !== 'fail')\n}\n\n/**\n * A machine-readable projection of the plan: blocking problems mapped to their\n * fix, warnings as messages. Derived from the same checks and target the human\n * report renders, so the two can't drift.\n */\nexport function undeployPlanToJson(plan: UndeployPlan): {\n application: UndeployTarget | null\n canUndeploy: boolean\n errors: Record<string, string | null>\n reason: string | null\n warnings: string[]\n} {\n const errors: Record<string, string | null> = {}\n const warnings: string[] = []\n for (const check of plan.checks) {\n if (check.status === 'fail') errors[check.message] = check.solution ?? null\n else if (check.status === 'warn') warnings.push(check.message)\n }\n\n return {\n application: plan.target,\n canUndeploy: canUndeploy(plan),\n errors,\n reason: plan.reason,\n warnings,\n }\n}\n\n/**\n * The single diagnosis for each undeploy-target verdict, shared by the dry-run\n * report and the real run so message and fix can't drift between the two.\n */\nexport function describeUndeployTarget(resolution: UndeployTargetResolution): Check {\n if (resolution.type === 'none') {\n return {message: resolution.message, solution: resolution.solution, status: 'skip'}\n }\n\n const {target} = resolution\n if (target.type === 'studio') {\n return {message: `Undeploys studio ${target.url ?? target.id}`, status: 'pass'}\n }\n const name = target.title ? `\"${target.title}\" (${target.id})` : target.id\n return {message: `Undeploys application ${name}`, status: 'pass'}\n}\n\nexport function renderUndeployPlan(plan: UndeployPlan, output: Output): void {\n const problems = plan.checks.filter((check) => check.status === 'fail')\n const warnings = plan.checks.filter((check) => check.status === 'warn')\n\n output.log('\\nDry run — no changes made.\\n')\n\n // Only pass/skip here; problems and warnings render below with their fixes.\n // A passing target check already says what gets undeployed, so an\n // undeployable plan needs no extra verdict line.\n for (const check of plan.checks) {\n if (check.status === 'pass' || check.status === 'skip') {\n const fix = check.solution ? `: ${check.solution}` : ''\n output.log(` ${checkStatusIcon(check.status)} ${check.message}${fix}`)\n }\n }\n\n if (plan.target) renderTarget(plan.target, output)\n\n if (!canUndeploy(plan)) {\n if (problems.length > 0) {\n const label = plan.type === 'coreApp' ? 'Application' : 'Studio'\n output.log(styleText('red', `\\n${checkStatusIcon('fail')} ${label} can not be undeployed.`))\n } else {\n output.log('\\nNothing to undeploy.')\n }\n }\n\n renderIssues(output, 'Problems to fix:', problems)\n renderIssues(output, 'Warnings:', warnings)\n}\n\nfunction renderTarget(target: UndeployTarget, output: Output): void {\n const rows: [string, string | null][] = [\n ['Title', target.title],\n ['ID', target.id],\n ['URL', target.url],\n ['Deployed', formatDeployment(target.activeDeployment)],\n ]\n\n output.log('')\n for (const [key, value] of rows) {\n if (value) output.log(` ${key.padEnd(8)} ${styleText('yellow', value)}`)\n }\n}\n\nfunction formatDeployment(deployment: UndeployTarget['activeDeployment']): string | null {\n if (!deployment) return null\n const parts = [\n deployment.version ? `version ${deployment.version}` : null,\n deployment.deployedAt ? `at ${deployment.deployedAt}` : null,\n deployment.deployedBy ? `by ${deployment.deployedBy}` : null,\n ].filter(Boolean)\n return parts.length > 0 ? parts.join(' ') : null\n}\n"],"names":["styleText","checkStatusIcon","renderIssues","canUndeploy","plan","target","checks","every","check","status","undeployPlanToJson","errors","warnings","message","solution","push","application","reason","describeUndeployTarget","resolution","type","url","id","name","title","renderUndeployPlan","output","problems","filter","log","fix","renderTarget","length","label","rows","formatDeployment","activeDeployment","key","value","padEnd","deployment","parts","version","deployedAt","deployedBy","Boolean","join"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/actions/undeploy/undeployPlan.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {type Output} from '@sanity/cli-core'\nimport {type UndeployTarget, type UndeployTargetResolution} from '@sanity/cli-core/undeploy'\n\nimport {type Check, checkStatusIcon, renderIssues} from '../../util/checks.js'\n\n/** What a `--dry-run` undeploy would do: the real undeploy sequence with the deletion gated off. */\nexport interface UndeployPlan {\n checks: Check[]\n /** Why there is nothing to undeploy; `null` when a target resolved. */\n reason: string | null\n /** What would be deleted; `null` when there is nothing to undeploy. */\n target: UndeployTarget | null\n type: 'coreApp' | 'studio'\n}\n\nexport function canUndeploy(plan: UndeployPlan): boolean {\n return plan.target !== null && plan.checks.every((check) => check.status !== 'fail')\n}\n\n/** What an undeploy deletes, phrased for the human output. */\nexport function undeployLabel(target: UndeployTarget | null, type: 'coreApp' | 'studio'): string {\n if (target?.deletes === 'config') {\n return `installation config${target.title ? ` for \"${target.title}\"` : ''}`\n }\n return type === 'coreApp' ? 'application' : 'studio'\n}\n\n/**\n * A machine-readable projection of the plan: blocking problems mapped to their\n * fix, warnings as messages. Derived from the same checks and target the human\n * report renders, so the two can't drift.\n */\nexport function undeployPlanToJson(plan: UndeployPlan): {\n application: UndeployTarget | null\n canUndeploy: boolean\n errors: Record<string, string | null>\n reason: string | null\n warnings: string[]\n} {\n const errors: Record<string, string | null> = {}\n const warnings: string[] = []\n for (const check of plan.checks) {\n if (check.status === 'fail') errors[check.message] = check.solution ?? null\n else if (check.status === 'warn') warnings.push(check.message)\n }\n\n return {\n application: plan.target,\n canUndeploy: canUndeploy(plan),\n errors,\n reason: plan.reason,\n warnings,\n }\n}\n\n/**\n * The single diagnosis for each undeploy-target verdict, shared by the dry-run\n * report and the real run so message and fix can't drift between the two.\n */\nexport function describeUndeployTarget(resolution: UndeployTargetResolution): Check {\n if (resolution.type === 'none') {\n return {message: resolution.message, solution: resolution.solution, status: 'skip'}\n }\n\n const {target} = resolution\n if (target.deletes === 'config') {\n return {\n message: target.title\n ? `Undeploys the installation config for \"${target.title}\"`\n : 'Undeploys the installation config',\n status: 'pass',\n }\n }\n if (target.type === 'studio') {\n return {message: `Undeploys studio ${target.url ?? target.id}`, status: 'pass'}\n }\n const name = target.title ? `\"${target.title}\" (${target.id})` : target.id\n return {message: `Undeploys application ${name}`, status: 'pass'}\n}\n\nexport function renderUndeployPlan(plan: UndeployPlan, output: Output): void {\n const problems = plan.checks.filter((check) => check.status === 'fail')\n const warnings = plan.checks.filter((check) => check.status === 'warn')\n\n output.log('\\nDry run — no changes made.\\n')\n\n // Only pass/skip here; problems and warnings render below with their fixes.\n // A passing target check already says what gets undeployed, so an\n // undeployable plan needs no extra verdict line.\n for (const check of plan.checks) {\n if (check.status === 'pass' || check.status === 'skip') {\n const fix = check.solution ? `: ${check.solution}` : ''\n output.log(` ${checkStatusIcon(check.status)} ${check.message}${fix}`)\n }\n }\n\n if (plan.target) renderTarget(plan.target, output)\n\n if (!canUndeploy(plan)) {\n if (problems.length > 0) {\n const label = undeployLabel(plan.target, plan.type)\n output.log(\n styleText(\n 'red',\n `\\n${checkStatusIcon('fail')} ${label[0].toUpperCase()}${label.slice(1)} can not be undeployed.`,\n ),\n )\n } else {\n output.log('\\nNothing to undeploy.')\n }\n }\n\n renderIssues(output, 'Problems to fix:', problems)\n renderIssues(output, 'Warnings:', warnings)\n}\n\nfunction renderTarget(target: UndeployTarget, output: Output): void {\n const rows: [string, string | null][] = [\n ['Title', target.title],\n ['ID', target.id],\n ['URL', target.url],\n ['Deployed', formatDeployment(target.activeDeployment)],\n ]\n\n output.log('')\n for (const [key, value] of rows) {\n if (value) output.log(` ${key.padEnd(8)} ${styleText('yellow', value)}`)\n }\n\n // Adapter-authored lines about what gets deleted (interfaces, config snapshots, …)\n for (const entry of target.summary ?? []) {\n for (const line of entry.split('\\n')) output.log(` ${line}`)\n }\n}\n\nfunction formatDeployment(deployment: UndeployTarget['activeDeployment']): string | null {\n if (!deployment) return null\n const parts = [\n deployment.deployedAt ? `at ${deployment.deployedAt}` : null,\n deployment.deployedBy ? `by ${deployment.deployedBy}` : null,\n ].filter(Boolean)\n return parts.length > 0 ? parts.join(' ') : null\n}\n"],"names":["styleText","checkStatusIcon","renderIssues","canUndeploy","plan","target","checks","every","check","status","undeployLabel","type","deletes","title","undeployPlanToJson","errors","warnings","message","solution","push","application","reason","describeUndeployTarget","resolution","url","id","name","renderUndeployPlan","output","problems","filter","log","fix","renderTarget","length","label","toUpperCase","slice","rows","formatDeployment","activeDeployment","key","value","padEnd","entry","summary","line","split","deployment","parts","deployedAt","deployedBy","Boolean","join"],"mappings":"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"}
@@ -1,8 +1,9 @@
1
- import fs from 'node:fs';
1
+ import { stat } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { styleText } from 'node:util';
4
4
  import { Flags } from '@oclif/core';
5
- import { ProjectRootNotFoundError, SanityCommand } from '@sanity/cli-core';
5
+ import { ProjectRootNotFoundError } from '@sanity/cli-core/errors';
6
+ import { SanityCommand } from '@sanity/cli-core/SanityCommand';
6
7
  import { confirm, logSymbols } from '@sanity/cli-core/ux';
7
8
  import { validateDocuments } from '../../actions/documents/validate.js';
8
9
  import { reporters } from '../../actions/documents/validation/reporters/index.js';
@@ -87,28 +88,28 @@ export class ValidateDocumentsCommand extends SanityCommand {
87
88
  cliConfig = await this.getCliConfig();
88
89
  } catch (err) {
89
90
  if (err instanceof ProjectRootNotFoundError) {
90
- this.error('This command must be run from within a Sanity project directory (requires studio schema for validation)', {
91
+ return this.output.error('This command must be run from within a Sanity project directory (requires studio schema for validation)', {
91
92
  exit: 1
92
93
  });
93
94
  }
94
95
  throw err;
95
96
  }
96
97
  if (!unattendedMode) {
97
- this.log(`${styleText('yellow', `${logSymbols.warning} Warning:`)} This command ${file ? 'reads all documents from your input file' : 'downloads all documents from your dataset'} and processes them through your local schema within a ` + `simulated browser environment.\n`);
98
- this.log(`Potential pitfalls:\n`);
99
- this.log(`- Processes all documents locally (excluding assets). Large datasets may require more resources.`);
100
- this.log(`- Executes all custom validation functions. Some functions may need to be refactored for compatibility.`);
101
- this.log(`- Not all standard browser features are available and may cause issues while loading your Studio.`);
102
- this.log(`- Adheres to document permissions. Ensure this account can see all desired documents.`);
98
+ this.output.log(`${styleText('yellow', `${logSymbols.warning} Warning:`)} This command ${file ? 'reads all documents from your input file' : 'downloads all documents from your dataset'} and processes them through your local schema within a ` + `simulated browser environment.\n`);
99
+ this.output.log(`Potential pitfalls:\n`);
100
+ this.output.log(`- Processes all documents locally (excluding assets). Large datasets may require more resources.`);
101
+ this.output.log(`- Executes all custom validation functions. Some functions may need to be refactored for compatibility.`);
102
+ this.output.log(`- Not all standard browser features are available and may cause issues while loading your Studio.`);
103
+ this.output.log(`- Adheres to document permissions. Ensure this account can see all desired documents.`);
103
104
  if (file) {
104
- this.log(`- Checks for missing document references against the live dataset if not found in your file.`);
105
+ this.output.log(`- Checks for missing document references against the live dataset if not found in your file.`);
105
106
  }
106
107
  const confirmed = await confirm({
107
108
  default: true,
108
109
  message: `Are you sure you want to continue?`
109
110
  });
110
111
  if (!confirmed) {
111
- this.error('User aborted', {
112
+ return this.output.error('User aborted', {
112
113
  exit: 1
113
114
  });
114
115
  }
@@ -118,16 +119,16 @@ export class ValidateDocumentsCommand extends SanityCommand {
118
119
  style: 'long',
119
120
  type: 'conjunction'
120
121
  });
121
- this.error(`Did not recognize format '${format}'. Available formats are ${formatter.format(Object.keys(reporters).map((key)=>`'${key}'`))}`, {
122
+ return this.output.error(`Did not recognize format '${format}'. Available formats are ${formatter.format(Object.keys(reporters).map((key)=>`'${key}'`))}`, {
122
123
  exit: 1
123
124
  });
124
125
  }
125
126
  let ndjsonFilePath;
126
127
  if (file) {
127
128
  const filePath = path.resolve(workDir, file);
128
- const stat = await fs.promises.stat(filePath);
129
- if (!stat.isFile()) {
130
- this.error(`'--file' must point to a valid ndjson file or tarball`, {
129
+ const st = await stat(filePath);
130
+ if (!st.isFile()) {
131
+ return this.output.error(`'--file' must point to a valid ndjson file or tarball`, {
131
132
  exit: 1
132
133
  });
133
134
  }
@@ -154,10 +155,10 @@ export class ValidateDocumentsCommand extends SanityCommand {
154
155
  workspace
155
156
  });
156
157
  if (overallLevel === 'error') {
157
- this.exit(1);
158
+ return this.exit(1);
158
159
  }
159
160
  } catch (err) {
160
- this.error(err instanceof Error ? err.message : String(err), {
161
+ return this.output.error(err instanceof Error ? err.message : String(err), {
161
162
  exit: 1
162
163
  });
163
164
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/commands/documents/validate.ts"],"sourcesContent":["import fs from 'node:fs'\nimport path from 'node:path'\nimport {styleText} from 'node:util'\n\nimport {Flags} from '@oclif/core'\nimport {type CliConfig, ProjectRootNotFoundError, SanityCommand} from '@sanity/cli-core'\nimport {confirm, logSymbols} from '@sanity/cli-core/ux'\n\nimport {type Level} from '../../actions/documents/types.js'\nimport {validateDocuments} from '../../actions/documents/validate.js'\nimport {reporters} from '../../actions/documents/validation/reporters/index.js'\nimport {getDatasetFlag, getProjectIdFlag} from '../../util/sharedFlags.js'\n\nexport class ValidateDocumentsCommand extends SanityCommand<typeof ValidateDocumentsCommand> {\n static description = 'Validate documents in a dataset against the studio schema'\n\n static examples = [\n {\n command: '<%= config.bin %> <%= command.id %> --workspace default',\n description: 'Validates all documents in a Sanity project with more than one workspace',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --workspace default --dataset staging',\n description: 'Override the dataset specified in the workspace',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --yes > report.txt',\n description: 'Save the results of the report into a file',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --level info',\n description: 'Report out info level validation markers too',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project-id abc123 --dataset production',\n description: 'Validate documents in a specific project and dataset',\n },\n ]\n\n static flags = {\n ...getProjectIdFlag({\n description:\n 'Override the project ID used. By default, this is derived from the given workspace',\n semantics: 'specify',\n }),\n ...getDatasetFlag({\n description:\n 'Override the dataset used. By default, this is derived from the given workspace',\n semantics: 'specify',\n }),\n file: Flags.string({\n description:\n 'Provide a path to either an .ndjson file or a tarball containing an .ndjson file',\n }),\n format: Flags.string({\n description:\n 'The output format used to print the found validation markers and report progress',\n }),\n level: Flags.custom<Level>({\n default: 'warning',\n description: 'The minimum level reported. Defaults to warning',\n options: ['error', 'warning', 'info'],\n })(),\n 'max-custom-validation-concurrency': Flags.integer({\n default: 5,\n description: 'Specify how many custom validators can run concurrently',\n }),\n 'max-fetch-concurrency': Flags.integer({\n default: 25,\n description: 'Specify how many `client.fetch` requests are allowed to run concurrently',\n }),\n workspace: Flags.string({\n description: 'The name of the workspace to use when downloading and validating all documents',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description: 'Skips the first confirmation prompt',\n }),\n }\n\n static override hiddenAliases: string[] = ['document:validate']\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(ValidateDocumentsCommand)\n const {\n dataset,\n file,\n format,\n level,\n 'max-custom-validation-concurrency': maxCustomValidationConcurrency,\n 'max-fetch-concurrency': maxFetchConcurrency,\n 'project-id': projectId,\n workspace,\n } = flags\n const unattendedMode = Boolean(flags.yes)\n\n let workDir: string\n let cliConfig: CliConfig\n try {\n const root = await this.getProjectRoot()\n workDir = root.directory\n cliConfig = await this.getCliConfig()\n } catch (err) {\n if (err instanceof ProjectRootNotFoundError) {\n this.error(\n 'This command must be run from within a Sanity project directory (requires studio schema for validation)',\n {exit: 1},\n )\n }\n throw err\n }\n\n if (!unattendedMode) {\n this.log(\n `${styleText('yellow', `${logSymbols.warning} Warning:`)} This command ${\n file\n ? 'reads all documents from your input file'\n : 'downloads all documents from your dataset'\n } and processes them through your local schema within a ` +\n `simulated browser environment.\\n`,\n )\n this.log(`Potential pitfalls:\\n`)\n this.log(\n `- Processes all documents locally (excluding assets). Large datasets may require more resources.`,\n )\n this.log(\n `- Executes all custom validation functions. Some functions may need to be refactored for compatibility.`,\n )\n this.log(\n `- Not all standard browser features are available and may cause issues while loading your Studio.`,\n )\n this.log(\n `- Adheres to document permissions. Ensure this account can see all desired documents.`,\n )\n if (file) {\n this.log(\n `- Checks for missing document references against the live dataset if not found in your file.`,\n )\n }\n\n const confirmed = await confirm({\n default: true,\n message: `Are you sure you want to continue?`,\n })\n\n if (!confirmed) {\n this.error('User aborted', {exit: 1})\n }\n }\n\n if (format && !(format in reporters)) {\n const formatter = new Intl.ListFormat('en-US', {\n style: 'long',\n type: 'conjunction',\n })\n this.error(\n `Did not recognize format '${format}'. Available formats are ${formatter.format(\n Object.keys(reporters).map((key) => `'${key}'`),\n )}`,\n {exit: 1},\n )\n }\n\n let ndjsonFilePath\n if (file) {\n const filePath = path.resolve(workDir, file)\n\n const stat = await fs.promises.stat(filePath)\n if (!stat.isFile()) {\n this.error(`'--file' must point to a valid ndjson file or tarball`, {exit: 1})\n }\n\n ndjsonFilePath = filePath\n }\n\n try {\n const overallLevel = await validateDocuments({\n dataset,\n level,\n maxCustomValidationConcurrency,\n maxFetchConcurrency,\n ndjsonFilePath,\n projectId,\n reporter: (worker) => {\n const reporter =\n format && format in reporters\n ? reporters[format as keyof typeof reporters]\n : reporters.pretty\n\n return reporter({flags, output: this.output, worker})\n },\n studioHost: cliConfig.studioHost,\n workDir,\n workspace,\n })\n\n if (overallLevel === 'error') {\n this.exit(1)\n }\n } catch (err) {\n this.error(err instanceof Error ? err.message : String(err), {exit: 1})\n }\n }\n}\n"],"names":["fs","path","styleText","Flags","ProjectRootNotFoundError","SanityCommand","confirm","logSymbols","validateDocuments","reporters","getDatasetFlag","getProjectIdFlag","ValidateDocumentsCommand","description","examples","command","flags","semantics","file","string","format","level","custom","default","options","integer","workspace","yes","boolean","char","hiddenAliases","run","parse","dataset","maxCustomValidationConcurrency","maxFetchConcurrency","projectId","unattendedMode","Boolean","workDir","cliConfig","root","getProjectRoot","directory","getCliConfig","err","error","exit","log","warning","confirmed","message","formatter","Intl","ListFormat","style","type","Object","keys","map","key","ndjsonFilePath","filePath","resolve","stat","promises","isFile","overallLevel","reporter","worker","pretty","output","studioHost","Error","String"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/commands/documents/validate.ts"],"sourcesContent":["import {stat} from 'node:fs/promises'\nimport path from 'node:path'\nimport {styleText} from 'node:util'\n\nimport {Flags} from '@oclif/core'\nimport {ProjectRootNotFoundError} from '@sanity/cli-core/errors'\nimport {SanityCommand} from '@sanity/cli-core/SanityCommand'\nimport {type CliConfig} from '@sanity/cli-core/types'\nimport {confirm, logSymbols} from '@sanity/cli-core/ux'\n\nimport {type Level} from '../../actions/documents/types.js'\nimport {validateDocuments} from '../../actions/documents/validate.js'\nimport {reporters} from '../../actions/documents/validation/reporters/index.js'\nimport {getDatasetFlag, getProjectIdFlag} from '../../util/sharedFlags.js'\n\nexport class ValidateDocumentsCommand extends SanityCommand<typeof ValidateDocumentsCommand> {\n static description = 'Validate documents in a dataset against the studio schema'\n\n static examples = [\n {\n command: '<%= config.bin %> <%= command.id %> --workspace default',\n description: 'Validates all documents in a Sanity project with more than one workspace',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --workspace default --dataset staging',\n description: 'Override the dataset specified in the workspace',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --yes > report.txt',\n description: 'Save the results of the report into a file',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --level info',\n description: 'Report out info level validation markers too',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --project-id abc123 --dataset production',\n description: 'Validate documents in a specific project and dataset',\n },\n ]\n\n static flags = {\n ...getProjectIdFlag({\n description:\n 'Override the project ID used. By default, this is derived from the given workspace',\n semantics: 'specify',\n }),\n ...getDatasetFlag({\n description:\n 'Override the dataset used. By default, this is derived from the given workspace',\n semantics: 'specify',\n }),\n file: Flags.string({\n description:\n 'Provide a path to either an .ndjson file or a tarball containing an .ndjson file',\n }),\n format: Flags.string({\n description:\n 'The output format used to print the found validation markers and report progress',\n }),\n level: Flags.custom<Level>({\n default: 'warning',\n description: 'The minimum level reported. Defaults to warning',\n options: ['error', 'warning', 'info'],\n })(),\n 'max-custom-validation-concurrency': Flags.integer({\n default: 5,\n description: 'Specify how many custom validators can run concurrently',\n }),\n 'max-fetch-concurrency': Flags.integer({\n default: 25,\n description: 'Specify how many `client.fetch` requests are allowed to run concurrently',\n }),\n workspace: Flags.string({\n description: 'The name of the workspace to use when downloading and validating all documents',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description: 'Skips the first confirmation prompt',\n }),\n }\n\n static override hiddenAliases: string[] = ['document:validate']\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(ValidateDocumentsCommand)\n const {\n dataset,\n file,\n format,\n level,\n 'max-custom-validation-concurrency': maxCustomValidationConcurrency,\n 'max-fetch-concurrency': maxFetchConcurrency,\n 'project-id': projectId,\n workspace,\n } = flags\n const unattendedMode = Boolean(flags.yes)\n\n let workDir: string\n let cliConfig: CliConfig\n try {\n const root = await this.getProjectRoot()\n workDir = root.directory\n cliConfig = await this.getCliConfig()\n } catch (err) {\n if (err instanceof ProjectRootNotFoundError) {\n return this.output.error(\n 'This command must be run from within a Sanity project directory (requires studio schema for validation)',\n {exit: 1},\n )\n }\n throw err\n }\n\n if (!unattendedMode) {\n this.output.log(\n `${styleText('yellow', `${logSymbols.warning} Warning:`)} This command ${\n file\n ? 'reads all documents from your input file'\n : 'downloads all documents from your dataset'\n } and processes them through your local schema within a ` +\n `simulated browser environment.\\n`,\n )\n this.output.log(`Potential pitfalls:\\n`)\n this.output.log(\n `- Processes all documents locally (excluding assets). Large datasets may require more resources.`,\n )\n this.output.log(\n `- Executes all custom validation functions. Some functions may need to be refactored for compatibility.`,\n )\n this.output.log(\n `- Not all standard browser features are available and may cause issues while loading your Studio.`,\n )\n this.output.log(\n `- Adheres to document permissions. Ensure this account can see all desired documents.`,\n )\n if (file) {\n this.output.log(\n `- Checks for missing document references against the live dataset if not found in your file.`,\n )\n }\n\n const confirmed = await confirm({\n default: true,\n message: `Are you sure you want to continue?`,\n })\n\n if (!confirmed) {\n return this.output.error('User aborted', {exit: 1})\n }\n }\n\n if (format && !(format in reporters)) {\n const formatter = new Intl.ListFormat('en-US', {\n style: 'long',\n type: 'conjunction',\n })\n return this.output.error(\n `Did not recognize format '${format}'. Available formats are ${formatter.format(\n Object.keys(reporters).map((key) => `'${key}'`),\n )}`,\n {exit: 1},\n )\n }\n\n let ndjsonFilePath\n if (file) {\n const filePath = path.resolve(workDir, file)\n\n const st = await stat(filePath)\n if (!st.isFile()) {\n return this.output.error(`'--file' must point to a valid ndjson file or tarball`, {exit: 1})\n }\n\n ndjsonFilePath = filePath\n }\n\n try {\n const overallLevel = await validateDocuments({\n dataset,\n level,\n maxCustomValidationConcurrency,\n maxFetchConcurrency,\n ndjsonFilePath,\n projectId,\n reporter: (worker) => {\n const reporter =\n format && format in reporters\n ? reporters[format as keyof typeof reporters]\n : reporters.pretty\n\n return reporter({flags, output: this.output, worker})\n },\n studioHost: cliConfig.studioHost,\n workDir,\n workspace,\n })\n\n if (overallLevel === 'error') {\n return this.exit(1)\n }\n } catch (err) {\n return this.output.error(err instanceof Error ? err.message : String(err), {exit: 1})\n }\n }\n}\n"],"names":["stat","path","styleText","Flags","ProjectRootNotFoundError","SanityCommand","confirm","logSymbols","validateDocuments","reporters","getDatasetFlag","getProjectIdFlag","ValidateDocumentsCommand","description","examples","command","flags","semantics","file","string","format","level","custom","default","options","integer","workspace","yes","boolean","char","hiddenAliases","run","parse","dataset","maxCustomValidationConcurrency","maxFetchConcurrency","projectId","unattendedMode","Boolean","workDir","cliConfig","root","getProjectRoot","directory","getCliConfig","err","output","error","exit","log","warning","confirmed","message","formatter","Intl","ListFormat","style","type","Object","keys","map","key","ndjsonFilePath","filePath","resolve","st","isFile","overallLevel","reporter","worker","pretty","studioHost","Error","String"],"mappings":"AAAA,SAAQA,IAAI,QAAO,mBAAkB;AACrC,OAAOC,UAAU,YAAW;AAC5B,SAAQC,SAAS,QAAO,YAAW;AAEnC,SAAQC,KAAK,QAAO,cAAa;AACjC,SAAQC,wBAAwB,QAAO,0BAAyB;AAChE,SAAQC,aAAa,QAAO,iCAAgC;AAE5D,SAAQC,OAAO,EAAEC,UAAU,QAAO,sBAAqB;AAGvD,SAAQC,iBAAiB,QAAO,sCAAqC;AACrE,SAAQC,SAAS,QAAO,wDAAuD;AAC/E,SAAQC,cAAc,EAAEC,gBAAgB,QAAO,4BAA2B;AAE1E,OAAO,MAAMC,iCAAiCP;IAC5C,OAAOQ,cAAc,4DAA2D;IAEhF,OAAOC,WAAW;QAChB;YACEC,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,OAAOG,QAAQ;QACb,GAAGL,iBAAiB;YAClBE,aACE;YACFI,WAAW;QACb,EAAE;QACF,GAAGP,eAAe;YAChBG,aACE;YACFI,WAAW;QACb,EAAE;QACFC,MAAMf,MAAMgB,MAAM,CAAC;YACjBN,aACE;QACJ;QACAO,QAAQjB,MAAMgB,MAAM,CAAC;YACnBN,aACE;QACJ;QACAQ,OAAOlB,MAAMmB,MAAM,CAAQ;YACzBC,SAAS;YACTV,aAAa;YACbW,SAAS;gBAAC;gBAAS;gBAAW;aAAO;QACvC;QACA,qCAAqCrB,MAAMsB,OAAO,CAAC;YACjDF,SAAS;YACTV,aAAa;QACf;QACA,yBAAyBV,MAAMsB,OAAO,CAAC;YACrCF,SAAS;YACTV,aAAa;QACf;QACAa,WAAWvB,MAAMgB,MAAM,CAAC;YACtBN,aAAa;QACf;QACAc,KAAKxB,MAAMyB,OAAO,CAAC;YACjBC,MAAM;YACNN,SAAS;YACTV,aAAa;QACf;IACF,EAAC;IAED,OAAgBiB,gBAA0B;QAAC;KAAoB,CAAA;IAE/D,MAAaC,MAAqB;QAChC,MAAM,EAACf,KAAK,EAAC,GAAG,MAAM,IAAI,CAACgB,KAAK,CAACpB;QACjC,MAAM,EACJqB,OAAO,EACPf,IAAI,EACJE,MAAM,EACNC,KAAK,EACL,qCAAqCa,8BAA8B,EACnE,yBAAyBC,mBAAmB,EAC5C,cAAcC,SAAS,EACvBV,SAAS,EACV,GAAGV;QACJ,MAAMqB,iBAAiBC,QAAQtB,MAAMW,GAAG;QAExC,IAAIY;QACJ,IAAIC;QACJ,IAAI;YACF,MAAMC,OAAO,MAAM,IAAI,CAACC,cAAc;YACtCH,UAAUE,KAAKE,SAAS;YACxBH,YAAY,MAAM,IAAI,CAACI,YAAY;QACrC,EAAE,OAAOC,KAAK;YACZ,IAAIA,eAAezC,0BAA0B;gBAC3C,OAAO,IAAI,CAAC0C,MAAM,CAACC,KAAK,CACtB,2GACA;oBAACC,MAAM;gBAAC;YAEZ;YACA,MAAMH;QACR;QAEA,IAAI,CAACR,gBAAgB;YACnB,IAAI,CAACS,MAAM,CAACG,GAAG,CACb,GAAG/C,UAAU,UAAU,GAAGK,WAAW2C,OAAO,CAAC,SAAS,CAAC,EAAE,cAAc,EACrEhC,OACI,6CACA,4CACL,uDAAuD,CAAC,GACvD,CAAC,gCAAgC,CAAC;YAEtC,IAAI,CAAC4B,MAAM,CAACG,GAAG,CAAC,CAAC,qBAAqB,CAAC;YACvC,IAAI,CAACH,MAAM,CAACG,GAAG,CACb,CAAC,gGAAgG,CAAC;YAEpG,IAAI,CAACH,MAAM,CAACG,GAAG,CACb,CAAC,uGAAuG,CAAC;YAE3G,IAAI,CAACH,MAAM,CAACG,GAAG,CACb,CAAC,iGAAiG,CAAC;YAErG,IAAI,CAACH,MAAM,CAACG,GAAG,CACb,CAAC,qFAAqF,CAAC;YAEzF,IAAI/B,MAAM;gBACR,IAAI,CAAC4B,MAAM,CAACG,GAAG,CACb,CAAC,4FAA4F,CAAC;YAElG;YAEA,MAAME,YAAY,MAAM7C,QAAQ;gBAC9BiB,SAAS;gBACT6B,SAAS,CAAC,kCAAkC,CAAC;YAC/C;YAEA,IAAI,CAACD,WAAW;gBACd,OAAO,IAAI,CAACL,MAAM,CAACC,KAAK,CAAC,gBAAgB;oBAACC,MAAM;gBAAC;YACnD;QACF;QAEA,IAAI5B,UAAU,CAAEA,CAAAA,UAAUX,SAAQ,GAAI;YACpC,MAAM4C,YAAY,IAAIC,KAAKC,UAAU,CAAC,SAAS;gBAC7CC,OAAO;gBACPC,MAAM;YACR;YACA,OAAO,IAAI,CAACX,MAAM,CAACC,KAAK,CACtB,CAAC,0BAA0B,EAAE3B,OAAO,yBAAyB,EAAEiC,UAAUjC,MAAM,CAC7EsC,OAAOC,IAAI,CAAClD,WAAWmD,GAAG,CAAC,CAACC,MAAQ,CAAC,CAAC,EAAEA,IAAI,CAAC,CAAC,IAC7C,EACH;gBAACb,MAAM;YAAC;QAEZ;QAEA,IAAIc;QACJ,IAAI5C,MAAM;YACR,MAAM6C,WAAW9D,KAAK+D,OAAO,CAACzB,SAASrB;YAEvC,MAAM+C,KAAK,MAAMjE,KAAK+D;YACtB,IAAI,CAACE,GAAGC,MAAM,IAAI;gBAChB,OAAO,IAAI,CAACpB,MAAM,CAACC,KAAK,CAAC,CAAC,qDAAqD,CAAC,EAAE;oBAACC,MAAM;gBAAC;YAC5F;YAEAc,iBAAiBC;QACnB;QAEA,IAAI;YACF,MAAMI,eAAe,MAAM3D,kBAAkB;gBAC3CyB;gBACAZ;gBACAa;gBACAC;gBACA2B;gBACA1B;gBACAgC,UAAU,CAACC;oBACT,MAAMD,WACJhD,UAAUA,UAAUX,YAChBA,SAAS,CAACW,OAAiC,GAC3CX,UAAU6D,MAAM;oBAEtB,OAAOF,SAAS;wBAACpD;wBAAO8B,QAAQ,IAAI,CAACA,MAAM;wBAAEuB;oBAAM;gBACrD;gBACAE,YAAY/B,UAAU+B,UAAU;gBAChChC;gBACAb;YACF;YAEA,IAAIyC,iBAAiB,SAAS;gBAC5B,OAAO,IAAI,CAACnB,IAAI,CAAC;YACnB;QACF,EAAE,OAAOH,KAAK;YACZ,OAAO,IAAI,CAACC,MAAM,CAACC,KAAK,CAACF,eAAe2B,QAAQ3B,IAAIO,OAAO,GAAGqB,OAAO5B,MAAM;gBAACG,MAAM;YAAC;QACrF;IACF;AACF"}