@emeryld/rrroutes-export 1.0.13 → 1.0.15

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
@@ -9,6 +9,8 @@ Export helpers and CLI for RRRoutes finalized leaves.
9
9
  - `writeFinalizedLeavesExport`
10
10
  - `exportFinalizedLeavesChangelog`
11
11
  - `writeFinalizedLeavesChangelogExport`
12
+ - `buildFinalizedLeavesViewerBundle`
13
+ - `writeFinalizedLeavesViewerBundle`
12
14
  - CLI:
13
15
  - `rrroutes-export-finalized-leaves`
14
16
  - `rrroutes-export-changelog`
@@ -134,6 +136,8 @@ Per run, it writes a timestamped directory:
134
136
  - `finalized-leaves.viewer.html`
135
137
  - `finalized-leaves.changelog.json`
136
138
  - `finalized-leaves.changelog.viewer.html`
139
+ - `finalized-leaves.bundle.json`
140
+ - `finalized-leaves.bundle.viewer.html`
137
141
  - `summary.json`
138
142
 
139
143
  This is the recommended CI/release flow because outputs are immutable per run and easy to archive.
@@ -142,8 +146,10 @@ This is the recommended CI/release flow because outputs are immutable per run an
142
146
 
143
147
  ```ts
144
148
  import {
149
+ buildFinalizedLeavesViewerBundle,
145
150
  exportFinalizedLeaves,
146
151
  exportFinalizedLeavesChangelog,
152
+ writeFinalizedLeavesViewerBundle,
147
153
  } from '@emeryld/rrroutes-export'
148
154
 
149
155
  const snapshot = await exportFinalizedLeaves(leaves, {
@@ -165,4 +171,14 @@ const changelog = await exportFinalizedLeavesChangelog({
165
171
  htmlFile: './finalized-leaves.changelog.viewer.html',
166
172
  log: (message) => console.log(message),
167
173
  })
174
+
175
+ const unified = buildFinalizedLeavesViewerBundle({
176
+ leavesPayload: snapshot,
177
+ changelogPayload: changelog,
178
+ })
179
+
180
+ await writeFinalizedLeavesViewerBundle(unified, {
181
+ outFile: './finalized-leaves.bundle.json',
182
+ htmlFile: './finalized-leaves.bundle.viewer.html',
183
+ })
168
184
  ```
