@bettercms-ai/convert 0.22.0 → 0.24.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 +12 -0
- package/README.md +39 -0
- package/dist/cli.js +502 -10
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +96 -2
- package/dist/index.js +455 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import
|
|
2
|
+
import MagicString7 from "magic-string";
|
|
3
3
|
|
|
4
4
|
// src/brief.ts
|
|
5
5
|
import { createHash } from "crypto";
|
|
@@ -3764,8 +3764,8 @@ function bindLists(ctx, members, module, out, indexes) {
|
|
|
3764
3764
|
const loops = iterations(ctx.parsed.roots, name);
|
|
3765
3765
|
if (loops.length > 1) return { refuse: "REPEATER_AMBIGUOUS" };
|
|
3766
3766
|
if (loops.length === 0) return null;
|
|
3767
|
-
const
|
|
3768
|
-
if (
|
|
3767
|
+
const mentions3 = ctx.content.match(new RegExp(`(?<![\\w$])${name}(?![\\w$])`, "g")) ?? [];
|
|
3768
|
+
if (mentions3.length !== 2 || escapes.has(name)) return { refuse: "IN_EXPRESSION" };
|
|
3769
3769
|
return { loop: loops[0], params: callbackParams(loops[0], name) };
|
|
3770
3770
|
}
|
|
3771
3771
|
});
|
|
@@ -4187,7 +4187,7 @@ async function bindImages(ctx, targets) {
|
|
|
4187
4187
|
|
|
4188
4188
|
// src/helper.ts
|
|
4189
4189
|
import { createHash as createHash2 } from "crypto";
|
|
4190
|
-
var HELPER_VERSION =
|
|
4190
|
+
var HELPER_VERSION = 12;
|
|
4191
4191
|
var HEADER = "// @bettercms-ai/convert helper v";
|
|
4192
4192
|
var HELPER_PATH = Object.fromEntries(
|
|
4193
4193
|
Object.keys(DIALECT_RULES).map((d) => [d, DIALECT_RULES[d].helper])
|
|
@@ -4244,8 +4244,69 @@ function render(value: unknown): string | undefined {
|
|
|
4244
4244
|
return undefined;
|
|
4245
4245
|
}
|
|
4246
4246
|
|
|
4247
|
+
/**
|
|
4248
|
+
* The PUBLISHED copy, on a server BetterCMS hosts \u2014 read at request time, not at build time.
|
|
4249
|
+
*
|
|
4250
|
+
* BetterCMS keeps one file current for a running server app (\`BCMS_CONTENT_FILE\`, the same
|
|
4251
|
+
* bcms-content.json the build was given) and rewrites it on every publish, so a server-rendered
|
|
4252
|
+
* page shows a publish within seconds instead of after a rebuild. Each snapshot this module reads
|
|
4253
|
+
* resolves to its live counterpart: the shared layout by identity, a page by its \`slug\`.
|
|
4254
|
+
*
|
|
4255
|
+
* Nowhere else does anything change: with no variable (a build, \`astro dev\`, a browser, an edge
|
|
4256
|
+
* runtime) every snapshot is the one the bundle imported, exactly as before. A file that cannot be
|
|
4257
|
+
* read keeps the last good copy; it is checked at most once a second.
|
|
4258
|
+
*/
|
|
4259
|
+
type BcmsLiveContent = { stamp: number; bySlug: Map<string, unknown>; layout: unknown };
|
|
4260
|
+
let bcmsLive: BcmsLiveContent | null = null;
|
|
4261
|
+
let bcmsLiveCheckedAt = 0;
|
|
4262
|
+
|
|
4263
|
+
function liveContent(): BcmsLiveContent | null {
|
|
4264
|
+
const proc = (globalThis as {
|
|
4265
|
+
process?: { env?: Record<string, string | undefined>; getBuiltinModule?: (id: string) => unknown };
|
|
4266
|
+
}).process;
|
|
4267
|
+
const file = proc?.env?.BCMS_CONTENT_FILE;
|
|
4268
|
+
if (!file || typeof proc?.getBuiltinModule !== "function") return null;
|
|
4269
|
+
const now = Date.now();
|
|
4270
|
+
if (now - bcmsLiveCheckedAt < 1000) return bcmsLive;
|
|
4271
|
+
bcmsLiveCheckedAt = now;
|
|
4272
|
+
try {
|
|
4273
|
+
const fs = proc.getBuiltinModule("node:fs") as {
|
|
4274
|
+
statSync(path: string): { mtimeMs: number };
|
|
4275
|
+
readFileSync(path: string, encoding: "utf8"): string;
|
|
4276
|
+
};
|
|
4277
|
+
const stamp = fs.statSync(file).mtimeMs;
|
|
4278
|
+
if (bcmsLive && bcmsLive.stamp === stamp) return bcmsLive;
|
|
4279
|
+
const snapshot: unknown = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
4280
|
+
const bySlug = new Map<string, unknown>();
|
|
4281
|
+
const collections = isRecord(snapshot) && isRecord(snapshot.collections) ? snapshot.collections : {};
|
|
4282
|
+
for (const rows of Object.values(collections)) {
|
|
4283
|
+
if (!Array.isArray(rows)) continue;
|
|
4284
|
+
for (const row of rows) {
|
|
4285
|
+
if (isRecord(row) && typeof row.slug === "string" && !bySlug.has(row.slug)) bySlug.set(row.slug, row);
|
|
4286
|
+
}
|
|
4287
|
+
}
|
|
4288
|
+
const pages = isRecord(snapshot) && Array.isArray(snapshot.pages) ? snapshot.pages : [];
|
|
4289
|
+
const withLayout = pages.find((page: unknown) => isRecord(page) && isRecord(page.layout));
|
|
4290
|
+
const layout = isRecord(withLayout) ? withLayout.layout : isRecord(snapshot) ? snapshot.layout : undefined;
|
|
4291
|
+
bcmsLive = { stamp, bySlug, layout };
|
|
4292
|
+
} catch {
|
|
4293
|
+
// Unreadable, or caught mid-write: keep serving the last good copy.
|
|
4294
|
+
}
|
|
4295
|
+
return bcmsLive;
|
|
4296
|
+
}
|
|
4297
|
+
|
|
4298
|
+
/** The published counterpart of a snapshot this module was handed, or the snapshot itself. */
|
|
4299
|
+
function live(page: unknown): unknown {
|
|
4300
|
+
const content = liveContent();
|
|
4301
|
+
if (!content) return page;
|
|
4302
|
+
if (page === layoutSnapshot) return isRecord(content.layout) ? content.layout : page;
|
|
4303
|
+
if (isRecord(page) && typeof page.slug === "string") return content.bySlug.get(page.slug) ?? page;
|
|
4304
|
+
return page;
|
|
4305
|
+
}
|
|
4306
|
+
|
|
4247
4307
|
/** One field of one page. \`slug\` selects the entry on a dynamic route. */
|
|
4248
4308
|
export function bcms(page: unknown, path: string, fallback: string, slug?: string): string {
|
|
4309
|
+
page = live(page);
|
|
4249
4310
|
const entries = isRecord(page) ? page.entries : undefined;
|
|
4250
4311
|
const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
|
|
4251
4312
|
return render(readPath(root, path)) ?? fallback;
|
|
@@ -4253,7 +4314,7 @@ export function bcms(page: unknown, path: string, fallback: string, slug?: strin
|
|
|
4253
4314
|
|
|
4254
4315
|
/** One field of the shared layout \u2014 the header and footer every route renders. */
|
|
4255
4316
|
export function bcmsLayout(path: string, fallback: string): string {
|
|
4256
|
-
return render(readPath(layoutSnapshot, path)) ?? fallback;
|
|
4317
|
+
return render(readPath(live(layoutSnapshot), path)) ?? fallback;
|
|
4257
4318
|
}
|
|
4258
4319
|
|
|
4259
4320
|
/**
|
|
@@ -4261,6 +4322,7 @@ export function bcmsLayout(path: string, fallback: string): string {
|
|
|
4261
4322
|
* an empty snapshot still renders the same number of cards it always did.
|
|
4262
4323
|
*/
|
|
4263
4324
|
export function bcmsRows<T>(page: unknown, path: string, fallback: T[], slug?: string): T[] {
|
|
4325
|
+
page = live(page);
|
|
4264
4326
|
const entries = isRecord(page) ? page.entries : undefined;
|
|
4265
4327
|
const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
|
|
4266
4328
|
const value = readPath(root, path);
|
|
@@ -4306,6 +4368,7 @@ export function bcmsRowsAs<T>(
|
|
|
4306
4368
|
* this out loud rather than leaving an editor to discover it.
|
|
4307
4369
|
*/
|
|
4308
4370
|
export function bcmsList(page: unknown, paths: string[], fallback: string[], slug?: string): string[] {
|
|
4371
|
+
page = live(page);
|
|
4309
4372
|
const entries = isRecord(page) ? page.entries : undefined;
|
|
4310
4373
|
const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
|
|
4311
4374
|
return paths.map((path, index) => render(readPath(root, path)) ?? fallback[index] ?? "");
|
|
@@ -4330,6 +4393,7 @@ export function bcmsTuples(page: unknown, paths: string[][], fallback: string[][
|
|
|
4330
4393
|
* no such rule and takes the CMS url directly.
|
|
4331
4394
|
*/
|
|
4332
4395
|
export function bcmsImage<T>(page: unknown, path: string, fallback: T, slug?: string): T | string {
|
|
4396
|
+
page = live(page);
|
|
4333
4397
|
if (fallback !== null && typeof fallback === "object") return fallback;
|
|
4334
4398
|
const entries = isRecord(page) ? page.entries : undefined;
|
|
4335
4399
|
const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
|
|
@@ -4358,6 +4422,7 @@ export function bcmsSection(
|
|
|
4358
4422
|
overrides?: unknown,
|
|
4359
4423
|
bindPaths?: Record<string, string>,
|
|
4360
4424
|
): Record<string, unknown> {
|
|
4425
|
+
page = live(page);
|
|
4361
4426
|
const group = bind ? readPath(page, bind) : undefined;
|
|
4362
4427
|
const base: Record<string, unknown> = isRecord(group) ? { ...group } : {};
|
|
4363
4428
|
// A locale copy on its twin's component spells its fields its own way: each prop's copy is
|
|
@@ -4429,6 +4494,7 @@ export function bcmsSectionPaths(
|
|
|
4429
4494
|
map: BcmsPathMap,
|
|
4430
4495
|
bindPaths?: Record<string, string>,
|
|
4431
4496
|
): Record<string, unknown> {
|
|
4497
|
+
page = live(page);
|
|
4432
4498
|
const out: Record<string, unknown> = isRecord(page) ? { ...page } : {};
|
|
4433
4499
|
const own = isRecord(overrides) ? overrides : {};
|
|
4434
4500
|
const separator = prefix.slice(-1);
|
|
@@ -4662,7 +4728,8 @@ var HISTORICAL = {
|
|
|
4662
4728
|
7: "f22514ef48c50230985e605bea6cf9bf41756384a7dc80603e347baba9b65d93",
|
|
4663
4729
|
8: "484e72852d2b496e016a1f2a2b1a77bb993f8a163b290781522d6114b6519bb9",
|
|
4664
4730
|
9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3",
|
|
4665
|
-
10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910"
|
|
4731
|
+
10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910",
|
|
4732
|
+
11: "ceb2a932d4c2761a5045ff35032e0b45130a0b5034547ff37579c72c9ce3594e"
|
|
4666
4733
|
},
|
|
4667
4734
|
"src/lib/bcms-content.ts": {
|
|
4668
4735
|
1: "ae72ae14d706943d481c880d70da5789b4816ba1ba319a10fbc63ded4d0b42f2",
|
|
@@ -4674,7 +4741,8 @@ var HISTORICAL = {
|
|
|
4674
4741
|
7: "ed92fb88de953d9951abbe6a1edae1d048f4050eaf6fb607ac08167f94d8bcc7",
|
|
4675
4742
|
8: "1a6f669d76d42effe77f082436e9d21c0efa6fc499e919e3d116bc3a27a7f3d5",
|
|
4676
4743
|
9: "eee9fb1635ae7d6f8f33676a1f390600ca24a62ee992775252273c73a011c494",
|
|
4677
|
-
10: "35bb7c0e30f5c41fe68e52259ec4764de6de2734064fa0e1521f3338eef0a09e"
|
|
4744
|
+
10: "35bb7c0e30f5c41fe68e52259ec4764de6de2734064fa0e1521f3338eef0a09e",
|
|
4745
|
+
11: "818784f2088150d64d91b42eac944dc9503f104d32a0e7a18c753d2982a5adc0"
|
|
4678
4746
|
},
|
|
4679
4747
|
"composables/bcms-content.ts": {
|
|
4680
4748
|
1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
|
|
@@ -4686,7 +4754,8 @@ var HISTORICAL = {
|
|
|
4686
4754
|
7: "f22514ef48c50230985e605bea6cf9bf41756384a7dc80603e347baba9b65d93",
|
|
4687
4755
|
8: "484e72852d2b496e016a1f2a2b1a77bb993f8a163b290781522d6114b6519bb9",
|
|
4688
4756
|
9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3",
|
|
4689
|
-
10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910"
|
|
4757
|
+
10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910",
|
|
4758
|
+
11: "ceb2a932d4c2761a5045ff35032e0b45130a0b5034547ff37579c72c9ce3594e"
|
|
4690
4759
|
}
|
|
4691
4760
|
};
|
|
4692
4761
|
var sha256 = (value) => createHash2("sha256").update(value, "utf8").digest("hex");
|
|
@@ -4700,6 +4769,15 @@ function isKnownHelper(content, dialect) {
|
|
|
4700
4769
|
}
|
|
4701
4770
|
return null;
|
|
4702
4771
|
}
|
|
4772
|
+
function helperUpgrades() {
|
|
4773
|
+
const out = /* @__PURE__ */ new Map();
|
|
4774
|
+
for (const dialect of Object.keys(HELPER_PATH)) {
|
|
4775
|
+
const path = HELPER_PATH[dialect];
|
|
4776
|
+
if (!path || out.has(path)) continue;
|
|
4777
|
+
out.set(path, { path, source: helperSource(dialect), digests: Object.values(HISTORICAL[path] ?? {}) });
|
|
4778
|
+
}
|
|
4779
|
+
return [...out.values()];
|
|
4780
|
+
}
|
|
4703
4781
|
|
|
4704
4782
|
// src/locate.ts
|
|
4705
4783
|
var flat = (value) => value.replace(/\s+/g, " ").trim();
|
|
@@ -9195,6 +9273,371 @@ function readComponentizePlan(value) {
|
|
|
9195
9273
|
return readPlan(value);
|
|
9196
9274
|
}
|
|
9197
9275
|
|
|
9276
|
+
// src/upgrade.ts
|
|
9277
|
+
import MagicString6 from "magic-string";
|
|
9278
|
+
var UpgradeError = class extends Error {
|
|
9279
|
+
code = "HELPER_CONFLICT";
|
|
9280
|
+
constructor(message) {
|
|
9281
|
+
super(message);
|
|
9282
|
+
this.name = "UpgradeError";
|
|
9283
|
+
}
|
|
9284
|
+
};
|
|
9285
|
+
var SDK_SINCE = {
|
|
9286
|
+
astro: { pkg: "@bettercms-ai/astro", version: [0, 19, 0] },
|
|
9287
|
+
jsx: { pkg: "@bettercms-ai/next", version: [0, 18, 0] }
|
|
9288
|
+
};
|
|
9289
|
+
function sdkHasSectionRootAttrs(packageJson, dialect) {
|
|
9290
|
+
if (!packageJson) return false;
|
|
9291
|
+
let pkg;
|
|
9292
|
+
try {
|
|
9293
|
+
pkg = JSON.parse(packageJson);
|
|
9294
|
+
} catch {
|
|
9295
|
+
return false;
|
|
9296
|
+
}
|
|
9297
|
+
const { pkg: name, version: since } = SDK_SINCE[dialect];
|
|
9298
|
+
const range = [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies].map((deps) => deps && typeof deps === "object" ? deps[name] : void 0).find((v2) => typeof v2 === "string");
|
|
9299
|
+
if (range === void 0) return false;
|
|
9300
|
+
if (range.trim() === "latest" || range.trim() === "*") return true;
|
|
9301
|
+
const match = /^\s*(?:\^|~|>=|=)?\s*v?(\d+)\.(\d+)\.(\d+)/.exec(range);
|
|
9302
|
+
if (!match) return false;
|
|
9303
|
+
const v = [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
9304
|
+
for (let i = 0; i < 3; i++) {
|
|
9305
|
+
if (v[i] !== since[i]) return v[i] > since[i];
|
|
9306
|
+
}
|
|
9307
|
+
return true;
|
|
9308
|
+
}
|
|
9309
|
+
function elementsIn2(nodes, out = []) {
|
|
9310
|
+
for (const node of nodes) {
|
|
9311
|
+
if (node.type === "element") {
|
|
9312
|
+
out.push(node);
|
|
9313
|
+
elementsIn2(node.children, out);
|
|
9314
|
+
} else if (node.type === "expr") elementsIn2(node.children, out);
|
|
9315
|
+
}
|
|
9316
|
+
return out;
|
|
9317
|
+
}
|
|
9318
|
+
var lineAt = (content, offset) => content.slice(0, offset).split("\n").length;
|
|
9319
|
+
var exprOf2 = (content, range) => content.slice(range.start + 1, range.end - 1).trim();
|
|
9320
|
+
var isSpread = (content, attr) => !!attr.expressionRange && content.slice(attr.expressionRange.start, attr.expressionRange.end).trimStart().startsWith("{...");
|
|
9321
|
+
var mentions2 = (content, name) => new RegExp(`(?<![\\w$.])${name.replace(/[$]/g, "\\$")}(?![\\w$])`).test(content);
|
|
9322
|
+
function importOf(content, matches) {
|
|
9323
|
+
const re = /import\s*\{([^}]*)\}\s*from\s*(["'])([^"']+)\2\s*;?/g;
|
|
9324
|
+
for (const m of content.matchAll(re)) {
|
|
9325
|
+
if (!matches(m[3])) continue;
|
|
9326
|
+
const names = m[1].split(",").map((n) => n.trim()).filter(Boolean);
|
|
9327
|
+
return { start: m.index, end: m.index + m[0].length, names };
|
|
9328
|
+
}
|
|
9329
|
+
return null;
|
|
9330
|
+
}
|
|
9331
|
+
function addNamedImport(source, content, parsed, specifier, sameModule, names) {
|
|
9332
|
+
const existing = importOf(content, sameModule);
|
|
9333
|
+
if (existing) {
|
|
9334
|
+
const missing = names.filter((n) => !existing.names.some((e) => e === n || e.endsWith(` as ${n}`)));
|
|
9335
|
+
if (missing.length === 0) return;
|
|
9336
|
+
const quote = content.slice(existing.start, existing.end).includes("'") ? "'" : '"';
|
|
9337
|
+
const spec = /from\s*["']([^"']+)["']/.exec(content.slice(existing.start, existing.end))[1];
|
|
9338
|
+
const all = [...existing.names, ...missing].sort((a, b) => a.localeCompare(b));
|
|
9339
|
+
const semi = content.slice(existing.start, existing.end).trimEnd().endsWith(";") ? ";" : "";
|
|
9340
|
+
source.overwrite(existing.start, existing.end, `import { ${all.join(", ")} } from ${quote}${spec}${quote}${semi}`);
|
|
9341
|
+
return;
|
|
9342
|
+
}
|
|
9343
|
+
const line = `import { ${[...names].sort().join(", ")} } from "${specifier}";`;
|
|
9344
|
+
const at = parsed.importAt;
|
|
9345
|
+
if (!at) return;
|
|
9346
|
+
if (at.wrap) source.appendLeft(at.at, `${at.wrap.before}${line}${at.wrap.after}`);
|
|
9347
|
+
else if (at.at === 0) source.appendLeft(0, `${line}
|
|
9348
|
+
`);
|
|
9349
|
+
else source.appendLeft(at.at, `
|
|
9350
|
+
${line}`);
|
|
9351
|
+
}
|
|
9352
|
+
var importedFrom = (content, name, sameModule) => !!importOf(content, sameModule)?.names.some((e) => e === name || e.endsWith(` as ${name}`));
|
|
9353
|
+
var LIB_PROPS_DOC = " /** The placement's `block.style`, which the section root paints (`data-bcms-style`). */\n blockStyle?: Record<string, unknown>;";
|
|
9354
|
+
var LIB_PLACEMENT_DOC = " /** The placement's `block.style` \u2014 what the editor's section spacing and style controls write. */\n style?: Record<string, unknown>;";
|
|
9355
|
+
function insertAfterLine(content, from, to, anchor, addition) {
|
|
9356
|
+
const slice = content.slice(from, to);
|
|
9357
|
+
const m = anchor.exec(slice);
|
|
9358
|
+
if (!m) return null;
|
|
9359
|
+
const eol = content.indexOf("\n", from + m.index);
|
|
9360
|
+
if (eol < 0 || eol > to) return null;
|
|
9361
|
+
const nl = eolOf(content);
|
|
9362
|
+
return content.slice(0, eol + 1) + addition.split("\n").join(nl) + nl + content.slice(eol + 1);
|
|
9363
|
+
}
|
|
9364
|
+
var eolOf = (content) => content.includes("\r\n") ? "\r\n" : "\n";
|
|
9365
|
+
function interfaceBody(content, name) {
|
|
9366
|
+
const at = content.indexOf(`export interface ${name} {`);
|
|
9367
|
+
if (at < 0) return null;
|
|
9368
|
+
const end = content.indexOf("\n}", at);
|
|
9369
|
+
return end < 0 ? null : { start: at, end };
|
|
9370
|
+
}
|
|
9371
|
+
function patchLib(content) {
|
|
9372
|
+
let out = content;
|
|
9373
|
+
if (!/blockStyle\?:/.test(out)) {
|
|
9374
|
+
const body = interfaceBody(out, "BcmsSectionProps");
|
|
9375
|
+
if (!body) return null;
|
|
9376
|
+
const next = insertAfterLine(out, body.start, body.end, /^\s*page\?: unknown;/m, LIB_PROPS_DOC);
|
|
9377
|
+
if (next === null) return null;
|
|
9378
|
+
out = next;
|
|
9379
|
+
}
|
|
9380
|
+
const placement = interfaceBody(out, "BcmsPlacement");
|
|
9381
|
+
if (!placement) return null;
|
|
9382
|
+
if (!/^\s*style\?:/m.test(out.slice(placement.start, placement.end))) {
|
|
9383
|
+
const next = insertAfterLine(out, placement.start, placement.end, /^\s*overrides\?: Record<string, unknown>;/m, LIB_PLACEMENT_DOC);
|
|
9384
|
+
if (next === null) return null;
|
|
9385
|
+
out = next;
|
|
9386
|
+
}
|
|
9387
|
+
if (!/style: isRecord\(block\.style\)/.test(out)) {
|
|
9388
|
+
const push = /^([ \t]*)overrides: isRecord\(props\.overrides\) \? props\.overrides : undefined,\r?$/m.exec(out);
|
|
9389
|
+
if (!push) return null;
|
|
9390
|
+
const eol = out.indexOf("\n", push.index);
|
|
9391
|
+
out = out.slice(0, eol + 1) + `${push[1]}style: isRecord(block.style) ? block.style : undefined,${eolOf(out)}` + out.slice(eol + 1);
|
|
9392
|
+
}
|
|
9393
|
+
return out;
|
|
9394
|
+
}
|
|
9395
|
+
function patchRegistry(content) {
|
|
9396
|
+
if (content.includes("blockStyle={placement.style}")) return content;
|
|
9397
|
+
const lines = content.split("\n");
|
|
9398
|
+
const out = [];
|
|
9399
|
+
let patched = 0;
|
|
9400
|
+
for (const line of lines) {
|
|
9401
|
+
out.push(line);
|
|
9402
|
+
const m = /^([ \t]*)overrides=\{placement\.overrides\}(\r?)$/.exec(line);
|
|
9403
|
+
if (m) {
|
|
9404
|
+
out.push(`${m[1]}blockStyle={placement.style}${m[2]}`);
|
|
9405
|
+
patched++;
|
|
9406
|
+
}
|
|
9407
|
+
}
|
|
9408
|
+
return patched > 0 ? out.join("\n") : null;
|
|
9409
|
+
}
|
|
9410
|
+
var STYLE_READER = { astro: "bcmsSectionStyleText", jsx: "bcmsSectionStyle" };
|
|
9411
|
+
var BLOCK_MEMBER = /^([A-Za-z_$][\w$]*)(\??\.)(id|blockId)$/;
|
|
9412
|
+
var ID_ONLY = /^(?:(?:Astro\.)?props\??\.)?[A-Za-z_$][\w$]*$/;
|
|
9413
|
+
async function upgradeSections(sources, options = {}) {
|
|
9414
|
+
const contents = new Map(sources.map((f) => [f.path, f.content]));
|
|
9415
|
+
const out = /* @__PURE__ */ new Map();
|
|
9416
|
+
const write = (path, content) => {
|
|
9417
|
+
if (contents.get(path) === content) return;
|
|
9418
|
+
out.set(path, { path, operation: contents.has(path) ? "modify" : "add", content });
|
|
9419
|
+
};
|
|
9420
|
+
const receipt = {
|
|
9421
|
+
helpers: [],
|
|
9422
|
+
registry: [],
|
|
9423
|
+
roots: { upgraded: [], alreadyMarked: [], skipped: [] }
|
|
9424
|
+
};
|
|
9425
|
+
let registryOk = true;
|
|
9426
|
+
for (const file of sources) {
|
|
9427
|
+
if (!file.content.includes(REGISTRY_MARKER)) continue;
|
|
9428
|
+
const isLib = file.content.includes("export function pageSections(");
|
|
9429
|
+
const next = isLib ? patchLib(file.content) : patchRegistry(file.content);
|
|
9430
|
+
if (next === null) {
|
|
9431
|
+
registryOk = false;
|
|
9432
|
+
continue;
|
|
9433
|
+
}
|
|
9434
|
+
receipt.registry.push({ path: file.path, operation: next === file.content ? "unchanged" : "modify" });
|
|
9435
|
+
write(file.path, next);
|
|
9436
|
+
}
|
|
9437
|
+
const helperState = /* @__PURE__ */ new Map();
|
|
9438
|
+
const helperDialect = /* @__PURE__ */ new Map();
|
|
9439
|
+
for (const dialect of Object.keys(HELPER_PATH)) {
|
|
9440
|
+
const path = HELPER_PATH[dialect];
|
|
9441
|
+
if (!path || helperDialect.has(path)) continue;
|
|
9442
|
+
helperDialect.set(path, dialect);
|
|
9443
|
+
const existing = contents.get(path);
|
|
9444
|
+
helperState.set(path, existing === void 0 ? void 0 : isKnownHelper(existing, dialect));
|
|
9445
|
+
}
|
|
9446
|
+
const helperNeeded = /* @__PURE__ */ new Set();
|
|
9447
|
+
for (const file of sources) {
|
|
9448
|
+
const dialect = dialectOf(file.path);
|
|
9449
|
+
if (!dialect || !/data-bcms-block|sectionRootAttrs/.test(file.content)) continue;
|
|
9450
|
+
const ref = (el, attr) => ({
|
|
9451
|
+
file: file.path,
|
|
9452
|
+
line: lineAt(file.content, el.start),
|
|
9453
|
+
tag: el.tag,
|
|
9454
|
+
block: attr.expression ? file.content.slice(attr.expressionRange?.start ?? attr.range.start, attr.expressionRange?.end ?? attr.range.end) : JSON.stringify(attr.value)
|
|
9455
|
+
});
|
|
9456
|
+
const skip = (el, attr, reason, message) => {
|
|
9457
|
+
receipt.roots.skipped.push({ ...ref(el, attr), reason, message });
|
|
9458
|
+
};
|
|
9459
|
+
const parsed = await parseFile4(dialect, file.content);
|
|
9460
|
+
if (parsed.error) {
|
|
9461
|
+
receipt.roots.skipped.push({
|
|
9462
|
+
file: file.path,
|
|
9463
|
+
line: 1,
|
|
9464
|
+
tag: "",
|
|
9465
|
+
block: "",
|
|
9466
|
+
reason: "PARSE_ERROR",
|
|
9467
|
+
message: parsed.error.message
|
|
9468
|
+
});
|
|
9469
|
+
continue;
|
|
9470
|
+
}
|
|
9471
|
+
const roots = elementsIn2(parsed.roots).flatMap((el) => {
|
|
9472
|
+
const attr = el.attrs.find((a) => a.name === "data-bcms-block");
|
|
9473
|
+
return attr ? [{ el, attr }] : [];
|
|
9474
|
+
});
|
|
9475
|
+
for (const el of elementsIn2(parsed.roots)) {
|
|
9476
|
+
const spread = el.attrs.find((a) => isSpread(file.content, a) && /sectionRootAttrs\s*\(/.test(file.content.slice(a.expressionRange.start, a.expressionRange.end)));
|
|
9477
|
+
if (spread && !el.attrs.some((a) => a.name === "data-bcms-block")) {
|
|
9478
|
+
receipt.roots.alreadyMarked.push({
|
|
9479
|
+
file: file.path,
|
|
9480
|
+
line: lineAt(file.content, el.start),
|
|
9481
|
+
tag: el.tag,
|
|
9482
|
+
block: file.content.slice(spread.expressionRange.start, spread.expressionRange.end)
|
|
9483
|
+
});
|
|
9484
|
+
}
|
|
9485
|
+
}
|
|
9486
|
+
if (roots.length === 0) continue;
|
|
9487
|
+
if (dialect !== "astro" && dialect !== "jsx") {
|
|
9488
|
+
for (const { el, attr } of roots) {
|
|
9489
|
+
if (el.attrs.some((a) => a.name === "data-bcms-style")) receipt.roots.alreadyMarked.push(ref(el, attr));
|
|
9490
|
+
else skip(el, attr, "DIALECT_UNSUPPORTED", `${dialect} section roots are not rewritten by this pass; add data-bcms-style and the block's style by hand.`);
|
|
9491
|
+
}
|
|
9492
|
+
continue;
|
|
9493
|
+
}
|
|
9494
|
+
const source = new MagicString6(file.content);
|
|
9495
|
+
const reader = STYLE_READER[dialect];
|
|
9496
|
+
const helperPath = HELPER_PATH[dialect];
|
|
9497
|
+
const helperSpecifier = relativeImport(file.path, helperPath).replace(/\.tsx?$/, "");
|
|
9498
|
+
const isHelperModule = (spec) => /(^|\/)bcms-content(\.[jt]sx?)?$/.test(spec);
|
|
9499
|
+
const sdk = SDK_SINCE[dialect].pkg;
|
|
9500
|
+
const isSdk = (spec) => spec === sdk;
|
|
9501
|
+
const useSdk = sdkHasSectionRootAttrs(options.packageJson, dialect);
|
|
9502
|
+
const generated = SECTION_MARKERS.some((m) => file.content.includes(m));
|
|
9503
|
+
const typed = dialect === "astro" || file.path.endsWith(".tsx");
|
|
9504
|
+
const free = (name, from) => importedFrom(file.content, name, from) || !mentions2(file.content, name);
|
|
9505
|
+
let wantReader = false;
|
|
9506
|
+
let wantSdk = false;
|
|
9507
|
+
let sectionProp = false;
|
|
9508
|
+
for (const { el, attr } of roots) {
|
|
9509
|
+
if (el.attrs.some((a) => a.name === "data-bcms-style")) {
|
|
9510
|
+
receipt.roots.alreadyMarked.push(ref(el, attr));
|
|
9511
|
+
continue;
|
|
9512
|
+
}
|
|
9513
|
+
if (!isIntrinsic(el.tag)) {
|
|
9514
|
+
skip(el, attr, "NOT_AN_ELEMENT", `<${el.tag}> is a component: data-bcms-block on it is a prop. Mark the element that component renders.`);
|
|
9515
|
+
continue;
|
|
9516
|
+
}
|
|
9517
|
+
if (el.attrs.some((a) => a.name === "style")) {
|
|
9518
|
+
skip(el, attr, "OWN_STYLE", "The root already sets its own style; a second style attribute would be resolved by the dialect and one of them would vanish. Merge the block's style into it by hand.");
|
|
9519
|
+
continue;
|
|
9520
|
+
}
|
|
9521
|
+
if (el.attrs.some((a) => isSpread(file.content, a))) {
|
|
9522
|
+
skip(el, attr, "SPREAD", "The root spreads props that may carry a style; marking it could promise a style the spread overrides.");
|
|
9523
|
+
continue;
|
|
9524
|
+
}
|
|
9525
|
+
if (!attr.expression || !attr.expressionRange) {
|
|
9526
|
+
skip(el, attr, "BLOCK_LITERAL", "data-bcms-block is a literal id: there is no block object here to read a style from.");
|
|
9527
|
+
continue;
|
|
9528
|
+
}
|
|
9529
|
+
const expr = exprOf2(file.content, attr.expressionRange);
|
|
9530
|
+
if (generated && expr === "blockId") {
|
|
9531
|
+
if (!registryOk) {
|
|
9532
|
+
skip(el, attr, "REGISTRY_UNRECOGNISED", "The generated registry or bcms-sections does not pass block.style and could not be patched; re-run --componentize, then this.");
|
|
9533
|
+
continue;
|
|
9534
|
+
}
|
|
9535
|
+
if (!free(reader, isHelperModule) || /\bblockStyle\b/.test(file.content)) {
|
|
9536
|
+
skip(el, attr, "NAME_TAKEN", `${reader} or blockStyle already means something else in this file.`);
|
|
9537
|
+
continue;
|
|
9538
|
+
}
|
|
9539
|
+
source.appendLeft(attr.range.end, ` data-bcms-style="" style={${reader}(blockStyle)}`);
|
|
9540
|
+
receipt.roots.upgraded.push({ ...ref(el, attr), form: "section-prop" });
|
|
9541
|
+
wantReader = true;
|
|
9542
|
+
sectionProp = true;
|
|
9543
|
+
continue;
|
|
9544
|
+
}
|
|
9545
|
+
const member = BLOCK_MEMBER.exec(expr);
|
|
9546
|
+
if (member) {
|
|
9547
|
+
const [, object, dot, key] = member;
|
|
9548
|
+
if (useSdk && key === "id") {
|
|
9549
|
+
if (!free("sectionRootAttrs", isSdk)) {
|
|
9550
|
+
skip(el, attr, "NAME_TAKEN", "sectionRootAttrs already means something else in this file.");
|
|
9551
|
+
continue;
|
|
9552
|
+
}
|
|
9553
|
+
source.overwrite(attr.range.start, attr.range.end, `{...sectionRootAttrs(${object})}`);
|
|
9554
|
+
receipt.roots.upgraded.push({ ...ref(el, attr), form: "sectionRootAttrs" });
|
|
9555
|
+
wantSdk = true;
|
|
9556
|
+
continue;
|
|
9557
|
+
}
|
|
9558
|
+
if (!free(reader, isHelperModule)) {
|
|
9559
|
+
skip(el, attr, "NAME_TAKEN", `${reader} already means something else in this file.`);
|
|
9560
|
+
continue;
|
|
9561
|
+
}
|
|
9562
|
+
const style = typed && dialect === "jsx" ? `(${object} as { style?: unknown } | null | undefined)?.style` : `${object}${dot}style`;
|
|
9563
|
+
source.appendLeft(attr.range.end, ` data-bcms-style="" style={${reader}(${style})}`);
|
|
9564
|
+
receipt.roots.upgraded.push({ ...ref(el, attr), form: "helper" });
|
|
9565
|
+
wantReader = true;
|
|
9566
|
+
continue;
|
|
9567
|
+
}
|
|
9568
|
+
if (ID_ONLY.test(expr)) {
|
|
9569
|
+
skip(
|
|
9570
|
+
el,
|
|
9571
|
+
attr,
|
|
9572
|
+
"BLOCK_ID_ONLY",
|
|
9573
|
+
`Only a block id reaches this root (${expr}); the block's style is not in scope. Pass the block (or its style) down with the id, then re-run.`
|
|
9574
|
+
);
|
|
9575
|
+
continue;
|
|
9576
|
+
}
|
|
9577
|
+
skip(el, attr, "BLOCK_UNRESOLVABLE", `data-bcms-block={${expr}} does not name one block whose style this root could paint.`);
|
|
9578
|
+
}
|
|
9579
|
+
if (sectionProp) {
|
|
9580
|
+
const destructure = dialect === "astro" ? /const\s*\{([^}]*\bblockId\b[^}]*)\}\s*=\s*Astro\.props\s*;?/.exec(file.content) : /\(\s*\{([^}]*\bblockId\b[^}]*)\}\s*:\s*BcmsSectionProps\s*\)/.exec(file.content);
|
|
9581
|
+
if (destructure) {
|
|
9582
|
+
const close = destructure.index + destructure[0].indexOf("}");
|
|
9583
|
+
let at = close;
|
|
9584
|
+
while (at > 0 && /\s/.test(file.content[at - 1])) at--;
|
|
9585
|
+
source.appendLeft(at, ", blockStyle");
|
|
9586
|
+
}
|
|
9587
|
+
if (dialect === "astro") {
|
|
9588
|
+
const props = /interface Props \{[\s\S]*?\n\}/.exec(file.content);
|
|
9589
|
+
if (props) {
|
|
9590
|
+
const end = props.index + props[0].length - 1;
|
|
9591
|
+
const nl = eolOf(file.content);
|
|
9592
|
+
source.appendLeft(
|
|
9593
|
+
end,
|
|
9594
|
+
` /** The placement's \`block.style\` \u2014 painted on the root, which says so with \`data-bcms-style\`. */${nl} blockStyle?: Record<string, unknown>;${nl}`
|
|
9595
|
+
);
|
|
9596
|
+
}
|
|
9597
|
+
}
|
|
9598
|
+
}
|
|
9599
|
+
if (wantReader) {
|
|
9600
|
+
addNamedImport(source, file.content, parsed, helperSpecifier, isHelperModule, [reader]);
|
|
9601
|
+
helperNeeded.add(helperPath);
|
|
9602
|
+
}
|
|
9603
|
+
if (wantSdk) addNamedImport(source, file.content, parsed, sdk, isSdk, ["sectionRootAttrs"]);
|
|
9604
|
+
if (source.hasChanged()) write(file.path, source.toString());
|
|
9605
|
+
}
|
|
9606
|
+
for (const [path, version] of helperState) {
|
|
9607
|
+
const dialect = helperDialect.get(path);
|
|
9608
|
+
const needed = helperNeeded.has(path);
|
|
9609
|
+
if (version === void 0) {
|
|
9610
|
+
if (!needed) continue;
|
|
9611
|
+
write(path, helperSource(dialect));
|
|
9612
|
+
receipt.helpers.push({ path, from: null, to: HELPER_VERSION, operation: "add" });
|
|
9613
|
+
const stub = `${CONTENT_DIR}/layout.json`;
|
|
9614
|
+
if (!contents.has(stub)) write(stub, STUB_CONTENT);
|
|
9615
|
+
continue;
|
|
9616
|
+
}
|
|
9617
|
+
if (version === null) {
|
|
9618
|
+
if (needed && !options.overwriteHelper) {
|
|
9619
|
+
throw new UpgradeError(
|
|
9620
|
+
`${path} already exists and its bytes are not ones @bettercms-ai/convert wrote \u2014 someone else's module, or ours with edits in it. The upgraded roots read bcmsSectionStyle from it. Move it, or re-run with --overwrite-helper.`
|
|
9621
|
+
);
|
|
9622
|
+
}
|
|
9623
|
+
if (needed) {
|
|
9624
|
+
write(path, helperSource(dialect));
|
|
9625
|
+
receipt.helpers.push({ path, from: null, to: HELPER_VERSION, operation: "modify" });
|
|
9626
|
+
} else receipt.helpers.push({ path, from: null, to: HELPER_VERSION, operation: "foreign" });
|
|
9627
|
+
continue;
|
|
9628
|
+
}
|
|
9629
|
+
if (version < HELPER_VERSION) {
|
|
9630
|
+
write(path, helperSource(dialect));
|
|
9631
|
+
receipt.helpers.push({ path, from: version, to: HELPER_VERSION, operation: "modify" });
|
|
9632
|
+
} else receipt.helpers.push({ path, from: version, to: HELPER_VERSION, operation: "unchanged" });
|
|
9633
|
+
}
|
|
9634
|
+
const byPlace = (a, b) => a.file.localeCompare(b.file) || a.line - b.line;
|
|
9635
|
+
receipt.roots.upgraded.sort(byPlace);
|
|
9636
|
+
receipt.roots.alreadyMarked.sort(byPlace);
|
|
9637
|
+
receipt.roots.skipped.sort(byPlace);
|
|
9638
|
+
return { files: [...out.values()].sort((a, b) => a.path.localeCompare(b.path)), receipt };
|
|
9639
|
+
}
|
|
9640
|
+
|
|
9198
9641
|
// src/index.ts
|
|
9199
9642
|
var UNPARSEABLE_DIALECT = {
|
|
9200
9643
|
roots: [],
|
|
@@ -9532,7 +9975,7 @@ async function convertSources(briefIn, sources, options) {
|
|
|
9532
9975
|
rewritten++;
|
|
9533
9976
|
}
|
|
9534
9977
|
for (const [file, splices] of [...componentEdits].sort(([a], [b]) => a.localeCompare(b))) {
|
|
9535
|
-
const source = new
|
|
9978
|
+
const source = new MagicString7(contents.get(file));
|
|
9536
9979
|
const seen = /* @__PURE__ */ new Set();
|
|
9537
9980
|
for (const splice of splices) {
|
|
9538
9981
|
const id = `${splice.start}:${splice.end}:${splice.text}`;
|
|
@@ -10326,6 +10769,7 @@ export {
|
|
|
10326
10769
|
SSR_DRAFT_RECIPE,
|
|
10327
10770
|
STUB_CONTENT,
|
|
10328
10771
|
SectionInvariantError,
|
|
10772
|
+
UpgradeError,
|
|
10329
10773
|
ValidationError,
|
|
10330
10774
|
aliasesFrom,
|
|
10331
10775
|
assertForms,
|
|
@@ -10353,6 +10797,7 @@ export {
|
|
|
10353
10797
|
fixFor,
|
|
10354
10798
|
flat,
|
|
10355
10799
|
helperSource,
|
|
10800
|
+
helperUpgrades,
|
|
10356
10801
|
importsIn,
|
|
10357
10802
|
isDecorativeGlyph,
|
|
10358
10803
|
isDynamicRoute,
|
|
@@ -10376,6 +10821,7 @@ export {
|
|
|
10376
10821
|
routeOfFile,
|
|
10377
10822
|
stripTags,
|
|
10378
10823
|
unreadableConfigs,
|
|
10824
|
+
upgradeSections,
|
|
10379
10825
|
walkAst,
|
|
10380
10826
|
wireForms
|
|
10381
10827
|
};
|