@bettercms-ai/preview-runtime 0.2.0 → 0.3.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/README.md +14 -2
- package/dist/cli.js +67 -16
- package/dist/cli.js.map +1 -1
- package/dist/scope-client.global.js +1 -0
- package/dist/server.d.ts +40 -1
- package/dist/server.js +50 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,8 +42,20 @@ A component does not have to be a standalone file with its fields as props. A se
|
|
|
42
42
|
`npx @bettercms-ai/convert --componentize` extracted from a page (a file starting
|
|
43
43
|
`// @bettercms-ai/convert section v…`) is recognised by that marker and rendered with its section contract:
|
|
44
44
|
`<Section blockId="bcms-preview" overrides={props} page={{}} />`, so the live props arrive as the instance's
|
|
45
|
-
own copy.
|
|
46
|
-
|
|
45
|
+
own copy.
|
|
46
|
+
|
|
47
|
+
## Components with no file
|
|
48
|
+
|
|
49
|
+
A component placed on a page, or used in the project Layout (navigation, footer), needs no recorded file.
|
|
50
|
+
The manifest lists it as `kind: "page"`; the preview renders that real route of your app — its loops, child
|
|
51
|
+
components, collections and page styles exactly as deployed — keeps only the component's own element
|
|
52
|
+
(`data-bcms-block`, else the element holding its fields, else the layout's `<header>`/`<footer>`), and writes
|
|
53
|
+
the live props onto it through the same `data-bcms-field` / `data-bcms-layout-field` / `data-bcms-props`
|
|
54
|
+
attributes the site already declares. A recorded file still wins when there is one.
|
|
55
|
+
|
|
56
|
+
v1 limits: scripts inside the kept element do not run, so a client island renders its server HTML without
|
|
57
|
+
hydrating (validation is unaffected). The Next.js page path (`render` → `render-page`) is untested against a
|
|
58
|
+
real Next app yet; Astro is verified.
|
|
47
59
|
|
|
48
60
|
A failed validation reports what broke — the app server's own error lines first — and the dashboard shows
|
|
49
61
|
it under "Runtime + console".
|
package/dist/cli.js
CHANGED
|
@@ -36,6 +36,25 @@ function readJson(file) {
|
|
|
36
36
|
fail(`could not read ${file}: ${error.message}`);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
+
var LANDMARKS = /* @__PURE__ */ new Set(["header", "footer", "nav"]);
|
|
40
|
+
var isStringRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((v) => typeof v === "string");
|
|
41
|
+
function pageSource(id, source) {
|
|
42
|
+
const { route, blockId, groupKey, layoutSectionId, landmark } = source;
|
|
43
|
+
if (typeof route !== "string" || !route.startsWith("/") || route.startsWith("//") || /[\s?#\\]/.test(route) || route.split("/").includes("..")) {
|
|
44
|
+
fail(`component ${id} has an unsafe page route: ${String(route)}`);
|
|
45
|
+
}
|
|
46
|
+
if (typeof blockId === "string" && blockId) {
|
|
47
|
+
if (groupKey != null && typeof groupKey !== "string") fail(`component ${id}: groupKey must be a string`);
|
|
48
|
+
if (source.source != null && !isStringRecord(source.source)) fail(`component ${id}: source must map prop keys to page paths`);
|
|
49
|
+
return { kind: "page", route, blockId, groupKey: groupKey ?? null, source: source.source ?? null };
|
|
50
|
+
}
|
|
51
|
+
if (typeof layoutSectionId === "string" && layoutSectionId) {
|
|
52
|
+
if (landmark != null && !LANDMARKS.has(landmark)) fail(`component ${id}: unknown landmark ${String(landmark)}`);
|
|
53
|
+
if (source.bindings != null && !isStringRecord(source.bindings)) fail(`component ${id}: bindings must map input ids to layout field ids`);
|
|
54
|
+
return { kind: "page", route, layoutSectionId, landmark: landmark ?? null, bindings: source.bindings ?? {} };
|
|
55
|
+
}
|
|
56
|
+
fail(`component ${id}: a page source names neither a placement (blockId) nor a layout section (layoutSectionId)`);
|
|
57
|
+
}
|
|
39
58
|
function validateManifest(root, manifest) {
|
|
40
59
|
if (!manifest || !Array.isArray(manifest.components) || manifest.components.length === 0) {
|
|
41
60
|
fail("the manifest lists no components");
|
|
@@ -45,6 +64,7 @@ function validateManifest(root, manifest) {
|
|
|
45
64
|
if (!entry || typeof entry.id !== "string" || !entry.id.trim()) fail("a manifest entry has no id");
|
|
46
65
|
if (seen.has(entry.id)) fail(`component ${entry.id} is listed twice`);
|
|
47
66
|
seen.add(entry.id);
|
|
67
|
+
if (entry.source?.kind === "page") return { id: entry.id, source: pageSource(entry.id, entry.source) };
|
|
48
68
|
const path = entry.source?.path;
|
|
49
69
|
if (typeof path !== "string" || path.startsWith("/") || path.includes("\\") || path.split("/").includes("..")) {
|
|
50
70
|
fail(`component ${entry.id} has an unsafe source path: ${String(path)}`);
|
|
@@ -80,14 +100,20 @@ function registrySource(fromDir, root, entries) {
|
|
|
80
100
|
const imports = [];
|
|
81
101
|
const keys = [];
|
|
82
102
|
const kinds = [];
|
|
103
|
+
const pages = [];
|
|
83
104
|
entries.forEach((entry, index) => {
|
|
84
|
-
|
|
105
|
+
const source = entry.source;
|
|
106
|
+
kinds.push(` ${JSON.stringify(entry.id)}: ${JSON.stringify(source.kind ?? "file")},`);
|
|
107
|
+
if (source.kind === "page") {
|
|
108
|
+
pages.push(` ${JSON.stringify(entry.id)}: ${JSON.stringify(source)},`);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
let specifier = relative(fromDir, join(root, source.path)).split(sep).join("/");
|
|
85
112
|
if (!specifier.startsWith(".")) specifier = `./${specifier}`;
|
|
86
113
|
if ([".tsx", ".ts", ".jsx", ".js"].includes(extname(specifier))) specifier = specifier.slice(0, -extname(specifier).length);
|
|
87
114
|
const local = `Component${index}`;
|
|
88
|
-
imports.push(
|
|
115
|
+
imports.push(source.export === void 0 || source.export === "default" ? `import ${local} from ${JSON.stringify(specifier)};` : `import { ${source.export} as ${local} } from ${JSON.stringify(specifier)};`);
|
|
89
116
|
keys.push(` ${JSON.stringify(entry.id)}: ${local},`);
|
|
90
|
-
kinds.push(` ${JSON.stringify(entry.id)}: ${JSON.stringify(entry.source.kind ?? "file")},`);
|
|
91
117
|
});
|
|
92
118
|
return `${imports.join("\n")}
|
|
93
119
|
|
|
@@ -95,11 +121,16 @@ export const registry: Record<string, any> = {
|
|
|
95
121
|
${keys.join("\n")}
|
|
96
122
|
};
|
|
97
123
|
|
|
98
|
-
export const kinds: Record<string, "file" | "section"> = {
|
|
124
|
+
export const kinds: Record<string, "file" | "section" | "page"> = {
|
|
99
125
|
${kinds.join("\n")}
|
|
100
126
|
};
|
|
101
127
|
|
|
102
|
-
export const
|
|
128
|
+
export const pages: Record<string, any> = {
|
|
129
|
+
${pages.join("\n")}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const own = (map: object, componentId: string) => Object.prototype.hasOwnProperty.call(map, componentId);
|
|
133
|
+
export const has = (componentId: string): boolean => own(registry, componentId) || own(pages, componentId);
|
|
103
134
|
`;
|
|
104
135
|
}
|
|
105
136
|
function writeRuntimeLibrary(dir) {
|
|
@@ -107,6 +138,8 @@ function writeRuntimeLibrary(dir) {
|
|
|
107
138
|
const types = join(here, "server.d.ts");
|
|
108
139
|
if (existsSync(types)) writeFileSync(join(dir, "server.d.mts"), readFileSync(types, "utf8"));
|
|
109
140
|
writeFileSync(join(dir, "shell.ts"), `export default ${JSON.stringify(readFileSync(join(here, "shell.global.js"), "utf8"))};
|
|
141
|
+
`);
|
|
142
|
+
writeFileSync(join(dir, "scope.ts"), `export default ${JSON.stringify(readFileSync(join(here, "scope-client.global.js"), "utf8"))};
|
|
110
143
|
`);
|
|
111
144
|
}
|
|
112
145
|
function astroGlobalStyles(root) {
|
|
@@ -136,7 +169,8 @@ function ensureAstroNodeAdapter(root) {
|
|
|
136
169
|
} catch {
|
|
137
170
|
}
|
|
138
171
|
const astroVersion = readJson(require2.resolve("astro/package.json")).version;
|
|
139
|
-
const
|
|
172
|
+
const [major, minor] = astroVersion.split(".").map(Number);
|
|
173
|
+
const range = major === 7 && minor < 3 ? ">=11.0.0 <11.1.3" : ASTRO_NODE_ADAPTER[String(major)];
|
|
140
174
|
if (!range) fail(`Astro ${astroVersion} is not supported for component previews yet`);
|
|
141
175
|
run(root, "npm", ["install", "--no-save", "--no-audit", "--no-fund", `@astrojs/node@${range}`]);
|
|
142
176
|
}
|
|
@@ -168,11 +202,13 @@ ${handler("GET", "() => handleHealth()")}`);
|
|
|
168
202
|
const styles = astroGlobalStyles(root).map((file) => `import ${JSON.stringify(relative(gen, file).split(sep).join("/"))};`).join("\n");
|
|
169
203
|
writeFileSync(join(gen, "render.astro"), `---
|
|
170
204
|
${styles}
|
|
171
|
-
import { renderEntry, renderHeaders } from "./server.mjs";
|
|
172
|
-
import { registry, kinds } from "./registry";
|
|
205
|
+
import { renderEntry, renderHeaders, renderPageSection } from "./server.mjs";
|
|
206
|
+
import { registry, kinds, pages } from "./registry";
|
|
207
|
+
import scope from "./scope";
|
|
173
208
|
export const prerender = false;
|
|
174
209
|
const headers = renderHeaders();
|
|
175
210
|
const entry = renderEntry(Astro.url.searchParams.get("id"));
|
|
211
|
+
if (entry && pages[entry.componentId]) return await renderPageSection(Astro.request, entry, pages[entry.componentId], scope);
|
|
176
212
|
const Component = entry ? registry[entry.componentId] : undefined;
|
|
177
213
|
const section = entry ? kinds[entry.componentId] === "section" : false;
|
|
178
214
|
for (const [name, value] of Object.entries(headers)) Astro.response.headers.set(name, value);
|
|
@@ -266,17 +302,30 @@ export function POST(request: Request) {
|
|
|
266
302
|
export function GET() {
|
|
267
303
|
return handleHealth();
|
|
268
304
|
}
|
|
305
|
+
`);
|
|
306
|
+
route("render-page", `import { renderEntry, renderPageSection } from "../server.mjs";
|
|
307
|
+
import { pages } from "../registry";
|
|
308
|
+
import scope from "../scope";
|
|
309
|
+
export function GET(request: Request) {
|
|
310
|
+
const entry = renderEntry(new URL(request.url).searchParams.get("id"));
|
|
311
|
+
const page = entry ? pages[entry.componentId] : undefined;
|
|
312
|
+
if (!entry || !page) return new Response("Not found", { status: 404 });
|
|
313
|
+
return renderPageSection(request, entry, page, scope);
|
|
314
|
+
}
|
|
269
315
|
`);
|
|
270
316
|
mkdirSync(join(gen, "render"), { recursive: true });
|
|
271
|
-
writeFileSync(join(gen, "render", "page.tsx"), `import { notFound } from "next/navigation";
|
|
317
|
+
writeFileSync(join(gen, "render", "page.tsx"), `import { notFound, redirect } from "next/navigation";
|
|
272
318
|
import { renderEntry } from "../server.mjs";
|
|
273
|
-
import { registry, kinds } from "../registry";
|
|
319
|
+
import { registry, kinds, pages } from "../registry";
|
|
274
320
|
|
|
275
321
|
export const dynamic = "force-dynamic";
|
|
276
322
|
|
|
277
323
|
export default async function BetterCMSComponentPreview({ searchParams }: { searchParams: Promise<{ id?: string }> }) {
|
|
278
324
|
const { id } = await searchParams;
|
|
279
325
|
const entry = renderEntry(id);
|
|
326
|
+
// A page-kind component answers with a whole document, which a page inside the root layout cannot be.
|
|
327
|
+
// Relative on purpose: Next prefixes basePath onto a "/"-rooted redirect, and the browser resolves this one.
|
|
328
|
+
if (entry && pages[entry.componentId]) redirect(\`render-page?id=\${encodeURIComponent(id!)}\`);
|
|
280
329
|
const Component = entry ? registry[entry.componentId] : undefined;
|
|
281
330
|
if (!entry || !Component) notFound();
|
|
282
331
|
// The marker the runtime page checks before acknowledging: a 404 or an error page never carries it.
|
|
@@ -538,16 +587,16 @@ async function validateComponent() {
|
|
|
538
587
|
const work = mkdtempSync(join2(tmpdir(), "bcms-validate-"));
|
|
539
588
|
let runtime = null;
|
|
540
589
|
try {
|
|
541
|
-
const manifest = await api(`/api/v1/projects/${projectId}/component-preview/manifest`);
|
|
590
|
+
const manifest = await api(`/api/v1/projects/${projectId}/component-preview/manifest?kinds=page`);
|
|
542
591
|
const entry = manifest.components.find((c) => c.id === componentId);
|
|
543
592
|
if (!entry) {
|
|
544
593
|
throw new ValidationFailure(
|
|
545
594
|
"COMPONENT_SOURCE_NOT_RECORDED",
|
|
546
|
-
"No file is recorded
|
|
595
|
+
"No file is recorded and this component is neither placed on a page nor used in the layout, so there is nothing to render. Place it, add it to the layout, or record its file."
|
|
547
596
|
);
|
|
548
597
|
}
|
|
549
598
|
const manifestPath = join2(work, "manifest.json");
|
|
550
|
-
writeFileSync2(manifestPath, JSON.stringify({ components: manifest.components.map(({ id: id2, source }) => ({ id: id2, source })) }));
|
|
599
|
+
writeFileSync2(manifestPath, JSON.stringify({ components: manifest.components.map(({ id: id2, source, fallback }) => ({ id: id2, source, ...fallback ? { fallback } : {} })) }));
|
|
551
600
|
const out = join2(work, "runtime");
|
|
552
601
|
let built;
|
|
553
602
|
try {
|
|
@@ -601,7 +650,8 @@ async function validateComponent() {
|
|
|
601
650
|
`${local}${PREVIEW_ROUTE_BASE}/render?id=${encodeURIComponent(id)}`,
|
|
602
651
|
nativeViewports,
|
|
603
652
|
manifest.brandTokenNames,
|
|
604
|
-
|
|
653
|
+
// A page-kind render lifts a section too, and stamps its root.
|
|
654
|
+
{ section: built.kinds[componentId] !== "file" }
|
|
605
655
|
);
|
|
606
656
|
const tarball = join2(work, "bundle.tgz");
|
|
607
657
|
if (spawnSync2("tar", ["-czf", tarball, "-C", out, "."], { stdio: "inherit" }).status !== 0) {
|
|
@@ -610,6 +660,7 @@ async function validateComponent() {
|
|
|
610
660
|
const bytes = readFileSync2(tarball);
|
|
611
661
|
runtime.kill();
|
|
612
662
|
runtime = null;
|
|
663
|
+
const hint = entry.fallback && built.kinds[componentId] === "file" ? [`Clear the recorded source: this component renders from page ${entry.fallback.route} without one.`] : [];
|
|
613
664
|
const target = (await claimRequest()).request;
|
|
614
665
|
const checks = {
|
|
615
666
|
brandKit: {
|
|
@@ -623,7 +674,7 @@ async function validateComponent() {
|
|
|
623
674
|
consoleErrors: render.consoleErrors,
|
|
624
675
|
// Only on a failure, and bounded: the server's own error lines first (the real cause), then what the
|
|
625
676
|
// browser saw. The database stores them and the panel shows the first one.
|
|
626
|
-
...render.runtimeErrors + render.consoleErrors > 0 ? { problems: [...serverErrors.map((line) => `server: ${line}`), ...render.problems].slice(0, 10).map((line) => line.slice(0, 200)) } : {}
|
|
677
|
+
...render.runtimeErrors + render.consoleErrors > 0 ? { problems: [...hint, ...serverErrors.map((line) => `server: ${line}`), ...render.problems].slice(0, 10).map((line) => line.slice(0, 200)) } : {}
|
|
627
678
|
},
|
|
628
679
|
visual: { status: "baseline-missing", reviewRequired: true, viewports: render.results }
|
|
629
680
|
};
|
|
@@ -661,7 +712,7 @@ async function validateComponent() {
|
|
|
661
712
|
console.log(`bcms-preview: validation ${passed ? "PASSED" : "FAILED"} for ${componentId}`);
|
|
662
713
|
if (!passed) {
|
|
663
714
|
if (render.missingTokens.length) console.log(` brand tokens used but not defined: ${render.missingTokens.join(", ")}`);
|
|
664
|
-
for (const problem of render.problems) console.log(` ${problem}`);
|
|
715
|
+
for (const problem of [...hint, ...render.problems]) console.log(` ${problem}`);
|
|
665
716
|
}
|
|
666
717
|
} catch (error) {
|
|
667
718
|
const code = error instanceof ValidationFailure ? error.code : "COMPONENT_VALIDATION_FAILED";
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/build.ts","../src/validate.ts","../src/cli.ts"],"sourcesContent":["/**\n * Builds a component preview runtime from an unmodified Next.js or Astro app. Used by `bcms-preview build`\n * and by the validator.\n *\n * The manifest names which file implements which component:\n * { \"components\": [{ \"id\": \"cmp_1\", \"source\": { \"path\": \"src/components/Hero.astro\" } }] }\n *\n * 🔴 NOTHING IN THE CUSTOMER'S REPOSITORY IS CHANGED. Routes and a registry are generated into the\n * checkout, the framework builds, the result is packaged as a runtime release, and every generated file\n * is removed again — including when the build fails. In CI the checkout is thrown away anyway; on a\n * developer machine this is the difference between a tool and a mess.\n */\nimport { spawnSync } from \"node:child_process\";\nimport { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, extname, join, relative, resolve, sep } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * `file`: a component module whose props ARE the component's fields — rendered with the props spread.\n * `section`: a section `npx @bettercms-ai/convert --componentize` extracted from a page. Its props are always\n * `{ blockId, bind, overrides, page }`, so it is rendered with the live props as `overrides`.\n */\ntype SourceKind = \"file\" | \"section\";\ntype ManifestEntry = { id: string; source: { path: string; export?: string; kind?: SourceKind } };\ntype Manifest = { components: ManifestEntry[] };\ntype Framework = \"astro\" | \"next\";\n\nconst here = dirname(fileURLToPath(import.meta.url));\nconst PREVIEW_BASE = \"/__bettercms/component-preview\";\nconst COMPONENT_EXTENSIONS = new Set([\".astro\", \".tsx\", \".jsx\", \".ts\", \".js\", \".mjs\"]);\nconst IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n/** Every section file the codemod writes carries this (`// @bettercms-ai/convert section v2`, earlier `v1`). */\nconst SECTION_MARKER_PREFIX = \"// @bettercms-ai/convert section v\";\n\n/**\n * 🔴 THROWN, NOT `process.exit`. Writes to a piped stderr are asynchronous in Node, so exiting on the\n * next line dropped the message: in CI the step failed with no reason in the log at all. The single\n * handler at the bottom prints and sets the exit code, and the process ends once the write has flushed.\n */\nexport class CliFailure extends Error {}\n\nexport function fail(message: string): never {\n throw new CliFailure(message);\n}\n\n/**\n * Files a framework build rewrites in place. `next build` edits tsconfig.json and next-env.d.ts; an\n * `npm install --no-save` still rewrites the lockfile. Restored afterwards, so a preview build leaves the\n * app exactly as it found it.\n */\nfunction snapshot(root: string, names: string[]): () => void {\n const saved = names.map((name) => {\n const file = join(root, name);\n return { file, content: existsSync(file) ? readFileSync(file) : null };\n });\n return () => {\n for (const { file, content } of saved) {\n if (content === null) rmSync(file, { force: true });\n else writeFileSync(file, content);\n }\n };\n}\n\nconst MUTATED_BY_BUILD = [\"tsconfig.json\", \"next-env.d.ts\", \"package-lock.json\", \"pnpm-lock.yaml\", \"yarn.lock\", \"bun.lock\"];\n\nfunction readJson<T>(file: string): T {\n try {\n return JSON.parse(readFileSync(file, \"utf8\")) as T;\n } catch (error) {\n fail(`could not read ${file}: ${(error as Error).message}`);\n }\n}\n\n/** Manifest paths travel from an API into generated imports: relative, inside the app, and real. */\nexport function validateManifest(root: string, manifest: Manifest): ManifestEntry[] {\n if (!manifest || !Array.isArray(manifest.components) || manifest.components.length === 0) {\n fail(\"the manifest lists no components\");\n }\n const seen = new Set<string>();\n return manifest.components.map((entry) => {\n if (!entry || typeof entry.id !== \"string\" || !entry.id.trim()) fail(\"a manifest entry has no id\");\n if (seen.has(entry.id)) fail(`component ${entry.id} is listed twice`);\n seen.add(entry.id);\n const path = entry.source?.path;\n if (typeof path !== \"string\" || path.startsWith(\"/\") || path.includes(\"\\\\\") || path.split(\"/\").includes(\"..\")) {\n fail(`component ${entry.id} has an unsafe source path: ${String(path)}`);\n }\n if (!COMPONENT_EXTENSIONS.has(extname(path))) fail(`component ${entry.id}: unsupported file type ${extname(path)}`);\n if (!existsSync(join(root, path))) fail(`component ${entry.id}: ${path} does not exist`);\n const named = entry.source.export;\n if (named !== undefined && named !== \"default\" && !IDENTIFIER.test(named)) {\n fail(`component ${entry.id}: export \"${named}\" is not a valid identifier`);\n }\n // A section is recognised by its own marker as well as by the manifest: a source recorded before kinds\n // existed defaults to `file`, and rendering a section as a file hands it none of its copy.\n const head = readFileSync(join(root, path), \"utf8\").slice(0, 512);\n const kind: SourceKind = entry.source.kind === \"section\" || head.includes(SECTION_MARKER_PREFIX) ? \"section\" : \"file\";\n return { id: entry.id, source: { path, export: named ?? \"default\", kind } };\n });\n}\n\nfunction detectFramework(root: string): Framework {\n const pkg = readJson<{ dependencies?: Record<string, string>; devDependencies?: Record<string, string> }>(join(root, \"package.json\"));\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (deps.astro) return \"astro\";\n if (deps.next) return \"next\";\n fail(\"this app depends on neither astro nor next\");\n}\n\nfunction run(root: string, command: string, args: string[]) {\n const result = spawnSync(command, args, { cwd: root, stdio: \"inherit\", env: process.env });\n if (result.status !== 0) throw new Error(`${command} ${args.join(\" \")} exited with ${result.status}`);\n}\n\nfunction bin(root: string, name: string): string {\n const local = join(root, \"node_modules\", \".bin\", name);\n if (!existsSync(local)) fail(`${name} is not installed in ${root}. Install the app's dependencies first.`);\n return local;\n}\n\n/** A registry module: one import per component, keyed by component id. */\nfunction registrySource(fromDir: string, root: string, entries: ManifestEntry[]): string {\n const imports: string[] = [];\n const keys: string[] = [];\n const kinds: string[] = [];\n entries.forEach((entry, index) => {\n let specifier = relative(fromDir, join(root, entry.source.path)).split(sep).join(\"/\");\n if (!specifier.startsWith(\".\")) specifier = `./${specifier}`;\n // TypeScript sources are imported without their extension, the way the app itself imports them.\n if ([\".tsx\", \".ts\", \".jsx\", \".js\"].includes(extname(specifier))) specifier = specifier.slice(0, -extname(specifier).length);\n const local = `Component${index}`;\n imports.push(entry.source.export === \"default\"\n ? `import ${local} from ${JSON.stringify(specifier)};`\n : `import { ${entry.source.export} as ${local} } from ${JSON.stringify(specifier)};`);\n keys.push(` ${JSON.stringify(entry.id)}: ${local},`);\n kinds.push(` ${JSON.stringify(entry.id)}: ${JSON.stringify(entry.source.kind ?? \"file\")},`);\n });\n return `${imports.join(\"\\n\")}\\n\\nexport const registry: Record<string, any> = {\\n${keys.join(\"\\n\")}\\n};\\n\\nexport const kinds: Record<string, \"file\" | \"section\"> = {\\n${kinds.join(\"\\n\")}\\n};\\n\\nexport const has = (componentId: string): boolean => Object.prototype.hasOwnProperty.call(registry, componentId);\\n`;\n}\n\nfunction writeRuntimeLibrary(dir: string) {\n writeFileSync(join(dir, \"server.mjs\"), readFileSync(join(here, \"server.js\"), \"utf8\"));\n const types = join(here, \"server.d.ts\");\n if (existsSync(types)) writeFileSync(join(dir, \"server.d.mts\"), readFileSync(types, \"utf8\"));\n writeFileSync(join(dir, \"shell.ts\"), `export default ${JSON.stringify(readFileSync(join(here, \"shell.global.js\"), \"utf8\"))};\\n`);\n}\n\n/** CSS the app's layouts import. The render page has no layout, so it imports them itself. */\nfunction astroGlobalStyles(root: string): string[] {\n const found = new Set<string>();\n const scan = (dir: string) => {\n if (!existsSync(dir)) return;\n for (const name of readdirSync(dir)) {\n const full = join(dir, name);\n if (statSync(full).isDirectory()) scan(full);\n else if (name.endsWith(\".astro\")) {\n for (const match of readFileSync(full, \"utf8\").matchAll(/^\\s*import\\s+[\"']([^\"']+\\.css)[\"'];?/gm)) {\n const target = resolve(dirname(full), match[1]!);\n if (target.startsWith(root) && existsSync(target)) found.add(target);\n }\n }\n }\n };\n scan(join(root, \"src\", \"layouts\"));\n return [...found];\n}\n\nconst ASTRO_NODE_ADAPTER: Record<string, string> = { \"5\": \"^9\", \"6\": \"^10\", \"7\": \"^11\" };\n\nfunction ensureAstroNodeAdapter(root: string) {\n const require = createRequire(join(root, \"package.json\"));\n try {\n require.resolve(\"@astrojs/node\");\n return;\n } catch {\n // Not installed: add it without touching package.json or the lockfile.\n }\n const astroVersion = readJson<{ version: string }>(require.resolve(\"astro/package.json\")).version;\n const range = ASTRO_NODE_ADAPTER[astroVersion.split(\".\")[0]!];\n if (!range) fail(`Astro ${astroVersion} is not supported for component previews yet`);\n run(root, \"npm\", [\"install\", \"--no-save\", \"--no-audit\", \"--no-fund\", `@astrojs/node@${range}`]);\n}\n\nfunction buildAstro(root: string, entries: ManifestEntry[], out: string) {\n const configName = [\"astro.config.mjs\", \"astro.config.js\", \"astro.config.ts\", \"astro.config.mts\"].find((f) => existsSync(join(root, f)));\n if (!configName) fail(\"no astro.config file found\");\n ensureAstroNodeAdapter(root);\n\n const gen = join(root, \".bcms-preview\");\n const wrapper = join(root, \"astro.config.bcms-preview.mjs\");\n rmSync(gen, { recursive: true, force: true });\n mkdirSync(gen, { recursive: true });\n try {\n writeRuntimeLibrary(gen);\n writeFileSync(join(gen, \"registry.ts\"), registrySource(gen, root, entries));\n const handler = (method: string, body: string) =>\n `export const prerender = false;\\nexport const ${method} = ${body};\\n`;\n writeFileSync(join(gen, \"runtime.ts\"), `import { handleRuntime } from \"./server.mjs\";\\nimport shell from \"./shell\";\\n${handler(\"GET\", \"() => handleRuntime(shell)\")}`);\n writeFileSync(join(gen, \"session.ts\"), `import { handleSession } from \"./server.mjs\";\\nimport { has } from \"./registry\";\\n${handler(\"POST\", \"({ request }: { request: Request }) => handleSession(request, has)\")}`);\n writeFileSync(join(gen, \"props.ts\"), `import { handleProps } from \"./server.mjs\";\\nimport { has } from \"./registry\";\\n${handler(\"POST\", \"({ request }: { request: Request }) => handleProps(request, has)\")}`);\n writeFileSync(join(gen, \"health.ts\"), `import { handleHealth } from \"./server.mjs\";\\n${handler(\"GET\", \"() => handleHealth()\")}`);\n const styles = astroGlobalStyles(root)\n .map((file) => `import ${JSON.stringify(relative(gen, file).split(sep).join(\"/\"))};`)\n .join(\"\\n\");\n writeFileSync(join(gen, \"render.astro\"), `---\n${styles}\nimport { renderEntry, renderHeaders } from \"./server.mjs\";\nimport { registry, kinds } from \"./registry\";\nexport const prerender = false;\nconst headers = renderHeaders();\nconst entry = renderEntry(Astro.url.searchParams.get(\"id\"));\nconst Component = entry ? registry[entry.componentId] : undefined;\nconst section = entry ? kinds[entry.componentId] === \"section\" : false;\nfor (const [name, value] of Object.entries(headers)) Astro.response.headers.set(name, value);\nif (!entry || !Component) return new Response(\"Not found\", { status: 404, headers });\n---\n<html lang=\"en\" data-bcms-preview-render=\"1\">\n <head><meta charset=\"utf-8\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /></head>\n <body>{section ? <Component blockId=\"bcms-preview\" overrides={entry.props} page={{}} /> : <Component {...entry.props} />}</body>\n</html>\n`);\n writeFileSync(wrapper, `import user from \"./${configName}\";\nimport node from \"@astrojs/node\";\n\nconst routes = [\"runtime.ts\", \"session.ts\", \"props.ts\", \"render.astro\", \"health.ts\"];\n\nexport default {\n ...user,\n output: \"server\",\n base: ${JSON.stringify(PREVIEW_BASE)},\n adapter: node({ mode: \"standalone\" }),\n integrations: [\n ...(user.integrations ?? []),\n {\n name: \"bettercms-component-preview\",\n hooks: {\n \"astro:config:setup\": ({ injectRoute }) => {\n for (const file of routes) {\n injectRoute({\n pattern: \"/__bcms/\" + file.replace(/\\\\.(ts|astro)$/, \"\"),\n entrypoint: new URL(\"./.bcms-preview/\" + file, import.meta.url),\n prerender: false,\n });\n }\n },\n },\n },\n ],\n};\n`);\n rmSync(join(root, \"dist\"), { recursive: true, force: true });\n run(root, bin(root, \"astro\"), [\"build\", \"--config\", \"astro.config.bcms-preview.mjs\"]);\n } finally {\n rmSync(gen, { recursive: true, force: true });\n rmSync(wrapper, { force: true });\n }\n\n const app = join(out, \"app\");\n mkdirSync(app, { recursive: true });\n cpSync(join(root, \"dist\"), app, { recursive: true });\n cpSync(join(root, \"package.json\"), join(app, \"package.json\"));\n // Astro does not bundle its dependencies, so the server entry needs them on disk.\n cpSync(join(root, \"node_modules\"), join(app, \"node_modules\"), { recursive: true, verbatimSymlinks: true });\n writeFileSync(join(out, \"bcms-runtime.json\"), `${JSON.stringify({ kind: \"node\", dir: \"app\", entry: \"server/entry.mjs\" })}\\n`);\n}\n\nfunction buildNext(root: string, entries: ManifestEntry[], out: string) {\n const appDir = [\"app\", join(\"src\", \"app\")].map((d) => join(root, d)).find((d) => existsSync(d));\n if (!appDir) fail(\"no App Router directory (app/ or src/app/) found\");\n const configName = [\"next.config.mjs\", \"next.config.js\", \"next.config.ts\", \"next.config.cjs\"].find((f) => existsSync(join(root, f)));\n\n // `%5F%5Fbcms` is how a URL segment starting with an underscore is spelled in the App Router: a plain\n // `__bcms` folder is a PRIVATE folder and silently produces no routes at all.\n const gen = join(appDir, \"%5F%5Fbcms\");\n const userConfig = configName ? join(root, configName.replace(\"next.config\", \"next.config.bcms-user\")) : null;\n const wrapperName = configName && configName.endsWith(\".ts\") ? \"next.config.ts\" : \"next.config.mjs\";\n rmSync(gen, { recursive: true, force: true });\n mkdirSync(gen, { recursive: true });\n if (configName && userConfig) renameSync(join(root, configName), userConfig);\n try {\n writeRuntimeLibrary(gen);\n writeFileSync(join(gen, \"registry.ts\"), registrySource(gen, root, entries));\n const route = (name: string, source: string) => {\n mkdirSync(join(gen, name), { recursive: true });\n writeFileSync(join(gen, name, \"route.ts\"), `export const dynamic = \"force-dynamic\";\\n${source}`);\n };\n route(\"runtime\", `import { handleRuntime } from \"../server.mjs\";\\nimport shell from \"../shell\";\\nexport function GET() {\\n return handleRuntime(shell);\\n}\\n`);\n route(\"session\", `import { handleSession } from \"../server.mjs\";\\nimport { has } from \"../registry\";\\nexport function POST(request: Request) {\\n return handleSession(request, has);\\n}\\n`);\n route(\"props\", `import { handleProps } from \"../server.mjs\";\\nimport { has } from \"../registry\";\\nexport function POST(request: Request) {\\n return handleProps(request, has);\\n}\\n`);\n route(\"health\", `import { handleHealth } from \"../server.mjs\";\\nexport function GET() {\\n return handleHealth();\\n}\\n`);\n mkdirSync(join(gen, \"render\"), { recursive: true });\n writeFileSync(join(gen, \"render\", \"page.tsx\"), `import { notFound } from \"next/navigation\";\nimport { renderEntry } from \"../server.mjs\";\nimport { registry, kinds } from \"../registry\";\n\nexport const dynamic = \"force-dynamic\";\n\nexport default async function BetterCMSComponentPreview({ searchParams }: { searchParams: Promise<{ id?: string }> }) {\n const { id } = await searchParams;\n const entry = renderEntry(id);\n const Component = entry ? registry[entry.componentId] : undefined;\n if (!entry || !Component) notFound();\n // The marker the runtime page checks before acknowledging: a 404 or an error page never carries it.\n return (\n <>\n {kinds[entry.componentId] === \"section\"\n ? <Component blockId=\"bcms-preview\" overrides={entry.props} page={{}} />\n : <Component {...entry.props} />}\n <template data-bcms-preview-render=\"1\" />\n </>\n );\n}\n`);\n const importUser = userConfig ? `import user from \"./${relative(root, userConfig)}\";` : \"const user = {};\";\n writeFileSync(join(root, wrapperName), `${importUser}\n\n// No frame-ancestors here: next.config headers are fixed at build time, and the dashboard origin the\n// render frame must allow is runtime configuration. The platform's proxy sets it for this whole prefix.\nconst RENDER_HEADERS = [\n { key: \"referrer-policy\", value: \"no-referrer\" },\n { key: \"cache-control\", value: \"no-store\" },\n];\n\nexport default async function betterCMSComponentPreviewConfig(phase, context) {\n const resolved = typeof user === \"function\" ? await user(phase, context) : user;\n const userHeaders = resolved.headers;\n return {\n ...resolved,\n output: \"standalone\",\n basePath: ${JSON.stringify(PREVIEW_BASE)},\n async headers() {\n const own = typeof userHeaders === \"function\" ? await userHeaders() : [];\n return [...own, { source: \"/__bcms/render\", headers: RENDER_HEADERS }];\n },\n };\n}\n`);\n rmSync(join(root, \".next\"), { recursive: true, force: true });\n run(root, bin(root, \"next\"), [\"build\"]);\n } finally {\n rmSync(gen, { recursive: true, force: true });\n rmSync(join(root, wrapperName), { force: true });\n if (configName && userConfig && existsSync(userConfig)) renameSync(userConfig, join(root, configName));\n }\n\n const standalone = join(root, \".next\", \"standalone\");\n if (!existsSync(join(standalone, \"server.js\"))) fail(\"next build produced no standalone server\");\n const app = join(out, \"app\");\n mkdirSync(app, { recursive: true });\n cpSync(standalone, app, { recursive: true, verbatimSymlinks: true });\n if (existsSync(join(root, \".next\", \"static\"))) cpSync(join(root, \".next\", \"static\"), join(app, \".next\", \"static\"), { recursive: true });\n if (existsSync(join(root, \"public\"))) cpSync(join(root, \"public\"), join(app, \"public\"), { recursive: true });\n writeFileSync(join(out, \"bcms-runtime.json\"), `${JSON.stringify({ kind: \"node\", dir: \"app\", entry: \"server.js\" })}\\n`);\n}\n\nexport function buildPreviewRuntime(input: { root: string; manifestPath: string; out: string }) {\n const root = resolve(input.root);\n const out = resolve(input.out);\n const entries = validateManifest(root, readJson<Manifest>(resolve(input.manifestPath)));\n const framework = detectFramework(root);\n\n rmSync(out, { recursive: true, force: true });\n mkdirSync(out, { recursive: true });\n const restore = snapshot(root, MUTATED_BY_BUILD);\n try {\n if (framework === \"astro\") buildAstro(root, entries, out);\n else buildNext(root, entries, out);\n } finally {\n restore();\n }\n return {\n framework,\n out,\n components: entries.map((e) => e.id),\n kinds: Object.fromEntries(entries.map((e) => [e.id, e.source.kind ?? \"file\"])) as Record<string, SourceKind>,\n };\n}\n","/**\n * `bcms-preview validate` — validates one component in CI with nothing configured in the repository.\n *\n * Run by `.github/workflows/bcms-component-validation.yml`, which BetterCMS commits and dispatches. In\n * order: build a preview runtime from the app as it is, render the component with its default props in a\n * real browser at every native viewport, check brand tokens and the console, package the runtime — and only\n * then claim the request, upload the bundle the platform will serve previews from, and complete it.\n *\n * 🔴 NOTHING FAILS SILENTLY. A component whose checks fail is still COMPLETED — failed evidence is a\n * result the dashboard shows, with the failing checks. Anything that prevents a result (no source file\n * recorded, a build that breaks, a runtime that never starts, no browser) FAILS the request with a named\n * code and the reason, so the panel says what happened instead of waiting for the request to expire.\n */\nimport { spawn, spawnSync, type ChildProcess } from \"node:child_process\";\nimport { createHash, randomBytes } from \"node:crypto\";\nimport { mkdtempSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { createServer } from \"node:net\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { buildPreviewRuntime, CliFailure } from \"./build\";\n\nconst PREVIEW_ROUTE_BASE = \"/__bettercms/component-preview/__bcms\";\nconst RUNTIME_PATH = `${PREVIEW_ROUTE_BASE}/runtime`;\n\ntype Viewport = { name: string; width: number; height: number };\ntype RequestTarget = {\n componentId: string;\n componentVersion: number;\n candidateId: string;\n familyKey: string;\n familyContractHash: string;\n schemaHash: string;\n brandContractHash: string;\n dependenciesHash: string;\n commitSha: string;\n adapterHash: string;\n familyManifestHash: string;\n nativeViewports: Viewport[];\n};\ntype Manifest = {\n components: { id: string; source: { path: string; export: string }; defaultProps: Record<string, unknown> }[];\n brandTokenNames: string[];\n};\n\nclass ValidationFailure extends Error {\n constructor(readonly code: string, message: string) {\n super(message);\n this.name = \"ValidationFailure\";\n }\n}\n\nfunction required(name: string): string {\n const value = process.env[name]?.trim();\n if (!value) throw new CliFailure(`${name} is not set. This command runs inside the BetterCMS component validation workflow.`);\n return value;\n}\n\nconst sha256 = (data: string | Buffer) => createHash(\"sha256\").update(data).digest(\"hex\");\n\nfunction canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.keys(value as Record<string, unknown>).sort()\n .map((key) => `${JSON.stringify(key)}:${canonical((value as Record<string, unknown>)[key])}`).join(\",\")}}`;\n }\n return JSON.stringify(value);\n}\n\nfunction freePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const server = createServer();\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address();\n const port = typeof address === \"object\" && address ? address.port : 0;\n server.close(() => resolve(port));\n });\n });\n}\n\nasync function waitForOk(url: string, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n try {\n if ((await fetch(url)).ok) return true;\n } catch {\n // not up yet\n }\n await new Promise((r) => setTimeout(r, 500));\n }\n return false;\n}\n\n/** Install Chromium for the bundled Playwright. On a Linux CI runner, with its system dependencies. */\nfunction ensureBrowser() {\n const require = createRequire(import.meta.url);\n const cli = join(dirname(require.resolve(\"playwright/package.json\")), \"cli.js\");\n const args = [cli, \"install\", \"chromium\"];\n if (process.platform === \"linux\" && process.env.CI) args.push(\"--with-deps\");\n const result = spawnSync(process.execPath, args, { stdio: \"inherit\" });\n if (result.status !== 0) {\n throw new ValidationFailure(\"COMPONENT_VALIDATION_BROWSER_UNAVAILABLE\", \"A headless browser could not be installed on this runner.\");\n }\n}\n\nasync function renderInBrowser(url: string, viewports: Viewport[], tokenNames: string[], options: { section: boolean }) {\n ensureBrowser();\n const { chromium } = await import(\"playwright\");\n const browser = await chromium.launch();\n try {\n let consoleErrors = 0;\n let runtimeErrors = 0;\n const missingTokens = new Set<string>();\n const results: { name: string; width: number; height: number; status: \"baseline-missing\"; candidateDigest: string }[] = [];\n const problems: string[] = [];\n for (const viewport of viewports) {\n const page = await browser.newPage({ viewport: { width: viewport.width, height: viewport.height } });\n page.on(\"console\", (message) => {\n if (message.type() === \"error\") {\n consoleErrors += 1;\n problems.push(`${viewport.name} console: ${message.text().slice(0, 200)}`);\n }\n });\n page.on(\"pageerror\", (error) => {\n runtimeErrors += 1;\n problems.push(`${viewport.name} error: ${error.message.slice(0, 200)}`);\n });\n const response = await page.goto(url, { waitUntil: \"load\", timeout: 45_000 });\n await page.waitForLoadState(\"networkidle\", { timeout: 15_000 }).catch(() => {});\n const rendered = await page.locator(\"[data-bcms-preview-render]\").count();\n if (!response?.ok() || rendered === 0) {\n runtimeErrors += 1;\n problems.push(`${viewport.name}: the component page did not render (HTTP ${response?.status() ?? \"none\"})`);\n } else if (options.section && (await page.locator(\"[data-bcms-block]\").count()) === 0) {\n // The codemod stamps data-bcms-block on every section root, and the editor resolves a section's\n // fields by walking up to it: a section without one renders but cannot be edited.\n runtimeErrors += 1;\n problems.push(`${viewport.name}: the section rendered without its [data-bcms-block] root`);\n }\n /**\n * A token is MISSING only when the rendered page uses it and it resolves to nothing. `--bcms-*` are\n * guaranteed on pages BetterCMS renders itself, not in a customer's framework build, so asking\n * \"is every token defined?\" would fail every starter while saying nothing about the component. The\n * question worth a failure is \"does this component reference a brand token the app never defines?\"\n * — a component that will render unstyled on the live site. Cross-origin stylesheets cannot be read\n * and are skipped.\n */\n const missing = await page.evaluate((names: string[]) => {\n const referenced = new Set<string>();\n const scan = (text: string) => {\n for (const match of text.matchAll(/var\\(\\s*(--[A-Za-z0-9_-]+)/g)) referenced.add(match[1]!);\n };\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n for (const rule of Array.from(sheet.cssRules)) scan(rule.cssText);\n } catch {\n // cross-origin sheet\n }\n }\n document.querySelectorAll(\"[style]\").forEach((element) => scan(element.getAttribute(\"style\") ?? \"\"));\n const style = getComputedStyle(document.documentElement);\n return names.filter((name) => referenced.has(name) && !style.getPropertyValue(name).trim());\n }, tokenNames);\n for (const token of missing) missingTokens.add(token);\n const png = await page.screenshot({ fullPage: true });\n results.push({\n name: viewport.name,\n width: viewport.width,\n height: viewport.height,\n // No baseline exists for a first validation. The database requires this exact shape for it:\n // no baseline or diff digest, and the check marked as needing review.\n status: \"baseline-missing\",\n candidateDigest: `sha256:${sha256(png)}`,\n });\n await page.close();\n }\n return { results, consoleErrors, runtimeErrors, missingTokens: [...missingTokens].sort(), problems };\n } finally {\n await browser.close();\n }\n}\n\nexport async function validateComponent() {\n const apiUrl = required(\"BCMS_API_URL\");\n const apiKey = required(\"BCMS_API_KEY\");\n const projectId = required(\"BCMS_PROJECT_ID\");\n const requestId = required(\"BCMS_REQUEST_ID\");\n const componentId = required(\"BCMS_COMPONENT_ID\");\n const commitSha = required(\"BCMS_COMMIT_SHA\");\n const familyKey = required(\"BCMS_FAMILY_KEY\");\n const previewOrigin = required(\"BCMS_PREVIEW_ORIGIN\");\n const nativeViewports = JSON.parse(required(\"BCMS_NATIVE_VIEWPORTS\")) as Viewport[];\n\n const api = async <T>(path: string, init: { method?: string; body?: unknown; claim?: string } = {}): Promise<T> => {\n const response = await fetch(new URL(path, apiUrl), {\n method: init.method ?? \"GET\",\n headers: {\n authorization: `Bearer ${apiKey}`,\n \"content-type\": \"application/json\",\n ...(init.claim ? { \"x-bcms-component-claim\": init.claim } : {}),\n },\n ...(init.body === undefined ? {} : { body: JSON.stringify(init.body) }),\n });\n const text = await response.text();\n let parsed: { data?: unknown; error?: unknown; message?: unknown } | null = null;\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = null;\n }\n if (!response.ok) {\n const code = typeof parsed?.error === \"string\" && /^[A-Z][A-Z0-9_]*$/.test(parsed.error) ? parsed.error : `API_HTTP_${response.status}`;\n const detail = typeof parsed?.message === \"string\" ? parsed.message : typeof parsed?.error === \"string\" ? parsed.error : text.slice(0, 300);\n throw new ValidationFailure(code, `${init.method ?? \"GET\"} ${path} answered ${response.status}: ${detail}`);\n }\n return ((parsed && \"data\" in parsed ? parsed.data : parsed) ?? {}) as T;\n };\n\n const claimPath = `/api/v1/projects/${projectId}/component-implementation/implementation-requests/${requestId}`;\n let claim: { request: RequestTarget; claimCapability: string } | null = null;\n /**\n * 🔴 CLAIMED LAST, NOT FIRST. A claim credential lives minutes, and `complete` refuses an expired one.\n * Claiming before a framework build, a browser install and a render meant every real run finished with\n * a credential that had already lapsed — and the request sat \"running\" until it expired. Everything slow\n * happens first; the claim is followed only by an upload and the completion.\n */\n const claimRequest = async () => {\n claim = await api<{ request: RequestTarget; claimCapability: string }>(`${claimPath}/claim`, {\n method: \"POST\",\n body: {\n componentId,\n commitSha,\n adapter: { protocol: \"bcms-component-runtime-v1\", kind: \"project-route\", path: RUNTIME_PATH, familyKey, previewOrigin, nativeViewports },\n providerRunId: required(\"BCMS_RUN_ID\"),\n providerRunAttempt: Number(process.env.BCMS_RUN_ATTEMPT) || 1,\n providerRunUrl: required(\"BCMS_RUN_URL\"),\n workflowRef: required(\"BCMS_WORKFLOW_REF\"),\n // The server caps this at the request's own expiry and at ten minutes.\n credentialExpiresAt: new Date(Date.now() + 10 * 60_000 - 5_000).toISOString(),\n },\n });\n return claim;\n };\n\n const work = mkdtempSync(join(tmpdir(), \"bcms-validate-\"));\n let runtime: ChildProcess | null = null;\n try {\n const manifest = await api<Manifest>(`/api/v1/projects/${projectId}/component-preview/manifest`);\n const entry = manifest.components.find((c) => c.id === componentId);\n if (!entry) {\n throw new ValidationFailure(\n \"COMPONENT_SOURCE_NOT_RECORDED\",\n \"No file is recorded as this component's source. The agent that writes a component records it with set_component_source.\",\n );\n }\n\n const manifestPath = join(work, \"manifest.json\");\n writeFileSync(manifestPath, JSON.stringify({ components: manifest.components.map(({ id, source }) => ({ id, source })) }));\n const out = join(work, \"runtime\");\n let built: ReturnType<typeof buildPreviewRuntime>;\n try {\n built = buildPreviewRuntime({ root: process.cwd(), manifestPath, out });\n } catch (error) {\n throw new ValidationFailure(\"COMPONENT_PREVIEW_BUILD_FAILED\", `The preview build failed: ${(error as Error).message}`);\n }\n\n const runtimeManifest = JSON.parse(readFileSync(join(out, \"bcms-runtime.json\"), \"utf8\")) as { dir: string; entry: string };\n const port = await freePort();\n const validatorKey = randomBytes(32).toString(\"base64url\");\n const local = `http://127.0.0.1:${port}`;\n // 🔴 The customer's code runs in this process tree. It gets no BetterCMS API key.\n const { BCMS_API_KEY: _withheld, ...inherited } = process.env;\n runtime = spawn(process.execPath, [join(out, runtimeManifest.dir, runtimeManifest.entry)], {\n cwd: join(out, runtimeManifest.dir),\n env: {\n ...inherited,\n NODE_ENV: \"production\",\n PORT: String(port),\n HOST: \"127.0.0.1\",\n HOSTNAME: \"127.0.0.1\",\n BCMS_API_URL: apiUrl,\n BCMS_DASHBOARD_ORIGIN: local,\n BCMS_PREVIEW_ORIGIN: local,\n BCMS_PREVIEW_VALIDATOR_KEY: validatorKey,\n },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n /**\n * The app's own server errors (`TypeError: Cannot read properties of undefined`) are the real reason a\n * render fails — the browser only sees \"500\". Still echoed to the CI log; the first few are kept for the\n * evidence so the panel can say what broke.\n */\n const serverErrors: string[] = [];\n const collect = (stream: NodeJS.WriteStream) => (chunk: Buffer) => {\n stream.write(chunk);\n for (const line of chunk.toString(\"utf8\").split(\"\\n\")) {\n const clean = line.replace(/\\x1b\\[[0-9;]*m/g, \"\").trim();\n if (serverErrors.length < 5 && /\\b(error|exception)\\b/i.test(clean)) serverErrors.push(clean.slice(0, 200));\n }\n };\n runtime.stdout?.on(\"data\", collect(process.stdout));\n runtime.stderr?.on(\"data\", collect(process.stderr));\n if (!(await waitForOk(`${local}${PREVIEW_ROUTE_BASE}/health`, 60_000))) {\n throw new ValidationFailure(\"COMPONENT_PREVIEW_RUNTIME_DID_NOT_START\", \"The preview runtime did not start within 60 seconds.\");\n }\n\n const stored = await fetch(`${local}${PREVIEW_ROUTE_BASE}/props`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"x-bcms-validator-key\": validatorKey },\n body: JSON.stringify({ componentId, props: entry.defaultProps }),\n });\n if (!stored.ok) {\n throw new ValidationFailure(\"COMPONENT_PREVIEW_RUNTIME_REFUSED\", `The preview runtime refused the component (HTTP ${stored.status}).`);\n }\n const { id } = (await stored.json()) as { id: string };\n\n // The viewports the dispatch declared are exactly the ones the claim records as the adapter's.\n const render = await renderInBrowser(\n `${local}${PREVIEW_ROUTE_BASE}/render?id=${encodeURIComponent(id)}`,\n nativeViewports,\n manifest.brandTokenNames,\n { section: built.kinds[componentId] === \"section\" },\n );\n\n const tarball = join(work, \"bundle.tgz\");\n if (spawnSync(\"tar\", [\"-czf\", tarball, \"-C\", out, \".\"], { stdio: \"inherit\" }).status !== 0) {\n throw new ValidationFailure(\"COMPONENT_PREVIEW_BUNDLE_PACK_FAILED\", \"The preview runtime could not be packaged.\");\n }\n const bytes = readFileSync(tarball);\n runtime.kill();\n runtime = null;\n\n const target = (await claimRequest()).request;\n const checks = {\n brandKit: {\n status: render.missingTokens.length === 0 ? \"passed\" : \"failed\",\n contractHash: target.brandContractHash,\n missingTokens: render.missingTokens,\n },\n runtime: {\n status: render.runtimeErrors === 0 && render.consoleErrors === 0 ? \"passed\" : \"failed\",\n runtimeErrors: render.runtimeErrors,\n consoleErrors: render.consoleErrors,\n // Only on a failure, and bounded: the server's own error lines first (the real cause), then what the\n // browser saw. The database stores them and the panel shows the first one.\n ...(render.runtimeErrors + render.consoleErrors > 0\n ? { problems: [...serverErrors.map((line) => `server: ${line}`), ...render.problems].slice(0, 10).map((line) => line.slice(0, 200)) }\n : {}),\n },\n visual: { status: \"baseline-missing\", reviewRequired: true, viewports: render.results },\n };\n\n // Uploaded before completing: validated evidence pointing at a runtime nobody can start renders nothing.\n const upload = await api<{ uploadUrl: string; uploadKey: string }>(`/api/v1/projects/${projectId}/artifacts/upload-url`, { method: \"POST\" });\n const put = await fetch(upload.uploadUrl, { method: \"PUT\", body: bytes, headers: { \"content-type\": \"application/gzip\" } });\n if (!put.ok) {\n throw new ValidationFailure(\"COMPONENT_PREVIEW_BUNDLE_UPLOAD_FAILED\", `Storage refused the preview bundle (HTTP ${put.status}).`);\n }\n await api(`/api/v1/projects/${projectId}/component-preview/bundles`, {\n method: \"POST\",\n body: { commitSha: target.commitSha, uploadKey: upload.uploadKey, checksum: `sha256:${sha256(bytes)}`, sizeBytes: bytes.byteLength },\n });\n\n const evidenceDigest = `sha256:${sha256(canonical({ requestId, commitSha: target.commitSha, checks }))}`;\n await api(`${claimPath}/complete`, {\n method: \"POST\",\n claim: claim!.claimCapability,\n body: {\n componentId: target.componentId,\n candidateId: target.candidateId,\n componentVersion: target.componentVersion,\n familyKey: target.familyKey,\n familyContractHash: target.familyContractHash,\n schemaHash: target.schemaHash,\n brandContractHash: target.brandContractHash,\n dependenciesHash: target.dependenciesHash,\n commitSha: target.commitSha,\n adapterHash: target.adapterHash,\n familyManifestHash: target.familyManifestHash,\n nativeViewports: target.nativeViewports,\n checks,\n evidenceDigest,\n },\n });\n\n const passed = checks.brandKit.status === \"passed\" && checks.runtime.status === \"passed\";\n console.log(`bcms-preview: validation ${passed ? \"PASSED\" : \"FAILED\"} for ${componentId}`);\n if (!passed) {\n if (render.missingTokens.length) console.log(` brand tokens used but not defined: ${render.missingTokens.join(\", \")}`);\n for (const problem of render.problems) console.log(` ${problem}`);\n }\n } catch (error) {\n const code = error instanceof ValidationFailure ? error.code : \"COMPONENT_VALIDATION_FAILED\";\n const message = (error as Error).message.slice(0, 2000);\n // A failure before the claim is still reported: claim, then fail at once, so the dashboard names the\n // reason instead of showing \"running\" until the request expires.\n const reported = claim ?? await claimRequest().catch((claimError) => {\n console.error(`bcms-preview: could not claim the request to report the failure: ${(claimError as Error).message}`);\n return null;\n });\n if (reported) {\n await api(`${claimPath}/fail`, {\n method: \"POST\",\n claim: reported.claimCapability,\n body: { componentId, errorCode: code, errorMessage: message },\n }).catch((reportError) => console.error(`bcms-preview: could not report the failure: ${(reportError as Error).message}`));\n }\n throw new CliFailure(`${code}: ${message}`);\n } finally {\n runtime?.kill();\n rmSync(work, { recursive: true, force: true });\n }\n}\n","/**\n * bcms-preview\n *\n * bcms-preview build --manifest <file> --out <dir> [--cwd <dir>]\n * bcms-preview validate (in CI; configured entirely by BCMS_* environment variables)\n */\nimport { buildPreviewRuntime, CliFailure } from \"./build\";\nimport { validateComponent } from \"./validate\";\n\nfunction arg(name: string): string | undefined {\n const index = process.argv.indexOf(`--${name}`);\n return index === -1 ? undefined : process.argv[index + 1];\n}\n\nasync function main() {\n const command = process.argv[2];\n if (command === \"build\") {\n const manifestPath = arg(\"manifest\");\n const out = arg(\"out\");\n if (!manifestPath || !out) throw new CliFailure(\"usage: bcms-preview build --manifest <file> --out <dir> [--cwd <dir>]\");\n console.log(JSON.stringify(buildPreviewRuntime({ root: arg(\"cwd\") ?? process.cwd(), manifestPath, out })));\n return;\n }\n if (command === \"validate\") {\n await validateComponent();\n return;\n }\n throw new CliFailure(\"usage: bcms-preview <build|validate>\");\n}\n\n/**\n * One handler for every failure, and no `process.exit`: writes to a piped stderr are asynchronous, so\n * exiting right after the write dropped the message and CI showed a failed step with no reason.\n */\nmain().catch((error) => {\n console.error(`bcms-preview: ${error instanceof CliFailure ? error.message : (error as Error).stack ?? String(error)}`);\n process.exitCode = 1;\n});\n"],"mappings":"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","names":["require","spawnSync","readFileSync","rmSync","writeFileSync","createRequire","dirname","join","resolve","require","createRequire","join","dirname","spawnSync","writeFileSync","id","readFileSync","rmSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/build.ts","../src/validate.ts","../src/cli.ts"],"sourcesContent":["/**\n * Builds a component preview runtime from an unmodified Next.js or Astro app. Used by `bcms-preview build`\n * and by the validator.\n *\n * The manifest names which file implements which component:\n * { \"components\": [{ \"id\": \"cmp_1\", \"source\": { \"path\": \"src/components/Hero.astro\" } }] }\n *\n * 🔴 NOTHING IN THE CUSTOMER'S REPOSITORY IS CHANGED. Routes and a registry are generated into the\n * checkout, the framework builds, the result is packaged as a runtime release, and every generated file\n * is removed again — including when the build fails. In CI the checkout is thrown away anyway; on a\n * developer machine this is the difference between a tool and a mess.\n */\nimport { spawnSync } from \"node:child_process\";\nimport { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, extname, join, relative, resolve, sep } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * `file`: a component module whose props ARE the component's fields — rendered with the props spread.\n * `section`: a section `npx @bettercms-ai/convert --componentize` extracted from a page. Its props are always\n * `{ blockId, bind, overrides, page }`, so it is rendered with the live props as `overrides`.\n * `page`: no file at all — the component is a placement on a page, or a section of the layout. The runtime\n * renders that page and keeps only the component's section (see scope.ts). Derived by the platform, never recorded.\n */\ntype SourceKind = \"file\" | \"section\" | \"page\";\ntype FileSource = { path: string; export?: string; kind?: \"file\" | \"section\" };\ntype PageSource = {\n kind: \"page\";\n route: string;\n blockId?: string;\n groupKey?: string | null;\n source?: Record<string, string> | null;\n layoutSectionId?: string;\n landmark?: \"header\" | \"footer\" | \"nav\" | null;\n bindings?: Record<string, string>;\n};\ntype ManifestEntry = { id: string; source: FileSource | PageSource };\ntype Manifest = { components: ManifestEntry[] };\ntype Framework = \"astro\" | \"next\";\n\nconst here = dirname(fileURLToPath(import.meta.url));\nconst PREVIEW_BASE = \"/__bettercms/component-preview\";\nconst COMPONENT_EXTENSIONS = new Set([\".astro\", \".tsx\", \".jsx\", \".ts\", \".js\", \".mjs\"]);\nconst IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n/** Every section file the codemod writes carries this (`// @bettercms-ai/convert section v2`, earlier `v1`). */\nconst SECTION_MARKER_PREFIX = \"// @bettercms-ai/convert section v\";\n\n/**\n * 🔴 THROWN, NOT `process.exit`. Writes to a piped stderr are asynchronous in Node, so exiting on the\n * next line dropped the message: in CI the step failed with no reason in the log at all. The single\n * handler at the bottom prints and sets the exit code, and the process ends once the write has flushed.\n */\nexport class CliFailure extends Error {}\n\nexport function fail(message: string): never {\n throw new CliFailure(message);\n}\n\n/**\n * Files a framework build rewrites in place. `next build` edits tsconfig.json and next-env.d.ts; an\n * `npm install --no-save` still rewrites the lockfile. Restored afterwards, so a preview build leaves the\n * app exactly as it found it.\n */\nfunction snapshot(root: string, names: string[]): () => void {\n const saved = names.map((name) => {\n const file = join(root, name);\n return { file, content: existsSync(file) ? readFileSync(file) : null };\n });\n return () => {\n for (const { file, content } of saved) {\n if (content === null) rmSync(file, { force: true });\n else writeFileSync(file, content);\n }\n };\n}\n\nconst MUTATED_BY_BUILD = [\"tsconfig.json\", \"next-env.d.ts\", \"package-lock.json\", \"pnpm-lock.yaml\", \"yarn.lock\", \"bun.lock\"];\n\nfunction readJson<T>(file: string): T {\n try {\n return JSON.parse(readFileSync(file, \"utf8\")) as T;\n } catch (error) {\n fail(`could not read ${file}: ${(error as Error).message}`);\n }\n}\n\nconst LANDMARKS = new Set([\"header\", \"footer\", \"nav\"]);\nconst isStringRecord = (value: unknown) =>\n !!value && typeof value === \"object\" && !Array.isArray(value) && Object.values(value).every((v) => typeof v === \"string\");\n\n/** A page source travels into a generated module as data: a same-origin path and plain strings, nothing else. */\nfunction pageSource(id: string, source: PageSource): PageSource {\n const { route, blockId, groupKey, layoutSectionId, landmark } = source;\n if (typeof route !== \"string\" || !route.startsWith(\"/\") || route.startsWith(\"//\") || /[\\s?#\\\\]/.test(route) || route.split(\"/\").includes(\"..\")) {\n fail(`component ${id} has an unsafe page route: ${String(route)}`);\n }\n if (typeof blockId === \"string\" && blockId) {\n if (groupKey != null && typeof groupKey !== \"string\") fail(`component ${id}: groupKey must be a string`);\n if (source.source != null && !isStringRecord(source.source)) fail(`component ${id}: source must map prop keys to page paths`);\n return { kind: \"page\", route, blockId, groupKey: groupKey ?? null, source: source.source ?? null };\n }\n if (typeof layoutSectionId === \"string\" && layoutSectionId) {\n if (landmark != null && !LANDMARKS.has(landmark)) fail(`component ${id}: unknown landmark ${String(landmark)}`);\n if (source.bindings != null && !isStringRecord(source.bindings)) fail(`component ${id}: bindings must map input ids to layout field ids`);\n return { kind: \"page\", route, layoutSectionId, landmark: landmark ?? null, bindings: source.bindings ?? {} };\n }\n fail(`component ${id}: a page source names neither a placement (blockId) nor a layout section (layoutSectionId)`);\n}\n\n/** Manifest paths travel from an API into generated imports: relative, inside the app, and real. */\nexport function validateManifest(root: string, manifest: Manifest): ManifestEntry[] {\n if (!manifest || !Array.isArray(manifest.components) || manifest.components.length === 0) {\n fail(\"the manifest lists no components\");\n }\n const seen = new Set<string>();\n return manifest.components.map((entry) => {\n if (!entry || typeof entry.id !== \"string\" || !entry.id.trim()) fail(\"a manifest entry has no id\");\n if (seen.has(entry.id)) fail(`component ${entry.id} is listed twice`);\n seen.add(entry.id);\n if (entry.source?.kind === \"page\") return { id: entry.id, source: pageSource(entry.id, entry.source) };\n const path = entry.source?.path;\n if (typeof path !== \"string\" || path.startsWith(\"/\") || path.includes(\"\\\\\") || path.split(\"/\").includes(\"..\")) {\n fail(`component ${entry.id} has an unsafe source path: ${String(path)}`);\n }\n if (!COMPONENT_EXTENSIONS.has(extname(path))) fail(`component ${entry.id}: unsupported file type ${extname(path)}`);\n if (!existsSync(join(root, path))) fail(`component ${entry.id}: ${path} does not exist`);\n const named = entry.source.export;\n if (named !== undefined && named !== \"default\" && !IDENTIFIER.test(named)) {\n fail(`component ${entry.id}: export \"${named}\" is not a valid identifier`);\n }\n // A section is recognised by its own marker as well as by the manifest: a source recorded before kinds\n // existed defaults to `file`, and rendering a section as a file hands it none of its copy.\n const head = readFileSync(join(root, path), \"utf8\").slice(0, 512);\n const kind: SourceKind = entry.source.kind === \"section\" || head.includes(SECTION_MARKER_PREFIX) ? \"section\" : \"file\";\n return { id: entry.id, source: { path, export: named ?? \"default\", kind } };\n });\n}\n\nfunction detectFramework(root: string): Framework {\n const pkg = readJson<{ dependencies?: Record<string, string>; devDependencies?: Record<string, string> }>(join(root, \"package.json\"));\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (deps.astro) return \"astro\";\n if (deps.next) return \"next\";\n fail(\"this app depends on neither astro nor next\");\n}\n\nfunction run(root: string, command: string, args: string[]) {\n const result = spawnSync(command, args, { cwd: root, stdio: \"inherit\", env: process.env });\n if (result.status !== 0) throw new Error(`${command} ${args.join(\" \")} exited with ${result.status}`);\n}\n\nfunction bin(root: string, name: string): string {\n const local = join(root, \"node_modules\", \".bin\", name);\n if (!existsSync(local)) fail(`${name} is not installed in ${root}. Install the app's dependencies first.`);\n return local;\n}\n\n/** A registry module: one import per file component, one data entry per page component, keyed by component id. */\nexport function registrySource(fromDir: string, root: string, entries: ManifestEntry[]): string {\n const imports: string[] = [];\n const keys: string[] = [];\n const kinds: string[] = [];\n const pages: string[] = [];\n entries.forEach((entry, index) => {\n const source = entry.source;\n kinds.push(` ${JSON.stringify(entry.id)}: ${JSON.stringify(source.kind ?? \"file\")},`);\n if (source.kind === \"page\") {\n pages.push(` ${JSON.stringify(entry.id)}: ${JSON.stringify(source)},`);\n return;\n }\n let specifier = relative(fromDir, join(root, source.path)).split(sep).join(\"/\");\n if (!specifier.startsWith(\".\")) specifier = `./${specifier}`;\n // TypeScript sources are imported without their extension, the way the app itself imports them.\n if ([\".tsx\", \".ts\", \".jsx\", \".js\"].includes(extname(specifier))) specifier = specifier.slice(0, -extname(specifier).length);\n const local = `Component${index}`;\n imports.push(source.export === undefined || source.export === \"default\"\n ? `import ${local} from ${JSON.stringify(specifier)};`\n : `import { ${source.export} as ${local} } from ${JSON.stringify(specifier)};`);\n keys.push(` ${JSON.stringify(entry.id)}: ${local},`);\n });\n return `${imports.join(\"\\n\")}\\n\\nexport const registry: Record<string, any> = {\\n${keys.join(\"\\n\")}\\n};\\n\\nexport const kinds: Record<string, \"file\" | \"section\" | \"page\"> = {\\n${kinds.join(\"\\n\")}\\n};\\n\\nexport const pages: Record<string, any> = {\\n${pages.join(\"\\n\")}\\n};\\n\\nconst own = (map: object, componentId: string) => Object.prototype.hasOwnProperty.call(map, componentId);\\nexport const has = (componentId: string): boolean => own(registry, componentId) || own(pages, componentId);\\n`;\n}\n\nfunction writeRuntimeLibrary(dir: string) {\n writeFileSync(join(dir, \"server.mjs\"), readFileSync(join(here, \"server.js\"), \"utf8\"));\n const types = join(here, \"server.d.ts\");\n if (existsSync(types)) writeFileSync(join(dir, \"server.d.mts\"), readFileSync(types, \"utf8\"));\n writeFileSync(join(dir, \"shell.ts\"), `export default ${JSON.stringify(readFileSync(join(here, \"shell.global.js\"), \"utf8\"))};\\n`);\n writeFileSync(join(dir, \"scope.ts\"), `export default ${JSON.stringify(readFileSync(join(here, \"scope-client.global.js\"), \"utf8\"))};\\n`);\n}\n\n/** CSS the app's layouts import. The render page has no layout, so it imports them itself. */\nfunction astroGlobalStyles(root: string): string[] {\n const found = new Set<string>();\n const scan = (dir: string) => {\n if (!existsSync(dir)) return;\n for (const name of readdirSync(dir)) {\n const full = join(dir, name);\n if (statSync(full).isDirectory()) scan(full);\n else if (name.endsWith(\".astro\")) {\n for (const match of readFileSync(full, \"utf8\").matchAll(/^\\s*import\\s+[\"']([^\"']+\\.css)[\"'];?/gm)) {\n const target = resolve(dirname(full), match[1]!);\n if (target.startsWith(root) && existsSync(target)) found.add(target);\n }\n }\n }\n };\n scan(join(root, \"src\", \"layouts\"));\n return [...found];\n}\n\nconst ASTRO_NODE_ADAPTER: Record<string, string> = { \"5\": \"^9\", \"6\": \"^10\", \"7\": \"^11\" };\n\nfunction ensureAstroNodeAdapter(root: string) {\n const require = createRequire(join(root, \"package.json\"));\n try {\n require.resolve(\"@astrojs/node\");\n return;\n } catch {\n // Not installed: add it without touching package.json or the lockfile.\n }\n const astroVersion = readJson<{ version: string }>(require.resolve(\"astro/package.json\")).version;\n const [major, minor] = astroVersion.split(\".\").map(Number);\n // @astrojs/node 11.1.3+ calls `app.getLogger()`, which Astro only has from 7.3 (its peer range still says\n // ^7.2.1), so on 7.0–7.2 the server crashed at startup: \"app.getLogger is not a function\".\n const range = major === 7 && minor! < 3 ? \">=11.0.0 <11.1.3\" : ASTRO_NODE_ADAPTER[String(major)];\n if (!range) fail(`Astro ${astroVersion} is not supported for component previews yet`);\n run(root, \"npm\", [\"install\", \"--no-save\", \"--no-audit\", \"--no-fund\", `@astrojs/node@${range}`]);\n}\n\nfunction buildAstro(root: string, entries: ManifestEntry[], out: string) {\n const configName = [\"astro.config.mjs\", \"astro.config.js\", \"astro.config.ts\", \"astro.config.mts\"].find((f) => existsSync(join(root, f)));\n if (!configName) fail(\"no astro.config file found\");\n ensureAstroNodeAdapter(root);\n\n const gen = join(root, \".bcms-preview\");\n const wrapper = join(root, \"astro.config.bcms-preview.mjs\");\n rmSync(gen, { recursive: true, force: true });\n mkdirSync(gen, { recursive: true });\n try {\n writeRuntimeLibrary(gen);\n writeFileSync(join(gen, \"registry.ts\"), registrySource(gen, root, entries));\n const handler = (method: string, body: string) =>\n `export const prerender = false;\\nexport const ${method} = ${body};\\n`;\n writeFileSync(join(gen, \"runtime.ts\"), `import { handleRuntime } from \"./server.mjs\";\\nimport shell from \"./shell\";\\n${handler(\"GET\", \"() => handleRuntime(shell)\")}`);\n writeFileSync(join(gen, \"session.ts\"), `import { handleSession } from \"./server.mjs\";\\nimport { has } from \"./registry\";\\n${handler(\"POST\", \"({ request }: { request: Request }) => handleSession(request, has)\")}`);\n writeFileSync(join(gen, \"props.ts\"), `import { handleProps } from \"./server.mjs\";\\nimport { has } from \"./registry\";\\n${handler(\"POST\", \"({ request }: { request: Request }) => handleProps(request, has)\")}`);\n writeFileSync(join(gen, \"health.ts\"), `import { handleHealth } from \"./server.mjs\";\\n${handler(\"GET\", \"() => handleHealth()\")}`);\n const styles = astroGlobalStyles(root)\n .map((file) => `import ${JSON.stringify(relative(gen, file).split(sep).join(\"/\"))};`)\n .join(\"\\n\");\n writeFileSync(join(gen, \"render.astro\"), `---\n${styles}\nimport { renderEntry, renderHeaders, renderPageSection } from \"./server.mjs\";\nimport { registry, kinds, pages } from \"./registry\";\nimport scope from \"./scope\";\nexport const prerender = false;\nconst headers = renderHeaders();\nconst entry = renderEntry(Astro.url.searchParams.get(\"id\"));\nif (entry && pages[entry.componentId]) return await renderPageSection(Astro.request, entry, pages[entry.componentId], scope);\nconst Component = entry ? registry[entry.componentId] : undefined;\nconst section = entry ? kinds[entry.componentId] === \"section\" : false;\nfor (const [name, value] of Object.entries(headers)) Astro.response.headers.set(name, value);\nif (!entry || !Component) return new Response(\"Not found\", { status: 404, headers });\n---\n<html lang=\"en\" data-bcms-preview-render=\"1\">\n <head><meta charset=\"utf-8\" /><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" /></head>\n <body>{section ? <Component blockId=\"bcms-preview\" overrides={entry.props} page={{}} /> : <Component {...entry.props} />}</body>\n</html>\n`);\n writeFileSync(wrapper, `import user from \"./${configName}\";\nimport node from \"@astrojs/node\";\n\nconst routes = [\"runtime.ts\", \"session.ts\", \"props.ts\", \"render.astro\", \"health.ts\"];\n\nexport default {\n ...user,\n output: \"server\",\n base: ${JSON.stringify(PREVIEW_BASE)},\n adapter: node({ mode: \"standalone\" }),\n integrations: [\n ...(user.integrations ?? []),\n {\n name: \"bettercms-component-preview\",\n hooks: {\n \"astro:config:setup\": ({ injectRoute }) => {\n for (const file of routes) {\n injectRoute({\n pattern: \"/__bcms/\" + file.replace(/\\\\.(ts|astro)$/, \"\"),\n entrypoint: new URL(\"./.bcms-preview/\" + file, import.meta.url),\n prerender: false,\n });\n }\n },\n },\n },\n ],\n};\n`);\n rmSync(join(root, \"dist\"), { recursive: true, force: true });\n run(root, bin(root, \"astro\"), [\"build\", \"--config\", \"astro.config.bcms-preview.mjs\"]);\n } finally {\n rmSync(gen, { recursive: true, force: true });\n rmSync(wrapper, { force: true });\n }\n\n const app = join(out, \"app\");\n mkdirSync(app, { recursive: true });\n cpSync(join(root, \"dist\"), app, { recursive: true });\n cpSync(join(root, \"package.json\"), join(app, \"package.json\"));\n // Astro does not bundle its dependencies, so the server entry needs them on disk.\n cpSync(join(root, \"node_modules\"), join(app, \"node_modules\"), { recursive: true, verbatimSymlinks: true });\n writeFileSync(join(out, \"bcms-runtime.json\"), `${JSON.stringify({ kind: \"node\", dir: \"app\", entry: \"server/entry.mjs\" })}\\n`);\n}\n\nfunction buildNext(root: string, entries: ManifestEntry[], out: string) {\n const appDir = [\"app\", join(\"src\", \"app\")].map((d) => join(root, d)).find((d) => existsSync(d));\n if (!appDir) fail(\"no App Router directory (app/ or src/app/) found\");\n const configName = [\"next.config.mjs\", \"next.config.js\", \"next.config.ts\", \"next.config.cjs\"].find((f) => existsSync(join(root, f)));\n\n // `%5F%5Fbcms` is how a URL segment starting with an underscore is spelled in the App Router: a plain\n // `__bcms` folder is a PRIVATE folder and silently produces no routes at all.\n const gen = join(appDir, \"%5F%5Fbcms\");\n const userConfig = configName ? join(root, configName.replace(\"next.config\", \"next.config.bcms-user\")) : null;\n const wrapperName = configName && configName.endsWith(\".ts\") ? \"next.config.ts\" : \"next.config.mjs\";\n rmSync(gen, { recursive: true, force: true });\n mkdirSync(gen, { recursive: true });\n if (configName && userConfig) renameSync(join(root, configName), userConfig);\n try {\n writeRuntimeLibrary(gen);\n writeFileSync(join(gen, \"registry.ts\"), registrySource(gen, root, entries));\n const route = (name: string, source: string) => {\n mkdirSync(join(gen, name), { recursive: true });\n writeFileSync(join(gen, name, \"route.ts\"), `export const dynamic = \"force-dynamic\";\\n${source}`);\n };\n route(\"runtime\", `import { handleRuntime } from \"../server.mjs\";\\nimport shell from \"../shell\";\\nexport function GET() {\\n return handleRuntime(shell);\\n}\\n`);\n route(\"session\", `import { handleSession } from \"../server.mjs\";\\nimport { has } from \"../registry\";\\nexport function POST(request: Request) {\\n return handleSession(request, has);\\n}\\n`);\n route(\"props\", `import { handleProps } from \"../server.mjs\";\\nimport { has } from \"../registry\";\\nexport function POST(request: Request) {\\n return handleProps(request, has);\\n}\\n`);\n route(\"health\", `import { handleHealth } from \"../server.mjs\";\\nexport function GET() {\\n return handleHealth();\\n}\\n`);\n route(\"render-page\", `import { renderEntry, renderPageSection } from \"../server.mjs\";\\nimport { pages } from \"../registry\";\\nimport scope from \"../scope\";\\nexport function GET(request: Request) {\\n const entry = renderEntry(new URL(request.url).searchParams.get(\"id\"));\\n const page = entry ? pages[entry.componentId] : undefined;\\n if (!entry || !page) return new Response(\"Not found\", { status: 404 });\\n return renderPageSection(request, entry, page, scope);\\n}\\n`);\n mkdirSync(join(gen, \"render\"), { recursive: true });\n writeFileSync(join(gen, \"render\", \"page.tsx\"), `import { notFound, redirect } from \"next/navigation\";\nimport { renderEntry } from \"../server.mjs\";\nimport { registry, kinds, pages } from \"../registry\";\n\nexport const dynamic = \"force-dynamic\";\n\nexport default async function BetterCMSComponentPreview({ searchParams }: { searchParams: Promise<{ id?: string }> }) {\n const { id } = await searchParams;\n const entry = renderEntry(id);\n // A page-kind component answers with a whole document, which a page inside the root layout cannot be.\n // Relative on purpose: Next prefixes basePath onto a \"/\"-rooted redirect, and the browser resolves this one.\n if (entry && pages[entry.componentId]) redirect(\\`render-page?id=\\${encodeURIComponent(id!)}\\`);\n const Component = entry ? registry[entry.componentId] : undefined;\n if (!entry || !Component) notFound();\n // The marker the runtime page checks before acknowledging: a 404 or an error page never carries it.\n return (\n <>\n {kinds[entry.componentId] === \"section\"\n ? <Component blockId=\"bcms-preview\" overrides={entry.props} page={{}} />\n : <Component {...entry.props} />}\n <template data-bcms-preview-render=\"1\" />\n </>\n );\n}\n`);\n const importUser = userConfig ? `import user from \"./${relative(root, userConfig)}\";` : \"const user = {};\";\n writeFileSync(join(root, wrapperName), `${importUser}\n\n// No frame-ancestors here: next.config headers are fixed at build time, and the dashboard origin the\n// render frame must allow is runtime configuration. The platform's proxy sets it for this whole prefix.\nconst RENDER_HEADERS = [\n { key: \"referrer-policy\", value: \"no-referrer\" },\n { key: \"cache-control\", value: \"no-store\" },\n];\n\nexport default async function betterCMSComponentPreviewConfig(phase, context) {\n const resolved = typeof user === \"function\" ? await user(phase, context) : user;\n const userHeaders = resolved.headers;\n return {\n ...resolved,\n output: \"standalone\",\n basePath: ${JSON.stringify(PREVIEW_BASE)},\n async headers() {\n const own = typeof userHeaders === \"function\" ? await userHeaders() : [];\n return [...own, { source: \"/__bcms/render\", headers: RENDER_HEADERS }];\n },\n };\n}\n`);\n rmSync(join(root, \".next\"), { recursive: true, force: true });\n run(root, bin(root, \"next\"), [\"build\"]);\n } finally {\n rmSync(gen, { recursive: true, force: true });\n rmSync(join(root, wrapperName), { force: true });\n if (configName && userConfig && existsSync(userConfig)) renameSync(userConfig, join(root, configName));\n }\n\n const standalone = join(root, \".next\", \"standalone\");\n if (!existsSync(join(standalone, \"server.js\"))) fail(\"next build produced no standalone server\");\n const app = join(out, \"app\");\n mkdirSync(app, { recursive: true });\n cpSync(standalone, app, { recursive: true, verbatimSymlinks: true });\n if (existsSync(join(root, \".next\", \"static\"))) cpSync(join(root, \".next\", \"static\"), join(app, \".next\", \"static\"), { recursive: true });\n if (existsSync(join(root, \"public\"))) cpSync(join(root, \"public\"), join(app, \"public\"), { recursive: true });\n writeFileSync(join(out, \"bcms-runtime.json\"), `${JSON.stringify({ kind: \"node\", dir: \"app\", entry: \"server.js\" })}\\n`);\n}\n\nexport function buildPreviewRuntime(input: { root: string; manifestPath: string; out: string }) {\n const root = resolve(input.root);\n const out = resolve(input.out);\n const entries = validateManifest(root, readJson<Manifest>(resolve(input.manifestPath)));\n const framework = detectFramework(root);\n\n rmSync(out, { recursive: true, force: true });\n mkdirSync(out, { recursive: true });\n const restore = snapshot(root, MUTATED_BY_BUILD);\n try {\n if (framework === \"astro\") buildAstro(root, entries, out);\n else buildNext(root, entries, out);\n } finally {\n restore();\n }\n return {\n framework,\n out,\n components: entries.map((e) => e.id),\n kinds: Object.fromEntries(entries.map((e) => [e.id, e.source.kind ?? \"file\"])) as Record<string, SourceKind>,\n };\n}\n","/**\n * `bcms-preview validate` — validates one component in CI with nothing configured in the repository.\n *\n * Run by `.github/workflows/bcms-component-validation.yml`, which BetterCMS commits and dispatches. In\n * order: build a preview runtime from the app as it is, render the component with its default props in a\n * real browser at every native viewport, check brand tokens and the console, package the runtime — and only\n * then claim the request, upload the bundle the platform will serve previews from, and complete it.\n *\n * 🔴 NOTHING FAILS SILENTLY. A component whose checks fail is still COMPLETED — failed evidence is a\n * result the dashboard shows, with the failing checks. Anything that prevents a result (no source file\n * recorded, a build that breaks, a runtime that never starts, no browser) FAILS the request with a named\n * code and the reason, so the panel says what happened instead of waiting for the request to expire.\n */\nimport { spawn, spawnSync, type ChildProcess } from \"node:child_process\";\nimport { createHash, randomBytes } from \"node:crypto\";\nimport { mkdtempSync, readFileSync, rmSync, writeFileSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { createServer } from \"node:net\";\nimport { tmpdir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport { buildPreviewRuntime, CliFailure } from \"./build\";\n\nconst PREVIEW_ROUTE_BASE = \"/__bettercms/component-preview/__bcms\";\nconst RUNTIME_PATH = `${PREVIEW_ROUTE_BASE}/runtime`;\n\ntype Viewport = { name: string; width: number; height: number };\ntype RequestTarget = {\n componentId: string;\n componentVersion: number;\n candidateId: string;\n familyKey: string;\n familyContractHash: string;\n schemaHash: string;\n brandContractHash: string;\n dependenciesHash: string;\n commitSha: string;\n adapterHash: string;\n familyManifestHash: string;\n nativeViewports: Viewport[];\n};\ntype Manifest = {\n components: {\n id: string;\n /** `{ path, export, kind? }` for a file or section; `{ kind: \"page\", route, … }` for a placement or layout section. */\n source: Record<string, unknown>;\n defaultProps: Record<string, unknown>;\n /** On an explicitly recorded source: the page this component would render from without it. */\n fallback?: { route: string };\n }[];\n brandTokenNames: string[];\n};\n\nclass ValidationFailure extends Error {\n constructor(readonly code: string, message: string) {\n super(message);\n this.name = \"ValidationFailure\";\n }\n}\n\nfunction required(name: string): string {\n const value = process.env[name]?.trim();\n if (!value) throw new CliFailure(`${name} is not set. This command runs inside the BetterCMS component validation workflow.`);\n return value;\n}\n\nconst sha256 = (data: string | Buffer) => createHash(\"sha256\").update(data).digest(\"hex\");\n\nfunction canonical(value: unknown): string {\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (value && typeof value === \"object\") {\n return `{${Object.keys(value as Record<string, unknown>).sort()\n .map((key) => `${JSON.stringify(key)}:${canonical((value as Record<string, unknown>)[key])}`).join(\",\")}}`;\n }\n return JSON.stringify(value);\n}\n\nfunction freePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const server = createServer();\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n const address = server.address();\n const port = typeof address === \"object\" && address ? address.port : 0;\n server.close(() => resolve(port));\n });\n });\n}\n\nasync function waitForOk(url: string, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n try {\n if ((await fetch(url)).ok) return true;\n } catch {\n // not up yet\n }\n await new Promise((r) => setTimeout(r, 500));\n }\n return false;\n}\n\n/** Install Chromium for the bundled Playwright. On a Linux CI runner, with its system dependencies. */\nfunction ensureBrowser() {\n const require = createRequire(import.meta.url);\n const cli = join(dirname(require.resolve(\"playwright/package.json\")), \"cli.js\");\n const args = [cli, \"install\", \"chromium\"];\n if (process.platform === \"linux\" && process.env.CI) args.push(\"--with-deps\");\n const result = spawnSync(process.execPath, args, { stdio: \"inherit\" });\n if (result.status !== 0) {\n throw new ValidationFailure(\"COMPONENT_VALIDATION_BROWSER_UNAVAILABLE\", \"A headless browser could not be installed on this runner.\");\n }\n}\n\nasync function renderInBrowser(url: string, viewports: Viewport[], tokenNames: string[], options: { section: boolean }) {\n ensureBrowser();\n const { chromium } = await import(\"playwright\");\n const browser = await chromium.launch();\n try {\n let consoleErrors = 0;\n let runtimeErrors = 0;\n const missingTokens = new Set<string>();\n const results: { name: string; width: number; height: number; status: \"baseline-missing\"; candidateDigest: string }[] = [];\n const problems: string[] = [];\n for (const viewport of viewports) {\n const page = await browser.newPage({ viewport: { width: viewport.width, height: viewport.height } });\n page.on(\"console\", (message) => {\n if (message.type() === \"error\") {\n consoleErrors += 1;\n problems.push(`${viewport.name} console: ${message.text().slice(0, 200)}`);\n }\n });\n page.on(\"pageerror\", (error) => {\n runtimeErrors += 1;\n problems.push(`${viewport.name} error: ${error.message.slice(0, 200)}`);\n });\n const response = await page.goto(url, { waitUntil: \"load\", timeout: 45_000 });\n await page.waitForLoadState(\"networkidle\", { timeout: 15_000 }).catch(() => {});\n const rendered = await page.locator(\"[data-bcms-preview-render]\").count();\n if (!response?.ok() || rendered === 0) {\n runtimeErrors += 1;\n problems.push(`${viewport.name}: the component page did not render (HTTP ${response?.status() ?? \"none\"})`);\n } else if (options.section && (await page.locator(\"[data-bcms-block]\").count()) === 0) {\n // The codemod stamps data-bcms-block on every section root, and the editor resolves a section's\n // fields by walking up to it: a section without one renders but cannot be edited.\n runtimeErrors += 1;\n problems.push(`${viewport.name}: the section rendered without its [data-bcms-block] root`);\n }\n /**\n * A token is MISSING only when the rendered page uses it and it resolves to nothing. `--bcms-*` are\n * guaranteed on pages BetterCMS renders itself, not in a customer's framework build, so asking\n * \"is every token defined?\" would fail every starter while saying nothing about the component. The\n * question worth a failure is \"does this component reference a brand token the app never defines?\"\n * — a component that will render unstyled on the live site. Cross-origin stylesheets cannot be read\n * and are skipped.\n */\n const missing = await page.evaluate((names: string[]) => {\n const referenced = new Set<string>();\n const scan = (text: string) => {\n for (const match of text.matchAll(/var\\(\\s*(--[A-Za-z0-9_-]+)/g)) referenced.add(match[1]!);\n };\n for (const sheet of Array.from(document.styleSheets)) {\n try {\n for (const rule of Array.from(sheet.cssRules)) scan(rule.cssText);\n } catch {\n // cross-origin sheet\n }\n }\n document.querySelectorAll(\"[style]\").forEach((element) => scan(element.getAttribute(\"style\") ?? \"\"));\n const style = getComputedStyle(document.documentElement);\n return names.filter((name) => referenced.has(name) && !style.getPropertyValue(name).trim());\n }, tokenNames);\n for (const token of missing) missingTokens.add(token);\n const png = await page.screenshot({ fullPage: true });\n results.push({\n name: viewport.name,\n width: viewport.width,\n height: viewport.height,\n // No baseline exists for a first validation. The database requires this exact shape for it:\n // no baseline or diff digest, and the check marked as needing review.\n status: \"baseline-missing\",\n candidateDigest: `sha256:${sha256(png)}`,\n });\n await page.close();\n }\n return { results, consoleErrors, runtimeErrors, missingTokens: [...missingTokens].sort(), problems };\n } finally {\n await browser.close();\n }\n}\n\nexport async function validateComponent() {\n const apiUrl = required(\"BCMS_API_URL\");\n const apiKey = required(\"BCMS_API_KEY\");\n const projectId = required(\"BCMS_PROJECT_ID\");\n const requestId = required(\"BCMS_REQUEST_ID\");\n const componentId = required(\"BCMS_COMPONENT_ID\");\n const commitSha = required(\"BCMS_COMMIT_SHA\");\n const familyKey = required(\"BCMS_FAMILY_KEY\");\n const previewOrigin = required(\"BCMS_PREVIEW_ORIGIN\");\n const nativeViewports = JSON.parse(required(\"BCMS_NATIVE_VIEWPORTS\")) as Viewport[];\n\n const api = async <T>(path: string, init: { method?: string; body?: unknown; claim?: string } = {}): Promise<T> => {\n const response = await fetch(new URL(path, apiUrl), {\n method: init.method ?? \"GET\",\n headers: {\n authorization: `Bearer ${apiKey}`,\n \"content-type\": \"application/json\",\n ...(init.claim ? { \"x-bcms-component-claim\": init.claim } : {}),\n },\n ...(init.body === undefined ? {} : { body: JSON.stringify(init.body) }),\n });\n const text = await response.text();\n let parsed: { data?: unknown; error?: unknown; message?: unknown } | null = null;\n try {\n parsed = JSON.parse(text);\n } catch {\n parsed = null;\n }\n if (!response.ok) {\n const code = typeof parsed?.error === \"string\" && /^[A-Z][A-Z0-9_]*$/.test(parsed.error) ? parsed.error : `API_HTTP_${response.status}`;\n const detail = typeof parsed?.message === \"string\" ? parsed.message : typeof parsed?.error === \"string\" ? parsed.error : text.slice(0, 300);\n throw new ValidationFailure(code, `${init.method ?? \"GET\"} ${path} answered ${response.status}: ${detail}`);\n }\n return ((parsed && \"data\" in parsed ? parsed.data : parsed) ?? {}) as T;\n };\n\n const claimPath = `/api/v1/projects/${projectId}/component-implementation/implementation-requests/${requestId}`;\n let claim: { request: RequestTarget; claimCapability: string } | null = null;\n /**\n * 🔴 CLAIMED LAST, NOT FIRST. A claim credential lives minutes, and `complete` refuses an expired one.\n * Claiming before a framework build, a browser install and a render meant every real run finished with\n * a credential that had already lapsed — and the request sat \"running\" until it expired. Everything slow\n * happens first; the claim is followed only by an upload and the completion.\n */\n const claimRequest = async () => {\n claim = await api<{ request: RequestTarget; claimCapability: string }>(`${claimPath}/claim`, {\n method: \"POST\",\n body: {\n componentId,\n commitSha,\n adapter: { protocol: \"bcms-component-runtime-v1\", kind: \"project-route\", path: RUNTIME_PATH, familyKey, previewOrigin, nativeViewports },\n providerRunId: required(\"BCMS_RUN_ID\"),\n providerRunAttempt: Number(process.env.BCMS_RUN_ATTEMPT) || 1,\n providerRunUrl: required(\"BCMS_RUN_URL\"),\n workflowRef: required(\"BCMS_WORKFLOW_REF\"),\n // The server caps this at the request's own expiry and at ten minutes.\n credentialExpiresAt: new Date(Date.now() + 10 * 60_000 - 5_000).toISOString(),\n },\n });\n return claim;\n };\n\n const work = mkdtempSync(join(tmpdir(), \"bcms-validate-\"));\n let runtime: ChildProcess | null = null;\n try {\n // `kinds=page`: this validator builds page-kind entries. A 0.2.0 validator never asks, and never gets one.\n const manifest = await api<Manifest>(`/api/v1/projects/${projectId}/component-preview/manifest?kinds=page`);\n const entry = manifest.components.find((c) => c.id === componentId);\n if (!entry) {\n throw new ValidationFailure(\n \"COMPONENT_SOURCE_NOT_RECORDED\",\n \"No file is recorded and this component is neither placed on a page nor used in the layout, so there is nothing to render. Place it, add it to the layout, or record its file.\",\n );\n }\n\n const manifestPath = join(work, \"manifest.json\");\n writeFileSync(manifestPath, JSON.stringify({ components: manifest.components.map(({ id, source, fallback }) => ({ id, source, ...(fallback ? { fallback } : {}) })) }));\n const out = join(work, \"runtime\");\n let built: ReturnType<typeof buildPreviewRuntime>;\n try {\n built = buildPreviewRuntime({ root: process.cwd(), manifestPath, out });\n } catch (error) {\n throw new ValidationFailure(\"COMPONENT_PREVIEW_BUILD_FAILED\", `The preview build failed: ${(error as Error).message}`);\n }\n\n const runtimeManifest = JSON.parse(readFileSync(join(out, \"bcms-runtime.json\"), \"utf8\")) as { dir: string; entry: string };\n const port = await freePort();\n const validatorKey = randomBytes(32).toString(\"base64url\");\n const local = `http://127.0.0.1:${port}`;\n // 🔴 The customer's code runs in this process tree. It gets no BetterCMS API key.\n const { BCMS_API_KEY: _withheld, ...inherited } = process.env;\n runtime = spawn(process.execPath, [join(out, runtimeManifest.dir, runtimeManifest.entry)], {\n cwd: join(out, runtimeManifest.dir),\n env: {\n ...inherited,\n NODE_ENV: \"production\",\n PORT: String(port),\n HOST: \"127.0.0.1\",\n HOSTNAME: \"127.0.0.1\",\n BCMS_API_URL: apiUrl,\n BCMS_DASHBOARD_ORIGIN: local,\n BCMS_PREVIEW_ORIGIN: local,\n BCMS_PREVIEW_VALIDATOR_KEY: validatorKey,\n },\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n /**\n * The app's own server errors (`TypeError: Cannot read properties of undefined`) are the real reason a\n * render fails — the browser only sees \"500\". Still echoed to the CI log; the first few are kept for the\n * evidence so the panel can say what broke.\n */\n const serverErrors: string[] = [];\n const collect = (stream: NodeJS.WriteStream) => (chunk: Buffer) => {\n stream.write(chunk);\n for (const line of chunk.toString(\"utf8\").split(\"\\n\")) {\n const clean = line.replace(/\\x1b\\[[0-9;]*m/g, \"\").trim();\n if (serverErrors.length < 5 && /\\b(error|exception)\\b/i.test(clean)) serverErrors.push(clean.slice(0, 200));\n }\n };\n runtime.stdout?.on(\"data\", collect(process.stdout));\n runtime.stderr?.on(\"data\", collect(process.stderr));\n if (!(await waitForOk(`${local}${PREVIEW_ROUTE_BASE}/health`, 60_000))) {\n throw new ValidationFailure(\"COMPONENT_PREVIEW_RUNTIME_DID_NOT_START\", \"The preview runtime did not start within 60 seconds.\");\n }\n\n const stored = await fetch(`${local}${PREVIEW_ROUTE_BASE}/props`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", \"x-bcms-validator-key\": validatorKey },\n body: JSON.stringify({ componentId, props: entry.defaultProps }),\n });\n if (!stored.ok) {\n throw new ValidationFailure(\"COMPONENT_PREVIEW_RUNTIME_REFUSED\", `The preview runtime refused the component (HTTP ${stored.status}).`);\n }\n const { id } = (await stored.json()) as { id: string };\n\n // The viewports the dispatch declared are exactly the ones the claim records as the adapter's.\n const render = await renderInBrowser(\n `${local}${PREVIEW_ROUTE_BASE}/render?id=${encodeURIComponent(id)}`,\n nativeViewports,\n manifest.brandTokenNames,\n // A page-kind render lifts a section too, and stamps its root.\n { section: built.kinds[componentId] !== \"file\" },\n );\n\n const tarball = join(work, \"bundle.tgz\");\n if (spawnSync(\"tar\", [\"-czf\", tarball, \"-C\", out, \".\"], { stdio: \"inherit\" }).status !== 0) {\n throw new ValidationFailure(\"COMPONENT_PREVIEW_BUNDLE_PACK_FAILED\", \"The preview runtime could not be packaged.\");\n }\n const bytes = readFileSync(tarball);\n runtime.kill();\n runtime = null;\n\n // A recorded file that fails while the component also renders from its page: the fix is to drop the file.\n const hint = entry.fallback && built.kinds[componentId] === \"file\"\n ? [`Clear the recorded source: this component renders from page ${entry.fallback.route} without one.`]\n : [];\n\n const target = (await claimRequest()).request;\n const checks = {\n brandKit: {\n status: render.missingTokens.length === 0 ? \"passed\" : \"failed\",\n contractHash: target.brandContractHash,\n missingTokens: render.missingTokens,\n },\n runtime: {\n status: render.runtimeErrors === 0 && render.consoleErrors === 0 ? \"passed\" : \"failed\",\n runtimeErrors: render.runtimeErrors,\n consoleErrors: render.consoleErrors,\n // Only on a failure, and bounded: the server's own error lines first (the real cause), then what the\n // browser saw. The database stores them and the panel shows the first one.\n ...(render.runtimeErrors + render.consoleErrors > 0\n ? { problems: [...hint, ...serverErrors.map((line) => `server: ${line}`), ...render.problems].slice(0, 10).map((line) => line.slice(0, 200)) }\n : {}),\n },\n visual: { status: \"baseline-missing\", reviewRequired: true, viewports: render.results },\n };\n\n // Uploaded before completing: validated evidence pointing at a runtime nobody can start renders nothing.\n const upload = await api<{ uploadUrl: string; uploadKey: string }>(`/api/v1/projects/${projectId}/artifacts/upload-url`, { method: \"POST\" });\n const put = await fetch(upload.uploadUrl, { method: \"PUT\", body: bytes, headers: { \"content-type\": \"application/gzip\" } });\n if (!put.ok) {\n throw new ValidationFailure(\"COMPONENT_PREVIEW_BUNDLE_UPLOAD_FAILED\", `Storage refused the preview bundle (HTTP ${put.status}).`);\n }\n await api(`/api/v1/projects/${projectId}/component-preview/bundles`, {\n method: \"POST\",\n body: { commitSha: target.commitSha, uploadKey: upload.uploadKey, checksum: `sha256:${sha256(bytes)}`, sizeBytes: bytes.byteLength },\n });\n\n const evidenceDigest = `sha256:${sha256(canonical({ requestId, commitSha: target.commitSha, checks }))}`;\n await api(`${claimPath}/complete`, {\n method: \"POST\",\n claim: claim!.claimCapability,\n body: {\n componentId: target.componentId,\n candidateId: target.candidateId,\n componentVersion: target.componentVersion,\n familyKey: target.familyKey,\n familyContractHash: target.familyContractHash,\n schemaHash: target.schemaHash,\n brandContractHash: target.brandContractHash,\n dependenciesHash: target.dependenciesHash,\n commitSha: target.commitSha,\n adapterHash: target.adapterHash,\n familyManifestHash: target.familyManifestHash,\n nativeViewports: target.nativeViewports,\n checks,\n evidenceDigest,\n },\n });\n\n const passed = checks.brandKit.status === \"passed\" && checks.runtime.status === \"passed\";\n console.log(`bcms-preview: validation ${passed ? \"PASSED\" : \"FAILED\"} for ${componentId}`);\n if (!passed) {\n if (render.missingTokens.length) console.log(` brand tokens used but not defined: ${render.missingTokens.join(\", \")}`);\n for (const problem of [...hint, ...render.problems]) console.log(` ${problem}`);\n }\n } catch (error) {\n const code = error instanceof ValidationFailure ? error.code : \"COMPONENT_VALIDATION_FAILED\";\n const message = (error as Error).message.slice(0, 2000);\n // A failure before the claim is still reported: claim, then fail at once, so the dashboard names the\n // reason instead of showing \"running\" until the request expires.\n const reported = claim ?? await claimRequest().catch((claimError) => {\n console.error(`bcms-preview: could not claim the request to report the failure: ${(claimError as Error).message}`);\n return null;\n });\n if (reported) {\n await api(`${claimPath}/fail`, {\n method: \"POST\",\n claim: reported.claimCapability,\n body: { componentId, errorCode: code, errorMessage: message },\n }).catch((reportError) => console.error(`bcms-preview: could not report the failure: ${(reportError as Error).message}`));\n }\n throw new CliFailure(`${code}: ${message}`);\n } finally {\n runtime?.kill();\n rmSync(work, { recursive: true, force: true });\n }\n}\n","/**\n * bcms-preview\n *\n * bcms-preview build --manifest <file> --out <dir> [--cwd <dir>]\n * bcms-preview validate (in CI; configured entirely by BCMS_* environment variables)\n */\nimport { buildPreviewRuntime, CliFailure } from \"./build\";\nimport { validateComponent } from \"./validate\";\n\nfunction arg(name: string): string | undefined {\n const index = process.argv.indexOf(`--${name}`);\n return index === -1 ? undefined : process.argv[index + 1];\n}\n\nasync function main() {\n const command = process.argv[2];\n if (command === \"build\") {\n const manifestPath = arg(\"manifest\");\n const out = arg(\"out\");\n if (!manifestPath || !out) throw new CliFailure(\"usage: bcms-preview build --manifest <file> --out <dir> [--cwd <dir>]\");\n console.log(JSON.stringify(buildPreviewRuntime({ root: arg(\"cwd\") ?? process.cwd(), manifestPath, out })));\n return;\n }\n if (command === \"validate\") {\n await validateComponent();\n return;\n }\n throw new CliFailure(\"usage: bcms-preview <build|validate>\");\n}\n\n/**\n * One handler for every failure, and no `process.exit`: writes to a piped stderr are asynchronous, so\n * exiting right after the write dropped the message and CI showed a failed step with no reason.\n */\nmain().catch((error) => {\n console.error(`bcms-preview: ${error instanceof CliFailure ? error.message : (error as Error).stack ?? String(error)}`);\n process.exitCode = 1;\n});\n"],"mappings":"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","names":["require","spawnSync","readFileSync","rmSync","writeFileSync","createRequire","dirname","join","resolve","require","createRequire","join","dirname","spawnSync","writeFileSync","id","readFileSync","rmSync"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";(()=>{var E=["section","article, header, footer, nav, aside"],$=["header, footer, nav, aside","section, article"],w=t=>`"${t.replace(/["\\]/g,"\\$&")}"`;function I(t){let e=[],n=t.getAttribute("data-bcms-field");n&&e.push(n);let o=t.getAttribute("data-bcms-layout-field");o&&e.push(o);for(let s of(t.getAttribute("data-bcms-props")??"").split(";")){let r=s.split("|");r.length===3&&r.every(Boolean)&&e.push(r[0])}return e}function m(t){let e=t[0]??null;for(let n of t.slice(1))for(;e&&!e.contains(n);)e=e.parentElement;return e}function b(t,e){for(let n of e){let o=t.closest(n);if(o)return o}for(let n=t;n;n=n.parentElement)if(n.parentElement?.tagName==="MAIN")return n;return t}function y(t,e){return Array.from(t.querySelectorAll("[data-bcms-field], [data-bcms-layout-field], [data-bcms-props]")).filter(n=>I(n).some(e))}function k(t,e){if(e.blockId){let n=t.querySelector(`[data-bcms-block=${w(e.blockId)}]`);if(n)return n;let o=new Set(Object.values(e.source??{})),s=y(t,c=>o.has(c)||!!e.groupKey&&c.startsWith(`${e.groupKey}-`)||c.startsWith(`${e.blockId}__`)),r=m(s);if(r)return b(r,E)}if(e.layoutSectionId){let n=`layout:${e.layoutSectionId}:`,o=m(y(t,s=>s.startsWith(n)));if(o)return b(o,$)}return e.landmark?t.querySelector(e.landmark):null}function h(t,e,n){if(e&&n.set(e,t),!(t===null||typeof t!="object"||typeof t.html=="string"))for(let[o,s]of Object.entries(t))h(s,e?`${e}.${o}`:o,n)}function M(t,e){let n=t.split(".");for(let o=n.length;o>0;o-=1){let s=n.slice(0,o).join("."),r=e?.[s]!==void 0?s:Object.keys(e??{}).find(c=>c.endsWith(`.${s}`));if(r!==void 0)return[e[r],...n.slice(o)].join(".")}return t}function R(t,e){let n=new Map;h(e,"",n);let o=new Map;for(let[s,r]of n)t.layoutSectionId&&o.set(`layout:${t.layoutSectionId}:${M(s,t.bindings)}`,r),t.blockId&&(o.set(t.source?.[s]??`${t.groupKey}-${s.replace(/_/g,"-")}`,r),o.set(`${t.blockId}__overrides.${s}`,r));return o}function p(t){if(t&&typeof t=="object"){let e=t,n=e.text??e.url??e.href??e.src??e.html;return typeof n=="string"?n:""}return String(t)}function S(t,e,n){let o=R(e,n),s=[t,...Array.from(t.querySelectorAll("[data-bcms-field], [data-bcms-layout-field], [data-bcms-props]"))];for(let r of s){let c=r.getAttribute("data-bcms-field")??r.getAttribute("data-bcms-layout-field");if(c&&o.has(c)){let i=o.get(c);if(i!=null){let a=i&&typeof i=="object"?i.html:void 0,d=r.getAttribute("data-bcms-kind");typeof a=="string"?r.innerHTML=a:d==="richtext"||d==="document"?r.innerHTML=p(i):r.textContent=p(i)}}for(let i of(r.getAttribute("data-bcms-props")??"").split(";")){let[a,,d]=i.split("|");if(!a||!d||!o.has(a))continue;let f=o.get(a);f!=null&&r.setAttribute(d,p(f))}}}function A(t){return t.blockId?`nothing on ${t.route} renders group ${t.groupKey??t.blockId}`:`nothing on ${t.route} renders layout section ${t.layoutSectionId??t.landmark}`}var P="data-bcms-preview-render",l=JSON.parse(document.getElementById("bcms-scope-data").textContent),g=document.getElementById("bcms-page"),u=k(g.content,l.meta);if(!u)console.error(`[bcms-preview] ${A(l.meta)}`);else{for(let t of Array.from(g.content.querySelectorAll('style, link[rel="stylesheet"]')))u.contains(t)||document.head.appendChild(t);S(u,l.meta,l.props),u.hasAttribute("data-bcms-block")||u.setAttribute("data-bcms-block",l.meta.blockId??`layout:${l.meta.layoutSectionId??l.meta.landmark}`),document.body.appendChild(u),g.remove(),document.documentElement.setAttribute(P,"1")}})();
|
package/dist/server.d.ts
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `page` source kind, in the browser: find the one section of a real page that a component is, and
|
|
3
|
+
* write live props onto it.
|
|
4
|
+
*
|
|
5
|
+
* A component the codemod never extracted (its markup lives in a page, in loops and child components) still
|
|
6
|
+
* renders exactly as deployed when the runtime renders its PAGE and keeps only its section. Which section is
|
|
7
|
+
* answered from the page's own binding attributes, never from markup guesses:
|
|
8
|
+
* 1. `[data-bcms-block=<blockId>]` — the placement's root, when the page declares it;
|
|
9
|
+
* 2. the common ancestor of the group's page fields (`wrap-…`, `<blockId>__…`, the placement's source map);
|
|
10
|
+
* 3. the common ancestor of a layout section's fields (`layout:footer:…`);
|
|
11
|
+
* 4. the layout section's landmark element itself (`<footer>`).
|
|
12
|
+
* 2–4 walk up to the nearest sectioning element, so a lone field inside a card still selects its band.
|
|
13
|
+
*
|
|
14
|
+
* Dependency-free: bundled into a script inlined into the render document.
|
|
15
|
+
*/
|
|
16
|
+
type PageSectionMeta = {
|
|
17
|
+
kind: "page";
|
|
18
|
+
route: string;
|
|
19
|
+
/** A placement on a page. */
|
|
20
|
+
blockId?: string;
|
|
21
|
+
groupKey?: string | null;
|
|
22
|
+
/** prop key → the page field path it was cut from. Null for a placement bound by group. */
|
|
23
|
+
source?: Record<string, string> | null;
|
|
24
|
+
/** A section of the project layout (chrome). */
|
|
25
|
+
layoutSectionId?: string;
|
|
26
|
+
landmark?: "header" | "footer" | "nav" | null;
|
|
27
|
+
/** Component input id → layout field id (`"footer.brand-name": "footer.brand-name"`). */
|
|
28
|
+
bindings?: Record<string, string>;
|
|
29
|
+
};
|
|
30
|
+
|
|
1
31
|
declare const PREVIEW_BASE = "/__bettercms/component-preview";
|
|
2
32
|
declare const PREVIEW_ROUTES: {
|
|
3
33
|
readonly runtime: "/__bettercms/component-preview/__bcms/runtime";
|
|
@@ -39,7 +69,16 @@ declare function renderEntry(id: string | null | undefined): {
|
|
|
39
69
|
componentId: string;
|
|
40
70
|
props: Record<string, unknown>;
|
|
41
71
|
} | null;
|
|
72
|
+
/**
|
|
73
|
+
* The render document for a `page`-kind component: the real page, held inert, plus the script that lifts
|
|
74
|
+
* the component's section out of it (see scope.ts). Head styles come along so the section renders styled;
|
|
75
|
+
* nothing else of the page runs.
|
|
76
|
+
*/
|
|
77
|
+
declare function scopeDocument(html: string, meta: PageSectionMeta, props: Record<string, unknown>, scopeScript: string): string;
|
|
78
|
+
declare function renderPageSection(request: Request, entry: {
|
|
79
|
+
props: Record<string, unknown>;
|
|
80
|
+
}, meta: PageSectionMeta, scopeScript: string): Promise<Response>;
|
|
42
81
|
declare function handleHealth(): Response;
|
|
43
82
|
declare function handleRuntime(shellSource: string): Response;
|
|
44
83
|
|
|
45
|
-
export { PREVIEW_BASE, PREVIEW_ROUTES, PREVIEW_RUNTIME_PATH, type PreviewRuntimeEnv, RENDER_MARKER, type RegistryCheck, handleHealth, handleProps, handleRuntime, handleSession, previewRuntimeEnv, renderEntry, renderHeaders };
|
|
84
|
+
export { PREVIEW_BASE, PREVIEW_ROUTES, PREVIEW_RUNTIME_PATH, type PageSectionMeta, type PreviewRuntimeEnv, RENDER_MARKER, type RegistryCheck, handleHealth, handleProps, handleRuntime, handleSession, previewRuntimeEnv, renderEntry, renderHeaders, renderPageSection, scopeDocument };
|
package/dist/server.js
CHANGED
|
@@ -312,6 +312,53 @@ function renderEntry(id) {
|
|
|
312
312
|
if (!entry || entry.expiresAt <= Date.now()) return null;
|
|
313
313
|
return { componentId: entry.componentId, props: entry.props };
|
|
314
314
|
}
|
|
315
|
+
function scopeDocument(html, meta, props, scopeScript) {
|
|
316
|
+
const headEnd = html.search(/<\/head>/i);
|
|
317
|
+
const head = headEnd >= 0 ? html.slice(0, headEnd) : "";
|
|
318
|
+
const styles = head.match(/<link\b[^>]*\brel=["']?stylesheet\b[^>]*>|<style\b[^>]*>[\s\S]*?<\/style>/gi) ?? [];
|
|
319
|
+
const body = html.match(/<body\b([^>]*)>([\s\S]*)<\/body>/i);
|
|
320
|
+
const htmlAttributes = html.match(/<html\b([^>]*)>/i)?.[1] ?? ' lang="en"';
|
|
321
|
+
const data = JSON.stringify({ meta, props }).replace(/</g, "\\u003c");
|
|
322
|
+
return `<!doctype html>
|
|
323
|
+
<html${htmlAttributes}>
|
|
324
|
+
<head>
|
|
325
|
+
<meta charset="utf-8">
|
|
326
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
327
|
+
${styles.join("\n")}
|
|
328
|
+
</head>
|
|
329
|
+
<body${body?.[1] ?? ""}>
|
|
330
|
+
<template id="bcms-page">${body?.[2] ?? (headEnd >= 0 ? html.slice(headEnd + "</head>".length) : html)}</template>
|
|
331
|
+
<script type="application/json" id="bcms-scope-data">${data}</script>
|
|
332
|
+
<script>${escapeForScript(scopeScript)}</script>
|
|
333
|
+
</body>
|
|
334
|
+
</html>`;
|
|
335
|
+
}
|
|
336
|
+
var loggedPageOrigin = false;
|
|
337
|
+
async function renderPageSection(request, entry, meta, scopeScript) {
|
|
338
|
+
const headers = { ...renderHeaders(), "content-type": "text/html; charset=utf-8" };
|
|
339
|
+
const port = env("PORT");
|
|
340
|
+
const origin = port ? `http://127.0.0.1:${port}` : new URL(request.url).origin;
|
|
341
|
+
if (!loggedPageOrigin) {
|
|
342
|
+
loggedPageOrigin = true;
|
|
343
|
+
console.log(`[bcms-preview] page sections render from ${origin}${PREVIEW_BASE}`);
|
|
344
|
+
}
|
|
345
|
+
let page;
|
|
346
|
+
try {
|
|
347
|
+
page = await fetch(`${origin}${PREVIEW_BASE}${meta.route}`, {
|
|
348
|
+
headers: { accept: "text/html" },
|
|
349
|
+
cache: "no-store",
|
|
350
|
+
signal: AbortSignal.timeout(3e4)
|
|
351
|
+
});
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.error(`[bcms-preview] error: page ${meta.route} could not be fetched: ${error.message}`);
|
|
354
|
+
return new Response("The page this component lives on could not be rendered.", { status: 502, headers });
|
|
355
|
+
}
|
|
356
|
+
if (!page.ok) {
|
|
357
|
+
console.error(`[bcms-preview] error: page ${meta.route} answered HTTP ${page.status}`);
|
|
358
|
+
return new Response("The page this component lives on could not be rendered.", { status: 502, headers });
|
|
359
|
+
}
|
|
360
|
+
return new Response(scopeDocument(await page.text(), meta, entry.props, scopeScript), { status: 200, headers });
|
|
361
|
+
}
|
|
315
362
|
function handleHealth() {
|
|
316
363
|
return json({ ok: true });
|
|
317
364
|
}
|
|
@@ -363,5 +410,7 @@ export {
|
|
|
363
410
|
handleSession,
|
|
364
411
|
previewRuntimeEnv,
|
|
365
412
|
renderEntry,
|
|
366
|
-
renderHeaders
|
|
413
|
+
renderHeaders,
|
|
414
|
+
renderPageSection,
|
|
415
|
+
scopeDocument
|
|
367
416
|
};
|
package/package.json
CHANGED