@sanity/cli 8.22.1 → 8.24.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 (98) hide show
  1. package/README.md +313 -0
  2. package/dist/actions/codemods/dashboard.js +136 -8
  3. package/dist/actions/codemods/dashboard.js.map +1 -1
  4. package/dist/actions/codemods/types.js.map +1 -1
  5. package/dist/actions/context/watchJob.js +9 -3
  6. package/dist/actions/context/watchJob.js.map +1 -1
  7. package/dist/actions/deploy/blueprintManaged.js +59 -0
  8. package/dist/actions/deploy/blueprintManaged.js.map +1 -0
  9. package/dist/actions/deploy/deployApp.js +117 -1
  10. package/dist/actions/deploy/deployApp.js.map +1 -1
  11. package/dist/actions/deploy/deployRunner.js.map +1 -1
  12. package/dist/actions/deploy/deployStudio.js +6 -31
  13. package/dist/actions/deploy/deployStudio.js.map +1 -1
  14. package/dist/actions/exec/runScript.worker.js +6 -2
  15. package/dist/actions/exec/runScript.worker.js.map +1 -1
  16. package/dist/actions/init/bootstrapLocalTemplate.js +35 -11
  17. package/dist/actions/init/bootstrapLocalTemplate.js.map +1 -1
  18. package/dist/actions/init/createMediaLibraryCliConfig.js +12 -0
  19. package/dist/actions/init/createMediaLibraryCliConfig.js.map +1 -0
  20. package/dist/actions/init/determineMediaLibraryTemplate.js +14 -0
  21. package/dist/actions/init/determineMediaLibraryTemplate.js.map +1 -0
  22. package/dist/actions/init/initAction.js +35 -6
  23. package/dist/actions/init/initAction.js.map +1 -1
  24. package/dist/actions/init/initApp.js +81 -6
  25. package/dist/actions/init/initApp.js.map +1 -1
  26. package/dist/actions/init/offerAspectPull.js +81 -0
  27. package/dist/actions/init/offerAspectPull.js.map +1 -0
  28. package/dist/actions/init/project/getOrCreateDataset.js +8 -5
  29. package/dist/actions/init/project/getOrCreateDataset.js.map +1 -1
  30. package/dist/actions/init/project/getOrCreateProject.js +7 -5
  31. package/dist/actions/init/project/getOrCreateProject.js.map +1 -1
  32. package/dist/actions/init/project/getProjectDetails.js +25 -2
  33. package/dist/actions/init/project/getProjectDetails.js.map +1 -1
  34. package/dist/actions/init/project/promptUserForOrganization.js +6 -4
  35. package/dist/actions/init/project/promptUserForOrganization.js.map +1 -1
  36. package/dist/actions/init/templates/index.js +3 -0
  37. package/dist/actions/init/templates/index.js.map +1 -1
  38. package/dist/actions/init/templates/mediaLibraryConfig.js +51 -0
  39. package/dist/actions/init/templates/mediaLibraryConfig.js.map +1 -0
  40. package/dist/actions/media/aspectName.js +8 -0
  41. package/dist/actions/media/aspectName.js.map +1 -0
  42. package/dist/actions/media/aspectSync/contract.js +163 -0
  43. package/dist/actions/media/aspectSync/contract.js.map +1 -0
  44. package/dist/actions/media/aspectSync/syncAspects.js +115 -0
  45. package/dist/actions/media/aspectSync/syncAspects.js.map +1 -0
  46. package/dist/actions/media/cliConfigFields.js +192 -0
  47. package/dist/actions/media/cliConfigFields.js.map +1 -0
  48. package/dist/actions/media/pullAspects.js +225 -0
  49. package/dist/actions/media/pullAspects.js.map +1 -0
  50. package/dist/actions/media/resolveMediaLibraryId.js +83 -0
  51. package/dist/actions/media/resolveMediaLibraryId.js.map +1 -0
  52. package/dist/commands/codemod.js +1 -0
  53. package/dist/commands/codemod.js.map +1 -1
  54. package/dist/commands/context/build.js +2 -2
  55. package/dist/commands/context/build.js.map +1 -1
  56. package/dist/commands/context/entries/get.js +83 -0
  57. package/dist/commands/context/entries/get.js.map +1 -0
  58. package/dist/commands/context/entries/list.js +82 -0
  59. package/dist/commands/context/entries/list.js.map +1 -0
  60. package/dist/commands/context/entries/rebuild.js +95 -0
  61. package/dist/commands/context/entries/rebuild.js.map +1 -0
  62. package/dist/commands/context/issues/apply.js +86 -0
  63. package/dist/commands/context/issues/apply.js.map +1 -0
  64. package/dist/commands/context/issues/dismiss.js +52 -0
  65. package/dist/commands/context/issues/dismiss.js.map +1 -0
  66. package/dist/commands/context/issues/get.js +105 -0
  67. package/dist/commands/context/issues/get.js.map +1 -0
  68. package/dist/commands/context/issues/list.js +128 -0
  69. package/dist/commands/context/issues/list.js.map +1 -0
  70. package/dist/commands/context/issues/reopen.js +53 -0
  71. package/dist/commands/context/issues/reopen.js.map +1 -0
  72. package/dist/commands/context/issues/resolve.js +137 -0
  73. package/dist/commands/context/issues/resolve.js.map +1 -0
  74. package/dist/commands/context/jobs/get.js +2 -2
  75. package/dist/commands/context/jobs/get.js.map +1 -1
  76. package/dist/commands/context/query.js +92 -0
  77. package/dist/commands/context/query.js.map +1 -0
  78. package/dist/commands/deploy.js +13 -0
  79. package/dist/commands/deploy.js.map +1 -1
  80. package/dist/commands/media/prune-aspect-data.js +170 -0
  81. package/dist/commands/media/prune-aspect-data.js.map +1 -0
  82. package/dist/commands/media/pull-aspects.js +309 -0
  83. package/dist/commands/media/pull-aspects.js.map +1 -0
  84. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +17 -0
  85. package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
  86. package/dist/services/context.js +96 -3
  87. package/dist/services/context.js.map +1 -1
  88. package/dist/services/mediaLibraries.js +175 -8
  89. package/dist/services/mediaLibraries.js.map +1 -1
  90. package/dist/util/searchChoices.js +52 -0
  91. package/dist/util/searchChoices.js.map +1 -0
  92. package/oclif.manifest.json +2134 -1445
  93. package/package.json +9 -8
  94. package/templates/media-library-config/README.md +52 -0
  95. package/templates/media-library-config/eslint.config.txt +52 -0
  96. package/templates/media-library-config/src/fields/caption.tsx +29 -0
  97. package/templates/media-library-config/src/fields/notes.tsx +54 -0
  98. package/templates/shopify/.gitignore +0 -26
