@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/dist/index.d.ts CHANGED
@@ -990,7 +990,11 @@ declare const FORM_SCRIPT = "<script>\nfunction bcmsPhone(cc,raw,trunk){var d=St
990
990
  * `ConversionReceipt` with an empty path arithmetic and the lane's own account under `forms`, so
991
991
  * `submit_conversion_receipt` takes it unchanged — the server's invariant reads `paths` only.
992
992
  */
993
- declare function wireForms(briefIn: Brief, sources: SourceFile[]): Promise<{
993
+ interface WireFormsOptions {
994
+ /** Form id or name → the file its `<form>` is in. The operator's answer to FORM_AMBIGUOUS. */
995
+ pins?: Record<string, string>;
996
+ }
997
+ declare function wireForms(briefIn: Brief, sources: SourceFile[], options_?: WireFormsOptions): Promise<{
994
998
  files: PlanFile[];
995
999
  receipt: ConversionReceipt;
996
1000
  }>;
package/dist/index.js CHANGED
@@ -6493,7 +6493,7 @@ function wire(source, candidate, form) {
6493
6493
  source.appendLeft(el.inner.end - tail.length, `${tail || "\n"}${messageFor(dialect)}`);
6494
6494
  }
6495
6495
  }
6496
- async function wireForms(briefIn, sources) {
6496
+ async function wireForms(briefIn, sources, options_ = {}) {
6497
6497
  const brief = readBrief(briefIn);
6498
6498
  const forms = brief.forms ?? [];
6499
6499
  const candidates = [];
@@ -6531,7 +6531,33 @@ async function wireForms(briefIn, sources) {
6531
6531
  }
6532
6532
  const holds = (c, f) => f.fields.filter((field) => c.controls.has(field.key)).length;
6533
6533
  const covers2 = (c, f) => f.fields.length > 0 && holds(c, f) === f.fields.length;
6534
+ const posts = (c) => new Set(c.controls.values()).size;
6535
+ const samePath = (a, b) => a.replace(/\\/g, "/").replace(/^\.\//, "") === b.replace(/\\/g, "/").replace(/^\.\//, "");
6534
6536
  const options = new Map(forms.map((f) => [f.id, candidates.filter((c) => covers2(c, f))]));
6537
+ const pinned = /* @__PURE__ */ new Map();
6538
+ for (const [ref, file] of Object.entries(options_.pins ?? {})) {
6539
+ const form = forms.find((f) => f.id === ref || f.name === ref);
6540
+ if (!form) throw new Error(`--pin ${ref}: the brief lists no form with that id or name.`);
6541
+ pinned.set(form.id, file);
6542
+ options.set(form.id, options.get(form.id).filter((c) => samePath(c.file, file)));
6543
+ for (const other of forms) {
6544
+ if (other.id !== form.id) options.set(other.id, options.get(other.id).filter((c) => !samePath(c.file, file)));
6545
+ }
6546
+ }
6547
+ for (const c of candidates) {
6548
+ const claimants = forms.filter((f) => options.get(f.id).includes(c));
6549
+ if (claimants.length < 2) continue;
6550
+ const top = Math.max(...claimants.map((f) => holds(c, f)));
6551
+ const winners = claimants.filter((f) => holds(c, f) === top);
6552
+ if (winners.length !== 1) continue;
6553
+ for (const f of claimants) if (f !== winners[0]) options.set(f.id, options.get(f.id).filter((x) => x !== c));
6554
+ }
6555
+ for (const f of forms) {
6556
+ const list = options.get(f.id);
6557
+ if (list.length < 2) continue;
6558
+ const exact = list.filter((c) => posts(c) === f.fields.length);
6559
+ if (exact.length === 1) options.set(f.id, exact);
6560
+ }
6535
6561
  const wanted = /* @__PURE__ */ new Map();
6536
6562
  for (const list of options.values()) for (const c of list) wanted.set(c, (wanted.get(c) ?? 0) + 1);
6537
6563
  const matched = [];
@@ -6542,7 +6568,12 @@ async function wireForms(briefIn, sources) {
6542
6568
  for (const form of forms) {
6543
6569
  const list = options.get(form.id) ?? [];
6544
6570
  if (list.length > 1) {
6545
- pend2(form, "FORM_AMBIGUOUS", void 0, `${list.length} forms post every field of "${form.name}": ${list.map((c) => c.file).join(", ")}.`);
6571
+ pend2(
6572
+ form,
6573
+ "FORM_AMBIGUOUS",
6574
+ void 0,
6575
+ `${list.length} forms post every field of "${form.name}": ${list.map((c) => c.file).join(", ")}. Name the right one with --pin "${form.name}=<file>".`
6576
+ );
6546
6577
  continue;
6547
6578
  }
6548
6579
  if (list.length === 1 && (wanted.get(list[0]) ?? 0) > 1) {
@@ -6551,7 +6582,13 @@ async function wireForms(briefIn, sources) {
6551
6582
  }
6552
6583
  if (list.length === 0) {
6553
6584
  const [best] = candidates.map((c) => ({ c, n: holds(c, form) })).filter((x) => x.n > 0).sort((a, b) => b.n - a.n);
6554
- if (best) {
6585
+ const pin = pinned.get(form.id);
6586
+ if (pin) {
6587
+ pend2(form, "FORM_NOT_IN_SOURCE", pin, `the pinned file has no <form> posting ${form.fields.map((f) => f.key).join(", ")}.`);
6588
+ } else if (best && best.n === form.fields.length) {
6589
+ const owner = forms.find((f) => f !== form && options.get(f.id).includes(best.c));
6590
+ 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.`);
6591
+ } else if (best) {
6555
6592
  const missing = form.fields.filter((f) => !best.c.controls.has(f.key)).map((f) => f.key);
6556
6593
  pend2(form, "FIELD_UNMATCHED", best.c.file, `no control posts ${missing.join(", ")} in the <form> that posts its other fields.`);
6557
6594
  } else if (unreadable[0]) {