@sanity/cli 8.22.0 → 8.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/context/watchJob.js +9 -3
- package/dist/actions/context/watchJob.js.map +1 -1
- package/dist/actions/deploy/blueprintManaged.js +59 -0
- package/dist/actions/deploy/blueprintManaged.js.map +1 -0
- package/dist/actions/deploy/deployApp.js +117 -1
- package/dist/actions/deploy/deployApp.js.map +1 -1
- package/dist/actions/deploy/deployRunner.js.map +1 -1
- package/dist/actions/deploy/deployStudio.js +6 -31
- package/dist/actions/deploy/deployStudio.js.map +1 -1
- package/dist/actions/deploy/deployStudioSchemasAndManifestsWorker.js +6 -15
- package/dist/actions/deploy/deployStudioSchemasAndManifestsWorker.js.map +1 -1
- package/dist/actions/exec/runScript.worker.js +6 -2
- package/dist/actions/exec/runScript.worker.js.map +1 -1
- package/dist/actions/init/bootstrapLocalTemplate.js +35 -11
- package/dist/actions/init/bootstrapLocalTemplate.js.map +1 -1
- package/dist/actions/init/createMediaLibraryCliConfig.js +12 -0
- package/dist/actions/init/createMediaLibraryCliConfig.js.map +1 -0
- package/dist/actions/init/determineMediaLibraryTemplate.js +14 -0
- package/dist/actions/init/determineMediaLibraryTemplate.js.map +1 -0
- package/dist/actions/init/initAction.js +35 -6
- package/dist/actions/init/initAction.js.map +1 -1
- package/dist/actions/init/initApp.js +81 -6
- package/dist/actions/init/initApp.js.map +1 -1
- package/dist/actions/init/offerAspectPull.js +81 -0
- package/dist/actions/init/offerAspectPull.js.map +1 -0
- package/dist/actions/init/project/getProjectDetails.js +25 -2
- package/dist/actions/init/project/getProjectDetails.js.map +1 -1
- package/dist/actions/init/templates/index.js +3 -0
- package/dist/actions/init/templates/index.js.map +1 -1
- package/dist/actions/init/templates/mediaLibraryConfig.js +51 -0
- package/dist/actions/init/templates/mediaLibraryConfig.js.map +1 -0
- package/dist/actions/media/aspectName.js +8 -0
- package/dist/actions/media/aspectName.js.map +1 -0
- package/dist/actions/media/aspectSync/contract.js +163 -0
- package/dist/actions/media/aspectSync/contract.js.map +1 -0
- package/dist/actions/media/aspectSync/syncAspects.js +115 -0
- package/dist/actions/media/aspectSync/syncAspects.js.map +1 -0
- package/dist/actions/media/cliConfigFields.js +192 -0
- package/dist/actions/media/cliConfigFields.js.map +1 -0
- package/dist/actions/media/pullAspects.js +225 -0
- package/dist/actions/media/pullAspects.js.map +1 -0
- package/dist/actions/media/resolveMediaLibraryId.js +83 -0
- package/dist/actions/media/resolveMediaLibraryId.js.map +1 -0
- package/dist/actions/schema/deploySchemas.js.map +1 -1
- package/dist/actions/schema/extractSchemaWatcher.js +69 -14
- package/dist/actions/schema/extractSchemaWatcher.js.map +1 -1
- package/dist/actions/schema/updateWorkspaceSchema.js.map +1 -1
- package/dist/commands/context/build.js +2 -2
- package/dist/commands/context/build.js.map +1 -1
- package/dist/commands/context/entries/get.js +83 -0
- package/dist/commands/context/entries/get.js.map +1 -0
- package/dist/commands/context/entries/list.js +82 -0
- package/dist/commands/context/entries/list.js.map +1 -0
- package/dist/commands/context/entries/rebuild.js +95 -0
- package/dist/commands/context/entries/rebuild.js.map +1 -0
- package/dist/commands/context/issues/apply.js +86 -0
- package/dist/commands/context/issues/apply.js.map +1 -0
- package/dist/commands/context/issues/dismiss.js +52 -0
- package/dist/commands/context/issues/dismiss.js.map +1 -0
- package/dist/commands/context/issues/get.js +105 -0
- package/dist/commands/context/issues/get.js.map +1 -0
- package/dist/commands/context/issues/list.js +128 -0
- package/dist/commands/context/issues/list.js.map +1 -0
- package/dist/commands/context/issues/reopen.js +53 -0
- package/dist/commands/context/issues/reopen.js.map +1 -0
- package/dist/commands/context/issues/resolve.js +137 -0
- package/dist/commands/context/issues/resolve.js.map +1 -0
- package/dist/commands/context/jobs/get.js +2 -2
- package/dist/commands/context/jobs/get.js.map +1 -1
- package/dist/commands/context/query.js +92 -0
- package/dist/commands/context/query.js.map +1 -0
- package/dist/commands/deploy.js +13 -0
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/media/prune-aspect-data.js +170 -0
- package/dist/commands/media/prune-aspect-data.js.map +1 -0
- package/dist/commands/media/pull-aspects.js +309 -0
- package/dist/commands/media/pull-aspects.js.map +1 -0
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +17 -0
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
- package/dist/services/context.js +96 -3
- package/dist/services/context.js.map +1 -1
- package/dist/services/mediaLibraries.js +175 -8
- package/dist/services/mediaLibraries.js.map +1 -1
- package/oclif.manifest.json +1050 -361
- package/package.json +6 -5
- package/templates/media-library-config/README.md +52 -0
- package/templates/media-library-config/eslint.config.txt +52 -0
- package/templates/media-library-config/src/fields/caption.tsx +29 -0
- package/templates/media-library-config/src/fields/notes.tsx +54 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/exports/invokeSanityCli/commandPolicies/mcpPolicy.ts"],"sourcesContent":["import {\n allow,\n type CommandPolicySet,\n conditionalDenyFlags,\n conditionalPolicy,\n deny,\n} from '@sanity/cli-core/commandPolicy'\n\nimport {isRemoteAssetSource} from '../../../util/isRemoteAssetSource.js'\n\nfunction apiValidator({\n args,\n flags,\n}: {\n args: Readonly<Record<string, unknown>>\n flags: Readonly<Record<string, unknown>>\n}): boolean {\n const fields: unknown[] = Array.isArray(flags.field) ? flags.field : []\n\n const fieldReadsFromHost = fields.some((field) => {\n if (typeof field !== 'string') return false\n const separatorIndex = field.indexOf('=')\n return separatorIndex > 0 && field[separatorIndex + 1] === '@'\n })\n if (fieldReadsFromHost) return false\n\n const headers: unknown[] = Array.isArray(flags.header) ? flags.header : []\n\n const headerOverridesAuthentication = headers.some((header) => {\n if (typeof header !== 'string') return false\n const separatorIndex = header.indexOf(':')\n if (separatorIndex <= 0) return false\n\n const name = header.slice(0, separatorIndex).trim().toLowerCase()\n return name === 'authorization' || name === 'cookie'\n })\n\n if (headerOverridesAuthentication) return false\n\n const endpoint = typeof args.endpoint === 'string' ? URL.parse(args.endpoint) : null\n const urlEmbedsCredentials =\n endpoint !== null && (endpoint.username !== '' || endpoint.password !== '')\n\n return !urlEmbedsCredentials\n}\n\n/**\n * MCP programmatic mode disables local project/config discovery (see the CLI\n * execution context). Missing project or dataset values may therefore produce\n * a usage error, but cannot cause local filesystem access. Destructive remote\n * operations are allowed and do not by themselves make a command unsafe.\n *\n * Every command this package contributes must have exactly one policy here:\n * - allow: every valid invocation is safe\n * - conditional: safety depends on parsed arguments or flags\n * - deny: no invocation is safe\n *\n * Commands contributed by plugins are not listed here. Each plugin declares\n * policies for its own commands (see `PluginInvocationPolicies` in\n * `@sanity/cli-core/commandPolicy`).\n */\nexport const mcpPolicy: CommandPolicySet = {\n // Special exception, this can be very dangerous but is also super useful\n // to expose. Refuse authentication overrides and host input channels:\n // `--token`, Authorization, and Cookie headers replace the MCP user's authentication,\n // URL-embedded credentials replace it with Basic authentication,\n // `--input` reads the request body from the host's filesystem or stdin, and\n // `-F key=@<file>` / `-F key=@-` field values do the same.\n api: conditionalPolicy({\n deniedFlags: ['input', 'token'],\n validate: apiValidator,\n }),\n\n // --file reads from the machine running the command and must only run locally.\n 'assets:upload': conditionalPolicy({\n deniedFlags: ['content-type', 'file'],\n validate: ({flags}) => typeof flags['from-url'] === 'string' && flags['from-url'].length > 0,\n }),\n\n 'backups:disable': allow,\n // Writes a downloaded backup to the local filesystem.\n 'backups:download': deny,\n 'backups:enable': allow,\n 'backups:list': allow,\n\n // Requires a local Studio project and writes build output to disk.\n build: deny,\n\n // Reads and rewrites local source code.\n codemod: deny,\n\n // --watch polls a job with no deadline; a stuck job would hang the MCP tool\n // call indefinitely. Non-watch invocations are single remote requests.\n 'context:build': conditionalDenyFlags('watch'),\n 'context:create': allow,\n 'context:delete': allow,\n 'context:get': allow,\n // --file reads the import payload from the local filesystem; text, URL and\n // dataset imports are remote-only.\n 'context:imports:create': conditionalDenyFlags('file'),\n 'context:imports:delete': allow,\n 'context:imports:download': allow,\n 'context:imports:get': allow,\n 'context:imports:list': allow,\n // See context:build — --watch has no deadline and would hang the MCP tool call.\n 'context:jobs:get': conditionalDenyFlags('watch'),\n 'context:list': allow,\n 'context:refresh': allow,\n 'context:update': allow,\n\n 'cors:add': allow,\n 'cors:delete': allow,\n 'cors:list': allow,\n\n 'datasets:alias:create': allow,\n 'datasets:alias:delete': allow,\n 'datasets:alias:link': allow,\n 'datasets:alias:unlink': allow,\n 'datasets:copy': allow,\n 'datasets:create': allow,\n 'datasets:delete': allow,\n 'datasets:embeddings:disable': allow,\n 'datasets:embeddings:enable': allow,\n 'datasets:embeddings:status': allow,\n // Writes dataset contents and assets to the local filesystem.\n 'datasets:export': deny,\n // Reads import data from the local filesystem and may replace documents.\n 'datasets:import': deny,\n 'datasets:list': allow,\n 'datasets:visibility:get': allow,\n 'datasets:visibility:set': allow,\n\n // Inspects local project files and can print authentication secrets.\n debug: deny,\n\n // Reads, builds, and deploys a local Studio project.\n deploy: deny,\n\n // Loads a local Studio project and starts a development server.\n dev: deny,\n\n // Opens a browser on the machine running the MCP server.\n 'docs:browse': deny,\n // --web opens a browser on the machine running the MCP server.\n 'docs:read': conditionalDenyFlags('web'),\n 'docs:search': allow,\n\n // Reads and executes local project configuration for diagnostics.\n doctor: deny,\n\n // Reads document input from disk or launches a local editor.\n 'documents:create': deny,\n 'documents:delete': allow,\n 'documents:get': allow,\n 'documents:query': allow,\n // Loads a local Studio schema to validate documents.\n 'documents:validate': deny,\n\n // Executes arbitrary code in the local Studio context.\n exec: deny,\n\n // Loads a local schema and deploys a GraphQL API.\n 'graphql:deploy': deny,\n 'graphql:list': allow,\n // --api loads GraphQL definitions from the local project; explicit project/dataset flags are safe.\n 'graphql:undeploy': conditionalDenyFlags('api'),\n\n 'hooks:attempt': allow,\n 'hooks:create': allow,\n 'hooks:delete': allow,\n 'hooks:list': allow,\n 'hooks:logs': allow,\n\n // Creates or modifies a local project and may install dependencies.\n init: deny,\n\n // Installs packages into the local project.\n install: deny,\n\n // Opens a browser on the machine running the MCP server.\n learn: deny,\n\n // Performs an authentication flow.\n login: deny,\n\n // Performs an authentication operation and clears local credentials.\n logout: deny,\n\n // Reads local project configuration and opens a browser.\n manage: deny,\n\n // Loads local Studio configuration and writes manifest files.\n 'manifest:extract': deny,\n\n // Reads and writes local MCP client configuration.\n 'mcp:configure': deny,\n\n // Writes an aspect definition to the local filesystem.\n 'media:create-aspect': deny,\n 'media:delete-aspect': allow,\n // Reads a local aspect definition before deploying it.\n 'media:deploy-aspect': deny,\n // Writes media assets to the local filesystem.\n 'media:export': deny,\n // A directory or archive source reads media assets from the local\n // filesystem. A URL source does not: Sanity fetches the asset itself, so the\n // only local input is the URL. `--replace-aspects` is meaningless for a URL\n // source (the command rejects the combination), so it is hidden here rather\n // than advertised as usable surface.\n 'media:import': conditionalPolicy({\n deniedFlags: ['replace-aspects'],\n validate: ({args}) => typeof args.source === 'string' && isRemoteAssetSource(args.source),\n }),\n\n // Creates migration source files in the local project.\n 'migrations:create': deny,\n // Reads and loads migration definitions from the local project.\n 'migrations:list': deny,\n // Executes local migration code that may perform arbitrary document mutations.\n 'migrations:run': deny,\n\n // Writes project setup values and scaffolds local applications.\n new: deny,\n\n // --web opens a browser on the machine running the MCP server.\n 'openapi:get': conditionalDenyFlags('web'),\n 'openapi:list': conditionalDenyFlags('web'),\n\n 'organizations:create': allow,\n 'organizations:delete': allow,\n 'organizations:get': allow,\n 'organizations:list': allow,\n 'organizations:update': allow,\n\n // Serves a local production build.\n preview: deny,\n\n 'projects:create': allow,\n 'projects:list': allow,\n // Reads locally stored claim URLs and robot tokens.\n 'projects:unclaimed': deny,\n\n // Loads the local Studio configuration to resolve the datasets containing each schema.\n 'schemas:delete': deny,\n // Loads local schema files before deploying them.\n 'schemas:deploy': deny,\n // Loads local Studio configuration and writes an extracted schema to disk.\n 'schemas:extract': deny,\n // Requires a local project and loads its schema configuration.\n 'schemas:list': deny,\n // Loads and executes a local Studio schema.\n 'schemas:validate': deny,\n\n // Installs skills into local editor configuration directories.\n 'skills:install': deny,\n\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:disable': deny,\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:enable': deny,\n 'telemetry:status': allow,\n\n // Creates authentication credentials.\n 'tokens:create': deny,\n // Deletes authentication credentials.\n 'tokens:delete': deny,\n // Exposes authentication credential metadata.\n 'tokens:list': deny,\n // Replaces authentication credentials and exposes the new secret.\n 'tokens:rotate': deny,\n\n // Reads the local schema and source files and writes generated types to the project.\n 'typegen:generate': deny,\n\n // Loads local CLI and workbench configuration to identify the deployed Studio or application.\n undeploy: deny,\n\n // Grants a user access to a project.\n 'users:invite': deny,\n 'users:list': allow,\n\n // Reads the local project and installed package tree.\n versions: deny,\n\n // Reads the authenticated user from the API using the execution context token.\n whoami: allow,\n}\n"],"names":["allow","conditionalDenyFlags","conditionalPolicy","deny","isRemoteAssetSource","apiValidator","args","flags","fields","Array","isArray","field","fieldReadsFromHost","some","separatorIndex","indexOf","headers","header","headerOverridesAuthentication","name","slice","trim","toLowerCase","endpoint","URL","parse","urlEmbedsCredentials","username","password","mcpPolicy","api","deniedFlags","validate","length","build","codemod","debug","deploy","dev","doctor","exec","init","install","learn","login","logout","manage","source","new","preview","undeploy","versions","whoami"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../src/exports/invokeSanityCli/commandPolicies/mcpPolicy.ts"],"sourcesContent":["import {\n allow,\n type CommandPolicySet,\n conditionalDenyFlags,\n conditionalPolicy,\n deny,\n} from '@sanity/cli-core/commandPolicy'\n\nimport {isRemoteAssetSource} from '../../../util/isRemoteAssetSource.js'\n\nfunction apiValidator({\n args,\n flags,\n}: {\n args: Readonly<Record<string, unknown>>\n flags: Readonly<Record<string, unknown>>\n}): boolean {\n const fields: unknown[] = Array.isArray(flags.field) ? flags.field : []\n\n const fieldReadsFromHost = fields.some((field) => {\n if (typeof field !== 'string') return false\n const separatorIndex = field.indexOf('=')\n return separatorIndex > 0 && field[separatorIndex + 1] === '@'\n })\n if (fieldReadsFromHost) return false\n\n const headers: unknown[] = Array.isArray(flags.header) ? flags.header : []\n\n const headerOverridesAuthentication = headers.some((header) => {\n if (typeof header !== 'string') return false\n const separatorIndex = header.indexOf(':')\n if (separatorIndex <= 0) return false\n\n const name = header.slice(0, separatorIndex).trim().toLowerCase()\n return name === 'authorization' || name === 'cookie'\n })\n\n if (headerOverridesAuthentication) return false\n\n const endpoint = typeof args.endpoint === 'string' ? URL.parse(args.endpoint) : null\n const urlEmbedsCredentials =\n endpoint !== null && (endpoint.username !== '' || endpoint.password !== '')\n\n return !urlEmbedsCredentials\n}\n\n/**\n * MCP programmatic mode disables local project/config discovery (see the CLI\n * execution context). Missing project or dataset values may therefore produce\n * a usage error, but cannot cause local filesystem access. Destructive remote\n * operations are allowed and do not by themselves make a command unsafe.\n *\n * Every command this package contributes must have exactly one policy here:\n * - allow: every valid invocation is safe\n * - conditional: safety depends on parsed arguments or flags\n * - deny: no invocation is safe\n *\n * Commands contributed by plugins are not listed here. Each plugin declares\n * policies for its own commands (see `PluginInvocationPolicies` in\n * `@sanity/cli-core/commandPolicy`).\n */\nexport const mcpPolicy: CommandPolicySet = {\n // Special exception, this can be very dangerous but is also super useful\n // to expose. Refuse authentication overrides and host input channels:\n // `--token`, Authorization, and Cookie headers replace the MCP user's authentication,\n // URL-embedded credentials replace it with Basic authentication,\n // `--input` reads the request body from the host's filesystem or stdin, and\n // `-F key=@<file>` / `-F key=@-` field values do the same.\n api: conditionalPolicy({\n deniedFlags: ['input', 'token'],\n validate: apiValidator,\n }),\n\n // --file reads from the machine running the command and must only run locally.\n 'assets:upload': conditionalPolicy({\n deniedFlags: ['content-type', 'file'],\n validate: ({flags}) => typeof flags['from-url'] === 'string' && flags['from-url'].length > 0,\n }),\n\n 'backups:disable': allow,\n // Writes a downloaded backup to the local filesystem.\n 'backups:download': deny,\n 'backups:enable': allow,\n 'backups:list': allow,\n\n // Requires a local Studio project and writes build output to disk.\n build: deny,\n\n // Reads and rewrites local source code.\n codemod: deny,\n\n // --watch polls a job with no deadline; a stuck job would hang the MCP tool\n // call indefinitely. Non-watch invocations are single remote requests.\n 'context:build': conditionalDenyFlags('watch'),\n 'context:create': allow,\n 'context:delete': allow,\n 'context:entries:get': allow,\n 'context:entries:list': allow,\n // See context:build — --watch has no deadline and would hang the MCP tool call.\n 'context:entries:rebuild': conditionalDenyFlags('watch'),\n 'context:get': allow,\n // --file reads the import payload from the local filesystem; text, URL and\n // dataset imports are remote-only.\n 'context:imports:create': conditionalDenyFlags('file'),\n 'context:imports:delete': allow,\n 'context:imports:download': allow,\n 'context:imports:get': allow,\n 'context:imports:list': allow,\n // See context:build — --watch has no deadline and would hang the MCP tool call.\n 'context:issues:apply': conditionalDenyFlags('watch'),\n 'context:issues:dismiss': allow,\n 'context:issues:get': allow,\n 'context:issues:list': allow,\n 'context:issues:reopen': allow,\n // See context:build — --watch has no deadline and would hang the MCP tool call.\n 'context:issues:resolve': conditionalDenyFlags('watch'),\n // See context:build — --watch has no deadline and would hang the MCP tool call.\n 'context:jobs:get': conditionalDenyFlags('watch'),\n 'context:list': allow,\n 'context:query': allow,\n 'context:refresh': allow,\n 'context:update': allow,\n\n 'cors:add': allow,\n 'cors:delete': allow,\n 'cors:list': allow,\n\n 'datasets:alias:create': allow,\n 'datasets:alias:delete': allow,\n 'datasets:alias:link': allow,\n 'datasets:alias:unlink': allow,\n 'datasets:copy': allow,\n 'datasets:create': allow,\n 'datasets:delete': allow,\n 'datasets:embeddings:disable': allow,\n 'datasets:embeddings:enable': allow,\n 'datasets:embeddings:status': allow,\n // Writes dataset contents and assets to the local filesystem.\n 'datasets:export': deny,\n // Reads import data from the local filesystem and may replace documents.\n 'datasets:import': deny,\n 'datasets:list': allow,\n 'datasets:visibility:get': allow,\n 'datasets:visibility:set': allow,\n\n // Inspects local project files and can print authentication secrets.\n debug: deny,\n\n // Reads, builds, and deploys a local Studio project.\n deploy: deny,\n\n // Loads a local Studio project and starts a development server.\n dev: deny,\n\n // Opens a browser on the machine running the MCP server.\n 'docs:browse': deny,\n // --web opens a browser on the machine running the MCP server.\n 'docs:read': conditionalDenyFlags('web'),\n 'docs:search': allow,\n\n // Reads and executes local project configuration for diagnostics.\n doctor: deny,\n\n // Reads document input from disk or launches a local editor.\n 'documents:create': deny,\n 'documents:delete': allow,\n 'documents:get': allow,\n 'documents:query': allow,\n // Loads a local Studio schema to validate documents.\n 'documents:validate': deny,\n\n // Executes arbitrary code in the local Studio context.\n exec: deny,\n\n // Loads a local schema and deploys a GraphQL API.\n 'graphql:deploy': deny,\n 'graphql:list': allow,\n // --api loads GraphQL definitions from the local project; explicit project/dataset flags are safe.\n 'graphql:undeploy': conditionalDenyFlags('api'),\n\n 'hooks:attempt': allow,\n 'hooks:create': allow,\n 'hooks:delete': allow,\n 'hooks:list': allow,\n 'hooks:logs': allow,\n\n // Creates or modifies a local project and may install dependencies.\n init: deny,\n\n // Installs packages into the local project.\n install: deny,\n\n // Opens a browser on the machine running the MCP server.\n learn: deny,\n\n // Performs an authentication flow.\n login: deny,\n\n // Performs an authentication operation and clears local credentials.\n logout: deny,\n\n // Reads local project configuration and opens a browser.\n manage: deny,\n\n // Loads local Studio configuration and writes manifest files.\n 'manifest:extract': deny,\n\n // Reads and writes local MCP client configuration.\n 'mcp:configure': deny,\n\n // Writes an aspect definition to the local filesystem.\n 'media:create-aspect': deny,\n 'media:delete-aspect': allow,\n // Reads a local aspect definition before deploying it.\n 'media:deploy-aspect': deny,\n // Writes media assets to the local filesystem.\n 'media:export': deny,\n // A directory or archive source reads media assets from the local\n // filesystem. A URL source does not: Sanity fetches the asset itself, so the\n // only local input is the URL. `--replace-aspects` is meaningless for a URL\n // source (the command rejects the combination), so it is hidden here rather\n // than advertised as usable surface.\n 'media:import': conditionalPolicy({\n deniedFlags: ['replace-aspects'],\n validate: ({args}) => typeof args.source === 'string' && isRemoteAssetSource(args.source),\n }),\n // media:pull-aspects and media:prune-aspect-data are hidden while the\n // config app is in beta, so they take the deny-by-default path like any\n // unlisted command. The exhaustiveness test forces explicit entries when\n // they are unhidden.\n\n // Creates migration source files in the local project.\n 'migrations:create': deny,\n // Reads and loads migration definitions from the local project.\n 'migrations:list': deny,\n // Executes local migration code that may perform arbitrary document mutations.\n 'migrations:run': deny,\n\n // Writes project setup values and scaffolds local applications.\n new: deny,\n\n // --web opens a browser on the machine running the MCP server.\n 'openapi:get': conditionalDenyFlags('web'),\n 'openapi:list': conditionalDenyFlags('web'),\n\n 'organizations:create': allow,\n 'organizations:delete': allow,\n 'organizations:get': allow,\n 'organizations:list': allow,\n 'organizations:update': allow,\n\n // Serves a local production build.\n preview: deny,\n\n 'projects:create': allow,\n 'projects:list': allow,\n // Reads locally stored claim URLs and robot tokens.\n 'projects:unclaimed': deny,\n\n // Loads the local Studio configuration to resolve the datasets containing each schema.\n 'schemas:delete': deny,\n // Loads local schema files before deploying them.\n 'schemas:deploy': deny,\n // Loads local Studio configuration and writes an extracted schema to disk.\n 'schemas:extract': deny,\n // Requires a local project and loads its schema configuration.\n 'schemas:list': deny,\n // Loads and executes a local Studio schema.\n 'schemas:validate': deny,\n\n // Installs skills into local editor configuration directories.\n 'skills:install': deny,\n\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:disable': deny,\n // Changes account telemetry preferences and mutates local cached configuration.\n 'telemetry:enable': deny,\n 'telemetry:status': allow,\n\n // Creates authentication credentials.\n 'tokens:create': deny,\n // Deletes authentication credentials.\n 'tokens:delete': deny,\n // Exposes authentication credential metadata.\n 'tokens:list': deny,\n // Replaces authentication credentials and exposes the new secret.\n 'tokens:rotate': deny,\n\n // Reads the local schema and source files and writes generated types to the project.\n 'typegen:generate': deny,\n\n // Loads local CLI and workbench configuration to identify the deployed Studio or application.\n undeploy: deny,\n\n // Grants a user access to a project.\n 'users:invite': deny,\n 'users:list': allow,\n\n // Reads the local project and installed package tree.\n versions: deny,\n\n // Reads the authenticated user from the API using the execution context token.\n whoami: allow,\n}\n"],"names":["allow","conditionalDenyFlags","conditionalPolicy","deny","isRemoteAssetSource","apiValidator","args","flags","fields","Array","isArray","field","fieldReadsFromHost","some","separatorIndex","indexOf","headers","header","headerOverridesAuthentication","name","slice","trim","toLowerCase","endpoint","URL","parse","urlEmbedsCredentials","username","password","mcpPolicy","api","deniedFlags","validate","length","build","codemod","debug","deploy","dev","doctor","exec","init","install","learn","login","logout","manage","source","new","preview","undeploy","versions","whoami"],"mappings":"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"}
|
package/dist/services/context.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { getGlobalCliClient } from '@sanity/cli-core';
|
|
2
2
|
const CONTEXT_API_VERSION = 'v2026-08-25';
|
|
3
3
|
/**
|
|
4
|
-
* Knowledge-base-scoped methods on `client.context`
|
|
5
|
-
*
|
|
4
|
+
* Knowledge-base-scoped methods on `client.context` operate on the client's
|
|
5
|
+
* configured `resource`; collection-level methods
|
|
6
6
|
* (`client.context.knowledgeBases.*`) are addressed per call and need none.
|
|
7
|
-
|
|
7
|
+
* GROQ-backed reads also need `context.organizationId`.
|
|
8
|
+
*/ async function getContextClient(knowledgeBaseId, organizationId) {
|
|
8
9
|
return getGlobalCliClient({
|
|
9
10
|
apiVersion: CONTEXT_API_VERSION,
|
|
10
11
|
requireUser: true,
|
|
@@ -13,9 +14,22 @@ const CONTEXT_API_VERSION = 'v2026-08-25';
|
|
|
13
14
|
id: knowledgeBaseId,
|
|
14
15
|
type: 'knowledge-base'
|
|
15
16
|
}
|
|
17
|
+
},
|
|
18
|
+
...organizationId === undefined ? {} : {
|
|
19
|
+
context: {
|
|
20
|
+
organizationId
|
|
21
|
+
}
|
|
16
22
|
}
|
|
17
23
|
});
|
|
18
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* GROQ-backed reads (issues, entries) query the organization's
|
|
27
|
+
* document store, where documents are keyed by the knowledge base's public ID.
|
|
28
|
+
* Fetching the knowledge base resolves both, whichever of its IDs was passed.
|
|
29
|
+
*/ async function getContextStoreClient(knowledgeBaseId) {
|
|
30
|
+
const { organizationId, publicId } = await getKnowledgeBase(knowledgeBaseId);
|
|
31
|
+
return getContextClient(publicId, organizationId);
|
|
32
|
+
}
|
|
19
33
|
/**
|
|
20
34
|
* Create a knowledge base in an organization
|
|
21
35
|
*/ export async function createKnowledgeBase(params) {
|
|
@@ -128,5 +142,84 @@ const CONTEXT_API_VERSION = 'v2026-08-25';
|
|
|
128
142
|
jobId
|
|
129
143
|
});
|
|
130
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* List all issues for a knowledge base, optionally narrowed to one status
|
|
147
|
+
*/ export async function listIssues(knowledgeBaseId, status) {
|
|
148
|
+
const client = await getContextStoreClient(knowledgeBaseId);
|
|
149
|
+
return client.context.issues.list({
|
|
150
|
+
status
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Get a single issue by ID, or `null` when it does not exist
|
|
155
|
+
*/ export async function getIssue(knowledgeBaseId, issueId) {
|
|
156
|
+
const client = await getContextStoreClient(knowledgeBaseId);
|
|
157
|
+
return client.context.issues.get({
|
|
158
|
+
issueId
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Resolve a conflict issue by choosing one of its sides (by index). Choosing
|
|
163
|
+
* a side other than the entry's current content starts a rewrite job.
|
|
164
|
+
*/ export async function resolveIssue(knowledgeBaseId, issueId, resolution) {
|
|
165
|
+
const client = await getContextClient(knowledgeBaseId);
|
|
166
|
+
return client.context.issues.resolve({
|
|
167
|
+
issueId,
|
|
168
|
+
resolution
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Dismiss an issue
|
|
173
|
+
*/ export async function dismissIssue(knowledgeBaseId, issueId) {
|
|
174
|
+
const client = await getContextClient(knowledgeBaseId);
|
|
175
|
+
return client.context.issues.dismiss({
|
|
176
|
+
issueId
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Reopen a resolved conflict issue
|
|
181
|
+
*/ export async function reopenIssue(knowledgeBaseId, issueId) {
|
|
182
|
+
const client = await getContextClient(knowledgeBaseId);
|
|
183
|
+
return client.context.issues.reopen({
|
|
184
|
+
issueId
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Apply accepted issues to a knowledge base in one batch
|
|
189
|
+
*/ export async function applyIssues(knowledgeBaseId, issueIds) {
|
|
190
|
+
const client = await getContextClient(knowledgeBaseId);
|
|
191
|
+
return client.context.issues.apply({
|
|
192
|
+
issueIds
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* List all entries for a knowledge base, path-ordered, without bodies
|
|
197
|
+
*/ export async function listEntries(knowledgeBaseId) {
|
|
198
|
+
const client = await getContextStoreClient(knowledgeBaseId);
|
|
199
|
+
return client.context.entries.list();
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get a single entry with its body and citations by outline path, or `null`
|
|
203
|
+
* when no entry sits at that path
|
|
204
|
+
*/ export async function getEntry(knowledgeBaseId, path) {
|
|
205
|
+
const client = await getContextStoreClient(knowledgeBaseId);
|
|
206
|
+
return client.context.entries.get({
|
|
207
|
+
path
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Rebuild one entry from its sources by outline path
|
|
212
|
+
*/ export async function rebuildEntry(knowledgeBaseId, path) {
|
|
213
|
+
const client = await getContextClient(knowledgeBaseId);
|
|
214
|
+
return client.context.entries.rebuild({
|
|
215
|
+
path
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Run a GROQ query against an organization's Context documents
|
|
220
|
+
*/ export async function queryContext(organizationId, query, params) {
|
|
221
|
+
const client = await getContextClient(undefined, organizationId);
|
|
222
|
+
return client.context.fetch(query, params);
|
|
223
|
+
}
|
|
131
224
|
|
|
132
225
|
//# sourceMappingURL=context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/services/context.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\nimport {type Context} from '@sanity/client'\n\nconst CONTEXT_API_VERSION = 'v2026-08-25'\n\n/**\n * Knowledge-base-scoped methods on `client.context` (imports, builds, jobs)\n * operate on the client's configured `resource`; collection-level methods\n * (`client.context.knowledgeBases.*`) are addressed per call and need none.\n */\nasync function getContextClient(knowledgeBaseId?: string) {\n return getGlobalCliClient({\n apiVersion: CONTEXT_API_VERSION,\n requireUser: true,\n ...(knowledgeBaseId === undefined\n ? {}\n : {resource: {id: knowledgeBaseId, type: 'knowledge-base' as const}}),\n })\n}\n\n/**\n * Create a knowledge base in an organization\n */\nexport async function createKnowledgeBase(\n params: Context.CreateKnowledgeBaseParams,\n): Promise<Context.KnowledgeBase> {\n const client = await getContextClient()\n return client.context.knowledgeBases.create(params)\n}\n\n/**\n * List all knowledge bases in an organization (drains pagination)\n */\nexport async function listKnowledgeBases(organizationId: string): Promise<Context.KnowledgeBase[]> {\n const client = await getContextClient()\n\n const knowledgeBases: Context.KnowledgeBase[] = []\n let cursor: string | undefined\n do {\n const page = await client.context.knowledgeBases.list({cursor, organizationId})\n knowledgeBases.push(...page.data)\n cursor = page.nextCursor ?? undefined\n } while (cursor !== undefined)\n\n return knowledgeBases\n}\n\n/**\n * Get a single knowledge base by ID\n */\nexport async function getKnowledgeBase(knowledgeBaseId: string): Promise<Context.KnowledgeBase> {\n const client = await getContextClient()\n return client.context.knowledgeBases.get(knowledgeBaseId)\n}\n\n/**\n * Update a knowledge base's configuration\n */\nexport async function updateKnowledgeBase(\n knowledgeBaseId: string,\n params: Context.EditKnowledgeBaseParams,\n): Promise<Context.KnowledgeBase> {\n const client = await getContextClient()\n return client.context.knowledgeBases.edit(knowledgeBaseId, params)\n}\n\n/**\n * Delete a knowledge base and its generated content\n */\nexport async function deleteKnowledgeBase(knowledgeBaseId: string): Promise<void> {\n const client = await getContextClient()\n await client.context.knowledgeBases.delete(knowledgeBaseId)\n}\n\n/**\n * Import content into a knowledge base. File imports are staged and uploaded\n * to signed storage by the client.\n */\nexport async function createImport(\n knowledgeBaseId: string,\n params: Context.CreateFileImportParams | Context.CreateImportParams,\n): Promise<Context.JobAccepted> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.imports.create(params)\n}\n\n/**\n * List all imports for a knowledge base (drains pagination)\n */\nexport async function listImports(knowledgeBaseId: string): Promise<Context.Import[]> {\n const client = await getContextClient(knowledgeBaseId)\n\n const imports: Context.Import[] = []\n let cursor: string | undefined\n do {\n const page = await client.context.imports.list({cursor})\n imports.push(...page.data)\n cursor = page.nextCursor ?? undefined\n } while (cursor !== undefined)\n\n return imports\n}\n\n/**\n * Get a single import by ID\n */\nexport async function getImport(\n knowledgeBaseId: string,\n importId: string,\n): Promise<Context.ImportDetail> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.imports.get({importId})\n}\n\n/**\n * Get a short-lived signed URL for an import's original uploaded bytes\n */\nexport async function downloadImport(\n knowledgeBaseId: string,\n importId: string,\n): Promise<Context.ImportDownloadResponse> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.imports.download({importId})\n}\n\n/**\n * Delete an import from a knowledge base\n */\nexport async function deleteImport(knowledgeBaseId: string, importId: string): Promise<void> {\n const client = await getContextClient(knowledgeBaseId)\n await client.context.imports.delete({importId})\n}\n\n/**\n * Start a build of a knowledge base\n */\nexport async function buildKnowledgeBase(knowledgeBaseId: string): Promise<Context.JobAccepted> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.build()\n}\n\n/**\n * Cancel the running build of a knowledge base, if any\n */\nexport async function cancelKnowledgeBaseBuild(\n knowledgeBaseId: string,\n): Promise<{cancelled: boolean}> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.cancelBuild()\n}\n\n/**\n * Run an incremental refresh: re-check sources and apply what changed\n */\nexport async function refreshKnowledgeBase(\n knowledgeBaseId: string,\n): Promise<{jobId: string; started: boolean}> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.refresh()\n}\n\n/**\n * Get an async job (build, import) by ID\n */\nexport async function getJob(knowledgeBaseId: string, jobId: string): Promise<Context.Job> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.jobs.get({jobId})\n}\n"],"names":["getGlobalCliClient","CONTEXT_API_VERSION","getContextClient","knowledgeBaseId","apiVersion","requireUser","undefined","resource","id","type","createKnowledgeBase","params","client","context","knowledgeBases","create","listKnowledgeBases","organizationId","cursor","page","list","push","data","nextCursor","getKnowledgeBase","get","updateKnowledgeBase","edit","deleteKnowledgeBase","delete","createImport","imports","listImports","getImport","importId","downloadImport","download","deleteImport","buildKnowledgeBase","build","cancelKnowledgeBaseBuild","cancelBuild","refreshKnowledgeBase","refresh","getJob","jobId","jobs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/services/context.ts"],"sourcesContent":["import {getGlobalCliClient} from '@sanity/cli-core'\nimport {type Context} from '@sanity/client'\n\nconst CONTEXT_API_VERSION = 'v2026-08-25'\n\n/**\n * Knowledge-base-scoped methods on `client.context` operate on the client's\n * configured `resource`; collection-level methods\n * (`client.context.knowledgeBases.*`) are addressed per call and need none.\n * GROQ-backed reads also need `context.organizationId`.\n */\nasync function getContextClient(knowledgeBaseId?: string, organizationId?: string) {\n return getGlobalCliClient({\n apiVersion: CONTEXT_API_VERSION,\n requireUser: true,\n ...(knowledgeBaseId === undefined\n ? {}\n : {resource: {id: knowledgeBaseId, type: 'knowledge-base' as const}}),\n ...(organizationId === undefined ? {} : {context: {organizationId}}),\n })\n}\n\n/**\n * GROQ-backed reads (issues, entries) query the organization's\n * document store, where documents are keyed by the knowledge base's public ID.\n * Fetching the knowledge base resolves both, whichever of its IDs was passed.\n */\nasync function getContextStoreClient(knowledgeBaseId: string) {\n const {organizationId, publicId} = await getKnowledgeBase(knowledgeBaseId)\n return getContextClient(publicId, organizationId)\n}\n\n/**\n * Create a knowledge base in an organization\n */\nexport async function createKnowledgeBase(\n params: Context.CreateKnowledgeBaseParams,\n): Promise<Context.KnowledgeBase> {\n const client = await getContextClient()\n return client.context.knowledgeBases.create(params)\n}\n\n/**\n * List all knowledge bases in an organization (drains pagination)\n */\nexport async function listKnowledgeBases(organizationId: string): Promise<Context.KnowledgeBase[]> {\n const client = await getContextClient()\n\n const knowledgeBases: Context.KnowledgeBase[] = []\n let cursor: string | undefined\n do {\n const page = await client.context.knowledgeBases.list({cursor, organizationId})\n knowledgeBases.push(...page.data)\n cursor = page.nextCursor ?? undefined\n } while (cursor !== undefined)\n\n return knowledgeBases\n}\n\n/**\n * Get a single knowledge base by ID\n */\nexport async function getKnowledgeBase(knowledgeBaseId: string): Promise<Context.KnowledgeBase> {\n const client = await getContextClient()\n return client.context.knowledgeBases.get(knowledgeBaseId)\n}\n\n/**\n * Update a knowledge base's configuration\n */\nexport async function updateKnowledgeBase(\n knowledgeBaseId: string,\n params: Context.EditKnowledgeBaseParams,\n): Promise<Context.KnowledgeBase> {\n const client = await getContextClient()\n return client.context.knowledgeBases.edit(knowledgeBaseId, params)\n}\n\n/**\n * Delete a knowledge base and its generated content\n */\nexport async function deleteKnowledgeBase(knowledgeBaseId: string): Promise<void> {\n const client = await getContextClient()\n await client.context.knowledgeBases.delete(knowledgeBaseId)\n}\n\n/**\n * Import content into a knowledge base. File imports are staged and uploaded\n * to signed storage by the client.\n */\nexport async function createImport(\n knowledgeBaseId: string,\n params: Context.CreateFileImportParams | Context.CreateImportParams,\n): Promise<Context.JobAccepted> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.imports.create(params)\n}\n\n/**\n * List all imports for a knowledge base (drains pagination)\n */\nexport async function listImports(knowledgeBaseId: string): Promise<Context.Import[]> {\n const client = await getContextClient(knowledgeBaseId)\n\n const imports: Context.Import[] = []\n let cursor: string | undefined\n do {\n const page = await client.context.imports.list({cursor})\n imports.push(...page.data)\n cursor = page.nextCursor ?? undefined\n } while (cursor !== undefined)\n\n return imports\n}\n\n/**\n * Get a single import by ID\n */\nexport async function getImport(\n knowledgeBaseId: string,\n importId: string,\n): Promise<Context.ImportDetail> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.imports.get({importId})\n}\n\n/**\n * Get a short-lived signed URL for an import's original uploaded bytes\n */\nexport async function downloadImport(\n knowledgeBaseId: string,\n importId: string,\n): Promise<Context.ImportDownloadResponse> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.imports.download({importId})\n}\n\n/**\n * Delete an import from a knowledge base\n */\nexport async function deleteImport(knowledgeBaseId: string, importId: string): Promise<void> {\n const client = await getContextClient(knowledgeBaseId)\n await client.context.imports.delete({importId})\n}\n\n/**\n * Start a build of a knowledge base\n */\nexport async function buildKnowledgeBase(knowledgeBaseId: string): Promise<Context.JobAccepted> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.build()\n}\n\n/**\n * Cancel the running build of a knowledge base, if any\n */\nexport async function cancelKnowledgeBaseBuild(\n knowledgeBaseId: string,\n): Promise<{cancelled: boolean}> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.cancelBuild()\n}\n\n/**\n * Run an incremental refresh: re-check sources and apply what changed\n */\nexport async function refreshKnowledgeBase(\n knowledgeBaseId: string,\n): Promise<{jobId: string; started: boolean}> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.refresh()\n}\n\n/**\n * Get an async job (build, import) by ID\n */\nexport async function getJob(knowledgeBaseId: string, jobId: string): Promise<Context.Job> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.jobs.get({jobId})\n}\n\n/**\n * List all issues for a knowledge base, optionally narrowed to one status\n */\nexport async function listIssues(\n knowledgeBaseId: string,\n status?: Context.IssueDoc['status'],\n): Promise<Context.IssueDoc[]> {\n const client = await getContextStoreClient(knowledgeBaseId)\n return client.context.issues.list({status})\n}\n\n/**\n * Get a single issue by ID, or `null` when it does not exist\n */\nexport async function getIssue(\n knowledgeBaseId: string,\n issueId: string,\n): Promise<Context.IssueDoc | null> {\n const client = await getContextStoreClient(knowledgeBaseId)\n return client.context.issues.get({issueId})\n}\n\n/**\n * Resolve a conflict issue by choosing one of its sides (by index). Choosing\n * a side other than the entry's current content starts a rewrite job.\n */\nexport async function resolveIssue(\n knowledgeBaseId: string,\n issueId: string,\n resolution: number,\n): Promise<Context.ResolveIssueResponse> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.issues.resolve({issueId, resolution})\n}\n\n/**\n * Dismiss an issue\n */\nexport async function dismissIssue(\n knowledgeBaseId: string,\n issueId: string,\n): Promise<Context.DismissIssueResponse> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.issues.dismiss({issueId})\n}\n\n/**\n * Reopen a resolved conflict issue\n */\nexport async function reopenIssue(\n knowledgeBaseId: string,\n issueId: string,\n): Promise<Context.ReopenIssueResponse> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.issues.reopen({issueId})\n}\n\n/**\n * Apply accepted issues to a knowledge base in one batch\n */\nexport async function applyIssues(\n knowledgeBaseId: string,\n issueIds: string[],\n): Promise<Context.ApplyIssuesResponse> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.issues.apply({issueIds})\n}\n\n/**\n * List all entries for a knowledge base, path-ordered, without bodies\n */\nexport async function listEntries(knowledgeBaseId: string): Promise<Context.Entry[]> {\n const client = await getContextStoreClient(knowledgeBaseId)\n return client.context.entries.list()\n}\n\n/**\n * Get a single entry with its body and citations by outline path, or `null`\n * when no entry sits at that path\n */\nexport async function getEntry(\n knowledgeBaseId: string,\n path: string,\n): Promise<Context.EntryDoc | null> {\n const client = await getContextStoreClient(knowledgeBaseId)\n return client.context.entries.get({path})\n}\n\n/**\n * Rebuild one entry from its sources by outline path\n */\nexport async function rebuildEntry(\n knowledgeBaseId: string,\n path: string,\n): Promise<Context.RebuildEntryResponse> {\n const client = await getContextClient(knowledgeBaseId)\n return client.context.entries.rebuild({path})\n}\n\n/**\n * Run a GROQ query against an organization's Context documents\n */\nexport async function queryContext(\n organizationId: string,\n query: string,\n params?: Record<string, unknown>,\n): Promise<unknown> {\n const client = await getContextClient(undefined, organizationId)\n return client.context.fetch(query, params)\n}\n"],"names":["getGlobalCliClient","CONTEXT_API_VERSION","getContextClient","knowledgeBaseId","organizationId","apiVersion","requireUser","undefined","resource","id","type","context","getContextStoreClient","publicId","getKnowledgeBase","createKnowledgeBase","params","client","knowledgeBases","create","listKnowledgeBases","cursor","page","list","push","data","nextCursor","get","updateKnowledgeBase","edit","deleteKnowledgeBase","delete","createImport","imports","listImports","getImport","importId","downloadImport","download","deleteImport","buildKnowledgeBase","build","cancelKnowledgeBaseBuild","cancelBuild","refreshKnowledgeBase","refresh","getJob","jobId","jobs","listIssues","status","issues","getIssue","issueId","resolveIssue","resolution","resolve","dismissIssue","dismiss","reopenIssue","reopen","applyIssues","issueIds","apply","listEntries","entries","getEntry","path","rebuildEntry","rebuild","queryContext","query","fetch"],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getGlobalCliClient } from '@sanity/cli-core';
|
|
2
2
|
import { MEDIA_LIBRARY_ASSET_ASPECT_TYPE_NAME } from '@sanity/types';
|
|
3
|
+
import { VALID_ASPECT_NAME } from '../actions/media/aspectName.js';
|
|
3
4
|
import { ASSET_UPLOAD_REQUEST_TAG_PREFIX, URL_INGEST_TIMEOUT_MS } from './assets.js';
|
|
4
5
|
export const MEDIA_LIBRARY_API_VERSION = 'v2025-02-19';
|
|
5
6
|
async function getMediaLibraryClient(options = {}) {
|
|
@@ -39,22 +40,188 @@ async function getMediaLibraryClient(options = {}) {
|
|
|
39
40
|
url: `/media-libraries/${mediaLibraryId}/mutate`
|
|
40
41
|
});
|
|
41
42
|
}
|
|
43
|
+
/** One listing endpoint, scoped by either owner — only the query param differs. */ async function listActiveMediaLibraries(query) {
|
|
44
|
+
const client = await getMediaLibraryClient();
|
|
45
|
+
const response = await client.request({
|
|
46
|
+
method: 'GET',
|
|
47
|
+
query,
|
|
48
|
+
url: `/media-libraries`
|
|
49
|
+
});
|
|
50
|
+
return response.data.filter((library)=>library.status === 'active');
|
|
51
|
+
}
|
|
42
52
|
/**
|
|
43
|
-
* Get a list of media libraries for a project
|
|
53
|
+
* Get a list of active media libraries for a project
|
|
44
54
|
* @param projectId - The project ID
|
|
45
55
|
* @returns A promise that resolves to the media libraries
|
|
46
56
|
*
|
|
47
57
|
* @internal
|
|
48
|
-
*/ export
|
|
58
|
+
*/ export function getMediaLibraries(projectId) {
|
|
59
|
+
return listActiveMediaLibraries({
|
|
60
|
+
projectId
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get a list of active media libraries for an organization
|
|
65
|
+
* @param organizationId - The organization ID
|
|
66
|
+
* @returns A promise that resolves to the active media libraries
|
|
67
|
+
*
|
|
68
|
+
* @internal
|
|
69
|
+
*/ export function getMediaLibrariesForOrganization(organizationId) {
|
|
70
|
+
return listActiveMediaLibraries({
|
|
71
|
+
organizationId
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/** Query page size — see the cursor loop in `queryAllPages`. */ const QUERY_PAGE_SIZE = 1000;
|
|
75
|
+
/**
|
|
76
|
+
* Run a media-library GROQ query to exhaustion, paging on an `_id` cursor:
|
|
77
|
+
* the query API caps single-response sizes, and a truncated read would
|
|
78
|
+
* silently drop documents. Only an *empty* page ends the loop — a short page
|
|
79
|
+
* is not trusted as exhaustion, in case the endpoint ever returns fewer rows
|
|
80
|
+
* than the requested slice; the cursor advances past the last returned id
|
|
81
|
+
* either way, so partial slices just mean extra round-trips.
|
|
82
|
+
*/ async function queryAllPages(options) {
|
|
83
|
+
const { filter, mediaLibraryId, params, perspective = 'published', projection = '', tag } = options;
|
|
49
84
|
const client = await getMediaLibraryClient();
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
85
|
+
const documents = [];
|
|
86
|
+
let lastId = '';
|
|
87
|
+
for(;;){
|
|
88
|
+
const response = await client.request({
|
|
89
|
+
body: {
|
|
90
|
+
params: {
|
|
91
|
+
...params,
|
|
92
|
+
lastId
|
|
93
|
+
},
|
|
94
|
+
query: `*[${filter} && _id > $lastId] | order(_id) [0...${QUERY_PAGE_SIZE}]${projection}`
|
|
95
|
+
},
|
|
96
|
+
method: 'POST',
|
|
97
|
+
// `published` is this API version's default; passed explicitly so a
|
|
98
|
+
// future default change can never silently surface `drafts.*`
|
|
99
|
+
// duplicates of published documents.
|
|
100
|
+
query: {
|
|
101
|
+
perspective
|
|
102
|
+
},
|
|
103
|
+
tag,
|
|
104
|
+
url: `/media-libraries/${mediaLibraryId}/query`
|
|
105
|
+
});
|
|
106
|
+
const page = response?.result ?? [];
|
|
107
|
+
documents.push(...page);
|
|
108
|
+
const last = page.at(-1);
|
|
109
|
+
if (!last) break;
|
|
110
|
+
lastId = last._id;
|
|
111
|
+
}
|
|
112
|
+
return documents;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Fetch the stored aspect documents of a media library
|
|
116
|
+
* @param mediaLibraryId - The media library ID
|
|
117
|
+
* @returns A promise that resolves to the aspect documents, ordered by id
|
|
118
|
+
*
|
|
119
|
+
* @internal
|
|
120
|
+
*/ export function getAspects(mediaLibraryId) {
|
|
121
|
+
// An incomplete read here is destructive downstream: a pull would generate
|
|
122
|
+
// a config missing the dropped aspects, and its deploy deletes them.
|
|
123
|
+
return queryAllPages({
|
|
124
|
+
filter: '_type == $type',
|
|
125
|
+
mediaLibraryId,
|
|
126
|
+
params: {
|
|
127
|
+
type: MEDIA_LIBRARY_ASSET_ASPECT_TYPE_NAME
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Fetch every stored aspect document `_id` of a media library. The consumer
|
|
133
|
+
* acts destructively on the result (mirror deletes), where a silently
|
|
134
|
+
* truncated list means reporting success while stale documents are left
|
|
135
|
+
* behind — `queryAllPages` reads to exhaustion.
|
|
136
|
+
*
|
|
137
|
+
* The `published` perspective (the helper's default) matters doubly here:
|
|
138
|
+
* stale detection must never see draft ids. The sync's upserts write
|
|
139
|
+
* published documents and its deletes match ids exactly, so drafts (not
|
|
140
|
+
* normally used for aspect docs) are out of scope either way — a
|
|
141
|
+
* default-perspective read could flag a `drafts.*` id as stale and target it
|
|
142
|
+
* for deletion.
|
|
143
|
+
*
|
|
144
|
+
* `tag` names the calling operation in request logs; it defaults to the
|
|
145
|
+
* deploy-time sync, the original consumer.
|
|
146
|
+
*
|
|
147
|
+
* @internal
|
|
148
|
+
*/ export async function getStoredAspectIds(mediaLibraryId, tag = 'aspect-sync') {
|
|
149
|
+
const documents = await queryAllPages({
|
|
150
|
+
filter: '_type == $type',
|
|
151
|
+
mediaLibraryId,
|
|
152
|
+
params: {
|
|
153
|
+
type: MEDIA_LIBRARY_ASSET_ASPECT_TYPE_NAME
|
|
54
154
|
},
|
|
55
|
-
|
|
155
|
+
projection: '{_id}',
|
|
156
|
+
tag
|
|
157
|
+
});
|
|
158
|
+
return documents.map((doc)=>doc._id);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Fetch the `_id` of every asset document carrying a value under
|
|
162
|
+
* `aspects.<aspectName>`.
|
|
163
|
+
*
|
|
164
|
+
* Queried under the `raw` perspective so drafts are included alongside
|
|
165
|
+
* published documents — a prune that missed drafts would resurrect the data
|
|
166
|
+
* on publish.
|
|
167
|
+
*
|
|
168
|
+
* @internal
|
|
169
|
+
*/ export async function getAspectDataCarrierIds(mediaLibraryId, aspectName) {
|
|
170
|
+
// Defense in depth: callers validate the name against the aspect-name rule,
|
|
171
|
+
// but this string is interpolated into GROQ, so re-guard here.
|
|
172
|
+
if (!VALID_ASPECT_NAME.test(aspectName)) {
|
|
173
|
+
throw new Error(`"${aspectName}" is not a valid aspect name`);
|
|
174
|
+
}
|
|
175
|
+
const documents = await queryAllPages({
|
|
176
|
+
filter: `_type == "sanity.asset" && defined(aspects["${aspectName}"])`,
|
|
177
|
+
mediaLibraryId,
|
|
178
|
+
params: {},
|
|
179
|
+
perspective: 'raw',
|
|
180
|
+
projection: '{_id}',
|
|
181
|
+
tag: 'prune-aspect-data'
|
|
182
|
+
});
|
|
183
|
+
return documents.map((doc)=>doc._id);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Remove `aspects.<aspectName>` from the given asset documents in one mutate
|
|
187
|
+
* request. Irreversible — this deletes aspect data, not a definition.
|
|
188
|
+
*
|
|
189
|
+
* @internal
|
|
190
|
+
*/ export async function unsetAspectData(options) {
|
|
191
|
+
const { aspectName, assetIds, mediaLibraryId } = options;
|
|
192
|
+
const client = await getMediaLibraryClient();
|
|
193
|
+
await client.request({
|
|
194
|
+
body: {
|
|
195
|
+
mutations: assetIds.map((id)=>({
|
|
196
|
+
patch: {
|
|
197
|
+
id,
|
|
198
|
+
unset: [
|
|
199
|
+
`aspects.${aspectName}`
|
|
200
|
+
]
|
|
201
|
+
}
|
|
202
|
+
}))
|
|
203
|
+
},
|
|
204
|
+
method: 'POST',
|
|
205
|
+
tag: 'prune-aspect-data',
|
|
206
|
+
url: `/media-libraries/${mediaLibraryId}/mutate`
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Apply one aspect-sync transaction — the upsert/delete mutation list built by
|
|
211
|
+
* `buildSyncMutations` — to a media library in a single request.
|
|
212
|
+
*
|
|
213
|
+
* @internal
|
|
214
|
+
*/ export async function mutateAspects(options) {
|
|
215
|
+
const { mediaLibraryId, mutations } = options;
|
|
216
|
+
const client = await getMediaLibraryClient();
|
|
217
|
+
return client.request({
|
|
218
|
+
body: {
|
|
219
|
+
mutations
|
|
220
|
+
},
|
|
221
|
+
method: 'POST',
|
|
222
|
+
tag: 'aspect-sync',
|
|
223
|
+
url: `/media-libraries/${mediaLibraryId}/mutate`
|
|
56
224
|
});
|
|
57
|
-
return response.data.filter((library)=>library.status === 'active');
|
|
58
225
|
}
|
|
59
226
|
/**
|
|
60
227
|
* Deploy one or more aspects to a media library
|
|
@@ -1 +1 @@
|
|
|
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"}
|
|
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 {VALID_ASPECT_NAME} from '../actions/media/aspectName.js'\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/** One listing endpoint, scoped by either owner — only the query param differs. */\nasync function listActiveMediaLibraries(\n query: {organizationId: string} | {projectId: string},\n): Promise<MediaLibrary[]> {\n const client = await getMediaLibraryClient()\n\n const response = await client.request<MediaLibrariesResponse>({\n method: 'GET',\n query,\n url: `/media-libraries`,\n })\n\n return response.data.filter((library) => library.status === 'active')\n}\n\n/**\n * Get a list of active 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 function getMediaLibraries(projectId: string): Promise<MediaLibrary[]> {\n return listActiveMediaLibraries({projectId})\n}\n\n/**\n * Get a list of active media libraries for an organization\n * @param organizationId - The organization ID\n * @returns A promise that resolves to the active media libraries\n *\n * @internal\n */\nexport function getMediaLibrariesForOrganization(organizationId: string): Promise<MediaLibrary[]> {\n return listActiveMediaLibraries({organizationId})\n}\n\ninterface QueryResponse<T> {\n result?: T\n}\n\n/**\n * A stored `sanity.asset.aspect` document as the query API returns it: `_id`\n * is guaranteed by the document store, everything else is validated by the\n * consumer (`aspectDocumentToFieldModule`).\n */\nexport interface StoredAspectDocument {\n _id: string\n\n assetType?: unknown\n definition?: unknown\n public?: unknown\n}\n\n/** Query page size — see the cursor loop in `queryAllPages`. */\nconst QUERY_PAGE_SIZE = 1000\n\n/**\n * Run a media-library GROQ query to exhaustion, paging on an `_id` cursor:\n * the query API caps single-response sizes, and a truncated read would\n * silently drop documents. Only an *empty* page ends the loop — a short page\n * is not trusted as exhaustion, in case the endpoint ever returns fewer rows\n * than the requested slice; the cursor advances past the last returned id\n * either way, so partial slices just mean extra round-trips.\n */\nasync function queryAllPages<T extends {_id: string}>(options: {\n /** GROQ filter the `_id` cursor condition is appended to, e.g. `_type == $type`. */\n filter: string\n mediaLibraryId: string\n params: Record<string, unknown>\n /** `raw` includes drafts — call sites say why they need them. */\n perspective?: 'published' | 'raw'\n /** GROQ projection, e.g. `{_id}`. Omitted, the whole documents return. */\n projection?: string\n /** Request tag, e.g. `aspect-sync`. */\n tag?: string\n}): Promise<T[]> {\n const {filter, mediaLibraryId, params, perspective = 'published', projection = '', tag} = options\n const client = await getMediaLibraryClient()\n\n const documents: T[] = []\n let lastId = ''\n for (;;) {\n const response = await client.request<QueryResponse<T[]> | undefined>({\n body: {\n params: {...params, lastId},\n query: `*[${filter} && _id > $lastId] | order(_id) [0...${QUERY_PAGE_SIZE}]${projection}`,\n },\n method: 'POST',\n // `published` is this API version's default; passed explicitly so a\n // future default change can never silently surface `drafts.*`\n // duplicates of published documents.\n query: {perspective},\n tag,\n url: `/media-libraries/${mediaLibraryId}/query`,\n })\n const page = response?.result ?? []\n documents.push(...page)\n const last = page.at(-1)\n if (!last) break\n lastId = last._id\n }\n\n return documents\n}\n\n/**\n * Fetch the stored aspect documents of a media library\n * @param mediaLibraryId - The media library ID\n * @returns A promise that resolves to the aspect documents, ordered by id\n *\n * @internal\n */\nexport function getAspects(mediaLibraryId: string): Promise<StoredAspectDocument[]> {\n // An incomplete read here is destructive downstream: a pull would generate\n // a config missing the dropped aspects, and its deploy deletes them.\n return queryAllPages<StoredAspectDocument>({\n filter: '_type == $type',\n mediaLibraryId,\n params: {type: MEDIA_LIBRARY_ASSET_ASPECT_TYPE_NAME},\n })\n}\n\n/**\n * Fetch every stored aspect document `_id` of a media library. The consumer\n * acts destructively on the result (mirror deletes), where a silently\n * truncated list means reporting success while stale documents are left\n * behind — `queryAllPages` reads to exhaustion.\n *\n * The `published` perspective (the helper's default) matters doubly here:\n * stale detection must never see draft ids. The sync's upserts write\n * published documents and its deletes match ids exactly, so drafts (not\n * normally used for aspect docs) are out of scope either way — a\n * default-perspective read could flag a `drafts.*` id as stale and target it\n * for deletion.\n *\n * `tag` names the calling operation in request logs; it defaults to the\n * deploy-time sync, the original consumer.\n *\n * @internal\n */\nexport async function getStoredAspectIds(\n mediaLibraryId: string,\n tag = 'aspect-sync',\n): Promise<string[]> {\n const documents = await queryAllPages<{_id: string}>({\n filter: '_type == $type',\n mediaLibraryId,\n params: {type: MEDIA_LIBRARY_ASSET_ASPECT_TYPE_NAME},\n projection: '{_id}',\n tag,\n })\n return documents.map((doc) => doc._id)\n}\n\n/**\n * Fetch the `_id` of every asset document carrying a value under\n * `aspects.<aspectName>`.\n *\n * Queried under the `raw` perspective so drafts are included alongside\n * published documents — a prune that missed drafts would resurrect the data\n * on publish.\n *\n * @internal\n */\nexport async function getAspectDataCarrierIds(\n mediaLibraryId: string,\n aspectName: string,\n): Promise<string[]> {\n // Defense in depth: callers validate the name against the aspect-name rule,\n // but this string is interpolated into GROQ, so re-guard here.\n if (!VALID_ASPECT_NAME.test(aspectName)) {\n throw new Error(`\"${aspectName}\" is not a valid aspect name`)\n }\n const documents = await queryAllPages<{_id: string}>({\n filter: `_type == \"sanity.asset\" && defined(aspects[\"${aspectName}\"])`,\n mediaLibraryId,\n params: {},\n perspective: 'raw',\n projection: '{_id}',\n tag: 'prune-aspect-data',\n })\n return documents.map((doc) => doc._id)\n}\n\n/**\n * Remove `aspects.<aspectName>` from the given asset documents in one mutate\n * request. Irreversible — this deletes aspect data, not a definition.\n *\n * @internal\n */\nexport async function unsetAspectData(options: {\n aspectName: string\n assetIds: string[]\n mediaLibraryId: string\n}): Promise<void> {\n const {aspectName, assetIds, mediaLibraryId} = options\n\n const client = await getMediaLibraryClient()\n\n await client.request({\n body: {\n mutations: assetIds.map((id) => ({\n patch: {id, unset: [`aspects.${aspectName}`]},\n })),\n },\n method: 'POST',\n tag: 'prune-aspect-data',\n url: `/media-libraries/${mediaLibraryId}/mutate`,\n })\n}\n\ninterface MutateAspectsResponse {\n results?: Array<{id: string; operation: string}>\n}\n\n/**\n * Apply one aspect-sync transaction — the upsert/delete mutation list built by\n * `buildSyncMutations` — to a media library in a single request.\n *\n * @internal\n */\nexport async function mutateAspects(options: {\n mediaLibraryId: string\n mutations: unknown[]\n}): Promise<MutateAspectsResponse> {\n const {mediaLibraryId, mutations} = options\n\n const client = await getMediaLibraryClient()\n\n return client.request<MutateAspectsResponse>({\n body: {mutations},\n method: 'POST',\n tag: 'aspect-sync',\n url: `/media-libraries/${mediaLibraryId}/mutate`,\n })\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","VALID_ASPECT_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","listActiveMediaLibraries","response","data","filter","library","status","getMediaLibraries","projectId","getMediaLibrariesForOrganization","organizationId","QUERY_PAGE_SIZE","queryAllPages","perspective","projection","tag","documents","lastId","page","result","push","last","at","_id","getAspects","getStoredAspectIds","map","doc","getAspectDataCarrierIds","test","Error","unsetAspectData","assetIds","patch","unset","mutateAspects","deployAspects","aspects","aspect","createOrReplace","ingestMediaLibraryAssetFromUrl","filename","signal","throwIfAborted","timeout"],"mappings":"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"}
|