@bettercms-ai/convert 0.9.0 → 0.11.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 +34 -0
- package/README.md +23 -0
- package/dist/cli.js +312 -25
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +81 -5
- package/dist/index.js +264 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2560,6 +2560,7 @@ var IMAGE_SOURCES = {
|
|
|
2560
2560
|
video: ["poster"]
|
|
2561
2561
|
};
|
|
2562
2562
|
var isImageSource = (site) => site.where === "attr" && (IMAGE_SOURCES[site.tag.toLowerCase()] ?? []).includes(site.attr ?? "");
|
|
2563
|
+
var ownsSubtree = (kind, site, inline) => kind === "richtext" && (inline === true ? !site.nonHtml : !(site.mixed && site.textRange !== null));
|
|
2563
2564
|
var norm = (value) => decodeEntities(value.normalize("NFC")).replace(/\s+/g, " ").trim();
|
|
2564
2565
|
var BCMS_ATTRS = [
|
|
2565
2566
|
"data-bcms-field",
|
|
@@ -2592,6 +2593,7 @@ function openTagEnd(content, start) {
|
|
|
2592
2593
|
}
|
|
2593
2594
|
return content.length;
|
|
2594
2595
|
}
|
|
2596
|
+
var OPAQUE_TAGS = /* @__PURE__ */ new Set(["pre"]);
|
|
2595
2597
|
var isBlank = (value) => value.trim() === "";
|
|
2596
2598
|
function renderedText(node) {
|
|
2597
2599
|
if (node.type === "text") return node.value;
|
|
@@ -2599,6 +2601,11 @@ function renderedText(node) {
|
|
|
2599
2601
|
return "";
|
|
2600
2602
|
}
|
|
2601
2603
|
var attrsOf = (node) => Object.fromEntries(node.attrs.map((a) => [a.name, a.value]));
|
|
2604
|
+
function nonHtmlSubtree(node) {
|
|
2605
|
+
return node.children.some(
|
|
2606
|
+
(c) => c.type === "expr" || c.type === "element" && (!isIntrinsic(c.tag) || c.tag === "slot" || nonHtmlSubtree(c))
|
|
2607
|
+
);
|
|
2608
|
+
}
|
|
2602
2609
|
function soleTextChild(node) {
|
|
2603
2610
|
const texts = node.children.filter((c) => c.type === "text" && !isBlank(c.value));
|
|
2604
2611
|
return texts.length === 1 ? texts[0] : null;
|
|
@@ -2635,10 +2642,12 @@ function collectSites(roots, literals) {
|
|
|
2635
2642
|
if (node.type !== "element") return matched;
|
|
2636
2643
|
const attrs = attrsOf(node);
|
|
2637
2644
|
let index = 0;
|
|
2638
|
-
|
|
2639
|
-
const
|
|
2640
|
-
|
|
2641
|
-
|
|
2645
|
+
if (!OPAQUE_TAGS.has(node.tag)) {
|
|
2646
|
+
for (const child of node.children) {
|
|
2647
|
+
const childWalk = { ancestors: [...walk.ancestors, node.tag], siblingIndex: index };
|
|
2648
|
+
if (child.type === "element") index++;
|
|
2649
|
+
for (const hit of visit(child, childWalk)) matched.add(hit);
|
|
2650
|
+
}
|
|
2642
2651
|
}
|
|
2643
2652
|
if (node.raw) {
|
|
2644
2653
|
const text2 = norm(renderedTextRaw(node));
|
|
@@ -2664,6 +2673,7 @@ function collectSites(roots, literals) {
|
|
|
2664
2673
|
textRange: soleTextChild(node)?.range ?? null,
|
|
2665
2674
|
literal,
|
|
2666
2675
|
partial: false,
|
|
2676
|
+
...nonHtmlSubtree(node) ? { nonHtml: true } : {},
|
|
2667
2677
|
ancestors: walk.ancestors,
|
|
2668
2678
|
siblingIndex: walk.siblingIndex
|
|
2669
2679
|
});
|
|
@@ -2689,6 +2699,7 @@ function collectSites(roots, literals) {
|
|
|
2689
2699
|
textRange: own.range,
|
|
2690
2700
|
literal,
|
|
2691
2701
|
partial: false,
|
|
2702
|
+
...nonHtmlSubtree(node) ? { nonHtml: true } : {},
|
|
2692
2703
|
ancestors: walk.ancestors,
|
|
2693
2704
|
siblingIndex: walk.siblingIndex
|
|
2694
2705
|
});
|
|
@@ -2739,10 +2750,12 @@ function partials(node, literals, walk, out) {
|
|
|
2739
2750
|
if (node.type !== "element" || node.raw) return false;
|
|
2740
2751
|
let deeper = false;
|
|
2741
2752
|
let index = 0;
|
|
2742
|
-
|
|
2743
|
-
const
|
|
2744
|
-
|
|
2745
|
-
|
|
2753
|
+
if (!OPAQUE_TAGS.has(node.tag)) {
|
|
2754
|
+
for (const child of node.children) {
|
|
2755
|
+
const childWalk = { ancestors: [...walk.ancestors, node.tag], siblingIndex: index };
|
|
2756
|
+
if (child.type === "element") index++;
|
|
2757
|
+
if (partials(child, literals, childWalk, out)) deeper = true;
|
|
2758
|
+
}
|
|
2746
2759
|
}
|
|
2747
2760
|
if (deeper) return true;
|
|
2748
2761
|
const text = norm(renderedText(node));
|
|
@@ -4127,7 +4140,7 @@ function bindImages(ctx, targets) {
|
|
|
4127
4140
|
|
|
4128
4141
|
// src/helper.ts
|
|
4129
4142
|
import { createHash as createHash2 } from "crypto";
|
|
4130
|
-
var HELPER_VERSION =
|
|
4143
|
+
var HELPER_VERSION = 7;
|
|
4131
4144
|
var HEADER = "// @bettercms-ai/convert helper v";
|
|
4132
4145
|
var HELPER_PATH = Object.fromEntries(
|
|
4133
4146
|
Object.keys(DIALECT_RULES).map((d) => [d, DIALECT_RULES[d].helper])
|
|
@@ -4320,9 +4333,65 @@ export function bcmsSection(page: unknown, bind?: string, overrides?: unknown):
|
|
|
4320
4333
|
* how the codemod ran: one component renders a Phase A placement on one page and a Phase B
|
|
4321
4334
|
* instance on the next, and a repository does not have to be re-cut when its copy moves.
|
|
4322
4335
|
*/
|
|
4336
|
+
/** How a flat field family maps page paths to an instance's override keys. @see bcmsSectionPaths */
|
|
4337
|
+
export type BcmsPathMap = {
|
|
4338
|
+
scalars?: Record<string, string>;
|
|
4339
|
+
tables?: Record<string, { key: string; columns: Record<string, string> }>;
|
|
4340
|
+
};
|
|
4341
|
+
|
|
4342
|
+
/**
|
|
4343
|
+
* The snapshot for a section whose page fields are a FLAT family (\`wrap-h2-latest\`, not \`wrap.h2_latest\`).
|
|
4344
|
+
*
|
|
4345
|
+
* \`bcmsSection\` returns a group object read through a dotted path, which a flat family does not have: its
|
|
4346
|
+
* copy lives in top-level keys of the page snapshot. So this answers in the page's own spelling \u2014 the
|
|
4347
|
+
* markup reads \`bcms(snapshot, "wrap-h2-latest", \u2026)\` verbatim \u2014 with every mapped path set explicitly:
|
|
4348
|
+
*
|
|
4349
|
+
* - the instance's override for that key when it holds one (a table's rows re-spelled with the page's
|
|
4350
|
+
* column names, \`{ rows }\` unwrapped as in \`bcmsSection\`);
|
|
4351
|
+
* - else, for a placement bound to a group, the page value at the path re-based onto that group;
|
|
4352
|
+
* - else NOTHING \u2014 a placement that owns its copy never falls back to the page's stale copy, exactly as
|
|
4353
|
+
* \`bcmsSection\` synthesizes no group for it.
|
|
4354
|
+
*
|
|
4355
|
+
* Every other page key passes through, so reads outside the family (a shared link) keep working.
|
|
4356
|
+
*/
|
|
4357
|
+
export function bcmsSectionPaths(
|
|
4358
|
+
page: unknown,
|
|
4359
|
+
bind: string | undefined,
|
|
4360
|
+
overrides: unknown,
|
|
4361
|
+
prefix: string,
|
|
4362
|
+
map: BcmsPathMap,
|
|
4363
|
+
): Record<string, unknown> {
|
|
4364
|
+
const out: Record<string, unknown> = isRecord(page) ? { ...page } : {};
|
|
4365
|
+
const own = isRecord(overrides) ? overrides : {};
|
|
4366
|
+
const separator = prefix.slice(-1);
|
|
4367
|
+
const fromPage = (path: string): unknown =>
|
|
4368
|
+
bind ? readPath(page, bind + separator + path.slice(prefix.length)) : undefined;
|
|
4369
|
+
for (const [path, key] of Object.entries(map.scalars ?? {})) {
|
|
4370
|
+
const value = own[key];
|
|
4371
|
+
out[path] = value !== undefined && value !== null ? value : fromPage(path);
|
|
4372
|
+
}
|
|
4373
|
+
for (const [path, table] of Object.entries(map.tables ?? {})) {
|
|
4374
|
+
const held = own[table.key];
|
|
4375
|
+
const rows = isRecord(held) && Array.isArray(held.rows) ? held.rows : held;
|
|
4376
|
+
if (!Array.isArray(rows)) {
|
|
4377
|
+
out[path] = fromPage(path);
|
|
4378
|
+
continue;
|
|
4379
|
+
}
|
|
4380
|
+
const pageColumn = new Map(Object.entries(table.columns).map(([column, key]) => [key, column]));
|
|
4381
|
+
out[path] = rows.map((row) => {
|
|
4382
|
+
if (!isRecord(row)) return row;
|
|
4383
|
+
const spelled: Record<string, unknown> = {};
|
|
4384
|
+
for (const [key, value] of Object.entries(row)) spelled[pageColumn.get(key) ?? key] = value;
|
|
4385
|
+
return spelled;
|
|
4386
|
+
});
|
|
4387
|
+
}
|
|
4388
|
+
return out;
|
|
4389
|
+
}
|
|
4390
|
+
|
|
4323
4391
|
export function bcmsFields(
|
|
4324
4392
|
placement: { blockId?: string; bind?: string; overrides?: unknown },
|
|
4325
4393
|
pathPrefix: string,
|
|
4394
|
+
map?: BcmsPathMap,
|
|
4326
4395
|
): (path: string) => string {
|
|
4327
4396
|
const id = placement.blockId;
|
|
4328
4397
|
const own = typeof id === "string" && id !== "" && !placement.bind && isRecord(placement.overrides);
|
|
@@ -4335,6 +4404,24 @@ export function bcmsFields(
|
|
|
4335
4404
|
* so prepending one would invent an address rather than correct it.
|
|
4336
4405
|
*/
|
|
4337
4406
|
const lane = own ? \`\${id}__overrides.\` : placement.bind && pathPrefix ? \`\${placement.bind}.\` : null;
|
|
4407
|
+
if (map) {
|
|
4408
|
+
/**
|
|
4409
|
+
* A FLAT family: the address is not "prefix swapped". The instance lane names the override key the
|
|
4410
|
+
* plan allocated (\`h2_latest\`, \`group_webgl[0].a_webgl\`); the bind lane keeps the page's own
|
|
4411
|
+
* separator (\`landing-h2-latest\`, never \`landing.h2-latest\`).
|
|
4412
|
+
*/
|
|
4413
|
+
const separator = pathPrefix.slice(-1);
|
|
4414
|
+
return (path: string): string => {
|
|
4415
|
+
if (!path.startsWith(pathPrefix) || !(own || placement.bind)) return path;
|
|
4416
|
+
if (!own) return placement.bind + separator + path.slice(pathPrefix.length);
|
|
4417
|
+
const scalar = map.scalars?.[path];
|
|
4418
|
+
if (scalar !== undefined) return id + "__overrides." + scalar;
|
|
4419
|
+
const row = /^([^[]+)\\[(\\d+)\\]\\.(.+)$/.exec(path);
|
|
4420
|
+
const table = row ? map.tables?.[row[1]] : undefined;
|
|
4421
|
+
const column = row && table ? table.columns[row[3]] : undefined;
|
|
4422
|
+
return table && column !== undefined ? id + "__overrides." + table.key + "[" + row![2] + "]." + column : path;
|
|
4423
|
+
};
|
|
4424
|
+
}
|
|
4338
4425
|
return (path: string): string => {
|
|
4339
4426
|
// A path outside the group this section was cut from is not one this placement re-addresses:
|
|
4340
4427
|
// it names a key the instance does not hold and a group this placement is not bound to.
|
|
@@ -4351,21 +4438,24 @@ var HISTORICAL = {
|
|
|
4351
4438
|
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0",
|
|
4352
4439
|
3: "d4305b2983adcb456ef782d35ea9d083a04465c2b68baa0d44a084dfa01a9227",
|
|
4353
4440
|
4: "a5742f64dacb1b26ee6dd5e1469a54871487934c71bc8267df19e3a80e417595",
|
|
4354
|
-
5: "0472fe3ffdcfa49e719c7b9da9cb63b6eba637cc22783bb00c3c2cadd3d800da"
|
|
4441
|
+
5: "0472fe3ffdcfa49e719c7b9da9cb63b6eba637cc22783bb00c3c2cadd3d800da",
|
|
4442
|
+
6: "c907e7245578d947c6541b1ab2c5c6659ea7789a1519e895b4a4aed122c6f649"
|
|
4355
4443
|
},
|
|
4356
4444
|
"src/lib/bcms-content.ts": {
|
|
4357
4445
|
1: "ae72ae14d706943d481c880d70da5789b4816ba1ba319a10fbc63ded4d0b42f2",
|
|
4358
4446
|
2: "a18731eb42f7db55dac0591c45f873758cde8e103fefcd6e5caf67936ae6a002",
|
|
4359
4447
|
3: "0be9c73160361882a038552bc02673c14ed532287823dabaafe36179bc55ed7e",
|
|
4360
4448
|
4: "0146d027a727ecd3c9e3fc336fcf1299b59ddf75b04091c05703f3f983ec28af",
|
|
4361
|
-
5: "c8c93655320fa65664611ba84541f3fa7bfa292cc9a2fd655373440b99049fba"
|
|
4449
|
+
5: "c8c93655320fa65664611ba84541f3fa7bfa292cc9a2fd655373440b99049fba",
|
|
4450
|
+
6: "63f1fa29e47e70c5193b81ee24addb238ba100247124d55bc17713102d587df5"
|
|
4362
4451
|
},
|
|
4363
4452
|
"composables/bcms-content.ts": {
|
|
4364
4453
|
1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
|
|
4365
4454
|
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0",
|
|
4366
4455
|
3: "d4305b2983adcb456ef782d35ea9d083a04465c2b68baa0d44a084dfa01a9227",
|
|
4367
4456
|
4: "a5742f64dacb1b26ee6dd5e1469a54871487934c71bc8267df19e3a80e417595",
|
|
4368
|
-
5: "0472fe3ffdcfa49e719c7b9da9cb63b6eba637cc22783bb00c3c2cadd3d800da"
|
|
4457
|
+
5: "0472fe3ffdcfa49e719c7b9da9cb63b6eba637cc22783bb00c3c2cadd3d800da",
|
|
4458
|
+
6: "c907e7245578d947c6541b1ab2c5c6659ea7789a1519e895b4a4aed122c6f649"
|
|
4369
4459
|
}
|
|
4370
4460
|
};
|
|
4371
4461
|
var sha256 = (value) => createHash2("sha256").update(value, "utf8").digest("hex");
|
|
@@ -4382,14 +4472,23 @@ function isKnownHelper(content, dialect) {
|
|
|
4382
4472
|
|
|
4383
4473
|
// src/locate.ts
|
|
4384
4474
|
var flat = (value) => value.replace(/\s+/g, " ").trim();
|
|
4385
|
-
var stripTags = (value) => value.replace(/<br\s*\/?>/gi,
|
|
4475
|
+
var stripTags = (value, br = " ") => value.replace(/<br\s*\/?>/gi, br).replace(/<[^>]*>/g, "");
|
|
4386
4476
|
function locate(brief, sources, options) {
|
|
4387
4477
|
const flattened = sources.map((s) => {
|
|
4388
4478
|
const readable = unwrapJsxStrings(decodeEntities(s.content));
|
|
4389
4479
|
return {
|
|
4390
4480
|
path: s.path,
|
|
4391
4481
|
content: flat(readable),
|
|
4392
|
-
text: options?.stripTags ? flat(stripTags(readable)) : null
|
|
4482
|
+
text: options?.stripTags ? flat(stripTags(readable)) : null,
|
|
4483
|
+
/**
|
|
4484
|
+
* 🔴 A `<br>` IS A SPACE TO A READER AND NOTHING TO `textContent`, and the derive lane reads
|
|
4485
|
+
* the DOM. `<h1>Launch your site<br /><span>fast.</span></h1>` is one INLINE richtext field
|
|
4486
|
+
* whose value is `Launch your sitefast.` — no space, because no text node holds one — while
|
|
4487
|
+
* this prefilter's only reading inserted one, so the file was never opened and every split
|
|
4488
|
+
* heading on every imported site came back NOT_IN_SOURCE. Both readings are searched: this
|
|
4489
|
+
* decides which FILES to open, and `collectSites` still has to match an element exactly.
|
|
4490
|
+
*/
|
|
4491
|
+
tight: options?.stripTags ? flat(stripTags(readable, "")) : null
|
|
4393
4492
|
};
|
|
4394
4493
|
});
|
|
4395
4494
|
const located = [];
|
|
@@ -4401,7 +4500,9 @@ function locate(brief, sources, options) {
|
|
|
4401
4500
|
unlocated.push({ route: page.route, scope: p.scope, path: p.path, reason: "no-original" });
|
|
4402
4501
|
continue;
|
|
4403
4502
|
}
|
|
4404
|
-
const files = flattened.filter(
|
|
4503
|
+
const files = flattened.filter(
|
|
4504
|
+
(f) => f.content.includes(original) || f.text !== null && f.text.includes(original) || f.tight !== null && f.tight.includes(original)
|
|
4505
|
+
).map((f) => f.path);
|
|
4405
4506
|
if (files.length === 0) {
|
|
4406
4507
|
unlocated.push({ route: page.route, scope: p.scope, path: p.path, reason: "not-in-source" });
|
|
4407
4508
|
continue;
|
|
@@ -5100,7 +5201,7 @@ function rewriteFile(content, dialect, rewrites, imports, extra = []) {
|
|
|
5100
5201
|
addDeclaration(site.attrInsertAt, declaration(rewrite));
|
|
5101
5202
|
continue;
|
|
5102
5203
|
}
|
|
5103
|
-
if (kind
|
|
5204
|
+
if (ownsSubtree(kind, site, rewrite.inline)) {
|
|
5104
5205
|
if (!isIntrinsic(site.tag)) continue;
|
|
5105
5206
|
if (rule.richtext && site.inner) {
|
|
5106
5207
|
source.remove(site.inner.start, site.inner.end);
|
|
@@ -6386,6 +6487,66 @@ function isSourceCandidate(path) {
|
|
|
6386
6487
|
return lower.split("/").slice(0, -1).some((segment) => TEMPLATE_DIRS.has(segment));
|
|
6387
6488
|
}
|
|
6388
6489
|
|
|
6490
|
+
// src/xray.ts
|
|
6491
|
+
var SKIP_TAGS = /* @__PURE__ */ new Set(["svg", "iframe", "head", "title"]);
|
|
6492
|
+
var CONTEXT_TAGS = /* @__PURE__ */ new Set([
|
|
6493
|
+
"nav",
|
|
6494
|
+
"header",
|
|
6495
|
+
"footer",
|
|
6496
|
+
"main",
|
|
6497
|
+
"aside",
|
|
6498
|
+
"form",
|
|
6499
|
+
"button",
|
|
6500
|
+
"a",
|
|
6501
|
+
"ul",
|
|
6502
|
+
"ol",
|
|
6503
|
+
"table",
|
|
6504
|
+
"dialog",
|
|
6505
|
+
"details",
|
|
6506
|
+
"figure",
|
|
6507
|
+
"blockquote"
|
|
6508
|
+
]);
|
|
6509
|
+
var BOUND_ATTRS = ["data-bcms-field", "data-bcms-layout-field", "data-bcms-path", "data-bcms-block"];
|
|
6510
|
+
var isDecorativeGlyph = (text) => text.length <= 2 || /^\d{1,2}\s*[/.)\-–]?$/.test(text) || !new RegExp("\\p{L}", "u").test(text) || /^made with\b/i.test(text) || /^skip to\b/i.test(text);
|
|
6511
|
+
var MAX_BUCKETS = 12;
|
|
6512
|
+
var flat2 = (value) => value.replace(/\s+/g, " ").trim();
|
|
6513
|
+
function measureUnaddressable(html) {
|
|
6514
|
+
const parsed = parse2(html);
|
|
6515
|
+
let visible = 0;
|
|
6516
|
+
let unaddressable = 0;
|
|
6517
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
6518
|
+
const visit = (node, tag, context, bound) => {
|
|
6519
|
+
if (node.type === "text") {
|
|
6520
|
+
const text = flat2(node.value);
|
|
6521
|
+
if (!text || isDecorativeGlyph(text)) return;
|
|
6522
|
+
visible += text.length;
|
|
6523
|
+
if (bound) return;
|
|
6524
|
+
unaddressable += text.length;
|
|
6525
|
+
const key = `${tag} @ ${context}`;
|
|
6526
|
+
const held = buckets.get(key) ?? { tag, context, chars: 0, nodes: 0, samples: [] };
|
|
6527
|
+
held.chars += text.length;
|
|
6528
|
+
held.nodes += 1;
|
|
6529
|
+
if (held.samples.length < 2) held.samples.push(text.slice(0, 50));
|
|
6530
|
+
buckets.set(key, held);
|
|
6531
|
+
return;
|
|
6532
|
+
}
|
|
6533
|
+
if (node.type !== "element" || node.raw || SKIP_TAGS.has(node.tag)) return;
|
|
6534
|
+
const attrs = new Map(node.attrs.map((a) => [a.name, a.value]));
|
|
6535
|
+
if (attrs.has("hidden") || attrs.get("aria-hidden") === "true" || /display\s*:\s*none/.test(attrs.get("style") ?? "")) {
|
|
6536
|
+
return;
|
|
6537
|
+
}
|
|
6538
|
+
const owned = bound || BOUND_ATTRS.some((name) => attrs.has(name));
|
|
6539
|
+
const chain = CONTEXT_TAGS.has(node.tag) ? context === "body" ? node.tag : `${context}>${node.tag}` : context;
|
|
6540
|
+
for (const child of node.children) visit(child, node.tag, chain, owned);
|
|
6541
|
+
};
|
|
6542
|
+
for (const root of parsed.roots) visit(root, "body", "body", false);
|
|
6543
|
+
return {
|
|
6544
|
+
visible,
|
|
6545
|
+
unaddressable,
|
|
6546
|
+
buckets: [...buckets.values()].sort((a, b) => b.chars - a.chars).slice(0, MAX_BUCKETS)
|
|
6547
|
+
};
|
|
6548
|
+
}
|
|
6549
|
+
|
|
6389
6550
|
// src/declared.ts
|
|
6390
6551
|
async function readDeclarations(file, content) {
|
|
6391
6552
|
const dialect = dialectOf(file, content);
|
|
@@ -6803,6 +6964,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
6803
6964
|
return held;
|
|
6804
6965
|
};
|
|
6805
6966
|
const fresh = /* @__PURE__ */ new Set();
|
|
6967
|
+
const freshFiles = /* @__PURE__ */ new Map();
|
|
6806
6968
|
const bindOf = (section) => instanceOwned(section) ? void 0 : section.groupKey;
|
|
6807
6969
|
const placementOf = (slug, section) => {
|
|
6808
6970
|
const bind = bindOf(section);
|
|
@@ -7130,6 +7292,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
7130
7292
|
}
|
|
7131
7293
|
extractable.set(resolved.slug, { name: resolved.name, path, kind: "default", imported: null });
|
|
7132
7294
|
fresh.add(resolved.slug);
|
|
7295
|
+
freshFiles.set(resolved.slug, path);
|
|
7133
7296
|
addFile(outFiles, contents, {
|
|
7134
7297
|
path,
|
|
7135
7298
|
operation: existing === void 0 ? "add" : "modify",
|
|
@@ -7215,6 +7378,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
7215
7378
|
sections,
|
|
7216
7379
|
// What THIS run wrote a file for. A component an earlier run left is reused, not re-reported.
|
|
7217
7380
|
components: [...fresh].sort(),
|
|
7381
|
+
componentFiles: [...freshFiles].sort(([a], [b]) => a.localeCompare(b)).map(([slug, path]) => ({ slug, path, export: "default" })),
|
|
7218
7382
|
planPending,
|
|
7219
7383
|
notes
|
|
7220
7384
|
}
|
|
@@ -7394,7 +7558,7 @@ async function locateSections(sections, briefPaths, sources, treeOf) {
|
|
|
7394
7558
|
for (const section of sections) {
|
|
7395
7559
|
const paths = fieldPaths(section);
|
|
7396
7560
|
const literals = new Set(
|
|
7397
|
-
briefPaths.filter((p) => (p.scope ?? "page") === "page" &&
|
|
7561
|
+
briefPaths.filter((p) => (p.scope ?? "page") === "page" && inSection(section, p.path) && p.original).map((p) => flat(stripTags(p.original))).filter(Boolean)
|
|
7398
7562
|
);
|
|
7399
7563
|
const hits = /* @__PURE__ */ new Map();
|
|
7400
7564
|
for (const source of sources) {
|
|
@@ -7405,7 +7569,7 @@ async function locateSections(sections, briefPaths, sources, treeOf) {
|
|
|
7405
7569
|
const found = /* @__PURE__ */ new Map();
|
|
7406
7570
|
for (const declaration2 of declarationsIn(parsed.roots, source.content)) {
|
|
7407
7571
|
if (declaration2.scope !== "page") continue;
|
|
7408
|
-
if (
|
|
7572
|
+
if (!inSection(section, declaration2.path)) continue;
|
|
7409
7573
|
const placed2 = elements.get(declaration2.at);
|
|
7410
7574
|
if (placed2) found.set(declaration2.at, placed2);
|
|
7411
7575
|
}
|
|
@@ -7430,6 +7594,12 @@ async function locateSections(sections, briefPaths, sources, treeOf) {
|
|
|
7430
7594
|
return { placed, misses };
|
|
7431
7595
|
}
|
|
7432
7596
|
var groupOf = (path) => path.split(/[.[]/)[0] ?? "";
|
|
7597
|
+
var inSection = (section, path) => {
|
|
7598
|
+
if (groupOf(path) === section.groupKey) return true;
|
|
7599
|
+
const rowless = (value) => value.replace(/\[\d+\]/g, "[]");
|
|
7600
|
+
const target = rowless(path);
|
|
7601
|
+
return fieldPaths(section).some((fieldPath) => rowless(fieldPath) === target);
|
|
7602
|
+
};
|
|
7433
7603
|
var fieldPaths = (section) => (section.fields ?? []).map((field) => field.path ?? `${section.groupKey}.${field.key ?? ""}`);
|
|
7434
7604
|
function elementIndex(roots) {
|
|
7435
7605
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -7532,6 +7702,9 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved, pathP
|
|
|
7532
7702
|
...addresses.opaque
|
|
7533
7703
|
];
|
|
7534
7704
|
if (unconvertible.length > 0) return { content: "", free: [], duplicates: [], unconvertible };
|
|
7705
|
+
const flat3 = pathPrefix !== "" && !pathPrefix.endsWith(".");
|
|
7706
|
+
const pathMap = flat3 ? flatPathMap(resolved.section, pathPrefix) : null;
|
|
7707
|
+
if (flat3 && !pathMap) return { content: "", free: [], duplicates: [], unconvertible: [`${pathPrefix}* (a repeater leaf without an override address)`] };
|
|
7535
7708
|
const cut = content.slice(resolved.root.start, resolved.root.end);
|
|
7536
7709
|
const reserved = /* @__PURE__ */ new Set([...topLevelBindings(dialect, parsed, content), ...referencedNames2(cut)]);
|
|
7537
7710
|
const introduced = allocateNames(reserved, [
|
|
@@ -7550,7 +7723,7 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved, pathP
|
|
|
7550
7723
|
const helpers = [
|
|
7551
7724
|
.../* @__PURE__ */ new Set([
|
|
7552
7725
|
...helperNames.map((h) => alias(h.name, h.local)),
|
|
7553
|
-
"bcmsSection",
|
|
7726
|
+
flat3 ? "bcmsSectionPaths" : "bcmsSection",
|
|
7554
7727
|
...bound > 0 ? [alias("bcmsFields", fieldsName)] : []
|
|
7555
7728
|
])
|
|
7556
7729
|
].sort();
|
|
@@ -7580,9 +7753,10 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved, pathP
|
|
|
7580
7753
|
const key = /^[A-Za-z_$][\w$]*$/.test(groupKey) ? groupKey : JSON.stringify(groupKey);
|
|
7581
7754
|
const carriedDeclarations = carry.declarations.length > 0 ? `${carry.declarations.join("\n")}
|
|
7582
7755
|
` : "";
|
|
7583
|
-
const copy = snapshot ? `const ${snapshot} = { ${key}: bcmsSection(page, bind, overrides) }
|
|
7584
|
-
const field = bound > 0 ? `const ${fieldName} = ${fieldsName}({ blockId, bind, overrides }, ${JSON.stringify(pathPrefix)});` : "";
|
|
7756
|
+
const copy = !snapshot ? "" : flat3 ? `const ${snapshot} = bcmsSectionPaths(page, bind, overrides, ${JSON.stringify(pathPrefix)}, ${JSON.stringify(pathMap)});` : `const ${snapshot} = { ${key}: bcmsSection(page, bind, overrides) };`;
|
|
7757
|
+
const field = bound > 0 ? `const ${fieldName} = ${fieldsName}({ blockId, bind, overrides }, ${JSON.stringify(pathPrefix)}${flat3 ? `, ${JSON.stringify(pathMap)}` : ""});` : "";
|
|
7585
7758
|
const declared = [copy, field].filter(Boolean).join("\n");
|
|
7759
|
+
const styles = dialect === "astro" ? carriedStyles(content) : "";
|
|
7586
7760
|
if (dialect === "astro") {
|
|
7587
7761
|
return { free: [], duplicates: [], unconvertible: [], content: `---
|
|
7588
7762
|
${SECTION_MARKER}
|
|
@@ -7602,7 +7776,9 @@ const { blockId, bind, overrides, page } = Astro.props;
|
|
|
7602
7776
|
${carriedDeclarations}${declared}
|
|
7603
7777
|
---
|
|
7604
7778
|
${markup}
|
|
7605
|
-
`
|
|
7779
|
+
${styles ? `
|
|
7780
|
+
${styles}
|
|
7781
|
+
` : ""}` };
|
|
7606
7782
|
}
|
|
7607
7783
|
return { free: [], duplicates: [], unconvertible: [], content: `${SECTION_MARKER}
|
|
7608
7784
|
${[...carried, `import { ${helpers.join(", ")} } from "${helperSpecifier}";`, `import type { BcmsSectionProps } from "${relativeImport(path, SECTIONS_LIB).replace(/\.tsx?$/, "")}";`].join("\n")}
|
|
@@ -7876,6 +8052,9 @@ import { bcmsFields } from "${helper}";`);
|
|
|
7876
8052
|
return { files, refused };
|
|
7877
8053
|
}
|
|
7878
8054
|
function propPrefix(section) {
|
|
8055
|
+
return exactPrefix(section) ?? flatPrefix(section);
|
|
8056
|
+
}
|
|
8057
|
+
function exactPrefix(section) {
|
|
7879
8058
|
const prefixes = /* @__PURE__ */ new Set();
|
|
7880
8059
|
for (const field of section.fields ?? []) {
|
|
7881
8060
|
const { path, key } = field;
|
|
@@ -7886,6 +8065,49 @@ function propPrefix(section) {
|
|
|
7886
8065
|
if (prefixes.size > 1) return null;
|
|
7887
8066
|
return prefixes.size === 1 ? [...prefixes][0] : `${section.groupKey}.`;
|
|
7888
8067
|
}
|
|
8068
|
+
var foldKey = (remainder) => remainder.replace(/\[(\d+)\]/g, "_$1").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "value";
|
|
8069
|
+
function flatPrefix(section) {
|
|
8070
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
8071
|
+
let informative = false;
|
|
8072
|
+
for (const field of section.fields ?? []) {
|
|
8073
|
+
const { path, key } = field;
|
|
8074
|
+
if (typeof path !== "string" || typeof key !== "string") continue;
|
|
8075
|
+
informative = true;
|
|
8076
|
+
const prefix = ["-", "_"].map((sep) => `${section.groupKey}${sep}`).find((candidate) => path.startsWith(candidate) && (typeof field.overrideKey === "string" || foldKey(path.slice(candidate.length)) === key));
|
|
8077
|
+
if (!prefix) return null;
|
|
8078
|
+
prefixes.add(prefix);
|
|
8079
|
+
}
|
|
8080
|
+
return informative && prefixes.size === 1 ? [...prefixes][0] : null;
|
|
8081
|
+
}
|
|
8082
|
+
function flatPathMap(section, prefix) {
|
|
8083
|
+
const scalars = {};
|
|
8084
|
+
const tables = {};
|
|
8085
|
+
for (const field of section.fields ?? []) {
|
|
8086
|
+
const { path, key } = field;
|
|
8087
|
+
if (typeof path !== "string" || typeof key !== "string" || !path.startsWith(prefix)) continue;
|
|
8088
|
+
const row = /^([^[]+)\[\d+\]\.(.+)$/.exec(path);
|
|
8089
|
+
if (!row) {
|
|
8090
|
+
scalars[path] = field.overrideKey ?? key;
|
|
8091
|
+
continue;
|
|
8092
|
+
}
|
|
8093
|
+
const address = typeof field.overrideKey === "string" ? /^([^[]+)\[\d+\]\.(.+)$/.exec(field.overrideKey) : null;
|
|
8094
|
+
if (!address) return null;
|
|
8095
|
+
const table = tables[row[1]] ??= { key: address[1], columns: {} };
|
|
8096
|
+
if (table.key !== address[1]) return null;
|
|
8097
|
+
table.columns[row[2]] = address[2];
|
|
8098
|
+
}
|
|
8099
|
+
return { scalars, tables };
|
|
8100
|
+
}
|
|
8101
|
+
function carriedStyles(content) {
|
|
8102
|
+
const blocks = [];
|
|
8103
|
+
for (const match of content.matchAll(/<style\b([^>]*)>([\s\S]*?)<\/style>/g)) {
|
|
8104
|
+
const attributes = match[1] ?? "";
|
|
8105
|
+
if (/\bis:global\b|\bdefine:vars\b/.test(attributes)) continue;
|
|
8106
|
+
if (/\blang\s*=/.test(attributes) || /@(import|use|forward)\b/.test(match[2] ?? "")) continue;
|
|
8107
|
+
blocks.push(match[0]);
|
|
8108
|
+
}
|
|
8109
|
+
return blocks.join("\n");
|
|
8110
|
+
}
|
|
7889
8111
|
function referencedNames2(markup) {
|
|
7890
8112
|
const out = /* @__PURE__ */ new Set();
|
|
7891
8113
|
for (const [name] of markup.matchAll(/[A-Za-z_$][\w$]*/g)) out.add(name);
|
|
@@ -8274,7 +8496,8 @@ async function convertSources(briefIn, sources, options) {
|
|
|
8274
8496
|
kind: p.kind,
|
|
8275
8497
|
fallback: p.original ?? "",
|
|
8276
8498
|
literal: norm(p.original ?? ""),
|
|
8277
|
-
locator: p.locator
|
|
8499
|
+
locator: p.locator,
|
|
8500
|
+
...p.inline ? { inline: true } : {}
|
|
8278
8501
|
});
|
|
8279
8502
|
}
|
|
8280
8503
|
}
|
|
@@ -8499,6 +8722,16 @@ async function convertSources(briefIn, sources, options) {
|
|
|
8499
8722
|
declaredRow(target, witness !== null, witness ?? byHand);
|
|
8500
8723
|
} else rewritten++;
|
|
8501
8724
|
}
|
|
8725
|
+
const templated = dynamicBindings.filter((d) => d.prop === null);
|
|
8726
|
+
for (let i = pending.length - 1; i >= 0; i--) {
|
|
8727
|
+
const row = pending[i];
|
|
8728
|
+
const target = targets.get(keyOf(row.route, row.scope, row.path));
|
|
8729
|
+
if (row.reason !== "NO_ORIGINAL" || !target || target.fallback !== "") continue;
|
|
8730
|
+
const shape = shapeOfPath(target.path);
|
|
8731
|
+
if (!templated.some((d) => d.path === shape && attributable(target, d.file))) continue;
|
|
8732
|
+
pending.splice(i, 1);
|
|
8733
|
+
rewritten++;
|
|
8734
|
+
}
|
|
8502
8735
|
for (const [file, splices] of [...componentEdits].sort(([a], [b]) => a.localeCompare(b))) {
|
|
8503
8736
|
const source = new MagicString6(contents.get(file));
|
|
8504
8737
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -8811,6 +9044,10 @@ async function plan(file, content, dialect, targets, sites, importAt, outcomes,
|
|
|
8811
9044
|
}
|
|
8812
9045
|
let refusal = null;
|
|
8813
9046
|
for (const site of fresh) {
|
|
9047
|
+
if (target.inline && site.where === "text" && !ownsSubtree(target.kind, site, true)) {
|
|
9048
|
+
refusal = "SUBTREE_NOT_HTML";
|
|
9049
|
+
continue;
|
|
9050
|
+
}
|
|
8814
9051
|
let bindings;
|
|
8815
9052
|
let bindingsMerge;
|
|
8816
9053
|
if (site.where === "prop" && site.attr) {
|
|
@@ -8845,7 +9082,8 @@ async function plan(file, content, dialect, targets, sites, importAt, outcomes,
|
|
|
8845
9082
|
// 🔴 AND ONLY WHEN THE ELEMENT'S SUBTREE IS THE VALUE. A value that is one TEXT NODE of
|
|
8846
9083
|
// a mixed element gets a wrapper of its own, so taking the element's inner range as its
|
|
8847
9084
|
// fallback swallowed the `<em>` beside it — the wrapper then rendered the sibling twice.
|
|
8848
|
-
|
|
9085
|
+
...target.inline ? { inline: true } : {},
|
|
9086
|
+
fallback: site.inner && ownsSubtree(target.kind, site, target.inline) ? content.slice(site.inner.start, site.inner.end).trim() : target.fallback,
|
|
8849
9087
|
snapshot: snapshotName(target.slug),
|
|
8850
9088
|
slug: target.slug,
|
|
8851
9089
|
scope: target.scope,
|
|
@@ -9249,10 +9487,12 @@ export {
|
|
|
9249
9487
|
flat,
|
|
9250
9488
|
helperSource,
|
|
9251
9489
|
importsIn,
|
|
9490
|
+
isDecorativeGlyph,
|
|
9252
9491
|
isDynamicRoute,
|
|
9253
9492
|
isKnownHelper,
|
|
9254
9493
|
isSourceCandidate,
|
|
9255
9494
|
locate,
|
|
9495
|
+
measureUnaddressable,
|
|
9256
9496
|
overlapping,
|
|
9257
9497
|
pageFilesFor,
|
|
9258
9498
|
parseFile4 as parseFile,
|