@bettercms-ai/convert 0.3.0 → 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 +232 -0
- package/README.md +97 -16
- package/dist/cli.js +582 -68
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +51 -4
- package/dist/index.js +564 -64
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -128,7 +128,7 @@ function propsAttribute(props) {
|
|
|
128
128
|
|
|
129
129
|
// src/helper.ts
|
|
130
130
|
import { createHash as createHash2 } from "crypto";
|
|
131
|
-
var HELPER_VERSION =
|
|
131
|
+
var HELPER_VERSION = 4;
|
|
132
132
|
var HEADER = "// @bettercms-ai/convert helper v";
|
|
133
133
|
var HELPER_PATH = Object.fromEntries(
|
|
134
134
|
Object.keys(DIALECT_RULES).map((d) => [d, DIALECT_RULES[d].helper])
|
|
@@ -211,32 +211,97 @@ export function bcmsRows<T>(page: unknown, path: string, fallback: T[], slug?: s
|
|
|
211
211
|
/**
|
|
212
212
|
* ONE SECTION's copy, for a component extracted by \`--componentize\`.
|
|
213
213
|
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
214
|
+
* OVERRIDES FIRST, PER KEY: \`overrides[key] ?? group[key]\`. An instance that owns its copy
|
|
215
|
+
* (Phase B) carries an override for every prop it declares, so the group underneath it is never
|
|
216
|
+
* read; a placement still bound to a page field group (Phase A) carries none, so the group is all
|
|
217
|
+
* there is. The interesting case is the one in between \u2014 a section half moved \u2014 where a key the
|
|
218
|
+
* instance does not hold has to keep rendering the page's value rather than nothing.
|
|
219
|
+
*
|
|
220
|
+
* \u{1F534} \`??\`, NOT \`||\`. An empty string is a value an editor typed, and treating it as absent put
|
|
221
|
+
* the page's old copy back into a heading somebody had deliberately cleared.
|
|
222
|
+
*
|
|
223
|
+
* With neither an override nor a group, the answer is \`{}\` and every \`bcms(...)\` below renders
|
|
224
|
+
* the fallback the template shipped with \u2014 which is why a section renders correctly against the
|
|
225
|
+
* committed \`{}\` stub.
|
|
218
226
|
*/
|
|
219
227
|
export function bcmsSection(page: unknown, bind?: string, overrides?: unknown): Record<string, unknown> {
|
|
220
|
-
// \u{1F534} AN EMPTY OBJECT IS AN ANSWER. \`overrides: {}\` is an instance an editor cleared, and
|
|
221
|
-
// "\`??\` unless it happens to be empty" quietly put the page's copy back into it.
|
|
222
|
-
if (isRecord(overrides)) return overrides;
|
|
223
228
|
const group = bind ? readPath(page, bind) : undefined;
|
|
224
|
-
|
|
229
|
+
const base: Record<string, unknown> = isRecord(group) ? { ...group } : {};
|
|
230
|
+
if (!isRecord(overrides)) return base;
|
|
231
|
+
for (const key of Object.keys(overrides)) {
|
|
232
|
+
const value = overrides[key];
|
|
233
|
+
if (value === undefined || value === null) continue;
|
|
234
|
+
/**
|
|
235
|
+
* \u{1F534} A TABLE PROP IS STORED AS \`{ rows: [...] }\` AND READ AS A LIST. That is the shape the
|
|
236
|
+
* dashboard edits a repeater in; handing it to \`bcmsRows\` unchanged is not an array, so the
|
|
237
|
+
* helper fell back to the rows the template shipped with and the editor's work never showed.
|
|
238
|
+
*/
|
|
239
|
+
base[key] = isRecord(value) && Array.isArray(value.rows) ? value.rows : value;
|
|
240
|
+
}
|
|
241
|
+
return base;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* WHICH ADDRESS one bound element declares \u2014 decided at RENDER time, from the placement.
|
|
246
|
+
*
|
|
247
|
+
* An element carries exactly ONE \`data-bcms-field\`, and which lane it names depends on where its
|
|
248
|
+
* copy lives. A placement that owns its copy declares the BLOCK lane \u2014
|
|
249
|
+
* \`<blockId>__overrides.<propKey>\` \u2014 because that is the address the editor commits an instance's
|
|
250
|
+
* own value to. A placement bound to a page field group declares the PAGE path, which is what the
|
|
251
|
+
* page form and the coverage meter already know.
|
|
252
|
+
*
|
|
253
|
+
* \u{1F534} THE BOUND ADDRESS IS RE-BASED ONTO **THIS** PLACEMENT'S GROUP. The markup was cut from ONE
|
|
254
|
+
* page and carries that page's paths (\`hero.title\`); the whole point of \`shapeHash\` is that a
|
|
255
|
+
* second page renders the same component bound to a group of its own (\`bind="landingHero"\`).
|
|
256
|
+
* Handing the embedded path through unchanged declared \`hero.title\` on a page whose field is
|
|
257
|
+
* \`landingHero.title\` \u2014 an address that page does not have, so the editor wrote into nothing and
|
|
258
|
+
* the meter counted a binding that was never there. The prefix the markup was cut with comes out,
|
|
259
|
+
* the placement's own group goes in.
|
|
260
|
+
*
|
|
261
|
+
* \u{1F534} THE SAME EXTRACTED FILE SERVES BOTH LANES. The choice is a function of the props, never of
|
|
262
|
+
* how the codemod ran: one component renders a Phase A placement on one page and a Phase B
|
|
263
|
+
* instance on the next, and a repository does not have to be re-cut when its copy moves.
|
|
264
|
+
*/
|
|
265
|
+
export function bcmsFields(
|
|
266
|
+
placement: { blockId?: string; bind?: string; overrides?: unknown },
|
|
267
|
+
pathPrefix: string,
|
|
268
|
+
): (path: string) => string {
|
|
269
|
+
const id = placement.blockId;
|
|
270
|
+
const own = typeof id === "string" && id !== "" && !placement.bind && isRecord(placement.overrides);
|
|
271
|
+
/**
|
|
272
|
+
* What replaces the prefix the markup was cut with, or null for "leave every path alone".
|
|
273
|
+
*
|
|
274
|
+
* Null covers a placement with neither a bind nor an instance \u2014 a shipped fallback, or a page
|
|
275
|
+
* nobody has componentized yet \u2014 and a bound placement in a component whose prefix is EMPTY,
|
|
276
|
+
* where the plan said the prop key IS the whole page path: there is no group segment to swap,
|
|
277
|
+
* so prepending one would invent an address rather than correct it.
|
|
278
|
+
*/
|
|
279
|
+
const lane = own ? \`\${id}__overrides.\` : placement.bind && pathPrefix ? \`\${placement.bind}.\` : null;
|
|
280
|
+
return (path: string): string => {
|
|
281
|
+
// A path outside the group this section was cut from is not one this placement re-addresses:
|
|
282
|
+
// it names a key the instance does not hold and a group this placement is not bound to.
|
|
283
|
+
// (An EMPTY prefix is a plan saying the prop key IS the page path, and matches everything.)
|
|
284
|
+
if (lane === null || !path.startsWith(pathPrefix)) return path;
|
|
285
|
+
return lane + path.slice(pathPrefix.length);
|
|
286
|
+
};
|
|
225
287
|
}
|
|
226
288
|
`;
|
|
227
289
|
}
|
|
228
290
|
var HISTORICAL = {
|
|
229
291
|
"src/bcms-content.ts": {
|
|
230
292
|
1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
|
|
231
|
-
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0"
|
|
293
|
+
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0",
|
|
294
|
+
3: "d4305b2983adcb456ef782d35ea9d083a04465c2b68baa0d44a084dfa01a9227"
|
|
232
295
|
},
|
|
233
296
|
"src/lib/bcms-content.ts": {
|
|
234
297
|
1: "ae72ae14d706943d481c880d70da5789b4816ba1ba319a10fbc63ded4d0b42f2",
|
|
235
|
-
2: "a18731eb42f7db55dac0591c45f873758cde8e103fefcd6e5caf67936ae6a002"
|
|
298
|
+
2: "a18731eb42f7db55dac0591c45f873758cde8e103fefcd6e5caf67936ae6a002",
|
|
299
|
+
3: "0be9c73160361882a038552bc02673c14ed532287823dabaafe36179bc55ed7e"
|
|
236
300
|
},
|
|
237
301
|
"composables/bcms-content.ts": {
|
|
238
302
|
1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
|
|
239
|
-
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0"
|
|
303
|
+
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0",
|
|
304
|
+
3: "d4305b2983adcb456ef782d35ea9d083a04465c2b68baa0d44a084dfa01a9227"
|
|
240
305
|
}
|
|
241
306
|
};
|
|
242
307
|
var sha256 = (value) => createHash2("sha256").update(value, "utf8").digest("hex");
|
|
@@ -4849,6 +4914,7 @@ function readPlan(value) {
|
|
|
4849
4914
|
const section = check.record(held, at2);
|
|
4850
4915
|
check.segment(section.groupKey, `${at2}.groupKey`);
|
|
4851
4916
|
check.optionalString(section.sectionType, `${at2}.sectionType`);
|
|
4917
|
+
check.optionalString(section.copy, `${at2}.copy`);
|
|
4852
4918
|
if (section.fields !== void 0) {
|
|
4853
4919
|
for (const [f, field] of check.array(section.fields, `${at2}.fields`).entries()) {
|
|
4854
4920
|
const shape = check.record(field, `${at2}.fields[${f}]`);
|
|
@@ -5246,7 +5312,8 @@ function assertSections(total, sections) {
|
|
|
5246
5312
|
}
|
|
5247
5313
|
return sections;
|
|
5248
5314
|
}
|
|
5249
|
-
var SECTION_MARKER = "// @bettercms-ai/convert section
|
|
5315
|
+
var SECTION_MARKER = "// @bettercms-ai/convert section v2";
|
|
5316
|
+
var SECTION_MARKERS = [SECTION_MARKER, "// @bettercms-ai/convert section v1"];
|
|
5250
5317
|
var REGISTRY_MARKER = "// @bettercms-ai/convert sections v1";
|
|
5251
5318
|
var COMPONENT_DIR = "src/components/bcms";
|
|
5252
5319
|
var SECTIONS_LIB = "src/lib/bcms-sections.ts";
|
|
@@ -5268,12 +5335,53 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5268
5335
|
let registered = 0;
|
|
5269
5336
|
let planPending = 0;
|
|
5270
5337
|
let total = 0;
|
|
5338
|
+
const pend = (route, groupKey, reason, message) => void pending.push({ route, groupKey, reason, ...message ? { message } : {} });
|
|
5271
5339
|
const brief = readBrief(briefIn);
|
|
5272
5340
|
const plan2 = readPlan(planIn);
|
|
5341
|
+
const copy = options?.copy ?? "bind";
|
|
5342
|
+
const instanceOwned = (section) => section.copy === "instance";
|
|
5343
|
+
const notes = unreadableConfigs(sources).map(
|
|
5344
|
+
(path) => ({
|
|
5345
|
+
code: "TSCONFIG_UNREADABLE",
|
|
5346
|
+
file: path,
|
|
5347
|
+
message: `${path} is not JSON this package can read, so its \`paths\` aliases resolved to nothing.`
|
|
5348
|
+
})
|
|
5349
|
+
);
|
|
5350
|
+
const refuseRun = (reason, message) => {
|
|
5351
|
+
for (const page of plan2.pages ?? []) {
|
|
5352
|
+
for (const section of page.sections ?? []) {
|
|
5353
|
+
if (section.pending) {
|
|
5354
|
+
planPending++;
|
|
5355
|
+
continue;
|
|
5356
|
+
}
|
|
5357
|
+
total++;
|
|
5358
|
+
pend(page.route, section.groupKey, reason, message);
|
|
5359
|
+
}
|
|
5360
|
+
}
|
|
5361
|
+
return {
|
|
5362
|
+
files: [],
|
|
5363
|
+
receipt: {
|
|
5364
|
+
planDigest: plan2.digest ?? "",
|
|
5365
|
+
copy,
|
|
5366
|
+
sections: assertSections(total, { extracted: 0, inlineOnly: 0, alreadyExtracted: 0, registered: 0, pending }),
|
|
5367
|
+
components: [],
|
|
5368
|
+
planPending,
|
|
5369
|
+
notes
|
|
5370
|
+
}
|
|
5371
|
+
};
|
|
5372
|
+
};
|
|
5373
|
+
const movesCopy = (plan2.pages ?? []).some(
|
|
5374
|
+
(page) => (page.sections ?? []).some((section) => !section.pending && instanceOwned(section))
|
|
5375
|
+
);
|
|
5376
|
+
if (copy === "instance" && !movesCopy) {
|
|
5377
|
+
return refuseRun(
|
|
5378
|
+
"COPY_MODE_MISMATCH",
|
|
5379
|
+
'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.'
|
|
5380
|
+
);
|
|
5381
|
+
}
|
|
5273
5382
|
const contents = new Map(sources.map((s) => [s.path, s.content]));
|
|
5274
5383
|
const fileSet = new Set(contents.keys());
|
|
5275
5384
|
const aliases = aliasesFrom(sources);
|
|
5276
|
-
const pend = (route, groupKey, reason, message) => void pending.push({ route, groupKey, reason, ...message ? { message } : {} });
|
|
5277
5385
|
const trees = /* @__PURE__ */ new Map();
|
|
5278
5386
|
const treeOf = async (file) => {
|
|
5279
5387
|
if (trees.has(file)) return trees.get(file);
|
|
@@ -5287,7 +5395,9 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5287
5395
|
const helperPath = HELPER_PATH.astro;
|
|
5288
5396
|
const helperExisting = contents.get(helperPath);
|
|
5289
5397
|
const helperVersion = helperExisting === void 0 ? null : isKnownHelper(helperExisting, "astro");
|
|
5290
|
-
|
|
5398
|
+
if (helperExisting !== void 0 && helperVersion === null && options?.overwriteHelper !== true) {
|
|
5399
|
+
return refuseRun("HELPER_CONFLICT", `${helperPath} is not a module this package wrote.`);
|
|
5400
|
+
}
|
|
5291
5401
|
const outFiles = /* @__PURE__ */ new Map();
|
|
5292
5402
|
const written = /* @__PURE__ */ new Map();
|
|
5293
5403
|
const shipped = /* @__PURE__ */ new Map();
|
|
@@ -5297,6 +5407,29 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5297
5407
|
return held;
|
|
5298
5408
|
};
|
|
5299
5409
|
const fresh = /* @__PURE__ */ new Set();
|
|
5410
|
+
const bindOf = (section) => instanceOwned(section) ? void 0 : section.groupKey;
|
|
5411
|
+
const placementOf = (slug, section) => {
|
|
5412
|
+
const bind = bindOf(section);
|
|
5413
|
+
return bind === void 0 ? { slug } : { slug, bind };
|
|
5414
|
+
};
|
|
5415
|
+
const rebind = (dialect, pageSlug, all, usable) => {
|
|
5416
|
+
if (dialect === null || !EXTRACTABLE.includes(dialect)) return;
|
|
5417
|
+
const order = shipped.get(dialect);
|
|
5418
|
+
const held = order?.get(pageSlug);
|
|
5419
|
+
if (!order || !held) return;
|
|
5420
|
+
const queues = /* @__PURE__ */ new Map();
|
|
5421
|
+
for (const section of all) {
|
|
5422
|
+
const slug = resolveComponent(plan2, section).slug;
|
|
5423
|
+
queues.set(slug, [...queues.get(slug) ?? [], section]);
|
|
5424
|
+
}
|
|
5425
|
+
order.set(
|
|
5426
|
+
pageSlug,
|
|
5427
|
+
held.map((placement) => {
|
|
5428
|
+
const section = queues.get(placement.slug)?.shift();
|
|
5429
|
+
return section && usable.has(section) ? placementOf(placement.slug, section) : placement;
|
|
5430
|
+
})
|
|
5431
|
+
);
|
|
5432
|
+
};
|
|
5300
5433
|
const registryConflicts = [];
|
|
5301
5434
|
const registryUnreadable = [];
|
|
5302
5435
|
for (const dialect of EXTRACTABLE) {
|
|
@@ -5322,6 +5455,11 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5322
5455
|
if (lib !== void 0 && !isGenerated(lib)) registryConflicts.push(SECTIONS_LIB);
|
|
5323
5456
|
const registryBlocked = registryConflicts.length > 0 && options?.overwriteHelper !== true;
|
|
5324
5457
|
const registryUnread = registryUnreadable.length > 0;
|
|
5458
|
+
const migrated = registryBlocked || registryUnread ? { files: [], refused: /* @__PURE__ */ new Map() } : await migrateSections(plan2, contents, written, fileSet, aliases);
|
|
5459
|
+
for (const file of migrated.files) {
|
|
5460
|
+
addFile(outFiles, contents, file);
|
|
5461
|
+
contents.set(file.path, file.content);
|
|
5462
|
+
}
|
|
5325
5463
|
for (const page of plan2.pages ?? []) {
|
|
5326
5464
|
const sections2 = (page.sections ?? []).filter((section) => {
|
|
5327
5465
|
if (section.pending) {
|
|
@@ -5332,23 +5470,33 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5332
5470
|
});
|
|
5333
5471
|
total += sections2.length;
|
|
5334
5472
|
if (sections2.length === 0) continue;
|
|
5473
|
+
const prefixes = /* @__PURE__ */ new Map();
|
|
5474
|
+
for (const section of sections2) {
|
|
5475
|
+
const prefix = propPrefix(section);
|
|
5476
|
+
if (prefix === null) {
|
|
5477
|
+
pend(
|
|
5478
|
+
page.route,
|
|
5479
|
+
section.groupKey,
|
|
5480
|
+
"PROP_MAP_INCONSISTENT",
|
|
5481
|
+
"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."
|
|
5482
|
+
);
|
|
5483
|
+
continue;
|
|
5484
|
+
}
|
|
5485
|
+
prefixes.set(section, prefix);
|
|
5486
|
+
}
|
|
5487
|
+
const usable = sections2.filter((section) => prefixes.has(section));
|
|
5488
|
+
if (usable.length === 0) continue;
|
|
5335
5489
|
const pendingBefore = pending.length;
|
|
5336
5490
|
const briefPage = brief.pages.find((p) => p.route === page.route);
|
|
5337
5491
|
const pageSlug = briefPage?.slug ?? slugify(page.route === "/" ? "home" : page.route);
|
|
5338
|
-
if (helperBlocked) {
|
|
5339
|
-
for (const section of sections2) {
|
|
5340
|
-
pend(page.route, section.groupKey, "HELPER_CONFLICT", `${helperPath} is not a module this package wrote.`);
|
|
5341
|
-
}
|
|
5342
|
-
continue;
|
|
5343
|
-
}
|
|
5344
5492
|
if (registryBlocked) {
|
|
5345
|
-
for (const section of
|
|
5493
|
+
for (const section of usable) {
|
|
5346
5494
|
pend(page.route, section.groupKey, "REGISTRY_CONFLICT", `${registryConflicts.join(", ")} is not a file this package wrote.`);
|
|
5347
5495
|
}
|
|
5348
5496
|
continue;
|
|
5349
5497
|
}
|
|
5350
5498
|
if (registryUnread) {
|
|
5351
|
-
for (const section of
|
|
5499
|
+
for (const section of usable) {
|
|
5352
5500
|
pend(
|
|
5353
5501
|
page.route,
|
|
5354
5502
|
section.groupKey,
|
|
@@ -5360,30 +5508,43 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5360
5508
|
}
|
|
5361
5509
|
const snapshotPath = `${CONTENT_DIR}/${pageSlug}.json`;
|
|
5362
5510
|
if (!snapshotUsable(contents.get(snapshotPath))) {
|
|
5363
|
-
for (const section of
|
|
5511
|
+
for (const section of usable) {
|
|
5364
5512
|
pend(page.route, section.groupKey, "SNAPSHOT_INVALID", `${snapshotPath} is not a JSON object.`);
|
|
5365
5513
|
}
|
|
5366
5514
|
continue;
|
|
5367
5515
|
}
|
|
5368
5516
|
const pageFiles = pageFilesFor(page.route, contents.keys());
|
|
5369
5517
|
if (pageFiles.length > 1) {
|
|
5370
|
-
for (const section of
|
|
5518
|
+
for (const section of usable) {
|
|
5371
5519
|
pend(page.route, section.groupKey, "ROUTE_FILE_AMBIGUOUS", `${pageFiles.join(" and ")} both serve ${page.route}.`);
|
|
5372
5520
|
}
|
|
5373
5521
|
continue;
|
|
5374
5522
|
}
|
|
5375
5523
|
const file = pageFiles[0];
|
|
5376
5524
|
if (file === void 0) {
|
|
5377
|
-
for (const section of
|
|
5525
|
+
for (const section of usable) {
|
|
5378
5526
|
pend(page.route, section.groupKey, "NOT_IN_SOURCE", `no page file found for ${page.route}.`);
|
|
5379
5527
|
}
|
|
5380
5528
|
continue;
|
|
5381
5529
|
}
|
|
5382
|
-
const
|
|
5530
|
+
const pageDialect = dialectOf(file, contents.get(file) ?? "");
|
|
5531
|
+
const done = new Set(alreadyOn(contents.get(file) ?? "", pageSlug, usable));
|
|
5532
|
+
let stuck = 0;
|
|
5533
|
+
for (const section of usable) {
|
|
5534
|
+
if (!done.has(section.groupKey)) continue;
|
|
5535
|
+
const why = pageDialect ? migrated.refused.get(refusalKey(pageDialect, resolveComponent(plan2, section).slug)) : void 0;
|
|
5536
|
+
if (why === void 0) continue;
|
|
5537
|
+
done.delete(section.groupKey);
|
|
5538
|
+
stuck++;
|
|
5539
|
+
pend(page.route, section.groupKey, "SECTION_UNMIGRATABLE", why);
|
|
5540
|
+
}
|
|
5383
5541
|
alreadyExtracted += done.size;
|
|
5384
|
-
if (done.size ===
|
|
5542
|
+
if (done.size + stuck === usable.length) {
|
|
5543
|
+
rebind(pageDialect, pageSlug, page.sections ?? [], new Set(usable));
|
|
5544
|
+
continue;
|
|
5545
|
+
}
|
|
5385
5546
|
const found = await locateSections(
|
|
5386
|
-
|
|
5547
|
+
usable.filter((section) => !done.has(section.groupKey)),
|
|
5387
5548
|
briefPage?.paths ?? [],
|
|
5388
5549
|
sources,
|
|
5389
5550
|
treeOf
|
|
@@ -5498,7 +5659,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5498
5659
|
registered++;
|
|
5499
5660
|
registeredHere.push({
|
|
5500
5661
|
slug: component.slug,
|
|
5501
|
-
|
|
5662
|
+
section: held.section,
|
|
5502
5663
|
call: callSiteOf(tree, member ? `${binding.local}.${member}` : held.call)
|
|
5503
5664
|
});
|
|
5504
5665
|
}
|
|
@@ -5510,7 +5671,13 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5510
5671
|
continue;
|
|
5511
5672
|
}
|
|
5512
5673
|
const component = resolveComponent(plan2, placed.section);
|
|
5513
|
-
roots.push({
|
|
5674
|
+
roots.push({
|
|
5675
|
+
section: placed.section,
|
|
5676
|
+
route: page.route,
|
|
5677
|
+
root,
|
|
5678
|
+
pathPrefix: prefixes.get(placed.section),
|
|
5679
|
+
...component
|
|
5680
|
+
});
|
|
5514
5681
|
}
|
|
5515
5682
|
const nested = /* @__PURE__ */ new Set();
|
|
5516
5683
|
for (const a of roots) {
|
|
@@ -5537,7 +5704,25 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5537
5704
|
placeable.push(resolved);
|
|
5538
5705
|
continue;
|
|
5539
5706
|
}
|
|
5540
|
-
const built = componentFile(dialect, path, file, content, parsed, resolved);
|
|
5707
|
+
const built = componentFile(dialect, path, file, content, parsed, resolved, resolved.pathPrefix);
|
|
5708
|
+
if (built.unconvertible.length > 0) {
|
|
5709
|
+
pend(
|
|
5710
|
+
page.route,
|
|
5711
|
+
resolved.section.groupKey,
|
|
5712
|
+
"PROP_MAP_INCONSISTENT",
|
|
5713
|
+
`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.`
|
|
5714
|
+
);
|
|
5715
|
+
continue;
|
|
5716
|
+
}
|
|
5717
|
+
if (built.duplicates.length > 0) {
|
|
5718
|
+
pend(
|
|
5719
|
+
page.route,
|
|
5720
|
+
resolved.section.groupKey,
|
|
5721
|
+
"DUPLICATE_FIELD_BINDING",
|
|
5722
|
+
`${built.duplicates.join(", ")} carries more than one data-bcms-field, so there is no single address to rewrite.`
|
|
5723
|
+
);
|
|
5724
|
+
continue;
|
|
5725
|
+
}
|
|
5541
5726
|
if (built.free.length > 0) {
|
|
5542
5727
|
pend(
|
|
5543
5728
|
page.route,
|
|
@@ -5558,8 +5743,8 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5558
5743
|
}
|
|
5559
5744
|
if (placeable.length === 0 && registeredHere.length === 0) continue;
|
|
5560
5745
|
const spans = [
|
|
5561
|
-
...placeable.map((r) => ({ slug: r.slug, bind: r.section
|
|
5562
|
-
...registeredHere.map((r) => ({ slug: r.slug, bind: r.
|
|
5746
|
+
...placeable.map((r) => ({ slug: r.slug, bind: bindOf(r.section), root: r.root })),
|
|
5747
|
+
...registeredHere.map((r) => ({ slug: r.slug, bind: bindOf(r.section), root: r.call }))
|
|
5563
5748
|
].sort((a, b) => (a.root?.start ?? Number.MAX_SAFE_INTEGER) - (b.root?.start ?? Number.MAX_SAFE_INTEGER));
|
|
5564
5749
|
const known = spans.filter((s) => s.root !== null);
|
|
5565
5750
|
const layout = runOf(known, parsed.roots, content);
|
|
@@ -5575,7 +5760,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5575
5760
|
shipped.set(dialect, order);
|
|
5576
5761
|
order.set(
|
|
5577
5762
|
pageSlug,
|
|
5578
|
-
spans.map((s) =>
|
|
5763
|
+
spans.map((s) => s.bind === void 0 ? { slug: s.slug } : { slug: s.slug, bind: s.bind })
|
|
5579
5764
|
);
|
|
5580
5765
|
if (useRun) {
|
|
5581
5766
|
addFile(outFiles, contents, {
|
|
@@ -5584,13 +5769,25 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5584
5769
|
content: splicePageRun(file, content, dialect, parsed, known, pageSlug)
|
|
5585
5770
|
});
|
|
5586
5771
|
extracted += placeable.length;
|
|
5587
|
-
} else
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5772
|
+
} else {
|
|
5773
|
+
const inline = placeable.filter((resolved) => !instanceOwned(resolved.section));
|
|
5774
|
+
for (const resolved of placeable) {
|
|
5775
|
+
if (inline.includes(resolved)) continue;
|
|
5776
|
+
pend(
|
|
5777
|
+
page.route,
|
|
5778
|
+
resolved.section.groupKey,
|
|
5779
|
+
"INLINE_NEEDS_INSTANCE",
|
|
5780
|
+
`${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.`
|
|
5781
|
+
);
|
|
5782
|
+
}
|
|
5783
|
+
if (inline.length > 0) {
|
|
5784
|
+
addFile(outFiles, contents, {
|
|
5785
|
+
path: file,
|
|
5786
|
+
operation: "modify",
|
|
5787
|
+
content: splicePageInline(file, content, dialect, parsed, inline, pageSlug, extractable)
|
|
5788
|
+
});
|
|
5789
|
+
}
|
|
5790
|
+
inlineOnly += inline.length;
|
|
5594
5791
|
}
|
|
5595
5792
|
}
|
|
5596
5793
|
const componentSlugs = [...written.values()].flatMap((held) => [...held.keys()]);
|
|
@@ -5618,17 +5815,12 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5618
5815
|
files: [...outFiles.values()].sort((a, b) => a.path.localeCompare(b.path)),
|
|
5619
5816
|
receipt: {
|
|
5620
5817
|
planDigest: plan2.digest ?? "",
|
|
5818
|
+
copy,
|
|
5621
5819
|
sections,
|
|
5622
5820
|
// What THIS run wrote a file for. A component an earlier run left is reused, not re-reported.
|
|
5623
5821
|
components: [...fresh].sort(),
|
|
5624
5822
|
planPending,
|
|
5625
|
-
notes
|
|
5626
|
-
(path) => ({
|
|
5627
|
-
code: "TSCONFIG_UNREADABLE",
|
|
5628
|
-
file: path,
|
|
5629
|
-
message: `${path} is not JSON this package can read, so its \`paths\` aliases resolved to nothing.`
|
|
5630
|
-
})
|
|
5631
|
-
)
|
|
5823
|
+
notes
|
|
5632
5824
|
}
|
|
5633
5825
|
};
|
|
5634
5826
|
}
|
|
@@ -5651,7 +5843,7 @@ async function readRegistry(content, dialect, path, files, aliases) {
|
|
|
5651
5843
|
if (!from.specifier.startsWith(".")) continue;
|
|
5652
5844
|
const resolved = resolveSpecifier(path, from.specifier, files, aliases);
|
|
5653
5845
|
if (resolved !== null) {
|
|
5654
|
-
imports.set(local, { name: local, path: resolved, kind: from.kind, imported: from.imported });
|
|
5846
|
+
imports.set(local, { name: local, path: preferDialect(resolved, dialect, files), kind: from.kind, imported: from.imported });
|
|
5655
5847
|
}
|
|
5656
5848
|
}
|
|
5657
5849
|
const registry = blockOf(content, "REGISTRY");
|
|
@@ -5660,6 +5852,7 @@ async function readRegistry(content, dialect, path, files, aliases) {
|
|
|
5660
5852
|
const registryRows = propertiesOf(registry);
|
|
5661
5853
|
const shippedRows = propertiesOf(table);
|
|
5662
5854
|
if (registryRows === null || shippedRows === null) return null;
|
|
5855
|
+
if (registryRows.length === 0) return null;
|
|
5663
5856
|
for (const row of registryRows) {
|
|
5664
5857
|
const named = row.value.match(/^([A-Za-z_$][\w$]*)(?:\.([A-Za-z_$][\w$]*))?$/);
|
|
5665
5858
|
const entry = named ? imports.get(named[1]) : void 0;
|
|
@@ -5669,7 +5862,8 @@ async function readRegistry(content, dialect, path, files, aliases) {
|
|
|
5669
5862
|
for (const row of shippedRows) {
|
|
5670
5863
|
try {
|
|
5671
5864
|
const placements = JSON.parse(row.value);
|
|
5672
|
-
if (!Array.isArray(placements)) return null;
|
|
5865
|
+
if (!Array.isArray(placements) || placements.length === 0 || !placements.every(isPlacement)) return null;
|
|
5866
|
+
if (placements.some((placement) => !components.has(placement.slug))) return null;
|
|
5673
5867
|
shipped.set(row.key, placements);
|
|
5674
5868
|
} catch {
|
|
5675
5869
|
return null;
|
|
@@ -5677,6 +5871,18 @@ async function readRegistry(content, dialect, path, files, aliases) {
|
|
|
5677
5871
|
}
|
|
5678
5872
|
return { components, shipped };
|
|
5679
5873
|
}
|
|
5874
|
+
function preferDialect(resolved, dialect, files) {
|
|
5875
|
+
const wanted = dialect === "astro" ? [".astro"] : [".tsx", ".jsx"];
|
|
5876
|
+
if (wanted.some((extension) => resolved.endsWith(extension))) return resolved;
|
|
5877
|
+
const base = resolved.replace(/\.[^./]+$/, "");
|
|
5878
|
+
return wanted.map((extension) => `${base}${extension}`).find((candidate) => files.has(candidate)) ?? resolved;
|
|
5879
|
+
}
|
|
5880
|
+
function isPlacement(value) {
|
|
5881
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
5882
|
+
const held = value;
|
|
5883
|
+
if (typeof held.slug !== "string") return false;
|
|
5884
|
+
return ["bind", "blockId"].every((key) => held[key] === void 0 || typeof held[key] === "string");
|
|
5885
|
+
}
|
|
5680
5886
|
function blockOf(content, name) {
|
|
5681
5887
|
const declaration2 = content.indexOf(`const ${name}`);
|
|
5682
5888
|
if (declaration2 < 0) return null;
|
|
@@ -5923,14 +6129,35 @@ function pascal(value) {
|
|
|
5923
6129
|
return /^[A-Za-z]/.test(name) ? name : `Section${name}`;
|
|
5924
6130
|
}
|
|
5925
6131
|
var slugify = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "section";
|
|
5926
|
-
function componentFile(dialect, path, fromFile, content, parsed, resolved) {
|
|
5927
|
-
const
|
|
6132
|
+
function componentFile(dialect, path, fromFile, content, parsed, resolved, pathPrefix) {
|
|
6133
|
+
const addresses = fieldAddresses(content, elementsUnder(resolved.root));
|
|
6134
|
+
const unconvertible = [
|
|
6135
|
+
...addresses.heads.filter((address) => !address.startsWith(pathPrefix)),
|
|
6136
|
+
...addresses.opaque
|
|
6137
|
+
];
|
|
6138
|
+
if (unconvertible.length > 0) return { content: "", free: [], duplicates: [], unconvertible };
|
|
6139
|
+
const cut = content.slice(resolved.root.start, resolved.root.end);
|
|
6140
|
+
const reserved = /* @__PURE__ */ new Set([...topLevelBindings(dialect, parsed, content), ...referencedNames2(cut)]);
|
|
6141
|
+
const introduced = allocateNames(reserved, [
|
|
6142
|
+
{ key: "bcmsFields", name: "bcmsFields" },
|
|
6143
|
+
{ key: "bcmsField", name: "bcmsField" }
|
|
6144
|
+
]);
|
|
6145
|
+
const fieldsName = introduced.get("bcmsFields");
|
|
6146
|
+
const fieldName = introduced.get("bcmsField");
|
|
6147
|
+
const { markup, bound, duplicates } = sectionMarkup(content, resolved.root, fieldName);
|
|
6148
|
+
if (duplicates.length > 0) return { content: "", free: [], duplicates, unconvertible: [] };
|
|
5928
6149
|
const groupKey = resolved.section.groupKey;
|
|
5929
6150
|
const locals = helperLocals(parsed, dialect, fromFile);
|
|
5930
6151
|
const helperNames = ["bcms", "bcmsRows", "bcmsLayout"].flatMap(
|
|
5931
6152
|
(name) => locals[name].filter((local) => mentions(markup, local)).map((local) => ({ name, local }))
|
|
5932
6153
|
);
|
|
5933
|
-
const helpers = [
|
|
6154
|
+
const helpers = [
|
|
6155
|
+
.../* @__PURE__ */ new Set([
|
|
6156
|
+
...helperNames.map((h) => alias(h.name, h.local)),
|
|
6157
|
+
"bcmsSection",
|
|
6158
|
+
...bound > 0 ? [alias("bcmsFields", fieldsName)] : []
|
|
6159
|
+
])
|
|
6160
|
+
].sort();
|
|
5934
6161
|
const helperSpecifier = relativeImport(path, HELPER_PATH[dialect]).replace(/\.tsx?$/, "");
|
|
5935
6162
|
const snapshot = snapshotOf(parsed, dialect, fromFile, markup);
|
|
5936
6163
|
const carry = carryFor({
|
|
@@ -5945,20 +6172,23 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved) {
|
|
|
5945
6172
|
"bind",
|
|
5946
6173
|
"overrides",
|
|
5947
6174
|
"page",
|
|
6175
|
+
fieldName,
|
|
5948
6176
|
...helperNames.map((h) => h.local),
|
|
5949
6177
|
...snapshot ? [snapshot] : []
|
|
5950
6178
|
],
|
|
5951
6179
|
// The helper and the snapshots are what the component declares for itself.
|
|
5952
6180
|
skipSpecifier: (repoPath, specifier) => specifier.includes(`${CONTENT_DIR}/`) || repoPath.startsWith(HELPER_PATH[dialect].replace(/\.tsx?$/, ""))
|
|
5953
6181
|
});
|
|
5954
|
-
if (carry.free.length > 0) return { content: "", free: carry.free };
|
|
6182
|
+
if (carry.free.length > 0) return { content: "", free: carry.free, duplicates: [], unconvertible: [] };
|
|
5955
6183
|
const carried = [...carry.imports];
|
|
5956
6184
|
const key = /^[A-Za-z_$][\w$]*$/.test(groupKey) ? groupKey : JSON.stringify(groupKey);
|
|
5957
6185
|
const carriedDeclarations = carry.declarations.length > 0 ? `${carry.declarations.join("\n")}
|
|
5958
6186
|
` : "";
|
|
5959
|
-
const copy = snapshot ? `const ${snapshot} = { ${key}: bcmsSection(page, bind
|
|
6187
|
+
const copy = snapshot ? `const ${snapshot} = { ${key}: bcmsSection(page, bind, overrides) };` : "";
|
|
6188
|
+
const field = bound > 0 ? `const ${fieldName} = ${fieldsName}({ blockId, bind, overrides }, ${JSON.stringify(pathPrefix)});` : "";
|
|
6189
|
+
const declared = [copy, field].filter(Boolean).join("\n");
|
|
5960
6190
|
if (dialect === "astro") {
|
|
5961
|
-
return { free: [], content: `---
|
|
6191
|
+
return { free: [], duplicates: [], unconvertible: [], content: `---
|
|
5962
6192
|
${SECTION_MARKER}
|
|
5963
6193
|
${[...carried, `import { ${helpers.join(", ")} } from "${helperSpecifier}";`].join("\n")}
|
|
5964
6194
|
|
|
@@ -5973,27 +6203,297 @@ interface Props {
|
|
|
5973
6203
|
page?: unknown;
|
|
5974
6204
|
}
|
|
5975
6205
|
const { blockId, bind, overrides, page } = Astro.props;
|
|
5976
|
-
${carriedDeclarations}${
|
|
6206
|
+
${carriedDeclarations}${declared}
|
|
5977
6207
|
---
|
|
5978
6208
|
${markup}
|
|
5979
6209
|
` };
|
|
5980
6210
|
}
|
|
5981
|
-
return { free: [], content: `${SECTION_MARKER}
|
|
6211
|
+
return { free: [], duplicates: [], unconvertible: [], content: `${SECTION_MARKER}
|
|
5982
6212
|
${[...carried, `import { ${helpers.join(", ")} } from "${helperSpecifier}";`, `import type { BcmsSectionProps } from "${relativeImport(path, SECTIONS_LIB).replace(/\.tsx?$/, "")}";`].join("\n")}
|
|
5983
6213
|
${carriedDeclarations === "" ? "" : `
|
|
5984
6214
|
${carriedDeclarations}`}
|
|
5985
6215
|
export default function ${resolved.name}({ blockId, bind, overrides, page }: BcmsSectionProps) {
|
|
5986
|
-
${
|
|
6216
|
+
${declared.split("\n").join("\n ")}
|
|
5987
6217
|
return (
|
|
5988
6218
|
${markup}
|
|
5989
6219
|
);
|
|
5990
6220
|
}
|
|
5991
6221
|
` };
|
|
5992
6222
|
}
|
|
5993
|
-
function
|
|
6223
|
+
function sectionMarkup(content, root, fieldName) {
|
|
5994
6224
|
const slice = new MagicString3(content.slice(root.start, root.end));
|
|
5995
|
-
|
|
5996
|
-
|
|
6225
|
+
const at = (offset) => offset - root.start;
|
|
6226
|
+
const stamps = root.attrs.filter((attr) => attr.name === "data-bcms-block");
|
|
6227
|
+
if (stamps.length === 0) slice.appendLeft(at(root.attrInsertAt), " data-bcms-block={blockId}");
|
|
6228
|
+
else {
|
|
6229
|
+
slice.overwrite(at(stamps[0].range.start), at(stamps[0].range.end), "data-bcms-block={blockId}");
|
|
6230
|
+
for (const extra of stamps.slice(1)) {
|
|
6231
|
+
let from = extra.range.start;
|
|
6232
|
+
while (from > root.start && /\s/.test(content[from - 1])) from--;
|
|
6233
|
+
slice.remove(at(from), at(extra.range.end));
|
|
6234
|
+
}
|
|
6235
|
+
}
|
|
6236
|
+
const wrapped = wrapFieldAttrs(slice, content, elementsUnder(root), at, fieldName);
|
|
6237
|
+
return { markup: slice.toString(), ...wrapped };
|
|
6238
|
+
}
|
|
6239
|
+
function wrapFieldAttrs(slice, content, elements, at, fieldName) {
|
|
6240
|
+
let bound = 0;
|
|
6241
|
+
const duplicates = [];
|
|
6242
|
+
for (const element of elements) {
|
|
6243
|
+
const fields = element.attrs.filter((attr) => attr.name === "data-bcms-field");
|
|
6244
|
+
if (fields.length > 1) {
|
|
6245
|
+
duplicates.push(`<${element.tag}>`);
|
|
6246
|
+
continue;
|
|
6247
|
+
}
|
|
6248
|
+
for (const attr of fields) {
|
|
6249
|
+
bound++;
|
|
6250
|
+
if (attr.expressionRange) {
|
|
6251
|
+
slice.appendRight(at(attr.expressionRange.start) + 1, `${fieldName}(`);
|
|
6252
|
+
slice.appendLeft(at(attr.expressionRange.end) - 1, ")");
|
|
6253
|
+
continue;
|
|
6254
|
+
}
|
|
6255
|
+
const value = attr.valueRange ? content.slice(attr.valueRange.start, attr.valueRange.end) : "";
|
|
6256
|
+
slice.overwrite(
|
|
6257
|
+
at(attr.range.start),
|
|
6258
|
+
at(attr.range.end),
|
|
6259
|
+
`data-bcms-field={${fieldName}(${JSON.stringify(value)})}`
|
|
6260
|
+
);
|
|
6261
|
+
}
|
|
6262
|
+
}
|
|
6263
|
+
return { bound, duplicates };
|
|
6264
|
+
}
|
|
6265
|
+
function provableAddress(expression) {
|
|
6266
|
+
const first = expression[0];
|
|
6267
|
+
if (first === '"' || first === "'") {
|
|
6268
|
+
for (let at = 1; at < expression.length; at++) {
|
|
6269
|
+
if (expression[at] === "\\") {
|
|
6270
|
+
at++;
|
|
6271
|
+
continue;
|
|
6272
|
+
}
|
|
6273
|
+
if (expression[at] === first) return at === expression.length - 1 ? expression.slice(1, at) : null;
|
|
6274
|
+
}
|
|
6275
|
+
return null;
|
|
6276
|
+
}
|
|
6277
|
+
if (first !== "`" || expression.length < 2 || !expression.endsWith("`")) return null;
|
|
6278
|
+
const body = expression.slice(1, -1);
|
|
6279
|
+
if (body.includes("`") || body.includes("\\")) return null;
|
|
6280
|
+
let head = null;
|
|
6281
|
+
for (let at = 0; at < body.length; at++) {
|
|
6282
|
+
if (body[at] !== "$" || body[at + 1] !== "{") continue;
|
|
6283
|
+
const close = body.indexOf("}", at + 2);
|
|
6284
|
+
if (close < 0) return null;
|
|
6285
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(body.slice(at + 2, close).trim())) return null;
|
|
6286
|
+
if (body[at - 1] !== "[" || body[close + 1] !== "]") return null;
|
|
6287
|
+
head ??= body.slice(0, at);
|
|
6288
|
+
at = close;
|
|
6289
|
+
}
|
|
6290
|
+
return head ?? body;
|
|
6291
|
+
}
|
|
6292
|
+
function fieldAddresses(content, elements) {
|
|
6293
|
+
const heads = [];
|
|
6294
|
+
const opaque = [];
|
|
6295
|
+
for (const element of elements) {
|
|
6296
|
+
for (const attr of element.attrs) {
|
|
6297
|
+
if (attr.name !== "data-bcms-field") continue;
|
|
6298
|
+
if (!attr.expression && attr.valueRange) {
|
|
6299
|
+
heads.push(content.slice(attr.valueRange.start, attr.valueRange.end));
|
|
6300
|
+
continue;
|
|
6301
|
+
}
|
|
6302
|
+
const inner = attr.expressionRange ? content.slice(attr.expressionRange.start + 1, attr.expressionRange.end - 1).trim() : "";
|
|
6303
|
+
const head = provableAddress(inner);
|
|
6304
|
+
if (head === null) opaque.push(`data-bcms-field={${inner}}`);
|
|
6305
|
+
else heads.push(head);
|
|
6306
|
+
}
|
|
6307
|
+
}
|
|
6308
|
+
return { heads, opaque };
|
|
6309
|
+
}
|
|
6310
|
+
function elementsUnder(...roots) {
|
|
6311
|
+
const out = [];
|
|
6312
|
+
const walk = (node) => {
|
|
6313
|
+
if (node.type === "element") {
|
|
6314
|
+
out.push(node);
|
|
6315
|
+
for (const child of node.children) walk(child);
|
|
6316
|
+
return;
|
|
6317
|
+
}
|
|
6318
|
+
if (node.type === "expr") for (const child of node.children) walk(child);
|
|
6319
|
+
};
|
|
6320
|
+
for (const root of roots) walk(root);
|
|
6321
|
+
return out;
|
|
6322
|
+
}
|
|
6323
|
+
var V1_ANCHOR = {
|
|
6324
|
+
astro: "const { blockId, bind, overrides, page } = Astro.props;",
|
|
6325
|
+
jsx: "}: BcmsSectionProps) {"
|
|
6326
|
+
};
|
|
6327
|
+
var refusalKey = (dialect, slug) => `${dialect}\0${slug}`;
|
|
6328
|
+
function generatedBindings(parsed) {
|
|
6329
|
+
const program = parsed.ast?.program;
|
|
6330
|
+
const body = program?.body ?? [];
|
|
6331
|
+
const scopes = [body];
|
|
6332
|
+
for (const statement of body) {
|
|
6333
|
+
const declaration2 = statement.type === "ExportDefaultDeclaration" ? statement.declaration : void 0;
|
|
6334
|
+
const inner = declaration2?.type === "FunctionDeclaration" ? declaration2.body?.body : void 0;
|
|
6335
|
+
if (inner) scopes.push(inner);
|
|
6336
|
+
}
|
|
6337
|
+
const out = /* @__PURE__ */ new Map();
|
|
6338
|
+
for (const scope of scopes) {
|
|
6339
|
+
for (const statement of scope) {
|
|
6340
|
+
if (statement.type !== "VariableDeclaration") continue;
|
|
6341
|
+
for (const declarator of statement.declarations ?? []) {
|
|
6342
|
+
const id = declarator.id;
|
|
6343
|
+
const name = id?.type === "Identifier" ? id.name : void 0;
|
|
6344
|
+
if (name !== void 0 && !out.has(name)) out.set(name, declarator.init);
|
|
6345
|
+
}
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
return out;
|
|
6349
|
+
}
|
|
6350
|
+
function stripBindFallback(parsed, source, content, locals) {
|
|
6351
|
+
if (!parsed.ast?.program) return "unreadable";
|
|
6352
|
+
const offset = parsed.script?.offset ?? 0;
|
|
6353
|
+
let rewritten = 0;
|
|
6354
|
+
let unproven = 0;
|
|
6355
|
+
walkAst(parsed.ast, (node) => {
|
|
6356
|
+
if (node.type !== "CallExpression") return;
|
|
6357
|
+
const argument = (node.arguments ?? [])[1];
|
|
6358
|
+
if (argument?.type !== "LogicalExpression" || argument.operator !== "??") return;
|
|
6359
|
+
const callee = node.callee;
|
|
6360
|
+
if (callee?.type !== "Identifier" || !locals.has(callee.name)) {
|
|
6361
|
+
unproven++;
|
|
6362
|
+
return;
|
|
6363
|
+
}
|
|
6364
|
+
const left = argument.left;
|
|
6365
|
+
source.overwrite(
|
|
6366
|
+
offset + argument.start,
|
|
6367
|
+
offset + argument.end,
|
|
6368
|
+
content.slice(offset + left.start, offset + left.end)
|
|
6369
|
+
);
|
|
6370
|
+
rewritten++;
|
|
6371
|
+
});
|
|
6372
|
+
return rewritten === 0 && unproven > 0 ? "unproven" : "ok";
|
|
6373
|
+
}
|
|
6374
|
+
var isCallOf = (init, callee) => {
|
|
6375
|
+
const fn = init?.type === "CallExpression" ? init.callee : void 0;
|
|
6376
|
+
return fn?.type === "Identifier" && fn.name === callee;
|
|
6377
|
+
};
|
|
6378
|
+
async function migrateSections(plan2, contents, written, fileSet, aliases) {
|
|
6379
|
+
const files = [];
|
|
6380
|
+
const refused = /* @__PURE__ */ new Map();
|
|
6381
|
+
const prefixesFor = (slug) => {
|
|
6382
|
+
const out = /* @__PURE__ */ new Set();
|
|
6383
|
+
for (const page of plan2.pages ?? []) {
|
|
6384
|
+
for (const section of page.sections ?? []) {
|
|
6385
|
+
if (resolveComponent(plan2, section).slug !== slug) continue;
|
|
6386
|
+
const prefix = propPrefix(section);
|
|
6387
|
+
if (prefix !== null) out.add(prefix);
|
|
6388
|
+
}
|
|
6389
|
+
}
|
|
6390
|
+
return [...out];
|
|
6391
|
+
};
|
|
6392
|
+
for (const [dialect, components] of written) {
|
|
6393
|
+
for (const [slug, entry] of components) {
|
|
6394
|
+
const content = contents.get(entry.path);
|
|
6395
|
+
if (content === void 0 || content.includes(SECTION_MARKER)) continue;
|
|
6396
|
+
const older = SECTION_MARKERS.find((marker) => content.includes(marker));
|
|
6397
|
+
if (older === void 0) continue;
|
|
6398
|
+
const candidates = prefixesFor(slug);
|
|
6399
|
+
if (candidates.length === 0) continue;
|
|
6400
|
+
const parsed = await parseFile4(dialect, content);
|
|
6401
|
+
if (parsed.error) {
|
|
6402
|
+
refused.set(refusalKey(dialect, slug), `${entry.path} carries our marker but does not parse (${parsed.error.message}).`);
|
|
6403
|
+
continue;
|
|
6404
|
+
}
|
|
6405
|
+
const elements = elementsUnder(...parsed.roots);
|
|
6406
|
+
const imports = [...importsIn(parsed, dialect)];
|
|
6407
|
+
const helperModule = HELPER_PATH[dialect];
|
|
6408
|
+
const helperLocalsFor = (exported) => new Set(
|
|
6409
|
+
imports.filter(
|
|
6410
|
+
([, from]) => from.imported === exported && resolveSpecifier(entry.path, from.specifier, fileSet, aliases) === helperModule
|
|
6411
|
+
).map(([local]) => local)
|
|
6412
|
+
);
|
|
6413
|
+
const { heads: evidence, opaque } = fieldAddresses(content, elements);
|
|
6414
|
+
const pathPrefix = evidence.length + opaque.length === 0 ? "" : opaque.length > 0 ? null : onlyOne(candidates.filter((prefix) => evidence.every((head) => head.startsWith(prefix)))) ?? null;
|
|
6415
|
+
if (pathPrefix === null) {
|
|
6416
|
+
const matching = candidates.filter((prefix) => evidence.every((head) => head.startsWith(prefix)));
|
|
6417
|
+
refused.set(
|
|
6418
|
+
refusalKey(dialect, slug),
|
|
6419
|
+
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.`
|
|
6420
|
+
);
|
|
6421
|
+
continue;
|
|
6422
|
+
}
|
|
6423
|
+
const source = new MagicString3(content);
|
|
6424
|
+
const { bound, duplicates } = wrapFieldAttrs(source, content, elements, (at) => at, "bcmsField");
|
|
6425
|
+
if (duplicates.length > 0) {
|
|
6426
|
+
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.`);
|
|
6427
|
+
continue;
|
|
6428
|
+
}
|
|
6429
|
+
const markerEnd = content.indexOf(older) + older.length;
|
|
6430
|
+
source.overwrite(markerEnd - older.length, markerEnd, SECTION_MARKER);
|
|
6431
|
+
const stripped = stripBindFallback(parsed, source, content, helperLocalsFor("bcmsSection"));
|
|
6432
|
+
if (stripped !== "ok") {
|
|
6433
|
+
refused.set(
|
|
6434
|
+
refusalKey(dialect, slug),
|
|
6435
|
+
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.`
|
|
6436
|
+
);
|
|
6437
|
+
continue;
|
|
6438
|
+
}
|
|
6439
|
+
if (bound > 0) {
|
|
6440
|
+
const locals = generatedBindings(parsed);
|
|
6441
|
+
const names = /* @__PURE__ */ new Set([...topLevelBindings(dialect, parsed, content), ...locals.keys()]);
|
|
6442
|
+
const fieldsLocal = [...helperLocalsFor("bcmsFields")][0];
|
|
6443
|
+
if (fieldsLocal === void 0 && names.has("bcmsFields")) {
|
|
6444
|
+
refused.set(
|
|
6445
|
+
refusalKey(dialect, slug),
|
|
6446
|
+
`${entry.path} already binds "bcmsFields" to something other than ${helperModule}'s export.`
|
|
6447
|
+
);
|
|
6448
|
+
continue;
|
|
6449
|
+
}
|
|
6450
|
+
const declared = names.has("bcmsField");
|
|
6451
|
+
if (declared && (fieldsLocal === void 0 || !isCallOf(locals.get("bcmsField"), fieldsLocal))) {
|
|
6452
|
+
refused.set(
|
|
6453
|
+
refusalKey(dialect, slug),
|
|
6454
|
+
`${entry.path} already binds "bcmsField" to something other than a call of ${helperModule}'s bcmsFields.`
|
|
6455
|
+
);
|
|
6456
|
+
continue;
|
|
6457
|
+
}
|
|
6458
|
+
if (!declared) {
|
|
6459
|
+
const anchor = V1_ANCHOR[dialect];
|
|
6460
|
+
const at = anchor ? content.indexOf(anchor) : -1;
|
|
6461
|
+
if (anchor === void 0 || at < 0) {
|
|
6462
|
+
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.`);
|
|
6463
|
+
continue;
|
|
6464
|
+
}
|
|
6465
|
+
source.appendLeft(
|
|
6466
|
+
at + anchor.length,
|
|
6467
|
+
`
|
|
6468
|
+
${dialect === "astro" ? "" : " "}const bcmsField = ${fieldsLocal ?? "bcmsFields"}({ blockId, bind, overrides }, ${JSON.stringify(pathPrefix)});`
|
|
6469
|
+
);
|
|
6470
|
+
}
|
|
6471
|
+
if (fieldsLocal === void 0) {
|
|
6472
|
+
const helper = imports.find(([, from]) => from.imported === "bcmsSection")?.[1].specifier ?? relativeImport(entry.path, HELPER_PATH[dialect]).replace(/\.tsx?$/, "");
|
|
6473
|
+
source.appendLeft(markerEnd, `
|
|
6474
|
+
import { bcmsFields } from "${helper}";`);
|
|
6475
|
+
}
|
|
6476
|
+
}
|
|
6477
|
+
files.push({ path: entry.path, operation: "modify", content: source.toString() });
|
|
6478
|
+
}
|
|
6479
|
+
}
|
|
6480
|
+
return { files, refused };
|
|
6481
|
+
}
|
|
6482
|
+
function propPrefix(section) {
|
|
6483
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
6484
|
+
for (const field of section.fields ?? []) {
|
|
6485
|
+
const { path, key } = field;
|
|
6486
|
+
if (typeof path !== "string" || typeof key !== "string") continue;
|
|
6487
|
+
if (!path.endsWith(key)) return null;
|
|
6488
|
+
prefixes.add(path.slice(0, path.length - key.length));
|
|
6489
|
+
}
|
|
6490
|
+
if (prefixes.size > 1) return null;
|
|
6491
|
+
return prefixes.size === 1 ? [...prefixes][0] : `${section.groupKey}.`;
|
|
6492
|
+
}
|
|
6493
|
+
function referencedNames2(markup) {
|
|
6494
|
+
const out = /* @__PURE__ */ new Set();
|
|
6495
|
+
for (const [name] of markup.matchAll(/[A-Za-z_$][\w$]*/g)) out.add(name);
|
|
6496
|
+
return out;
|
|
5997
6497
|
}
|
|
5998
6498
|
var alias = (name, local) => name === local ? name : `${name} as ${local}`;
|
|
5999
6499
|
var mentions = (markup, name) => new RegExp(`(?<![\\w$])${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w$])`).test(markup);
|
|
@@ -6252,7 +6752,7 @@ function addImports(source, parsed, lines) {
|
|
|
6252
6752
|
else source.appendLeft(at.at, `
|
|
6253
6753
|
${lines.join("\n")}`);
|
|
6254
6754
|
}
|
|
6255
|
-
var isGenerated = (content) => content.includes(
|
|
6755
|
+
var isGenerated = (content) => SECTION_MARKERS.some((marker) => content.includes(marker)) || content.includes(REGISTRY_MARKER);
|
|
6256
6756
|
function emitHelper(out, contents, version, overwrite) {
|
|
6257
6757
|
const path = HELPER_PATH.astro;
|
|
6258
6758
|
const source = helperSource("astro");
|