@emeryld/rrroutes-contract 2.7.1 → 2.7.2

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
@@ -365,6 +365,8 @@ import { exportFinalizedLeaves } from '@emeryld/rrroutes-contract'
365
365
 
366
366
  const payload = await exportFinalizedLeaves(registry, {
367
367
  outFile: './finalized-leaves.export.json',
368
+ htmlFile: './finalized-leaves-viewer.baked.html',
369
+ openOnFinish: true,
368
370
  })
369
371
  ```
370
372
 
@@ -373,3 +375,92 @@ const payload = await exportFinalizedLeaves(registry, {
373
375
  - `_meta`: export/documentation metadata
374
376
  - `leaves`: contract-native serialized leaves
375
377
  - `schemaFlatByLeaf`: flattened schema map per leaf
378
+
379
+ `htmlFile` writes a self-contained viewer HTML with the export payload baked in (no file picker needed).
380
+ `viewerTemplateFile` optionally points to a custom viewer HTML template instead of the default bundled viewer.
381
+ `openOnFinish` opens the generated `htmlFile` in your default browser after write completes.
382
+
383
+ ### Custom `viewerTemplateFile`
384
+
385
+ Use `viewerTemplateFile` when you want your own branded/layout HTML while still baking export data directly into the page.
386
+
387
+ Behavior:
388
+
389
+ - If omitted, RRRoutes uses `packages/contract/tools/finalized-leaves-viewer.html`.
390
+ - If provided, RRRoutes reads your template and injects a script like:
391
+ - `window.__FINALIZED_LEAVES_PAYLOAD = {...}`
392
+ - If your template contains this marker comment, payload is injected exactly there:
393
+ - `<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->`
394
+ - If marker is missing, payload script is inserted before `</body>` (or prepended if no `</body>` exists).
395
+
396
+ Minimal custom template example:
397
+
398
+ ```html
399
+ <!doctype html>
400
+ <html>
401
+ <head>
402
+ <meta charset="UTF-8" />
403
+ <title>My Leaves Viewer</title>
404
+ </head>
405
+ <body>
406
+ <h1>My API Routes</h1>
407
+ <div id="app"></div>
408
+
409
+ <!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->
410
+
411
+ <script>
412
+ const payload = window.__FINALIZED_LEAVES_PAYLOAD
413
+ document.getElementById('app').textContent = payload
414
+ ? `Loaded ${payload.leaves.length} leaves`
415
+ : 'No baked payload found'
416
+ </script>
417
+ </body>
418
+ </html>
419
+ ```
420
+
421
+ Runtime usage with custom template:
422
+
423
+ ```ts
424
+ await exportFinalizedLeaves(registry, {
425
+ htmlFile: './dist/leaves-viewer.html',
426
+ viewerTemplateFile: './tools/my-viewer-template.html',
427
+ openOnFinish: true,
428
+ })
429
+ ```
430
+
431
+ ### Viewer HTML (searchable UI)
432
+
433
+ A simple local viewer is included at:
434
+
435
+ - `packages/contract/tools/finalized-leaves-viewer.html`
436
+
437
+ How to use:
438
+
439
+ 1. Generate an export JSON with `export:finalized-leaves`.
440
+ 2. Open the HTML file in your browser.
441
+ 3. Load the JSON file using the file picker.
442
+ 4. Use the single search textbox + field checkboxes to filter routes.
443
+
444
+ Each result is rendered as a collapsible block with title `METHOD path`.
445
+
446
+ To access it from your project:
447
+
448
+ - Quick local use: open the HTML file directly.
449
+ - Team/shared use: serve it as a static file (Express example):
450
+
451
+ ```ts
452
+ import express from 'express'
453
+ import path from 'node:path'
454
+
455
+ const app = express()
456
+
457
+ app.use(
458
+ '/tools/finalized-leaves-viewer',
459
+ express.static(
460
+ path.resolve(process.cwd(), 'packages/contract/tools'),
461
+ ),
462
+ )
463
+
464
+ app.listen(3000)
465
+ // open http://localhost:3000/tools/finalized-leaves-viewer/finalized-leaves-viewer.html
466
+ ```
@@ -25,6 +25,18 @@ export type FinalizedLeavesExport = {
25
25
  };
26
26
  export type ExportFinalizedLeavesOptions = SerializeLeafContractOptions & {
27
27
  outFile?: string;
28
+ htmlFile?: string;
29
+ viewerTemplateFile?: string;
30
+ openOnFinish?: boolean;
28
31
  };
29
- export declare function writeFinalizedLeavesExport(payload: FinalizedLeavesExport, outFile: string): Promise<string>;
32
+ export type WriteFinalizedLeavesExportOptions = {
33
+ outFile?: string;
34
+ htmlFile?: string;
35
+ viewerTemplateFile?: string;
36
+ openOnFinish?: boolean;
37
+ };
38
+ export declare function writeFinalizedLeavesExport(payload: FinalizedLeavesExport, outFileOrOptions: string | WriteFinalizedLeavesExportOptions): Promise<{
39
+ outFile?: string;
40
+ htmlFile?: string;
41
+ }>;
30
42
  export declare function exportFinalizedLeaves(input: ExportFinalizedLeavesInput, options?: ExportFinalizedLeavesOptions): Promise<FinalizedLeavesExport>;
package/dist/index.cjs CHANGED
@@ -741,6 +741,7 @@ function flattenLeafSchemas(leaf) {
741
741
  // src/export/exportFinalizedLeaves.ts
742
742
  var import_promises = __toESM(require("fs/promises"), 1);
743
743
  var import_node_path = __toESM(require("path"), 1);
744
+ var import_node_child_process = require("child_process");
744
745
  function isRegistry(value) {
745
746
  return typeof value === "object" && value !== null && "all" in value && "byKey" in value;
746
747
  }
@@ -786,13 +787,104 @@ function buildMeta() {
786
787
  }
787
788
  };
788
789
  }
789
- async function writeFinalizedLeavesExport(payload, outFile) {
790
+ var BAKED_PAYLOAD_MARKER = "<!--__FINALIZED_LEAVES_BAKED_PAYLOAD__-->";
791
+ function escapePayloadForInlineScript(payload) {
792
+ return JSON.stringify(payload).replace(/<\//g, "<\\/").replace(/<!--/g, "<\\!--");
793
+ }
794
+ function injectPayloadIntoViewerHtml(htmlTemplate, payload) {
795
+ const payloadScript = `${BAKED_PAYLOAD_MARKER}
796
+ <script id="finalized-leaves-baked-payload">window.__FINALIZED_LEAVES_PAYLOAD = ${escapePayloadForInlineScript(
797
+ payload
798
+ )};</script>`;
799
+ if (htmlTemplate.includes(BAKED_PAYLOAD_MARKER)) {
800
+ return htmlTemplate.replace(BAKED_PAYLOAD_MARKER, payloadScript);
801
+ }
802
+ if (htmlTemplate.includes("</body>")) {
803
+ return htmlTemplate.replace("</body>", `${payloadScript}
804
+ </body>`);
805
+ }
806
+ return `${payloadScript}
807
+ ${htmlTemplate}`;
808
+ }
809
+ async function resolveViewerTemplatePath(viewerTemplateFile) {
810
+ if (viewerTemplateFile) {
811
+ return import_node_path.default.resolve(viewerTemplateFile);
812
+ }
813
+ const candidates = [
814
+ import_node_path.default.resolve(process.cwd(), "tools/finalized-leaves-viewer.html"),
815
+ import_node_path.default.resolve(
816
+ process.cwd(),
817
+ "packages/contract/tools/finalized-leaves-viewer.html"
818
+ )
819
+ ];
820
+ for (const candidate of candidates) {
821
+ try {
822
+ await import_promises.default.access(candidate);
823
+ return candidate;
824
+ } catch {
825
+ }
826
+ }
827
+ throw new Error(
828
+ `Could not locate finalized-leaves viewer template. Checked: ${candidates.join(
829
+ ", "
830
+ )}`
831
+ );
832
+ }
833
+ async function writeJsonExport(payload, outFile) {
790
834
  const resolved = import_node_path.default.resolve(outFile);
791
835
  await import_promises.default.mkdir(import_node_path.default.dirname(resolved), { recursive: true });
792
836
  await import_promises.default.writeFile(resolved, `${JSON.stringify(payload, null, 2)}
