@bettercms-ai/convert 0.18.2 → 0.20.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 +23 -0
- package/README.md +33 -3
- package/dist/cli.js +258 -34
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +256 -32
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @bettercms-ai/convert
|
|
2
2
|
|
|
3
|
+
## 0.20.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c4bd14b: `--forms` wires an Astro `<form>` by the CMS form's NAME: `data-bcms-form={bcmsFormId("Contact")}`, `action={bcmsFormAction("Contact")}` and `method={bcmsFormMethod("Contact")}`, resolved at build by a committed `src/bcms-forms.ts` from `bcms-content.json` and `BCMS_API_URL`. The source carries no host and no form id, so a repository built by more than one BetterCMS environment wires each build to the environment that built it. An Astro form wired by id is rewired by name once, one wired by name with a static `method="post"` gets `method={bcmsFormMethod(…)}` once, and one already wired by name counts as wired whatever quotes or spacing a formatter gave the calls; a shared name pends `FORM_NAME_AMBIGUOUS`. Form NAMES are the cross-rung contract; rename on both rungs: a wiring by name is never rewritten, so a form this project calls something else pends `FORM_WIRED_TO_OTHER_NAME` with nothing written, and a wiring by a name no form of the brief covers gets one note. At build the helper never fails: a name it cannot resolve among the project's own non-ignored forms, a shared name or a missing `BCMS_API_URL` renders that form unwired (no `action`, no `method`, no `data-bcms-form`) with one warning line naming `BCMS_FORM_NOT_FOUND`, `BCMS_FORM_NAME_AMBIGUOUS` or `BCMS_FORM_API_URL_MISSING` and the form, and a `bcms-content.json` it cannot parse, or one listing a form with no id or name, renders every form unwired with one `FORMS_SNAPSHOT_UNREADABLE` line, because forms are derived only by the release of a successful build. The helper passes a strict site tsconfig (`astro check`). Its first line, `// @bettercms-ai/convert forms-helper v2`, marks it as ours: a copy at that version or older (v1 included) is rewritten to the current bytes, and a `src/bcms-forms.ts` without that line pends `HELPER_CONFLICT`. A matched `<form>` that already carries every attribute no longer gains a stray space.
|
|
8
|
+
|
|
9
|
+
Not changed: html and JSX forms stay wired by id (html has no build, and JSX may render in a client bundle that cannot use `node:fs`). The backend's orchestration-validation workflow is left unsuffixed; only the convert, section-validation and component-validation workflows are per rung.
|
|
10
|
+
|
|
11
|
+
## 0.19.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- 3d022b0: A locale copy placed on its default-locale twin's section component now carries `props.bindPaths` (`{ <propKey>: "<this page's field path>" }`) beside `bind`, because its own field keys differ from the component's. `ComponentProps.bindPaths` is new in types; the convert helper (v9) and the extracted section components and registry read a prop's copy and declare its address through it, so `/fr` renders and edits its own words instead of a field it does not have. Older readers ignore it.
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- Updated dependencies [12b2d62]
|
|
20
|
+
- Updated dependencies [3d022b0]
|
|
21
|
+
- Updated dependencies [a0a6ad9]
|
|
22
|
+
- Updated dependencies [621a70e]
|
|
23
|
+
- Updated dependencies [2729e58]
|
|
24
|
+
- @bettercms-ai/types@1.25.0
|
|
25
|
+
|
|
3
26
|
## 0.18.2
|
|
4
27
|
|
|
5
28
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -624,13 +624,43 @@ It writes six things onto a matched `<form>` and nothing else: `action="<submitU
|
|
|
624
624
|
`method="post"`, `data-bcms-form="<id>"` (plus `data-bcms-form-success` when the CMS form has a
|
|
625
625
|
success message), `data-bcms-form-field="<key>"` on each matched control, a
|
|
626
626
|
`<p class="bcms-form-msg" hidden>` before `</form>` when there is none, and the submit script once
|
|
627
|
-
per file — the same bytes the hosted renderer ships, pinned by a test.
|
|
628
|
-
|
|
627
|
+
per file — the same bytes the hosted renderer ships, pinned by a test.
|
|
628
|
+
|
|
629
|
+
In an Astro file the form is named, not addressed: `data-bcms-form={bcmsFormId("Contact")}`,
|
|
630
|
+
`action={bcmsFormAction("Contact")}` and `method={bcmsFormMethod("Contact")}`, imported from
|
|
631
|
+
`src/bcms-forms.ts`, which this run adds. At build that helper reads the form's id from
|
|
632
|
+
`bcms-content.json` and the API from `BCMS_API_URL`, so the source holds no host and no id and every
|
|
633
|
+
BetterCMS environment that builds the repository wires it to itself. The built page is unchanged: an
|
|
634
|
+
id, an absolute action and `post`. The helper reads the forms the brief names from: the building
|
|
635
|
+
project's own, ignored ones left out. With no snapshot (a local build) the form renders unwired. The
|
|
636
|
+
helper never fails a build: a name it cannot resolve, a name two forms share, or a build with no
|
|
637
|
+
`BCMS_API_URL` renders that form unwired too (no `action`, no `method`, no `data-bcms-form`, so
|
|
638
|
+
Astro drops all three) and prints one warning line naming the code (`BCMS_FORM_NOT_FOUND`,
|
|
639
|
+
`BCMS_FORM_NAME_AMBIGUOUS`, `BCMS_FORM_API_URL_MISSING`) and the form; a `bcms-content.json` it
|
|
640
|
+
cannot parse, or one listing a form with no id or name, renders every form unwired with one
|
|
641
|
+
`FORMS_SNAPSHOT_UNREADABLE` line. Forms are derived by the release of a successful build and convert
|
|
642
|
+
runs after it, so a helper that threw would leave a first import or a renamed form undeployable for
|
|
643
|
+
good. The helper type-checks under a strict site tsconfig, because `astro check` lints it. Its first
|
|
644
|
+
line, `// @bettercms-ai/convert forms-helper v2`, is what makes the file ours: a copy at that version
|
|
645
|
+
or older is rewritten to the current bytes, a newer one is left alone, and a file without the line is
|
|
646
|
+
`HELPER_CONFLICT`. An Astro form an earlier run wired by id is rewired by name once, a form wired by
|
|
647
|
+
name with v1's static `method="post"` gets `method={bcmsFormMethod(…)}` once, and a form wired by
|
|
648
|
+
name counts as wired whatever quotes or spacing a formatter gave the calls. html and JSX still get
|
|
649
|
+
the literal id and endpoint of the environment that ran the codemod — html has no build, and a JSX
|
|
650
|
+
form may render in a client bundle that cannot read a file.
|
|
651
|
+
|
|
652
|
+
**Form NAMES are the cross-rung contract; rename on both rungs.** A wiring by name is never
|
|
653
|
+
rewritten, whether or not the name resolves on the environment running the codemod: prod and dev
|
|
654
|
+
share the name and nothing else, so rewriting it for one unwires the form on the other. A form this
|
|
655
|
+
project calls something else is pending `FORM_WIRED_TO_OTHER_NAME` and nothing is written; a wiring by
|
|
656
|
+
a name no form of the brief even covers gets one line in `notes`.
|
|
629
657
|
|
|
630
658
|
The match is the FIELD KEYS: a `<form>` is a CMS form's only when the names its controls post are a
|
|
631
659
|
superset of that form's field keys and exactly one `<form>` answers. Everything else is pending with
|
|
632
660
|
a reason — `FORM_NOT_IN_SOURCE`, `FORM_AMBIGUOUS`, `FIELD_UNMATCHED`, `ACTION_IS_EXPRESSION`,
|
|
633
|
-
`DIALECT_UNSUPPORTED`, `PARSE_ERROR
|
|
661
|
+
`DIALECT_UNSUPPORTED`, `PARSE_ERROR`, and for Astro `FORM_NAME_AMBIGUOUS` (two CMS forms share the
|
|
662
|
+
name), `HELPER_CONFLICT` (`src/bcms-forms.ts` is somebody else's file) and
|
|
663
|
+
`FORM_WIRED_TO_OTHER_NAME` (the `<form>` is wired by another name) — because a form wired to the wrong id sends the customer's
|
|
634
664
|
leads into another form's inbox, silently, forever. A `<form>` that already carries
|
|
635
665
|
`data-bcms-form` is `alreadyWired` and is left alone, so a second run is a no-op.
|
|
636
666
|
|
package/dist/cli.js
CHANGED
|
@@ -4190,7 +4190,7 @@ async function bindImages(ctx, targets) {
|
|
|
4190
4190
|
|
|
4191
4191
|
// src/helper.ts
|
|
4192
4192
|
import { createHash as createHash2 } from "crypto";
|
|
4193
|
-
var HELPER_VERSION =
|
|
4193
|
+
var HELPER_VERSION = 9;
|
|
4194
4194
|
var HEADER = "// @bettercms-ai/convert helper v";
|
|
4195
4195
|
var HELPER_PATH = Object.fromEntries(
|
|
4196
4196
|
Object.keys(DIALECT_RULES).map((d) => [d, DIALECT_RULES[d].helper])
|
|
@@ -4355,9 +4355,19 @@ export function bcmsImage<T>(page: unknown, path: string, fallback: T, slug?: st
|
|
|
4355
4355
|
* the fallback the template shipped with \u2014 which is why a section renders correctly against the
|
|
4356
4356
|
* committed \`{}\` stub.
|
|
4357
4357
|
*/
|
|
4358
|
-
export function bcmsSection(
|
|
4358
|
+
export function bcmsSection(
|
|
4359
|
+
page: unknown,
|
|
4360
|
+
bind?: string,
|
|
4361
|
+
overrides?: unknown,
|
|
4362
|
+
bindPaths?: Record<string, string>,
|
|
4363
|
+
): Record<string, unknown> {
|
|
4359
4364
|
const group = bind ? readPath(page, bind) : undefined;
|
|
4360
4365
|
const base: Record<string, unknown> = isRecord(group) ? { ...group } : {};
|
|
4366
|
+
// A locale copy on its twin's component spells its fields its own way: each prop's copy is
|
|
4367
|
+
// read from the path the placement names for it.
|
|
4368
|
+
if (bind && isRecord(bindPaths)) {
|
|
4369
|
+
for (const [key, path] of Object.entries(bindPaths)) base[key] = readPath(page, path);
|
|
4370
|
+
}
|
|
4361
4371
|
if (!isRecord(overrides)) return base;
|
|
4362
4372
|
for (const key of Object.keys(overrides)) {
|
|
4363
4373
|
const value = overrides[key];
|
|
@@ -4420,21 +4430,25 @@ export function bcmsSectionPaths(
|
|
|
4420
4430
|
overrides: unknown,
|
|
4421
4431
|
prefix: string,
|
|
4422
4432
|
map: BcmsPathMap,
|
|
4433
|
+
bindPaths?: Record<string, string>,
|
|
4423
4434
|
): Record<string, unknown> {
|
|
4424
4435
|
const out: Record<string, unknown> = isRecord(page) ? { ...page } : {};
|
|
4425
4436
|
const own = isRecord(overrides) ? overrides : {};
|
|
4426
4437
|
const separator = prefix.slice(-1);
|
|
4427
|
-
|
|
4428
|
-
|
|
4438
|
+
// A re-keyed locale copy names each prop's path on its own page; any other placement re-bases.
|
|
4439
|
+
const fromPage = (path: string, key: string): unknown =>
|
|
4440
|
+
!bind ? undefined
|
|
4441
|
+
: isRecord(bindPaths) && typeof bindPaths[key] === "string" ? readPath(page, bindPaths[key])
|
|
4442
|
+
: readPath(page, bind + separator + path.slice(prefix.length));
|
|
4429
4443
|
for (const [path, key] of Object.entries(map.scalars ?? {})) {
|
|
4430
4444
|
const value = own[key];
|
|
4431
|
-
out[path] = value !== undefined && value !== null ? value : fromPage(path);
|
|
4445
|
+
out[path] = value !== undefined && value !== null ? value : fromPage(path, key);
|
|
4432
4446
|
}
|
|
4433
4447
|
for (const [path, table] of Object.entries(map.tables ?? {})) {
|
|
4434
4448
|
const held = own[table.key];
|
|
4435
4449
|
const rows = isRecord(held) && Array.isArray(held.rows) ? held.rows : held;
|
|
4436
4450
|
if (!Array.isArray(rows)) {
|
|
4437
|
-
out[path] = fromPage(path);
|
|
4451
|
+
out[path] = fromPage(path, table.key);
|
|
4438
4452
|
continue;
|
|
4439
4453
|
}
|
|
4440
4454
|
const pageColumn = new Map(Object.entries(table.columns).map(([column, key]) => [key, column]));
|
|
@@ -4449,12 +4463,17 @@ export function bcmsSectionPaths(
|
|
|
4449
4463
|
}
|
|
4450
4464
|
|
|
4451
4465
|
export function bcmsFields(
|
|
4452
|
-
placement: { blockId?: string; bind?: string; overrides?: unknown },
|
|
4466
|
+
placement: { blockId?: string; bind?: string; overrides?: unknown; bindPaths?: Record<string, string> },
|
|
4453
4467
|
pathPrefix: string,
|
|
4454
4468
|
map?: BcmsPathMap,
|
|
4455
4469
|
): (path: string) => string {
|
|
4456
4470
|
const id = placement.blockId;
|
|
4457
4471
|
const own = typeof id === "string" && id !== "" && !placement.bind && isRecord(placement.overrides);
|
|
4472
|
+
/** A re-keyed locale copy's own path for a prop, with whatever follows the prop kept. */
|
|
4473
|
+
const moved = (key: string | undefined, rest: string): string | undefined => {
|
|
4474
|
+
const paths = placement.bind && isRecord(placement.bindPaths) ? placement.bindPaths : undefined;
|
|
4475
|
+
return key !== undefined && paths && typeof paths[key] === "string" ? paths[key] + rest : undefined;
|
|
4476
|
+
};
|
|
4458
4477
|
/**
|
|
4459
4478
|
* What replaces the prefix the markup was cut with, or null for "leave every path alone".
|
|
4460
4479
|
*
|
|
@@ -4473,11 +4492,14 @@ export function bcmsFields(
|
|
|
4473
4492
|
const separator = pathPrefix.slice(-1);
|
|
4474
4493
|
return (path: string): string => {
|
|
4475
4494
|
if (!path.startsWith(pathPrefix) || !(own || placement.bind)) return path;
|
|
4476
|
-
if (!own) return placement.bind + separator + path.slice(pathPrefix.length);
|
|
4477
4495
|
const scalar = map.scalars?.[path];
|
|
4478
|
-
if (scalar !== undefined) return id + "__overrides." + scalar;
|
|
4479
4496
|
const row = /^([^[]+)\\[(\\d+)\\]\\.(.+)$/.exec(path);
|
|
4480
4497
|
const table = row ? map.tables?.[row[1]] : undefined;
|
|
4498
|
+
if (!own) {
|
|
4499
|
+
return moved(scalar, "") ?? moved(table?.key, row ? "[" + row[2] + "]." + row[3] : "")
|
|
4500
|
+
?? placement.bind + separator + path.slice(pathPrefix.length);
|
|
4501
|
+
}
|
|
4502
|
+
if (scalar !== undefined) return id + "__overrides." + scalar;
|
|
4481
4503
|
const column = row && table ? table.columns[row[3]] : undefined;
|
|
4482
4504
|
return table && column !== undefined ? id + "__overrides." + table.key + "[" + row![2] + "]." + column : path;
|
|
4483
4505
|
};
|
|
@@ -4487,7 +4509,9 @@ export function bcmsFields(
|
|
|
4487
4509
|
// it names a key the instance does not hold and a group this placement is not bound to.
|
|
4488
4510
|
// (An EMPTY prefix is a plan saying the prop key IS the page path, and matches everything.)
|
|
4489
4511
|
if (lane === null || !path.startsWith(pathPrefix)) return path;
|
|
4490
|
-
|
|
4512
|
+
const rest = path.slice(pathPrefix.length);
|
|
4513
|
+
const key = /^[^.[]+/.exec(rest)?.[0];
|
|
4514
|
+
return (own ? undefined : moved(key, rest.slice(key?.length ?? 0))) ?? lane + rest;
|
|
4491
4515
|
};
|
|
4492
4516
|
}
|
|
4493
4517
|
`;
|
|
@@ -4500,7 +4524,8 @@ var HISTORICAL = {
|
|
|
4500
4524
|
4: "a5742f64dacb1b26ee6dd5e1469a54871487934c71bc8267df19e3a80e417595",
|
|
4501
4525
|
5: "0472fe3ffdcfa49e719c7b9da9cb63b6eba637cc22783bb00c3c2cadd3d800da",
|
|
4502
4526
|
6: "c907e7245578d947c6541b1ab2c5c6659ea7789a1519e895b4a4aed122c6f649",
|
|
4503
|
-
7: "f22514ef48c50230985e605bea6cf9bf41756384a7dc80603e347baba9b65d93"
|
|
4527
|
+
7: "f22514ef48c50230985e605bea6cf9bf41756384a7dc80603e347baba9b65d93",
|
|
4528
|
+
8: "484e72852d2b496e016a1f2a2b1a77bb993f8a163b290781522d6114b6519bb9"
|
|
4504
4529
|
},
|
|
4505
4530
|
"src/lib/bcms-content.ts": {
|
|
4506
4531
|
1: "ae72ae14d706943d481c880d70da5789b4816ba1ba319a10fbc63ded4d0b42f2",
|
|
@@ -4509,7 +4534,8 @@ var HISTORICAL = {
|
|
|
4509
4534
|
4: "0146d027a727ecd3c9e3fc336fcf1299b59ddf75b04091c05703f3f983ec28af",
|
|
4510
4535
|
5: "c8c93655320fa65664611ba84541f3fa7bfa292cc9a2fd655373440b99049fba",
|
|
4511
4536
|
6: "63f1fa29e47e70c5193b81ee24addb238ba100247124d55bc17713102d587df5",
|
|
4512
|
-
7: "ed92fb88de953d9951abbe6a1edae1d048f4050eaf6fb607ac08167f94d8bcc7"
|
|
4537
|
+
7: "ed92fb88de953d9951abbe6a1edae1d048f4050eaf6fb607ac08167f94d8bcc7",
|
|
4538
|
+
8: "1a6f669d76d42effe77f082436e9d21c0efa6fc499e919e3d116bc3a27a7f3d5"
|
|
4513
4539
|
},
|
|
4514
4540
|
"composables/bcms-content.ts": {
|
|
4515
4541
|
1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
|
|
@@ -4518,7 +4544,8 @@ var HISTORICAL = {
|
|
|
4518
4544
|
4: "a5742f64dacb1b26ee6dd5e1469a54871487934c71bc8267df19e3a80e417595",
|
|
4519
4545
|
5: "0472fe3ffdcfa49e719c7b9da9cb63b6eba637cc22783bb00c3c2cadd3d800da",
|
|
4520
4546
|
6: "c907e7245578d947c6541b1ab2c5c6659ea7789a1519e895b4a4aed122c6f649",
|
|
4521
|
-
7: "f22514ef48c50230985e605bea6cf9bf41756384a7dc80603e347baba9b65d93"
|
|
4547
|
+
7: "f22514ef48c50230985e605bea6cf9bf41756384a7dc80603e347baba9b65d93",
|
|
4548
|
+
8: "484e72852d2b496e016a1f2a2b1a77bb993f8a163b290781522d6114b6519bb9"
|
|
4522
4549
|
}
|
|
4523
4550
|
};
|
|
4524
4551
|
var sha256 = (value) => createHash2("sha256").update(value, "utf8").digest("hex");
|
|
@@ -6407,6 +6434,114 @@ function readPlan(value) {
|
|
|
6407
6434
|
// src/forms.ts
|
|
6408
6435
|
import MagicString4 from "magic-string";
|
|
6409
6436
|
var WIRABLE = ["html", "astro", "jsx"];
|
|
6437
|
+
var BY_NAME = ["astro"];
|
|
6438
|
+
var FORMS_HELPER_PATH = "src/bcms-forms.ts";
|
|
6439
|
+
var FORMS_HELPER_VERSION = 2;
|
|
6440
|
+
var FORMS_HELPER_HEADER = /^\/\/ @bettercms-ai\/convert forms-helper v(\d+)\r?\n/;
|
|
6441
|
+
var formsHelperVersion = (content) => {
|
|
6442
|
+
const m = FORMS_HELPER_HEADER.exec(content);
|
|
6443
|
+
return m ? Number(m[1]) : null;
|
|
6444
|
+
};
|
|
6445
|
+
var FORMS_HELPER_SOURCE = `// @bettercms-ai/convert forms-helper v${FORMS_HELPER_VERSION}
|
|
6446
|
+
// Written by @bettercms-ai/convert; a re-run rewrites it, so do not edit it.
|
|
6447
|
+
// Resolves a BetterCMS form by NAME at build, for the environment that is building this site: the id
|
|
6448
|
+
// from bcms-content.json (written into the tree before the build) and the endpoint from BCMS_API_URL.
|
|
6449
|
+
// The source names no host and no id, so every BetterCMS environment builds its own wiring.
|
|
6450
|
+
// A form it cannot resolve renders unwired (no action, no method, no id) with one warning line, and
|
|
6451
|
+
// the build goes on. Form NAMES are the contract between environments: rename a form on every one.
|
|
6452
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
6453
|
+
|
|
6454
|
+
type SnapshotForm = { id: string; name: string; projectId?: string | null; ignoredAt?: string | null };
|
|
6455
|
+
type Snapshot = { projectId: string | null; forms: SnapshotForm[] };
|
|
6456
|
+
type Wiring = { id: string; action: string };
|
|
6457
|
+
|
|
6458
|
+
let snapshot: Snapshot | null | undefined;
|
|
6459
|
+
const resolved = new Map<string, Wiring | undefined>();
|
|
6460
|
+
|
|
6461
|
+
const env = (key: string): string | undefined => process.env[key] || process.env[\`PUBLIC_\${key}\`] || undefined;
|
|
6462
|
+
|
|
6463
|
+
const isForm = (value: unknown): value is SnapshotForm =>
|
|
6464
|
+
typeof value === "object" &&
|
|
6465
|
+
value !== null &&
|
|
6466
|
+
typeof (value as { id?: unknown }).id === "string" &&
|
|
6467
|
+
typeof (value as { name?: unknown }).name === "string";
|
|
6468
|
+
|
|
6469
|
+
function readSnapshot(): Snapshot | null {
|
|
6470
|
+
for (const file of ["bcms-content.json", "bcms-content/forms.json"]) {
|
|
6471
|
+
if (!existsSync(file)) continue;
|
|
6472
|
+
try {
|
|
6473
|
+
const data: unknown = JSON.parse(readFileSync(file, "utf8"));
|
|
6474
|
+
const record = (Array.isArray(data) ? { forms: data } : (data ?? {})) as { forms?: unknown; projectId?: unknown };
|
|
6475
|
+
const forms: unknown[] = Array.isArray(record.forms) ? record.forms : [];
|
|
6476
|
+
if (!forms.every(isForm)) throw new Error("a form entry has no id or name");
|
|
6477
|
+
return { projectId: typeof record.projectId === "string" ? record.projectId : null, forms };
|
|
6478
|
+
} catch (error) {
|
|
6479
|
+
// Read once per build, so this is the one line however many forms the site has.
|
|
6480
|
+
console.warn(\`[bcms-forms] FORMS_SNAPSHOT_UNREADABLE: \${file} could not be read (\${error instanceof Error ? error.message : String(error)}); every form renders unwired.\`);
|
|
6481
|
+
return null;
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
return null;
|
|
6485
|
+
}
|
|
6486
|
+
|
|
6487
|
+
function unwired(code: string, name: string, why: string): undefined {
|
|
6488
|
+
console.warn(\`[bcms-forms] \${code}: form "\${name}" renders unwired; \${why}.\`);
|
|
6489
|
+
return undefined;
|
|
6490
|
+
}
|
|
6491
|
+
|
|
6492
|
+
function resolve(name: string): Wiring | undefined {
|
|
6493
|
+
if (snapshot === undefined) snapshot = readSnapshot();
|
|
6494
|
+
// No snapshot (a local dev build), or one it could not read: the form renders unwired, as before.
|
|
6495
|
+
if (snapshot === null) return undefined;
|
|
6496
|
+
const project = env("BCMS_PROJECT_ID") ?? snapshot.projectId;
|
|
6497
|
+
// The forms the conversion brief names from: this project's own, ignored ones left out.
|
|
6498
|
+
const own = snapshot.forms.filter((f) => !f.ignoredAt && (!project || f.projectId === project));
|
|
6499
|
+
const hits = own.filter((f) => f.name === name);
|
|
6500
|
+
const form = hits.length === 1 ? hits[0] : undefined;
|
|
6501
|
+
if (!form && hits.length > 1) {
|
|
6502
|
+
return unwired("BCMS_FORM_NAME_AMBIGUOUS", name, \`\${hits.length} forms in this project share that name; rename one in the Forms tab\`);
|
|
6503
|
+
}
|
|
6504
|
+
if (!form) {
|
|
6505
|
+
return unwired(
|
|
6506
|
+
"BCMS_FORM_NOT_FOUND",
|
|
6507
|
+
name,
|
|
6508
|
+
own.length === 0
|
|
6509
|
+
? "this project has no forms yet; the release of this build derives them"
|
|
6510
|
+
: "no form in this project has that name; a name is shared by every environment, so rename the form on each one",
|
|
6511
|
+
);
|
|
6512
|
+
}
|
|
6513
|
+
const api = env("BCMS_API_URL");
|
|
6514
|
+
if (!api) return unwired("BCMS_FORM_API_URL_MISSING", name, "BCMS_API_URL is not set, so it has nowhere to post");
|
|
6515
|
+
return { id: form.id, action: \`\${api.replace(/\\/$/, "")}/api/v1/forms/public/\${form.id}/submissions\` };
|
|
6516
|
+
}
|
|
6517
|
+
|
|
6518
|
+
/** Resolved once per name, so a form's three attributes print one warning, not three. */
|
|
6519
|
+
function wiring(name: string): Wiring | undefined {
|
|
6520
|
+
if (!resolved.has(name)) resolved.set(name, resolve(name));
|
|
6521
|
+
return resolved.get(name);
|
|
6522
|
+
}
|
|
6523
|
+
|
|
6524
|
+
export const bcmsFormId = (name: string): string | undefined => wiring(name)?.id;
|
|
6525
|
+
|
|
6526
|
+
export const bcmsFormAction = (name: string): string | undefined => wiring(name)?.action;
|
|
6527
|
+
|
|
6528
|
+
/** "post" only for a form that resolved: an unwired form keeps no method, like its action and id. */
|
|
6529
|
+
export const bcmsFormMethod = (name: string): "post" | undefined => (wiring(name) ? "post" : undefined);
|
|
6530
|
+
`;
|
|
6531
|
+
var idCall = (form) => `bcmsFormId(${JSON.stringify(form.name)})`;
|
|
6532
|
+
var actionCall = (form) => `bcmsFormAction(${JSON.stringify(form.name)})`;
|
|
6533
|
+
var methodCall = (form) => `bcmsFormMethod(${JSON.stringify(form.name)})`;
|
|
6534
|
+
function calledName(code, fn) {
|
|
6535
|
+
const m = code === null ? null : new RegExp(`^${fn}\\s*\\(\\s*(["'\`])((?:\\\\.|(?!\\1)[^\\\\])*)\\1\\s*,?\\s*\\)$`).exec(code);
|
|
6536
|
+
if (!m) return null;
|
|
6537
|
+
const [, quote, raw] = m;
|
|
6538
|
+
if (quote === "`" && raw.includes("${")) return null;
|
|
6539
|
+
try {
|
|
6540
|
+
return JSON.parse(`"${raw.replace(/\\(['`])/g, "$1").replace(/(?<!\\)"/g, '\\"')}"`);
|
|
6541
|
+
} catch {
|
|
6542
|
+
return null;
|
|
6543
|
+
}
|
|
6544
|
+
}
|
|
6410
6545
|
var CONTROLS = /* @__PURE__ */ new Set(["input", "select", "textarea"]);
|
|
6411
6546
|
var FORM_SCRIPT = `<script>
|
|
6412
6547
|
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:'';}
|
|
@@ -6468,6 +6603,10 @@ function* elementsIn(nodes) {
|
|
|
6468
6603
|
var childElements = (node) => node.children.flatMap(
|
|
6469
6604
|
(child) => child.type === "element" ? [child] : child.type === "expr" ? childElements(child) : []
|
|
6470
6605
|
);
|
|
6606
|
+
function exprOf(c, name) {
|
|
6607
|
+
const range = attrOf(c.el, name)?.expressionRange;
|
|
6608
|
+
return range ? c.content.slice(range.start + 1, range.end - 1).trim() : null;
|
|
6609
|
+
}
|
|
6471
6610
|
var LANDMARK_TAGS = /* @__PURE__ */ new Set(["footer", "header", "nav", "main", "aside", "dialog"]);
|
|
6472
6611
|
function landmarkOf(chain) {
|
|
6473
6612
|
for (let i = chain.length - 1; i >= 0; i--) {
|
|
@@ -6489,7 +6628,7 @@ function* formsIn(nodes, chain = []) {
|
|
|
6489
6628
|
}
|
|
6490
6629
|
}
|
|
6491
6630
|
var isComponentTag = (dialect, tag) => dialect !== "html" && /^[A-Z]/.test(tag);
|
|
6492
|
-
function candidateOf(file, dialect, form, imported, landmark, content) {
|
|
6631
|
+
function candidateOf(file, dialect, form, imported, landmark, content, importAt) {
|
|
6493
6632
|
const controls = /* @__PURE__ */ new Map();
|
|
6494
6633
|
const wrapper = /* @__PURE__ */ new Map();
|
|
6495
6634
|
const foreign = /* @__PURE__ */ new Set();
|
|
@@ -6530,7 +6669,7 @@ function candidateOf(file, dialect, form, imported, landmark, content) {
|
|
|
6530
6669
|
content.slice(attr.expressionRange.start, attr.expressionRange.end).replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*|"(?:[^"\\\n]|\\.)*"|'(?:[^'\\\n]|\\.)*'|`(?:[^`\\$]|\\.|\$(?!\{))*`/g, "")
|
|
6531
6670
|
);
|
|
6532
6671
|
const dynamic = all.some((control) => !foreign.has(control) && computed(attrOf(control, "name")));
|
|
6533
|
-
return { file, dialect, el: form, controls, wrapper, foreign, landmark, dynamic };
|
|
6672
|
+
return { file, dialect, el: form, controls, wrapper, foreign, landmark, dynamic, content, importAt };
|
|
6534
6673
|
}
|
|
6535
6674
|
async function importedControls(source, parsed, dialect, project, cache) {
|
|
6536
6675
|
const out = /* @__PURE__ */ new Map();
|
|
@@ -6563,11 +6702,20 @@ function wire(source, candidate, form) {
|
|
|
6563
6702
|
else if (attr.valueRange) source.overwrite(attr.valueRange.start, attr.valueRange.end, escapeAttr(value));
|
|
6564
6703
|
else source.overwrite(attr.range.start, attr.range.end, `${name}="${escapeAttr(value)}"`);
|
|
6565
6704
|
};
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6705
|
+
const setExpr = (name, code) => {
|
|
6706
|
+
const attr = attrOf(el, name);
|
|
6707
|
+
if (!attr) inserts.push(`${name}={${code}}`);
|
|
6708
|
+
else source.overwrite(attr.range.start, attr.range.end, `${name}={${code}}`);
|
|
6709
|
+
};
|
|
6710
|
+
const byName = BY_NAME.includes(dialect);
|
|
6711
|
+
if (byName) setExpr("action", actionCall(form));
|
|
6712
|
+
else set("action", form.submitUrl);
|
|
6713
|
+
if (byName) setExpr("method", methodCall(form));
|
|
6714
|
+
else set("method", "post");
|
|
6715
|
+
if (byName) setExpr("data-bcms-form", idCall(form));
|
|
6716
|
+
else set("data-bcms-form", form.id);
|
|
6569
6717
|
if (form.successMessage) set("data-bcms-form-success", form.successMessage);
|
|
6570
|
-
source.appendLeft(el.attrInsertAt, ` ${inserts.join(" ")}`);
|
|
6718
|
+
if (inserts.length > 0) source.appendLeft(el.attrInsertAt, ` ${inserts.join(" ")}`);
|
|
6571
6719
|
for (const inner of elementsIn(el.children)) {
|
|
6572
6720
|
for (const attr of inner.attrs) {
|
|
6573
6721
|
if (!DECLARED_BINDING_ATTR.test(attr.name)) continue;
|
|
@@ -6624,12 +6772,20 @@ async function wireForms(briefIn, sources, options_ = {}) {
|
|
|
6624
6772
|
}
|
|
6625
6773
|
const imported = dialect === "html" ? /* @__PURE__ */ new Map() : await importedControls(source, parsed, dialect, project, componentFiles);
|
|
6626
6774
|
for (const { el, landmark } of formsIn(parsed.roots)) {
|
|
6627
|
-
candidates.push(candidateOf(source.path, dialect, el, imported, landmark, source.content));
|
|
6775
|
+
candidates.push(candidateOf(source.path, dialect, el, imported, landmark, source.content, parsed.importAt));
|
|
6628
6776
|
}
|
|
6629
6777
|
}
|
|
6630
6778
|
const holds = (c, f) => f.fields.filter((field) => c.controls.has(field.key)).length;
|
|
6631
|
-
const
|
|
6632
|
-
const
|
|
6779
|
+
const byId = (c, f) => !attrOf(c.el, "data-bcms-form")?.expression && attrOf(c.el, "data-bcms-form")?.value === f.id;
|
|
6780
|
+
const programAction = (c) => !!attrOf(c.el, "action")?.expression && calledName(exprOf(c, "action"), "bcmsFormAction") === null;
|
|
6781
|
+
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);
|
|
6782
|
+
const wiredName = (c) => BY_NAME.includes(c.dialect) ? calledName(exprOf(c, "data-bcms-form"), "bcmsFormId") : null;
|
|
6783
|
+
const staticPost = (c) => {
|
|
6784
|
+
const method = attrOf(c.el, "method");
|
|
6785
|
+
return !!method && !method.expression && method.value.toLowerCase() === "post";
|
|
6786
|
+
};
|
|
6787
|
+
const claims = (c, f) => wiredTo(c, f) || byId(c, f);
|
|
6788
|
+
const covers2 = (c, f) => c.dynamic && claims(c, f) || f.fields.length > 0 && holds(c, f) === f.fields.length;
|
|
6633
6789
|
const samePath = (a, b) => a.replace(/\\/g, "/").replace(/^\.\//, "") === b.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
6634
6790
|
const options = new Map(forms.map((f) => [f.id, candidates.filter((c) => covers2(c, f))]));
|
|
6635
6791
|
const pinned = /* @__PURE__ */ new Map();
|
|
@@ -6673,7 +6829,14 @@ async function wireForms(briefIn, sources, options_ = {}) {
|
|
|
6673
6829
|
const wanted = /* @__PURE__ */ new Map();
|
|
6674
6830
|
for (const list of options.values()) for (const c of list) wanted.set(c, (wanted.get(c) ?? 0) + 1);
|
|
6675
6831
|
const matched = [];
|
|
6832
|
+
const methodUpgrades = [];
|
|
6833
|
+
const pendedByName = /* @__PURE__ */ new Set();
|
|
6676
6834
|
const pending = [];
|
|
6835
|
+
const existingHelper = sources.find((s) => samePath(s.path, FORMS_HELPER_PATH))?.content;
|
|
6836
|
+
const helperVersion = existingHelper === void 0 ? null : formsHelperVersion(existingHelper);
|
|
6837
|
+
const helperOk = existingHelper === void 0 || helperVersion !== null;
|
|
6838
|
+
const helperStale = existingHelper !== void 0 && helperVersion !== null && helperVersion <= FORMS_HELPER_VERSION && existingHelper !== FORMS_HELPER_SOURCE;
|
|
6839
|
+
let byNameOnSite = false;
|
|
6677
6840
|
const onSite = [];
|
|
6678
6841
|
let alreadyWired = 0;
|
|
6679
6842
|
const pend2 = (f, reason, file, message) => void pending.push({ id: f.id, name: f.name, reason, ...file ? { file } : {}, ...message ? { message } : {} });
|
|
@@ -6712,12 +6875,25 @@ async function wireForms(briefIn, sources, options_ = {}) {
|
|
|
6712
6875
|
continue;
|
|
6713
6876
|
}
|
|
6714
6877
|
const candidate = list[0];
|
|
6878
|
+
const byName = BY_NAME.includes(candidate.dialect);
|
|
6879
|
+
if (byName && forms.filter((f) => f.name === form.name).length > 1) {
|
|
6880
|
+
pend2(form, "FORM_NAME_AMBIGUOUS", candidate.file, `more than one CMS form is named "${form.name}"; rename one in the Forms tab.`);
|
|
6881
|
+
continue;
|
|
6882
|
+
}
|
|
6715
6883
|
if (wiredTo(candidate, form)) {
|
|
6716
6884
|
alreadyWired++;
|
|
6717
6885
|
onSite.push({ form, file: candidate.file });
|
|
6886
|
+
if (byName) byNameOnSite = true;
|
|
6887
|
+
if (wiredName(candidate) === form.name && staticPost(candidate) && helperOk) methodUpgrades.push({ form, candidate });
|
|
6888
|
+
continue;
|
|
6889
|
+
}
|
|
6890
|
+
const other = wiredName(candidate);
|
|
6891
|
+
if (other !== null) {
|
|
6892
|
+
pendedByName.add(candidate);
|
|
6893
|
+
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.`);
|
|
6718
6894
|
continue;
|
|
6719
6895
|
}
|
|
6720
|
-
if (
|
|
6896
|
+
if (programAction(candidate)) {
|
|
6721
6897
|
pend2(form, "ACTION_IS_EXPRESSION", candidate.file, "its action is an expression, so this run will not replace it.");
|
|
6722
6898
|
continue;
|
|
6723
6899
|
}
|
|
@@ -6729,18 +6905,46 @@ async function wireForms(briefIn, sources, options_ = {}) {
|
|
|
6729
6905
|
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.`);
|
|
6730
6906
|
continue;
|
|
6731
6907
|
}
|
|
6908
|
+
if (byName && !helperOk) {
|
|
6909
|
+
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.`);
|
|
6910
|
+
continue;
|
|
6911
|
+
}
|
|
6732
6912
|
matched.push({ form, candidate });
|
|
6733
6913
|
onSite.push({ form, file: candidate.file });
|
|
6914
|
+
if (byName) byNameOnSite = true;
|
|
6734
6915
|
}
|
|
6735
6916
|
const contents = new Map(sources.map((s) => [s.path, s.content]));
|
|
6736
6917
|
const byFile = /* @__PURE__ */ new Map();
|
|
6737
|
-
|
|
6918
|
+
const editsOf = (path) => {
|
|
6919
|
+
if (!byFile.has(path)) byFile.set(path, { wire: [], upgrade: [] });
|
|
6920
|
+
return byFile.get(path);
|
|
6921
|
+
};
|
|
6922
|
+
for (const m of matched) editsOf(m.candidate.file).wire.push(m);
|
|
6923
|
+
for (const m of methodUpgrades) editsOf(m.candidate.file).upgrade.push(m);
|
|
6738
6924
|
const files = [];
|
|
6739
|
-
for (const [path, list] of [...byFile].sort(([a], [b]) => a.localeCompare(b))) {
|
|
6925
|
+
for (const [path, { wire: list, upgrade }] of [...byFile].sort(([a], [b]) => a.localeCompare(b))) {
|
|
6740
6926
|
const content = contents.get(path);
|
|
6741
6927
|
const source = new MagicString4(content);
|
|
6742
6928
|
for (const { form, candidate } of list) wire(source, candidate, form);
|
|
6743
|
-
|
|
6929
|
+
for (const { form, candidate } of upgrade) {
|
|
6930
|
+
const method = attrOf(candidate.el, "method");
|
|
6931
|
+
source.overwrite(method.range.start, method.range.end, `method={${methodCall(form)}}`);
|
|
6932
|
+
}
|
|
6933
|
+
const first = (list[0] ?? upgrade[0]).candidate;
|
|
6934
|
+
if (BY_NAME.includes(first.dialect) && first.importAt) {
|
|
6935
|
+
const at = first.importAt;
|
|
6936
|
+
const helper = relativeImport(path, FORMS_HELPER_PATH.replace(/\.ts$/, ""));
|
|
6937
|
+
const names = !/\bbcmsFormId\b/.test(content) ? "bcmsFormAction, bcmsFormId, bcmsFormMethod" : !/\bbcmsFormMethod\b/.test(content) ? "bcmsFormMethod" : null;
|
|
6938
|
+
if (names) {
|
|
6939
|
+
const line = `import { ${names} } from "${helper}";`;
|
|
6940
|
+
if (at.wrap) source.appendLeft(at.at, `${at.wrap.before}${line}${at.wrap.after}`);
|
|
6941
|
+
else if (at.at === 0) source.appendLeft(0, `${line}
|
|
6942
|
+
`);
|
|
6943
|
+
else source.appendLeft(at.at, `
|
|
6944
|
+
${line}`);
|
|
6945
|
+
}
|
|
6946
|
+
}
|
|
6947
|
+
if (list.length > 0 && !content.includes("form[data-bcms-form]")) {
|
|
6744
6948
|
const last = list.reduce((a, b) => b.candidate.el.end > a.candidate.el.end ? b : a);
|
|
6745
6949
|
const indent = /[^\S\n]*$/.exec(content.slice(0, last.candidate.el.start))[0];
|
|
6746
6950
|
source.appendRight(last.candidate.el.end, `
|
|
@@ -6748,6 +6952,10 @@ ${indent}${scriptFor(last.candidate.dialect)}`);
|
|
|
6748
6952
|
}
|
|
6749
6953
|
files.push({ path, operation: "modify", content: source.toString() });
|
|
6750
6954
|
}
|
|
6955
|
+
const helperAdded = existingHelper === void 0 && (matched.some((m) => BY_NAME.includes(m.candidate.dialect)) || methodUpgrades.length > 0);
|
|
6956
|
+
if (helperAdded) files.push({ path: FORMS_HELPER_PATH, operation: "add", content: FORMS_HELPER_SOURCE });
|
|
6957
|
+
const helperUpgraded = helperStale && byNameOnSite;
|
|
6958
|
+
if (helperUpgraded) files.push({ path: FORMS_HELPER_PATH, operation: "modify", content: FORMS_HELPER_SOURCE });
|
|
6751
6959
|
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.`);
|
|
6752
6960
|
for (const { form, candidate } of matched) {
|
|
6753
6961
|
const tie = beat.get(candidate);
|
|
@@ -6763,6 +6971,13 @@ ${indent}${scriptFor(last.candidate.dialect)}`);
|
|
|
6763
6971
|
);
|
|
6764
6972
|
}
|
|
6765
6973
|
}
|
|
6974
|
+
for (const c of candidates) {
|
|
6975
|
+
const name = wiredName(c);
|
|
6976
|
+
if (name === null || pendedByName.has(c) || forms.some((f) => f.name === name)) continue;
|
|
6977
|
+
notes.push(
|
|
6978
|
+
`${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.`
|
|
6979
|
+
);
|
|
6980
|
+
}
|
|
6766
6981
|
for (const { form, candidate } of matched) {
|
|
6767
6982
|
const keys = form.fields.filter((field) => candidate.foreign.has(candidate.controls.get(field.key))).map((field) => field.key);
|
|
6768
6983
|
if (keys.length > 0) {
|
|
@@ -6780,8 +6995,8 @@ ${indent}${scriptFor(last.candidate.dialect)}`);
|
|
|
6780
6995
|
paths: { declared: 0, rewritten: 0, alreadyDeclared: 0, pending: [] },
|
|
6781
6996
|
occurrences: { located: 0, rewritten: 0 },
|
|
6782
6997
|
files: [],
|
|
6783
|
-
helpers: [],
|
|
6784
|
-
helperUpgraded
|
|
6998
|
+
helpers: helperAdded || helperUpgraded ? [FORMS_HELPER_PATH] : [],
|
|
6999
|
+
helperUpgraded,
|
|
6785
7000
|
dynamicBindings: [],
|
|
6786
7001
|
forms: assertForms(forms.length, {
|
|
6787
7002
|
wired: matched.length,
|
|
@@ -8114,6 +8329,7 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved, pathP
|
|
|
8114
8329
|
provided: [
|
|
8115
8330
|
"blockId",
|
|
8116
8331
|
"bind",
|
|
8332
|
+
"bindPaths",
|
|
8117
8333
|
"overrides",
|
|
8118
8334
|
"page",
|
|
8119
8335
|
fieldName,
|
|
@@ -8128,8 +8344,8 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved, pathP
|
|
|
8128
8344
|
const key = /^[A-Za-z_$][\w$]*$/.test(groupKey) ? groupKey : JSON.stringify(groupKey);
|
|
8129
8345
|
const carriedDeclarations = carry.declarations.length > 0 ? `${carry.declarations.join("\n")}
|
|
8130
8346
|
` : "";
|
|
8131
|
-
const copy = !snapshot ? "" : flat3 ? `const ${snapshot} = bcmsSectionPaths(page, bind, overrides, ${JSON.stringify(pathPrefix)}, ${JSON.stringify(pathMap)});` : `const ${snapshot} = { ${key}: bcmsSection(page, bind, overrides) };`;
|
|
8132
|
-
const field = bound > 0 ? `const ${fieldName} = ${fieldsName}({ blockId, bind, overrides }, ${JSON.stringify(pathPrefix)}${flat3 ? `, ${JSON.stringify(pathMap)}` : ""});` : "";
|
|
8347
|
+
const copy = !snapshot ? "" : flat3 ? `const ${snapshot} = bcmsSectionPaths(page, bind, overrides, ${JSON.stringify(pathPrefix)}, ${JSON.stringify(pathMap)}, bindPaths);` : `const ${snapshot} = { ${key}: bcmsSection(page, bind, overrides, bindPaths) };`;
|
|
8348
|
+
const field = bound > 0 ? `const ${fieldName} = ${fieldsName}({ blockId, bind, bindPaths, overrides }, ${JSON.stringify(pathPrefix)}${flat3 ? `, ${JSON.stringify(pathMap)}` : ""});` : "";
|
|
8133
8349
|
const declared = [copy, field].filter(Boolean).join("\n");
|
|
8134
8350
|
const styles = dialect === "astro" ? carriedStyles(content) : "";
|
|
8135
8351
|
if (dialect === "astro") {
|
|
@@ -8142,12 +8358,14 @@ interface Props {
|
|
|
8142
8358
|
blockId?: string;
|
|
8143
8359
|
/** The page field group this placement renders \u2014 Phase A keeps the copy where P2 put it. */
|
|
8144
8360
|
bind?: string;
|
|
8361
|
+
/** A locale copy's own field path for each prop, when its keys differ from this component's. */
|
|
8362
|
+
bindPaths?: Record<string, string>;
|
|
8145
8363
|
/** The instance's own copy, when an editor gave it any. Wins over the bound group. */
|
|
8146
8364
|
overrides?: Record<string, unknown>;
|
|
8147
8365
|
/** The page snapshot, so one component can serve every page whose group has this shape. */
|
|
8148
8366
|
page?: unknown;
|
|
8149
8367
|
}
|
|
8150
|
-
const { blockId, bind, overrides, page } = Astro.props;
|
|
8368
|
+
const { blockId, bind, bindPaths, overrides, page } = Astro.props;
|
|
8151
8369
|
${carriedDeclarations}${declared}
|
|
8152
8370
|
---
|
|
8153
8371
|
${markup}
|
|
@@ -8159,7 +8377,7 @@ ${styles}
|
|
|
8159
8377
|
${[...carried, `import { ${helpers.join(", ")} } from "${helperSpecifier}";`, `import type { BcmsSectionProps } from "${relativeImport(path, SECTIONS_LIB).replace(/\.tsx?$/, "")}";`].join("\n")}
|
|
8160
8378
|
${carriedDeclarations === "" ? "" : `
|
|
8161
8379
|
${carriedDeclarations}`}
|
|
8162
|
-
export default function ${resolved.name}({ blockId, bind, overrides, page }: BcmsSectionProps) {
|
|
8380
|
+
export default function ${resolved.name}({ blockId, bind, bindPaths, overrides, page }: BcmsSectionProps) {
|
|
8163
8381
|
${declared.split("\n").join("\n ")}
|
|
8164
8382
|
return (
|
|
8165
8383
|
${markup}
|
|
@@ -8521,6 +8739,7 @@ const isRecord = (v: unknown): v is Record<string, unknown> =>
|
|
|
8521
8739
|
export interface BcmsSectionProps {
|
|
8522
8740
|
blockId?: string;
|
|
8523
8741
|
bind?: string;
|
|
8742
|
+
bindPaths?: Record<string, string>;
|
|
8524
8743
|
overrides?: Record<string, unknown>;
|
|
8525
8744
|
page?: unknown;
|
|
8526
8745
|
}
|
|
@@ -8530,6 +8749,8 @@ export interface BcmsPlacement {
|
|
|
8530
8749
|
blockId?: string;
|
|
8531
8750
|
slug: string;
|
|
8532
8751
|
bind?: string;
|
|
8752
|
+
/** A locale copy's own field path for each prop, when its keys differ from the component's. */
|
|
8753
|
+
bindPaths?: Record<string, string>;
|
|
8533
8754
|
overrides?: Record<string, unknown>;
|
|
8534
8755
|
}
|
|
8535
8756
|
|
|
@@ -8590,6 +8811,7 @@ export function pageSections(pageSlug: string): BcmsPlacement[] {
|
|
|
8590
8811
|
blockId: typeof block.id === "string" ? block.id : undefined,
|
|
8591
8812
|
slug,
|
|
8592
8813
|
bind: typeof props.bind === "string" ? props.bind : undefined,
|
|
8814
|
+
bindPaths: isRecord(props.bindPaths) ? (props.bindPaths as Record<string, string>) : undefined,
|
|
8593
8815
|
overrides: isRecord(props.overrides) ? props.overrides : undefined,
|
|
8594
8816
|
});
|
|
8595
8817
|
}
|
|
@@ -8655,6 +8877,7 @@ const snapshot = pageSnapshot(page);
|
|
|
8655
8877
|
<Component
|
|
8656
8878
|
blockId={placement.blockId}
|
|
8657
8879
|
bind={placement.bind}
|
|
8880
|
+
bindPaths={placement.bindPaths}
|
|
8658
8881
|
overrides={placement.overrides}
|
|
8659
8882
|
page={snapshot}
|
|
8660
8883
|
/>
|
|
@@ -8677,6 +8900,7 @@ export default function Sections({ page }: { page: string }) {
|
|
|
8677
8900
|
key={placement.blockId ?? \`\${placement.slug}-\${i}\`}
|
|
8678
8901
|
blockId={placement.blockId}
|
|
8679
8902
|
bind={placement.bind}
|
|
8903
|
+
bindPaths={placement.bindPaths}
|
|
8680
8904
|
overrides={placement.overrides}
|
|
8681
8905
|
page={snapshot}
|
|
8682
8906
|
/>
|
|
@@ -9952,7 +10176,7 @@ function styleEdits(before, after) {
|
|
|
9952
10176
|
// package.json
|
|
9953
10177
|
var package_default = {
|
|
9954
10178
|
name: "@bettercms-ai/convert",
|
|
9955
|
-
version: "0.
|
|
10179
|
+
version: "0.20.0",
|
|
9956
10180
|
type: "module",
|
|
9957
10181
|
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.",
|
|
9958
10182
|
bin: {
|
|
@@ -9985,7 +10209,7 @@ var package_default = {
|
|
|
9985
10209
|
dependencies: {
|
|
9986
10210
|
"@astrojs/compiler": "^2.13.1",
|
|
9987
10211
|
"@babel/parser": "^7.29.2",
|
|
9988
|
-
"@bettercms-ai/types": "^1.
|
|
10212
|
+
"@bettercms-ai/types": "^1.25.0",
|
|
9989
10213
|
"@vue/compiler-sfc": "^3.5.0",
|
|
9990
10214
|
"magic-string": "^0.30.21",
|
|
9991
10215
|
parse5: "^7.3.0",
|