@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
@@ -2,7 +2,9 @@
2
2
  * Programmatic (in-process) invocation of CLI commands, e.g. from an MCP
3
3
  * server. The invokable surface is governed by a per-source command policy
4
4
  * (see ./commandPolicies): every CLI command is explicitly allowed, denied,
5
- * or allowed conditionally on the parsed invocation.
5
+ * or allowed conditionally on the parsed invocation. Commands contributed by
6
+ * plugins are covered by the policies those plugins declare, and are denied
7
+ * unless they declare one.
6
8
  *
7
9
  * {@link invokeSanityCli} handles arg parsing, policy enforcement, command
8
10
  * dispatch, per-invocation auth, and output capture.
@@ -19,34 +21,42 @@
19
21
  * Help works like the regular CLI, scoped to the source's policy: `--help`
20
22
  * (or `help`) renders root help listing the invokable topics, and a subject
21
23
  * (`cors --help`, `cors list --help`) renders topic or command help.
22
- */ import { Config, Parser, settings } from '@oclif/core';
24
+ */ import { fileURLToPath } from 'node:url';
25
+ import { Config, Parser, settings } from '@oclif/core';
23
26
  import { getHelpFlagAdditions, normalizeArgv } from '@oclif/core/help';
24
27
  import { exitCodes } from '@sanity/cli-core';
28
+ import { deny, isConditionalInvocationPolicy } from '@sanity/cli-core/commandPolicy';
25
29
  import { runWithCliExecutionContext } from '@sanity/cli-core/executionContext';
26
30
  import { parseArgsStringToArgv } from 'string-argv';
27
31
  import { resolveTopicAliasInArgv } from '../../topicAliases.js';
28
- import { commandPolicies } from './commandPolicies/index.js';
29
- import { deny, isConditionalInvocationPolicy } from './commandPolicies/policy.js';
32
+ import { resolveCommandPolicies } from './commandPolicies/index.js';
30
33
  import { isHelpRequest, renderInvokableHelp } from './help.js';
31
34
  import { prettyPrintError } from './prettyPrintError.js';
35
+ /**
36
+ * Whether a command can run in isolation, which in practice means it extends
37
+ * `SanityCommand`: only that base class routes output, token resolution,
38
+ * interactivity, and project discovery through the CLI execution context.
39
+ *
40
+ * This is a capability check, not a policy check. A plugin can declare a
41
+ * policy for a command that still extends oclif's `Command` directly, and
42
+ * such a command must not run here — the policy says the invocation is safe,
43
+ * but nothing would hold it to the isolation guarantees that assessment
44
+ * assumes.
45
+ */ function supportsIsolatedExecution(CommandClass) {
46
+ const prototype = CommandClass.prototype;
47
+ return typeof prototype?.runInExecutionContext === 'function';
48
+ }
32
49
  /**
33
50
  * Instantiate a policy-approved command for isolated execution.
34
51
  *
35
- * Every invokable command extends `SanityCommand`, whose
36
52
  * `runInExecutionContext()` runs the instance without oclif's static
37
53
  * `Command.run()` — the static runner re-enters `Config.load()` (filesystem
38
54
  * reads and a process-global config cache write) on every call. The manifest
39
55
  * types command classes as the abstract oclif `Command`, hence the
40
- * constructor cast. The method is probed rather than checked with
41
- * `instanceof`, because command modules may load from the compiled build
42
- * while this module runs from source, giving `SanityCommand` two identities.
56
+ * constructor cast.
43
57
  */ function instantiateCommand(CommandClass, argv, config) {
44
58
  const ConcreteCommand = CommandClass;
45
- const command = new ConcreteCommand(argv, config);
46
- if (typeof command.runInExecutionContext !== 'function') {
47
- throw new TypeError(`Command "${CommandClass.id}" does not support isolated execution`);
48
- }
49
- return command;
59
+ return new ConcreteCommand(argv, config);
50
60
  }
51
61
  /**
52
62
  * Load the oclif `Config` for this package, needed to resolve, load, and run
@@ -54,8 +64,14 @@ import { prettyPrintError } from './prettyPrintError.js';
54
64
  * re-reading the command manifest per call. It only reads this package's own
55
65
  * installed files — process-lifetime initialization, not per-invocation host
56
66
  * state — so it happens outside any execution context.
67
+ *
68
+ * `userPlugins: false` keeps this surface to the plugins this package ships
69
+ * with. oclif otherwise loads whatever `<dataDir>/package.json` lists.
57
70
  */ function loadCliCommandConfig() {
58
- return Config.load(import.meta.url);
71
+ return Config.load({
72
+ root: fileURLToPath(import.meta.url),
73
+ userPlugins: false
74
+ });
59
75
  }
60
76
  let cachedConfig;
61
77
  function unknownCommandResult(argv, policySet) {
@@ -106,7 +122,9 @@ function unknownCommandResult(argv, policySet) {
106
122
  }, ()=>invokeSanityCliInContext(options, resolvedConfig, output));
107
123
  }
