@sanity/cli 8.11.0 → 8.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +79 -29
  2. package/dist/actions/assets/ingestAssetFromUrlWithProgress.js +21 -0
  3. package/dist/actions/assets/ingestAssetFromUrlWithProgress.js.map +1 -0
  4. package/dist/actions/assets/withUrlIngestProgress.js +45 -0
  5. package/dist/actions/assets/withUrlIngestProgress.js.map +1 -0
  6. package/dist/actions/auth/authServer.js +5 -8
  7. package/dist/actions/auth/authServer.js.map +1 -1
  8. package/dist/actions/deploy/deployApp.js +5 -2
  9. package/dist/actions/deploy/deployApp.js.map +1 -1
  10. package/dist/actions/deploy/deployChecks.js +3 -2
  11. package/dist/actions/deploy/deployChecks.js.map +1 -1
  12. package/dist/actions/deploy/findUserApplication.js +3 -2
  13. package/dist/actions/deploy/findUserApplication.js.map +1 -1
  14. package/dist/actions/deploy/resolveDeployTarget.js +6 -1
  15. package/dist/actions/deploy/resolveDeployTarget.js.map +1 -1
  16. package/dist/actions/documents/validateDocuments.worker.js +1 -4
  17. package/dist/actions/documents/validateDocuments.worker.js.map +1 -1
  18. package/dist/actions/media/importMedia.js.map +1 -1
  19. package/dist/actions/media/ingestMediaAssetFromUrlWithProgress.js +19 -0
  20. package/dist/actions/media/ingestMediaAssetFromUrlWithProgress.js.map +1 -0
  21. package/dist/commands/assets/upload.js +78 -52
  22. package/dist/commands/assets/upload.js.map +1 -1
  23. package/dist/commands/deploy.js +33 -1
  24. package/dist/commands/deploy.js.map +1 -1
  25. package/dist/commands/media/import.js +149 -13
  26. package/dist/commands/media/import.js.map +1 -1
  27. package/dist/commands/undeploy.js +41 -5
  28. package/dist/commands/undeploy.js.map +1 -1
  29. package/dist/exports/invokeSanityCli/commandPolicies/index.js +1 -4
  30. package/dist/exports/invokeSanityCli/commandPolicies/index.js.map +1 -1
  31. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +26 -6
  32. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
  33. package/dist/exports/invokeSanityCli/commandPolicies/resolve.js +112 -0
  34. package/dist/exports/invokeSanityCli/commandPolicies/resolve.js.map +1 -0
  35. package/dist/exports/invokeSanityCli/help.js +8 -1
  36. package/dist/exports/invokeSanityCli/help.js.map +1 -1
  37. package/dist/exports/invokeSanityCli/index.d.ts +1 -2
  38. package/dist/exports/invokeSanityCli/index.js +37 -15
  39. package/dist/exports/invokeSanityCli/index.js.map +1 -1
  40. package/dist/generated/apiRoutes.js +12 -0
  41. package/dist/generated/apiRoutes.js.map +1 -1
  42. package/dist/services/assets.js +46 -1
  43. package/dist/services/assets.js.map +1 -1
  44. package/dist/services/mediaLibraries.js +39 -1
  45. package/dist/services/mediaLibraries.js.map +1 -1
  46. package/dist/util/assetSourceValidation.js +56 -0
  47. package/dist/util/assetSourceValidation.js.map +1 -0
  48. package/dist/util/assetUploadErrors.js +64 -0
  49. package/dist/util/assetUploadErrors.js.map +1 -0
  50. package/dist/util/isRemoteAssetSource.js +19 -0
  51. package/dist/util/isRemoteAssetSource.js.map +1 -0
  52. package/dist/util/parseAspectFlags.js +34 -0
  53. package/dist/util/parseAspectFlags.js.map +1 -0
  54. package/oclif.config.js +0 -1
  55. package/oclif.manifest.json +952 -864
  56. package/package.json +18 -18
  57. package/templates/app-quickstart/AGENTS.md +18 -0
  58. package/templates/app-sanity-ui/AGENTS.md +18 -0
  59. package/templates/shopify/.gitignore +26 -0
  60. package/dist/exports/invokeSanityCli/commandPolicies/policy.js +0 -46
  61. package/dist/exports/invokeSanityCli/commandPolicies/policy.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/commands/media/import.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Args, Flags} from '@oclif/core'\nimport {exitCodes, getProjectCliClient, SanityCommand} from '@sanity/cli-core'\nimport {requiredWhenUnattended} from '@sanity/cli-core/flags'\nimport {boxen, spinner} from '@sanity/cli-core/ux'\nimport {SanityClient} from '@sanity/client'\nimport {type OperatorFunction, pipe, scan, tap} from 'rxjs'\n\nimport {importer, type State} from '../../actions/media/importMedia.js'\nimport {importMediaDebug} from '../../actions/media/importMediaDebug.js'\nimport {promptForMediaLibrary} from '../../prompts/promptForMediaLibrary.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {getMediaLibraries} from '../../services/mediaLibraries.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\n\nexport class MediaImportCommand extends SanityCommand<typeof MediaImportCommand> {\n static override args = {\n source: Args.string({\n description: 'Image file or folder to import from',\n required: true,\n }),\n }\n\n static override description = 'Import a set of assets to the target media library.'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %> products',\n description: 'Import all assets from the \"products\" directory',\n },\n {\n command: '<%= config.bin %> <%= command.id %> gallery.tar.gz',\n description: 'Import all assets from \"gallery\" archive',\n },\n {\n command: '<%= config.bin %> <%= command.id %> products --replace-aspects',\n description: 'Import all assets from the \"products\" directory and replace aspects',\n },\n ]\n\n static override flags = {\n ...getProjectIdFlag({\n description: 'Project ID to import media to',\n semantics: 'override',\n }),\n 'media-library-id': requiredWhenUnattended(\n Flags.string({\n description: 'The id of the target media library',\n }),\n ),\n 'replace-aspects': Flags.boolean({\n description:\n 'Replace existing aspect data. All versions will be replaced (e.g. published and draft aspect data)',\n }),\n }\n\n public async run(): Promise<void> {\n const {args, flags} = await this.parse(MediaImportCommand)\n const {source} = args\n const replaceAspects = flags['replace-aspects']\n\n const projectId = await this.getProjectId({fallback: () => promptForProject({})})\n\n const cliConfig = await this.tryGetCliConfig()\n const dataset = cliConfig.api?.dataset\n\n let mediaLibraries\n try {\n mediaLibraries = await getMediaLibraries(projectId)\n } catch (error) {\n importMediaDebug('Error listing media libraries', error)\n this.error(\n `Failed to list media libraries:\\n${error instanceof Error ? error.message : error}`,\n {\n exit: exitCodes.RUNTIME_ERROR,\n },\n )\n }\n\n if (mediaLibraries.length === 0) {\n this.error('No active media libraries found in this project', {exit: exitCodes.RUNTIME_ERROR})\n }\n\n let mediaLibraryId = flags['media-library-id']\n if (!mediaLibraryId) {\n try {\n mediaLibraryId = await promptForMediaLibrary({mediaLibraries})\n } catch (error) {\n importMediaDebug('Error selecting media library', error)\n this.error(\n `Failed to select media library:\\n${error instanceof Error ? error.message : error}`,\n {\n exit: exitCodes.RUNTIME_ERROR,\n },\n )\n }\n }\n\n if (!mediaLibraries.some((library) => library.id === mediaLibraryId)) {\n this.error(`Media library with id \"${mediaLibraryId}\" not found`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n const projectClient = await getProjectCliClient({\n apiVersion: 'v2025-02-19',\n dataset,\n perspective: 'drafts',\n projectId,\n requestTagPrefix: 'sanity.mediaLibraryCli.import',\n requireUser: true,\n '~experimental_resource': {\n id: mediaLibraryId,\n type: 'media-library',\n },\n })\n\n this.log(\n boxen(\n `\n Importing to media library: ${mediaLibraryId.padEnd(37)}\n Importing from path: ${source}\n `,\n {\n borderColor: 'yellow',\n borderStyle: 'round',\n },\n ),\n )\n\n const spin = spinner('Beginning import…').start()\n\n await this.importAssets({projectClient, replaceAspects, source, spin})\n }\n\n private async importAssets(options: {\n projectClient: SanityClient\n replaceAspects: boolean\n source: string\n spin: ReturnType<typeof spinner>\n }): Promise<void> {\n const {projectClient, replaceAspects, source, spin} = options\n\n return new Promise<void>((resolve, reject) => {\n const subscription = importer({\n client: projectClient,\n replaceAspects,\n sourcePath: source,\n spinner: spin,\n })\n .pipe(this.reportResult(spin))\n .subscribe({\n complete: () => {\n resolve()\n },\n error: (error: unknown) => {\n const message = error instanceof Error ? error.message : String(error)\n spin.stop()\n reject(new Error(message))\n },\n })\n\n // Cleanup on Ctrl+C\n process.once('SIGINT', () => {\n subscription.unsubscribe()\n spin.fail('Import interrupted.')\n process.exit(exitCodes.SIGINT)\n })\n }).catch((error) => {\n this.error(styleText('red', error.message), {exit: exitCodes.RUNTIME_ERROR})\n })\n }\n\n private reportResult(\n spin: ReturnType<typeof spinner>,\n ): OperatorFunction<State, [number, State | undefined]> {\n let previousState: State | undefined\n\n return pipe(\n scan<State, [number, State | undefined]>(\n (processedAssetsCount, state) => [processedAssetsCount[0] + 1, state],\n [0, undefined],\n ),\n tap({\n complete: () => spin.succeed(`Imported ${previousState?.fileCount} assets`),\n next: ([processedAssetsCount, state]) => {\n previousState = state\n spin.text = `${processedAssetsCount} of ${state?.fileCount} assets imported ${styleText('dim', state?.asset.originalFilename ?? '')}`\n },\n }),\n )\n }\n}\n"],"names":["styleText","Args","Flags","exitCodes","getProjectCliClient","SanityCommand","requiredWhenUnattended","boxen","spinner","pipe","scan","tap","importer","importMediaDebug","promptForMediaLibrary","promptForProject","getMediaLibraries","getProjectIdFlag","MediaImportCommand","args","source","string","description","required","examples","command","flags","semantics","boolean","run","parse","replaceAspects","projectId","getProjectId","fallback","cliConfig","tryGetCliConfig","dataset","api","mediaLibraries","error","Error","message","exit","RUNTIME_ERROR","length","mediaLibraryId","some","library","id","projectClient","apiVersion","perspective","requestTagPrefix","requireUser","type","log","padEnd","borderColor","borderStyle","spin","start","importAssets","options","Promise","resolve","reject","subscription","client","sourcePath","reportResult","subscribe","complete","String","stop","process","once","unsubscribe","fail","SIGINT","catch","previousState","processedAssetsCount","state","undefined","succeed","fileCount","next","text","asset","originalFilename"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/commands/media/import.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Args, Flags} from '@oclif/core'\nimport {type FlagInput} from '@oclif/core/interfaces'\nimport {exitCodes, getProjectCliClient, SanityCommand} from '@sanity/cli-core'\nimport {requiredWhenUnattended} from '@sanity/cli-core/flags'\nimport {boxen, spinner} from '@sanity/cli-core/ux'\nimport {SanityClient} from '@sanity/client'\nimport {type OperatorFunction, pipe, scan, tap} from 'rxjs'\n\nimport {importer, type State} from '../../actions/media/importMedia.js'\nimport {importMediaDebug} from '../../actions/media/importMediaDebug.js'\nimport {ingestMediaAssetFromUrlWithProgress} from '../../actions/media/ingestMediaAssetFromUrlWithProgress.js'\nimport {promptForMediaLibrary} from '../../prompts/promptForMediaLibrary.js'\nimport {promptForProject} from '../../prompts/promptForProject.js'\nimport {getMediaLibraries} from '../../services/mediaLibraries.js'\nimport {getAssetFilenameError, getIngestUrlError} from '../../util/assetSourceValidation.js'\nimport {getAssetUploadErrorMessage} from '../../util/assetUploadErrors.js'\nimport {isRemoteAssetSource} from '../../util/isRemoteAssetSource.js'\nimport {parseAspectFlags} from '../../util/parseAspectFlags.js'\nimport {getProjectIdFlag} from '../../util/sharedFlags.js'\nimport {defineCommandTelemetry} from '../../util/telemetry/commandTelemetry.js'\n\nconst flags = {\n ...getProjectIdFlag({\n description: 'Project ID to import media to',\n semantics: 'override',\n }),\n aspect: Flags.string({\n description:\n 'Aspect value to set on the imported asset, as key=value. Repeatable. Only applies when the source is a URL - directory and archive imports read aspects from their data.ndjson',\n helpValue: '<key=value>',\n multiple: true,\n }),\n filename: Flags.string({\n description:\n 'Original filename to store on the asset. Only applies when the source is a URL. Defaults to a name derived from the URL',\n helpValue: '<filename>',\n }),\n 'media-library-id': requiredWhenUnattended(\n Flags.string({\n description: 'The id of the target media library',\n }),\n ),\n 'replace-aspects': Flags.boolean({\n description:\n 'Replace existing aspect data. All versions will be replaced (e.g. published and draft aspect data). Only applies to directory and archive imports',\n }),\n} satisfies FlagInput\n\nexport class MediaImportCommand extends SanityCommand<typeof MediaImportCommand> {\n static override args = {\n source: Args.string({\n description:\n 'Directory, archive, or asset URL to import from. An https URL imports one asset that Sanity fetches itself',\n required: true,\n }),\n }\n\n static override description = 'Import a set of assets to the target media library.'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %> products',\n description: 'Import all assets from the \"products\" directory',\n },\n {\n command: '<%= config.bin %> <%= command.id %> gallery.tar.gz',\n description: 'Import all assets from \"gallery\" archive',\n },\n {\n command: '<%= config.bin %> <%= command.id %> products --replace-aspects',\n description: 'Import all assets from the \"products\" directory and replace aspects',\n },\n {\n command: '<%= config.bin %> <%= command.id %> https://example.com/hero.png',\n description: 'Have Sanity fetch a single asset from a public URL',\n },\n {\n command:\n '<%= config.bin %> <%= command.id %> https://example.com/hero.png --aspect department=Brand',\n description: 'Fetch an asset from a URL and set aspect data on it',\n },\n ]\n\n static override flags = flags\n\n static telemetry = defineCommandTelemetry(flags, {\n redact: ['aspect', 'filename'],\n })\n\n public async run(): Promise<void> {\n const {args, flags} = await this.parse(MediaImportCommand)\n const {source} = args\n const replaceAspects = flags['replace-aspects']\n const isUrlSource = isRemoteAssetSource(source)\n\n const aspects = this.resolveAspects({aspectFlags: flags.aspect, isUrlSource})\n\n if (!isUrlSource && flags.filename !== undefined) {\n this.error(\n 'The --filename flag only applies when importing from a URL. Directory and archive imports keep each asset’s own filename.',\n {exit: exitCodes.USAGE_ERROR},\n )\n }\n\n if (isUrlSource && replaceAspects) {\n this.error(\n 'The --replace-aspects flag only applies to directory and archive imports. Aspects passed with --aspect are set when the asset is created.',\n {exit: exitCodes.USAGE_ERROR},\n )\n }\n\n const filenameError =\n flags.filename === undefined ? undefined : getAssetFilenameError(flags.filename)\n if (filenameError) {\n this.error(filenameError, {exit: exitCodes.USAGE_ERROR})\n }\n\n const sourceUrlError = isUrlSource ? getIngestUrlError(source) : undefined\n if (sourceUrlError) {\n this.error(sourceUrlError, {exit: exitCodes.USAGE_ERROR})\n }\n\n const projectId = await this.getProjectId({fallback: () => promptForProject({})})\n\n const cliConfig = await this.tryGetCliConfig()\n const dataset = cliConfig.api?.dataset\n\n let mediaLibraries\n try {\n mediaLibraries = await getMediaLibraries(projectId)\n } catch (error) {\n importMediaDebug('Error listing media libraries', error)\n this.error(\n `Failed to list media libraries:\\n${error instanceof Error ? error.message : error}`,\n {\n exit: exitCodes.RUNTIME_ERROR,\n },\n )\n }\n\n if (mediaLibraries.length === 0) {\n this.error('No active media libraries found in this project', {exit: exitCodes.RUNTIME_ERROR})\n }\n\n let mediaLibraryId = flags['media-library-id']\n if (!mediaLibraryId) {\n try {\n mediaLibraryId = await promptForMediaLibrary({mediaLibraries})\n } catch (error) {\n importMediaDebug('Error selecting media library', error)\n this.error(\n `Failed to select media library:\\n${error instanceof Error ? error.message : error}`,\n {\n exit: exitCodes.RUNTIME_ERROR,\n },\n )\n }\n }\n\n if (!mediaLibraries.some((library) => library.id === mediaLibraryId)) {\n this.error(`Media library with id \"${mediaLibraryId}\" not found`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n if (isUrlSource) {\n await this.importFromUrl({\n ...(aspects ? {aspects} : {}),\n ...(flags.filename ? {filename: flags.filename} : {}),\n mediaLibraryId,\n url: source,\n })\n return\n }\n\n const projectClient = await getProjectCliClient({\n apiVersion: 'v2025-02-19',\n dataset,\n perspective: 'drafts',\n projectId,\n requestTagPrefix: 'sanity.mediaLibraryCli.import',\n requireUser: true,\n '~experimental_resource': {\n id: mediaLibraryId,\n type: 'media-library',\n },\n })\n\n this.log(\n boxen(\n `\n Importing to media library: ${mediaLibraryId.padEnd(37)}\n Importing from path: ${source}\n `,\n {\n borderColor: 'yellow',\n borderStyle: 'round',\n },\n ),\n )\n\n const spin = spinner('Beginning import…').start()\n\n await this.importAssets({projectClient, replaceAspects, source, spin})\n }\n\n private async importAssets(options: {\n projectClient: SanityClient\n replaceAspects: boolean\n source: string\n spin: ReturnType<typeof spinner>\n }): Promise<void> {\n const {projectClient, replaceAspects, source, spin} = options\n\n return new Promise<void>((resolve, reject) => {\n const subscription = importer({\n client: projectClient,\n replaceAspects,\n sourcePath: source,\n spinner: spin,\n })\n .pipe(this.reportResult(spin))\n .subscribe({\n complete: () => {\n resolve()\n },\n error: (error: unknown) => {\n const message = error instanceof Error ? error.message : String(error)\n spin.stop()\n reject(new Error(message))\n },\n })\n\n // Cleanup on Ctrl+C\n process.once('SIGINT', () => {\n subscription.unsubscribe()\n spin.fail('Import interrupted.')\n process.exit(exitCodes.SIGINT)\n })\n }).catch((error) => {\n this.error(styleText('red', error.message), {exit: exitCodes.RUNTIME_ERROR})\n })\n }\n\n /**\n * Import one asset that Sanity fetches from a URL itself.\n *\n * Nothing but the resulting documents goes to stdout, so the output stays\n * consumable as JSON; the wait is reported on stderr by the spinner.\n */\n private async importFromUrl(options: {\n aspects?: Record<string, string>\n filename?: string\n\n mediaLibraryId: string\n url: string\n }): Promise<void> {\n const {aspects, filename, mediaLibraryId, url} = options\n\n let result\n try {\n result = await ingestMediaAssetFromUrlWithProgress({\n ...(aspects ? {aspects} : {}),\n ...(filename ? {filename} : {}),\n isInteractive: this.resolveIsInteractive(),\n logToStderr: (message) => this.logToStderr(message),\n mediaLibraryId,\n url,\n })\n } catch (error) {\n if (error instanceof Error && error.message === 'SIGINT') throw error\n importMediaDebug('Error importing asset from URL', error)\n this.error(getAssetUploadErrorMessage(error, {fromUrl: true, target: 'media-library'}), {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n\n this.log(\n JSON.stringify(\n {\n asset: {\n _id: result.asset._id,\n _type: result.asset._type,\n assetType: result.asset.assetType,\n },\n assetInstance: {\n _id: result.assetInstance._id,\n extension: result.assetInstance.extension,\n mimeType: result.assetInstance.mimeType,\n originalFilename: result.assetInstance.originalFilename,\n size: result.assetInstance.size,\n url: result.assetInstance.url,\n },\n },\n null,\n 2,\n ),\n )\n }\n\n private reportResult(\n spin: ReturnType<typeof spinner>,\n ): OperatorFunction<State, [number, State | undefined]> {\n let previousState: State | undefined\n\n return pipe(\n scan<State, [number, State | undefined]>(\n (processedAssetsCount, state) => [processedAssetsCount[0] + 1, state],\n [0, undefined],\n ),\n tap({\n complete: () => spin.succeed(`Imported ${previousState?.fileCount} assets`),\n next: ([processedAssetsCount, state]) => {\n previousState = state\n spin.text = `${processedAssetsCount} of ${state?.fileCount} assets imported ${styleText('dim', state?.asset.originalFilename ?? '')}`\n },\n }),\n )\n }\n\n /**\n * Validate `--aspect` against the source kind and shape it for the request.\n * Runs before any network call so a malformed flag fails immediately.\n */\n private resolveAspects(options: {\n aspectFlags?: string[]\n\n isUrlSource: boolean\n }): Record<string, string> | undefined {\n const {aspectFlags, isUrlSource} = options\n if (!aspectFlags?.length) return undefined\n\n if (!isUrlSource) {\n this.error(\n 'The --aspect flag only applies when importing from a URL. Directory and archive imports read aspect data from their data.ndjson file.',\n {exit: exitCodes.USAGE_ERROR},\n )\n }\n\n const parsed = parseAspectFlags(aspectFlags)\n if ('error' in parsed) {\n this.error(parsed.error, {exit: exitCodes.USAGE_ERROR})\n }\n\n return parsed.aspects\n }\n}\n"],"names":["styleText","Args","Flags","exitCodes","getProjectCliClient","SanityCommand","requiredWhenUnattended","boxen","spinner","pipe","scan","tap","importer","importMediaDebug","ingestMediaAssetFromUrlWithProgress","promptForMediaLibrary","promptForProject","getMediaLibraries","getAssetFilenameError","getIngestUrlError","getAssetUploadErrorMessage","isRemoteAssetSource","parseAspectFlags","getProjectIdFlag","defineCommandTelemetry","flags","description","semantics","aspect","string","helpValue","multiple","filename","boolean","MediaImportCommand","args","source","required","examples","command","telemetry","redact","run","parse","replaceAspects","isUrlSource","aspects","resolveAspects","aspectFlags","undefined","error","exit","USAGE_ERROR","filenameError","sourceUrlError","projectId","getProjectId","fallback","cliConfig","tryGetCliConfig","dataset","api","mediaLibraries","Error","message","RUNTIME_ERROR","length","mediaLibraryId","some","library","id","importFromUrl","url","projectClient","apiVersion","perspective","requestTagPrefix","requireUser","type","log","padEnd","borderColor","borderStyle","spin","start","importAssets","options","Promise","resolve","reject","subscription","client","sourcePath","reportResult","subscribe","complete","String","stop","process","once","unsubscribe","fail","SIGINT","catch","result","isInteractive","resolveIsInteractive","logToStderr","fromUrl","target","JSON","stringify","asset","_id","_type","assetType","assetInstance","extension","mimeType","originalFilename","size","previousState","processedAssetsCount","state","succeed","fileCount","next","text","parsed"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import { Flags } from '@oclif/core';
2
- import { SanityCommand } from '@sanity/cli-core';
1
+ import { Args, Flags } from '@oclif/core';
2
+ import { exitCodes, SanityCommand } from '@sanity/cli-core';
3
3
  import { getWorkbench, resolveWorkbenchConfig } from '@sanity/workbench-cli/deploy';
4
4
  import { createWorkbenchUndeployAdapter } from '@sanity/workbench-cli/undeploy';
5
5
  import { createAppUndeployAdapter, createStudioUndeployAdapter } from '../actions/undeploy/adapters.js';
@@ -7,12 +7,21 @@ import { runUndeploy } from '../actions/undeploy/runUndeploy.js';
7
7
  import { getAppId } from '../util/appId.js';
8
8
  import { determineIsApp } from '../util/determineIsApp.js';
9
9
  export class UndeployCommand extends SanityCommand {
10
+ static args = {
11
+ appId: Args.string({
12
+ description: 'ID of the application to undeploy. Overrides `deployment.appId` in sanity.cli.ts'
13
+ })
14
+ };
10
15
  static description = 'Removes the deployed Sanity Studio/App from Sanity hosting';
11
16
  static examples = [
12
17
  {
13
18
  command: '<%= config.bin %> <%= command.id %>',
14
19
  description: 'Undeploy the studio or application after confirming'
15
20
  },
21
+ {
22
+ command: '<%= config.bin %> <%= command.id %> abc123',
23
+ description: 'Undeploy the studio or application with the given ID'
24
+ },
16
25
  {
17
26
  command: '<%= config.bin %> <%= command.id %> --dry-run',
18
27
  description: 'Report what would be undeployed without deleting anything'
@@ -39,13 +48,28 @@ export class UndeployCommand extends SanityCommand {
39
48
  })
40
49
  };
41
50
  async run() {
42
- const { flags } = await this.parse(UndeployCommand);
43
- const cliConfig = await this.getCliConfig();
44
- const isApp = determineIsApp(cliConfig);
51
+ const { args, flags } = await this.parse(UndeployCommand);
52
+ const projectConfig = await this.getCliConfig();
53
+ const isApp = determineIsApp(projectConfig);
54
+ if (args.appId) this.warnIfOverridingConfig(args.appId, projectConfig, isApp);
55
+ // An explicit ID stands in for `deployment.appId`, which every adapter
56
+ // already prefers over `studioHost` and the deprecated `app.id`.
57
+ const cliConfig = args.appId ? {
58
+ ...projectConfig,
59
+ deployment: {
60
+ ...projectConfig.deployment,
61
+ appId: args.appId
62
+ }
63
+ } : projectConfig;
45
64
  // Workbench apps and configs deploy through Brett, so they undeploy through
46
65
  // it too; plain projects keep the user-applications backend.
47
66
  const workbench = getWorkbench(cliConfig);
48
67
  const config = resolveWorkbenchConfig(cliConfig);
68
+ if (args.appId && config) {
69
+ this.error('An app ID cannot be used to undeploy a config. Run without the argument.', {
70
+ exit: exitCodes.USAGE_ERROR
71
+ });
72
+ }
49
73
  const adapter = workbench || config ? createWorkbenchUndeployAdapter({
50
74
  appId: getAppId(cliConfig),
51
75
  config: config ?? undefined,
@@ -59,6 +83,18 @@ export class UndeployCommand extends SanityCommand {
59
83
  output: this.output
60
84
  }, adapter);
61
85
  }
86
+ warnIfOverridingConfig(appId, cliConfig, isApp) {
87
+ const configuredAppId = getAppId(cliConfig);
88
+ if (configuredAppId) {
89
+ if (configuredAppId !== appId) {
90
+ this.output.warn(`Using app ID "${appId}" instead of "${configuredAppId}" configured in sanity.cli.ts`);
91
+ }
92
+ return;
93
+ }
94
+ if (!isApp && cliConfig.studioHost) {
95
+ this.output.warn(`Using app ID "${appId}" instead of studio host "${cliConfig.studioHost}" configured in sanity.cli.ts`);
96
+ }
97
+ }
62
98
  }
63
99
 
64
100
  //# sourceMappingURL=undeploy.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/undeploy.ts"],"sourcesContent":["import {Flags} from '@oclif/core'\nimport {SanityCommand} from '@sanity/cli-core'\nimport {type UndeployAdapter} from '@sanity/cli-core/undeploy'\nimport {getWorkbench, resolveWorkbenchConfig} from '@sanity/workbench-cli/deploy'\nimport {createWorkbenchUndeployAdapter} from '@sanity/workbench-cli/undeploy'\n\nimport {\n createAppUndeployAdapter,\n createStudioUndeployAdapter,\n} from '../actions/undeploy/adapters.js'\nimport {runUndeploy} from '../actions/undeploy/runUndeploy.js'\nimport {getAppId} from '../util/appId.js'\nimport {determineIsApp} from '../util/determineIsApp.js'\n\nexport class UndeployCommand extends SanityCommand<typeof UndeployCommand> {\n static override description = 'Removes the deployed Sanity Studio/App from Sanity hosting'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Undeploy the studio or application after confirming',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --dry-run',\n description: 'Report what would be undeployed without deleting anything',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --json --yes',\n description: 'Undeploy without prompting and report the result as JSON',\n },\n ]\n\n static override flags = {\n 'dry-run': Flags.boolean({\n default: false,\n description: 'Report what would be undeployed without deleting anything',\n }),\n json: Flags.boolean({\n char: 'j',\n default: false,\n description: 'Output the result as JSON',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description:\n 'Unattended mode, answers \"yes\" to any \"yes/no\" prompt and otherwise uses defaults',\n }),\n }\n\n public async run(): Promise<void> {\n const {flags} = await this.parse(UndeployCommand)\n\n const cliConfig = await this.getCliConfig()\n const isApp = determineIsApp(cliConfig)\n\n // Workbench apps and configs deploy through Brett, so they undeploy through\n // it too; plain projects keep the user-applications backend.\n const workbench = getWorkbench(cliConfig)\n const config = resolveWorkbenchConfig(cliConfig)\n const adapter: UndeployAdapter =\n workbench || config\n ? createWorkbenchUndeployAdapter({\n appId: getAppId(cliConfig),\n config: config ?? undefined,\n organizationId: cliConfig.app?.organizationId,\n type: isApp ? 'coreApp' : 'studio',\n workbench: workbench ?? undefined,\n })\n : isApp\n ? createAppUndeployAdapter(cliConfig)\n : createStudioUndeployAdapter(cliConfig)\n\n await runUndeploy({flags, isUnattended: this.isUnattended(), output: this.output}, adapter)\n }\n}\n"],"names":["Flags","SanityCommand","getWorkbench","resolveWorkbenchConfig","createWorkbenchUndeployAdapter","createAppUndeployAdapter","createStudioUndeployAdapter","runUndeploy","getAppId","determineIsApp","UndeployCommand","description","examples","command","flags","boolean","default","json","char","yes","run","parse","cliConfig","getCliConfig","isApp","workbench","config","adapter","appId","undefined","organizationId","app","type","isUnattended","output"],"mappings":"AAAA,SAAQA,KAAK,QAAO,cAAa;AACjC,SAAQC,aAAa,QAAO,mBAAkB;AAE9C,SAAQC,YAAY,EAAEC,sBAAsB,QAAO,+BAA8B;AACjF,SAAQC,8BAA8B,QAAO,iCAAgC;AAE7E,SACEC,wBAAwB,EACxBC,2BAA2B,QACtB,kCAAiC;AACxC,SAAQC,WAAW,QAAO,qCAAoC;AAC9D,SAAQC,QAAQ,QAAO,mBAAkB;AACzC,SAAQC,cAAc,QAAO,4BAA2B;AAExD,OAAO,MAAMC,wBAAwBT;IACnC,OAAgBU,cAAc,6DAA4D;IAE1F,OAAgBC,WAAW;QACzB;YACEC,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;QACA;YACEE,SAAS;YACTF,aAAa;QACf;KACD,CAAA;IAED,OAAgBG,QAAQ;QACtB,WAAWd,MAAMe,OAAO,CAAC;YACvBC,SAAS;YACTL,aAAa;QACf;QACAM,MAAMjB,MAAMe,OAAO,CAAC;YAClBG,MAAM;YACNF,SAAS;YACTL,aAAa;QACf;QACAQ,KAAKnB,MAAMe,OAAO,CAAC;YACjBG,MAAM;YACNF,SAAS;YACTL,aACE;QACJ;IACF,EAAC;IAED,MAAaS,MAAqB;QAChC,MAAM,EAACN,KAAK,EAAC,GAAG,MAAM,IAAI,CAACO,KAAK,CAACX;QAEjC,MAAMY,YAAY,MAAM,IAAI,CAACC,YAAY;QACzC,MAAMC,QAAQf,eAAea;QAE7B,4EAA4E;QAC5E,6DAA6D;QAC7D,MAAMG,YAAYvB,aAAaoB;QAC/B,MAAMI,SAASvB,uBAAuBmB;QACtC,MAAMK,UACJF,aAAaC,SACTtB,+BAA+B;YAC7BwB,OAAOpB,SAASc;YAChBI,QAAQA,UAAUG;YAClBC,gBAAgBR,UAAUS,GAAG,EAAED;YAC/BE,MAAMR,QAAQ,YAAY;YAC1BC,WAAWA,aAAaI;QAC1B,KACAL,QACEnB,yBAAyBiB,aACzBhB,4BAA4BgB;QAEpC,MAAMf,YAAY;YAACO;YAAOmB,cAAc,IAAI,CAACA,YAAY;YAAIC,QAAQ,IAAI,CAACA,MAAM;QAAA,GAAGP;IACrF;AACF"}
1
+ {"version":3,"sources":["../../src/commands/undeploy.ts"],"sourcesContent":["import {Args, Flags} from '@oclif/core'\nimport {type CliConfig, exitCodes, SanityCommand} from '@sanity/cli-core'\nimport {type UndeployAdapter} from '@sanity/cli-core/undeploy'\nimport {getWorkbench, resolveWorkbenchConfig} from '@sanity/workbench-cli/deploy'\nimport {createWorkbenchUndeployAdapter} from '@sanity/workbench-cli/undeploy'\n\nimport {\n createAppUndeployAdapter,\n createStudioUndeployAdapter,\n} from '../actions/undeploy/adapters.js'\nimport {runUndeploy} from '../actions/undeploy/runUndeploy.js'\nimport {getAppId} from '../util/appId.js'\nimport {determineIsApp} from '../util/determineIsApp.js'\n\nexport class UndeployCommand extends SanityCommand<typeof UndeployCommand> {\n static override args = {\n appId: Args.string({\n description:\n 'ID of the application to undeploy. Overrides `deployment.appId` in sanity.cli.ts',\n }),\n }\n\n static override description = 'Removes the deployed Sanity Studio/App from Sanity hosting'\n\n static override examples = [\n {\n command: '<%= config.bin %> <%= command.id %>',\n description: 'Undeploy the studio or application after confirming',\n },\n {\n command: '<%= config.bin %> <%= command.id %> abc123',\n description: 'Undeploy the studio or application with the given ID',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --dry-run',\n description: 'Report what would be undeployed without deleting anything',\n },\n {\n command: '<%= config.bin %> <%= command.id %> --json --yes',\n description: 'Undeploy without prompting and report the result as JSON',\n },\n ]\n\n static override flags = {\n 'dry-run': Flags.boolean({\n default: false,\n description: 'Report what would be undeployed without deleting anything',\n }),\n json: Flags.boolean({\n char: 'j',\n default: false,\n description: 'Output the result as JSON',\n }),\n yes: Flags.boolean({\n char: 'y',\n default: false,\n description:\n 'Unattended mode, answers \"yes\" to any \"yes/no\" prompt and otherwise uses defaults',\n }),\n }\n\n public async run(): Promise<void> {\n const {args, flags} = await this.parse(UndeployCommand)\n\n const projectConfig = await this.getCliConfig()\n const isApp = determineIsApp(projectConfig)\n if (args.appId) this.warnIfOverridingConfig(args.appId, projectConfig, isApp)\n // An explicit ID stands in for `deployment.appId`, which every adapter\n // already prefers over `studioHost` and the deprecated `app.id`.\n const cliConfig = args.appId\n ? {...projectConfig, deployment: {...projectConfig.deployment, appId: args.appId}}\n : projectConfig\n\n // Workbench apps and configs deploy through Brett, so they undeploy through\n // it too; plain projects keep the user-applications backend.\n const workbench = getWorkbench(cliConfig)\n const config = resolveWorkbenchConfig(cliConfig)\n if (args.appId && config) {\n this.error('An app ID cannot be used to undeploy a config. Run without the argument.', {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n\n const adapter: UndeployAdapter =\n workbench || config\n ? createWorkbenchUndeployAdapter({\n appId: getAppId(cliConfig),\n config: config ?? undefined,\n organizationId: cliConfig.app?.organizationId,\n type: isApp ? 'coreApp' : 'studio',\n workbench: workbench ?? undefined,\n })\n : isApp\n ? createAppUndeployAdapter(cliConfig)\n : createStudioUndeployAdapter(cliConfig)\n\n await runUndeploy({flags, isUnattended: this.isUnattended(), output: this.output}, adapter)\n }\n\n private warnIfOverridingConfig(appId: string, cliConfig: CliConfig, isApp: boolean): void {\n const configuredAppId = getAppId(cliConfig)\n if (configuredAppId) {\n if (configuredAppId !== appId) {\n this.output.warn(\n `Using app ID \"${appId}\" instead of \"${configuredAppId}\" configured in sanity.cli.ts`,\n )\n }\n return\n }\n\n if (!isApp && cliConfig.studioHost) {\n this.output.warn(\n `Using app ID \"${appId}\" instead of studio host \"${cliConfig.studioHost}\" configured in sanity.cli.ts`,\n )\n }\n }\n}\n"],"names":["Args","Flags","exitCodes","SanityCommand","getWorkbench","resolveWorkbenchConfig","createWorkbenchUndeployAdapter","createAppUndeployAdapter","createStudioUndeployAdapter","runUndeploy","getAppId","determineIsApp","UndeployCommand","args","appId","string","description","examples","command","flags","boolean","default","json","char","yes","run","parse","projectConfig","getCliConfig","isApp","warnIfOverridingConfig","cliConfig","deployment","workbench","config","error","exit","USAGE_ERROR","adapter","undefined","organizationId","app","type","isUnattended","output","configuredAppId","warn","studioHost"],"mappings":"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"}
@@ -1,6 +1,3 @@
1
- import { mcpPolicy } from './mcpPolicy.js';
2
- export const commandPolicies = {
3
- mcp: mcpPolicy
4
- };
1
+ export { commandPolicies, resolveCommandPolicies } from './resolve.js';
5
2
 
6
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/exports/invokeSanityCli/commandPolicies/index.ts"],"sourcesContent":["import {mcpPolicy} from './mcpPolicy.js'\nimport {type CommandPolicySet, type InvocationSource} from './policy.js'\n\nexport const commandPolicies: Record<InvocationSource, CommandPolicySet> = {\n mcp: mcpPolicy,\n}\n"],"names":["mcpPolicy","commandPolicies","mcp"],"mappings":"AAAA,SAAQA,SAAS,QAAO,iBAAgB;AAGxC,OAAO,MAAMC,kBAA8D;IACzEC,KAAKF;AACP,EAAC"}
1
+ {"version":3,"sources":["../../../../src/exports/invokeSanityCli/commandPolicies/index.ts"],"sourcesContent":["export {commandPolicies, resolveCommandPolicies} from './resolve.js'\n"],"names":["commandPolicies","resolveCommandPolicies"],"mappings":"AAAA,SAAQA,eAAe,EAAEC,sBAAsB,QAAO,eAAc"}
@@ -1,4 +1,5 @@
1
- import { allow, conditionalDenyFlags, conditionalPolicy, deny } from './policy.js';
1
+ import { allow, conditionalDenyFlags, conditionalPolicy, deny } from '@sanity/cli-core/commandPolicy';
2
+ import { isRemoteAssetSource } from '../../../util/isRemoteAssetSource.js';
2
3
  function apiValidator({ args, flags }) {
3
4
  const fields = Array.isArray(flags.field) ? flags.field : [];
4
5
  const fieldReadsFromHost = fields.some((field)=>{
@@ -26,10 +27,14 @@ function apiValidator({ args, flags }) {
26
27
  * a usage error, but cannot cause local filesystem access. Destructive remote
27
28
  * operations are allowed and do not by themselves make a command unsafe.
28
29
  *
29
- * Every manifest command must have exactly one policy here:
30
+ * Every command this package contributes must have exactly one policy here:
30
31
  * - allow: every valid invocation is safe
31
32
  * - conditional: safety depends on parsed arguments or flags
32
33
  * - deny: no invocation is safe
34
+ *
35
+ * Commands contributed by plugins are not listed here. Each plugin declares
36
+ * policies for its own commands (see `PluginInvocationPolicies` in
37
+ * `@sanity/cli-core/commandPolicy`).
33
38
  */ export const mcpPolicy = {
34
39
  // Special exception, this can be very dangerous but is also super useful
35
40
  // to expose. Refuse authentication overrides and host input channels:
@@ -44,8 +49,14 @@ function apiValidator({ args, flags }) {
44
49
  ],
45
50
  validate: apiValidator
46
51
  }),
47
- // Reads a file from the machine running the command and must only run locally.
48
- 'assets:upload': deny,
52
+ // --file reads from the machine running the command and must only run locally.
53
+ 'assets:upload': conditionalPolicy({
54
+ deniedFlags: [
55
+ 'content-type',
56
+ 'file'
57
+ ],
58
+ validate: ({ flags })=>typeof flags['from-url'] === 'string' && flags['from-url'].length > 0
59
+ }),
49
60
  'backups:disable': allow,
50
61
  // Writes a downloaded backup to the local filesystem.
51
62
  'backups:download': deny,
@@ -148,8 +159,17 @@ function apiValidator({ args, flags }) {
148
159
  'media:deploy-aspect': deny,
149
160
  // Writes media assets to the local filesystem.
150
161
  'media:export': deny,
151
- // Reads media assets from the local filesystem.
152
- 'media:import': deny,
162
+ // A directory or archive source reads media assets from the local
163
+ // filesystem. A URL source does not: Sanity fetches the asset itself, so the
164
+ // only local input is the URL. `--replace-aspects` is meaningless for a URL
165
+ // source (the command rejects the combination), so it is hidden here rather
166
+ // than advertised as usable surface.
167
+ 'media:import': conditionalPolicy({
168
+ deniedFlags: [
169
+ 'replace-aspects'
170
+ ],
171
+ validate: ({ args })=>typeof args.source === 'string' && isRemoteAssetSource(args.source)
172
+ }),
153
173
  // Creates migration source files in the local project.
154
174
  'migrations:create': deny,
155
175
  // Reads and loads migration definitions from the local project.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/exports/invokeSanityCli/commandPolicies/mcpPolicy.ts"],"sourcesContent":["import {\n allow,\n type CommandPolicySet,\n conditionalDenyFlags,\n conditionalPolicy,\n deny,\n} from './policy.js'\n\nfunction apiValidator({\n args,\n flags,\n}: {\n args: Readonly<Record<string, unknown>>\n flags: Readonly<Record<string, unknown>>\n}): boolean {\n const fields: unknown[] = Array.isArray(flags.field) ? flags.field : []\n\n const fieldReadsFromHost = fields.some((field) => {\n if (typeof field !== 'string') return false\n const separatorIndex = field.indexOf('=')\n return separatorIndex > 0 && field[separatorIndex + 1] === '@'\n })\n if (fieldReadsFromHost) return false\n\n const headers: unknown[] = Array.isArray(flags.header) ? flags.header : []\n\n const headerOverridesAuthentication = headers.some((header) => {\n if (typeof header !== 'string') return false\n const separatorIndex = header.indexOf(':')\n if (separatorIndex <= 0) return false\n\n const name = header.slice(0, separatorIndex).trim().toLowerCase()\n return name === 'authorization' || name === 'cookie'\n })\n\n if (headerOverridesAuthentication) return false\n\n const endpoint = typeof args.endpoint === 'string' ? URL.parse(args.endpoint) : null\n const urlEmbedsCredentials =\n endpoint !== null && (endpoint.username !== '' || endpoint.password !== '')\n\n return !urlEmbedsCredentials\n}\n\n/**\n * MCP programmatic mode disables local project/config discovery (see the CLI\n * execution context). Missing project or dataset values may therefore produce\n * a usage error, but cannot cause local filesystem access. Destructive remote\n * operations are allowed and do not by themselves make a command unsafe.\n *\n * Every manifest command must have exactly one policy here:\n * - allow: every valid invocation is safe\n * - conditional: safety depends on parsed arguments or flags\n * - deny: no invocation is safe\n */\nexport const mcpPolicy: CommandPolicySet = {\n // Special exception, this can be very dangerous but is also super useful\n // to expose. Refuse authentication overrides and host input channels:\n // `--token`, Authorization, and Cookie headers replace the MCP user's authentication,\n // URL-embedded credentials replace it with Basic authentication,\n // `--input` reads the request body from the host's filesystem or stdin, and\n // `-F key=@<file>` / `-F key=@-` field values do the same.\n api: conditionalPolicy({\n deniedFlags: ['input', 'token'],\n validate: apiValidator,\n }),\n\n // Reads a file from the machine running the command and must only run locally.\n 'assets:upload': deny,\n\n 'backups:disable': allow,\n // Writes a downloaded backup to the local filesystem.\n 'backups:download': deny,\n 'backups:enable': allow,\n 'backups:list': allow,\n\n // Requires a local Studio project and writes build output to disk.\n build: deny,\n\n // Reads and rewrites local source code.\n codemod: deny,\n\n // --watch polls a job with no deadline; a stuck job would hang the MCP tool\n // call indefinitely. Non-watch invocations are single remote requests.\n 'context:build': conditionalDenyFlags('watch'),\n 'context:create': allow,\n 'context:delete': allow,\n 'context:get': allow,\n // --file reads the import payload from the local filesystem; text, URL and\n // dataset imports are remote-only.\n 'context:imports:create': conditionalDenyFlags('file'),\n 'context:imports:delete': allow,\n 'context:imports:download': allow,\n 'context:imports:get': allow,\n 'context:imports:list': allow,\n // See context:build — --watch has no deadline and would hang the MCP tool call.\n 'context:jobs:get': conditionalDenyFlags('watch'),\n 'context:list': allow,\n 'context:refresh': allow,\n 'context:update': allow,\n\n 'cors:add': allow,\n 'cors:delete': allow,\n 'cors:list': allow,\n\n 'datasets:alias:create': allow,\n 'datasets:alias:delete': allow,\n 'datasets:alias:link': allow,\n 'datasets:alias:unlink': allow,\n 'datasets:copy': allow,\n 'datasets:create': allow,\n 'datasets:delete': allow,\n 'datasets:embeddings:disable': allow,\n 'datasets:embeddings:enable': allow,\n 'datasets:embeddings:status': allow,\n // Writes dataset contents and assets to the local filesystem.\n 'datasets:export': deny,\n // Reads import data from the local filesystem and may replace documents.\n 'datasets:import': deny,\n 'datasets:list': allow,\n 'datasets:visibility:get': allow,\n 'datasets:visibility:set': allow,\n\n // Inspects local project files and can print authentication secrets.\n debug: deny,\n\n // Reads, builds, and deploys a local Studio project.\n deploy: deny,\n\n // Loads a local Studio project and starts a development server.\n dev: deny,\n\n // Opens a browser on the machine running the MCP server.\n 'docs:browse': deny,\n // --web opens a browser on the machine running the MCP server.\n 'docs:read': conditionalDenyFlags('web'),\n 'docs:search': allow,\n\n // Reads and executes local project configuration for diagnostics.\n doctor: deny,\n\n // Reads document input from disk or launches a local editor.\n 'documents:create': deny,\n 'documents:delete': allow,\n 'documents:get': allow,\n 'documents:query': allow,\n // Loads a local Studio schema to validate documents.\n 'documents:validate': deny,\n\n // Executes arbitrary code in the local Studio context.\n exec: deny,\n\n // Loads a local schema and deploys a GraphQL API.\n 'graphql:deploy': deny,\n 'graphql:list': allow,\n // --api loads GraphQL definitions from the local project; explicit project/dataset flags are safe.\n 'graphql:undeploy': conditionalDenyFlags('api'),\n\n 'hooks:attempt': allow,\n 'hooks:create': allow,\n 'hooks:delete': allow,\n 'hooks:list': allow,\n 'hooks:logs': allow,\n\n // Creates or modifies a local project and may install dependencies.\n init: deny,\n\n // Installs packages into the local project.\n install: deny,\n\n // Opens a browser on the machine running the MCP server.\n learn: deny,\n\n // Performs an authentication flow.\n login: deny,\n\n // Performs an authentication operation and clears local credentials.\n logout: deny,\n\n // Reads local project configuration and opens a browser.\n manage: deny,\n\n // Loads local Studio configuration and writes manifest files.\n 'manifest:extract': deny,\n\n // Reads and writes local MCP client configuration.\n 'mcp:configure': deny,\n\n // Writes an aspect definition to the local filesystem.\n 'media:create-aspect': deny,\n 'media:delete-aspect': allow,\n // Reads a local aspect definition before deploying it.\n 'media:deploy-aspect': deny,\n // Writes media assets to the local filesystem.\n 'media:export': deny,\n // Reads media assets from the local filesystem.\n 'media:import': deny,\n\n // Creates migration source files in the local project.\n 'migrations:create': deny,\n // Reads and loads migration definitions from the local project.\n 'migrations:list': deny,\n // Executes local migration code that may perform arbitrary document mutations.\n 'migrations:run': deny,\n\n // Writes project setup values and scaffolds local applications.\n new: deny,\n\n // --web opens a browser on the machine running the MCP server.\n 'openapi:get': conditionalDenyFlags('web'),\n 'openapi:list': conditionalDenyFlags('web'),\n\n 'organizations:create': allow,\n 'organizations:delete': allow,\n 'organizations:get': allow,\n 'organizations:list': allow,\n 'organizations:update': allow,\n\n // Serves a local production build.\n preview: deny,\n\n 'projects:create': allow,\n 'projects:list': allow,\n // Reads locally stored claim URLs and robot tokens.\n 'projects:unclaimed': deny,\n\n // Loads the local Studio configuration to resolve the datasets containing each schema.\n 'schemas:delete': deny,\n // Loads local schema files before deploying them.\n 'schemas:deploy': deny,\n // Loads local Studio configuration and writes an extracted schema to disk.\n 'schemas:extract': deny,\n // Requires a local project and loads its schema configuration.\n 'schemas:list': deny,\n // Loads and executes a local Studio schema.\n 'schemas:validate': deny,\n\n // Installs skills into local editor configuration directories.\n 'skills:install': deny,\n\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:disable': deny,\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:enable': deny,\n 'telemetry:status': allow,\n\n // Creates authentication credentials.\n 'tokens:create': deny,\n // Deletes authentication credentials.\n 'tokens:delete': deny,\n // Exposes authentication credential metadata.\n 'tokens:list': deny,\n // Replaces authentication credentials and exposes the new secret.\n 'tokens:rotate': deny,\n\n // Reads the local schema and source files and writes generated types to the project.\n 'typegen:generate': deny,\n\n // Loads local CLI and workbench configuration to identify the deployed Studio or application.\n undeploy: deny,\n\n // Grants a user access to a project.\n 'users:invite': deny,\n 'users:list': allow,\n\n // Reads the local project and installed package tree.\n versions: deny,\n}\n"],"names":["allow","conditionalDenyFlags","conditionalPolicy","deny","apiValidator","args","flags","fields","Array","isArray","field","fieldReadsFromHost","some","separatorIndex","indexOf","headers","header","headerOverridesAuthentication","name","slice","trim","toLowerCase","endpoint","URL","parse","urlEmbedsCredentials","username","password","mcpPolicy","api","deniedFlags","validate","build","codemod","debug","deploy","dev","doctor","exec","init","install","learn","login","logout","manage","new","preview","undeploy","versions"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../src/exports/invokeSanityCli/commandPolicies/mcpPolicy.ts"],"sourcesContent":["import {\n allow,\n type CommandPolicySet,\n conditionalDenyFlags,\n conditionalPolicy,\n deny,\n} from '@sanity/cli-core/commandPolicy'\n\nimport {isRemoteAssetSource} from '../../../util/isRemoteAssetSource.js'\n\nfunction apiValidator({\n args,\n flags,\n}: {\n args: Readonly<Record<string, unknown>>\n flags: Readonly<Record<string, unknown>>\n}): boolean {\n const fields: unknown[] = Array.isArray(flags.field) ? flags.field : []\n\n const fieldReadsFromHost = fields.some((field) => {\n if (typeof field !== 'string') return false\n const separatorIndex = field.indexOf('=')\n return separatorIndex > 0 && field[separatorIndex + 1] === '@'\n })\n if (fieldReadsFromHost) return false\n\n const headers: unknown[] = Array.isArray(flags.header) ? flags.header : []\n\n const headerOverridesAuthentication = headers.some((header) => {\n if (typeof header !== 'string') return false\n const separatorIndex = header.indexOf(':')\n if (separatorIndex <= 0) return false\n\n const name = header.slice(0, separatorIndex).trim().toLowerCase()\n return name === 'authorization' || name === 'cookie'\n })\n\n if (headerOverridesAuthentication) return false\n\n const endpoint = typeof args.endpoint === 'string' ? URL.parse(args.endpoint) : null\n const urlEmbedsCredentials =\n endpoint !== null && (endpoint.username !== '' || endpoint.password !== '')\n\n return !urlEmbedsCredentials\n}\n\n/**\n * MCP programmatic mode disables local project/config discovery (see the CLI\n * execution context). Missing project or dataset values may therefore produce\n * a usage error, but cannot cause local filesystem access. Destructive remote\n * operations are allowed and do not by themselves make a command unsafe.\n *\n * Every command this package contributes must have exactly one policy here:\n * - allow: every valid invocation is safe\n * - conditional: safety depends on parsed arguments or flags\n * - deny: no invocation is safe\n *\n * Commands contributed by plugins are not listed here. Each plugin declares\n * policies for its own commands (see `PluginInvocationPolicies` in\n * `@sanity/cli-core/commandPolicy`).\n */\nexport const mcpPolicy: CommandPolicySet = {\n // Special exception, this can be very dangerous but is also super useful\n // to expose. Refuse authentication overrides and host input channels:\n // `--token`, Authorization, and Cookie headers replace the MCP user's authentication,\n // URL-embedded credentials replace it with Basic authentication,\n // `--input` reads the request body from the host's filesystem or stdin, and\n // `-F key=@<file>` / `-F key=@-` field values do the same.\n api: conditionalPolicy({\n deniedFlags: ['input', 'token'],\n validate: apiValidator,\n }),\n\n // --file reads from the machine running the command and must only run locally.\n 'assets:upload': conditionalPolicy({\n deniedFlags: ['content-type', 'file'],\n validate: ({flags}) => typeof flags['from-url'] === 'string' && flags['from-url'].length > 0,\n }),\n\n 'backups:disable': allow,\n // Writes a downloaded backup to the local filesystem.\n 'backups:download': deny,\n 'backups:enable': allow,\n 'backups:list': allow,\n\n // Requires a local Studio project and writes build output to disk.\n build: deny,\n\n // Reads and rewrites local source code.\n codemod: deny,\n\n // --watch polls a job with no deadline; a stuck job would hang the MCP tool\n // call indefinitely. Non-watch invocations are single remote requests.\n 'context:build': conditionalDenyFlags('watch'),\n 'context:create': allow,\n 'context:delete': allow,\n 'context:get': allow,\n // --file reads the import payload from the local filesystem; text, URL and\n // dataset imports are remote-only.\n 'context:imports:create': conditionalDenyFlags('file'),\n 'context:imports:delete': allow,\n 'context:imports:download': allow,\n 'context:imports:get': allow,\n 'context:imports:list': allow,\n // See context:build — --watch has no deadline and would hang the MCP tool call.\n 'context:jobs:get': conditionalDenyFlags('watch'),\n 'context:list': allow,\n 'context:refresh': allow,\n 'context:update': allow,\n\n 'cors:add': allow,\n 'cors:delete': allow,\n 'cors:list': allow,\n\n 'datasets:alias:create': allow,\n 'datasets:alias:delete': allow,\n 'datasets:alias:link': allow,\n 'datasets:alias:unlink': allow,\n 'datasets:copy': allow,\n 'datasets:create': allow,\n 'datasets:delete': allow,\n 'datasets:embeddings:disable': allow,\n 'datasets:embeddings:enable': allow,\n 'datasets:embeddings:status': allow,\n // Writes dataset contents and assets to the local filesystem.\n 'datasets:export': deny,\n // Reads import data from the local filesystem and may replace documents.\n 'datasets:import': deny,\n 'datasets:list': allow,\n 'datasets:visibility:get': allow,\n 'datasets:visibility:set': allow,\n\n // Inspects local project files and can print authentication secrets.\n debug: deny,\n\n // Reads, builds, and deploys a local Studio project.\n deploy: deny,\n\n // Loads a local Studio project and starts a development server.\n dev: deny,\n\n // Opens a browser on the machine running the MCP server.\n 'docs:browse': deny,\n // --web opens a browser on the machine running the MCP server.\n 'docs:read': conditionalDenyFlags('web'),\n 'docs:search': allow,\n\n // Reads and executes local project configuration for diagnostics.\n doctor: deny,\n\n // Reads document input from disk or launches a local editor.\n 'documents:create': deny,\n 'documents:delete': allow,\n 'documents:get': allow,\n 'documents:query': allow,\n // Loads a local Studio schema to validate documents.\n 'documents:validate': deny,\n\n // Executes arbitrary code in the local Studio context.\n exec: deny,\n\n // Loads a local schema and deploys a GraphQL API.\n 'graphql:deploy': deny,\n 'graphql:list': allow,\n // --api loads GraphQL definitions from the local project; explicit project/dataset flags are safe.\n 'graphql:undeploy': conditionalDenyFlags('api'),\n\n 'hooks:attempt': allow,\n 'hooks:create': allow,\n 'hooks:delete': allow,\n 'hooks:list': allow,\n 'hooks:logs': allow,\n\n // Creates or modifies a local project and may install dependencies.\n init: deny,\n\n // Installs packages into the local project.\n install: deny,\n\n // Opens a browser on the machine running the MCP server.\n learn: deny,\n\n // Performs an authentication flow.\n login: deny,\n\n // Performs an authentication operation and clears local credentials.\n logout: deny,\n\n // Reads local project configuration and opens a browser.\n manage: deny,\n\n // Loads local Studio configuration and writes manifest files.\n 'manifest:extract': deny,\n\n // Reads and writes local MCP client configuration.\n 'mcp:configure': deny,\n\n // Writes an aspect definition to the local filesystem.\n 'media:create-aspect': deny,\n 'media:delete-aspect': allow,\n // Reads a local aspect definition before deploying it.\n 'media:deploy-aspect': deny,\n // Writes media assets to the local filesystem.\n 'media:export': deny,\n // A directory or archive source reads media assets from the local\n // filesystem. A URL source does not: Sanity fetches the asset itself, so the\n // only local input is the URL. `--replace-aspects` is meaningless for a URL\n // source (the command rejects the combination), so it is hidden here rather\n // than advertised as usable surface.\n 'media:import': conditionalPolicy({\n deniedFlags: ['replace-aspects'],\n validate: ({args}) => typeof args.source === 'string' && isRemoteAssetSource(args.source),\n }),\n\n // Creates migration source files in the local project.\n 'migrations:create': deny,\n // Reads and loads migration definitions from the local project.\n 'migrations:list': deny,\n // Executes local migration code that may perform arbitrary document mutations.\n 'migrations:run': deny,\n\n // Writes project setup values and scaffolds local applications.\n new: deny,\n\n // --web opens a browser on the machine running the MCP server.\n 'openapi:get': conditionalDenyFlags('web'),\n 'openapi:list': conditionalDenyFlags('web'),\n\n 'organizations:create': allow,\n 'organizations:delete': allow,\n 'organizations:get': allow,\n 'organizations:list': allow,\n 'organizations:update': allow,\n\n // Serves a local production build.\n preview: deny,\n\n 'projects:create': allow,\n 'projects:list': allow,\n // Reads locally stored claim URLs and robot tokens.\n 'projects:unclaimed': deny,\n\n // Loads the local Studio configuration to resolve the datasets containing each schema.\n 'schemas:delete': deny,\n // Loads local schema files before deploying them.\n 'schemas:deploy': deny,\n // Loads local Studio configuration and writes an extracted schema to disk.\n 'schemas:extract': deny,\n // Requires a local project and loads its schema configuration.\n 'schemas:list': deny,\n // Loads and executes a local Studio schema.\n 'schemas:validate': deny,\n\n // Installs skills into local editor configuration directories.\n 'skills:install': deny,\n\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:disable': deny,\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:enable': deny,\n 'telemetry:status': allow,\n\n // Creates authentication credentials.\n 'tokens:create': deny,\n // Deletes authentication credentials.\n 'tokens:delete': deny,\n // Exposes authentication credential metadata.\n 'tokens:list': deny,\n // Replaces authentication credentials and exposes the new secret.\n 'tokens:rotate': deny,\n\n // Reads the local schema and source files and writes generated types to the project.\n 'typegen:generate': deny,\n\n // Loads local CLI and workbench configuration to identify the deployed Studio or application.\n undeploy: deny,\n\n // Grants a user access to a project.\n 'users:invite': deny,\n 'users:list': allow,\n\n // Reads the local project and installed package tree.\n versions: deny,\n}\n"],"names":["allow","conditionalDenyFlags","conditionalPolicy","deny","isRemoteAssetSource","apiValidator","args","flags","fields","Array","isArray","field","fieldReadsFromHost","some","separatorIndex","indexOf","headers","header","headerOverridesAuthentication","name","slice","trim","toLowerCase","endpoint","URL","parse","urlEmbedsCredentials","username","password","mcpPolicy","api","deniedFlags","validate","length","build","codemod","debug","deploy","dev","doctor","exec","init","install","learn","login","logout","manage","source","new","preview","undeploy","versions"],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Builds the effective policy table for one invocation source, combining what
3
+ * this CLI declares for its own commands with what each plugin declares for
4
+ * the commands it contributes.
5
+ *
6
+ * The table is deny-by-default in both directions: a command with no entry is
7
+ * denied by the caller (`policySet[id] ?? deny`), and every step here that
8
+ * cannot establish permission simply omits an entry rather than falling back
9
+ * to something permissive.
10
+ */ import { resolve } from 'node:path';
11
+ import { isCommandPolicySet } from '@sanity/cli-core/commandPolicy';
12
+ import { subdebug } from '@sanity/cli-core/debug';
13
+ import { doImport } from '@sanity/cli-core/util';
14
+ import { mcpPolicy } from './mcpPolicy.js';
15
+ const debug = subdebug('commandPolicies');
16
+ /** Policies this CLI declares for its own commands, by invocation source. */ export const commandPolicies = {
17
+ mcp: mcpPolicy
18
+ };
19
+ /**
20
+ * Resolution reads plugin package.json files and imports their policy
21
+ * modules, so it is async and depends on the loaded oclif config. Memoized
22
+ * per config so that work happens once per process rather than per invocation.
23
+ */ const cache = new WeakMap();
24
+ export function resolveCommandPolicies(config, source) {
25
+ let bySource = cache.get(config);
26
+ if (!bySource) {
27
+ bySource = new Map();
28
+ cache.set(config, bySource);
29
+ }
30
+ let resolved = bySource.get(source);
31
+ if (!resolved) {
32
+ resolved = buildCommandPolicies(config, source);
33
+ bySource.set(source, resolved);
34
+ }
35
+ return resolved;
36
+ }
37
+ async function buildCommandPolicies(config, source) {
38
+ const hostName = config.pjson.name;
39
+ const entries = {};
40
+ for (const [id, policy] of Object.entries(commandPolicies[source])){
41
+ entries[id] = {
42
+ ...policy,
43
+ declaredBy: hostName
44
+ };
45
+ }
46
+ for (const plugin of config.plugins.values()){
47
+ if (plugin.name === hostName) continue;
48
+ // Only plugins this CLI declares itself should contribute commands.
49
+ // oclif loads plugins recursively, so a dependency of a plugin also lands here.
50
+ if (plugin.parent) {
51
+ debug('ignoring %s, contributed by %s rather than declared here', plugin.name, plugin.parent.name);
52
+ continue;
53
+ }
54
+ const declared = await loadPluginPolicies(plugin, source);
55
+ if (!declared) continue;
56
+ // Plugin policy can only be set for commands they actually contributed.
57
+ const contributed = new Set(plugin.commands.map((command)=>command.id));
58
+ for (const [id, policy] of Object.entries(declared)){
59
+ if (!contributed.has(id)) {
60
+ debug('%s declared a policy for %s, which it does not contribute', plugin.name, id);
61
+ continue;
62
+ }
63
+ // Ids already claimed keep their existing declaration, mirroring
64
+ // oclif's own first-one-wins command resolution.
65
+ if (entries[id]) continue;
66
+ entries[id] = {
67
+ ...policy,
68
+ declaredBy: plugin.name
69
+ };
70
+ }
71
+ }
72
+ return entries;
73
+ }
74
+ /**
75
+ * Load the policy table a plugin declares for one invocation source, or
76
+ * `undefined` when it declares none, points at a module that cannot be
77
+ * loaded, or exports something that is not a policy table.
78
+ *
79
+ * Every failure is silent and non-fatal by design. A plugin's policy module
80
+ * is third-party code resolved at runtime: it must not be able to take down
81
+ * the whole invocation surface by throwing, and it must not be able to reach
82
+ * a permissive outcome by being malformed. Returning `undefined` leaves the
83
+ * plugin's commands uncategorized, which the caller treats as denied.
84
+ */ async function loadPluginPolicies(plugin, source) {
85
+ const declaredPath = readInvocationPoliciesPath(plugin);
86
+ if (!declaredPath) return undefined;
87
+ try {
88
+ const loaded = await doImport(resolve(plugin.root, declaredPath));
89
+ const policies = loaded.invocationPolicies;
90
+ if (typeof policies !== 'object' || policies === null) {
91
+ debug('%s policy module has no usable `invocationPolicies` export', plugin.name);
92
+ return undefined;
93
+ }
94
+ const forSource = policies[source];
95
+ if (forSource === undefined) return undefined;
96
+ if (!isCommandPolicySet(forSource)) {
97
+ debug('%s declared a malformed policy table for %s', plugin.name, source);
98
+ return undefined;
99
+ }
100
+ return forSource;
101
+ } catch (err) {
102
+ debug('failed to load policy module for %s: %s', plugin.name, err);
103
+ return undefined;
104
+ }
105
+ }
106
+ function readInvocationPoliciesPath(plugin) {
107
+ const { sanity } = plugin.pjson;
108
+ const declared = sanity?.invocationPolicies;
109
+ return typeof declared === 'string' && declared !== '' ? declared : undefined;
110
+ }
111
+
112
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/exports/invokeSanityCli/commandPolicies/resolve.ts"],"sourcesContent":["/**\n * Builds the effective policy table for one invocation source, combining what\n * this CLI declares for its own commands with what each plugin declares for\n * the commands it contributes.\n *\n * The table is deny-by-default in both directions: a command with no entry is\n * denied by the caller (`policySet[id] ?? deny`), and every step here that\n * cannot establish permission simply omits an entry rather than falling back\n * to something permissive.\n */\nimport {resolve} from 'node:path'\n\nimport {type Config, type Interfaces} from '@oclif/core'\nimport {\n type CommandPolicy,\n type CommandPolicySet,\n type InvocationSource,\n isCommandPolicySet,\n} from '@sanity/cli-core/commandPolicy'\nimport {subdebug} from '@sanity/cli-core/debug'\nimport {doImport} from '@sanity/cli-core/util'\n\nimport {mcpPolicy} from './mcpPolicy.js'\n\nconst debug = subdebug('commandPolicies')\n\n/** Policies this CLI declares for its own commands, by invocation source. */\nexport const commandPolicies: Record<InvocationSource, CommandPolicySet> = {\n mcp: mcpPolicy,\n}\n\n/**\n * Policy entries annotated with the package that declared each one.\n */\nexport type ResolvedCommandPolicySet = Readonly<\n Record<string, CommandPolicy & {declaredBy: string}>\n>\n\ntype ResolvedCommandPolicy = ResolvedCommandPolicySet[string]\n\n/**\n * Resolution reads plugin package.json files and imports their policy\n * modules, so it is async and depends on the loaded oclif config. Memoized\n * per config so that work happens once per process rather than per invocation.\n */\nconst cache = new WeakMap<Config, Map<InvocationSource, Promise<ResolvedCommandPolicySet>>>()\n\nexport function resolveCommandPolicies(\n config: Config,\n source: InvocationSource,\n): Promise<ResolvedCommandPolicySet> {\n let bySource = cache.get(config)\n if (!bySource) {\n bySource = new Map()\n cache.set(config, bySource)\n }\n\n let resolved = bySource.get(source)\n if (!resolved) {\n resolved = buildCommandPolicies(config, source)\n bySource.set(source, resolved)\n }\n return resolved\n}\n\nasync function buildCommandPolicies(\n config: Config,\n source: InvocationSource,\n): Promise<ResolvedCommandPolicySet> {\n const hostName = config.pjson.name\n const entries: Record<string, ResolvedCommandPolicy> = {}\n\n for (const [id, policy] of Object.entries(commandPolicies[source])) {\n entries[id] = {...policy, declaredBy: hostName}\n }\n\n for (const plugin of config.plugins.values()) {\n if (plugin.name === hostName) continue\n\n // Only plugins this CLI declares itself should contribute commands.\n // oclif loads plugins recursively, so a dependency of a plugin also lands here.\n if (plugin.parent) {\n debug(\n 'ignoring %s, contributed by %s rather than declared here',\n plugin.name,\n plugin.parent.name,\n )\n continue\n }\n\n const declared = await loadPluginPolicies(plugin, source)\n if (!declared) continue\n\n // Plugin policy can only be set for commands they actually contributed.\n const contributed = new Set(plugin.commands.map((command) => command.id))\n\n for (const [id, policy] of Object.entries(declared)) {\n if (!contributed.has(id)) {\n debug('%s declared a policy for %s, which it does not contribute', plugin.name, id)\n continue\n }\n // Ids already claimed keep their existing declaration, mirroring\n // oclif's own first-one-wins command resolution.\n if (entries[id]) continue\n\n entries[id] = {...policy, declaredBy: plugin.name}\n }\n }\n\n return entries\n}\n\n/**\n * Load the policy table a plugin declares for one invocation source, or\n * `undefined` when it declares none, points at a module that cannot be\n * loaded, or exports something that is not a policy table.\n *\n * Every failure is silent and non-fatal by design. A plugin's policy module\n * is third-party code resolved at runtime: it must not be able to take down\n * the whole invocation surface by throwing, and it must not be able to reach\n * a permissive outcome by being malformed. Returning `undefined` leaves the\n * plugin's commands uncategorized, which the caller treats as denied.\n */\nasync function loadPluginPolicies(\n plugin: Interfaces.Plugin,\n source: InvocationSource,\n): Promise<CommandPolicySet | undefined> {\n const declaredPath = readInvocationPoliciesPath(plugin)\n if (!declaredPath) return undefined\n\n try {\n const loaded: unknown = await doImport(resolve(plugin.root, declaredPath))\n const policies = (loaded as {invocationPolicies?: unknown}).invocationPolicies\n\n if (typeof policies !== 'object' || policies === null) {\n debug('%s policy module has no usable `invocationPolicies` export', plugin.name)\n return undefined\n }\n\n const forSource = (policies as Record<string, unknown>)[source]\n if (forSource === undefined) return undefined\n\n if (!isCommandPolicySet(forSource)) {\n debug('%s declared a malformed policy table for %s', plugin.name, source)\n return undefined\n }\n\n return forSource\n } catch (err) {\n debug('failed to load policy module for %s: %s', plugin.name, err)\n return undefined\n }\n}\n\nfunction readInvocationPoliciesPath(plugin: Interfaces.Plugin): string | undefined {\n const {sanity} = plugin.pjson as {sanity?: {invocationPolicies?: unknown}}\n const declared = sanity?.invocationPolicies\n return typeof declared === 'string' && declared !== '' ? declared : undefined\n}\n"],"names":["resolve","isCommandPolicySet","subdebug","doImport","mcpPolicy","debug","commandPolicies","mcp","cache","WeakMap","resolveCommandPolicies","config","source","bySource","get","Map","set","resolved","buildCommandPolicies","hostName","pjson","name","entries","id","policy","Object","declaredBy","plugin","plugins","values","parent","declared","loadPluginPolicies","contributed","Set","commands","map","command","has","declaredPath","readInvocationPoliciesPath","undefined","loaded","root","policies","invocationPolicies","forSource","err","sanity"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { Help } from '@oclif/core';
2
2
  import { getHelpFlagAdditions } from '@oclif/core/help';
3
+ import { isConditionalInvocationPolicy } from '@sanity/cli-core/commandPolicy';
3
4
  import { resolveTopicAliasInArgv } from '../../topicAliases.js';
4
- import { isConditionalInvocationPolicy } from './commandPolicies/policy.js';
5
5
  /** Command ids a policy exposes: entries that are not denied. */ function visibleCommandIds(policySet) {
6
6
  return new Set(Object.entries(policySet).filter(([, policy])=>policy.kind !== 'deny').map(([id])=>id));
7
7
  }
@@ -74,6 +74,13 @@ import { isConditionalInvocationPolicy } from './commandPolicies/policy.js';
74
74
  this.commandIds = visibleCommandIds(policySet);
75
75
  this.topicNames = visibleTopicNames(this.commandIds);
76
76
  }
77
+ // Both listings start from oclif's, which already drops `hidden` entries, so
78
+ // help advertises the same commands a CLI user sees, minus the ones the
79
+ // policy denies. Hiding and denying answer different questions — "should this
80
+ // be advertised?" and "may this be invoked?" — and a command that is hidden
81
+ // but allowed stays invocable by id, exactly as it is for a CLI user who
82
+ // knows the name.
83
+ //
77
84
  // The copies below are load-bearing: oclif's formatters rewrite ids/names
78
85
  // in place (`cors:list` → `cors list`). Without copies those writes corrupt
79
86
  // the shared (cached) config, breaking the policy checks on subsequent
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/exports/invokeSanityCli/help.ts"],"sourcesContent":["import {type Command, type Config, Help, type Interfaces} from '@oclif/core'\nimport {getHelpFlagAdditions} from '@oclif/core/help'\n\nimport {resolveTopicAliasInArgv} from '../../topicAliases.js'\nimport {\n type CommandPolicy,\n type CommandPolicySet,\n isConditionalInvocationPolicy,\n} from './commandPolicies/policy.js'\n\n/** Command ids a policy exposes: entries that are not denied. */\nfunction visibleCommandIds(policySet: CommandPolicySet): Set<string> {\n return new Set(\n Object.entries(policySet)\n .filter(([, policy]) => policy.kind !== 'deny')\n .map(([id]) => id),\n )\n}\n\n/** Topic names covering the visible commands: every prefix of every id. */\nfunction visibleTopicNames(commandIds: Set<string>): Set<string> {\n const names = new Set<string>()\n for (const id of commandIds) {\n const parts = id.split(':')\n for (let length = 1; length < parts.length; length++) {\n names.add(parts.slice(0, length).join(':'))\n }\n }\n return names\n}\n\n/** Thrown when help is requested for a subject outside the policy surface. */\nclass NotInvokableError extends Error {}\n\n/**\n * A copy of `command` without the policy's denied flags, and without examples\n * that use them (under any spelling: `--web`, `--w`, `-w`), so rendered help\n * only advertises invocations the policy accepts.\n */\nfunction withoutDeniedFlags(command: Command.Loadable, policy?: CommandPolicy): Command.Loadable {\n if (!policy || !isConditionalInvocationPolicy(policy)) return {...command}\n\n const denied = policy.deniedFlags\n if (denied.length === 0) return {...command}\n\n const flags = {...command.flags}\n const spellings: string[] = []\n for (const name of denied) {\n const definition = flags[name]\n if (!definition) continue\n spellings.push(`--${name}`)\n if (definition.char) spellings.push(`-${definition.char}`)\n for (const alias of definition.aliases ?? []) spellings.push(`--${alias}`, `-${alias}`)\n delete flags[name]\n }\n\n const usesDeniedFlag = (example: Command.Example) => {\n const text = typeof example === 'string' ? example : example.command\n return spellings.some((spelling) =>\n new RegExp(String.raw`(^|\\s)${spelling}(=|\\s|$)`).test(text ?? ''),\n )\n }\n\n return {\n ...command,\n examples: command.examples?.filter((example) => !usesDeniedFlag(example)),\n flags,\n }\n}\n\n/**\n * oclif help renderer scoped to a policy's command surface. Subject\n * resolution (root vs topic vs command help) is oclif's own; the policy is\n * enforced at the rendering entry points so the rest of the CLI stays\n * invisible to embedding hosts, and listings are filtered to non-denied\n * commands and their topics only.\n *\n * Output is collected in {@link InvokableHelp.lines} instead of being written\n * to the process streams.\n */\nclass InvokableHelp extends Help {\n public commandId: string | undefined\n public readonly lines: string[] = []\n\n private readonly commandIds: Set<string>\n private readonly policySet: CommandPolicySet\n private readonly topicNames: Set<string>\n\n constructor(config: Config, policySet: CommandPolicySet) {\n super(config, {stripAnsi: true})\n this.policySet = policySet\n this.commandIds = visibleCommandIds(policySet)\n this.topicNames = visibleTopicNames(this.commandIds)\n }\n\n // The copies below are load-bearing: oclif's formatters rewrite ids/names\n // in place (`cors:list` → `cors list`). Without copies those writes corrupt\n // the shared (cached) config, breaking the policy checks on subsequent\n // invocations.\n\n protected override get sortedCommands(): Command.Loadable[] {\n return super.sortedCommands\n .filter((command) => this.commandIds.has(command.id))\n .map((command) => ({...command}))\n }\n\n protected override get sortedTopics(): Interfaces.Topic[] {\n // Topic names and descriptions come from the oclif config (oclif.config.js)\n return super.sortedTopics\n .filter((topic) => this.topicNames.has(topic.name))\n .map((topic) => ({...topic}))\n }\n\n protected override log(...args: string[]): void {\n this.lines.push(...args)\n }\n\n public override async showCommandHelp(command: Command.Loadable): Promise<void> {\n if (!this.commandIds.has(command.id)) throw new NotInvokableError(command.id)\n this.commandId = command.id\n return super.showCommandHelp(withoutDeniedFlags(command, this.policySet[command.id]))\n }\n\n public override async showHelp(argv: string[]): Promise<void> {\n return super.showHelp(resolveTopicAliasInArgv(argv))\n }\n\n protected override async showTopicHelp(topic: Interfaces.Topic): Promise<void> {\n if (!this.topicNames.has(topic.name)) throw new NotInvokableError(topic.name)\n return super.showTopicHelp({...topic})\n }\n}\n\n/**\n * Render help text for a policy's command surface: root help for a bare help\n * request, or topic/command help when `argv` names a subject (e.g.\n * `['cors', '--help']`), exactly as oclif would resolve it. ANSI styling is\n * stripped so programmatic callers get plain text.\n *\n * Returns `undefined` when the subject is unknown or denied — callers should\n * respond the same way as for an unknown command, so hosts cannot probe the\n * full CLI surface through help.\n *\n * @internal\n */\nexport async function renderInvokableHelp(\n config: Config,\n argv: string[],\n policySet: CommandPolicySet,\n): Promise<{commandId?: string; output: string} | undefined> {\n const help = new InvokableHelp(config, policySet)\n try {\n await help.showHelp(argv)\n } catch (err) {\n // Subjects outside the policy surface (NotInvokableError) and subjects\n // oclif itself cannot resolve (CLIError, marked with an `oclif` property)\n // both yield no help output.\n if (err instanceof NotInvokableError || err.oclif) return undefined\n throw err\n }\n return {\n ...(help.commandId && {commandId: help.commandId}),\n output: help.lines.join('\\n').trimEnd(),\n }\n}\n\n/**\n * Whether `argv` asks for help rather than a command invocation: a leading\n * `help` (oclif's help command), or a recognized help flag before any `--`\n * terminator. The flags come from {@link getHelpFlagAdditions} — the same\n * source oclif's own dispatch consults — so this surface recognizes exactly\n * what the regular CLI recognizes.\n *\n * @internal\n */\nexport function isHelpRequest(argv: string[], config: Config): boolean {\n if (argv[0] === 'help') return true\n const helpFlags = getHelpFlagAdditions(config)\n for (const token of argv) {\n if (token === '--') return false\n if (helpFlags.includes(token)) return true\n }\n return false\n}\n"],"names":["Help","getHelpFlagAdditions","resolveTopicAliasInArgv","isConditionalInvocationPolicy","visibleCommandIds","policySet","Set","Object","entries","filter","policy","kind","map","id","visibleTopicNames","commandIds","names","parts","split","length","add","slice","join","NotInvokableError","Error","withoutDeniedFlags","command","denied","deniedFlags","flags","spellings","name","definition","push","char","alias","aliases","usesDeniedFlag","example","text","some","spelling","RegExp","String","raw","test","examples","InvokableHelp","commandId","lines","topicNames","config","stripAnsi","sortedCommands","has","sortedTopics","topic","log","args","showCommandHelp","showHelp","argv","showTopicHelp","renderInvokableHelp","help","err","oclif","undefined","output","trimEnd","isHelpRequest","helpFlags","token","includes"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/exports/invokeSanityCli/help.ts"],"sourcesContent":["import {type Command, type Config, Help, type Interfaces} from '@oclif/core'\nimport {getHelpFlagAdditions} from '@oclif/core/help'\nimport {\n type CommandPolicy,\n type CommandPolicySet,\n isConditionalInvocationPolicy,\n} from '@sanity/cli-core/commandPolicy'\n\nimport {resolveTopicAliasInArgv} from '../../topicAliases.js'\n\n/** Command ids a policy exposes: entries that are not denied. */\nfunction visibleCommandIds(policySet: CommandPolicySet): Set<string> {\n return new Set(\n Object.entries(policySet)\n .filter(([, policy]) => policy.kind !== 'deny')\n .map(([id]) => id),\n )\n}\n\n/** Topic names covering the visible commands: every prefix of every id. */\nfunction visibleTopicNames(commandIds: Set<string>): Set<string> {\n const names = new Set<string>()\n for (const id of commandIds) {\n const parts = id.split(':')\n for (let length = 1; length < parts.length; length++) {\n names.add(parts.slice(0, length).join(':'))\n }\n }\n return names\n}\n\n/** Thrown when help is requested for a subject outside the policy surface. */\nclass NotInvokableError extends Error {}\n\n/**\n * A copy of `command` without the policy's denied flags, and without examples\n * that use them (under any spelling: `--web`, `--w`, `-w`), so rendered help\n * only advertises invocations the policy accepts.\n */\nfunction withoutDeniedFlags(command: Command.Loadable, policy?: CommandPolicy): Command.Loadable {\n if (!policy || !isConditionalInvocationPolicy(policy)) return {...command}\n\n const denied = policy.deniedFlags\n if (denied.length === 0) return {...command}\n\n const flags = {...command.flags}\n const spellings: string[] = []\n for (const name of denied) {\n const definition = flags[name]\n if (!definition) continue\n spellings.push(`--${name}`)\n if (definition.char) spellings.push(`-${definition.char}`)\n for (const alias of definition.aliases ?? []) spellings.push(`--${alias}`, `-${alias}`)\n delete flags[name]\n }\n\n const usesDeniedFlag = (example: Command.Example) => {\n const text = typeof example === 'string' ? example : example.command\n return spellings.some((spelling) =>\n new RegExp(String.raw`(^|\\s)${spelling}(=|\\s|$)`).test(text ?? ''),\n )\n }\n\n return {\n ...command,\n examples: command.examples?.filter((example) => !usesDeniedFlag(example)),\n flags,\n }\n}\n\n/**\n * oclif help renderer scoped to a policy's command surface. Subject\n * resolution (root vs topic vs command help) is oclif's own; the policy is\n * enforced at the rendering entry points so the rest of the CLI stays\n * invisible to embedding hosts, and listings are filtered to non-denied\n * commands and their topics only.\n *\n * Output is collected in {@link InvokableHelp.lines} instead of being written\n * to the process streams.\n */\nclass InvokableHelp extends Help {\n public commandId: string | undefined\n public readonly lines: string[] = []\n\n private readonly commandIds: Set<string>\n private readonly policySet: CommandPolicySet\n private readonly topicNames: Set<string>\n\n constructor(config: Config, policySet: CommandPolicySet) {\n super(config, {stripAnsi: true})\n this.policySet = policySet\n this.commandIds = visibleCommandIds(policySet)\n this.topicNames = visibleTopicNames(this.commandIds)\n }\n\n // Both listings start from oclif's, which already drops `hidden` entries, so\n // help advertises the same commands a CLI user sees, minus the ones the\n // policy denies. Hiding and denying answer different questions — \"should this\n // be advertised?\" and \"may this be invoked?\" — and a command that is hidden\n // but allowed stays invocable by id, exactly as it is for a CLI user who\n // knows the name.\n //\n // The copies below are load-bearing: oclif's formatters rewrite ids/names\n // in place (`cors:list` → `cors list`). Without copies those writes corrupt\n // the shared (cached) config, breaking the policy checks on subsequent\n // invocations.\n\n protected override get sortedCommands(): Command.Loadable[] {\n return super.sortedCommands\n .filter((command) => this.commandIds.has(command.id))\n .map((command) => ({...command}))\n }\n\n protected override get sortedTopics(): Interfaces.Topic[] {\n // Topic names and descriptions come from the oclif config (oclif.config.js)\n return super.sortedTopics\n .filter((topic) => this.topicNames.has(topic.name))\n .map((topic) => ({...topic}))\n }\n\n protected override log(...args: string[]): void {\n this.lines.push(...args)\n }\n\n public override async showCommandHelp(command: Command.Loadable): Promise<void> {\n if (!this.commandIds.has(command.id)) throw new NotInvokableError(command.id)\n this.commandId = command.id\n return super.showCommandHelp(withoutDeniedFlags(command, this.policySet[command.id]))\n }\n\n public override async showHelp(argv: string[]): Promise<void> {\n return super.showHelp(resolveTopicAliasInArgv(argv))\n }\n\n protected override async showTopicHelp(topic: Interfaces.Topic): Promise<void> {\n if (!this.topicNames.has(topic.name)) throw new NotInvokableError(topic.name)\n return super.showTopicHelp({...topic})\n }\n}\n\n/**\n * Render help text for a policy's command surface: root help for a bare help\n * request, or topic/command help when `argv` names a subject (e.g.\n * `['cors', '--help']`), exactly as oclif would resolve it. ANSI styling is\n * stripped so programmatic callers get plain text.\n *\n * Returns `undefined` when the subject is unknown or denied — callers should\n * respond the same way as for an unknown command, so hosts cannot probe the\n * full CLI surface through help.\n *\n * @internal\n */\nexport async function renderInvokableHelp(\n config: Config,\n argv: string[],\n policySet: CommandPolicySet,\n): Promise<{commandId?: string; output: string} | undefined> {\n const help = new InvokableHelp(config, policySet)\n try {\n await help.showHelp(argv)\n } catch (err) {\n // Subjects outside the policy surface (NotInvokableError) and subjects\n // oclif itself cannot resolve (CLIError, marked with an `oclif` property)\n // both yield no help output.\n if (err instanceof NotInvokableError || err.oclif) return undefined\n throw err\n }\n return {\n ...(help.commandId && {commandId: help.commandId}),\n output: help.lines.join('\\n').trimEnd(),\n }\n}\n\n/**\n * Whether `argv` asks for help rather than a command invocation: a leading\n * `help` (oclif's help command), or a recognized help flag before any `--`\n * terminator. The flags come from {@link getHelpFlagAdditions} — the same\n * source oclif's own dispatch consults — so this surface recognizes exactly\n * what the regular CLI recognizes.\n *\n * @internal\n */\nexport function isHelpRequest(argv: string[], config: Config): boolean {\n if (argv[0] === 'help') return true\n const helpFlags = getHelpFlagAdditions(config)\n for (const token of argv) {\n if (token === '--') return false\n if (helpFlags.includes(token)) return true\n }\n return false\n}\n"],"names":["Help","getHelpFlagAdditions","isConditionalInvocationPolicy","resolveTopicAliasInArgv","visibleCommandIds","policySet","Set","Object","entries","filter","policy","kind","map","id","visibleTopicNames","commandIds","names","parts","split","length","add","slice","join","NotInvokableError","Error","withoutDeniedFlags","command","denied","deniedFlags","flags","spellings","name","definition","push","char","alias","aliases","usesDeniedFlag","example","text","some","spelling","RegExp","String","raw","test","examples","InvokableHelp","commandId","lines","topicNames","config","stripAnsi","sortedCommands","has","sortedTopics","topic","log","args","showCommandHelp","showHelp","argv","showTopicHelp","renderInvokableHelp","help","err","oclif","undefined","output","trimEnd","isHelpRequest","helpFlags","token","includes"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import { Config } from "@oclif/core";
2
+ import { InvocationSource } from "@sanity/cli-core/commandPolicy";
2
3
  import { SanityEnvironment } from "@sanity/cli-core/executionContext";
3
4
  /**
4
5
  * Valid body types for fetch requests.
@@ -89,8 +90,6 @@ interface FetchResponse {
89
90
  * @public
90
91
  */
91
92
  type FetchFunction = (input: string, init?: FetchInit) => Promise<FetchResponse>;
92
- /** Where an invocation originates; selects the policy to enforce. */
93
- type InvocationSource = 'mcp';
94
93
  /**
95
94
  * @internal
96
95
  */