@bettercms-ai/preview-runtime 0.1.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 CHANGED
@@ -36,6 +36,30 @@ bcms-preview validate
36
36
  { "components": [{ "id": "cmp_1", "source": { "path": "src/components/Hero.astro" } }] }
37
37
  ```
38
38
 
39
+ ## Sections extracted from pages
40
+
41
+ A component does not have to be a standalone file with its fields as props. A section
42
+ `npx @bettercms-ai/convert --componentize` extracted from a page (a file starting
43
+ `// @bettercms-ai/convert section v…`) is recognised by that marker and rendered with its section contract:
44
+ `<Section blockId="bcms-preview" overrides={props} page={{}} />`, so the live props arrive as the instance's
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.
59
+
60
+ A failed validation reports what broke — the app server's own error lines first — and the dashboard shows
61
+ it under "Runtime + console".
62
+
39
63
  ## How a preview renders
40
64
 
41
65
  - **Astro**: built with `output: "server"` and `@astrojs/node`, so `.astro` components render on the
package/dist/cli.js CHANGED
@@ -10,6 +10,7 @@ var here = dirname(fileURLToPath(import.meta.url));
10
10
  var PREVIEW_BASE = "/__bettercms/component-preview";
11
11
  var COMPONENT_EXTENSIONS = /* @__PURE__ */ new Set([".astro", ".tsx", ".jsx", ".ts", ".js", ".mjs"]);
12
12
  var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
13
+ var SECTION_MARKER_PREFIX = "// @bettercms-ai/convert section v";
13
14
  var CliFailure = class extends Error {
14
15
  };
15
16
  function fail(message) {
@@ -35,6 +36,25 @@ function readJson(file) {
35
36
  fail(`could not read ${file}: ${error.message}`);
36
37
  }
37
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
+ }
38
58
  function validateManifest(root, manifest) {
39
59
  if (!manifest || !Array.isArray(manifest.components) || manifest.components.length === 0) {
40
60
  fail("the manifest lists no components");
@@ -44,6 +64,7 @@ function validateManifest(root, manifest) {
44
64
  if (!entry || typeof entry.id !== "string" || !entry.id.trim()) fail("a manifest entry has no id");
45
65
  if (seen.has(entry.id)) fail(`component ${entry.id} is listed twice`);
46
66
  seen.add(entry.id);
67
+ if (entry.source?.kind === "page") return { id: entry.id, source: pageSource(entry.id, entry.source) };
47
68
  const path = entry.source?.path;
48
69
  if (typeof path !== "string" || path.startsWith("/") || path.includes("\\") || path.split("/").includes("..")) {
49
70
  fail(`component ${entry.id} has an unsafe source path: ${String(path)}`);
@@ -54,7 +75,9 @@ function validateManifest(root, manifest) {
54
75
  if (named !== void 0 && named !== "default" && !IDENTIFIER.test(named)) {
55
76
  fail(`component ${entry.id}: export "${named}" is not a valid identifier`);
56
77
  }
57
- return { id: entry.id, source: { path, export: named ?? "default" } };
78
+ const head = readFileSync(join(root, path), "utf8").slice(0, 512);
79
+ const kind = entry.source.kind === "section" || head.includes(SECTION_MARKER_PREFIX) ? "section" : "file";
80
+ return { id: entry.id, source: { path, export: named ?? "default", kind } };
58
81
  });
59
82
  }
60
83
  function detectFramework(root) {
@@ -76,12 +99,20 @@ function bin(root, name) {
76
99
  function registrySource(fromDir, root, entries) {
77
100
  const imports = [];
78
101
  const keys = [];
102
+ const kinds = [];
103
+ const pages = [];
79
104
  entries.forEach((entry, index) => {
80
- let specifier = relative(fromDir, join(root, entry.source.path)).split(sep).join("/");
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("/");
81
112
  if (!specifier.startsWith(".")) specifier = `./${specifier}`;
82
113
  if ([".tsx", ".ts", ".jsx", ".js"].includes(extname(specifier))) specifier = specifier.slice(0, -extname(specifier).length);
83
114
  const local = `Component${index}`;
84
- imports.push(entry.source.export === "default" ? `import ${local} from ${JSON.stringify(specifier)};` : `import { ${entry.source.export} as ${local} } from ${JSON.stringify(specifier)};`);
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)};`);
85
116
  keys.push(` ${JSON.stringify(entry.id)}: ${local},`);
86
117
  });
87
118
  return `${imports.join("\n")}
@@ -90,7 +121,16 @@ export const registry: Record<string, any> = {
90
121
  ${keys.join("\n")}
91
122
  };
92
123
 
93
- export const has = (componentId: string): boolean => Object.prototype.hasOwnProperty.call(registry, componentId);
124
+ export const kinds: Record<string, "file" | "section" | "page"> = {
125
+ ${kinds.join("\n")}
126
+ };
127
+
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);
94
134
  `;
95
135
  }
96
136
  function writeRuntimeLibrary(dir) {
@@ -98,6 +138,8 @@ function writeRuntimeLibrary(dir) {
98
138
  const types = join(here, "server.d.ts");
99
139
  if (existsSync(types)) writeFileSync(join(dir, "server.d.mts"), readFileSync(types, "utf8"));
100
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"))};
101
143
  `);
102
144
  }
103
145
  function astroGlobalStyles(root) {
@@ -127,7 +169,8 @@ function ensureAstroNodeAdapter(root) {
127
169
  } catch {
128
170
  }
129
171
  const astroVersion = readJson(require2.resolve("astro/package.json")).version;
130
- const range = ASTRO_NODE_ADAPTER[astroVersion.split(".")[0]];
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)];
131
174
  if (!range) fail(`Astro ${astroVersion} is not supported for component previews yet`);
132
175
  run(root, "npm", ["install", "--no-save", "--no-audit", "--no-fund", `@astrojs/node@${range}`]);
133
176
  }
@@ -159,18 +202,21 @@ ${handler("GET", "() => handleHealth()")}`);
159
202
  const styles = astroGlobalStyles(root).map((file) => `import ${JSON.stringify(relative(gen, file).split(sep).join("/"))};`).join("\n");
160
203
  writeFileSync(join(gen, "render.astro"), `---
161
204
  ${styles}
162
- import { renderEntry, renderHeaders } from "./server.mjs";
163
- import { registry } from "./registry";
205
+ import { renderEntry, renderHeaders, renderPageSection } from "./server.mjs";
206
+ import { registry, kinds, pages } from "./registry";
207
+ import scope from "./scope";
164
208
  export const prerender = false;
165
209
  const headers = renderHeaders();
166
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);
167
212
  const Component = entry ? registry[entry.componentId] : undefined;
213
+ const section = entry ? kinds[entry.componentId] === "section" : false;
168
214
  for (const [name, value] of Object.entries(headers)) Astro.response.headers.set(name, value);
169
215
  if (!entry || !Component) return new Response("Not found", { status: 404, headers });
170
216
  ---
171
217
  <html lang="en" data-bcms-preview-render="1">
172
218
  <head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /></head>
173
- <body><Component {...entry.props} /></body>
219
+ <body>{section ? <Component blockId="bcms-preview" overrides={entry.props} page={{}} /> : <Component {...entry.props} />}</body>
174
220
  </html>
175
221
  `);
176
222
  writeFileSync(wrapper, `import user from "./${configName}";
