@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 CHANGED
@@ -1,5 +1,26 @@
1
1
  # @bettercms-ai/convert
2
2
 
3
+ ## 0.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - df570ef: Receipt and brief shapes a consumer reads:
8
+ - `BriefPath.kind` is typed `BindingKind` rather than `BindingKind | ""` — the brief always names a kind, and the empty case only forced callers to handle a value it never sends.
9
+ - New `PendingReason` `"DOCUMENT_DECLARE_ONLY"`: a `document` path is never located and never rewritten, because its `original` is only the body's FIRST block and declaring the paragraph that renders it is what publish refuses. The row's fix names the wrapper to declare instead.
10
+ - `PendingFix.kind` (`"document" | "richtext"`, optional) accompanies a `declare-richtext` action to say WHICH container is meant.
11
+
12
+ ## 0.13.0
13
+
14
+ ### Minor Changes
15
+
16
+ - 0b05aa6: The forms receipt names the file each wired form is in
17
+
18
+ `receipt.forms.wiredForms` lists `{ id, file }` for every form on the site after a
19
+ `--forms` run — wired now or by an earlier one. The server matches the file to the
20
+ component recorded as its owner, so the Forms tab and a component's form prop can say
21
+ which component renders each form. Additive: `wired` is still a number, and a server
22
+ that has never heard of the list accepts the receipt unchanged.
23
+
3
24
  ## 0.12.1
4
25
 
5
26
  ### Patch Changes
package/dist/cli.js CHANGED
@@ -2562,7 +2562,11 @@ var IMAGE_SOURCES = {
2562
2562
  video: ["poster"]
2563
2563
  };
2564
2564
  var isImageSource = (site) => site.where === "attr" && (IMAGE_SOURCES[site.tag.toLowerCase()] ?? []).includes(site.attr ?? "");
2565
- var ownsSubtree = (kind, site, inline) => kind === "richtext" && (inline === true ? !site.nonHtml : !(site.mixed && site.textRange !== null));
2565
+ var ownsSubtree = (kind, site, inline) => (
2566
+ // A `document` is the same claim at article scale: the element renders EVERY block of the Body,
2567
+ // so its children come from the stored value and nothing else may be bound inside it.
2568
+ (kind === "richtext" || kind === "document") && (inline === true ? !site.nonHtml : !(site.mixed && site.textRange !== null))
2569
+ );
2566
2570
  var norm = (value) => decodeEntities(value.normalize("NFC")).replace(/\s+/g, " ").trim();
