@bettercms-ai/convert 0.4.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 +28 -0
- package/README.md +12 -0
- package/dist/cli.js +330 -50
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +45 -2
- package/dist/index.js +333 -50
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import { readdir, readFile, writeFile, mkdir, stat } from "fs/promises";
|
|
|
5
5
|
import { dirname, join as join2, relative, resolve, sep } from "path";
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
|
8
|
-
import
|
|
8
|
+
import MagicString5 from "magic-string";
|
|
9
9
|
|
|
10
10
|
// src/brief.ts
|
|
11
11
|
import { createHash } from "crypto";
|
|
@@ -27,6 +27,21 @@ var BY_EXTENSION = {
|
|
|
27
27
|
".svelte": "svelte",
|
|
28
28
|
".vue": "vue"
|
|
29
29
|
};
|
|
30
|
+
var TOLERANT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
31
|
+
".md",
|
|
32
|
+
".mdx",
|
|
33
|
+
".njk",
|
|
34
|
+
".hbs",
|
|
35
|
+
".ejs",
|
|
36
|
+
".liquid",
|
|
37
|
+
".erb",
|
|
38
|
+
".twig"
|
|
39
|
+
]);
|
|
40
|
+
function isTolerantDialect(path) {
|
|
41
|
+
const lower = path.toLowerCase();
|
|
42
|
+
const at = lower.lastIndexOf(".");
|
|
43
|
+
return at >= 0 && TOLERANT_EXTENSIONS.has(lower.slice(at));
|
|
44
|
+
}
|
|
30
45
|
function dialectOf(path, _content) {
|
|
31
46
|
const lower = path.toLowerCase();
|
|
32
47
|
const at = lower.lastIndexOf(".");
|
|
@@ -4263,6 +4278,7 @@ function importLines(imports) {
|
|
|
4263
4278
|
}
|
|
4264
4279
|
|
|
4265
4280
|
// src/receipt.ts
|
|
4281
|
+
var CANVAS_REASONS = /* @__PURE__ */ new Set(["CANVAS_BRIDGE_MANUAL", "SSR_DRAFT_ROUTE"]);
|
|
4266
4282
|
var ReceiptInvariantError = class extends Error {
|
|
4267
4283
|
code = "RECEIPT_INVARIANT";
|
|
4268
4284
|
constructor(message) {
|
|
@@ -4272,10 +4288,11 @@ var ReceiptInvariantError = class extends Error {
|
|
|
4272
4288
|
};
|
|
4273
4289
|
function assertReceipt(receipt) {
|
|
4274
4290
|
const { declared, rewritten, alreadyDeclared, pending } = receipt.paths;
|
|
4275
|
-
const
|
|
4291
|
+
const paths = pending.filter((p) => !CANVAS_REASONS.has(p.reason));
|
|
4292
|
+
const sum = rewritten + alreadyDeclared + paths.length;
|
|
4276
4293
|
if (sum !== declared) {
|
|
4277
4294
|
throw new ReceiptInvariantError(
|
|
4278
|
-
`The receipt does not account for every path: ${declared} declared but ${rewritten} rewritten + ${alreadyDeclared} already declared + ${
|
|
4295
|
+
`The receipt does not account for every path: ${declared} declared but ${rewritten} rewritten + ${alreadyDeclared} already declared + ${paths.length} pending = ${sum}.`
|
|
4279
4296
|
);
|
|
4280
4297
|
}
|
|
4281
4298
|
if (receipt.occurrences.rewritten > receipt.occurrences.located) {
|
|
@@ -4775,6 +4792,218 @@ function tryParse3(code) {
|
|
|
4775
4792
|
var PARSE_FILE = { html: parse2, astro: parseFile, jsx: parse, svelte: parseFile2, vue: parseFile3 };
|
|
4776
4793
|
var parseFile4 = (dialect, content) => Promise.resolve(PARSE_FILE[dialect](content));
|
|
4777
4794
|
|
|
4795
|
+
// src/canvas.ts
|
|
4796
|
+
import MagicString3 from "magic-string";
|
|
4797
|
+
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'.";
|
|
4798
|
+
var isNode = (value) => !!value && typeof value === "object" && typeof value.type === "string";
|
|
4799
|
+
var FUNCTIONS3 = /* @__PURE__ */ new Set([
|
|
4800
|
+
"FunctionDeclaration",
|
|
4801
|
+
"FunctionExpression",
|
|
4802
|
+
"ArrowFunctionExpression",
|
|
4803
|
+
"ObjectMethod",
|
|
4804
|
+
"ClassMethod"
|
|
4805
|
+
]);
|
|
4806
|
+
function patternNames2(node, out) {
|
|
4807
|
+
if (!isNode(node)) return;
|
|
4808
|
+
switch (node.type) {
|
|
4809
|
+
case "Identifier":
|
|
4810
|
+
out.add(String(node.name));
|
|
4811
|
+
return;
|
|
4812
|
+
case "ObjectPattern":
|
|
4813
|
+
for (const p of node.properties ?? [])
|
|
4814
|
+
patternNames2(p.type === "RestElement" ? p.argument : p.value, out);
|
|
4815
|
+
return;
|
|
4816
|
+
case "ArrayPattern":
|
|
4817
|
+
for (const el of node.elements ?? []) patternNames2(el, out);
|
|
4818
|
+
return;
|
|
4819
|
+
case "AssignmentPattern":
|
|
4820
|
+
patternNames2(node.left, out);
|
|
4821
|
+
return;
|
|
4822
|
+
case "RestElement":
|
|
4823
|
+
patternNames2(node.argument, out);
|
|
4824
|
+
return;
|
|
4825
|
+
default:
|
|
4826
|
+
return;
|
|
4827
|
+
}
|
|
4828
|
+
}
|
|
4829
|
+
function statementNames2(statements, out) {
|
|
4830
|
+
for (const statement of statements ?? []) {
|
|
4831
|
+
if (!isNode(statement)) continue;
|
|
4832
|
+
const node = statement.type === "ExportNamedDeclaration" || statement.type === "ExportDefaultDeclaration" ? statement.declaration : statement;
|
|
4833
|
+
if (!isNode(node)) continue;
|
|
4834
|
+
if (node.type === "VariableDeclaration")
|
|
4835
|
+
for (const d of node.declarations ?? []) patternNames2(d.id, out);
|
|
4836
|
+
else if (node.type === "FunctionDeclaration" || node.type === "ClassDeclaration") patternNames2(node.id, out);
|
|
4837
|
+
else if (node.type === "ImportDeclaration")
|
|
4838
|
+
for (const specifier of node.specifiers ?? []) patternNames2(specifier.local, out);
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
function functionBindings(fn) {
|
|
4842
|
+
const out = /* @__PURE__ */ new Set();
|
|
4843
|
+
for (const param of fn.params ?? []) patternNames2(param, out);
|
|
4844
|
+
const body = fn.body;
|
|
4845
|
+
if (body?.type === "BlockStatement") statementNames2(body.body, out);
|
|
4846
|
+
return out;
|
|
4847
|
+
}
|
|
4848
|
+
function stringLiterals(node, out) {
|
|
4849
|
+
if (!isNode(node)) {
|
|
4850
|
+
if (Array.isArray(node)) for (const child of node) stringLiterals(child, out);
|
|
4851
|
+
return;
|
|
4852
|
+
}
|
|
4853
|
+
if (FUNCTIONS3.has(node.type)) return;
|
|
4854
|
+
if (node.type === "StringLiteral" && typeof node.value === "string") out.add(node.value);
|
|
4855
|
+
for (const key of Object.keys(node)) {
|
|
4856
|
+
if (key === "loc" || key === "leadingComments" || key === "trailingComments") continue;
|
|
4857
|
+
stringLiterals(node[key], out);
|
|
4858
|
+
}
|
|
4859
|
+
}
|
|
4860
|
+
function usages(ast, local) {
|
|
4861
|
+
const found = [];
|
|
4862
|
+
const moduleScope2 = /* @__PURE__ */ new Set();
|
|
4863
|
+
statementNames2(ast?.program?.body, moduleScope2);
|
|
4864
|
+
const walk = (node, scope, literals) => {
|
|
4865
|
+
if (!isNode(node)) {
|
|
4866
|
+
if (Array.isArray(node)) for (const child of node) walk(child, scope, literals);
|
|
4867
|
+
return;
|
|
4868
|
+
}
|
|
4869
|
+
let nextScope = scope;
|
|
4870
|
+
let nextLiterals = literals;
|
|
4871
|
+
if (FUNCTIONS3.has(node.type)) {
|
|
4872
|
+
nextScope = /* @__PURE__ */ new Set([...scope, ...functionBindings(node)]);
|
|
4873
|
+
nextLiterals = /* @__PURE__ */ new Set();
|
|
4874
|
+
stringLiterals(node.body, nextLiterals);
|
|
4875
|
+
}
|
|
4876
|
+
if (node.type === "JSXElement") {
|
|
4877
|
+
const name = node.openingElement?.name;
|
|
4878
|
+
if (name?.type === "JSXIdentifier" && name.name === local)
|
|
4879
|
+
found.push({ element: node, scope: nextScope, literals: nextLiterals });
|
|
4880
|
+
}
|
|
4881
|
+
for (const key of Object.keys(node)) {
|
|
4882
|
+
if (key === "loc" || key === "leadingComments" || key === "trailingComments") continue;
|
|
4883
|
+
walk(node[key], nextScope, nextLiterals);
|
|
4884
|
+
}
|
|
4885
|
+
};
|
|
4886
|
+
walk(ast?.program ?? ast, moduleScope2, /* @__PURE__ */ new Set());
|
|
4887
|
+
return found;
|
|
4888
|
+
}
|
|
4889
|
+
function bridgedAlready(ast) {
|
|
4890
|
+
const body = ast?.program?.body ?? [];
|
|
4891
|
+
return body.some(
|
|
4892
|
+
(statement) => statement.type === "ImportDeclaration" && String(statement.source?.value ?? "") === "@bettercms-ai/next/draft-bridge" && (statement.specifiers ?? []).some(
|
|
4893
|
+
(specifier) => specifier.imported?.name === "BcmsDraftBridge"
|
|
4894
|
+
)
|
|
4895
|
+
);
|
|
4896
|
+
}
|
|
4897
|
+
function blocksImport(ast) {
|
|
4898
|
+
const body = ast?.program?.body ?? [];
|
|
4899
|
+
for (const statement of body) {
|
|
4900
|
+
if (statement.type !== "ImportDeclaration") continue;
|
|
4901
|
+
const source = String(statement.source?.value ?? "");
|
|
4902
|
+
if (source !== "@bettercms-ai/next" && source !== "@bettercms-ai/next/blocks") continue;
|
|
4903
|
+
for (const specifier of statement.specifiers ?? []) {
|
|
4904
|
+
const imported = specifier.imported;
|
|
4905
|
+
if (specifier.type !== "ImportSpecifier" || imported?.name !== "BcmsBlocks") continue;
|
|
4906
|
+
return { declaration: statement, specifier, local: String(specifier.local.name) };
|
|
4907
|
+
}
|
|
4908
|
+
}
|
|
4909
|
+
return null;
|
|
4910
|
+
}
|
|
4911
|
+
function slugExpression(usage, routes) {
|
|
4912
|
+
if (usage.scope.has("slug")) return "{slug}";
|
|
4913
|
+
if (usage.scope.has("page")) return "{page.slug}";
|
|
4914
|
+
const matched = [...new Set([...usage.literals].map((l) => routes.get(l)).filter((s) => !!s))];
|
|
4915
|
+
return matched.length === 1 ? JSON.stringify(matched[0]) : null;
|
|
4916
|
+
}
|
|
4917
|
+
var lineOf = (content, offset) => content.slice(0, offset).split("\n").length;
|
|
4918
|
+
var pend = (reason, hint, file, line) => ({
|
|
4919
|
+
route: "",
|
|
4920
|
+
scope: "page",
|
|
4921
|
+
path: reason === "SSR_DRAFT_ROUTE" ? "canvas.lane" : "canvas.bridge",
|
|
4922
|
+
kind: "canvas",
|
|
4923
|
+
...file ? { file } : {},
|
|
4924
|
+
...line ? { line } : {},
|
|
4925
|
+
reason,
|
|
4926
|
+
hint
|
|
4927
|
+
});
|
|
4928
|
+
var ASTRO_LANE_PENDING = pend("SSR_DRAFT_ROUTE", SSR_DRAFT_RECIPE);
|
|
4929
|
+
function canvasBridge(sources, routes) {
|
|
4930
|
+
if (sources.some((s) => s.path.toLowerCase().endsWith(".astro")))
|
|
4931
|
+
return { lane: "pending", files: [], edits: /* @__PURE__ */ new Map(), pending: [ASTRO_LANE_PENDING] };
|
|
4932
|
+
const files = [];
|
|
4933
|
+
const edits = /* @__PURE__ */ new Map();
|
|
4934
|
+
const pending = [];
|
|
4935
|
+
for (const source of sources) {
|
|
4936
|
+
if (!/\.[jt]sx$/i.test(source.path) || !source.ast) continue;
|
|
4937
|
+
if (bridgedAlready(source.ast)) {
|
|
4938
|
+
files.push(source.path);
|
|
4939
|
+
continue;
|
|
4940
|
+
}
|
|
4941
|
+
const imported = blocksImport(source.ast);
|
|
4942
|
+
if (!imported) continue;
|
|
4943
|
+
const found = usages(source.ast, imported.local);
|
|
4944
|
+
const slugs = found.map((usage) => slugExpression(usage, routes));
|
|
4945
|
+
const unresolved = found.findIndex((_, i) => slugs[i] === null);
|
|
4946
|
+
if (found.length === 0 || unresolved !== -1) {
|
|
4947
|
+
const at = found[unresolved === -1 ? 0 : unresolved]?.element.start;
|
|
4948
|
+
pending.push(
|
|
4949
|
+
pend(
|
|
4950
|
+
"CANVAS_BRIDGE_MANUAL",
|
|
4951
|
+
"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.",
|
|
4952
|
+
source.path,
|
|
4953
|
+
at === void 0 ? void 0 : lineOf(source.content, at)
|
|
4954
|
+
)
|
|
4955
|
+
);
|
|
4956
|
+
continue;
|
|
4957
|
+
}
|
|
4958
|
+
const out = new MagicString3(source.content);
|
|
4959
|
+
rewriteImport(out, imported);
|
|
4960
|
+
for (const [i, usage] of found.entries()) rewriteElement(out, usage, imported.local, slugs[i]);
|
|
4961
|
+
edits.set(source.path, out.toString());
|
|
4962
|
+
files.push(source.path);
|
|
4963
|
+
}
|
|
4964
|
+
return { lane: files.length > 0 ? "bridge" : "pending", files, edits, pending };
|
|
4965
|
+
}
|
|
4966
|
+
function rewriteImport(out, imported) {
|
|
4967
|
+
const { declaration: declaration2, specifier, local } = imported;
|
|
4968
|
+
const line = `import { BcmsDraftBridge${local === "BcmsBlocks" ? "" : ` as ${local}`} } from "@bettercms-ai/next/draft-bridge";`;
|
|
4969
|
+
const specifiers = declaration2.specifiers ?? [];
|
|
4970
|
+
const named = specifiers.filter((s) => s.type === "ImportSpecifier");
|
|
4971
|
+
if (named.length === 1) {
|
|
4972
|
+
if (specifiers.length === 1) {
|
|
4973
|
+
out.overwrite(declaration2.start, declaration2.end, line);
|
|
4974
|
+
return;
|
|
4975
|
+
}
|
|
4976
|
+
const other = specifiers.find((s) => s.type !== "ImportSpecifier");
|
|
4977
|
+
const close = out.original.indexOf("}", named[0].end);
|
|
4978
|
+
out.remove(other.end, close + 1);
|
|
4979
|
+
out.appendRight(declaration2.end, `
|
|
4980
|
+
${line}`);
|
|
4981
|
+
return;
|
|
4982
|
+
}
|
|
4983
|
+
const index = named.indexOf(specifier);
|
|
4984
|
+
const previous = named[index - 1];
|
|
4985
|
+
const from = previous ? previous.end : specifier.start;
|
|
4986
|
+
const to = previous ? specifier.end : named[index + 1].start;
|
|
4987
|
+
out.remove(from, to);
|
|
4988
|
+
out.appendRight(declaration2.end, `
|
|
4989
|
+
${line}`);
|
|
4990
|
+
}
|
|
4991
|
+
function rewriteElement(out, usage, local, slug) {
|
|
4992
|
+
const opening = usage.element.openingElement;
|
|
4993
|
+
const name = opening.name;
|
|
4994
|
+
if (local === "BcmsBlocks") {
|
|
4995
|
+
out.overwrite(name.start, name.end, "BcmsDraftBridge");
|
|
4996
|
+
const closing = usage.element.closingElement;
|
|
4997
|
+
if (closing) {
|
|
4998
|
+
const closingName = closing.name;
|
|
4999
|
+
out.overwrite(closingName.start, closingName.end, "BcmsDraftBridge");
|
|
5000
|
+
}
|
|
5001
|
+
}
|
|
5002
|
+
const attributes = opening.attributes ?? [];
|
|
5003
|
+
const hasSlug = attributes.some((a) => a.name?.name === "slug");
|
|
5004
|
+
if (!hasSlug) out.appendLeft(name.end, ` slug=${slug}`);
|
|
5005
|
+
}
|
|
5006
|
+
|
|
4778
5007
|
// src/tolerant.ts
|
|
4779
5008
|
var escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4780
5009
|
var flexible = (literal) => new RegExp(escape(literal).split(" ").map(escape).join("\\s+"), "gu");
|
|
@@ -5196,10 +5425,10 @@ function scanExpression(code) {
|
|
|
5196
5425
|
for (const key of ["typeName", "left", "right", "id"]) nameOf2(node[key], skip);
|
|
5197
5426
|
return;
|
|
5198
5427
|
case "VariableDeclarator":
|
|
5199
|
-
|
|
5428
|
+
patternNames3(node.id, bound);
|
|
5200
5429
|
return;
|
|
5201
5430
|
case "CatchClause":
|
|
5202
|
-
|
|
5431
|
+
patternNames3(node.param, bound);
|
|
5203
5432
|
return;
|
|
5204
5433
|
default:
|
|
5205
5434
|
return;
|
|
@@ -5207,8 +5436,8 @@ function scanExpression(code) {
|
|
|
5207
5436
|
});
|
|
5208
5437
|
walkAst(ast, (node) => {
|
|
5209
5438
|
if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression" || node.type === "FunctionDeclaration" || node.type === "ObjectMethod" || node.type === "ClassMethod") {
|
|
5210
|
-
for (const param of node.params ?? [])
|
|
5211
|
-
if (node.id)
|
|
5439
|
+
for (const param of node.params ?? []) patternNames3(param, bound);
|
|
5440
|
+
if (node.id) patternNames3(node.id, bound);
|
|
5212
5441
|
}
|
|
5213
5442
|
});
|
|
5214
5443
|
const free = /* @__PURE__ */ new Set();
|
|
@@ -5223,7 +5452,7 @@ function nameOf2(node, out) {
|
|
|
5223
5452
|
nameOf2(node.name, out);
|
|
5224
5453
|
}
|
|
5225
5454
|
}
|
|
5226
|
-
function
|
|
5455
|
+
function patternNames3(node, out) {
|
|
5227
5456
|
if (!node || typeof node !== "object") return;
|
|
5228
5457
|
const typed = node;
|
|
5229
5458
|
switch (typed.type) {
|
|
@@ -5232,17 +5461,17 @@ function patternNames2(node, out) {
|
|
|
5232
5461
|
return;
|
|
5233
5462
|
case "ObjectPattern":
|
|
5234
5463
|
for (const property of typed.properties ?? []) {
|
|
5235
|
-
|
|
5464
|
+
patternNames3(property.type === "RestElement" ? property.argument : property.value, out);
|
|
5236
5465
|
}
|
|
5237
5466
|
return;
|
|
5238
5467
|
case "ArrayPattern":
|
|
5239
|
-
for (const element of typed.elements ?? [])
|
|
5468
|
+
for (const element of typed.elements ?? []) patternNames3(element, out);
|
|
5240
5469
|
return;
|
|
5241
5470
|
case "AssignmentPattern":
|
|
5242
|
-
|
|
5471
|
+
patternNames3(typed.left, out);
|
|
5243
5472
|
return;
|
|
5244
5473
|
case "RestElement":
|
|
5245
|
-
|
|
5474
|
+
patternNames3(typed.argument, out);
|
|
5246
5475
|
return;
|
|
5247
5476
|
default:
|
|
5248
5477
|
return;
|
|
@@ -5264,9 +5493,9 @@ function moduleScope(parsed, content) {
|
|
|
5264
5493
|
const text = source.slice(start, end);
|
|
5265
5494
|
const names = /* @__PURE__ */ new Set();
|
|
5266
5495
|
if (kind === "VariableDeclaration") {
|
|
5267
|
-
for (const declarator of statement.declarations ?? [])
|
|
5496
|
+
for (const declarator of statement.declarations ?? []) patternNames3(declarator.id, names);
|
|
5268
5497
|
} else {
|
|
5269
|
-
|
|
5498
|
+
patternNames3(statement.id, names);
|
|
5270
5499
|
}
|
|
5271
5500
|
if (names.size === 0) continue;
|
|
5272
5501
|
const scanned = scanExpression(text.replace(/^(?:export\s+)?/, ""));
|
|
@@ -5277,7 +5506,7 @@ function moduleScope(parsed, content) {
|
|
|
5277
5506
|
}
|
|
5278
5507
|
|
|
5279
5508
|
// src/componentize.ts
|
|
5280
|
-
import
|
|
5509
|
+
import MagicString4 from "magic-string";
|
|
5281
5510
|
var SectionInvariantError = class extends Error {
|
|
5282
5511
|
code = "SECTION_INVARIANT";
|
|
5283
5512
|
constructor(message) {
|
|
@@ -5317,7 +5546,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5317
5546
|
let registered = 0;
|
|
5318
5547
|
let planPending = 0;
|
|
5319
5548
|
let total = 0;
|
|
5320
|
-
const
|
|
5549
|
+
const pend2 = (route, groupKey, reason, message) => void pending.push({ route, groupKey, reason, ...message ? { message } : {} });
|
|
5321
5550
|
const brief = readBrief(briefIn);
|
|
5322
5551
|
const plan2 = readPlan(planIn);
|
|
5323
5552
|
const copy = options?.copy ?? "bind";
|
|
@@ -5337,7 +5566,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5337
5566
|
continue;
|
|
5338
5567
|
}
|
|
5339
5568
|
total++;
|
|
5340
|
-
|
|
5569
|
+
pend2(page.route, section.groupKey, reason, message);
|
|
5341
5570
|
}
|
|
5342
5571
|
}
|
|
5343
5572
|
return {
|
|
@@ -5456,7 +5685,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5456
5685
|
for (const section of sections2) {
|
|
5457
5686
|
const prefix = propPrefix(section);
|
|
5458
5687
|
if (prefix === null) {
|
|
5459
|
-
|
|
5688
|
+
pend2(
|
|
5460
5689
|
page.route,
|
|
5461
5690
|
section.groupKey,
|
|
5462
5691
|
"PROP_MAP_INCONSISTENT",
|
|
@@ -5473,13 +5702,13 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5473
5702
|
const pageSlug = briefPage?.slug ?? slugify(page.route === "/" ? "home" : page.route);
|
|
5474
5703
|
if (registryBlocked) {
|
|
5475
5704
|
for (const section of usable) {
|
|
5476
|
-
|
|
5705
|
+
pend2(page.route, section.groupKey, "REGISTRY_CONFLICT", `${registryConflicts.join(", ")} is not a file this package wrote.`);
|
|
5477
5706
|
}
|
|
5478
5707
|
continue;
|
|
5479
5708
|
}
|
|
5480
5709
|
if (registryUnread) {
|
|
5481
5710
|
for (const section of usable) {
|
|
5482
|
-
|
|
5711
|
+
pend2(
|
|
5483
5712
|
page.route,
|
|
5484
5713
|
section.groupKey,
|
|
5485
5714
|
"REGISTRY_UNREADABLE",
|
|
@@ -5491,21 +5720,21 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5491
5720
|
const snapshotPath = `${CONTENT_DIR}/${pageSlug}.json`;
|
|
5492
5721
|
if (!snapshotUsable(contents.get(snapshotPath))) {
|
|
5493
5722
|
for (const section of usable) {
|
|
5494
|
-
|
|
5723
|
+
pend2(page.route, section.groupKey, "SNAPSHOT_INVALID", `${snapshotPath} is not a JSON object.`);
|
|
5495
5724
|
}
|
|
5496
5725
|
continue;
|
|
5497
5726
|
}
|
|
5498
5727
|
const pageFiles = pageFilesFor(page.route, contents.keys());
|
|
5499
5728
|
if (pageFiles.length > 1) {
|
|
5500
5729
|
for (const section of usable) {
|
|
5501
|
-
|
|
5730
|
+
pend2(page.route, section.groupKey, "ROUTE_FILE_AMBIGUOUS", `${pageFiles.join(" and ")} both serve ${page.route}.`);
|
|
5502
5731
|
}
|
|
5503
5732
|
continue;
|
|
5504
5733
|
}
|
|
5505
5734
|
const file = pageFiles[0];
|
|
5506
5735
|
if (file === void 0) {
|
|
5507
5736
|
for (const section of usable) {
|
|
5508
|
-
|
|
5737
|
+
pend2(page.route, section.groupKey, "NOT_IN_SOURCE", `no page file found for ${page.route}.`);
|
|
5509
5738
|
}
|
|
5510
5739
|
continue;
|
|
5511
5740
|
}
|
|
@@ -5518,7 +5747,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5518
5747
|
if (why === void 0) continue;
|
|
5519
5748
|
done.delete(section.groupKey);
|
|
5520
5749
|
stuck++;
|
|
5521
|
-
|
|
5750
|
+
pend2(page.route, section.groupKey, "SECTION_UNMIGRATABLE", why);
|
|
5522
5751
|
}
|
|
5523
5752
|
alreadyExtracted += done.size;
|
|
5524
5753
|
if (done.size + stuck === usable.length) {
|
|
@@ -5532,7 +5761,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5532
5761
|
treeOf
|
|
5533
5762
|
);
|
|
5534
5763
|
for (const [groupKey, reason] of found.misses) {
|
|
5535
|
-
|
|
5764
|
+
pend2(page.route, groupKey, reason.code, reason.message);
|
|
5536
5765
|
}
|
|
5537
5766
|
const live = found.placed;
|
|
5538
5767
|
if (live.length === 0) continue;
|
|
@@ -5540,14 +5769,14 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5540
5769
|
const dialect = dialectOf(file, content);
|
|
5541
5770
|
if (!dialect || !EXTRACTABLE.includes(dialect)) {
|
|
5542
5771
|
for (const placed of live) {
|
|
5543
|
-
|
|
5772
|
+
pend2(page.route, placed.section.groupKey, "DIALECT_UNSUPPORTED", `${file} is ${dialect ?? "not a template"}.`);
|
|
5544
5773
|
}
|
|
5545
5774
|
continue;
|
|
5546
5775
|
}
|
|
5547
5776
|
const tree = await treeOf(file);
|
|
5548
5777
|
if (!tree) {
|
|
5549
5778
|
for (const placed of live) {
|
|
5550
|
-
|
|
5779
|
+
pend2(page.route, placed.section.groupKey, "NOT_IN_SOURCE", `${file} did not parse.`);
|
|
5551
5780
|
}
|
|
5552
5781
|
continue;
|
|
5553
5782
|
}
|
|
@@ -5572,7 +5801,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5572
5801
|
const alsoRendered = [...placed.byFile.keys()].filter((held2) => held2 !== file && importedFiles.has(held2));
|
|
5573
5802
|
if (elements && elements.length > 0) {
|
|
5574
5803
|
if (alsoRendered.length > 0) {
|
|
5575
|
-
|
|
5804
|
+
pend2(
|
|
5576
5805
|
page.route,
|
|
5577
5806
|
placed.section.groupKey,
|
|
5578
5807
|
"SECTION_ROOT_AMBIGUOUS",
|
|
@@ -5584,7 +5813,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5584
5813
|
continue;
|
|
5585
5814
|
}
|
|
5586
5815
|
if (alsoRendered.length > 1) {
|
|
5587
|
-
|
|
5816
|
+
pend2(
|
|
5588
5817
|
page.route,
|
|
5589
5818
|
placed.section.groupKey,
|
|
5590
5819
|
"SECTION_ROOT_AMBIGUOUS",
|
|
@@ -5598,7 +5827,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5598
5827
|
importedSections.push({ section: placed.section, path: only, call: held.local });
|
|
5599
5828
|
continue;
|
|
5600
5829
|
}
|
|
5601
|
-
|
|
5830
|
+
pend2(
|
|
5602
5831
|
page.route,
|
|
5603
5832
|
placed.section.groupKey,
|
|
5604
5833
|
"SECTION_ROOT_AMBIGUOUS",
|
|
@@ -5612,7 +5841,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5612
5841
|
const component = resolveComponent(plan2, held.section);
|
|
5613
5842
|
const already = extractable.get(component.slug);
|
|
5614
5843
|
if (already !== void 0 && already.path !== held.path) {
|
|
5615
|
-
|
|
5844
|
+
pend2(
|
|
5616
5845
|
page.route,
|
|
5617
5846
|
held.section.groupKey,
|
|
5618
5847
|
"COMPONENT_CONFLICT",
|
|
@@ -5623,7 +5852,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5623
5852
|
const binding = bindingOf(held.path);
|
|
5624
5853
|
const member = binding.kind === "namespace" ? namespaceMember(tree, binding.local) : void 0;
|
|
5625
5854
|
if (binding.kind === "namespace" && member === void 0) {
|
|
5626
|
-
|
|
5855
|
+
pend2(
|
|
5627
5856
|
page.route,
|
|
5628
5857
|
held.section.groupKey,
|
|
5629
5858
|
"SECTION_ROOT_AMBIGUOUS",
|
|
@@ -5649,7 +5878,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5649
5878
|
for (const placed of placedHere) {
|
|
5650
5879
|
const root = lowestCommonAncestor(placed.elements);
|
|
5651
5880
|
if (!root) {
|
|
5652
|
-
|
|
5881
|
+
pend2(page.route, placed.section.groupKey, "SECTION_ROOT_AMBIGUOUS", "its fields sit under no single element.");
|
|
5653
5882
|
continue;
|
|
5654
5883
|
}
|
|
5655
5884
|
const component = resolveComponent(plan2, placed.section);
|
|
@@ -5671,7 +5900,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5671
5900
|
}
|
|
5672
5901
|
}
|
|
5673
5902
|
for (const resolved of nested) {
|
|
5674
|
-
|
|
5903
|
+
pend2(page.route, resolved.section.groupKey, "SECTION_ROOT_AMBIGUOUS", "its root contains another section's.");
|
|
5675
5904
|
}
|
|
5676
5905
|
const candidates = roots.filter((r) => !nested.has(r)).sort((a, b) => a.root.start - b.root.start);
|
|
5677
5906
|
const placeable = [];
|
|
@@ -5679,7 +5908,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5679
5908
|
const path = `${COMPONENT_DIR}/${resolved.name}.${dialect === "astro" ? "astro" : "tsx"}`;
|
|
5680
5909
|
const existing = contents.get(path);
|
|
5681
5910
|
if (existing !== void 0 && !isGenerated(existing) && !extractable.has(resolved.slug)) {
|
|
5682
|
-
|
|
5911
|
+
pend2(page.route, resolved.section.groupKey, "COMPONENT_CONFLICT", `${path} is not a file this package wrote.`);
|
|
5683
5912
|
continue;
|
|
5684
5913
|
}
|
|
5685
5914
|
if (extractable.has(resolved.slug)) {
|
|
@@ -5688,7 +5917,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5688
5917
|
}
|
|
5689
5918
|
const built = componentFile(dialect, path, file, content, parsed, resolved, resolved.pathPrefix);
|
|
5690
5919
|
if (built.unconvertible.length > 0) {
|
|
5691
|
-
|
|
5920
|
+
pend2(
|
|
5692
5921
|
page.route,
|
|
5693
5922
|
resolved.section.groupKey,
|
|
5694
5923
|
"PROP_MAP_INCONSISTENT",
|
|
@@ -5697,7 +5926,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5697
5926
|
continue;
|
|
5698
5927
|
}
|
|
5699
5928
|
if (built.duplicates.length > 0) {
|
|
5700
|
-
|
|
5929
|
+
pend2(
|
|
5701
5930
|
page.route,
|
|
5702
5931
|
resolved.section.groupKey,
|
|
5703
5932
|
"DUPLICATE_FIELD_BINDING",
|
|
@@ -5706,7 +5935,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5706
5935
|
continue;
|
|
5707
5936
|
}
|
|
5708
5937
|
if (built.free.length > 0) {
|
|
5709
|
-
|
|
5938
|
+
pend2(
|
|
5710
5939
|
page.route,
|
|
5711
5940
|
resolved.section.groupKey,
|
|
5712
5941
|
"SECTION_FREE_IDENTIFIERS",
|
|
@@ -5732,7 +5961,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5732
5961
|
const layout = runOf(known, parsed.roots, content);
|
|
5733
5962
|
if (layout === null) {
|
|
5734
5963
|
for (const resolved of placeable) {
|
|
5735
|
-
|
|
5964
|
+
pend2(page.route, resolved.section.groupKey, "SECTION_NOT_CONTIGUOUS", "its roots share no container.");
|
|
5736
5965
|
}
|
|
5737
5966
|
continue;
|
|
5738
5967
|
}
|
|
@@ -5755,7 +5984,7 @@ async function componentizeSources(briefIn, planIn, sources, options) {
|
|
|
5755
5984
|
const inline = placeable.filter((resolved) => !instanceOwned(resolved.section));
|
|
5756
5985
|
for (const resolved of placeable) {
|
|
5757
5986
|
if (inline.includes(resolved)) continue;
|
|
5758
|
-
|
|
5987
|
+
pend2(
|
|
5759
5988
|
page.route,
|
|
5760
5989
|
resolved.section.groupKey,
|
|
5761
5990
|
"INLINE_NEEDS_INSTANCE",
|
|
@@ -6203,7 +6432,7 @@ export default function ${resolved.name}({ blockId, bind, overrides, page }: Bcm
|
|
|
6203
6432
|
` };
|
|
6204
6433
|
}
|
|
6205
6434
|
function sectionMarkup(content, root, fieldName) {
|
|
6206
|
-
const slice = new
|
|
6435
|
+
const slice = new MagicString4(content.slice(root.start, root.end));
|
|
6207
6436
|
const at = (offset) => offset - root.start;
|
|
6208
6437
|
const stamps = root.attrs.filter((attr) => attr.name === "data-bcms-block");
|
|
6209
6438
|
if (stamps.length === 0) slice.appendLeft(at(root.attrInsertAt), " data-bcms-block={blockId}");
|
|
@@ -6402,7 +6631,7 @@ async function migrateSections(plan2, contents, written, fileSet, aliases) {
|
|
|
6402
6631
|
);
|
|
6403
6632
|
continue;
|
|
6404
6633
|
}
|
|
6405
|
-
const source = new
|
|
6634
|
+
const source = new MagicString4(content);
|
|
6406
6635
|
const { bound, duplicates } = wrapFieldAttrs(source, content, elements, (at) => at, "bcmsField");
|
|
6407
6636
|
if (duplicates.length > 0) {
|
|
6408
6637
|
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.`);
|
|
@@ -6679,7 +6908,7 @@ export default function Sections({ page }: { page: string }) {
|
|
|
6679
6908
|
function splicePageRun(file, content, dialect, parsed, placeable, pageSlug) {
|
|
6680
6909
|
const names = allocateNames(topLevelBindings(dialect, parsed, content), [{ key: "Sections", name: "Sections" }]);
|
|
6681
6910
|
const local = names.get("Sections");
|
|
6682
|
-
const source = new
|
|
6911
|
+
const source = new MagicString4(content);
|
|
6683
6912
|
const first = placeable[0].root;
|
|
6684
6913
|
const last = placeable[placeable.length - 1].root;
|
|
6685
6914
|
source.overwrite(first.start, last.end, `<${local} page="${pageSlug}" />`);
|
|
@@ -6699,7 +6928,7 @@ function splicePageInline(file, content, dialect, parsed, placeable, pageSlug, w
|
|
|
6699
6928
|
{ key: "pageSnapshot", name: "pageSnapshot" }
|
|
6700
6929
|
]);
|
|
6701
6930
|
const snapshot = names.get("pageSnapshot");
|
|
6702
|
-
const source = new
|
|
6931
|
+
const source = new MagicString4(content);
|
|
6703
6932
|
for (const resolved of placeable) {
|
|
6704
6933
|
const component = written.get(resolved.slug);
|
|
6705
6934
|
const tag = component.member ? `${names.get(resolved.slug)}.${component.member}` : names.get(resolved.slug);
|
|
@@ -6780,6 +7009,10 @@ function readComponentizePlan(value) {
|
|
|
6780
7009
|
}
|
|
6781
7010
|
|
|
6782
7011
|
// src/index.ts
|
|
7012
|
+
var UNPARSEABLE_DIALECT = {
|
|
7013
|
+
roots: [],
|
|
7014
|
+
error: { code: "DIALECT_UNSUPPORTED", message: "no parser for this extension" }
|
|
7015
|
+
};
|
|
6783
7016
|
var ConvertError = class extends Error {
|
|
6784
7017
|
constructor(code, message) {
|
|
6785
7018
|
super(message);
|
|
@@ -6861,7 +7094,7 @@ async function convertSources(briefIn, sources, options) {
|
|
|
6861
7094
|
}
|
|
6862
7095
|
const { located, unlocated } = locate(brief, sources, { stripTags: true });
|
|
6863
7096
|
const pending = [];
|
|
6864
|
-
const
|
|
7097
|
+
const pend2 = (target, reason, file, message) => {
|
|
6865
7098
|
pending.push({
|
|
6866
7099
|
route: target.route,
|
|
6867
7100
|
scope: target.scope,
|
|
@@ -6962,7 +7195,7 @@ async function convertSources(briefIn, sources, options) {
|
|
|
6962
7195
|
declaredRow(target, false, byHand);
|
|
6963
7196
|
continue;
|
|
6964
7197
|
}
|
|
6965
|
-
|
|
7198
|
+
pend2(target, miss.reason === "no-original" ? "NO_ORIGINAL" : "NOT_IN_SOURCE");
|
|
6966
7199
|
}
|
|
6967
7200
|
const byFile = /* @__PURE__ */ new Map();
|
|
6968
7201
|
for (const [key, hit] of locatedByKey) {
|
|
@@ -7019,12 +7252,12 @@ async function convertSources(briefIn, sources, options) {
|
|
|
7019
7252
|
const worst = outcomes.find((o) => o !== "rewritten" && o !== "declared");
|
|
7020
7253
|
if (worst) {
|
|
7021
7254
|
const file = hit.files[outcomes.indexOf(worst)];
|
|
7022
|
-
|
|
7255
|
+
pend2(target, worst, file);
|
|
7023
7256
|
} else if (outcomes.every((o) => o === "declared")) {
|
|
7024
7257
|
const witness = await witnessed(target);
|
|
7025
7258
|
const byHand = witness === null ? declaredByHand(target, hit.files) : null;
|
|
7026
7259
|
if (witness === null && byHand === null) {
|
|
7027
|
-
|
|
7260
|
+
pend2(
|
|
7028
7261
|
target,
|
|
7029
7262
|
"AMBIGUOUS_LITERAL",
|
|
7030
7263
|
hit.files[0],
|
|
@@ -7037,7 +7270,7 @@ async function convertSources(briefIn, sources, options) {
|
|
|
7037
7270
|
} else rewritten++;
|
|
7038
7271
|
}
|
|
7039
7272
|
for (const [file, splices] of [...componentEdits].sort(([a], [b]) => a.localeCompare(b))) {
|
|
7040
|
-
const source = new
|
|
7273
|
+
const source = new MagicString5(contents.get(file));
|
|
7041
7274
|
const seen = /* @__PURE__ */ new Set();
|
|
7042
7275
|
for (const splice of splices) {
|
|
7043
7276
|
const id = `${splice.start}:${splice.end}:${splice.text}`;
|
|
@@ -7058,6 +7291,28 @@ async function convertSources(briefIn, sources, options) {
|
|
|
7058
7291
|
}
|
|
7059
7292
|
const { helpers, helperUpgraded } = emitHelpers(dialects, contents, outFiles, options?.overwriteHelper);
|
|
7060
7293
|
emitStubs2(dialects, snapshotSlugs, contents, outFiles);
|
|
7294
|
+
const latest = new Map(contents);
|
|
7295
|
+
for (const file of outFiles) if (file.operation === "modify") latest.set(file.path, file.content);
|
|
7296
|
+
const canvasSources = [];
|
|
7297
|
+
for (const source of sources) {
|
|
7298
|
+
const content = latest.get(source.path) ?? source.content;
|
|
7299
|
+
if (!/\.[jt]sx$/i.test(source.path)) {
|
|
7300
|
+
canvasSources.push({ path: source.path, content });
|
|
7301
|
+
continue;
|
|
7302
|
+
}
|
|
7303
|
+
const parsed = content === source.content ? await parseOf(source.path) : await parseFile4("jsx", content);
|
|
7304
|
+
canvasSources.push({ path: source.path, content, ...parsed && !parsed.error ? { ast: parsed.ast } : {} });
|
|
7305
|
+
}
|
|
7306
|
+
const canvas = canvasBridge(
|
|
7307
|
+
canvasSources,
|
|
7308
|
+
new Map(brief.pages.map((page) => [page.route, page.slug]))
|
|
7309
|
+
);
|
|
7310
|
+
for (const [path, content] of canvas.edits) {
|
|
7311
|
+
const existing = outFiles.find((f) => f.path === path);
|
|
7312
|
+
if (existing) existing.content = content;
|
|
7313
|
+
else outFiles.push({ path, operation: "modify", content });
|
|
7314
|
+
}
|
|
7315
|
+
pending.push(...canvas.pending);
|
|
7061
7316
|
const receipt = assertReceipt({
|
|
7062
7317
|
briefDigest: digest,
|
|
7063
7318
|
paths: {
|
|
@@ -7074,6 +7329,7 @@ async function convertSources(briefIn, sources, options) {
|
|
|
7074
7329
|
helpers,
|
|
7075
7330
|
helperUpgraded,
|
|
7076
7331
|
dynamicBindings,
|
|
7332
|
+
canvas: { lane: canvas.lane, ...canvas.files.length > 0 ? { files: canvas.files } : {} },
|
|
7077
7333
|
notes: unreadableConfigs(sources).map(
|
|
7078
7334
|
(file) => ({
|
|
7079
7335
|
code: "TSCONFIG_UNREADABLE",
|
|
@@ -7106,6 +7362,30 @@ async function convertFile(file, content, targets, parsed, declared, llmFallback
|
|
|
7106
7362
|
};
|
|
7107
7363
|
if (pendingTargets.length === 0) return empty;
|
|
7108
7364
|
if (!dialect) {
|
|
7365
|
+
if (isTolerantDialect(file)) {
|
|
7366
|
+
const tolerantLiterals = [...new Set(pendingTargets.map((t) => t.literal))];
|
|
7367
|
+
const tolerantSites = findSitesTolerant(content, tolerantLiterals);
|
|
7368
|
+
if (tolerantSites.length > 0) {
|
|
7369
|
+
return plan(
|
|
7370
|
+
file,
|
|
7371
|
+
content,
|
|
7372
|
+
// Never read: `plan` forces `writing = "html"` at tier 2, and every other use of this
|
|
7373
|
+
// argument is behind `!parsed.error` or `tier !== 2`. Passing the writing dialect keeps
|
|
7374
|
+
// the parameter honest rather than widening its type for one caller.
|
|
7375
|
+
"html",
|
|
7376
|
+
pendingTargets,
|
|
7377
|
+
tolerantSites,
|
|
7378
|
+
void 0,
|
|
7379
|
+
// no parse, no import point: tier 2 declares, it does not read
|
|
7380
|
+
outcomes,
|
|
7381
|
+
2,
|
|
7382
|
+
UNPARSEABLE_DIALECT,
|
|
7383
|
+
drill,
|
|
7384
|
+
[]
|
|
7385
|
+
// no parse, so nothing in this file is already declared
|
|
7386
|
+
);
|
|
7387
|
+
}
|
|
7388
|
+
}
|
|
7109
7389
|
for (const target of pendingTargets) outcomes.set(target.key, "DIALECT_UNSUPPORTED");
|
|
7110
7390
|
return { ...empty, dialect: null, outcomes };
|
|
7111
7391
|
}
|
|
@@ -7164,7 +7444,7 @@ async function convertFile(file, content, targets, parsed, declared, llmFallback
|
|
|
7164
7444
|
}
|
|
7165
7445
|
async function plan(file, content, dialect, targets, sites, importAt, outcomes, tier, parsed, drill, declarations) {
|
|
7166
7446
|
const writing = tier === 2 ? "html" : dialect;
|
|
7167
|
-
const missing = tier
|
|
7447
|
+
const missing = tier !== 2 ? "NOT_IN_SOURCE" : parsed.error?.code === "DIALECT_UNSUPPORTED" ? "DIALECT_UNSUPPORTED" : "PARSE_ERROR";
|
|
7168
7448
|
const taken = parsed.error ? /* @__PURE__ */ new Set() : topLevelBindings(dialect, parsed, content);
|
|
7169
7449
|
const slugs = [...new Set(targets.map((t) => t.slug))].sort();
|
|
7170
7450
|
const preset = /* @__PURE__ */ new Map();
|