@@ -256,23 +302,38 @@ export function POST(request: Request) {
256
302
  export function GET() {
257
303
  return handleHealth();
258
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
+ }
259
315
  `);
260
316
  mkdirSync(join(gen, "render"), { recursive: true });
261
- writeFileSync(join(gen, "render", "page.tsx"), `import { notFound } from "next/navigation";
317
+ writeFileSync(join(gen, "render", "page.tsx"), `import { notFound, redirect } from "next/navigation";
262
318
  import { renderEntry } from "../server.mjs";
263
- import { registry } from "../registry";
319
+ import { registry, kinds, pages } from "../registry";
264
320
 
265
321
  export const dynamic = "force-dynamic";
266
322
 
267
323
  export default async function BetterCMSComponentPreview({ searchParams }: { searchParams: Promise<{ id?: string }> }) {
268
324
  const { id } = await searchParams;
269
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!)}\`);
270
329
  const Component = entry ? registry[entry.componentId] : undefined;
271
330
  if (!entry || !Component) notFound();
272
331
  // The marker the runtime page checks before acknowledging: a 404 or an error page never carries it.
273
332
  return (
274
333
  <>
275
- <Component {...entry.props} />
334
+ {kinds[entry.componentId] === "section"
335
+ ? <Component blockId="bcms-preview" overrides={entry.props} page={{}} />
336
+ : <Component {...entry.props} />}
276
337
  <template data-bcms-preview-render="1" />
277
338
  </>
278
339
  );
@@ -333,7 +394,12 @@ function buildPreviewRuntime(input) {
333
394
  } finally {
334
395
  restore();
335
396
  }
336
- return { framework, out, components: entries.map((e) => e.id) };
397
+ return {
398
+ framework,
399
+ out,
400
+ components: entries.map((e) => e.id),
401
+ kinds: Object.fromEntries(entries.map((e) => [e.id, e.source.kind ?? "file"]))
402
+ };
337
403
  }
338
404
 
339
405
  // src/validate.ts
@@ -399,7 +465,7 @@ function ensureBrowser() {
399
465
  throw new ValidationFailure("COMPONENT_VALIDATION_BROWSER_UNAVAILABLE", "A headless browser could not be installed on this runner.");
400
466
  }
401
467
  }
402
- async function renderInBrowser(url, viewports, tokenNames) {
468
+ async function renderInBrowser(url, viewports, tokenNames, options) {
403
469
  ensureBrowser();
404
470
  const { chromium } = await import("playwright");
405
471
  const browser = await chromium.launch();
@@ -428,6 +494,9 @@ async function renderInBrowser(url, viewports, tokenNames) {
428
494
  if (!response?.ok() || rendered === 0) {
429
495
  runtimeErrors += 1;
430
496
  problems.push(`${viewport.name}: the component page did not render (HTTP ${response?.status() ?? "none"})`);
497
+ } else if (options.section && await page.locator("[data-bcms-block]").count() === 0) {
498
+ runtimeErrors += 1;
499
+ problems.push(`${viewport.name}: the section rendered without its [data-bcms-block] root`);
431
500
  }
432
501
  const missing = await page.evaluate((names) => {
433
502
  const referenced = /* @__PURE__ */ new Set();
@@ -518,19 +587,20 @@ async function validateComponent() {
518
587
  const work = mkdtempSync(join2(tmpdir(), "bcms-validate-"));
519
588
  let runtime = null;
520
589
  try {
521
- 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`);
522
591
  const entry = manifest.components.find((c) => c.id === componentId);
523
592
  if (!entry) {
524
593
  throw new ValidationFailure(
525
594
  "COMPONENT_SOURCE_NOT_RECORDED",
526
- "No file is recorded as this component's source. The agent that writes a component records it with set_component_source."
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."
527
596
  );
528
597
  }
529
598
  const manifestPath = join2(work, "manifest.json");
530
- 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 } : {} })) }));
531
600
  const out = join2(work, "runtime");
601
+ let built;
532
602
  try {
533
- buildPreviewRuntime({ root: process.cwd(), manifestPath, out });
603
+ built = buildPreviewRuntime({ root: process.cwd(), manifestPath, out });
534
604
  } catch (error) {
535
605
  throw new ValidationFailure("COMPONENT_PREVIEW_BUILD_FAILED", `The preview build failed: ${error.message}`);
536
606
  }
@@ -552,8 +622,18 @@ async function validateComponent() {
552
622
  BCMS_PREVIEW_ORIGIN: local,
553
623
  BCMS_PREVIEW_VALIDATOR_KEY: validatorKey
554
624
  },
555
- stdio: ["ignore", "inherit", "inherit"]
625
+ stdio: ["ignore", "pipe", "pipe"]
556
626
  });
