@emeryld/rrroutes-contract 2.7.4 → 2.7.6

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
@@ -321,7 +321,8 @@ From the repo root:
321
321
  pnpm --filter @emeryld/rrroutes-contract export:finalized-leaves -- \
322
322
  --module ./path/to/contract-module.ts \
323
323
  --export registry \
324
- --out ./finalized-leaves.export.json
324
+ --out ./finalized-leaves.export.json \
325
+ --with-source
325
326
  ```
326
327
 
327
328
  Arguments:
@@ -329,6 +330,18 @@ Arguments:
329
330
  - `--module` required path to the module that exports your data.
330
331
  - `--export` optional export name (default: `leaves`).
331
332
  - `--out` optional output file path (default: `finalized-leaves.export.json`).
333
+ - `--with-source` optional flag to enrich leaves with AST definition/schema source metadata.
334
+ - `--tsconfig` optional tsconfig path used for AST analysis (default: first `tsconfig.json` found from cwd).
335
+
336
+ Published package CLI (no ts-node wiring needed):
337
+
338
+ ```sh
339
+ npx rrroutes-export-finalized-leaves \
340
+ --module ./path/to/contract-module.ts \
341
+ --export registry \
342
+ --out ./finalized-leaves.export.json \
343
+ --with-source
344
+ ```
332
345
 
333
346
  ### Example module shapes
334
347
 
@@ -367,6 +380,10 @@ const payload = await exportFinalizedLeaves(registry, {
367
380
  outFile: './finalized-leaves.export.json',
368
381
  htmlFile: './finalized-leaves-viewer.baked.html',
369
382
  openOnFinish: true,
383
+ includeSource: true,
384
+ sourceModulePath: './path/to/contract-module.ts',
385
+ sourceExportName: 'registry',
386
+ tsconfigPath: './tsconfig.json',
370
387
  })
371
388
  ```
372
389
 
