@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
@@ -0,0 +1,192 @@
1
+ /**
2
+ * Locates and edits the `fields: [...]` array in a Media Library config app's
3
+ * `sanity.cli.ts` — appending entries (`sanity media pull-aspects`) or
4
+ * replacing them (the init-time pull). Deploying mirrors the declared fields
5
+ * into the library and deletes stored aspects left undeclared, so unsafe
6
+ * edits are refused rather than risked: the AST (vite's `parseAst`) locates
7
+ * the one array literal the deploy will read, the edit is a byte-preserving
8
+ * splice at its offsets, and any other shape returns a reason the caller
9
+ * turns into a paste-ready fallback.
10
+ */ import { parseAst } from 'vite';
11
+ const CALL_NAME = 'unstable_defineMediaLibrary';
12
+ /**
13
+ * Depth-first collection of every `unstable_defineMediaLibrary(...)` call in
14
+ * the (sub)tree at `value`. The one cast is guarded by the `type`
15
+ * discriminant it narrows on.
16
+ */ function collectCalls(value, calls) {
17
+ if (Array.isArray(value)) {
18
+ for (const item of value)collectCalls(item, calls);
19
+ return;
20
+ }
21
+ if (typeof value !== 'object' || value === null) return;
22
+ if (value.type === 'CallExpression') {
23
+ const call = value;
24
+ if (call.callee.type === 'Identifier' && call.callee.name === CALL_NAME) {
25
+ calls.push(call);
26
+ }
27
+ }
28
+ for (const child of Object.values(value)){
29
+ if (typeof child === 'object') collectCalls(child, calls);
30
+ }
31
+ }
32
+ /** Non-computed `fields` key — `fields:` or `'fields':`. */ function isFieldsProperty(prop) {
33
+ if (prop.type !== 'Property' || prop.computed) return false;
34
+ const { key } = prop;
35
+ return key.type === 'Identifier' && key.name === 'fields' || key.type === 'Literal' && key.value === 'fields';
36
+ }
37
+ /**
38
+ * The index of the `,` between a trailing array element ending at `from` and
39
+ * the `]` at `to`, or -1 when the element has no trailing comma. Only
40
+ * whitespace, comments, and at most one comma can sit in that gap, and the
41
+ * comments terminate (the source parsed), so this scan just has to not read a
42
+ * comma out of a comment.
43
+ */ function indexOfTrailingComma(source, from, to) {
44
+ let index = from;
45
+ while(index < to){
46
+ const char = source[index];
47
+ if (char === ',') return index;
48
+ if (char === '/' && source[index + 1] === '/') {
49
+ const newline = source.indexOf('\n', index);
50
+ if (newline === -1 || newline >= to) return -1;
51
+ index = newline + 1;
52
+ continue;
53
+ }
54
+ if (char === '/' && source[index + 1] === '*') {
55
+ index = source.indexOf('*/', index + 2) + 2;
56
+ continue;
57
+ }
58
+ index++;
59
+ }
60
+ return -1;
61
+ }
62
+ function locateFieldsArray(source) {
63
+ // Node offsets are UTF-16 string indices, so splicing `source` with them is
64
+ // exact even around non-ASCII content.
65
+ let program;
66
+ try {
67
+ program = parseAst(source, {
68
+ lang: 'ts'
69
+ });
70
+ } catch {
71
+ return {
72
+ reason: 'the config does not parse as TypeScript'
73
+ };
74
+ }
75
+ // The import statement also mentions the identifier, but only call sites
76
+ // show up as CallExpressions.
77
+ const calls = [];
78
+ collectCalls(program.body, calls);
79
+ if (calls.length === 0) {
80
+ return {
81
+ reason: 'no `unstable_defineMediaLibrary(...)` call found'
82
+ };
83
+ }
84
+ if (calls.length > 1) {
85
+ return {
86
+ reason: 'more than one `unstable_defineMediaLibrary(...)` call found'
87
+ };
88
+ }
89
+ const [config] = calls[0].arguments;
90
+ if (config?.type !== 'ObjectExpression') {
91
+ return {
92
+ reason: 'the `unstable_defineMediaLibrary(...)` argument is not an object literal'
93
+ };
94
+ }
95
+ // Only the config object's own `fields` property may be edited — deploy
96
+ // evaluates exactly that one, so a `fields` array nested elsewhere (or
97
+ // sitting in a helper outside the call) must neither be edited nor block
98
+ // the real one.
99
+ const fieldsProps = config.properties.filter((prop)=>isFieldsProperty(prop));
100
+ if (fieldsProps.length === 0) {
101
+ return {
102
+ reason: 'no `fields` property in the `unstable_defineMediaLibrary({...})` config'
103
+ };
104
+ }
105
+ if (fieldsProps.length > 1) {
106
+ return {
107
+ reason: 'more than one `fields` property in the `unstable_defineMediaLibrary({...})` config'
108
+ };
109
+ }
110
+ const fields = fieldsProps[0];
111
+ const array = fields.value;
112
+ if (array.type !== 'ArrayExpression') {
113
+ // A computed value (`fields: sharedFields`) stays the user's to manage —
114
+ // deploy keeps evaluating it, so an "edit" elsewhere would be a lie.
115
+ return {
116
+ reason: 'the `fields` value is not an array literal'
117
+ };
118
+ }
119
+ const open = array.start;
120
+ const close = array.end - 1;
121
+ const last = array.elements.at(-1);
122
+ if (last === null) {
123
+ return {
124
+ reason: 'the `fields` array ends in an elision'
125
+ };
126
+ }
127
+ let lastContentEnd = open + 1;
128
+ if (last) {
129
+ const comma = indexOfTrailingComma(source, last.end, close);
130
+ lastContentEnd = comma === -1 ? last.end : comma + 1;
131
+ }
132
+ const lineStart = source.lastIndexOf('\n', fields.start) + 1;
133
+ const indent = /^[\t ]*/.exec(source.slice(lineStart, fields.start))?.[0] ?? '';
134
+ return {
135
+ close,
136
+ indent,
137
+ lastContentEnd,
138
+ open
139
+ };
140
+ }
141
+ /**
142
+ * Append `entryLines` (one rendered `{name, src, title},` object per line) to
143
+ * the config's `fields` array, preserving the existing entries and adding a
144
+ * trailing comma to the last one when needed.
145
+ */ export function appendFieldEntries(source, entryLines) {
146
+ const located = locateFieldsArray(source);
147
+ if ('reason' in located) return {
148
+ ok: false,
149
+ reason: located.reason
150
+ };
151
+ const { indent, lastContentEnd, open } = located;
152
+ const entryIndent = `${indent} `;
153
+ const block = entryLines.map((line)=>entryIndent + line).join('\n');
154
+ if (lastContentEnd === open + 1) {
155
+ const inner = source.slice(open + 1, located.close);
156
+ if (inner.trim() !== '') {
157
+ // Only comments inside: rewriting would silently drop them.
158
+ return {
159
+ ok: false,
160
+ reason: 'the `fields` array contains only comments'
161
+ };
162
+ }
163
+ const updated = `${source.slice(0, open + 1)}\n${block}\n${indent}${source.slice(located.close)}`;
164
+ return {
165
+ ok: true,
166
+ updated
167
+ };
168
+ }
169
+ const comma = source[lastContentEnd - 1] === ',' ? '' : ',';
170
+ const updated = `${source.slice(0, lastContentEnd)}${comma}\n${block}${source.slice(lastContentEnd)}`;
171
+ return {
172
+ ok: true,
173
+ updated
174
+ };
175
+ }
176
+ /** Replace the config's `fields` array contents with `entryLines` wholesale. */ export function replaceFieldEntries(source, entryLines) {
177
+ const located = locateFieldsArray(source);
178
+ if ('reason' in located) return {
179
+ ok: false,
180
+ reason: located.reason
181
+ };
182
+ const { close, indent, open } = located;
183
+ const entryIndent = `${indent} `;
184
+ const block = entryLines.map((line)=>entryIndent + line).join('\n');
185
+ const updated = `${source.slice(0, open + 1)}\n${block}\n${indent}${source.slice(close)}`;
186
+ return {
187
+ ok: true,
188
+ updated
189
+ };
190
+ }
191
+
192
+ //# sourceMappingURL=cliConfigFields.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/actions/media/cliConfigFields.ts"],"sourcesContent":["/**\n * Locates and edits the `fields: [...]` array in a Media Library config app's\n * `sanity.cli.ts` — appending entries (`sanity media pull-aspects`) or\n * replacing them (the init-time pull). Deploying mirrors the declared fields\n * into the library and deletes stored aspects left undeclared, so unsafe\n * edits are refused rather than risked: the AST (vite's `parseAst`) locates\n * the one array literal the deploy will read, the edit is a byte-preserving\n * splice at its offsets, and any other shape returns a reason the caller\n * turns into a paste-ready fallback.\n */\n\nimport {type CallExpression, type ObjectProperty, type ObjectPropertyKind} from '@oxc-project/types'\nimport {parseAst} from 'vite'\n\nexport type EditResult = {ok: false; reason: string} | {ok: true; updated: string}\n\nconst CALL_NAME = 'unstable_defineMediaLibrary'\n\ninterface LocatedArray {\n /** Index of the `]`. */\n close: number\n /** Leading whitespace of the line containing the `fields` property. */\n indent: string\n /**\n * Index just past the last element — past its trailing comma when there is\n * one — or `open + 1` for an empty array. Appending inserts here, so a\n * trailing comment never ends up with a comma after it (which would create\n * a sparse-array hole).\n */\n lastContentEnd: number\n /** Index of the `[`. */\n open: number\n}\n\n/**\n * Depth-first collection of every `unstable_defineMediaLibrary(...)` call in\n * the (sub)tree at `value`. The one cast is guarded by the `type`\n * discriminant it narrows on.\n */\nfunction collectCalls(value: unknown, calls: CallExpression[]): void {\n if (Array.isArray(value)) {\n for (const item of value) collectCalls(item, calls)\n return\n }\n if (typeof value !== 'object' || value === null) return\n if ((value as {type?: unknown}).type === 'CallExpression') {\n const call = value as CallExpression\n if (call.callee.type === 'Identifier' && call.callee.name === CALL_NAME) {\n calls.push(call)\n }\n }\n for (const child of Object.values(value)) {\n if (typeof child === 'object') collectCalls(child, calls)\n }\n}\n\n/** Non-computed `fields` key — `fields:` or `'fields':`. */\nfunction isFieldsProperty(prop: ObjectPropertyKind): prop is ObjectProperty {\n if (prop.type !== 'Property' || prop.computed) return false\n const {key} = prop\n return (\n (key.type === 'Identifier' && key.name === 'fields') ||\n (key.type === 'Literal' && key.value === 'fields')\n )\n}\n\n/**\n * The index of the `,` between a trailing array element ending at `from` and\n * the `]` at `to`, or -1 when the element has no trailing comma. Only\n * whitespace, comments, and at most one comma can sit in that gap, and the\n * comments terminate (the source parsed), so this scan just has to not read a\n * comma out of a comment.\n */\nfunction indexOfTrailingComma(source: string, from: number, to: number): number {\n let index = from\n while (index < to) {\n const char = source[index]\n if (char === ',') return index\n if (char === '/' && source[index + 1] === '/') {\n const newline = source.indexOf('\\n', index)\n if (newline === -1 || newline >= to) return -1\n index = newline + 1\n continue\n }\n if (char === '/' && source[index + 1] === '*') {\n index = source.indexOf('*/', index + 2) + 2\n continue\n }\n index++\n }\n return -1\n}\n\nfunction locateFieldsArray(source: string): LocatedArray | {reason: string} {\n // Node offsets are UTF-16 string indices, so splicing `source` with them is\n // exact even around non-ASCII content.\n let program: ReturnType<typeof parseAst>\n try {\n program = parseAst(source, {lang: 'ts'})\n } catch {\n return {reason: 'the config does not parse as TypeScript'}\n }\n\n // The import statement also mentions the identifier, but only call sites\n // show up as CallExpressions.\n const calls: CallExpression[] = []\n collectCalls(program.body, calls)\n if (calls.length === 0) {\n return {reason: 'no `unstable_defineMediaLibrary(...)` call found'}\n }\n if (calls.length > 1) {\n return {reason: 'more than one `unstable_defineMediaLibrary(...)` call found'}\n }\n\n const [config] = calls[0].arguments\n if (config?.type !== 'ObjectExpression') {\n return {reason: 'the `unstable_defineMediaLibrary(...)` argument is not an object literal'}\n }\n\n // Only the config object's own `fields` property may be edited — deploy\n // evaluates exactly that one, so a `fields` array nested elsewhere (or\n // sitting in a helper outside the call) must neither be edited nor block\n // the real one.\n const fieldsProps = config.properties.filter((prop) => isFieldsProperty(prop))\n if (fieldsProps.length === 0) {\n return {reason: 'no `fields` property in the `unstable_defineMediaLibrary({...})` config'}\n }\n if (fieldsProps.length > 1) {\n return {\n reason: 'more than one `fields` property in the `unstable_defineMediaLibrary({...})` config',\n }\n }\n\n const fields = fieldsProps[0]\n const array = fields.value\n if (array.type !== 'ArrayExpression') {\n // A computed value (`fields: sharedFields`) stays the user's to manage —\n // deploy keeps evaluating it, so an \"edit\" elsewhere would be a lie.\n return {reason: 'the `fields` value is not an array literal'}\n }\n\n const open = array.start\n const close = array.end - 1\n const last = array.elements.at(-1)\n\n if (last === null) {\n return {reason: 'the `fields` array ends in an elision'}\n }\n\n let lastContentEnd = open + 1\n if (last) {\n const comma = indexOfTrailingComma(source, last.end, close)\n lastContentEnd = comma === -1 ? last.end : comma + 1\n }\n\n const lineStart = source.lastIndexOf('\\n', fields.start) + 1\n const indent = /^[\\t ]*/.exec(source.slice(lineStart, fields.start))?.[0] ?? ''\n\n return {close, indent, lastContentEnd, open}\n}\n\n/**\n * Append `entryLines` (one rendered `{name, src, title},` object per line) to\n * the config's `fields` array, preserving the existing entries and adding a\n * trailing comma to the last one when needed.\n */\nexport function appendFieldEntries(source: string, entryLines: string[]): EditResult {\n const located = locateFieldsArray(source)\n if ('reason' in located) return {ok: false, reason: located.reason}\n\n const {indent, lastContentEnd, open} = located\n const entryIndent = `${indent} `\n const block = entryLines.map((line) => entryIndent + line).join('\\n')\n\n if (lastContentEnd === open + 1) {\n const inner = source.slice(open + 1, located.close)\n if (inner.trim() !== '') {\n // Only comments inside: rewriting would silently drop them.\n return {ok: false, reason: 'the `fields` array contains only comments'}\n }\n const updated = `${source.slice(0, open + 1)}\\n${block}\\n${indent}${source.slice(located.close)}`\n return {ok: true, updated}\n }\n\n const comma = source[lastContentEnd - 1] === ',' ? '' : ','\n const updated = `${source.slice(0, lastContentEnd)}${comma}\\n${block}${source.slice(lastContentEnd)}`\n return {ok: true, updated}\n}\n\n/** Replace the config's `fields` array contents with `entryLines` wholesale. */\nexport function replaceFieldEntries(source: string, entryLines: string[]): EditResult {\n const located = locateFieldsArray(source)\n if ('reason' in located) return {ok: false, reason: located.reason}\n\n const {close, indent, open} = located\n const entryIndent = `${indent} `\n const block = entryLines.map((line) => entryIndent + line).join('\\n')\n\n const updated = `${source.slice(0, open + 1)}\\n${block}\\n${indent}${source.slice(close)}`\n return {ok: true, updated}\n}\n"],"names":["parseAst","CALL_NAME","collectCalls","value","calls","Array","isArray","item","type","call","callee","name","push","child","Object","values","isFieldsProperty","prop","computed","key","indexOfTrailingComma","source","from","to","index","char","newline","indexOf","locateFieldsArray","program","lang","reason","body","length","config","arguments","fieldsProps","properties","filter","fields","array","open","start","close","end","last","elements","at","lastContentEnd","comma","lineStart","lastIndexOf","indent","exec","slice","appendFieldEntries","entryLines","located","ok","entryIndent","block","map","line","join","inner","trim","updated","replaceFieldEntries"],"mappings":"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"}
@@ -0,0 +1,225 @@
1
+ import { validateMediaLibraryAssetAspect } from '@sanity/schema/_internal';
2
+ import startCase from 'lodash-es/startCase.js';
3
+ import { VALID_ASPECT_NAME } from './aspectName.js';
4
+ /** Keys whose object members are schema definitions, printed wrapped in a definer call. */ const DEFINER_BY_PARENT_KEY = {
5
+ fields: 'defineField',
6
+ of: 'defineArrayMember'
7
+ };
8
+ const IDENTIFIER_KEY = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
9
+ /** Scalar arrays shorter than this print inline (e.g. `options.list`). */ const INLINE_ARRAY_MAX_WIDTH = 60;
10
+ /**
11
+ * Convert a page of stored aspect documents into field modules, one per field
12
+ * name. Two stored documents can resolve to the same name (a legacy document
13
+ * whose `_id` predates its `definition.name`), which would otherwise produce
14
+ * the same module twice — the document whose `_id` matches the name wins, the
15
+ * rest are reported as skipped. Shared by `sanity media pull-aspects` and the
16
+ * init-time pull offer.
17
+ *
18
+ * @internal
19
+ */ export function convertAspectDocuments(documents) {
20
+ const skipped = [];
21
+ // First pass: convert every stored document.
22
+ const candidates = [];
23
+ for (const doc of documents){
24
+ const outcome = aspectDocumentToFieldModule(doc);
25
+ if (outcome.status === 'skipped') {
26
+ skipped.push(outcome);
27
+ } else {
28
+ candidates.push({
29
+ module: outcome,
30
+ sourceId: String(doc._id)
31
+ });
32
+ }
33
+ }
34
+ // Second pass: pick one winner per name.
35
+ const byName = new Map();
36
+ for (const candidate of candidates){
37
+ const group = byName.get(candidate.module.name) ?? [];
38
+ group.push(candidate);
39
+ byName.set(candidate.module.name, group);
40
+ }
41
+ const modules = [];
42
+ for (const [name, group] of byName){
43
+ const winner = group.find((candidate)=>candidate.sourceId === name) ?? group[0];
44
+ modules.push(winner.module);
45
+ for (const loser of group){
46
+ if (loser === winner) continue;
47
+ skipped.push({
48
+ id: loser.sourceId,
49
+ reasons: [
50
+ `Also defines "${name}" — generated from "${winner.sourceId}" instead`
51
+ ],
52
+ status: 'skipped',
53
+ validationErrors: []
54
+ });
55
+ }
56
+ }
57
+ return {
58
+ modules,
59
+ skipped
60
+ };
61
+ }
62
+ /**
63
+ * Codegen one stored aspect document into a `src/fields/<name>.tsx` field
64
+ * module for a Media Library config app.
65
+ *
66
+ * Stored aspects are migration-ready by construction: `definition` is the
67
+ * JSON-serialized `defineField(...)` value with functions and component
68
+ * references already stripped, so generation is mechanical pretty-printing
69
+ * into a `defineField({...})` call (nested `fields`/`of` members wrapped in
70
+ * `defineField`/`defineArrayMember`, matching hand-authored style).
71
+ *
72
+ * Edge cases handled here, not silently:
73
+ *
74
+ * - `_id` vs `definition.name` mismatch: the module is generated under
75
+ * `definition.name` — the `aspects.<name>` key existing asset data lives
76
+ * under. Normalizing onto `_id` instead would silently orphan that data.
77
+ * The old document (keyed `_id`) is reported for manual deletion.
78
+ * - `assetType` has no equivalent in the config model; a document carrying it
79
+ * generates with a warning that the scoping is lost.
80
+ * - Definitions failing current schema validation are skipped with the
81
+ * validation errors, mirroring `sanity media deploy-aspect`.
82
+ *
83
+ * Pure (no I/O): the command decides where modules are written.
84
+ *
85
+ * @internal
86
+ */ export function aspectDocumentToFieldModule(doc) {
87
+ const { _id } = doc;
88
+ if (!isRecord(doc.definition)) {
89
+ return {
90
+ id: _id,
91
+ reasons: [
92
+ 'The stored document has no field definition object'
93
+ ],
94
+ status: 'skipped',
95
+ validationErrors: []
96
+ };
97
+ }
98
+ const definitionName = typeof doc.definition.name === 'string' ? doc.definition.name : undefined;
99
+ const name = definitionName ?? _id;
100
+ if (!VALID_ASPECT_NAME.test(name)) {
101
+ return {
102
+ id: _id,
103
+ reasons: [
104
+ `"${name}" is not a valid field name (${VALID_ASPECT_NAME}) — it becomes the module name and the aspects.<name> key on assets`
105
+ ],
106
+ status: 'skipped',
107
+ validationErrors: []
108
+ };
109
+ }
110
+ const warnings = [];
111
+ if (definitionName === undefined) {
112
+ warnings.push(`The stored definition has no name — using the document _id "${_id}"`);
113
+ } else if (definitionName !== _id) {
114
+ warnings.push(`Stored _id "${_id}" and definition name "${definitionName}" differ. Generated as ` + `"${definitionName}" — the aspects.<name> key existing asset data lives under; using ` + `"${_id}" would orphan that data. Deploying the config creates "${definitionName}" ` + `and deletes the old "${_id}" document.`);
115
+ }
116
+ if (doc.assetType !== undefined && doc.assetType !== null) {
117
+ warnings.push(`The stored document scopes the aspect by assetType (${JSON.stringify(doc.assetType)}), ` + `which has no equivalent in the config model — the generated field applies to all ` + `asset types, and syncing the config drops the scoping.`);
118
+ }
119
+ // Normalize the name in before validating, so a document that only carries
120
+ // its name as `_id` isn't rejected for the missing property.
121
+ const definition = definitionName ? doc.definition : {
122
+ name,
123
+ ...doc.definition
124
+ };
125
+ const [valid, validationErrors] = validateMediaLibraryAssetAspect(definition);
126
+ if (!valid) {
127
+ return {
128
+ id: _id,
129
+ reasons: [
130
+ 'The stored definition fails schema validation'
131
+ ],
132
+ status: 'skipped',
133
+ validationErrors
134
+ };
135
+ }
136
+ return {
137
+ // .tsx although the generated content has no JSX: field modules are
138
+ // expected to grow custom input components (the one thing the stored
139
+ // aspect could not carry), and a later rename would also mean fixing the
140
+ // `src:` entry in sanity.cli.ts. Matches the template's example modules.
141
+ filename: `${name}.tsx`,
142
+ name,
143
+ ...typeof doc.public === 'boolean' ? {
144
+ public: doc.public
145
+ } : {},
146
+ source: printFieldModule(definition),
147
+ status: 'generated',
148
+ title: typeof definition.title === 'string' ? definition.title : startCase(name),
149
+ warnings
150
+ };
151
+ }
152
+ /**
153
+ * The paste-ready `fields: [...]` entries for `sanity.cli.ts`, one line per
154
+ * generated module. When `writtenPaths` (module name to root-relative path)
155
+ * has an entry for a module, that path wins — the module may have replaced an
156
+ * existing file (e.g. a `.tsx` variant) rather than landing at the default
157
+ * `<srcDir>/<filename>` location.
158
+ *
159
+ * @internal
160
+ */ export function formatFieldEntries(modules, srcDir, writtenPaths) {
161
+ const dir = srcDir.replaceAll('\\', '/').replace(/\/+$/, '');
162
+ return modules.map((module)=>{
163
+ const src = writtenPaths?.get(module.name)?.replaceAll('\\', '/') ?? `${dir}/${module.filename}`;
164
+ const publicPart = module.public === undefined ? '' : `, public: ${module.public}`;
165
+ return `{name: '${module.name}', src: './${src}', title: ${quote(module.title)}${publicPart}},`;
166
+ }).join('\n');
167
+ }
168
+ function isRecord(value) {
169
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
170
+ }
171
+ function printFieldModule(definition) {
172
+ const state = {
173
+ usesDefineArrayMember: false
174
+ };
175
+ const literal = printObject(definition, '', state);
176
+ const imports = state.usesDefineArrayMember ? 'defineArrayMember, defineField' : 'defineField';
177
+ return `import {${imports}} from 'sanity'\n\nexport default defineField(${literal})\n`;
178
+ }
179
+ function quote(value) {
180
+ const escaped = value.replaceAll('\\', '\\\\').replaceAll("'", String.raw`\'`).replaceAll('\n', String.raw`\n`).replaceAll('\r', String.raw`\r`).replaceAll('\t', String.raw`\t`);
181
+ return `'${escaped}'`;
182
+ }
183
+ function printKey(key) {
184
+ return IDENTIFIER_KEY.test(key) ? key : quote(key);
185
+ }
186
+ function printValue(value, indent, parentKey, state) {
187
+ if (value === null) return 'null';
188
+ if (typeof value === 'string') return quote(value);
189
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value);
190
+ if (Array.isArray(value)) return printArray(value, indent, parentKey, state);
191
+ if (isRecord(value)) return printObject(value, indent, state);
192
+ // Stored definitions are JSON documents; anything else cannot occur.
193
+ throw new TypeError(`Cannot print value of type ${typeof value}`);
194
+ }
195
+ function printArray(items, indent, parentKey, state) {
196
+ if (items.length === 0) return '[]';
197
+ const allScalar = items.every((item)=>item === null || [
198
+ 'boolean',
199
+ 'number',
200
+ 'string'
201
+ ].includes(typeof item));
202
+ if (allScalar) {
203
+ const inline = `[${items.map((item)=>printValue(item, indent, null, state)).join(', ')}]`;
204
+ if (inline.length <= INLINE_ARRAY_MAX_WIDTH) return inline;
205
+ }
206
+ const definer = parentKey === null ? undefined : DEFINER_BY_PARENT_KEY[parentKey];
207
+ const innerIndent = `${indent} `;
208
+ const lines = items.map((item)=>{
209
+ if (definer !== undefined && isRecord(item)) {
210
+ if (definer === 'defineArrayMember') state.usesDefineArrayMember = true;
211
+ return `${innerIndent}${definer}(${printObject(item, innerIndent, state)}),`;
212
+ }
213
+ return `${innerIndent}${printValue(item, innerIndent, null, state)},`;
214
+ });
215
+ return `[\n${lines.join('\n')}\n${indent}]`;
216
+ }
217
+ function printObject(obj, indent, state) {
218
+ const entries = Object.entries(obj).filter(([, value])=>value !== undefined);
219
+ if (entries.length === 0) return '{}';
220
+ const innerIndent = `${indent} `;
221
+ const lines = entries.map(([key, value])=>`${innerIndent}${printKey(key)}: ${printValue(value, innerIndent, key, state)},`);
222
+ return `{\n${lines.join('\n')}\n${indent}}`;
223
+ }
224
+
225
+ //# sourceMappingURL=pullAspects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/actions/media/pullAspects.ts"],"sourcesContent":["import {validateMediaLibraryAssetAspect} from '@sanity/schema/_internal'\nimport {type SchemaValidationProblem} from '@sanity/types'\nimport startCase from 'lodash-es/startCase.js'\n\nimport {type StoredAspectDocument} from '../../services/mediaLibraries.js'\nimport {VALID_ASPECT_NAME} from './aspectName.js'\n\n/** Keys whose object members are schema definitions, printed wrapped in a definer call. */\nconst DEFINER_BY_PARENT_KEY: Record<string, string> = {\n fields: 'defineField',\n of: 'defineArrayMember',\n}\n\nconst IDENTIFIER_KEY = /^[A-Za-z_$][A-Za-z0-9_$]*$/\n\n/** Scalar arrays shorter than this print inline (e.g. `options.list`). */\nconst INLINE_ARRAY_MAX_WIDTH = 60\n\n/**\n * The stored `sanity.asset.aspect` document shape this migration reads.\n * Deliberately loose: documents may predate the config model (authored via\n * `sanity media deploy-aspect` or by hand) and carry fields the model has no\n * equivalent for.\n */\nexport type PullableAspectDocument = StoredAspectDocument\n\nexport interface GeneratedFieldModule {\n filename: string\n /** The `aspects.<name>` data key — module filename and config entry name. */\n name: string\n source: string\n status: 'generated'\n title: string\n warnings: string[]\n\n public?: boolean\n}\n\nexport interface SkippedAspect {\n id: string\n reasons: string[]\n status: 'skipped'\n validationErrors: SchemaValidationProblem[][]\n}\n\nexport type PullAspectOutcome = GeneratedFieldModule | SkippedAspect\n\n/**\n * Convert a page of stored aspect documents into field modules, one per field\n * name. Two stored documents can resolve to the same name (a legacy document\n * whose `_id` predates its `definition.name`), which would otherwise produce\n * the same module twice — the document whose `_id` matches the name wins, the\n * rest are reported as skipped. Shared by `sanity media pull-aspects` and the\n * init-time pull offer.\n *\n * @internal\n */\nexport function convertAspectDocuments(documents: PullableAspectDocument[]): {\n modules: GeneratedFieldModule[]\n skipped: SkippedAspect[]\n} {\n const skipped: SkippedAspect[] = []\n\n // First pass: convert every stored document.\n const candidates: Array<{module: GeneratedFieldModule; sourceId: string}> = []\n for (const doc of documents) {\n const outcome = aspectDocumentToFieldModule(doc)\n if (outcome.status === 'skipped') {\n skipped.push(outcome)\n } else {\n candidates.push({module: outcome, sourceId: String(doc._id)})\n }\n }\n\n // Second pass: pick one winner per name.\n const byName = new Map<string, Array<{module: GeneratedFieldModule; sourceId: string}>>()\n for (const candidate of candidates) {\n const group = byName.get(candidate.module.name) ?? []\n group.push(candidate)\n byName.set(candidate.module.name, group)\n }\n const modules: GeneratedFieldModule[] = []\n for (const [name, group] of byName) {\n const winner = group.find((candidate) => candidate.sourceId === name) ?? group[0]\n modules.push(winner.module)\n for (const loser of group) {\n if (loser === winner) continue\n skipped.push({\n id: loser.sourceId,\n reasons: [`Also defines \"${name}\" — generated from \"${winner.sourceId}\" instead`],\n status: 'skipped',\n validationErrors: [],\n })\n }\n }\n\n return {modules, skipped}\n}\n\n/**\n * Codegen one stored aspect document into a `src/fields/<name>.tsx` field\n * module for a Media Library config app.\n *\n * Stored aspects are migration-ready by construction: `definition` is the\n * JSON-serialized `defineField(...)` value with functions and component\n * references already stripped, so generation is mechanical pretty-printing\n * into a `defineField({...})` call (nested `fields`/`of` members wrapped in\n * `defineField`/`defineArrayMember`, matching hand-authored style).\n *\n * Edge cases handled here, not silently:\n *\n * - `_id` vs `definition.name` mismatch: the module is generated under\n * `definition.name` — the `aspects.<name>` key existing asset data lives\n * under. Normalizing onto `_id` instead would silently orphan that data.\n * The old document (keyed `_id`) is reported for manual deletion.\n * - `assetType` has no equivalent in the config model; a document carrying it\n * generates with a warning that the scoping is lost.\n * - Definitions failing current schema validation are skipped with the\n * validation errors, mirroring `sanity media deploy-aspect`.\n *\n * Pure (no I/O): the command decides where modules are written.\n *\n * @internal\n */\nexport function aspectDocumentToFieldModule(doc: PullableAspectDocument): PullAspectOutcome {\n const {_id} = doc\n\n if (!isRecord(doc.definition)) {\n return {\n id: _id,\n reasons: ['The stored document has no field definition object'],\n status: 'skipped',\n validationErrors: [],\n }\n }\n\n const definitionName = typeof doc.definition.name === 'string' ? doc.definition.name : undefined\n const name = definitionName ?? _id\n\n if (!VALID_ASPECT_NAME.test(name)) {\n return {\n id: _id,\n reasons: [\n `\"${name}\" is not a valid field name (${VALID_ASPECT_NAME}) — it becomes the module name and the aspects.<name> key on assets`,\n ],\n status: 'skipped',\n validationErrors: [],\n }\n }\n\n const warnings: string[] = []\n\n if (definitionName === undefined) {\n warnings.push(`The stored definition has no name — using the document _id \"${_id}\"`)\n } else if (definitionName !== _id) {\n warnings.push(\n `Stored _id \"${_id}\" and definition name \"${definitionName}\" differ. Generated as ` +\n `\"${definitionName}\" — the aspects.<name> key existing asset data lives under; using ` +\n `\"${_id}\" would orphan that data. Deploying the config creates \"${definitionName}\" ` +\n `and deletes the old \"${_id}\" document.`,\n )\n }\n\n if (doc.assetType !== undefined && doc.assetType !== null) {\n warnings.push(\n `The stored document scopes the aspect by assetType (${JSON.stringify(doc.assetType)}), ` +\n `which has no equivalent in the config model — the generated field applies to all ` +\n `asset types, and syncing the config drops the scoping.`,\n )\n }\n\n // Normalize the name in before validating, so a document that only carries\n // its name as `_id` isn't rejected for the missing property.\n const definition: Record<string, unknown> = definitionName\n ? doc.definition\n : {name, ...doc.definition}\n\n const [valid, validationErrors] = validateMediaLibraryAssetAspect(definition)\n if (!valid) {\n return {\n id: _id,\n reasons: ['The stored definition fails schema validation'],\n status: 'skipped',\n validationErrors,\n }\n }\n\n return {\n // .tsx although the generated content has no JSX: field modules are\n // expected to grow custom input components (the one thing the stored\n // aspect could not carry), and a later rename would also mean fixing the\n // `src:` entry in sanity.cli.ts. Matches the template's example modules.\n filename: `${name}.tsx`,\n name,\n ...(typeof doc.public === 'boolean' ? {public: doc.public} : {}),\n source: printFieldModule(definition),\n status: 'generated',\n title: typeof definition.title === 'string' ? definition.title : startCase(name),\n warnings,\n }\n}\n\n/**\n * The paste-ready `fields: [...]` entries for `sanity.cli.ts`, one line per\n * generated module. When `writtenPaths` (module name to root-relative path)\n * has an entry for a module, that path wins — the module may have replaced an\n * existing file (e.g. a `.tsx` variant) rather than landing at the default\n * `<srcDir>/<filename>` location.\n *\n * @internal\n */\nexport function formatFieldEntries(\n modules: GeneratedFieldModule[],\n srcDir: string,\n writtenPaths?: Map<string, string>,\n): string {\n const dir = srcDir.replaceAll('\\\\', '/').replace(/\\/+$/, '')\n return modules\n .map((module) => {\n const src =\n writtenPaths?.get(module.name)?.replaceAll('\\\\', '/') ?? `${dir}/${module.filename}`\n const publicPart = module.public === undefined ? '' : `, public: ${module.public}`\n return `{name: '${module.name}', src: './${src}', title: ${quote(module.title)}${publicPart}},`\n })\n .join('\\n')\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\ninterface PrinterState {\n usesDefineArrayMember: boolean\n}\n\nfunction printFieldModule(definition: Record<string, unknown>): string {\n const state: PrinterState = {usesDefineArrayMember: false}\n const literal = printObject(definition, '', state)\n const imports = state.usesDefineArrayMember ? 'defineArrayMember, defineField' : 'defineField'\n\n return `import {${imports}} from 'sanity'\\n\\nexport default defineField(${literal})\\n`\n}\n\nfunction quote(value: string): string {\n const escaped = value\n .replaceAll('\\\\', '\\\\\\\\')\n .replaceAll(\"'\", String.raw`\\'`)\n .replaceAll('\\n', String.raw`\\n`)\n .replaceAll('\\r', String.raw`\\r`)\n .replaceAll('\\t', String.raw`\\t`)\n return `'${escaped}'`\n}\n\nfunction printKey(key: string): string {\n return IDENTIFIER_KEY.test(key) ? key : quote(key)\n}\n\nfunction printValue(\n value: unknown,\n indent: string,\n parentKey: string | null,\n state: PrinterState,\n): string {\n if (value === null) return 'null'\n if (typeof value === 'string') return quote(value)\n if (typeof value === 'number' || typeof value === 'boolean') return String(value)\n if (Array.isArray(value)) return printArray(value, indent, parentKey, state)\n if (isRecord(value)) return printObject(value, indent, state)\n // Stored definitions are JSON documents; anything else cannot occur.\n throw new TypeError(`Cannot print value of type ${typeof value}`)\n}\n\nfunction printArray(\n items: unknown[],\n indent: string,\n parentKey: string | null,\n state: PrinterState,\n): string {\n if (items.length === 0) return '[]'\n\n const allScalar = items.every(\n (item) => item === null || ['boolean', 'number', 'string'].includes(typeof item),\n )\n if (allScalar) {\n const inline = `[${items.map((item) => printValue(item, indent, null, state)).join(', ')}]`\n if (inline.length <= INLINE_ARRAY_MAX_WIDTH) return inline\n }\n\n const definer = parentKey === null ? undefined : DEFINER_BY_PARENT_KEY[parentKey]\n const innerIndent = `${indent} `\n const lines = items.map((item) => {\n if (definer !== undefined && isRecord(item)) {\n if (definer === 'defineArrayMember') state.usesDefineArrayMember = true\n return `${innerIndent}${definer}(${printObject(item, innerIndent, state)}),`\n }\n return `${innerIndent}${printValue(item, innerIndent, null, state)},`\n })\n return `[\\n${lines.join('\\n')}\\n${indent}]`\n}\n\nfunction printObject(obj: Record<string, unknown>, indent: string, state: PrinterState): string {\n const entries = Object.entries(obj).filter(([, value]) => value !== undefined)\n if (entries.length === 0) return '{}'\n\n const innerIndent = `${indent} `\n const lines = entries.map(\n ([key, value]) =>\n `${innerIndent}${printKey(key)}: ${printValue(value, innerIndent, key, state)},`,\n )\n return `{\\n${lines.join('\\n')}\\n${indent}}`\n}\n"],"names":["validateMediaLibraryAssetAspect","startCase","VALID_ASPECT_NAME","DEFINER_BY_PARENT_KEY","fields","of","IDENTIFIER_KEY","INLINE_ARRAY_MAX_WIDTH","convertAspectDocuments","documents","skipped","candidates","doc","outcome","aspectDocumentToFieldModule","status","push","module","sourceId","String","_id","byName","Map","candidate","group","get","name","set","modules","winner","find","loser","id","reasons","validationErrors","isRecord","definition","definitionName","undefined","test","warnings","assetType","JSON","stringify","valid","filename","public","source","printFieldModule","title","formatFieldEntries","srcDir","writtenPaths","dir","replaceAll","replace","map","src","publicPart","quote","join","value","Array","isArray","state","usesDefineArrayMember","literal","printObject","imports","escaped","raw","printKey","key","printValue","indent","parentKey","printArray","TypeError","items","length","allScalar","every","item","includes","inline","definer","innerIndent","lines","obj","entries","Object","filter"],"mappings":"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"}
@@ -0,0 +1,83 @@
1
+ import { CLIError } from '@oclif/core/errors';
2
+ import { exitCodes } from '@sanity/cli-core';
3
+ import { promptForMediaLibrary } from '../../prompts/promptForMediaLibrary.js';
4
+ import { getMediaLibrariesForOrganization } from '../../services/mediaLibraries.js';
5
+ /** @see MediaLibraryResolution */ export async function resolveMediaLibrary(options) {
6
+ const { mediaLibraryIdFlag, organizationId } = options;
7
+ const libraries = await getMediaLibrariesForOrganization(organizationId);
8
+ const libraryIds = libraries.map((library)=>library.id);
9
+ if (mediaLibraryIdFlag) {
10
+ if (!libraryIds.includes(mediaLibraryIdFlag)) {
11
+ return {
12
+ message: `Media library ${mediaLibraryIdFlag} is not an active library of organization ` + `${organizationId} (found: ${libraryIds.join(', ') || 'none'})`,
13
+ solution: 'Pass --media-library-id with one of the active library ids',
14
+ type: 'invalid-flag'
15
+ };
16
+ }
17
+ return {
18
+ mediaLibraryId: mediaLibraryIdFlag,
19
+ type: 'resolved'
20
+ };
21
+ }
22
+ if (libraries.length === 0) {
23
+ return {
24
+ message: `Organization ${organizationId} has no active media library`,
25
+ solution: 'Install the Media Library for the organization first',
26
+ type: 'none-active'
27
+ };
28
+ }
29
+ if (libraries.length > 1) {
30
+ return {
31
+ libraries,
32
+ message: `Organization ${organizationId} has ${libraries.length} active media libraries ` + `(${libraryIds.join(', ')})`,
33
+ solution: 'Pass --media-library-id with the library this config belongs to',
34
+ type: 'ambiguous'
35
+ };
36
+ }
37
+ return {
38
+ mediaLibraryId: libraries[0].id,
39
+ type: 'resolved'
40
+ };
41
+ }
42
+ /**
43
+ * Command-facing resolution: ambiguity prompts when interactive (Ctrl+C there
44
+ * raises `ExitPromptError`, which callers must let propagate to
45
+ * `SanityCommand.catch`); misuse — a bad flag value, a missing required flag
46
+ * in unattended mode — exits with the usage code. No active library is an
47
+ * environment problem, not misuse, so it keeps the runtime code.
48
+ */ export async function resolveMediaLibraryId(options) {
49
+ const { mediaLibraryIdFlag, organizationId, unattended } = options;
50
+ const resolution = await resolveMediaLibrary({
51
+ mediaLibraryIdFlag,
52
+ organizationId
53
+ });
54
+ switch(resolution.type){
55
+ case 'ambiguous':
56
+ {
57
+ if (!unattended) return promptForMediaLibrary({
58
+ mediaLibraries: resolution.libraries
59
+ });
60
+ throw new CLIError(`${resolution.message}. ${resolution.solution}.`, {
61
+ exit: exitCodes.USAGE_ERROR
62
+ });
63
+ }
64
+ case 'invalid-flag':
65
+ {
66
+ throw new CLIError(`${resolution.message}. ${resolution.solution}.`, {
67
+ exit: exitCodes.USAGE_ERROR
68
+ });
69
+ }
70
+ case 'none-active':
71
+ {
72
+ throw new CLIError(`${resolution.message}. ${resolution.solution}.`, {
73
+ exit: exitCodes.RUNTIME_ERROR
74
+ });
75
+ }
76
+ default:
77
+ {
78
+ return resolution.mediaLibraryId;
79
+ }
80
+ }
81
+ }
82
+
83
+ //# sourceMappingURL=resolveMediaLibraryId.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/actions/media/resolveMediaLibraryId.ts"],"sourcesContent":["import {CLIError} from '@oclif/core/errors'\nimport {exitCodes} from '@sanity/cli-core'\n\nimport {promptForMediaLibrary} from '../../prompts/promptForMediaLibrary.js'\nimport {getMediaLibrariesForOrganization, type MediaLibrary} from '../../services/mediaLibraries.js'\n\n/**\n * The target-library resolution config-app media operations share: the flag\n * wins (validated against the organization's active libraries), a single\n * active library is unambiguous, anything else is a verdict the caller turns\n * into its own failure shape — a prompt, a usage error, or a deploy check.\n */\nexport type MediaLibraryResolution =\n | {\n /** The active libraries a prompt would offer. */\n libraries: MediaLibrary[]\n message: string\n solution: string\n type: 'ambiguous'\n }\n | {mediaLibraryId: string; type: 'resolved'}\n | {message: string; solution: string; type: 'invalid-flag'}\n | {message: string; solution: string; type: 'none-active'}\n\n/** @see MediaLibraryResolution */\nexport async function resolveMediaLibrary(options: {\n mediaLibraryIdFlag: string | undefined\n organizationId: string\n}): Promise<MediaLibraryResolution> {\n const {mediaLibraryIdFlag, organizationId} = options\n const libraries = await getMediaLibrariesForOrganization(organizationId)\n const libraryIds = libraries.map((library) => library.id)\n\n if (mediaLibraryIdFlag) {\n if (!libraryIds.includes(mediaLibraryIdFlag)) {\n return {\n message:\n `Media library ${mediaLibraryIdFlag} is not an active library of organization ` +\n `${organizationId} (found: ${libraryIds.join(', ') || 'none'})`,\n solution: 'Pass --media-library-id with one of the active library ids',\n type: 'invalid-flag',\n }\n }\n return {mediaLibraryId: mediaLibraryIdFlag, type: 'resolved'}\n }\n\n if (libraries.length === 0) {\n return {\n message: `Organization ${organizationId} has no active media library`,\n solution: 'Install the Media Library for the organization first',\n type: 'none-active',\n }\n }\n if (libraries.length > 1) {\n return {\n libraries,\n message:\n `Organization ${organizationId} has ${libraries.length} active media libraries ` +\n `(${libraryIds.join(', ')})`,\n solution: 'Pass --media-library-id with the library this config belongs to',\n type: 'ambiguous',\n }\n }\n return {mediaLibraryId: libraries[0].id, type: 'resolved'}\n}\n\n/**\n * Command-facing resolution: ambiguity prompts when interactive (Ctrl+C there\n * raises `ExitPromptError`, which callers must let propagate to\n * `SanityCommand.catch`); misuse — a bad flag value, a missing required flag\n * in unattended mode — exits with the usage code. No active library is an\n * environment problem, not misuse, so it keeps the runtime code.\n */\nexport async function resolveMediaLibraryId(options: {\n mediaLibraryIdFlag: string | undefined\n organizationId: string\n unattended: boolean\n}): Promise<string> {\n const {mediaLibraryIdFlag, organizationId, unattended} = options\n const resolution = await resolveMediaLibrary({mediaLibraryIdFlag, organizationId})\n\n switch (resolution.type) {\n case 'ambiguous': {\n if (!unattended) return promptForMediaLibrary({mediaLibraries: resolution.libraries})\n throw new CLIError(`${resolution.message}. ${resolution.solution}.`, {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n case 'invalid-flag': {\n throw new CLIError(`${resolution.message}. ${resolution.solution}.`, {\n exit: exitCodes.USAGE_ERROR,\n })\n }\n case 'none-active': {\n throw new CLIError(`${resolution.message}. ${resolution.solution}.`, {\n exit: exitCodes.RUNTIME_ERROR,\n })\n }\n default: {\n return resolution.mediaLibraryId\n }\n }\n}\n"],"names":["CLIError","exitCodes","promptForMediaLibrary","getMediaLibrariesForOrganization","resolveMediaLibrary","options","mediaLibraryIdFlag","organizationId","libraries","libraryIds","map","library","id","includes","message","join","solution","type","mediaLibraryId","length","resolveMediaLibraryId","unattended","resolution","mediaLibraries","exit","USAGE_ERROR","RUNTIME_ERROR"],"mappings":"AAAA,SAAQA,QAAQ,QAAO,qBAAoB;AAC3C,SAAQC,SAAS,QAAO,mBAAkB;AAE1C,SAAQC,qBAAqB,QAAO,yCAAwC;AAC5E,SAAQC,gCAAgC,QAA0B,mCAAkC;AAoBpG,gCAAgC,GAChC,OAAO,eAAeC,oBAAoBC,OAGzC;IACC,MAAM,EAACC,kBAAkB,EAAEC,cAAc,EAAC,GAAGF;IAC7C,MAAMG,YAAY,MAAML,iCAAiCI;IACzD,MAAME,aAAaD,UAAUE,GAAG,CAAC,CAACC,UAAYA,QAAQC,EAAE;IAExD,IAAIN,oBAAoB;QACtB,IAAI,CAACG,WAAWI,QAAQ,CAACP,qBAAqB;YAC5C,OAAO;gBACLQ,SACE,CAAC,cAAc,EAAER,mBAAmB,0CAA0C,CAAC,GAC/E,GAAGC,eAAe,SAAS,EAAEE,WAAWM,IAAI,CAAC,SAAS,OAAO,CAAC,CAAC;gBACjEC,UAAU;gBACVC,MAAM;YACR;QACF;QACA,OAAO;YAACC,gBAAgBZ;YAAoBW,MAAM;QAAU;IAC9D;IAEA,IAAIT,UAAUW,MAAM,KAAK,GAAG;QAC1B,OAAO;YACLL,SAAS,CAAC,aAAa,EAAEP,eAAe,4BAA4B,CAAC;YACrES,UAAU;YACVC,MAAM;QACR;IACF;IACA,IAAIT,UAAUW,MAAM,GAAG,GAAG;QACxB,OAAO;YACLX;YACAM,SACE,CAAC,aAAa,EAAEP,eAAe,KAAK,EAAEC,UAAUW,MAAM,CAAC,wBAAwB,CAAC,GAChF,CAAC,CAAC,EAAEV,WAAWM,IAAI,CAAC,MAAM,CAAC,CAAC;YAC9BC,UAAU;YACVC,MAAM;QACR;IACF;IACA,OAAO;QAACC,gBAAgBV,SAAS,CAAC,EAAE,CAACI,EAAE;QAAEK,MAAM;IAAU;AAC3D;AAEA;;;;;;CAMC,GACD,OAAO,eAAeG,sBAAsBf,OAI3C;IACC,MAAM,EAACC,kBAAkB,EAAEC,cAAc,EAAEc,UAAU,EAAC,GAAGhB;IACzD,MAAMiB,aAAa,MAAMlB,oBAAoB;QAACE;QAAoBC;IAAc;IAEhF,OAAQe,WAAWL,IAAI;QACrB,KAAK;YAAa;gBAChB,IAAI,CAACI,YAAY,OAAOnB,sBAAsB;oBAACqB,gBAAgBD,WAAWd,SAAS;gBAAA;gBACnF,MAAM,IAAIR,SAAS,GAAGsB,WAAWR,OAAO,CAAC,EAAE,EAAEQ,WAAWN,QAAQ,CAAC,CAAC,CAAC,EAAE;oBACnEQ,MAAMvB,UAAUwB,WAAW;gBAC7B;YACF;QACA,KAAK;YAAgB;gBACnB,MAAM,IAAIzB,SAAS,GAAGsB,WAAWR,OAAO,CAAC,EAAE,EAAEQ,WAAWN,QAAQ,CAAC,CAAC,CAAC,EAAE;oBACnEQ,MAAMvB,UAAUwB,WAAW;gBAC7B;YACF;QACA,KAAK;YAAe;gBAClB,MAAM,IAAIzB,SAAS,GAAGsB,WAAWR,OAAO,CAAC,EAAE,EAAEQ,WAAWN,QAAQ,CAAC,CAAC,CAAC,EAAE;oBACnEQ,MAAMvB,UAAUyB,aAAa;gBAC/B;YACF;QACA;YAAS;gBACP,OAAOJ,WAAWJ,cAAc;YAClC;IACF;AACF"}
@@ -82,6 +82,7 @@ export class CodemodCommand extends SanityCommand {
82
82
  // We use npx to run jscodeshift, so verify that it exists and works before using it
83
83
  this.ensureNpx();
84
84
  const prepared = await mod.prepare?.({
85
+ dry: dryRun,
85
86
  output: this.output,
86
87
  workDir
87
88
  });