793
837
  `, "utf8");
794
838
  return resolved;
795
839
  }
840
+ async function writeBakedHtmlExport(payload, htmlFile, viewerTemplateFile) {
841
+ const templatePath = await resolveViewerTemplatePath(viewerTemplateFile);
842
+ const template = await import_promises.default.readFile(templatePath, "utf8");
843
+ const baked = injectPayloadIntoViewerHtml(template, payload);
844
+ const resolved = import_node_path.default.resolve(htmlFile);
845
+ await import_promises.default.mkdir(import_node_path.default.dirname(resolved), { recursive: true });
846
+ await import_promises.default.writeFile(resolved, baked, "utf8");
847
+ return resolved;
848
+ }
849
+ async function openHtmlInBrowser(filePath) {
850
+ const resolved = import_node_path.default.resolve(filePath);
851
+ const platform = process.platform;
852
+ if (platform === "darwin") {
853
+ (0, import_node_child_process.spawn)("open", [resolved], { detached: true, stdio: "ignore" }).unref();
854
+ return;
855
+ }
856
+ if (platform === "win32") {
857
+ (0, import_node_child_process.spawn)("cmd", ["/c", "start", "", resolved], {
858
+ detached: true,
859
+ stdio: "ignore"
860
+ }).unref();
861
+ return;
862
+ }
863
+ (0, import_node_child_process.spawn)("xdg-open", [resolved], { detached: true, stdio: "ignore" }).unref();
864
+ }
865
+ async function writeFinalizedLeavesExport(payload, outFileOrOptions) {
866
+ const options = typeof outFileOrOptions === "string" ? { outFile: outFileOrOptions } : outFileOrOptions;
867
+ const written = {};
868
+ if (options.outFile) {
869
+ written.outFile = await writeJsonExport(payload, options.outFile);
870
+ }
871
+ if (options.htmlFile) {
872
+ written.htmlFile = await writeBakedHtmlExport(
873
+ payload,
874
+ options.htmlFile,
875
+ options.viewerTemplateFile
876
+ );
877
+ }
878
+ if (options.openOnFinish) {
879
+ if (!written.htmlFile) {
880
+ throw new Error(
881
+ "openOnFinish requires htmlFile. Provide htmlFile to open the baked viewer."
882
+ );
883
+ }
884
+ await openHtmlInBrowser(written.htmlFile);
885
+ }
886
+ return written;
887
+ }
796
888
  async function exportFinalizedLeaves(input, options = {}) {
797
889
  const leaves = getLeaves(input);
798
890
  const serializedLeaves = serializeLeavesContract(leaves, options);
@@ -804,8 +896,13 @@ async function exportFinalizedLeaves(input, options = {}) {
804
896
  leaves: serializedLeaves,
805
897
  schemaFlatByLeaf
806
898
  };
807
- if (options.outFile) {
808
- await writeFinalizedLeavesExport(payload, options.outFile);
899
+ if (options.outFile || options.htmlFile) {
900
+ await writeFinalizedLeavesExport(payload, {
901
+ outFile: options.outFile,
902
+ htmlFile: options.htmlFile,
903
+ viewerTemplateFile: options.viewerTemplateFile,
904
+ openOnFinish: options.openOnFinish
905
+ });
809
906
  }
810
907
  return payload;
811
908
  }