@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.
- package/README.md +313 -0
- package/dist/actions/codemods/dashboard.js +136 -8
- package/dist/actions/codemods/dashboard.js.map +1 -1
- package/dist/actions/codemods/types.js.map +1 -1
- 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/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/getOrCreateDataset.js +8 -5
- package/dist/actions/init/project/getOrCreateDataset.js.map +1 -1
- package/dist/actions/init/project/getOrCreateProject.js +7 -5
- package/dist/actions/init/project/getOrCreateProject.js.map +1 -1
- package/dist/actions/init/project/getProjectDetails.js +25 -2
- package/dist/actions/init/project/getProjectDetails.js.map +1 -1
- package/dist/actions/init/project/promptUserForOrganization.js +6 -4
- package/dist/actions/init/project/promptUserForOrganization.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/commands/codemod.js +1 -0
- package/dist/commands/codemod.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/dist/util/searchChoices.js +52 -0
- package/dist/util/searchChoices.js.map +1 -0
- package/oclif.manifest.json +2134 -1445
- package/package.json +9 -8
- 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
- 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"}
|
package/dist/commands/codemod.js
CHANGED