@bettercms-ai/convert 0.2.1 → 0.4.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/CHANGELOG.md +337 -0
- package/README.md +182 -22
- package/dist/cli.js +1185 -209
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +128 -5
- package/dist/index.js +1171 -208
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -134,7 +134,7 @@ function propsAttribute(props) {
|
|
|
134
134
|
|
|
135
135
|
// src/helper.ts
|
|
136
136
|
import { createHash as createHash2 } from "crypto";
|
|
137
|
-
var HELPER_VERSION =
|
|
137
|
+
var HELPER_VERSION = 4;
|
|
138
138
|
var HEADER = "// @bettercms-ai/convert helper v";
|
|
139
139
|
var HELPER_PATH = Object.fromEntries(
|
|
140
140
|
Object.keys(DIALECT_RULES).map((d) => [d, DIALECT_RULES[d].helper])
|
|
@@ -217,32 +217,97 @@ export function bcmsRows<T>(page: unknown, path: string, fallback: T[], slug?: s
|
|
|
217
217
|
/**
|
|
218
218
|
* ONE SECTION's copy, for a component extracted by \`--componentize\`.
|
|
219
219
|
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
220
|
+
* OVERRIDES FIRST, PER KEY: \`overrides[key] ?? group[key]\`. An instance that owns its copy
|
|
221
|
+
* (Phase B) carries an override for every prop it declares, so the group underneath it is never
|
|
222
|
+
* read; a placement still bound to a page field group (Phase A) carries none, so the group is all
|
|
223
|
+
* there is. The interesting case is the one in between \u2014 a section half moved \u2014 where a key the
|
|
224
|
+
* instance does not hold has to keep rendering the page's value rather than nothing.
|
|
225
|
+
*
|
|
226
|
+
* \u{1F534} \`??\`, NOT \`||\`. An empty string is a value an editor typed, and treating it as absent put
|
|
227
|
+
* the page's old copy back into a heading somebody had deliberately cleared.
|
|
228
|
+
*
|
|
229
|
+
* With neither an override nor a group, the answer is \`{}\` and every \`bcms(...)\` below renders
|
|
230
|
+
* the fallback the template shipped with \u2014 which is why a section renders correctly against the
|
|
231
|
+
* committed \`{}\` stub.
|
|
224
232
|
*/
|
|
225
233
|
export function bcmsSection(page: unknown, bind?: string, overrides?: unknown): Record<string, unknown> {
|
|
226
|
-
// \u{1F534} AN EMPTY OBJECT IS AN ANSWER. \`overrides: {}\` is an instance an editor cleared, and
|
|
227
|
-
// "\`??\` unless it happens to be empty" quietly put the page's copy back into it.
|
|
228
|
-
if (isRecord(overrides)) return overrides;
|
|
229
234
|
const group = bind ? readPath(page, bind) : undefined;
|
|
230
|
-
|
|
235
|
+
const base: Record<string, unknown> = isRecord(group) ? { ...group } : {};
|
|
236
|
+
if (!isRecord(overrides)) return base;
|
|
237
|
+
for (const key of Object.keys(overrides)) {
|
|
238
|
+
const value = overrides[key];
|
|
239
|
+
if (value === undefined || value === null) continue;
|
|
240
|
+
/**
|
|
241
|
+
* \u{1F534} A TABLE PROP IS STORED AS \`{ rows: [...] }\` AND READ AS A LIST. That is the shape the
|
|
242
|
+
* dashboard edits a repeater in; handing it to \`bcmsRows\` unchanged is not an array, so the
|
|
243
|
+
* helper fell back to the rows the template shipped with and the editor's work never showed.
|
|
244
|
+
*/
|
|
245
|
+
base[key] = isRecord(value) && Array.isArray(value.rows) ? value.rows : value;
|
|
246
|
+
}
|
|
247
|
+
return base;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* WHICH ADDRESS one bound element declares \u2014 decided at RENDER time, from the placement.
|
|
252
|
+
*
|
|
253
|
+
* An element carries exactly ONE \`data-bcms-field\`, and which lane it names depends on where its
|
|
254
|
+
* copy lives. A placement that owns its copy declares the BLOCK lane \u2014
|
|
255
|
+
* \`<blockId>__overrides.<propKey>\` \u2014 because that is the address the editor commits an instance's
|
|
256
|
+
* own value to. A placement bound to a page field group declares the PAGE path, which is what the
|
|
257
|
+
* page form and the coverage meter already know.
|
|
258
|
+
*
|
|
259
|
+
* \u{1F534} THE BOUND ADDRESS IS RE-BASED ONTO **THIS** PLACEMENT'S GROUP. The markup was cut from ONE
|
|
260
|
+
* page and carries that page's paths (\`hero.title\`); the whole point of \`shapeHash\` is that a
|
|
261
|
+
* second page renders the same component bound to a group of its own (\`bind="landingHero"\`).
|
|
262
|
+
* Handing the embedded path through unchanged declared \`hero.title\` on a page whose field is
|
|
263
|
+
* \`landingHero.title\` \u2014 an address that page does not have, so the editor wrote into nothing and
|
|
264
|
+
* the meter counted a binding that was never there. The prefix the markup was cut with comes out,
|
|
265
|
+
* the placement's own group goes in.
|
|
266
|
+
*
|
|
267
|
+
* \u{1F534} THE SAME EXTRACTED FILE SERVES BOTH LANES. The choice is a function of the props, never of
|
|
268
|
+
* how the codemod ran: one component renders a Phase A placement on one page and a Phase B
|
|
269
|
+
* instance on the next, and a repository does not have to be re-cut when its copy moves.
|
|
270
|
+
*/
|
|
271
|
+
export function bcmsFields(
|
|
272
|
+
placement: { blockId?: string; bind?: string; overrides?: unknown },
|
|
273
|
+
pathPrefix: string,
|
|
274
|
+
): (path: string) => string {
|
|
275
|
+
const id = placement.blockId;
|
|
276
|
+
const own = typeof id === "string" && id !== "" && !placement.bind && isRecord(placement.overrides);
|
|
277
|
+
/**
|
|
278
|
+
* What replaces the prefix the markup was cut with, or null for "leave every path alone".
|
|
279
|
+
*
|
|
280
|
+
* Null covers a placement with neither a bind nor an instance \u2014 a shipped fallback, or a page
|
|
281
|
+
* nobody has componentized yet \u2014 and a bound placement in a component whose prefix is EMPTY,
|
|
282
|
+
* where the plan said the prop key IS the whole page path: there is no group segment to swap,
|
|
283
|
+
* so prepending one would invent an address rather than correct it.
|
|
284
|
+
*/
|
|
285
|
+
const lane = own ? \`\${id}__overrides.\` : placement.bind && pathPrefix ? \`\${placement.bind}.\` : null;
|
|
286
|
+
return (path: string): string => {
|
|
287
|
+
// A path outside the group this section was cut from is not one this placement re-addresses:
|
|
288
|
+
// it names a key the instance does not hold and a group this placement is not bound to.
|
|
289
|
+
// (An EMPTY prefix is a plan saying the prop key IS the page path, and matches everything.)
|
|
290
|
+
if (lane === null || !path.startsWith(pathPrefix)) return path;
|
|
291
|
+
return lane + path.slice(pathPrefix.length);
|
|
292
|
+
};
|
|
231
293
|
}
|
|
232
294
|
`;
|
|
233
295
|
}
|
|
234
296
|
var HISTORICAL = {
|
|
235
297
|
"src/bcms-content.ts": {
|
|
236
298
|
1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
|
|
237
|
-
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0"
|
|
299
|
+
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0",
|
|
300
|
+
3: "d4305b2983adcb456ef782d35ea9d083a04465c2b68baa0d44a084dfa01a9227"
|
|
238
301
|
},
|
|
239
302
|
"src/lib/bcms-content.ts": {
|
|
240
303
|
1: "ae72ae14d706943d481c880d70da5789b4816ba1ba319a10fbc63ded4d0b42f2",
|
|
241
|
-
2: "a18731eb42f7db55dac0591c45f873758cde8e103fefcd6e5caf67936ae6a002"
|
|
304
|
+
2: "a18731eb42f7db55dac0591c45f873758cde8e103fefcd6e5caf67936ae6a002",
|
|
305
|
+
3: "0be9c73160361882a038552bc02673c14ed532287823dabaafe36179bc55ed7e"
|
|
242
306
|
},
|
|
243
307
|
"composables/bcms-content.ts": {
|
|
244
308
|
1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
|
|
245
|
-
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0"
|
|
309
|
+
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0",
|
|
310
|
+
3: "d4305b2983adcb456ef782d35ea9d083a04465c2b68baa0d44a084dfa01a9227"
|
|
246
311
|
}
|
|
247
312
|
};
|
|
248
313
|
var sha256 = (value) => createHash2("sha256").update(value, "utf8").digest("hex");
|
|
@@ -2628,6 +2693,71 @@ function locate(brief, sources, options) {
|
|
|
2628
2693
|
// src/loops.ts
|
|
2629
2694
|
import MagicString from "magic-string";
|
|
2630
2695
|
|
|
2696
|
+
// src/declarations.ts
|
|
2697
|
+
var BINDING_ATTR = /data-bcms-(?:layout-)?field\s*=\s*["']([^"']+)["']/g;
|
|
2698
|
+
var PROPS_ATTR = /data-bcms-props\s*=\s*["']([^"']+)["']/g;
|
|
2699
|
+
var TEMPLATE_SCAN = /:?data-bcms-(?:layout-)?field\s*=\s*(?:\{\s*`([^`]+)`\s*\}|"\s*`([^`]+)`\s*")/g;
|
|
2700
|
+
var BINDINGS_SCAN = /bcmsBindings\s*=\s*\{\{[^}]*\}\}/g;
|
|
2701
|
+
var propsDeclarations = (value, at) => value.split(";").flatMap((entry) => {
|
|
2702
|
+
const [address, kind] = entry.split("|");
|
|
2703
|
+
if (!address) return [];
|
|
2704
|
+
const scope = address.startsWith("layout:") ? "layout" : "page";
|
|
2705
|
+
return [{ scope, path: stripLayoutPrefix(address), kind: kind ?? "", at, via: "props" }];
|
|
2706
|
+
});
|
|
2707
|
+
var propsPaths = (value) => propsDeclarations(value, 0).map((d) => ({ scope: d.scope, path: d.path }));
|
|
2708
|
+
var TEMPLATE_BINDING = /^:?data-bcms-(?:layout-)?field\s*=\s*(?:\{\s*`([^`]+)`\s*\}|"\s*`([^`]+)`\s*")$/;
|
|
2709
|
+
var shapeOfPath = (path) => path.replace(/\[(?:\d+|\$\{[^}]*\})\]/g, "[*]");
|
|
2710
|
+
function declarationsIn(roots, content) {
|
|
2711
|
+
const out = [];
|
|
2712
|
+
const visit = (node) => {
|
|
2713
|
+
if (node.type === "expr") {
|
|
2714
|
+
for (const child of node.children) visit(child);
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2717
|
+
if (node.type !== "element") return;
|
|
2718
|
+
const kind = node.attrs.find((a) => a.name === "data-bcms-kind")?.value ?? "";
|
|
2719
|
+
const at = node.attrInsertAt;
|
|
2720
|
+
for (const attr of node.attrs) {
|
|
2721
|
+
const name = attr.name.replace(/^:/, "");
|
|
2722
|
+
if (name === "data-bcms-field" || name === "data-bcms-layout-field") {
|
|
2723
|
+
const scope = name === "data-bcms-layout-field" ? "layout" : "page";
|
|
2724
|
+
const template = content.slice(attr.range.start, attr.range.end).match(TEMPLATE_BINDING);
|
|
2725
|
+
const templated = template?.[1] ?? template?.[2];
|
|
2726
|
+
if (templated) out.push({ scope, path: shapeOfPath(stripLayoutPrefix(templated)), kind, at, via: "field" });
|
|
2727
|
+
else if (attr.value) out.push({ scope, path: stripLayoutPrefix(attr.value), kind, at, via: "field" });
|
|
2728
|
+
} else if (attr.name === "data-bcms-props") {
|
|
2729
|
+
out.push(...propsDeclarations(attr.value, at));
|
|
2730
|
+
} else if (attr.name === "bcmsBindings") {
|
|
2731
|
+
for (const path of bindingsPaths(content.slice(attr.range.start, attr.range.end))) {
|
|
2732
|
+
out.push({ scope: "page", path, kind: "", at, via: "prop" });
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
for (const child of node.children) visit(child);
|
|
2737
|
+
};
|
|
2738
|
+
for (const root of roots) visit(root);
|
|
2739
|
+
return out;
|
|
2740
|
+
}
|
|
2741
|
+
var bindingsPaths = (text) => [...text.matchAll(/["']([^"']+)["']/g)].map((match) => match[1]);
|
|
2742
|
+
var scopedPath = (scope, path) => `${scope}::${path}`;
|
|
2743
|
+
var covers = (declared, target) => declared !== void 0 && (declared.has(scopedPath(target.scope, target.path)) || declared.has(scopedPath(target.scope, shapeOfPath(target.path))));
|
|
2744
|
+
var declaredInTree = (roots, content) => new Set(declarationsIn(roots, content).map((d) => scopedPath(d.scope, d.path)));
|
|
2745
|
+
function declaredByScan(content) {
|
|
2746
|
+
const out = /* @__PURE__ */ new Set();
|
|
2747
|
+
const add = (scope, path) => void out.add(scopedPath(scope, path));
|
|
2748
|
+
for (const match of content.matchAll(BINDING_ATTR)) {
|
|
2749
|
+
add(match[0].includes("layout-field") ? "layout" : "page", stripLayoutPrefix(match[1]));
|
|
2750
|
+
}
|
|
2751
|
+
for (const match of content.matchAll(TEMPLATE_SCAN)) {
|
|
2752
|
+
add(match[0].includes("layout-field") ? "layout" : "page", shapeOfPath(stripLayoutPrefix(match[1] ?? match[2])));
|
|
2753
|
+
}
|
|
2754
|
+
for (const match of content.matchAll(BINDINGS_SCAN)) for (const path of bindingsPaths(match[0])) add("page", path);
|
|
2755
|
+
for (const match of content.matchAll(PROPS_ATTR))
|
|
2756
|
+
for (const entry of propsPaths(match[1])) add(entry.scope, entry.path);
|
|
2757
|
+
return out;
|
|
2758
|
+
}
|
|
2759
|
+
var stripLayoutPrefix = (value) => value.startsWith("layout:") ? value.slice("layout:".length) : value;
|
|
2760
|
+
|
|
2631
2761
|
// src/scope.ts
|
|
2632
2762
|
function patternNames(node, out) {
|
|
2633
2763
|
if (!node || typeof node !== "object") return;
|
|
@@ -2914,7 +3044,10 @@ function wordBounded(haystack, needle) {
|
|
|
2914
3044
|
}
|
|
2915
3045
|
|
|
2916
3046
|
// src/loops.ts
|
|
2917
|
-
|
|
3047
|
+
function splitNested(leaf) {
|
|
3048
|
+
const match = leaf.match(/^([^[\]]+)\[(\d+)\]\.(.+)$/);
|
|
3049
|
+
return match ? { array: match[1], index: Number(match[2]), leaf: match[3] } : null;
|
|
3050
|
+
}
|
|
2918
3051
|
var elementEnd = (node) => node.inner ? node.inner.end + node.tag.length + 3 : null;
|
|
2919
3052
|
function findRows(roots, members) {
|
|
2920
3053
|
const wanted = new Set(members.map((m) => m.index));
|
|
@@ -2986,6 +3119,69 @@ function indentOf(content, at) {
|
|
|
2986
3119
|
const prefix = content.slice(lineStart, at);
|
|
2987
3120
|
return prefix.trim() === "" ? prefix : "";
|
|
2988
3121
|
}
|
|
3122
|
+
function writeLeaf(source, shift, member, rule, read, row, literal) {
|
|
3123
|
+
const site = member.site;
|
|
3124
|
+
const expression = `${read}(${row}, ${JSON.stringify(member.leaf)}, "")`;
|
|
3125
|
+
if (member.kind === "richtext" && rule.richtext) {
|
|
3126
|
+
source.remove(site.inner.start + shift, site.inner.end + shift);
|
|
3127
|
+
source.appendLeft(site.attrInsertAt + shift, ` ${rule.richtext(expression)}`);
|
|
3128
|
+
} else {
|
|
3129
|
+
source.overwrite(site.inner.start + shift, site.inner.end + shift, rule.text(expression));
|
|
3130
|
+
}
|
|
3131
|
+
const kind = member.kind ? ` data-bcms-kind="${member.kind}"` : "";
|
|
3132
|
+
source.appendLeft(site.attrInsertAt + shift, ` ${rule.attr("data-bcms-field", `\`${literal}\``)}${kind}`);
|
|
3133
|
+
}
|
|
3134
|
+
function emitNested(rule, array, members, prefix, outer, parent, ctx) {
|
|
3135
|
+
if (!rule.loop || !rule.text) return null;
|
|
3136
|
+
const inner = [];
|
|
3137
|
+
for (const member of members) {
|
|
3138
|
+
const split = splitNested(member.leaf);
|
|
3139
|
+
if (!split || splitNested(split.leaf) !== null) return null;
|
|
3140
|
+
inner.push({ ...member, index: split.index, leaf: split.leaf });
|
|
3141
|
+
}
|
|
3142
|
+
const rows = findRows([parent.node], inner);
|
|
3143
|
+
if (!rows || rows.length === 0) return null;
|
|
3144
|
+
if (!consecutive(rows, ctx.content)) return null;
|
|
3145
|
+
if (!sameShape(rows, ctx.content)) return null;
|
|
3146
|
+
const template = rows[0];
|
|
3147
|
+
const used = /* @__PURE__ */ new Set([
|
|
3148
|
+
...ctx.content.slice(template.range.start, template.range.end).match(/[A-Za-z_$][\w$]*/g) ?? [],
|
|
3149
|
+
outer.row,
|
|
3150
|
+
outer.index
|
|
3151
|
+
]);
|
|
3152
|
+
const allocated = allocateNames(used, [
|
|
3153
|
+
{ key: "row", name: "item" },
|
|
3154
|
+
{ key: "index", name: "j" }
|
|
3155
|
+
]);
|
|
3156
|
+
const names = { row: allocated.get("row"), index: allocated.get("index") };
|
|
3157
|
+
const fallback = [];
|
|
3158
|
+
const writable = inner.every((member) => setLeaf(fallback[member.index] ??= emptyRow(), member.leaf, member.fallback));
|
|
3159
|
+
if (!writable) return null;
|
|
3160
|
+
const source = new MagicString(ctx.content.slice(template.range.start, template.range.end));
|
|
3161
|
+
const shift = -template.range.start;
|
|
3162
|
+
const declarations = [];
|
|
3163
|
+
for (const member of template.members) {
|
|
3164
|
+
const literal = `${prefix}.${array}[\${${names.index}}].${member.leaf}`;
|
|
3165
|
+
writeLeaf(source, shift, member, rule, ctx.read, names.row, literal);
|
|
3166
|
+
declarations.push({ path: shapeOfPath(literal), literal });
|
|
3167
|
+
}
|
|
3168
|
+
const rowsExpr = `${ctx.rows}(${outer.row}, ${JSON.stringify(array)}, ${JSON.stringify(fallback.filter(Boolean))})`;
|
|
3169
|
+
if (rule.loop.kind === "attr") {
|
|
3170
|
+
source.appendRight(template.attrInsertAt + shift, ` ${rule.loop.attr(rowsExpr, names.row, names.index)}`);
|
|
3171
|
+
}
|
|
3172
|
+
if (rule.loopKey) {
|
|
3173
|
+
const key = rule.loopKey(names.index);
|
|
3174
|
+
if (!template.keyAttr) source.appendRight(template.attrInsertAt + shift, ` ${key}`);
|
|
3175
|
+
else if (template.keyAttr.end > template.keyAttr.start)
|
|
3176
|
+
source.overwrite(template.keyAttr.start + shift, template.keyAttr.end + shift, key);
|
|
3177
|
+
else return null;
|
|
3178
|
+
}
|
|
3179
|
+
const indent = indentOf(ctx.content, template.range.start);
|
|
3180
|
+
const text = rule.loop.kind === "wrap" ? `${rule.loop.open(rowsExpr, names.row, names.index)}
|
|
3181
|
+
${indented(source.toString(), indent)}
|
|
3182
|
+
${indent}${rule.loop.close}` : source.toString();
|
|
3183
|
+
return { range: { start: template.range.start, end: rows[rows.length - 1].range.end }, text, declarations };
|
|
3184
|
+
}
|
|
2989
3185
|
function emitRepeater(dialect, group, arrayPath, fallbackRows, ctx) {
|
|
2990
3186
|
const rule = DIALECT_RULES[dialect];
|
|
2991
3187
|
if (!rule.loop || !rule.text || group.length === 0) return null;
|
|
@@ -3003,20 +3199,25 @@ function emitRepeater(dialect, group, arrayPath, fallbackRows, ctx) {
|
|
|
3003
3199
|
const names = { row: allocated.get("row"), index: allocated.get("index") };
|
|
3004
3200
|
const source = new MagicString(ctx.content.slice(template.range.start, template.range.end));
|
|
3005
3201
|
const shift = -template.range.start;
|
|
3006
|
-
const
|
|
3202
|
+
const declarations = [];
|
|
3203
|
+
const nested = /* @__PURE__ */ new Map();
|
|
3204
|
+
const direct = [];
|
|
3007
3205
|
for (const member of template.members) {
|
|
3008
|
-
const
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
}
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3206
|
+
const inner = splitNested(member.leaf);
|
|
3207
|
+
if (inner === null) direct.push(member);
|
|
3208
|
+
else nested.set(inner.array, [...nested.get(inner.array) ?? [], member]);
|
|
3209
|
+
}
|
|
3210
|
+
for (const [array, members] of nested) {
|
|
3211
|
+
const plan2 = emitNested(rule, array, members, `${arrayPath}[\${${names.index}}]`, names, template, ctx);
|
|
3212
|
+
if (!plan2) return null;
|
|
3213
|
+
if (direct.some((m) => m.site.range.start >= plan2.range.start && m.site.range.end <= plan2.range.end)) return null;
|
|
3214
|
+
source.overwrite(plan2.range.start + shift, plan2.range.end + shift, plan2.text);
|
|
3215
|
+
declarations.push(...plan2.declarations);
|
|
3216
|
+
}
|
|
3217
|
+
for (const member of direct) {
|
|
3218
|
+
const literal = `${arrayPath}[\${${names.index}}].${member.leaf}`;
|
|
3219
|
+
writeLeaf(source, shift, member, rule, ctx.read, names.row, literal);
|
|
3220
|
+
declarations.push({ path: shapeOfPath(literal), literal });
|
|
3020
3221
|
}
|
|
3021
3222
|
const args = [ctx.snapshot, JSON.stringify(arrayPath), JSON.stringify(fallbackRows), ...ctx.slug ? [ctx.slug] : []];
|
|
3022
3223
|
const rowsExpr = `${ctx.rows}(${args.join(", ")})`;
|
|
@@ -3037,7 +3238,8 @@ ${indent}${rule.loop.close}` : source.toString();
|
|
|
3037
3238
|
return {
|
|
3038
3239
|
range: { start: template.range.start, end: rows[rows.length - 1].range.end },
|
|
3039
3240
|
text,
|
|
3040
|
-
|
|
3241
|
+
declarations,
|
|
3242
|
+
bindings: [...new Set(declarations.map((d) => d.path))],
|
|
3041
3243
|
names
|
|
3042
3244
|
};
|
|
3043
3245
|
}
|
|
@@ -3067,9 +3269,9 @@ function setLeaf(row, leaf, value) {
|
|
|
3067
3269
|
var FORBIDDEN = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
3068
3270
|
var emptyRow = () => /* @__PURE__ */ Object.create(null);
|
|
3069
3271
|
function splitIndexed(arrayPath, path) {
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
return { index: Number(match[
|
|
3272
|
+
if (!path.startsWith(`${arrayPath}[`)) return null;
|
|
3273
|
+
const match = path.slice(arrayPath.length).match(/^\[(\d+)\]\.(.+)$/);
|
|
3274
|
+
return match ? { index: Number(match[1]), leaf: match[2] } : null;
|
|
3073
3275
|
}
|
|
3074
3276
|
|
|
3075
3277
|
// src/sites/jsx.ts
|
|
@@ -3562,22 +3764,68 @@ var firstPresent = (base, files) => [base, ...EXTENSIONS.map((ext) => `${base}${
|
|
|
3562
3764
|
function aliasesFrom(sources) {
|
|
3563
3765
|
const out = /* @__PURE__ */ new Map();
|
|
3564
3766
|
for (const source of sources) {
|
|
3565
|
-
if (!
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
}
|
|
3576
|
-
} catch {
|
|
3767
|
+
if (!isProjectConfig(source.path)) continue;
|
|
3768
|
+
const parsed = readJsonc(source.content);
|
|
3769
|
+
if (parsed === null) continue;
|
|
3770
|
+
const base = parsed.compilerOptions?.baseUrl?.replace(/^\.\/?/, "").replace(/\/$/, "") ?? "";
|
|
3771
|
+
const root = source.path.split("/").slice(0, -1).join("/");
|
|
3772
|
+
for (const [pattern, targets] of Object.entries(parsed.compilerOptions?.paths ?? {})) {
|
|
3773
|
+
out.set(
|
|
3774
|
+
pattern,
|
|
3775
|
+
targets.map((target) => [root, base, target.replace(/^\.\/?/, "")].filter(Boolean).join("/"))
|
|
3776
|
+
);
|
|
3577
3777
|
}
|
|
3578
3778
|
}
|
|
3579
3779
|
return out;
|
|
3580
3780
|
}
|
|
3781
|
+
var isProjectConfig = (path) => path.endsWith("tsconfig.json") || path.endsWith("jsconfig.json");
|
|
3782
|
+
function readJsonc(content) {
|
|
3783
|
+
let out = "";
|
|
3784
|
+
let at = 0;
|
|
3785
|
+
while (at < content.length) {
|
|
3786
|
+
const char = content[at];
|
|
3787
|
+
if (char === '"') {
|
|
3788
|
+
const start = at++;
|
|
3789
|
+
while (at < content.length && content[at] !== '"') at += content[at] === "\\" ? 2 : 1;
|
|
3790
|
+
out += content.slice(start, Math.min(at + 1, content.length));
|
|
3791
|
+
at++;
|
|
3792
|
+
continue;
|
|
3793
|
+
}
|
|
3794
|
+
if (char === "/" && content[at + 1] === "/") {
|
|
3795
|
+
while (at < content.length && content[at] !== "\n") at++;
|
|
3796
|
+
continue;
|
|
3797
|
+
}
|
|
3798
|
+
if (char === "/" && content[at + 1] === "*") {
|
|
3799
|
+
const close = content.indexOf("*/", at + 2);
|
|
3800
|
+
at = close < 0 ? content.length : close + 2;
|
|
3801
|
+
continue;
|
|
3802
|
+
}
|
|
3803
|
+
out += char;
|
|
3804
|
+
at++;
|
|
3805
|
+
}
|
|
3806
|
+
try {
|
|
3807
|
+
const parsed = JSON.parse(out.replace(/,(?=\s*[}\]])/g, ""));
|
|
3808
|
+
return isAliasShaped(parsed) ? parsed : null;
|
|
3809
|
+
} catch {
|
|
3810
|
+
return null;
|
|
3811
|
+
}
|
|
3812
|
+
}
|
|
3813
|
+
var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3814
|
+
function isAliasShaped(value) {
|
|
3815
|
+
if (!isRecord(value)) return false;
|
|
3816
|
+
const options = value.compilerOptions;
|
|
3817
|
+
if (options === void 0) return true;
|
|
3818
|
+
if (!isRecord(options)) return false;
|
|
3819
|
+
if (options.baseUrl !== void 0 && typeof options.baseUrl !== "string") return false;
|
|
3820
|
+
if (options.paths === void 0) return true;
|
|
3821
|
+
if (!isRecord(options.paths)) return false;
|
|
3822
|
+
return Object.values(options.paths).every(
|
|
3823
|
+
(targets) => Array.isArray(targets) && targets.every((target) => typeof target === "string")
|
|
3824
|
+
);
|
|
3825
|
+
}
|
|
3826
|
+
function unreadableConfigs(sources) {
|
|
3827
|
+
return sources.filter((source) => isProjectConfig(source.path) && readJsonc(source.content) === null).map((source) => source.path);
|
|
3828
|
+
}
|
|
3581
3829
|
function expand(specifier, aliases) {
|
|
3582
3830
|
const out = [];
|
|
3583
3831
|
for (const [pattern, targets] of aliases) {
|
|
@@ -3746,70 +3994,6 @@ function typeMember(content, literal) {
|
|
|
3746
3994
|
return { start: at, end: at, text: `${terminated ? "" : ";"} ${text}` };
|
|
3747
3995
|
}
|
|
3748
3996
|
|
|
3749
|
-
// src/declarations.ts
|
|
3750
|
-
var BINDING_ATTR = /data-bcms-(?:layout-)?field\s*=\s*["']([^"']+)["']/g;
|
|
3751
|
-
var PROPS_ATTR = /data-bcms-props\s*=\s*["']([^"']+)["']/g;
|
|
3752
|
-
var TEMPLATE_SCAN = /:?data-bcms-(?:layout-)?field\s*=\s*(?:\{\s*`([^`]+)`\s*\}|"\s*`([^`]+)`\s*")/g;
|
|
3753
|
-
var BINDINGS_SCAN = /bcmsBindings\s*=\s*\{\{[^}]*\}\}/g;
|
|
3754
|
-
var propsDeclarations = (value, at) => value.split(";").flatMap((entry) => {
|
|
3755
|
-
const [address, kind] = entry.split("|");
|
|
3756
|
-
if (!address) return [];
|
|
3757
|
-
const scope = address.startsWith("layout:") ? "layout" : "page";
|
|
3758
|
-
return [{ scope, path: stripLayoutPrefix(address), kind: kind ?? "", at, via: "props" }];
|
|
3759
|
-
});
|
|
3760
|
-
var propsPaths = (value) => propsDeclarations(value, 0).map((d) => ({ scope: d.scope, path: d.path }));
|
|
3761
|
-
var TEMPLATE_BINDING = /^:?data-bcms-(?:layout-)?field\s*=\s*(?:\{\s*`([^`]+)`\s*\}|"\s*`([^`]+)`\s*")$/;
|
|
3762
|
-
var shapeOfPath = (path) => path.replace(/\[(?:\d+|\$\{[^}]*\})\]/g, "[*]");
|
|
3763
|
-
function declarationsIn(roots, content) {
|
|
3764
|
-
const out = [];
|
|
3765
|
-
const visit = (node) => {
|
|
3766
|
-
if (node.type === "expr") {
|
|
3767
|
-
for (const child of node.children) visit(child);
|
|
3768
|
-
return;
|
|
3769
|
-
}
|
|
3770
|
-
if (node.type !== "element") return;
|
|
3771
|
-
const kind = node.attrs.find((a) => a.name === "data-bcms-kind")?.value ?? "";
|
|
3772
|
-
const at = node.attrInsertAt;
|
|
3773
|
-
for (const attr of node.attrs) {
|
|
3774
|
-
const name = attr.name.replace(/^:/, "");
|
|
3775
|
-
if (name === "data-bcms-field" || name === "data-bcms-layout-field") {
|
|
3776
|
-
const scope = name === "data-bcms-layout-field" ? "layout" : "page";
|
|
3777
|
-
const template = content.slice(attr.range.start, attr.range.end).match(TEMPLATE_BINDING);
|
|
3778
|
-
const templated = template?.[1] ?? template?.[2];
|
|
3779
|
-
if (templated) out.push({ scope, path: shapeOfPath(stripLayoutPrefix(templated)), kind, at, via: "field" });
|
|
3780
|
-
else if (attr.value) out.push({ scope, path: stripLayoutPrefix(attr.value), kind, at, via: "field" });
|
|
3781
|
-
} else if (attr.name === "data-bcms-props") {
|
|
3782
|
-
out.push(...propsDeclarations(attr.value, at));
|
|
3783
|
-
} else if (attr.name === "bcmsBindings") {
|
|
3784
|
-
for (const path of bindingsPaths(content.slice(attr.range.start, attr.range.end))) {
|
|
3785
|
-
out.push({ scope: "page", path, kind: "", at, via: "prop" });
|
|
3786
|
-
}
|
|
3787
|
-
}
|
|
3788
|
-
}
|
|
3789
|
-
for (const child of node.children) visit(child);
|
|
3790
|
-
};
|
|
3791
|
-
for (const root of roots) visit(root);
|
|
3792
|
-
return out;
|
|
3793
|
-
}
|
|
3794
|
-
var bindingsPaths = (text) => [...text.matchAll(/["']([^"']+)["']/g)].map((match) => match[1]);
|
|
3795
|
-
var scopedPath = (scope, path) => `${scope}::${path}`;
|
|
3796
|
-
var declaredInTree = (roots, content) => new Set(declarationsIn(roots, content).map((d) => scopedPath(d.scope, d.path)));
|
|
3797
|
-
function declaredByScan(content) {
|
|
3798
|
-
const out = /* @__PURE__ */ new Set();
|
|
3799
|
-
const add = (scope, path) => void out.add(scopedPath(scope, path));
|
|
3800
|
-
for (const match of content.matchAll(BINDING_ATTR)) {
|
|
3801
|
-
add(match[0].includes("layout-field") ? "layout" : "page", stripLayoutPrefix(match[1]));
|
|
3802
|
-
}
|
|
3803
|
-
for (const match of content.matchAll(TEMPLATE_SCAN)) {
|
|
3804
|
-
add(match[0].includes("layout-field") ? "layout" : "page", shapeOfPath(stripLayoutPrefix(match[1] ?? match[2])));
|
|
3805
|
-
}
|
|
3806
|
-
for (const match of content.matchAll(BINDINGS_SCAN)) for (const path of bindingsPaths(match[0])) add("page", path);
|
|
3807
|
-
for (const match of content.matchAll(PROPS_ATTR))
|
|
3808
|
-
for (const entry of propsPaths(match[1])) add(entry.scope, entry.path);
|
|
3809
|
-
return out;
|
|
3810
|
-
}
|
|
3811
|
-
var stripLayoutPrefix = (value) => value.startsWith("layout:") ? value.slice("layout:".length) : value;
|
|
3812
|
-
|
|
3813
3997
|
// src/converted.ts
|
|
3814
3998
|
var escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3815
3999
|
function splitArrayPath(path) {
|
|
@@ -4643,7 +4827,7 @@ var ValidationError = class extends Error {
|
|
|
4643
4827
|
}
|
|
4644
4828
|
code;
|
|
4645
4829
|
};
|
|
4646
|
-
var
|
|
4830
|
+
var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4647
4831
|
var Check = class {
|
|
4648
4832
|
constructor(code) {
|
|
4649
4833
|
this.code = code;
|
|
@@ -4653,7 +4837,7 @@ var Check = class {
|
|
|
4653
4837
|
throw new ValidationError(this.code, `${where} ${why}.`);
|
|
4654
4838
|
}
|
|
4655
4839
|
record(value, where) {
|
|
4656
|
-
if (!
|
|
4840
|
+
if (!isRecord2(value)) this.fail(where, "is not an object");
|
|
4657
4841
|
return value;
|
|
4658
4842
|
}
|
|
4659
4843
|
array(value, where) {
|
|
@@ -4720,6 +4904,7 @@ function readPlan(value) {
|
|
|
4720
4904
|
const section = check.record(held, at2);
|
|
4721
4905
|
check.segment(section.groupKey, `${at2}.groupKey`);
|
|
4722
4906
|
check.optionalString(section.sectionType, `${at2}.sectionType`);
|
|
4907
|
+
check.optionalString(section.copy, `${at2}.copy`);
|
|
4723
4908
|
if (section.fields !== void 0) {
|
|
4724
4909
|
for (const [f, field] of check.array(section.fields, `${at2}.fields`).entries()) {
|
|
4725
4910
|
const shape = check.record(field, `${at2}.fields[${f}]`);
|
|
@@ -5101,30 +5286,84 @@ var SectionInvariantError = class extends Error {
|
|
|
5101
5286
|
}
|
|
5102
5287
|
};
|
|
5103
5288
|
function assertSections(total, sections) {
|
|
5104
|
-
const sum = sections.extracted + sections.inlineOnly + sections.alreadyExtracted + sections.pending.length;
|
|
5289
|
+
const sum = sections.extracted + sections.inlineOnly + sections.alreadyExtracted + sections.registered + sections.pending.length;
|
|
5105
5290
|
if (sum !== total) {
|
|
5106
5291
|
throw new SectionInvariantError(
|
|
5107
|
-
`The receipt does not account for every section: ${total} planned but ${sections.extracted} extracted + ${sections.inlineOnly} inline-only + ${sections.alreadyExtracted} already extracted + ${sections.pending.length} pending = ${sum}.`
|
|
5292
|
+
`The receipt does not account for every section: ${total} planned but ${sections.extracted} extracted + ${sections.inlineOnly} inline-only + ${sections.alreadyExtracted} already extracted + ${sections.registered} registered + ${sections.pending.length} pending = ${sum}.`
|
|
5108
5293
|
);
|
|
5109
5294
|
}
|
|
5110
5295
|
return sections;
|
|
5111
5296
|
}
|
|
5112
|
-
var SECTION_MARKER = "// @bettercms-ai/convert section
|
|
5297
|
+
var SECTION_MARKER = "// @bettercms-ai/convert section v2";
|
|
5298
|
+
var SECTION_MARKERS = [SECTION_MARKER, "// @bettercms-ai/convert section v1"];
|
|
5113
5299
|
var REGISTRY_MARKER = "// @bettercms-ai/convert sections v1";
|
|
5114
5300
|
var COMPONENT_DIR = "src/components/bcms";
|
|
5115
5301
|
var SECTIONS_LIB = "src/lib/bcms-sections.ts";
|
|
5116
5302
|
var EXTRACTABLE = ["astro", "jsx"];
|
|
5303
|
+
var registryValue = (entry) => entry.member ? `${entry.name}.${entry.member}` : entry.name;
|
|
5304
|
+
function importLine(name, specifier, entry) {
|
|
5305
|
+
if (entry.kind === "namespace") return `import * as ${name} from "${specifier}";`;
|
|
5306
|
+
if (entry.kind === "named") {
|
|
5307
|
+
const exported = entry.imported ?? name;
|
|
5308
|
+
return `import { ${exported === name ? name : `${exported} as ${name}`} } from "${specifier}";`;
|
|
5309
|
+
}
|
|
5310
|
+
return `import ${name} from "${specifier}";`;
|
|
5311
|
+
}
|
|
5117
5312
|
async function componentizeSources(briefIn, planIn, sources, options) {
|
|
5118
5313
|
const pending = [];
|
|
5119
5314
|
let extracted = 0;
|
|
5120
5315
|
let inlineOnly = 0;
|
|
5121
5316
|
let alreadyExtracted = 0;
|
|
5317
|
+
let registered = 0;
|
|
5122
5318
|
let planPending = 0;
|
|
5123
5319
|
let total = 0;
|
|
5320
|
+
const pend = (route, groupKey, reason, message) => void pending.push({ route, groupKey, reason, ...message ? { message } : {} });
|
|
5124
5321
|
const brief = readBrief(briefIn);
|
|
5125
5322
|
const plan2 = readPlan(planIn);
|
|
5323
|
+
const copy = options?.copy ?? "bind";
|
|
5324
|
+
const instanceOwned = (section) => section.copy === "instance";
|
|
5325
|
+
const notes = unreadableConfigs(sources).map(
|
|
5326
|
+
(path) => ({
|
|
5327
|
+
code: "TSCONFIG_UNREADABLE",
|
|
5328
|
+
file: path,
|
|
5329
|
+
message: `${path} is not JSON this package can read, so its \`paths\` aliases resolved to nothing.`
|
|
5330
|
+
})
|
|
5331
|
+
);
|
|
5332
|
+
const refuseRun = (reason, message) => {
|
|
5333
|
+
for (const page of plan2.pages ?? []) {
|
|
5334
|
+
for (const section of page.sections ?? []) {
|
|
5335
|
+
if (section.pending) {
|
|
5336
|
+
planPending++;
|
|
5337
|
+
continue;
|
|
5338
|
+
}
|
|
5339
|
+
total++;
|
|
5340
|
+
pend(page.route, section.groupKey, reason, message);
|
|
5341
|
+
}
|
|
5342
|
+
}
|
|
5343
|
+
return {
|
|
5344
|
+
files: [],
|
|
5345
|
+
receipt: {
|
|
5346
|
+
planDigest: plan2.digest ?? "",
|
|
5347
|
+
copy,
|
|
5348
|
+
sections: assertSections(total, { extracted: 0, inlineOnly: 0, alreadyExtracted: 0, registered: 0, pending }),
|
|
5349
|
+
components: [],
|
|
5350
|
+
planPending,
|
|
5351
|
+
notes
|
|
5352
|
+
}
|
|
5353
|
+
};
|
|
5354
|
+
};
|
|
5355
|
+
const movesCopy = (plan2.pages ?? []).some(
|
|
5356
|
+
(page) => (page.sections ?? []).some((section) => !section.pending && instanceOwned(section))
|
|
5357
|
+
);
|
|
5358
|
+
if (copy === "instance" && !movesCopy) {
|
|
5359
|
+
return refuseRun(
|
|
5360
|
+
"COPY_MODE_MISMATCH",
|
|
5361
|
+
'this run was asked for --copy instance, but no placement in the plan carries copy: "instance" \u2014 run componentize_sections { copy: "instance" } on the server first, then fetch the plan again.'
|
|
5362
|
+
);
|
|
5363
|
+
}
|
|
5126
5364
|
const contents = new Map(sources.map((s) => [s.path, s.content]));
|
|
5127
|
-
const
|
|
5365
|
+
const fileSet = new Set(contents.keys());
|
|
5366
|
+
const aliases = aliasesFrom(sources);
|
|
5128
5367
|
const trees = /* @__PURE__ */ new Map();
|
|
5129
5368
|
const treeOf = async (file) => {
|
|
5130
5369
|
if (trees.has(file)) return trees.get(file);
|
|
@@ -5138,7 +5377,9 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5138
5377
|
const helperPath = HELPER_PATH.astro;
|
|
5139
5378
|
const helperExisting = contents.get(helperPath);
|
|
5140
5379
|
const helperVersion = helperExisting === void 0 ? null : isKnownHelper(helperExisting, "astro");
|
|
5141
|
-
|
|
5380
|
+
if (helperExisting !== void 0 && helperVersion === null && options?.overwriteHelper !== true) {
|
|
5381
|
+
return refuseRun("HELPER_CONFLICT", `${helperPath} is not a module this package wrote.`);
|
|
5382
|
+
}
|
|
5142
5383
|
const outFiles = /* @__PURE__ */ new Map();
|
|
5143
5384
|
const written = /* @__PURE__ */ new Map();
|
|
5144
5385
|
const shipped = /* @__PURE__ */ new Map();
|
|
@@ -5148,7 +5389,31 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5148
5389
|
return held;
|
|
5149
5390
|
};
|
|
5150
5391
|
const fresh = /* @__PURE__ */ new Set();
|
|
5392
|
+
const bindOf = (section) => instanceOwned(section) ? void 0 : section.groupKey;
|
|
5393
|
+
const placementOf = (slug, section) => {
|
|
5394
|
+
const bind = bindOf(section);
|
|
5395
|
+
return bind === void 0 ? { slug } : { slug, bind };
|
|
5396
|
+
};
|
|
5397
|
+
const rebind = (dialect, pageSlug, all, usable) => {
|
|
5398
|
+
if (dialect === null || !EXTRACTABLE.includes(dialect)) return;
|
|
5399
|
+
const order = shipped.get(dialect);
|
|
5400
|
+
const held = order?.get(pageSlug);
|
|
5401
|
+
if (!order || !held) return;
|
|
5402
|
+
const queues = /* @__PURE__ */ new Map();
|
|
5403
|
+
for (const section of all) {
|
|
5404
|
+
const slug = resolveComponent(plan2, section).slug;
|
|
5405
|
+
queues.set(slug, [...queues.get(slug) ?? [], section]);
|
|
5406
|
+
}
|
|
5407
|
+
order.set(
|
|
5408
|
+
pageSlug,
|
|
5409
|
+
held.map((placement) => {
|
|
5410
|
+
const section = queues.get(placement.slug)?.shift();
|
|
5411
|
+
return section && usable.has(section) ? placementOf(placement.slug, section) : placement;
|
|
5412
|
+
})
|
|
5413
|
+
);
|
|
5414
|
+
};
|
|
5151
5415
|
const registryConflicts = [];
|
|
5416
|
+
const registryUnreadable = [];
|
|
5152
5417
|
for (const dialect of EXTRACTABLE) {
|
|
5153
5418
|
const path = `${COMPONENT_DIR}/Sections.${dialect === "astro" ? "astro" : "tsx"}`;
|
|
5154
5419
|
const existing = contents.get(path);
|
|
@@ -5157,7 +5422,11 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5157
5422
|
registryConflicts.push(path);
|
|
5158
5423
|
continue;
|
|
5159
5424
|
}
|
|
5160
|
-
const held = readRegistry(existing, dialect);
|
|
5425
|
+
const held = await readRegistry(existing, dialect, path, fileSet, aliases);
|
|
5426
|
+
if (held === null) {
|
|
5427
|
+
registryUnreadable.push(path);
|
|
5428
|
+
continue;
|
|
5429
|
+
}
|
|
5161
5430
|
componentsOf(dialect);
|
|
5162
5431
|
for (const [slug, component] of held.components) written.get(dialect).set(slug, component);
|
|
5163
5432
|
const order = shipped.get(dialect) ?? /* @__PURE__ */ new Map();
|
|
@@ -5167,6 +5436,12 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5167
5436
|
const lib = contents.get(SECTIONS_LIB);
|
|
5168
5437
|
if (lib !== void 0 && !isGenerated(lib)) registryConflicts.push(SECTIONS_LIB);
|
|
5169
5438
|
const registryBlocked = registryConflicts.length > 0 && options?.overwriteHelper !== true;
|
|
5439
|
+
const registryUnread = registryUnreadable.length > 0;
|
|
5440
|
+
const migrated = registryBlocked || registryUnread ? { files: [], refused: /* @__PURE__ */ new Map() } : await migrateSections(plan2, contents, written, fileSet, aliases);
|
|
5441
|
+
for (const file of migrated.files) {
|
|
5442
|
+
addFile(outFiles, contents, file);
|
|
5443
|
+
contents.set(file.path, file.content);
|
|
5444
|
+
}
|
|
5170
5445
|
for (const page of plan2.pages ?? []) {
|
|
5171
5446
|
const sections2 = (page.sections ?? []).filter((section) => {
|
|
5172
5447
|
if (section.pending) {
|
|
@@ -5177,46 +5452,81 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5177
5452
|
});
|
|
5178
5453
|
total += sections2.length;
|
|
5179
5454
|
if (sections2.length === 0) continue;
|
|
5455
|
+
const prefixes = /* @__PURE__ */ new Map();
|
|
5456
|
+
for (const section of sections2) {
|
|
5457
|
+
const prefix = propPrefix(section);
|
|
5458
|
+
if (prefix === null) {
|
|
5459
|
+
pend(
|
|
5460
|
+
page.route,
|
|
5461
|
+
section.groupKey,
|
|
5462
|
+
"PROP_MAP_INCONSISTENT",
|
|
5463
|
+
"the plan's fields do not spell every path as one common prefix plus its key, so a page path cannot be turned into a prop key."
|
|
5464
|
+
);
|
|
5465
|
+
continue;
|
|
5466
|
+
}
|
|
5467
|
+
prefixes.set(section, prefix);
|
|
5468
|
+
}
|
|
5469
|
+
const usable = sections2.filter((section) => prefixes.has(section));
|
|
5470
|
+
if (usable.length === 0) continue;
|
|
5471
|
+
const pendingBefore = pending.length;
|
|
5180
5472
|
const briefPage = brief.pages.find((p) => p.route === page.route);
|
|
5181
5473
|
const pageSlug = briefPage?.slug ?? slugify(page.route === "/" ? "home" : page.route);
|
|
5182
|
-
if (
|
|
5183
|
-
for (const section of
|
|
5184
|
-
pend(page.route, section.groupKey, "
|
|
5474
|
+
if (registryBlocked) {
|
|
5475
|
+
for (const section of usable) {
|
|
5476
|
+
pend(page.route, section.groupKey, "REGISTRY_CONFLICT", `${registryConflicts.join(", ")} is not a file this package wrote.`);
|
|
5185
5477
|
}
|
|
5186
5478
|
continue;
|
|
5187
5479
|
}
|
|
5188
|
-
if (
|
|
5189
|
-
for (const section of
|
|
5190
|
-
pend(
|
|
5480
|
+
if (registryUnread) {
|
|
5481
|
+
for (const section of usable) {
|
|
5482
|
+
pend(
|
|
5483
|
+
page.route,
|
|
5484
|
+
section.groupKey,
|
|
5485
|
+
"REGISTRY_UNREADABLE",
|
|
5486
|
+
`${registryUnreadable.join(", ")} carries our marker but its REGISTRY/SHIPPED tables cannot be read back \u2014 rebuilding it would drop the components it names.`
|
|
5487
|
+
);
|
|
5191
5488
|
}
|
|
5192
5489
|
continue;
|
|
5193
5490
|
}
|
|
5194
5491
|
const snapshotPath = `${CONTENT_DIR}/${pageSlug}.json`;
|
|
5195
5492
|
if (!snapshotUsable(contents.get(snapshotPath))) {
|
|
5196
|
-
for (const section of
|
|
5493
|
+
for (const section of usable) {
|
|
5197
5494
|
pend(page.route, section.groupKey, "SNAPSHOT_INVALID", `${snapshotPath} is not a JSON object.`);
|
|
5198
5495
|
}
|
|
5199
5496
|
continue;
|
|
5200
5497
|
}
|
|
5201
5498
|
const pageFiles = pageFilesFor(page.route, contents.keys());
|
|
5202
5499
|
if (pageFiles.length > 1) {
|
|
5203
|
-
for (const section of
|
|
5500
|
+
for (const section of usable) {
|
|
5204
5501
|
pend(page.route, section.groupKey, "ROUTE_FILE_AMBIGUOUS", `${pageFiles.join(" and ")} both serve ${page.route}.`);
|
|
5205
5502
|
}
|
|
5206
5503
|
continue;
|
|
5207
5504
|
}
|
|
5208
5505
|
const file = pageFiles[0];
|
|
5209
5506
|
if (file === void 0) {
|
|
5210
|
-
for (const section of
|
|
5507
|
+
for (const section of usable) {
|
|
5211
5508
|
pend(page.route, section.groupKey, "NOT_IN_SOURCE", `no page file found for ${page.route}.`);
|
|
5212
5509
|
}
|
|
5213
5510
|
continue;
|
|
5214
5511
|
}
|
|
5215
|
-
const
|
|
5512
|
+
const pageDialect = dialectOf(file, contents.get(file) ?? "");
|
|
5513
|
+
const done = new Set(alreadyOn(contents.get(file) ?? "", pageSlug, usable));
|
|
5514
|
+
let stuck = 0;
|
|
5515
|
+
for (const section of usable) {
|
|
5516
|
+
if (!done.has(section.groupKey)) continue;
|
|
5517
|
+
const why = pageDialect ? migrated.refused.get(refusalKey(pageDialect, resolveComponent(plan2, section).slug)) : void 0;
|
|
5518
|
+
if (why === void 0) continue;
|
|
5519
|
+
done.delete(section.groupKey);
|
|
5520
|
+
stuck++;
|
|
5521
|
+
pend(page.route, section.groupKey, "SECTION_UNMIGRATABLE", why);
|
|
5522
|
+
}
|
|
5216
5523
|
alreadyExtracted += done.size;
|
|
5217
|
-
if (done.size ===
|
|
5524
|
+
if (done.size + stuck === usable.length) {
|
|
5525
|
+
rebind(pageDialect, pageSlug, page.sections ?? [], new Set(usable));
|
|
5526
|
+
continue;
|
|
5527
|
+
}
|
|
5218
5528
|
const found = await locateSections(
|
|
5219
|
-
|
|
5529
|
+
usable.filter((section) => !done.has(section.groupKey)),
|
|
5220
5530
|
briefPage?.paths ?? [],
|
|
5221
5531
|
sources,
|
|
5222
5532
|
treeOf
|
|
@@ -5226,37 +5536,115 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5226
5536
|
}
|
|
5227
5537
|
const live = found.placed;
|
|
5228
5538
|
if (live.length === 0) continue;
|
|
5539
|
+
const content = contents.get(file);
|
|
5540
|
+
const dialect = dialectOf(file, content);
|
|
5541
|
+
if (!dialect || !EXTRACTABLE.includes(dialect)) {
|
|
5542
|
+
for (const placed of live) {
|
|
5543
|
+
pend(page.route, placed.section.groupKey, "DIALECT_UNSUPPORTED", `${file} is ${dialect ?? "not a template"}.`);
|
|
5544
|
+
}
|
|
5545
|
+
continue;
|
|
5546
|
+
}
|
|
5547
|
+
const tree = await treeOf(file);
|
|
5548
|
+
if (!tree) {
|
|
5549
|
+
for (const placed of live) {
|
|
5550
|
+
pend(page.route, placed.section.groupKey, "NOT_IN_SOURCE", `${file} did not parse.`);
|
|
5551
|
+
}
|
|
5552
|
+
continue;
|
|
5553
|
+
}
|
|
5554
|
+
const parsed = tree.parsed;
|
|
5555
|
+
const importedFiles = /* @__PURE__ */ new Map();
|
|
5556
|
+
for (const [local, from] of importsIn(parsed, dialect)) {
|
|
5557
|
+
const resolved = resolveSpecifier(file, from.specifier, fileSet, aliases);
|
|
5558
|
+
if (resolved === null) continue;
|
|
5559
|
+
importedFiles.set(resolved, [
|
|
5560
|
+
...importedFiles.get(resolved) ?? [],
|
|
5561
|
+
{ local, kind: from.kind, imported: from.imported }
|
|
5562
|
+
]);
|
|
5563
|
+
}
|
|
5564
|
+
const bindingOf = (held) => {
|
|
5565
|
+
const bindings = importedFiles.get(held) ?? [];
|
|
5566
|
+
return bindings.find((binding) => calledIn(tree, binding)) ?? bindings[0];
|
|
5567
|
+
};
|
|
5229
5568
|
const placedHere = [];
|
|
5569
|
+
const importedSections = [];
|
|
5230
5570
|
for (const placed of live) {
|
|
5231
5571
|
const elements = placed.byFile.get(file);
|
|
5572
|
+
const alsoRendered = [...placed.byFile.keys()].filter((held2) => held2 !== file && importedFiles.has(held2));
|
|
5232
5573
|
if (elements && elements.length > 0) {
|
|
5574
|
+
if (alsoRendered.length > 0) {
|
|
5575
|
+
pend(
|
|
5576
|
+
page.route,
|
|
5577
|
+
placed.section.groupKey,
|
|
5578
|
+
"SECTION_ROOT_AMBIGUOUS",
|
|
5579
|
+
`rendered inline by ${file} AND by ${alsoRendered.join(", ")}, which it imports.`
|
|
5580
|
+
);
|
|
5581
|
+
continue;
|
|
5582
|
+
}
|
|
5233
5583
|
placedHere.push({ section: placed.section, elements });
|
|
5234
5584
|
continue;
|
|
5235
5585
|
}
|
|
5586
|
+
if (alsoRendered.length > 1) {
|
|
5587
|
+
pend(
|
|
5588
|
+
page.route,
|
|
5589
|
+
placed.section.groupKey,
|
|
5590
|
+
"SECTION_ROOT_AMBIGUOUS",
|
|
5591
|
+
`rendered by ${alsoRendered.join(" and ")}, both imported by ${file}.`
|
|
5592
|
+
);
|
|
5593
|
+
continue;
|
|
5594
|
+
}
|
|
5595
|
+
const only = alsoRendered[0] ?? [...placed.byFile.keys()][0];
|
|
5596
|
+
const held = bindingOf(only);
|
|
5597
|
+
if (held !== void 0 && dialectOf(only, contents.get(only) ?? "") === dialect) {
|
|
5598
|
+
importedSections.push({ section: placed.section, path: only, call: held.local });
|
|
5599
|
+
continue;
|
|
5600
|
+
}
|
|
5236
5601
|
pend(
|
|
5237
5602
|
page.route,
|
|
5238
5603
|
placed.section.groupKey,
|
|
5239
5604
|
"SECTION_ROOT_AMBIGUOUS",
|
|
5240
|
-
`rendered by ${[...placed.byFile.keys()].join(", ")}, not ${file}.`
|
|
5605
|
+
held === void 0 ? `rendered by ${[...placed.byFile.keys()].join(", ")}, which ${file} does not import.` : `rendered by ${only}, which is ${dialectOf(only, contents.get(only) ?? "") ?? "not a template"}, not ${dialect} like ${file}.`
|
|
5241
5606
|
);
|
|
5242
5607
|
}
|
|
5243
|
-
if (placedHere.length === 0) continue;
|
|
5244
|
-
const
|
|
5245
|
-
const
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5608
|
+
if (placedHere.length === 0 && importedSections.length === 0) continue;
|
|
5609
|
+
const extractable = componentsOf(dialect);
|
|
5610
|
+
const registeredHere = [];
|
|
5611
|
+
for (const held of importedSections) {
|
|
5612
|
+
const component = resolveComponent(plan2, held.section);
|
|
5613
|
+
const already = extractable.get(component.slug);
|
|
5614
|
+
if (already !== void 0 && already.path !== held.path) {
|
|
5615
|
+
pend(
|
|
5616
|
+
page.route,
|
|
5617
|
+
held.section.groupKey,
|
|
5618
|
+
"COMPONENT_CONFLICT",
|
|
5619
|
+
`the slug "${component.slug}" already names ${already.path}, not ${held.path}.`
|
|
5620
|
+
);
|
|
5621
|
+
continue;
|
|
5249
5622
|
}
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
|
|
5623
|
+
const binding = bindingOf(held.path);
|
|
5624
|
+
const member = binding.kind === "namespace" ? namespaceMember(tree, binding.local) : void 0;
|
|
5625
|
+
if (binding.kind === "namespace" && member === void 0) {
|
|
5626
|
+
pend(
|
|
5627
|
+
page.route,
|
|
5628
|
+
held.section.groupKey,
|
|
5629
|
+
"SECTION_ROOT_AMBIGUOUS",
|
|
5630
|
+
`${held.path} is imported as a namespace and ${file} never calls one of its components, so nothing names the export that renders it.`
|
|
5631
|
+
);
|
|
5632
|
+
continue;
|
|
5256
5633
|
}
|
|
5257
|
-
|
|
5634
|
+
extractable.set(component.slug, {
|
|
5635
|
+
name: component.name,
|
|
5636
|
+
path: held.path,
|
|
5637
|
+
kind: binding.kind,
|
|
5638
|
+
imported: binding.imported,
|
|
5639
|
+
...member ? { member } : {}
|
|
5640
|
+
});
|
|
5641
|
+
registered++;
|
|
5642
|
+
registeredHere.push({
|
|
5643
|
+
slug: component.slug,
|
|
5644
|
+
section: held.section,
|
|
5645
|
+
call: callSiteOf(tree, member ? `${binding.local}.${member}` : held.call)
|
|
5646
|
+
});
|
|
5258
5647
|
}
|
|
5259
|
-
const parsed = tree.parsed;
|
|
5260
5648
|
const roots = [];
|
|
5261
5649
|
for (const placed of placedHere) {
|
|
5262
5650
|
const root = lowestCommonAncestor(placed.elements);
|
|
@@ -5265,7 +5653,13 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5265
5653
|
continue;
|
|
5266
5654
|
}
|
|
5267
5655
|
const component = resolveComponent(plan2, placed.section);
|
|
5268
|
-
roots.push({
|
|
5656
|
+
roots.push({
|
|
5657
|
+
section: placed.section,
|
|
5658
|
+
route: page.route,
|
|
5659
|
+
root,
|
|
5660
|
+
pathPrefix: prefixes.get(placed.section),
|
|
5661
|
+
...component
|
|
5662
|
+
});
|
|
5269
5663
|
}
|
|
5270
5664
|
const nested = /* @__PURE__ */ new Set();
|
|
5271
5665
|
for (const a of roots) {
|
|
@@ -5280,8 +5674,6 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5280
5674
|
pend(page.route, resolved.section.groupKey, "SECTION_ROOT_AMBIGUOUS", "its root contains another section's.");
|
|
5281
5675
|
}
|
|
5282
5676
|
const candidates = roots.filter((r) => !nested.has(r)).sort((a, b) => a.root.start - b.root.start);
|
|
5283
|
-
if (candidates.length === 0) continue;
|
|
5284
|
-
const extractable = componentsOf(dialect);
|
|
5285
5677
|
const placeable = [];
|
|
5286
5678
|
for (const resolved of candidates) {
|
|
5287
5679
|
const path = `${COMPONENT_DIR}/${resolved.name}.${dialect === "astro" ? "astro" : "tsx"}`;
|
|
@@ -5294,7 +5686,25 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5294
5686
|
placeable.push(resolved);
|
|
5295
5687
|
continue;
|
|
5296
5688
|
}
|
|
5297
|
-
const built = componentFile(dialect, path, file, content, parsed, resolved);
|
|
5689
|
+
const built = componentFile(dialect, path, file, content, parsed, resolved, resolved.pathPrefix);
|
|
5690
|
+
if (built.unconvertible.length > 0) {
|
|
5691
|
+
pend(
|
|
5692
|
+
page.route,
|
|
5693
|
+
resolved.section.groupKey,
|
|
5694
|
+
"PROP_MAP_INCONSISTENT",
|
|
5695
|
+
`its fields map to the prefix "${resolved.pathPrefix}", which ${[...new Set(built.unconvertible)].slice(0, 3).join(", ")} cannot be converted through \u2014 so those elements would stay on the page lane whatever the placement says.`
|
|
5696
|
+
);
|
|
5697
|
+
continue;
|
|
5698
|
+
}
|
|
5699
|
+
if (built.duplicates.length > 0) {
|
|
5700
|
+
pend(
|
|
5701
|
+
page.route,
|
|
5702
|
+
resolved.section.groupKey,
|
|
5703
|
+
"DUPLICATE_FIELD_BINDING",
|
|
5704
|
+
`${built.duplicates.join(", ")} carries more than one data-bcms-field, so there is no single address to rewrite.`
|
|
5705
|
+
);
|
|
5706
|
+
continue;
|
|
5707
|
+
}
|
|
5298
5708
|
if (built.free.length > 0) {
|
|
5299
5709
|
pend(
|
|
5300
5710
|
page.route,
|
|
@@ -5304,7 +5714,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5304
5714
|
);
|
|
5305
5715
|
continue;
|
|
5306
5716
|
}
|
|
5307
|
-
extractable.set(resolved.slug, { name: resolved.name, path });
|
|
5717
|
+
extractable.set(resolved.slug, { name: resolved.name, path, kind: "default", imported: null });
|
|
5308
5718
|
fresh.add(resolved.slug);
|
|
5309
5719
|
addFile(outFiles, contents, {
|
|
5310
5720
|
path,
|
|
@@ -5313,24 +5723,54 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5313
5723
|
});
|
|
5314
5724
|
placeable.push(resolved);
|
|
5315
5725
|
}
|
|
5316
|
-
if (placeable.length === 0) continue;
|
|
5317
|
-
const
|
|
5726
|
+
if (placeable.length === 0 && registeredHere.length === 0) continue;
|
|
5727
|
+
const spans = [
|
|
5728
|
+
...placeable.map((r) => ({ slug: r.slug, bind: bindOf(r.section), root: r.root })),
|
|
5729
|
+
...registeredHere.map((r) => ({ slug: r.slug, bind: bindOf(r.section), root: r.call }))
|
|
5730
|
+
].sort((a, b) => (a.root?.start ?? Number.MAX_SAFE_INTEGER) - (b.root?.start ?? Number.MAX_SAFE_INTEGER));
|
|
5731
|
+
const known = spans.filter((s) => s.root !== null);
|
|
5732
|
+
const layout = runOf(known, parsed.roots, content);
|
|
5318
5733
|
if (layout === null) {
|
|
5319
5734
|
for (const resolved of placeable) {
|
|
5320
5735
|
pend(page.route, resolved.section.groupKey, "SECTION_NOT_CONTIGUOUS", "its roots share no container.");
|
|
5321
5736
|
}
|
|
5322
5737
|
continue;
|
|
5323
5738
|
}
|
|
5739
|
+
const wholePage = registeredHere.length === 0 || known.length === spans.length && pending.length === pendingBefore;
|
|
5740
|
+
const useRun = layout.contiguous && wholePage;
|
|
5324
5741
|
const order = shipped.get(dialect) ?? /* @__PURE__ */ new Map();
|
|
5325
5742
|
shipped.set(dialect, order);
|
|
5326
5743
|
order.set(
|
|
5327
5744
|
pageSlug,
|
|
5328
|
-
|
|
5745
|
+
spans.map((s) => s.bind === void 0 ? { slug: s.slug } : { slug: s.slug, bind: s.bind })
|
|
5329
5746
|
);
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5747
|
+
if (useRun) {
|
|
5748
|
+
addFile(outFiles, contents, {
|
|
5749
|
+
path: file,
|
|
5750
|
+
operation: "modify",
|
|
5751
|
+
content: splicePageRun(file, content, dialect, parsed, known, pageSlug)
|
|
5752
|
+
});
|
|
5753
|
+
extracted += placeable.length;
|
|
5754
|
+
} else {
|
|
5755
|
+
const inline = placeable.filter((resolved) => !instanceOwned(resolved.section));
|
|
5756
|
+
for (const resolved of placeable) {
|
|
5757
|
+
if (inline.includes(resolved)) continue;
|
|
5758
|
+
pend(
|
|
5759
|
+
page.route,
|
|
5760
|
+
resolved.section.groupKey,
|
|
5761
|
+
"INLINE_NEEDS_INSTANCE",
|
|
5762
|
+
`${file} cannot be replaced by one <Sections page="${pageSlug}" />, and an in-place call site carries no blockId or overrides \u2014 so this placement's own copy could not be rendered or addressed.`
|
|
5763
|
+
);
|
|
5764
|
+
}
|
|
5765
|
+
if (inline.length > 0) {
|
|
5766
|
+
addFile(outFiles, contents, {
|
|
5767
|
+
path: file,
|
|
5768
|
+
operation: "modify",
|
|
5769
|
+
content: splicePageInline(file, content, dialect, parsed, inline, pageSlug, extractable)
|
|
5770
|
+
});
|
|
5771
|
+
}
|
|
5772
|
+
inlineOnly += inline.length;
|
|
5773
|
+
}
|
|
5334
5774
|
}
|
|
5335
5775
|
const componentSlugs = [...written.values()].flatMap((held) => [...held.keys()]);
|
|
5336
5776
|
if (componentSlugs.length > 0) {
|
|
@@ -5352,15 +5792,17 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5352
5792
|
emitHelper(outFiles, contents, helperVersion, options?.overwriteHelper);
|
|
5353
5793
|
emitStubs(outFiles, contents, pageSlugs);
|
|
5354
5794
|
}
|
|
5355
|
-
const sections = assertSections(total, { extracted, inlineOnly, alreadyExtracted, pending });
|
|
5795
|
+
const sections = assertSections(total, { extracted, inlineOnly, alreadyExtracted, registered, pending });
|
|
5356
5796
|
return {
|
|
5357
5797
|
files: [...outFiles.values()].sort((a, b) => a.path.localeCompare(b.path)),
|
|
5358
5798
|
receipt: {
|
|
5359
5799
|
planDigest: plan2.digest ?? "",
|
|
5800
|
+
copy,
|
|
5360
5801
|
sections,
|
|
5361
5802
|
// What THIS run wrote a file for. A component an earlier run left is reused, not re-reported.
|
|
5362
5803
|
components: [...fresh].sort(),
|
|
5363
|
-
planPending
|
|
5804
|
+
planPending,
|
|
5805
|
+
notes
|
|
5364
5806
|
}
|
|
5365
5807
|
};
|
|
5366
5808
|
}
|
|
@@ -5373,29 +5815,152 @@ function alreadyOn(content, pageSlug, sections) {
|
|
|
5373
5815
|
}
|
|
5374
5816
|
return sections.map((section) => section.groupKey).filter((groupKey) => new RegExp(`bind=\\s*["']${groupKey.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}["']`).test(content));
|
|
5375
5817
|
}
|
|
5376
|
-
function readRegistry(content, dialect) {
|
|
5818
|
+
async function readRegistry(content, dialect, path, files, aliases) {
|
|
5377
5819
|
const components = /* @__PURE__ */ new Map();
|
|
5378
5820
|
const shipped = /* @__PURE__ */ new Map();
|
|
5379
|
-
const
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5383
|
-
|
|
5384
|
-
|
|
5385
|
-
|
|
5386
|
-
|
|
5387
|
-
|
|
5388
|
-
|
|
5389
|
-
|
|
5390
|
-
const table = content
|
|
5391
|
-
|
|
5821
|
+
const parsed = await parseFile4(dialect, content);
|
|
5822
|
+
if (parsed.error) return null;
|
|
5823
|
+
const imports = /* @__PURE__ */ new Map();
|
|
5824
|
+
for (const [local, from] of importsIn(parsed, dialect)) {
|
|
5825
|
+
if (!from.specifier.startsWith(".")) continue;
|
|
5826
|
+
const resolved = resolveSpecifier(path, from.specifier, files, aliases);
|
|
5827
|
+
if (resolved !== null) {
|
|
5828
|
+
imports.set(local, { name: local, path: preferDialect(resolved, dialect, files), kind: from.kind, imported: from.imported });
|
|
5829
|
+
}
|
|
5830
|
+
}
|
|
5831
|
+
const registry = blockOf(content, "REGISTRY");
|
|
5832
|
+
const table = blockOf(content, "SHIPPED");
|
|
5833
|
+
if (registry === null || table === null) return null;
|
|
5834
|
+
const registryRows = propertiesOf(registry);
|
|
5835
|
+
const shippedRows = propertiesOf(table);
|
|
5836
|
+
if (registryRows === null || shippedRows === null) return null;
|
|
5837
|
+
if (registryRows.length === 0) return null;
|
|
5838
|
+
for (const row of registryRows) {
|
|
5839
|
+
const named = row.value.match(/^([A-Za-z_$][\w$]*)(?:\.([A-Za-z_$][\w$]*))?$/);
|
|
5840
|
+
const entry = named ? imports.get(named[1]) : void 0;
|
|
5841
|
+
if (!named || !entry) return null;
|
|
5842
|
+
components.set(row.key, named[2] === void 0 ? entry : { ...entry, member: named[2] });
|
|
5843
|
+
}
|
|
5844
|
+
for (const row of shippedRows) {
|
|
5392
5845
|
try {
|
|
5393
|
-
|
|
5846
|
+
const placements = JSON.parse(row.value);
|
|
5847
|
+
if (!Array.isArray(placements) || placements.length === 0 || !placements.every(isPlacement)) return null;
|
|
5848
|
+
if (placements.some((placement) => !components.has(placement.slug))) return null;
|
|
5849
|
+
shipped.set(row.key, placements);
|
|
5394
5850
|
} catch {
|
|
5851
|
+
return null;
|
|
5395
5852
|
}
|
|
5396
5853
|
}
|
|
5397
5854
|
return { components, shipped };
|
|
5398
5855
|
}
|
|
5856
|
+
function preferDialect(resolved, dialect, files) {
|
|
5857
|
+
const wanted = dialect === "astro" ? [".astro"] : [".tsx", ".jsx"];
|
|
5858
|
+
if (wanted.some((extension) => resolved.endsWith(extension))) return resolved;
|
|
5859
|
+
const base = resolved.replace(/\.[^./]+$/, "");
|
|
5860
|
+
return wanted.map((extension) => `${base}${extension}`).find((candidate) => files.has(candidate)) ?? resolved;
|
|
5861
|
+
}
|
|
5862
|
+
function isPlacement(value) {
|
|
5863
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
5864
|
+
const held = value;
|
|
5865
|
+
if (typeof held.slug !== "string") return false;
|
|
5866
|
+
return ["bind", "blockId"].every((key) => held[key] === void 0 || typeof held[key] === "string");
|
|
5867
|
+
}
|
|
5868
|
+
function blockOf(content, name) {
|
|
5869
|
+
const declaration2 = content.indexOf(`const ${name}`);
|
|
5870
|
+
if (declaration2 < 0) return null;
|
|
5871
|
+
const open = content.indexOf("{", declaration2);
|
|
5872
|
+
if (open < 0) return null;
|
|
5873
|
+
let depth = 0;
|
|
5874
|
+
let at = open;
|
|
5875
|
+
while (at < content.length) {
|
|
5876
|
+
const skip = skipTrivia(content, at);
|
|
5877
|
+
if (skip >= 0) {
|
|
5878
|
+
at = skip;
|
|
5879
|
+
continue;
|
|
5880
|
+
}
|
|
5881
|
+
if (content[at] === "{") depth++;
|
|
5882
|
+
else if (content[at] === "}" && --depth === 0) return content.slice(open + 1, at);
|
|
5883
|
+
at++;
|
|
5884
|
+
}
|
|
5885
|
+
return null;
|
|
5886
|
+
}
|
|
5887
|
+
function skipTrivia(source, at) {
|
|
5888
|
+
const char = source[at];
|
|
5889
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
5890
|
+
let cursor = at + 1;
|
|
5891
|
+
while (cursor < source.length && source[cursor] !== char) cursor += source[cursor] === "\\" ? 2 : 1;
|
|
5892
|
+
return Math.min(cursor + 1, source.length);
|
|
5893
|
+
}
|
|
5894
|
+
if (char === "/" && source[at + 1] === "/") {
|
|
5895
|
+
const line = source.indexOf("\n", at);
|
|
5896
|
+
return line < 0 ? source.length : line;
|
|
5897
|
+
}
|
|
5898
|
+
if (char === "/" && source[at + 1] === "*") {
|
|
5899
|
+
const close = source.indexOf("*/", at + 2);
|
|
5900
|
+
return close < 0 ? source.length : close + 2;
|
|
5901
|
+
}
|
|
5902
|
+
return -1;
|
|
5903
|
+
}
|
|
5904
|
+
function withoutComments(text) {
|
|
5905
|
+
let out = "";
|
|
5906
|
+
let at = 0;
|
|
5907
|
+
while (at < text.length) {
|
|
5908
|
+
const skip = skipTrivia(text, at);
|
|
5909
|
+
if (skip < 0) {
|
|
5910
|
+
out += text[at];
|
|
5911
|
+
at++;
|
|
5912
|
+
continue;
|
|
5913
|
+
}
|
|
5914
|
+
out += text[at] === '"' || text[at] === "'" || text[at] === "`" ? text.slice(at, skip) : "";
|
|
5915
|
+
at = skip;
|
|
5916
|
+
}
|
|
5917
|
+
return out;
|
|
5918
|
+
}
|
|
5919
|
+
function propertiesOf(block) {
|
|
5920
|
+
const out = [];
|
|
5921
|
+
const push = (text) => {
|
|
5922
|
+
const held = text.trim();
|
|
5923
|
+
if (held === "") return true;
|
|
5924
|
+
let depth2 = 0;
|
|
5925
|
+
for (let at = 0; at < held.length; ) {
|
|
5926
|
+
const skip = skipTrivia(held, at);
|
|
5927
|
+
if (skip >= 0) {
|
|
5928
|
+
at = skip;
|
|
5929
|
+
continue;
|
|
5930
|
+
}
|
|
5931
|
+
const char = held[at];
|
|
5932
|
+
if ("{[(".includes(char)) depth2++;
|
|
5933
|
+
else if ("}])".includes(char)) depth2--;
|
|
5934
|
+
else if (char === ":" && depth2 === 0) {
|
|
5935
|
+
const key = withoutComments(held.slice(0, at)).trim().replace(/^["'`]|["'`]$/g, "");
|
|
5936
|
+
const value = withoutComments(held.slice(at + 1)).trim();
|
|
5937
|
+
if (key === "" || value === "") return false;
|
|
5938
|
+
out.push({ key, value });
|
|
5939
|
+
return true;
|
|
5940
|
+
}
|
|
5941
|
+
at++;
|
|
5942
|
+
}
|
|
5943
|
+
return false;
|
|
5944
|
+
};
|
|
5945
|
+
let depth = 0;
|
|
5946
|
+
let start = 0;
|
|
5947
|
+
for (let at = 0; at < block.length; ) {
|
|
5948
|
+
const skip = skipTrivia(block, at);
|
|
5949
|
+
if (skip >= 0) {
|
|
5950
|
+
at = skip;
|
|
5951
|
+
continue;
|
|
5952
|
+
}
|
|
5953
|
+
const char = block[at];
|
|
5954
|
+
if ("{[(".includes(char)) depth++;
|
|
5955
|
+
else if ("}])".includes(char)) depth--;
|
|
5956
|
+
else if (char === "," && depth === 0) {
|
|
5957
|
+
if (!push(block.slice(start, at))) return null;
|
|
5958
|
+
start = at + 1;
|
|
5959
|
+
}
|
|
5960
|
+
at++;
|
|
5961
|
+
}
|
|
5962
|
+
return push(block.slice(start)) ? out : null;
|
|
5963
|
+
}
|
|
5399
5964
|
function snapshotUsable(existing) {
|
|
5400
5965
|
if (existing === void 0) return true;
|
|
5401
5966
|
try {
|
|
@@ -5480,7 +6045,22 @@ function lowestCommonAncestor(elements) {
|
|
|
5480
6045
|
}
|
|
5481
6046
|
return common[common.length - 1] ?? null;
|
|
5482
6047
|
}
|
|
5483
|
-
|
|
6048
|
+
var calledIn = (tree, binding) => [...tree.elements.values()].some(
|
|
6049
|
+
(placed) => binding.kind === "namespace" ? placed.node.tag.startsWith(`${binding.local}.`) : placed.node.tag === binding.local
|
|
6050
|
+
);
|
|
6051
|
+
function namespaceMember(tree, local) {
|
|
6052
|
+
const members = new Set(
|
|
6053
|
+
[...tree.elements.values()].map((placed) => placed.node.tag).filter((tag) => tag.startsWith(`${local}.`)).map((tag) => tag.slice(local.length + 1))
|
|
6054
|
+
);
|
|
6055
|
+
return members.size === 1 ? [...members][0] : void 0;
|
|
6056
|
+
}
|
|
6057
|
+
function callSiteOf(tree, local) {
|
|
6058
|
+
const calls = [...tree.elements.values()].map((placed) => placed.node).filter((node) => node.tag === local);
|
|
6059
|
+
const only = calls.length === 1 ? calls[0] : null;
|
|
6060
|
+
return only !== null && only.attrs.length === 0 ? only : null;
|
|
6061
|
+
}
|
|
6062
|
+
function runOf(placeable, roots, content) {
|
|
6063
|
+
if (placeable.length === 0) return null;
|
|
5484
6064
|
const parents = placeable.map((r) => parentOf(r.root, roots));
|
|
5485
6065
|
if (parents.some((p) => p === void 0)) return null;
|
|
5486
6066
|
const [first, ...rest] = parents;
|
|
@@ -5490,9 +6070,14 @@ function runOf(placeable, roots) {
|
|
|
5490
6070
|
);
|
|
5491
6071
|
const indices = placeable.map((r) => siblings.indexOf(r.root)).sort((a, b) => a - b);
|
|
5492
6072
|
if (indices.some((at) => at < 0)) return null;
|
|
5493
|
-
const
|
|
5494
|
-
|
|
6073
|
+
const consecutive2 = indices.every((at, i) => i === 0 || at === indices[i - 1] + 1);
|
|
6074
|
+
const ordered = [...placeable].sort((a, b) => a.root.start - b.root.start);
|
|
6075
|
+
const clean = ordered.every(
|
|
6076
|
+
(span, at) => at === 0 || isTrivia(content.slice(ordered[at - 1].root.end, span.root.start))
|
|
6077
|
+
);
|
|
6078
|
+
return { contiguous: consecutive2 && clean };
|
|
5495
6079
|
}
|
|
6080
|
+
var isTrivia = (between) => between.replace(/<!--[\s\S]*?-->/g, "").replace(/\{\s*\/\*[\s\S]*?\*\/\s*\}/g, "").trim() === "";
|
|
5496
6081
|
function parentOf(target, roots) {
|
|
5497
6082
|
if (roots.some((node) => node === target)) return null;
|
|
5498
6083
|
const search = (nodes, parent) => {
|
|
@@ -5526,14 +6111,35 @@ function pascal(value) {
|
|
|
5526
6111
|
return /^[A-Za-z]/.test(name) ? name : `Section${name}`;
|
|
5527
6112
|
}
|
|
5528
6113
|
var slugify = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "section";
|
|
5529
|
-
function componentFile(dialect, path, fromFile, content, parsed, resolved) {
|
|
5530
|
-
const
|
|
6114
|
+
function componentFile(dialect, path, fromFile, content, parsed, resolved, pathPrefix) {
|
|
6115
|
+
const addresses = fieldAddresses(content, elementsUnder(resolved.root));
|
|
6116
|
+
const unconvertible = [
|
|
6117
|
+
...addresses.heads.filter((address) => !address.startsWith(pathPrefix)),
|
|
6118
|
+
...addresses.opaque
|
|
6119
|
+
];
|
|
6120
|
+
if (unconvertible.length > 0) return { content: "", free: [], duplicates: [], unconvertible };
|
|
6121
|
+
const cut = content.slice(resolved.root.start, resolved.root.end);
|
|
6122
|
+
const reserved = /* @__PURE__ */ new Set([...topLevelBindings(dialect, parsed, content), ...referencedNames2(cut)]);
|
|
6123
|
+
const introduced = allocateNames(reserved, [
|
|
6124
|
+
{ key: "bcmsFields", name: "bcmsFields" },
|
|
6125
|
+
{ key: "bcmsField", name: "bcmsField" }
|
|
6126
|
+
]);
|
|
6127
|
+
const fieldsName = introduced.get("bcmsFields");
|
|
6128
|
+
const fieldName = introduced.get("bcmsField");
|
|
6129
|
+
const { markup, bound, duplicates } = sectionMarkup(content, resolved.root, fieldName);
|
|
6130
|
+
if (duplicates.length > 0) return { content: "", free: [], duplicates, unconvertible: [] };
|
|
5531
6131
|
const groupKey = resolved.section.groupKey;
|
|
5532
6132
|
const locals = helperLocals(parsed, dialect, fromFile);
|
|
5533
6133
|
const helperNames = ["bcms", "bcmsRows", "bcmsLayout"].flatMap(
|
|
5534
6134
|
(name) => locals[name].filter((local) => mentions(markup, local)).map((local) => ({ name, local }))
|
|
5535
6135
|
);
|
|
5536
|
-
const helpers = [
|
|
6136
|
+
const helpers = [
|
|
6137
|
+
.../* @__PURE__ */ new Set([
|
|
6138
|
+
...helperNames.map((h) => alias(h.name, h.local)),
|
|
6139
|
+
"bcmsSection",
|
|
6140
|
+
...bound > 0 ? [alias("bcmsFields", fieldsName)] : []
|
|
6141
|
+
])
|
|
6142
|
+
].sort();
|
|
5537
6143
|
const helperSpecifier = relativeImport(path, HELPER_PATH[dialect]).replace(/\.tsx?$/, "");
|
|
5538
6144
|
const snapshot = snapshotOf(parsed, dialect, fromFile, markup);
|
|
5539
6145
|
const carry = carryFor({
|
|
@@ -5548,20 +6154,23 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved) {
|
|
|
5548
6154
|
"bind",
|
|
5549
6155
|
"overrides",
|
|
5550
6156
|
"page",
|
|
6157
|
+
fieldName,
|
|
5551
6158
|
...helperNames.map((h) => h.local),
|
|
5552
6159
|
...snapshot ? [snapshot] : []
|
|
5553
6160
|
],
|
|
5554
6161
|
// The helper and the snapshots are what the component declares for itself.
|
|
5555
6162
|
skipSpecifier: (repoPath, specifier) => specifier.includes(`${CONTENT_DIR}/`) || repoPath.startsWith(HELPER_PATH[dialect].replace(/\.tsx?$/, ""))
|
|
5556
6163
|
});
|
|
5557
|
-
if (carry.free.length > 0) return { content: "", free: carry.free };
|
|
6164
|
+
if (carry.free.length > 0) return { content: "", free: carry.free, duplicates: [], unconvertible: [] };
|
|
5558
6165
|
const carried = [...carry.imports];
|
|
5559
6166
|
const key = /^[A-Za-z_$][\w$]*$/.test(groupKey) ? groupKey : JSON.stringify(groupKey);
|
|
5560
6167
|
const carriedDeclarations = carry.declarations.length > 0 ? `${carry.declarations.join("\n")}
|
|
5561
6168
|
` : "";
|
|
5562
|
-
const copy = snapshot ? `const ${snapshot} = { ${key}: bcmsSection(page, bind
|
|
6169
|
+
const copy = snapshot ? `const ${snapshot} = { ${key}: bcmsSection(page, bind, overrides) };` : "";
|
|
6170
|
+
const field = bound > 0 ? `const ${fieldName} = ${fieldsName}({ blockId, bind, overrides }, ${JSON.stringify(pathPrefix)});` : "";
|
|
6171
|
+
const declared = [copy, field].filter(Boolean).join("\n");
|
|
5563
6172
|
if (dialect === "astro") {
|
|
5564
|
-
return { free: [], content: `---
|
|
6173
|
+
return { free: [], duplicates: [], unconvertible: [], content: `---
|
|
5565
6174
|
${SECTION_MARKER}
|
|
5566
6175
|
${[...carried, `import { ${helpers.join(", ")} } from "${helperSpecifier}";`].join("\n")}
|
|
5567
6176
|
|
|
@@ -5576,27 +6185,297 @@ interface Props {
|
|
|
5576
6185
|
page?: unknown;
|
|
5577
6186
|
}
|
|
5578
6187
|
const { blockId, bind, overrides, page } = Astro.props;
|
|
5579
|
-
${carriedDeclarations}${
|
|
6188
|
+
${carriedDeclarations}${declared}
|
|
5580
6189
|
---
|
|
5581
6190
|
${markup}
|
|
5582
6191
|
` };
|
|
5583
6192
|
}
|
|
5584
|
-
return { free: [], content: `${SECTION_MARKER}
|
|
6193
|
+
return { free: [], duplicates: [], unconvertible: [], content: `${SECTION_MARKER}
|
|
5585
6194
|
${[...carried, `import { ${helpers.join(", ")} } from "${helperSpecifier}";`, `import type { BcmsSectionProps } from "${relativeImport(path, SECTIONS_LIB).replace(/\.tsx?$/, "")}";`].join("\n")}
|
|
5586
6195
|
${carriedDeclarations === "" ? "" : `
|
|
5587
6196
|
${carriedDeclarations}`}
|
|
5588
6197
|
export default function ${resolved.name}({ blockId, bind, overrides, page }: BcmsSectionProps) {
|
|
5589
|
-
${
|
|
6198
|
+
${declared.split("\n").join("\n ")}
|
|
5590
6199
|
return (
|
|
5591
6200
|
${markup}
|
|
5592
6201
|
);
|
|
5593
6202
|
}
|
|
5594
6203
|
` };
|
|
5595
6204
|
}
|
|
5596
|
-
function
|
|
6205
|
+
function sectionMarkup(content, root, fieldName) {
|
|
5597
6206
|
const slice = new MagicString3(content.slice(root.start, root.end));
|
|
5598
|
-
|
|
5599
|
-
|
|
6207
|
+
const at = (offset) => offset - root.start;
|
|
6208
|
+
const stamps = root.attrs.filter((attr) => attr.name === "data-bcms-block");
|
|
6209
|
+
if (stamps.length === 0) slice.appendLeft(at(root.attrInsertAt), " data-bcms-block={blockId}");
|
|
6210
|
+
else {
|
|
6211
|
+
slice.overwrite(at(stamps[0].range.start), at(stamps[0].range.end), "data-bcms-block={blockId}");
|
|
6212
|
+
for (const extra of stamps.slice(1)) {
|
|
6213
|
+
let from = extra.range.start;
|
|
6214
|
+
while (from > root.start && /\s/.test(content[from - 1])) from--;
|
|
6215
|
+
slice.remove(at(from), at(extra.range.end));
|
|
6216
|
+
}
|
|
6217
|
+
}
|
|
6218
|
+
const wrapped = wrapFieldAttrs(slice, content, elementsUnder(root), at, fieldName);
|
|
6219
|
+
return { markup: slice.toString(), ...wrapped };
|
|
6220
|
+
}
|
|
6221
|
+
function wrapFieldAttrs(slice, content, elements, at, fieldName) {
|
|
6222
|
+
let bound = 0;
|
|
6223
|
+
const duplicates = [];
|
|
6224
|
+
for (const element of elements) {
|
|
6225
|
+
const fields = element.attrs.filter((attr) => attr.name === "data-bcms-field");
|
|
6226
|
+
if (fields.length > 1) {
|
|
6227
|
+
duplicates.push(`<${element.tag}>`);
|
|
6228
|
+
continue;
|
|
6229
|
+
}
|
|
6230
|
+
for (const attr of fields) {
|
|
6231
|
+
bound++;
|
|
6232
|
+
if (attr.expressionRange) {
|
|
6233
|
+
slice.appendRight(at(attr.expressionRange.start) + 1, `${fieldName}(`);
|
|
6234
|
+
slice.appendLeft(at(attr.expressionRange.end) - 1, ")");
|
|
6235
|
+
continue;
|
|
6236
|
+
}
|
|
6237
|
+
const value = attr.valueRange ? content.slice(attr.valueRange.start, attr.valueRange.end) : "";
|
|
6238
|
+
slice.overwrite(
|
|
6239
|
+
at(attr.range.start),
|
|
6240
|
+
at(attr.range.end),
|
|
6241
|
+
`data-bcms-field={${fieldName}(${JSON.stringify(value)})}`
|
|
6242
|
+
);
|
|
6243
|
+
}
|
|
6244
|
+
}
|
|
6245
|
+
return { bound, duplicates };
|
|
6246
|
+
}
|
|
6247
|
+
function provableAddress(expression) {
|
|
6248
|
+
const first = expression[0];
|
|
6249
|
+
if (first === '"' || first === "'") {
|
|
6250
|
+
for (let at = 1; at < expression.length; at++) {
|
|
6251
|
+
if (expression[at] === "\\") {
|
|
6252
|
+
at++;
|
|
6253
|
+
continue;
|
|
6254
|
+
}
|
|
6255
|
+
if (expression[at] === first) return at === expression.length - 1 ? expression.slice(1, at) : null;
|
|
6256
|
+
}
|
|
6257
|
+
return null;
|
|
6258
|
+
}
|
|
6259
|
+
if (first !== "`" || expression.length < 2 || !expression.endsWith("`")) return null;
|
|
6260
|
+
const body = expression.slice(1, -1);
|
|
6261
|
+
if (body.includes("`") || body.includes("\\")) return null;
|
|
6262
|
+
let head = null;
|
|
6263
|
+
for (let at = 0; at < body.length; at++) {
|
|
6264
|
+
if (body[at] !== "$" || body[at + 1] !== "{") continue;
|
|
6265
|
+
const close = body.indexOf("}", at + 2);
|
|
6266
|
+
if (close < 0) return null;
|
|
6267
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(body.slice(at + 2, close).trim())) return null;
|
|
6268
|
+
if (body[at - 1] !== "[" || body[close + 1] !== "]") return null;
|
|
6269
|
+
head ??= body.slice(0, at);
|
|
6270
|
+
at = close;
|
|
6271
|
+
}
|
|
6272
|
+
return head ?? body;
|
|
6273
|
+
}
|
|
6274
|
+
function fieldAddresses(content, elements) {
|
|
6275
|
+
const heads = [];
|
|
6276
|
+
const opaque = [];
|
|
6277
|
+
for (const element of elements) {
|
|
6278
|
+
for (const attr of element.attrs) {
|
|
6279
|
+
if (attr.name !== "data-bcms-field") continue;
|
|
6280
|
+
if (!attr.expression && attr.valueRange) {
|
|
6281
|
+
heads.push(content.slice(attr.valueRange.start, attr.valueRange.end));
|
|
6282
|
+
continue;
|
|
6283
|
+
}
|
|
6284
|
+
const inner = attr.expressionRange ? content.slice(attr.expressionRange.start + 1, attr.expressionRange.end - 1).trim() : "";
|
|
6285
|
+
const head = provableAddress(inner);
|
|
6286
|
+
if (head === null) opaque.push(`data-bcms-field={${inner}}`);
|
|
6287
|
+
else heads.push(head);
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6290
|
+
return { heads, opaque };
|
|
6291
|
+
}
|
|
6292
|
+
function elementsUnder(...roots) {
|
|
6293
|
+
const out = [];
|
|
6294
|
+
const walk = (node) => {
|
|
6295
|
+
if (node.type === "element") {
|
|
6296
|
+
out.push(node);
|
|
6297
|
+
for (const child of node.children) walk(child);
|
|
6298
|
+
return;
|
|
6299
|
+
}
|
|
6300
|
+
if (node.type === "expr") for (const child of node.children) walk(child);
|
|
6301
|
+
};
|
|
6302
|
+
for (const root of roots) walk(root);
|
|
6303
|
+
return out;
|
|
6304
|
+
}
|
|
6305
|
+
var V1_ANCHOR = {
|
|
6306
|
+
astro: "const { blockId, bind, overrides, page } = Astro.props;",
|
|
6307
|
+
jsx: "}: BcmsSectionProps) {"
|
|
6308
|
+
};
|
|
6309
|
+
var refusalKey = (dialect, slug) => `${dialect}\0${slug}`;
|
|
6310
|
+
function generatedBindings(parsed) {
|
|
6311
|
+
const program = parsed.ast?.program;
|
|
6312
|
+
const body = program?.body ?? [];
|
|
6313
|
+
const scopes = [body];
|
|
6314
|
+
for (const statement of body) {
|
|
6315
|
+
const declaration2 = statement.type === "ExportDefaultDeclaration" ? statement.declaration : void 0;
|
|
6316
|
+
const inner = declaration2?.type === "FunctionDeclaration" ? declaration2.body?.body : void 0;
|
|
6317
|
+
if (inner) scopes.push(inner);
|
|
6318
|
+
}
|
|
6319
|
+
const out = /* @__PURE__ */ new Map();
|
|
6320
|
+
for (const scope of scopes) {
|
|
6321
|
+
for (const statement of scope) {
|
|
6322
|
+
if (statement.type !== "VariableDeclaration") continue;
|
|
6323
|
+
for (const declarator of statement.declarations ?? []) {
|
|
6324
|
+
const id = declarator.id;
|
|
6325
|
+
const name = id?.type === "Identifier" ? id.name : void 0;
|
|
6326
|
+
if (name !== void 0 && !out.has(name)) out.set(name, declarator.init);
|
|
6327
|
+
}
|
|
6328
|
+
}
|
|
6329
|
+
}
|
|
6330
|
+
return out;
|
|
6331
|
+
}
|
|
6332
|
+
function stripBindFallback(parsed, source, content, locals) {
|
|
6333
|
+
if (!parsed.ast?.program) return "unreadable";
|
|
6334
|
+
const offset = parsed.script?.offset ?? 0;
|
|
6335
|
+
let rewritten = 0;
|
|
6336
|
+
let unproven = 0;
|
|
6337
|
+
walkAst(parsed.ast, (node) => {
|
|
6338
|
+
if (node.type !== "CallExpression") return;
|
|
6339
|
+
const argument = (node.arguments ?? [])[1];
|
|
6340
|
+
if (argument?.type !== "LogicalExpression" || argument.operator !== "??") return;
|
|
6341
|
+
const callee = node.callee;
|
|
6342
|
+
if (callee?.type !== "Identifier" || !locals.has(callee.name)) {
|
|
6343
|
+
unproven++;
|
|
6344
|
+
return;
|
|
6345
|
+
}
|
|
6346
|
+
const left = argument.left;
|
|
6347
|
+
source.overwrite(
|
|
6348
|
+
offset + argument.start,
|
|
6349
|
+
offset + argument.end,
|
|
6350
|
+
content.slice(offset + left.start, offset + left.end)
|
|
6351
|
+
);
|
|
6352
|
+
rewritten++;
|
|
6353
|
+
});
|
|
6354
|
+
return rewritten === 0 && unproven > 0 ? "unproven" : "ok";
|
|
6355
|
+
}
|
|
6356
|
+
var isCallOf = (init, callee) => {
|
|
6357
|
+
const fn = init?.type === "CallExpression" ? init.callee : void 0;
|
|
6358
|
+
return fn?.type === "Identifier" && fn.name === callee;
|
|
6359
|
+
};
|
|
6360
|
+
async function migrateSections(plan2, contents, written, fileSet, aliases) {
|
|
6361
|
+
const files = [];
|
|
6362
|
+
const refused = /* @__PURE__ */ new Map();
|
|
6363
|
+
const prefixesFor = (slug) => {
|
|
6364
|
+
const out = /* @__PURE__ */ new Set();
|
|
6365
|
+
for (const page of plan2.pages ?? []) {
|
|
6366
|
+
for (const section of page.sections ?? []) {
|
|
6367
|
+
if (resolveComponent(plan2, section).slug !== slug) continue;
|
|
6368
|
+
const prefix = propPrefix(section);
|
|
6369
|
+
if (prefix !== null) out.add(prefix);
|
|
6370
|
+
}
|
|
6371
|
+
}
|
|
6372
|
+
return [...out];
|
|
6373
|
+
};
|
|
6374
|
+
for (const [dialect, components] of written) {
|
|
6375
|
+
for (const [slug, entry] of components) {
|
|
6376
|
+
const content = contents.get(entry.path);
|
|
6377
|
+
if (content === void 0 || content.includes(SECTION_MARKER)) continue;
|
|
6378
|
+
const older = SECTION_MARKERS.find((marker) => content.includes(marker));
|
|
6379
|
+
if (older === void 0) continue;
|
|
6380
|
+
const candidates = prefixesFor(slug);
|
|
6381
|
+
if (candidates.length === 0) continue;
|
|
6382
|
+
const parsed = await parseFile4(dialect, content);
|
|
6383
|
+
if (parsed.error) {
|
|
6384
|
+
refused.set(refusalKey(dialect, slug), `${entry.path} carries our marker but does not parse (${parsed.error.message}).`);
|
|
6385
|
+
continue;
|
|
6386
|
+
}
|
|
6387
|
+
const elements = elementsUnder(...parsed.roots);
|
|
6388
|
+
const imports = [...importsIn(parsed, dialect)];
|
|
6389
|
+
const helperModule = HELPER_PATH[dialect];
|
|
6390
|
+
const helperLocalsFor = (exported) => new Set(
|
|
6391
|
+
imports.filter(
|
|
6392
|
+
([, from]) => from.imported === exported && resolveSpecifier(entry.path, from.specifier, fileSet, aliases) === helperModule
|
|
6393
|
+
).map(([local]) => local)
|
|
6394
|
+
);
|
|
6395
|
+
const { heads: evidence, opaque } = fieldAddresses(content, elements);
|
|
6396
|
+
const pathPrefix = evidence.length + opaque.length === 0 ? "" : opaque.length > 0 ? null : onlyOne(candidates.filter((prefix) => evidence.every((head) => head.startsWith(prefix)))) ?? null;
|
|
6397
|
+
if (pathPrefix === null) {
|
|
6398
|
+
const matching = candidates.filter((prefix) => evidence.every((head) => head.startsWith(prefix)));
|
|
6399
|
+
refused.set(
|
|
6400
|
+
refusalKey(dialect, slug),
|
|
6401
|
+
opaque.length > 0 ? `${entry.path} carries ${[...new Set(opaque)].slice(0, 3).join(", ")}, whose address this cannot read \u2014 so there is no prefix it can be shown to convert through.` : matching.length === 0 ? `${entry.path} declares ${evidence.length > 0 ? `addresses (${[...new Set(evidence)].slice(0, 3).join(", ")})` : "addresses"} that match none of this slug's placements (${candidates.join(", ") || "none with a usable prop map"}), so the prefix its markup was cut with cannot be recovered.` : `${entry.path} matches more than one of this slug's placements (${matching.join(", ")}), so the prefix its markup was cut with is ambiguous.`
|
|
6402
|
+
);
|
|
6403
|
+
continue;
|
|
6404
|
+
}
|
|
6405
|
+
const source = new MagicString3(content);
|
|
6406
|
+
const { bound, duplicates } = wrapFieldAttrs(source, content, elements, (at) => at, "bcmsField");
|
|
6407
|
+
if (duplicates.length > 0) {
|
|
6408
|
+
refused.set(refusalKey(dialect, slug), `${entry.path} has ${duplicates.join(", ")} carrying more than one data-bcms-field, so there is no single address to rewrite.`);
|
|
6409
|
+
continue;
|
|
6410
|
+
}
|
|
6411
|
+
const markerEnd = content.indexOf(older) + older.length;
|
|
6412
|
+
source.overwrite(markerEnd - older.length, markerEnd, SECTION_MARKER);
|
|
6413
|
+
const stripped = stripBindFallback(parsed, source, content, helperLocalsFor("bcmsSection"));
|
|
6414
|
+
if (stripped !== "ok") {
|
|
6415
|
+
refused.set(
|
|
6416
|
+
refusalKey(dialect, slug),
|
|
6417
|
+
stripped === "unreadable" ? `${entry.path} carries our marker but its module could not be read as a program, so the Phase A \`bind ?? "\u2026"\` fallback cannot be shown to be gone.` : `${entry.path} carries a \`?? \` fallback in a call this cannot identify as ${helperModule}'s bcmsSection, so the Phase A page-group default cannot be shown to be gone.`
|
|
6418
|
+
);
|
|
6419
|
+
continue;
|
|
6420
|
+
}
|
|
6421
|
+
if (bound > 0) {
|
|
6422
|
+
const locals = generatedBindings(parsed);
|
|
6423
|
+
const names = /* @__PURE__ */ new Set([...topLevelBindings(dialect, parsed, content), ...locals.keys()]);
|
|
6424
|
+
const fieldsLocal = [...helperLocalsFor("bcmsFields")][0];
|
|
6425
|
+
if (fieldsLocal === void 0 && names.has("bcmsFields")) {
|
|
6426
|
+
refused.set(
|
|
6427
|
+
refusalKey(dialect, slug),
|
|
6428
|
+
`${entry.path} already binds "bcmsFields" to something other than ${helperModule}'s export.`
|
|
6429
|
+
);
|
|
6430
|
+
continue;
|
|
6431
|
+
}
|
|
6432
|
+
const declared = names.has("bcmsField");
|
|
6433
|
+
if (declared && (fieldsLocal === void 0 || !isCallOf(locals.get("bcmsField"), fieldsLocal))) {
|
|
6434
|
+
refused.set(
|
|
6435
|
+
refusalKey(dialect, slug),
|
|
6436
|
+
`${entry.path} already binds "bcmsField" to something other than a call of ${helperModule}'s bcmsFields.`
|
|
6437
|
+
);
|
|
6438
|
+
continue;
|
|
6439
|
+
}
|
|
6440
|
+
if (!declared) {
|
|
6441
|
+
const anchor = V1_ANCHOR[dialect];
|
|
6442
|
+
const at = anchor ? content.indexOf(anchor) : -1;
|
|
6443
|
+
if (anchor === void 0 || at < 0) {
|
|
6444
|
+
refused.set(refusalKey(dialect, slug), `${entry.path} carries our marker but no longer declares its placement, so there is nowhere the chooser can be put.`);
|
|
6445
|
+
continue;
|
|
6446
|
+
}
|
|
6447
|
+
source.appendLeft(
|
|
6448
|
+
at + anchor.length,
|
|
6449
|
+
`
|
|
6450
|
+
${dialect === "astro" ? "" : " "}const bcmsField = ${fieldsLocal ?? "bcmsFields"}({ blockId, bind, overrides }, ${JSON.stringify(pathPrefix)});`
|
|
6451
|
+
);
|
|
6452
|
+
}
|
|
6453
|
+
if (fieldsLocal === void 0) {
|
|
6454
|
+
const helper = imports.find(([, from]) => from.imported === "bcmsSection")?.[1].specifier ?? relativeImport(entry.path, HELPER_PATH[dialect]).replace(/\.tsx?$/, "");
|
|
6455
|
+
source.appendLeft(markerEnd, `
|
|
6456
|
+
import { bcmsFields } from "${helper}";`);
|
|
6457
|
+
}
|
|
6458
|
+
}
|
|
6459
|
+
files.push({ path: entry.path, operation: "modify", content: source.toString() });
|
|
6460
|
+
}
|
|
6461
|
+
}
|
|
6462
|
+
return { files, refused };
|
|
6463
|
+
}
|
|
6464
|
+
function propPrefix(section) {
|
|
6465
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
6466
|
+
for (const field of section.fields ?? []) {
|
|
6467
|
+
const { path, key } = field;
|
|
6468
|
+
if (typeof path !== "string" || typeof key !== "string") continue;
|
|
6469
|
+
if (!path.endsWith(key)) return null;
|
|
6470
|
+
prefixes.add(path.slice(0, path.length - key.length));
|
|
6471
|
+
}
|
|
6472
|
+
if (prefixes.size > 1) return null;
|
|
6473
|
+
return prefixes.size === 1 ? [...prefixes][0] : `${section.groupKey}.`;
|
|
6474
|
+
}
|
|
6475
|
+
function referencedNames2(markup) {
|
|
6476
|
+
const out = /* @__PURE__ */ new Set();
|
|
6477
|
+
for (const [name] of markup.matchAll(/[A-Za-z_$][\w$]*/g)) out.add(name);
|
|
6478
|
+
return out;
|
|
5600
6479
|
}
|
|
5601
6480
|
var alias = (name, local) => name === local ? name : `${name} as ${local}`;
|
|
5602
6481
|
var mentions = (markup, name) => new RegExp(`(?<![\\w$])${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w$])`).test(markup);
|
|
@@ -5712,9 +6591,9 @@ function registryFile(dialect, path, written, shipped) {
|
|
|
5712
6591
|
const entries = [...written].sort(([a], [b]) => a.localeCompare(b));
|
|
5713
6592
|
const imports = entries.map(([, component]) => {
|
|
5714
6593
|
const specifier = relativeImport(path, component.path);
|
|
5715
|
-
return
|
|
6594
|
+
return importLine(component.name, dialect === "astro" ? specifier : specifier.replace(/\.tsx?$/, ""), component);
|
|
5716
6595
|
}).join("\n");
|
|
5717
|
-
const registry = entries.map(([slug, component]) => ` ${JSON.stringify(slug)}: ${component
|
|
6596
|
+
const registry = entries.map(([slug, component]) => ` ${JSON.stringify(slug)}: ${registryValue(component)},`).join("\n");
|
|
5718
6597
|
const shippedTable = [...shipped].sort(([a], [b]) => a.localeCompare(b)).map(([page, list]) => ` ${JSON.stringify(page)}: ${JSON.stringify(list)},`).join("\n");
|
|
5719
6598
|
const lib = relativeImport(path, SECTIONS_LIB).replace(/\.tsx?$/, "");
|
|
5720
6599
|
const registryType = dialect === "astro" ? "Record<string, unknown>" : "Record<string, (props: BcmsSectionProps) => JSX.Element | null>";
|
|
@@ -5822,17 +6701,24 @@ function splicePageInline(file, content, dialect, parsed, placeable, pageSlug, w
|
|
|
5822
6701
|
const snapshot = names.get("pageSnapshot");
|
|
5823
6702
|
const source = new MagicString3(content);
|
|
5824
6703
|
for (const resolved of placeable) {
|
|
6704
|
+
const component = written.get(resolved.slug);
|
|
6705
|
+
const tag = component.member ? `${names.get(resolved.slug)}.${component.member}` : names.get(resolved.slug);
|
|
5825
6706
|
source.overwrite(
|
|
5826
6707
|
resolved.root.start,
|
|
5827
6708
|
resolved.root.end,
|
|
5828
|
-
`<${
|
|
6709
|
+
`<${tag} bind=${JSON.stringify(resolved.section.groupKey)} page={${snapshot}(${JSON.stringify(pageSlug)})} />`
|
|
5829
6710
|
);
|
|
5830
6711
|
}
|
|
5831
6712
|
const lib = relativeImport(file, SECTIONS_LIB).replace(/\.tsx?$/, "");
|
|
5832
6713
|
const imports = [
|
|
5833
6714
|
...wanted.map(({ key }) => {
|
|
5834
|
-
const
|
|
5835
|
-
|
|
6715
|
+
const component = written.get(key);
|
|
6716
|
+
const specifier = relativeImport(file, component.path);
|
|
6717
|
+
return importLine(
|
|
6718
|
+
names.get(key),
|
|
6719
|
+
dialect === "astro" ? specifier : specifier.replace(/\.tsx?$/, ""),
|
|
6720
|
+
component
|
|
6721
|
+
);
|
|
5836
6722
|
}),
|
|
5837
6723
|
`import { pageSnapshot${snapshot === "pageSnapshot" ? "" : ` as ${snapshot}`} } from "${lib}";`
|
|
5838
6724
|
];
|
|
@@ -5848,7 +6734,7 @@ function addImports(source, parsed, lines) {
|
|
|
5848
6734
|
else source.appendLeft(at.at, `
|
|
5849
6735
|
${lines.join("\n")}`);
|
|
5850
6736
|
}
|
|
5851
|
-
var isGenerated = (content) => content.includes(
|
|
6737
|
+
var isGenerated = (content) => SECTION_MARKERS.some((marker) => content.includes(marker)) || content.includes(REGISTRY_MARKER);
|
|
5852
6738
|
function emitHelper(out, contents, version, overwrite) {
|
|
5853
6739
|
const path = HELPER_PATH.astro;
|
|
5854
6740
|
const source = helperSource("astro");
|
|
@@ -6005,28 +6891,78 @@ async function convertSources(briefIn, sources, options) {
|
|
|
6005
6891
|
);
|
|
6006
6892
|
}
|
|
6007
6893
|
let declaredOnly = 0;
|
|
6894
|
+
const declaredRows = [];
|
|
6895
|
+
const briefRoutes = new Set(brief.pages.map((page) => page.route));
|
|
6896
|
+
const declaredRow = (target, readsFromCms, file) => {
|
|
6897
|
+
declaredRows.push({
|
|
6898
|
+
route: target.route,
|
|
6899
|
+
scope: target.scope,
|
|
6900
|
+
path: target.path,
|
|
6901
|
+
kind: target.kind,
|
|
6902
|
+
...file ? { file } : {},
|
|
6903
|
+
readsFromCms
|
|
6904
|
+
});
|
|
6905
|
+
};
|
|
6008
6906
|
const witnessed = async (target) => {
|
|
6009
6907
|
for (const source of sources) {
|
|
6010
6908
|
const parsed = await parseOf(source.path);
|
|
6011
6909
|
const dialect = dialectOf(source.path, source.content);
|
|
6012
6910
|
if (!parsed || !dialect) continue;
|
|
6013
|
-
if (convertedHere(source.content, parsed, dialect, target, source.path))
|
|
6911
|
+
if (convertedHere(source.content, parsed, dialect, target, source.path) && attributable(target, source.path)) {
|
|
6912
|
+
return source.path;
|
|
6913
|
+
}
|
|
6014
6914
|
}
|
|
6015
|
-
return
|
|
6915
|
+
return null;
|
|
6016
6916
|
};
|
|
6017
6917
|
const locatedByKey = /* @__PURE__ */ new Map();
|
|
6018
6918
|
for (const hit of located) {
|
|
6019
6919
|
const key = keyOf(hit.route, hit.scope ?? "page", hit.path);
|
|
6020
6920
|
if (!locatedByKey.has(key)) locatedByKey.set(key, hit);
|
|
6021
6921
|
}
|
|
6922
|
+
const sharingPath = /* @__PURE__ */ new Map();
|
|
6923
|
+
for (const [key, hit] of locatedByKey) {
|
|
6924
|
+
const held = targets.get(key);
|
|
6925
|
+
if (!held) continue;
|
|
6926
|
+
const id = scopedPath(held.scope, held.path);
|
|
6927
|
+
sharingPath.set(id, [...sharingPath.get(id) ?? [], { key, files: hit.files }]);
|
|
6928
|
+
}
|
|
6929
|
+
const claimedByAnother = (target, file) => (sharingPath.get(scopedPath(target.scope, target.path)) ?? []).some(
|
|
6930
|
+
(other) => other.key !== target.key && other.files.includes(file)
|
|
6931
|
+
);
|
|
6932
|
+
const attributable = (target, file, claimed) => {
|
|
6933
|
+
if (target.scope === "layout") return true;
|
|
6934
|
+
const serves = routeOfFile(file);
|
|
6935
|
+
if (serves !== null && serves !== target.route && briefRoutes.has(serves)) return false;
|
|
6936
|
+
if (serves === target.route && pageFilesFor(target.route, contents.keys()).length !== 1) return false;
|
|
6937
|
+
return claimed !== true;
|
|
6938
|
+
};
|
|
6939
|
+
const declaredByHand = (target, within) => {
|
|
6940
|
+
const candidates = within ?? (target.scope === "layout" ? sources.map((s) => s.path) : pageFilesFor(target.route, contents.keys()));
|
|
6941
|
+
for (const file of candidates) {
|
|
6942
|
+
if (!covers(declarations.get(file), target)) continue;
|
|
6943
|
+
if (attributable(target, file, within !== void 0 && claimedByAnother(target, file))) return file;
|
|
6944
|
+
}
|
|
6945
|
+
return null;
|
|
6946
|
+
};
|
|
6022
6947
|
const missed = /* @__PURE__ */ new Set();
|
|
6023
6948
|
for (const miss of unlocated) {
|
|
6024
6949
|
const key = keyOf(miss.route, miss.scope ?? "page", miss.path);
|
|
6025
6950
|
const target = targets.get(key);
|
|
6026
6951
|
if (!target || locatedByKey.has(key) || missed.has(key)) continue;
|
|
6027
6952
|
missed.add(key);
|
|
6028
|
-
|
|
6029
|
-
|
|
6953
|
+
const witness = await witnessed(target);
|
|
6954
|
+
if (witness !== null) {
|
|
6955
|
+
declaredOnly++;
|
|
6956
|
+
declaredRow(target, true, witness);
|
|
6957
|
+
continue;
|
|
6958
|
+
}
|
|
6959
|
+
const byHand = declaredByHand(target);
|
|
6960
|
+
if (byHand !== null) {
|
|
6961
|
+
declaredOnly++;
|
|
6962
|
+
declaredRow(target, false, byHand);
|
|
6963
|
+
continue;
|
|
6964
|
+
}
|
|
6965
|
+
pend(target, miss.reason === "no-original" ? "NO_ORIGINAL" : "NOT_IN_SOURCE");
|
|
6030
6966
|
}
|
|
6031
6967
|
const byFile = /* @__PURE__ */ new Map();
|
|
6032
6968
|
for (const [key, hit] of locatedByKey) {
|
|
@@ -6084,8 +7020,21 @@ async function convertSources(briefIn, sources, options) {
|
|
|
6084
7020
|
if (worst) {
|
|
6085
7021
|
const file = hit.files[outcomes.indexOf(worst)];
|
|
6086
7022
|
pend(target, worst, file);
|
|
6087
|
-
} else if (outcomes.every((o) => o === "declared"))
|
|
6088
|
-
|
|
7023
|
+
} else if (outcomes.every((o) => o === "declared")) {
|
|
7024
|
+
const witness = await witnessed(target);
|
|
7025
|
+
const byHand = witness === null ? declaredByHand(target, hit.files) : null;
|
|
7026
|
+
if (witness === null && byHand === null) {
|
|
7027
|
+
pend(
|
|
7028
|
+
target,
|
|
7029
|
+
"AMBIGUOUS_LITERAL",
|
|
7030
|
+
hit.files[0],
|
|
7031
|
+
`${hit.files.join(", ")} already declares ${target.path}, and nothing there says it is ${target.route}'s.`
|
|
7032
|
+
);
|
|
7033
|
+
continue;
|
|
7034
|
+
}
|
|
7035
|
+
alreadyDeclared++;
|
|
7036
|
+
declaredRow(target, witness !== null, witness ?? byHand);
|
|
7037
|
+
} else rewritten++;
|
|
6089
7038
|
}
|
|
6090
7039
|
for (const [file, splices] of [...componentEdits].sort(([a], [b]) => a.localeCompare(b))) {
|
|
6091
7040
|
const source = new MagicString4(contents.get(file));
|
|
@@ -6111,12 +7060,27 @@ async function convertSources(briefIn, sources, options) {
|
|
|
6111
7060
|
emitStubs2(dialects, snapshotSlugs, contents, outFiles);
|
|
6112
7061
|
const receipt = assertReceipt({
|
|
6113
7062
|
briefDigest: digest,
|
|
6114
|
-
paths: {
|
|
7063
|
+
paths: {
|
|
7064
|
+
declared: targets.size,
|
|
7065
|
+
rewritten,
|
|
7066
|
+
alreadyDeclared,
|
|
7067
|
+
declarations: declaredRows.sort(
|
|
7068
|
+
(a, b) => a.route.localeCompare(b.route) || a.scope.localeCompare(b.scope) || a.path.localeCompare(b.path)
|
|
7069
|
+
),
|
|
7070
|
+
pending
|
|
7071
|
+
},
|
|
6115
7072
|
occurrences: { located: occurrencesLocated, rewritten: occurrencesRewritten },
|
|
6116
7073
|
files: receiptFiles,
|
|
6117
7074
|
helpers,
|
|
6118
7075
|
helperUpgraded,
|
|
6119
|
-
dynamicBindings
|
|
7076
|
+
dynamicBindings,
|
|
7077
|
+
notes: unreadableConfigs(sources).map(
|
|
7078
|
+
(file) => ({
|
|
7079
|
+
code: "TSCONFIG_UNREADABLE",
|
|
7080
|
+
file,
|
|
7081
|
+
message: `${file} is not JSON this package can read, so its \`paths\` aliases resolved to nothing.`
|
|
7082
|
+
})
|
|
7083
|
+
)
|
|
6120
7084
|
});
|
|
6121
7085
|
return { files: outFiles.sort((a, b) => a.path.localeCompare(b.path)), receipt };
|
|
6122
7086
|
}
|
|
@@ -6384,14 +7348,12 @@ async function plan(file, content, dialect, targets, sites, importAt, outcomes,
|
|
|
6384
7348
|
looped.add(member.key);
|
|
6385
7349
|
outcomes.set(member.key, "rewritten");
|
|
6386
7350
|
}
|
|
6387
|
-
for (const
|
|
7351
|
+
for (const declaration2 of plan2.declarations) {
|
|
6388
7352
|
dynamic.push({
|
|
6389
7353
|
file,
|
|
6390
7354
|
prop: null,
|
|
6391
|
-
path:
|
|
6392
|
-
|
|
6393
|
-
// described a binding the file does not contain, which is what the proposer looks for.
|
|
6394
|
-
callSites: [{ file, literal: `${arrayPath}[\${${plan2.names.index}}].${leaf}` }]
|
|
7355
|
+
path: declaration2.path,
|
|
7356
|
+
callSites: [{ file, literal: declaration2.literal }]
|
|
6395
7357
|
});
|
|
6396
7358
|
}
|
|
6397
7359
|
rewrites.push({
|
|
@@ -6625,7 +7587,7 @@ function emitStubs2(dialects, slugs, contents, out) {
|
|
|
6625
7587
|
}
|
|
6626
7588
|
|
|
6627
7589
|
// src/cli.ts
|
|
6628
|
-
var VERSION = "0.
|
|
7590
|
+
var VERSION = "0.4.0";
|
|
6629
7591
|
var MAX_FILE_BYTES = 512 * 1024;
|
|
6630
7592
|
var CliError = class extends Error {
|
|
6631
7593
|
};
|
|
@@ -6634,6 +7596,7 @@ function parseArgs(argv) {
|
|
|
6634
7596
|
brief: void 0,
|
|
6635
7597
|
componentize: false,
|
|
6636
7598
|
plan: void 0,
|
|
7599
|
+
copy: "bind",
|
|
6637
7600
|
root: ".",
|
|
6638
7601
|
dryRun: false,
|
|
6639
7602
|
receipt: void 0,
|
|
@@ -6647,7 +7610,13 @@ function parseArgs(argv) {
|
|
|
6647
7610
|
if (arg === "--brief") args.brief = argv[++i];
|
|
6648
7611
|
else if (arg === "--componentize") args.componentize = true;
|
|
6649
7612
|
else if (arg === "--plan") args.plan = argv[++i];
|
|
6650
|
-
else if (arg === "--
|
|
7613
|
+
else if (arg === "--copy") {
|
|
7614
|
+
const mode = argv[++i];
|
|
7615
|
+
if (mode !== "bind" && mode !== "instance") {
|
|
7616
|
+
throw new CliError(`--copy takes "bind" or "instance", not ${JSON.stringify(mode ?? "")}. See --help.`);
|
|
7617
|
+
}
|
|
7618
|
+
args.copy = mode;
|
|
7619
|
+
} else if (arg === "--root") args.root = argv[++i] ?? ".";
|
|
6651
7620
|
else if (arg === "--receipt") args.receipt = argv[++i];
|
|
6652
7621
|
else if (arg === "--dry-run") args.dryRun = true;
|
|
6653
7622
|
else if (arg === "--strict") args.strict = true;
|
|
@@ -6671,6 +7640,10 @@ var HELP = `bettercms-convert ${VERSION}
|
|
|
6671
7640
|
--componentize Lift each planned SECTION into a component the CMS can reorder.
|
|
6672
7641
|
--plan <file> The componentize plan, as get_componentize_plan returns it.
|
|
6673
7642
|
Required with --componentize.
|
|
7643
|
+
--copy bind|instance Which copy model the plan is expected to describe. Default: bind
|
|
7644
|
+
(the copy stays in the page's field groups). "instance" is refused
|
|
7645
|
+
\u2014 COPY_MODE_MISMATCH \u2014 unless the plan's placements say the server
|
|
7646
|
+
has already moved it onto them.
|
|
6674
7647
|
--root <dir> The repository to convert. Default: the working directory.
|
|
6675
7648
|
--dry-run Print what would change; write nothing.
|
|
6676
7649
|
--receipt <file> Write the receipt as JSON.
|
|
@@ -6748,14 +7721,14 @@ ${rewritten} of ${declared} paths bound (${alreadyDeclared} already declared, ${
|
|
|
6748
7721
|
}
|
|
6749
7722
|
}
|
|
6750
7723
|
function reportSections(files, receipt, dryRun) {
|
|
6751
|
-
const { extracted, inlineOnly, alreadyExtracted, pending } = receipt.sections;
|
|
7724
|
+
const { extracted, inlineOnly, alreadyExtracted, registered, pending } = receipt.sections;
|
|
6752
7725
|
for (const file of files) {
|
|
6753
7726
|
process.stdout.write(`${dryRun ? "would " : ""}${file.operation} ${file.path}
|
|
6754
7727
|
`);
|
|
6755
7728
|
}
|
|
6756
7729
|
process.stdout.write(
|
|
6757
7730
|
`
|
|
6758
|
-
${extracted} sections extracted (${inlineOnly} inline-only, ${alreadyExtracted} already extracted, ${pending.length} pending)
|
|
7731
|
+
${extracted} sections extracted (${registered} registered, ${inlineOnly} inline-only, ${alreadyExtracted} already extracted, ${pending.length} pending), copy: ${receipt.copy}
|
|
6759
7732
|
`
|
|
6760
7733
|
);
|
|
6761
7734
|
for (const section of pending) {
|
|
@@ -6775,6 +7748,9 @@ async function main() {
|
|
|
6775
7748
|
if (args.plan && !args.componentize) {
|
|
6776
7749
|
throw new CliError("--plan is only read with --componentize. See --help.");
|
|
6777
7750
|
}
|
|
7751
|
+
if (args.copy === "instance" && !args.componentize) {
|
|
7752
|
+
throw new CliError("--copy is only read with --componentize. See --help.");
|
|
7753
|
+
}
|
|
6778
7754
|
const root = resolve(args.root);
|
|
6779
7755
|
const brief = JSON.parse(await readFile(resolve(args.brief), "utf-8"));
|
|
6780
7756
|
if (!Array.isArray(brief.pages)) {
|
|
@@ -6808,7 +7784,7 @@ async function main() {
|
|
|
6808
7784
|
brief,
|
|
6809
7785
|
plan2,
|
|
6810
7786
|
[...sources, ...await readSnapshots(root)],
|
|
6811
|
-
{ overwriteHelper: args.overwriteHelper }
|
|
7787
|
+
{ overwriteHelper: args.overwriteHelper, copy: args.copy }
|
|
6812
7788
|
);
|
|
6813
7789
|
await written(files2);
|
|
6814
7790
|
await saveReceipt(receipt2);
|