@@ -375,6 +392,7 @@ const payload = await exportFinalizedLeaves(registry, {
375
392
  - `_meta`: export/documentation metadata
376
393
  - `leaves`: contract-native serialized leaves
377
394
  - `schemaFlatByLeaf`: flattened schema map per leaf
395
+ - `sourceByLeaf` (when `includeSource` is true): AST-derived definition + schema source metadata keyed by `METHOD path`
378
396
 
379
397
  `htmlFile` writes a self-contained viewer HTML with the export payload baked in (no file picker needed).
380
398
  `viewerTemplateFile` optionally points to a custom viewer HTML template instead of the default bundled viewer.
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { runExportFinalizedLeavesCli } from '../dist/index.mjs'
4
+
5
+ runExportFinalizedLeavesCli(process.argv.slice(2))
6
+ .then(({ payload, outFile }) => {
7
+ process.stdout.write(`Exported ${payload.leaves.length} finalized leaves to ${outFile}\n`)
8
+ })
9
+ .catch((error) => {
10
+ process.stderr.write(
11
+ `${error instanceof Error ? error.message : String(error)}\n`,
12
+ )
13
+ process.exitCode = 1
14
+ })
@@ -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: #f5f7fb;\n --surface: #ffffff;\n --border: #d6ddea;\n --text: #172033;\n --muted: #5b6680;\n --accent: #1858c6;\n }\n body {\n margin: 0;\n font-family: 'Iosevka Web', 'SFMono-Regular', Menlo, Consolas, monospace;\n color: var(--text);\n background: linear-gradient(180deg, var(--bg), #eef2fa);\n }\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: #fafcff; }\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 || !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 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 details.appendChild(summary)\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>Finalized Leaves Viewer</title>\n <style>\n :root {\n --bg: #f5f7fb;\n --surface: #ffffff;\n --border: #d6ddea;\n --text: #172033;\n --muted: #5b6680;\n --accent: #1858c6;\n }\n body {\n margin: 0;\n font-family: 'Iosevka Web', 'SFMono-Regular', Menlo, Consolas, monospace;\n color: var(--text);\n background: linear-gradient(180deg, var(--bg), #eef2fa);\n }\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: #fafcff; }\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 || !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 prefix = normalizedPath.startsWith('/') ? 'file://' : 'file:///'\n return prefix + encodeURI(normalizedPath)\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 =\n 'definition: ' +\n source.definition.file +\n ':' +\n source.definition.line +\n ':' +\n source.definition.column\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 +\n ': ' +\n (schema.sourceName || schema.tag || '<anonymous>') +\n ' (' +\n schema.file +\n ':' +\n schema.line +\n ':' +\n schema.column +\n ')'\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";
@@ -3,6 +3,8 @@ export type FinalizedLeavesCliArgs = {
3
3
  modulePath: string;
4
4
  exportName: string;
5
5
  outFile: string;
6
+ withSource: boolean;
7
+ tsconfigPath?: string;
6
8
  };
7
9
  export declare function parseFinalizedLeavesCliArgs(argv: string[]): FinalizedLeavesCliArgs;
8
10
  export declare function loadFinalizedLeavesInput({ modulePath, exportName, }: FinalizedLeavesCliArgs): Promise<ExportFinalizedLeavesInput>;
@@ -2,10 +2,19 @@ import type { AnyLeafLowProfile } from '../core/routesV3.core';
2
2
  import type { FinalizedRegistry } from '../core/routesV3.finalize';
3
3
  import { type FlatSchemaMap } from './flattenSchema';
4
4
  import { type SerializeLeafContractOptions, type SerializedLeafContract } from './serializeLeafContract';
5
+ import { type LeafSourceByKey } from './extractLeafSourceByAst';
5
6
  export type ExportFinalizedLeavesInput = readonly AnyLeafLowProfile[] | FinalizedRegistry<readonly AnyLeafLowProfile[]>;
6
7
  export type ExportFinalizedLeavesMeta = {
7
8
  generatedAt: string;
8
9
  description: string;
10
+ sourceExtraction?: {
11
+ mode: 'ast';
12
+ enabled: boolean;
13
+ modulePath?: string;
14
+ exportName?: string;
15
+ tsconfigPath?: string;
16
+ resolvedLeafCount: number;
17
+ };
9
18
  fieldCatalog: {
10
19
  leaf: string[];
11
20
  cfg: string[];
@@ -22,12 +31,17 @@ export type FinalizedLeavesExport = {
22
31
  _meta: ExportFinalizedLeavesMeta;
23
32
  leaves: SerializedLeafContract[];
24
33
  schemaFlatByLeaf: Record<string, FlatSchemaMap>;
34
+ sourceByLeaf?: LeafSourceByKey;
25
35
  };
26
36
  export type ExportFinalizedLeavesOptions = SerializeLeafContractOptions & {
27
37
  outFile?: string;
28
38
  htmlFile?: string;
29
39
  viewerTemplateFile?: string;
30
40
  openOnFinish?: boolean;
41
+ includeSource?: boolean;
42
+ tsconfigPath?: string;
43
+ sourceModulePath?: string;
44
+ sourceExportName?: string;
31
45
  };
32
46
  export type WriteFinalizedLeavesExportOptions = {
33
47
  outFile?: string;
@@ -0,0 +1,30 @@
1
+ declare const SCHEMA_KEYS: readonly ["bodySchema", "querySchema", "paramsSchema", "outputSchema", "outputMetaSchema", "queryExtensionSchema"];
2
+ type SchemaKey = (typeof SCHEMA_KEYS)[number];
3
+ export type LeafSourceLocation = {
4
+ file: string;
5
+ line: number;
6
+ column: number;
7
+ };
8
+ export type SchemaSourceMetadata = LeafSourceLocation & {
9
+ sourceName?: string;
10
+ tag?: '<inline>' | '<anonymous>' | '<expression>';
11
+ };
12
+ export type LeafSourceMetadata = {
13
+ definition: LeafSourceLocation & {
14
+ symbolName?: string;
15
+ };
16
+ schemas: Partial<Record<SchemaKey, SchemaSourceMetadata>>;
17
+ };
18
+ export type LeafSourceByKey = Record<string, LeafSourceMetadata>;
19
+ export type ExtractLeafSourceByAstOptions = {
20
+ modulePath: string;
21
+ exportName: string;
22
+ tsconfigPath?: string;
23
+ cwd?: string;
24
+ };
25
+ export type ExtractLeafSourceByAstResult = {
26
+ sourceByLeaf: LeafSourceByKey;
27
+ tsconfigPath?: string;
28
+ };
29
+ export declare function extractLeafSourceByAst({ modulePath, exportName, tsconfigPath, cwd, }: ExtractLeafSourceByAstOptions): ExtractLeafSourceByAstResult;
30
+ export {};
@@ -4,3 +4,4 @@ export * from './flattenSchema';
4
4
  export * from './exportFinalizedLeaves';
5
5
  export * from './exportFinalizedLeaves.cli';
6
6
  export * from './defaultViewerTemplate';
7
+ export * from './extractLeafSourceByAst';