@sanity/cli 8.22.1 → 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/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/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 +2930 -2241
- 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,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Media Library configuration lifecycle — pure serialization and mirror
|
|
3
|
+
* semantics, vendored from huey's `@repo/media-library-config` (the package
|
|
4
|
+
* that proved the contract). Keep the two copies behaviorally identical until
|
|
5
|
+
* huey's standalone sync bin is retired in favor of `sanity deploy`.
|
|
6
|
+
*
|
|
7
|
+
* A media library's aspect fields are declared as *field modules* in a config
|
|
8
|
+
* app's `sanity.cli.ts` (`unstable_defineMediaLibrary({fields: [...]})`).
|
|
9
|
+
* Environments that evaluate the config app live (the Workbench) get the full
|
|
10
|
+
* definitions — custom inputs, validation callbacks, initial-value resolvers.
|
|
11
|
+
* Everything else (Studio plugin mode, standalone dashboard, Gradient) reads
|
|
12
|
+
* stored `sanity.asset.aspect` documents instead.
|
|
13
|
+
*
|
|
14
|
+
* This module owns the contract between those two worlds:
|
|
15
|
+
*
|
|
16
|
+
* - **Serialization** (`serializeField`): field modules are live JS, stored
|
|
17
|
+
* aspect documents are JSON. Literal values pass through; functions and
|
|
18
|
+
* component references are stripped deterministically, with every stripped
|
|
19
|
+
* path reported so the capability gap is explicit.
|
|
20
|
+
* - **Mirror semantics** (`computeMirrorDiff`, `buildSyncMutations`): the
|
|
21
|
+
* config app is the source of truth for the aspect namespace. A sync
|
|
22
|
+
* upserts every config field; stored aspect documents without a matching
|
|
23
|
+
* config field are *stale* and are deleted to complete the mirror. Deleting
|
|
24
|
+
* an aspect document removes the definition, not asset data — values remain
|
|
25
|
+
* on assets under `aspects.<name>` and reappear if the field is restored.
|
|
26
|
+
*
|
|
27
|
+
* This module is pure (no I/O); composition with the CLI's API services lives
|
|
28
|
+
* in `./syncAspects.ts`.
|
|
29
|
+
*/ import { VALID_ASPECT_NAME } from '../aspectName.js';
|
|
30
|
+
/** The document type stored aspect definitions use. */ export const ASPECT_TYPE = 'sanity.asset.aspect';
|
|
31
|
+
/**
|
|
32
|
+
* Serialize a field module's live `defineField(...)` value into a stored
|
|
33
|
+
* aspect document.
|
|
34
|
+
*
|
|
35
|
+
* The `_id` convention is the field/aspect name, matching the legacy
|
|
36
|
+
* `sanity media deploy-aspect` command (its delete targets `id == name`).
|
|
37
|
+
*/ export function serializeField(entry, definition) {
|
|
38
|
+
if (!VALID_ASPECT_NAME.test(entry.name)) {
|
|
39
|
+
throw new Error(`"${entry.name}" is not a valid aspect name (${VALID_ASPECT_NAME}) — it becomes the document _id and the aspects.<name> key on assets`);
|
|
40
|
+
}
|
|
41
|
+
if (!definition || typeof definition !== 'object' || typeof definition.name !== 'string' || typeof definition.type !== 'string') {
|
|
42
|
+
throw new Error(`Field "${entry.name}" must be a defineField(...) value (an object with name and type)`);
|
|
43
|
+
}
|
|
44
|
+
const definitionName = definition.name;
|
|
45
|
+
// A mismatch must not serialize: `entry.name` becomes the document _id
|
|
46
|
+
// (mirror upserts/deletes) while `definition.name` becomes the
|
|
47
|
+
// aspects.<name> data key on assets — a document carrying both would have
|
|
48
|
+
// its data under one name and its lifecycle under another.
|
|
49
|
+
if (definitionName !== entry.name) {
|
|
50
|
+
throw new Error(`Field "${entry.name}": its field module defines name "${definitionName}" — the names must match`);
|
|
51
|
+
}
|
|
52
|
+
const stripped = [];
|
|
53
|
+
const serialized = serializeValue(definition, 'definition', stripped);
|
|
54
|
+
return {
|
|
55
|
+
document: {
|
|
56
|
+
_id: entry.name,
|
|
57
|
+
_type: ASPECT_TYPE,
|
|
58
|
+
definition: serialized,
|
|
59
|
+
// Strict boolean check, mirroring the read-side guard in the Media
|
|
60
|
+
// Library app: `entry` is a runtime value from a config app, and a
|
|
61
|
+
// mistyped `public: 'false'` must not serialize into the stored doc
|
|
62
|
+
// where later Boolean(...) coercion would flip the aspect to public.
|
|
63
|
+
...typeof entry.public === 'boolean' ? {
|
|
64
|
+
public: entry.public
|
|
65
|
+
} : {}
|
|
66
|
+
},
|
|
67
|
+
stripped
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Deterministic strip rule: literal values pass through; functions (initial
|
|
72
|
+
* value resolvers, validation callbacks, input components) and other
|
|
73
|
+
* non-JSON values are dropped with their path recorded. The `components` key
|
|
74
|
+
* is dropped wholesale — it is UI-only and never meaningful in a stored doc.
|
|
75
|
+
*
|
|
76
|
+
* `ancestors` holds the objects on the current recursion path: revisiting one
|
|
77
|
+
* means a cycle, which is stripped like any other non-JSON value (instead of
|
|
78
|
+
* overflowing the stack). Shared acyclic references serialize at every site.
|
|
79
|
+
*/ function serializeValue(value, atPath, stripped, ancestors = new Set()) {
|
|
80
|
+
if (value === null) return null;
|
|
81
|
+
const kind = typeof value;
|
|
82
|
+
if (kind === 'string' || kind === 'number' || kind === 'boolean') return value;
|
|
83
|
+
if (kind === 'function' || kind === 'symbol' || kind === 'bigint' || kind === 'undefined') {
|
|
84
|
+
stripped.push(atPath);
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
if (ancestors.has(value)) {
|
|
88
|
+
stripped.push(atPath);
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
if (Array.isArray(value)) {
|
|
92
|
+
ancestors.add(value);
|
|
93
|
+
const out = [];
|
|
94
|
+
for (const [index, item] of value.entries()){
|
|
95
|
+
const serialized = serializeValue(item, `${atPath}[${index}]`, stripped, ancestors);
|
|
96
|
+
if (serialized !== undefined) out.push(serialized);
|
|
97
|
+
}
|
|
98
|
+
ancestors.delete(value);
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
if (kind === 'object') {
|
|
102
|
+
// React elements / memo components etc. are tagged objects, not plain data
|
|
103
|
+
if (value.$$typeof) {
|
|
104
|
+
stripped.push(atPath);
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
// Dates, RegExps, Maps etc. would walk to `{}` (no enumerable own keys)
|
|
108
|
+
// without ever being reported — strip anything that isn't a plain object
|
|
109
|
+
// so the "every stripped path reported" contract holds.
|
|
110
|
+
const proto = Object.getPrototypeOf(value);
|
|
111
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
112
|
+
stripped.push(atPath);
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
ancestors.add(value);
|
|
116
|
+
const out = {};
|
|
117
|
+
for (const [key, entryValue] of Object.entries(value)){
|
|
118
|
+
if (key === 'components') {
|
|
119
|
+
stripped.push(`${atPath}.components`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const serialized = serializeValue(entryValue, `${atPath}.${key}`, stripped, ancestors);
|
|
123
|
+
if (serialized !== undefined) out[key] = serialized;
|
|
124
|
+
}
|
|
125
|
+
ancestors.delete(value);
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
stripped.push(atPath);
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Mirror semantics: given the documents a config declares and the ids stored
|
|
133
|
+
* in the library, compute which stored documents are stale (present in the
|
|
134
|
+
* library, absent from the config) and must be deleted for the library to
|
|
135
|
+
* mirror the config.
|
|
136
|
+
*/ export function computeMirrorDiff(configDocuments, storedIds) {
|
|
137
|
+
const configIds = new Set(configDocuments.map((doc)=>doc._id));
|
|
138
|
+
return {
|
|
139
|
+
staleIds: storedIds.filter((id)=>!configIds.has(id))
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Build the mutation transaction for one sync: upsert every config document,
|
|
144
|
+
* delete every stale stored document. The delete shape matches the CLI's
|
|
145
|
+
* `deleteAspect` (`sanity media delete-aspect`).
|
|
146
|
+
*/ export function buildSyncMutations(configDocuments, staleIds) {
|
|
147
|
+
return [
|
|
148
|
+
...configDocuments.map((document)=>({
|
|
149
|
+
createOrReplace: document
|
|
150
|
+
})),
|
|
151
|
+
...staleIds.map((id)=>({
|
|
152
|
+
delete: {
|
|
153
|
+
params: {
|
|
154
|
+
id,
|
|
155
|
+
type: ASPECT_TYPE
|
|
156
|
+
},
|
|
157
|
+
query: `*[_type == $type && _id == $id]`
|
|
158
|
+
}
|
|
159
|
+
}))
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/actions/media/aspectSync/contract.ts"],"sourcesContent":["/**\n * The Media Library configuration lifecycle — pure serialization and mirror\n * semantics, vendored from huey's `@repo/media-library-config` (the package\n * that proved the contract). Keep the two copies behaviorally identical until\n * huey's standalone sync bin is retired in favor of `sanity deploy`.\n *\n * A media library's aspect fields are declared as *field modules* in a config\n * app's `sanity.cli.ts` (`unstable_defineMediaLibrary({fields: [...]})`).\n * Environments that evaluate the config app live (the Workbench) get the full\n * definitions — custom inputs, validation callbacks, initial-value resolvers.\n * Everything else (Studio plugin mode, standalone dashboard, Gradient) reads\n * stored `sanity.asset.aspect` documents instead.\n *\n * This module owns the contract between those two worlds:\n *\n * - **Serialization** (`serializeField`): field modules are live JS, stored\n * aspect documents are JSON. Literal values pass through; functions and\n * component references are stripped deterministically, with every stripped\n * path reported so the capability gap is explicit.\n * - **Mirror semantics** (`computeMirrorDiff`, `buildSyncMutations`): the\n * config app is the source of truth for the aspect namespace. A sync\n * upserts every config field; stored aspect documents without a matching\n * config field are *stale* and are deleted to complete the mirror. Deleting\n * an aspect document removes the definition, not asset data — values remain\n * on assets under `aspects.<name>` and reappear if the field is restored.\n *\n * This module is pure (no I/O); composition with the CLI's API services lives\n * in `./syncAspects.ts`.\n */\n\nimport {VALID_ASPECT_NAME} from '../aspectName.js'\n\n/** The document type stored aspect definitions use. */\nexport const ASPECT_TYPE = 'sanity.asset.aspect'\n\n/**\n * A field entry as declared in a media-library config app's `sanity.cli.ts`.\n */\nexport interface FieldEntry {\n name: string\n src: string\n\n public?: boolean\n title?: string\n}\n\n/**\n * The `sanity.asset.aspect` *write payload* produced by a sync.\n *\n * This is deliberately not `MediaLibraryAssetAspectDocument` from\n * `@sanity/types` — that is the read-side type: it carries required\n * server-assigned `_rev`/`_createdAt`/`_updatedAt` (which a to-be-written\n * document does not have) and types `definition` as a full `FieldDefinition`,\n * which the serializer's stripped-JSON output is only guaranteed to be after\n * schema validation. Read-side documents can be passed to\n * {@link computeMirrorDiff} (it only needs ids), but upserts via\n * {@link buildSyncMutations} require this write shape — echoing read-side\n * server metadata back into a `createOrReplace` would be a bug.\n */\nexport interface AspectDocument {\n _id: string\n _type: typeof ASPECT_TYPE\n definition: Record<string, unknown>\n\n public?: boolean\n}\n\n/**\n * Result of serializing a live field definition into a stored document.\n */\nexport interface SerializedField {\n document: AspectDocument\n /** Dot-paths of values dropped because they are not JSON-serializable. */\n stripped: string[]\n}\n\n/** One mutation in a sync transaction (see {@link buildSyncMutations}). */\nexport type SyncMutation =\n | {createOrReplace: AspectDocument}\n | {delete: {params: {id: string; type: string}; query: string}}\n\n/**\n * Serialize a field module's live `defineField(...)` value into a stored\n * aspect document.\n *\n * The `_id` convention is the field/aspect name, matching the legacy\n * `sanity media deploy-aspect` command (its delete targets `id == name`).\n */\nexport function serializeField(entry: FieldEntry, definition: unknown): SerializedField {\n if (!VALID_ASPECT_NAME.test(entry.name)) {\n throw new Error(\n `\"${entry.name}\" is not a valid aspect name (${VALID_ASPECT_NAME}) — it becomes the document _id and the aspects.<name> key on assets`,\n )\n }\n if (\n !definition ||\n typeof definition !== 'object' ||\n typeof (definition as {name?: unknown}).name !== 'string' ||\n typeof (definition as {type?: unknown}).type !== 'string'\n ) {\n throw new Error(\n `Field \"${entry.name}\" must be a defineField(...) value (an object with name and type)`,\n )\n }\n\n const definitionName = (definition as {name: string}).name\n // A mismatch must not serialize: `entry.name` becomes the document _id\n // (mirror upserts/deletes) while `definition.name` becomes the\n // aspects.<name> data key on assets — a document carrying both would have\n // its data under one name and its lifecycle under another.\n if (definitionName !== entry.name) {\n throw new Error(\n `Field \"${entry.name}\": its field module defines name \"${definitionName}\" — the names must match`,\n )\n }\n\n const stripped: string[] = []\n const serialized = serializeValue(definition, 'definition', stripped) as Record<string, unknown>\n\n return {\n document: {\n _id: entry.name,\n _type: ASPECT_TYPE,\n definition: serialized,\n // Strict boolean check, mirroring the read-side guard in the Media\n // Library app: `entry` is a runtime value from a config app, and a\n // mistyped `public: 'false'` must not serialize into the stored doc\n // where later Boolean(...) coercion would flip the aspect to public.\n ...(typeof entry.public === 'boolean' ? {public: entry.public} : {}),\n },\n stripped,\n }\n}\n\n/**\n * Deterministic strip rule: literal values pass through; functions (initial\n * value resolvers, validation callbacks, input components) and other\n * non-JSON values are dropped with their path recorded. The `components` key\n * is dropped wholesale — it is UI-only and never meaningful in a stored doc.\n *\n * `ancestors` holds the objects on the current recursion path: revisiting one\n * means a cycle, which is stripped like any other non-JSON value (instead of\n * overflowing the stack). Shared acyclic references serialize at every site.\n */\nfunction serializeValue(\n value: unknown,\n atPath: string,\n stripped: string[],\n ancestors: Set<object> = new Set(),\n): unknown {\n if (value === null) return null\n const kind = typeof value\n if (kind === 'string' || kind === 'number' || kind === 'boolean') return value\n if (kind === 'function' || kind === 'symbol' || kind === 'bigint' || kind === 'undefined') {\n stripped.push(atPath)\n return undefined\n }\n if (ancestors.has(value as object)) {\n stripped.push(atPath)\n return undefined\n }\n if (Array.isArray(value)) {\n ancestors.add(value)\n const out: unknown[] = []\n for (const [index, item] of value.entries()) {\n const serialized = serializeValue(item, `${atPath}[${index}]`, stripped, ancestors)\n if (serialized !== undefined) out.push(serialized)\n }\n ancestors.delete(value)\n return out\n }\n if (kind === 'object') {\n // React elements / memo components etc. are tagged objects, not plain data\n if ((value as {$$typeof?: unknown}).$$typeof) {\n stripped.push(atPath)\n return undefined\n }\n // Dates, RegExps, Maps etc. would walk to `{}` (no enumerable own keys)\n // without ever being reported — strip anything that isn't a plain object\n // so the \"every stripped path reported\" contract holds.\n const proto = Object.getPrototypeOf(value) as object | null\n if (proto !== Object.prototype && proto !== null) {\n stripped.push(atPath)\n return undefined\n }\n ancestors.add(value as object)\n const out: Record<string, unknown> = {}\n for (const [key, entryValue] of Object.entries(value as Record<string, unknown>)) {\n if (key === 'components') {\n stripped.push(`${atPath}.components`)\n continue\n }\n const serialized = serializeValue(entryValue, `${atPath}.${key}`, stripped, ancestors)\n if (serialized !== undefined) out[key] = serialized\n }\n ancestors.delete(value as object)\n return out\n }\n stripped.push(atPath)\n return undefined\n}\n\n/**\n * Mirror semantics: given the documents a config declares and the ids stored\n * in the library, compute which stored documents are stale (present in the\n * library, absent from the config) and must be deleted for the library to\n * mirror the config.\n */\nexport function computeMirrorDiff(\n configDocuments: readonly {_id: string}[],\n storedIds: readonly string[],\n): {staleIds: string[]} {\n const configIds = new Set(configDocuments.map((doc) => doc._id))\n return {staleIds: storedIds.filter((id) => !configIds.has(id))}\n}\n\n/**\n * Build the mutation transaction for one sync: upsert every config document,\n * delete every stale stored document. The delete shape matches the CLI's\n * `deleteAspect` (`sanity media delete-aspect`).\n */\nexport function buildSyncMutations(\n configDocuments: readonly AspectDocument[],\n staleIds: readonly string[],\n): SyncMutation[] {\n return [\n ...configDocuments.map((document) => ({createOrReplace: document})),\n ...staleIds.map((id) => ({\n delete: {\n params: {id, type: ASPECT_TYPE},\n query: `*[_type == $type && _id == $id]`,\n },\n })),\n ]\n}\n"],"names":["VALID_ASPECT_NAME","ASPECT_TYPE","serializeField","entry","definition","test","name","Error","type","definitionName","stripped","serialized","serializeValue","document","_id","_type","public","value","atPath","ancestors","Set","kind","push","undefined","has","Array","isArray","add","out","index","item","entries","delete","$$typeof","proto","Object","getPrototypeOf","prototype","key","entryValue","computeMirrorDiff","configDocuments","storedIds","configIds","map","doc","staleIds","filter","id","buildSyncMutations","createOrReplace","params","query"],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composition of the pure aspect-sync contract (`./contract.ts`) with the
|
|
3
|
+
* CLI's Media Library API services, for `sanity deploy` of a media-library
|
|
4
|
+
* config app.
|
|
5
|
+
*
|
|
6
|
+
* Split into a read-only **plan** and a mutating **apply** so the deploy flow
|
|
7
|
+
* can report the plan — including the deletion list — in both dry-run and
|
|
8
|
+
* real mode before anything is written:
|
|
9
|
+
*
|
|
10
|
+
* - {@link planAspectSync}: evaluate + serialize + validate every config
|
|
11
|
+
* field module, resolve the target media library, and diff against the
|
|
12
|
+
* stored aspect ids. No mutations.
|
|
13
|
+
* - {@link applyAspectSync}: apply the plan's upserts and deletes as one
|
|
14
|
+
* mutation transaction.
|
|
15
|
+
*/ import path from 'node:path';
|
|
16
|
+
import { exitCodes, importModule } from '@sanity/cli-core';
|
|
17
|
+
import { validateMediaLibraryAssetAspect } from '@sanity/schema/_internal';
|
|
18
|
+
import { getStoredAspectIds, mutateAspects } from '../../../services/mediaLibraries.js';
|
|
19
|
+
import { resolveMediaLibrary } from '../resolveMediaLibraryId.js';
|
|
20
|
+
import { buildSyncMutations, computeMirrorDiff, serializeField } from './contract.js';
|
|
21
|
+
/**
|
|
22
|
+
* Build the sync plan: evaluate, serialize, and schema-validate every field
|
|
23
|
+
* module before touching the network, then resolve the target library and
|
|
24
|
+
* diff against its stored aspect ids. Read-only.
|
|
25
|
+
*/ export async function planAspectSync(options) {
|
|
26
|
+
const { fields, mediaLibraryId: explicitMediaLibraryId, organizationId, workDir } = options;
|
|
27
|
+
// Evaluate + serialize + validate every field module before touching the
|
|
28
|
+
// network, collecting all failures so one broken module doesn't hide the next.
|
|
29
|
+
const documents = [];
|
|
30
|
+
const strippedByField = {};
|
|
31
|
+
const failures = [];
|
|
32
|
+
for (const entry of fields){
|
|
33
|
+
try {
|
|
34
|
+
// jsx: fields may be .tsx modules (custom input components) — the
|
|
35
|
+
// template ships one — and jiti does not parse JSX by default.
|
|
36
|
+
const definition = await importModule(path.resolve(workDir, entry.src), {
|
|
37
|
+
jsx: true
|
|
38
|
+
});
|
|
39
|
+
const { document, stripped } = serializeField(entry, definition);
|
|
40
|
+
const [valid, validationErrors] = validateMediaLibraryAssetAspect(document.definition);
|
|
41
|
+
if (!valid) {
|
|
42
|
+
const messages = validationErrors.flat().map((problem)=>problem.message);
|
|
43
|
+
failures.push(`${entry.name}: schema validation failed: ${messages.join('; ')}`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
documents.push(document);
|
|
47
|
+
if (stripped.length > 0) strippedByField[entry.name] = stripped;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
failures.push(`${entry.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (failures.length > 0) {
|
|
53
|
+
return {
|
|
54
|
+
failure: {
|
|
55
|
+
exitCode: exitCodes.USAGE_ERROR,
|
|
56
|
+
message: `${failures.length === 1 ? 'A config field' : `${failures.length} config fields`} cannot sync to stored aspect documents:\n${failures.map((failure)=>` - ${failure}`).join('\n')}`,
|
|
57
|
+
solution: 'Fix the field modules listed above, then deploy again'
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const library = await resolveTargetMediaLibrary(organizationId, explicitMediaLibraryId);
|
|
62
|
+
if ('failure' in library) return library;
|
|
63
|
+
const storedIds = await getStoredAspectIds(library.mediaLibraryId);
|
|
64
|
+
const { staleIds } = computeMirrorDiff(documents, storedIds);
|
|
65
|
+
return {
|
|
66
|
+
plan: {
|
|
67
|
+
documents,
|
|
68
|
+
mediaLibraryId: library.mediaLibraryId,
|
|
69
|
+
staleIds,
|
|
70
|
+
strippedByField
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Apply the plan as one mutation transaction: upsert every config document,
|
|
76
|
+
* delete every stale stored document. Definitions only — asset data under
|
|
77
|
+
* `aspects.<name>` is never touched.
|
|
78
|
+
*/ export async function applyAspectSync(plan) {
|
|
79
|
+
await mutateAspects({
|
|
80
|
+
mediaLibraryId: plan.mediaLibraryId,
|
|
81
|
+
mutations: buildSyncMutations(plan.documents, plan.staleIds)
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Shares the one resolution rule (`resolveMediaLibrary`) with the media
|
|
86
|
+
* commands, mapped to deploy-check failures. Ambiguity always fails here —
|
|
87
|
+
* deploy can run unattended (CI), so it never prompts, and a sync that
|
|
88
|
+
* guesses the target would mirror the config into the wrong library. Misuse
|
|
89
|
+
* (a bad flag value, a missing required flag) carries the usage exit code;
|
|
90
|
+
* a missing library is an environment problem and keeps the default.
|
|
91
|
+
*/ async function resolveTargetMediaLibrary(organizationId, explicitMediaLibraryId) {
|
|
92
|
+
const resolution = await resolveMediaLibrary({
|
|
93
|
+
mediaLibraryIdFlag: explicitMediaLibraryId,
|
|
94
|
+
organizationId
|
|
95
|
+
});
|
|
96
|
+
if (resolution.type === 'resolved') {
|
|
97
|
+
return {
|
|
98
|
+
mediaLibraryId: resolution.mediaLibraryId
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Deploy-specific wording over the shared resolution's generic remedies:
|
|
102
|
+
// the install matters *before deploying the config*, and deploy never
|
|
103
|
+
// prompts, so the ambiguity message says the sync needs an explicit target.
|
|
104
|
+
return {
|
|
105
|
+
failure: {
|
|
106
|
+
...resolution.type === 'none-active' ? {} : {
|
|
107
|
+
exitCode: exitCodes.USAGE_ERROR
|
|
108
|
+
},
|
|
109
|
+
message: resolution.type === 'ambiguous' ? `${resolution.message} — the sync needs an explicit target` : resolution.message,
|
|
110
|
+
solution: resolution.type === 'none-active' ? 'Install the Media Library for the organization before deploying its config' : resolution.solution
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//# sourceMappingURL=syncAspects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/actions/media/aspectSync/syncAspects.ts"],"sourcesContent":["/**\n * Composition of the pure aspect-sync contract (`./contract.ts`) with the\n * CLI's Media Library API services, for `sanity deploy` of a media-library\n * config app.\n *\n * Split into a read-only **plan** and a mutating **apply** so the deploy flow\n * can report the plan — including the deletion list — in both dry-run and\n * real mode before anything is written:\n *\n * - {@link planAspectSync}: evaluate + serialize + validate every config\n * field module, resolve the target media library, and diff against the\n * stored aspect ids. No mutations.\n * - {@link applyAspectSync}: apply the plan's upserts and deletes as one\n * mutation transaction.\n */\n\nimport path from 'node:path'\n\nimport {exitCodes, importModule} from '@sanity/cli-core'\nimport {validateMediaLibraryAssetAspect} from '@sanity/schema/_internal'\n\nimport {getStoredAspectIds, mutateAspects} from '../../../services/mediaLibraries.js'\nimport {resolveMediaLibrary} from '../resolveMediaLibraryId.js'\nimport {\n type AspectDocument,\n buildSyncMutations,\n computeMirrorDiff,\n type FieldEntry,\n serializeField,\n} from './contract.js'\n\n/** The read-only sync plan `sanity deploy` reports before mutating. */\nexport interface AspectSyncPlan {\n /** The documents the sync upserts, one per config field. */\n documents: AspectDocument[]\n mediaLibraryId: string\n /** Stored aspect ids with no matching config field — deleted by the sync. */\n staleIds: string[]\n /** Per-field dot-paths stripped during serialization, for fields that had any. */\n strippedByField: Record<string, string[]>\n}\n\ninterface AspectSyncFailure {\n message: string\n\n /** Exit code a real deploy uses for this failure; defaults to 1. */\n exitCode?: number\n solution?: string\n}\n\nexport type AspectSyncPlanResult = {failure: AspectSyncFailure} | {plan: AspectSyncPlan}\n\n/**\n * Build the sync plan: evaluate, serialize, and schema-validate every field\n * module before touching the network, then resolve the target library and\n * diff against its stored aspect ids. Read-only.\n */\nexport async function planAspectSync(options: {\n fields: readonly FieldEntry[]\n mediaLibraryId?: string\n organizationId: string\n workDir: string\n}): Promise<AspectSyncPlanResult> {\n const {fields, mediaLibraryId: explicitMediaLibraryId, organizationId, workDir} = options\n\n // Evaluate + serialize + validate every field module before touching the\n // network, collecting all failures so one broken module doesn't hide the next.\n const documents: AspectDocument[] = []\n const strippedByField: Record<string, string[]> = {}\n const failures: string[] = []\n\n for (const entry of fields) {\n try {\n // jsx: fields may be .tsx modules (custom input components) — the\n // template ships one — and jiti does not parse JSX by default.\n const definition = await importModule(path.resolve(workDir, entry.src), {jsx: true})\n const {document, stripped} = serializeField(entry, definition)\n\n const [valid, validationErrors] = validateMediaLibraryAssetAspect(document.definition)\n if (!valid) {\n const messages = validationErrors.flat().map((problem) => problem.message)\n failures.push(`${entry.name}: schema validation failed: ${messages.join('; ')}`)\n continue\n }\n\n documents.push(document)\n if (stripped.length > 0) strippedByField[entry.name] = stripped\n } catch (error) {\n failures.push(`${entry.name}: ${error instanceof Error ? error.message : String(error)}`)\n }\n }\n\n if (failures.length > 0) {\n return {\n failure: {\n exitCode: exitCodes.USAGE_ERROR,\n message: `${failures.length === 1 ? 'A config field' : `${failures.length} config fields`} cannot sync to stored aspect documents:\\n${failures.map((failure) => ` - ${failure}`).join('\\n')}`,\n solution: 'Fix the field modules listed above, then deploy again',\n },\n }\n }\n\n const library = await resolveTargetMediaLibrary(organizationId, explicitMediaLibraryId)\n if ('failure' in library) return library\n\n const storedIds = await getStoredAspectIds(library.mediaLibraryId)\n const {staleIds} = computeMirrorDiff(documents, storedIds)\n\n return {plan: {documents, mediaLibraryId: library.mediaLibraryId, staleIds, strippedByField}}\n}\n\n/**\n * Apply the plan as one mutation transaction: upsert every config document,\n * delete every stale stored document. Definitions only — asset data under\n * `aspects.<name>` is never touched.\n */\nexport async function applyAspectSync(plan: AspectSyncPlan): Promise<void> {\n await mutateAspects({\n mediaLibraryId: plan.mediaLibraryId,\n mutations: buildSyncMutations(plan.documents, plan.staleIds),\n })\n}\n\n/**\n * Shares the one resolution rule (`resolveMediaLibrary`) with the media\n * commands, mapped to deploy-check failures. Ambiguity always fails here —\n * deploy can run unattended (CI), so it never prompts, and a sync that\n * guesses the target would mirror the config into the wrong library. Misuse\n * (a bad flag value, a missing required flag) carries the usage exit code;\n * a missing library is an environment problem and keeps the default.\n */\nasync function resolveTargetMediaLibrary(\n organizationId: string,\n explicitMediaLibraryId: string | undefined,\n): Promise<{failure: AspectSyncFailure} | {mediaLibraryId: string}> {\n const resolution = await resolveMediaLibrary({\n mediaLibraryIdFlag: explicitMediaLibraryId,\n organizationId,\n })\n if (resolution.type === 'resolved') {\n return {mediaLibraryId: resolution.mediaLibraryId}\n }\n // Deploy-specific wording over the shared resolution's generic remedies:\n // the install matters *before deploying the config*, and deploy never\n // prompts, so the ambiguity message says the sync needs an explicit target.\n return {\n failure: {\n ...(resolution.type === 'none-active' ? {} : {exitCode: exitCodes.USAGE_ERROR}),\n message:\n resolution.type === 'ambiguous'\n ? `${resolution.message} — the sync needs an explicit target`\n : resolution.message,\n solution:\n resolution.type === 'none-active'\n ? 'Install the Media Library for the organization before deploying its config'\n : resolution.solution,\n },\n }\n}\n"],"names":["path","exitCodes","importModule","validateMediaLibraryAssetAspect","getStoredAspectIds","mutateAspects","resolveMediaLibrary","buildSyncMutations","computeMirrorDiff","serializeField","planAspectSync","options","fields","mediaLibraryId","explicitMediaLibraryId","organizationId","workDir","documents","strippedByField","failures","entry","definition","resolve","src","jsx","document","stripped","valid","validationErrors","messages","flat","map","problem","message","push","name","join","length","error","Error","String","failure","exitCode","USAGE_ERROR","solution","library","resolveTargetMediaLibrary","storedIds","staleIds","plan","applyAspectSync","mutations","resolution","mediaLibraryIdFlag","type"],"mappings":"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"}
|
|
@@ -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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"}
|