@bettercms-ai/convert 0.19.0 → 0.20.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 +14 -0
- package/README.md +33 -3
- package/dist/cli.js +205 -17
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +204 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -428,8 +428,12 @@ interface ConversionReceipt {
|
|
|
428
428
|
* `ACTION_IS_EXPRESSION` the `<form>`'s `action` is code; replacing it would rewrite a program.
|
|
429
429
|
* `DIALECT_UNSUPPORTED` the `<form>` is in a svelte/vue file, which this lane does not write.
|
|
430
430
|
* `PARSE_ERROR` the file holding it did not parse.
|
|
431
|
+
* `FORM_NAME_AMBIGUOUS` an Astro form is wired by name, and two CMS forms share this one's.
|
|
432
|
+
* `HELPER_CONFLICT` `src/bcms-forms.ts` exists with bytes this package did not write.
|
|
433
|
+
* `FORM_WIRED_TO_OTHER_NAME` an Astro `<form>` is already wired by another name. Never rewritten:
|
|
434
|
+
* the name is what every environment building the repo shares.
|
|
431
435
|
*/
|
|
432
|
-
type FormPendingReason = "FORM_NOT_IN_SOURCE" | "FORM_AMBIGUOUS" | "FIELD_UNMATCHED" | "ACTION_IS_EXPRESSION" | "DIALECT_UNSUPPORTED" | "PARSE_ERROR";
|
|
436
|
+
type FormPendingReason = "FORM_NOT_IN_SOURCE" | "FORM_AMBIGUOUS" | "FIELD_UNMATCHED" | "ACTION_IS_EXPRESSION" | "DIALECT_UNSUPPORTED" | "PARSE_ERROR" | "FORM_NAME_AMBIGUOUS" | "HELPER_CONFLICT" | "FORM_WIRED_TO_OTHER_NAME";
|
|
433
437
|
interface PendingForm {
|
|
434
438
|
id: string;
|
|
435
439
|
name: string;
|
package/dist/index.js
CHANGED
|
@@ -6443,6 +6443,114 @@ function readPlan(value) {
|
|
|
6443
6443
|
// src/forms.ts
|
|
6444
6444
|
import MagicString4 from "magic-string";
|
|
6445
6445
|
var WIRABLE = ["html", "astro", "jsx"];
|
|
6446
|
+
var BY_NAME = ["astro"];
|
|
6447
|
+
var FORMS_HELPER_PATH = "src/bcms-forms.ts";
|
|
6448
|
+
var FORMS_HELPER_VERSION = 2;
|
|
6449
|
+
var FORMS_HELPER_HEADER = /^\/\/ @bettercms-ai\/convert forms-helper v(\d+)\r?\n/;
|
|
6450
|
+
var formsHelperVersion = (content) => {
|
|
6451
|
+
const m = FORMS_HELPER_HEADER.exec(content);
|
|
6452
|
+
return m ? Number(m[1]) : null;
|
|
6453
|
+
};
|
|
6454
|
+
var FORMS_HELPER_SOURCE = `// @bettercms-ai/convert forms-helper v${FORMS_HELPER_VERSION}
|
|
6455
|
+
// Written by @bettercms-ai/convert; a re-run rewrites it, so do not edit it.
|
|
6456
|
+
// Resolves a BetterCMS form by NAME at build, for the environment that is building this site: the id
|
|
6457
|
+
// from bcms-content.json (written into the tree before the build) and the endpoint from BCMS_API_URL.
|
|
6458
|
+
// The source names no host and no id, so every BetterCMS environment builds its own wiring.
|
|
6459
|
+
// A form it cannot resolve renders unwired (no action, no method, no id) with one warning line, and
|
|
6460
|
+
// the build goes on. Form NAMES are the contract between environments: rename a form on every one.
|
|
6461
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6462
|
+
|
|
6463
|
+
type SnapshotForm = { id: string; name: string; projectId?: string | null; ignoredAt?: string | null };
|
|
6464
|
+
type Snapshot = { projectId: string | null; forms: SnapshotForm[] };
|
|
6465
|
+
type Wiring = { id: string; action: string };
|
|
6466
|
+
|
|
6467
|
+
let snapshot: Snapshot | null | undefined;
|
|
6468
|
+
const resolved = new Map<string, Wiring | undefined>();
|
|
6469
|
+
|
|
6470
|
+
const env = (key: string): string | undefined => process.env[key] || process.env[\`PUBLIC_\${key}\`] || undefined;
|
|
6471
|
+
|
|
6472
|
+
const isForm = (value: unknown): value is SnapshotForm =>
|
|
6473
|
+
typeof value === "object" &&
|
|
6474
|
+
value !== null &&
|
|
6475
|
+
typeof (value as { id?: unknown }).id === "string" &&
|
|
6476
|
+
typeof (value as { name?: unknown }).name === "string";
|
|
6477
|
+
|
|
6478
|
+
function readSnapshot(): Snapshot | null {
|
|
6479
|
+
for (const file of ["bcms-content.json", "bcms-content/forms.json"]) {
|
|
6480
|
+
if (!existsSync(file)) continue;
|
|
6481
|
+
try {
|
|
6482
|
+
const data: unknown = JSON.parse(readFileSync(file, "utf8"));
|
|
6483
|
+
const record = (Array.isArray(data) ? { forms: data } : (data ?? {})) as { forms?: unknown; projectId?: unknown };
|
|
6484
|
+
const forms: unknown[] = Array.isArray(record.forms) ? record.forms : [];
|
|
6485
|
+
if (!forms.every(isForm)) throw new Error("a form entry has no id or name");
|
|
6486
|
+
return { projectId: typeof record.projectId === "string" ? record.projectId : null, forms };
|
|
6487
|
+
} catch (error) {
|
|
6488
|
+
// Read once per build, so this is the one line however many forms the site has.
|
|
6489
|
+
console.warn(\`[bcms-forms] FORMS_SNAPSHOT_UNREADABLE: \${file} could not be read (\${error instanceof Error ? error.message : String(error)}); every form renders unwired.\`);
|
|
6490
|
+
return null;
|
|
6491
|
+
}
|
|
6492
|
+
}
|
|
6493
|
+
return null;
|
|
6494
|
+
}
|
|
6495
|
+
|
|
6496
|
+
function unwired(code: string, name: string, why: string): undefined {
|
|
6497
|
+
console.warn(\`[bcms-forms] \${code}: form "\${name}" renders unwired; \${why}.\`);
|
|
6498
|
+
return undefined;
|
|
6499
|
+
}
|
|
6500
|
+
|
|
6501
|
+
function resolve(name: string): Wiring | undefined {
|
|
6502
|
+
if (snapshot === undefined) snapshot = readSnapshot();
|
|
6503
|
+
// No snapshot (a local dev build), or one it could not read: the form renders unwired, as before.
|
|
6504
|
+
if (snapshot === null) return undefined;
|
|
6505
|
+
const project = env("BCMS_PROJECT_ID") ?? snapshot.projectId;
|
|
6506
|
+
// The forms the conversion brief names from: this project's own, ignored ones left out.
|
|
6507
|
+
const own = snapshot.forms.filter((f) => !f.ignoredAt && (!project || f.projectId === project));
|
|
6508
|
+
const hits = own.filter((f) => f.name === name);
|
|
6509
|
+
const form = hits.length === 1 ? hits[0] : undefined;
|
|
6510
|
+
if (!form && hits.length > 1) {
|
|
6511
|
+
return unwired("BCMS_FORM_NAME_AMBIGUOUS", name, \`\${hits.length} forms in this project share that name; rename one in the Forms tab\`);
|
|
6512
|
+
}
|
|
6513
|
+
if (!form) {
|
|
6514
|
+
return unwired(
|
|
6515
|
+
"BCMS_FORM_NOT_FOUND",
|
|
6516
|
+
name,
|
|
6517
|
+
own.length === 0
|
|
6518
|
+
? "this project has no forms yet; the release of this build derives them"
|
|
6519
|
+
: "no form in this project has that name; a name is shared by every environment, so rename the form on each one",
|
|
6520
|
+
);
|
|
6521
|
+
}
|
|
6522
|
+
const api = env("BCMS_API_URL");
|
|
6523
|
+
if (!api) return unwired("BCMS_FORM_API_URL_MISSING", name, "BCMS_API_URL is not set, so it has nowhere to post");
|
|
6524
|
+
return { id: form.id, action: \`\${api.replace(/\\/$/, "")}/api/v1/forms/public/\${form.id}/submissions\` };
|
|
6525
|
+
}
|
|
6526
|
+
|
|
6527
|
+
/** Resolved once per name, so a form's three attributes print one warning, not three. */
|
|
6528
|
+
function wiring(name: string): Wiring | undefined {
|
|
6529
|
+
if (!resolved.has(name)) resolved.set(name, resolve(name));
|
|
6530
|
+
return resolved.get(name);
|
|
6531
|
+
}
|
|
6532
|
+
|
|
6533
|
+
export const bcmsFormId = (name: string): string | undefined => wiring(name)?.id;
|
|
6534
|
+
|
|
6535
|
+
export const bcmsFormAction = (name: string): string | undefined => wiring(name)?.action;
|
|
6536
|
+
|
|
6537
|
+
/** "post" only for a form that resolved: an unwired form keeps no method, like its action and id. */
|
|
6538
|
+
export const bcmsFormMethod = (name: string): "post" | undefined => (wiring(name) ? "post" : undefined);
|
|
6539
|
+
`;
|
|
6540
|
+
var idCall = (form) => `bcmsFormId(${JSON.stringify(form.name)})`;
|
|
6541
|
+
var actionCall = (form) => `bcmsFormAction(${JSON.stringify(form.name)})`;
|
|
6542
|
+
var methodCall = (form) => `bcmsFormMethod(${JSON.stringify(form.name)})`;
|
|
6543
|
+
function calledName(code, fn) {
|
|
6544
|
+
const m = code === null ? null : new RegExp(`^${fn}\\s*\\(\\s*(["'\`])((?:\\\\.|(?!\\1)[^\\\\])*)\\1\\s*,?\\s*\\)$`).exec(code);
|
|
6545
|
+
if (!m) return null;
|
|
6546
|
+
const [, quote, raw] = m;
|
|
6547
|
+
if (quote === "`" && raw.includes("${")) return null;
|
|
6548
|
+
try {
|
|
6549
|
+
return JSON.parse(`"${raw.replace(/\\(['`])/g, "$1").replace(/(?<!\\)"/g, '\\"')}"`);
|
|
6550
|
+
} catch {
|
|
6551
|
+
return null;
|
|
6552
|
+
}
|
|
6553
|
+
}
|
|
6446
6554
|
var CONTROLS = /* @__PURE__ */ new Set(["input", "select", "textarea"]);
|
|
6447
6555
|
var FORM_SCRIPT = `<script>
|
|
6448
6556
|
function bcmsPhone(cc,raw,trunk){var d=String(raw||'').trim();if(!d)return '';if(d.charAt(0)==='+'){var i=d.replace(/D/g,'');return i?'+'+i:'';}if(!cc)return d;var n=d.replace(/D/g,'');if(trunk&&n.charAt(0)==='0')n=n.slice(1);return n?'+'+cc+n:'';}
|
|
@@ -6504,6 +6612,10 @@ function* elementsIn(nodes) {
|
|
|
6504
6612
|
var childElements = (node) => node.children.flatMap(
|
|
6505
6613
|
(child) => child.type === "element" ? [child] : child.type === "expr" ? childElements(child) : []
|
|
6506
6614
|
);
|
|
6615
|
+
function exprOf(c, name) {
|
|
6616
|
+
const range = attrOf(c.el, name)?.expressionRange;
|
|
6617
|
+
return range ? c.content.slice(range.start + 1, range.end - 1).trim() : null;
|
|
6618
|
+
}
|
|
6507
6619
|
var LANDMARK_TAGS = /* @__PURE__ */ new Set(["footer", "header", "nav", "main", "aside", "dialog"]);
|
|
6508
6620
|
function landmarkOf(chain) {
|
|
6509
6621
|
for (let i = chain.length - 1; i >= 0; i--) {
|
|
@@ -6525,7 +6637,7 @@ function* formsIn(nodes, chain = []) {
|
|
|
6525
6637
|
}
|
|
6526
6638
|
}
|
|
6527
6639
|
var isComponentTag = (dialect, tag) => dialect !== "html" && /^[A-Z]/.test(tag);
|
|
6528
|
-
function candidateOf(file, dialect, form, imported, landmark, content) {
|
|
6640
|
+
function candidateOf(file, dialect, form, imported, landmark, content, importAt) {
|
|
6529
6641
|
const controls = /* @__PURE__ */ new Map();
|
|
6530
6642
|
const wrapper = /* @__PURE__ */ new Map();
|
|
6531
6643
|
const foreign = /* @__PURE__ */ new Set();
|
|
@@ -6566,7 +6678,7 @@ function candidateOf(file, dialect, form, imported, landmark, content) {
|
|
|
6566
6678
|
content.slice(attr.expressionRange.start, attr.expressionRange.end).replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'|`(?:[^`\\$]|\\.|\$(?!\{))*`/g, "")
|
|
6567
6679
|
);
|
|
6568
6680
|
const dynamic = all.some((control) => !foreign.has(control) && computed(attrOf(control, "name")));
|
|
6569
|
-
return { file, dialect, el: form, controls, wrapper, foreign, landmark, dynamic };
|
|
6681
|
+
return { file, dialect, el: form, controls, wrapper, foreign, landmark, dynamic, content, importAt };
|
|
6570
6682
|
}
|
|
6571
6683
|
async function importedControls(source, parsed, dialect, project, cache) {
|
|
6572
6684
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -6599,11 +6711,20 @@ function wire(source, candidate, form) {
|
|
|
6599
6711
|
else if (attr.valueRange) source.overwrite(attr.valueRange.start, attr.valueRange.end, escapeAttr(value));
|
|
6600
6712
|
else source.overwrite(attr.range.start, attr.range.end, `${name}="${escapeAttr(value)}"`);
|
|
6601
6713
|
};
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6714
|
+
const setExpr = (name, code) => {
|
|
6715
|
+
const attr = attrOf(el, name);
|
|
6716
|
+
if (!attr) inserts.push(`${name}={${code}}`);
|
|
6717
|
+
else source.overwrite(attr.range.start, attr.range.end, `${name}={${code}}`);
|
|
6718
|
+
};
|
|
6719
|
+
const byName = BY_NAME.includes(dialect);
|
|
6720
|
+
if (byName) setExpr("action", actionCall(form));
|
|
6721
|
+
else set("action", form.submitUrl);
|
|
6722
|
+
if (byName) setExpr("method", methodCall(form));
|
|
6723
|
+
else set("method", "post");
|
|
6724
|
+
if (byName) setExpr("data-bcms-form", idCall(form));
|
|
6725
|
+
else set("data-bcms-form", form.id);
|
|
6605
6726
|
if (form.successMessage) set("data-bcms-form-success", form.successMessage);
|
|
6606
|
-
source.appendLeft(el.attrInsertAt, ` ${inserts.join(" ")}`);
|
|
6727
|
+
if (inserts.length > 0) source.appendLeft(el.attrInsertAt, ` ${inserts.join(" ")}`);
|
|
6607
6728
|
for (const inner of elementsIn(el.children)) {
|
|
6608
6729
|
for (const attr of inner.attrs) {
|
|
6609
6730
|
if (!DECLARED_BINDING_ATTR.test(attr.name)) continue;
|
|
@@ -6660,12 +6781,20 @@ async function wireForms(briefIn, sources, options_ = {}) {
|
|
|
6660
6781
|
}
|
|
6661
6782
|
const imported = dialect === "html" ? /* @__PURE__ */ new Map() : await importedControls(source, parsed, dialect, project, componentFiles);
|
|
6662
6783
|
for (const { el, landmark } of formsIn(parsed.roots)) {
|
|
6663
|
-
candidates.push(candidateOf(source.path, dialect, el, imported, landmark, source.content));
|
|
6784
|
+
candidates.push(candidateOf(source.path, dialect, el, imported, landmark, source.content, parsed.importAt));
|
|
6664
6785
|
}
|
|
6665
6786
|
}
|
|
6666
6787
|
const holds = (c, f) => f.fields.filter((field) => c.controls.has(field.key)).length;
|
|
6667
|
-
const
|
|
6668
|
-
const
|
|
6788
|
+
const byId = (c, f) => !attrOf(c.el, "data-bcms-form")?.expression && attrOf(c.el, "data-bcms-form")?.value === f.id;
|
|
6789
|
+
const programAction = (c) => !!attrOf(c.el, "action")?.expression && calledName(exprOf(c, "action"), "bcmsFormAction") === null;
|
|
6790
|
+
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);
|
|
6791
|
+
const wiredName = (c) => BY_NAME.includes(c.dialect) ? calledName(exprOf(c, "data-bcms-form"), "bcmsFormId") : null;
|
|
6792
|
+
const staticPost = (c) => {
|
|
6793
|
+
const method = attrOf(c.el, "method");
|
|
6794
|
+
return !!method && !method.expression && method.value.toLowerCase() === "post";
|
|
6795
|
+
};
|
|
6796
|
+
const claims = (c, f) => wiredTo(c, f) || byId(c, f);
|
|
6797
|
+
const covers2 = (c, f) => c.dynamic && claims(c, f) || f.fields.length > 0 && holds(c, f) === f.fields.length;
|
|
6669
6798
|
const samePath = (a, b) => a.replace(/\\/g, "/").replace(/^\.\//, "") === b.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
6670
6799
|
const options = new Map(forms.map((f) => [f.id, candidates.filter((c) => covers2(c, f))]));
|
|
6671
6800
|
const pinned = /* @__PURE__ */ new Map();
|
|
@@ -6709,7 +6838,14 @@ async function wireForms(briefIn, sources, options_ = {}) {
|
|
|
6709
6838
|
const wanted = /* @__PURE__ */ new Map();
|
|
6710
6839
|
for (const list of options.values()) for (const c of list) wanted.set(c, (wanted.get(c) ?? 0) + 1);
|
|
6711
6840
|
const matched = [];
|
|
6841
|
+
const methodUpgrades = [];
|
|
6842
|
+
const pendedByName = /* @__PURE__ */ new Set();
|
|
6712
6843
|
const pending = [];
|
|
6844
|
+
const existingHelper = sources.find((s) => samePath(s.path, FORMS_HELPER_PATH))?.content;
|
|
6845
|
+
const helperVersion = existingHelper === void 0 ? null : formsHelperVersion(existingHelper);
|
|
6846
|
+
const helperOk = existingHelper === void 0 || helperVersion !== null;
|
|
6847
|
+
const helperStale = existingHelper !== void 0 && helperVersion !== null && helperVersion <= FORMS_HELPER_VERSION && existingHelper !== FORMS_HELPER_SOURCE;
|
|
6848
|
+
let byNameOnSite = false;
|
|
6713
6849
|
const onSite = [];
|
|
6714
6850
|
let alreadyWired = 0;
|
|
6715
6851
|
const pend2 = (f, reason, file, message) => void pending.push({ id: f.id, name: f.name, reason, ...file ? { file } : {}, ...message ? { message } : {} });
|
|
@@ -6748,12 +6884,25 @@ async function wireForms(briefIn, sources, options_ = {}) {
|
|
|
6748
6884
|
continue;
|
|
6749
6885
|
}
|
|
6750
6886
|
const candidate = list[0];
|
|
6887
|
+
const byName = BY_NAME.includes(candidate.dialect);
|
|
6888
|
+
if (byName && forms.filter((f) => f.name === form.name).length > 1) {
|
|
6889
|
+
pend2(form, "FORM_NAME_AMBIGUOUS", candidate.file, `more than one CMS form is named "${form.name}"; rename one in the Forms tab.`);
|
|
6890
|
+
continue;
|
|
6891
|
+
}
|
|
6751
6892
|
if (wiredTo(candidate, form)) {
|
|
6752
6893
|
alreadyWired++;
|
|
6753
6894
|
onSite.push({ form, file: candidate.file });
|
|
6895
|
+
if (byName) byNameOnSite = true;
|
|
6896
|
+
if (wiredName(candidate) === form.name && staticPost(candidate) && helperOk) methodUpgrades.push({ form, candidate });
|
|
6754
6897
|
continue;
|
|
6755
6898
|
}
|
|
6756
|
-
|
|
6899
|
+
const other = wiredName(candidate);
|
|
6900
|
+
if (other !== null) {
|
|
6901
|
+
pendedByName.add(candidate);
|
|
6902
|
+
pend2(form, "FORM_WIRED_TO_OTHER_NAME", candidate.file, `its <form> is wired by name to "${other}", and a wiring by name is never rewritten: form names are shared by every environment, so give this form the same name on each one.`);
|
|
6903
|
+
continue;
|
|
6904
|
+
}
|
|
6905
|
+
if (programAction(candidate)) {
|
|
6757
6906
|
pend2(form, "ACTION_IS_EXPRESSION", candidate.file, "its action is an expression, so this run will not replace it.");
|
|
6758
6907
|
continue;
|
|
6759
6908
|
}
|
|
@@ -6765,18 +6914,46 @@ async function wireForms(briefIn, sources, options_ = {}) {
|
|
|
6765
6914
|
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.`);
|
|
6766
6915
|
continue;
|
|
6767
6916
|
}
|
|
6917
|
+
if (byName && !helperOk) {
|
|
6918
|
+
pend2(form, "HELPER_CONFLICT", FORMS_HELPER_PATH, `${FORMS_HELPER_PATH} exists and is not the file @bettercms-ai/convert writes, so nothing may import it. Move it and re-run.`);
|
|
6919
|
+
continue;
|
|
6920
|
+
}
|
|
6768
6921
|
matched.push({ form, candidate });
|
|
6769
6922
|
onSite.push({ form, file: candidate.file });
|
|
6923
|
+
if (byName) byNameOnSite = true;
|
|
6770
6924
|
}
|
|
6771
6925
|
const contents = new Map(sources.map((s) => [s.path, s.content]));
|
|
6772
6926
|
const byFile = /* @__PURE__ */ new Map();
|
|
6773
|
-
|
|
6927
|
+
const editsOf = (path) => {
|
|
6928
|
+
if (!byFile.has(path)) byFile.set(path, { wire: [], upgrade: [] });
|
|
6929
|
+
return byFile.get(path);
|
|
6930
|
+
};
|
|
6931
|
+
for (const m of matched) editsOf(m.candidate.file).wire.push(m);
|
|
6932
|
+
for (const m of methodUpgrades) editsOf(m.candidate.file).upgrade.push(m);
|
|
6774
6933
|
const files = [];
|
|
6775
|
-
for (const [path, list] of [...byFile].sort(([a], [b]) => a.localeCompare(b))) {
|
|
6934
|
+
for (const [path, { wire: list, upgrade }] of [...byFile].sort(([a], [b]) => a.localeCompare(b))) {
|
|
6776
6935
|
const content = contents.get(path);
|
|
6777
6936
|
const source = new MagicString4(content);
|
|
6778
6937
|
for (const { form, candidate } of list) wire(source, candidate, form);
|
|
6779
|
-
|
|
6938
|
+
for (const { form, candidate } of upgrade) {
|
|
6939
|
+
const method = attrOf(candidate.el, "method");
|
|
6940
|
+
source.overwrite(method.range.start, method.range.end, `method={${methodCall(form)}}`);
|
|
6941
|
+
}
|
|
6942
|
+
const first = (list[0] ?? upgrade[0]).candidate;
|
|
6943
|
+
if (BY_NAME.includes(first.dialect) && first.importAt) {
|
|
6944
|
+
const at = first.importAt;
|
|
6945
|
+
const helper = relativeImport(path, FORMS_HELPER_PATH.replace(/\.ts$/, ""));
|
|
6946
|
+
const names = !/\bbcmsFormId\b/.test(content) ? "bcmsFormAction, bcmsFormId, bcmsFormMethod" : !/\bbcmsFormMethod\b/.test(content) ? "bcmsFormMethod" : null;
|
|
6947
|
+
if (names) {
|
|
6948
|
+
const line = `import { ${names} } from "${helper}";`;
|
|
6949
|
+
if (at.wrap) source.appendLeft(at.at, `${at.wrap.before}${line}${at.wrap.after}`);
|
|
6950
|
+
else if (at.at === 0) source.appendLeft(0, `${line}
|
|
6951
|
+
`);
|
|
6952
|
+
else source.appendLeft(at.at, `
|
|
6953
|
+
${line}`);
|
|
6954
|
+
}
|
|
6955
|
+
}
|
|
6956
|
+
if (list.length > 0 && !content.includes("form[data-bcms-form]")) {
|
|
6780
6957
|
const last = list.reduce((a, b) => b.candidate.el.end > a.candidate.el.end ? b : a);
|
|
6781
6958
|
const indent = /[^\S\n]*$/.exec(content.slice(0, last.candidate.el.start))[0];
|
|
6782
6959
|
source.appendRight(last.candidate.el.end, `
|
|
@@ -6784,6 +6961,10 @@ ${indent}${scriptFor(last.candidate.dialect)}`);
|
|
|
6784
6961
|
}
|
|
6785
6962
|
files.push({ path, operation: "modify", content: source.toString() });
|
|
6786
6963
|
}
|
|
6964
|
+
const helperAdded = existingHelper === void 0 && (matched.some((m) => BY_NAME.includes(m.candidate.dialect)) || methodUpgrades.length > 0);
|
|
6965
|
+
if (helperAdded) files.push({ path: FORMS_HELPER_PATH, operation: "add", content: FORMS_HELPER_SOURCE });
|
|
6966
|
+
const helperUpgraded = helperStale && byNameOnSite;
|
|
6967
|
+
if (helperUpgraded) files.push({ path: FORMS_HELPER_PATH, operation: "modify", content: FORMS_HELPER_SOURCE });
|
|
6787
6968
|
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.`);
|
|
6788
6969
|
for (const { form, candidate } of matched) {
|
|
6789
6970
|
const tie = beat.get(candidate);
|
|
@@ -6799,6 +6980,13 @@ ${indent}${scriptFor(last.candidate.dialect)}`);
|
|
|
6799
6980
|
);
|
|
6800
6981
|
}
|
|
6801
6982
|
}
|
|
6983
|
+
for (const c of candidates) {
|
|
6984
|
+
const name = wiredName(c);
|
|
6985
|
+
if (name === null || pendedByName.has(c) || forms.some((f) => f.name === name)) continue;
|
|
6986
|
+
notes.push(
|
|
6987
|
+
`${c.file}: its <form> is wired by name to "${name}", which no form of this project has, so this environment's build renders it unwired; it was not rewritten \u2014 form names are shared by every environment, so give the form that name here too.`
|
|
6988
|
+
);
|
|
6989
|
+
}
|
|
6802
6990
|
for (const { form, candidate } of matched) {
|
|
6803
6991
|
const keys = form.fields.filter((field) => candidate.foreign.has(candidate.controls.get(field.key))).map((field) => field.key);
|
|
6804
6992
|
if (keys.length > 0) {
|
|
@@ -6816,8 +7004,8 @@ ${indent}${scriptFor(last.candidate.dialect)}`);
|
|
|
6816
7004
|
paths: { declared: 0, rewritten: 0, alreadyDeclared: 0, pending: [] },
|
|
6817
7005
|
occurrences: { located: 0, rewritten: 0 },
|
|
6818
7006
|
files: [],
|
|
6819
|
-
helpers: [],
|
|
6820
|
-
helperUpgraded
|
|
7007
|
+
helpers: helperAdded || helperUpgraded ? [FORMS_HELPER_PATH] : [],
|
|
7008
|
+
helperUpgraded,
|
|
6821
7009
|
dynamicBindings: [],
|
|
6822
7010
|
forms: assertForms(forms.length, {
|
|
6823
7011
|
wired: matched.length,
|
|
@@ -6921,7 +7109,7 @@ var CONTEXT_TAGS = /* @__PURE__ */ new Set([
|
|
|
6921
7109
|
var BOUND_ATTRS = ["data-bcms-field", "data-bcms-layout-field"];
|
|
6922
7110
|
var isDecorativeGlyph = (text) => {
|
|
6923
7111
|
const t = flat2(text);
|
|
6924
|
-
return t === "" || !/[\p{L}\p{N}]/u.test(t) || new RegExp("
|
|
7112
|
+
return t === "" || !/[\p{L}\p{N}]/u.test(t) || new RegExp("^(?:0\\p{Nd}|\\p{Nd}{1,2}\\s*[/.)\\-\u2013\u2014])$", "u").test(t);
|
|
6925
7113
|
};
|
|
6926
7114
|
var PLATFORM_BADGE = /^made with\b/iu;
|
|
6927
7115
|
var MAX_BUCKETS = 12;
|