@sanity/cli 8.22.0 → 8.23.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/context/watchJob.js +9 -3
- package/dist/actions/context/watchJob.js.map +1 -1
- package/dist/actions/deploy/blueprintManaged.js +59 -0
- package/dist/actions/deploy/blueprintManaged.js.map +1 -0
- package/dist/actions/deploy/deployApp.js +117 -1
- package/dist/actions/deploy/deployApp.js.map +1 -1
- package/dist/actions/deploy/deployRunner.js.map +1 -1
- package/dist/actions/deploy/deployStudio.js +6 -31
- package/dist/actions/deploy/deployStudio.js.map +1 -1
- package/dist/actions/deploy/deployStudioSchemasAndManifestsWorker.js +6 -15
- package/dist/actions/deploy/deployStudioSchemasAndManifestsWorker.js.map +1 -1
- package/dist/actions/exec/runScript.worker.js +6 -2
- package/dist/actions/exec/runScript.worker.js.map +1 -1
- package/dist/actions/init/bootstrapLocalTemplate.js +35 -11
- package/dist/actions/init/bootstrapLocalTemplate.js.map +1 -1
- package/dist/actions/init/createMediaLibraryCliConfig.js +12 -0
- package/dist/actions/init/createMediaLibraryCliConfig.js.map +1 -0
- package/dist/actions/init/determineMediaLibraryTemplate.js +14 -0
- package/dist/actions/init/determineMediaLibraryTemplate.js.map +1 -0
- package/dist/actions/init/initAction.js +35 -6
- package/dist/actions/init/initAction.js.map +1 -1
- package/dist/actions/init/initApp.js +81 -6
- package/dist/actions/init/initApp.js.map +1 -1
- package/dist/actions/init/offerAspectPull.js +81 -0
- package/dist/actions/init/offerAspectPull.js.map +1 -0
- package/dist/actions/init/project/getProjectDetails.js +25 -2
- package/dist/actions/init/project/getProjectDetails.js.map +1 -1
- package/dist/actions/init/templates/index.js +3 -0
- package/dist/actions/init/templates/index.js.map +1 -1
- package/dist/actions/init/templates/mediaLibraryConfig.js +51 -0
- package/dist/actions/init/templates/mediaLibraryConfig.js.map +1 -0
- package/dist/actions/media/aspectName.js +8 -0
- package/dist/actions/media/aspectName.js.map +1 -0
- package/dist/actions/media/aspectSync/contract.js +163 -0
- package/dist/actions/media/aspectSync/contract.js.map +1 -0
- package/dist/actions/media/aspectSync/syncAspects.js +115 -0
- package/dist/actions/media/aspectSync/syncAspects.js.map +1 -0
- package/dist/actions/media/cliConfigFields.js +192 -0
- package/dist/actions/media/cliConfigFields.js.map +1 -0
- package/dist/actions/media/pullAspects.js +225 -0
- package/dist/actions/media/pullAspects.js.map +1 -0
- package/dist/actions/media/resolveMediaLibraryId.js +83 -0
- package/dist/actions/media/resolveMediaLibraryId.js.map +1 -0
- package/dist/actions/schema/deploySchemas.js.map +1 -1
- package/dist/actions/schema/extractSchemaWatcher.js +69 -14
- package/dist/actions/schema/extractSchemaWatcher.js.map +1 -1
- package/dist/actions/schema/updateWorkspaceSchema.js.map +1 -1
- package/dist/commands/context/build.js +2 -2
- package/dist/commands/context/build.js.map +1 -1
- package/dist/commands/context/entries/get.js +83 -0
- package/dist/commands/context/entries/get.js.map +1 -0
- package/dist/commands/context/entries/list.js +82 -0
- package/dist/commands/context/entries/list.js.map +1 -0
- package/dist/commands/context/entries/rebuild.js +95 -0
- package/dist/commands/context/entries/rebuild.js.map +1 -0
- package/dist/commands/context/issues/apply.js +86 -0
- package/dist/commands/context/issues/apply.js.map +1 -0
- package/dist/commands/context/issues/dismiss.js +52 -0
- package/dist/commands/context/issues/dismiss.js.map +1 -0
- package/dist/commands/context/issues/get.js +105 -0
- package/dist/commands/context/issues/get.js.map +1 -0
- package/dist/commands/context/issues/list.js +128 -0
- package/dist/commands/context/issues/list.js.map +1 -0
- package/dist/commands/context/issues/reopen.js +53 -0
- package/dist/commands/context/issues/reopen.js.map +1 -0
- package/dist/commands/context/issues/resolve.js +137 -0
- package/dist/commands/context/issues/resolve.js.map +1 -0
- package/dist/commands/context/jobs/get.js +2 -2
- package/dist/commands/context/jobs/get.js.map +1 -1
- package/dist/commands/context/query.js +92 -0
- package/dist/commands/context/query.js.map +1 -0
- package/dist/commands/deploy.js +13 -0
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/media/prune-aspect-data.js +170 -0
- package/dist/commands/media/prune-aspect-data.js.map +1 -0
- package/dist/commands/media/pull-aspects.js +309 -0
- package/dist/commands/media/pull-aspects.js.map +1 -0
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js +17 -0
- package/dist/exports/invokeSanityCli/commandPolicies/mcpPolicy.js.map +1 -1
- package/dist/services/context.js +96 -3
- package/dist/services/context.js.map +1 -1
- package/dist/services/mediaLibraries.js +175 -8
- package/dist/services/mediaLibraries.js.map +1 -1
- package/oclif.manifest.json +1050 -361
- package/package.json +6 -5
- package/templates/media-library-config/README.md +52 -0
- package/templates/media-library-config/eslint.config.txt +52 -0
- package/templates/media-library-config/src/fields/caption.tsx +29 -0
- package/templates/media-library-config/src/fields/notes.tsx +54 -0
|
@@ -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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/schema/deploySchemas.ts"],"sourcesContent":["import {SchemaExtractionError} from '@sanity/cli-build/_internal/extract'\nimport {type ManifestSchemaType} from '@sanity/cli-build/_internal/manifest'\nimport {studioWorkerTask} from '@sanity/cli-core'\nimport {type SchemaValidationProblemGroup} from '@sanity/types'\nimport {type Workspace} from 'sanity'\n\nimport {type ExtractWorkspaceWorkerData} from './types.js'\nimport {updateWorkspacesSchemas
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/schema/deploySchemas.ts"],"sourcesContent":["import {SchemaExtractionError} from '@sanity/cli-build/_internal/extract'\nimport {\n type ManifestSchemaType,\n type WorkspaceSchemaInput,\n} from '@sanity/cli-build/_internal/manifest'\nimport {studioWorkerTask} from '@sanity/cli-core'\nimport {type SchemaValidationProblemGroup} from '@sanity/types'\nimport {type Workspace} from 'sanity'\n\nimport {type ExtractWorkspaceWorkerData} from './types.js'\nimport {updateWorkspacesSchemas} from './updateWorkspaceSchema.js'\n\ninterface DeploySchemasOptions {\n verbose: boolean\n workDir: string\n\n tag?: string\n workspaceName?: string\n}\n\ntype WorkspaceWithManifest = Workspace & {manifestSchema: ManifestSchemaType[]}\n\ntype ExtractWorkspaceWorkerMessage =\n | {\n error: string\n type: 'error'\n validation?: SchemaValidationProblemGroup[]\n }\n | {\n type: 'success'\n workspaces: WorkspaceWithManifest[]\n }\n\nexport async function deploySchemas(options: DeploySchemasOptions): Promise<void> {\n const {tag, verbose, workDir, workspaceName} = options\n\n const result = await studioWorkerTask<ExtractWorkspaceWorkerMessage>(\n new URL('extractSanityWorkspace.worker.js', import.meta.url),\n {\n name: 'extractSanityWorkspace',\n studioRootPath: workDir,\n workerData: {\n configPath: workDir,\n workDir,\n } satisfies ExtractWorkspaceWorkerData,\n },\n )\n\n if (result.type === 'error') {\n throw new SchemaExtractionError(result.error, result.validation)\n }\n\n const workspaces: WorkspaceSchemaInput[] = result.workspaces\n .filter((workspace) => !workspaceName || workspace.name === workspaceName)\n .map((workspace) => ({\n dataset: workspace.dataset,\n manifestSchema: workspace.manifestSchema,\n name: workspace.name,\n projectId: workspace.projectId,\n title: workspace.title,\n }))\n\n if (workspaces.length === 0) {\n const error = workspaceName\n ? new Error(`Found no workspaces named \"${workspaceName}\"`)\n : new Error('No workspaces found')\n throw error\n }\n\n await updateWorkspacesSchemas({\n tag,\n verbose,\n workspaces,\n })\n}\n"],"names":["SchemaExtractionError","studioWorkerTask","updateWorkspacesSchemas","deploySchemas","options","tag","verbose","workDir","workspaceName","result","URL","url","name","studioRootPath","workerData","configPath","type","error","validation","workspaces","filter","workspace","map","dataset","manifestSchema","projectId","title","length","Error"],"mappings":"AAAA,SAAQA,qBAAqB,QAAO,sCAAqC;AAKzE,SAAQC,gBAAgB,QAAO,mBAAkB;AAKjD,SAAQC,uBAAuB,QAAO,6BAA4B;AAuBlE,OAAO,eAAeC,cAAcC,OAA6B;IAC/D,MAAM,EAACC,GAAG,EAAEC,OAAO,EAAEC,OAAO,EAAEC,aAAa,EAAC,GAAGJ;IAE/C,MAAMK,SAAS,MAAMR,iBACnB,IAAIS,IAAI,oCAAoC,YAAYC,GAAG,GAC3D;QACEC,MAAM;QACNC,gBAAgBN;QAChBO,YAAY;YACVC,YAAYR;YACZA;QACF;IACF;IAGF,IAAIE,OAAOO,IAAI,KAAK,SAAS;QAC3B,MAAM,IAAIhB,sBAAsBS,OAAOQ,KAAK,EAAER,OAAOS,UAAU;IACjE;IAEA,MAAMC,aAAqCV,OAAOU,UAAU,CACzDC,MAAM,CAAC,CAACC,YAAc,CAACb,iBAAiBa,UAAUT,IAAI,KAAKJ,eAC3Dc,GAAG,CAAC,CAACD,YAAe,CAAA;YACnBE,SAASF,UAAUE,OAAO;YAC1BC,gBAAgBH,UAAUG,cAAc;YACxCZ,MAAMS,UAAUT,IAAI;YACpBa,WAAWJ,UAAUI,SAAS;YAC9BC,OAAOL,UAAUK,KAAK;QACxB,CAAA;IAEF,IAAIP,WAAWQ,MAAM,KAAK,GAAG;QAC3B,MAAMV,QAAQT,gBACV,IAAIoB,MAAM,CAAC,2BAA2B,EAAEpB,cAAc,CAAC,CAAC,IACxD,IAAIoB,MAAM;QACd,MAAMX;IACR;IAEA,MAAMf,wBAAwB;QAC5BG;QACAC;QACAa;IACF;AACF"}
|
|
@@ -1,21 +1,64 @@
|
|
|
1
|
-
import { dirname, isAbsolute,
|
|
1
|
+
import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
2
2
|
import { createSchemaPatternMatcher, formatSchemaValidation, runSchemaExtraction, SchemaExtractionError } from '@sanity/cli-build/_internal/extract';
|
|
3
3
|
import { exitCodes } from '@sanity/cli-core';
|
|
4
4
|
import { spinner } from '@sanity/cli-core/ux';
|
|
5
5
|
import { watch as chokidarWatch } from 'chokidar';
|
|
6
6
|
import debounce from 'lodash-es/debounce.js';
|
|
7
|
+
import picomatch from 'picomatch';
|
|
7
8
|
import { schemasExtractDebug } from './utils/debug.js';
|
|
8
9
|
/** Default glob patterns to watch for schema changes */ export const DEFAULT_WATCH_PATTERNS = [
|
|
9
10
|
'sanity.config.{js,jsx,ts,tsx,mjs}',
|
|
10
11
|
'schema*/**/*.{js,jsx,ts,tsx,mjs}'
|
|
11
12
|
];
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Directory names never descended into, unless a watch pattern names one directly. chokidar 4 and
|
|
15
|
+
* later reject glob `ignored` patterns, so this matches on the directory name alone.
|
|
16
|
+
*/ const IGNORED_DIRECTORIES = new Set([
|
|
17
|
+
'.astro',
|
|
18
|
+
'.cache',
|
|
19
|
+
'.git',
|
|
20
|
+
'.next',
|
|
21
|
+
'.nuxt',
|
|
22
|
+
'.output',
|
|
23
|
+
'.sanity',
|
|
24
|
+
'.svelte-kit',
|
|
25
|
+
'.turbo',
|
|
26
|
+
'.vercel',
|
|
27
|
+
'build',
|
|
28
|
+
'coverage',
|
|
29
|
+
'dist',
|
|
30
|
+
'lib',
|
|
31
|
+
'node_modules',
|
|
32
|
+
'out'
|
|
33
|
+
]);
|
|
34
|
+
/**
|
|
35
|
+
* Returns `pattern` relative to `workDir` with forward slashes, so one string can feed both the
|
|
36
|
+
* base scan and the matcher. A pattern with no glob characters names a file or a directory, and a
|
|
37
|
+
* directory matches everything inside it.
|
|
38
|
+
*/ function normalizeWatchPattern(pattern, workDir) {
|
|
39
|
+
const normalized = relative(workDir, resolve(workDir, pattern)).replaceAll('\\', '/');
|
|
40
|
+
if (!normalized) return [
|
|
41
|
+
'**'
|
|
42
|
+
];
|
|
43
|
+
return picomatch.scan(normalized).isGlob ? [
|
|
44
|
+
normalized
|
|
45
|
+
] : [
|
|
46
|
+
normalized,
|
|
47
|
+
`${normalized}/**`
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns the directories to hand chokidar for `patterns`, deduplicated.
|
|
52
|
+
*
|
|
53
|
+
* chokidar 4 and later do not expand globs, so a pattern cannot be watched directly: it has to
|
|
54
|
+
* be reduced to its static base directory, with `isMatch` filtering the events that base emits.
|
|
55
|
+
* Both default patterns begin with a glob segment, so both reduce to `workDir` itself.
|
|
56
|
+
*/ function watchRootsFor(patterns, workDir) {
|
|
57
|
+
const bases = patterns.map((pattern)=>resolve(workDir, picomatch.scan(pattern).base));
|
|
58
|
+
return [
|
|
59
|
+
...new Set(bases)
|
|
60
|
+
];
|
|
61
|
+
}
|
|
19
62
|
/**
|
|
20
63
|
* Creates an extraction runner with concurrency control.
|
|
21
64
|
* If extraction is already running, queues one more extraction to run after completion.
|
|
@@ -90,7 +133,6 @@ import { schemasExtractDebug } from './utils/debug.js';
|
|
|
90
133
|
};
|
|
91
134
|
// Run initial extraction
|
|
92
135
|
await runExtraction();
|
|
93
|
-
const absoluteWatchPatterns = watchPatterns.map((pattern)=>join(workDir, pattern));
|
|
94
136
|
// Create extraction runner with concurrency control
|
|
95
137
|
const { runExtraction: runConcurrentExtraction } = createExtractionRunner(async ()=>{
|
|
96
138
|
await runExtraction();
|
|
@@ -99,14 +141,23 @@ import { schemasExtractDebug } from './utils/debug.js';
|
|
|
99
141
|
const debouncedExtract = debounce(()=>{
|
|
100
142
|
void runConcurrentExtraction();
|
|
101
143
|
}, 1000);
|
|
102
|
-
const
|
|
103
|
-
const
|
|
144
|
+
const normalizedPatterns = watchPatterns.flatMap((pattern)=>normalizeWatchPattern(pattern, workDir));
|
|
145
|
+
const { isMatch } = createSchemaPatternMatcher(normalizedPatterns);
|
|
146
|
+
const watchRoots = watchRootsFor(normalizedPatterns, workDir);
|
|
147
|
+
// A pattern can name a directory this would otherwise skip, such as the documented
|
|
148
|
+
// `--watch-patterns "lib/**/*.ts"`. chokidar asks about each root before descending, so
|
|
149
|
+
// skipping one there would leave it unwatched and silently match nothing.
|
|
150
|
+
const watchRootSet = new Set(watchRoots);
|
|
151
|
+
// Every extraction rewrites this file. Watching it would make each run trigger the next.
|
|
152
|
+
const outputFile = resolve(workDir, outputPath).toLowerCase();
|
|
153
|
+
const isOutputFile = (path)=>resolve(workDir, path).toLowerCase() === outputFile;
|
|
154
|
+
const watcher = chokidarWatch(watchRoots, {
|
|
104
155
|
cwd: workDir,
|
|
105
|
-
ignored:
|
|
156
|
+
ignored: (path)=>isOutputFile(path) || !watchRootSet.has(resolve(workDir, path)) && IGNORED_DIRECTORIES.has(basename(path)),
|
|
106
157
|
ignoreInitial: true
|
|
107
158
|
});
|
|
108
159
|
watcher.on('all', (event, filePath)=>{
|
|
109
|
-
if (!isMatch(filePath, workDir)) {
|
|
160
|
+
if (isOutputFile(filePath) || !isMatch(filePath, workDir)) {
|
|
110
161
|
return;
|
|
111
162
|
}
|
|
112
163
|
const timestamp = new Date().toLocaleTimeString();
|
|
@@ -115,7 +166,11 @@ import { schemasExtractDebug } from './utils/debug.js';
|
|
|
115
166
|
debouncedExtract();
|
|
116
167
|
});
|
|
117
168
|
watcher.on('error', (err)=>{
|
|
118
|
-
output.error
|
|
169
|
+
// `output.error` throws unless told not to exit. A watch session should survive a transient
|
|
170
|
+
// fs error such as EMFILE or an inotify limit, the way the typegen watcher does.
|
|
171
|
+
output.error(`Watcher error: ${err instanceof Error ? err.message : String(err)}`, {
|
|
172
|
+
exit: false
|
|
173
|
+
});
|
|
119
174
|
});
|
|
120
175
|
return {
|
|
121
176
|
close: ()=>watcher.close(),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/schema/extractSchemaWatcher.ts"],"sourcesContent":["import {dirname, isAbsolute, join, relative} from 'node:path'\n\nimport {\n createSchemaPatternMatcher,\n type ExtractOptions,\n formatSchemaValidation,\n runSchemaExtraction,\n SchemaExtractionError,\n} from '@sanity/cli-build/_internal/extract'\nimport {exitCodes, type Output} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {watch as chokidarWatch, type FSWatcher} from 'chokidar'\nimport debounce from 'lodash-es/debounce.js'\n\nimport {schemasExtractDebug} from './utils/debug.js'\n\n/** Default glob patterns to watch for schema changes */\nexport const DEFAULT_WATCH_PATTERNS = [\n 'sanity.config.{js,jsx,ts,tsx,mjs}',\n 'schema*/**/*.{js,jsx,ts,tsx,mjs}',\n]\n\n/** Default patterns to ignore when watching */\nconst IGNORED_PATTERNS = [\n '**/node_modules/**',\n '**/.git/**',\n '**/dist/**',\n '**/lib/**',\n '**/.sanity/**',\n]\n\ninterface ExtractSchemaWatcherOptions {\n extractOptions: ExtractOptions\n output: Output\n watchPatterns: string[]\n\n onExtraction?: (result: {duration: number; success: boolean}) => void\n}\n\ninterface ExtractSchemaWatcher {\n close: () => Promise<void>\n watcher: FSWatcher\n}\n\n/** State for tracking extraction status */\ninterface WatchState {\n isExtracting: boolean\n pendingExtraction: boolean\n}\n\n/** Return type for createExtractionRunner */\ninterface ExtractionRunner {\n runExtraction: () => Promise<void>\n state: WatchState\n}\n\n/**\n * Creates an extraction runner with concurrency control.\n * If extraction is already running, queues one more extraction to run after completion.\n * Multiple queued requests are coalesced into a single pending extraction.\n */\nfunction createExtractionRunner(onExtract: () => Promise<void>): ExtractionRunner {\n const state: WatchState = {\n isExtracting: false,\n pendingExtraction: false,\n }\n\n async function runExtraction(): Promise<void> {\n if (state.isExtracting) {\n state.pendingExtraction = true\n return\n }\n\n state.isExtracting = true\n state.pendingExtraction = false\n\n try {\n await onExtract()\n } finally {\n state.isExtracting = false\n\n // If a change came in during extraction, run again\n if (state.pendingExtraction) {\n state.pendingExtraction = false\n await runExtraction()\n }\n }\n }\n\n return {runExtraction, state}\n}\n\n/**\n * Starts a schema watcher that extracts schema on file changes.\n * Returns a watcher instance and a stop function.\n */\nexport async function startExtractSchemaWatcher(\n options: ExtractSchemaWatcherOptions,\n): Promise<ExtractSchemaWatcher> {\n const {extractOptions, onExtraction, output, watchPatterns} = options\n\n const {configPath, enforceRequiredFields, outputPath} = extractOptions\n const workDir = dirname(configPath)\n\n // Helper function to run extraction with spinner and error handling\n const runExtraction = async (): Promise<boolean> => {\n const spin = spinner(\n enforceRequiredFields\n ? 'Extracting schema with enforced required fields'\n : 'Extracting schema...',\n ).start()\n const extractionStartTime = Date.now()\n\n try {\n await runSchemaExtraction(extractOptions)\n\n spin.succeed(\n enforceRequiredFields\n ? `Extracted schema to ${outputPath} with enforced required fields`\n : `Extracted schema to ${outputPath}`,\n )\n\n const duration = Date.now() - extractionStartTime\n onExtraction?.({duration, success: true})\n\n return true\n } catch (err) {\n const duration = Date.now() - extractionStartTime\n onExtraction?.({duration, success: false})\n\n schemasExtractDebug('Failed to extract schema', err)\n spin.fail('Extraction failed')\n\n // Display validation errors if available\n if (err instanceof SchemaExtractionError && err.validation && err.validation.length > 0) {\n output.log('')\n output.log(formatSchemaValidation(err.validation))\n } else if (err instanceof Error) {\n output.error(err.message, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n return false\n }\n }\n\n // Run initial extraction\n await runExtraction()\n\n const absoluteWatchPatterns = watchPatterns.map((pattern) => join(workDir, pattern))\n\n // Create extraction runner with concurrency control\n const {runExtraction: runConcurrentExtraction} = createExtractionRunner(async () => {\n await runExtraction()\n })\n\n // Debounced extraction trigger (1 second delay)\n const debouncedExtract = debounce(() => {\n void runConcurrentExtraction()\n }, 1000)\n\n const {isMatch} = createSchemaPatternMatcher(watchPatterns)\n\n const watcher: FSWatcher = chokidarWatch(absoluteWatchPatterns, {\n cwd: workDir,\n ignored: IGNORED_PATTERNS,\n ignoreInitial: true,\n })\n\n watcher.on('all', (event, filePath) => {\n if (!isMatch(filePath, workDir)) {\n return\n }\n\n const timestamp = new Date().toLocaleTimeString()\n const relativePath = isAbsolute(filePath) ? relative(workDir, filePath) : filePath\n output.log(`[${timestamp}] ${event}: ${relativePath}`)\n debouncedExtract()\n })\n\n watcher.on('error', (err) => {\n output.error(`Watcher error: ${err instanceof Error ? err.message : String(err)}`)\n })\n\n return {\n close: () => watcher.close(),\n watcher,\n }\n}\n"],"names":["dirname","isAbsolute","join","relative","createSchemaPatternMatcher","formatSchemaValidation","runSchemaExtraction","SchemaExtractionError","exitCodes","spinner","watch","chokidarWatch","debounce","schemasExtractDebug","DEFAULT_WATCH_PATTERNS","IGNORED_PATTERNS","createExtractionRunner","onExtract","state","isExtracting","pendingExtraction","runExtraction","startExtractSchemaWatcher","options","extractOptions","onExtraction","output","watchPatterns","configPath","enforceRequiredFields","outputPath","workDir","spin","start","extractionStartTime","Date","now","succeed","duration","success","err","fail","validation","length","log","Error","error","message","exit","RUNTIME_ERROR","absoluteWatchPatterns","map","pattern","runConcurrentExtraction","debouncedExtract","isMatch","watcher","cwd","ignored","ignoreInitial","on","event","filePath","timestamp","toLocaleTimeString","relativePath","String","close"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/schema/extractSchemaWatcher.ts"],"sourcesContent":["import {basename, dirname, isAbsolute, relative, resolve} from 'node:path'\n\nimport {\n createSchemaPatternMatcher,\n type ExtractOptions,\n formatSchemaValidation,\n runSchemaExtraction,\n SchemaExtractionError,\n} from '@sanity/cli-build/_internal/extract'\nimport {exitCodes, type Output} from '@sanity/cli-core'\nimport {spinner} from '@sanity/cli-core/ux'\nimport {watch as chokidarWatch, type FSWatcher} from 'chokidar'\nimport debounce from 'lodash-es/debounce.js'\nimport picomatch from 'picomatch'\n\nimport {schemasExtractDebug} from './utils/debug.js'\n\n/** Default glob patterns to watch for schema changes */\nexport const DEFAULT_WATCH_PATTERNS = [\n 'sanity.config.{js,jsx,ts,tsx,mjs}',\n 'schema*/**/*.{js,jsx,ts,tsx,mjs}',\n]\n\n/**\n * Directory names never descended into, unless a watch pattern names one directly. chokidar 4 and\n * later reject glob `ignored` patterns, so this matches on the directory name alone.\n */\nconst IGNORED_DIRECTORIES = new Set([\n '.astro',\n '.cache',\n '.git',\n '.next',\n '.nuxt',\n '.output',\n '.sanity',\n '.svelte-kit',\n '.turbo',\n '.vercel',\n 'build',\n 'coverage',\n 'dist',\n 'lib',\n 'node_modules',\n 'out',\n])\n\n/**\n * Returns `pattern` relative to `workDir` with forward slashes, so one string can feed both the\n * base scan and the matcher. A pattern with no glob characters names a file or a directory, and a\n * directory matches everything inside it.\n */\nfunction normalizeWatchPattern(pattern: string, workDir: string): string[] {\n const normalized = relative(workDir, resolve(workDir, pattern)).replaceAll('\\\\', '/')\n if (!normalized) return ['**']\n return picomatch.scan(normalized).isGlob ? [normalized] : [normalized, `${normalized}/**`]\n}\n\n/**\n * Returns the directories to hand chokidar for `patterns`, deduplicated.\n *\n * chokidar 4 and later do not expand globs, so a pattern cannot be watched directly: it has to\n * be reduced to its static base directory, with `isMatch` filtering the events that base emits.\n * Both default patterns begin with a glob segment, so both reduce to `workDir` itself.\n */\nfunction watchRootsFor(patterns: string[], workDir: string): string[] {\n const bases = patterns.map((pattern) => resolve(workDir, picomatch.scan(pattern).base))\n return [...new Set(bases)]\n}\n\ninterface ExtractSchemaWatcherOptions {\n extractOptions: ExtractOptions\n output: Output\n watchPatterns: string[]\n\n onExtraction?: (result: {duration: number; success: boolean}) => void\n}\n\ninterface ExtractSchemaWatcher {\n close: () => Promise<void>\n watcher: FSWatcher\n}\n\n/** State for tracking extraction status */\ninterface WatchState {\n isExtracting: boolean\n pendingExtraction: boolean\n}\n\n/** Return type for createExtractionRunner */\ninterface ExtractionRunner {\n runExtraction: () => Promise<void>\n state: WatchState\n}\n\n/**\n * Creates an extraction runner with concurrency control.\n * If extraction is already running, queues one more extraction to run after completion.\n * Multiple queued requests are coalesced into a single pending extraction.\n */\nfunction createExtractionRunner(onExtract: () => Promise<void>): ExtractionRunner {\n const state: WatchState = {\n isExtracting: false,\n pendingExtraction: false,\n }\n\n async function runExtraction(): Promise<void> {\n if (state.isExtracting) {\n state.pendingExtraction = true\n return\n }\n\n state.isExtracting = true\n state.pendingExtraction = false\n\n try {\n await onExtract()\n } finally {\n state.isExtracting = false\n\n // If a change came in during extraction, run again\n if (state.pendingExtraction) {\n state.pendingExtraction = false\n await runExtraction()\n }\n }\n }\n\n return {runExtraction, state}\n}\n\n/**\n * Starts a schema watcher that extracts schema on file changes.\n * Returns a watcher instance and a stop function.\n */\nexport async function startExtractSchemaWatcher(\n options: ExtractSchemaWatcherOptions,\n): Promise<ExtractSchemaWatcher> {\n const {extractOptions, onExtraction, output, watchPatterns} = options\n\n const {configPath, enforceRequiredFields, outputPath} = extractOptions\n const workDir = dirname(configPath)\n\n // Helper function to run extraction with spinner and error handling\n const runExtraction = async (): Promise<boolean> => {\n const spin = spinner(\n enforceRequiredFields\n ? 'Extracting schema with enforced required fields'\n : 'Extracting schema...',\n ).start()\n const extractionStartTime = Date.now()\n\n try {\n await runSchemaExtraction(extractOptions)\n\n spin.succeed(\n enforceRequiredFields\n ? `Extracted schema to ${outputPath} with enforced required fields`\n : `Extracted schema to ${outputPath}`,\n )\n\n const duration = Date.now() - extractionStartTime\n onExtraction?.({duration, success: true})\n\n return true\n } catch (err) {\n const duration = Date.now() - extractionStartTime\n onExtraction?.({duration, success: false})\n\n schemasExtractDebug('Failed to extract schema', err)\n spin.fail('Extraction failed')\n\n // Display validation errors if available\n if (err instanceof SchemaExtractionError && err.validation && err.validation.length > 0) {\n output.log('')\n output.log(formatSchemaValidation(err.validation))\n } else if (err instanceof Error) {\n output.error(err.message, {exit: exitCodes.RUNTIME_ERROR})\n }\n\n return false\n }\n }\n\n // Run initial extraction\n await runExtraction()\n\n // Create extraction runner with concurrency control\n const {runExtraction: runConcurrentExtraction} = createExtractionRunner(async () => {\n await runExtraction()\n })\n\n // Debounced extraction trigger (1 second delay)\n const debouncedExtract = debounce(() => {\n void runConcurrentExtraction()\n }, 1000)\n\n const normalizedPatterns = watchPatterns.flatMap((pattern) =>\n normalizeWatchPattern(pattern, workDir),\n )\n const {isMatch} = createSchemaPatternMatcher(normalizedPatterns)\n\n const watchRoots = watchRootsFor(normalizedPatterns, workDir)\n // A pattern can name a directory this would otherwise skip, such as the documented\n // `--watch-patterns \"lib/**/*.ts\"`. chokidar asks about each root before descending, so\n // skipping one there would leave it unwatched and silently match nothing.\n const watchRootSet = new Set(watchRoots)\n // Every extraction rewrites this file. Watching it would make each run trigger the next.\n const outputFile = resolve(workDir, outputPath).toLowerCase()\n const isOutputFile = (path: string) => resolve(workDir, path).toLowerCase() === outputFile\n\n const watcher: FSWatcher = chokidarWatch(watchRoots, {\n cwd: workDir,\n ignored: (path) =>\n isOutputFile(path) ||\n (!watchRootSet.has(resolve(workDir, path)) && IGNORED_DIRECTORIES.has(basename(path))),\n ignoreInitial: true,\n })\n\n watcher.on('all', (event, filePath) => {\n if (isOutputFile(filePath) || !isMatch(filePath, workDir)) {\n return\n }\n\n const timestamp = new Date().toLocaleTimeString()\n const relativePath = isAbsolute(filePath) ? relative(workDir, filePath) : filePath\n output.log(`[${timestamp}] ${event}: ${relativePath}`)\n debouncedExtract()\n })\n\n watcher.on('error', (err) => {\n // `output.error` throws unless told not to exit. A watch session should survive a transient\n // fs error such as EMFILE or an inotify limit, the way the typegen watcher does.\n output.error(`Watcher error: ${err instanceof Error ? err.message : String(err)}`, {\n exit: false,\n })\n })\n\n return {\n close: () => watcher.close(),\n watcher,\n }\n}\n"],"names":["basename","dirname","isAbsolute","relative","resolve","createSchemaPatternMatcher","formatSchemaValidation","runSchemaExtraction","SchemaExtractionError","exitCodes","spinner","watch","chokidarWatch","debounce","picomatch","schemasExtractDebug","DEFAULT_WATCH_PATTERNS","IGNORED_DIRECTORIES","Set","normalizeWatchPattern","pattern","workDir","normalized","replaceAll","scan","isGlob","watchRootsFor","patterns","bases","map","base","createExtractionRunner","onExtract","state","isExtracting","pendingExtraction","runExtraction","startExtractSchemaWatcher","options","extractOptions","onExtraction","output","watchPatterns","configPath","enforceRequiredFields","outputPath","spin","start","extractionStartTime","Date","now","succeed","duration","success","err","fail","validation","length","log","Error","error","message","exit","RUNTIME_ERROR","runConcurrentExtraction","debouncedExtract","normalizedPatterns","flatMap","isMatch","watchRoots","watchRootSet","outputFile","toLowerCase","isOutputFile","path","watcher","cwd","ignored","has","ignoreInitial","on","event","filePath","timestamp","toLocaleTimeString","relativePath","String","close"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/actions/schema/updateWorkspaceSchema.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {ux} from '@oclif/core/ux'\nimport {\n CURRENT_WORKSPACE_SCHEMA_VERSION,\n type
|
|
1
|
+
{"version":3,"sources":["../../../src/actions/schema/updateWorkspaceSchema.ts"],"sourcesContent":["import {styleText} from 'node:util'\n\nimport {ux} from '@oclif/core/ux'\nimport {\n CURRENT_WORKSPACE_SCHEMA_VERSION,\n type StoredWorkspaceSchema,\n type WorkspaceSchemaInput,\n} from '@sanity/cli-build/_internal/manifest'\nimport {spinner} from '@sanity/cli-core/ux'\nimport partition from 'lodash-es/partition.js'\n\nimport {updateSchemas} from '../../services/schemas.js'\nimport {SCHEMA_PERMISSION_HELP_TEXT} from './utils/schemaStoreValidation.js'\nimport {getWorkspaceSchemaId} from './utils/workspaceSchemaId.js'\n\n/**\n * Updates the schemas for a list of workspaces.\n */\nexport async function updateWorkspacesSchemas(args: {\n tag?: string\n verbose: boolean\n workspaces: WorkspaceSchemaInput[]\n}) {\n const {tag, verbose, workspaces} = args\n\n /* Known caveat: we _don't_ rollback failed operations or partial success */\n const results = await Promise.allSettled(\n workspaces.map(async (workspace): Promise<void> => {\n await updateWorkspaceSchema({\n tag,\n verbose,\n workspace,\n })\n }),\n )\n\n const [fulfilledUpdates, rejectedUpdates] = partition(\n results,\n (result) => result.status === 'fulfilled',\n )\n\n if (rejectedUpdates.length > 0) {\n throw new Error(\n `Failed to deploy ${rejectedUpdates.length}/${workspaces.length} schemas. Successfully deployed ${fulfilledUpdates.length}/${workspaces.length} schemas.`,\n )\n }\n\n spinner(`Deployed ${fulfilledUpdates.length}/${workspaces.length} schemas`).succeed()\n}\n\n/**\n * Updates a workspace schema in the dataset.\n */\nasync function updateWorkspaceSchema(args: {\n tag?: string\n verbose: boolean\n workspace: WorkspaceSchemaInput\n}) {\n const {tag, verbose, workspace} = args\n\n const {dataset, manifestSchema, projectId} = workspace\n\n const {idWarning, safeBaseId: id} = getWorkspaceSchemaId({\n tag,\n workspaceName: workspace.name,\n })\n\n if (idWarning) ux.warn(idWarning)\n\n try {\n await updateSchemas<Omit<StoredWorkspaceSchema, '_id' | '_type'>[]>(dataset, projectId, [\n {\n // the API will stringify the schema – we send as JSON\n schema: manifestSchema,\n tag,\n version: CURRENT_WORKSPACE_SCHEMA_VERSION,\n workspace: {\n name: workspace.name,\n title: workspace.title,\n },\n },\n ])\n\n if (verbose) {\n ux.stdout(\n styleText('gray', `↳ schemaId: ${id}, projectId: ${projectId}, dataset: ${dataset}`),\n )\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n if (\n err instanceof Error &&\n 'statusCode' in err &&\n (err as {statusCode: unknown}).statusCode === 401\n ) {\n ux.warn(\n `↳ No permissions to write schema for workspace \"${workspace.name}\" in dataset \"${workspace.dataset}\". ${\n SCHEMA_PERMISSION_HELP_TEXT\n }:\\n ${styleText('red', message)}`,\n )\n } else {\n ux.stdout(\n styleText(\n 'red',\n `↳ Error deploying schema for workspace \"${workspace.name}\":\\n ${styleText('red', message)}`,\n ),\n )\n }\n\n throw err\n }\n}\n"],"names":["styleText","ux","CURRENT_WORKSPACE_SCHEMA_VERSION","spinner","partition","updateSchemas","SCHEMA_PERMISSION_HELP_TEXT","getWorkspaceSchemaId","updateWorkspacesSchemas","args","tag","verbose","workspaces","results","Promise","allSettled","map","workspace","updateWorkspaceSchema","fulfilledUpdates","rejectedUpdates","result","status","length","Error","succeed","dataset","manifestSchema","projectId","idWarning","safeBaseId","id","workspaceName","name","warn","schema","version","title","stdout","err","message","String","statusCode"],"mappings":"AAAA,SAAQA,SAAS,QAAO,YAAW;AAEnC,SAAQC,EAAE,QAAO,iBAAgB;AACjC,SACEC,gCAAgC,QAG3B,uCAAsC;AAC7C,SAAQC,OAAO,QAAO,sBAAqB;AAC3C,OAAOC,eAAe,yBAAwB;AAE9C,SAAQC,aAAa,QAAO,4BAA2B;AACvD,SAAQC,2BAA2B,QAAO,mCAAkC;AAC5E,SAAQC,oBAAoB,QAAO,+BAA8B;AAEjE;;CAEC,GACD,OAAO,eAAeC,wBAAwBC,IAI7C;IACC,MAAM,EAACC,GAAG,EAAEC,OAAO,EAAEC,UAAU,EAAC,GAAGH;IAEnC,0EAA0E,GAC1E,MAAMI,UAAU,MAAMC,QAAQC,UAAU,CACtCH,WAAWI,GAAG,CAAC,OAAOC;QACpB,MAAMC,sBAAsB;YAC1BR;YACAC;YACAM;QACF;IACF;IAGF,MAAM,CAACE,kBAAkBC,gBAAgB,GAAGhB,UAC1CS,SACA,CAACQ,SAAWA,OAAOC,MAAM,KAAK;IAGhC,IAAIF,gBAAgBG,MAAM,GAAG,GAAG;QAC9B,MAAM,IAAIC,MACR,CAAC,iBAAiB,EAAEJ,gBAAgBG,MAAM,CAAC,CAAC,EAAEX,WAAWW,MAAM,CAAC,gCAAgC,EAAEJ,iBAAiBI,MAAM,CAAC,CAAC,EAAEX,WAAWW,MAAM,CAAC,SAAS,CAAC;IAE7J;IAEApB,QAAQ,CAAC,SAAS,EAAEgB,iBAAiBI,MAAM,CAAC,CAAC,EAAEX,WAAWW,MAAM,CAAC,QAAQ,CAAC,EAAEE,OAAO;AACrF;AAEA;;CAEC,GACD,eAAeP,sBAAsBT,IAIpC;IACC,MAAM,EAACC,GAAG,EAAEC,OAAO,EAAEM,SAAS,EAAC,GAAGR;IAElC,MAAM,EAACiB,OAAO,EAAEC,cAAc,EAAEC,SAAS,EAAC,GAAGX;IAE7C,MAAM,EAACY,SAAS,EAAEC,YAAYC,EAAE,EAAC,GAAGxB,qBAAqB;QACvDG;QACAsB,eAAef,UAAUgB,IAAI;IAC/B;IAEA,IAAIJ,WAAW5B,GAAGiC,IAAI,CAACL;IAEvB,IAAI;QACF,MAAMxB,cAA8DqB,SAASE,WAAW;YACtF;gBACE,sDAAsD;gBACtDO,QAAQR;gBACRjB;gBACA0B,SAASlC;gBACTe,WAAW;oBACTgB,MAAMhB,UAAUgB,IAAI;oBACpBI,OAAOpB,UAAUoB,KAAK;gBACxB;YACF;SACD;QAED,IAAI1B,SAAS;YACXV,GAAGqC,MAAM,CACPtC,UAAU,QAAQ,CAAC,YAAY,EAAE+B,GAAG,aAAa,EAAEH,UAAU,WAAW,EAAEF,SAAS;QAEvF;IACF,EAAE,OAAOa,KAAK;QACZ,MAAMC,UAAUD,eAAef,QAAQe,IAAIC,OAAO,GAAGC,OAAOF;QAC5D,IACEA,eAAef,SACf,gBAAgBe,OAChB,AAACA,IAA8BG,UAAU,KAAK,KAC9C;YACAzC,GAAGiC,IAAI,CACL,CAAC,gDAAgD,EAAEjB,UAAUgB,IAAI,CAAC,cAAc,EAAEhB,UAAUS,OAAO,CAAC,GAAG,EACrGpB,4BACD,KAAK,EAAEN,UAAU,OAAOwC,UAAU;QAEvC,OAAO;YACLvC,GAAGqC,MAAM,CACPtC,UACE,OACA,CAAC,wCAAwC,EAAEiB,UAAUgB,IAAI,CAAC,MAAM,EAAEjC,UAAU,OAAOwC,UAAU;QAGnG;QAEA,MAAMD;IACR;AACF"}
|
|
@@ -3,7 +3,7 @@ import { exitCodes, SanityCommand, subdebug } from '@sanity/cli-core';
|
|
|
3
3
|
import { getErrorMessage } from '@sanity/cli-core/errors';
|
|
4
4
|
import { spinner } from '@sanity/cli-core/ux';
|
|
5
5
|
import { isHttpError } from '@sanity/client';
|
|
6
|
-
import { watchJob } from '../../actions/context/watchJob.js';
|
|
6
|
+
import { formatJobFailure, watchJob } from '../../actions/context/watchJob.js';
|
|
7
7
|
import { formatKeyValue } from '../../actions/debug/output.js';
|
|
8
8
|
import { buildKnowledgeBase, cancelKnowledgeBaseBuild } from '../../services/context.js';
|
|
9
9
|
const buildContextDebug = subdebug('context:build');
|
|
@@ -85,7 +85,7 @@ export class BuildKnowledgeBaseCommand extends SanityCommand {
|
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
87
|
if (job.status !== 'succeeded') {
|
|
88
|
-
this.error(
|
|
88
|
+
this.error(formatJobFailure('Build', job), {
|
|
89
89
|
exit: exitCodes.RUNTIME_ERROR
|
|
90
90
|
});
|
|
91
91
|
}
|