@bettercms-ai/convert 0.15.0 → 0.16.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 +6 -0
- package/dist/cli.js +54 -6
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +40 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @bettercms-ai/convert
|
|
2
2
|
|
|
3
|
+
## 0.16.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 17aaccb: `--forms` breaks the ties that left ScrewFast's two forms `FORM_AMBIGUOUS`: a `<form>` two CMS forms both cover goes to the one it posts more fields of (a contact form is not the newsletter because it also posts `email`), and among several `<form>`s covering one CMS form the one posting nothing beyond its fields wins. For the tie nothing can break — two email-only forms, a footer newsletter and a "forgot password" dialog — the new `--pin "<form>=<file>"` names the file; the pending message says so.
|
|
8
|
+
|
|
3
9
|
## 0.15.0
|
|
4
10
|
|
|
5
11
|
### Minor Changes
|
package/dist/cli.js
CHANGED
|
@@ -6483,7 +6483,7 @@ function wire(source, candidate, form) {
|
|
|
6483
6483
|
source.appendLeft(el.inner.end - tail.length, `${tail || "\n"}${messageFor(dialect)}`);
|
|
6484
6484
|
}
|
|
6485
6485
|
}
|
|
6486
|
-
async function wireForms(briefIn, sources) {
|
|
6486
|
+
async function wireForms(briefIn, sources, options_ = {}) {
|
|
6487
6487
|
const brief = readBrief(briefIn);
|
|
6488
6488
|
const forms = brief.forms ?? [];
|
|
6489
6489
|
const candidates = [];
|
|
@@ -6521,7 +6521,33 @@ async function wireForms(briefIn, sources) {
|
|
|
6521
6521
|
}
|
|
6522
6522
|
const holds = (c, f) => f.fields.filter((field) => c.controls.has(field.key)).length;
|
|
6523
6523
|
const covers2 = (c, f) => f.fields.length > 0 && holds(c, f) === f.fields.length;
|
|
6524
|
+
const posts = (c) => new Set(c.controls.values()).size;
|
|
6525
|
+
const samePath = (a, b) => a.replace(/\\/g, "/").replace(/^\.\//, "") === b.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
6524
6526
|
const options = new Map(forms.map((f) => [f.id, candidates.filter((c) => covers2(c, f))]));
|
|
6527
|
+
const pinned = /* @__PURE__ */ new Map();
|
|
6528
|
+
for (const [ref, file] of Object.entries(options_.pins ?? {})) {
|
|
6529
|
+
const form = forms.find((f) => f.id === ref || f.name === ref);
|
|
6530
|
+
if (!form) throw new Error(`--pin ${ref}: the brief lists no form with that id or name.`);
|
|
6531
|
+
pinned.set(form.id, file);
|
|
6532
|
+
options.set(form.id, options.get(form.id).filter((c) => samePath(c.file, file)));
|
|
6533
|
+
for (const other of forms) {
|
|
6534
|
+
if (other.id !== form.id) options.set(other.id, options.get(other.id).filter((c) => !samePath(c.file, file)));
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6537
|
+
for (const c of candidates) {
|
|
6538
|
+
const claimants = forms.filter((f) => options.get(f.id).includes(c));
|
|
6539
|
+
if (claimants.length < 2) continue;
|
|
6540
|
+
const top = Math.max(...claimants.map((f) => holds(c, f)));
|
|
6541
|
+
const winners = claimants.filter((f) => holds(c, f) === top);
|
|
6542
|
+
if (winners.length !== 1) continue;
|
|
6543
|
+
for (const f of claimants) if (f !== winners[0]) options.set(f.id, options.get(f.id).filter((x) => x !== c));
|
|
6544
|
+
}
|
|
6545
|
+
for (const f of forms) {
|
|
6546
|
+
const list = options.get(f.id);
|
|
6547
|
+
if (list.length < 2) continue;
|
|
6548
|
+
const exact = list.filter((c) => posts(c) === f.fields.length);
|
|
6549
|
+
if (exact.length === 1) options.set(f.id, exact);
|
|
6550
|
+
}
|
|
6525
6551
|
const wanted = /* @__PURE__ */ new Map();
|
|
6526
6552
|
for (const list of options.values()) for (const c of list) wanted.set(c, (wanted.get(c) ?? 0) + 1);
|
|
6527
6553
|
const matched = [];
|
|
@@ -6532,7 +6558,12 @@ async function wireForms(briefIn, sources) {
|
|
|
6532
6558
|
for (const form of forms) {
|
|
6533
6559
|
const list = options.get(form.id) ?? [];
|
|
6534
6560
|
if (list.length > 1) {
|
|
6535
|
-
pend2(
|
|
6561
|
+
pend2(
|
|
6562
|
+
form,
|
|
6563
|
+
"FORM_AMBIGUOUS",
|
|
6564
|
+
void 0,
|
|
6565
|
+
`${list.length} forms post every field of "${form.name}": ${list.map((c) => c.file).join(", ")}. Name the right one with --pin "${form.name}=<file>".`
|
|
6566
|
+
);
|
|
6536
6567
|
continue;
|
|
6537
6568
|
}
|
|
6538
6569
|
if (list.length === 1 && (wanted.get(list[0]) ?? 0) > 1) {
|
|
@@ -6541,7 +6572,13 @@ async function wireForms(briefIn, sources) {
|
|
|
6541
6572
|
}
|
|
6542
6573
|
if (list.length === 0) {
|
|
6543
6574
|
const [best] = candidates.map((c) => ({ c, n: holds(c, form) })).filter((x) => x.n > 0).sort((a, b) => b.n - a.n);
|
|
6544
|
-
|
|
6575
|
+
const pin = pinned.get(form.id);
|
|
6576
|
+
if (pin) {
|
|
6577
|
+
pend2(form, "FORM_NOT_IN_SOURCE", pin, `the pinned file has no <form> posting ${form.fields.map((f) => f.key).join(", ")}.`);
|
|
6578
|
+
} else if (best && best.n === form.fields.length) {
|
|
6579
|
+
const owner = forms.find((f) => f !== form && options.get(f.id).includes(best.c));
|
|
6580
|
+
pend2(form, "FORM_NOT_IN_SOURCE", best.c.file, `the only <form> posting ${form.fields.map((f) => f.key).join(", ")} is "${owner?.name ?? "another form"}"'s.`);
|
|
6581
|
+
} else if (best) {
|
|
6545
6582
|
const missing = form.fields.filter((f) => !best.c.controls.has(f.key)).map((f) => f.key);
|
|
6546
6583
|
pend2(form, "FIELD_UNMATCHED", best.c.file, `no control posts ${missing.join(", ")} in the <form> that posts its other fields.`);
|
|
6547
6584
|
} else if (unreadable[0]) {
|
|
@@ -9689,7 +9726,7 @@ function emitStubs2(dialects, slugs, contents, out) {
|
|
|
9689
9726
|
// package.json
|
|
9690
9727
|
var package_default = {
|
|
9691
9728
|
name: "@bettercms-ai/convert",
|
|
9692
|
-
version: "0.
|
|
9729
|
+
version: "0.16.0",
|
|
9693
9730
|
type: "module",
|
|
9694
9731
|
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.",
|
|
9695
9732
|
bin: {
|
|
@@ -9750,6 +9787,7 @@ function parseArgs(argv) {
|
|
|
9750
9787
|
brief: void 0,
|
|
9751
9788
|
componentize: false,
|
|
9752
9789
|
forms: false,
|
|
9790
|
+
pins: {},
|
|
9753
9791
|
xray: void 0,
|
|
9754
9792
|
plan: void 0,
|
|
9755
9793
|
copy: "bind",
|
|
@@ -9766,7 +9804,11 @@ function parseArgs(argv) {
|
|
|
9766
9804
|
if (arg === "--brief") args.brief = argv[++i];
|
|
9767
9805
|
else if (arg === "--componentize") args.componentize = true;
|
|
9768
9806
|
else if (arg === "--forms") args.forms = true;
|
|
9769
|
-
else if (arg === "--
|
|
9807
|
+
else if (arg === "--pin") {
|
|
9808
|
+
const at = (argv[++i] ?? "").indexOf("=");
|
|
9809
|
+
if (at < 1) throw new CliError(`--pin takes <form>=<file>, e.g. --pin "Newsletter=src/components/FooterSection.astro". See --help.`);
|
|
9810
|
+
args.pins[argv[i].slice(0, at)] = argv[i].slice(at + 1);
|
|
9811
|
+
} else if (arg === "--xray") args.xray = argv[++i];
|
|
9770
9812
|
else if (arg === "--plan") args.plan = argv[++i];
|
|
9771
9813
|
else if (arg === "--copy") {
|
|
9772
9814
|
const mode = argv[++i];
|
|
@@ -9802,6 +9844,9 @@ var HELP = `bettercms-convert ${VERSION}
|
|
|
9802
9844
|
endpoint, the id, a marker per field and the submit script. A form
|
|
9803
9845
|
the brief lists as a DRAFT is wired and receipted: publish it in the
|
|
9804
9846
|
Forms tab, or it rejects every submission with a 403.
|
|
9847
|
+
--pin <form>=<file> With --forms: the file a form's <form> is in, when the run reports
|
|
9848
|
+
FORM_AMBIGUOUS (two files post exactly its fields). Form by id or
|
|
9849
|
+
name. Repeatable.
|
|
9805
9850
|
--xray <dist-dir> Measure a LOCAL build instead of converting: how much of each page's
|
|
9806
9851
|
visible copy no binding owns, and the biggest buckets of it. Run it
|
|
9807
9852
|
after a build to see what the next conversion still has to reach,
|
|
@@ -10024,8 +10069,11 @@ async function main() {
|
|
|
10024
10069
|
reportSections(files2, receipt2, args.dryRun);
|
|
10025
10070
|
return args.strict && receipt2.sections.pending.length > 0 ? 2 : 0;
|
|
10026
10071
|
}
|
|
10072
|
+
if (Object.keys(args.pins).length > 0 && !args.forms) {
|
|
10073
|
+
throw new CliError("--pin only means something with --forms. See --help.");
|
|
10074
|
+
}
|
|
10027
10075
|
if (args.forms) {
|
|
10028
|
-
const { files: files2, receipt: receipt2 } = await wireForms(brief, sources);
|
|
10076
|
+
const { files: files2, receipt: receipt2 } = await wireForms(brief, sources, { pins: args.pins });
|
|
10029
10077
|
await written(files2);
|
|
10030
10078
|
await saveReceipt(receipt2);
|
|
10031
10079
|
reportForms(files2, receipt2, args.dryRun);
|