@@ -1 +1 @@
1
- export declare const DEFAULT_VIEWER_TEMPLATE = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>Finalized Leaves Viewer</title>\n <style>\n :root {\n --bg: #212121;\n --surface: #2a2a2a;\n --border: #4a4a4a;\n --text: #fffafa;\n --muted: #c8c2c2;\n --accent: #a764d3;\n --schema-accent: #fbbd23;\n }\n body {\n margin: 0;\n font-family: 'Iosevka Web', 'SFMono-Regular', Menlo, Consolas, monospace;\n color: var(--text);\n background: var(--bg);\n }\n a { color: var(--schema-accent); }\n .wrap { max-width: 1100px; margin: 0 auto; padding: 20px; }\n .card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px; }\n .meta { color: var(--muted); font-size: 12px; }\n #results { margin-top: 12px; display: grid; gap: 8px; }\n details { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 8px 10px; }\n summary { cursor: pointer; font-weight: 700; color: var(--accent); }\n pre { margin: 10px 0 0; overflow: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px; background: #303030; color: var(--text); }\n </style>\n </head>\n <body>\n <div class=\"wrap\">\n <h1>Finalized Leaves Viewer (Baked)</h1>\n <div class=\"card\">\n <div id=\"status\" class=\"meta\">Waiting for baked payload...</div>\n </div>\n <div id=\"results\"></div>\n </div>\n\n <!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->\n\n <script>\n const statusEl = document.getElementById('status')\n const resultsEl = document.getElementById('results')\n const payload = window.__FINALIZED_LEAVES_PAYLOAD\n\n if (payload && payload.kind === 'finalized-leaves-changelog') {\n statusEl.textContent =\n 'Loaded changelog payload with ' + (payload.commits?.length || 0) + ' commits.'\n\n const wrapSection = (title, value) => {\n const card = document.createElement('details')\n const summary = document.createElement('summary')\n summary.textContent = title\n const pre = document.createElement('pre')\n pre.textContent = JSON.stringify(value, null, 2)\n card.appendChild(summary)\n card.appendChild(pre)\n return card\n }\n\n resultsEl.appendChild(wrapSection('Commits', payload.commits || []))\n resultsEl.appendChild(wrapSection('By Route', payload.byRoute || {}))\n resultsEl.appendChild(\n wrapSection('By Source Object', payload.bySourceObject || {}),\n )\n } else if (!payload || !Array.isArray(payload.leaves)) {\n statusEl.textContent = 'No baked payload found in this HTML file.'\n } else {\n statusEl.textContent = 'Loaded baked payload with ' + payload.leaves.length + ' routes.'\n\n const toHref = (source) => {\n if (!source || !source.file) return null\n const normalizedPath = String(source.file).replace(/\\\\/g, '/')\n const platform =\n (navigator.userAgentData && navigator.userAgentData.platform) ||\n navigator.platform ||\n ''\n const isWindows = /win/i.test(platform)\n const vscodePath = isWindows\n ? normalizedPath.replace(/^\\/+/, '')\n : normalizedPath.startsWith('/')\n ? normalizedPath\n : '/' + normalizedPath\n const line = Number.isFinite(source.line) ? source.line : 1\n const column = Number.isFinite(source.column) ? source.column : 1\n return 'vscode://file/' + encodeURI(vscodePath) + ':' + line + ':' + column\n }\n\n const sourceDisplay = (source) => {\n return ''\n }\n\n payload.leaves.forEach((leaf) => {\n const details = document.createElement('details')\n const summary = document.createElement('summary')\n summary.textContent = String(leaf.method || '').toUpperCase() + ' ' + (leaf.path || '')\n\n const pre = document.createElement('pre')\n pre.textContent = JSON.stringify(leaf, null, 2)\n\n const source = payload.sourceByLeaf && payload.sourceByLeaf[leaf.key]\n let sourceWrap = null\n if (source) {\n sourceWrap = document.createElement('div')\n sourceWrap.className = 'meta'\n\n const definitionHref = toHref(source.definition)\n if (definitionHref) {\n const label = document.createElement('div')\n const link = document.createElement('a')\n link.href = definitionHref\n link.target = '_blank'\n link.rel = 'noopener noreferrer'\n link.textContent = 'definition'\n label.appendChild(link)\n sourceWrap.appendChild(label)\n }\n\n if (source.schemas && typeof source.schemas === 'object') {\n Object.entries(source.schemas).forEach(([name, schema]) => {\n if (!schema) return\n const href = toHref(schema)\n const row = document.createElement('div')\n if (href) {\n const link = document.createElement('a')\n link.href = href\n link.target = '_blank'\n link.rel = 'noopener noreferrer'\n link.textContent =\n name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')\n row.appendChild(link)\n } else {\n row.textContent = name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')\n }\n sourceWrap.appendChild(row)\n })\n }\n\n }\n\n details.appendChild(summary)\n if (sourceWrap) details.appendChild(sourceWrap)\n details.appendChild(pre)\n resultsEl.appendChild(details)\n })\n }\n </script>\n </body>\n</html>\n";
1
+ export declare const DEFAULT_VIEWER_TEMPLATE = "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>RRRoutes Export Viewer</title>\n <style>\n :root {\n --bg: #212121;\n --surface: #2a2a2a;\n --border: #4a4a4a;\n --text: #fffafa;\n --muted: #c8c2c2;\n --accent: #a764d3;\n --schema-accent: #fbbd23;\n }\n body {\n margin: 0;\n font-family: 'Iosevka Web', 'SFMono-Regular', Menlo, Consolas, monospace;\n color: var(--text);\n background: var(--bg);\n }\n a { color: var(--schema-accent); }\n .wrap { max-width: 1100px; margin: 0 auto; padding: 20px; }\n .card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 14px; }\n .meta { color: var(--muted); font-size: 12px; }\n #results { margin-top: 12px; display: grid; gap: 8px; }\n details { background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 8px 10px; }\n summary { cursor: pointer; font-weight: 700; color: var(--accent); }\n pre { margin: 10px 0 0; overflow: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px; background: #303030; color: var(--text); }\n </style>\n </head>\n <body>\n <div class=\"wrap\">\n <h1>RRRoutes Export Viewer (Baked)</h1>\n <div class=\"card\">\n <div id=\"status\" class=\"meta\">Waiting for baked payload...</div>\n </div>\n <div id=\"results\"></div>\n </div>\n\n <!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->\n\n <script>\n const statusEl = document.getElementById('status')\n const resultsEl = document.getElementById('results')\n const payload = window.__FINALIZED_LEAVES_PAYLOAD\n\n if (payload && payload.kind === 'finalized-leaves-changelog') {\n statusEl.textContent =\n 'Loaded changelog payload with ' + (payload.commits?.length || 0) + ' commits.'\n\n const wrapSection = (title, value) => {\n const card = document.createElement('details')\n const summary = document.createElement('summary')\n summary.textContent = title\n const pre = document.createElement('pre')\n pre.textContent = JSON.stringify(value, null, 2)\n card.appendChild(summary)\n card.appendChild(pre)\n return card\n }\n\n resultsEl.appendChild(wrapSection('Commits', payload.commits || []))\n resultsEl.appendChild(wrapSection('By Route', payload.byRoute || {}))\n resultsEl.appendChild(\n wrapSection('By Source Object', payload.bySourceObject || {}),\n )\n } else if (!payload || !Array.isArray(payload.leaves)) {\n statusEl.textContent = 'No baked leaves/changelog/bundle payload found in this HTML file.'\n } else {\n statusEl.textContent = 'Loaded baked payload with ' + payload.leaves.length + ' routes.'\n\n const toHref = (source) => {\n if (!source || !source.file) return null\n const normalizedPath = String(source.file).replace(/\\\\/g, '/')\n const platform =\n (navigator.userAgentData && navigator.userAgentData.platform) ||\n navigator.platform ||\n ''\n const isWindows = /win/i.test(platform)\n const vscodePath = isWindows\n ? normalizedPath.replace(/^\\/+/, '')\n : normalizedPath.startsWith('/')\n ? normalizedPath\n : '/' + normalizedPath\n const line = Number.isFinite(source.line) ? source.line : 1\n const column = Number.isFinite(source.column) ? source.column : 1\n return 'vscode://file/' + encodeURI(vscodePath) + ':' + line + ':' + column\n }\n\n const sourceDisplay = (source) => {\n return ''\n }\n\n payload.leaves.forEach((leaf) => {\n const details = document.createElement('details')\n const summary = document.createElement('summary')\n summary.textContent = String(leaf.method || '').toUpperCase() + ' ' + (leaf.path || '')\n\n const pre = document.createElement('pre')\n pre.textContent = JSON.stringify(leaf, null, 2)\n\n const source = payload.sourceByLeaf && payload.sourceByLeaf[leaf.key]\n let sourceWrap = null\n if (source) {\n sourceWrap = document.createElement('div')\n sourceWrap.className = 'meta'\n\n const definitionHref = toHref(source.definition)\n if (definitionHref) {\n const label = document.createElement('div')\n const link = document.createElement('a')\n link.href = definitionHref\n link.target = '_blank'\n link.rel = 'noopener noreferrer'\n link.textContent = 'definition'\n label.appendChild(link)\n sourceWrap.appendChild(label)\n }\n\n if (source.schemas && typeof source.schemas === 'object') {\n Object.entries(source.schemas).forEach(([name, schema]) => {\n if (!schema) return\n const href = toHref(schema)\n const row = document.createElement('div')\n if (href) {\n const link = document.createElement('a')\n link.href = href\n link.target = '_blank'\n link.rel = 'noopener noreferrer'\n link.textContent =\n name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')\n row.appendChild(link)\n } else {\n row.textContent = name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')\n }\n sourceWrap.appendChild(row)\n })\n }\n\n }\n\n details.appendChild(summary)\n if (sourceWrap) details.appendChild(sourceWrap)\n details.appendChild(pre)\n resultsEl.appendChild(details)\n })\n }\n </script>\n </body>\n</html>\n";
@@ -0,0 +1,21 @@
1
+ import type { FinalizedLeavesExport, WriteFinalizedLeavesExportOptions } from './exportFinalizedLeaves';
2
+ import type { FinalizedLeavesChangelogExport } from './exportFinalizedLeaves.changelog';
3
+ export type FinalizedLeavesViewerBundleMeta = {
4
+ generatedAt: string;
5
+ description: string;
6
+ };
7
+ export type FinalizedLeavesViewerBundle = {
8
+ kind: 'finalized-leaves-bundle';
9
+ _meta: FinalizedLeavesViewerBundleMeta;
10
+ leavesPayload: FinalizedLeavesExport;
11
+ changelogPayload: FinalizedLeavesChangelogExport;
12
+ };
13
+ export type BuildFinalizedLeavesViewerBundleInput = {
14
+ leavesPayload: FinalizedLeavesExport;
15
+ changelogPayload: FinalizedLeavesChangelogExport;
16
+ };
17
+ export declare function buildFinalizedLeavesViewerBundle(input: BuildFinalizedLeavesViewerBundleInput): FinalizedLeavesViewerBundle;
18
+ export declare function writeFinalizedLeavesViewerBundle(payload: FinalizedLeavesViewerBundle, outFileOrOptions: string | WriteFinalizedLeavesExportOptions): Promise<{
19
+ outFile?: string;
20
+ htmlFile?: string;
21
+ }>;
package/dist/index.cjs CHANGED
@@ -32,6 +32,7 @@ var index_exports = {};
32
32
  __export(index_exports, {
33
33
  DEFAULT_VIEWER_TEMPLATE: () => DEFAULT_VIEWER_TEMPLATE,
34
34
  __private: () => __private,
35
+ buildFinalizedLeavesViewerBundle: () => buildFinalizedLeavesViewerBundle,
35
36
  clearSchemaIntrospectionHandlers: () => clearSchemaIntrospectionHandlers,
36
37
  createSchemaIntrospector: () => createSchemaIntrospector,
37
38
  exportFinalizedLeaves: () => exportFinalizedLeaves,
@@ -50,7 +51,8 @@ __export(index_exports, {
50
51
  serializeLeafContract: () => serializeLeafContract,
51
52
  serializeLeavesContract: () => serializeLeavesContract,
52
53
  writeFinalizedLeavesChangelogExport: () => writeFinalizedLeavesChangelogExport,
53
- writeFinalizedLeavesExport: () => writeFinalizedLeavesExport
54
+ writeFinalizedLeavesExport: () => writeFinalizedLeavesExport,
55
+ writeFinalizedLeavesViewerBundle: () => writeFinalizedLeavesViewerBundle
54
56
  });
55
57
  module.exports = __toCommonJS(index_exports);
56
58
 
@@ -350,46 +352,46 @@ function normalizeType(schema) {
350
352
  return "unknown";
351
353
  }
352
354
  }
353
- function isNonEmptyPath(path6) {
354
- return typeof path6 === "string" && path6.length > 0;
355
+ function isNonEmptyPath(path7) {
356
+ return typeof path7 === "string" && path7.length > 0;
355
357
  }
356
- function setNode(out, path6, schema, inherited) {
357
- if (!isNonEmptyPath(path6)) return;
358
- out[path6] = {
358
+ function setNode(out, path7, schema, inherited) {
359
+ if (!isNonEmptyPath(path7)) return;
360
+ out[path7] = {
359
361
  type: normalizeType(schema),
360
362
  nullable: inherited.nullable || Boolean(schema.nullable),
361
363
  optional: inherited.optional || Boolean(schema.optional),
362
364
  literal: schema.kind === "literal" ? schema.literal : void 0
363
365
  };
364
366
  }
365
- function flattenInto(out, schema, path6, inherited) {
366
- if (!schema || !isNonEmptyPath(path6)) return;
367
+ function flattenInto(out, schema, path7, inherited) {
368
+ if (!schema || !isNonEmptyPath(path7)) return;
367
369
  const nextInherited = {
368
370
  optional: inherited.optional || Boolean(schema.optional),
369
371
  nullable: inherited.nullable || Boolean(schema.nullable)
370
372
  };
371
373
  if (schema.kind === "union" && Array.isArray(schema.union) && schema.union.length > 0) {
372
374
  schema.union.forEach((option, index) => {
373
- const optionPath = `${path6}-${index + 1}`;
375
+ const optionPath = `${path7}-${index + 1}`;
374
376
  flattenInto(out, option, optionPath, inherited);
375
377
  });
376
378
  return;
377
379
  }
378
- setNode(out, path6, schema, inherited);
380
+ setNode(out, path7, schema, inherited);
379
381
  if (schema.kind === "object" && schema.properties) {
380
382
  for (const [key, child] of Object.entries(schema.properties)) {
381
- const childPath = `${path6}.${key}`;
383
+ const childPath = `${path7}.${key}`;
382
384
  flattenInto(out, child, childPath, nextInherited);
383
385
  }
384
386
  return;
385
387
  }
386
388
  if (schema.kind === "array" && schema.element) {
387
- flattenInto(out, schema.element, `${path6}[]`, nextInherited);
389
+ flattenInto(out, schema.element, `${path7}[]`, nextInherited);
388
390
  }
389
391
  }
390
- function flattenSerializableSchema(schema, path6) {
392
+ function flattenSerializableSchema(schema, path7) {
391
393
  const out = {};
392
- flattenInto(out, schema, path6, { optional: false, nullable: false });
394
+ flattenInto(out, schema, path7, { optional: false, nullable: false });
393
395
  return out;
394
396
  }
395
397
  function isSerializedLeaf(value) {
@@ -419,7 +421,7 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
419
421
  <head>
420
422
  <meta charset="UTF-8" />
421
423
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
422
- <title>Finalized Leaves Viewer</title>
424
+ <title>RRRoutes Export Viewer</title>
423
425
  <style>
424
426
  :root {
425
427
  --bg: #212121;
@@ -448,7 +450,7 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
448
450
  </head>
449
451
  <body>
450
452
  <div class="wrap">
451
- <h1>Finalized Leaves Viewer (Baked)</h1>
453
+ <h1>RRRoutes Export Viewer (Baked)</h1>
452
454
  <div class="card">
453
455
  <div id="status" class="meta">Waiting for baked payload...</div>
454
456
  </div>
@@ -483,7 +485,7 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
483
485
  wrapSection('By Source Object', payload.bySourceObject || {}),
484
486
  )
485
487
  } else if (!payload || !Array.isArray(payload.leaves)) {
486
- statusEl.textContent = 'No baked payload found in this HTML file.'
488
+ statusEl.textContent = 'No baked leaves/changelog/bundle payload found in this HTML file.'
487
489
  } else {
488
490
  statusEl.textContent = 'Loaded baked payload with ' + payload.leaves.length + ' routes.'
489
491
 
@@ -2620,10 +2622,124 @@ async function runExportFinalizedLeavesChangelogCli(argv) {
2620
2622
  htmlFile: args.htmlFile ? import_node_path5.default.resolve(args.htmlFile) : void 0
2621
2623
  };
2622
2624
  }
2625
+
2626
+ // src/exportFinalizedLeaves.bundle.ts
2627
+ var import_promises3 = __toESM(require("fs/promises"), 1);
2628
+ var import_node_path6 = __toESM(require("path"), 1);
2629
+ var import_node_child_process3 = require("child_process");
2630
+ var BAKED_PAYLOAD_MARKER3 = "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->";
2631
+ function escapePayloadForInlineScript3(payload) {
2632
+ return JSON.stringify(payload).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--");
2633
+ }
2634
+ function injectPayloadIntoViewerHtml3(htmlTemplate, payload) {
2635
+ const payloadScript = `${BAKED_PAYLOAD_MARKER3}
2636
+ <script id="finalized-leaves-baked-payload">window.__FINALIZED_LEAVES_PAYLOAD = ${escapePayloadForInlineScript3(
2637
+ payload
2638
+ )};</script>`;
2639
+ if (htmlTemplate.includes(BAKED_PAYLOAD_MARKER3)) {
2640
+ return htmlTemplate.replace(BAKED_PAYLOAD_MARKER3, payloadScript);
2641
+ }
2642
+ if (htmlTemplate.includes("</body>")) {
2643
+ return htmlTemplate.replace("</body>", `${payloadScript}
2644
+ </body>`);
2645
+ }
2646
+ return `${payloadScript}
2647
+ ${htmlTemplate}`;
2648
+ }
2649
+ async function resolveViewerTemplatePath3(viewerTemplateFile) {
2650
+ if (viewerTemplateFile) {
2651
+ const resolved = import_node_path6.default.resolve(viewerTemplateFile);
2652
+ await import_promises3.default.access(resolved);
2653
+ return resolved;
2654
+ }
2655
+ const candidates = [
2656
+ import_node_path6.default.resolve(
2657
+ process.cwd(),
2658
+ "node_modules/@emeryld/rrroutes-export/tools/finalized-leaves-viewer.html"
2659
+ ),
2660
+ import_node_path6.default.resolve(process.cwd(), "tools/finalized-leaves-viewer.html"),
2661
+ import_node_path6.default.resolve(process.cwd(), "packages/export/tools/finalized-leaves-viewer.html")
2662
+ ];
2663
+ for (const candidate of candidates) {
2664
+ try {
2665
+ await import_promises3.default.access(candidate);
2666
+ return candidate;
2667
+ } catch {
2668
+ }
2669
+ }
2670
+ return void 0;
2671
+ }
2672
+ async function writeJsonExport3(payload, outFile) {
2673
+ const resolved = import_node_path6.default.resolve(outFile);
2674
+ await import_promises3.default.mkdir(import_node_path6.default.dirname(resolved), { recursive: true });
2675
+ await import_promises3.default.writeFile(resolved, `${JSON.stringify(payload, null, 2)}
2676
+ `, "utf8");
2677
+ return resolved;
2678
+ }
2679
+ async function writeBakedHtmlExport3(payload, htmlFile, viewerTemplateFile) {
2680
+ const templatePath = await resolveViewerTemplatePath3(viewerTemplateFile);
2681
+ const template = templatePath ? await import_promises3.default.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
2682
+ const baked = injectPayloadIntoViewerHtml3(template, payload);
2683
+ const resolved = import_node_path6.default.resolve(htmlFile);
2684
+ await import_promises3.default.mkdir(import_node_path6.default.dirname(resolved), { recursive: true });
2685
+ await import_promises3.default.writeFile(resolved, baked, "utf8");
2686
+ return resolved;
2687
+ }
2688
+ async function openHtmlInBrowser3(filePath) {
2689
+ const resolved = import_node_path6.default.resolve(filePath);
2690
+ const platform = process.platform;
2691
+ if (platform === "darwin") {
2692
+ (0, import_node_child_process3.spawn)("open", [resolved], { detached: true, stdio: "ignore" }).unref();
2693
+ return;
2694
+ }
2695
+ if (platform === "win32") {
2696
+ (0, import_node_child_process3.spawn)("cmd", ["/c", "start", "", resolved], {
2697
+ detached: true,
2698
+ stdio: "ignore"
2699
+ }).unref();
2700
+ return;
2701
+ }
2702
+ (0, import_node_child_process3.spawn)("xdg-open", [resolved], { detached: true, stdio: "ignore" }).unref();
2703
+ }
2704
+ function buildFinalizedLeavesViewerBundle(input) {
2705
+ return {
2706
+ kind: "finalized-leaves-bundle",
2707
+ _meta: {
2708
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2709
+ description: "Unified viewer bundle including finalized leaves snapshot and changelog timeline payloads."
2710
+ },
2711
+ leavesPayload: input.leavesPayload,
2712
+ changelogPayload: input.changelogPayload
2713
+ };
2714
+ }
2715
+ async function writeFinalizedLeavesViewerBundle(payload, outFileOrOptions) {
2716
+ const options = typeof outFileOrOptions === "string" ? { outFile: outFileOrOptions } : outFileOrOptions;
2717
+ const written = {};
2718
+ if (options.outFile) {
2719
+ written.outFile = await writeJsonExport3(payload, options.outFile);
2720
+ }
2721
+ if (options.htmlFile) {
2722
+ written.htmlFile = await writeBakedHtmlExport3(
2723
+ payload,
2724
+ options.htmlFile,
2725
+ options.viewerTemplateFile
2726
+ );
2727
+ }
2728
+ if (options.openOnFinish) {
2729
+ if (!written.htmlFile) {
2730
+ throw new Error(
2731
+ "openOnFinish requires htmlFile. Provide htmlFile to open the baked viewer."
2732
+ );
2733
+ }
2734
+ await openHtmlInBrowser3(written.htmlFile);
2735
+ }
2736
+ return written;
2737
+ }
2623
2738
  // Annotate the CommonJS export names for ESM import in node:
2624
2739
  0 && (module.exports = {
2625
2740
  DEFAULT_VIEWER_TEMPLATE,
2626
2741
  __private,
2742
+ buildFinalizedLeavesViewerBundle,
2627
2743
  clearSchemaIntrospectionHandlers,
2628
2744
  createSchemaIntrospector,
2629
2745
  exportFinalizedLeaves,
@@ -2642,6 +2758,7 @@ async function runExportFinalizedLeavesChangelogCli(argv) {
2642
2758
  serializeLeafContract,
2643
2759
  serializeLeavesContract,
2644
2760
  writeFinalizedLeavesChangelogExport,
2645
- writeFinalizedLeavesExport
2761
+ writeFinalizedLeavesExport,
2762
+ writeFinalizedLeavesViewerBundle
2646
2763
  });
2647
2764
  //# sourceMappingURL=index.cjs.map