@emeryld/rrroutes-contract 2.7.6 → 2.7.7
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 +1 -0
- package/dist/export/defaultViewerTemplate.d.ts +1 -1
- package/dist/export/exportFinalizedLeaves.d.ts +7 -1
- package/dist/export/extractLeafSourceByAst.d.ts +8 -0
- package/dist/index.cjs +247 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +247 -82
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/tools/finalized-leaves-viewer.html +18 -7
package/README.md
CHANGED
|
@@ -390,6 +390,7 @@ const payload = await exportFinalizedLeaves(registry, {
|
|
|
390
390
|
`payload` contains:
|
|
391
391
|
|
|
392
392
|
- `_meta`: export/documentation metadata
|
|
393
|
+
- when source extraction is enabled, `_meta.sourceExtraction` also includes diagnostics (`reason`, `stats`)
|
|
393
394
|
- `leaves`: contract-native serialized leaves
|
|
394
395
|
- `schemaFlatByLeaf`: flattened schema map per leaf
|
|
395
396
|
- `sourceByLeaf` (when `includeSource` is true): AST-derived definition + schema source metadata keyed by `METHOD path`
|
|
@@ -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 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 '
|
|
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 = 'definition'\n label.appendChild(link)\n const location = document.createElement('span')\n location.textContent =\n ' (' +\n source.definition.file +\n ':' +\n source.definition.line +\n ':' +\n source.definition.column +\n ')'\n label.appendChild(location)\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 const location = document.createElement('span')\n location.textContent =\n ' (' + schema.file + ':' + schema.line + ':' + schema.column + ')'\n row.appendChild(location)\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";
|
|
@@ -2,7 +2,7 @@ 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
|
+
import { type LeafSourceByKey, type SourceExtractionReason } from './extractLeafSourceByAst';
|
|
6
6
|
export type ExportFinalizedLeavesInput = readonly AnyLeafLowProfile[] | FinalizedRegistry<readonly AnyLeafLowProfile[]>;
|
|
7
7
|
export type ExportFinalizedLeavesMeta = {
|
|
8
8
|
generatedAt: string;
|
|
@@ -14,6 +14,12 @@ export type ExportFinalizedLeavesMeta = {
|
|
|
14
14
|
exportName?: string;
|
|
15
15
|
tsconfigPath?: string;
|
|
16
16
|
resolvedLeafCount: number;
|
|
17
|
+
reason?: SourceExtractionReason;
|
|
18
|
+
stats?: {
|
|
19
|
+
visitedSymbols: number;
|
|
20
|
+
visitedFiles: number;
|
|
21
|
+
unresolvedReferences: number;
|
|
22
|
+
};
|
|
17
23
|
};
|
|
18
24
|
fieldCatalog: {
|
|
19
25
|
leaf: string[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
declare const SCHEMA_KEYS: readonly ["bodySchema", "querySchema", "paramsSchema", "outputSchema", "outputMetaSchema", "queryExtensionSchema"];
|
|
2
|
+
export type SourceExtractionReason = 'module_not_in_program' | 'export_not_found' | 'unsupported_expression_shape' | 'resolved_zero_leaves';
|
|
2
3
|
type SchemaKey = (typeof SCHEMA_KEYS)[number];
|
|
3
4
|
export type LeafSourceLocation = {
|
|
4
5
|
file: string;
|
|
@@ -22,9 +23,16 @@ export type ExtractLeafSourceByAstOptions = {
|
|
|
22
23
|
tsconfigPath?: string;
|
|
23
24
|
cwd?: string;
|
|
24
25
|
};
|
|
26
|
+
export type ExtractLeafSourceByAstStats = {
|
|
27
|
+
visitedSymbols: number;
|
|
28
|
+
visitedFiles: number;
|
|
29
|
+
unresolvedReferences: number;
|
|
30
|
+
};
|
|
25
31
|
export type ExtractLeafSourceByAstResult = {
|
|
26
32
|
sourceByLeaf: LeafSourceByKey;
|
|
27
33
|
tsconfigPath?: string;
|
|
34
|
+
reason?: SourceExtractionReason;
|
|
35
|
+
stats: ExtractLeafSourceByAstStats;
|
|
28
36
|
};
|
|
29
37
|
export declare function extractLeafSourceByAst({ modulePath, exportName, tsconfigPath, cwd, }: ExtractLeafSourceByAstOptions): ExtractLeafSourceByAstResult;
|
|
30
38
|
export {};
|
package/dist/index.cjs
CHANGED
|
@@ -825,14 +825,18 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
|
|
|
825
825
|
link.href = definitionHref
|
|
826
826
|
link.target = '_blank'
|
|
827
827
|
link.rel = 'noopener noreferrer'
|
|
828
|
-
link.textContent =
|
|
829
|
-
|
|
828
|
+
link.textContent = 'definition'
|
|
829
|
+
label.appendChild(link)
|
|
830
|
+
const location = document.createElement('span')
|
|
831
|
+
location.textContent =
|
|
832
|
+
' (' +
|
|
830
833
|
source.definition.file +
|
|
831
834
|
':' +
|
|
832
835
|
source.definition.line +
|
|
833
836
|
':' +
|
|
834
|
-
source.definition.column
|
|
835
|
-
|
|
837
|
+
source.definition.column +
|
|
838
|
+
')'
|
|
839
|
+
label.appendChild(location)
|
|
836
840
|
sourceWrap.appendChild(label)
|
|
837
841
|
}
|
|
838
842
|
|
|
@@ -847,17 +851,12 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
|
|
|
847
851
|
link.target = '_blank'
|
|
848
852
|
link.rel = 'noopener noreferrer'
|
|
849
853
|
link.textContent =
|
|
850
|
-
name +
|
|
851
|
-
': ' +
|
|
852
|
-
(schema.sourceName || schema.tag || '<anonymous>') +
|
|
853
|
-
' (' +
|
|
854
|
-
schema.file +
|
|
855
|
-
':' +
|
|
856
|
-
schema.line +
|
|
857
|
-
':' +
|
|
858
|
-
schema.column +
|
|
859
|
-
')'
|
|
854
|
+
name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')
|
|
860
855
|
row.appendChild(link)
|
|
856
|
+
const location = document.createElement('span')
|
|
857
|
+
location.textContent =
|
|
858
|
+
' (' + schema.file + ':' + schema.line + ':' + schema.column + ')'
|
|
859
|
+
row.appendChild(location)
|
|
861
860
|
} else {
|
|
862
861
|
row.textContent = name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')
|
|
863
862
|
}
|
|
@@ -890,6 +889,7 @@ var SCHEMA_KEYS = [
|
|
|
890
889
|
"queryExtensionSchema"
|
|
891
890
|
];
|
|
892
891
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete"]);
|
|
892
|
+
var MAX_RECURSION_DEPTH = 120;
|
|
893
893
|
function toLocation(node) {
|
|
894
894
|
const sourceFile = node.getSourceFile();
|
|
895
895
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(
|
|
@@ -901,6 +901,10 @@ function toLocation(node) {
|
|
|
901
901
|
column: character + 1
|
|
902
902
|
};
|
|
903
903
|
}
|
|
904
|
+
function markFile(ctx, sourceFile) {
|
|
905
|
+
if (!sourceFile) return;
|
|
906
|
+
ctx.visitedFilePaths.add(import_node_path.default.resolve(sourceFile.fileName));
|
|
907
|
+
}
|
|
904
908
|
function trimPreview(text, max = 80) {
|
|
905
909
|
const normalized = text.replace(/\s+/g, " ").trim();
|
|
906
910
|
return normalized.length > max ? `${normalized.slice(0, max)}...` : normalized;
|
|
@@ -958,7 +962,7 @@ function findSourceFile(program, filePath) {
|
|
|
958
962
|
const normalizedWanted = import_node_path.default.normalize(wanted);
|
|
959
963
|
return program.getSourceFiles().find((file) => import_node_path.default.normalize(import_node_path.default.resolve(file.fileName)) === normalizedWanted);
|
|
960
964
|
}
|
|
961
|
-
function
|
|
965
|
+
function declarationToExpression(declaration) {
|
|
962
966
|
if (!declaration) return void 0;
|
|
963
967
|
if (import_typescript.default.isVariableDeclaration(declaration)) {
|
|
964
968
|
return declaration.initializer;
|
|
@@ -975,14 +979,95 @@ function expressionFromDeclaration(declaration) {
|
|
|
975
979
|
if (import_typescript.default.isBindingElement(declaration)) {
|
|
976
980
|
return declaration.initializer;
|
|
977
981
|
}
|
|
982
|
+
if (import_typescript.default.isEnumMember(declaration)) {
|
|
983
|
+
return declaration.initializer;
|
|
984
|
+
}
|
|
978
985
|
return void 0;
|
|
979
986
|
}
|
|
980
|
-
function
|
|
981
|
-
const
|
|
982
|
-
if (!
|
|
987
|
+
function symbolKey(symbol) {
|
|
988
|
+
const decl = symbol.declarations?.[0];
|
|
989
|
+
if (!decl) return `${symbol.getName()}#${symbol.flags}`;
|
|
990
|
+
const file = import_node_path.default.resolve(decl.getSourceFile().fileName);
|
|
991
|
+
return `${file}:${decl.getStart()}:${symbol.getName()}`;
|
|
992
|
+
}
|
|
993
|
+
function resolveSymbolFromNode(node, ctx) {
|
|
994
|
+
const symbol = ctx.checker.getSymbolAtLocation(node);
|
|
995
|
+
if (!symbol) {
|
|
996
|
+
ctx.unresolvedReferences += 1;
|
|
997
|
+
return void 0;
|
|
998
|
+
}
|
|
983
999
|
const target = getAliasedSymbolIfNeeded(ctx.checker, symbol);
|
|
984
|
-
const
|
|
985
|
-
|
|
1000
|
+
const key = symbolKey(target);
|
|
1001
|
+
ctx.visitedSymbolKeys.add(key);
|
|
1002
|
+
return target;
|
|
1003
|
+
}
|
|
1004
|
+
function getExpressionFromSymbol(symbol, ctx, depth) {
|
|
1005
|
+
const key = symbolKey(symbol);
|
|
1006
|
+
if (ctx.activeSymbols.has(key)) return void 0;
|
|
1007
|
+
if (depth > MAX_RECURSION_DEPTH) return void 0;
|
|
1008
|
+
ctx.activeSymbols.add(key);
|
|
1009
|
+
try {
|
|
1010
|
+
const declaration = symbol.declarations?.[0];
|
|
1011
|
+
markFile(ctx, declaration?.getSourceFile());
|
|
1012
|
+
const direct = declarationToExpression(declaration);
|
|
1013
|
+
if (direct) return direct;
|
|
1014
|
+
if (declaration && import_typescript.default.isImportSpecifier(declaration)) {
|
|
1015
|
+
const target = getAliasedSymbolIfNeeded(ctx.checker, symbol);
|
|
1016
|
+
if (target !== symbol) {
|
|
1017
|
+
return getExpressionFromSymbol(target, ctx, depth + 1);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return void 0;
|
|
1021
|
+
} finally {
|
|
1022
|
+
ctx.activeSymbols.delete(key);
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
function resolveIdentifierExpression(identifier, ctx, depth) {
|
|
1026
|
+
const symbol = resolveSymbolFromNode(identifier, ctx);
|
|
1027
|
+
if (!symbol) return void 0;
|
|
1028
|
+
return getExpressionFromSymbol(symbol, ctx, depth);
|
|
1029
|
+
}
|
|
1030
|
+
function resolvePropertyExpression(expression, ctx, depth) {
|
|
1031
|
+
if (depth > MAX_RECURSION_DEPTH) return void 0;
|
|
1032
|
+
if (import_typescript.default.isPropertyAccessExpression(expression)) {
|
|
1033
|
+
const symbol = resolveSymbolFromNode(expression.name, ctx);
|
|
1034
|
+
if (symbol) {
|
|
1035
|
+
const fromSymbol = getExpressionFromSymbol(symbol, ctx, depth + 1);
|
|
1036
|
+
if (fromSymbol) return fromSymbol;
|
|
1037
|
+
}
|
|
1038
|
+
} else if (expression.argumentExpression) {
|
|
1039
|
+
const symbol = resolveSymbolFromNode(expression.argumentExpression, ctx);
|
|
1040
|
+
if (symbol) {
|
|
1041
|
+
const fromSymbol = getExpressionFromSymbol(symbol, ctx, depth + 1);
|
|
1042
|
+
if (fromSymbol) return fromSymbol;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
const ownerExpr = evaluateExpressionReference(expression.expression, ctx, depth + 1);
|
|
1046
|
+
const owner = ownerExpr ? maybeObjectLiteral(ownerExpr, ctx, depth + 1) : void 0;
|
|
1047
|
+
if (!owner) return void 0;
|
|
1048
|
+
const propName = import_typescript.default.isPropertyAccessExpression(expression) ? expression.name.text : expression.argumentExpression && import_typescript.default.isStringLiteralLike(expression.argumentExpression) ? expression.argumentExpression.text : void 0;
|
|
1049
|
+
if (!propName) return void 0;
|
|
1050
|
+
for (const property of owner.properties) {
|
|
1051
|
+
if (import_typescript.default.isPropertyAssignment(property) && getTextName(property.name) === propName) {
|
|
1052
|
+
return property.initializer;
|
|
1053
|
+
}
|
|
1054
|
+
if (import_typescript.default.isShorthandPropertyAssignment(property) && property.name.text === propName) {
|
|
1055
|
+
return property.name;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
return void 0;
|
|
1059
|
+
}
|
|
1060
|
+
function evaluateExpressionReference(expression, ctx, depth) {
|
|
1061
|
+
const resolved = unwrapExpression(expression);
|
|
1062
|
+
markFile(ctx, resolved.getSourceFile());
|
|
1063
|
+
if (depth > MAX_RECURSION_DEPTH) return void 0;
|
|
1064
|
+
if (import_typescript.default.isIdentifier(resolved)) {
|
|
1065
|
+
return resolveIdentifierExpression(resolved, ctx, depth + 1);
|
|
1066
|
+
}
|
|
1067
|
+
if (import_typescript.default.isPropertyAccessExpression(resolved) || import_typescript.default.isElementAccessExpression(resolved)) {
|
|
1068
|
+
return resolvePropertyExpression(resolved, ctx, depth + 1);
|
|
1069
|
+
}
|
|
1070
|
+
return void 0;
|
|
986
1071
|
}
|
|
987
1072
|
function resolveExportExpression(sourceFile, exportName, checker) {
|
|
988
1073
|
const moduleSymbol = getModuleSymbol(checker, sourceFile);
|
|
@@ -991,12 +1076,12 @@ function resolveExportExpression(sourceFile, exportName, checker) {
|
|
|
991
1076
|
const explicit = exports2.find((entry) => entry.getName() === exportName);
|
|
992
1077
|
if (explicit) {
|
|
993
1078
|
const declaration = getAliasedSymbolIfNeeded(checker, explicit).declarations?.[0];
|
|
994
|
-
return
|
|
1079
|
+
return declarationToExpression(declaration);
|
|
995
1080
|
}
|
|
996
1081
|
const defaultExport = exports2.find((entry) => entry.getName() === "default");
|
|
997
1082
|
if (!defaultExport) return void 0;
|
|
998
1083
|
const defaultDecl = getAliasedSymbolIfNeeded(checker, defaultExport).declarations?.[0];
|
|
999
|
-
const defaultExpr =
|
|
1084
|
+
const defaultExpr = declarationToExpression(defaultDecl);
|
|
1000
1085
|
if (!defaultExpr) return void 0;
|
|
1001
1086
|
const resolved = unwrapExpression(defaultExpr);
|
|
1002
1087
|
if (!import_typescript.default.isObjectLiteralExpression(resolved)) return void 0;
|
|
@@ -1008,24 +1093,25 @@ function resolveExportExpression(sourceFile, exportName, checker) {
|
|
|
1008
1093
|
}
|
|
1009
1094
|
return void 0;
|
|
1010
1095
|
}
|
|
1011
|
-
function maybeObjectLiteral(expression, ctx) {
|
|
1012
|
-
if (!expression) return void 0;
|
|
1096
|
+
function maybeObjectLiteral(expression, ctx, depth = 0) {
|
|
1097
|
+
if (!expression || depth > MAX_RECURSION_DEPTH) return void 0;
|
|
1013
1098
|
const resolved = unwrapExpression(expression);
|
|
1099
|
+
markFile(ctx, resolved.getSourceFile());
|
|
1014
1100
|
if (import_typescript.default.isObjectLiteralExpression(resolved)) return resolved;
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
return maybeObjectLiteral(target, ctx);
|
|
1019
|
-
}
|
|
1020
|
-
return void 0;
|
|
1101
|
+
const referenced = evaluateExpressionReference(resolved, ctx, depth + 1);
|
|
1102
|
+
if (!referenced) return void 0;
|
|
1103
|
+
return maybeObjectLiteral(referenced, ctx, depth + 1);
|
|
1021
1104
|
}
|
|
1022
|
-
function collectSchemaExpressionsFromObject(objectLiteral, ctx) {
|
|
1105
|
+
function collectSchemaExpressionsFromObject(objectLiteral, ctx, depth) {
|
|
1023
1106
|
const schemas = {};
|
|
1024
1107
|
for (const property of objectLiteral.properties) {
|
|
1025
1108
|
if (import_typescript.default.isSpreadAssignment(property)) {
|
|
1026
|
-
const spreadObject = maybeObjectLiteral(property.expression, ctx);
|
|
1109
|
+
const spreadObject = maybeObjectLiteral(property.expression, ctx, depth + 1);
|
|
1027
1110
|
if (!spreadObject) continue;
|
|
1028
|
-
Object.assign(
|
|
1111
|
+
Object.assign(
|
|
1112
|
+
schemas,
|
|
1113
|
+
collectSchemaExpressionsFromObject(spreadObject, ctx, depth + 1)
|
|
1114
|
+
);
|
|
1029
1115
|
continue;
|
|
1030
1116
|
}
|
|
1031
1117
|
if (import_typescript.default.isPropertyAssignment(property)) {
|
|
@@ -1042,10 +1128,10 @@ function collectSchemaExpressionsFromObject(objectLiteral, ctx) {
|
|
|
1042
1128
|
}
|
|
1043
1129
|
return schemas;
|
|
1044
1130
|
}
|
|
1045
|
-
function extractSchemaExpressions(cfgExpression, ctx) {
|
|
1046
|
-
const objectLiteral = maybeObjectLiteral(cfgExpression, ctx);
|
|
1131
|
+
function extractSchemaExpressions(cfgExpression, ctx, depth) {
|
|
1132
|
+
const objectLiteral = maybeObjectLiteral(cfgExpression, ctx, depth);
|
|
1047
1133
|
if (!objectLiteral) return {};
|
|
1048
|
-
return collectSchemaExpressionsFromObject(objectLiteral, ctx);
|
|
1134
|
+
return collectSchemaExpressionsFromObject(objectLiteral, ctx, depth);
|
|
1049
1135
|
}
|
|
1050
1136
|
function getNearestVariableName(node) {
|
|
1051
1137
|
let current = node;
|
|
@@ -1057,31 +1143,45 @@ function getNearestVariableName(node) {
|
|
|
1057
1143
|
}
|
|
1058
1144
|
return void 0;
|
|
1059
1145
|
}
|
|
1060
|
-
function
|
|
1146
|
+
function isAllAccess(expression) {
|
|
1147
|
+
if (import_typescript.default.isPropertyAccessExpression(expression)) {
|
|
1148
|
+
return expression.name.text === "all";
|
|
1149
|
+
}
|
|
1150
|
+
return Boolean(
|
|
1151
|
+
expression.argumentExpression && import_typescript.default.isStringLiteralLike(expression.argumentExpression) && expression.argumentExpression.text === "all"
|
|
1152
|
+
);
|
|
1153
|
+
}
|
|
1154
|
+
function evaluateBranchExpression(expression, ctx, depth) {
|
|
1061
1155
|
const resolved = unwrapExpression(expression);
|
|
1156
|
+
markFile(ctx, resolved.getSourceFile());
|
|
1157
|
+
if (depth > MAX_RECURSION_DEPTH) return void 0;
|
|
1062
1158
|
if (import_typescript.default.isIdentifier(resolved)) {
|
|
1063
|
-
const valueExpr = resolveIdentifierExpression(resolved, ctx);
|
|
1159
|
+
const valueExpr = resolveIdentifierExpression(resolved, ctx, depth + 1);
|
|
1064
1160
|
if (!valueExpr) return void 0;
|
|
1065
|
-
return evaluateBranchExpression(valueExpr, ctx);
|
|
1161
|
+
return evaluateBranchExpression(valueExpr, ctx, depth + 1);
|
|
1066
1162
|
}
|
|
1067
1163
|
if (!import_typescript.default.isCallExpression(resolved)) return void 0;
|
|
1068
1164
|
const call = resolved;
|
|
1069
1165
|
if (import_typescript.default.isIdentifier(call.expression) && call.expression.text === "resource") {
|
|
1070
1166
|
const firstArg = call.arguments[0];
|
|
1071
|
-
if (!firstArg || !import_typescript.default.isStringLiteralLike(firstArg))
|
|
1167
|
+
if (!firstArg || !import_typescript.default.isStringLiteralLike(firstArg)) {
|
|
1168
|
+
ctx.unsupportedShapeSeen = true;
|
|
1169
|
+
return void 0;
|
|
1170
|
+
}
|
|
1072
1171
|
return { base: normalizeResourceBase(firstArg.text), leaves: [] };
|
|
1073
1172
|
}
|
|
1074
|
-
if (!import_typescript.default.isPropertyAccessExpression(call.expression))
|
|
1173
|
+
if (!import_typescript.default.isPropertyAccessExpression(call.expression)) {
|
|
1174
|
+
ctx.unsupportedShapeSeen = true;
|
|
1175
|
+
return void 0;
|
|
1176
|
+
}
|
|
1075
1177
|
const owner = call.expression.expression;
|
|
1076
1178
|
const method = call.expression.name.text;
|
|
1077
|
-
const branch = evaluateBranchExpression(owner, ctx);
|
|
1179
|
+
const branch = evaluateBranchExpression(owner, ctx, depth + 1);
|
|
1078
1180
|
if (!branch) return void 0;
|
|
1079
|
-
if (method === "with")
|
|
1080
|
-
return branch;
|
|
1081
|
-
}
|
|
1181
|
+
if (method === "with") return branch;
|
|
1082
1182
|
if (HTTP_METHODS.has(method)) {
|
|
1083
1183
|
const cfgExpression = call.arguments[0];
|
|
1084
|
-
const schemas = extractSchemaExpressions(cfgExpression, ctx);
|
|
1184
|
+
const schemas = extractSchemaExpressions(cfgExpression, ctx, depth + 1);
|
|
1085
1185
|
const nextLeaf = {
|
|
1086
1186
|
method,
|
|
1087
1187
|
path: branch.base,
|
|
@@ -1095,7 +1195,7 @@ function evaluateBranchExpression(expression, ctx) {
|
|
|
1095
1195
|
}
|
|
1096
1196
|
if (method === "sub") {
|
|
1097
1197
|
const mountedLeaves = call.arguments.flatMap(
|
|
1098
|
-
(arg) => evaluateLeavesFromExpression(arg, ctx)
|
|
1198
|
+
(arg) => evaluateLeavesFromExpression(arg, ctx, depth + 1)
|
|
1099
1199
|
);
|
|
1100
1200
|
const prefixed = mountedLeaves.map((leaf) => ({
|
|
1101
1201
|
...leaf,
|
|
@@ -1106,29 +1206,44 @@ function evaluateBranchExpression(expression, ctx) {
|
|
|
1106
1206
|
leaves: [...branch.leaves, ...prefixed]
|
|
1107
1207
|
};
|
|
1108
1208
|
}
|
|
1209
|
+
ctx.unsupportedShapeSeen = true;
|
|
1109
1210
|
return void 0;
|
|
1110
1211
|
}
|
|
1111
|
-
function
|
|
1212
|
+
function expressionKey(expression) {
|
|
1213
|
+
const source = expression.getSourceFile();
|
|
1214
|
+
return `${import_node_path.default.resolve(source.fileName)}:${expression.getStart(source)}:${expression.getEnd()}:${expression.kind}`;
|
|
1215
|
+
}
|
|
1216
|
+
function evaluateLeavesFromExpression(expression, ctx, depth = 0) {
|
|
1112
1217
|
const resolved = unwrapExpression(expression);
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
ctx.
|
|
1218
|
+
markFile(ctx, resolved.getSourceFile());
|
|
1219
|
+
const key = expressionKey(resolved);
|
|
1220
|
+
if (ctx.activeExpressionKeys.has(key)) return [];
|
|
1221
|
+
if (depth > MAX_RECURSION_DEPTH) return [];
|
|
1222
|
+
ctx.activeExpressionKeys.add(key);
|
|
1118
1223
|
try {
|
|
1119
1224
|
if (import_typescript.default.isIdentifier(resolved)) {
|
|
1120
|
-
const valueExpr = resolveIdentifierExpression(resolved, ctx);
|
|
1225
|
+
const valueExpr = resolveIdentifierExpression(resolved, ctx, depth + 1);
|
|
1121
1226
|
if (!valueExpr) return [];
|
|
1122
|
-
return evaluateLeavesFromExpression(valueExpr, ctx);
|
|
1227
|
+
return evaluateLeavesFromExpression(valueExpr, ctx, depth + 1);
|
|
1228
|
+
}
|
|
1229
|
+
if (import_typescript.default.isPropertyAccessExpression(resolved) || import_typescript.default.isElementAccessExpression(resolved)) {
|
|
1230
|
+
if (isAllAccess(resolved)) {
|
|
1231
|
+
return evaluateLeavesFromExpression(resolved.expression, ctx, depth + 1);
|
|
1232
|
+
}
|
|
1233
|
+
const refExpr = resolvePropertyExpression(resolved, ctx, depth + 1);
|
|
1234
|
+
if (refExpr) {
|
|
1235
|
+
return evaluateLeavesFromExpression(refExpr, ctx, depth + 1);
|
|
1236
|
+
}
|
|
1237
|
+
return [];
|
|
1123
1238
|
}
|
|
1124
1239
|
if (import_typescript.default.isArrayLiteralExpression(resolved)) {
|
|
1125
1240
|
const leaves = [];
|
|
1126
1241
|
for (const element of resolved.elements) {
|
|
1127
1242
|
if (import_typescript.default.isSpreadElement(element)) {
|
|
1128
|
-
leaves.push(...evaluateLeavesFromExpression(element.expression, ctx));
|
|
1243
|
+
leaves.push(...evaluateLeavesFromExpression(element.expression, ctx, depth + 1));
|
|
1129
1244
|
continue;
|
|
1130
1245
|
}
|
|
1131
|
-
leaves.push(...evaluateLeavesFromExpression(element, ctx));
|
|
1246
|
+
leaves.push(...evaluateLeavesFromExpression(element, ctx, depth + 1));
|
|
1132
1247
|
}
|
|
1133
1248
|
return leaves;
|
|
1134
1249
|
}
|
|
@@ -1138,34 +1253,43 @@ function evaluateLeavesFromExpression(expression, ctx) {
|
|
|
1138
1253
|
if (callName === "finalize") {
|
|
1139
1254
|
const arg = resolved.arguments[0];
|
|
1140
1255
|
if (!arg) return [];
|
|
1141
|
-
return evaluateLeavesFromExpression(arg, ctx);
|
|
1256
|
+
return evaluateLeavesFromExpression(arg, ctx, depth + 1);
|
|
1142
1257
|
}
|
|
1143
1258
|
if (callName === "mergeArrays") {
|
|
1144
1259
|
return resolved.arguments.flatMap(
|
|
1145
|
-
(arg) => evaluateLeavesFromExpression(arg, ctx)
|
|
1260
|
+
(arg) => evaluateLeavesFromExpression(arg, ctx, depth + 1)
|
|
1146
1261
|
);
|
|
1147
1262
|
}
|
|
1148
1263
|
}
|
|
1149
1264
|
if (import_typescript.default.isPropertyAccessExpression(resolved.expression)) {
|
|
1150
1265
|
const prop = resolved.expression.name.text;
|
|
1151
1266
|
if (prop === "done") {
|
|
1152
|
-
const branch2 = evaluateBranchExpression(
|
|
1267
|
+
const branch2 = evaluateBranchExpression(
|
|
1268
|
+
resolved.expression.expression,
|
|
1269
|
+
ctx,
|
|
1270
|
+
depth + 1
|
|
1271
|
+
);
|
|
1153
1272
|
return branch2?.leaves ?? [];
|
|
1154
1273
|
}
|
|
1155
1274
|
}
|
|
1275
|
+
const refExpr = evaluateExpressionReference(resolved, ctx, depth + 1);
|
|
1276
|
+
if (refExpr) return evaluateLeavesFromExpression(refExpr, ctx, depth + 1);
|
|
1277
|
+
ctx.unsupportedShapeSeen = true;
|
|
1278
|
+
return [];
|
|
1156
1279
|
}
|
|
1157
|
-
const branch = evaluateBranchExpression(resolved, ctx);
|
|
1158
|
-
return branch
|
|
1280
|
+
const branch = evaluateBranchExpression(resolved, ctx, depth + 1);
|
|
1281
|
+
if (branch) return branch.leaves;
|
|
1282
|
+
ctx.unsupportedShapeSeen = true;
|
|
1283
|
+
return [];
|
|
1159
1284
|
} finally {
|
|
1160
|
-
ctx.
|
|
1285
|
+
ctx.activeExpressionKeys.delete(key);
|
|
1161
1286
|
}
|
|
1162
1287
|
}
|
|
1163
1288
|
function resolveSchemaMetadata(expression, ctx) {
|
|
1164
1289
|
const resolved = unwrapExpression(expression);
|
|
1165
1290
|
if (import_typescript.default.isIdentifier(resolved)) {
|
|
1166
|
-
const symbol =
|
|
1167
|
-
const
|
|
1168
|
-
const declaration = target?.declarations?.[0];
|
|
1291
|
+
const symbol = resolveSymbolFromNode(resolved, ctx);
|
|
1292
|
+
const declaration = symbol?.declarations?.[0];
|
|
1169
1293
|
const locationNode = declaration ? import_typescript.default.isVariableDeclaration(declaration) ? declaration.name : declaration : resolved;
|
|
1170
1294
|
const sourceName = declaration && import_typescript.default.isVariableDeclaration(declaration) && import_typescript.default.isIdentifier(declaration.name) ? declaration.name.text : resolved.text;
|
|
1171
1295
|
return {
|
|
@@ -1203,13 +1327,25 @@ function parseTsConfig(cwd, tsconfigPath) {
|
|
|
1203
1327
|
void 0,
|
|
1204
1328
|
resolvedTsconfig
|
|
1205
1329
|
);
|
|
1206
|
-
|
|
1330
|
+
const nonEmptyInputErrors = parsed.errors.filter((entry) => entry.code !== 18003);
|
|
1331
|
+
if (nonEmptyInputErrors.length > 0) {
|
|
1207
1332
|
throw new Error(
|
|
1208
|
-
|
|
1333
|
+
nonEmptyInputErrors.map((entry) => import_typescript.default.flattenDiagnosticMessageText(entry.messageText, "\n")).join("\n")
|
|
1209
1334
|
);
|
|
1210
1335
|
}
|
|
1211
1336
|
return { resolvedTsconfig, parsed };
|
|
1212
1337
|
}
|
|
1338
|
+
function createProgramWithFallback(parsed, moduleFileAbs) {
|
|
1339
|
+
const base = import_typescript.default.createProgram({
|
|
1340
|
+
rootNames: parsed.fileNames,
|
|
1341
|
+
options: parsed.options
|
|
1342
|
+
});
|
|
1343
|
+
if (findSourceFile(base, moduleFileAbs)) {
|
|
1344
|
+
return base;
|
|
1345
|
+
}
|
|
1346
|
+
const rootNames = Array.from(/* @__PURE__ */ new Set([...parsed.fileNames, moduleFileAbs]));
|
|
1347
|
+
return import_typescript.default.createProgram({ rootNames, options: parsed.options });
|
|
1348
|
+
}
|
|
1213
1349
|
function extractLeafSourceByAst({
|
|
1214
1350
|
modulePath,
|
|
1215
1351
|
exportName,
|
|
@@ -1218,32 +1354,43 @@ function extractLeafSourceByAst({
|
|
|
1218
1354
|
}) {
|
|
1219
1355
|
const parsedConfig = parseTsConfig(cwd, tsconfigPath);
|
|
1220
1356
|
if (!parsedConfig) {
|
|
1221
|
-
return {
|
|
1357
|
+
return {
|
|
1358
|
+
sourceByLeaf: {},
|
|
1359
|
+
reason: "module_not_in_program",
|
|
1360
|
+
stats: { visitedSymbols: 0, visitedFiles: 0, unresolvedReferences: 0 }
|
|
1361
|
+
};
|
|
1222
1362
|
}
|
|
1223
|
-
const
|
|
1224
|
-
|
|
1225
|
-
options: parsedConfig.parsed.options
|
|
1226
|
-
});
|
|
1363
|
+
const moduleAbs = import_node_path.default.resolve(cwd, modulePath);
|
|
1364
|
+
const program = createProgramWithFallback(parsedConfig.parsed, moduleAbs);
|
|
1227
1365
|
const checker = program.getTypeChecker();
|
|
1228
|
-
const moduleFile = findSourceFile(program,
|
|
1366
|
+
const moduleFile = findSourceFile(program, moduleAbs);
|
|
1229
1367
|
if (!moduleFile) {
|
|
1230
1368
|
return {
|
|
1231
1369
|
sourceByLeaf: {},
|
|
1232
|
-
tsconfigPath: parsedConfig.resolvedTsconfig
|
|
1370
|
+
tsconfigPath: parsedConfig.resolvedTsconfig,
|
|
1371
|
+
reason: "module_not_in_program",
|
|
1372
|
+
stats: { visitedSymbols: 0, visitedFiles: 0, unresolvedReferences: 0 }
|
|
1233
1373
|
};
|
|
1234
1374
|
}
|
|
1235
1375
|
const exportedExpression = resolveExportExpression(moduleFile, exportName, checker);
|
|
1236
1376
|
if (!exportedExpression) {
|
|
1237
1377
|
return {
|
|
1238
1378
|
sourceByLeaf: {},
|
|
1239
|
-
tsconfigPath: parsedConfig.resolvedTsconfig
|
|
1379
|
+
tsconfigPath: parsedConfig.resolvedTsconfig,
|
|
1380
|
+
reason: "export_not_found",
|
|
1381
|
+
stats: { visitedSymbols: 0, visitedFiles: 1, unresolvedReferences: 0 }
|
|
1240
1382
|
};
|
|
1241
1383
|
}
|
|
1242
1384
|
const ctx = {
|
|
1243
1385
|
checker,
|
|
1244
|
-
|
|
1386
|
+
activeExpressionKeys: /* @__PURE__ */ new Set(),
|
|
1387
|
+
activeSymbols: /* @__PURE__ */ new Set(),
|
|
1388
|
+
visitedSymbolKeys: /* @__PURE__ */ new Set(),
|
|
1389
|
+
visitedFilePaths: /* @__PURE__ */ new Set([import_node_path.default.resolve(moduleFile.fileName)]),
|
|
1390
|
+
unresolvedReferences: 0,
|
|
1391
|
+
unsupportedShapeSeen: false
|
|
1245
1392
|
};
|
|
1246
|
-
const evaluatedLeaves = evaluateLeavesFromExpression(exportedExpression, ctx);
|
|
1393
|
+
const evaluatedLeaves = evaluateLeavesFromExpression(exportedExpression, ctx, 0);
|
|
1247
1394
|
const sourceByLeaf = {};
|
|
1248
1395
|
for (const leaf of evaluatedLeaves) {
|
|
1249
1396
|
const key = buildLeafKey(leaf.method, leaf.path);
|
|
@@ -1251,17 +1398,27 @@ function extractLeafSourceByAst({
|
|
|
1251
1398
|
...toLocation(leaf.definitionNode),
|
|
1252
1399
|
symbolName: getNearestVariableName(leaf.definitionNode)
|
|
1253
1400
|
};
|
|
1401
|
+
ctx.visitedFilePaths.add(definition.file);
|
|
1254
1402
|
const schemas = {};
|
|
1255
1403
|
for (const schemaKey of SCHEMA_KEYS) {
|
|
1256
1404
|
const schemaExpression = leaf.schemas[schemaKey];
|
|
1257
1405
|
if (!schemaExpression) continue;
|
|
1258
|
-
|
|
1406
|
+
const schemaMeta = resolveSchemaMetadata(schemaExpression, ctx);
|
|
1407
|
+
ctx.visitedFilePaths.add(schemaMeta.file);
|
|
1408
|
+
schemas[schemaKey] = schemaMeta;
|
|
1259
1409
|
}
|
|
1260
1410
|
sourceByLeaf[key] = { definition, schemas };
|
|
1261
1411
|
}
|
|
1412
|
+
const reason = Object.keys(sourceByLeaf).length > 0 ? void 0 : ctx.unsupportedShapeSeen ? "unsupported_expression_shape" : "resolved_zero_leaves";
|
|
1262
1413
|
return {
|
|
1263
1414
|
sourceByLeaf,
|
|
1264
|
-
tsconfigPath: parsedConfig.resolvedTsconfig
|
|
1415
|
+
tsconfigPath: parsedConfig.resolvedTsconfig,
|
|
1416
|
+
reason,
|
|
1417
|
+
stats: {
|
|
1418
|
+
visitedSymbols: ctx.visitedSymbolKeys.size,
|
|
1419
|
+
visitedFiles: ctx.visitedFilePaths.size,
|
|
1420
|
+
unresolvedReferences: ctx.unresolvedReferences
|
|
1421
|
+
}
|
|
1265
1422
|
};
|
|
1266
1423
|
}
|
|
1267
1424
|
|
|
@@ -1444,7 +1601,9 @@ async function exportFinalizedLeaves(input, options = {}) {
|
|
|
1444
1601
|
modulePath: import_node_path2.default.resolve(modulePath),
|
|
1445
1602
|
exportName,
|
|
1446
1603
|
tsconfigPath: extracted.tsconfigPath,
|
|
1447
|
-
resolvedLeafCount: Object.keys(sourceByLeaf).length
|
|
1604
|
+
resolvedLeafCount: Object.keys(sourceByLeaf).length,
|
|
1605
|
+
reason: extracted.reason,
|
|
1606
|
+
stats: extracted.stats
|
|
1448
1607
|
};
|
|
1449
1608
|
} else {
|
|
1450
1609
|
sourceExtraction = {
|
|
@@ -1452,7 +1611,13 @@ async function exportFinalizedLeaves(input, options = {}) {
|
|
|
1452
1611
|
enabled: false,
|
|
1453
1612
|
exportName,
|
|
1454
1613
|
tsconfigPath: options.tsconfigPath ? import_node_path2.default.resolve(options.tsconfigPath) : void 0,
|
|
1455
|
-
resolvedLeafCount: 0
|
|
1614
|
+
resolvedLeafCount: 0,
|
|
1615
|
+
reason: "resolved_zero_leaves",
|
|
1616
|
+
stats: {
|
|
1617
|
+
visitedSymbols: 0,
|
|
1618
|
+
visitedFiles: 0,
|
|
1619
|
+
unresolvedReferences: 0
|
|
1620
|
+
}
|
|
1456
1621
|
};
|
|
1457
1622
|
}
|
|
1458
1623
|
}
|