@bettercms-ai/convert 0.24.1 → 0.25.1
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 +18 -0
- package/dist/cli.js +148 -51
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +33 -1
- package/dist/index.js +122 -43
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @bettercms-ai/convert
|
|
2
2
|
|
|
3
|
+
## 0.25.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b1e5eb0: Prices and other numbers are now editable: a value written as `49` or `price={49}` in a data object, a `const`, or a component prop is bound like any other copy, and still renders the same text. `--brief` no longer writes a second `data-bcms-field` on an element, and two paths that want the same element, or a rewrite inside text another rewrite replaces, are reported as ambiguous instead of corrupting the file; a second run over converted source changes nothing.
|
|
8
|
+
- Updated dependencies [56335ff]
|
|
9
|
+
- @bettercms-ai/types@1.32.0
|
|
10
|
+
|
|
11
|
+
## 0.25.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- 832e08a: A converted repository whose `build` runs checks before the build (`astro check && astro build`, `tsc --noEmit && vite build`, `npm run lint && next build`) gets a `build:bcms` script beside it with the same build minus those checks, `prebuild`/`postbuild` folded in. BetterCMS builds with `build:bcms` when a project declares one, so a deploy no longer pays for the project's CI checks; `build` is left unchanged. Every writing run adds it once (a repository that already has one is never touched), and only for a plain `&&` chain. Exported as `bcmsBuildScript`.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- c6fd790: The codemod never walks into a directory holding its own `.git` (a git worktree, submodule or vendored clone), so a tool's worktree copy of the repo is no longer converted alongside the real source.
|
|
20
|
+
|
|
3
21
|
## 0.24.1
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { execFileSync } from "child_process";
|
|
5
|
+
import { existsSync } from "fs";
|
|
5
6
|
import { readdir, readFile, writeFile, mkdir, stat } from "fs/promises";
|
|
6
7
|
import { dirname, join as join2, relative, resolve, sep } from "path";
|
|
7
8
|
|
|
@@ -2714,8 +2715,9 @@ function collectSites(roots, literals) {
|
|
|
2714
2715
|
}
|
|
2715
2716
|
const bindingsAttr = node.attrs.find((a) => a.name === "bcmsBindings")?.range;
|
|
2716
2717
|
for (const attr of node.attrs) {
|
|
2717
|
-
|
|
2718
|
-
|
|
2718
|
+
const digits = attr.expression && attr.expressionRange ? attr.number : void 0;
|
|
2719
|
+
if (attr.expression && digits === void 0 || !digits && !attr.valueRange || BCMS_ATTRS.includes(attr.name)) continue;
|
|
2720
|
+
const value = norm(digits ?? attr.value);
|
|
2719
2721
|
for (const literal of wanted) {
|
|
2720
2722
|
if (value !== literal) continue;
|
|
2721
2723
|
matched.add(literal);
|
|
@@ -2723,7 +2725,7 @@ function collectSites(roots, literals) {
|
|
|
2723
2725
|
file: "",
|
|
2724
2726
|
tag: node.tag,
|
|
2725
2727
|
attrInsertAt: node.attrInsertAt,
|
|
2726
|
-
range: attr.valueRange,
|
|
2728
|
+
range: digits === void 0 ? attr.valueRange : attr.expressionRange,
|
|
2727
2729
|
inner: node.inner,
|
|
2728
2730
|
// 🔴 A COMPONENT'S ATTRIBUTE IS A PROP, and a prop is a different conversion: the value
|
|
2729
2731
|
// is rendered in another file, by an element only that file knows. @see props.ts
|
|
@@ -2831,6 +2833,8 @@ function attrsOf2(open, content) {
|
|
|
2831
2833
|
const value = attr.value;
|
|
2832
2834
|
const range = { start: attr.start ?? 0, end: attr.end ?? 0 };
|
|
2833
2835
|
const literal = value?.type === "StringLiteral";
|
|
2836
|
+
const inside = value?.type === "JSXExpressionContainer" ? value.expression : null;
|
|
2837
|
+
const numeric = inside?.type === "NumericLiteral" ? String(inside.extra.raw) : "";
|
|
2834
2838
|
return [
|
|
2835
2839
|
{
|
|
2836
2840
|
name: nameOf(attr.name),
|
|
@@ -2839,7 +2843,8 @@ function attrsOf2(open, content) {
|
|
|
2839
2843
|
valueRange: literal ? { start: (value.start ?? 0) + 1, end: (value.end ?? 0) - 1 } : null,
|
|
2840
2844
|
expression: !literal,
|
|
2841
2845
|
range,
|
|
2842
|
-
expressionRange: value?.type === "JSXExpressionContainer" ? { start: value.start ?? 0, end: value.end ?? 0 } : null
|
|
2846
|
+
expressionRange: value?.type === "JSXExpressionContainer" ? { start: value.start ?? 0, end: value.end ?? 0 } : null,
|
|
2847
|
+
...numeric ? { number: numeric } : {}
|
|
2843
2848
|
}
|
|
2844
2849
|
];
|
|
2845
2850
|
});
|
|
@@ -3315,14 +3320,21 @@ function exportedNames(ast) {
|
|
|
3315
3320
|
});
|
|
3316
3321
|
return out;
|
|
3317
3322
|
}
|
|
3323
|
+
function printed(node) {
|
|
3324
|
+
if (node?.type === "StringLiteral") return [String(node.value)];
|
|
3325
|
+
if (node?.type === "NumericLiteral") return [String(node.value), String(node.extra?.raw ?? "")];
|
|
3326
|
+
const quasis = node?.type === "TemplateLiteral" ? node.quasis ?? [] : [];
|
|
3327
|
+
const expressions = node?.expressions ?? [];
|
|
3328
|
+
return quasis.length === 1 && expressions.length === 0 ? [String(quasis[0].value.cooked ?? "")] : [];
|
|
3329
|
+
}
|
|
3318
3330
|
function propertyFor(row, literal) {
|
|
3319
3331
|
if (row?.type !== "ObjectExpression") return null;
|
|
3320
3332
|
for (const property of row.properties ?? []) {
|
|
3321
3333
|
if (property.type !== "ObjectProperty" && property.type !== "Property") continue;
|
|
3322
3334
|
const key = property.key;
|
|
3323
3335
|
const value = property.value;
|
|
3324
|
-
if (
|
|
3325
|
-
if (
|
|
3336
|
+
if (key?.type !== "Identifier") continue;
|
|
3337
|
+
if (printed(value).some((text) => norm(text) === literal)) return String(key.name);
|
|
3326
3338
|
}
|
|
3327
3339
|
return null;
|
|
3328
3340
|
}
|
|
@@ -3396,7 +3408,7 @@ function stringLiterals(ast) {
|
|
|
3396
3408
|
if (node.type !== "VariableDeclarator") return;
|
|
3397
3409
|
const id = node.id;
|
|
3398
3410
|
const init = node.init;
|
|
3399
|
-
if (id?.type !== "Identifier" || init
|
|
3411
|
+
if (id?.type !== "Identifier" || !init || printed(init).length === 0) return;
|
|
3400
3412
|
out.set(String(id.name), init);
|
|
3401
3413
|
});
|
|
3402
3414
|
return out;
|
|
@@ -3427,7 +3439,7 @@ function scalarUses(content, roots, id) {
|
|
|
3427
3439
|
}
|
|
3428
3440
|
function bindScalar(ctx, member, literals, offset) {
|
|
3429
3441
|
const rule = DIALECT_RULES[ctx.dialect];
|
|
3430
|
-
const named = [...literals].filter(([, init2]) =>
|
|
3442
|
+
const named = [...literals].filter(([, init2]) => printed(init2).some((text) => norm(text) === member.literal));
|
|
3431
3443
|
if (named.length !== 1) return { splices: [], refusal: null };
|
|
3432
3444
|
const [id, init] = named[0];
|
|
3433
3445
|
const uses = scalarUses(ctx.content, ctx.parsed.roots, id);
|
|
@@ -3642,16 +3654,17 @@ function literalTable(init) {
|
|
|
3642
3654
|
const first = elements[0];
|
|
3643
3655
|
if (!first || elements.length !== (init.elements ?? []).length) return null;
|
|
3644
3656
|
if (elements.some((element) => element.type !== first.type)) return null;
|
|
3645
|
-
if (first.
|
|
3646
|
-
|
|
3657
|
+
if (printed(first).length > 0) {
|
|
3658
|
+
if (elements.some((element) => printed(element).length === 0)) return null;
|
|
3659
|
+
return { tuple: false, rows: elements.map((element) => [norm(printed(element)[0])]) };
|
|
3647
3660
|
}
|
|
3648
3661
|
if (first.type !== "ArrayExpression") return null;
|
|
3649
3662
|
const rows = [];
|
|
3650
3663
|
for (const element of elements) {
|
|
3651
3664
|
const inner = (element.elements ?? []).filter((x) => x !== null);
|
|
3652
|
-
if (inner.length === 0 || inner.some((x) => x.
|
|
3665
|
+
if (inner.length === 0 || inner.some((x) => printed(x).length === 0)) return null;
|
|
3653
3666
|
if (inner.length !== (element.elements ?? []).length) return null;
|
|
3654
|
-
rows.push(inner.map((x) => norm(
|
|
3667
|
+
rows.push(inner.map((x) => norm(printed(x)[0])));
|
|
3655
3668
|
}
|
|
3656
3669
|
if (new Set(rows.map((row) => row.length)).size !== 1) return null;
|
|
3657
3670
|
return { tuple: true, rows };
|
|
@@ -5459,7 +5472,8 @@ function overlapping(rewrites) {
|
|
|
5459
5472
|
for (const a of rewrites) {
|
|
5460
5473
|
for (const b of rewrites) {
|
|
5461
5474
|
if (a.raw !== void 0 || b.raw !== void 0) continue;
|
|
5462
|
-
if (a
|
|
5475
|
+
if (a === b || a.key === b.key) continue;
|
|
5476
|
+
if (collide(a, b)) {
|
|
5463
5477
|
out.add(a);
|
|
5464
5478
|
out.add(b);
|
|
5465
5479
|
}
|
|
@@ -5467,6 +5481,16 @@ function overlapping(rewrites) {
|
|
|
5467
5481
|
}
|
|
5468
5482
|
return [...out];
|
|
5469
5483
|
}
|
|
5484
|
+
var writes = ({ site }) => [site.range, site.attrRange, site.where === "text" ? site.inner : null].filter((r) => r != null);
|
|
5485
|
+
function collide(a, b) {
|
|
5486
|
+
const inside = (at, spans) => spans.some((r) => r.start < at && at < r.end);
|
|
5487
|
+
const [wa, wb] = [writes(a), writes(b)];
|
|
5488
|
+
if (wa.some((x) => wb.some((y) => overlaps(x, y)))) return true;
|
|
5489
|
+
if (inside(a.site.attrInsertAt, wb) || inside(b.site.attrInsertAt, wa)) return true;
|
|
5490
|
+
const [fa, fb] = [field(a), field(b)];
|
|
5491
|
+
return a.site.attrInsertAt === b.site.attrInsertAt && !!fa && !!fb && fa !== fb;
|
|
5492
|
+
}
|
|
5493
|
+
var field = (r) => r.site.where === "prop" || r.site.where === "text" && !ownsSubtree(r.kind, r.site, r.inline) && r.site.mixed && r.site.textRange ? void 0 : declaration(r).plain[0];
|
|
5470
5494
|
function declaration(rewrite) {
|
|
5471
5495
|
const { site, path, kind, scope } = rewrite;
|
|
5472
5496
|
if (site.where === "attr" && kind !== "image") {
|
|
@@ -5481,8 +5505,8 @@ function rewriteFile(content, dialect, rewrites, imports, extra = []) {
|
|
|
5481
5505
|
const bindings = /* @__PURE__ */ new Map();
|
|
5482
5506
|
const addDeclaration = (at, add) => {
|
|
5483
5507
|
const held = attributes.get(at) ?? { plain: [], props: [] };
|
|
5484
|
-
held.plain.push(...add.plain);
|
|
5485
|
-
held.props.push(...add.props);
|
|
5508
|
+
held.plain.push(...add.plain.filter((text) => !held.plain.includes(text)));
|
|
5509
|
+
held.props.push(...add.props.filter((p) => !held.props.some((q) => JSON.stringify(q) === JSON.stringify(p))));
|
|
5486
5510
|
attributes.set(at, held);
|
|
5487
5511
|
};
|
|
5488
5512
|
for (const rewrite of rewrites) {
|
|
@@ -5905,7 +5929,8 @@ function attrsOf4(node, content) {
|
|
|
5905
5929
|
valueRange: quoted && rawStart >= 0 && attr.value !== "" ? { start: rawStart + (unquoted ? 0 : 1), end: rawStart + raw.length - (unquoted ? 0 : 1) } : null,
|
|
5906
5930
|
expression: !quoted,
|
|
5907
5931
|
range,
|
|
5908
|
-
expressionRange: !quoted && open >= 0 && close > open ? { start: open, end: close + 1 } : null
|
|
5932
|
+
expressionRange: !quoted && open >= 0 && close > open ? { start: open, end: close + 1 } : null,
|
|
5933
|
+
...attr.kind === "expression" && /^\d+(\.\d+)?$/.test(attr.value.trim()) ? { number: attr.value.trim() } : {}
|
|
5909
5934
|
}
|
|
5910
5935
|
];
|
|
5911
5936
|
});
|
|
@@ -6024,6 +6049,9 @@ function attrsOf5(node) {
|
|
|
6024
6049
|
return [{ name: `\0${name}`, value: "", valueRange: null, expression: true, expressionRange: null, range }];
|
|
6025
6050
|
}
|
|
6026
6051
|
const literal = literalValue(attr);
|
|
6052
|
+
const tag = attr.value;
|
|
6053
|
+
const raw = tag?.type === "ExpressionTag" ? tag.expression : void 0;
|
|
6054
|
+
const digits = typeof raw?.value === "number" ? String(raw.raw ?? "") : "";
|
|
6027
6055
|
return [
|
|
6028
6056
|
{
|
|
6029
6057
|
name,
|
|
@@ -6031,7 +6059,8 @@ function attrsOf5(node) {
|
|
|
6031
6059
|
valueRange: literal && literal.value !== "" ? literal.range : null,
|
|
6032
6060
|
expression: literal === null,
|
|
6033
6061
|
// Not read: svelte sections are DIALECT_UNSUPPORTED. @see componentize.ts
|
|
6034
|
-
expressionRange: null,
|
|
6062
|
+
expressionRange: digits ? { start: tag.start ?? 0, end: tag.end ?? 0 } : null,
|
|
6063
|
+
...digits ? { number: digits } : {},
|
|
6035
6064
|
range
|
|
6036
6065
|
}
|
|
6037
6066
|
];
|
|
@@ -6136,13 +6165,15 @@ var spanOf = (loc) => ({
|
|
|
6136
6165
|
function attrsOf6(node) {
|
|
6137
6166
|
return (node.props ?? []).flatMap((prop) => {
|
|
6138
6167
|
if (prop.type === DIRECTIVE && prop.name === "bind" && prop.arg?.content) {
|
|
6168
|
+
const digits = /^\d+(\.\d+)?$/.test(String(prop.exp?.content ?? "").trim()) ? String(prop.exp.content).trim() : "";
|
|
6139
6169
|
return [
|
|
6140
6170
|
{
|
|
6141
6171
|
name: prop.arg.content,
|
|
6142
6172
|
value: "",
|
|
6143
6173
|
valueRange: null,
|
|
6144
6174
|
expression: true,
|
|
6145
|
-
expressionRange: null,
|
|
6175
|
+
expressionRange: digits ? spanOf(prop.exp?.loc) : null,
|
|
6176
|
+
...digits ? { number: digits } : {},
|
|
6146
6177
|
range: spanOf(prop.loc)
|
|
6147
6178
|
}
|
|
6148
6179
|
];
|
|
@@ -6626,8 +6657,8 @@ function readPlan(value) {
|
|
|
6626
6657
|
check.optionalString(section.sectionType, `${at2}.sectionType`);
|
|
6627
6658
|
check.optionalString(section.copy, `${at2}.copy`);
|
|
6628
6659
|
if (section.fields !== void 0) {
|
|
6629
|
-
for (const [f,
|
|
6630
|
-
const shape = check.record(
|
|
6660
|
+
for (const [f, field2] of check.array(section.fields, `${at2}.fields`).entries()) {
|
|
6661
|
+
const shape = check.record(field2, `${at2}.fields[${f}]`);
|
|
6631
6662
|
check.optionalString(shape.key, `${at2}.fields[${f}].key`);
|
|
6632
6663
|
check.optionalString(shape.path, `${at2}.fields[${f}].path`);
|
|
6633
6664
|
}
|
|
@@ -6946,15 +6977,15 @@ function wire(source, candidate, form) {
|
|
|
6946
6977
|
source.remove(start, attr.range.end);
|
|
6947
6978
|
}
|
|
6948
6979
|
}
|
|
6949
|
-
for (const
|
|
6950
|
-
const control = candidate.controls.get(
|
|
6980
|
+
for (const field2 of form.fields) {
|
|
6981
|
+
const control = candidate.controls.get(field2.key);
|
|
6951
6982
|
if (candidate.foreign.has(control)) continue;
|
|
6952
6983
|
const host = candidate.wrapper.get(control) ?? control;
|
|
6953
|
-
if (!attrOf(host, "data-bcms-form-field")) source.appendLeft(host.attrInsertAt, ` data-bcms-form-field="${escapeAttr(
|
|
6984
|
+
if (!attrOf(host, "data-bcms-form-field")) source.appendLeft(host.attrInsertAt, ` data-bcms-form-field="${escapeAttr(field2.key)}"`);
|
|
6954
6985
|
const name = attrOf(control, "name");
|
|
6955
|
-
if (!name) source.appendLeft(control.attrInsertAt, ` name="${escapeAttr(
|
|
6956
|
-
else if (!name.expression && name.value !==
|
|
6957
|
-
source.overwrite(name.valueRange.start, name.valueRange.end, escapeAttr(
|
|
6986
|
+
if (!name) source.appendLeft(control.attrInsertAt, ` name="${escapeAttr(field2.key)}"`);
|
|
6987
|
+
else if (!name.expression && name.value !== field2.key && name.valueRange) {
|
|
6988
|
+
source.overwrite(name.valueRange.start, name.valueRange.end, escapeAttr(field2.key));
|
|
6958
6989
|
}
|
|
6959
6990
|
}
|
|
6960
6991
|
if (el.inner && !hasMessage(el)) {
|
|
@@ -6998,7 +7029,7 @@ async function wireForms(briefIn, sources, options_ = {}) {
|
|
|
6998
7029
|
candidates.push(candidateOf(source.path, dialect, el, imported, landmark, source.content, parsed.importAt));
|
|
6999
7030
|
}
|
|
7000
7031
|
}
|
|
7001
|
-
const holds = (c, f) => f.fields.filter((
|
|
7032
|
+
const holds = (c, f) => f.fields.filter((field2) => c.controls.has(field2.key)).length;
|
|
7002
7033
|
const byId = (c, f) => !attrOf(c.el, "data-bcms-form")?.expression && attrOf(c.el, "data-bcms-form")?.value === f.id;
|
|
7003
7034
|
const programAction = (c) => !!attrOf(c.el, "action")?.expression && calledName(exprOf(c, "action"), "bcmsFormAction") === null;
|
|
7004
7035
|
const wiredTo = (c, f) => BY_NAME.includes(c.dialect) ? calledName(exprOf(c, "data-bcms-form"), "bcmsFormId") === f.name || byId(c, f) && programAction(c) : byId(c, f);
|
|
@@ -7120,9 +7151,9 @@ async function wireForms(briefIn, sources, options_ = {}) {
|
|
|
7120
7151
|
pend2(form, "ACTION_IS_EXPRESSION", candidate.file, "its action is an expression, so this run will not replace it.");
|
|
7121
7152
|
continue;
|
|
7122
7153
|
}
|
|
7123
|
-
const misnamed = form.fields.find((
|
|
7124
|
-
const attr = attrOf(candidate.controls.get(
|
|
7125
|
-
return !!attr && attr.value !==
|
|
7154
|
+
const misnamed = form.fields.find((field2) => {
|
|
7155
|
+
const attr = attrOf(candidate.controls.get(field2.key), "name");
|
|
7156
|
+
return !!attr && attr.value !== field2.key && (attr.expression || !attr.valueRange);
|
|
7126
7157
|
});
|
|
7127
7158
|
if (misnamed) {
|
|
7128
7159
|
pend2(form, "FIELD_UNMATCHED", candidate.file, `the control for ${misnamed.key} posts under an expression, so its name cannot be set to the CMS key.`);
|
|
@@ -7202,7 +7233,7 @@ ${indent}${scriptFor(last.candidate.dialect)}`);
|
|
|
7202
7233
|
);
|
|
7203
7234
|
}
|
|
7204
7235
|
for (const { form, candidate } of matched) {
|
|
7205
|
-
const keys = form.fields.filter((
|
|
7236
|
+
const keys = form.fields.filter((field2) => candidate.foreign.has(candidate.controls.get(field2.key))).map((field2) => field2.key);
|
|
7206
7237
|
if (keys.length > 0) {
|
|
7207
7238
|
notes.push(
|
|
7208
7239
|
`form ${form.name}: ${keys.join(", ")} matched a control inside an imported component, so no data-bcms-form-field marker was written for ${keys.length === 1 ? "it" : "them"}.`
|
|
@@ -7664,6 +7695,54 @@ function moduleScope(parsed, content) {
|
|
|
7664
7695
|
return out;
|
|
7665
7696
|
}
|
|
7666
7697
|
|
|
7698
|
+
// src/build-script.ts
|
|
7699
|
+
var CHECK = /^(?:npx\s+|pnpm\s+(?:exec\s+)?|yarn\s+|bunx\s+)?(?:astro\s+check|svelte-check|vue-tsc|tsc(?:\s|$)|eslint|next\s+lint|biome\s+(?:check|lint|ci)|prettier\s+(?:--check|-c)\b|stylelint|oxlint)/;
|
|
7700
|
+
var RUN_CHECK = /^(?:npm\s+run|pnpm(?:\s+run)?|yarn(?:\s+run)?|bun\s+run)\s+(?:lint|typecheck|type-check|check|check:types|types|format:check|test)(?::[\w-]+)?\s*$/;
|
|
7701
|
+
var UNSAFE = /;|\|\||\||(?:^|[^&])&(?:[^&]|$)|\$\(|`|\n/;
|
|
7702
|
+
var isCheck = (step) => CHECK.test(step) || RUN_CHECK.test(step);
|
|
7703
|
+
function steps(script) {
|
|
7704
|
+
if (UNSAFE.test(script)) return null;
|
|
7705
|
+
const out = script.split("&&").map((s) => s.trim());
|
|
7706
|
+
return out.some((s) => s === "") ? null : out;
|
|
7707
|
+
}
|
|
7708
|
+
function bcmsBuildScript(packageJson) {
|
|
7709
|
+
let pkg;
|
|
7710
|
+
try {
|
|
7711
|
+
pkg = JSON.parse(packageJson);
|
|
7712
|
+
} catch {
|
|
7713
|
+
return null;
|
|
7714
|
+
}
|
|
7715
|
+
const scripts = pkg?.scripts;
|
|
7716
|
+
if (!scripts || typeof scripts !== "object" || "build:bcms" in scripts) return null;
|
|
7717
|
+
const build = scripts.build;
|
|
7718
|
+
if (typeof build !== "string") return null;
|
|
7719
|
+
const parts = [];
|
|
7720
|
+
for (const name of ["prebuild", "build", "postbuild"]) {
|
|
7721
|
+
const value = scripts[name];
|
|
7722
|
+
if (value === void 0) continue;
|
|
7723
|
+
if (typeof value !== "string") return null;
|
|
7724
|
+
const s = steps(value);
|
|
7725
|
+
if (!s) return null;
|
|
7726
|
+
parts.push(s);
|
|
7727
|
+
}
|
|
7728
|
+
const all = parts.flat();
|
|
7729
|
+
const dropped = all.filter(isCheck);
|
|
7730
|
+
const kept = all.filter((s) => !isCheck(s));
|
|
7731
|
+
if (dropped.length === 0 || kept.length === 0) return null;
|
|
7732
|
+
const script = kept.join(" && ");
|
|
7733
|
+
const next = {};
|
|
7734
|
+
for (const [key, value] of Object.entries(scripts)) {
|
|
7735
|
+
next[key] = value;
|
|
7736
|
+
if (key === "build") next["build:bcms"] = script;
|
|
7737
|
+
}
|
|
7738
|
+
const indent = /^[ \t]+(?=")/m.exec(packageJson)?.[0] ?? " ";
|
|
7739
|
+
const eol = packageJson.includes("\r\n") ? "\r\n" : "\n";
|
|
7740
|
+
let content = JSON.stringify({ ...pkg, scripts: next }, null, indent);
|
|
7741
|
+
if (eol !== "\n") content = content.replace(/\n/g, eol);
|
|
7742
|
+
if (/\r?\n$/.test(packageJson)) content += eol;
|
|
7743
|
+
return { content, script, dropped };
|
|
7744
|
+
}
|
|
7745
|
+
|
|
7667
7746
|
// src/componentize.ts
|
|
7668
7747
|
import MagicString5 from "magic-string";
|
|
7669
7748
|
var SectionInvariantError = class extends Error {
|
|
@@ -8413,7 +8492,7 @@ var inSection = (section, path) => {
|
|
|
8413
8492
|
const target = rowless(path);
|
|
8414
8493
|
return fieldPaths(section).some((fieldPath) => rowless(fieldPath) === target);
|
|
8415
8494
|
};
|
|
8416
|
-
var fieldPaths = (section) => (section.fields ?? []).map((
|
|
8495
|
+
var fieldPaths = (section) => (section.fields ?? []).map((field2) => field2.path ?? `${section.groupKey}.${field2.key ?? ""}`);
|
|
8417
8496
|
function elementIndex(roots) {
|
|
8418
8497
|
const out = /* @__PURE__ */ new Map();
|
|
8419
8498
|
const visit = (node, chain) => {
|
|
@@ -8571,8 +8650,8 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved, pathP
|
|
|
8571
8650
|
const carriedDeclarations = carry.declarations.length > 0 ? `${carry.declarations.join("\n")}
|
|
8572
8651
|
` : "";
|
|
8573
8652
|
const copy = !snapshot ? "" : flat3 ? `const ${snapshot} = bcmsSectionPaths(page, bind, overrides, ${JSON.stringify(pathPrefix)}, ${JSON.stringify(pathMap)}, bindPaths);` : `const ${snapshot} = { ${key}: bcmsSection(page, bind, overrides, bindPaths) };`;
|
|
8574
|
-
const
|
|
8575
|
-
const declared = [copy,
|
|
8653
|
+
const field2 = bound > 0 ? `const ${fieldName} = ${fieldsName}({ blockId, bind, bindPaths, overrides }, ${JSON.stringify(pathPrefix)}${flat3 ? `, ${JSON.stringify(pathMap)}` : ""});` : "";
|
|
8654
|
+
const declared = [copy, field2].filter(Boolean).join("\n");
|
|
8576
8655
|
const styles = dialect === "astro" ? carriedStyles(content) : "";
|
|
8577
8656
|
if (dialect === "astro") {
|
|
8578
8657
|
return { free: [], duplicates: [], unconvertible: [], content: `---
|
|
@@ -8885,8 +8964,8 @@ function propPrefix(section) {
|
|
|
8885
8964
|
}
|
|
8886
8965
|
function exactPrefix(section) {
|
|
8887
8966
|
const prefixes = /* @__PURE__ */ new Set();
|
|
8888
|
-
for (const
|
|
8889
|
-
const { path, key } =
|
|
8967
|
+
for (const field2 of section.fields ?? []) {
|
|
8968
|
+
const { path, key } = field2;
|
|
8890
8969
|
if (typeof path !== "string" || typeof key !== "string") continue;
|
|
8891
8970
|
if (!path.endsWith(key)) return null;
|
|
8892
8971
|
prefixes.add(path.slice(0, path.length - key.length));
|
|
@@ -8898,11 +8977,11 @@ var foldKey = (remainder) => remainder.replace(/\[(\d+)\]/g, "_$1").replace(/[^a
|
|
|
8898
8977
|
function flatPrefix(section) {
|
|
8899
8978
|
const prefixes = /* @__PURE__ */ new Set();
|
|
8900
8979
|
let informative = false;
|
|
8901
|
-
for (const
|
|
8902
|
-
const { path, key } =
|
|
8980
|
+
for (const field2 of section.fields ?? []) {
|
|
8981
|
+
const { path, key } = field2;
|
|
8903
8982
|
if (typeof path !== "string" || typeof key !== "string") continue;
|
|
8904
8983
|
informative = true;
|
|
8905
|
-
const prefix = ["-", "_"].map((sep2) => `${section.groupKey}${sep2}`).find((candidate) => path.startsWith(candidate) && (typeof
|
|
8984
|
+
const prefix = ["-", "_"].map((sep2) => `${section.groupKey}${sep2}`).find((candidate) => path.startsWith(candidate) && (typeof field2.overrideKey === "string" || foldKey(path.slice(candidate.length)) === key));
|
|
8906
8985
|
if (!prefix) return null;
|
|
8907
8986
|
prefixes.add(prefix);
|
|
8908
8987
|
}
|
|
@@ -8911,15 +8990,15 @@ function flatPrefix(section) {
|
|
|
8911
8990
|
function flatPathMap(section, prefix) {
|
|
8912
8991
|
const scalars = {};
|
|
8913
8992
|
const tables = {};
|
|
8914
|
-
for (const
|
|
8915
|
-
const { path, key } =
|
|
8993
|
+
for (const field2 of section.fields ?? []) {
|
|
8994
|
+
const { path, key } = field2;
|
|
8916
8995
|
if (typeof path !== "string" || typeof key !== "string" || !path.startsWith(prefix)) continue;
|
|
8917
8996
|
const row = /^([^[]+)\[\d+\]\.(.+)$/.exec(path);
|
|
8918
8997
|
if (!row) {
|
|
8919
|
-
scalars[path] =
|
|
8998
|
+
scalars[path] = field2.overrideKey ?? key;
|
|
8920
8999
|
continue;
|
|
8921
9000
|
}
|
|
8922
|
-
const address = typeof
|
|
9001
|
+
const address = typeof field2.overrideKey === "string" ? /^([^[]+)\[\d+\]\.(.+)$/.exec(field2.overrideKey) : null;
|
|
8923
9002
|
if (!address) return null;
|
|
8924
9003
|
const table = tables[row[1]] ??= { key: address[1], columns: {} };
|
|
8925
9004
|
if (table.key !== address[1]) return null;
|
|
@@ -10784,7 +10863,7 @@ function styleEdits(before, after) {
|
|
|
10784
10863
|
// package.json
|
|
10785
10864
|
var package_default = {
|
|
10786
10865
|
name: "@bettercms-ai/convert",
|
|
10787
|
-
version: "0.
|
|
10866
|
+
version: "0.25.1",
|
|
10788
10867
|
type: "module",
|
|
10789
10868
|
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.",
|
|
10790
10869
|
bin: {
|
|
@@ -10817,7 +10896,7 @@ var package_default = {
|
|
|
10817
10896
|
dependencies: {
|
|
10818
10897
|
"@astrojs/compiler": "^2.13.1",
|
|
10819
10898
|
"@babel/parser": "^7.29.2",
|
|
10820
|
-
"@bettercms-ai/types": "^1.
|
|
10899
|
+
"@bettercms-ai/types": "^1.32.0",
|
|
10821
10900
|
"@vue/compiler-sfc": "^3.5.0",
|
|
10822
10901
|
"magic-string": "^0.30.21",
|
|
10823
10902
|
parse5: "^7.3.0",
|
|
@@ -10961,7 +11040,7 @@ async function readTree(root, verbose) {
|
|
|
10961
11040
|
const full = join2(dir, entry.name);
|
|
10962
11041
|
const rel = relative(root, full).split(sep).join("/");
|
|
10963
11042
|
if (entry.isDirectory()) {
|
|
10964
|
-
if (isSourceCandidate(`${rel}/x.html`)) await walk(full);
|
|
11043
|
+
if (isSourceCandidate(`${rel}/x.html`) && !existsSync(join2(full, ".git"))) await walk(full);
|
|
10965
11044
|
continue;
|
|
10966
11045
|
}
|
|
10967
11046
|
if (!entry.isFile() || !isSourceCandidate(rel)) continue;
|
|
@@ -11142,6 +11221,21 @@ async function verifyStyles(root, base, verbose) {
|
|
|
11142
11221
|
);
|
|
11143
11222
|
return 1;
|
|
11144
11223
|
}
|
|
11224
|
+
async function addBcmsBuild(root, dryRun) {
|
|
11225
|
+
let packageJson;
|
|
11226
|
+
try {
|
|
11227
|
+
packageJson = await readFile(join2(root, "package.json"), "utf-8");
|
|
11228
|
+
} catch {
|
|
11229
|
+
return;
|
|
11230
|
+
}
|
|
11231
|
+
const added = bcmsBuildScript(packageJson);
|
|
11232
|
+
if (!added) return;
|
|
11233
|
+
if (!dryRun) await writeFile(join2(root, "package.json"), added.content, "utf-8");
|
|
11234
|
+
process.stderr.write(
|
|
11235
|
+
`package.json: ${dryRun ? "would add" : "added"} "build:bcms": "${added.script}" \u2014 BetterCMS builds with it, without ${added.dropped.join(", ")}; "build" is unchanged.
|
|
11236
|
+
`
|
|
11237
|
+
);
|
|
11238
|
+
}
|
|
11145
11239
|
async function main() {
|
|
11146
11240
|
const args = parseArgs(process.argv.slice(2));
|
|
11147
11241
|
if (args.help) {
|
|
@@ -11173,6 +11267,7 @@ async function main() {
|
|
|
11173
11267
|
await writeFile(target, file.content, "utf-8");
|
|
11174
11268
|
}
|
|
11175
11269
|
}
|
|
11270
|
+
await addBcmsBuild(root2, args.dryRun);
|
|
11176
11271
|
if (args.receipt) await writeFile(resolve(args.receipt), `${JSON.stringify(receipt2, null, 2)}
|
|
11177
11272
|
`, "utf-8");
|
|
11178
11273
|
reportUpgrade(files2, receipt2, args.dryRun);
|
|
@@ -11200,12 +11295,14 @@ async function main() {
|
|
|
11200
11295
|
if (args.verbose) process.stderr.write(`read ${sources.length} candidate files under ${root}
|
|
11201
11296
|
`);
|
|
11202
11297
|
const written = async (files2) => {
|
|
11203
|
-
if (args.dryRun)
|
|
11204
|
-
|
|
11205
|
-
|
|
11206
|
-
|
|
11207
|
-
|
|
11298
|
+
if (!args.dryRun) {
|
|
11299
|
+
for (const file of files2) {
|
|
11300
|
+
const target = under(root, file.path);
|
|
11301
|
+
await mkdir(dirname(target), { recursive: true });
|
|
11302
|
+
await writeFile(target, file.content, "utf-8");
|
|
11303
|
+
}
|
|
11208
11304
|
}
|
|
11305
|
+
await addBcmsBuild(root, args.dryRun);
|
|
11209
11306
|
};
|
|
11210
11307
|
const saveReceipt = async (receipt2) => {
|
|
11211
11308
|
if (!args.receipt) return;
|