@bettercms-ai/convert 0.10.0 → 0.12.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 +47 -0
- package/README.md +54 -0
- package/dist/cli.js +338 -31
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +103 -2
- package/dist/index.js +340 -30
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4140,7 +4140,7 @@ function bindImages(ctx, targets) {
|
|
|
4140
4140
|
|
|
4141
4141
|
// src/helper.ts
|
|
4142
4142
|
import { createHash as createHash2 } from "crypto";
|
|
4143
|
-
var HELPER_VERSION =
|
|
4143
|
+
var HELPER_VERSION = 7;
|
|
4144
4144
|
var HEADER = "// @bettercms-ai/convert helper v";
|
|
4145
4145
|
var HELPER_PATH = Object.fromEntries(
|
|
4146
4146
|
Object.keys(DIALECT_RULES).map((d) => [d, DIALECT_RULES[d].helper])
|
|
@@ -4333,9 +4333,65 @@ export function bcmsSection(page: unknown, bind?: string, overrides?: unknown):
|
|
|
4333
4333
|
* how the codemod ran: one component renders a Phase A placement on one page and a Phase B
|
|
4334
4334
|
* instance on the next, and a repository does not have to be re-cut when its copy moves.
|
|
4335
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
|
+
|
|
4336
4391
|
export function bcmsFields(
|
|
4337
4392
|
placement: { blockId?: string; bind?: string; overrides?: unknown },
|
|
4338
4393
|
pathPrefix: string,
|
|
4394
|
+
map?: BcmsPathMap,
|
|
4339
4395
|
): (path: string) => string {
|
|
4340
4396
|
const id = placement.blockId;
|
|
4341
4397
|
const own = typeof id === "string" && id !== "" && !placement.bind && isRecord(placement.overrides);
|
|
@@ -4348,6 +4404,24 @@ export function bcmsFields(
|
|
|
4348
4404
|
* so prepending one would invent an address rather than correct it.
|
|
4349
4405
|
*/
|
|
4350
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
|
+
}
|
|
4351
4425
|
return (path: string): string => {
|
|
4352
4426
|
// A path outside the group this section was cut from is not one this placement re-addresses:
|
|
4353
4427
|
// it names a key the instance does not hold and a group this placement is not bound to.
|
|
@@ -4364,21 +4438,24 @@ var HISTORICAL = {
|
|
|
4364
4438
|
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0",
|
|
4365
4439
|
3: "d4305b2983adcb456ef782d35ea9d083a04465c2b68baa0d44a084dfa01a9227",
|
|
4366
4440
|
4: "a5742f64dacb1b26ee6dd5e1469a54871487934c71bc8267df19e3a80e417595",
|
|
4367
|
-
5: "0472fe3ffdcfa49e719c7b9da9cb63b6eba637cc22783bb00c3c2cadd3d800da"
|
|
4441
|
+
5: "0472fe3ffdcfa49e719c7b9da9cb63b6eba637cc22783bb00c3c2cadd3d800da",
|
|
4442
|
+
6: "c907e7245578d947c6541b1ab2c5c6659ea7789a1519e895b4a4aed122c6f649"
|
|
4368
4443
|
},
|
|
4369
4444
|
"src/lib/bcms-content.ts": {
|
|
4370
4445
|
1: "ae72ae14d706943d481c880d70da5789b4816ba1ba319a10fbc63ded4d0b42f2",
|
|
4371
4446
|
2: "a18731eb42f7db55dac0591c45f873758cde8e103fefcd6e5caf67936ae6a002",
|
|
4372
4447
|
3: "0be9c73160361882a038552bc02673c14ed532287823dabaafe36179bc55ed7e",
|
|
4373
4448
|
4: "0146d027a727ecd3c9e3fc336fcf1299b59ddf75b04091c05703f3f983ec28af",
|
|
4374
|
-
5: "c8c93655320fa65664611ba84541f3fa7bfa292cc9a2fd655373440b99049fba"
|
|
4449
|
+
5: "c8c93655320fa65664611ba84541f3fa7bfa292cc9a2fd655373440b99049fba",
|
|
4450
|
+
6: "63f1fa29e47e70c5193b81ee24addb238ba100247124d55bc17713102d587df5"
|
|
4375
4451
|
},
|
|
4376
4452
|
"composables/bcms-content.ts": {
|
|
4377
4453
|
1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
|
|
4378
4454
|
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0",
|
|
4379
4455
|
3: "d4305b2983adcb456ef782d35ea9d083a04465c2b68baa0d44a084dfa01a9227",
|
|
4380
4456
|
4: "a5742f64dacb1b26ee6dd5e1469a54871487934c71bc8267df19e3a80e417595",
|
|
4381
|
-
5: "0472fe3ffdcfa49e719c7b9da9cb63b6eba637cc22783bb00c3c2cadd3d800da"
|
|
4457
|
+
5: "0472fe3ffdcfa49e719c7b9da9cb63b6eba637cc22783bb00c3c2cadd3d800da",
|
|
4458
|
+
6: "c907e7245578d947c6541b1ab2c5c6659ea7789a1519e895b4a4aed122c6f649"
|
|
4382
4459
|
}
|
|
4383
4460
|
};
|
|
4384
4461
|
var sha256 = (value) => createHash2("sha256").update(value, "utf8").digest("hex");
|
|
@@ -5241,6 +5318,138 @@ function coverageOf(receipt) {
|
|
|
5241
5318
|
};
|
|
5242
5319
|
}
|
|
5243
5320
|
|
|
5321
|
+
// src/fix.ts
|
|
5322
|
+
var MAX_SNIPPET = 160;
|
|
5323
|
+
var MAX_WORDS = 12;
|
|
5324
|
+
var escapeRegExp2 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5325
|
+
function findLiteral(content, literal) {
|
|
5326
|
+
const words = literal.split(/\s+/).filter(Boolean).slice(0, MAX_WORDS);
|
|
5327
|
+
if (words.length === 0) return null;
|
|
5328
|
+
const pattern = new RegExp(words.map(escapeRegExp2).join("(?:\\s|<[^>]*>)+"), "u");
|
|
5329
|
+
const match = pattern.exec(content);
|
|
5330
|
+
return match ? { index: match.index, length: match[0].length } : null;
|
|
5331
|
+
}
|
|
5332
|
+
function placeOf(content, index) {
|
|
5333
|
+
const before = content.slice(0, index);
|
|
5334
|
+
const line = before.split("\n").length;
|
|
5335
|
+
const start = before.lastIndexOf("\n") + 1;
|
|
5336
|
+
const end = content.indexOf("\n", index);
|
|
5337
|
+
return {
|
|
5338
|
+
line,
|
|
5339
|
+
col: index - start + 1,
|
|
5340
|
+
snippet: content.slice(start, end < 0 ? content.length : end).trim().slice(0, MAX_SNIPPET)
|
|
5341
|
+
};
|
|
5342
|
+
}
|
|
5343
|
+
function fileHead(content) {
|
|
5344
|
+
return { line: 1, snippet: (content ?? "").split("\n", 1)[0].trim().slice(0, MAX_SNIPPET) };
|
|
5345
|
+
}
|
|
5346
|
+
var isMarkdown = (file) => /\.mdx?$/i.test(file);
|
|
5347
|
+
var isDataModule = (file) => /\.(json|ya?ml|[mc]?[jt]s)$/i.test(file) && !/\.d\.ts$/i.test(file);
|
|
5348
|
+
function inDataRegion(content, index) {
|
|
5349
|
+
if (content.startsWith("---")) {
|
|
5350
|
+
const close = content.indexOf("\n---", 3);
|
|
5351
|
+
if (close >= 0 && index < close) return true;
|
|
5352
|
+
}
|
|
5353
|
+
for (const match of content.matchAll(/<script\b[^>]*>([\s\S]*?)<\/script>/gi)) {
|
|
5354
|
+
const start = match.index + match[0].indexOf(">") + 1;
|
|
5355
|
+
if (index >= start && index < start + match[1].length) return true;
|
|
5356
|
+
}
|
|
5357
|
+
return false;
|
|
5358
|
+
}
|
|
5359
|
+
var ELEMENT_ACTIONS = /* @__PURE__ */ new Set([
|
|
5360
|
+
"wrap-span",
|
|
5361
|
+
"declare-attr",
|
|
5362
|
+
"declare-richtext"
|
|
5363
|
+
]);
|
|
5364
|
+
function actionFor(reason, input, file, inData) {
|
|
5365
|
+
const chosen = reasonAction(reason, input, file, inData);
|
|
5366
|
+
if (inData && ELEMENT_ACTIONS.has(chosen.action)) {
|
|
5367
|
+
return { action: "bind-data", why: "The copy is a row in this file's own data \u2014 bind that field." };
|
|
5368
|
+
}
|
|
5369
|
+
return chosen;
|
|
5370
|
+
}
|
|
5371
|
+
function reasonAction(reason, input, file, inData) {
|
|
5372
|
+
const richtext = input.kind === "richtext" || input.inline === true;
|
|
5373
|
+
switch (reason) {
|
|
5374
|
+
case "AMBIGUOUS_LITERAL":
|
|
5375
|
+
if (input.routes > 1) {
|
|
5376
|
+
return {
|
|
5377
|
+
action: "manual",
|
|
5378
|
+
why: `${file} renders this value on ${input.routes} routes, so it is shared chrome rather than page copy: promote it to the project Layout (a Layout field is one value for the whole site) and rerun, or give each route its own element.`
|
|
5379
|
+
};
|
|
5380
|
+
}
|
|
5381
|
+
return {
|
|
5382
|
+
action: "declare-attr",
|
|
5383
|
+
why: input.candidates.length > 1 ? `This copy appears in ${input.candidates.join(", ")}; declare the one this route renders.` : "Two paths want this element and nothing says which \u2014 declare the right one by hand."
|
|
5384
|
+
};
|
|
5385
|
+
case "REPEATER_AMBIGUOUS":
|
|
5386
|
+
case "REPEATER_FIXED_LENGTH":
|
|
5387
|
+
return {
|
|
5388
|
+
action: "declare-attr",
|
|
5389
|
+
why: "Declare this row's leaf on the element itself, or rewrite the loop to read the CMS array."
|
|
5390
|
+
};
|
|
5391
|
+
case "IN_EXPRESSION":
|
|
5392
|
+
if (inData) {
|
|
5393
|
+
return { action: "bind-data", why: "The copy is a row in this file's own data \u2014 bind that field." };
|
|
5394
|
+
}
|
|
5395
|
+
return richtext ? { action: "declare-richtext", why: "The container is fed markup \u2014 bind it with the richtext helper." } : { action: "bind-expression", why: "Replace the computed value with the helper's read for this path." };
|
|
5396
|
+
case "DIALECT_UNSUPPORTED":
|
|
5397
|
+
if (isMarkdown(file)) {
|
|
5398
|
+
return {
|
|
5399
|
+
action: "bind-data",
|
|
5400
|
+
why: "This is a content-collection body: bind the element that renders it and make the entry the source."
|
|
5401
|
+
};
|
|
5402
|
+
}
|
|
5403
|
+
return isDataModule(file) ? { action: "bind-data", why: `${file} holds this copy as data \u2014 bind the element that renders it.` } : { action: "manual", why: `No parser here reads ${file}, so the declaration has to be written by hand.` };
|
|
5404
|
+
case "NOT_IN_SOURCE":
|
|
5405
|
+
case "NO_ORIGINAL":
|
|
5406
|
+
return isDataModule(file) || inData ? { action: "bind-data", why: "The template computes this from data \u2014 bind the data field instead of the text." } : {
|
|
5407
|
+
action: "manual",
|
|
5408
|
+
why: "The built page renders this and no source file holds it verbatim; find what formats it."
|
|
5409
|
+
};
|
|
5410
|
+
case "IN_DATA_FILE":
|
|
5411
|
+
case "IMAGE_ASSET_UNRESOLVED":
|
|
5412
|
+
return { action: "bind-data", why: "Bind the data field this value comes from." };
|
|
5413
|
+
case "PROP_TARGET_NOT_FOUND":
|
|
5414
|
+
case "PROP_DRILLED_DEEP":
|
|
5415
|
+
return {
|
|
5416
|
+
action: "wrap-span",
|
|
5417
|
+
why: "The component renders this in a position no attribute reaches \u2014 wrap it in a span of its own."
|
|
5418
|
+
};
|
|
5419
|
+
case "SUBSTRING_ONLY":
|
|
5420
|
+
return { action: "wrap-span", why: "Only part of the element's text is this value \u2014 give it its own span." };
|
|
5421
|
+
case "SUBTREE_NOT_HTML":
|
|
5422
|
+
return { action: "declare-richtext", why: "The value is the element's whole subtree \u2014 bind it as richtext." };
|
|
5423
|
+
default:
|
|
5424
|
+
return { action: "manual", why: `${reason} \u2014 see the receipt's message for what blocked this path.` };
|
|
5425
|
+
}
|
|
5426
|
+
}
|
|
5427
|
+
function fixFor(row, input) {
|
|
5428
|
+
const file = row.file ?? input.candidates[0] ?? "";
|
|
5429
|
+
const content = input.contents.get(file);
|
|
5430
|
+
const at = content ? findLiteral(content, input.literal) : null;
|
|
5431
|
+
const place = at ? placeOf(content, at.index) : { ...fileHead(content), col: void 0 };
|
|
5432
|
+
const { action, why } = actionFor(row.reason, input, file, at !== null && inDataRegion(content, at.index));
|
|
5433
|
+
return {
|
|
5434
|
+
file,
|
|
5435
|
+
line: row.line ?? place.line,
|
|
5436
|
+
...place.col !== void 0 ? { col: place.col } : {},
|
|
5437
|
+
snippet: place.snippet,
|
|
5438
|
+
action,
|
|
5439
|
+
...why ? { why } : {}
|
|
5440
|
+
};
|
|
5441
|
+
}
|
|
5442
|
+
function routeCounts(pending) {
|
|
5443
|
+
const routes = /* @__PURE__ */ new Map();
|
|
5444
|
+
for (const row of pending) {
|
|
5445
|
+
const id = `${row.scope}|${row.path}`;
|
|
5446
|
+
const held = routes.get(id) ?? /* @__PURE__ */ new Set();
|
|
5447
|
+
held.add(row.route);
|
|
5448
|
+
routes.set(id, held);
|
|
5449
|
+
}
|
|
5450
|
+
return new Map([...routes].map(([id, set]) => [id, set.size]));
|
|
5451
|
+
}
|
|
5452
|
+
|
|
5244
5453
|
// src/sites/html.ts
|
|
5245
5454
|
import { parse as parseHtml } from "parse5";
|
|
5246
5455
|
var RAW_TAGS = /* @__PURE__ */ new Set(["script", "style", "noscript", "template"]);
|
|
@@ -6361,7 +6570,9 @@ var SKIP_DIRS = [
|
|
|
6361
6570
|
".vercel/",
|
|
6362
6571
|
".cache/",
|
|
6363
6572
|
"coverage/",
|
|
6364
|
-
"vendor/"
|
|
6573
|
+
"vendor/",
|
|
6574
|
+
"__tests__/",
|
|
6575
|
+
"__mocks__/"
|
|
6365
6576
|
];
|
|
6366
6577
|
var SKIP_FILES = ["package-lock.json", "bun.lock", "bun.lockb", "yarn.lock", "pnpm-lock.yaml"];
|
|
6367
6578
|
var SOURCE_EXTENSIONS = [
|
|
@@ -6399,11 +6610,13 @@ var TEMPLATE_DIRS = /* @__PURE__ */ new Set([
|
|
|
6399
6610
|
"_includes",
|
|
6400
6611
|
"_layouts"
|
|
6401
6612
|
]);
|
|
6613
|
+
var isAboutTheSite = (lower) => /\.(test|spec)\.[^./]+$/.test(lower) || /^[^/]*\.config\.[^./]+$/.test(lower);
|
|
6402
6614
|
function isSourceCandidate(path) {
|
|
6403
6615
|
const lower = path.toLowerCase();
|
|
6404
6616
|
if (SKIP_DIRS.some((dir) => lower === dir.slice(0, -1) || lower.startsWith(dir) || lower.includes(`/${dir}`)))
|
|
6405
6617
|
return false;
|
|
6406
6618
|
if (SKIP_FILES.includes(lower.split("/").pop() ?? "")) return false;
|
|
6619
|
+
if (isAboutTheSite(lower)) return false;
|
|
6407
6620
|
if (/(^|\/)[jt]sconfig\.json$/.test(lower)) return true;
|
|
6408
6621
|
if (!SOURCE_EXTENSIONS.some((ext) => lower.endsWith(ext))) return false;
|
|
6409
6622
|
if (!PROSE_EXTENSIONS.some((ext) => lower.endsWith(ext))) return true;
|
|
@@ -6887,6 +7100,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
6887
7100
|
return held;
|
|
6888
7101
|
};
|
|
6889
7102
|
const fresh = /* @__PURE__ */ new Set();
|
|
7103
|
+
const freshFiles = /* @__PURE__ */ new Map();
|
|
6890
7104
|
const bindOf = (section) => instanceOwned(section) ? void 0 : section.groupKey;
|
|
6891
7105
|
const placementOf = (slug, section) => {
|
|
6892
7106
|
const bind = bindOf(section);
|
|
@@ -7214,6 +7428,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
7214
7428
|
}
|
|
7215
7429
|
extractable.set(resolved.slug, { name: resolved.name, path, kind: "default", imported: null });
|
|
7216
7430
|
fresh.add(resolved.slug);
|
|
7431
|
+
freshFiles.set(resolved.slug, path);
|
|
7217
7432
|
addFile(outFiles, contents, {
|
|
7218
7433
|
path,
|
|
7219
7434
|
operation: existing === void 0 ? "add" : "modify",
|
|
@@ -7299,6 +7514,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
7299
7514
|
sections,
|
|
7300
7515
|
// What THIS run wrote a file for. A component an earlier run left is reused, not re-reported.
|
|
7301
7516
|
components: [...fresh].sort(),
|
|
7517
|
+
componentFiles: [...freshFiles].sort(([a], [b]) => a.localeCompare(b)).map(([slug, path]) => ({ slug, path, export: "default" })),
|
|
7302
7518
|
planPending,
|
|
7303
7519
|
notes
|
|
7304
7520
|
}
|
|
@@ -7478,7 +7694,7 @@ async function locateSections(sections, briefPaths, sources, treeOf) {
|
|
|
7478
7694
|
for (const section of sections) {
|
|
7479
7695
|
const paths = fieldPaths(section);
|
|
7480
7696
|
const literals = new Set(
|
|
7481
|
-
briefPaths.filter((p) => (p.scope ?? "page") === "page" &&
|
|
7697
|
+
briefPaths.filter((p) => (p.scope ?? "page") === "page" && inSection(section, p.path) && p.original).map((p) => flat(stripTags(p.original))).filter(Boolean)
|
|
7482
7698
|
);
|
|
7483
7699
|
const hits = /* @__PURE__ */ new Map();
|
|
7484
7700
|
for (const source of sources) {
|
|
@@ -7489,7 +7705,7 @@ async function locateSections(sections, briefPaths, sources, treeOf) {
|
|
|
7489
7705
|
const found = /* @__PURE__ */ new Map();
|
|
7490
7706
|
for (const declaration2 of declarationsIn(parsed.roots, source.content)) {
|
|
7491
7707
|
if (declaration2.scope !== "page") continue;
|
|
7492
|
-
if (
|
|
7708
|
+
if (!inSection(section, declaration2.path)) continue;
|
|
7493
7709
|
const placed2 = elements.get(declaration2.at);
|
|
7494
7710
|
if (placed2) found.set(declaration2.at, placed2);
|
|
7495
7711
|
}
|
|
@@ -7514,6 +7730,12 @@ async function locateSections(sections, briefPaths, sources, treeOf) {
|
|
|
7514
7730
|
return { placed, misses };
|
|
7515
7731
|
}
|
|
7516
7732
|
var groupOf = (path) => path.split(/[.[]/)[0] ?? "";
|
|
7733
|
+
var inSection = (section, path) => {
|
|
7734
|
+
if (groupOf(path) === section.groupKey) return true;
|
|
7735
|
+
const rowless = (value) => value.replace(/\[\d+\]/g, "[]");
|
|
7736
|
+
const target = rowless(path);
|
|
7737
|
+
return fieldPaths(section).some((fieldPath) => rowless(fieldPath) === target);
|
|
7738
|
+
};
|
|
7517
7739
|
var fieldPaths = (section) => (section.fields ?? []).map((field) => field.path ?? `${section.groupKey}.${field.key ?? ""}`);
|
|
7518
7740
|
function elementIndex(roots) {
|
|
7519
7741
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -7616,6 +7838,9 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved, pathP
|
|
|
7616
7838
|
...addresses.opaque
|
|
7617
7839
|
];
|
|
7618
7840
|
if (unconvertible.length > 0) return { content: "", free: [], duplicates: [], unconvertible };
|
|
7841
|
+
const flat3 = pathPrefix !== "" && !pathPrefix.endsWith(".");
|
|
7842
|
+
const pathMap = flat3 ? flatPathMap(resolved.section, pathPrefix) : null;
|
|
7843
|
+
if (flat3 && !pathMap) return { content: "", free: [], duplicates: [], unconvertible: [`${pathPrefix}* (a repeater leaf without an override address)`] };
|
|
7619
7844
|
const cut = content.slice(resolved.root.start, resolved.root.end);
|
|
7620
7845
|
const reserved = /* @__PURE__ */ new Set([...topLevelBindings(dialect, parsed, content), ...referencedNames2(cut)]);
|
|
7621
7846
|
const introduced = allocateNames(reserved, [
|
|
@@ -7634,7 +7859,7 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved, pathP
|
|
|
7634
7859
|
const helpers = [
|
|
7635
7860
|
.../* @__PURE__ */ new Set([
|
|
7636
7861
|
...helperNames.map((h) => alias(h.name, h.local)),
|
|
7637
|
-
"bcmsSection",
|
|
7862
|
+
flat3 ? "bcmsSectionPaths" : "bcmsSection",
|
|
7638
7863
|
...bound > 0 ? [alias("bcmsFields", fieldsName)] : []
|
|
7639
7864
|
])
|
|
7640
7865
|
].sort();
|
|
@@ -7664,9 +7889,10 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved, pathP
|
|
|
7664
7889
|
const key = /^[A-Za-z_$][\w$]*$/.test(groupKey) ? groupKey : JSON.stringify(groupKey);
|
|
7665
7890
|
const carriedDeclarations = carry.declarations.length > 0 ? `${carry.declarations.join("\n")}
|
|
7666
7891
|
` : "";
|
|
7667
|
-
const copy = snapshot ? `const ${snapshot} = { ${key}: bcmsSection(page, bind, overrides) }
|
|
7668
|
-
const field = bound > 0 ? `const ${fieldName} = ${fieldsName}({ blockId, bind, overrides }, ${JSON.stringify(pathPrefix)});` : "";
|
|
7892
|
+
const copy = !snapshot ? "" : flat3 ? `const ${snapshot} = bcmsSectionPaths(page, bind, overrides, ${JSON.stringify(pathPrefix)}, ${JSON.stringify(pathMap)});` : `const ${snapshot} = { ${key}: bcmsSection(page, bind, overrides) };`;
|
|
7893
|
+
const field = bound > 0 ? `const ${fieldName} = ${fieldsName}({ blockId, bind, overrides }, ${JSON.stringify(pathPrefix)}${flat3 ? `, ${JSON.stringify(pathMap)}` : ""});` : "";
|
|
7669
7894
|
const declared = [copy, field].filter(Boolean).join("\n");
|
|
7895
|
+
const styles = dialect === "astro" ? carriedStyles(content) : "";
|
|
7670
7896
|
if (dialect === "astro") {
|
|
7671
7897
|
return { free: [], duplicates: [], unconvertible: [], content: `---
|
|
7672
7898
|
${SECTION_MARKER}
|
|
@@ -7686,7 +7912,9 @@ const { blockId, bind, overrides, page } = Astro.props;
|
|
|
7686
7912
|
${carriedDeclarations}${declared}
|
|
7687
7913
|
---
|
|
7688
7914
|
${markup}
|
|
7689
|
-
`
|
|
7915
|
+
${styles ? `
|
|
7916
|
+
${styles}
|
|
7917
|
+
` : ""}` };
|
|
7690
7918
|
}
|
|
7691
7919
|
return { free: [], duplicates: [], unconvertible: [], content: `${SECTION_MARKER}
|
|
7692
7920
|
${[...carried, `import { ${helpers.join(", ")} } from "${helperSpecifier}";`, `import type { BcmsSectionProps } from "${relativeImport(path, SECTIONS_LIB).replace(/\.tsx?$/, "")}";`].join("\n")}
|
|
@@ -7960,6 +8188,9 @@ import { bcmsFields } from "${helper}";`);
|
|
|
7960
8188
|
return { files, refused };
|
|
7961
8189
|
}
|
|
7962
8190
|
function propPrefix(section) {
|
|
8191
|
+
return exactPrefix(section) ?? flatPrefix(section);
|
|
8192
|
+
}
|
|
8193
|
+
function exactPrefix(section) {
|
|
7963
8194
|
const prefixes = /* @__PURE__ */ new Set();
|
|
7964
8195
|
for (const field of section.fields ?? []) {
|
|
7965
8196
|
const { path, key } = field;
|
|
@@ -7970,6 +8201,49 @@ function propPrefix(section) {
|
|
|
7970
8201
|
if (prefixes.size > 1) return null;
|
|
7971
8202
|
return prefixes.size === 1 ? [...prefixes][0] : `${section.groupKey}.`;
|
|
7972
8203
|
}
|
|
8204
|
+
var foldKey = (remainder) => remainder.replace(/\[(\d+)\]/g, "_$1").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "") || "value";
|
|
8205
|
+
function flatPrefix(section) {
|
|
8206
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
8207
|
+
let informative = false;
|
|
8208
|
+
for (const field of section.fields ?? []) {
|
|
8209
|
+
const { path, key } = field;
|
|
8210
|
+
if (typeof path !== "string" || typeof key !== "string") continue;
|
|
8211
|
+
informative = true;
|
|
8212
|
+
const prefix = ["-", "_"].map((sep) => `${section.groupKey}${sep}`).find((candidate) => path.startsWith(candidate) && (typeof field.overrideKey === "string" || foldKey(path.slice(candidate.length)) === key));
|
|
8213
|
+
if (!prefix) return null;
|
|
8214
|
+
prefixes.add(prefix);
|
|
8215
|
+
}
|
|
8216
|
+
return informative && prefixes.size === 1 ? [...prefixes][0] : null;
|
|
8217
|
+
}
|
|
8218
|
+
function flatPathMap(section, prefix) {
|
|
8219
|
+
const scalars = {};
|
|
8220
|
+
const tables = {};
|
|
8221
|
+
for (const field of section.fields ?? []) {
|
|
8222
|
+
const { path, key } = field;
|
|
8223
|
+
if (typeof path !== "string" || typeof key !== "string" || !path.startsWith(prefix)) continue;
|
|
8224
|
+
const row = /^([^[]+)\[\d+\]\.(.+)$/.exec(path);
|
|
8225
|
+
if (!row) {
|
|
8226
|
+
scalars[path] = field.overrideKey ?? key;
|
|
8227
|
+
continue;
|
|
8228
|
+
}
|
|
8229
|
+
const address = typeof field.overrideKey === "string" ? /^([^[]+)\[\d+\]\.(.+)$/.exec(field.overrideKey) : null;
|
|
8230
|
+
if (!address) return null;
|
|
8231
|
+
const table = tables[row[1]] ??= { key: address[1], columns: {} };
|
|
8232
|
+
if (table.key !== address[1]) return null;
|
|
8233
|
+
table.columns[row[2]] = address[2];
|
|
8234
|
+
}
|
|
8235
|
+
return { scalars, tables };
|
|
8236
|
+
}
|
|
8237
|
+
function carriedStyles(content) {
|
|
8238
|
+
const blocks = [];
|
|
8239
|
+
for (const match of content.matchAll(/<style\b([^>]*)>([\s\S]*?)<\/style>/g)) {
|
|
8240
|
+
const attributes = match[1] ?? "";
|
|
8241
|
+
if (/\bis:global\b|\bdefine:vars\b/.test(attributes)) continue;
|
|
8242
|
+
if (/\blang\s*=/.test(attributes) || /@(import|use|forward)\b/.test(match[2] ?? "")) continue;
|
|
8243
|
+
blocks.push(match[0]);
|
|
8244
|
+
}
|
|
8245
|
+
return blocks.join("\n");
|
|
8246
|
+
}
|
|
7973
8247
|
function referencedNames2(markup) {
|
|
7974
8248
|
const out = /* @__PURE__ */ new Set();
|
|
7975
8249
|
for (const [name] of markup.matchAll(/[A-Za-z_$][\w$]*/g)) out.add(name);
|
|
@@ -8291,7 +8565,7 @@ var ConvertError = class extends Error {
|
|
|
8291
8565
|
code;
|
|
8292
8566
|
};
|
|
8293
8567
|
var keyOf = (route, scope, path) => scope === "layout" ? `layout::${path}` : `${route}::page::${path}`;
|
|
8294
|
-
var
|
|
8568
|
+
var escapeRegExp3 = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8295
8569
|
var identifierFor2 = (slug) => `bcms${slug.replace(/[^A-Za-z0-9]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^./, (c) => c.toUpperCase())}`;
|
|
8296
8570
|
var UNPLACED = /* @__PURE__ */ new Set(["NOT_IN_SOURCE", "IN_EXPRESSION"]);
|
|
8297
8571
|
async function drillProp(ctx, fromFile, component, prop, kind, hop) {
|
|
@@ -8638,6 +8912,33 @@ async function convertSources(briefIn, sources, options) {
|
|
|
8638
8912
|
else outFiles.push({ path, operation: "modify", content });
|
|
8639
8913
|
}
|
|
8640
8914
|
pending.push(...canvas.pending);
|
|
8915
|
+
for (const file of outFiles) if (file.operation === "modify") latest.set(file.path, file.content);
|
|
8916
|
+
const pageFilesOrTemplate = (route, files) => {
|
|
8917
|
+
const all = [...files];
|
|
8918
|
+
const exact = pageFilesFor(route, all);
|
|
8919
|
+
if (exact.length > 0) return exact;
|
|
8920
|
+
const wanted = route.split("/").filter(Boolean);
|
|
8921
|
+
return all.filter((file) => {
|
|
8922
|
+
if (!isDynamicRoute(file)) return false;
|
|
8923
|
+
const pattern = routeOfFile(file)?.split("/").filter(Boolean);
|
|
8924
|
+
if (!pattern) return false;
|
|
8925
|
+
const rest = pattern.some((segment) => segment.startsWith("[..."));
|
|
8926
|
+
if (rest ? wanted.length < pattern.length : wanted.length !== pattern.length) return false;
|
|
8927
|
+
return pattern.every((segment, i) => segment.startsWith("[") || segment === wanted[i]);
|
|
8928
|
+
}).sort();
|
|
8929
|
+
};
|
|
8930
|
+
const routes = routeCounts(pending);
|
|
8931
|
+
for (const row of pending) {
|
|
8932
|
+
const target = targets.get(keyOf(row.route, row.scope, row.path));
|
|
8933
|
+
row.fix = fixFor(row, {
|
|
8934
|
+
literal: target?.literal ?? "",
|
|
8935
|
+
kind: row.kind,
|
|
8936
|
+
...target?.inline ? { inline: true } : {},
|
|
8937
|
+
candidates: locatedByKey.get(keyOf(row.route, row.scope, row.path))?.files ?? pageFilesOrTemplate(row.route, latest.keys()),
|
|
8938
|
+
contents: latest,
|
|
8939
|
+
routes: routes.get(`${row.scope}|${row.path}`) ?? 1
|
|
8940
|
+
});
|
|
8941
|
+
}
|
|
8641
8942
|
const receipt = assertReceipt({
|
|
8642
8943
|
briefDigest: digest,
|
|
8643
8944
|
paths: {
|
|
@@ -8850,13 +9151,13 @@ async function plan(file, content, dialect, targets, sites, importAt, outcomes,
|
|
|
8850
9151
|
for (const target of sharing) outcomes.set(target.key, missing(literal));
|
|
8851
9152
|
continue;
|
|
8852
9153
|
}
|
|
8853
|
-
const
|
|
8854
|
-
if (!assigned) {
|
|
8855
|
-
for (const target of sharing) outcomes.set(target.key, "AMBIGUOUS_LITERAL");
|
|
8856
|
-
continue;
|
|
8857
|
-
}
|
|
9154
|
+
const resolved = sharing.length === 1 ? { assigned: /* @__PURE__ */ new Map([[sharing[0].key, forLiteral]]), ambiguous: /* @__PURE__ */ new Set() } : resolvePositionally(sharing, forLiteral);
|
|
8858
9155
|
for (const target of sharing) {
|
|
8859
|
-
|
|
9156
|
+
if (resolved.ambiguous.has(target.key)) {
|
|
9157
|
+
outcomes.set(target.key, "AMBIGUOUS_LITERAL");
|
|
9158
|
+
continue;
|
|
9159
|
+
}
|
|
9160
|
+
const mine = resolved.assigned.get(target.key) ?? [];
|
|
8860
9161
|
const rewritable = mine.filter((s) => s.where !== "script" && s.where !== "comment" && !s.partial);
|
|
8861
9162
|
if (rewritable.length === 0) {
|
|
8862
9163
|
outcomes.set(
|
|
@@ -9201,19 +9502,25 @@ function repeatGroups(targets) {
|
|
|
9201
9502
|
}
|
|
9202
9503
|
function resolvePositionally(targets, sites) {
|
|
9203
9504
|
const assigned = /* @__PURE__ */ new Map();
|
|
9204
|
-
const
|
|
9505
|
+
const ambiguous = /* @__PURE__ */ new Set();
|
|
9506
|
+
const picked = /* @__PURE__ */ new Map();
|
|
9507
|
+
const wanters = /* @__PURE__ */ new Map();
|
|
9205
9508
|
for (const target of targets) {
|
|
9206
9509
|
const locator = target.locator;
|
|
9207
|
-
|
|
9208
|
-
const
|
|
9209
|
-
|
|
9210
|
-
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
|
|
9214
|
-
|
|
9510
|
+
const candidates = locator ? sites.filter((s) => s.siblingIndex === locator.siblingIndex && isSubsequence(locator.ancestors, s.ancestors)) : [];
|
|
9511
|
+
const site = locator?.repeat ? candidates[locator.repeat.index] : candidates.length === 1 ? candidates[0] : void 0;
|
|
9512
|
+
if (!site) {
|
|
9513
|
+
ambiguous.add(target.key);
|
|
9514
|
+
continue;
|
|
9515
|
+
}
|
|
9516
|
+
picked.set(target.key, site);
|
|
9517
|
+
wanters.set(site, (wanters.get(site) ?? 0) + 1);
|
|
9518
|
+
}
|
|
9519
|
+
for (const [key, site] of picked) {
|
|
9520
|
+
if ((wanters.get(site) ?? 0) > 1) ambiguous.add(key);
|
|
9521
|
+
else assigned.set(key, [site]);
|
|
9215
9522
|
}
|
|
9216
|
-
return assigned;
|
|
9523
|
+
return { assigned, ambiguous };
|
|
9217
9524
|
}
|
|
9218
9525
|
function isSubsequence(wanted, chain) {
|
|
9219
9526
|
let at = 0;
|
|
@@ -9236,7 +9543,7 @@ async function tierThree(file, content, dialect, targets, llmFallback) {
|
|
|
9236
9543
|
if (elements.length !== targets.length) return { ok: false, reason: "unverifiable" };
|
|
9237
9544
|
const locals = helperLocals(reparsed, dialect, file);
|
|
9238
9545
|
const reads = DIALECT_RULES[dialect].text !== null;
|
|
9239
|
-
const boundary = (name) => `(?<![\\w$])${
|
|
9546
|
+
const boundary = (name) => `(?<![\\w$])${escapeRegExp3(name)}(?![\\w$])`;
|
|
9240
9547
|
const claimed = /* @__PURE__ */ new Set();
|
|
9241
9548
|
for (const element of elements) {
|
|
9242
9549
|
const candidates = targets.filter(
|
|
@@ -9244,7 +9551,7 @@ async function tierThree(file, content, dialect, targets, llmFallback) {
|
|
|
9244
9551
|
);
|
|
9245
9552
|
const match = candidates.find((target) => {
|
|
9246
9553
|
if (!reads) return true;
|
|
9247
|
-
const quoted =
|
|
9554
|
+
const quoted = escapeRegExp3(JSON.stringify(target.path));
|
|
9248
9555
|
if (target.scope === "layout") {
|
|
9249
9556
|
return locals.bcmsLayout.some(
|
|
9250
9557
|
(name) => new RegExp(`${boundary(name)}\\s*\\(\\s*${quoted}`).test(element.own)
|
|
@@ -9344,8 +9651,10 @@ export {
|
|
|
9344
9651
|
dialectOf,
|
|
9345
9652
|
exportedFunction,
|
|
9346
9653
|
extendedConfigs,
|
|
9654
|
+
findLiteral,
|
|
9347
9655
|
findSites,
|
|
9348
9656
|
findSitesTolerant,
|
|
9657
|
+
fixFor,
|
|
9349
9658
|
flat,
|
|
9350
9659
|
helperSource,
|
|
9351
9660
|
importsIn,
|
|
@@ -9367,6 +9676,7 @@ export {
|
|
|
9367
9676
|
relativeImport,
|
|
9368
9677
|
resolveSpecifier,
|
|
9369
9678
|
rewriteFile,
|
|
9679
|
+
routeCounts,
|
|
9370
9680
|
routeOfFile,
|
|
9371
9681
|
stripTags,
|
|
9372
9682
|
unreadableConfigs,
|