@bettercms-ai/convert 0.25.0 → 0.26.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 +14 -0
- package/dist/cli.js +126 -62
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +124 -60
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -589,6 +589,8 @@ interface AttrNode {
|
|
|
589
589
|
* that has an expression says where it starts, rather than leaving each reader to scan for it.
|
|
590
590
|
*/
|
|
591
591
|
expressionRange: Range | null;
|
|
592
|
+
/** The digits of `price={49}` — a number is copy a reader sees, so it is matched like a string. */
|
|
593
|
+
number?: string;
|
|
592
594
|
}
|
|
593
595
|
type Node = {
|
|
594
596
|
type: "element";
|
|
@@ -955,7 +957,7 @@ declare function attrsFor(path: string, kind: string, props?: AttrBinding[], sco
|
|
|
955
957
|
declare function propsAttribute(props: AttrBinding[]): string;
|
|
956
958
|
|
|
957
959
|
/** Bumped whenever the emitted source changes. `isKnownHelper` upgrades anything older. */
|
|
958
|
-
declare const HELPER_VERSION =
|
|
960
|
+
declare const HELPER_VERSION = 14;
|
|
959
961
|
/** Where each dialect's helper lives. Null = this dialect reads nothing at build time. */
|
|
960
962
|
declare const HELPER_PATH: Record<Dialect, string | null>;
|
|
961
963
|
/** The directory the snapshots live in, at the repository root. */
|
package/dist/index.js
CHANGED
|
@@ -2711,8 +2711,9 @@ function collectSites(roots, literals) {
|
|
|
2711
2711
|
}
|
|
2712
2712
|
const bindingsAttr = node.attrs.find((a) => a.name === "bcmsBindings")?.range;
|
|
2713
2713
|
for (const attr of node.attrs) {
|
|
2714
|
-
|
|
2715
|
-
|
|
2714
|
+
const digits = attr.expression && attr.expressionRange ? attr.number : void 0;
|
|
2715
|
+
if (attr.expression && digits === void 0 || !digits && !attr.valueRange || BCMS_ATTRS.includes(attr.name)) continue;
|
|
2716
|
+
const value = norm(digits ?? attr.value);
|
|
2716
2717
|
for (const literal of wanted) {
|
|
2717
2718
|
if (value !== literal) continue;
|
|
2718
2719
|
matched.add(literal);
|
|
@@ -2720,7 +2721,7 @@ function collectSites(roots, literals) {
|
|
|
2720
2721
|
file: "",
|
|
2721
2722
|
tag: node.tag,
|
|
2722
2723
|
attrInsertAt: node.attrInsertAt,
|
|
2723
|
-
range: attr.valueRange,
|
|
2724
|
+
range: digits === void 0 ? attr.valueRange : attr.expressionRange,
|
|
2724
2725
|
inner: node.inner,
|
|
2725
2726
|
// 🔴 A COMPONENT'S ATTRIBUTE IS A PROP, and a prop is a different conversion: the value
|
|
2726
2727
|
// is rendered in another file, by an element only that file knows. @see props.ts
|
|
@@ -2828,6 +2829,8 @@ function attrsOf2(open, content) {
|
|
|
2828
2829
|
const value = attr.value;
|
|
2829
2830
|
const range = { start: attr.start ?? 0, end: attr.end ?? 0 };
|
|
2830
2831
|
const literal = value?.type === "StringLiteral";
|
|
2832
|
+
const inside = value?.type === "JSXExpressionContainer" ? value.expression : null;
|
|
2833
|
+
const numeric = inside?.type === "NumericLiteral" ? String(inside.extra.raw) : "";
|
|
2831
2834
|
return [
|
|
2832
2835
|
{
|
|
2833
2836
|
name: nameOf(attr.name),
|
|
@@ -2836,7 +2839,8 @@ function attrsOf2(open, content) {
|
|
|
2836
2839
|
valueRange: literal ? { start: (value.start ?? 0) + 1, end: (value.end ?? 0) - 1 } : null,
|
|
2837
2840
|
expression: !literal,
|
|
2838
2841
|
range,
|
|
2839
|
-
expressionRange: value?.type === "JSXExpressionContainer" ? { start: value.start ?? 0, end: value.end ?? 0 } : null
|
|
2842
|
+
expressionRange: value?.type === "JSXExpressionContainer" ? { start: value.start ?? 0, end: value.end ?? 0 } : null,
|
|
2843
|
+
...numeric ? { number: numeric } : {}
|
|
2840
2844
|
}
|
|
2841
2845
|
];
|
|
2842
2846
|
});
|
|
@@ -3312,14 +3316,21 @@ function exportedNames(ast) {
|
|
|
3312
3316
|
});
|
|
3313
3317
|
return out;
|
|
3314
3318
|
}
|
|
3319
|
+
function printed(node) {
|
|
3320
|
+
if (node?.type === "StringLiteral") return [String(node.value)];
|
|
3321
|
+
if (node?.type === "NumericLiteral") return [String(node.value), String(node.extra?.raw ?? "")];
|
|
3322
|
+
const quasis = node?.type === "TemplateLiteral" ? node.quasis ?? [] : [];
|
|
3323
|
+
const expressions = node?.expressions ?? [];
|
|
3324
|
+
return quasis.length === 1 && expressions.length === 0 ? [String(quasis[0].value.cooked ?? "")] : [];
|
|
3325
|
+
}
|
|
3315
3326
|
function propertyFor(row, literal) {
|
|
3316
3327
|
if (row?.type !== "ObjectExpression") return null;
|
|
3317
3328
|
for (const property of row.properties ?? []) {
|
|
3318
3329
|
if (property.type !== "ObjectProperty" && property.type !== "Property") continue;
|
|
3319
3330
|
const key = property.key;
|
|
3320
3331
|
const value = property.value;
|
|
3321
|
-
if (
|
|
3322
|
-
if (
|
|
3332
|
+
if (key?.type !== "Identifier") continue;
|
|
3333
|
+
if (printed(value).some((text) => norm(text) === literal)) return String(key.name);
|
|
3323
3334
|
}
|
|
3324
3335
|
return null;
|
|
3325
3336
|
}
|
|
@@ -3393,7 +3404,7 @@ function stringLiterals(ast) {
|
|
|
3393
3404
|
if (node.type !== "VariableDeclarator") return;
|
|
3394
3405
|
const id = node.id;
|
|
3395
3406
|
const init = node.init;
|
|
3396
|
-
if (id?.type !== "Identifier" || init
|
|
3407
|
+
if (id?.type !== "Identifier" || !init || printed(init).length === 0) return;
|
|
3397
3408
|
out.set(String(id.name), init);
|
|
3398
3409
|
});
|
|
3399
3410
|
return out;
|
|
@@ -3424,7 +3435,7 @@ function scalarUses(content, roots, id) {
|
|
|
3424
3435
|
}
|
|
3425
3436
|
function bindScalar(ctx, member, literals, offset) {
|
|
3426
3437
|
const rule = DIALECT_RULES[ctx.dialect];
|
|
3427
|
-
const named = [...literals].filter(([, init2]) =>
|
|
3438
|
+
const named = [...literals].filter(([, init2]) => printed(init2).some((text) => norm(text) === member.literal));
|
|
3428
3439
|
if (named.length !== 1) return { splices: [], refusal: null };
|
|
3429
3440
|
const [id, init] = named[0];
|
|
3430
3441
|
const uses = scalarUses(ctx.content, ctx.parsed.roots, id);
|
|
@@ -3639,16 +3650,17 @@ function literalTable(init) {
|
|
|
3639
3650
|
const first = elements[0];
|
|
3640
3651
|
if (!first || elements.length !== (init.elements ?? []).length) return null;
|
|
3641
3652
|
if (elements.some((element) => element.type !== first.type)) return null;
|
|
3642
|
-
if (first.
|
|
3643
|
-
|
|
3653
|
+
if (printed(first).length > 0) {
|
|
3654
|
+
if (elements.some((element) => printed(element).length === 0)) return null;
|
|
3655
|
+
return { tuple: false, rows: elements.map((element) => [norm(printed(element)[0])]) };
|
|
3644
3656
|
}
|
|
3645
3657
|
if (first.type !== "ArrayExpression") return null;
|
|
3646
3658
|
const rows = [];
|
|
3647
3659
|
for (const element of elements) {
|
|
3648
3660
|
const inner = (element.elements ?? []).filter((x) => x !== null);
|
|
3649
|
-
if (inner.length === 0 || inner.some((x) => x.
|
|
3661
|
+
if (inner.length === 0 || inner.some((x) => printed(x).length === 0)) return null;
|
|
3650
3662
|
if (inner.length !== (element.elements ?? []).length) return null;
|
|
3651
|
-
rows.push(inner.map((x) => norm(
|
|
3663
|
+
rows.push(inner.map((x) => norm(printed(x)[0])));
|
|
3652
3664
|
}
|
|
3653
3665
|
if (new Set(rows.map((row) => row.length)).size !== 1) return null;
|
|
3654
3666
|
return { tuple: true, rows };
|
|
@@ -4187,7 +4199,7 @@ async function bindImages(ctx, targets) {
|
|
|
4187
4199
|
|
|
4188
4200
|
// src/helper.ts
|
|
4189
4201
|
import { createHash as createHash2 } from "crypto";
|
|
4190
|
-
var HELPER_VERSION =
|
|
4202
|
+
var HELPER_VERSION = 14;
|
|
4191
4203
|
var HEADER = "// @bettercms-ai/convert helper v";
|
|
4192
4204
|
var HELPER_PATH = Object.fromEntries(
|
|
4193
4205
|
Object.keys(DIALECT_RULES).map((d) => [d, DIALECT_RULES[d].helper])
|
|
@@ -4261,11 +4273,11 @@ function render(value: unknown): string | undefined {
|
|
|
4261
4273
|
*
|
|
4262
4274
|
* Nowhere else does anything change: with no variable (a build, \`astro dev\`, a browser, an edge
|
|
4263
4275
|
* runtime) every snapshot is the one the bundle imported, exactly as before. A file that cannot be
|
|
4264
|
-
* read keeps the last good copy
|
|
4276
|
+
* read keeps the last good copy. Its modification time is asked on EVERY read (a stat, microseconds;
|
|
4277
|
+
* the file is parsed again only when it changed), so a publish shows on the very next request.
|
|
4265
4278
|
*/
|
|
4266
|
-
type BcmsLiveContent = { stamp: number; bySlug: Map<string, unknown>; layout: unknown };
|
|
4279
|
+
type BcmsLiveContent = { helper: string; stamp: number; bySlug: Map<string, unknown>; entries: Record<string, unknown>; layout: unknown };
|
|
4267
4280
|
let bcmsLive: BcmsLiveContent | null = null;
|
|
4268
|
-
let bcmsLiveCheckedAt = 0;
|
|
4269
4281
|
|
|
4270
4282
|
function liveContent(): BcmsLiveContent | null {
|
|
4271
4283
|
const proc = (globalThis as {
|
|
@@ -4273,9 +4285,6 @@ function liveContent(): BcmsLiveContent | null {
|
|
|
4273
4285
|
}).process;
|
|
4274
4286
|
const file = proc?.env?.BCMS_CONTENT_FILE;
|
|
4275
4287
|
if (!file || typeof proc?.getBuiltinModule !== "function") return null;
|
|
4276
|
-
const now = Date.now();
|
|
4277
|
-
if (now - bcmsLiveCheckedAt < 1000) return bcmsLive;
|
|
4278
|
-
bcmsLiveCheckedAt = now;
|
|
4279
4288
|
try {
|
|
4280
4289
|
const fs = proc.getBuiltinModule("node:fs") as {
|
|
4281
4290
|
statSync(path: string): { mtimeMs: number };
|
|
@@ -4289,13 +4298,30 @@ function liveContent(): BcmsLiveContent | null {
|
|
|
4289
4298
|
for (const rows of Object.values(collections)) {
|
|
4290
4299
|
if (!Array.isArray(rows)) continue;
|
|
4291
4300
|
for (const row of rows) {
|
|
4292
|
-
|
|
4301
|
+
// A published row carries its values under \`data\` (the delivery API's shape); the snapshot a
|
|
4302
|
+
// template reads is the values themselves. Read flat, as before, where a row has none.
|
|
4303
|
+
if (isRecord(row) && typeof row.slug === "string" && !bySlug.has(row.slug)) {
|
|
4304
|
+
bySlug.set(row.slug, isRecord(row.data) ? { ...row.data, slug: row.slug } : row);
|
|
4305
|
+
}
|
|
4293
4306
|
}
|
|
4294
4307
|
}
|
|
4308
|
+
// A dynamic route reads \`page.entries[slug]\`: every published row, by its slug.
|
|
4309
|
+
const entries: Record<string, unknown> = Object.fromEntries(bySlug);
|
|
4310
|
+
for (const row of bySlug.values()) if (isRecord(row) && !("entries" in row)) row.entries = entries;
|
|
4295
4311
|
const pages = isRecord(snapshot) && Array.isArray(snapshot.pages) ? snapshot.pages : [];
|
|
4296
4312
|
const withLayout = pages.find((page: unknown) => isRecord(page) && isRecord(page.layout));
|
|
4297
|
-
|
|
4298
|
-
|
|
4313
|
+
// The site's Layout first; a page's own (its overrides resolved in) only where there is no other.
|
|
4314
|
+
const delivered = isRecord(snapshot) && isRecord(snapshot.layout) ? snapshot.layout : isRecord(withLayout) ? withLayout.layout : undefined;
|
|
4315
|
+
// A template addresses a Layout value as \`<section id>:<field id>\`; the published Layout holds
|
|
4316
|
+
// each Section's values under \`sections.<slug>.fields\`. Both spellings are answered.
|
|
4317
|
+
const layout: Record<string, unknown> | undefined = isRecord(delivered) ? { ...delivered } : undefined;
|
|
4318
|
+
for (const section of layout && isRecord(delivered) && isRecord(delivered.sections) ? Object.values(delivered.sections) : []) {
|
|
4319
|
+
if (!isRecord(section) || typeof section.id !== "string" || !isRecord(section.fields)) continue;
|
|
4320
|
+
for (const [field, value] of Object.entries(section.fields)) layout![section.id + ":" + field] = value;
|
|
4321
|
+
}
|
|
4322
|
+
// \`helper\` names this reader in the built bundle: the platform reads it to know the app shows a
|
|
4323
|
+
// publish on its next request, and an image BetterCMS copied beside it (@see bcmsImage).
|
|
4324
|
+
bcmsLive = { helper: "bcms-helper@${HELPER_VERSION}", stamp, bySlug, entries, layout };
|
|
4299
4325
|
} catch {
|
|
4300
4326
|
// Unreadable, or caught mid-write: keep serving the last good copy.
|
|
4301
4327
|
}
|
|
@@ -4307,7 +4333,10 @@ function live(page: unknown): unknown {
|
|
|
4307
4333
|
const content = liveContent();
|
|
4308
4334
|
if (!content) return page;
|
|
4309
4335
|
if (page === layoutSnapshot) return isRecord(content.layout) ? content.layout : page;
|
|
4310
|
-
if (isRecord(page) && typeof page.slug === "string")
|
|
4336
|
+
if (isRecord(page) && typeof page.slug === "string") {
|
|
4337
|
+
// A collection's page has no row of its own: its entries are the published ones all the same.
|
|
4338
|
+
return content.bySlug.get(page.slug) ?? ("entries" in page ? page : { ...page, entries: content.entries });
|
|
4339
|
+
}
|
|
4311
4340
|
return page;
|
|
4312
4341
|
}
|
|
4313
4342
|
|
|
@@ -4321,7 +4350,10 @@ export function bcms(page: unknown, path: string, fallback: string, slug?: strin
|
|
|
4321
4350
|
|
|
4322
4351
|
/** One field of the shared layout \u2014 the header and footer every route renders. */
|
|
4323
4352
|
export function bcmsLayout(path: string, fallback: string): string {
|
|
4324
|
-
|
|
4353
|
+
const layout = live(layoutSnapshot);
|
|
4354
|
+
// The whole address first: a field id may carry a dot of its own (\`derived.nav-cta\`).
|
|
4355
|
+
const whole = isRecord(layout) ? layout[path] : undefined;
|
|
4356
|
+
return render(whole !== undefined ? whole : readPath(layout, path)) ?? fallback;
|
|
4325
4357
|
}
|
|
4326
4358
|
|
|
4327
4359
|
/**
|
|
@@ -4398,13 +4430,24 @@ export function bcmsTuples(page: unknown, paths: string[][], fallback: string[][
|
|
|
4398
4430
|
* release write (\`writeImage\`) puts the editor's image on the \`data-bcms-field\` element, which
|
|
4399
4431
|
* is where every published value lands. A plain string fallback (\`<img src="/hero.png">\`) has
|
|
4400
4432
|
* no such rule and takes the CMS url directly.
|
|
4433
|
+
*
|
|
4434
|
+
* \u{1F534} ON A HOSTED SERVER THE EDITOR'S IMAGE IS A LOCAL FILE. A page the app renders per request has no
|
|
4435
|
+
* release write: what this returns is the page. BetterCMS copies a replaced image beside the app and
|
|
4436
|
+
* names the copy on the published value (\`local\`, with its size), and it is returned in the shape
|
|
4437
|
+
* of an import (\`{ src, width, height, format }\`), so \`<Image>\` optimises it like the site's own.
|
|
4438
|
+
* Absent (a build, or the site's own picture) the import stands.
|
|
4401
4439
|
*/
|
|
4402
4440
|
export function bcmsImage<T>(page: unknown, path: string, fallback: T, slug?: string): T | string {
|
|
4403
4441
|
page = live(page);
|
|
4404
|
-
if (fallback !== null && typeof fallback === "object") return fallback;
|
|
4405
4442
|
const entries = isRecord(page) ? page.entries : undefined;
|
|
4406
4443
|
const root = slug !== undefined && isRecord(entries) ? entries[slug] : page;
|
|
4407
|
-
|
|
4444
|
+
const value = readPath(root, path);
|
|
4445
|
+
if (fallback !== null && typeof fallback === "object") {
|
|
4446
|
+
if (!isRecord(value) || typeof value.local !== "string" || typeof value.width !== "number" || typeof value.height !== "number") return fallback;
|
|
4447
|
+
const format = /\\.([a-z0-9]+)$/i.exec(value.local)?.[1]?.toLowerCase() ?? "";
|
|
4448
|
+
return { src: value.local, width: value.width, height: value.height, format: format === "jpg" ? "jpeg" : format } as unknown as T;
|
|
4449
|
+
}
|
|
4450
|
+
return render(value) ?? fallback;
|
|
4408
4451
|
}
|
|
4409
4452
|
|
|
4410
4453
|
/**
|
|
@@ -4737,7 +4780,8 @@ var HISTORICAL = {
|
|
|
4737
4780
|
9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3",
|
|
4738
4781
|
10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910",
|
|
4739
4782
|
11: "ceb2a932d4c2761a5045ff35032e0b45130a0b5034547ff37579c72c9ce3594e",
|
|
4740
|
-
12: "10f71724484c84af12aa3eb90db44bf6acbf71fcf8ddff5b6361c1190ba16528"
|
|
4783
|
+
12: "10f71724484c84af12aa3eb90db44bf6acbf71fcf8ddff5b6361c1190ba16528",
|
|
4784
|
+
13: "cd8bdd74dbc944f2331503bdf8e995cb7f74f0d5b307d08aee4690e1695ae798"
|
|
4741
4785
|
},
|
|
4742
4786
|
"src/lib/bcms-content.ts": {
|
|
4743
4787
|
1: "ae72ae14d706943d481c880d70da5789b4816ba1ba319a10fbc63ded4d0b42f2",
|
|
@@ -4751,7 +4795,8 @@ var HISTORICAL = {
|
|
|
4751
4795
|
9: "eee9fb1635ae7d6f8f33676a1f390600ca24a62ee992775252273c73a011c494",
|
|
4752
4796
|
10: "35bb7c0e30f5c41fe68e52259ec4764de6de2734064fa0e1521f3338eef0a09e",
|
|
4753
4797
|
11: "818784f2088150d64d91b42eac944dc9503f104d32a0e7a18c753d2982a5adc0",
|
|
4754
|
-
12: "79dafc525a541feae55ada5dbe2ea966cac82acd741a4b41d61fe9e0cdfaa208"
|
|
4798
|
+
12: "79dafc525a541feae55ada5dbe2ea966cac82acd741a4b41d61fe9e0cdfaa208",
|
|
4799
|
+
13: "06ee70c9f0bb25e03f3930d2250da8dae172ade457f27ebba20382b6482ad3f4"
|
|
4755
4800
|
},
|
|
4756
4801
|
"composables/bcms-content.ts": {
|
|
4757
4802
|
1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
|
|
@@ -4765,7 +4810,8 @@ var HISTORICAL = {
|
|
|
4765
4810
|
9: "d6441db2ff4578c3c5d4b7d461f3374b89247ef6ba9e0d535791e840d803e4f3",
|
|
4766
4811
|
10: "ffcb59862972b15a67c5951e9f3b94ce5b8b50345e6aac2bfa5789cfc3136910",
|
|
4767
4812
|
11: "ceb2a932d4c2761a5045ff35032e0b45130a0b5034547ff37579c72c9ce3594e",
|
|
4768
|
-
12: "10f71724484c84af12aa3eb90db44bf6acbf71fcf8ddff5b6361c1190ba16528"
|
|
4813
|
+
12: "10f71724484c84af12aa3eb90db44bf6acbf71fcf8ddff5b6361c1190ba16528",
|
|
4814
|
+
13: "cd8bdd74dbc944f2331503bdf8e995cb7f74f0d5b307d08aee4690e1695ae798"
|
|
4769
4815
|
}
|
|
4770
4816
|
};
|
|
4771
4817
|
var sha256 = (value) => createHash2("sha256").update(value, "utf8").digest("hex");
|
|
@@ -5465,7 +5511,8 @@ function overlapping(rewrites) {
|
|
|
5465
5511
|
for (const a of rewrites) {
|
|
5466
5512
|
for (const b of rewrites) {
|
|
5467
5513
|
if (a.raw !== void 0 || b.raw !== void 0) continue;
|
|
5468
|
-
if (a
|
|
5514
|
+
if (a === b || a.key === b.key) continue;
|
|
5515
|
+
if (collide(a, b)) {
|
|
5469
5516
|
out.add(a);
|
|
5470
5517
|
out.add(b);
|
|
5471
5518
|
}
|
|
@@ -5473,6 +5520,16 @@ function overlapping(rewrites) {
|
|
|
5473
5520
|
}
|
|
5474
5521
|
return [...out];
|
|
5475
5522
|
}
|
|
5523
|
+
var writes = ({ site }) => [site.range, site.attrRange, site.where === "text" ? site.inner : null].filter((r) => r != null);
|
|
5524
|
+
function collide(a, b) {
|
|
5525
|
+
const inside = (at, spans) => spans.some((r) => r.start < at && at < r.end);
|
|
5526
|
+
const [wa, wb] = [writes(a), writes(b)];
|
|
5527
|
+
if (wa.some((x) => wb.some((y) => overlaps(x, y)))) return true;
|
|
5528
|
+
if (inside(a.site.attrInsertAt, wb) || inside(b.site.attrInsertAt, wa)) return true;
|
|
5529
|
+
const [fa, fb] = [field(a), field(b)];
|
|
5530
|
+
return a.site.attrInsertAt === b.site.attrInsertAt && !!fa && !!fb && fa !== fb;
|
|
5531
|
+
}
|
|
5532
|
+
var field = (r) => r.site.where === "prop" || r.site.where === "text" && !ownsSubtree(r.kind, r.site, r.inline) && r.site.mixed && r.site.textRange ? void 0 : declaration(r).plain[0];
|
|
5476
5533
|
function declaration(rewrite) {
|
|
5477
5534
|
const { site, path, kind, scope } = rewrite;
|
|
5478
5535
|
if (site.where === "attr" && kind !== "image") {
|
|
@@ -5487,8 +5544,8 @@ function rewriteFile(content, dialect, rewrites, imports, extra = []) {
|
|
|
5487
5544
|
const bindings = /* @__PURE__ */ new Map();
|
|
5488
5545
|
const addDeclaration = (at, add) => {
|
|
5489
5546
|
const held = attributes.get(at) ?? { plain: [], props: [] };
|
|
5490
|
-
held.plain.push(...add.plain);
|
|
5491
|
-
held.props.push(...add.props);
|
|
5547
|
+
held.plain.push(...add.plain.filter((text) => !held.plain.includes(text)));
|
|
5548
|
+
held.props.push(...add.props.filter((p) => !held.props.some((q) => JSON.stringify(q) === JSON.stringify(p))));
|
|
5492
5549
|
attributes.set(at, held);
|
|
5493
5550
|
};
|
|
5494
5551
|
for (const rewrite of rewrites) {
|
|
@@ -5919,7 +5976,8 @@ function attrsOf4(node, content) {
|
|
|
5919
5976
|
valueRange: quoted && rawStart >= 0 && attr.value !== "" ? { start: rawStart + (unquoted ? 0 : 1), end: rawStart + raw.length - (unquoted ? 0 : 1) } : null,
|
|
5920
5977
|
expression: !quoted,
|
|
5921
5978
|
range,
|
|
5922
|
-
expressionRange: !quoted && open >= 0 && close > open ? { start: open, end: close + 1 } : null
|
|
5979
|
+
expressionRange: !quoted && open >= 0 && close > open ? { start: open, end: close + 1 } : null,
|
|
5980
|
+
...attr.kind === "expression" && /^\d+(\.\d+)?$/.test(attr.value.trim()) ? { number: attr.value.trim() } : {}
|
|
5923
5981
|
}
|
|
5924
5982
|
];
|
|
5925
5983
|
});
|
|
@@ -6038,6 +6096,9 @@ function attrsOf5(node) {
|
|
|
6038
6096
|
return [{ name: `\0${name}`, value: "", valueRange: null, expression: true, expressionRange: null, range }];
|
|
6039
6097
|
}
|
|
6040
6098
|
const literal = literalValue(attr);
|
|
6099
|
+
const tag = attr.value;
|
|
6100
|
+
const raw = tag?.type === "ExpressionTag" ? tag.expression : void 0;
|
|
6101
|
+
const digits = typeof raw?.value === "number" ? String(raw.raw ?? "") : "";
|
|
6041
6102
|
return [
|
|
6042
6103
|
{
|
|
6043
6104
|
name,
|
|
@@ -6045,7 +6106,8 @@ function attrsOf5(node) {
|
|
|
6045
6106
|
valueRange: literal && literal.value !== "" ? literal.range : null,
|
|
6046
6107
|
expression: literal === null,
|
|
6047
6108
|
// Not read: svelte sections are DIALECT_UNSUPPORTED. @see componentize.ts
|
|
6048
|
-
expressionRange: null,
|
|
6109
|
+
expressionRange: digits ? { start: tag.start ?? 0, end: tag.end ?? 0 } : null,
|
|
6110
|
+
...digits ? { number: digits } : {},
|
|
6049
6111
|
range
|
|
6050
6112
|
}
|
|
6051
6113
|
];
|
|
@@ -6150,13 +6212,15 @@ var spanOf = (loc) => ({
|
|
|
6150
6212
|
function attrsOf6(node) {
|
|
6151
6213
|
return (node.props ?? []).flatMap((prop) => {
|
|
6152
6214
|
if (prop.type === DIRECTIVE && prop.name === "bind" && prop.arg?.content) {
|
|
6215
|
+
const digits = /^\d+(\.\d+)?$/.test(String(prop.exp?.content ?? "").trim()) ? String(prop.exp.content).trim() : "";
|
|
6153
6216
|
return [
|
|
6154
6217
|
{
|
|
6155
6218
|
name: prop.arg.content,
|
|
6156
6219
|
value: "",
|
|
6157
6220
|
valueRange: null,
|
|
6158
6221
|
expression: true,
|
|
6159
|
-
expressionRange: null,
|
|
6222
|
+
expressionRange: digits ? spanOf(prop.exp?.loc) : null,
|
|
6223
|
+
...digits ? { number: digits } : {},
|
|
6160
6224
|
range: spanOf(prop.loc)
|
|
6161
6225
|
}
|
|
6162
6226
|
];
|
|
@@ -6644,8 +6708,8 @@ function readPlan(value) {
|
|
|
6644
6708
|
check.optionalString(section.sectionType, `${at2}.sectionType`);
|
|
6645
6709
|
check.optionalString(section.copy, `${at2}.copy`);
|
|
6646
6710
|
if (section.fields !== void 0) {
|
|
6647
|
-
for (const [f,
|
|
6648
|
-
const shape = check.record(
|
|
6711
|
+
for (const [f, field2] of check.array(section.fields, `${at2}.fields`).entries()) {
|
|
6712
|
+
const shape = check.record(field2, `${at2}.fields[${f}]`);
|
|
6649
6713
|
check.optionalString(shape.key, `${at2}.fields[${f}].key`);
|
|
6650
6714
|
check.optionalString(shape.path, `${at2}.fields[${f}].path`);
|
|
6651
6715
|
}
|
|
@@ -6964,15 +7028,15 @@ function wire(source, candidate, form) {
|
|
|
6964
7028
|
source.remove(start, attr.range.end);
|
|
6965
7029
|
}
|
|
6966
7030
|
}
|
|
6967
|
-
for (const
|
|
6968
|
-
const control = candidate.controls.get(
|
|
7031
|
+
for (const field2 of form.fields) {
|
|
7032
|
+
const control = candidate.controls.get(field2.key);
|
|
6969
7033
|
if (candidate.foreign.has(control)) continue;
|
|
6970
7034
|
const host = candidate.wrapper.get(control) ?? control;
|
|
6971
|
-
if (!attrOf(host, "data-bcms-form-field")) source.appendLeft(host.attrInsertAt, ` data-bcms-form-field="${escapeAttr(
|
|
7035
|
+
if (!attrOf(host, "data-bcms-form-field")) source.appendLeft(host.attrInsertAt, ` data-bcms-form-field="${escapeAttr(field2.key)}"`);
|
|
6972
7036
|
const name = attrOf(control, "name");
|
|
6973
|
-
if (!name) source.appendLeft(control.attrInsertAt, ` name="${escapeAttr(
|
|
6974
|
-
else if (!name.expression && name.value !==
|
|
6975
|
-
source.overwrite(name.valueRange.start, name.valueRange.end, escapeAttr(
|
|
7037
|
+
if (!name) source.appendLeft(control.attrInsertAt, ` name="${escapeAttr(field2.key)}"`);
|
|
7038
|
+
else if (!name.expression && name.value !== field2.key && name.valueRange) {
|
|
7039
|
+
source.overwrite(name.valueRange.start, name.valueRange.end, escapeAttr(field2.key));
|
|
6976
7040
|
}
|
|
6977
7041
|
}
|
|
6978
7042
|
if (el.inner && !hasMessage(el)) {
|
|
@@ -7016,7 +7080,7 @@ async function wireForms(briefIn, sources, options_ = {}) {
|
|
|
7016
7080
|
candidates.push(candidateOf(source.path, dialect, el, imported, landmark, source.content, parsed.importAt));
|
|
7017
7081
|
}
|
|
7018
7082
|
}
|
|
7019
|
-
const holds = (c, f) => f.fields.filter((
|
|
7083
|
+
const holds = (c, f) => f.fields.filter((field2) => c.controls.has(field2.key)).length;
|
|
7020
7084
|
const byId = (c, f) => !attrOf(c.el, "data-bcms-form")?.expression && attrOf(c.el, "data-bcms-form")?.value === f.id;
|
|
7021
7085
|
const programAction = (c) => !!attrOf(c.el, "action")?.expression && calledName(exprOf(c, "action"), "bcmsFormAction") === null;
|
|
7022
7086
|
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);
|
|
@@ -7138,9 +7202,9 @@ async function wireForms(briefIn, sources, options_ = {}) {
|
|
|
7138
7202
|
pend2(form, "ACTION_IS_EXPRESSION", candidate.file, "its action is an expression, so this run will not replace it.");
|
|
7139
7203
|
continue;
|
|
7140
7204
|
}
|
|
7141
|
-
const misnamed = form.fields.find((
|
|
7142
|
-
const attr = attrOf(candidate.controls.get(
|
|
7143
|
-
return !!attr && attr.value !==
|
|
7205
|
+
const misnamed = form.fields.find((field2) => {
|
|
7206
|
+
const attr = attrOf(candidate.controls.get(field2.key), "name");
|
|
7207
|
+
return !!attr && attr.value !== field2.key && (attr.expression || !attr.valueRange);
|
|
7144
7208
|
});
|
|
7145
7209
|
if (misnamed) {
|
|
7146
7210
|
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.`);
|
|
@@ -7220,7 +7284,7 @@ ${indent}${scriptFor(last.candidate.dialect)}`);
|
|
|
7220
7284
|
);
|
|
7221
7285
|
}
|
|
7222
7286
|
for (const { form, candidate } of matched) {
|
|
7223
|
-
const keys = form.fields.filter((
|
|
7287
|
+
const keys = form.fields.filter((field2) => candidate.foreign.has(candidate.controls.get(field2.key))).map((field2) => field2.key);
|
|
7224
7288
|
if (keys.length > 0) {
|
|
7225
7289
|
notes.push(
|
|
7226
7290
|
`form ${form.name}: ${keys.join(", ")} matched a control inside an imported component, so no data-bcms-form-field marker was written for ${keys.length === 1 ? "it" : "them"}.`
|
|
@@ -8487,7 +8551,7 @@ var inSection = (section, path) => {
|
|
|
8487
8551
|
const target = rowless(path);
|
|
8488
8552
|
return fieldPaths(section).some((fieldPath) => rowless(fieldPath) === target);
|
|
8489
8553
|
};
|
|
8490
|
-
var fieldPaths = (section) => (section.fields ?? []).map((
|
|
8554
|
+
var fieldPaths = (section) => (section.fields ?? []).map((field2) => field2.path ?? `${section.groupKey}.${field2.key ?? ""}`);
|
|
8491
8555
|
function elementIndex(roots) {
|
|
8492
8556
|
const out = /* @__PURE__ */ new Map();
|
|
8493
8557
|
const visit = (node, chain) => {
|
|
@@ -8645,8 +8709,8 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved, pathP
|
|
|
8645
8709
|
const carriedDeclarations = carry.declarations.length > 0 ? `${carry.declarations.join("\n")}
|
|
8646
8710
|
` : "";
|
|
8647
8711
|
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) };`;
|
|
8648
|
-
const
|
|
8649
|
-
const declared = [copy,
|
|
8712
|
+
const field2 = bound > 0 ? `const ${fieldName} = ${fieldsName}({ blockId, bind, bindPaths, overrides }, ${JSON.stringify(pathPrefix)}${flat3 ? `, ${JSON.stringify(pathMap)}` : ""});` : "";
|
|
8713
|
+
const declared = [copy, field2].filter(Boolean).join("\n");
|
|
8650
8714
|
const styles = dialect === "astro" ? carriedStyles(content) : "";
|
|
8651
8715
|
if (dialect === "astro") {
|
|
8652
8716
|
return { free: [], duplicates: [], unconvertible: [], content: `---
|
|
@@ -8959,8 +9023,8 @@ function propPrefix(section) {
|
|
|
8959
9023
|
}
|
|
8960
9024
|
function exactPrefix(section) {
|
|
8961
9025
|
const prefixes = /* @__PURE__ */ new Set();
|
|
8962
|
-
for (const
|
|
8963
|
-
const { path, key } =
|
|
9026
|
+
for (const field2 of section.fields ?? []) {
|
|
9027
|
+
const { path, key } = field2;
|
|
8964
9028
|
if (typeof path !== "string" || typeof key !== "string") continue;
|
|
8965
9029
|
if (!path.endsWith(key)) return null;
|
|
8966
9030
|
prefixes.add(path.slice(0, path.length - key.length));
|
|
@@ -8972,11 +9036,11 @@ var foldKey = (remainder) => remainder.replace(/\[(\d+)\]/g, "_$1").replace(/[^a
|
|
|
8972
9036
|
function flatPrefix(section) {
|
|
8973
9037
|
const prefixes = /* @__PURE__ */ new Set();
|
|
8974
9038
|
let informative = false;
|
|
8975
|
-
for (const
|
|
8976
|
-
const { path, key } =
|
|
9039
|
+
for (const field2 of section.fields ?? []) {
|
|
9040
|
+
const { path, key } = field2;
|
|
8977
9041
|
if (typeof path !== "string" || typeof key !== "string") continue;
|
|
8978
9042
|
informative = true;
|
|
8979
|
-
const prefix = ["-", "_"].map((sep) => `${section.groupKey}${sep}`).find((candidate) => path.startsWith(candidate) && (typeof
|
|
9043
|
+
const prefix = ["-", "_"].map((sep) => `${section.groupKey}${sep}`).find((candidate) => path.startsWith(candidate) && (typeof field2.overrideKey === "string" || foldKey(path.slice(candidate.length)) === key));
|
|
8980
9044
|
if (!prefix) return null;
|
|
8981
9045
|
prefixes.add(prefix);
|
|
8982
9046
|
}
|
|
@@ -8985,15 +9049,15 @@ function flatPrefix(section) {
|
|
|
8985
9049
|
function flatPathMap(section, prefix) {
|
|
8986
9050
|
const scalars = {};
|
|
8987
9051
|
const tables = {};
|
|
8988
|
-
for (const
|
|
8989
|
-
const { path, key } =
|
|
9052
|
+
for (const field2 of section.fields ?? []) {
|
|
9053
|
+
const { path, key } = field2;
|
|
8990
9054
|
if (typeof path !== "string" || typeof key !== "string" || !path.startsWith(prefix)) continue;
|
|
8991
9055
|
const row = /^([^[]+)\[\d+\]\.(.+)$/.exec(path);
|
|
8992
9056
|
if (!row) {
|
|
8993
|
-
scalars[path] =
|
|
9057
|
+
scalars[path] = field2.overrideKey ?? key;
|
|
8994
9058
|
continue;
|
|
8995
9059
|
}
|
|
8996
|
-
const address = typeof
|
|
9060
|
+
const address = typeof field2.overrideKey === "string" ? /^([^[]+)\[\d+\]\.(.+)$/.exec(field2.overrideKey) : null;
|
|
8997
9061
|
if (!address) return null;
|
|
8998
9062
|
const table = tables[row[1]] ??= { key: address[1], columns: {} };
|
|
8999
9063
|
if (table.key !== address[1]) return null;
|