@bettercms-ai/convert 0.12.1 → 0.14.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 +21 -0
- package/dist/cli.js +42 -9
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +30 -4
- package/dist/index.js +41 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -33,9 +33,12 @@ interface BriefPath {
|
|
|
33
33
|
* what it said.
|
|
34
34
|
*/
|
|
35
35
|
scope?: "page" | "layout";
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
/**
|
|
37
|
+
* `data-bcms-kind`, as `kindOf` spells it: text | richtext | image | document. The props-lane
|
|
38
|
+
* members of the union (url, number, array) never appear on a brief path.
|
|
39
|
+
*/
|
|
40
|
+
kind: BindingKind;
|
|
41
|
+
/** The declared field type. It agrees with `kind` now; the injector reads it for LINK_TYPES. */
|
|
39
42
|
type: string;
|
|
40
43
|
/**
|
|
41
44
|
* The richtext value IS the element's whole inline subtree — the field's `config.inline`.
|
|
@@ -229,6 +232,12 @@ interface PendingFix {
|
|
|
229
232
|
/** The bytes at `line`, trimmed — so the agent can see it is looking at the right place. */
|
|
230
233
|
snippet: string;
|
|
231
234
|
action: FixAction;
|
|
235
|
+
/**
|
|
236
|
+
* `declare-richtext` only: WHICH container this is. A `document` is a whole Body — one element
|
|
237
|
+
* holds every block — and binding a paragraph of it instead publishes the entire article into
|
|
238
|
+
* that paragraph. The two are different instructions, not one with a longer value.
|
|
239
|
+
*/
|
|
240
|
+
kind?: "richtext" | "document";
|
|
232
241
|
/** One sentence, present whenever the action alone does not say enough. */
|
|
233
242
|
why?: string;
|
|
234
243
|
}
|
|
@@ -287,7 +296,14 @@ declare function routeCounts(pending: readonly PendingPath[]): Map<string, numbe
|
|
|
287
296
|
* is only `rewritten` when EVERY located occurrence of it was.
|
|
288
297
|
*/
|
|
289
298
|
|
|
290
|
-
type PendingReason =
|
|
299
|
+
type PendingReason =
|
|
300
|
+
/**
|
|
301
|
+
* A Body the codemod refuses to touch, and the only pending reason that is a DECISION rather
|
|
302
|
+
* than a failure. Its value is a whole Portable Text array and its `original` is only the first
|
|
303
|
+
* block's text, so locating it lands on one paragraph — and publish then writes the entire
|
|
304
|
+
* article into that paragraph, or refuses it outright. The fix names the wrapper to declare.
|
|
305
|
+
*/
|
|
306
|
+
"DOCUMENT_DECLARE_ONLY" | "NO_ORIGINAL" | "NOT_IN_SOURCE" | "DIALECT_UNSUPPORTED" | "IN_EXPRESSION" | "IN_SCRIPT_OR_COMMENT" | "IN_DATA_FILE" | "AMBIGUOUS_LITERAL" | "KIND_MISMATCH" | "SUBSTRING_ONLY"
|
|
291
307
|
/** An INLINE richtext value whose element holds a component, a `<slot>` or an expression. */
|
|
292
308
|
| "SUBTREE_NOT_HTML" | "PROP_TARGET_NOT_FOUND" | "PROP_DRILLED_DEEP" | "REPEATER_FIXED_LENGTH"
|
|
293
309
|
/** One array declaration, two loops over it: the row this leaf belongs to has two homes. */
|
|
@@ -429,6 +445,16 @@ interface FormsReceipt {
|
|
|
429
445
|
pending: PendingForm[];
|
|
430
446
|
/** Prose a human has to act on — a draft form still 403s every submission. */
|
|
431
447
|
notes?: string[];
|
|
448
|
+
/**
|
|
449
|
+
* Every form on the site after this run, and the file its `<form>` is in — wired now, or by an
|
|
450
|
+
* earlier run. The server matches the file to the component recorded as that file's owner, so
|
|
451
|
+
* the Forms tab can say which component renders each form. OPTIONAL and ADDITIVE: `wired` stays
|
|
452
|
+
* a number, and a receipt without this list still adds up.
|
|
453
|
+
*/
|
|
454
|
+
wiredForms?: {
|
|
455
|
+
id: string;
|
|
456
|
+
file: string;
|
|
457
|
+
}[];
|
|
432
458
|
}
|
|
433
459
|
/** Raised when the receipt does not account for every form. Never caught inside this package. */
|
|
434
460
|
declare class FormInvariantError extends Error {
|
package/dist/index.js
CHANGED
|
@@ -2560,7 +2560,11 @@ var IMAGE_SOURCES = {
|
|
|
2560
2560
|
video: ["poster"]
|
|
2561
2561
|
};
|
|
2562
2562
|
var isImageSource = (site) => site.where === "attr" && (IMAGE_SOURCES[site.tag.toLowerCase()] ?? []).includes(site.attr ?? "");
|
|
2563
|
-
var ownsSubtree = (kind, site, inline) =>
|
|
2563
|
+
var ownsSubtree = (kind, site, inline) => (
|
|
2564
|
+
// A `document` is the same claim at article scale: the element renders EVERY block of the Body,
|
|
2565
|
+
// so its children come from the stored value and nothing else may be bound inside it.
|
|
2566
|
+
(kind === "richtext" || kind === "document") && (inline === true ? !site.nonHtml : !(site.mixed && site.textRange !== null))
|
|
2567
|
+
);
|
|
2564
2568
|
var norm = (value) => decodeEntities(value.normalize("NFC")).replace(/\s+/g, " ").trim();
|
|
2565
2569
|
var BCMS_ATTRS = [
|
|
2566
2570
|
"data-bcms-field",
|
|
@@ -5369,7 +5373,7 @@ function actionFor(reason, input, file, inData) {
|
|
|
5369
5373
|
return chosen;
|
|
5370
5374
|
}
|
|
5371
5375
|
function reasonAction(reason, input, file, inData) {
|
|
5372
|
-
const richtext = input.kind === "richtext" || input.inline === true;
|
|
5376
|
+
const richtext = input.kind === "richtext" || input.kind === "document" || input.inline === true;
|
|
5373
5377
|
switch (reason) {
|
|
5374
5378
|
case "AMBIGUOUS_LITERAL":
|
|
5375
5379
|
if (input.routes > 1) {
|
|
@@ -5420,6 +5424,11 @@ function reasonAction(reason, input, file, inData) {
|
|
|
5420
5424
|
return { action: "wrap-span", why: "Only part of the element's text is this value \u2014 give it its own span." };
|
|
5421
5425
|
case "SUBTREE_NOT_HTML":
|
|
5422
5426
|
return { action: "declare-richtext", why: "The value is the element's whole subtree \u2014 bind it as richtext." };
|
|
5427
|
+
case "DOCUMENT_DECLARE_ONLY":
|
|
5428
|
+
return {
|
|
5429
|
+
action: "declare-richtext",
|
|
5430
|
+
why: 'This is a Body: bind the ONE element that renders every block of it \u2014 an article, a main or a div \u2014 with the richtext helper and data-bcms-kind="document". Never the paragraph holding its first block: that is where the whole article would be published.'
|
|
5431
|
+
};
|
|
5423
5432
|
default:
|
|
5424
5433
|
return { action: "manual", why: `${reason} \u2014 see the receipt's message for what blocked this path.` };
|
|
5425
5434
|
}
|
|
@@ -5436,7 +5445,10 @@ function fixFor(row, input) {
|
|
|
5436
5445
|
...place.col !== void 0 ? { col: place.col } : {},
|
|
5437
5446
|
snippet: place.snippet,
|
|
5438
5447
|
action,
|
|
5439
|
-
...why ? { why } : {}
|
|
5448
|
+
...why ? { why } : {},
|
|
5449
|
+
// Only `declare-richtext` carries it, and only to say WHICH container is meant: one element
|
|
5450
|
+
// for a rich-text value, the whole Body's wrapper for a document.
|
|
5451
|
+
...action === "declare-richtext" && (input.kind === "document" || input.kind === "richtext") ? { kind: input.kind } : {}
|
|
5440
5452
|
};
|
|
5441
5453
|
}
|
|
5442
5454
|
function routeCounts(pending) {
|
|
@@ -6521,11 +6533,11 @@ async function wireForms(briefIn, sources) {
|
|
|
6521
6533
|
}
|
|
6522
6534
|
if (attrOf(candidate.el, "data-bcms-form")) {
|
|
6523
6535
|
alreadyWired++;
|
|
6524
|
-
onSite.push(form);
|
|
6536
|
+
onSite.push({ form, file: candidate.file });
|
|
6525
6537
|
continue;
|
|
6526
6538
|
}
|
|
6527
6539
|
matched.push({ form, candidate });
|
|
6528
|
-
onSite.push(form);
|
|
6540
|
+
onSite.push({ form, file: candidate.file });
|
|
6529
6541
|
}
|
|
6530
6542
|
const contents = new Map(sources.map((s) => [s.path, s.content]));
|
|
6531
6543
|
const byFile = /* @__PURE__ */ new Map();
|
|
@@ -6543,7 +6555,7 @@ ${indent}${scriptFor(last.candidate.dialect)}`);
|
|
|
6543
6555
|
}
|
|
6544
6556
|
files.push({ path, operation: "modify", content: source.toString() });
|
|
6545
6557
|
}
|
|
6546
|
-
const notes = onSite.filter((
|
|
6558
|
+
const notes = onSite.filter(({ form }) => form.status === "draft").map(({ form }) => `publish form ${form.name} in the Forms tab \u2014 a draft form rejects every submission with 403.`);
|
|
6547
6559
|
return {
|
|
6548
6560
|
files,
|
|
6549
6561
|
receipt: {
|
|
@@ -6560,7 +6572,9 @@ ${indent}${scriptFor(last.candidate.dialect)}`);
|
|
|
6560
6572
|
wired: matched.length,
|
|
6561
6573
|
alreadyWired,
|
|
6562
6574
|
pending,
|
|
6563
|
-
...notes.length > 0 ? { notes } : {}
|
|
6575
|
+
...notes.length > 0 ? { notes } : {},
|
|
6576
|
+
// Always, even empty: a run where every form went pending must retire the files an earlier one named.
|
|
6577
|
+
wiredForms: onSite.map(({ form, file }) => ({ id: form.id, file }))
|
|
6564
6578
|
})
|
|
6565
6579
|
}
|
|
6566
6580
|
};
|
|
@@ -8787,10 +8801,28 @@ async function convertSources(briefIn, sources, options) {
|
|
|
8787
8801
|
}
|
|
8788
8802
|
pend2(target, miss.reason === "no-original" ? "NO_ORIGINAL" : "NOT_IN_SOURCE");
|
|
8789
8803
|
}
|
|
8804
|
+
const documents = /* @__PURE__ */ new Set();
|
|
8805
|
+
for (const [key, hit] of locatedByKey) {
|
|
8806
|
+
const target = targets.get(key);
|
|
8807
|
+
if (!target || target.kind !== "document") continue;
|
|
8808
|
+
documents.add(key);
|
|
8809
|
+
const byHand = declaredByHand(target, hit.files);
|
|
8810
|
+
if (byHand !== null) {
|
|
8811
|
+
declaredOnly++;
|
|
8812
|
+
declaredRow(target, false, byHand);
|
|
8813
|
+
continue;
|
|
8814
|
+
}
|
|
8815
|
+
pend2(
|
|
8816
|
+
target,
|
|
8817
|
+
"DOCUMENT_DECLARE_ONLY",
|
|
8818
|
+
hit.files[0],
|
|
8819
|
+
"A Body is bound by declaring the one element that renders every block of it, never the paragraph that happens to hold its first block."
|
|
8820
|
+
);
|
|
8821
|
+
}
|
|
8790
8822
|
const byFile = /* @__PURE__ */ new Map();
|
|
8791
8823
|
for (const [key, hit] of locatedByKey) {
|
|
8792
8824
|
const target = targets.get(key);
|
|
8793
|
-
if (!target) continue;
|
|
8825
|
+
if (!target || documents.has(key)) continue;
|
|
8794
8826
|
for (const file of hit.files) byFile.set(file, [...byFile.get(file) ?? [], target]);
|
|
8795
8827
|
}
|
|
8796
8828
|
const drill = {
|
|
@@ -8839,6 +8871,7 @@ async function convertSources(briefIn, sources, options) {
|
|
|
8839
8871
|
let rewritten = 0;
|
|
8840
8872
|
let alreadyDeclared = declaredOnly;
|
|
8841
8873
|
for (const [key, hit] of locatedByKey) {
|
|
8874
|
+
if (documents.has(key)) continue;
|
|
8842
8875
|
const target = targets.get(key);
|
|
8843
8876
|
const outcomes = hit.files.map((file) => results.get(file)?.get(target.key) ?? "NOT_IN_SOURCE");
|
|
8844
8877
|
const worst = outcomes.find((o) => o !== "rewritten" && o !== "declared");
|