@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 +91 -0
- package/dist/export/exportFinalizedLeaves.d.ts +13 -1
- package/dist/index.cjs +100 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +100 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
|
|
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,
|
|
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
|
}
|