@bettercms-ai/convert 0.22.0 → 0.23.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 +6 -0
- package/README.md +39 -0
- package/dist/cli.js +428 -5
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +79 -1
- package/dist/index.js +371 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1837,6 +1837,84 @@ declare function readBrief(value: unknown): Brief;
|
|
|
1837
1837
|
*/
|
|
1838
1838
|
declare function readPlan(value: unknown): ComponentizePlan;
|
|
1839
1839
|
|
|
1840
|
+
/** The plan's own file shape, restated rather than imported to keep `index.ts` acyclic. */
|
|
1841
|
+
interface UpgradeFile {
|
|
1842
|
+
path: string;
|
|
1843
|
+
operation: "modify" | "add";
|
|
1844
|
+
content: string;
|
|
1845
|
+
}
|
|
1846
|
+
/** Why a root was left without the marker. */
|
|
1847
|
+
type UpgradeSkipReason =
|
|
1848
|
+
/** The root has its own `style` attribute; a second one would be resolved by the dialect. */
|
|
1849
|
+
"OWN_STYLE"
|
|
1850
|
+
/** The root has a spread (`{...rest}`) that may carry a style. */
|
|
1851
|
+
| "SPREAD"
|
|
1852
|
+
/** Only a block ID reaches the root (`{Astro.props.bcmsBlock}`, `{id}`): its style is not in scope. */
|
|
1853
|
+
| "BLOCK_ID_ONLY"
|
|
1854
|
+
/** `data-bcms-block="abc"`: a literal id, with no block object behind it. */
|
|
1855
|
+
| "BLOCK_LITERAL"
|
|
1856
|
+
/** A call, a conditional, anything that does not name one block. */
|
|
1857
|
+
| "BLOCK_UNRESOLVABLE"
|
|
1858
|
+
/** `data-bcms-block` on a component tag is a prop, not an element the editor can find. */
|
|
1859
|
+
| "NOT_AN_ELEMENT"
|
|
1860
|
+
/** A dialect this pass does not rewrite (html, vue, svelte). */
|
|
1861
|
+
| "DIALECT_UNSUPPORTED"
|
|
1862
|
+
/** The name this pass would introduce is already something else in the file. */
|
|
1863
|
+
| "NAME_TAKEN"
|
|
1864
|
+
/** A generated section whose registry or `bcms-sections` no longer has the shape it can patch. */
|
|
1865
|
+
| "REGISTRY_UNRECOGNISED"
|
|
1866
|
+
/** The file did not parse. */
|
|
1867
|
+
| "PARSE_ERROR";
|
|
1868
|
+
interface UpgradeRootRef {
|
|
1869
|
+
file: string;
|
|
1870
|
+
/** 1-based line of the root's `<`. */
|
|
1871
|
+
line: number;
|
|
1872
|
+
tag: string;
|
|
1873
|
+
/** What `data-bcms-block` was given, as written: `{blockId}`, `"abc"`. */
|
|
1874
|
+
block: string;
|
|
1875
|
+
}
|
|
1876
|
+
interface UpgradedRoot extends UpgradeRootRef {
|
|
1877
|
+
form: "section-prop" | "sectionRootAttrs" | "helper";
|
|
1878
|
+
}
|
|
1879
|
+
interface SkippedRoot extends UpgradeRootRef {
|
|
1880
|
+
reason: UpgradeSkipReason;
|
|
1881
|
+
message: string;
|
|
1882
|
+
}
|
|
1883
|
+
interface SectionUpgradeReceipt {
|
|
1884
|
+
/** The helper at `src/bcms-content.ts` (and its siblings), when there is one or this pass wrote one. */
|
|
1885
|
+
helpers: {
|
|
1886
|
+
path: string;
|
|
1887
|
+
from: number | null;
|
|
1888
|
+
to: number;
|
|
1889
|
+
operation: "add" | "modify" | "unchanged" | "foreign";
|
|
1890
|
+
}[];
|
|
1891
|
+
/** Generated registry / `bcms-sections` modules patched to hand `block.style` down. */
|
|
1892
|
+
registry: {
|
|
1893
|
+
path: string;
|
|
1894
|
+
operation: "modify" | "unchanged";
|
|
1895
|
+
}[];
|
|
1896
|
+
roots: {
|
|
1897
|
+
upgraded: UpgradedRoot[];
|
|
1898
|
+
alreadyMarked: UpgradeRootRef[];
|
|
1899
|
+
skipped: SkippedRoot[];
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
interface UpgradeOptions {
|
|
1903
|
+
/** Replace a helper module this package did not write, when a root needs the helper. */
|
|
1904
|
+
overwriteHelper?: boolean;
|
|
1905
|
+
/** The repository's root `package.json`, to tell whether its SDK exports `sectionRootAttrs`. */
|
|
1906
|
+
packageJson?: string;
|
|
1907
|
+
}
|
|
1908
|
+
/** Thrown, like `convertSources`, when the helper is needed and is not ours to replace. */
|
|
1909
|
+
declare class UpgradeError extends Error {
|
|
1910
|
+
readonly code = "HELPER_CONFLICT";
|
|
1911
|
+
constructor(message: string);
|
|
1912
|
+
}
|
|
1913
|
+
declare function upgradeSections(sources: readonly SourceFile[], options?: UpgradeOptions): Promise<{
|
|
1914
|
+
files: UpgradeFile[];
|
|
1915
|
+
receipt: SectionUpgradeReceipt;
|
|
1916
|
+
}>;
|
|
1917
|
+
|
|
1840
1918
|
/** One file operation, exactly as `src/lib/github/plan-digest.ts` spells it. */
|
|
1841
1919
|
interface PlanFile {
|
|
1842
1920
|
path: string;
|
|
@@ -1871,4 +1949,4 @@ declare function convertSources(briefIn: Brief, sources: SourceFile[], options?:
|
|
|
1871
1949
|
receipt: ConversionReceipt;
|
|
1872
1950
|
}>;
|
|
1873
1951
|
|
|
1874
|
-
export { ASTRO_LANE_PENDING, type AstNode, type AttrBinding, type Brief, type BriefForm, type BriefPage, type BriefPath, COMPONENT_DIR, CONTENT_DIR, type CanvasResult, type CanvasSource, type Carried, type ComponentizeOptions, type ComponentizePlan, type ComponentizePlanComponent, type ComponentizePlanField, type ComponentizePlanPage, type ComponentizePlanSection, type ComponentizeReceipt, type ConversionReceipt, ConvertError, type ConvertErrorCode, type ConvertOptions, DIALECT_RULES, type DataMember, type DataResult, type DeclaredPath, type Dialect, type DynamicBinding, FORM_SCRIPT, type FileDeclaration, type FindSitesResult, type FixAction, FormInvariantError, type FormPendingReason, type FormsReceipt, HELPER_PATH, HELPER_VERSION, type ImageResult, type ImageTarget, type ImportedFrom, type LlmFallback, type LocatedPath, type Node, PARSE_FILE, type ParsedFile, type ParserError, type PathLocator, type PendingFix, type PendingForm, type PendingPath, type PendingReason, type PendingSection, type PlanFile, REGISTRY_MARKER, type ReceiptFile, ReceiptInvariantError, type ReceiptNote, type Rewrite, SECTIONS_LIB, SECTION_MARKER, SKIP_DIRS, SKIP_FILES, SOURCE_EXTENSIONS, SSR_DRAFT_RECIPE, STUB_CONTENT, SectionInvariantError, type SectionPendingReason, type SectionsReceipt, type Site, type SiteWhere, type SourceFile, type Splice, type TargetIdentity, type UnlocatedPath, type ValidationCode, ValidationError, type Xray, type XrayBucket, aliasesFrom, assertForms, assertReceipt, assertSections, assetCandidates, assetImport, attrsFor, bindDataLiterals, bindImages, briefDigest, canvasBridge, carryFor, componentizeSources, convertSources, convertedHere, coverageOf, declaringElements, dialectOf, exportedFunction, extendedConfigs, findLiteral, findSites, findSitesTolerant, fixFor, flat, helperSource, importsIn, isDecorativeGlyph, isDynamicRoute, isKnownHelper, isSourceCandidate, locate, measureUnaddressable, overlapping, pageFilesFor, parseFile, propsAttribute, readBrief, readComponentizePlan, readDeclarations, readExpr, readPlan, relativeImport, resolveSpecifier, rewriteFile, routeCounts, routeOfFile, stripTags, unreadableConfigs, walkAst, wireForms };
|
|
1952
|
+
export { ASTRO_LANE_PENDING, type AstNode, type AttrBinding, type Brief, type BriefForm, type BriefPage, type BriefPath, COMPONENT_DIR, CONTENT_DIR, type CanvasResult, type CanvasSource, type Carried, type ComponentizeOptions, type ComponentizePlan, type ComponentizePlanComponent, type ComponentizePlanField, type ComponentizePlanPage, type ComponentizePlanSection, type ComponentizeReceipt, type ConversionReceipt, ConvertError, type ConvertErrorCode, type ConvertOptions, DIALECT_RULES, type DataMember, type DataResult, type DeclaredPath, type Dialect, type DynamicBinding, FORM_SCRIPT, type FileDeclaration, type FindSitesResult, type FixAction, FormInvariantError, type FormPendingReason, type FormsReceipt, HELPER_PATH, HELPER_VERSION, type ImageResult, type ImageTarget, type ImportedFrom, type LlmFallback, type LocatedPath, type Node, PARSE_FILE, type ParsedFile, type ParserError, type PathLocator, type PendingFix, type PendingForm, type PendingPath, type PendingReason, type PendingSection, type PlanFile, REGISTRY_MARKER, type ReceiptFile, ReceiptInvariantError, type ReceiptNote, type Rewrite, SECTIONS_LIB, SECTION_MARKER, SKIP_DIRS, SKIP_FILES, SOURCE_EXTENSIONS, SSR_DRAFT_RECIPE, STUB_CONTENT, SectionInvariantError, type SectionPendingReason, type SectionUpgradeReceipt, type SectionsReceipt, type Site, type SiteWhere, type SkippedRoot, type SourceFile, type Splice, type TargetIdentity, type UnlocatedPath, UpgradeError, type UpgradeOptions, type UpgradeRootRef, type UpgradeSkipReason, type UpgradedRoot, type ValidationCode, ValidationError, type Xray, type XrayBucket, aliasesFrom, assertForms, assertReceipt, assertSections, assetCandidates, assetImport, attrsFor, bindDataLiterals, bindImages, briefDigest, canvasBridge, carryFor, componentizeSources, convertSources, convertedHere, coverageOf, declaringElements, dialectOf, exportedFunction, extendedConfigs, findLiteral, findSites, findSitesTolerant, fixFor, flat, helperSource, importsIn, isDecorativeGlyph, isDynamicRoute, isKnownHelper, isSourceCandidate, locate, measureUnaddressable, overlapping, pageFilesFor, parseFile, propsAttribute, readBrief, readComponentizePlan, readDeclarations, readExpr, readPlan, relativeImport, resolveSpecifier, rewriteFile, routeCounts, routeOfFile, stripTags, unreadableConfigs, upgradeSections, walkAst, wireForms };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import
|
|
2
|
+
import MagicString7 from "magic-string";
|
|
3
3
|
|
|
4
4
|
// src/brief.ts
|
|
5
5
|
import { createHash } from "crypto";
|
|
@@ -3764,8 +3764,8 @@ function bindLists(ctx, members, module, out, indexes) {
|
|
|
3764
3764
|
const loops = iterations(ctx.parsed.roots, name);
|
|
3765
3765
|
if (loops.length > 1) return { refuse: "REPEATER_AMBIGUOUS" };
|
|
3766
3766
|
if (loops.length === 0) return null;
|
|
3767
|
-
const
|
|
3768
|
-
if (
|
|
3767
|
+
const mentions3 = ctx.content.match(new RegExp(`(?<![\\w$])${name}(?![\\w$])`, "g")) ?? [];
|
|
3768
|
+
if (mentions3.length !== 2 || escapes.has(name)) return { refuse: "IN_EXPRESSION" };
|
|
3769
3769
|
return { loop: loops[0], params: callbackParams(loops[0], name) };
|
|
3770
3770
|
}
|
|
3771
3771
|
});
|
|
@@ -9195,6 +9195,371 @@ function readComponentizePlan(value) {
|
|
|
9195
9195
|
return readPlan(value);
|
|
9196
9196
|
}
|
|
9197
9197
|
|
|
9198
|
+
// src/upgrade.ts
|
|
9199
|
+
import MagicString6 from "magic-string";
|
|
9200
|
+
var UpgradeError = class extends Error {
|
|
9201
|
+
code = "HELPER_CONFLICT";
|
|
9202
|
+
constructor(message) {
|
|
9203
|
+
super(message);
|
|
9204
|
+
this.name = "UpgradeError";
|
|
9205
|
+
}
|
|
9206
|
+
};
|
|
9207
|
+
var SDK_SINCE = {
|
|
9208
|
+
astro: { pkg: "@bettercms-ai/astro", version: [0, 19, 0] },
|
|
9209
|
+
jsx: { pkg: "@bettercms-ai/next", version: [0, 18, 0] }
|
|
9210
|
+
};
|
|
9211
|
+
function sdkHasSectionRootAttrs(packageJson, dialect) {
|
|
9212
|
+
if (!packageJson) return false;
|
|
9213
|
+
let pkg;
|
|
9214
|
+
try {
|
|
9215
|
+
pkg = JSON.parse(packageJson);
|
|
9216
|
+
} catch {
|
|
9217
|
+
return false;
|
|
9218
|
+
}
|
|
9219
|
+
const { pkg: name, version: since } = SDK_SINCE[dialect];
|
|
9220
|
+
const range = [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies].map((deps) => deps && typeof deps === "object" ? deps[name] : void 0).find((v2) => typeof v2 === "string");
|
|
9221
|
+
if (range === void 0) return false;
|
|
9222
|
+
if (range.trim() === "latest" || range.trim() === "*") return true;
|
|
9223
|
+
const match = /^\s*(?:\^|~|>=|=)?\s*v?(\d+)\.(\d+)\.(\d+)/.exec(range);
|
|
9224
|
+
if (!match) return false;
|
|
9225
|
+
const v = [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
9226
|
+
for (let i = 0; i < 3; i++) {
|
|
9227
|
+
if (v[i] !== since[i]) return v[i] > since[i];
|
|
9228
|
+
}
|
|
9229
|
+
return true;
|
|
9230
|
+
}
|
|
9231
|
+
function elementsIn2(nodes, out = []) {
|
|
9232
|
+
for (const node of nodes) {
|
|
9233
|
+
if (node.type === "element") {
|
|
9234
|
+
out.push(node);
|
|
9235
|
+
elementsIn2(node.children, out);
|
|
9236
|
+
} else if (node.type === "expr") elementsIn2(node.children, out);
|
|
9237
|
+
}
|
|
9238
|
+
return out;
|
|
9239
|
+
}
|
|
9240
|
+
var lineAt = (content, offset) => content.slice(0, offset).split("\n").length;
|
|
9241
|
+
var exprOf2 = (content, range) => content.slice(range.start + 1, range.end - 1).trim();
|
|
9242
|
+
var isSpread = (content, attr) => !!attr.expressionRange && content.slice(attr.expressionRange.start, attr.expressionRange.end).trimStart().startsWith("{...");
|
|
9243
|
+
var mentions2 = (content, name) => new RegExp(`(?<![\\w$.])${name.replace(/[$]/g, "\\$")}(?![\\w$])`).test(content);
|
|
9244
|
+
function importOf(content, matches) {
|
|
9245
|
+
const re = /import\s*\{([^}]*)\}\s*from\s*(["'])([^"']+)\2\s*;?/g;
|
|
9246
|
+
for (const m of content.matchAll(re)) {
|
|
9247
|
+
if (!matches(m[3])) continue;
|
|
9248
|
+
const names = m[1].split(",").map((n) => n.trim()).filter(Boolean);
|
|
9249
|
+
return { start: m.index, end: m.index + m[0].length, names };
|
|
9250
|
+
}
|
|
9251
|
+
return null;
|
|
9252
|
+
}
|
|
9253
|
+
function addNamedImport(source, content, parsed, specifier, sameModule, names) {
|
|
9254
|
+
const existing = importOf(content, sameModule);
|
|
9255
|
+
if (existing) {
|
|
9256
|
+
const missing = names.filter((n) => !existing.names.some((e) => e === n || e.endsWith(` as ${n}`)));
|
|
9257
|
+
if (missing.length === 0) return;
|
|
9258
|
+
const quote = content.slice(existing.start, existing.end).includes("'") ? "'" : '"';
|
|
9259
|
+
const spec = /from\s*["']([^"']+)["']/.exec(content.slice(existing.start, existing.end))[1];
|
|
9260
|
+
const all = [...existing.names, ...missing].sort((a, b) => a.localeCompare(b));
|
|
9261
|
+
const semi = content.slice(existing.start, existing.end).trimEnd().endsWith(";") ? ";" : "";
|
|
9262
|
+
source.overwrite(existing.start, existing.end, `import { ${all.join(", ")} } from ${quote}${spec}${quote}${semi}`);
|
|
9263
|
+
return;
|
|
9264
|
+
}
|
|
9265
|
+
const line = `import { ${[...names].sort().join(", ")} } from "${specifier}";`;
|
|
9266
|
+
const at = parsed.importAt;
|
|
9267
|
+
if (!at) return;
|
|
9268
|
+
if (at.wrap) source.appendLeft(at.at, `${at.wrap.before}${line}${at.wrap.after}`);
|
|
9269
|
+
else if (at.at === 0) source.appendLeft(0, `${line}
|
|
9270
|
+
`);
|
|
9271
|
+
else source.appendLeft(at.at, `
|
|
9272
|
+
${line}`);
|
|
9273
|
+
}
|
|
9274
|
+
var importedFrom = (content, name, sameModule) => !!importOf(content, sameModule)?.names.some((e) => e === name || e.endsWith(` as ${name}`));
|
|
9275
|
+
var LIB_PROPS_DOC = " /** The placement's `block.style`, which the section root paints (`data-bcms-style`). */\n blockStyle?: Record<string, unknown>;";
|
|
9276
|
+
var LIB_PLACEMENT_DOC = " /** The placement's `block.style` \u2014 what the editor's section spacing and style controls write. */\n style?: Record<string, unknown>;";
|
|
9277
|
+
function insertAfterLine(content, from, to, anchor, addition) {
|
|
9278
|
+
const slice = content.slice(from, to);
|
|
9279
|
+
const m = anchor.exec(slice);
|
|
9280
|
+
if (!m) return null;
|
|
9281
|
+
const eol = content.indexOf("\n", from + m.index);
|
|
9282
|
+
if (eol < 0 || eol > to) return null;
|
|
9283
|
+
const nl = eolOf(content);
|
|
9284
|
+
return content.slice(0, eol + 1) + addition.split("\n").join(nl) + nl + content.slice(eol + 1);
|
|
9285
|
+
}
|
|
9286
|
+
var eolOf = (content) => content.includes("\r\n") ? "\r\n" : "\n";
|
|
9287
|
+
function interfaceBody(content, name) {
|
|
9288
|
+
const at = content.indexOf(`export interface ${name} {`);
|
|
9289
|
+
if (at < 0) return null;
|
|
9290
|
+
const end = content.indexOf("\n}", at);
|
|
9291
|
+
return end < 0 ? null : { start: at, end };
|
|
9292
|
+
}
|
|
9293
|
+
function patchLib(content) {
|
|
9294
|
+
let out = content;
|
|
9295
|
+
if (!/blockStyle\?:/.test(out)) {
|
|
9296
|
+
const body = interfaceBody(out, "BcmsSectionProps");
|
|
9297
|
+
if (!body) return null;
|
|
9298
|
+
const next = insertAfterLine(out, body.start, body.end, /^\s*page\?: unknown;/m, LIB_PROPS_DOC);
|
|
9299
|
+
if (next === null) return null;
|
|
9300
|
+
out = next;
|
|
9301
|
+
}
|
|
9302
|
+
const placement = interfaceBody(out, "BcmsPlacement");
|
|
9303
|
+
if (!placement) return null;
|
|
9304
|
+
if (!/^\s*style\?:/m.test(out.slice(placement.start, placement.end))) {
|
|
9305
|
+
const next = insertAfterLine(out, placement.start, placement.end, /^\s*overrides\?: Record<string, unknown>;/m, LIB_PLACEMENT_DOC);
|
|
9306
|
+
if (next === null) return null;
|
|
9307
|
+
out = next;
|
|
9308
|
+
}
|
|
9309
|
+
if (!/style: isRecord\(block\.style\)/.test(out)) {
|
|
9310
|
+
const push = /^([ \t]*)overrides: isRecord\(props\.overrides\) \? props\.overrides : undefined,\r?$/m.exec(out);
|
|
9311
|
+
if (!push) return null;
|
|
9312
|
+
const eol = out.indexOf("\n", push.index);
|
|
9313
|
+
out = out.slice(0, eol + 1) + `${push[1]}style: isRecord(block.style) ? block.style : undefined,${eolOf(out)}` + out.slice(eol + 1);
|
|
9314
|
+
}
|
|
9315
|
+
return out;
|
|
9316
|
+
}
|
|
9317
|
+
function patchRegistry(content) {
|
|
9318
|
+
if (content.includes("blockStyle={placement.style}")) return content;
|
|
9319
|
+
const lines = content.split("\n");
|
|
9320
|
+
const out = [];
|
|
9321
|
+
let patched = 0;
|
|
9322
|
+
for (const line of lines) {
|
|
9323
|
+
out.push(line);
|
|
9324
|
+
const m = /^([ \t]*)overrides=\{placement\.overrides\}(\r?)$/.exec(line);
|
|
9325
|
+
if (m) {
|
|
9326
|
+
out.push(`${m[1]}blockStyle={placement.style}${m[2]}`);
|
|
9327
|
+
patched++;
|
|
9328
|
+
}
|
|
9329
|
+
}
|
|
9330
|
+
return patched > 0 ? out.join("\n") : null;
|
|
9331
|
+
}
|
|
9332
|
+
var STYLE_READER = { astro: "bcmsSectionStyleText", jsx: "bcmsSectionStyle" };
|
|
9333
|
+
var BLOCK_MEMBER = /^([A-Za-z_$][\w$]*)(\??\.)(id|blockId)$/;
|
|
9334
|
+
var ID_ONLY = /^(?:(?:Astro\.)?props\??\.)?[A-Za-z_$][\w$]*$/;
|
|
9335
|
+
async function upgradeSections(sources, options = {}) {
|
|
9336
|
+
const contents = new Map(sources.map((f) => [f.path, f.content]));
|
|
9337
|
+
const out = /* @__PURE__ */ new Map();
|
|
9338
|
+
const write = (path, content) => {
|
|
9339
|
+
if (contents.get(path) === content) return;
|
|
9340
|
+
out.set(path, { path, operation: contents.has(path) ? "modify" : "add", content });
|
|
9341
|
+
};
|
|
9342
|
+
const receipt = {
|
|
9343
|
+
helpers: [],
|
|
9344
|
+
registry: [],
|
|
9345
|
+
roots: { upgraded: [], alreadyMarked: [], skipped: [] }
|
|
9346
|
+
};
|
|
9347
|
+
let registryOk = true;
|
|
9348
|
+
for (const file of sources) {
|
|
9349
|
+
if (!file.content.includes(REGISTRY_MARKER)) continue;
|
|
9350
|
+
const isLib = file.content.includes("export function pageSections(");
|
|
9351
|
+
const next = isLib ? patchLib(file.content) : patchRegistry(file.content);
|
|
9352
|
+
if (next === null) {
|
|
9353
|
+
registryOk = false;
|
|
9354
|
+
continue;
|
|
9355
|
+
}
|
|
9356
|
+
receipt.registry.push({ path: file.path, operation: next === file.content ? "unchanged" : "modify" });
|
|
9357
|
+
write(file.path, next);
|
|
9358
|
+
}
|
|
9359
|
+
const helperState = /* @__PURE__ */ new Map();
|
|
9360
|
+
const helperDialect = /* @__PURE__ */ new Map();
|
|
9361
|
+
for (const dialect of Object.keys(HELPER_PATH)) {
|
|
9362
|
+
const path = HELPER_PATH[dialect];
|
|
9363
|
+
if (!path || helperDialect.has(path)) continue;
|
|
9364
|
+
helperDialect.set(path, dialect);
|
|
9365
|
+
const existing = contents.get(path);
|
|
9366
|
+
helperState.set(path, existing === void 0 ? void 0 : isKnownHelper(existing, dialect));
|
|
9367
|
+
}
|
|
9368
|
+
const helperNeeded = /* @__PURE__ */ new Set();
|
|
9369
|
+
for (const file of sources) {
|
|
9370
|
+
const dialect = dialectOf(file.path);
|
|
9371
|
+
if (!dialect || !/data-bcms-block|sectionRootAttrs/.test(file.content)) continue;
|
|
9372
|
+
const ref = (el, attr) => ({
|
|
9373
|
+
file: file.path,
|
|
9374
|
+
line: lineAt(file.content, el.start),
|
|
9375
|
+
tag: el.tag,
|
|
9376
|
+
block: attr.expression ? file.content.slice(attr.expressionRange?.start ?? attr.range.start, attr.expressionRange?.end ?? attr.range.end) : JSON.stringify(attr.value)
|
|
9377
|
+
});
|
|
9378
|
+
const skip = (el, attr, reason, message) => {
|
|
9379
|
+
receipt.roots.skipped.push({ ...ref(el, attr), reason, message });
|
|
9380
|
+
};
|
|
9381
|
+
const parsed = await parseFile4(dialect, file.content);
|
|
9382
|
+
if (parsed.error) {
|
|
9383
|
+
receipt.roots.skipped.push({
|
|
9384
|
+
file: file.path,
|
|
9385
|
+
line: 1,
|
|
9386
|
+
tag: "",
|
|
9387
|
+
block: "",
|
|
9388
|
+
reason: "PARSE_ERROR",
|
|
9389
|
+
message: parsed.error.message
|
|
9390
|
+
});
|
|
9391
|
+
continue;
|
|
9392
|
+
}
|
|
9393
|
+
const roots = elementsIn2(parsed.roots).flatMap((el) => {
|
|
9394
|
+
const attr = el.attrs.find((a) => a.name === "data-bcms-block");
|
|
9395
|
+
return attr ? [{ el, attr }] : [];
|
|
9396
|
+
});
|
|
9397
|
+
for (const el of elementsIn2(parsed.roots)) {
|
|
9398
|
+
const spread = el.attrs.find((a) => isSpread(file.content, a) && /sectionRootAttrs\s*\(/.test(file.content.slice(a.expressionRange.start, a.expressionRange.end)));
|
|
9399
|
+
if (spread && !el.attrs.some((a) => a.name === "data-bcms-block")) {
|
|
9400
|
+
receipt.roots.alreadyMarked.push({
|
|
9401
|
+
file: file.path,
|
|
9402
|
+
line: lineAt(file.content, el.start),
|
|
9403
|
+
tag: el.tag,
|
|
9404
|
+
block: file.content.slice(spread.expressionRange.start, spread.expressionRange.end)
|
|
9405
|
+
});
|
|
9406
|
+
}
|
|
9407
|
+
}
|
|
9408
|
+
if (roots.length === 0) continue;
|
|
9409
|
+
if (dialect !== "astro" && dialect !== "jsx") {
|
|
9410
|
+
for (const { el, attr } of roots) {
|
|
9411
|
+
if (el.attrs.some((a) => a.name === "data-bcms-style")) receipt.roots.alreadyMarked.push(ref(el, attr));
|
|
9412
|
+
else skip(el, attr, "DIALECT_UNSUPPORTED", `${dialect} section roots are not rewritten by this pass; add data-bcms-style and the block's style by hand.`);
|
|
9413
|
+
}
|
|
9414
|
+
continue;
|
|
9415
|
+
}
|
|
9416
|
+
const source = new MagicString6(file.content);
|
|
9417
|
+
const reader = STYLE_READER[dialect];
|
|
9418
|
+
const helperPath = HELPER_PATH[dialect];
|
|
9419
|
+
const helperSpecifier = relativeImport(file.path, helperPath).replace(/\.tsx?$/, "");
|
|
9420
|
+
const isHelperModule = (spec) => /(^|\/)bcms-content(\.[jt]sx?)?$/.test(spec);
|
|
9421
|
+
const sdk = SDK_SINCE[dialect].pkg;
|
|
9422
|
+
const isSdk = (spec) => spec === sdk;
|
|
9423
|
+
const useSdk = sdkHasSectionRootAttrs(options.packageJson, dialect);
|
|
9424
|
+
const generated = SECTION_MARKERS.some((m) => file.content.includes(m));
|
|
9425
|
+
const typed = dialect === "astro" || file.path.endsWith(".tsx");
|
|
9426
|
+
const free = (name, from) => importedFrom(file.content, name, from) || !mentions2(file.content, name);
|
|
9427
|
+
let wantReader = false;
|
|
9428
|
+
let wantSdk = false;
|
|
9429
|
+
let sectionProp = false;
|
|
9430
|
+
for (const { el, attr } of roots) {
|
|
9431
|
+
if (el.attrs.some((a) => a.name === "data-bcms-style")) {
|
|
9432
|
+
receipt.roots.alreadyMarked.push(ref(el, attr));
|
|
9433
|
+
continue;
|
|
9434
|
+
}
|
|
9435
|
+
if (!isIntrinsic(el.tag)) {
|
|
9436
|
+
skip(el, attr, "NOT_AN_ELEMENT", `<${el.tag}> is a component: data-bcms-block on it is a prop. Mark the element that component renders.`);
|
|
9437
|
+
continue;
|
|
9438
|
+
}
|
|
9439
|
+
if (el.attrs.some((a) => a.name === "style")) {
|
|
9440
|
+
skip(el, attr, "OWN_STYLE", "The root already sets its own style; a second style attribute would be resolved by the dialect and one of them would vanish. Merge the block's style into it by hand.");
|
|
9441
|
+
continue;
|
|
9442
|
+
}
|
|
9443
|
+
if (el.attrs.some((a) => isSpread(file.content, a))) {
|
|
9444
|
+
skip(el, attr, "SPREAD", "The root spreads props that may carry a style; marking it could promise a style the spread overrides.");
|
|
9445
|
+
continue;
|
|
9446
|
+
}
|
|
9447
|
+
if (!attr.expression || !attr.expressionRange) {
|
|
9448
|
+
skip(el, attr, "BLOCK_LITERAL", "data-bcms-block is a literal id: there is no block object here to read a style from.");
|
|
9449
|
+
continue;
|
|
9450
|
+
}
|
|
9451
|
+
const expr = exprOf2(file.content, attr.expressionRange);
|
|
9452
|
+
if (generated && expr === "blockId") {
|
|
9453
|
+
if (!registryOk) {
|
|
9454
|
+
skip(el, attr, "REGISTRY_UNRECOGNISED", "The generated registry or bcms-sections does not pass block.style and could not be patched; re-run --componentize, then this.");
|
|
9455
|
+
continue;
|
|
9456
|
+
}
|
|
9457
|
+
if (!free(reader, isHelperModule) || /\bblockStyle\b/.test(file.content)) {
|
|
9458
|
+
skip(el, attr, "NAME_TAKEN", `${reader} or blockStyle already means something else in this file.`);
|
|
9459
|
+
continue;
|
|
9460
|
+
}
|
|
9461
|
+
source.appendLeft(attr.range.end, ` data-bcms-style="" style={${reader}(blockStyle)}`);
|
|
9462
|
+
receipt.roots.upgraded.push({ ...ref(el, attr), form: "section-prop" });
|
|
9463
|
+
wantReader = true;
|
|
9464
|
+
sectionProp = true;
|
|
9465
|
+
continue;
|
|
9466
|
+
}
|
|
9467
|
+
const member = BLOCK_MEMBER.exec(expr);
|
|
9468
|
+
if (member) {
|
|
9469
|
+
const [, object, dot, key] = member;
|
|
9470
|
+
if (useSdk && key === "id") {
|
|
9471
|
+
if (!free("sectionRootAttrs", isSdk)) {
|
|
9472
|
+
skip(el, attr, "NAME_TAKEN", "sectionRootAttrs already means something else in this file.");
|
|
9473
|
+
continue;
|
|
9474
|
+
}
|
|
9475
|
+
source.overwrite(attr.range.start, attr.range.end, `{...sectionRootAttrs(${object})}`);
|
|
9476
|
+
receipt.roots.upgraded.push({ ...ref(el, attr), form: "sectionRootAttrs" });
|
|
9477
|
+
wantSdk = true;
|
|
9478
|
+
continue;
|
|
9479
|
+
}
|
|
9480
|
+
if (!free(reader, isHelperModule)) {
|
|
9481
|
+
skip(el, attr, "NAME_TAKEN", `${reader} already means something else in this file.`);
|
|
9482
|
+
continue;
|
|
9483
|
+
}
|
|
9484
|
+
const style = typed && dialect === "jsx" ? `(${object} as { style?: unknown } | null | undefined)?.style` : `${object}${dot}style`;
|
|
9485
|
+
source.appendLeft(attr.range.end, ` data-bcms-style="" style={${reader}(${style})}`);
|
|
9486
|
+
receipt.roots.upgraded.push({ ...ref(el, attr), form: "helper" });
|
|
9487
|
+
wantReader = true;
|
|
9488
|
+
continue;
|
|
9489
|
+
}
|
|
9490
|
+
if (ID_ONLY.test(expr)) {
|
|
9491
|
+
skip(
|
|
9492
|
+
el,
|
|
9493
|
+
attr,
|
|
9494
|
+
"BLOCK_ID_ONLY",
|
|
9495
|
+
`Only a block id reaches this root (${expr}); the block's style is not in scope. Pass the block (or its style) down with the id, then re-run.`
|
|
9496
|
+
);
|
|
9497
|
+
continue;
|
|
9498
|
+
}
|
|
9499
|
+
skip(el, attr, "BLOCK_UNRESOLVABLE", `data-bcms-block={${expr}} does not name one block whose style this root could paint.`);
|
|
9500
|
+
}
|
|
9501
|
+
if (sectionProp) {
|
|
9502
|
+
const destructure = dialect === "astro" ? /const\s*\{([^}]*\bblockId\b[^}]*)\}\s*=\s*Astro\.props\s*;?/.exec(file.content) : /\(\s*\{([^}]*\bblockId\b[^}]*)\}\s*:\s*BcmsSectionProps\s*\)/.exec(file.content);
|
|
9503
|
+
if (destructure) {
|
|
9504
|
+
const close = destructure.index + destructure[0].indexOf("}");
|
|
9505
|
+
let at = close;
|
|
9506
|
+
while (at > 0 && /\s/.test(file.content[at - 1])) at--;
|
|
9507
|
+
source.appendLeft(at, ", blockStyle");
|
|
9508
|
+
}
|
|
9509
|
+
if (dialect === "astro") {
|
|
9510
|
+
const props = /interface Props \{[\s\S]*?\n\}/.exec(file.content);
|
|
9511
|
+
if (props) {
|
|
9512
|
+
const end = props.index + props[0].length - 1;
|
|
9513
|
+
const nl = eolOf(file.content);
|
|
9514
|
+
source.appendLeft(
|
|
9515
|
+
end,
|
|
9516
|
+
` /** The placement's \`block.style\` \u2014 painted on the root, which says so with \`data-bcms-style\`. */${nl} blockStyle?: Record<string, unknown>;${nl}`
|
|
9517
|
+
);
|
|
9518
|
+
}
|
|
9519
|
+
}
|
|
9520
|
+
}
|
|
9521
|
+
if (wantReader) {
|
|
9522
|
+
addNamedImport(source, file.content, parsed, helperSpecifier, isHelperModule, [reader]);
|
|
9523
|
+
helperNeeded.add(helperPath);
|
|
9524
|
+
}
|
|
9525
|
+
if (wantSdk) addNamedImport(source, file.content, parsed, sdk, isSdk, ["sectionRootAttrs"]);
|
|
9526
|
+
if (source.hasChanged()) write(file.path, source.toString());
|
|
9527
|
+
}
|
|
9528
|
+
for (const [path, version] of helperState) {
|
|
9529
|
+
const dialect = helperDialect.get(path);
|
|
9530
|
+
const needed = helperNeeded.has(path);
|
|
9531
|
+
if (version === void 0) {
|
|
9532
|
+
if (!needed) continue;
|
|
9533
|
+
write(path, helperSource(dialect));
|
|
9534
|
+
receipt.helpers.push({ path, from: null, to: HELPER_VERSION, operation: "add" });
|
|
9535
|
+
const stub = `${CONTENT_DIR}/layout.json`;
|
|
9536
|
+
if (!contents.has(stub)) write(stub, STUB_CONTENT);
|
|
9537
|
+
continue;
|
|
9538
|
+
}
|
|
9539
|
+
if (version === null) {
|
|
9540
|
+
if (needed && !options.overwriteHelper) {
|
|
9541
|
+
throw new UpgradeError(
|
|
9542
|
+
`${path} already exists and its bytes are not ones @bettercms-ai/convert wrote \u2014 someone else's module, or ours with edits in it. The upgraded roots read bcmsSectionStyle from it. Move it, or re-run with --overwrite-helper.`
|
|
9543
|
+
);
|
|
9544
|
+
}
|
|
9545
|
+
if (needed) {
|
|
9546
|
+
write(path, helperSource(dialect));
|
|
9547
|
+
receipt.helpers.push({ path, from: null, to: HELPER_VERSION, operation: "modify" });
|
|
9548
|
+
} else receipt.helpers.push({ path, from: null, to: HELPER_VERSION, operation: "foreign" });
|
|
9549
|
+
continue;
|
|
9550
|
+
}
|
|
9551
|
+
if (version < HELPER_VERSION) {
|
|
9552
|
+
write(path, helperSource(dialect));
|
|
9553
|
+
receipt.helpers.push({ path, from: version, to: HELPER_VERSION, operation: "modify" });
|
|
9554
|
+
} else receipt.helpers.push({ path, from: version, to: HELPER_VERSION, operation: "unchanged" });
|
|
9555
|
+
}
|
|
9556
|
+
const byPlace = (a, b) => a.file.localeCompare(b.file) || a.line - b.line;
|
|
9557
|
+
receipt.roots.upgraded.sort(byPlace);
|
|
9558
|
+
receipt.roots.alreadyMarked.sort(byPlace);
|
|
9559
|
+
receipt.roots.skipped.sort(byPlace);
|
|
9560
|
+
return { files: [...out.values()].sort((a, b) => a.path.localeCompare(b.path)), receipt };
|
|
9561
|
+
}
|
|
9562
|
+
|
|
9198
9563
|
// src/index.ts
|
|
9199
9564
|
var UNPARSEABLE_DIALECT = {
|
|
9200
9565
|
roots: [],
|
|
@@ -9532,7 +9897,7 @@ async function convertSources(briefIn, sources, options) {
|
|
|
9532
9897
|
rewritten++;
|
|
9533
9898
|
}
|
|
9534
9899
|
for (const [file, splices] of [...componentEdits].sort(([a], [b]) => a.localeCompare(b))) {
|
|
9535
|
-
const source = new
|
|
9900
|
+
const source = new MagicString7(contents.get(file));
|
|
9536
9901
|
const seen = /* @__PURE__ */ new Set();
|
|
9537
9902
|
for (const splice of splices) {
|
|
9538
9903
|
const id = `${splice.start}:${splice.end}:${splice.text}`;
|
|
@@ -10326,6 +10691,7 @@ export {
|
|
|
10326
10691
|
SSR_DRAFT_RECIPE,
|
|
10327
10692
|
STUB_CONTENT,
|
|
10328
10693
|
SectionInvariantError,
|
|
10694
|
+
UpgradeError,
|
|
10329
10695
|
ValidationError,
|
|
10330
10696
|
aliasesFrom,
|
|
10331
10697
|
assertForms,
|
|
@@ -10376,6 +10742,7 @@ export {
|
|
|
10376
10742
|
routeOfFile,
|
|
10377
10743
|
stripTags,
|
|
10378
10744
|
unreadableConfigs,
|
|
10745
|
+
upgradeSections,
|
|
10379
10746
|
walkAst,
|
|
10380
10747
|
wireForms
|
|
10381
10748
|
};
|