2567
2571
  var BCMS_ATTRS = [
2568
2572
  "data-bcms-field",
@@ -5363,7 +5367,7 @@ function actionFor(reason, input, file, inData) {
5363
5367
  return chosen;
5364
5368
  }
5365
5369
  function reasonAction(reason, input, file, inData) {
5366
- const richtext = input.kind === "richtext" || input.inline === true;
5370
+ const richtext = input.kind === "richtext" || input.kind === "document" || input.inline === true;
5367
5371
  switch (reason) {
5368
5372
  case "AMBIGUOUS_LITERAL":
5369
5373
  if (input.routes > 1) {
@@ -5414,6 +5418,11 @@ function reasonAction(reason, input, file, inData) {
5414
5418
  return { action: "wrap-span", why: "Only part of the element's text is this value \u2014 give it its own span." };
5415
5419
  case "SUBTREE_NOT_HTML":
5416
5420
  return { action: "declare-richtext", why: "The value is the element's whole subtree \u2014 bind it as richtext." };
5421
+ case "DOCUMENT_DECLARE_ONLY":
5422
+ return {
5423
+ action: "declare-richtext",
5424
+ 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.'
5425
+ };
5417
5426
  default:
5418
5427
  return { action: "manual", why: `${reason} \u2014 see the receipt's message for what blocked this path.` };
5419
5428
  }
@@ -5430,7 +5439,10 @@ function fixFor(row, input) {
5430
5439
  ...place.col !== void 0 ? { col: place.col } : {},
5431
5440
  snippet: place.snippet,
5432
5441
  action,
5433
- ...why ? { why } : {}
5442
+ ...why ? { why } : {},
5443
+ // Only `declare-richtext` carries it, and only to say WHICH container is meant: one element
5444
+ // for a rich-text value, the whole Body's wrapper for a document.
5445
+ ...action === "declare-richtext" && (input.kind === "document" || input.kind === "richtext") ? { kind: input.kind } : {}
5434
5446
  };
5435
5447
  }
5436
5448
  function routeCounts(pending) {
@@ -6511,11 +6523,11 @@ async function wireForms(briefIn, sources) {
6511
6523
  }
6512
6524
  if (attrOf(candidate.el, "data-bcms-form")) {
6513
6525
  alreadyWired++;
6514
- onSite.push(form);
6526
+ onSite.push({ form, file: candidate.file });
6515
6527
  continue;
6516
6528
  }
6517
6529
  matched.push({ form, candidate });
6518
- onSite.push(form);
6530
+ onSite.push({ form, file: candidate.file });
6519
6531
  }
6520
6532
  const contents = new Map(sources.map((s) => [s.path, s.content]));
6521
6533
  const byFile = /* @__PURE__ */ new Map();
@@ -6533,7 +6545,7 @@ ${indent}${scriptFor(last.candidate.dialect)}`);
6533
6545
  }
6534
6546
  files.push({ path, operation: "modify", content: source.toString() });
6535
6547
  }
6536
- const notes = onSite.filter((f) => f.status === "draft").map((f) => `publish form ${f.name} in the Forms tab \u2014 a draft form rejects every submission with 403.`);
6548
+ 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.`);
6537
6549
  return {
6538
6550
  files,
6539
6551
  receipt: {
@@ -6550,7 +6562,9 @@ ${indent}${scriptFor(last.candidate.dialect)}`);
6550
6562
  wired: matched.length,
6551
6563
  alreadyWired,
6552
6564
  pending,
6553
- ...notes.length > 0 ? { notes } : {}
6565
+ ...notes.length > 0 ? { notes } : {},
6566
+ // Always, even empty: a run where every form went pending must retire the files an earlier one named.
6567
+ wiredForms: onSite.map(({ form, file }) => ({ id: form.id, file }))
6554
6568
  })
6555
6569
  }
6556
6570
  };
@@ -8769,10 +8783,28 @@ async function convertSources(briefIn, sources, options) {
8769
8783
  }
8770
8784
  pend2(target, miss.reason === "no-original" ? "NO_ORIGINAL" : "NOT_IN_SOURCE");
8771
8785
  }
8786
+ const documents = /* @__PURE__ */ new Set();
8787
+ for (const [key, hit] of locatedByKey) {
8788
+ const target = targets.get(key);
8789
+ if (!target || target.kind !== "document") continue;
8790
+ documents.add(key);
8791
+ const byHand = declaredByHand(target, hit.files);
8792
+ if (byHand !== null) {
8793
+ declaredOnly++;
8794
+ declaredRow(target, false, byHand);
8795
+ continue;
8796
+ }
8797
+ pend2(
8798
+ target,
8799
+ "DOCUMENT_DECLARE_ONLY",
8800
+ hit.files[0],
8801
+ "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."
8802
+ );
8803
+ }
8772
8804
  const byFile = /* @__PURE__ */ new Map();
8773
8805
  for (const [key, hit] of locatedByKey) {
8774
8806
  const target = targets.get(key);
8775
- if (!target) continue;
8807
+ if (!target || documents.has(key)) continue;
8776
8808
  for (const file of hit.files) byFile.set(file, [...byFile.get(file) ?? [], target]);
8777
8809
  }
8778
8810
  const drill = {
@@ -8821,6 +8853,7 @@ async function convertSources(briefIn, sources, options) {
8821
8853
  let rewritten = 0;
8822
8854
  let alreadyDeclared = declaredOnly;
8823
8855
  for (const [key, hit] of locatedByKey) {
8856
+ if (documents.has(key)) continue;
8824
8857
  const target = targets.get(key);
8825
8858
  const outcomes = hit.files.map((file) => results.get(file)?.get(target.key) ?? "NOT_IN_SOURCE");
8826
8859
  const worst = outcomes.find((o) => o !== "rewritten" && o !== "declared");
@@ -9605,7 +9638,7 @@ function emitStubs2(dialects, slugs, contents, out) {
9605
9638
  // package.json
9606
9639
  var package_default = {
9607
9640
  name: "@bettercms-ai/convert",
9608
- version: "0.12.1",
9641
+ version: "0.14.0",
9609
9642
  type: "module",
9610
9643
  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.",
9611
9644
  bin: {