@bettercms-ai/convert 0.3.0 → 0.5.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 +260 -0
- package/README.md +109 -16
- package/dist/cli.js +904 -110
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +96 -6
- package/dist/index.js +889 -106
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import
|
|
2
|
+
import MagicString5 from "magic-string";
|
|
3
3
|
|
|
4
4
|
// src/brief.ts
|
|
5
5
|
import { createHash } from "crypto";
|
|
@@ -21,6 +21,21 @@ var BY_EXTENSION = {
|
|
|
21
21
|
".svelte": "svelte",
|
|
22
22
|
".vue": "vue"
|
|
23
23
|
};
|
|
24
|
+
var TOLERANT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
25
|
+
".md",
|
|
26
|
+
".mdx",
|
|
27
|
+
".njk",
|
|
28
|
+
".hbs",
|
|
29
|
+
".ejs",
|
|
30
|
+
".liquid",
|
|
31
|
+
".erb",
|
|
32
|
+
".twig"
|
|
33
|
+
]);
|
|
34
|
+
function isTolerantDialect(path) {
|
|
35
|
+
const lower = path.toLowerCase();
|
|
36
|
+
const at = lower.lastIndexOf(".");
|
|
37
|
+
return at >= 0 && TOLERANT_EXTENSIONS.has(lower.slice(at));
|
|
38
|
+
}
|
|
24
39
|
function dialectOf(path, _content) {
|
|
25
40
|
const lower = path.toLowerCase();
|
|
26
41
|
const at = lower.lastIndexOf(".");
|
|
@@ -128,7 +143,7 @@ function propsAttribute(props) {
|
|
|
128
143
|
|
|
129
144
|
// src/helper.ts
|
|
130
145
|
import { createHash as createHash2 } from "crypto";
|
|
131
|
-
var HELPER_VERSION =
|
|
146
|
+
var HELPER_VERSION = 4;
|
|
132
147
|
var HEADER = "// @bettercms-ai/convert helper v";
|
|
133
148
|
var HELPER_PATH = Object.fromEntries(
|
|
134
149
|
Object.keys(DIALECT_RULES).map((d) => [d, DIALECT_RULES[d].helper])
|
|
@@ -211,32 +226,97 @@ export function bcmsRows<T>(page: unknown, path: string, fallback: T[], slug?: s
|
|
|
211
226
|
/**
|
|
212
227
|
* ONE SECTION's copy, for a component extracted by \`--componentize\`.
|
|
213
228
|
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
229
|
+
* OVERRIDES FIRST, PER KEY: \`overrides[key] ?? group[key]\`. An instance that owns its copy
|
|
230
|
+
* (Phase B) carries an override for every prop it declares, so the group underneath it is never
|
|
231
|
+
* read; a placement still bound to a page field group (Phase A) carries none, so the group is all
|
|
232
|
+
* there is. The interesting case is the one in between \u2014 a section half moved \u2014 where a key the
|
|
233
|
+
* instance does not hold has to keep rendering the page's value rather than nothing.
|
|
234
|
+
*
|
|
235
|
+
* \u{1F534} \`??\`, NOT \`||\`. An empty string is a value an editor typed, and treating it as absent put
|
|
236
|
+
* the page's old copy back into a heading somebody had deliberately cleared.
|
|
237
|
+
*
|
|
238
|
+
* With neither an override nor a group, the answer is \`{}\` and every \`bcms(...)\` below renders
|
|
239
|
+
* the fallback the template shipped with \u2014 which is why a section renders correctly against the
|
|
240
|
+
* committed \`{}\` stub.
|
|
218
241
|
*/
|
|
219
242
|
export function bcmsSection(page: unknown, bind?: string, overrides?: unknown): Record<string, unknown> {
|
|
220
|
-
// \u{1F534} AN EMPTY OBJECT IS AN ANSWER. \`overrides: {}\` is an instance an editor cleared, and
|
|
221
|
-
// "\`??\` unless it happens to be empty" quietly put the page's copy back into it.
|
|
222
|
-
if (isRecord(overrides)) return overrides;
|
|
223
243
|
const group = bind ? readPath(page, bind) : undefined;
|
|
224
|
-
|
|
244
|
+
const base: Record<string, unknown> = isRecord(group) ? { ...group } : {};
|
|
245
|
+
if (!isRecord(overrides)) return base;
|
|
246
|
+
for (const key of Object.keys(overrides)) {
|
|
247
|
+
const value = overrides[key];
|
|
248
|
+
if (value === undefined || value === null) continue;
|
|
249
|
+
/**
|
|
250
|
+
* \u{1F534} A TABLE PROP IS STORED AS \`{ rows: [...] }\` AND READ AS A LIST. That is the shape the
|
|
251
|
+
* dashboard edits a repeater in; handing it to \`bcmsRows\` unchanged is not an array, so the
|
|
252
|
+
* helper fell back to the rows the template shipped with and the editor's work never showed.
|
|
253
|
+
*/
|
|
254
|
+
base[key] = isRecord(value) && Array.isArray(value.rows) ? value.rows : value;
|
|
255
|
+
}
|
|
256
|
+
return base;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* WHICH ADDRESS one bound element declares \u2014 decided at RENDER time, from the placement.
|
|
261
|
+
*
|
|
262
|
+
* An element carries exactly ONE \`data-bcms-field\`, and which lane it names depends on where its
|
|
263
|
+
* copy lives. A placement that owns its copy declares the BLOCK lane \u2014
|
|
264
|
+
* \`<blockId>__overrides.<propKey>\` \u2014 because that is the address the editor commits an instance's
|
|
265
|
+
* own value to. A placement bound to a page field group declares the PAGE path, which is what the
|
|
266
|
+
* page form and the coverage meter already know.
|
|
267
|
+
*
|
|
268
|
+
* \u{1F534} THE BOUND ADDRESS IS RE-BASED ONTO **THIS** PLACEMENT'S GROUP. The markup was cut from ONE
|
|
269
|
+
* page and carries that page's paths (\`hero.title\`); the whole point of \`shapeHash\` is that a
|
|
270
|
+
* second page renders the same component bound to a group of its own (\`bind="landingHero"\`).
|
|
271
|
+
* Handing the embedded path through unchanged declared \`hero.title\` on a page whose field is
|
|
272
|
+
* \`landingHero.title\` \u2014 an address that page does not have, so the editor wrote into nothing and
|
|
273
|
+
* the meter counted a binding that was never there. The prefix the markup was cut with comes out,
|
|
274
|
+
* the placement's own group goes in.
|
|
275
|
+
*
|
|
276
|
+
* \u{1F534} THE SAME EXTRACTED FILE SERVES BOTH LANES. The choice is a function of the props, never of
|
|
277
|
+
* how the codemod ran: one component renders a Phase A placement on one page and a Phase B
|
|
278
|
+
* instance on the next, and a repository does not have to be re-cut when its copy moves.
|
|
279
|
+
*/
|
|
280
|
+
export function bcmsFields(
|
|
281
|
+
placement: { blockId?: string; bind?: string; overrides?: unknown },
|
|
282
|
+
pathPrefix: string,
|
|
283
|
+
): (path: string) => string {
|
|
284
|
+
const id = placement.blockId;
|
|
285
|
+
const own = typeof id === "string" && id !== "" && !placement.bind && isRecord(placement.overrides);
|
|
286
|
+
/**
|
|
287
|
+
* What replaces the prefix the markup was cut with, or null for "leave every path alone".
|
|
288
|
+
*
|
|
289
|
+
* Null covers a placement with neither a bind nor an instance \u2014 a shipped fallback, or a page
|
|
290
|
+
* nobody has componentized yet \u2014 and a bound placement in a component whose prefix is EMPTY,
|
|
291
|
+
* where the plan said the prop key IS the whole page path: there is no group segment to swap,
|
|
292
|
+
* so prepending one would invent an address rather than correct it.
|
|
293
|
+
*/
|
|
294
|
+
const lane = own ? \`\${id}__overrides.\` : placement.bind && pathPrefix ? \`\${placement.bind}.\` : null;
|
|
295
|
+
return (path: string): string => {
|
|
296
|
+
// A path outside the group this section was cut from is not one this placement re-addresses:
|
|
297
|
+
// it names a key the instance does not hold and a group this placement is not bound to.
|
|
298
|
+
// (An EMPTY prefix is a plan saying the prop key IS the page path, and matches everything.)
|
|
299
|
+
if (lane === null || !path.startsWith(pathPrefix)) return path;
|
|
300
|
+
return lane + path.slice(pathPrefix.length);
|
|
301
|
+
};
|
|
225
302
|
}
|
|
226
303
|
`;
|
|
227
304
|
}
|
|
228
305
|
var HISTORICAL = {
|
|
229
306
|
"src/bcms-content.ts": {
|
|
230
307
|
1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
|
|
231
|
-
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0"
|
|
308
|
+
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0",
|
|
309
|
+
3: "d4305b2983adcb456ef782d35ea9d083a04465c2b68baa0d44a084dfa01a9227"
|
|
232
310
|
},
|
|
233
311
|
"src/lib/bcms-content.ts": {
|
|
234
312
|
1: "ae72ae14d706943d481c880d70da5789b4816ba1ba319a10fbc63ded4d0b42f2",
|
|
235
|
-
2: "a18731eb42f7db55dac0591c45f873758cde8e103fefcd6e5caf67936ae6a002"
|
|
313
|
+
2: "a18731eb42f7db55dac0591c45f873758cde8e103fefcd6e5caf67936ae6a002",
|
|
314
|
+
3: "0be9c73160361882a038552bc02673c14ed532287823dabaafe36179bc55ed7e"
|
|
236
315
|
},
|
|
237
316
|
"composables/bcms-content.ts": {
|
|
238
317
|
1: "c272412532efed7db64b6cf313b2bed4291f9951430412289c8a588b7f565407",
|
|
239
|
-
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0"
|
|
318
|
+
2: "51e2ae65ddd86bf43d14e29b3865e3f16e45f72069692d1fea57e1f986a894f0",
|
|
319
|
+
3: "d4305b2983adcb456ef782d35ea9d083a04465c2b68baa0d44a084dfa01a9227"
|
|
240
320
|
}
|
|
241
321
|
};
|
|
242
322
|
var sha256 = (value) => createHash2("sha256").update(value, "utf8").digest("hex");
|
|
@@ -4196,6 +4276,7 @@ function importLines(imports) {
|
|
|
4196
4276
|
}
|
|
4197
4277
|
|
|
4198
4278
|
// src/receipt.ts
|
|
4279
|
+
var CANVAS_REASONS = /* @__PURE__ */ new Set(["CANVAS_BRIDGE_MANUAL", "SSR_DRAFT_ROUTE"]);
|
|
4199
4280
|
var ReceiptInvariantError = class extends Error {
|
|
4200
4281
|
code = "RECEIPT_INVARIANT";
|
|
4201
4282
|
constructor(message) {
|
|
@@ -4205,10 +4286,11 @@ var ReceiptInvariantError = class extends Error {
|
|
|
4205
4286
|
};
|
|
4206
4287
|
function assertReceipt(receipt) {
|
|
4207
4288
|
const { declared, rewritten, alreadyDeclared, pending } = receipt.paths;
|
|
4208
|
-
const
|
|
4289
|
+
const paths = pending.filter((p) => !CANVAS_REASONS.has(p.reason));
|
|
4290
|
+
const sum = rewritten + alreadyDeclared + paths.length;
|
|
4209
4291
|
if (sum !== declared) {
|
|
4210
4292
|
throw new ReceiptInvariantError(
|
|
4211
|
-
`The receipt does not account for every path: ${declared} declared but ${rewritten} rewritten + ${alreadyDeclared} already declared + ${
|
|
4293
|
+
`The receipt does not account for every path: ${declared} declared but ${rewritten} rewritten + ${alreadyDeclared} already declared + ${paths.length} pending = ${sum}.`
|
|
4212
4294
|
);
|
|
4213
4295
|
}
|
|
4214
4296
|
if (receipt.occurrences.rewritten > receipt.occurrences.located) {
|
|
@@ -4720,6 +4802,218 @@ var findSites = async (dialect, content, literals) => {
|
|
|
4720
4802
|
return parsed.error ? { sites: [], error: parsed.error } : { sites: collectSites(parsed.roots, literals), importAt: parsed.importAt };
|
|
4721
4803
|
};
|
|
4722
4804
|
|
|
4805
|
+
// src/canvas.ts
|
|
4806
|
+
import MagicString3 from "magic-string";
|
|
4807
|
+
var SSR_DRAFT_RECIPE = "Astro renders this site at build time, so there is no server to answer the editor's draft request. Either mount <BcmsLiveBlocks client:load> from @bettercms-ai/astro on the pages whose body is a block tree, or switch the preview routes to `output: 'server'` with the node adapter, mount @bettercms-ai/astro's draft routes, set BCMS_DRAFT_SECRET, redeploy, and switch the project to 'Run as a server app'.";
|
|
4808
|
+
var isNode = (value) => !!value && typeof value === "object" && typeof value.type === "string";
|
|
4809
|
+
var FUNCTIONS3 = /* @__PURE__ */ new Set([
|
|
4810
|
+
"FunctionDeclaration",
|
|
4811
|
+
"FunctionExpression",
|
|
4812
|
+
"ArrowFunctionExpression",
|
|
4813
|
+
"ObjectMethod",
|
|
4814
|
+
"ClassMethod"
|
|
4815
|
+
]);
|
|
4816
|
+
function patternNames2(node, out) {
|
|
4817
|
+
if (!isNode(node)) return;
|
|
4818
|
+
switch (node.type) {
|
|
4819
|
+
case "Identifier":
|
|
4820
|
+
out.add(String(node.name));
|
|
4821
|
+
return;
|
|
4822
|
+
case "ObjectPattern":
|
|
4823
|
+
for (const p of node.properties ?? [])
|
|
4824
|
+
patternNames2(p.type === "RestElement" ? p.argument : p.value, out);
|
|
4825
|
+
return;
|
|
4826
|
+
case "ArrayPattern":
|
|
4827
|
+
for (const el of node.elements ?? []) patternNames2(el, out);
|
|
4828
|
+
return;
|
|
4829
|
+
case "AssignmentPattern":
|
|
4830
|
+
patternNames2(node.left, out);
|
|
4831
|
+
return;
|
|
4832
|
+
case "RestElement":
|
|
4833
|
+
patternNames2(node.argument, out);
|
|
4834
|
+
return;
|
|
4835
|
+
default:
|
|
4836
|
+
return;
|
|
4837
|
+
}
|
|
4838
|
+
}
|
|
4839
|
+
function statementNames2(statements, out) {
|
|
4840
|
+
for (const statement of statements ?? []) {
|
|
4841
|
+
if (!isNode(statement)) continue;
|
|
4842
|
+
const node = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
|
|
4843
|
+
if (!isNode(node)) continue;
|
|
4844
|
+
if (node.type === "VariableDeclaration")
|
|
4845
|
+
for (const d of node.declarations ?? []) patternNames2(d.id, out);
|
|
4846
|
+
else if (node.type === "FunctionDeclaration" || node.type === "ClassDeclaration") patternNames2(node.id, out);
|
|
4847
|
+
else if (node.type === "ImportDeclaration")
|
|
4848
|
+
for (const specifier of node.specifiers ?? []) patternNames2(specifier.local, out);
|
|
4849
|
+
}
|
|
4850
|
+
}
|
|
4851
|
+
function functionBindings(fn) {
|
|
4852
|
+
const out = /* @__PURE__ */ new Set();
|
|
4853
|
+
for (const param of fn.params ?? []) patternNames2(param, out);
|
|
4854
|
+
const body = fn.body;
|
|
4855
|
+
if (body?.type === "BlockStatement") statementNames2(body.body, out);
|
|
4856
|
+
return out;
|
|
4857
|
+
}
|
|
4858
|
+
function stringLiterals(node, out) {
|
|
4859
|
+
if (!isNode(node)) {
|
|
4860
|
+
if (Array.isArray(node)) for (const child of node) stringLiterals(child, out);
|
|
4861
|
+
return;
|
|
4862
|
+
}
|
|
4863
|
+
if (FUNCTIONS3.has(node.type)) return;
|
|
4864
|
+
if (node.type === "StringLiteral" && typeof node.value === "string") out.add(node.value);
|
|
4865
|
+
for (const key of Object.keys(node)) {
|
|
4866
|
+
if (key === "loc" || key === "leadingComments" || key === "trailingComments") continue;
|
|
4867
|
+
stringLiterals(node[key], out);
|
|
4868
|
+
}
|
|
4869
|
+
}
|
|
4870
|
+
function usages(ast, local) {
|
|
4871
|
+
const found = [];
|
|
4872
|
+
const moduleScope2 = /* @__PURE__ */ new Set();
|
|
4873
|
+
statementNames2(ast?.program?.body, moduleScope2);
|
|
4874
|
+
const walk = (node, scope, literals) => {
|
|
4875
|
+
if (!isNode(node)) {
|
|
4876
|
+
if (Array.isArray(node)) for (const child of node) walk(child, scope, literals);
|
|
4877
|
+
return;
|
|
4878
|
+
}
|
|
4879
|
+
let nextScope = scope;
|
|
4880
|
+
let nextLiterals = literals;
|
|
4881
|
+
if (FUNCTIONS3.has(node.type)) {
|
|
4882
|
+
nextScope = /* @__PURE__ */ new Set([...scope, ...functionBindings(node)]);
|
|
4883
|
+
nextLiterals = /* @__PURE__ */ new Set();
|
|
4884
|
+
stringLiterals(node.body, nextLiterals);
|
|
4885
|
+
}
|
|
4886
|
+
if (node.type === "JSXElement") {
|
|
4887
|
+
const name = node.openingElement?.name;
|
|
4888
|
+
if (name?.type === "JSXIdentifier" && name.name === local)
|
|
4889
|
+
found.push({ element: node, scope: nextScope, literals: nextLiterals });
|
|
4890
|
+
}
|
|
4891
|
+
for (const key of Object.keys(node)) {
|
|
4892
|
+
if (key === "loc" || key === "leadingComments" || key === "trailingComments") continue;
|
|
4893
|
+
walk(node[key], nextScope, nextLiterals);
|
|
4894
|
+
}
|
|
4895
|
+
};
|
|
4896
|
+
walk(ast?.program ?? ast, moduleScope2, /* @__PURE__ */ new Set());
|
|
4897
|
+
return found;
|
|
4898
|
+
}
|
|
4899
|
+
function bridgedAlready(ast) {
|
|
4900
|
+
const body = ast?.program?.body ?? [];
|
|
4901
|
+
return body.some(
|
|
4902
|
+
(statement) => statement.type === "ImportDeclaration" && String(statement.source?.value ?? "") === "@bettercms-ai/next/draft-bridge" && (statement.specifiers ?? []).some(
|
|
4903
|
+
(specifier) => specifier.imported?.name === "BcmsDraftBridge"
|
|
4904
|
+
)
|
|
4905
|
+
);
|
|
4906
|
+
}
|
|
4907
|
+
function blocksImport(ast) {
|
|
4908
|
+
const body = ast?.program?.body ?? [];
|
|
4909
|
+
for (const statement of body) {
|
|
4910
|
+
if (statement.type !== "ImportDeclaration") continue;
|
|
4911
|
+
const source = String(statement.source?.value ?? "");
|
|
4912
|
+
if (source !== "@bettercms-ai/next" && source !== "@bettercms-ai/next/blocks") continue;
|
|
4913
|
+
for (const specifier of statement.specifiers ?? []) {
|
|
4914
|
+
const imported = specifier.imported;
|
|
4915
|
+
if (specifier.type !== "ImportSpecifier" || imported?.name !== "BcmsBlocks") continue;
|
|
4916
|
+
return { declaration: statement, specifier, local: String(specifier.local.name) };
|
|
4917
|
+
}
|
|
4918
|
+
}
|
|
4919
|
+
return null;
|
|
4920
|
+
}
|
|
4921
|
+
function slugExpression(usage, routes) {
|
|
4922
|
+
if (usage.scope.has("slug")) return "{slug}";
|
|
4923
|
+
if (usage.scope.has("page")) return "{page.slug}";
|
|
4924
|
+
const matched = [...new Set([...usage.literals].map((l) => routes.get(l)).filter((s) => !!s))];
|
|
4925
|
+
return matched.length === 1 ? JSON.stringify(matched[0]) : null;
|
|
4926
|
+
}
|
|
4927
|
+
var lineOf = (content, offset) => content.slice(0, offset).split("\n").length;
|
|
4928
|
+
var pend = (reason, hint, file, line) => ({
|
|
4929
|
+
route: "",
|
|
4930
|
+
scope: "page",
|
|
4931
|
+
path: reason === "SSR_DRAFT_ROUTE" ? "canvas.lane" : "canvas.bridge",
|
|
4932
|
+
kind: "canvas",
|
|
4933
|
+
...file ? { file } : {},
|
|
4934
|
+
...line ? { line } : {},
|
|
4935
|
+
reason,
|
|
4936
|
+
hint
|
|
4937
|
+
});
|
|
4938
|
+
var ASTRO_LANE_PENDING = pend("SSR_DRAFT_ROUTE", SSR_DRAFT_RECIPE);
|
|
4939
|
+
function canvasBridge(sources, routes) {
|
|
4940
|
+
if (sources.some((s) => s.path.toLowerCase().endsWith(".astro")))
|
|
4941
|
+
return { lane: "pending", files: [], edits: /* @__PURE__ */ new Map(), pending: [ASTRO_LANE_PENDING] };
|
|
4942
|
+
const files = [];
|
|
4943
|
+
const edits = /* @__PURE__ */ new Map();
|
|
4944
|
+
const pending = [];
|
|
4945
|
+
for (const source of sources) {
|
|
4946
|
+
if (!/\.[jt]sx$/i.test(source.path) || !source.ast) continue;
|
|
4947
|
+
if (bridgedAlready(source.ast)) {
|
|
4948
|
+
files.push(source.path);
|
|
4949
|
+
continue;
|
|
4950
|
+
}
|
|
4951
|
+
const imported = blocksImport(source.ast);
|
|
4952
|
+
if (!imported) continue;
|
|
4953
|
+
const found = usages(source.ast, imported.local);
|
|
4954
|
+
const slugs = found.map((usage) => slugExpression(usage, routes));
|
|
4955
|
+
const unresolved = found.findIndex((_, i) => slugs[i] === null);
|
|
4956
|
+
if (found.length === 0 || unresolved !== -1) {
|
|
4957
|
+
const at = found[unresolved === -1 ? 0 : unresolved]?.element.start;
|
|
4958
|
+
pending.push(
|
|
4959
|
+
pend(
|
|
4960
|
+
"CANVAS_BRIDGE_MANUAL",
|
|
4961
|
+
"Wrap this page's <BcmsBlocks> in <BcmsDraftBridge slug={\u2026}> from @bettercms-ai/next/draft-bridge. No slug expression was derivable in the component's own scope, so the codemod will not guess one: a bridge on the wrong slug never updates in the editor.",
|
|
4962
|
+
source.path,
|
|
4963
|
+
at === void 0 ? void 0 : lineOf(source.content, at)
|
|
4964
|
+
)
|
|
4965
|
+
);
|
|
4966
|
+
continue;
|
|
4967
|
+
}
|
|
4968
|
+
const out = new MagicString3(source.content);
|
|
4969
|
+
rewriteImport(out, imported);
|
|
4970
|
+
for (const [i, usage] of found.entries()) rewriteElement(out, usage, imported.local, slugs[i]);
|
|
4971
|
+
edits.set(source.path, out.toString());
|
|
4972
|
+
files.push(source.path);
|
|
4973
|
+
}
|
|
4974
|
+
return { lane: files.length > 0 ? "bridge" : "pending", files, edits, pending };
|
|
4975
|
+
}
|
|
4976
|
+
function rewriteImport(out, imported) {
|
|
4977
|
+
const { declaration: declaration2, specifier, local } = imported;
|
|
4978
|
+
const line = `import { BcmsDraftBridge${local === "BcmsBlocks" ? "" : ` as ${local}`} } from "@bettercms-ai/next/draft-bridge";`;
|
|
4979
|
+
const specifiers = declaration2.specifiers ?? [];
|
|
4980
|
+
const named = specifiers.filter((s) => s.type === "ImportSpecifier");
|
|
4981
|
+
if (named.length === 1) {
|
|
4982
|
+
if (specifiers.length === 1) {
|
|
4983
|
+
out.overwrite(declaration2.start, declaration2.end, line);
|
|
4984
|
+
return;
|
|
4985
|
+
}
|
|
4986
|
+
const other = specifiers.find((s) => s.type !== "ImportSpecifier");
|
|
4987
|
+
const close = out.original.indexOf("}", named[0].end);
|
|
4988
|
+
out.remove(other.end, close + 1);
|
|
4989
|
+
out.appendRight(declaration2.end, `
|
|
4990
|
+
${line}`);
|
|
4991
|
+
return;
|
|
4992
|
+
}
|
|
4993
|
+
const index = named.indexOf(specifier);
|
|
4994
|
+
const previous = named[index - 1];
|
|
4995
|
+
const from = previous ? previous.end : specifier.start;
|
|
4996
|
+
const to = previous ? specifier.end : named[index + 1].start;
|
|
4997
|
+
out.remove(from, to);
|
|
4998
|
+
out.appendRight(declaration2.end, `
|
|
4999
|
+
${line}`);
|
|
5000
|
+
}
|
|
5001
|
+
function rewriteElement(out, usage, local, slug) {
|
|
5002
|
+
const opening = usage.element.openingElement;
|
|
5003
|
+
const name = opening.name;
|
|
5004
|
+
if (local === "BcmsBlocks") {
|
|
5005
|
+
out.overwrite(name.start, name.end, "BcmsDraftBridge");
|
|
5006
|
+
const closing = usage.element.closingElement;
|
|
5007
|
+
if (closing) {
|
|
5008
|
+
const closingName = closing.name;
|
|
5009
|
+
out.overwrite(closingName.start, closingName.end, "BcmsDraftBridge");
|
|
5010
|
+
}
|
|
5011
|
+
}
|
|
5012
|
+
const attributes = opening.attributes ?? [];
|
|
5013
|
+
const hasSlug = attributes.some((a) => a.name?.name === "slug");
|
|
5014
|
+
if (!hasSlug) out.appendLeft(name.end, ` slug=${slug}`);
|
|
5015
|
+
}
|
|
5016
|
+
|
|
4723
5017
|
// src/tolerant.ts
|
|
4724
5018
|
var escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4725
5019
|
var flexible = (literal) => new RegExp(escape(literal).split(" ").map(escape).join("\\s+"), "gu");
|
|
@@ -4849,6 +5143,7 @@ function readPlan(value) {
|
|
|
4849
5143
|
const section = check.record(held, at2);
|
|
4850
5144
|
check.segment(section.groupKey, `${at2}.groupKey`);
|
|
4851
5145
|
check.optionalString(section.sectionType, `${at2}.sectionType`);
|
|
5146
|
+
check.optionalString(section.copy, `${at2}.copy`);
|
|
4852
5147
|
if (section.fields !== void 0) {
|
|
4853
5148
|
for (const [f, field] of check.array(section.fields, `${at2}.fields`).entries()) {
|
|
4854
5149
|
const shape = check.record(field, `${at2}.fields[${f}]`);
|
|
@@ -5148,10 +5443,10 @@ function scanExpression(code) {
|
|
|
5148
5443
|
for (const key of ["typeName", "left", "right", "id"]) nameOf2(node[key], skip);
|
|
5149
5444
|
return;
|
|
5150
5445
|
case "VariableDeclarator":
|
|
5151
|
-
|
|
5446
|
+
patternNames3(node.id, bound);
|
|
5152
5447
|
return;
|
|
5153
5448
|
case "CatchClause":
|
|
5154
|
-
|
|
5449
|
+
patternNames3(node.param, bound);
|
|
5155
5450
|
return;
|
|
5156
5451
|
default:
|
|
5157
5452
|
return;
|
|
@@ -5159,8 +5454,8 @@ function scanExpression(code) {
|
|
|
5159
5454
|
});
|
|
5160
5455
|
walkAst(ast, (node) => {
|
|
5161
5456
|
if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression" || node.type === "FunctionDeclaration" || node.type === "ObjectMethod" || node.type === "ClassMethod") {
|
|
5162
|
-
for (const param of node.params ?? [])
|
|
5163
|
-
if (node.id)
|
|
5457
|
+
for (const param of node.params ?? []) patternNames3(param, bound);
|
|
5458
|
+
if (node.id) patternNames3(node.id, bound);
|
|
5164
5459
|
}
|
|
5165
5460
|
});
|
|
5166
5461
|
const free = /* @__PURE__ */ new Set();
|
|
@@ -5175,7 +5470,7 @@ function nameOf2(node, out) {
|
|
|
5175
5470
|
nameOf2(node.name, out);
|
|
5176
5471
|
}
|
|
5177
5472
|
}
|
|
5178
|
-
function
|
|
5473
|
+
function patternNames3(node, out) {
|
|
5179
5474
|
if (!node || typeof node !== "object") return;
|
|
5180
5475
|
const typed = node;
|
|
5181
5476
|
switch (typed.type) {
|
|
@@ -5184,17 +5479,17 @@ function patternNames2(node, out) {
|
|
|
5184
5479
|
return;
|
|
5185
5480
|
case "ObjectPattern":
|
|
5186
5481
|
for (const property of typed.properties ?? []) {
|
|
5187
|
-
|
|
5482
|
+
patternNames3(property.type === "RestElement" ? property.argument : property.value, out);
|
|
5188
5483
|
}
|
|
5189
5484
|
return;
|
|
5190
5485
|
case "ArrayPattern":
|
|
5191
|
-
for (const element of typed.elements ?? [])
|
|
5486
|
+
for (const element of typed.elements ?? []) patternNames3(element, out);
|
|
5192
5487
|
return;
|
|
5193
5488
|
case "AssignmentPattern":
|
|
5194
|
-
|
|
5489
|
+
patternNames3(typed.left, out);
|
|
5195
5490
|
return;
|
|
5196
5491
|
case "RestElement":
|
|
5197
|
-
|
|
5492
|
+
patternNames3(typed.argument, out);
|
|
5198
5493
|
return;
|
|
5199
5494
|
default:
|
|
5200
5495
|
return;
|
|
@@ -5216,9 +5511,9 @@ function moduleScope(parsed, content) {
|
|
|
5216
5511
|
const text = source.slice(start, end);
|
|
5217
5512
|
const names = /* @__PURE__ */ new Set();
|
|
5218
5513
|
if (kind === "VariableDeclaration") {
|
|
5219
|
-
for (const declarator of statement.declarations ?? [])
|
|
5514
|
+
for (const declarator of statement.declarations ?? []) patternNames3(declarator.id, names);
|
|
5220
5515
|
} else {
|
|
5221
|
-
|
|
5516
|
+
patternNames3(statement.id, names);
|
|
5222
5517
|
}
|
|
5223
5518
|
if (names.size === 0) continue;
|
|
5224
5519
|
const scanned = scanExpression(text.replace(/^(?:export\s+)?/, ""));
|
|
@@ -5229,7 +5524,7 @@ function moduleScope(parsed, content) {
|
|
|
5229
5524
|
}
|
|
5230
5525
|
|
|
5231
5526
|
// src/componentize.ts
|
|
5232
|
-
import
|
|
5527
|
+
import MagicString4 from "magic-string";
|
|
5233
5528
|
var SectionInvariantError = class extends Error {
|
|
5234
5529
|
code = "SECTION_INVARIANT";
|
|
5235
5530
|
constructor(message) {
|
|
@@ -5246,7 +5541,8 @@ function assertSections(total, sections) {
|
|
|
5246
5541
|
}
|
|
5247
5542
|
return sections;
|
|
5248
5543
|
}
|
|
5249
|
-
var SECTION_MARKER = "// @bettercms-ai/convert section
|
|
5544
|
+
var SECTION_MARKER = "// @bettercms-ai/convert section v2";
|
|
5545
|
+
var SECTION_MARKERS = [SECTION_MARKER, "// @bettercms-ai/convert section v1"];
|
|
5250
5546
|
var REGISTRY_MARKER = "// @bettercms-ai/convert sections v1";
|
|
5251
5547
|
var COMPONENT_DIR = "src/components/bcms";
|
|
5252
5548
|
var SECTIONS_LIB = "src/lib/bcms-sections.ts";
|
|
@@ -5268,12 +5564,53 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5268
5564
|
let registered = 0;
|
|
5269
5565
|
let planPending = 0;
|
|
5270
5566
|
let total = 0;
|
|
5567
|
+
const pend2 = (route, groupKey, reason, message) => void pending.push({ route, groupKey, reason, ...message ? { message } : {} });
|
|
5271
5568
|
const brief = readBrief(briefIn);
|
|
5272
5569
|
const plan2 = readPlan(planIn);
|
|
5570
|
+
const copy = options?.copy ?? "bind";
|
|
5571
|
+
const instanceOwned = (section) => section.copy === "instance";
|
|
5572
|
+
const notes = unreadableConfigs(sources).map(
|
|
5573
|
+
(path) => ({
|
|
5574
|
+
code: "TSCONFIG_UNREADABLE",
|
|
5575
|
+
file: path,
|
|
5576
|
+
message: `${path} is not JSON this package can read, so its \`paths\` aliases resolved to nothing.`
|
|
5577
|
+
})
|
|
5578
|
+
);
|
|
5579
|
+
const refuseRun = (reason, message) => {
|
|
5580
|
+
for (const page of plan2.pages ?? []) {
|
|
5581
|
+
for (const section of page.sections ?? []) {
|
|
5582
|
+
if (section.pending) {
|
|
5583
|
+
planPending++;
|
|
5584
|
+
continue;
|
|
5585
|
+
}
|
|
5586
|
+
total++;
|
|
5587
|
+
pend2(page.route, section.groupKey, reason, message);
|
|
5588
|
+
}
|
|
5589
|
+
}
|
|
5590
|
+
return {
|
|
5591
|
+
files: [],
|
|
5592
|
+
receipt: {
|
|
5593
|
+
planDigest: plan2.digest ?? "",
|
|
5594
|
+
copy,
|
|
5595
|
+
sections: assertSections(total, { extracted: 0, inlineOnly: 0, alreadyExtracted: 0, registered: 0, pending }),
|
|
5596
|
+
components: [],
|
|
5597
|
+
planPending,
|
|
5598
|
+
notes
|
|
5599
|
+
}
|
|
5600
|
+
};
|
|
5601
|
+
};
|
|
5602
|
+
const movesCopy = (plan2.pages ?? []).some(
|
|
5603
|
+
(page) => (page.sections ?? []).some((section) => !section.pending && instanceOwned(section))
|
|
5604
|
+
);
|
|
5605
|
+
if (copy === "instance" && !movesCopy) {
|
|
5606
|
+
return refuseRun(
|
|
5607
|
+
"COPY_MODE_MISMATCH",
|
|
5608
|
+
'this run was asked for --copy instance, but no placement in the plan carries copy: "instance" \u2014 run componentize_sections { copy: "instance" } on the server first, then fetch the plan again.'
|
|
5609
|
+
);
|
|
5610
|
+
}
|
|
5273
5611
|
const contents = new Map(sources.map((s) => [s.path, s.content]));
|
|
5274
5612
|
const fileSet = new Set(contents.keys());
|
|
5275
5613
|
const aliases = aliasesFrom(sources);
|
|
5276
|
-
const pend = (route, groupKey, reason, message) => void pending.push({ route, groupKey, reason, ...message ? { message } : {} });
|
|
5277
5614
|
const trees = /* @__PURE__ */ new Map();
|
|
5278
5615
|
const treeOf = async (file) => {
|
|
5279
5616
|
if (trees.has(file)) return trees.get(file);
|
|
@@ -5287,7 +5624,9 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5287
5624
|
const helperPath = HELPER_PATH.astro;
|
|
5288
5625
|
const helperExisting = contents.get(helperPath);
|
|
5289
5626
|
const helperVersion = helperExisting === void 0 ? null : isKnownHelper(helperExisting, "astro");
|
|
5290
|
-
|
|
5627
|
+
if (helperExisting !== void 0 && helperVersion === null && options?.overwriteHelper !== true) {
|
|
5628
|
+
return refuseRun("HELPER_CONFLICT", `${helperPath} is not a module this package wrote.`);
|
|
5629
|
+
}
|
|
5291
5630
|
const outFiles = /* @__PURE__ */ new Map();
|
|
5292
5631
|
const written = /* @__PURE__ */ new Map();
|
|
5293
5632
|
const shipped = /* @__PURE__ */ new Map();
|
|
@@ -5297,6 +5636,29 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5297
5636
|
return held;
|
|
5298
5637
|
};
|
|
5299
5638
|
const fresh = /* @__PURE__ */ new Set();
|
|
5639
|
+
const bindOf = (section) => instanceOwned(section) ? void 0 : section.groupKey;
|
|
5640
|
+
const placementOf = (slug, section) => {
|
|
5641
|
+
const bind = bindOf(section);
|
|
5642
|
+
return bind === void 0 ? { slug } : { slug, bind };
|
|
5643
|
+
};
|
|
5644
|
+
const rebind = (dialect, pageSlug, all, usable) => {
|
|
5645
|
+
if (dialect === null || !EXTRACTABLE.includes(dialect)) return;
|
|
5646
|
+
const order = shipped.get(dialect);
|
|
5647
|
+
const held = order?.get(pageSlug);
|
|
5648
|
+
if (!order || !held) return;
|
|
5649
|
+
const queues = /* @__PURE__ */ new Map();
|
|
5650
|
+
for (const section of all) {
|
|
5651
|
+
const slug = resolveComponent(plan2, section).slug;
|
|
5652
|
+
queues.set(slug, [...queues.get(slug) ?? [], section]);
|
|
5653
|
+
}
|
|
5654
|
+
order.set(
|
|
5655
|
+
pageSlug,
|
|
5656
|
+
held.map((placement) => {
|
|
5657
|
+
const section = queues.get(placement.slug)?.shift();
|
|
5658
|
+
return section && usable.has(section) ? placementOf(placement.slug, section) : placement;
|
|
5659
|
+
})
|
|
5660
|
+
);
|
|
5661
|
+
};
|
|
5300
5662
|
const registryConflicts = [];
|
|
5301
5663
|
const registryUnreadable = [];
|
|
5302
5664
|
for (const dialect of EXTRACTABLE) {
|
|
@@ -5322,6 +5684,11 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5322
5684
|
if (lib !== void 0 && !isGenerated(lib)) registryConflicts.push(SECTIONS_LIB);
|
|
5323
5685
|
const registryBlocked = registryConflicts.length > 0 && options?.overwriteHelper !== true;
|
|
5324
5686
|
const registryUnread = registryUnreadable.length > 0;
|
|
5687
|
+
const migrated = registryBlocked || registryUnread ? { files: [], refused: /* @__PURE__ */ new Map() } : await migrateSections(plan2, contents, written, fileSet, aliases);
|
|
5688
|
+
for (const file of migrated.files) {
|
|
5689
|
+
addFile(outFiles, contents, file);
|
|
5690
|
+
contents.set(file.path, file.content);
|
|
5691
|
+
}
|
|
5325
5692
|
for (const page of plan2.pages ?? []) {
|
|
5326
5693
|
const sections2 = (page.sections ?? []).filter((section) => {
|
|
5327
5694
|
if (section.pending) {
|
|
@@ -5332,24 +5699,34 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5332
5699
|
});
|
|
5333
5700
|
total += sections2.length;
|
|
5334
5701
|
if (sections2.length === 0) continue;
|
|
5702
|
+
const prefixes = /* @__PURE__ */ new Map();
|
|
5703
|
+
for (const section of sections2) {
|
|
5704
|
+
const prefix = propPrefix(section);
|
|
5705
|
+
if (prefix === null) {
|
|
5706
|
+
pend2(
|
|
5707
|
+
page.route,
|
|
5708
|
+
section.groupKey,
|
|
5709
|
+
"PROP_MAP_INCONSISTENT",
|
|
5710
|
+
"the plan's fields do not spell every path as one common prefix plus its key, so a page path cannot be turned into a prop key."
|
|
5711
|
+
);
|
|
5712
|
+
continue;
|
|
5713
|
+
}
|
|
5714
|
+
prefixes.set(section, prefix);
|
|
5715
|
+
}
|
|
5716
|
+
const usable = sections2.filter((section) => prefixes.has(section));
|
|
5717
|
+
if (usable.length === 0) continue;
|
|
5335
5718
|
const pendingBefore = pending.length;
|
|
5336
5719
|
const briefPage = brief.pages.find((p) => p.route === page.route);
|
|
5337
5720
|
const pageSlug = briefPage?.slug ?? slugify(page.route === "/" ? "home" : page.route);
|
|
5338
|
-
if (helperBlocked) {
|
|
5339
|
-
for (const section of sections2) {
|
|
5340
|
-
pend(page.route, section.groupKey, "HELPER_CONFLICT", `${helperPath} is not a module this package wrote.`);
|
|
5341
|
-
}
|
|
5342
|
-
continue;
|
|
5343
|
-
}
|
|
5344
5721
|
if (registryBlocked) {
|
|
5345
|
-
for (const section of
|
|
5346
|
-
|
|
5722
|
+
for (const section of usable) {
|
|
5723
|
+
pend2(page.route, section.groupKey, "REGISTRY_CONFLICT", `${registryConflicts.join(", ")} is not a file this package wrote.`);
|
|
5347
5724
|
}
|
|
5348
5725
|
continue;
|
|
5349
5726
|
}
|
|
5350
5727
|
if (registryUnread) {
|
|
5351
|
-
for (const section of
|
|
5352
|
-
|
|
5728
|
+
for (const section of usable) {
|
|
5729
|
+
pend2(
|
|
5353
5730
|
page.route,
|
|
5354
5731
|
section.groupKey,
|
|
5355
5732
|
"REGISTRY_UNREADABLE",
|
|
@@ -5360,36 +5737,49 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5360
5737
|
}
|
|
5361
5738
|
const snapshotPath = `${CONTENT_DIR}/${pageSlug}.json`;
|
|
5362
5739
|
if (!snapshotUsable(contents.get(snapshotPath))) {
|
|
5363
|
-
for (const section of
|
|
5364
|
-
|
|
5740
|
+
for (const section of usable) {
|
|
5741
|
+
pend2(page.route, section.groupKey, "SNAPSHOT_INVALID", `${snapshotPath} is not a JSON object.`);
|
|
5365
5742
|
}
|
|
5366
5743
|
continue;
|
|
5367
5744
|
}
|
|
5368
5745
|
const pageFiles = pageFilesFor(page.route, contents.keys());
|
|
5369
5746
|
if (pageFiles.length > 1) {
|
|
5370
|
-
for (const section of
|
|
5371
|
-
|
|
5747
|
+
for (const section of usable) {
|
|
5748
|
+
pend2(page.route, section.groupKey, "ROUTE_FILE_AMBIGUOUS", `${pageFiles.join(" and ")} both serve ${page.route}.`);
|
|
5372
5749
|
}
|
|
5373
5750
|
continue;
|
|
5374
5751
|
}
|
|
5375
5752
|
const file = pageFiles[0];
|
|
5376
5753
|
if (file === void 0) {
|
|
5377
|
-
for (const section of
|
|
5378
|
-
|
|
5754
|
+
for (const section of usable) {
|
|
5755
|
+
pend2(page.route, section.groupKey, "NOT_IN_SOURCE", `no page file found for ${page.route}.`);
|
|
5379
5756
|
}
|
|
5380
5757
|
continue;
|
|
5381
5758
|
}
|
|
5382
|
-
const
|
|
5759
|
+
const pageDialect = dialectOf(file, contents.get(file) ?? "");
|
|
5760
|
+
const done = new Set(alreadyOn(contents.get(file) ?? "", pageSlug, usable));
|
|
5761
|
+
let stuck = 0;
|
|
5762
|
+
for (const section of usable) {
|
|
5763
|
+
if (!done.has(section.groupKey)) continue;
|
|
5764
|
+
const why = pageDialect ? migrated.refused.get(refusalKey(pageDialect, resolveComponent(plan2, section).slug)) : void 0;
|
|
5765
|
+
if (why === void 0) continue;
|
|
5766
|
+
done.delete(section.groupKey);
|
|
5767
|
+
stuck++;
|
|
5768
|
+
pend2(page.route, section.groupKey, "SECTION_UNMIGRATABLE", why);
|
|
5769
|
+
}
|
|
5383
5770
|
alreadyExtracted += done.size;
|
|
5384
|
-
if (done.size ===
|
|
5771
|
+
if (done.size + stuck === usable.length) {
|
|
5772
|
+
rebind(pageDialect, pageSlug, page.sections ?? [], new Set(usable));
|
|
5773
|
+
continue;
|
|
5774
|
+
}
|
|
5385
5775
|
const found = await locateSections(
|
|
5386
|
-
|
|
5776
|
+
usable.filter((section) => !done.has(section.groupKey)),
|
|
5387
5777
|
briefPage?.paths ?? [],
|
|
5388
5778
|
sources,
|
|
5389
5779
|
treeOf
|
|
5390
5780
|
);
|
|
5391
5781
|
for (const [groupKey, reason] of found.misses) {
|
|
5392
|
-
|
|
5782
|
+
pend2(page.route, groupKey, reason.code, reason.message);
|
|
5393
5783
|
}
|
|
5394
5784
|
const live = found.placed;
|
|
5395
5785
|
if (live.length === 0) continue;
|
|
@@ -5397,14 +5787,14 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5397
5787
|
const dialect = dialectOf(file, content);
|
|
5398
5788
|
if (!dialect || !EXTRACTABLE.includes(dialect)) {
|
|
5399
5789
|
for (const placed of live) {
|
|
5400
|
-
|
|
5790
|
+
pend2(page.route, placed.section.groupKey, "DIALECT_UNSUPPORTED", `${file} is ${dialect ?? "not a template"}.`);
|
|
5401
5791
|
}
|
|
5402
5792
|
continue;
|
|
5403
5793
|
}
|
|
5404
5794
|
const tree = await treeOf(file);
|
|
5405
5795
|
if (!tree) {
|
|
5406
5796
|
for (const placed of live) {
|
|
5407
|
-
|
|
5797
|
+
pend2(page.route, placed.section.groupKey, "NOT_IN_SOURCE", `${file} did not parse.`);
|
|
5408
5798
|
}
|
|
5409
5799
|
continue;
|
|
5410
5800
|
}
|
|
@@ -5429,7 +5819,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5429
5819
|
const alsoRendered = [...placed.byFile.keys()].filter((held2) => held2 !== file && importedFiles.has(held2));
|
|
5430
5820
|
if (elements && elements.length > 0) {
|
|
5431
5821
|
if (alsoRendered.length > 0) {
|
|
5432
|
-
|
|
5822
|
+
pend2(
|
|
5433
5823
|
page.route,
|
|
5434
5824
|
placed.section.groupKey,
|
|
5435
5825
|
"SECTION_ROOT_AMBIGUOUS",
|
|
@@ -5441,7 +5831,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5441
5831
|
continue;
|
|
5442
5832
|
}
|
|
5443
5833
|
if (alsoRendered.length > 1) {
|
|
5444
|
-
|
|
5834
|
+
pend2(
|
|
5445
5835
|
page.route,
|
|
5446
5836
|
placed.section.groupKey,
|
|
5447
5837
|
"SECTION_ROOT_AMBIGUOUS",
|
|
@@ -5455,7 +5845,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5455
5845
|
importedSections.push({ section: placed.section, path: only, call: held.local });
|
|
5456
5846
|
continue;
|
|
5457
5847
|
}
|
|
5458
|
-
|
|
5848
|
+
pend2(
|
|
5459
5849
|
page.route,
|
|
5460
5850
|
placed.section.groupKey,
|
|
5461
5851
|
"SECTION_ROOT_AMBIGUOUS",
|
|
@@ -5469,7 +5859,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5469
5859
|
const component = resolveComponent(plan2, held.section);
|
|
5470
5860
|
const already = extractable.get(component.slug);
|
|
5471
5861
|
if (already !== void 0 && already.path !== held.path) {
|
|
5472
|
-
|
|
5862
|
+
pend2(
|
|
5473
5863
|
page.route,
|
|
5474
5864
|
held.section.groupKey,
|
|
5475
5865
|
"COMPONENT_CONFLICT",
|
|
@@ -5480,7 +5870,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5480
5870
|
const binding = bindingOf(held.path);
|
|
5481
5871
|
const member = binding.kind === "namespace" ? namespaceMember(tree, binding.local) : void 0;
|
|
5482
5872
|
if (binding.kind === "namespace" && member === void 0) {
|
|
5483
|
-
|
|
5873
|
+
pend2(
|
|
5484
5874
|
page.route,
|
|
5485
5875
|
held.section.groupKey,
|
|
5486
5876
|
"SECTION_ROOT_AMBIGUOUS",
|
|
@@ -5498,7 +5888,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5498
5888
|
registered++;
|
|
5499
5889
|
registeredHere.push({
|
|
5500
5890
|
slug: component.slug,
|
|
5501
|
-
|
|
5891
|
+
section: held.section,
|
|
5502
5892
|
call: callSiteOf(tree, member ? `${binding.local}.${member}` : held.call)
|
|
5503
5893
|
});
|
|
5504
5894
|
}
|
|
@@ -5506,11 +5896,17 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5506
5896
|
for (const placed of placedHere) {
|
|
5507
5897
|
const root = lowestCommonAncestor(placed.elements);
|
|
5508
5898
|
if (!root) {
|
|
5509
|
-
|
|
5899
|
+
pend2(page.route, placed.section.groupKey, "SECTION_ROOT_AMBIGUOUS", "its fields sit under no single element.");
|
|
5510
5900
|
continue;
|
|
5511
5901
|
}
|
|
5512
5902
|
const component = resolveComponent(plan2, placed.section);
|
|
5513
|
-
roots.push({
|
|
5903
|
+
roots.push({
|
|
5904
|
+
section: placed.section,
|
|
5905
|
+
route: page.route,
|
|
5906
|
+
root,
|
|
5907
|
+
pathPrefix: prefixes.get(placed.section),
|
|
5908
|
+
...component
|
|
5909
|
+
});
|
|
5514
5910
|
}
|
|
5515
5911
|
const nested = /* @__PURE__ */ new Set();
|
|
5516
5912
|
for (const a of roots) {
|
|
@@ -5522,7 +5918,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5522
5918
|
}
|
|
5523
5919
|
}
|
|
5524
5920
|
for (const resolved of nested) {
|
|
5525
|
-
|
|
5921
|
+
pend2(page.route, resolved.section.groupKey, "SECTION_ROOT_AMBIGUOUS", "its root contains another section's.");
|
|
5526
5922
|
}
|
|
5527
5923
|
const candidates = roots.filter((r) => !nested.has(r)).sort((a, b) => a.root.start - b.root.start);
|
|
5528
5924
|
const placeable = [];
|
|
@@ -5530,16 +5926,34 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5530
5926
|
const path = `${COMPONENT_DIR}/${resolved.name}.${dialect === "astro" ? "astro" : "tsx"}`;
|
|
5531
5927
|
const existing = contents.get(path);
|
|
5532
5928
|
if (existing !== void 0 && !isGenerated(existing) && !extractable.has(resolved.slug)) {
|
|
5533
|
-
|
|
5929
|
+
pend2(page.route, resolved.section.groupKey, "COMPONENT_CONFLICT", `${path} is not a file this package wrote.`);
|
|
5534
5930
|
continue;
|
|
5535
5931
|
}
|
|
5536
5932
|
if (extractable.has(resolved.slug)) {
|
|
5537
5933
|
placeable.push(resolved);
|
|
5538
5934
|
continue;
|
|
5539
5935
|
}
|
|
5540
|
-
const built = componentFile(dialect, path, file, content, parsed, resolved);
|
|
5936
|
+
const built = componentFile(dialect, path, file, content, parsed, resolved, resolved.pathPrefix);
|
|
5937
|
+
if (built.unconvertible.length > 0) {
|
|
5938
|
+
pend2(
|
|
5939
|
+
page.route,
|
|
5940
|
+
resolved.section.groupKey,
|
|
5941
|
+
"PROP_MAP_INCONSISTENT",
|
|
5942
|
+
`its fields map to the prefix "${resolved.pathPrefix}", which ${[...new Set(built.unconvertible)].slice(0, 3).join(", ")} cannot be converted through \u2014 so those elements would stay on the page lane whatever the placement says.`
|
|
5943
|
+
);
|
|
5944
|
+
continue;
|
|
5945
|
+
}
|
|
5946
|
+
if (built.duplicates.length > 0) {
|
|
5947
|
+
pend2(
|
|
5948
|
+
page.route,
|
|
5949
|
+
resolved.section.groupKey,
|
|
5950
|
+
"DUPLICATE_FIELD_BINDING",
|
|
5951
|
+
`${built.duplicates.join(", ")} carries more than one data-bcms-field, so there is no single address to rewrite.`
|
|
5952
|
+
);
|
|
5953
|
+
continue;
|
|
5954
|
+
}
|
|
5541
5955
|
if (built.free.length > 0) {
|
|
5542
|
-
|
|
5956
|
+
pend2(
|
|
5543
5957
|
page.route,
|
|
5544
5958
|
resolved.section.groupKey,
|
|
5545
5959
|
"SECTION_FREE_IDENTIFIERS",
|
|
@@ -5558,14 +5972,14 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5558
5972
|
}
|
|
5559
5973
|
if (placeable.length === 0 && registeredHere.length === 0) continue;
|
|
5560
5974
|
const spans = [
|
|
5561
|
-
...placeable.map((r) => ({ slug: r.slug, bind: r.section
|
|
5562
|
-
...registeredHere.map((r) => ({ slug: r.slug, bind: r.
|
|
5975
|
+
...placeable.map((r) => ({ slug: r.slug, bind: bindOf(r.section), root: r.root })),
|
|
5976
|
+
...registeredHere.map((r) => ({ slug: r.slug, bind: bindOf(r.section), root: r.call }))
|
|
5563
5977
|
].sort((a, b) => (a.root?.start ?? Number.MAX_SAFE_INTEGER) - (b.root?.start ?? Number.MAX_SAFE_INTEGER));
|
|
5564
5978
|
const known = spans.filter((s) => s.root !== null);
|
|
5565
5979
|
const layout = runOf(known, parsed.roots, content);
|
|
5566
5980
|
if (layout === null) {
|
|
5567
5981
|
for (const resolved of placeable) {
|
|
5568
|
-
|
|
5982
|
+
pend2(page.route, resolved.section.groupKey, "SECTION_NOT_CONTIGUOUS", "its roots share no container.");
|
|
5569
5983
|
}
|
|
5570
5984
|
continue;
|
|
5571
5985
|
}
|
|
@@ -5575,7 +5989,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5575
5989
|
shipped.set(dialect, order);
|
|
5576
5990
|
order.set(
|
|
5577
5991
|
pageSlug,
|
|
5578
|
-
spans.map((s) =>
|
|
5992
|
+
spans.map((s) => s.bind === void 0 ? { slug: s.slug } : { slug: s.slug, bind: s.bind })
|
|
5579
5993
|
);
|
|
5580
5994
|
if (useRun) {
|
|
5581
5995
|
addFile(outFiles, contents, {
|
|
@@ -5584,13 +5998,25 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5584
5998
|
content: splicePageRun(file, content, dialect, parsed, known, pageSlug)
|
|
5585
5999
|
});
|
|
5586
6000
|
extracted += placeable.length;
|
|
5587
|
-
} else
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
6001
|
+
} else {
|
|
6002
|
+
const inline = placeable.filter((resolved) => !instanceOwned(resolved.section));
|
|
6003
|
+
for (const resolved of placeable) {
|
|
6004
|
+
if (inline.includes(resolved)) continue;
|
|
6005
|
+
pend2(
|
|
6006
|
+
page.route,
|
|
6007
|
+
resolved.section.groupKey,
|
|
6008
|
+
"INLINE_NEEDS_INSTANCE",
|
|
6009
|
+
`${file} cannot be replaced by one <Sections page="${pageSlug}" />, and an in-place call site carries no blockId or overrides \u2014 so this placement's own copy could not be rendered or addressed.`
|
|
6010
|
+
);
|
|
6011
|
+
}
|
|
6012
|
+
if (inline.length > 0) {
|
|
6013
|
+
addFile(outFiles, contents, {
|
|
6014
|
+
path: file,
|
|
6015
|
+
operation: "modify",
|
|
6016
|
+
content: splicePageInline(file, content, dialect, parsed, inline, pageSlug, extractable)
|
|
6017
|
+
});
|
|
6018
|
+
}
|
|
6019
|
+
inlineOnly += inline.length;
|
|
5594
6020
|
}
|
|
5595
6021
|
}
|
|
5596
6022
|
const componentSlugs = [...written.values()].flatMap((held) => [...held.keys()]);
|
|
@@ -5618,17 +6044,12 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5618
6044
|
files: [...outFiles.values()].sort((a, b) => a.path.localeCompare(b.path)),
|
|
5619
6045
|
receipt: {
|
|
5620
6046
|
planDigest: plan2.digest ?? "",
|
|
6047
|
+
copy,
|
|
5621
6048
|
sections,
|
|
5622
6049
|
// What THIS run wrote a file for. A component an earlier run left is reused, not re-reported.
|
|
5623
6050
|
components: [...fresh].sort(),
|
|
5624
6051
|
planPending,
|
|
5625
|
-
notes
|
|
5626
|
-
(path) => ({
|
|
5627
|
-
code: "TSCONFIG_UNREADABLE",
|
|
5628
|
-
file: path,
|
|
5629
|
-
message: `${path} is not JSON this package can read, so its \`paths\` aliases resolved to nothing.`
|
|
5630
|
-
})
|
|
5631
|
-
)
|
|
6052
|
+
notes
|
|
5632
6053
|
}
|
|
5633
6054
|
};
|
|
5634
6055
|
}
|
|
@@ -5651,7 +6072,7 @@ async function readRegistry(content, dialect, path, files, aliases) {
|
|
|
5651
6072
|
if (!from.specifier.startsWith(".")) continue;
|
|
5652
6073
|
const resolved = resolveSpecifier(path, from.specifier, files, aliases);
|
|
5653
6074
|
if (resolved !== null) {
|
|
5654
|
-
imports.set(local, { name: local, path: resolved, kind: from.kind, imported: from.imported });
|
|
6075
|
+
imports.set(local, { name: local, path: preferDialect(resolved, dialect, files), kind: from.kind, imported: from.imported });
|
|
5655
6076
|
}
|
|
5656
6077
|
}
|
|
5657
6078
|
const registry = blockOf(content, "REGISTRY");
|
|
@@ -5660,6 +6081,7 @@ async function readRegistry(content, dialect, path, files, aliases) {
|
|
|
5660
6081
|
const registryRows = propertiesOf(registry);
|
|
5661
6082
|
const shippedRows = propertiesOf(table);
|
|
5662
6083
|
if (registryRows === null || shippedRows === null) return null;
|
|
6084
|
+
if (registryRows.length === 0) return null;
|
|
5663
6085
|
for (const row of registryRows) {
|
|
5664
6086
|
const named = row.value.match(/^([A-Za-z_$][\w$]*)(?:\.([A-Za-z_$][\w$]*))?$/);
|
|
5665
6087
|
const entry = named ? imports.get(named[1]) : void 0;
|
|
@@ -5669,7 +6091,8 @@ async function readRegistry(content, dialect, path, files, aliases) {
|
|
|
5669
6091
|
for (const row of shippedRows) {
|
|
5670
6092
|
try {
|
|
5671
6093
|
const placements = JSON.parse(row.value);
|
|
5672
|
-
if (!Array.isArray(placements)) return null;
|
|
6094
|
+
if (!Array.isArray(placements) || placements.length === 0 || !placements.every(isPlacement)) return null;
|
|
6095
|
+
if (placements.some((placement) => !components.has(placement.slug))) return null;
|
|
5673
6096
|
shipped.set(row.key, placements);
|
|
5674
6097
|
} catch {
|
|
5675
6098
|
return null;
|
|
@@ -5677,6 +6100,18 @@ async function readRegistry(content, dialect, path, files, aliases) {
|
|
|
5677
6100
|
}
|
|
5678
6101
|
return { components, shipped };
|
|
5679
6102
|
}
|
|
6103
|
+
function preferDialect(resolved, dialect, files) {
|
|
6104
|
+
const wanted = dialect === "astro" ? [".astro"] : [".tsx", ".jsx"];
|
|
6105
|
+
if (wanted.some((extension) => resolved.endsWith(extension))) return resolved;
|
|
6106
|
+
const base = resolved.replace(/\.[^./]+$/, "");
|
|
6107
|
+
return wanted.map((extension) => `${base}${extension}`).find((candidate) => files.has(candidate)) ?? resolved;
|
|
6108
|
+
}
|
|
6109
|
+
function isPlacement(value) {
|
|
6110
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
6111
|
+
const held = value;
|
|
6112
|
+
if (typeof held.slug !== "string") return false;
|
|
6113
|
+
return ["bind", "blockId"].every((key) => held[key] === void 0 || typeof held[key] === "string");
|
|
6114
|
+
}
|
|
5680
6115
|
function blockOf(content, name) {
|
|
5681
6116
|
const declaration2 = content.indexOf(`const ${name}`);
|
|
5682
6117
|
if (declaration2 < 0) return null;
|
|
@@ -5923,14 +6358,35 @@ function pascal(value) {
|
|
|
5923
6358
|
return /^[A-Za-z]/.test(name) ? name : `Section${name}`;
|
|
5924
6359
|
}
|
|
5925
6360
|
var slugify = (value) => value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "section";
|
|
5926
|
-
function componentFile(dialect, path, fromFile, content, parsed, resolved) {
|
|
5927
|
-
const
|
|
6361
|
+
function componentFile(dialect, path, fromFile, content, parsed, resolved, pathPrefix) {
|
|
6362
|
+
const addresses = fieldAddresses(content, elementsUnder(resolved.root));
|
|
6363
|
+
const unconvertible = [
|
|
6364
|
+
...addresses.heads.filter((address) => !address.startsWith(pathPrefix)),
|
|
6365
|
+
...addresses.opaque
|
|
6366
|
+
];
|
|
6367
|
+
if (unconvertible.length > 0) return { content: "", free: [], duplicates: [], unconvertible };
|
|
6368
|
+
const cut = content.slice(resolved.root.start, resolved.root.end);
|
|
6369
|
+
const reserved = /* @__PURE__ */ new Set([...topLevelBindings(dialect, parsed, content), ...referencedNames2(cut)]);
|
|
6370
|
+
const introduced = allocateNames(reserved, [
|
|
6371
|
+
{ key: "bcmsFields", name: "bcmsFields" },
|
|
6372
|
+
{ key: "bcmsField", name: "bcmsField" }
|
|
6373
|
+
]);
|
|
6374
|
+
const fieldsName = introduced.get("bcmsFields");
|
|
6375
|
+
const fieldName = introduced.get("bcmsField");
|
|
6376
|
+
const { markup, bound, duplicates } = sectionMarkup(content, resolved.root, fieldName);
|
|
6377
|
+
if (duplicates.length > 0) return { content: "", free: [], duplicates, unconvertible: [] };
|
|
5928
6378
|
const groupKey = resolved.section.groupKey;
|
|
5929
6379
|
const locals = helperLocals(parsed, dialect, fromFile);
|
|
5930
6380
|
const helperNames = ["bcms", "bcmsRows", "bcmsLayout"].flatMap(
|
|
5931
6381
|
(name) => locals[name].filter((local) => mentions(markup, local)).map((local) => ({ name, local }))
|
|
5932
6382
|
);
|
|
5933
|
-
const helpers = [
|
|
6383
|
+
const helpers = [
|
|
6384
|
+
.../* @__PURE__ */ new Set([
|
|
6385
|
+
...helperNames.map((h) => alias(h.name, h.local)),
|
|
6386
|
+
"bcmsSection",
|
|
6387
|
+
...bound > 0 ? [alias("bcmsFields", fieldsName)] : []
|
|
6388
|
+
])
|
|
6389
|
+
].sort();
|
|
5934
6390
|
const helperSpecifier = relativeImport(path, HELPER_PATH[dialect]).replace(/\.tsx?$/, "");
|
|
5935
6391
|
const snapshot = snapshotOf(parsed, dialect, fromFile, markup);
|
|
5936
6392
|
const carry = carryFor({
|
|
@@ -5945,20 +6401,23 @@ function componentFile(dialect, path, fromFile, content, parsed, resolved) {
|
|
|
5945
6401
|
"bind",
|
|
5946
6402
|
"overrides",
|
|
5947
6403
|
"page",
|
|
6404
|
+
fieldName,
|
|
5948
6405
|
...helperNames.map((h) => h.local),
|
|
5949
6406
|
...snapshot ? [snapshot] : []
|
|
5950
6407
|
],
|
|
5951
6408
|
// The helper and the snapshots are what the component declares for itself.
|
|
5952
6409
|
skipSpecifier: (repoPath, specifier) => specifier.includes(`${CONTENT_DIR}/`) || repoPath.startsWith(HELPER_PATH[dialect].replace(/\.tsx?$/, ""))
|
|
5953
6410
|
});
|
|
5954
|
-
if (carry.free.length > 0) return { content: "", free: carry.free };
|
|
6411
|
+
if (carry.free.length > 0) return { content: "", free: carry.free, duplicates: [], unconvertible: [] };
|
|
5955
6412
|
const carried = [...carry.imports];
|
|
5956
6413
|
const key = /^[A-Za-z_$][\w$]*$/.test(groupKey) ? groupKey : JSON.stringify(groupKey);
|
|
5957
6414
|
const carriedDeclarations = carry.declarations.length > 0 ? `${carry.declarations.join("\n")}
|
|
5958
6415
|
` : "";
|
|
5959
|
-
const copy = snapshot ? `const ${snapshot} = { ${key}: bcmsSection(page, bind
|
|
6416
|
+
const copy = snapshot ? `const ${snapshot} = { ${key}: bcmsSection(page, bind, overrides) };` : "";
|
|
6417
|
+
const field = bound > 0 ? `const ${fieldName} = ${fieldsName}({ blockId, bind, overrides }, ${JSON.stringify(pathPrefix)});` : "";
|
|
6418
|
+
const declared = [copy, field].filter(Boolean).join("\n");
|
|
5960
6419
|
if (dialect === "astro") {
|
|
5961
|
-
return { free: [], content: `---
|
|
6420
|
+
return { free: [], duplicates: [], unconvertible: [], content: `---
|
|
5962
6421
|
${SECTION_MARKER}
|
|
5963
6422
|
${[...carried, `import { ${helpers.join(", ")} } from "${helperSpecifier}";`].join("\n")}
|
|
5964
6423
|
|
|
@@ -5973,27 +6432,297 @@ interface Props {
|
|
|
5973
6432
|
page?: unknown;
|
|
5974
6433
|
}
|
|
5975
6434
|
const { blockId, bind, overrides, page } = Astro.props;
|
|
5976
|
-
${carriedDeclarations}${
|
|
6435
|
+
${carriedDeclarations}${declared}
|
|
5977
6436
|
---
|
|
5978
6437
|
${markup}
|
|
5979
6438
|
` };
|
|
5980
6439
|
}
|
|
5981
|
-
return { free: [], content: `${SECTION_MARKER}
|
|
6440
|
+
return { free: [], duplicates: [], unconvertible: [], content: `${SECTION_MARKER}
|
|
5982
6441
|
${[...carried, `import { ${helpers.join(", ")} } from "${helperSpecifier}";`, `import type { BcmsSectionProps } from "${relativeImport(path, SECTIONS_LIB).replace(/\.tsx?$/, "")}";`].join("\n")}
|
|
5983
6442
|
${carriedDeclarations === "" ? "" : `
|
|
5984
6443
|
${carriedDeclarations}`}
|
|
5985
6444
|
export default function ${resolved.name}({ blockId, bind, overrides, page }: BcmsSectionProps) {
|
|
5986
|
-
${
|
|
6445
|
+
${declared.split("\n").join("\n ")}
|
|
5987
6446
|
return (
|
|
5988
6447
|
${markup}
|
|
5989
6448
|
);
|
|
5990
6449
|
}
|
|
5991
6450
|
` };
|
|
5992
6451
|
}
|
|
5993
|
-
function
|
|
5994
|
-
const slice = new
|
|
5995
|
-
|
|
5996
|
-
|
|
6452
|
+
function sectionMarkup(content, root, fieldName) {
|
|
6453
|
+
const slice = new MagicString4(content.slice(root.start, root.end));
|
|
6454
|
+
const at = (offset) => offset - root.start;
|
|
6455
|
+
const stamps = root.attrs.filter((attr) => attr.name === "data-bcms-block");
|
|
6456
|
+
if (stamps.length === 0) slice.appendLeft(at(root.attrInsertAt), " data-bcms-block={blockId}");
|
|
6457
|
+
else {
|
|
6458
|
+
slice.overwrite(at(stamps[0].range.start), at(stamps[0].range.end), "data-bcms-block={blockId}");
|
|
6459
|
+
for (const extra of stamps.slice(1)) {
|
|
6460
|
+
let from = extra.range.start;
|
|
6461
|
+
while (from > root.start && /\s/.test(content[from - 1])) from--;
|
|
6462
|
+
slice.remove(at(from), at(extra.range.end));
|
|
6463
|
+
}
|
|
6464
|
+
}
|
|
6465
|
+
const wrapped = wrapFieldAttrs(slice, content, elementsUnder(root), at, fieldName);
|
|
6466
|
+
return { markup: slice.toString(), ...wrapped };
|
|
6467
|
+
}
|
|
6468
|
+
function wrapFieldAttrs(slice, content, elements, at, fieldName) {
|
|
6469
|
+
let bound = 0;
|
|
6470
|
+
const duplicates = [];
|
|
6471
|
+
for (const element of elements) {
|
|
6472
|
+
const fields = element.attrs.filter((attr) => attr.name === "data-bcms-field");
|
|
6473
|
+
if (fields.length > 1) {
|
|
6474
|
+
duplicates.push(`<${element.tag}>`);
|
|
6475
|
+
continue;
|
|
6476
|
+
}
|
|
6477
|
+
for (const attr of fields) {
|
|
6478
|
+
bound++;
|
|
6479
|
+
if (attr.expressionRange) {
|
|
6480
|
+
slice.appendRight(at(attr.expressionRange.start) + 1, `${fieldName}(`);
|
|
6481
|
+
slice.appendLeft(at(attr.expressionRange.end) - 1, ")");
|
|
6482
|
+
continue;
|
|
6483
|
+
}
|
|
6484
|
+
const value = attr.valueRange ? content.slice(attr.valueRange.start, attr.valueRange.end) : "";
|
|
6485
|
+
slice.overwrite(
|
|
6486
|
+
at(attr.range.start),
|
|
6487
|
+
at(attr.range.end),
|
|
6488
|
+
`data-bcms-field={${fieldName}(${JSON.stringify(value)})}`
|
|
6489
|
+
);
|
|
6490
|
+
}
|
|
6491
|
+
}
|
|
6492
|
+
return { bound, duplicates };
|
|
6493
|
+
}
|
|
6494
|
+
function provableAddress(expression) {
|
|
6495
|
+
const first = expression[0];
|
|
6496
|
+
if (first === '"' || first === "'") {
|
|
6497
|
+
for (let at = 1; at < expression.length; at++) {
|
|
6498
|
+
if (expression[at] === "\\") {
|
|
6499
|
+
at++;
|
|
6500
|
+
continue;
|
|
6501
|
+
}
|
|
6502
|
+
if (expression[at] === first) return at === expression.length - 1 ? expression.slice(1, at) : null;
|
|
6503
|
+
}
|
|
6504
|
+
return null;
|
|
6505
|
+
}
|
|
6506
|
+
if (first !== "`" || expression.length < 2 || !expression.endsWith("`")) return null;
|
|
6507
|
+
const body = expression.slice(1, -1);
|
|
6508
|
+
if (body.includes("`") || body.includes("\\")) return null;
|
|
6509
|
+
let head = null;
|
|
6510
|
+
for (let at = 0; at < body.length; at++) {
|
|
6511
|
+
if (body[at] !== "$" || body[at + 1] !== "{") continue;
|
|
6512
|
+
const close = body.indexOf("}", at + 2);
|
|
6513
|
+
if (close < 0) return null;
|
|
6514
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(body.slice(at + 2, close).trim())) return null;
|
|
6515
|
+
if (body[at - 1] !== "[" || body[close + 1] !== "]") return null;
|
|
6516
|
+
head ??= body.slice(0, at);
|
|
6517
|
+
at = close;
|
|
6518
|
+
}
|
|
6519
|
+
return head ?? body;
|
|
6520
|
+
}
|
|
6521
|
+
function fieldAddresses(content, elements) {
|
|
6522
|
+
const heads = [];
|
|
6523
|
+
const opaque = [];
|
|
6524
|
+
for (const element of elements) {
|
|
6525
|
+
for (const attr of element.attrs) {
|
|
6526
|
+
if (attr.name !== "data-bcms-field") continue;
|
|
6527
|
+
if (!attr.expression && attr.valueRange) {
|
|
6528
|
+
heads.push(content.slice(attr.valueRange.start, attr.valueRange.end));
|
|
6529
|
+
continue;
|
|
6530
|
+
}
|
|
6531
|
+
const inner = attr.expressionRange ? content.slice(attr.expressionRange.start + 1, attr.expressionRange.end - 1).trim() : "";
|
|
6532
|
+
const head = provableAddress(inner);
|
|
6533
|
+
if (head === null) opaque.push(`data-bcms-field={${inner}}`);
|
|
6534
|
+
else heads.push(head);
|
|
6535
|
+
}
|
|
6536
|
+
}
|
|
6537
|
+
return { heads, opaque };
|
|
6538
|
+
}
|
|
6539
|
+
function elementsUnder(...roots) {
|
|
6540
|
+
const out = [];
|
|
6541
|
+
const walk = (node) => {
|
|
6542
|
+
if (node.type === "element") {
|
|
6543
|
+
out.push(node);
|
|
6544
|
+
for (const child of node.children) walk(child);
|
|
6545
|
+
return;
|
|
6546
|
+
}
|
|
6547
|
+
if (node.type === "expr") for (const child of node.children) walk(child);
|
|
6548
|
+
};
|
|
6549
|
+
for (const root of roots) walk(root);
|
|
6550
|
+
return out;
|
|
6551
|
+
}
|
|
6552
|
+
var V1_ANCHOR = {
|
|
6553
|
+
astro: "const { blockId, bind, overrides, page } = Astro.props;",
|
|
6554
|
+
jsx: "}: BcmsSectionProps) {"
|
|
6555
|
+
};
|
|
6556
|
+
var refusalKey = (dialect, slug) => `${dialect}\0${slug}`;
|
|
6557
|
+
function generatedBindings(parsed) {
|
|
6558
|
+
const program = parsed.ast?.program;
|
|
6559
|
+
const body = program?.body ?? [];
|
|
6560
|
+
const scopes = [body];
|
|
6561
|
+
for (const statement of body) {
|
|
6562
|
+
const declaration2 = statement.type === "ExportDefaultDeclaration" ? statement.declaration : void 0;
|
|
6563
|
+
const inner = declaration2?.type === "FunctionDeclaration" ? declaration2.body?.body : void 0;
|
|
6564
|
+
if (inner) scopes.push(inner);
|
|
6565
|
+
}
|
|
6566
|
+
const out = /* @__PURE__ */ new Map();
|
|
6567
|
+
for (const scope of scopes) {
|
|
6568
|
+
for (const statement of scope) {
|
|
6569
|
+
if (statement.type !== "VariableDeclaration") continue;
|
|
6570
|
+
for (const declarator of statement.declarations ?? []) {
|
|
6571
|
+
const id = declarator.id;
|
|
6572
|
+
const name = id?.type === "Identifier" ? id.name : void 0;
|
|
6573
|
+
if (name !== void 0 && !out.has(name)) out.set(name, declarator.init);
|
|
6574
|
+
}
|
|
6575
|
+
}
|
|
6576
|
+
}
|
|
6577
|
+
return out;
|
|
6578
|
+
}
|
|
6579
|
+
function stripBindFallback(parsed, source, content, locals) {
|
|
6580
|
+
if (!parsed.ast?.program) return "unreadable";
|
|
6581
|
+
const offset = parsed.script?.offset ?? 0;
|
|
6582
|
+
let rewritten = 0;
|
|
6583
|
+
let unproven = 0;
|
|
6584
|
+
walkAst(parsed.ast, (node) => {
|
|
6585
|
+
if (node.type !== "CallExpression") return;
|
|
6586
|
+
const argument = (node.arguments ?? [])[1];
|
|
6587
|
+
if (argument?.type !== "LogicalExpression" || argument.operator !== "??") return;
|
|
6588
|
+
const callee = node.callee;
|
|
6589
|
+
if (callee?.type !== "Identifier" || !locals.has(callee.name)) {
|
|
6590
|
+
unproven++;
|
|
6591
|
+
return;
|
|
6592
|
+
}
|
|
6593
|
+
const left = argument.left;
|
|
6594
|
+
source.overwrite(
|
|
6595
|
+
offset + argument.start,
|
|
6596
|
+
offset + argument.end,
|
|
6597
|
+
content.slice(offset + left.start, offset + left.end)
|
|
6598
|
+
);
|
|
6599
|
+
rewritten++;
|
|
6600
|
+
});
|
|
6601
|
+
return rewritten === 0 && unproven > 0 ? "unproven" : "ok";
|
|
6602
|
+
}
|
|
6603
|
+
var isCallOf = (init, callee) => {
|
|
6604
|
+
const fn = init?.type === "CallExpression" ? init.callee : void 0;
|
|
6605
|
+
return fn?.type === "Identifier" && fn.name === callee;
|
|
6606
|
+
};
|
|
6607
|
+
async function migrateSections(plan2, contents, written, fileSet, aliases) {
|
|
6608
|
+
const files = [];
|
|
6609
|
+
const refused = /* @__PURE__ */ new Map();
|
|
6610
|
+
const prefixesFor = (slug) => {
|
|
6611
|
+
const out = /* @__PURE__ */ new Set();
|
|
6612
|
+
for (const page of plan2.pages ?? []) {
|
|
6613
|
+
for (const section of page.sections ?? []) {
|
|
6614
|
+
if (resolveComponent(plan2, section).slug !== slug) continue;
|
|
6615
|
+
const prefix = propPrefix(section);
|
|
6616
|
+
if (prefix !== null) out.add(prefix);
|
|
6617
|
+
}
|
|
6618
|
+
}
|
|
6619
|
+
return [...out];
|
|
6620
|
+
};
|
|
6621
|
+
for (const [dialect, components] of written) {
|
|
6622
|
+
for (const [slug, entry] of components) {
|
|
6623
|
+
const content = contents.get(entry.path);
|
|
6624
|
+
if (content === void 0 || content.includes(SECTION_MARKER)) continue;
|
|
6625
|
+
const older = SECTION_MARKERS.find((marker) => content.includes(marker));
|
|
6626
|
+
if (older === void 0) continue;
|
|
6627
|
+
const candidates = prefixesFor(slug);
|
|
6628
|
+
if (candidates.length === 0) continue;
|
|
6629
|
+
const parsed = await parseFile4(dialect, content);
|
|
6630
|
+
if (parsed.error) {
|
|
6631
|
+
refused.set(refusalKey(dialect, slug), `${entry.path} carries our marker but does not parse (${parsed.error.message}).`);
|
|
6632
|
+
continue;
|
|
6633
|
+
}
|
|
6634
|
+
const elements = elementsUnder(...parsed.roots);
|
|
6635
|
+
const imports = [...importsIn(parsed, dialect)];
|
|
6636
|
+
const helperModule = HELPER_PATH[dialect];
|
|
6637
|
+
const helperLocalsFor = (exported) => new Set(
|
|
6638
|
+
imports.filter(
|
|
6639
|
+
([, from]) => from.imported === exported && resolveSpecifier(entry.path, from.specifier, fileSet, aliases) === helperModule
|
|
6640
|
+
).map(([local]) => local)
|
|
6641
|
+
);
|
|
6642
|
+
const { heads: evidence, opaque } = fieldAddresses(content, elements);
|
|
6643
|
+
const pathPrefix = evidence.length + opaque.length === 0 ? "" : opaque.length > 0 ? null : onlyOne(candidates.filter((prefix) => evidence.every((head) => head.startsWith(prefix)))) ?? null;
|
|
6644
|
+
if (pathPrefix === null) {
|
|
6645
|
+
const matching = candidates.filter((prefix) => evidence.every((head) => head.startsWith(prefix)));
|
|
6646
|
+
refused.set(
|
|
6647
|
+
refusalKey(dialect, slug),
|
|
6648
|
+
opaque.length > 0 ? `${entry.path} carries ${[...new Set(opaque)].slice(0, 3).join(", ")}, whose address this cannot read \u2014 so there is no prefix it can be shown to convert through.` : matching.length === 0 ? `${entry.path} declares ${evidence.length > 0 ? `addresses (${[...new Set(evidence)].slice(0, 3).join(", ")})` : "addresses"} that match none of this slug's placements (${candidates.join(", ") || "none with a usable prop map"}), so the prefix its markup was cut with cannot be recovered.` : `${entry.path} matches more than one of this slug's placements (${matching.join(", ")}), so the prefix its markup was cut with is ambiguous.`
|
|
6649
|
+
);
|
|
6650
|
+
continue;
|
|
6651
|
+
}
|
|
6652
|
+
const source = new MagicString4(content);
|
|
6653
|
+
const { bound, duplicates } = wrapFieldAttrs(source, content, elements, (at) => at, "bcmsField");
|
|
6654
|
+
if (duplicates.length > 0) {
|
|
6655
|
+
refused.set(refusalKey(dialect, slug), `${entry.path} has ${duplicates.join(", ")} carrying more than one data-bcms-field, so there is no single address to rewrite.`);
|
|
6656
|
+
continue;
|
|
6657
|
+
}
|
|
6658
|
+
const markerEnd = content.indexOf(older) + older.length;
|
|
6659
|
+
source.overwrite(markerEnd - older.length, markerEnd, SECTION_MARKER);
|
|
6660
|
+
const stripped = stripBindFallback(parsed, source, content, helperLocalsFor("bcmsSection"));
|
|
6661
|
+
if (stripped !== "ok") {
|
|
6662
|
+
refused.set(
|
|
6663
|
+
refusalKey(dialect, slug),
|
|
6664
|
+
stripped === "unreadable" ? `${entry.path} carries our marker but its module could not be read as a program, so the Phase A \`bind ?? "\u2026"\` fallback cannot be shown to be gone.` : `${entry.path} carries a \`?? \` fallback in a call this cannot identify as ${helperModule}'s bcmsSection, so the Phase A page-group default cannot be shown to be gone.`
|
|
6665
|
+
);
|
|
6666
|
+
continue;
|
|
6667
|
+
}
|
|
6668
|
+
if (bound > 0) {
|
|
6669
|
+
const locals = generatedBindings(parsed);
|
|
6670
|
+
const names = /* @__PURE__ */ new Set([...topLevelBindings(dialect, parsed, content), ...locals.keys()]);
|
|
6671
|
+
const fieldsLocal = [...helperLocalsFor("bcmsFields")][0];
|
|
6672
|
+
if (fieldsLocal === void 0 && names.has("bcmsFields")) {
|
|
6673
|
+
refused.set(
|
|
6674
|
+
refusalKey(dialect, slug),
|
|
6675
|
+
`${entry.path} already binds "bcmsFields" to something other than ${helperModule}'s export.`
|
|
6676
|
+
);
|
|
6677
|
+
continue;
|
|
6678
|
+
}
|
|
6679
|
+
const declared = names.has("bcmsField");
|
|
6680
|
+
if (declared && (fieldsLocal === void 0 || !isCallOf(locals.get("bcmsField"), fieldsLocal))) {
|
|
6681
|
+
refused.set(
|
|
6682
|
+
refusalKey(dialect, slug),
|
|
6683
|
+
`${entry.path} already binds "bcmsField" to something other than a call of ${helperModule}'s bcmsFields.`
|
|
6684
|
+
);
|
|
6685
|
+
continue;
|
|
6686
|
+
}
|
|
6687
|
+
if (!declared) {
|
|
6688
|
+
const anchor = V1_ANCHOR[dialect];
|
|
6689
|
+
const at = anchor ? content.indexOf(anchor) : -1;
|
|
6690
|
+
if (anchor === void 0 || at < 0) {
|
|
6691
|
+
refused.set(refusalKey(dialect, slug), `${entry.path} carries our marker but no longer declares its placement, so there is nowhere the chooser can be put.`);
|
|
6692
|
+
continue;
|
|
6693
|
+
}
|
|
6694
|
+
source.appendLeft(
|
|
6695
|
+
at + anchor.length,
|
|
6696
|
+
`
|
|
6697
|
+
${dialect === "astro" ? "" : " "}const bcmsField = ${fieldsLocal ?? "bcmsFields"}({ blockId, bind, overrides }, ${JSON.stringify(pathPrefix)});`
|
|
6698
|
+
);
|
|
6699
|
+
}
|
|
6700
|
+
if (fieldsLocal === void 0) {
|
|
6701
|
+
const helper = imports.find(([, from]) => from.imported === "bcmsSection")?.[1].specifier ?? relativeImport(entry.path, HELPER_PATH[dialect]).replace(/\.tsx?$/, "");
|
|
6702
|
+
source.appendLeft(markerEnd, `
|
|
6703
|
+
import { bcmsFields } from "${helper}";`);
|
|
6704
|
+
}
|
|
6705
|
+
}
|
|
6706
|
+
files.push({ path: entry.path, operation: "modify", content: source.toString() });
|
|
6707
|
+
}
|
|
6708
|
+
}
|
|
6709
|
+
return { files, refused };
|
|
6710
|
+
}
|
|
6711
|
+
function propPrefix(section) {
|
|
6712
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
6713
|
+
for (const field of section.fields ?? []) {
|
|
6714
|
+
const { path, key } = field;
|
|
6715
|
+
if (typeof path !== "string" || typeof key !== "string") continue;
|
|
6716
|
+
if (!path.endsWith(key)) return null;
|
|
6717
|
+
prefixes.add(path.slice(0, path.length - key.length));
|
|
6718
|
+
}
|
|
6719
|
+
if (prefixes.size > 1) return null;
|
|
6720
|
+
return prefixes.size === 1 ? [...prefixes][0] : `${section.groupKey}.`;
|
|
6721
|
+
}
|
|
6722
|
+
function referencedNames2(markup) {
|
|
6723
|
+
const out = /* @__PURE__ */ new Set();
|
|
6724
|
+
for (const [name] of markup.matchAll(/[A-Za-z_$][\w$]*/g)) out.add(name);
|
|
6725
|
+
return out;
|
|
5997
6726
|
}
|
|
5998
6727
|
var alias = (name, local) => name === local ? name : `${name} as ${local}`;
|
|
5999
6728
|
var mentions = (markup, name) => new RegExp(`(?<![\\w$])${name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?![\\w$])`).test(markup);
|
|
@@ -6197,7 +6926,7 @@ export default function Sections({ page }: { page: string }) {
|
|
|
6197
6926
|
function splicePageRun(file, content, dialect, parsed, placeable, pageSlug) {
|
|
6198
6927
|
const names = allocateNames(topLevelBindings(dialect, parsed, content), [{ key: "Sections", name: "Sections" }]);
|
|
6199
6928
|
const local = names.get("Sections");
|
|
6200
|
-
const source = new
|
|
6929
|
+
const source = new MagicString4(content);
|
|
6201
6930
|
const first = placeable[0].root;
|
|
6202
6931
|
const last = placeable[placeable.length - 1].root;
|
|
6203
6932
|
source.overwrite(first.start, last.end, `<${local} page="${pageSlug}" />`);
|
|
@@ -6217,7 +6946,7 @@ function splicePageInline(file, content, dialect, parsed, placeable, pageSlug, w
|
|
|
6217
6946
|
{ key: "pageSnapshot", name: "pageSnapshot" }
|
|
6218
6947
|
]);
|
|
6219
6948
|
const snapshot = names.get("pageSnapshot");
|
|
6220
|
-
const source = new
|
|
6949
|
+
const source = new MagicString4(content);
|
|
6221
6950
|
for (const resolved of placeable) {
|
|
6222
6951
|
const component = written.get(resolved.slug);
|
|
6223
6952
|
const tag = component.member ? `${names.get(resolved.slug)}.${component.member}` : names.get(resolved.slug);
|
|
@@ -6252,7 +6981,7 @@ function addImports(source, parsed, lines) {
|
|
|
6252
6981
|
else source.appendLeft(at.at, `
|
|
6253
6982
|
${lines.join("\n")}`);
|
|
6254
6983
|
}
|
|
6255
|
-
var isGenerated = (content) => content.includes(
|
|
6984
|
+
var isGenerated = (content) => SECTION_MARKERS.some((marker) => content.includes(marker)) || content.includes(REGISTRY_MARKER);
|
|
6256
6985
|
function emitHelper(out, contents, version, overwrite) {
|
|
6257
6986
|
const path = HELPER_PATH.astro;
|
|
6258
6987
|
const source = helperSource("astro");
|
|
@@ -6298,6 +7027,10 @@ function readComponentizePlan(value) {
|
|
|
6298
7027
|
}
|
|
6299
7028
|
|
|
6300
7029
|
// src/index.ts
|
|
7030
|
+
var UNPARSEABLE_DIALECT = {
|
|
7031
|
+
roots: [],
|
|
7032
|
+
error: { code: "DIALECT_UNSUPPORTED", message: "no parser for this extension" }
|
|
7033
|
+
};
|
|
6301
7034
|
var ConvertError = class extends Error {
|
|
6302
7035
|
constructor(code, message) {
|
|
6303
7036
|
super(message);
|
|
@@ -6379,7 +7112,7 @@ async function convertSources(briefIn, sources, options) {
|
|
|
6379
7112
|
}
|
|
6380
7113
|
const { located, unlocated } = locate(brief, sources, { stripTags: true });
|
|
6381
7114
|
const pending = [];
|
|
6382
|
-
const
|
|
7115
|
+
const pend2 = (target, reason, file, message) => {
|
|
6383
7116
|
pending.push({
|
|
6384
7117
|
route: target.route,
|
|
6385
7118
|
scope: target.scope,
|
|
@@ -6480,7 +7213,7 @@ async function convertSources(briefIn, sources, options) {
|
|
|
6480
7213
|
declaredRow(target, false, byHand);
|
|
6481
7214
|
continue;
|
|
6482
7215
|
}
|
|
6483
|
-
|
|
7216
|
+
pend2(target, miss.reason === "no-original" ? "NO_ORIGINAL" : "NOT_IN_SOURCE");
|
|
6484
7217
|
}
|
|
6485
7218
|
const byFile = /* @__PURE__ */ new Map();
|
|
6486
7219
|
for (const [key, hit] of locatedByKey) {
|
|
@@ -6537,12 +7270,12 @@ async function convertSources(briefIn, sources, options) {
|
|
|
6537
7270
|
const worst = outcomes.find((o) => o !== "rewritten" && o !== "declared");
|
|
6538
7271
|
if (worst) {
|
|
6539
7272
|
const file = hit.files[outcomes.indexOf(worst)];
|
|
6540
|
-
|
|
7273
|
+
pend2(target, worst, file);
|
|
6541
7274
|
} else if (outcomes.every((o) => o === "declared")) {
|
|
6542
7275
|
const witness = await witnessed(target);
|
|
6543
7276
|
const byHand = witness === null ? declaredByHand(target, hit.files) : null;
|
|
6544
7277
|
if (witness === null && byHand === null) {
|
|
6545
|
-
|
|
7278
|
+
pend2(
|
|
6546
7279
|
target,
|
|
6547
7280
|
"AMBIGUOUS_LITERAL",
|
|
6548
7281
|
hit.files[0],
|
|
@@ -6555,7 +7288,7 @@ async function convertSources(briefIn, sources, options) {
|
|
|
6555
7288
|
} else rewritten++;
|
|
6556
7289
|
}
|
|
6557
7290
|
for (const [file, splices] of [...componentEdits].sort(([a], [b]) => a.localeCompare(b))) {
|
|
6558
|
-
const source = new
|
|
7291
|
+
const source = new MagicString5(contents.get(file));
|
|
6559
7292
|
const seen = /* @__PURE__ */ new Set();
|
|
6560
7293
|
for (const splice of splices) {
|
|
6561
7294
|
const id = `${splice.start}:${splice.end}:${splice.text}`;
|
|
@@ -6576,6 +7309,28 @@ async function convertSources(briefIn, sources, options) {
|
|
|
6576
7309
|
}
|
|
6577
7310
|
const { helpers, helperUpgraded } = emitHelpers(dialects, contents, outFiles, options?.overwriteHelper);
|
|
6578
7311
|
emitStubs2(dialects, snapshotSlugs, contents, outFiles);
|
|
7312
|
+
const latest = new Map(contents);
|
|
7313
|
+
for (const file of outFiles) if (file.operation === "modify") latest.set(file.path, file.content);
|
|
7314
|
+
const canvasSources = [];
|
|
7315
|
+
for (const source of sources) {
|
|
7316
|
+
const content = latest.get(source.path) ?? source.content;
|
|
7317
|
+
if (!/\.[jt]sx$/i.test(source.path)) {
|
|
7318
|
+
canvasSources.push({ path: source.path, content });
|
|
7319
|
+
continue;
|
|
7320
|
+
}
|
|
7321
|
+
const parsed = content === source.content ? await parseOf(source.path) : await parseFile4("jsx", content);
|
|
7322
|
+
canvasSources.push({ path: source.path, content, ...parsed && !parsed.error ? { ast: parsed.ast } : {} });
|
|
7323
|
+
}
|
|
7324
|
+
const canvas = canvasBridge(
|
|
7325
|
+
canvasSources,
|
|
7326
|
+
new Map(brief.pages.map((page) => [page.route, page.slug]))
|
|
7327
|
+
);
|
|
7328
|
+
for (const [path, content] of canvas.edits) {
|
|
7329
|
+
const existing = outFiles.find((f) => f.path === path);
|
|
7330
|
+
if (existing) existing.content = content;
|
|
7331
|
+
else outFiles.push({ path, operation: "modify", content });
|
|
7332
|
+
}
|
|
7333
|
+
pending.push(...canvas.pending);
|
|
6579
7334
|
const receipt = assertReceipt({
|
|
6580
7335
|
briefDigest: digest,
|
|
6581
7336
|
paths: {
|
|
@@ -6592,6 +7347,7 @@ async function convertSources(briefIn, sources, options) {
|
|
|
6592
7347
|
helpers,
|
|
6593
7348
|
helperUpgraded,
|
|
6594
7349
|
dynamicBindings,
|
|
7350
|
+
canvas: { lane: canvas.lane, ...canvas.files.length > 0 ? { files: canvas.files } : {} },
|
|
6595
7351
|
notes: unreadableConfigs(sources).map(
|
|
6596
7352
|
(file) => ({
|
|
6597
7353
|
code: "TSCONFIG_UNREADABLE",
|
|
@@ -6624,6 +7380,30 @@ async function convertFile(file, content, targets, parsed, declared, llmFallback
|
|
|
6624
7380
|
};
|
|
6625
7381
|
if (pendingTargets.length === 0) return empty;
|
|
6626
7382
|
if (!dialect) {
|
|
7383
|
+
if (isTolerantDialect(file)) {
|
|
7384
|
+
const tolerantLiterals = [...new Set(pendingTargets.map((t) => t.literal))];
|
|
7385
|
+
const tolerantSites = findSitesTolerant(content, tolerantLiterals);
|
|
7386
|
+
if (tolerantSites.length > 0) {
|
|
7387
|
+
return plan(
|
|
7388
|
+
file,
|
|
7389
|
+
content,
|
|
7390
|
+
// Never read: `plan` forces `writing = "html"` at tier 2, and every other use of this
|
|
7391
|
+
// argument is behind `!parsed.error` or `tier !== 2`. Passing the writing dialect keeps
|
|
7392
|
+
// the parameter honest rather than widening its type for one caller.
|
|
7393
|
+
"html",
|
|
7394
|
+
pendingTargets,
|
|
7395
|
+
tolerantSites,
|
|
7396
|
+
void 0,
|
|
7397
|
+
// no parse, no import point: tier 2 declares, it does not read
|
|
7398
|
+
outcomes,
|
|
7399
|
+
2,
|
|
7400
|
+
UNPARSEABLE_DIALECT,
|
|
7401
|
+
drill,
|
|
7402
|
+
[]
|
|
7403
|
+
// no parse, so nothing in this file is already declared
|
|
7404
|
+
);
|
|
7405
|
+
}
|
|
7406
|
+
}
|
|
6627
7407
|
for (const target of pendingTargets) outcomes.set(target.key, "DIALECT_UNSUPPORTED");
|
|
6628
7408
|
return { ...empty, dialect: null, outcomes };
|
|
6629
7409
|
}
|
|
@@ -6682,7 +7462,7 @@ async function convertFile(file, content, targets, parsed, declared, llmFallback
|
|
|
6682
7462
|
}
|
|
6683
7463
|
async function plan(file, content, dialect, targets, sites, importAt, outcomes, tier, parsed, drill, declarations) {
|
|
6684
7464
|
const writing = tier === 2 ? "html" : dialect;
|
|
6685
|
-
const missing = tier
|
|
7465
|
+
const missing = tier !== 2 ? "NOT_IN_SOURCE" : parsed.error?.code === "DIALECT_UNSUPPORTED" ? "DIALECT_UNSUPPORTED" : "PARSE_ERROR";
|
|
6686
7466
|
const taken = parsed.error ? /* @__PURE__ */ new Set() : topLevelBindings(dialect, parsed, content);
|
|
6687
7467
|
const slugs = [...new Set(targets.map((t) => t.slug))].sort();
|
|
6688
7468
|
const preset = /* @__PURE__ */ new Map();
|
|
@@ -7104,6 +7884,7 @@ function emitStubs2(dialects, slugs, contents, out) {
|
|
|
7104
7884
|
}
|
|
7105
7885
|
}
|
|
7106
7886
|
export {
|
|
7887
|
+
ASTRO_LANE_PENDING,
|
|
7107
7888
|
COMPONENT_DIR,
|
|
7108
7889
|
CONTENT_DIR,
|
|
7109
7890
|
ConvertError,
|
|
@@ -7118,6 +7899,7 @@ export {
|
|
|
7118
7899
|
SKIP_DIRS,
|
|
7119
7900
|
SKIP_FILES,
|
|
7120
7901
|
SOURCE_EXTENSIONS,
|
|
7902
|
+
SSR_DRAFT_RECIPE,
|
|
7121
7903
|
STUB_CONTENT,
|
|
7122
7904
|
SectionInvariantError,
|
|
7123
7905
|
ValidationError,
|
|
@@ -7126,6 +7908,7 @@ export {
|
|
|
7126
7908
|
assertSections,
|
|
7127
7909
|
attrsFor,
|
|
7128
7910
|
briefDigest,
|
|
7911
|
+
canvasBridge,
|
|
7129
7912
|
carryFor,
|
|
7130
7913
|
componentizeSources,
|
|
7131
7914
|
convertSources,
|