@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/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { readdir, readFile, writeFile, mkdir, stat } from "fs/promises";
|
|
|
6
6
|
import { dirname, join as join2, relative, resolve, sep } from "path";
|
|
7
7
|
|
|
8
8
|
// src/index.ts
|
|
9
|
-
import
|
|
9
|
+
import MagicString7 from "magic-string";
|
|
10
10
|
|
|
11
11
|
// src/brief.ts
|
|
12
12
|
import { createHash } from "crypto";
|
|
@@ -3767,8 +3767,8 @@ function bindLists(ctx, members, module, out, indexes) {
|
|
|
3767
3767
|
const loops = iterations(ctx.parsed.roots, name);
|
|
3768
3768
|
if (loops.length > 1) return { refuse: "REPEATER_AMBIGUOUS" };
|
|
3769
3769
|
if (loops.length === 0) return null;
|
|
3770
|
-
const
|
|
3771
|
-
if (
|
|
3770
|
+
const mentions3 = ctx.content.match(new RegExp(`(?<![\\w$])${name}(?![\\w$])`, "g")) ?? [];
|
|
3771
|
+
if (mentions3.length !== 2 || escapes.has(name)) return { refuse: "IN_EXPRESSION" };
|
|
3772
3772
|
return { loop: loops[0], params: callbackParams(loops[0], name) };
|
|
3773
3773
|
}
|
|
3774
3774
|
});
|
|
@@ -4190,7 +4190,7 @@ async function bindImages(ctx, targets) {
|
|
|
4190
4190
|
|
|
4191
4191
|
// src/helper.ts
|
|
4192
4192
|
import { createHash as createHash2 } from "crypto";
|
|
4193
|
-
var HELPER_VERSION =
|
|
4193
|
+
var HELPER_VERSION = 12;
|
|
4194
4194
|
var HEADER = "// @bettercms-ai/convert helper v";
|
|
4195
4195
|
var HELPER_PATH = Object.fromEntries(
|
|
4196
4196
|
Object.keys(DIALECT_RULES).map((d) => [d, DIALECT_RULES[d].helper])
|
|
@@ -4247,8 +4247,69 @@ function render(value: unknown): string | undefined {
|
|
|
4247
4247
|
return undefined;
|
|
4248
4248
|
}
|
|
4249
4249
|
|
|
4250
|
+
/**
|
|
4251
|
+
* The PUBLISHED copy, on a server BetterCMS hosts \u2014 read at request time, not at build time.
|
|
4252
|
+
*
|
|
4253
|
+
* BetterCMS keeps one file current for a running server app (\`BCMS_CONTENT_FILE\`, the same
|
|
4254
|
+
* bcms-content.json the build was given) and rewrites it on every publish, so a server-rendered
|
|
4255
|
+
* page shows a publish within seconds instead of after a rebuild. Each snapshot this module reads
|
|
4256
|
+
* resolves to its live counterpart: the shared layout by identity, a page by its \`slug\`.
|
|
4257
|
+
*
|
|
4258
|
+
* Nowhere else does anything change: with no variable (a build, \`astro dev\`, a browser, an edge
|
|
4259
|
+
* runtime) every snapshot is the one the bundle imported, exactly as before. A file that cannot be
|
|
4260
|
+
* read keeps the last good copy; it is checked at most once a second.
|
|
4261
|
+
*/
|
|
4262
|
+
type BcmsLiveContent = { stamp: number; bySlug: Map<string, unknown>; layout: unknown };
|
|
4263
|
+
let bcmsLive: BcmsLiveContent | null = null;
|
|
4264
|
+
let bcmsLiveCheckedAt = 0;
|
|
4265
|
+
|
|
4266
|
+
function liveContent(): BcmsLiveContent | null {
|
|
4267
|
+
const proc = (globalThis as {
|
|
4268
|
+
process?: { env?: Record<string, string | undefined>; getBuiltinModule?: (id: string) => unknown };
|
|
4269
|
+
}).process;
|
|
4270
|
+
const file = proc?.env?.BCMS_CONTENT_FILE;
|
|
4271
|
+
if (!file || typeof proc?.getBuiltinModule !== "function") return null;
|
|
4272
|
+
const now = Date.now();
|
|
4273
|
+
if (now - bcmsLiveCheckedAt < 1000) return bcmsLive;
|
|
4274
|
+
bcmsLiveCheckedAt = now;
|
|
4275
|
+
try {
|
|
4276
|
+
const fs = proc.getBuiltinModule("node:fs") as {
|
|
4277
|
+
statSync(path: string): { mtimeMs: number };
|
|
4278
|
+
readFileSync(path: string, encoding: "utf8"): string;
|
|
4279
|
+
};
|
|
4280
|
+
const stamp = fs.statSync(file).mtimeMs;
|
|
4281
|
+
if (bcmsLive && bcmsLive.stamp === stamp) return bcmsLive;
|
|
4282
|
+
const snapshot: unknown = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
4283
|
+
const bySlug = new Map<string, unknown>();
|
|
4284
|
+
const collections = isRecord(snapshot) && isRecord(snapshot.collections) ? snapshot.collections : {};
|
|
4285
|
+
for (const rows of Object.values(collections)) {
|
|
4286
|
+
if (!Array.isArray(rows)) continue;
|
|
4287
|
+
for (const row of rows) {
|
|
4288
|
+
if (isRecord(row) && typeof row.slug === "string" && !bySlug.has(row.slug)) bySlug.set(row.slug, row);
|
|
4289
|
+
}
|
|
4290
|
+
}
|
|
4291
|
+
const pages = isRecord(snapshot) && Array.isArray(snapshot.pages) ? snapshot.pages : [];
|
|
4292
|
+
const withLayout = pages.find((page: unknown) => isRecord(page) && isRecord(page.layout));
|
|
4293
|
+
const layout = isRecord(withLayout) ? withLayout.layout : isRecord(snapshot) ? snapshot.layout : undefined;
|
|
4294
|
+
bcmsLive = { stamp, bySlug, layout };
|
|
4295
|
+
} catch {
|
|
4296
|
+
// Unreadable, or caught mid-write: keep serving the last good copy.
|
|
4297
|
+
}
|
|
4298
|
+
return bcmsLive;
|
|
4299
|
+
}
|
|
4300
|
+
|
|
4301
|
+
/** The published counterpart of a snapshot this module was handed, or the snapshot itself. */
|
|
4302
|
+
function live(page: unknown): unknown {
|
|
4303
|
+
const content = liveContent();
|
|
4304
|
+
if (!content) return page;
|
|
4305
|
+
if (page === layoutSnapshot) return isRecord(content.layout) ? content.layout : page;
|
|
4306
|
+
if (isRecord(page) && typeof page.slug === "string") return content.bySlug.get(page.slug) ?? page;
|
|
4307
|
+
return page;
|
|
4308
|
+
}
|
|
4309
|
+
|
|
4250
4310
|
/** One field of one page. \`slug\` selects the entry on a dynamic route. */
|
|
4251
4311
|
export function bcms(page: unknown, path: string, fallback: string, slug?: string): string {
|
|
4312
|
+
page = live(page);
|
|
4252
4313
|
const entries = isRecord(page) ? page.entries : undefined;
|
|
4253
4314
|
const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
|
|
4254
4315
|
return render(readPath(root, path)) ?? fallback;
|
|
@@ -4256,7 +4317,7 @@ export function bcms(page: unknown, path: string, fallback: string, slug?: strin
|
|
|
4256
4317
|
|
|
4257
4318
|
/** One field of the shared layout \u2014 the header and footer every route renders. */
|
|
4258
4319
|
export function bcmsLayout(path: string, fallback: string): string {
|
|
4259
|
-
return render(readPath(layoutSnapshot, path)) ?? fallback;
|
|
4320
|
+
return render(readPath(live(layoutSnapshot), path)) ?? fallback;
|
|
4260
4321
|
}
|
|
4261
4322
|
|
|
4262
4323
|
/**
|
|
@@ -4264,6 +4325,7 @@ export function bcmsLayout(path: string, fallback: string): string {
|
|
|
4264
4325
|
* an empty snapshot still renders the same number of cards it always did.
|
|
4265
4326
|
*/
|
|
4266
4327
|
export function bcmsRows<T>(page: unknown, path: string, fallback: T[], slug?: string): T[] {
|
|
4328
|
+
page = live(page);
|
|
4267
4329
|
const entries = isRecord(page) ? page.entries : undefined;
|
|
4268
4330
|
const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
|
|
4269
4331
|
const value = readPath(root, path);
|
|
@@ -4309,6 +4371,7 @@ export function bcmsRowsAs<T>(
|
|
|
4309
4371
|
* this out loud rather than leaving an editor to discover it.
|
|
4310
4372
|
*/
|
|
4311
4373
|
export function bcmsList(page: unknown, paths: string[], fallback: string[], slug?: string): string[] {
|
|
4374
|
+
page = live(page);
|
|
4312
4375
|
const entries = isRecord(page) ? page.entries : undefined;
|
|
4313
4376
|
const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
|
|
4314
4377
|
return paths.map((path, index) => render(readPath(root, path)) ?? fallback[index] ?? "");
|
|
@@ -4333,6 +4396,7 @@ export function bcmsTuples(page: unknown, paths: string[][], fallback: string[][
|
|
|
4333
4396
|
* no such rule and takes the CMS url directly.
|
|
4334
4397
|
*/
|
|
4335
4398
|
export function bcmsImage<T>(page: unknown, path: string, fallback: T, slug?: string): T | string {
|
|
4399
|
+
page = live(page);
|
|
4336
4400
|
if (fallback !== null && typeof fallback === "object") return fallback;
|
|
4337
4401
|
const entries = isRecord(page) ? page.entries : undefined;
|
|
4338
4402
|
const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
|
|
@@ -4361,6 +4425,7 @@ export function bcmsSection(
|
|
|
4361
4425
|
overrides?: unknown,
|
|
4362
4426
|
bindPaths?: Record<string, string>,
|
|
4363
4427
|
): Record<string, unknown> {
|
|
4428
|
+
page = live(page);
|
|
4364
4429
|
const group = bind ? readPath(page, bind) : undefined;
|
|
4365
4430
|
const base: Record<string, unknown> = isRecord(group) ? { ...group } : {};
|
|
4366
4431
|
// A locale copy on its twin's component spells its fields its own way: each prop's copy is
|
|
@@ -4432,6 +4497,7 @@ export function bcmsSectionPaths(
|
|
|
4432
4497
|
map: BcmsPathMap,
|
|
4433
4498
|
bindPaths?: Record<string, string>,
|
|
4434
4499
|
): Record<string, unknown> {
|
|
4500
|
+
page = live(page);
|
|
4435
4501
|
const out: Record<string, unknown> = isRecord(page) ? { ...page } : {};
|
|
4436
4502
|
const own = isRecord(overrides) ? overrides : {};
|
|
4437
4503
|
const separator = prefix.slice(-1);
|
|
@@ -4665,7 +4731,8 @@ var HISTORICAL = {
|
|
|
4665
4731
|
7: "f22514ef48c50230985e605bea6cf9bf41756384a7dc80603e347baba9b65d93",
|
|
4666
4732
|
8: "484e72852d2b496e016a1f2a2b1a77bb993f8a163b290781522d6114b6519bb9",
|
|
4667
4733
|
9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3",
|
|
4668
|
-
10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910"
|
|
4734
|
+
10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910",
|
|
4735
|
+
11: "ceb2a932d4c2761a5045ff35032e0b45130a0b5034547ff37579c72c9ce3594e"
|
|
4669
4736
|
},
|
|
4670
4737
|
"src/lib/bcms-content.ts": {
|
|
4671
4738
|
1: "ae72ae14d706943d481c880d70da5789b4816ba1ba319a10fbc63ded4d0b42f2",
|
|
@@ -4677,7 +4744,8 @@ var HISTORICAL = {
|
|
|
4677
4744
|
7: "ed92fb88de953d9951abbe6a1edae1d048f4050eaf6fb607ac08167f94d8bcc7",
|
|
4678
4745
|
8: "1a6f669d76d42effe77f082436e9d21c0efa6fc499e919e3d116bc3a27a7f3d5",
|
|
4679
4746
|
9: "eee9fb1635ae7d6f8f33676a1f390600ca24a62ee992775252273c73a011c494",
|
|
4680
|
-
10: "35bb7c0e30f5c41fe68e52259ec4764de6de2734064fa0e1521f3338eef0a09e"
|
|
4747
|
+
10: "35bb7c0e30f5c41fe68e52259ec4764de6de2734064fa0e1521f3338eef0a09e",
|
|
4748
|
+
11: "818784f2088150d64d91b42eac944dc9503f104d32a0e7a18c753d2982a5adc0"
|
|
4681
4749
|
},
|
|
4682
4750
|
"composables/bcms-content.ts": {
|
|
4683
4751
|
1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
|
|
@@ -4689,7 +4757,8 @@ var HISTORICAL = {
|
|
|
4689
4757
|
7: "f22514ef48c50230985e605bea6cf9bf41756384a7dc80603e347baba9b65d93",
|
|
4690
4758
|
8: "484e72852d2b496e016a1f2a2b1a77bb993f8a163b290781522d6114b6519bb9",
|
|
4691
4759
|
9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3",
|
|
4692
|
-
10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910"
|
|
4760
|
+
10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910",
|
|
4761
|
+
11: "ceb2a932d4c2761a5045ff35032e0b45130a0b5034547ff37579c72c9ce3594e"
|
|
4693
4762
|
}
|
|
4694
4763
|
};
|
|
4695
4764
|
var sha256 = (value) => createHash2("sha256").update(value, "utf8").digest("hex");
|
|
@@ -9178,6 +9247,371 @@ function readComponentizePlan(value) {
|
|
|
9178
9247
|
return readPlan(value);
|
|
9179
9248
|
}
|
|
9180
9249
|
|
|
9250
|
+
// src/upgrade.ts
|
|
9251
|
+
import MagicString6 from "magic-string";
|
|
9252
|
+
var UpgradeError = class extends Error {
|
|
9253
|
+
code = "HELPER_CONFLICT";
|
|
9254
|
+
constructor(message) {
|
|
9255
|
+
super(message);
|
|
9256
|
+
this.name = "UpgradeError";
|
|
9257
|
+
}
|
|
9258
|
+
};
|
|
9259
|
+
var SDK_SINCE = {
|
|
9260
|
+
astro: { pkg: "@bettercms-ai/astro", version: [0, 19, 0] },
|
|
9261
|
+
jsx: { pkg: "@bettercms-ai/next", version: [0, 18, 0] }
|
|
9262
|
+
};
|
|
9263
|
+
function sdkHasSectionRootAttrs(packageJson, dialect) {
|
|
9264
|
+
if (!packageJson) return false;
|
|
9265
|
+
let pkg;
|
|
9266
|
+
try {
|
|
9267
|
+
pkg = JSON.parse(packageJson);
|
|
9268
|
+
} catch {
|
|
9269
|
+
return false;
|
|
9270
|
+
}
|
|
9271
|
+
const { pkg: name, version: since } = SDK_SINCE[dialect];
|
|
9272
|
+
const range = [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies].map((deps) => deps && typeof deps === "object" ? deps[name] : void 0).find((v2) => typeof v2 === "string");
|
|
9273
|
+
if (range === void 0) return false;
|
|
9274
|
+
if (range.trim() === "latest" || range.trim() === "*") return true;
|
|
9275
|
+
const match = /^\s*(?:\^|~|>=|=)?\s*v?(\d+)\.(\d+)\.(\d+)/.exec(range);
|
|
9276
|
+
if (!match) return false;
|
|
9277
|
+
const v = [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
9278
|
+
for (let i = 0; i < 3; i++) {
|
|
9279
|
+
if (v[i] !== since[i]) return v[i] > since[i];
|
|
9280
|
+
}
|
|
9281
|
+
return true;
|
|
9282
|
+
}
|
|
9283
|
+
function elementsIn2(nodes, out = []) {
|
|
9284
|
+
for (const node of nodes) {
|
|
9285
|
+
if (node.type === "element") {
|
|
9286
|
+
out.push(node);
|
|
9287
|
+
elementsIn2(node.children, out);
|
|
9288
|
+
} else if (node.type === "expr") elementsIn2(node.children, out);
|
|
9289
|
+
}
|
|
9290
|
+
return out;
|
|
9291
|
+
}
|
|
9292
|
+
var lineAt = (content, offset) => content.slice(0, offset).split("\n").length;
|
|
9293
|
+
var exprOf2 = (content, range) => content.slice(range.start + 1, range.end - 1).trim();
|
|
9294
|
+
var isSpread = (content, attr) => !!attr.expressionRange && content.slice(attr.expressionRange.start, attr.expressionRange.end).trimStart().startsWith("{...");
|
|
9295
|
+
var mentions2 = (content, name) => new RegExp(`(?<![\\w$.])${name.replace(/[$]/g, "\\$")}(?![\\w$])`).test(content);
|
|
9296
|
+
function importOf(content, matches) {
|
|
9297
|
+
const re = /import\s*\{([^}]*)\}\s*from\s*(["'])([^"']+)\2\s*;?/g;
|
|
9298
|
+
for (const m of content.matchAll(re)) {
|
|
9299
|
+
if (!matches(m[3])) continue;
|
|
9300
|
+
const names = m[1].split(",").map((n) => n.trim()).filter(Boolean);
|
|
9301
|
+
return { start: m.index, end: m.index + m[0].length, names };
|
|
9302
|
+
}
|
|
9303
|
+
return null;
|
|
9304
|
+
}
|
|
9305
|
+
function addNamedImport(source, content, parsed, specifier, sameModule, names) {
|
|
9306
|
+
const existing = importOf(content, sameModule);
|
|
9307
|
+
if (existing) {
|
|
9308
|
+
const missing = names.filter((n) => !existing.names.some((e) => e === n || e.endsWith(` as ${n}`)));
|
|
9309
|
+
if (missing.length === 0) return;
|
|
9310
|
+
const quote = content.slice(existing.start, existing.end).includes("'") ? "'" : '"';
|
|
9311
|
+
const spec = /from\s*["']([^"']+)["']/.exec(content.slice(existing.start, existing.end))[1];
|
|
9312
|
+
const all = [...existing.names, ...missing].sort((a, b) => a.localeCompare(b));
|
|
9313
|
+
const semi = content.slice(existing.start, existing.end).trimEnd().endsWith(";") ? ";" : "";
|
|
9314
|
+
source.overwrite(existing.start, existing.end, `import { ${all.join(", ")} } from ${quote}${spec}${quote}${semi}`);
|
|
9315
|
+
return;
|
|
9316
|
+
}
|
|
9317
|
+
const line = `import { ${[...names].sort().join(", ")} } from "${specifier}";`;
|
|
9318
|
+
const at = parsed.importAt;
|
|
9319
|
+
if (!at) return;
|
|
9320
|
+
if (at.wrap) source.appendLeft(at.at, `${at.wrap.before}${line}${at.wrap.after}`);
|
|
9321
|
+
else if (at.at === 0) source.appendLeft(0, `${line}
|
|
9322
|
+
`);
|
|
9323
|
+
else source.appendLeft(at.at, `
|
|
9324
|
+
${line}`);
|
|
9325
|
+
}
|
|
9326
|
+
var importedFrom = (content, name, sameModule) => !!importOf(content, sameModule)?.names.some((e) => e === name || e.endsWith(` as ${name}`));
|
|
9327
|
+
var LIB_PROPS_DOC = " /** The placement's `block.style`, which the section root paints (`data-bcms-style`). */\n blockStyle?: Record<string, unknown>;";
|
|
9328
|
+
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>;";
|
|
9329
|
+
function insertAfterLine(content, from, to, anchor, addition) {
|
|
9330
|
+
const slice = content.slice(from, to);
|
|
9331
|
+
const m = anchor.exec(slice);
|
|
9332
|
+
if (!m) return null;
|
|
9333
|
+
const eol = content.indexOf("\n", from + m.index);
|
|
9334
|
+
if (eol < 0 || eol > to) return null;
|
|
9335
|
+
const nl = eolOf(content);
|
|
9336
|
+
return content.slice(0, eol + 1) + addition.split("\n").join(nl) + nl + content.slice(eol + 1);
|
|
9337
|
+
}
|
|
9338
|
+
var eolOf = (content) => content.includes("\r\n") ? "\r\n" : "\n";
|
|
9339
|
+
function interfaceBody(content, name) {
|
|
9340
|
+
const at = content.indexOf(`export interface ${name} {`);
|
|
9341
|
+
if (at < 0) return null;
|
|
9342
|
+
const end = content.indexOf("\n}", at);
|
|
9343
|
+
return end < 0 ? null : { start: at, end };
|
|
9344
|
+
}
|
|
9345
|
+
function patchLib(content) {
|
|
9346
|
+
let out = content;
|
|
9347
|
+
if (!/blockStyle\?:/.test(out)) {
|
|
9348
|
+
const body = interfaceBody(out, "BcmsSectionProps");
|
|
9349
|
+
if (!body) return null;
|
|
9350
|
+
const next = insertAfterLine(out, body.start, body.end, /^\s*page\?: unknown;/m, LIB_PROPS_DOC);
|
|
9351
|
+
if (next === null) return null;
|
|
9352
|
+
out = next;
|
|
9353
|
+
}
|
|
9354
|
+
const placement = interfaceBody(out, "BcmsPlacement");
|
|
9355
|
+
if (!placement) return null;
|
|
9356
|
+
if (!/^\s*style\?:/m.test(out.slice(placement.start, placement.end))) {
|
|
9357
|
+
const next = insertAfterLine(out, placement.start, placement.end, /^\s*overrides\?: Record<string, unknown>;/m, LIB_PLACEMENT_DOC);
|
|
9358
|
+
if (next === null) return null;
|
|
9359
|
+
out = next;
|
|
9360
|
+
}
|
|
9361
|
+
if (!/style: isRecord\(block\.style\)/.test(out)) {
|
|
9362
|
+
const push = /^([ \t]*)overrides: isRecord\(props\.overrides\) \? props\.overrides : undefined,\r?$/m.exec(out);
|
|
9363
|
+
if (!push) return null;
|
|
9364
|
+
const eol = out.indexOf("\n", push.index);
|
|
9365
|
+
out = out.slice(0, eol + 1) + `${push[1]}style: isRecord(block.style) ? block.style : undefined,${eolOf(out)}` + out.slice(eol + 1);
|
|
9366
|
+
}
|
|
9367
|
+
return out;
|
|
9368
|
+
}
|
|
9369
|
+
function patchRegistry(content) {
|
|
9370
|
+
if (content.includes("blockStyle={placement.style}")) return content;
|
|
9371
|
+
const lines = content.split("\n");
|
|
9372
|
+
const out = [];
|
|
9373
|
+
let patched = 0;
|
|
9374
|
+
for (const line of lines) {
|
|
9375
|
+
out.push(line);
|
|
9376
|
+
const m = /^([ \t]*)overrides=\{placement\.overrides\}(\r?)$/.exec(line);
|
|
9377
|
+
if (m) {
|
|
9378
|
+
out.push(`${m[1]}blockStyle={placement.style}${m[2]}`);
|
|
9379
|
+
patched++;
|
|
9380
|
+
}
|
|
9381
|
+
}
|
|
9382
|
+
return patched > 0 ? out.join("\n") : null;
|
|
9383
|
+
}
|
|
9384
|
+
var STYLE_READER = { astro: "bcmsSectionStyleText", jsx: "bcmsSectionStyle" };
|
|
9385
|
+
var BLOCK_MEMBER = /^([A-Za-z_$][\w$]*)(\??\.)(id|blockId)$/;
|
|
9386
|
+
var ID_ONLY = /^(?:(?:Astro\.)?props\??\.)?[A-Za-z_$][\w$]*$/;
|
|
9387
|
+
async function upgradeSections(sources, options = {}) {
|
|
9388
|
+
const contents = new Map(sources.map((f) => [f.path, f.content]));
|
|
9389
|
+
const out = /* @__PURE__ */ new Map();
|
|
9390
|
+
const write = (path, content) => {
|
|
9391
|
+
if (contents.get(path) === content) return;
|
|
9392
|
+
out.set(path, { path, operation: contents.has(path) ? "modify" : "add", content });
|
|
9393
|
+
};
|
|
9394
|
+
const receipt = {
|
|
9395
|
+
helpers: [],
|
|
9396
|
+
registry: [],
|
|
9397
|
+
roots: { upgraded: [], alreadyMarked: [], skipped: [] }
|
|
9398
|
+
};
|
|
9399
|
+
let registryOk = true;
|
|
9400
|
+
for (const file of sources) {
|
|
9401
|
+
if (!file.content.includes(REGISTRY_MARKER)) continue;
|
|
9402
|
+
const isLib = file.content.includes("export function pageSections(");
|
|
9403
|
+
const next = isLib ? patchLib(file.content) : patchRegistry(file.content);
|
|
9404
|
+
if (next === null) {
|
|
9405
|
+
registryOk = false;
|
|
9406
|
+
continue;
|
|
9407
|
+
}
|
|
9408
|
+
receipt.registry.push({ path: file.path, operation: next === file.content ? "unchanged" : "modify" });
|
|
9409
|
+
write(file.path, next);
|
|
9410
|
+
}
|
|
9411
|
+
const helperState = /* @__PURE__ */ new Map();
|
|
9412
|
+
const helperDialect = /* @__PURE__ */ new Map();
|
|
9413
|
+
for (const dialect of Object.keys(HELPER_PATH)) {
|
|
9414
|
+
const path = HELPER_PATH[dialect];
|
|
9415
|
+
if (!path || helperDialect.has(path)) continue;
|
|
9416
|
+
helperDialect.set(path, dialect);
|
|
9417
|
+
const existing = contents.get(path);
|
|
9418
|
+
helperState.set(path, existing === void 0 ? void 0 : isKnownHelper(existing, dialect));
|
|
9419
|
+
}
|
|
9420
|
+
const helperNeeded = /* @__PURE__ */ new Set();
|
|
9421
|
+
for (const file of sources) {
|
|
9422
|
+
const dialect = dialectOf(file.path);
|
|
9423
|
+
if (!dialect || !/data-bcms-block|sectionRootAttrs/.test(file.content)) continue;
|
|
9424
|
+
const ref = (el, attr) => ({
|
|
9425
|
+
file: file.path,
|
|
9426
|
+
line: lineAt(file.content, el.start),
|
|
9427
|
+
tag: el.tag,
|
|
9428
|
+
block: attr.expression ? file.content.slice(attr.expressionRange?.start ?? attr.range.start, attr.expressionRange?.end ?? attr.range.end) : JSON.stringify(attr.value)
|
|
9429
|
+
});
|
|
9430
|
+
const skip = (el, attr, reason, message) => {
|
|
9431
|
+
receipt.roots.skipped.push({ ...ref(el, attr), reason, message });
|
|
9432
|
+
};
|
|
9433
|
+
const parsed = await parseFile4(dialect, file.content);
|
|
9434
|
+
if (parsed.error) {
|
|
9435
|
+
receipt.roots.skipped.push({
|
|
9436
|
+
file: file.path,
|
|
9437
|
+
line: 1,
|
|
9438
|
+
tag: "",
|
|
9439
|
+
block: "",
|
|
9440
|
+
reason: "PARSE_ERROR",
|
|
9441
|
+
message: parsed.error.message
|
|
9442
|
+
});
|
|
9443
|
+
continue;
|
|
9444
|
+
}
|
|
9445
|
+
const roots = elementsIn2(parsed.roots).flatMap((el) => {
|
|
9446
|
+
const attr = el.attrs.find((a) => a.name === "data-bcms-block");
|
|
9447
|
+
return attr ? [{ el, attr }] : [];
|
|
9448
|
+
});
|
|
9449
|
+
for (const el of elementsIn2(parsed.roots)) {
|
|
9450
|
+
const spread = el.attrs.find((a) => isSpread(file.content, a) && /sectionRootAttrs\s*\(/.test(file.content.slice(a.expressionRange.start, a.expressionRange.end)));
|
|
9451
|
+
if (spread && !el.attrs.some((a) => a.name === "data-bcms-block")) {
|
|
9452
|
+
receipt.roots.alreadyMarked.push({
|
|
9453
|
+
file: file.path,
|
|
9454
|
+
line: lineAt(file.content, el.start),
|
|
9455
|
+
tag: el.tag,
|
|
9456
|
+
block: file.content.slice(spread.expressionRange.start, spread.expressionRange.end)
|
|
9457
|
+
});
|
|
9458
|
+
}
|
|
9459
|
+
}
|
|
9460
|
+
if (roots.length === 0) continue;
|
|
9461
|
+
if (dialect !== "astro" && dialect !== "jsx") {
|
|
9462
|
+
for (const { el, attr } of roots) {
|
|
9463
|
+
if (el.attrs.some((a) => a.name === "data-bcms-style")) receipt.roots.alreadyMarked.push(ref(el, attr));
|
|
9464
|
+
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.`);
|
|
9465
|
+
}
|
|
9466
|
+
continue;
|
|
9467
|
+
}
|
|
9468
|
+
const source = new MagicString6(file.content);
|
|
9469
|
+
const reader = STYLE_READER[dialect];
|
|
9470
|
+
const helperPath = HELPER_PATH[dialect];
|
|
9471
|
+
const helperSpecifier = relativeImport(file.path, helperPath).replace(/\.tsx?$/, "");
|
|
9472
|
+
const isHelperModule = (spec) => /(^|\/)bcms-content(\.[jt]sx?)?$/.test(spec);
|
|
9473
|
+
const sdk = SDK_SINCE[dialect].pkg;
|
|
9474
|
+
const isSdk = (spec) => spec === sdk;
|
|
9475
|
+
const useSdk = sdkHasSectionRootAttrs(options.packageJson, dialect);
|
|
9476
|
+
const generated = SECTION_MARKERS.some((m) => file.content.includes(m));
|
|
9477
|
+
const typed = dialect === "astro" || file.path.endsWith(".tsx");
|
|
9478
|
+
const free = (name, from) => importedFrom(file.content, name, from) || !mentions2(file.content, name);
|
|
9479
|
+
let wantReader = false;
|
|
9480
|
+
let wantSdk = false;
|
|
9481
|
+
let sectionProp = false;
|
|
9482
|
+
for (const { el, attr } of roots) {
|
|
9483
|
+
if (el.attrs.some((a) => a.name === "data-bcms-style")) {
|
|
9484
|
+
receipt.roots.alreadyMarked.push(ref(el, attr));
|
|
9485
|
+
continue;
|
|
9486
|
+
}
|
|
9487
|
+
if (!isIntrinsic(el.tag)) {
|
|
9488
|
+
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.`);
|
|
9489
|
+
continue;
|
|
9490
|
+
}
|
|
9491
|
+
if (el.attrs.some((a) => a.name === "style")) {
|
|
9492
|
+
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.");
|
|
9493
|
+
continue;
|
|
9494
|
+
}
|
|
9495
|
+
if (el.attrs.some((a) => isSpread(file.content, a))) {
|
|
9496
|
+
skip(el, attr, "SPREAD", "The root spreads props that may carry a style; marking it could promise a style the spread overrides.");
|
|
9497
|
+
continue;
|
|
9498
|
+
}
|
|
9499
|
+
if (!attr.expression || !attr.expressionRange) {
|
|
9500
|
+
skip(el, attr, "BLOCK_LITERAL", "data-bcms-block is a literal id: there is no block object here to read a style from.");
|
|
9501
|
+
continue;
|
|
9502
|
+
}
|
|
9503
|
+
const expr = exprOf2(file.content, attr.expressionRange);
|
|
9504
|
+
if (generated && expr === "blockId") {
|
|
9505
|
+
if (!registryOk) {
|
|
9506
|
+
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.");
|
|
9507
|
+
continue;
|
|
9508
|
+
}
|
|
9509
|
+
if (!free(reader, isHelperModule) || /\bblockStyle\b/.test(file.content)) {
|
|
9510
|
+
skip(el, attr, "NAME_TAKEN", `${reader} or blockStyle already means something else in this file.`);
|
|
9511
|
+
continue;
|
|
9512
|
+
}
|
|
9513
|
+
source.appendLeft(attr.range.end, ` data-bcms-style="" style={${reader}(blockStyle)}`);
|
|
9514
|
+
receipt.roots.upgraded.push({ ...ref(el, attr), form: "section-prop" });
|
|
9515
|
+
wantReader = true;
|
|
9516
|
+
sectionProp = true;
|
|
9517
|
+
continue;
|
|
9518
|
+
}
|
|
9519
|
+
const member = BLOCK_MEMBER.exec(expr);
|
|
9520
|
+
if (member) {
|
|
9521
|
+
const [, object, dot, key] = member;
|
|
9522
|
+
if (useSdk && key === "id") {
|
|
9523
|
+
if (!free("sectionRootAttrs", isSdk)) {
|
|
9524
|
+
skip(el, attr, "NAME_TAKEN", "sectionRootAttrs already means something else in this file.");
|
|
9525
|
+
continue;
|
|
9526
|
+
}
|
|
9527
|
+
source.overwrite(attr.range.start, attr.range.end, `{...sectionRootAttrs(${object})}`);
|
|
9528
|
+
receipt.roots.upgraded.push({ ...ref(el, attr), form: "sectionRootAttrs" });
|
|
9529
|
+
wantSdk = true;
|
|
9530
|
+
continue;
|
|
9531
|
+
}
|
|
9532
|
+
if (!free(reader, isHelperModule)) {
|
|
9533
|
+
skip(el, attr, "NAME_TAKEN", `${reader} already means something else in this file.`);
|
|
9534
|
+
continue;
|
|
9535
|
+
}
|
|
9536
|
+
const style = typed && dialect === "jsx" ? `(${object} as { style?: unknown } | null | undefined)?.style` : `${object}${dot}style`;
|
|
9537
|
+
source.appendLeft(attr.range.end, ` data-bcms-style="" style={${reader}(${style})}`);
|
|
9538
|
+
receipt.roots.upgraded.push({ ...ref(el, attr), form: "helper" });
|
|
9539
|
+
wantReader = true;
|
|
9540
|
+
continue;
|
|
9541
|
+
}
|
|
9542
|
+
if (ID_ONLY.test(expr)) {
|
|
9543
|
+
skip(
|
|
9544
|
+
el,
|
|
9545
|
+
attr,
|
|
9546
|
+
"BLOCK_ID_ONLY",
|
|
9547
|
+
`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.`
|
|
9548
|
+
);
|
|
9549
|
+
continue;
|
|
9550
|
+
}
|
|
9551
|
+
skip(el, attr, "BLOCK_UNRESOLVABLE", `data-bcms-block={${expr}} does not name one block whose style this root could paint.`);
|
|
9552
|
+
}
|
|
9553
|
+
if (sectionProp) {
|
|
9554
|
+
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);
|
|
9555
|
+
if (destructure) {
|
|
9556
|
+
const close = destructure.index + destructure[0].indexOf("}");
|
|
9557
|
+
let at = close;
|
|
9558
|
+
while (at > 0 && /\s/.test(file.content[at - 1])) at--;
|
|
9559
|
+
source.appendLeft(at, ", blockStyle");
|
|
9560
|
+
}
|
|
9561
|
+
if (dialect === "astro") {
|
|
9562
|
+
const props = /interface Props \{[\s\S]*?\n\}/.exec(file.content);
|
|
9563
|
+
if (props) {
|
|
9564
|
+
const end = props.index + props[0].length - 1;
|
|
9565
|
+
const nl = eolOf(file.content);
|
|
9566
|
+
source.appendLeft(
|
|
9567
|
+
end,
|
|
9568
|
+
` /** The placement's \`block.style\` \u2014 painted on the root, which says so with \`data-bcms-style\`. */${nl} blockStyle?: Record<string, unknown>;${nl}`
|
|
9569
|
+
);
|
|
9570
|
+
}
|
|
9571
|
+
}
|
|
9572
|
+
}
|
|
9573
|
+
if (wantReader) {
|
|
9574
|
+
addNamedImport(source, file.content, parsed, helperSpecifier, isHelperModule, [reader]);
|
|
9575
|
+
helperNeeded.add(helperPath);
|
|
9576
|
+
}
|
|
9577
|
+
if (wantSdk) addNamedImport(source, file.content, parsed, sdk, isSdk, ["sectionRootAttrs"]);
|
|
9578
|
+
if (source.hasChanged()) write(file.path, source.toString());
|
|
9579
|
+
}
|
|
9580
|
+
for (const [path, version] of helperState) {
|
|
9581
|
+
const dialect = helperDialect.get(path);
|
|
9582
|
+
const needed = helperNeeded.has(path);
|
|
9583
|
+
if (version === void 0) {
|
|
9584
|
+
if (!needed) continue;
|
|
9585
|
+
write(path, helperSource(dialect));
|
|
9586
|
+
receipt.helpers.push({ path, from: null, to: HELPER_VERSION, operation: "add" });
|
|
9587
|
+
const stub = `${CONTENT_DIR}/layout.json`;
|
|
9588
|
+
if (!contents.has(stub)) write(stub, STUB_CONTENT);
|
|
9589
|
+
continue;
|
|
9590
|
+
}
|
|
9591
|
+
if (version === null) {
|
|
9592
|
+
if (needed && !options.overwriteHelper) {
|
|
9593
|
+
throw new UpgradeError(
|
|
9594
|
+
`${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.`
|
|
9595
|
+
);
|
|
9596
|
+
}
|
|
9597
|
+
if (needed) {
|
|
9598
|
+
write(path, helperSource(dialect));
|
|
9599
|
+
receipt.helpers.push({ path, from: null, to: HELPER_VERSION, operation: "modify" });
|
|
9600
|
+
} else receipt.helpers.push({ path, from: null, to: HELPER_VERSION, operation: "foreign" });
|
|
9601
|
+
continue;
|
|
9602
|
+
}
|
|
9603
|
+
if (version < HELPER_VERSION) {
|
|
9604
|
+
write(path, helperSource(dialect));
|
|
9605
|
+
receipt.helpers.push({ path, from: version, to: HELPER_VERSION, operation: "modify" });
|
|
9606
|
+
} else receipt.helpers.push({ path, from: version, to: HELPER_VERSION, operation: "unchanged" });
|
|
9607
|
+
}
|
|
9608
|
+
const byPlace = (a, b) => a.file.localeCompare(b.file) || a.line - b.line;
|
|
9609
|
+
receipt.roots.upgraded.sort(byPlace);
|
|
9610
|
+
receipt.roots.alreadyMarked.sort(byPlace);
|
|
9611
|
+
receipt.roots.skipped.sort(byPlace);
|
|
9612
|
+
return { files: [...out.values()].sort((a, b) => a.path.localeCompare(b.path)), receipt };
|
|
9613
|
+
}
|
|
9614
|
+
|
|
9181
9615
|
// src/index.ts
|
|
9182
9616
|
var UNPARSEABLE_DIALECT = {
|
|
9183
9617
|
roots: [],
|
|
@@ -9515,7 +9949,7 @@ async function convertSources(briefIn, sources, options) {
|
|
|
9515
9949
|
rewritten++;
|
|
9516
9950
|
}
|
|
9517
9951
|
for (const [file, splices] of [...componentEdits].sort(([a], [b]) => a.localeCompare(b))) {
|
|
9518
|
-
const source = new
|
|
9952
|
+
const source = new MagicString7(contents.get(file));
|
|
9519
9953
|
const seen = /* @__PURE__ */ new Set();
|
|
9520
9954
|
for (const splice of splices) {
|
|
9521
9955
|
const id = `${splice.start}:${splice.end}:${splice.text}`;
|
|
@@ -10340,7 +10774,7 @@ function styleEdits(before, after) {
|
|
|
10340
10774
|
// package.json
|
|
10341
10775
|
var package_default = {
|
|
10342
10776
|
name: "@bettercms-ai/convert",
|
|
10343
|
-
version: "0.
|
|
10777
|
+
version: "0.24.0",
|
|
10344
10778
|
type: "module",
|
|
10345
10779
|
description: "Deterministic codemod that turns an imported site's text-matched bindings into declared ones \u2014 parser offsets in, one magic-string pass out, plus a receipt of everything it could not do.",
|
|
10346
10780
|
bin: {
|
|
@@ -10403,6 +10837,7 @@ function parseArgs(argv) {
|
|
|
10403
10837
|
componentize: false,
|
|
10404
10838
|
forms: false,
|
|
10405
10839
|
pins: {},
|
|
10840
|
+
upgradeSections: false,
|
|
10406
10841
|
xray: void 0,
|
|
10407
10842
|
verifyStyles: false,
|
|
10408
10843
|
base: "HEAD",
|
|
@@ -10421,6 +10856,7 @@ function parseArgs(argv) {
|
|
|
10421
10856
|
if (arg === "--brief") args.brief = argv[++i];
|
|
10422
10857
|
else if (arg === "--componentize") args.componentize = true;
|
|
10423
10858
|
else if (arg === "--forms") args.forms = true;
|
|
10859
|
+
else if (arg === "--upgrade-sections") args.upgradeSections = true;
|
|
10424
10860
|
else if (arg === "--pin") {
|
|
10425
10861
|
const raw = argv[++i] ?? "";
|
|
10426
10862
|
const at = raw.indexOf("=");
|
|
@@ -10460,6 +10896,7 @@ var HELP = `bettercms-convert ${VERSION}
|
|
|
10460
10896
|
bettercms-convert --brief <json> --root . [options]
|
|
10461
10897
|
bettercms-convert --componentize --brief <json> --plan <json> --root . [options]
|
|
10462
10898
|
bettercms-convert --forms --brief <json> --root . [options]
|
|
10899
|
+
bettercms-convert --upgrade-sections --root . [--dry-run]
|
|
10463
10900
|
bettercms-convert --xray <dist-dir>
|
|
10464
10901
|
bettercms-convert --verify-styles [--base <ref>] --root .
|
|
10465
10902
|
|
|
@@ -10472,6 +10909,14 @@ var HELP = `bettercms-convert ${VERSION}
|
|
|
10472
10909
|
--pin <form>=<file> With --forms: the file a form's <form> is in, when the run reports
|
|
10473
10910
|
FORM_AMBIGUOUS (two files post exactly its fields). Form by id or
|
|
10474
10911
|
name. Repeatable.
|
|
10912
|
+
--upgrade-sections For a site converted before helper v10: give every section root
|
|
10913
|
+
(data-bcms-block) the data-bcms-style marker and the block's style,
|
|
10914
|
+
so the editor's section spacing and style handles turn on. Upgrades
|
|
10915
|
+
the helper, patches a generated registry to pass block.style, and
|
|
10916
|
+
spreads {...sectionRootAttrs(block)} where the SDK has it. A root
|
|
10917
|
+
with its own style or a spread, or whose block is not in scope, is
|
|
10918
|
+
left alone and listed with its reason. Takes no --brief; a second
|
|
10919
|
+
run changes nothing. Exit 2 with --strict when any root was skipped.
|
|
10475
10920
|
--xray <dist-dir> Measure a LOCAL build instead of converting: how much of each page's
|
|
10476
10921
|
visible copy no binding owns, and the biggest buckets of it. Run it
|
|
10477
10922
|
after a build to see what the next conversion still has to reach,
|
|
@@ -10603,6 +11048,28 @@ ${extracted} sections extracted (${registered} registered, ${inlineOnly} inline-
|
|
|
10603
11048
|
);
|
|
10604
11049
|
}
|
|
10605
11050
|
}
|
|
11051
|
+
function reportUpgrade(files, receipt, dryRun) {
|
|
11052
|
+
for (const file of files) {
|
|
11053
|
+
process.stdout.write(`${dryRun ? "would " : ""}${file.operation} ${file.path}
|
|
11054
|
+
`);
|
|
11055
|
+
}
|
|
11056
|
+
for (const h of receipt.helpers) {
|
|
11057
|
+
process.stdout.write(`helper ${h.path} ${h.from === null ? "(none)" : `v${h.from}`} -> v${h.to} (${h.operation})
|
|
11058
|
+
`);
|
|
11059
|
+
}
|
|
11060
|
+
for (const r of receipt.registry) process.stdout.write(`registry ${r.path} (${r.operation})
|
|
11061
|
+
`);
|
|
11062
|
+
const { upgraded, alreadyMarked, skipped } = receipt.roots;
|
|
11063
|
+
process.stdout.write(
|
|
11064
|
+
`
|
|
11065
|
+
${upgraded.length} section roots upgraded (${alreadyMarked.length} already marked, ${skipped.length} skipped)
|
|
11066
|
+
`
|
|
11067
|
+
);
|
|
11068
|
+
for (const r of upgraded) process.stdout.write(` upgraded ${r.file}:${r.line} <${r.tag}> ${r.block} \u2014 ${r.form}
|
|
11069
|
+
`);
|
|
11070
|
+
for (const r of skipped) process.stdout.write(` skipped ${r.file}:${r.line} <${r.tag}> ${r.block} \u2014 ${r.reason}: ${r.message}
|
|
11071
|
+
`);
|
|
11072
|
+
}
|
|
10606
11073
|
async function xray(dir, strict) {
|
|
10607
11074
|
const pages = [];
|
|
10608
11075
|
const walk = async (at) => {
|
|
@@ -10676,6 +11143,31 @@ async function main() {
|
|
|
10676
11143
|
return await xray(resolve(args.xray), args.strict);
|
|
10677
11144
|
}
|
|
10678
11145
|
if (args.verifyStyles) return await verifyStyles(resolve(args.root), args.base, args.verbose);
|
|
11146
|
+
if (args.upgradeSections) {
|
|
11147
|
+
if (args.brief || args.componentize || args.forms || args.plan) {
|
|
11148
|
+
throw new CliError("--upgrade-sections reads the repository only: no --brief, --plan, --componentize or --forms. See --help.");
|
|
11149
|
+
}
|
|
11150
|
+
const root2 = resolve(args.root);
|
|
11151
|
+
let packageJson;
|
|
11152
|
+
try {
|
|
11153
|
+
packageJson = await readFile(join2(root2, "package.json"), "utf-8");
|
|
11154
|
+
} catch {
|
|
11155
|
+
packageJson = void 0;
|
|
11156
|
+
}
|
|
11157
|
+
const sources2 = [...await readTree(root2, args.verbose), ...await readSnapshots(root2)];
|
|
11158
|
+
const { files: files2, receipt: receipt2 } = await upgradeSections(sources2, { overwriteHelper: args.overwriteHelper, packageJson });
|
|
11159
|
+
if (!args.dryRun) {
|
|
11160
|
+
for (const file of files2) {
|
|
11161
|
+
const target = under(root2, file.path);
|
|
11162
|
+
await mkdir(dirname(target), { recursive: true });
|
|
11163
|
+
await writeFile(target, file.content, "utf-8");
|
|
11164
|
+
}
|
|
11165
|
+
}
|
|
11166
|
+
if (args.receipt) await writeFile(resolve(args.receipt), `${JSON.stringify(receipt2, null, 2)}
|
|
11167
|
+
`, "utf-8");
|
|
11168
|
+
reportUpgrade(files2, receipt2, args.dryRun);
|
|
11169
|
+
return args.strict && receipt2.roots.skipped.length > 0 ? 2 : 0;
|
|
11170
|
+
}
|
|
10679
11171
|
if (!args.brief) throw new CliError("--brief <file> is required. See --help.");
|
|
10680
11172
|
if (args.plan && !args.componentize) {
|
|
10681
11173
|
throw new CliError("--plan is only read with --componentize. See --help.");
|