108
124
  async function invokeSanityCliInContext({ args, source }, resolvedConfig, output) {
109
- const policySet = commandPolicies[source];
125
+ // Combines this package's own policies with those each plugin declares for
126
+ // the commands it contributes. Resolved once per config, not per call.
127
+ const policySet = await resolveCommandPolicies(resolvedConfig, source);
110
128
  // Pre-split argv arrays are taken verbatim; only string input goes through
111
129
  // shell-style tokenization and quote normalization.
112
130
  let argv = typeof args === 'string' ? parseArgsStringToArgv(args).map((t)=>stripFlagQuotes(t)) : [
@@ -148,6 +166,10 @@ async function invokeSanityCliInContext({ args, source }, resolvedConfig, output
148
166
  const commandDefinition = policy.kind === 'deny' ? undefined : resolvedConfig.findCommand(commandId);
149
167
  if (!commandDefinition) return unknownCommandResult(argv, policySet);
150
168
  const CommandClass = await commandDefinition.load();
169
+ // A policy can only speak to whether an invocation is safe; it cannot make
170
+ // a command isolatable. One that isn't is treated as if it did not exist,
171
+ // like every other fail-closed path here.
172
+ if (!supportsIsolatedExecution(CommandClass)) return unknownCommandResult(argv, policySet);
151
173
  // Parse with the command's real definitions (without executing anything) so
152
174
  // conditional policies are evaluated against typed args/flags, not tokens.
153
175
  let invocation;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/exports/invokeSanityCli/index.ts"],"sourcesContent":["/**\n * Programmatic (in-process) invocation of CLI commands, e.g. from an MCP\n * server. The invokable surface is governed by a per-source command policy\n * (see ./commandPolicies): every CLI command is explicitly allowed, denied,\n * or allowed conditionally on the parsed invocation.\n *\n * {@link invokeSanityCli} handles arg parsing, policy enforcement, command\n * dispatch, per-invocation auth, and output capture.\n * ```ts\n * import {invokeSanityCli} from '@sanity/cli/invokeSanityCli'\n *\n * const {exitCode, output} = await invokeSanityCli({\n * args: 'cors list --project-id abc123',\n * source: 'mcp',\n * token: extra.authInfo.token,\n * })\n * ```\n *\n * Help works like the regular CLI, scoped to the source's policy: `--help`\n * (or `help`) renders root help listing the invokable topics, and a subject\n * (`cors --help`, `cors list --help`) renders topic or command help.\n */\nimport {Command, Config, Parser, settings} from '@oclif/core'\nimport {getHelpFlagAdditions, normalizeArgv} from '@oclif/core/help'\nimport {exitCodes} from '@sanity/cli-core'\nimport {runWithCliExecutionContext, type SanityEnvironment} from '@sanity/cli-core/executionContext'\nimport {type SanityCommand} from '@sanity/cli-core/SanityCommand'\nimport {type FetchFunction} from 'get-it'\nimport {parseArgsStringToArgv} from 'string-argv'\n\nimport {resolveTopicAliasInArgv} from '../../topicAliases.js'\nimport {commandPolicies} from './commandPolicies/index.js'\nimport {\n type CommandPolicySet,\n deny,\n type InvocationSource,\n isConditionalInvocationPolicy,\n} from './commandPolicies/policy.js'\nimport {isHelpRequest, renderInvokableHelp} from './help.js'\nimport {prettyPrintError} from './prettyPrintError.js'\n\ntype InvokableCommand = Pick<SanityCommand<typeof Command>, 'runInExecutionContext'>\n\n/**\n * Instantiate a policy-approved command for isolated execution.\n *\n * Every invokable command extends `SanityCommand`, whose\n * `runInExecutionContext()` runs the instance without oclif's static\n * `Command.run()` — the static runner re-enters `Config.load()` (filesystem\n * reads and a process-global config cache write) on every call. The manifest\n * types command classes as the abstract oclif `Command`, hence the\n * constructor cast. The method is probed rather than checked with\n * `instanceof`, because command modules may load from the compiled build\n * while this module runs from source, giving `SanityCommand` two identities.\n */\nfunction instantiateCommand(\n CommandClass: Command.Class,\n argv: string[],\n config: Config,\n): InvokableCommand {\n const ConcreteCommand = CommandClass as unknown as new (argv: string[], config: Config) => Command\n const command = new ConcreteCommand(argv, config) as Command & Partial<InvokableCommand>\n if (typeof command.runInExecutionContext !== 'function') {\n throw new TypeError(`Command \"${CommandClass.id}\" does not support isolated execution`)\n }\n return command as InvokableCommand\n}\n\n/**\n * Load the oclif `Config` for this package, needed to resolve, load, and run\n * commands. Loading it once and reusing it across invocations avoids\n * re-reading the command manifest per call. It only reads this package's own\n * installed files — process-lifetime initialization, not per-invocation host\n * state — so it happens outside any execution context.\n */\nfunction loadCliCommandConfig(): Promise<Config> {\n return Config.load(import.meta.url)\n}\n\nlet cachedConfig: Promise<Config> | undefined\n\nfunction unknownCommandResult(argv: string[], policySet: CommandPolicySet): InvokeSanityCliResult {\n const available = Object.entries(policySet)\n .filter(([, policy]) => policy.kind !== 'deny')\n .map(([id]) => id.replaceAll(':', ' '))\n .toSorted()\n return {\n exitCode: exitCodes.USAGE_ERROR,\n output: [\n `Unknown or unsupported command: ${argv.slice(0, 2).join(' ') || '(none)'}`,\n `Available commands: ${available.join(', ')}`,\n ].join('\\n'),\n }\n}\n\n/**\n * @internal\n */\nexport interface InvokeSanityCliOptions {\n /**\n * Arguments after `sanity` (a leading `sanity` token is tolerated), either\n * as a single string — shell-style quoting is supported, but no shell is\n * ever executed — or as a pre-split argv array.\n */\n args: string | string[]\n\n /**\n * Where this invocation originates. Selects the command policy to enforce:\n * which commands are invokable and which invocations of them are permitted.\n */\n source: InvocationSource\n\n /**\n * Auth token for this invocation. Scoped to this call via the CLI execution\n * context: it never touches process env or the process-wide token cache, so\n * concurrent invocations with different tokens are fully isolated.\n */\n token: string\n\n /**\n * Optional oclif config override (mainly for tests). Defaults to this\n * package's config, loaded once and cached across invocations.\n */\n config?: Config\n\n /**\n * Optional fetch implementation for API requests made by this invocation.\n * Scoped to this call via the CLI execution context; defaults to the CLI's\n * own transport. The execution context's transport hygiene (such as\n * stripping the embedding process's lineage header) is applied on top.\n */\n fetch?: FetchFunction\n\n /**\n * Sanity deployment environment for this invocation. Scoped to this call\n * via the CLI execution context and defaults to production. The embedding\n * process's `SANITY_INTERNAL_ENV` is never consulted.\n */\n sanityEnv?: SanityEnvironment\n}\n\n/**\n * @internal\n */\nexport interface InvokeSanityCliResult {\n /** `0` on success, the command's exit code otherwise. */\n exitCode: number\n\n /** Combined stdout and stderr output, in emission order. */\n output: string\n\n /**\n * Canonical oclif command id when the invocation resolved to a command\n * exposed by the selected policy (for example, `datasets:create`).\n */\n commandId?: string\n}\n\n/**\n * Unlike a shell, string-argv keeps quotes that are glued to unquoted text:\n * `--name=\"my project\"` tokenizes with the quotes intact. Strip a matching\n * wrapping quote pair from the value side of `--flag=`-shaped tokens so the\n * common shell-style form yields the value the caller intended.\n */\nfunction stripFlagQuotes(rawToken: string): string {\n const match = /^(-{1,2}[^\\s=]+=)(['\"])([\\s\\S]*)\\2$/.exec(rawToken)\n return match ? match[1] + match[3] : rawToken\n}\n\n/**\n * Run a policy-permitted CLI command in-process and capture its result.\n *\n * Command-level failures (unknown command, bad flags, API errors) are\n * reported through `exitCode`/`output` rather than thrown, so callers can\n * relay them verbatim.\n *\n * @internal\n */\nexport async function invokeSanityCli(\n options: InvokeSanityCliOptions,\n): Promise<InvokeSanityCliResult> {\n // Always load compiled command modules. Oclif's dev-mode source resolution\n // otherwise probes tsconfigs (reading the filesystem and process.cwd) on\n // every command load, and may register ts-node process-wide.\n settings.enableAutoTranspile = false\n\n const resolvedConfig = options.config ?? (await (cachedConfig ??= loadCliCommandConfig()))\n const output: string[] = []\n const sink = (line: string) => output.push(line)\n const {fetch, sanityEnv, token} = options\n\n // Establish the isolation boundary before rendering help, loading command\n // modules, parsing, or executing. Any code reached by an external\n // invocation can therefore fail closed on context.\n return runWithCliExecutionContext({fetch, sanityEnv, stderr: sink, stdout: sink, token}, () =>\n invokeSanityCliInContext(options, resolvedConfig, output),\n )\n}\n\nasync function invokeSanityCliInContext(\n {args, source}: InvokeSanityCliOptions,\n resolvedConfig: Config,\n output: string[],\n): Promise<InvokeSanityCliResult> {\n const policySet = commandPolicies[source]\n\n // Pre-split argv arrays are taken verbatim; only string input goes through\n // shell-style tokenization and quote normalization.\n let argv =\n typeof args === 'string'\n ? parseArgsStringToArgv(args).map((t) => stripFlagQuotes(t))\n : [...args]\n if (argv[0] === 'sanity') argv = argv.slice(1)\n argv = resolveTopicAliasInArgv(argv)\n\n // Help requests are routed through oclif's help system, scoped to the\n // source's policy: root help for a bare request, topic/command help when a\n // subject is given. Denied subjects get the standard unknown-command\n // response (identical to a truly unknown command, so hosts can't probe the\n // full CLI surface through help), and a help request never executes a\n // command.\n if (isHelpRequest(argv, resolvedConfig)) {\n try {\n // Drop a leading `help` so the rest is the subject, mirroring how\n // oclif's dispatch consumes the token before the help command sees argv\n const helpArgv = argv[0] === 'help' ? argv.slice(1) : argv\n const result = await renderInvokableHelp(resolvedConfig, helpArgv, policySet)\n if (result) return {...result, exitCode: exitCodes.SUCCESS}\n const helpFlags = getHelpFlagAdditions(resolvedConfig)\n return unknownCommandResult(\n helpArgv.filter((token) => !helpFlags.includes(token)),\n policySet,\n )\n } catch (err) {\n return {\n exitCode: exitCodes.RUNTIME_ERROR,\n output: err instanceof Error ? err.message : String(err),\n }\n }\n }\n\n // Resolve the command id the same way oclif's dispatch would (collating\n // space-separated topics, accepting colon-separated ids as-is), then apply\n // the policy. Denied and uncategorized ids fail closed, indistinguishable\n // from commands that don't exist.\n const [commandId = '', ...commandArgv] = normalizeArgv(resolvedConfig, argv)\n const policy = policySet[commandId] ?? deny\n const commandDefinition =\n policy.kind === 'deny' ? undefined : resolvedConfig.findCommand(commandId)\n if (!commandDefinition) return unknownCommandResult(argv, policySet)\n\n const CommandClass = await commandDefinition.load()\n\n // Parse with the command's real definitions (without executing anything) so\n // conditional policies are evaluated against typed args/flags, not tokens.\n let invocation: {args: Record<string, unknown>; flags: Record<string, unknown>}\n try {\n const parsed = await Parser.parse(commandArgv, {\n args: CommandClass.args,\n baseFlags: CommandClass.baseFlags,\n enableJsonFlag: CommandClass.enableJsonFlag,\n flags: CommandClass.flags,\n strict: CommandClass.strict,\n })\n invocation = {\n args: parsed.args as Record<string, unknown>,\n flags: parsed.flags as Record<string, unknown>,\n }\n } catch (err) {\n return {\n commandId,\n exitCode: exitCodes.USAGE_ERROR,\n output: err instanceof Error ? err.message : String(err),\n }\n }\n\n if (!policy.validate(invocation)) {\n const displayId = commandId.replaceAll(':', ' ')\n let output = `This invocation of \\`${displayId}\\` is not supported here`\n\n if (isConditionalInvocationPolicy(policy)) {\n const usedDeniedFlags = policy.deniedFlags.filter(\n (name) => invocation.flags[name] !== undefined && invocation.flags[name] !== false,\n )\n if (usedDeniedFlags.length > 0) {\n output = `\\nThe ${usedDeniedFlags.map((name) => `--${name}`).join(', ')} flag is not supported here for \\`${displayId}\\``\n }\n }\n\n return {\n commandId,\n exitCode: exitCodes.USAGE_ERROR,\n output,\n }\n }\n\n try {\n const command = instantiateCommand(CommandClass, commandArgv, resolvedConfig)\n await command.runInExecutionContext()\n return {commandId, exitCode: exitCodes.SUCCESS, output: output.join('\\n')}\n } catch (err) {\n const exit = err.oclif?.exit\n\n // `this.exit(0)` throws an ExitError but is a successful outcome\n if (exit === exitCodes.SUCCESS) {\n return {commandId, exitCode: exitCodes.SUCCESS, output: output.join('\\n')}\n }\n\n const message = prettyPrintError(err) || String(err)\n if (message) output.push(message)\n return {\n commandId,\n exitCode: typeof exit === 'number' ? exit : exitCodes.RUNTIME_ERROR,\n output: output.join('\\n'),\n }\n }\n}\n"],"names":["Config","Parser","settings","getHelpFlagAdditions","normalizeArgv","exitCodes","runWithCliExecutionContext","parseArgsStringToArgv","resolveTopicAliasInArgv","commandPolicies","deny","isConditionalInvocationPolicy","isHelpRequest","renderInvokableHelp","prettyPrintError","instantiateCommand","CommandClass","argv","config","ConcreteCommand","command","runInExecutionContext","TypeError","id","loadCliCommandConfig","load","url","cachedConfig","unknownCommandResult","policySet","available","Object","entries","filter","policy","kind","map","replaceAll","toSorted","exitCode","USAGE_ERROR","output","slice","join","stripFlagQuotes","rawToken","match","exec","invokeSanityCli","options","enableAutoTranspile","resolvedConfig","sink","line","push","fetch","sanityEnv","token","stderr","stdout","invokeSanityCliInContext","args","source","t","helpArgv","result","SUCCESS","helpFlags","includes","err","RUNTIME_ERROR","Error","message","String","commandId","commandArgv","commandDefinition","undefined","findCommand","invocation","parsed","parse","baseFlags","enableJsonFlag","flags","strict","validate","displayId","usedDeniedFlags","deniedFlags","name","length","exit","oclif"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/exports/invokeSanityCli/index.ts"],"sourcesContent":["/**\n * Programmatic (in-process) invocation of CLI commands, e.g. from an MCP\n * server. The invokable surface is governed by a per-source command policy\n * (see ./commandPolicies): every CLI command is explicitly allowed, denied,\n * or allowed conditionally on the parsed invocation. Commands contributed by\n * plugins are covered by the policies those plugins declare, and are denied\n * unless they declare one.\n *\n * {@link invokeSanityCli} handles arg parsing, policy enforcement, command\n * dispatch, per-invocation auth, and output capture.\n * ```ts\n * import {invokeSanityCli} from '@sanity/cli/invokeSanityCli'\n *\n * const {exitCode, output} = await invokeSanityCli({\n * args: 'cors list --project-id abc123',\n * source: 'mcp',\n * token: extra.authInfo.token,\n * })\n * ```\n *\n * Help works like the regular CLI, scoped to the source's policy: `--help`\n * (or `help`) renders root help listing the invokable topics, and a subject\n * (`cors --help`, `cors list --help`) renders topic or command help.\n */\nimport {fileURLToPath} from 'node:url'\n\nimport {Command, Config, Parser, settings} from '@oclif/core'\nimport {getHelpFlagAdditions, normalizeArgv} from '@oclif/core/help'\nimport {exitCodes} from '@sanity/cli-core'\nimport {\n type CommandPolicySet,\n deny,\n type InvocationSource,\n isConditionalInvocationPolicy,\n} from '@sanity/cli-core/commandPolicy'\nimport {runWithCliExecutionContext, type SanityEnvironment} from '@sanity/cli-core/executionContext'\nimport {type SanityCommand} from '@sanity/cli-core/SanityCommand'\nimport {type FetchFunction} from 'get-it'\nimport {parseArgsStringToArgv} from 'string-argv'\n\nimport {resolveTopicAliasInArgv} from '../../topicAliases.js'\nimport {resolveCommandPolicies} from './commandPolicies/index.js'\nimport {isHelpRequest, renderInvokableHelp} from './help.js'\nimport {prettyPrintError} from './prettyPrintError.js'\n\ntype InvokableCommand = Pick<SanityCommand<typeof Command>, 'runInExecutionContext'>\n\n/**\n * Whether a command can run in isolation, which in practice means it extends\n * `SanityCommand`: only that base class routes output, token resolution,\n * interactivity, and project discovery through the CLI execution context.\n *\n * This is a capability check, not a policy check. A plugin can declare a\n * policy for a command that still extends oclif's `Command` directly, and\n * such a command must not run here — the policy says the invocation is safe,\n * but nothing would hold it to the isolation guarantees that assessment\n * assumes.\n */\nfunction supportsIsolatedExecution(CommandClass: Command.Class): boolean {\n const prototype = CommandClass.prototype as Partial<InvokableCommand> | undefined\n return typeof prototype?.runInExecutionContext === 'function'\n}\n\n/**\n * Instantiate a policy-approved command for isolated execution.\n *\n * `runInExecutionContext()` runs the instance without oclif's static\n * `Command.run()` — the static runner re-enters `Config.load()` (filesystem\n * reads and a process-global config cache write) on every call. The manifest\n * types command classes as the abstract oclif `Command`, hence the\n * constructor cast.\n */\nfunction instantiateCommand(\n CommandClass: Command.Class,\n argv: string[],\n config: Config,\n): InvokableCommand {\n const ConcreteCommand = CommandClass as unknown as new (argv: string[], config: Config) => Command\n return new ConcreteCommand(argv, config) as unknown as InvokableCommand\n}\n\n/**\n * Load the oclif `Config` for this package, needed to resolve, load, and run\n * commands. Loading it once and reusing it across invocations avoids\n * re-reading the command manifest per call. It only reads this package's own\n * installed files — process-lifetime initialization, not per-invocation host\n * state — so it happens outside any execution context.\n *\n * `userPlugins: false` keeps this surface to the plugins this package ships\n * with. oclif otherwise loads whatever `<dataDir>/package.json` lists.\n */\nfunction loadCliCommandConfig(): Promise<Config> {\n return Config.load({root: fileURLToPath(import.meta.url), userPlugins: false})\n}\n\nlet cachedConfig: Promise<Config> | undefined\n\nfunction unknownCommandResult(argv: string[], policySet: CommandPolicySet): InvokeSanityCliResult {\n const available = Object.entries(policySet)\n .filter(([, policy]) => policy.kind !== 'deny')\n .map(([id]) => id.replaceAll(':', ' '))\n .toSorted()\n return {\n exitCode: exitCodes.USAGE_ERROR,\n output: [\n `Unknown or unsupported command: ${argv.slice(0, 2).join(' ') || '(none)'}`,\n `Available commands: ${available.join(', ')}`,\n ].join('\\n'),\n }\n}\n\n/**\n * @internal\n */\nexport interface InvokeSanityCliOptions {\n /**\n * Arguments after `sanity` (a leading `sanity` token is tolerated), either\n * as a single string — shell-style quoting is supported, but no shell is\n * ever executed — or as a pre-split argv array.\n */\n args: string | string[]\n\n /**\n * Where this invocation originates. Selects the command policy to enforce:\n * which commands are invokable and which invocations of them are permitted.\n */\n source: InvocationSource\n\n /**\n * Auth token for this invocation. Scoped to this call via the CLI execution\n * context: it never touches process env or the process-wide token cache, so\n * concurrent invocations with different tokens are fully isolated.\n */\n token: string\n\n /**\n * Optional oclif config override (mainly for tests). Defaults to this\n * package's config, loaded once and cached across invocations.\n */\n config?: Config\n\n /**\n * Optional fetch implementation for API requests made by this invocation.\n * Scoped to this call via the CLI execution context; defaults to the CLI's\n * own transport. The execution context's transport hygiene (such as\n * stripping the embedding process's lineage header) is applied on top.\n */\n fetch?: FetchFunction\n\n /**\n * Sanity deployment environment for this invocation. Scoped to this call\n * via the CLI execution context and defaults to production. The embedding\n * process's `SANITY_INTERNAL_ENV` is never consulted.\n */\n sanityEnv?: SanityEnvironment\n}\n\n/**\n * @internal\n */\nexport interface InvokeSanityCliResult {\n /** `0` on success, the command's exit code otherwise. */\n exitCode: number\n\n /** Combined stdout and stderr output, in emission order. */\n output: string\n\n /**\n * Canonical oclif command id when the invocation resolved to a command\n * exposed by the selected policy (for example, `datasets:create`).\n */\n commandId?: string\n}\n\n/**\n * Unlike a shell, string-argv keeps quotes that are glued to unquoted text:\n * `--name=\"my project\"` tokenizes with the quotes intact. Strip a matching\n * wrapping quote pair from the value side of `--flag=`-shaped tokens so the\n * common shell-style form yields the value the caller intended.\n */\nfunction stripFlagQuotes(rawToken: string): string {\n const match = /^(-{1,2}[^\\s=]+=)(['\"])([\\s\\S]*)\\2$/.exec(rawToken)\n return match ? match[1] + match[3] : rawToken\n}\n\n/**\n * Run a policy-permitted CLI command in-process and capture its result.\n *\n * Command-level failures (unknown command, bad flags, API errors) are\n * reported through `exitCode`/`output` rather than thrown, so callers can\n * relay them verbatim.\n *\n * @internal\n */\nexport async function invokeSanityCli(\n options: InvokeSanityCliOptions,\n): Promise<InvokeSanityCliResult> {\n // Always load compiled command modules. Oclif's dev-mode source resolution\n // otherwise probes tsconfigs (reading the filesystem and process.cwd) on\n // every command load, and may register ts-node process-wide.\n settings.enableAutoTranspile = false\n\n const resolvedConfig = options.config ?? (await (cachedConfig ??= loadCliCommandConfig()))\n const output: string[] = []\n const sink = (line: string) => output.push(line)\n const {fetch, sanityEnv, token} = options\n\n // Establish the isolation boundary before rendering help, loading command\n // modules, parsing, or executing. Any code reached by an external\n // invocation can therefore fail closed on context.\n return runWithCliExecutionContext({fetch, sanityEnv, stderr: sink, stdout: sink, token}, () =>\n invokeSanityCliInContext(options, resolvedConfig, output),\n )\n}\n\nasync function invokeSanityCliInContext(\n {args, source}: InvokeSanityCliOptions,\n resolvedConfig: Config,\n output: string[],\n): Promise<InvokeSanityCliResult> {\n // Combines this package's own policies with those each plugin declares for\n // the commands it contributes. Resolved once per config, not per call.\n const policySet = await resolveCommandPolicies(resolvedConfig, source)\n\n // Pre-split argv arrays are taken verbatim; only string input goes through\n // shell-style tokenization and quote normalization.\n let argv =\n typeof args === 'string'\n ? parseArgsStringToArgv(args).map((t) => stripFlagQuotes(t))\n : [...args]\n if (argv[0] === 'sanity') argv = argv.slice(1)\n argv = resolveTopicAliasInArgv(argv)\n\n // Help requests are routed through oclif's help system, scoped to the\n // source's policy: root help for a bare request, topic/command help when a\n // subject is given. Denied subjects get the standard unknown-command\n // response (identical to a truly unknown command, so hosts can't probe the\n // full CLI surface through help), and a help request never executes a\n // command.\n if (isHelpRequest(argv, resolvedConfig)) {\n try {\n // Drop a leading `help` so the rest is the subject, mirroring how\n // oclif's dispatch consumes the token before the help command sees argv\n const helpArgv = argv[0] === 'help' ? argv.slice(1) : argv\n const result = await renderInvokableHelp(resolvedConfig, helpArgv, policySet)\n if (result) return {...result, exitCode: exitCodes.SUCCESS}\n const helpFlags = getHelpFlagAdditions(resolvedConfig)\n return unknownCommandResult(\n helpArgv.filter((token) => !helpFlags.includes(token)),\n policySet,\n )\n } catch (err) {\n return {\n exitCode: exitCodes.RUNTIME_ERROR,\n output: err instanceof Error ? err.message : String(err),\n }\n }\n }\n\n // Resolve the command id the same way oclif's dispatch would (collating\n // space-separated topics, accepting colon-separated ids as-is), then apply\n // the policy. Denied and uncategorized ids fail closed, indistinguishable\n // from commands that don't exist.\n const [commandId = '', ...commandArgv] = normalizeArgv(resolvedConfig, argv)\n const policy = policySet[commandId] ?? deny\n const commandDefinition =\n policy.kind === 'deny' ? undefined : resolvedConfig.findCommand(commandId)\n if (!commandDefinition) return unknownCommandResult(argv, policySet)\n\n const CommandClass = await commandDefinition.load()\n\n // A policy can only speak to whether an invocation is safe; it cannot make\n // a command isolatable. One that isn't is treated as if it did not exist,\n // like every other fail-closed path here.\n if (!supportsIsolatedExecution(CommandClass)) return unknownCommandResult(argv, policySet)\n\n // Parse with the command's real definitions (without executing anything) so\n // conditional policies are evaluated against typed args/flags, not tokens.\n let invocation: {args: Record<string, unknown>; flags: Record<string, unknown>}\n try {\n const parsed = await Parser.parse(commandArgv, {\n args: CommandClass.args,\n baseFlags: CommandClass.baseFlags,\n enableJsonFlag: CommandClass.enableJsonFlag,\n flags: CommandClass.flags,\n strict: CommandClass.strict,\n })\n invocation = {\n args: parsed.args as Record<string, unknown>,\n flags: parsed.flags as Record<string, unknown>,\n }\n } catch (err) {\n return {\n commandId,\n exitCode: exitCodes.USAGE_ERROR,\n output: err instanceof Error ? err.message : String(err),\n }\n }\n\n if (!policy.validate(invocation)) {\n const displayId = commandId.replaceAll(':', ' ')\n let output = `This invocation of \\`${displayId}\\` is not supported here`\n\n if (isConditionalInvocationPolicy(policy)) {\n const usedDeniedFlags = policy.deniedFlags.filter(\n (name) => invocation.flags[name] !== undefined && invocation.flags[name] !== false,\n )\n if (usedDeniedFlags.length > 0) {\n output = `\\nThe ${usedDeniedFlags.map((name) => `--${name}`).join(', ')} flag is not supported here for \\`${displayId}\\``\n }\n }\n\n return {\n commandId,\n exitCode: exitCodes.USAGE_ERROR,\n output,\n }\n }\n\n try {\n const command = instantiateCommand(CommandClass, commandArgv, resolvedConfig)\n await command.runInExecutionContext()\n return {commandId, exitCode: exitCodes.SUCCESS, output: output.join('\\n')}\n } catch (err) {\n const exit = err.oclif?.exit\n\n // `this.exit(0)` throws an ExitError but is a successful outcome\n if (exit === exitCodes.SUCCESS) {\n return {commandId, exitCode: exitCodes.SUCCESS, output: output.join('\\n')}\n }\n\n const message = prettyPrintError(err) || String(err)\n if (message) output.push(message)\n return {\n commandId,\n exitCode: typeof exit === 'number' ? exit : exitCodes.RUNTIME_ERROR,\n output: output.join('\\n'),\n }\n }\n}\n"],"names":["fileURLToPath","Config","Parser","settings","getHelpFlagAdditions","normalizeArgv","exitCodes","deny","isConditionalInvocationPolicy","runWithCliExecutionContext","parseArgsStringToArgv","resolveTopicAliasInArgv","resolveCommandPolicies","isHelpRequest","renderInvokableHelp","prettyPrintError","supportsIsolatedExecution","CommandClass","prototype","runInExecutionContext","instantiateCommand","argv","config","ConcreteCommand","loadCliCommandConfig","load","root","url","userPlugins","cachedConfig","unknownCommandResult","policySet","available","Object","entries","filter","policy","kind","map","id","replaceAll","toSorted","exitCode","USAGE_ERROR","output","slice","join","stripFlagQuotes","rawToken","match","exec","invokeSanityCli","options","enableAutoTranspile","resolvedConfig","sink","line","push","fetch","sanityEnv","token","stderr","stdout","invokeSanityCliInContext","args","source","t","helpArgv","result","SUCCESS","helpFlags","includes","err","RUNTIME_ERROR","Error","message","String","commandId","commandArgv","commandDefinition","undefined","findCommand","invocation","parsed","parse","baseFlags","enableJsonFlag","flags","strict","validate","displayId","usedDeniedFlags","deniedFlags","name","length","command","exit","oclif"],"mappings":"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"}
@@ -156,7 +156,9 @@
156
156
  host: 'project',
157
157
  pathPatterns: [
158
158
  'assets/files/{dataset}',
159
+ 'assets/files/{dataset}/from-url',
159
160
  'assets/images/{dataset}',
161
+ 'assets/images/{dataset}/from-url',
160
162
  'assets/media-library-link/{dataset}'
161
163
  ],
162
164
  slug: 'assets',
@@ -173,6 +175,16 @@
173
175
  slug: 'backups',
174
176
  title: 'Backups API reference'
175
177
  },
178
+ {
179
+ defaultApiVersion: 'v1',
180
+ host: 'global',
181
+ pathPatterns: [
182
+ 'projects/{project_id}/datasets/{dataset_name}/assets/{asset_id}/purges',
183
+ 'projects/{project_id}/datasets/{dataset_name}/purges'
184
+ ],
185
+ slug: 'cdn-cache-purge',
186
+ title: 'CDN Cache Purge API Reference'
187
+ },
176
188
  {
177
189
  defaultApiVersion: 'vX',
178
190
  host: 'global',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/generated/apiRoutes.ts"],"sourcesContent":["/**\n * GENERATED FILE - DO NOT EDIT\n *\n * Routing manifest for `sanity api`, distilled from the published OpenAPI\n * specifications at https://www.sanity.io/docs/api/openapi\n *\n * Regenerate with: pnpm generate:api-routes\n */\nimport {type ApiRouteEntry} from '../actions/api/types.js'\n\nexport const apiRoutes: ApiRouteEntry[] = [\n {\n defaultApiVersion: 'v2025-07-11',\n host: 'global',\n pathPatterns: [\n 'access/invites/me',\n 'access/organization/{resourceId}/users/roles/default',\n 'access/requests/me',\n 'access/robots/me',\n 'access/robots/me/rotate',\n 'access/{resourceType}/{resourceId}/invites',\n 'access/{resourceType}/{resourceId}/invites/token/{inviteToken}',\n 'access/{resourceType}/{resourceId}/invites/token/{inviteToken}/accept',\n 'access/{resourceType}/{resourceId}/invites/token/{inviteToken}/eligibility',\n 'access/{resourceType}/{resourceId}/invites/{inviteId}',\n 'access/{resourceType}/{resourceId}/permissions',\n 'access/{resourceType}/{resourceId}/permissions/{permissionName}',\n 'access/{resourceType}/{resourceId}/requests',\n 'access/{resourceType}/{resourceId}/requests/state',\n 'access/{resourceType}/{resourceId}/requests/{requestId}/accept',\n 'access/{resourceType}/{resourceId}/requests/{requestId}/decline',\n 'access/{resourceType}/{resourceId}/robots',\n 'access/{resourceType}/{resourceId}/robots/{robotId}',\n 'access/{resourceType}/{resourceId}/roles',\n 'access/{resourceType}/{resourceId}/roles/{roleName}',\n 'access/{resourceType}/{resourceId}/user-permissions/me',\n 'access/{resourceType}/{resourceId}/user-permissions/me/check',\n 'access/{resourceType}/{resourceId}/users',\n 'access/{resourceType}/{resourceId}/users/me',\n 'access/{resourceType}/{resourceId}/users/{sanityUserId}',\n 'access/{resourceType}/{resourceId}/users/{sanityUserId}/roles',\n 'access/{resourceType}/{resourceId}/users/{sanityUserId}/roles/{roleName}'\n ],\n slug: 'access-api',\n title: 'Access API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'data/actions/{dataset}'\n ],\n slug: 'actions',\n title: 'Actions API reference'\n },\n {\n defaultApiVersion: 'v2021-02-01',\n host: 'global',\n pathPatterns: [\n 'activity',\n 'activity/export/csv'\n ],\n slug: 'activity-log',\n title: 'Activity Log API Reference'\n },\n {\n defaultApiVersion: 'vX',\n host: 'project',\n pathPatterns: [\n 'agent/action/generate/{dataset}',\n 'agent/action/patch/{dataset}',\n 'agent/action/prompt/{dataset}',\n 'agent/action/transform/{dataset}',\n 'agent/action/translate/{dataset}'\n ],\n slug: 'agent-actions',\n title: 'Agent Actions'\n },\n {\n host: 'global',\n pathPatterns: [\n 'applications',\n 'applications/{applicationId}',\n 'applications/{applicationId}/access',\n 'applications/{applicationId}/deployments',\n 'applications/{applicationId}/installations',\n 'applications/{applicationId}/interfaces',\n 'applications/{applicationId}/workspaces',\n 'deploy',\n 'deploy/quickstart',\n 'installations',\n 'installations/catalog',\n 'installations/defaults',\n 'installations/{installationId}',\n 'installations/{installationId}/access',\n 'installations/{installationId}/configs',\n 'installations/{installationId}/configs/{configId}',\n 'installations/{installationId}/interfaces',\n 'projects/{projectId}/user-applications',\n 'projects/{projectId}/user-applications/{userApplicationId}',\n 'projects/{projectId}/user-applications/{userApplicationId}/config/{configKey}',\n 'projects/{projectId}/user-applications/{userApplicationId}/deployments',\n 'studiohosts/{appHost}/availability',\n 'user-applications',\n 'user-applications/auto-update-version/{userApplicationId}',\n 'user-applications/resolve/application',\n 'user-applications/resolve/project',\n 'user-applications/workspaces',\n 'user-applications/{userApplicationId}',\n 'user-applications/{userApplicationId}/applicationData',\n 'user-applications/{userApplicationId}/config/{configKey}',\n 'user-applications/{userApplicationId}/deployments',\n 'user-applications/{userApplicationId}/workspaces'\n ],\n slug: 'applications-api',\n title: 'Applications API Reference'\n },\n {\n host: 'project',\n pathPatterns: [\n 'applications',\n 'applications/{applicationId}',\n 'applications/{applicationId}/access',\n 'applications/{applicationId}/deployments',\n 'applications/{applicationId}/installations',\n 'applications/{applicationId}/interfaces',\n 'applications/{applicationId}/workspaces',\n 'deploy',\n 'deploy/quickstart',\n 'installations',\n 'installations/catalog',\n 'installations/defaults',\n 'installations/{installationId}',\n 'installations/{installationId}/access',\n 'installations/{installationId}/configs',\n 'installations/{installationId}/configs/{configId}',\n 'installations/{installationId}/interfaces',\n 'projects/{projectId}/user-applications',\n 'projects/{projectId}/user-applications/{userApplicationId}',\n 'projects/{projectId}/user-applications/{userApplicationId}/config/{configKey}',\n 'projects/{projectId}/user-applications/{userApplicationId}/deployments',\n 'studiohosts/{appHost}/availability',\n 'user-applications',\n 'user-applications/auto-update-version/{userApplicationId}',\n 'user-applications/resolve/application',\n 'user-applications/resolve/project',\n 'user-applications/workspaces',\n 'user-applications/{userApplicationId}',\n 'user-applications/{userApplicationId}/applicationData',\n 'user-applications/{userApplicationId}/config/{configKey}',\n 'user-applications/{userApplicationId}/deployments',\n 'user-applications/{userApplicationId}/workspaces'\n ],\n slug: 'applications-api',\n title: 'Applications API Reference'\n },\n {\n defaultApiVersion: 'v2024-06-24',\n host: 'project',\n pathPatterns: [\n 'assets/files/{dataset}',\n 'assets/images/{dataset}',\n 'assets/media-library-link/{dataset}'\n ],\n slug: 'assets',\n title: 'Assets API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'global',\n pathPatterns: [\n 'projects/{projectId}/datasets/{datasetName}/backups',\n 'projects/{projectId}/datasets/{datasetName}/backups/{backupId}',\n 'projects/{projectId}/datasets/{datasetName}/settings/backups'\n ],\n slug: 'backups',\n title: 'Backups API reference'\n },\n {\n defaultApiVersion: 'vX',\n host: 'global',\n pathPatterns: [\n 'agent/prompt',\n 'agent/{organizationId}/threads/{threadId}'\n ],\n slug: 'content-agent',\n title: 'Content Agent API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'global',\n pathPatterns: [\n 'projects/{projectId}/datasets/{dataset}/copy'\n ],\n slug: 'copy',\n title: 'Copy API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'data/doc/{dataset}/{documentId}'\n ],\n slug: 'doc',\n title: 'Doc API reference'\n },\n {\n defaultApiVersion: 'vX',\n host: 'project',\n pathPatterns: [\n 'embeddings-index/query/{dataset}/{indexName}',\n 'embeddings-index/{dataset}',\n 'embeddings-index/{dataset}/{indexName}'\n ],\n slug: 'embeddings-index',\n title: 'Embeddings Index API reference (deprecated)'\n },\n {\n defaultApiVersion: 'v2021-03-25',\n host: 'project',\n pathPatterns: [\n 'data/export/{dataset}'\n ],\n slug: 'export',\n title: 'Export API reference'\n },\n {\n host: 'global',\n pathPatterns: [\n 'functions',\n 'functions/{functionId}',\n 'functions/{functionId}/envvars',\n 'functions/{functionId}/envvars/{envVarKey}',\n 'functions/{functionId}/invoke',\n 'functions/{functionId}/logs',\n 'functions/{functionId}/logs/stream'\n ],\n slug: 'functions-api',\n title: 'Functions API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'data/history/{dataset}/documents/{documentId}',\n 'data/history/{dataset}/transactions',\n 'data/history/{dataset}/transactions/{documentIds}'\n ],\n slug: 'history',\n title: 'History API reference'\n },\n {\n defaultApiVersion: 'v2021-06-07',\n host: 'global',\n pathPatterns: [\n 'jobs/{jobId}',\n 'jobs/{jobId}/listen'\n ],\n slug: 'jobs',\n title: 'Jobs API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'data/listen/{dataset}'\n ],\n slug: 'listen',\n title: 'Listen API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'data/live/events/{dataset}'\n ],\n slug: 'live',\n title: 'Live Content API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'global',\n pathPatterns: [\n 'media-libraries/{libraryId}/from-url',\n 'media-libraries/{libraryId}/mutate',\n 'media-libraries/{libraryId}/query',\n 'media-libraries/{libraryId}/references/documents/{assetId}/to',\n 'media-libraries/{libraryId}/refsearch',\n 'media-libraries/{libraryId}/signing-keys',\n 'media-libraries/{libraryId}/signing-keys/{keyId}',\n 'media-libraries/{libraryId}/upload',\n 'media-libraries/{libraryId}/video/{videoId}/playback-info',\n 'media-libraries/{libraryId}/video/{videoId}/playback/{type}',\n 'media-libraries/{libraryId}/video/{videoId}/renditions/{name}',\n 'media-libraries/{libraryId}/video/{videoId}/subtitles',\n 'media-libraries/{libraryId}/video/{videoId}/subtitles/upload',\n 'media-libraries/{libraryId}/video/{videoId}/subtitles/{subtitleId}'\n ],\n slug: 'media-library',\n title: 'Media Library API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'media-libraries/{libraryId}/from-url',\n 'media-libraries/{libraryId}/mutate',\n 'media-libraries/{libraryId}/query',\n 'media-libraries/{libraryId}/references/documents/{assetId}/to',\n 'media-libraries/{libraryId}/refsearch',\n 'media-libraries/{libraryId}/signing-keys',\n 'media-libraries/{libraryId}/signing-keys/{keyId}',\n 'media-libraries/{libraryId}/upload',\n 'media-libraries/{libraryId}/video/{videoId}/playback-info',\n 'media-libraries/{libraryId}/video/{videoId}/playback/{type}',\n 'media-libraries/{libraryId}/video/{videoId}/renditions/{name}',\n 'media-libraries/{libraryId}/video/{videoId}/subtitles',\n 'media-libraries/{libraryId}/video/{videoId}/subtitles/upload',\n 'media-libraries/{libraryId}/video/{videoId}/subtitles/{subtitleId}'\n ],\n slug: 'media-library',\n title: 'Media Library API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'data/mutate/{dataset}'\n ],\n slug: 'mutation',\n title: 'Mutation API reference'\n },\n {\n defaultApiVersion: 'v2021-06-07',\n host: 'global',\n pathPatterns: [\n 'projects',\n 'projects/{projectId}',\n 'projects/{projectId}/cors',\n 'projects/{projectId}/cors/{id}',\n 'projects/{projectId}/datasets',\n 'projects/{projectId}/datasets/{datasetName}/tags/{tagIdentifier}',\n 'projects/{projectId}/datasets/{name}',\n 'projects/{projectId}/datasets/{name}/copy',\n 'projects/{projectId}/datasets/{name}/settings/embeddings',\n 'projects/{projectId}/datasets/{name}/tags',\n 'projects/{projectId}/features',\n 'projects/{projectId}/permissions',\n 'projects/{projectId}/tags',\n 'projects/{projectId}/tags/{tagIdentifier}',\n 'projects/{projectId}/tokens',\n 'projects/{projectId}/tokens/{tokenId}',\n 'projects/{projectId}/users/{userId}'\n ],\n slug: 'projects-api',\n title: 'Projects API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'data/query/{dataset}'\n ],\n slug: 'query',\n title: 'Query API reference'\n },\n {\n defaultApiVersion: 'v2021-10-04',\n host: 'global',\n pathPatterns: [\n 'organizations/{organizationId}/acl',\n 'organizations/{organizationId}/acl/{sanityUserId}',\n 'organizations/{organizationId}/grants',\n 'organizations/{organizationId}/providers/{providerId}/projects/{projectId}/role-mappings',\n 'projects/{projectId}/acl',\n 'projects/{projectId}/acl/{userId}',\n 'projects/{projectId}/datasets/{datasetName}/acl',\n 'projects/{projectId}/datasets/{datasetName}/grants',\n 'projects/{projectId}/grants',\n 'projects/{projectId}/permissionResourceSchemas',\n 'projects/{projectId}/roles',\n 'projects/{projectId}/roles/{roleName}'\n ],\n slug: 'roles',\n title: 'Roles API reference'\n },\n {\n defaultApiVersion: 'v2022-04-01',\n host: 'project',\n pathPatterns: [\n 'publish/{projectId}/{dataset}',\n 'schedules/{projectId}/{dataset}',\n 'schedules/{projectId}/{dataset}/{scheduleId}',\n 'schedules/{projectId}/{dataset}/{scheduleId}/publish',\n 'unpublish/{projectId}/{dataset}'\n ],\n slug: 'scheduling',\n title: 'Scheduling API reference'\n },\n {\n defaultApiVersion: 'v2026-04-27',\n host: 'global',\n pathPatterns: [\n 'organizations/{organizationId}/attribute-definitions',\n 'organizations/{organizationId}/attribute-definitions/{key}',\n 'organizations/{organizationId}/users/me/attributes',\n 'organizations/{organizationId}/users/{sanityUserId}/attributes'\n ],\n slug: 'user-attributes',\n title: 'User attributes API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'hooks/projects/{projectId}',\n 'hooks/projects/{projectId}/{id}',\n 'hooks/projects/{projectId}/{id}/attempts',\n 'hooks/{id}/messages'\n ],\n slug: 'webhooks',\n title: 'Webhooks API reference'\n }\n]\n"],"names":["apiRoutes","defaultApiVersion","host","pathPatterns","slug","title"],"mappings":"AAAA;;;;;;;CAOC,GAGD,OAAO,MAAMA,YAA6B;IACxC;QACEC,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEH,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEH,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEH,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;CACD,CAAA"}
1
+ {"version":3,"sources":["../../src/generated/apiRoutes.ts"],"sourcesContent":["/**\n * GENERATED FILE - DO NOT EDIT\n *\n * Routing manifest for `sanity api`, distilled from the published OpenAPI\n * specifications at https://www.sanity.io/docs/api/openapi\n *\n * Regenerate with: pnpm generate:api-routes\n */\nimport {type ApiRouteEntry} from '../actions/api/types.js'\n\nexport const apiRoutes: ApiRouteEntry[] = [\n {\n defaultApiVersion: 'v2025-07-11',\n host: 'global',\n pathPatterns: [\n 'access/invites/me',\n 'access/organization/{resourceId}/users/roles/default',\n 'access/requests/me',\n 'access/robots/me',\n 'access/robots/me/rotate',\n 'access/{resourceType}/{resourceId}/invites',\n 'access/{resourceType}/{resourceId}/invites/token/{inviteToken}',\n 'access/{resourceType}/{resourceId}/invites/token/{inviteToken}/accept',\n 'access/{resourceType}/{resourceId}/invites/token/{inviteToken}/eligibility',\n 'access/{resourceType}/{resourceId}/invites/{inviteId}',\n 'access/{resourceType}/{resourceId}/permissions',\n 'access/{resourceType}/{resourceId}/permissions/{permissionName}',\n 'access/{resourceType}/{resourceId}/requests',\n 'access/{resourceType}/{resourceId}/requests/state',\n 'access/{resourceType}/{resourceId}/requests/{requestId}/accept',\n 'access/{resourceType}/{resourceId}/requests/{requestId}/decline',\n 'access/{resourceType}/{resourceId}/robots',\n 'access/{resourceType}/{resourceId}/robots/{robotId}',\n 'access/{resourceType}/{resourceId}/roles',\n 'access/{resourceType}/{resourceId}/roles/{roleName}',\n 'access/{resourceType}/{resourceId}/user-permissions/me',\n 'access/{resourceType}/{resourceId}/user-permissions/me/check',\n 'access/{resourceType}/{resourceId}/users',\n 'access/{resourceType}/{resourceId}/users/me',\n 'access/{resourceType}/{resourceId}/users/{sanityUserId}',\n 'access/{resourceType}/{resourceId}/users/{sanityUserId}/roles',\n 'access/{resourceType}/{resourceId}/users/{sanityUserId}/roles/{roleName}'\n ],\n slug: 'access-api',\n title: 'Access API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'data/actions/{dataset}'\n ],\n slug: 'actions',\n title: 'Actions API reference'\n },\n {\n defaultApiVersion: 'v2021-02-01',\n host: 'global',\n pathPatterns: [\n 'activity',\n 'activity/export/csv'\n ],\n slug: 'activity-log',\n title: 'Activity Log API Reference'\n },\n {\n defaultApiVersion: 'vX',\n host: 'project',\n pathPatterns: [\n 'agent/action/generate/{dataset}',\n 'agent/action/patch/{dataset}',\n 'agent/action/prompt/{dataset}',\n 'agent/action/transform/{dataset}',\n 'agent/action/translate/{dataset}'\n ],\n slug: 'agent-actions',\n title: 'Agent Actions'\n },\n {\n host: 'global',\n pathPatterns: [\n 'applications',\n 'applications/{applicationId}',\n 'applications/{applicationId}/access',\n 'applications/{applicationId}/deployments',\n 'applications/{applicationId}/installations',\n 'applications/{applicationId}/interfaces',\n 'applications/{applicationId}/workspaces',\n 'deploy',\n 'deploy/quickstart',\n 'installations',\n 'installations/catalog',\n 'installations/defaults',\n 'installations/{installationId}',\n 'installations/{installationId}/access',\n 'installations/{installationId}/configs',\n 'installations/{installationId}/configs/{configId}',\n 'installations/{installationId}/interfaces',\n 'projects/{projectId}/user-applications',\n 'projects/{projectId}/user-applications/{userApplicationId}',\n 'projects/{projectId}/user-applications/{userApplicationId}/config/{configKey}',\n 'projects/{projectId}/user-applications/{userApplicationId}/deployments',\n 'studiohosts/{appHost}/availability',\n 'user-applications',\n 'user-applications/auto-update-version/{userApplicationId}',\n 'user-applications/resolve/application',\n 'user-applications/resolve/project',\n 'user-applications/workspaces',\n 'user-applications/{userApplicationId}',\n 'user-applications/{userApplicationId}/applicationData',\n 'user-applications/{userApplicationId}/config/{configKey}',\n 'user-applications/{userApplicationId}/deployments',\n 'user-applications/{userApplicationId}/workspaces'\n ],\n slug: 'applications-api',\n title: 'Applications API Reference'\n },\n {\n host: 'project',\n pathPatterns: [\n 'applications',\n 'applications/{applicationId}',\n 'applications/{applicationId}/access',\n 'applications/{applicationId}/deployments',\n 'applications/{applicationId}/installations',\n 'applications/{applicationId}/interfaces',\n 'applications/{applicationId}/workspaces',\n 'deploy',\n 'deploy/quickstart',\n 'installations',\n 'installations/catalog',\n 'installations/defaults',\n 'installations/{installationId}',\n 'installations/{installationId}/access',\n 'installations/{installationId}/configs',\n 'installations/{installationId}/configs/{configId}',\n 'installations/{installationId}/interfaces',\n 'projects/{projectId}/user-applications',\n 'projects/{projectId}/user-applications/{userApplicationId}',\n 'projects/{projectId}/user-applications/{userApplicationId}/config/{configKey}',\n 'projects/{projectId}/user-applications/{userApplicationId}/deployments',\n 'studiohosts/{appHost}/availability',\n 'user-applications',\n 'user-applications/auto-update-version/{userApplicationId}',\n 'user-applications/resolve/application',\n 'user-applications/resolve/project',\n 'user-applications/workspaces',\n 'user-applications/{userApplicationId}',\n 'user-applications/{userApplicationId}/applicationData',\n 'user-applications/{userApplicationId}/config/{configKey}',\n 'user-applications/{userApplicationId}/deployments',\n 'user-applications/{userApplicationId}/workspaces'\n ],\n slug: 'applications-api',\n title: 'Applications API Reference'\n },\n {\n defaultApiVersion: 'v2024-06-24',\n host: 'project',\n pathPatterns: [\n 'assets/files/{dataset}',\n 'assets/files/{dataset}/from-url',\n 'assets/images/{dataset}',\n 'assets/images/{dataset}/from-url',\n 'assets/media-library-link/{dataset}'\n ],\n slug: 'assets',\n title: 'Assets API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'global',\n pathPatterns: [\n 'projects/{projectId}/datasets/{datasetName}/backups',\n 'projects/{projectId}/datasets/{datasetName}/backups/{backupId}',\n 'projects/{projectId}/datasets/{datasetName}/settings/backups'\n ],\n slug: 'backups',\n title: 'Backups API reference'\n },\n {\n defaultApiVersion: 'v1',\n host: 'global',\n pathPatterns: [\n 'projects/{project_id}/datasets/{dataset_name}/assets/{asset_id}/purges',\n 'projects/{project_id}/datasets/{dataset_name}/purges'\n ],\n slug: 'cdn-cache-purge',\n title: 'CDN Cache Purge API Reference'\n },\n {\n defaultApiVersion: 'vX',\n host: 'global',\n pathPatterns: [\n 'agent/prompt',\n 'agent/{organizationId}/threads/{threadId}'\n ],\n slug: 'content-agent',\n title: 'Content Agent API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'global',\n pathPatterns: [\n 'projects/{projectId}/datasets/{dataset}/copy'\n ],\n slug: 'copy',\n title: 'Copy API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'data/doc/{dataset}/{documentId}'\n ],\n slug: 'doc',\n title: 'Doc API reference'\n },\n {\n defaultApiVersion: 'vX',\n host: 'project',\n pathPatterns: [\n 'embeddings-index/query/{dataset}/{indexName}',\n 'embeddings-index/{dataset}',\n 'embeddings-index/{dataset}/{indexName}'\n ],\n slug: 'embeddings-index',\n title: 'Embeddings Index API reference (deprecated)'\n },\n {\n defaultApiVersion: 'v2021-03-25',\n host: 'project',\n pathPatterns: [\n 'data/export/{dataset}'\n ],\n slug: 'export',\n title: 'Export API reference'\n },\n {\n host: 'global',\n pathPatterns: [\n 'functions',\n 'functions/{functionId}',\n 'functions/{functionId}/envvars',\n 'functions/{functionId}/envvars/{envVarKey}',\n 'functions/{functionId}/invoke',\n 'functions/{functionId}/logs',\n 'functions/{functionId}/logs/stream'\n ],\n slug: 'functions-api',\n title: 'Functions API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'data/history/{dataset}/documents/{documentId}',\n 'data/history/{dataset}/transactions',\n 'data/history/{dataset}/transactions/{documentIds}'\n ],\n slug: 'history',\n title: 'History API reference'\n },\n {\n defaultApiVersion: 'v2021-06-07',\n host: 'global',\n pathPatterns: [\n 'jobs/{jobId}',\n 'jobs/{jobId}/listen'\n ],\n slug: 'jobs',\n title: 'Jobs API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'data/listen/{dataset}'\n ],\n slug: 'listen',\n title: 'Listen API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'data/live/events/{dataset}'\n ],\n slug: 'live',\n title: 'Live Content API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'global',\n pathPatterns: [\n 'media-libraries/{libraryId}/from-url',\n 'media-libraries/{libraryId}/mutate',\n 'media-libraries/{libraryId}/query',\n 'media-libraries/{libraryId}/references/documents/{assetId}/to',\n 'media-libraries/{libraryId}/refsearch',\n 'media-libraries/{libraryId}/signing-keys',\n 'media-libraries/{libraryId}/signing-keys/{keyId}',\n 'media-libraries/{libraryId}/upload',\n 'media-libraries/{libraryId}/video/{videoId}/playback-info',\n 'media-libraries/{libraryId}/video/{videoId}/playback/{type}',\n 'media-libraries/{libraryId}/video/{videoId}/renditions/{name}',\n 'media-libraries/{libraryId}/video/{videoId}/subtitles',\n 'media-libraries/{libraryId}/video/{videoId}/subtitles/upload',\n 'media-libraries/{libraryId}/video/{videoId}/subtitles/{subtitleId}'\n ],\n slug: 'media-library',\n title: 'Media Library API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'media-libraries/{libraryId}/from-url',\n 'media-libraries/{libraryId}/mutate',\n 'media-libraries/{libraryId}/query',\n 'media-libraries/{libraryId}/references/documents/{assetId}/to',\n 'media-libraries/{libraryId}/refsearch',\n 'media-libraries/{libraryId}/signing-keys',\n 'media-libraries/{libraryId}/signing-keys/{keyId}',\n 'media-libraries/{libraryId}/upload',\n 'media-libraries/{libraryId}/video/{videoId}/playback-info',\n 'media-libraries/{libraryId}/video/{videoId}/playback/{type}',\n 'media-libraries/{libraryId}/video/{videoId}/renditions/{name}',\n 'media-libraries/{libraryId}/video/{videoId}/subtitles',\n 'media-libraries/{libraryId}/video/{videoId}/subtitles/upload',\n 'media-libraries/{libraryId}/video/{videoId}/subtitles/{subtitleId}'\n ],\n slug: 'media-library',\n title: 'Media Library API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'data/mutate/{dataset}'\n ],\n slug: 'mutation',\n title: 'Mutation API reference'\n },\n {\n defaultApiVersion: 'v2021-06-07',\n host: 'global',\n pathPatterns: [\n 'projects',\n 'projects/{projectId}',\n 'projects/{projectId}/cors',\n 'projects/{projectId}/cors/{id}',\n 'projects/{projectId}/datasets',\n 'projects/{projectId}/datasets/{datasetName}/tags/{tagIdentifier}',\n 'projects/{projectId}/datasets/{name}',\n 'projects/{projectId}/datasets/{name}/copy',\n 'projects/{projectId}/datasets/{name}/settings/embeddings',\n 'projects/{projectId}/datasets/{name}/tags',\n 'projects/{projectId}/features',\n 'projects/{projectId}/permissions',\n 'projects/{projectId}/tags',\n 'projects/{projectId}/tags/{tagIdentifier}',\n 'projects/{projectId}/tokens',\n 'projects/{projectId}/tokens/{tokenId}',\n 'projects/{projectId}/users/{userId}'\n ],\n slug: 'projects-api',\n title: 'Projects API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'data/query/{dataset}'\n ],\n slug: 'query',\n title: 'Query API reference'\n },\n {\n defaultApiVersion: 'v2021-10-04',\n host: 'global',\n pathPatterns: [\n 'organizations/{organizationId}/acl',\n 'organizations/{organizationId}/acl/{sanityUserId}',\n 'organizations/{organizationId}/grants',\n 'organizations/{organizationId}/providers/{providerId}/projects/{projectId}/role-mappings',\n 'projects/{projectId}/acl',\n 'projects/{projectId}/acl/{userId}',\n 'projects/{projectId}/datasets/{datasetName}/acl',\n 'projects/{projectId}/datasets/{datasetName}/grants',\n 'projects/{projectId}/grants',\n 'projects/{projectId}/permissionResourceSchemas',\n 'projects/{projectId}/roles',\n 'projects/{projectId}/roles/{roleName}'\n ],\n slug: 'roles',\n title: 'Roles API reference'\n },\n {\n defaultApiVersion: 'v2022-04-01',\n host: 'project',\n pathPatterns: [\n 'publish/{projectId}/{dataset}',\n 'schedules/{projectId}/{dataset}',\n 'schedules/{projectId}/{dataset}/{scheduleId}',\n 'schedules/{projectId}/{dataset}/{scheduleId}/publish',\n 'unpublish/{projectId}/{dataset}'\n ],\n slug: 'scheduling',\n title: 'Scheduling API reference'\n },\n {\n defaultApiVersion: 'v2026-04-27',\n host: 'global',\n pathPatterns: [\n 'organizations/{organizationId}/attribute-definitions',\n 'organizations/{organizationId}/attribute-definitions/{key}',\n 'organizations/{organizationId}/users/me/attributes',\n 'organizations/{organizationId}/users/{sanityUserId}/attributes'\n ],\n slug: 'user-attributes',\n title: 'User attributes API reference'\n },\n {\n defaultApiVersion: 'v2025-02-19',\n host: 'project',\n pathPatterns: [\n 'hooks/projects/{projectId}',\n 'hooks/projects/{projectId}/{id}',\n 'hooks/projects/{projectId}/{id}/attempts',\n 'hooks/{id}/messages'\n ],\n slug: 'webhooks',\n title: 'Webhooks API reference'\n }\n]\n"],"names":["apiRoutes","defaultApiVersion","host","pathPatterns","slug","title"],"mappings":"AAAA;;;;;;;CAOC,GAGD,OAAO,MAAMA,YAA6B;IACxC;QACEC,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEH,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEH,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEH,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;IACA;QACEJ,mBAAmB;QACnBC,MAAM;QACNC,cAAc;YACZ;YACA;YACA;YACA;SACD;QACDC,MAAM;QACNC,OAAO;IACT;CACD,CAAA"}
@@ -2,13 +2,22 @@ import { Transform } from 'node:stream';
2
2
  import { getProjectCliClient } from '@sanity/cli-core';
3
3
  import { filter, lastValueFrom, map, Observable, race } from 'rxjs';
4
4
  export const ASSETS_API_VERSION = 'v2024-06-24';
5
+ export const ASSET_UPLOAD_REQUEST_TAG_PREFIX = 'sanity.cli.assets.upload';
6
+ /**
7
+ * Content Lake allows itself 300 seconds to fetch the source URL and a further
8
+ * budget to persist the asset, so this waits far longer than an ordinary API
9
+ * call before giving up on a request that may still be doing useful work.
10
+ */ export const URL_INGEST_TIMEOUT_MS = 370_000;
11
+ function assetPathSegment(assetType) {
12
+ return assetType === 'image' ? 'images' : 'files';
13
+ }
5
14
  export async function uploadAsset({ assetType, body, contentType, dataset, filename, fileSize, onProgress, projectId, signal }) {
6
15
  signal?.throwIfAborted();
7
16
  const client = await getProjectCliClient({
8
17
  apiVersion: ASSETS_API_VERSION,
9
18
  dataset,
10
19
  projectId,
11
- requestTagPrefix: 'sanity.cli.assets.upload',
20
+ requestTagPrefix: ASSET_UPLOAD_REQUEST_TAG_PREFIX,
12
21
  requireUser: true
13
22
  });
14
23
  signal?.throwIfAborted();
@@ -42,5 +51,41 @@ export async function uploadAsset({ assetType, body, contentType, dataset, filen
42
51
  });
43
52
  return lastValueFrom(race(response$, abort$));
44
53
  }
54
+ /**
55
+ * Create an asset from a URL that Content Lake fetches itself.
56
+ *
57
+ * The bytes never pass through this process, so unlike {@link uploadAsset}
58
+ * there is no stream to pipe and no progress to report — the request is a
59
+ * small JSON body and a long wait. The source must be reachable without
60
+ * authentication from Sanity's side; a presigned URL qualifies, a private one
61
+ * behind a login does not.
62
+ *
63
+ * `assets.upload()` always streams a body, so this calls the endpoint directly
64
+ * rather than going through the client's asset helper.
65
+ */ export async function ingestAssetFromUrl({ assetType, dataset, filename, projectId, signal, url }) {
66
+ signal?.throwIfAborted();
67
+ const client = await getProjectCliClient({
68
+ apiVersion: ASSETS_API_VERSION,
69
+ dataset,
70
+ projectId,
71
+ requestTagPrefix: ASSET_UPLOAD_REQUEST_TAG_PREFIX,
72
+ requireUser: true
73
+ });
74
+ signal?.throwIfAborted();
75
+ const response = await client.request({
76
+ body: {
77
+ url,
78
+ ...filename ? {
79
+ filename
80
+ } : {}
81
+ },
82
+ method: 'POST',
83
+ signal,
84
+ tag: 'asset.upload.from-url',
85
+ timeout: URL_INGEST_TIMEOUT_MS,
86
+ url: `/assets/${assetPathSegment(assetType)}/${dataset}/from-url`
87
+ });
88
+ return response.document;
89
+ }
45
90
 
46
91
  //# sourceMappingURL=assets.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/assets.ts"],"sourcesContent":["import {type Readable, Transform} from 'node:stream'\n\nimport {getProjectCliClient} from '@sanity/cli-core'\nimport {filter, lastValueFrom, map, Observable, race} from 'rxjs'\n\nexport const ASSETS_API_VERSION = 'v2024-06-24'\n\nexport type AssetType = 'file' | 'image'\n\ninterface UploadAssetOptions {\n assetType: AssetType\n body: Readable\n dataset: string\n filename: string\n fileSize: number\n projectId: string\n\n contentType?: string\n onProgress?: (percent: number) => void\n signal?: AbortSignal\n}\n\nexport async function uploadAsset({\n assetType,\n body,\n contentType,\n dataset,\n filename,\n fileSize,\n onProgress,\n projectId,\n signal,\n}: UploadAssetOptions) {\n signal?.throwIfAborted()\n\n const client = await getProjectCliClient({\n apiVersion: ASSETS_API_VERSION,\n dataset,\n projectId,\n requestTagPrefix: 'sanity.cli.assets.upload',\n requireUser: true,\n })\n signal?.throwIfAborted()\n\n let uploadedBytes = 0\n const uploadStream = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n uploadedBytes += chunk.length\n onProgress?.((uploadedBytes / fileSize) * 100)\n callback(null, chunk)\n },\n })\n body.on('error', (error) => uploadStream.destroy(error)).pipe(uploadStream)\n\n const response$ = client.observable.assets\n .upload(assetType, uploadStream, {\n ...(contentType ? {contentType} : {}),\n filename,\n tag: 'asset.upload',\n })\n .pipe(\n filter((event) => event.type === 'response'),\n map((event) => event.body.document),\n )\n\n if (!signal) return lastValueFrom(response$)\n\n const abort$ = new Observable<never>((subscriber) => {\n const abortUpload = () => {\n body.destroy()\n uploadStream.destroy()\n subscriber.error(signal.reason instanceof Error ? signal.reason : new Error('SIGINT'))\n }\n\n signal.addEventListener('abort', abortUpload, {once: true})\n return () => signal.removeEventListener('abort', abortUpload)\n })\n\n return lastValueFrom(race(response$, abort$))\n}\n"],"names":["Transform","getProjectCliClient","filter","lastValueFrom","map","Observable","race","ASSETS_API_VERSION","uploadAsset","assetType","body","contentType","dataset","filename","fileSize","onProgress","projectId","signal","throwIfAborted","client","apiVersion","requestTagPrefix","requireUser","uploadedBytes","uploadStream","transform","chunk","_encoding","callback","length","on","error","destroy","pipe","response$","observable","assets","upload","tag","event","type","document","abort$","subscriber","abortUpload","reason","Error","addEventListener","once","removeEventListener"],"mappings":"AAAA,SAAuBA,SAAS,QAAO,cAAa;AAEpD,SAAQC,mBAAmB,QAAO,mBAAkB;AACpD,SAAQC,MAAM,EAAEC,aAAa,EAAEC,GAAG,EAAEC,UAAU,EAAEC,IAAI,QAAO,OAAM;AAEjE,OAAO,MAAMC,qBAAqB,cAAa;AAiB/C,OAAO,eAAeC,YAAY,EAChCC,SAAS,EACTC,IAAI,EACJC,WAAW,EACXC,OAAO,EACPC,QAAQ,EACRC,QAAQ,EACRC,UAAU,EACVC,SAAS,EACTC,MAAM,EACa;IACnBA,QAAQC;IAER,MAAMC,SAAS,MAAMlB,oBAAoB;QACvCmB,YAAYb;QACZK;QACAI;QACAK,kBAAkB;QAClBC,aAAa;IACf;IACAL,QAAQC;IAER,IAAIK,gBAAgB;IACpB,MAAMC,eAAe,IAAIxB,UAAU;QACjCyB,WAAUC,KAAa,EAAEC,SAAS,EAAEC,QAAQ;YAC1CL,iBAAiBG,MAAMG,MAAM;YAC7Bd,aAAa,AAACQ,gBAAgBT,WAAY;YAC1Cc,SAAS,MAAMF;QACjB;IACF;IACAhB,KAAKoB,EAAE,CAAC,SAAS,CAACC,QAAUP,aAAaQ,OAAO,CAACD,QAAQE,IAAI,CAACT;IAE9D,MAAMU,YAAYf,OAAOgB,UAAU,CAACC,MAAM,CACvCC,MAAM,CAAC5B,WAAWe,cAAc;QAC/B,GAAIb,cAAc;YAACA;QAAW,IAAI,CAAC,CAAC;QACpCE;QACAyB,KAAK;IACP,GACCL,IAAI,CACH/B,OAAO,CAACqC,QAAUA,MAAMC,IAAI,KAAK,aACjCpC,IAAI,CAACmC,QAAUA,MAAM7B,IAAI,CAAC+B,QAAQ;IAGtC,IAAI,CAACxB,QAAQ,OAAOd,cAAc+B;IAElC,MAAMQ,SAAS,IAAIrC,WAAkB,CAACsC;QACpC,MAAMC,cAAc;YAClBlC,KAAKsB,OAAO;YACZR,aAAaQ,OAAO;YACpBW,WAAWZ,KAAK,CAACd,OAAO4B,MAAM,YAAYC,QAAQ7B,OAAO4B,MAAM,GAAG,IAAIC,MAAM;QAC9E;QAEA7B,OAAO8B,gBAAgB,CAAC,SAASH,aAAa;YAACI,MAAM;QAAI;QACzD,OAAO,IAAM/B,OAAOgC,mBAAmB,CAAC,SAASL;IACnD;IAEA,OAAOzC,cAAcG,KAAK4B,WAAWQ;AACvC"}
1
+ {"version":3,"sources":["../../src/services/assets.ts"],"sourcesContent":["import {type Readable, Transform} from 'node:stream'\n\nimport {getProjectCliClient} from '@sanity/cli-core'\nimport {type SanityAssetDocument, type SanityImageAssetDocument} from '@sanity/client'\nimport {filter, lastValueFrom, map, Observable, race} from 'rxjs'\n\nexport const ASSETS_API_VERSION = 'v2024-06-24'\n\nexport const ASSET_UPLOAD_REQUEST_TAG_PREFIX = 'sanity.cli.assets.upload'\n\n/**\n * Content Lake allows itself 300 seconds to fetch the source URL and a further\n * budget to persist the asset, so this waits far longer than an ordinary API\n * call before giving up on a request that may still be doing useful work.\n */\nexport const URL_INGEST_TIMEOUT_MS = 370_000\n\nexport type AssetType = 'file' | 'image'\n\nfunction assetPathSegment(assetType: AssetType): string {\n return assetType === 'image' ? 'images' : 'files'\n}\n\ninterface UploadAssetOptions {\n assetType: AssetType\n body: Readable\n dataset: string\n filename: string\n fileSize: number\n projectId: string\n\n contentType?: string\n onProgress?: (percent: number) => void\n signal?: AbortSignal\n}\n\nexport async function uploadAsset({\n assetType,\n body,\n contentType,\n dataset,\n filename,\n fileSize,\n onProgress,\n projectId,\n signal,\n}: UploadAssetOptions) {\n signal?.throwIfAborted()\n\n const client = await getProjectCliClient({\n apiVersion: ASSETS_API_VERSION,\n dataset,\n projectId,\n requestTagPrefix: ASSET_UPLOAD_REQUEST_TAG_PREFIX,\n requireUser: true,\n })\n signal?.throwIfAborted()\n\n let uploadedBytes = 0\n const uploadStream = new Transform({\n transform(chunk: Buffer, _encoding, callback) {\n uploadedBytes += chunk.length\n onProgress?.((uploadedBytes / fileSize) * 100)\n callback(null, chunk)\n },\n })\n body.on('error', (error) => uploadStream.destroy(error)).pipe(uploadStream)\n\n const response$ = client.observable.assets\n .upload(assetType, uploadStream, {\n ...(contentType ? {contentType} : {}),\n filename,\n tag: 'asset.upload',\n })\n .pipe(\n filter((event) => event.type === 'response'),\n map((event) => event.body.document),\n )\n\n if (!signal) return lastValueFrom(response$)\n\n const abort$ = new Observable<never>((subscriber) => {\n const abortUpload = () => {\n body.destroy()\n uploadStream.destroy()\n subscriber.error(signal.reason instanceof Error ? signal.reason : new Error('SIGINT'))\n }\n\n signal.addEventListener('abort', abortUpload, {once: true})\n return () => signal.removeEventListener('abort', abortUpload)\n })\n\n return lastValueFrom(race(response$, abort$))\n}\n\ninterface IngestAssetFromUrlOptions {\n assetType: AssetType\n dataset: string\n projectId: string\n url: string\n\n filename?: string\n signal?: AbortSignal\n}\n\n/**\n * Create an asset from a URL that Content Lake fetches itself.\n *\n * The bytes never pass through this process, so unlike {@link uploadAsset}\n * there is no stream to pipe and no progress to report — the request is a\n * small JSON body and a long wait. The source must be reachable without\n * authentication from Sanity's side; a presigned URL qualifies, a private one\n * behind a login does not.\n *\n * `assets.upload()` always streams a body, so this calls the endpoint directly\n * rather than going through the client's asset helper.\n */\nexport async function ingestAssetFromUrl({\n assetType,\n dataset,\n filename,\n projectId,\n signal,\n url,\n}: IngestAssetFromUrlOptions): Promise<SanityAssetDocument | SanityImageAssetDocument> {\n signal?.throwIfAborted()\n\n const client = await getProjectCliClient({\n apiVersion: ASSETS_API_VERSION,\n dataset,\n projectId,\n requestTagPrefix: ASSET_UPLOAD_REQUEST_TAG_PREFIX,\n requireUser: true,\n })\n signal?.throwIfAborted()\n\n const response = await client.request<{\n document: SanityAssetDocument | SanityImageAssetDocument\n }>({\n body: {url, ...(filename ? {filename} : {})},\n method: 'POST',\n signal,\n tag: 'asset.upload.from-url',\n timeout: URL_INGEST_TIMEOUT_MS,\n url: `/assets/${assetPathSegment(assetType)}/${dataset}/from-url`,\n })\n\n return response.document\n}\n"],"names":["Transform","getProjectCliClient","filter","lastValueFrom","map","Observable","race","ASSETS_API_VERSION","ASSET_UPLOAD_REQUEST_TAG_PREFIX","URL_INGEST_TIMEOUT_MS","assetPathSegment","assetType","uploadAsset","body","contentType","dataset","filename","fileSize","onProgress","projectId","signal","throwIfAborted","client","apiVersion","requestTagPrefix","requireUser","uploadedBytes","uploadStream","transform","chunk","_encoding","callback","length","on","error","destroy","pipe","response$","observable","assets","upload","tag","event","type","document","abort$","subscriber","abortUpload","reason","Error","addEventListener","once","removeEventListener","ingestAssetFromUrl","url","response","request","method","timeout"],"mappings":"AAAA,SAAuBA,SAAS,QAAO,cAAa;AAEpD,SAAQC,mBAAmB,QAAO,mBAAkB;AAEpD,SAAQC,MAAM,EAAEC,aAAa,EAAEC,GAAG,EAAEC,UAAU,EAAEC,IAAI,QAAO,OAAM;AAEjE,OAAO,MAAMC,qBAAqB,cAAa;AAE/C,OAAO,MAAMC,kCAAkC,2BAA0B;AAEzE;;;;CAIC,GACD,OAAO,MAAMC,wBAAwB,QAAO;AAI5C,SAASC,iBAAiBC,SAAoB;IAC5C,OAAOA,cAAc,UAAU,WAAW;AAC5C;AAeA,OAAO,eAAeC,YAAY,EAChCD,SAAS,EACTE,IAAI,EACJC,WAAW,EACXC,OAAO,EACPC,QAAQ,EACRC,QAAQ,EACRC,UAAU,EACVC,SAAS,EACTC,MAAM,EACa;IACnBA,QAAQC;IAER,MAAMC,SAAS,MAAMrB,oBAAoB;QACvCsB,YAAYhB;QACZQ;QACAI;QACAK,kBAAkBhB;QAClBiB,aAAa;IACf;IACAL,QAAQC;IAER,IAAIK,gBAAgB;IACpB,MAAMC,eAAe,IAAI3B,UAAU;QACjC4B,WAAUC,KAAa,EAAEC,SAAS,EAAEC,QAAQ;YAC1CL,iBAAiBG,MAAMG,MAAM;YAC7Bd,aAAa,AAACQ,gBAAgBT,WAAY;YAC1Cc,SAAS,MAAMF;QACjB;IACF;IACAhB,KAAKoB,EAAE,CAAC,SAAS,CAACC,QAAUP,aAAaQ,OAAO,CAACD,QAAQE,IAAI,CAACT;IAE9D,MAAMU,YAAYf,OAAOgB,UAAU,CAACC,MAAM,CACvCC,MAAM,CAAC7B,WAAWgB,cAAc;QAC/B,GAAIb,cAAc;YAACA;QAAW,IAAI,CAAC,CAAC;QACpCE;QACAyB,KAAK;IACP,GACCL,IAAI,CACHlC,OAAO,CAACwC,QAAUA,MAAMC,IAAI,KAAK,aACjCvC,IAAI,CAACsC,QAAUA,MAAM7B,IAAI,CAAC+B,QAAQ;IAGtC,IAAI,CAACxB,QAAQ,OAAOjB,cAAckC;IAElC,MAAMQ,SAAS,IAAIxC,WAAkB,CAACyC;QACpC,MAAMC,cAAc;YAClBlC,KAAKsB,OAAO;YACZR,aAAaQ,OAAO;YACpBW,WAAWZ,KAAK,CAACd,OAAO4B,MAAM,YAAYC,QAAQ7B,OAAO4B,MAAM,GAAG,IAAIC,MAAM;QAC9E;QAEA7B,OAAO8B,gBAAgB,CAAC,SAASH,aAAa;YAACI,MAAM;QAAI;QACzD,OAAO,IAAM/B,OAAOgC,mBAAmB,CAAC,SAASL;IACnD;IAEA,OAAO5C,cAAcG,KAAK+B,WAAWQ;AACvC;AAYA;;;;;;;;;;;CAWC,GACD,OAAO,eAAeQ,mBAAmB,EACvC1C,SAAS,EACTI,OAAO,EACPC,QAAQ,EACRG,SAAS,EACTC,MAAM,EACNkC,GAAG,EACuB;IAC1BlC,QAAQC;IAER,MAAMC,SAAS,MAAMrB,oBAAoB;QACvCsB,YAAYhB;QACZQ;QACAI;QACAK,kBAAkBhB;QAClBiB,aAAa;IACf;IACAL,QAAQC;IAER,MAAMkC,WAAW,MAAMjC,OAAOkC,OAAO,CAElC;QACD3C,MAAM;YAACyC;YAAK,GAAItC,WAAW;gBAACA;YAAQ,IAAI,CAAC,CAAC;QAAC;QAC3CyC,QAAQ;QACRrC;QACAqB,KAAK;QACLiB,SAASjD;QACT6C,KAAK,CAAC,QAAQ,EAAE5C,iBAAiBC,WAAW,CAAC,EAAEI,QAAQ,SAAS,CAAC;IACnE;IAEA,OAAOwC,SAASX,QAAQ;AAC1B"}
@@ -1,9 +1,14 @@
1
1
  import { getGlobalCliClient } from '@sanity/cli-core';
2
2
  import { MEDIA_LIBRARY_ASSET_ASPECT_TYPE_NAME } from '@sanity/types';
3
+ import { ASSET_UPLOAD_REQUEST_TAG_PREFIX, URL_INGEST_TIMEOUT_MS } from './assets.js';
3
4
  export const MEDIA_LIBRARY_API_VERSION = 'v2025-02-19';
4
- async function getMediaLibraryClient() {
5
+ async function getMediaLibraryClient(options = {}) {
6
+ const { requestTagPrefix } = options;
5
7
  return getGlobalCliClient({
6
8
  apiVersion: MEDIA_LIBRARY_API_VERSION,
9
+ ...requestTagPrefix ? {
10
+ requestTagPrefix
11
+ } : {},
7
12
  requireUser: true
8
13
  });
9
14
  }
@@ -70,5 +75,38 @@ async function getMediaLibraryClient() {
70
75
  url: `/media-libraries/${mediaLibraryId}/mutate`
71
76
  });
72
77
  }
78
+ /**
79
+ * Create a media library asset from a URL that Sanity fetches itself.
80
+ *
81
+ * Unlike the dataset equivalent in `./assets.ts`, the asset type is not part of
82
+ * the request — the library derives it from the fetched content — and aspects
83
+ * are set inline here rather than patched on afterwards.
84
+ *
85
+ * The source must be reachable without authentication from Sanity's side; a
86
+ * presigned URL qualifies, a private one behind a login does not.
87
+ *
88
+ * @internal
89
+ */ export async function ingestMediaLibraryAssetFromUrl({ aspects, filename, mediaLibraryId, signal, url }) {
90
+ signal?.throwIfAborted();
91
+ const client = await getMediaLibraryClient({
92
+ requestTagPrefix: ASSET_UPLOAD_REQUEST_TAG_PREFIX
93
+ });
94
+ return client.request({
95
+ body: {
96
+ url,
97
+ ...filename ? {
98
+ filename
99
+ } : {},
100
+ ...aspects ? {
101
+ aspects
102
+ } : {}
103
+ },
104
+ method: 'POST',
105
+ signal,
106
+ tag: 'asset.upload.from-url',
107
+ timeout: URL_INGEST_TIMEOUT_MS,
108
+ url: `/media-libraries/${mediaLibraryId}/from-url`
109
+ });
110
+ }
73
111
 
74
112
  //# sourceMappingURL=mediaLibraries.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/services/mediaLibraries.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\nimport {\n MEDIA_LIBRARY_ASSET_ASPECT_TYPE_NAME,\n type MediaLibraryAssetAspectDocument,\n} from '@sanity/types'\n\nexport const MEDIA_LIBRARY_API_VERSION = 'v2025-02-19'\n\nasync function getMediaLibraryClient() {\n return getGlobalCliClient({\n apiVersion: MEDIA_LIBRARY_API_VERSION,\n requireUser: true,\n })\n}\n\ninterface DeleteAspectOptions {\n aspectName: string\n mediaLibraryId: string\n projectId: string\n}\n\ninterface DeleteAspectResponse {\n results: Array<{id: string}>\n}\n\n/**\n * Delete an aspect from a media library\n * @param options - The options for deleting an aspect\n * @returns A promise that resolves to the deletion response\n *\n * @internal\n */\nexport async function deleteAspect(options: DeleteAspectOptions): Promise<DeleteAspectResponse> {\n const {aspectName, mediaLibraryId} = options\n\n const client = await getMediaLibraryClient()\n\n return client.request<DeleteAspectResponse>({\n body: {\n mutations: [\n {\n delete: {\n params: {\n id: aspectName,\n type: MEDIA_LIBRARY_ASSET_ASPECT_TYPE_NAME,\n },\n query: `*[_type == $type && _id == $id]`,\n },\n },\n ],\n },\n method: 'POST',\n url: `/media-libraries/${mediaLibraryId}/mutate`,\n })\n}\n\nexport interface MediaLibrary {\n id: string\n organizationId: string\n status: 'active' | 'inactive'\n}\n\ninterface MediaLibrariesResponse {\n data: MediaLibrary[]\n}\n\n/**\n * Get a list of media libraries for a project\n * @param projectId - The project ID\n * @returns A promise that resolves to the media libraries\n *\n * @internal\n */\nexport async function getMediaLibraries(projectId: string): Promise<MediaLibrary[]> {\n const client = await getMediaLibraryClient()\n\n const response = await client.request<MediaLibrariesResponse>({\n method: 'GET',\n query: {\n projectId,\n },\n url: `/media-libraries`,\n })\n\n return response.data.filter((library) => library.status === 'active')\n}\n\ninterface DeployAspectsOptions {\n aspects: MediaLibraryAssetAspectDocument[]\n mediaLibraryId: string\n}\n\ninterface DeployAspectsResponse {\n results: Array<{id: string; operation: string}>\n}\n\n/**\n * Deploy one or more aspects to a media library\n * @param options - The options for deploying aspects\n * @returns A promise that resolves to the deployment response\n *\n * @internal\n */\nexport async function deployAspects(options: DeployAspectsOptions): Promise<DeployAspectsResponse> {\n const {aspects, mediaLibraryId} = options\n\n const client = await getMediaLibraryClient()\n\n return client.request<DeployAspectsResponse>({\n body: {\n mutations: aspects.map((aspect) => ({\n createOrReplace: aspect,\n })),\n },\n method: 'POST',\n url: `/media-libraries/${mediaLibraryId}/mutate`,\n })\n}\n"],"names":["getGlobalCliClient","MEDIA_LIBRARY_ASSET_ASPECT_TYPE_NAME","MEDIA_LIBRARY_API_VERSION","getMediaLibraryClient","apiVersion","requireUser","deleteAspect","options","aspectName","mediaLibraryId","client","request","body","mutations","delete","params","id","type","query","method","url","getMediaLibraries","projectId","response","data","filter","library","status","deployAspects","aspects","map","aspect","createOrReplace"],"mappings":"AAAA,SAAQA,kBAAkB,QAAO,mBAAkB;AACnD,SACEC,oCAAoC,QAE/B,gBAAe;AAEtB,OAAO,MAAMC,4BAA4B,cAAa;AAEtD,eAAeC;IACb,OAAOH,mBAAmB;QACxBI,YAAYF;QACZG,aAAa;IACf;AACF;AAYA;;;;;;CAMC,GACD,OAAO,eAAeC,aAAaC,OAA4B;IAC7D,MAAM,EAACC,UAAU,EAAEC,cAAc,EAAC,GAAGF;IAErC,MAAMG,SAAS,MAAMP;IAErB,OAAOO,OAAOC,OAAO,CAAuB;QAC1CC,MAAM;YACJC,WAAW;gBACT;oBACEC,QAAQ;wBACNC,QAAQ;4BACNC,IAAIR;4BACJS,MAAMhB;wBACR;wBACAiB,OAAO,CAAC,+BAA+B,CAAC;oBAC1C;gBACF;aACD;QACH;QACAC,QAAQ;QACRC,KAAK,CAAC,iBAAiB,EAAEX,eAAe,OAAO,CAAC;IAClD;AACF;AAYA;;;;;;CAMC,GACD,OAAO,eAAeY,kBAAkBC,SAAiB;IACvD,MAAMZ,SAAS,MAAMP;IAErB,MAAMoB,WAAW,MAAMb,OAAOC,OAAO,CAAyB;QAC5DQ,QAAQ;QACRD,OAAO;YACLI;QACF;QACAF,KAAK,CAAC,gBAAgB,CAAC;IACzB;IAEA,OAAOG,SAASC,IAAI,CAACC,MAAM,CAAC,CAACC,UAAYA,QAAQC,MAAM,KAAK;AAC9D;AAWA;;;;;;CAMC,GACD,OAAO,eAAeC,cAAcrB,OAA6B;IAC/D,MAAM,EAACsB,OAAO,EAAEpB,cAAc,EAAC,GAAGF;IAElC,MAAMG,SAAS,MAAMP;IAErB,OAAOO,OAAOC,OAAO,CAAwB;QAC3CC,MAAM;YACJC,WAAWgB,QAAQC,GAAG,CAAC,CAACC,SAAY,CAAA;oBAClCC,iBAAiBD;gBACnB,CAAA;QACF;QACAZ,QAAQ;QACRC,KAAK,CAAC,iBAAiB,EAAEX,eAAe,OAAO,CAAC;IAClD;AACF"}
1
+ {"version":3,"sources":["../../src/services/mediaLibraries.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\nimport {\n type FileAsset,\n type ImageAsset,\n MEDIA_LIBRARY_ASSET_ASPECT_TYPE_NAME,\n type MediaLibraryAssetAspectDocument,\n type SanityDocument,\n} from '@sanity/types'\n\nimport {ASSET_UPLOAD_REQUEST_TAG_PREFIX, URL_INGEST_TIMEOUT_MS} from './assets.js'\n\nexport const MEDIA_LIBRARY_API_VERSION = 'v2025-02-19'\n\nasync function getMediaLibraryClient(options: {requestTagPrefix?: string} = {}) {\n const {requestTagPrefix} = options\n\n return getGlobalCliClient({\n apiVersion: MEDIA_LIBRARY_API_VERSION,\n ...(requestTagPrefix ? {requestTagPrefix} : {}),\n requireUser: true,\n })\n}\n\ninterface DeleteAspectOptions {\n aspectName: string\n mediaLibraryId: string\n projectId: string\n}\n\ninterface DeleteAspectResponse {\n results: Array<{id: string}>\n}\n\n/**\n * Delete an aspect from a media library\n * @param options - The options for deleting an aspect\n * @returns A promise that resolves to the deletion response\n *\n * @internal\n */\nexport async function deleteAspect(options: DeleteAspectOptions): Promise<DeleteAspectResponse> {\n const {aspectName, mediaLibraryId} = options\n\n const client = await getMediaLibraryClient()\n\n return client.request<DeleteAspectResponse>({\n body: {\n mutations: [\n {\n delete: {\n params: {\n id: aspectName,\n type: MEDIA_LIBRARY_ASSET_ASPECT_TYPE_NAME,\n },\n query: `*[_type == $type && _id == $id]`,\n },\n },\n ],\n },\n method: 'POST',\n url: `/media-libraries/${mediaLibraryId}/mutate`,\n })\n}\n\nexport interface MediaLibrary {\n id: string\n organizationId: string\n status: 'active' | 'inactive'\n}\n\ninterface MediaLibrariesResponse {\n data: MediaLibrary[]\n}\n\n/**\n * Get a list of media libraries for a project\n * @param projectId - The project ID\n * @returns A promise that resolves to the media libraries\n *\n * @internal\n */\nexport async function getMediaLibraries(projectId: string): Promise<MediaLibrary[]> {\n const client = await getMediaLibraryClient()\n\n const response = await client.request<MediaLibrariesResponse>({\n method: 'GET',\n query: {\n projectId,\n },\n url: `/media-libraries`,\n })\n\n return response.data.filter((library) => library.status === 'active')\n}\n\ninterface DeployAspectsOptions {\n aspects: MediaLibraryAssetAspectDocument[]\n mediaLibraryId: string\n}\n\ninterface DeployAspectsResponse {\n results: Array<{id: string; operation: string}>\n}\n\n/**\n * Deploy one or more aspects to a media library\n * @param options - The options for deploying aspects\n * @returns A promise that resolves to the deployment response\n *\n * @internal\n */\nexport async function deployAspects(options: DeployAspectsOptions): Promise<DeployAspectsResponse> {\n const {aspects, mediaLibraryId} = options\n\n const client = await getMediaLibraryClient()\n\n return client.request<DeployAspectsResponse>({\n body: {\n mutations: aspects.map((aspect) => ({\n createOrReplace: aspect,\n })),\n },\n method: 'POST',\n url: `/media-libraries/${mediaLibraryId}/mutate`,\n })\n}\n\n/**\n * The pair of documents a media library creates for one asset: the\n * `sanity.asset` document that carries aspects, and the asset instance holding\n * the file metadata.\n *\n * @internal\n */\nexport interface MediaLibraryAsset {\n asset: SanityDocument & {\n _type: 'sanity.asset'\n aspects: unknown\n assetType: FileAsset['_type'] | ImageAsset['_type']\n }\n assetInstance: FileAsset | ImageAsset\n}\n\ninterface IngestMediaLibraryAssetFromUrlOptions {\n mediaLibraryId: string\n url: string\n\n aspects?: Record<string, unknown>\n filename?: string\n signal?: AbortSignal\n}\n\n/**\n * Create a media library asset from a URL that Sanity fetches itself.\n *\n * Unlike the dataset equivalent in `./assets.ts`, the asset type is not part of\n * the request — the library derives it from the fetched content — and aspects\n * are set inline here rather than patched on afterwards.\n *\n * The source must be reachable without authentication from Sanity's side; a\n * presigned URL qualifies, a private one behind a login does not.\n *\n * @internal\n */\nexport async function ingestMediaLibraryAssetFromUrl({\n aspects,\n filename,\n mediaLibraryId,\n signal,\n url,\n}: IngestMediaLibraryAssetFromUrlOptions): Promise<MediaLibraryAsset> {\n signal?.throwIfAborted()\n\n const client = await getMediaLibraryClient({\n requestTagPrefix: ASSET_UPLOAD_REQUEST_TAG_PREFIX,\n })\n\n return client.request<MediaLibraryAsset>({\n body: {\n url,\n ...(filename ? {filename} : {}),\n ...(aspects ? {aspects} : {}),\n },\n method: 'POST',\n signal,\n tag: 'asset.upload.from-url',\n timeout: URL_INGEST_TIMEOUT_MS,\n url: `/media-libraries/${mediaLibraryId}/from-url`,\n })\n}\n"],"names":["getGlobalCliClient","MEDIA_LIBRARY_ASSET_ASPECT_TYPE_NAME","ASSET_UPLOAD_REQUEST_TAG_PREFIX","URL_INGEST_TIMEOUT_MS","MEDIA_LIBRARY_API_VERSION","getMediaLibraryClient","options","requestTagPrefix","apiVersion","requireUser","deleteAspect","aspectName","mediaLibraryId","client","request","body","mutations","delete","params","id","type","query","method","url","getMediaLibraries","projectId","response","data","filter","library","status","deployAspects","aspects","map","aspect","createOrReplace","ingestMediaLibraryAssetFromUrl","filename","signal","throwIfAborted","tag","timeout"],"mappings":"AAAA,SAAQA,kBAAkB,QAAO,mBAAkB;AACnD,SAGEC,oCAAoC,QAG/B,gBAAe;AAEtB,SAAQC,+BAA+B,EAAEC,qBAAqB,QAAO,cAAa;AAElF,OAAO,MAAMC,4BAA4B,cAAa;AAEtD,eAAeC,sBAAsBC,UAAuC,CAAC,CAAC;IAC5E,MAAM,EAACC,gBAAgB,EAAC,GAAGD;IAE3B,OAAON,mBAAmB;QACxBQ,YAAYJ;QACZ,GAAIG,mBAAmB;YAACA;QAAgB,IAAI,CAAC,CAAC;QAC9CE,aAAa;IACf;AACF;AAYA;;;;;;CAMC,GACD,OAAO,eAAeC,aAAaJ,OAA4B;IAC7D,MAAM,EAACK,UAAU,EAAEC,cAAc,EAAC,GAAGN;IAErC,MAAMO,SAAS,MAAMR;IAErB,OAAOQ,OAAOC,OAAO,CAAuB;QAC1CC,MAAM;YACJC,WAAW;gBACT;oBACEC,QAAQ;wBACNC,QAAQ;4BACNC,IAAIR;4BACJS,MAAMnB;wBACR;wBACAoB,OAAO,CAAC,+BAA+B,CAAC;oBAC1C;gBACF;aACD;QACH;QACAC,QAAQ;QACRC,KAAK,CAAC,iBAAiB,EAAEX,eAAe,OAAO,CAAC;IAClD;AACF;AAYA;;;;;;CAMC,GACD,OAAO,eAAeY,kBAAkBC,SAAiB;IACvD,MAAMZ,SAAS,MAAMR;IAErB,MAAMqB,WAAW,MAAMb,OAAOC,OAAO,CAAyB;QAC5DQ,QAAQ;QACRD,OAAO;YACLI;QACF;QACAF,KAAK,CAAC,gBAAgB,CAAC;IACzB;IAEA,OAAOG,SAASC,IAAI,CAACC,MAAM,CAAC,CAACC,UAAYA,QAAQC,MAAM,KAAK;AAC9D;AAWA;;;;;;CAMC,GACD,OAAO,eAAeC,cAAczB,OAA6B;IAC/D,MAAM,EAAC0B,OAAO,EAAEpB,cAAc,EAAC,GAAGN;IAElC,MAAMO,SAAS,MAAMR;IAErB,OAAOQ,OAAOC,OAAO,CAAwB;QAC3CC,MAAM;YACJC,WAAWgB,QAAQC,GAAG,CAAC,CAACC,SAAY,CAAA;oBAClCC,iBAAiBD;gBACnB,CAAA;QACF;QACAZ,QAAQ;QACRC,KAAK,CAAC,iBAAiB,EAAEX,eAAe,OAAO,CAAC;IAClD;AACF;AA2BA;;;;;;;;;;;CAWC,GACD,OAAO,eAAewB,+BAA+B,EACnDJ,OAAO,EACPK,QAAQ,EACRzB,cAAc,EACd0B,MAAM,EACNf,GAAG,EACmC;IACtCe,QAAQC;IAER,MAAM1B,SAAS,MAAMR,sBAAsB;QACzCE,kBAAkBL;IACpB;IAEA,OAAOW,OAAOC,OAAO,CAAoB;QACvCC,MAAM;YACJQ;YACA,GAAIc,WAAW;gBAACA;YAAQ,IAAI,CAAC,CAAC;YAC9B,GAAIL,UAAU;gBAACA;YAAO,IAAI,CAAC,CAAC;QAC9B;QACAV,QAAQ;QACRgB;QACAE,KAAK;QACLC,SAAStC;QACToB,KAAK,CAAC,iBAAiB,EAAEX,eAAe,SAAS,CAAC;IACpD;AACF"}
@@ -0,0 +1,56 @@
1
+ const MAX_URL_LENGTH = 2048;
2
+ const MAX_FILENAME_LENGTH = 255;
3
+ /**
4
+ * Check a URL that Sanity will fetch an asset from.
5
+ *
6
+ * Sanity fetches the source itself, over https and without credentials, so a
7
+ * URL it cannot act on is worth rejecting here rather than spending a request
8
+ * to have it come back as an opaque server error.
9
+ *
10
+ * Embedded credentials are rejected rather than stripped: they would travel to
11
+ * Sanity and be recorded wherever the request is, and a presigned URL is the
12
+ * supported way to reach a source that needs authentication.
13
+ *
14
+ * @returns The problem with the URL, or `undefined` when it is usable.
15
+ *
16
+ * @internal
17
+ */ export function getIngestUrlError(url) {
18
+ if (url.length > MAX_URL_LENGTH) {
19
+ return `The asset URL must be ${MAX_URL_LENGTH} characters or fewer, but is ${url.length}. Shorten the URL, then try again.`;
20
+ }
21
+ if (!URL.canParse(url)) {
22
+ return `"${url}" is not a valid URL. Pass a full URL, such as https://example.com/hero.png.`;
23
+ }
24
+ const { password, protocol, username } = new URL(url);
25
+ if (protocol !== 'https:') {
26
+ return `The asset URL must use https, not "${protocol}". Sanity fetches the asset over the public internet.`;
27
+ }
28
+ if (username || password) {
29
+ return 'The asset URL must not contain a username or password. Use a presigned URL when the source needs authentication.';
30
+ }
31
+ return undefined;
32
+ }
33
+ /**
34
+ * Check a `--filename` value against what an asset document can store.
35
+ *
36
+ * Only validates a filename the user supplied. A filename derived from a local
37
+ * path is already a single path segment, and on POSIX it may legitimately
38
+ * contain the backslash rejected here.
39
+ *
40
+ * @returns The problem with the filename, or `undefined` when it is usable.
41
+ *
42
+ * @internal
43
+ */ export function getAssetFilenameError(filename) {
44
+ if (filename.length === 0) {
45
+ return '--filename must not be empty. Pass a filename, or omit the flag to let Sanity derive one.';
46
+ }
47
+ if (filename.length > MAX_FILENAME_LENGTH) {
48
+ return `--filename must be ${MAX_FILENAME_LENGTH} characters or fewer, but is ${filename.length}. Shorten the filename, then try again.`;
49
+ }
50
+ if (/[/\\\0]/.test(filename)) {
51
+ return '--filename must not contain path separators or null bytes. Pass the filename on its own, such as hero.png.';
52
+ }
53
+ return undefined;
54
+ }
55
+
56
+ //# sourceMappingURL=assetSourceValidation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/util/assetSourceValidation.ts"],"sourcesContent":["const MAX_URL_LENGTH = 2048\nconst MAX_FILENAME_LENGTH = 255\n\n/**\n * Check a URL that Sanity will fetch an asset from.\n *\n * Sanity fetches the source itself, over https and without credentials, so a\n * URL it cannot act on is worth rejecting here rather than spending a request\n * to have it come back as an opaque server error.\n *\n * Embedded credentials are rejected rather than stripped: they would travel to\n * Sanity and be recorded wherever the request is, and a presigned URL is the\n * supported way to reach a source that needs authentication.\n *\n * @returns The problem with the URL, or `undefined` when it is usable.\n *\n * @internal\n */\nexport function getIngestUrlError(url: string): string | undefined {\n if (url.length > MAX_URL_LENGTH) {\n return `The asset URL must be ${MAX_URL_LENGTH} characters or fewer, but is ${url.length}. Shorten the URL, then try again.`\n }\n\n if (!URL.canParse(url)) {\n return `\"${url}\" is not a valid URL. Pass a full URL, such as https://example.com/hero.png.`\n }\n\n const {password, protocol, username} = new URL(url)\n\n if (protocol !== 'https:') {\n return `The asset URL must use https, not \"${protocol}\". Sanity fetches the asset over the public internet.`\n }\n\n if (username || password) {\n return 'The asset URL must not contain a username or password. Use a presigned URL when the source needs authentication.'\n }\n\n return undefined\n}\n\n/**\n * Check a `--filename` value against what an asset document can store.\n *\n * Only validates a filename the user supplied. A filename derived from a local\n * path is already a single path segment, and on POSIX it may legitimately\n * contain the backslash rejected here.\n *\n * @returns The problem with the filename, or `undefined` when it is usable.\n *\n * @internal\n */\nexport function getAssetFilenameError(filename: string): string | undefined {\n if (filename.length === 0) {\n return '--filename must not be empty. Pass a filename, or omit the flag to let Sanity derive one.'\n }\n\n if (filename.length > MAX_FILENAME_LENGTH) {\n return `--filename must be ${MAX_FILENAME_LENGTH} characters or fewer, but is ${filename.length}. Shorten the filename, then try again.`\n }\n\n if (/[/\\\\\\0]/.test(filename)) {\n return '--filename must not contain path separators or null bytes. Pass the filename on its own, such as hero.png.'\n }\n\n return undefined\n}\n"],"names":["MAX_URL_LENGTH","MAX_FILENAME_LENGTH","getIngestUrlError","url","length","URL","canParse","password","protocol","username","undefined","getAssetFilenameError","filename","test"],"mappings":"AAAA,MAAMA,iBAAiB;AACvB,MAAMC,sBAAsB;AAE5B;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,kBAAkBC,GAAW;IAC3C,IAAIA,IAAIC,MAAM,GAAGJ,gBAAgB;QAC/B,OAAO,CAAC,sBAAsB,EAAEA,eAAe,6BAA6B,EAAEG,IAAIC,MAAM,CAAC,kCAAkC,CAAC;IAC9H;IAEA,IAAI,CAACC,IAAIC,QAAQ,CAACH,MAAM;QACtB,OAAO,CAAC,CAAC,EAAEA,IAAI,4EAA4E,CAAC;IAC9F;IAEA,MAAM,EAACI,QAAQ,EAAEC,QAAQ,EAAEC,QAAQ,EAAC,GAAG,IAAIJ,IAAIF;IAE/C,IAAIK,aAAa,UAAU;QACzB,OAAO,CAAC,mCAAmC,EAAEA,SAAS,qDAAqD,CAAC;IAC9G;IAEA,IAAIC,YAAYF,UAAU;QACxB,OAAO;IACT;IAEA,OAAOG;AACT;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASC,sBAAsBC,QAAgB;IACpD,IAAIA,SAASR,MAAM,KAAK,GAAG;QACzB,OAAO;IACT;IAEA,IAAIQ,SAASR,MAAM,GAAGH,qBAAqB;QACzC,OAAO,CAAC,mBAAmB,EAAEA,oBAAoB,6BAA6B,EAAEW,SAASR,MAAM,CAAC,uCAAuC,CAAC;IAC1I;IAEA,IAAI,UAAUS,IAAI,CAACD,WAAW;QAC5B,OAAO;IACT;IAEA,OAAOF;AACT"}