627
+ const serverErrors = [];
628
+ const collect = (stream) => (chunk) => {
629
+ stream.write(chunk);
630
+ for (const line of chunk.toString("utf8").split("\n")) {
631
+ const clean = line.replace(/\x1b\[[0-9;]*m/g, "").trim();
632
+ if (serverErrors.length < 5 && /\b(error|exception)\b/i.test(clean)) serverErrors.push(clean.slice(0, 200));
633
+ }
634
+ };
635
+ runtime.stdout?.on("data", collect(process.stdout));
636
+ runtime.stderr?.on("data", collect(process.stderr));
557
637
  if (!await waitForOk(`${local}${PREVIEW_ROUTE_BASE}/health`, 6e4)) {
558
638
  throw new ValidationFailure("COMPONENT_PREVIEW_RUNTIME_DID_NOT_START", "The preview runtime did not start within 60 seconds.");
559
639
  }
@@ -569,7 +649,9 @@ async function validateComponent() {
569
649
  const render = await renderInBrowser(
570
650
  `${local}${PREVIEW_ROUTE_BASE}/render?id=${encodeURIComponent(id)}`,
571
651
  nativeViewports,
572
- manifest.brandTokenNames
652
+ manifest.brandTokenNames,
653
+ // A page-kind render lifts a section too, and stamps its root.
654
+ { section: built.kinds[componentId] !== "file" }
573
655
  );
574
656
  const tarball = join2(work, "bundle.tgz");
575
657
  if (spawnSync2("tar", ["-czf", tarball, "-C", out, "."], { stdio: "inherit" }).status !== 0) {
@@ -578,6 +660,7 @@ async function validateComponent() {
578
660
  const bytes = readFileSync2(tarball);
579
661
  runtime.kill();
580
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.`] : [];
581
664
  const target = (await claimRequest()).request;
582
665
  const checks = {
583
666
  brandKit: {
@@ -588,7 +671,10 @@ async function validateComponent() {
588
671
  runtime: {
589
672
  status: render.runtimeErrors === 0 && render.consoleErrors === 0 ? "passed" : "failed",
590
673
  runtimeErrors: render.runtimeErrors,
591
- consoleErrors: render.consoleErrors
674
+ consoleErrors: render.consoleErrors,
675
+ // Only on a failure, and bounded: the server's own error lines first (the real cause), then what the
676
+ // browser saw. The database stores them and the panel shows the first one.
677
+ ...render.runtimeErrors + render.consoleErrors > 0 ? { problems: [...hint, ...serverErrors.map((line) => `server: ${line}`), ...render.problems].slice(0, 10).map((line) => line.slice(0, 200)) } : {}
592
678
  },
593
679
  visual: { status: "baseline-missing", reviewRequired: true, viewports: render.results }
594
680
  };
@@ -626,7 +712,7 @@ async function validateComponent() {
626
712
  console.log(`bcms-preview: validation ${passed ? "PASSED" : "FAILED"} for ${componentId}`);
627
713
  if (!passed) {
628
714
  if (render.missingTokens.length) console.log(` brand tokens used but not defined: ${render.missingTokens.join(", ")}`);
629
- for (const problem of render.problems) console.log(` ${problem}`);
715
+ for (const problem of [...hint, ...render.problems]) console.log(` ${problem}`);
630
716
  }
631
717
  } catch (error) {
632
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\ntype ManifestEntry = { id: string; source: { path: string; export?: string } };\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\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 return { id: entry.id, source: { path, export: named ?? \"default\" } };\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 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 });\n return `${imports.join(\"\\n\")}\\n\\nexport const registry: Record<string, any> = {\\n${keys.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 } from \"./registry\";\nexport const prerender = false;\nconst headers = renderHeaders();\nconst entry = renderEntry(Astro.url.searchParams.get(\"id\"));\nconst Component = entry ? registry[entry.componentId] : undefined;\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><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 } 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 <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 { framework, out, components: entries.map((e) => e.id) };\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[]) {\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 }\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 try {\n 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\", \"inherit\", \"inherit\"],\n });\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 );\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 },\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":";;;AAYA,SAAS,iBAAiB;AAC1B,SAAS,QAAQ,YAAY,WAAW,cAAc,aAAa,YAAY,QAAQ,UAAU,qBAAqB;AACtH,SAAS,qBAAqB;AAC9B,SAAS,SAAS,SAAS,MAAM,UAAU,SAAS,WAAW;AAC/D,SAAS,qBAAqB;AAyB9B,IAAM,OAAO,QAAQ,cAAc,YAAY,GAAG,CAAC;AACnD,IAAM,eAAe;AACrB,IAAM,uBAAuB,oBAAI,IAAI,CAAC,UAAU,QAAQ,QAAQ,OAAO,OAAO,MAAM,CAAC;AACrF,IAAM,aAAa;AAEnB,IAAM,wBAAwB;AAOvB,IAAM,aAAN,cAAyB,MAAM;AAAC;AAEhC,SAAS,KAAK,SAAwB;AAC3C,QAAM,IAAI,WAAW,OAAO;AAC9B;AAOA,SAAS,SAAS,MAAc,OAA6B;AAC3D,QAAM,QAAQ,MAAM,IAAI,CAAC,SAAS;AAChC,UAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,WAAO,EAAE,MAAM,SAAS,WAAW,IAAI,IAAI,aAAa,IAAI,IAAI,KAAK;AAAA,EACvE,CAAC;AACD,SAAO,MAAM;AACX,eAAW,EAAE,MAAM,QAAQ,KAAK,OAAO;AACrC,UAAI,YAAY,KAAM,QAAO,MAAM,EAAE,OAAO,KAAK,CAAC;AAAA,UAC7C,eAAc,MAAM,OAAO;AAAA,IAClC;AAAA,EACF;AACF;AAEA,IAAM,mBAAmB,CAAC,iBAAiB,iBAAiB,qBAAqB,kBAAkB,aAAa,UAAU;AAE1H,SAAS,SAAY,MAAiB;AACpC,MAAI;AACF,WAAO,KAAK,MAAM,aAAa,MAAM,MAAM,CAAC;AAAA,EAC9C,SAAS,OAAO;AACd,SAAK,kBAAkB,IAAI,KAAM,MAAgB,OAAO,EAAE;AAAA,EAC5D;AACF;AAEA,IAAM,YAAY,oBAAI,IAAI,CAAC,UAAU,UAAU,KAAK,CAAC;AACrD,IAAM,iBAAiB,CAAC,UACtB,CAAC,CAAC,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,KAAK,OAAO,OAAO,KAAK,EAAE,MAAM,CAAC,MAAM,OAAO,MAAM,QAAQ;AAG1H,SAAS,WAAW,IAAY,QAAgC;AAC9D,QAAM,EAAE,OAAO,SAAS,UAAU,iBAAiB,SAAS,IAAI;AAChE,MAAI,OAAO,UAAU,YAAY,CAAC,MAAM,WAAW,GAAG,KAAK,MAAM,WAAW,IAAI,KAAK,WAAW,KAAK,KAAK,KAAK,MAAM,MAAM,GAAG,EAAE,SAAS,IAAI,GAAG;AAC9I,SAAK,aAAa,EAAE,8BAA8B,OAAO,KAAK,CAAC,EAAE;AAAA,EACnE;AACA,MAAI,OAAO,YAAY,YAAY,SAAS;AAC1C,QAAI,YAAY,QAAQ,OAAO,aAAa,SAAU,MAAK,aAAa,EAAE,6BAA6B;AACvG,QAAI,OAAO,UAAU,QAAQ,CAAC,eAAe,OAAO,MAAM,EAAG,MAAK,aAAa,EAAE,2CAA2C;AAC5H,WAAO,EAAE,MAAM,QAAQ,OAAO,SAAS,UAAU,YAAY,MAAM,QAAQ,OAAO,UAAU,KAAK;AAAA,EACnG;AACA,MAAI,OAAO,oBAAoB,YAAY,iBAAiB;AAC1D,QAAI,YAAY,QAAQ,CAAC,UAAU,IAAI,QAAQ,EAAG,MAAK,aAAa,EAAE,sBAAsB,OAAO,QAAQ,CAAC,EAAE;AAC9G,QAAI,OAAO,YAAY,QAAQ,CAAC,eAAe,OAAO,QAAQ,EAAG,MAAK,aAAa,EAAE,mDAAmD;AACxI,WAAO,EAAE,MAAM,QAAQ,OAAO,iBAAiB,UAAU,YAAY,MAAM,UAAU,OAAO,YAAY,CAAC,EAAE;AAAA,EAC7G;AACA,OAAK,aAAa,EAAE,4FAA4F;AAClH;AAGO,SAAS,iBAAiB,MAAc,UAAqC;AAClF,MAAI,CAAC,YAAY,CAAC,MAAM,QAAQ,SAAS,UAAU,KAAK,SAAS,WAAW,WAAW,GAAG;AACxF,SAAK,kCAAkC;AAAA,EACzC;AACA,QAAM,OAAO,oBAAI,IAAY;AAC7B,SAAO,SAAS,WAAW,IAAI,CAAC,UAAU;AACxC,QAAI,CAAC,SAAS,OAAO,MAAM,OAAO,YAAY,CAAC,MAAM,GAAG,KAAK,EAAG,MAAK,4BAA4B;AACjG,QAAI,KAAK,IAAI,MAAM,EAAE,EAAG,MAAK,aAAa,MAAM,EAAE,kBAAkB;AACpE,SAAK,IAAI,MAAM,EAAE;AACjB,QAAI,MAAM,QAAQ,SAAS,OAAQ,QAAO,EAAE,IAAI,MAAM,IAAI,QAAQ,WAAW,MAAM,IAAI,MAAM,MAAM,EAAE;AACrG,UAAM,OAAO,MAAM,QAAQ;AAC3B,QAAI,OAAO,SAAS,YAAY,KAAK,WAAW,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,MAAM,GAAG,EAAE,SAAS,IAAI,GAAG;AAC7G,WAAK,aAAa,MAAM,EAAE,+BAA+B,OAAO,IAAI,CAAC,EAAE;AAAA,IACzE;AACA,QAAI,CAAC,qBAAqB,IAAI,QAAQ,IAAI,CAAC,EAAG,MAAK,aAAa,MAAM,EAAE,2BAA2B,QAAQ,IAAI,CAAC,EAAE;AAClH,QAAI,CAAC,WAAW,KAAK,MAAM,IAAI,CAAC,EAAG,MAAK,aAAa,MAAM,EAAE,KAAK,IAAI,iBAAiB;AACvF,UAAM,QAAQ,MAAM,OAAO;AAC3B,QAAI,UAAU,UAAa,UAAU,aAAa,CAAC,WAAW,KAAK,KAAK,GAAG;AACzE,WAAK,aAAa,MAAM,EAAE,aAAa,KAAK,6BAA6B;AAAA,IAC3E;AAGA,UAAM,OAAO,aAAa,KAAK,MAAM,IAAI,GAAG,MAAM,EAAE,MAAM,GAAG,GAAG;AAChE,UAAM,OAAmB,MAAM,OAAO,SAAS,aAAa,KAAK,SAAS,qBAAqB,IAAI,YAAY;AAC/G,WAAO,EAAE,IAAI,MAAM,IAAI,QAAQ,EAAE,MAAM,QAAQ,SAAS,WAAW,KAAK,EAAE;AAAA,EAC5E,CAAC;AACH;AAEA,SAAS,gBAAgB,MAAyB;AAChD,QAAM,MAAM,SAA8F,KAAK,MAAM,cAAc,CAAC;AACpI,QAAM,OAAO,EAAE,GAAG,IAAI,cAAc,GAAG,IAAI,gBAAgB;AAC3D,MAAI,KAAK,MAAO,QAAO;AACvB,MAAI,KAAK,KAAM,QAAO;AACtB,OAAK,4CAA4C;AACnD;AAEA,SAAS,IAAI,MAAc,SAAiB,MAAgB;AAC1D,QAAM,SAAS,UAAU,SAAS,MAAM,EAAE,KAAK,MAAM,OAAO,WAAW,KAAK,QAAQ,IAAI,CAAC;AACzF,MAAI,OAAO,WAAW,EAAG,OAAM,IAAI,MAAM,GAAG,OAAO,IAAI,KAAK,KAAK,GAAG,CAAC,gBAAgB,OAAO,MAAM,EAAE;AACtG;AAEA,SAAS,IAAI,MAAc,MAAsB;AAC/C,QAAM,QAAQ,KAAK,MAAM,gBAAgB,QAAQ,IAAI;AACrD,MAAI,CAAC,WAAW,KAAK,EAAG,MAAK,GAAG,IAAI,wBAAwB,IAAI,yCAAyC;AACzG,SAAO;AACT;AAGO,SAAS,eAAe,SAAiB,MAAc,SAAkC;AAC9F,QAAM,UAAoB,CAAC;AAC3B,QAAM,OAAiB,CAAC;AACxB,QAAM,QAAkB,CAAC;AACzB,QAAM,QAAkB,CAAC;AACzB,UAAQ,QAAQ,CAAC,OAAO,UAAU;AAChC,UAAM,SAAS,MAAM;AACrB,UAAM,KAAK,KAAK,KAAK,UAAU,MAAM,EAAE,CAAC,KAAK,KAAK,UAAU,OAAO,QAAQ,MAAM,CAAC,GAAG;AACrF,QAAI,OAAO,SAAS,QAAQ;AAC1B,YAAM,KAAK,KAAK,KAAK,UAAU,MAAM,EAAE,CAAC,KAAK,KAAK,UAAU,MAAM,CAAC,GAAG;AACtE;AAAA,IACF;AACA,QAAI,YAAY,SAAS,SAAS,KAAK,MAAM,OAAO,IAAI,CAAC,EAAE,MAAM,GAAG,EAAE,KAAK,GAAG;AAC9E,QAAI,CAAC,UAAU,WAAW,GAAG,EAAG,aAAY,KAAK,SAAS;AAE1D,QAAI,CAAC,QAAQ,OAAO,QAAQ,KAAK,EAAE,SAAS,QAAQ,SAAS,CAAC,EAAG,aAAY,UAAU,MAAM,GAAG,CAAC,QAAQ,SAAS,EAAE,MAAM;AAC1H,UAAM,QAAQ,YAAY,KAAK;AAC/B,YAAQ,KAAK,OAAO,WAAW,UAAa,OAAO,WAAW,YAC1D,UAAU,KAAK,SAAS,KAAK,UAAU,SAAS,CAAC,MACjD,YAAY,OAAO,MAAM,OAAO,KAAK,WAAW,KAAK,UAAU,SAAS,CAAC,GAAG;AAChF,SAAK,KAAK,KAAK,KAAK,UAAU,MAAM,EAAE,CAAC,KAAK,KAAK,GAAG;AAAA,EACtD,CAAC;AACD,SAAO,GAAG,QAAQ,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA,EAAuD,KAAK,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA,EAAgF,MAAM,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA,EAAwD,MAAM,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAC5Q;AAEA,SAAS,oBAAoB,KAAa;AACxC,gBAAc,KAAK,KAAK,YAAY,GAAG,aAAa,KAAK,MAAM,WAAW,GAAG,MAAM,CAAC;AACpF,QAAM,QAAQ,KAAK,MAAM,aAAa;AACtC,MAAI,WAAW,KAAK,EAAG,eAAc,KAAK,KAAK,cAAc,GAAG,aAAa,OAAO,MAAM,CAAC;AAC3F,gBAAc,KAAK,KAAK,UAAU,GAAG,kBAAkB,KAAK,UAAU,aAAa,KAAK,MAAM,iBAAiB,GAAG,MAAM,CAAC,CAAC;AAAA,CAAK;AAC/H,gBAAc,KAAK,KAAK,UAAU,GAAG,kBAAkB,KAAK,UAAU,aAAa,KAAK,MAAM,wBAAwB,GAAG,MAAM,CAAC,CAAC;AAAA,CAAK;AACxI;AAGA,SAAS,kBAAkB,MAAwB;AACjD,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,OAAO,CAAC,QAAgB;AAC5B,QAAI,CAAC,WAAW,GAAG,EAAG;AACtB,eAAW,QAAQ,YAAY,GAAG,GAAG;AACnC,YAAM,OAAO,KAAK,KAAK,IAAI;AAC3B,UAAI,SAAS,IAAI,EAAE,YAAY,EAAG,MAAK,IAAI;AAAA,eAClC,KAAK,SAAS,QAAQ,GAAG;AAChC,mBAAW,SAAS,aAAa,MAAM,MAAM,EAAE,SAAS,wCAAwC,GAAG;AACjG,gBAAM,SAAS,QAAQ,QAAQ,IAAI,GAAG,MAAM,CAAC,CAAE;AAC/C,cAAI,OAAO,WAAW,IAAI,KAAK,WAAW,MAAM,EAAG,OAAM,IAAI,MAAM;AAAA,QACrE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,OAAK,KAAK,MAAM,OAAO,SAAS,CAAC;AACjC,SAAO,CAAC,GAAG,KAAK;AAClB;AAEA,IAAM,qBAA6C,EAAE,KAAK,MAAM,KAAK,OAAO,KAAK,MAAM;AAEvF,SAAS,uBAAuB,MAAc;AAC5C,QAAMA,WAAU,cAAc,KAAK,MAAM,cAAc,CAAC;AACxD,MAAI;AACF,IAAAA,SAAQ,QAAQ,eAAe;AAC/B;AAAA,EACF,QAAQ;AAAA,EAER;AACA,QAAM,eAAe,SAA8BA,SAAQ,QAAQ,oBAAoB,CAAC,EAAE;AAC1F,QAAM,CAAC,OAAO,KAAK,IAAI,aAAa,MAAM,GAAG,EAAE,IAAI,MAAM;AAGzD,QAAM,QAAQ,UAAU,KAAK,QAAS,IAAI,qBAAqB,mBAAmB,OAAO,KAAK,CAAC;AAC/F,MAAI,CAAC,MAAO,MAAK,SAAS,YAAY,8CAA8C;AACpF,MAAI,MAAM,OAAO,CAAC,WAAW,aAAa,cAAc,aAAa,iBAAiB,KAAK,EAAE,CAAC;AAChG;AAEA,SAAS,WAAW,MAAc,SAA0B,KAAa;AACvE,QAAM,aAAa,CAAC,oBAAoB,mBAAmB,mBAAmB,kBAAkB,EAAE,KAAK,CAAC,MAAM,WAAW,KAAK,MAAM,CAAC,CAAC,CAAC;AACvI,MAAI,CAAC,WAAY,MAAK,4BAA4B;AAClD,yBAAuB,IAAI;AAE3B,QAAM,MAAM,KAAK,MAAM,eAAe;AACtC,QAAM,UAAU,KAAK,MAAM,+BAA+B;AAC1D,SAAO,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC5C,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAClC,MAAI;AACF,wBAAoB,GAAG;AACvB,kBAAc,KAAK,KAAK,aAAa,GAAG,eAAe,KAAK,MAAM,OAAO,CAAC;AAC1E,UAAM,UAAU,CAAC,QAAgB,SAC/B;AAAA,eAAiD,MAAM,MAAM,IAAI;AAAA;AACnE,kBAAc,KAAK,KAAK,YAAY,GAAG;AAAA;AAAA,EAAgF,QAAQ,OAAO,4BAA4B,CAAC,EAAE;AACrK,kBAAc,KAAK,KAAK,YAAY,GAAG;AAAA;AAAA,EAAqF,QAAQ,QAAQ,oEAAoE,CAAC,EAAE;AACnN,kBAAc,KAAK,KAAK,UAAU,GAAG;AAAA;AAAA,EAAmF,QAAQ,QAAQ,kEAAkE,CAAC,EAAE;AAC7M,kBAAc,KAAK,KAAK,WAAW,GAAG;AAAA,EAAiD,QAAQ,OAAO,sBAAsB,CAAC,EAAE;AAC/H,UAAM,SAAS,kBAAkB,IAAI,EAClC,IAAI,CAAC,SAAS,UAAU,KAAK,UAAU,SAAS,KAAK,IAAI,EAAE,MAAM,GAAG,EAAE,KAAK,GAAG,CAAC,CAAC,GAAG,EACnF,KAAK,IAAI;AACZ,kBAAc,KAAK,KAAK,cAAc,GAAG;AAAA,EAC3C,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAiBP;AACG,kBAAc,SAAS,uBAAuB,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,UAQlD,KAAK,UAAU,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAoBrC;AACG,WAAO,KAAK,MAAM,MAAM,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC3D,QAAI,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC,SAAS,YAAY,+BAA+B,CAAC;AAAA,EACtF,UAAE;AACA,WAAO,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC5C,WAAO,SAAS,EAAE,OAAO,KAAK,CAAC;AAAA,EACjC;AAEA,QAAM,MAAM,KAAK,KAAK,KAAK;AAC3B,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAClC,SAAO,KAAK,MAAM,MAAM,GAAG,KAAK,EAAE,WAAW,KAAK,CAAC;AACnD,SAAO,KAAK,MAAM,cAAc,GAAG,KAAK,KAAK,cAAc,CAAC;AAE5D,SAAO,KAAK,MAAM,cAAc,GAAG,KAAK,KAAK,cAAc,GAAG,EAAE,WAAW,MAAM,kBAAkB,KAAK,CAAC;AACzG,gBAAc,KAAK,KAAK,mBAAmB,GAAG,GAAG,KAAK,UAAU,EAAE,MAAM,QAAQ,KAAK,OAAO,OAAO,mBAAmB,CAAC,CAAC;AAAA,CAAI;AAC9H;AAEA,SAAS,UAAU,MAAc,SAA0B,KAAa;AACtE,QAAM,SAAS,CAAC,OAAO,KAAK,OAAO,KAAK,CAAC,EAAE,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,MAAM,WAAW,CAAC,CAAC;AAC9F,MAAI,CAAC,OAAQ,MAAK,kDAAkD;AACpE,QAAM,aAAa,CAAC,mBAAmB,kBAAkB,kBAAkB,iBAAiB,EAAE,KAAK,CAAC,MAAM,WAAW,KAAK,MAAM,CAAC,CAAC,CAAC;AAInI,QAAM,MAAM,KAAK,QAAQ,YAAY;AACrC,QAAM,aAAa,aAAa,KAAK,MAAM,WAAW,QAAQ,eAAe,uBAAuB,CAAC,IAAI;AACzG,QAAM,cAAc,cAAc,WAAW,SAAS,KAAK,IAAI,mBAAmB;AAClF,SAAO,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC5C,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAClC,MAAI,cAAc,WAAY,YAAW,KAAK,MAAM,UAAU,GAAG,UAAU;AAC3E,MAAI;AACF,wBAAoB,GAAG;AACvB,kBAAc,KAAK,KAAK,aAAa,GAAG,eAAe,KAAK,MAAM,OAAO,CAAC;AAC1E,UAAM,QAAQ,CAAC,MAAc,WAAmB;AAC9C,gBAAU,KAAK,KAAK,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC9C,oBAAc,KAAK,KAAK,MAAM,UAAU,GAAG;AAAA,EAA4C,MAAM,EAAE;AAAA,IACjG;AACA,UAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,CAA6I;AAC9J,UAAM,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA,CAA0K;AAC3L,UAAM,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,CAAsK;AACrL,UAAM,UAAU;AAAA;AAAA;AAAA;AAAA,CAAuG;AACvH,UAAM,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAkc;AACvd,cAAU,KAAK,KAAK,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAClD,kBAAc,KAAK,KAAK,UAAU,UAAU,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAwBlD;AACG,UAAM,aAAa,aAAa,uBAAuB,SAAS,MAAM,UAAU,CAAC,OAAO;AACxF,kBAAc,KAAK,MAAM,WAAW,GAAG,GAAG,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,gBAexC,KAAK,UAAU,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAO3C;AACG,WAAO,KAAK,MAAM,OAAO,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC5D,QAAI,MAAM,IAAI,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC;AAAA,EACxC,UAAE;AACA,WAAO,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC5C,WAAO,KAAK,MAAM,WAAW,GAAG,EAAE,OAAO,KAAK,CAAC;AAC/C,QAAI,cAAc,cAAc,WAAW,UAAU,EAAG,YAAW,YAAY,KAAK,MAAM,UAAU,CAAC;AAAA,EACvG;AAEA,QAAM,aAAa,KAAK,MAAM,SAAS,YAAY;AACnD,MAAI,CAAC,WAAW,KAAK,YAAY,WAAW,CAAC,EAAG,MAAK,0CAA0C;AAC/F,QAAM,MAAM,KAAK,KAAK,KAAK;AAC3B,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAClC,SAAO,YAAY,KAAK,EAAE,WAAW,MAAM,kBAAkB,KAAK,CAAC;AACnE,MAAI,WAAW,KAAK,MAAM,SAAS,QAAQ,CAAC,EAAG,QAAO,KAAK,MAAM,SAAS,QAAQ,GAAG,KAAK,KAAK,SAAS,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AACtI,MAAI,WAAW,KAAK,MAAM,QAAQ,CAAC,EAAG,QAAO,KAAK,MAAM,QAAQ,GAAG,KAAK,KAAK,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAC3G,gBAAc,KAAK,KAAK,mBAAmB,GAAG,GAAG,KAAK,UAAU,EAAE,MAAM,QAAQ,KAAK,OAAO,OAAO,YAAY,CAAC,CAAC;AAAA,CAAI;AACvH;AAEO,SAAS,oBAAoB,OAA4D;AAC9F,QAAM,OAAO,QAAQ,MAAM,IAAI;AAC/B,QAAM,MAAM,QAAQ,MAAM,GAAG;AAC7B,QAAM,UAAU,iBAAiB,MAAM,SAAmB,QAAQ,MAAM,YAAY,CAAC,CAAC;AACtF,QAAM,YAAY,gBAAgB,IAAI;AAEtC,SAAO,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAC5C,YAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAClC,QAAM,UAAU,SAAS,MAAM,gBAAgB;AAC/C,MAAI;AACF,QAAI,cAAc,QAAS,YAAW,MAAM,SAAS,GAAG;AAAA,QACnD,WAAU,MAAM,SAAS,GAAG;AAAA,EACnC,UAAE;AACA,YAAQ;AAAA,EACV;AACA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,YAAY,QAAQ,IAAI,CAAC,MAAM,EAAE,EAAE;AAAA,IACnC,OAAO,OAAO,YAAY,QAAQ,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,OAAO,QAAQ,MAAM,CAAC,CAAC;AAAA,EAC/E;AACF;;;ACjaA,SAAS,OAAO,aAAAC,kBAAoC;AACpD,SAAS,YAAY,mBAAmB;AACxC,SAAS,aAAa,gBAAAC,eAAc,UAAAC,SAAQ,iBAAAC,sBAAqB;AACjE,SAAS,iBAAAC,sBAAqB;AAC9B,SAAS,oBAAoB;AAC7B,SAAS,cAAc;AACvB,SAAS,WAAAC,UAAS,QAAAC,aAAY;AAG9B,IAAM,qBAAqB;AAC3B,IAAM,eAAe,GAAG,kBAAkB;AA6B1C,IAAM,oBAAN,cAAgC,MAAM;AAAA,EACpC,YAAqB,MAAc,SAAiB;AAClD,UAAM,OAAO;AADM;AAEnB,SAAK,OAAO;AAAA,EACd;AAAA,EAHqB;AAIvB;AAEA,SAAS,SAAS,MAAsB;AACtC,QAAM,QAAQ,QAAQ,IAAI,IAAI,GAAG,KAAK;AACtC,MAAI,CAAC,MAAO,OAAM,IAAI,WAAW,GAAG,IAAI,oFAAoF;AAC5H,SAAO;AACT;AAEA,IAAM,SAAS,CAAC,SAA0B,WAAW,QAAQ,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AAExF,SAAS,UAAU,OAAwB;AACzC,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,IAAI,MAAM,IAAI,SAAS,EAAE,KAAK,GAAG,CAAC;AACnE,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,WAAO,IAAI,OAAO,KAAK,KAAgC,EAAE,KAAK,EAC3D,IAAI,CAAC,QAAQ,GAAG,KAAK,UAAU,GAAG,CAAC,IAAI,UAAW,MAAkC,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,GAAG,CAAC;AAAA,EAC3G;AACA,SAAO,KAAK,UAAU,KAAK;AAC7B;AAEA,SAAS,WAA4B;AACnC,SAAO,IAAI,QAAQ,CAACC,UAAS,WAAW;AACtC,UAAM,SAAS,aAAa;AAC5B,WAAO,KAAK,SAAS,MAAM;AAC3B,WAAO,OAAO,GAAG,aAAa,MAAM;AAClC,YAAM,UAAU,OAAO,QAAQ;AAC/B,YAAM,OAAO,OAAO,YAAY,YAAY,UAAU,QAAQ,OAAO;AACrE,aAAO,MAAM,MAAMA,SAAQ,IAAI,CAAC;AAAA,IAClC,CAAC;AAAA,EACH,CAAC;AACH;AAEA,eAAe,UAAU,KAAa,WAAqC;AACzE,QAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,SAAO,KAAK,IAAI,IAAI,UAAU;AAC5B,QAAI;AACF,WAAK,MAAM,MAAM,GAAG,GAAG,GAAI,QAAO;AAAA,IACpC,QAAQ;AAAA,IAER;AACA,UAAM,IAAI,QAAQ,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC;AAAA,EAC7C;AACA,SAAO;AACT;AAGA,SAAS,gBAAgB;AACvB,QAAMC,WAAUC,eAAc,YAAY,GAAG;AAC7C,QAAM,MAAMC,MAAKC,SAAQH,SAAQ,QAAQ,yBAAyB,CAAC,GAAG,QAAQ;AAC9E,QAAM,OAAO,CAAC,KAAK,WAAW,UAAU;AACxC,MAAI,QAAQ,aAAa,WAAW,QAAQ,IAAI,GAAI,MAAK,KAAK,aAAa;AAC3E,QAAM,SAASI,WAAU,QAAQ,UAAU,MAAM,EAAE,OAAO,UAAU,CAAC;AACrE,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,IAAI,kBAAkB,4CAA4C,2DAA2D;AAAA,EACrI;AACF;AAEA,eAAe,gBAAgB,KAAa,WAAuB,YAAsB,SAA+B;AACtH,gBAAc;AACd,QAAM,EAAE,SAAS,IAAI,MAAM,OAAO,YAAY;AAC9C,QAAM,UAAU,MAAM,SAAS,OAAO;AACtC,MAAI;AACF,QAAI,gBAAgB;AACpB,QAAI,gBAAgB;AACpB,UAAM,gBAAgB,oBAAI,IAAY;AACtC,UAAM,UAAkH,CAAC;AACzH,UAAM,WAAqB,CAAC;AAC5B,eAAW,YAAY,WAAW;AAChC,YAAM,OAAO,MAAM,QAAQ,QAAQ,EAAE,UAAU,EAAE,OAAO,SAAS,OAAO,QAAQ,SAAS,OAAO,EAAE,CAAC;AACnG,WAAK,GAAG,WAAW,CAAC,YAAY;AAC9B,YAAI,QAAQ,KAAK,MAAM,SAAS;AAC9B,2BAAiB;AACjB,mBAAS,KAAK,GAAG,SAAS,IAAI,aAAa,QAAQ,KAAK,EAAE,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,QAC3E;AAAA,MACF,CAAC;AACD,WAAK,GAAG,aAAa,CAAC,UAAU;AAC9B,yBAAiB;AACjB,iBAAS,KAAK,GAAG,SAAS,IAAI,WAAW,MAAM,QAAQ,MAAM,GAAG,GAAG,CAAC,EAAE;AAAA,MACxE,CAAC;AACD,YAAM,WAAW,MAAM,KAAK,KAAK,KAAK,EAAE,WAAW,QAAQ,SAAS,KAAO,CAAC;AAC5E,YAAM,KAAK,iBAAiB,eAAe,EAAE,SAAS,KAAO,CAAC,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAC9E,YAAM,WAAW,MAAM,KAAK,QAAQ,4BAA4B,EAAE,MAAM;AACxE,UAAI,CAAC,UAAU,GAAG,KAAK,aAAa,GAAG;AACrC,yBAAiB;AACjB,iBAAS,KAAK,GAAG,SAAS,IAAI,6CAA6C,UAAU,OAAO,KAAK,MAAM,GAAG;AAAA,MAC5G,WAAW,QAAQ,WAAY,MAAM,KAAK,QAAQ,mBAAmB,EAAE,MAAM,MAAO,GAAG;AAGrF,yBAAiB;AACjB,iBAAS,KAAK,GAAG,SAAS,IAAI,2DAA2D;AAAA,MAC3F;AASA,YAAM,UAAU,MAAM,KAAK,SAAS,CAAC,UAAoB;AACvD,cAAM,aAAa,oBAAI,IAAY;AACnC,cAAM,OAAO,CAAC,SAAiB;AAC7B,qBAAW,SAAS,KAAK,SAAS,6BAA6B,EAAG,YAAW,IAAI,MAAM,CAAC,CAAE;AAAA,QAC5F;AACA,mBAAW,SAAS,MAAM,KAAK,SAAS,WAAW,GAAG;AACpD,cAAI;AACF,uBAAW,QAAQ,MAAM,KAAK,MAAM,QAAQ,EAAG,MAAK,KAAK,OAAO;AAAA,UAClE,QAAQ;AAAA,UAER;AAAA,QACF;AACA,iBAAS,iBAAiB,SAAS,EAAE,QAAQ,CAAC,YAAY,KAAK,QAAQ,aAAa,OAAO,KAAK,EAAE,CAAC;AACnG,cAAM,QAAQ,iBAAiB,SAAS,eAAe;AACvD,eAAO,MAAM,OAAO,CAAC,SAAS,WAAW,IAAI,IAAI,KAAK,CAAC,MAAM,iBAAiB,IAAI,EAAE,KAAK,CAAC;AAAA,MAC5F,GAAG,UAAU;AACb,iBAAW,SAAS,QAAS,eAAc,IAAI,KAAK;AACpD,YAAM,MAAM,MAAM,KAAK,WAAW,EAAE,UAAU,KAAK,CAAC;AACpD,cAAQ,KAAK;AAAA,QACX,MAAM,SAAS;AAAA,QACf,OAAO,SAAS;AAAA,QAChB,QAAQ,SAAS;AAAA;AAAA;AAAA,QAGjB,QAAQ;AAAA,QACR,iBAAiB,UAAU,OAAO,GAAG,CAAC;AAAA,MACxC,CAAC;AACD,YAAM,KAAK,MAAM;AAAA,IACnB;AACA,WAAO,EAAE,SAAS,eAAe,eAAe,eAAe,CAAC,GAAG,aAAa,EAAE,KAAK,GAAG,SAAS;AAAA,EACrG,UAAE;AACA,UAAM,QAAQ,MAAM;AAAA,EACtB;AACF;AAEA,eAAsB,oBAAoB;AACxC,QAAM,SAAS,SAAS,cAAc;AACtC,QAAM,SAAS,SAAS,cAAc;AACtC,QAAM,YAAY,SAAS,iBAAiB;AAC5C,QAAM,YAAY,SAAS,iBAAiB;AAC5C,QAAM,cAAc,SAAS,mBAAmB;AAChD,QAAM,YAAY,SAAS,iBAAiB;AAC5C,QAAM,YAAY,SAAS,iBAAiB;AAC5C,QAAM,gBAAgB,SAAS,qBAAqB;AACpD,QAAM,kBAAkB,KAAK,MAAM,SAAS,uBAAuB,CAAC;AAEpE,QAAM,MAAM,OAAU,MAAc,OAA4D,CAAC,MAAkB;AACjH,UAAM,WAAW,MAAM,MAAM,IAAI,IAAI,MAAM,MAAM,GAAG;AAAA,MAClD,QAAQ,KAAK,UAAU;AAAA,MACvB,SAAS;AAAA,QACP,eAAe,UAAU,MAAM;AAAA,QAC/B,gBAAgB;AAAA,QAChB,GAAI,KAAK,QAAQ,EAAE,0BAA0B,KAAK,MAAM,IAAI,CAAC;AAAA,MAC/D;AAAA,MACA,GAAI,KAAK,SAAS,SAAY,CAAC,IAAI,EAAE,MAAM,KAAK,UAAU,KAAK,IAAI,EAAE;AAAA,IACvE,CAAC;AACD,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,SAAwE;AAC5E,QAAI;AACF,eAAS,KAAK,MAAM,IAAI;AAAA,IAC1B,QAAQ;AACN,eAAS;AAAA,IACX;AACA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,OAAO,OAAO,QAAQ,UAAU,YAAY,oBAAoB,KAAK,OAAO,KAAK,IAAI,OAAO,QAAQ,YAAY,SAAS,MAAM;AACrI,YAAM,SAAS,OAAO,QAAQ,YAAY,WAAW,OAAO,UAAU,OAAO,QAAQ,UAAU,WAAW,OAAO,QAAQ,KAAK,MAAM,GAAG,GAAG;AAC1I,YAAM,IAAI,kBAAkB,MAAM,GAAG,KAAK,UAAU,KAAK,IAAI,IAAI,aAAa,SAAS,MAAM,KAAK,MAAM,EAAE;AAAA,IAC5G;AACA,YAAS,UAAU,UAAU,SAAS,OAAO,OAAO,WAAW,CAAC;AAAA,EAClE;AAEA,QAAM,YAAY,oBAAoB,SAAS,qDAAqD,SAAS;AAC7G,MAAI,QAAoE;AAOxE,QAAM,eAAe,YAAY;AAC/B,YAAQ,MAAM,IAAyD,GAAG,SAAS,UAAU;AAAA,MAC3F,QAAQ;AAAA,MACR,MAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA,SAAS,EAAE,UAAU,6BAA6B,MAAM,iBAAiB,MAAM,cAAc,WAAW,eAAe,gBAAgB;AAAA,QACvI,eAAe,SAAS,aAAa;AAAA,QACrC,oBAAoB,OAAO,QAAQ,IAAI,gBAAgB,KAAK;AAAA,QAC5D,gBAAgB,SAAS,cAAc;AAAA,QACvC,aAAa,SAAS,mBAAmB;AAAA;AAAA,QAEzC,qBAAqB,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,MAAS,GAAK,EAAE,YAAY;AAAA,MAC9E;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAEA,QAAM,OAAO,YAAYF,MAAK,OAAO,GAAG,gBAAgB,CAAC;AACzD,MAAI,UAA+B;AACnC,MAAI;AAEF,UAAM,WAAW,MAAM,IAAc,oBAAoB,SAAS,wCAAwC;AAC1G,UAAM,QAAQ,SAAS,WAAW,KAAK,CAAC,MAAM,EAAE,OAAO,WAAW;AAClE,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;AAAA,QACR;AAAA,QACA;AAAA,MACF;AAAA,IACF;AAEA,UAAM,eAAeA,MAAK,MAAM,eAAe;AAC/C,IAAAG,eAAc,cAAc,KAAK,UAAU,EAAE,YAAY,SAAS,WAAW,IAAI,CAAC,EAAE,IAAAC,KAAI,QAAQ,SAAS,OAAO,EAAE,IAAAA,KAAI,QAAQ,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC,EAAG,EAAE,EAAE,CAAC,CAAC;AACtK,UAAM,MAAMJ,MAAK,MAAM,SAAS;AAChC,QAAI;AACJ,QAAI;AACF,cAAQ,oBAAoB,EAAE,MAAM,QAAQ,IAAI,GAAG,cAAc,IAAI,CAAC;AAAA,IACxE,SAAS,OAAO;AACd,YAAM,IAAI,kBAAkB,kCAAkC,6BAA8B,MAAgB,OAAO,EAAE;AAAA,IACvH;AAEA,UAAM,kBAAkB,KAAK,MAAMK,cAAaL,MAAK,KAAK,mBAAmB,GAAG,MAAM,CAAC;AACvF,UAAM,OAAO,MAAM,SAAS;AAC5B,UAAM,eAAe,YAAY,EAAE,EAAE,SAAS,WAAW;AACzD,UAAM,QAAQ,oBAAoB,IAAI;AAEtC,UAAM,EAAE,cAAc,WAAW,GAAG,UAAU,IAAI,QAAQ;AAC1D,cAAU,MAAM,QAAQ,UAAU,CAACA,MAAK,KAAK,gBAAgB,KAAK,gBAAgB,KAAK,CAAC,GAAG;AAAA,MACzF,KAAKA,MAAK,KAAK,gBAAgB,GAAG;AAAA,MAClC,KAAK;AAAA,QACH,GAAG;AAAA,QACH,UAAU;AAAA,QACV,MAAM,OAAO,IAAI;AAAA,QACjB,MAAM;AAAA,QACN,UAAU;AAAA,QACV,cAAc;AAAA,QACd,uBAAuB;AAAA,QACvB,qBAAqB;AAAA,QACrB,4BAA4B;AAAA,MAC9B;AAAA,MACA,OAAO,CAAC,UAAU,QAAQ,MAAM;AAAA,IAClC,CAAC;AAMD,UAAM,eAAyB,CAAC;AAChC,UAAM,UAAU,CAAC,WAA+B,CAAC,UAAkB;AACjE,aAAO,MAAM,KAAK;AAClB,iBAAW,QAAQ,MAAM,SAAS,MAAM,EAAE,MAAM,IAAI,GAAG;AACrD,cAAM,QAAQ,KAAK,QAAQ,mBAAmB,EAAE,EAAE,KAAK;AACvD,YAAI,aAAa,SAAS,KAAK,yBAAyB,KAAK,KAAK,EAAG,cAAa,KAAK,MAAM,MAAM,GAAG,GAAG,CAAC;AAAA,MAC5G;AAAA,IACF;AACA,YAAQ,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,MAAM,CAAC;AAClD,YAAQ,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,MAAM,CAAC;AAClD,QAAI,CAAE,MAAM,UAAU,GAAG,KAAK,GAAG,kBAAkB,WAAW,GAAM,GAAI;AACtE,YAAM,IAAI,kBAAkB,2CAA2C,sDAAsD;AAAA,IAC/H;AAEA,UAAM,SAAS,MAAM,MAAM,GAAG,KAAK,GAAG,kBAAkB,UAAU;AAAA,MAChE,QAAQ;AAAA,MACR,SAAS,EAAE,gBAAgB,oBAAoB,wBAAwB,aAAa;AAAA,MACpF,MAAM,KAAK,UAAU,EAAE,aAAa,OAAO,MAAM,aAAa,CAAC;AAAA,IACjE,CAAC;AACD,QAAI,CAAC,OAAO,IAAI;AACd,YAAM,IAAI,kBAAkB,qCAAqC,mDAAmD,OAAO,MAAM,IAAI;AAAA,IACvI;AACA,UAAM,EAAE,GAAG,IAAK,MAAM,OAAO,KAAK;AAGlC,UAAM,SAAS,MAAM;AAAA,MACnB,GAAG,KAAK,GAAG,kBAAkB,cAAc,mBAAmB,EAAE,CAAC;AAAA,MACjE;AAAA,MACA,SAAS;AAAA;AAAA,MAET,EAAE,SAAS,MAAM,MAAM,WAAW,MAAM,OAAO;AAAA,IACjD;AAEA,UAAM,UAAUA,MAAK,MAAM,YAAY;AACvC,QAAIE,WAAU,OAAO,CAAC,QAAQ,SAAS,MAAM,KAAK,GAAG,GAAG,EAAE,OAAO,UAAU,CAAC,EAAE,WAAW,GAAG;AAC1F,YAAM,IAAI,kBAAkB,wCAAwC,4CAA4C;AAAA,IAClH;AACA,UAAM,QAAQG,cAAa,OAAO;AAClC,YAAQ,KAAK;AACb,cAAU;AAGV,UAAM,OAAO,MAAM,YAAY,MAAM,MAAM,WAAW,MAAM,SACxD,CAAC,+DAA+D,MAAM,SAAS,KAAK,eAAe,IACnG,CAAC;AAEL,UAAM,UAAU,MAAM,aAAa,GAAG;AACtC,UAAM,SAAS;AAAA,MACb,UAAU;AAAA,QACR,QAAQ,OAAO,cAAc,WAAW,IAAI,WAAW;AAAA,QACvD,cAAc,OAAO;AAAA,QACrB,eAAe,OAAO;AAAA,MACxB;AAAA,MACA,SAAS;AAAA,QACP,QAAQ,OAAO,kBAAkB,KAAK,OAAO,kBAAkB,IAAI,WAAW;AAAA,QAC9E,eAAe,OAAO;AAAA,QACtB,eAAe,OAAO;AAAA;AAAA;AAAA,QAGtB,GAAI,OAAO,gBAAgB,OAAO,gBAAgB,IAC9C,EAAE,UAAU,CAAC,GAAG,MAAM,GAAG,aAAa,IAAI,CAAC,SAAS,WAAW,IAAI,EAAE,GAAG,GAAG,OAAO,QAAQ,EAAE,MAAM,GAAG,EAAE,EAAE,IAAI,CAAC,SAAS,KAAK,MAAM,GAAG,GAAG,CAAC,EAAE,IAC3I,CAAC;AAAA,MACP;AAAA,MACA,QAAQ,EAAE,QAAQ,oBAAoB,gBAAgB,MAAM,WAAW,OAAO,QAAQ;AAAA,IACxF;AAGA,UAAM,SAAS,MAAM,IAA8C,oBAAoB,SAAS,yBAAyB,EAAE,QAAQ,OAAO,CAAC;AAC3I,UAAM,MAAM,MAAM,MAAM,OAAO,WAAW,EAAE,QAAQ,OAAO,MAAM,OAAO,SAAS,EAAE,gBAAgB,mBAAmB,EAAE,CAAC;AACzH,QAAI,CAAC,IAAI,IAAI;AACX,YAAM,IAAI,kBAAkB,0CAA0C,4CAA4C,IAAI,MAAM,IAAI;AAAA,IAClI;AACA,UAAM,IAAI,oBAAoB,SAAS,8BAA8B;AAAA,MACnE,QAAQ;AAAA,MACR,MAAM,EAAE,WAAW,OAAO,WAAW,WAAW,OAAO,WAAW,UAAU,UAAU,OAAO,KAAK,CAAC,IAAI,WAAW,MAAM,WAAW;AAAA,IACrI,CAAC;AAED,UAAM,iBAAiB,UAAU,OAAO,UAAU,EAAE,WAAW,WAAW,OAAO,WAAW,OAAO,CAAC,CAAC,CAAC;AACtG,UAAM,IAAI,GAAG,SAAS,aAAa;AAAA,MACjC,QAAQ;AAAA,MACR,OAAO,MAAO;AAAA,MACd,MAAM;AAAA,QACJ,aAAa,OAAO;AAAA,QACpB,aAAa,OAAO;AAAA,QACpB,kBAAkB,OAAO;AAAA,QACzB,WAAW,OAAO;AAAA,QAClB,oBAAoB,OAAO;AAAA,QAC3B,YAAY,OAAO;AAAA,QACnB,mBAAmB,OAAO;AAAA,QAC1B,kBAAkB,OAAO;AAAA,QACzB,WAAW,OAAO;AAAA,QAClB,aAAa,OAAO;AAAA,QACpB,oBAAoB,OAAO;AAAA,QAC3B,iBAAiB,OAAO;AAAA,QACxB;AAAA,QACA;AAAA,MACF;AAAA,IACF,CAAC;AAED,UAAM,SAAS,OAAO,SAAS,WAAW,YAAY,OAAO,QAAQ,WAAW;AAChF,YAAQ,IAAI,4BAA4B,SAAS,WAAW,QAAQ,QAAQ,WAAW,EAAE;AACzF,QAAI,CAAC,QAAQ;AACX,UAAI,OAAO,cAAc,OAAQ,SAAQ,IAAI,wCAAwC,OAAO,cAAc,KAAK,IAAI,CAAC,EAAE;AACtH,iBAAW,WAAW,CAAC,GAAG,MAAM,GAAG,OAAO,QAAQ,EAAG,SAAQ,IAAI,KAAK,OAAO,EAAE;AAAA,IACjF;AAAA,EACF,SAAS,OAAO;AACd,UAAM,OAAO,iBAAiB,oBAAoB,MAAM,OAAO;AAC/D,UAAM,UAAW,MAAgB,QAAQ,MAAM,GAAG,GAAI;AAGtD,UAAM,WAAW,SAAS,MAAM,aAAa,EAAE,MAAM,CAAC,eAAe;AACnE,cAAQ,MAAM,oEAAqE,WAAqB,OAAO,EAAE;AACjH,aAAO;AAAA,IACT,CAAC;AACD,QAAI,UAAU;AACZ,YAAM,IAAI,GAAG,SAAS,SAAS;AAAA,QAC7B,QAAQ;AAAA,QACR,OAAO,SAAS;AAAA,QAChB,MAAM,EAAE,aAAa,WAAW,MAAM,cAAc,QAAQ;AAAA,MAC9D,CAAC,EAAE,MAAM,CAAC,gBAAgB,QAAQ,MAAM,+CAAgD,YAAsB,OAAO,EAAE,CAAC;AAAA,IAC1H;AACA,UAAM,IAAI,WAAW,GAAG,IAAI,KAAK,OAAO,EAAE;AAAA,EAC5C,UAAE;AACA,aAAS,KAAK;AACd,IAAAC,QAAO,MAAM,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAC/C;AACF;;;AClaA,SAAS,IAAI,MAAkC;AAC7C,QAAM,QAAQ,QAAQ,KAAK,QAAQ,KAAK,IAAI,EAAE;AAC9C,SAAO,UAAU,KAAK,SAAY,QAAQ,KAAK,QAAQ,CAAC;AAC1D;AAEA,eAAe,OAAO;AACpB,QAAM,UAAU,QAAQ,KAAK,CAAC;AAC9B,MAAI,YAAY,SAAS;AACvB,UAAM,eAAe,IAAI,UAAU;AACnC,UAAM,MAAM,IAAI,KAAK;AACrB,QAAI,CAAC,gBAAgB,CAAC,IAAK,OAAM,IAAI,WAAW,uEAAuE;AACvH,YAAQ,IAAI,KAAK,UAAU,oBAAoB,EAAE,MAAM,IAAI,KAAK,KAAK,QAAQ,IAAI,GAAG,cAAc,IAAI,CAAC,CAAC,CAAC;AACzG;AAAA,EACF;AACA,MAAI,YAAY,YAAY;AAC1B,UAAM,kBAAkB;AACxB;AAAA,EACF;AACA,QAAM,IAAI,WAAW,sCAAsC;AAC7D;AAMA,KAAK,EAAE,MAAM,CAAC,UAAU;AACtB,UAAQ,MAAM,iBAAiB,iBAAiB,aAAa,MAAM,UAAW,MAAgB,SAAS,OAAO,KAAK,CAAC,EAAE;AACtH,UAAQ,WAAW;AACrB,CAAC;","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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bettercms-ai/preview-runtime",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Zero-config BetterCMS component previews: builds a preview runtime for Next.js and Astro apps and validates components in CI.",
5
5
  "type": "module",
6
6
  "bin": {