@@ -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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"}
@@ -0,0 +1,52 @@
1
+ import { styleText } from 'node:util';
2
+ import { Separator } from '@sanity/cli-core/ux';
3
+ /** `search` prompt theme that adds a "type to search" hint to the key help line. */ export const searchTheme = {
4
+ style: {
5
+ keysHelpTip: (keys)=>[
6
+ [
7
+ 'type',
8
+ 'to search'
9
+ ],
10
+ ...keys
11
+ ].map(([key, action])=>`${styleText('bold', key)} ${styleText('dim', action)}`).join(styleText('dim', ' • '))
12
+ }
13
+ };
14
+ const isChoice = (choice)=>!Separator.isSeparator(choice);
15
+ /**
16
+ * Builds a `search` prompt source that keeps choices whose `name` contains every search word.
17
+ * Actions above the first separator (e.g. "Create new ...") stay visible, below the matches while
18
+ * searching so that Enter picks the top match. Without a selectable match the actions are
19
+ * disabled, so Enter does nothing.
20
+ */ export function searchChoices(choices) {
21
+ const separatorIndex = choices.findIndex((choice)=>Separator.isSeparator(choice));
22
+ const actions = choices.slice(0, Math.max(separatorIndex, 0)).filter((choice)=>isChoice(choice));
23
+ const searchable = choices.slice(separatorIndex + 1).filter((choice)=>isChoice(choice));
24
+ return (term)=>{
25
+ const trimmed = term?.trim() ?? '';
26
+ if (!trimmed) {
27
+ return choices;
28
+ }
29
+ const words = trimmed.toLowerCase().split(/\s+/);
30
+ const matches = searchable.filter((choice)=>words.every((word)=>choice.name.toLowerCase().includes(word)));
31
+ if (actions.length === 0) {
32
+ return matches;
33
+ }
34
+ if (matches.some((choice)=>!choice.disabled)) {
35
+ return [
36
+ ...matches,
37
+ new Separator(),
38
+ ...actions
39
+ ];
40
+ }
41
+ return [
42
+ ...matches,
43
+ new Separator(matches.length > 0 ? undefined : `No matches for "${trimmed}"`),
44
+ ...actions.map((action)=>({
45
+ ...action,
46
+ disabled: '(clear search to select)'
47
+ }))
48
+ ];
49
+ };
50
+ }
51
+
52
+ //# sourceMappingURL=searchChoices.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/util/searchChoices.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {Separator} from '@sanity/cli-core/ux'\n\ntype Choice = {disabled?: boolean | string; name: string; value: string}\n\n/** `search` prompt theme that adds a \"type to search\" hint to the key help line. */\nexport const searchTheme = {\n style: {\n keysHelpTip: (keys: [key: string, action: string][]) =>\n [['type', 'to search'], ...keys]\n .map(([key, action]) => `${styleText('bold', key)} ${styleText('dim', action)}`)\n .join(styleText('dim', ' • ')),\n },\n}\n\nconst isChoice = (choice: Choice | Separator): choice is Choice => !Separator.isSeparator(choice)\n\n/**\n * Builds a `search` prompt source that keeps choices whose `name` contains every search word.\n * Actions above the first separator (e.g. \"Create new ...\") stay visible, below the matches while\n * searching so that Enter picks the top match. Without a selectable match the actions are\n * disabled, so Enter does nothing.\n */\nexport function searchChoices(choices: Array<Choice | Separator>) {\n const separatorIndex = choices.findIndex((choice) => Separator.isSeparator(choice))\n const actions = choices.slice(0, Math.max(separatorIndex, 0)).filter((choice) => isChoice(choice))\n const searchable = choices.slice(separatorIndex + 1).filter((choice) => isChoice(choice))\n\n return (term: string | undefined) => {\n const trimmed = term?.trim() ?? ''\n\n if (!trimmed) {\n return choices\n }\n\n const words = trimmed.toLowerCase().split(/\\s+/)\n const matches = searchable.filter((choice) =>\n words.every((word) => choice.name.toLowerCase().includes(word)),\n )\n\n if (actions.length === 0) {\n return matches\n }\n\n if (matches.some((choice) => !choice.disabled)) {\n return [...matches, new Separator(), ...actions]\n }\n\n return [\n ...matches,\n new Separator(matches.length > 0 ? undefined : `No matches for \"${trimmed}\"`),\n ...actions.map((action) => ({...action, disabled: '(clear search to select)'})),\n ]\n }\n}\n"],"names":["styleText","Separator","searchTheme","style","keysHelpTip","keys","map","key","action","join","isChoice","choice","isSeparator","searchChoices","choices","separatorIndex","findIndex","actions","slice","Math","max","filter","searchable","term","trimmed","trim","words","toLowerCase","split","matches","every","word","name","includes","length","some","disabled","undefined"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,SAAS,QAAO,sBAAqB;AAI7C,kFAAkF,GAClF,OAAO,MAAMC,cAAc;IACzBC,OAAO;QACLC,aAAa,CAACC,OACZ;gBAAC;oBAAC;oBAAQ;iBAAY;mBAAKA;aAAK,CAC7BC,GAAG,CAAC,CAAC,CAACC,KAAKC,OAAO,GAAK,GAAGR,UAAU,QAAQO,KAAK,CAAC,EAAEP,UAAU,OAAOQ,SAAS,EAC9EC,IAAI,CAACT,UAAU,OAAO;IAC7B;AACF,EAAC;AAED,MAAMU,WAAW,CAACC,SAAiD,CAACV,UAAUW,WAAW,CAACD;AAE1F;;;;;CAKC,GACD,OAAO,SAASE,cAAcC,OAAkC;IAC9D,MAAMC,iBAAiBD,QAAQE,SAAS,CAAC,CAACL,SAAWV,UAAUW,WAAW,CAACD;IAC3E,MAAMM,UAAUH,QAAQI,KAAK,CAAC,GAAGC,KAAKC,GAAG,CAACL,gBAAgB,IAAIM,MAAM,CAAC,CAACV,SAAWD,SAASC;IAC1F,MAAMW,aAAaR,QAAQI,KAAK,CAACH,iBAAiB,GAAGM,MAAM,CAAC,CAACV,SAAWD,SAASC;IAEjF,OAAO,CAACY;QACN,MAAMC,UAAUD,MAAME,UAAU;QAEhC,IAAI,CAACD,SAAS;YACZ,OAAOV;QACT;QAEA,MAAMY,QAAQF,QAAQG,WAAW,GAAGC,KAAK,CAAC;QAC1C,MAAMC,UAAUP,WAAWD,MAAM,CAAC,CAACV,SACjCe,MAAMI,KAAK,CAAC,CAACC,OAASpB,OAAOqB,IAAI,CAACL,WAAW,GAAGM,QAAQ,CAACF;QAG3D,IAAId,QAAQiB,MAAM,KAAK,GAAG;YACxB,OAAOL;QACT;QAEA,IAAIA,QAAQM,IAAI,CAAC,CAACxB,SAAW,CAACA,OAAOyB,QAAQ,GAAG;YAC9C,OAAO;mBAAIP;gBAAS,IAAI5B;mBAAgBgB;aAAQ;QAClD;QAEA,OAAO;eACFY;YACH,IAAI5B,UAAU4B,QAAQK,MAAM,GAAG,IAAIG,YAAY,CAAC,gBAAgB,EAAEb,QAAQ,CAAC,CAAC;eACzEP,QAAQX,GAAG,CAAC,CAACE,SAAY,CAAA;oBAAC,GAAGA,MAAM;oBAAE4B,UAAU;gBAA0B,CAAA;SAC7E;IACH;AACF"}