@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 +19 -1
- package/bin/rrroutes-export-finalized-leaves.mjs +14 -0
- package/dist/export/defaultViewerTemplate.d.ts +1 -1
- package/dist/export/exportFinalizedLeaves.cli.d.ts +2 -0
- package/dist/export/exportFinalizedLeaves.d.ts +14 -0
- package/dist/export/extractLeafSourceByAst.d.ts +30 -0
- package/dist/export/index.d.ts +1 -0
- package/dist/index.cjs +540 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +539 -38
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -2
- package/tools/finalized-leaves-viewer.html +272 -138
package/dist/index.cjs
CHANGED
|
@@ -39,6 +39,7 @@ __export(index_exports, {
|
|
|
39
39
|
createSchemaIntrospector: () => createSchemaIntrospector,
|
|
40
40
|
defineSocketEvents: () => defineSocketEvents,
|
|
41
41
|
exportFinalizedLeaves: () => exportFinalizedLeaves,
|
|
42
|
+
extractLeafSourceByAst: () => extractLeafSourceByAst,
|
|
42
43
|
finalize: () => finalize,
|
|
43
44
|
flattenLeafSchemas: () => flattenLeafSchemas,
|
|
44
45
|
flattenSerializableSchema: () => flattenSerializableSchema,
|
|
@@ -107,9 +108,9 @@ function collectNestedFieldSuggestions(shape, prefix = []) {
|
|
|
107
108
|
}
|
|
108
109
|
return suggestions;
|
|
109
110
|
}
|
|
110
|
-
function compilePath(
|
|
111
|
-
if (!params) return
|
|
112
|
-
const withParams =
|
|
111
|
+
function compilePath(path4, params) {
|
|
112
|
+
if (!params) return path4;
|
|
113
|
+
const withParams = path4.replace(/:([A-Za-z0-9_]+)/g, (_, k) => {
|
|
113
114
|
const v = params[k];
|
|
114
115
|
if (v === void 0 || v === null) throw new Error(`Missing param :${k}`);
|
|
115
116
|
return String(v);
|
|
@@ -184,8 +185,8 @@ function buildLowProfileLeaf(leaf) {
|
|
|
184
185
|
}
|
|
185
186
|
};
|
|
186
187
|
}
|
|
187
|
-
var keyOf = (method,
|
|
188
|
-
const key = `${method.toUpperCase()} ${
|
|
188
|
+
var keyOf = (method, path4, encodeSafe) => {
|
|
189
|
+
const key = `${method.toUpperCase()} ${path4}`;
|
|
189
190
|
return encodeSafe ? encodeURIComponent(key) : key;
|
|
190
191
|
};
|
|
191
192
|
|
|
@@ -361,8 +362,8 @@ function joinPaths(parent, child) {
|
|
|
361
362
|
if (!trimmedChild) return trimmedParent;
|
|
362
363
|
return `${trimmedParent}/${trimmedChild}`;
|
|
363
364
|
}
|
|
364
|
-
function assertDynamicLayerUniqueness(
|
|
365
|
-
const segments =
|
|
365
|
+
function assertDynamicLayerUniqueness(path4, dynamicLayerMap) {
|
|
366
|
+
const segments = path4.split("/").filter(Boolean);
|
|
366
367
|
if (segments.length === 0) return;
|
|
367
368
|
for (let i = 0; i < segments.length; i++) {
|
|
368
369
|
const segment = segments[i];
|
|
@@ -682,45 +683,45 @@ function normalizeType(schema) {
|
|
|
682
683
|
return "unknown";
|
|
683
684
|
}
|
|
684
685
|
}
|
|
685
|
-
function isNonEmptyPath(
|
|
686
|
-
return typeof
|
|
686
|
+
function isNonEmptyPath(path4) {
|
|
687
|
+
return typeof path4 === "string" && path4.length > 0;
|
|
687
688
|
}
|
|
688
|
-
function setNode(out,
|
|
689
|
-
if (!isNonEmptyPath(
|
|
690
|
-
out[
|
|
689
|
+
function setNode(out, path4, schema, inherited) {
|
|
690
|
+
if (!isNonEmptyPath(path4)) return;
|
|
691
|
+
out[path4] = {
|
|
691
692
|
type: normalizeType(schema),
|
|
692
693
|
nullable: inherited.nullable || Boolean(schema.nullable),
|
|
693
694
|
optional: inherited.optional || Boolean(schema.optional)
|
|
694
695
|
};
|
|
695
696
|
}
|
|
696
|
-
function flattenInto(out, schema,
|
|
697
|
-
if (!schema || !isNonEmptyPath(
|
|
697
|
+
function flattenInto(out, schema, path4, inherited) {
|
|
698
|
+
if (!schema || !isNonEmptyPath(path4)) return;
|
|
698
699
|
const nextInherited = {
|
|
699
700
|
optional: inherited.optional || Boolean(schema.optional),
|
|
700
701
|
nullable: inherited.nullable || Boolean(schema.nullable)
|
|
701
702
|
};
|
|
702
703
|
if (schema.kind === "union" && Array.isArray(schema.union) && schema.union.length > 0) {
|
|
703
704
|
schema.union.forEach((option, index) => {
|
|
704
|
-
const optionPath = `${
|
|
705
|
+
const optionPath = `${path4}-${index + 1}`;
|
|
705
706
|
flattenInto(out, option, optionPath, inherited);
|
|
706
707
|
});
|
|
707
708
|
return;
|
|
708
709
|
}
|
|
709
|
-
setNode(out,
|
|
710
|
+
setNode(out, path4, schema, inherited);
|
|
710
711
|
if (schema.kind === "object" && schema.properties) {
|
|
711
712
|
for (const [key, child] of Object.entries(schema.properties)) {
|
|
712
|
-
const childPath = `${
|
|
713
|
+
const childPath = `${path4}.${key}`;
|
|
713
714
|
flattenInto(out, child, childPath, nextInherited);
|
|
714
715
|
}
|
|
715
716
|
return;
|
|
716
717
|
}
|
|
717
718
|
if (schema.kind === "array" && schema.element) {
|
|
718
|
-
flattenInto(out, schema.element, `${
|
|
719
|
+
flattenInto(out, schema.element, `${path4}[]`, nextInherited);
|
|
719
720
|
}
|
|
720
721
|
}
|
|
721
|
-
function flattenSerializableSchema(schema,
|
|
722
|
+
function flattenSerializableSchema(schema, path4) {
|
|
722
723
|
const out = {};
|
|
723
|
-
flattenInto(out, schema,
|
|
724
|
+
flattenInto(out, schema, path4, { optional: false, nullable: false });
|
|
724
725
|
return out;
|
|
725
726
|
}
|
|
726
727
|
function isSerializedLeaf(value) {
|
|
@@ -741,7 +742,7 @@ function flattenLeafSchemas(leaf) {
|
|
|
741
742
|
|
|
742
743
|
// src/export/exportFinalizedLeaves.ts
|
|
743
744
|
var import_promises = __toESM(require("fs/promises"), 1);
|
|
744
|
-
var
|
|
745
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
745
746
|
var import_node_child_process = require("child_process");
|
|
746
747
|
|
|
747
748
|
// src/export/defaultViewerTemplate.ts
|
|
@@ -796,6 +797,13 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
|
|
|
796
797
|
} else {
|
|
797
798
|
statusEl.textContent = 'Loaded baked payload with ' + payload.leaves.length + ' routes.'
|
|
798
799
|
|
|
800
|
+
const toHref = (source) => {
|
|
801
|
+
if (!source || !source.file) return null
|
|
802
|
+
const normalizedPath = String(source.file).replace(/\\\\/g, '/')
|
|
803
|
+
const prefix = normalizedPath.startsWith('/') ? 'file://' : 'file:///'
|
|
804
|
+
return prefix + encodeURI(normalizedPath)
|
|
805
|
+
}
|
|
806
|
+
|
|
799
807
|
payload.leaves.forEach((leaf) => {
|
|
800
808
|
const details = document.createElement('details')
|
|
801
809
|
const summary = document.createElement('summary')
|
|
@@ -804,7 +812,63 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
|
|
|
804
812
|
const pre = document.createElement('pre')
|
|
805
813
|
pre.textContent = JSON.stringify(leaf, null, 2)
|
|
806
814
|
|
|
815
|
+
const source = payload.sourceByLeaf && payload.sourceByLeaf[leaf.key]
|
|
816
|
+
let sourceWrap = null
|
|
817
|
+
if (source) {
|
|
818
|
+
sourceWrap = document.createElement('div')
|
|
819
|
+
sourceWrap.className = 'meta'
|
|
820
|
+
|
|
821
|
+
const definitionHref = toHref(source.definition)
|
|
822
|
+
if (definitionHref) {
|
|
823
|
+
const label = document.createElement('div')
|
|
824
|
+
const link = document.createElement('a')
|
|
825
|
+
link.href = definitionHref
|
|
826
|
+
link.target = '_blank'
|
|
827
|
+
link.rel = 'noopener noreferrer'
|
|
828
|
+
link.textContent =
|
|
829
|
+
'definition: ' +
|
|
830
|
+
source.definition.file +
|
|
831
|
+
':' +
|
|
832
|
+
source.definition.line +
|
|
833
|
+
':' +
|
|
834
|
+
source.definition.column
|
|
835
|
+
label.appendChild(link)
|
|
836
|
+
sourceWrap.appendChild(label)
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
if (source.schemas && typeof source.schemas === 'object') {
|
|
840
|
+
Object.entries(source.schemas).forEach(([name, schema]) => {
|
|
841
|
+
if (!schema) return
|
|
842
|
+
const href = toHref(schema)
|
|
843
|
+
const row = document.createElement('div')
|
|
844
|
+
if (href) {
|
|
845
|
+
const link = document.createElement('a')
|
|
846
|
+
link.href = href
|
|
847
|
+
link.target = '_blank'
|
|
848
|
+
link.rel = 'noopener noreferrer'
|
|
849
|
+
link.textContent =
|
|
850
|
+
name +
|
|
851
|
+
': ' +
|
|
852
|
+
(schema.sourceName || schema.tag || '<anonymous>') +
|
|
853
|
+
' (' +
|
|
854
|
+
schema.file +
|
|
855
|
+
':' +
|
|
856
|
+
schema.line +
|
|
857
|
+
':' +
|
|
858
|
+
schema.column +
|
|
859
|
+
')'
|
|
860
|
+
row.appendChild(link)
|
|
861
|
+
} else {
|
|
862
|
+
row.textContent = name + ': ' + (schema.sourceName || schema.tag || '<anonymous>')
|
|
863
|
+
}
|
|
864
|
+
sourceWrap.appendChild(row)
|
|
865
|
+
})
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
}
|
|
869
|
+
|
|
807
870
|
details.appendChild(summary)
|
|
871
|
+
if (sourceWrap) details.appendChild(sourceWrap)
|
|
808
872
|
details.appendChild(pre)
|
|
809
873
|
resultsEl.appendChild(details)
|
|
810
874
|
})
|
|
@@ -814,6 +878,393 @@ var DEFAULT_VIEWER_TEMPLATE = `<!doctype html>
|
|
|
814
878
|
</html>
|
|
815
879
|
`;
|
|
816
880
|
|
|
881
|
+
// src/export/extractLeafSourceByAst.ts
|
|
882
|
+
var import_node_path = __toESM(require("path"), 1);
|
|
883
|
+
var import_typescript = __toESM(require("typescript"), 1);
|
|
884
|
+
var SCHEMA_KEYS = [
|
|
885
|
+
"bodySchema",
|
|
886
|
+
"querySchema",
|
|
887
|
+
"paramsSchema",
|
|
888
|
+
"outputSchema",
|
|
889
|
+
"outputMetaSchema",
|
|
890
|
+
"queryExtensionSchema"
|
|
891
|
+
];
|
|
892
|
+
var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "post", "put", "patch", "delete"]);
|
|
893
|
+
function toLocation(node) {
|
|
894
|
+
const sourceFile = node.getSourceFile();
|
|
895
|
+
const { line, character } = sourceFile.getLineAndCharacterOfPosition(
|
|
896
|
+
node.getStart(sourceFile)
|
|
897
|
+
);
|
|
898
|
+
return {
|
|
899
|
+
file: import_node_path.default.resolve(sourceFile.fileName),
|
|
900
|
+
line: line + 1,
|
|
901
|
+
column: character + 1
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
function trimPreview(text, max = 80) {
|
|
905
|
+
const normalized = text.replace(/\s+/g, " ").trim();
|
|
906
|
+
return normalized.length > max ? `${normalized.slice(0, max)}...` : normalized;
|
|
907
|
+
}
|
|
908
|
+
function unwrapExpression(expression) {
|
|
909
|
+
let current = expression;
|
|
910
|
+
while (true) {
|
|
911
|
+
if (import_typescript.default.isParenthesizedExpression(current) || import_typescript.default.isAsExpression(current) || import_typescript.default.isTypeAssertionExpression(current) || import_typescript.default.isNonNullExpression(current) || import_typescript.default.isSatisfiesExpression(current)) {
|
|
912
|
+
current = current.expression;
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
break;
|
|
916
|
+
}
|
|
917
|
+
return current;
|
|
918
|
+
}
|
|
919
|
+
function getTextName(name) {
|
|
920
|
+
if (import_typescript.default.isIdentifier(name) || import_typescript.default.isStringLiteral(name) || import_typescript.default.isNumericLiteral(name)) {
|
|
921
|
+
return name.text;
|
|
922
|
+
}
|
|
923
|
+
return void 0;
|
|
924
|
+
}
|
|
925
|
+
function joinPaths2(parent, child) {
|
|
926
|
+
if (!parent) return child;
|
|
927
|
+
if (!child) return parent;
|
|
928
|
+
const trimmedParent = parent.endsWith("/") ? parent.replace(/\/+$/, "") : parent;
|
|
929
|
+
const trimmedChild = child.startsWith("/") ? child.replace(/^\/+/, "") : child;
|
|
930
|
+
if (!trimmedChild) return trimmedParent;
|
|
931
|
+
return `${trimmedParent}/${trimmedChild}`;
|
|
932
|
+
}
|
|
933
|
+
function buildLeafKey(method, leafPath) {
|
|
934
|
+
return `${method.toUpperCase()} ${leafPath}`;
|
|
935
|
+
}
|
|
936
|
+
function normalizeResourceBase(raw) {
|
|
937
|
+
if (raw === "") return "";
|
|
938
|
+
if (raw.startsWith("/") || raw.startsWith(":") || raw.startsWith("*")) {
|
|
939
|
+
return raw;
|
|
940
|
+
}
|
|
941
|
+
return `/${raw}`;
|
|
942
|
+
}
|
|
943
|
+
function getModuleSymbol(checker, sourceFile) {
|
|
944
|
+
return checker.getSymbolAtLocation(sourceFile);
|
|
945
|
+
}
|
|
946
|
+
function getAliasedSymbolIfNeeded(checker, symbol) {
|
|
947
|
+
if ((symbol.flags & import_typescript.default.SymbolFlags.Alias) !== 0) {
|
|
948
|
+
try {
|
|
949
|
+
return checker.getAliasedSymbol(symbol);
|
|
950
|
+
} catch {
|
|
951
|
+
return symbol;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
return symbol;
|
|
955
|
+
}
|
|
956
|
+
function findSourceFile(program, filePath) {
|
|
957
|
+
const wanted = import_node_path.default.resolve(filePath);
|
|
958
|
+
const normalizedWanted = import_node_path.default.normalize(wanted);
|
|
959
|
+
return program.getSourceFiles().find((file) => import_node_path.default.normalize(import_node_path.default.resolve(file.fileName)) === normalizedWanted);
|
|
960
|
+
}
|
|
961
|
+
function expressionFromDeclaration(declaration) {
|
|
962
|
+
if (!declaration) return void 0;
|
|
963
|
+
if (import_typescript.default.isVariableDeclaration(declaration)) {
|
|
964
|
+
return declaration.initializer;
|
|
965
|
+
}
|
|
966
|
+
if (import_typescript.default.isExportAssignment(declaration)) {
|
|
967
|
+
return declaration.expression;
|
|
968
|
+
}
|
|
969
|
+
if (import_typescript.default.isPropertyAssignment(declaration)) {
|
|
970
|
+
return declaration.initializer;
|
|
971
|
+
}
|
|
972
|
+
if (import_typescript.default.isShorthandPropertyAssignment(declaration)) {
|
|
973
|
+
return declaration.name;
|
|
974
|
+
}
|
|
975
|
+
if (import_typescript.default.isBindingElement(declaration)) {
|
|
976
|
+
return declaration.initializer;
|
|
977
|
+
}
|
|
978
|
+
return void 0;
|
|
979
|
+
}
|
|
980
|
+
function resolveIdentifierExpression(identifier, ctx) {
|
|
981
|
+
const symbol = ctx.checker.getSymbolAtLocation(identifier);
|
|
982
|
+
if (!symbol) return void 0;
|
|
983
|
+
const target = getAliasedSymbolIfNeeded(ctx.checker, symbol);
|
|
984
|
+
const declaration = target.declarations?.[0];
|
|
985
|
+
return expressionFromDeclaration(declaration);
|
|
986
|
+
}
|
|
987
|
+
function resolveExportExpression(sourceFile, exportName, checker) {
|
|
988
|
+
const moduleSymbol = getModuleSymbol(checker, sourceFile);
|
|
989
|
+
if (!moduleSymbol) return void 0;
|
|
990
|
+
const exports2 = checker.getExportsOfModule(moduleSymbol);
|
|
991
|
+
const explicit = exports2.find((entry) => entry.getName() === exportName);
|
|
992
|
+
if (explicit) {
|
|
993
|
+
const declaration = getAliasedSymbolIfNeeded(checker, explicit).declarations?.[0];
|
|
994
|
+
return expressionFromDeclaration(declaration);
|
|
995
|
+
}
|
|
996
|
+
const defaultExport = exports2.find((entry) => entry.getName() === "default");
|
|
997
|
+
if (!defaultExport) return void 0;
|
|
998
|
+
const defaultDecl = getAliasedSymbolIfNeeded(checker, defaultExport).declarations?.[0];
|
|
999
|
+
const defaultExpr = expressionFromDeclaration(defaultDecl);
|
|
1000
|
+
if (!defaultExpr) return void 0;
|
|
1001
|
+
const resolved = unwrapExpression(defaultExpr);
|
|
1002
|
+
if (!import_typescript.default.isObjectLiteralExpression(resolved)) return void 0;
|
|
1003
|
+
for (const property of resolved.properties) {
|
|
1004
|
+
if (!import_typescript.default.isPropertyAssignment(property)) continue;
|
|
1005
|
+
const propertyName = getTextName(property.name);
|
|
1006
|
+
if (propertyName !== exportName) continue;
|
|
1007
|
+
return property.initializer;
|
|
1008
|
+
}
|
|
1009
|
+
return void 0;
|
|
1010
|
+
}
|
|
1011
|
+
function maybeObjectLiteral(expression, ctx) {
|
|
1012
|
+
if (!expression) return void 0;
|
|
1013
|
+
const resolved = unwrapExpression(expression);
|
|
1014
|
+
if (import_typescript.default.isObjectLiteralExpression(resolved)) return resolved;
|
|
1015
|
+
if (import_typescript.default.isIdentifier(resolved)) {
|
|
1016
|
+
const target = resolveIdentifierExpression(resolved, ctx);
|
|
1017
|
+
if (!target) return void 0;
|
|
1018
|
+
return maybeObjectLiteral(target, ctx);
|
|
1019
|
+
}
|
|
1020
|
+
return void 0;
|
|
1021
|
+
}
|
|
1022
|
+
function collectSchemaExpressionsFromObject(objectLiteral, ctx) {
|
|
1023
|
+
const schemas = {};
|
|
1024
|
+
for (const property of objectLiteral.properties) {
|
|
1025
|
+
if (import_typescript.default.isSpreadAssignment(property)) {
|
|
1026
|
+
const spreadObject = maybeObjectLiteral(property.expression, ctx);
|
|
1027
|
+
if (!spreadObject) continue;
|
|
1028
|
+
Object.assign(schemas, collectSchemaExpressionsFromObject(spreadObject, ctx));
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
if (import_typescript.default.isPropertyAssignment(property)) {
|
|
1032
|
+
const key = getTextName(property.name);
|
|
1033
|
+
if (!key || !SCHEMA_KEYS.includes(key)) continue;
|
|
1034
|
+
schemas[key] = property.initializer;
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
if (import_typescript.default.isShorthandPropertyAssignment(property)) {
|
|
1038
|
+
const key = property.name.text;
|
|
1039
|
+
if (!SCHEMA_KEYS.includes(key)) continue;
|
|
1040
|
+
schemas[key] = property.name;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
return schemas;
|
|
1044
|
+
}
|
|
1045
|
+
function extractSchemaExpressions(cfgExpression, ctx) {
|
|
1046
|
+
const objectLiteral = maybeObjectLiteral(cfgExpression, ctx);
|
|
1047
|
+
if (!objectLiteral) return {};
|
|
1048
|
+
return collectSchemaExpressionsFromObject(objectLiteral, ctx);
|
|
1049
|
+
}
|
|
1050
|
+
function getNearestVariableName(node) {
|
|
1051
|
+
let current = node;
|
|
1052
|
+
while (current) {
|
|
1053
|
+
if (import_typescript.default.isVariableDeclaration(current) && import_typescript.default.isIdentifier(current.name)) {
|
|
1054
|
+
return current.name.text;
|
|
1055
|
+
}
|
|
1056
|
+
current = current.parent;
|
|
1057
|
+
}
|
|
1058
|
+
return void 0;
|
|
1059
|
+
}
|
|
1060
|
+
function evaluateBranchExpression(expression, ctx) {
|
|
1061
|
+
const resolved = unwrapExpression(expression);
|
|
1062
|
+
if (import_typescript.default.isIdentifier(resolved)) {
|
|
1063
|
+
const valueExpr = resolveIdentifierExpression(resolved, ctx);
|
|
1064
|
+
if (!valueExpr) return void 0;
|
|
1065
|
+
return evaluateBranchExpression(valueExpr, ctx);
|
|
1066
|
+
}
|
|
1067
|
+
if (!import_typescript.default.isCallExpression(resolved)) return void 0;
|
|
1068
|
+
const call = resolved;
|
|
1069
|
+
if (import_typescript.default.isIdentifier(call.expression) && call.expression.text === "resource") {
|
|
1070
|
+
const firstArg = call.arguments[0];
|
|
1071
|
+
if (!firstArg || !import_typescript.default.isStringLiteralLike(firstArg)) return void 0;
|
|
1072
|
+
return { base: normalizeResourceBase(firstArg.text), leaves: [] };
|
|
1073
|
+
}
|
|
1074
|
+
if (!import_typescript.default.isPropertyAccessExpression(call.expression)) return void 0;
|
|
1075
|
+
const owner = call.expression.expression;
|
|
1076
|
+
const method = call.expression.name.text;
|
|
1077
|
+
const branch = evaluateBranchExpression(owner, ctx);
|
|
1078
|
+
if (!branch) return void 0;
|
|
1079
|
+
if (method === "with") {
|
|
1080
|
+
return branch;
|
|
1081
|
+
}
|
|
1082
|
+
if (HTTP_METHODS.has(method)) {
|
|
1083
|
+
const cfgExpression = call.arguments[0];
|
|
1084
|
+
const schemas = extractSchemaExpressions(cfgExpression, ctx);
|
|
1085
|
+
const nextLeaf = {
|
|
1086
|
+
method,
|
|
1087
|
+
path: branch.base,
|
|
1088
|
+
definitionNode: call.expression.name,
|
|
1089
|
+
schemas
|
|
1090
|
+
};
|
|
1091
|
+
return {
|
|
1092
|
+
...branch,
|
|
1093
|
+
leaves: [...branch.leaves, nextLeaf]
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
if (method === "sub") {
|
|
1097
|
+
const mountedLeaves = call.arguments.flatMap(
|
|
1098
|
+
(arg) => evaluateLeavesFromExpression(arg, ctx)
|
|
1099
|
+
);
|
|
1100
|
+
const prefixed = mountedLeaves.map((leaf) => ({
|
|
1101
|
+
...leaf,
|
|
1102
|
+
path: joinPaths2(branch.base, leaf.path)
|
|
1103
|
+
}));
|
|
1104
|
+
return {
|
|
1105
|
+
...branch,
|
|
1106
|
+
leaves: [...branch.leaves, ...prefixed]
|
|
1107
|
+
};
|
|
1108
|
+
}
|
|
1109
|
+
return void 0;
|
|
1110
|
+
}
|
|
1111
|
+
function evaluateLeavesFromExpression(expression, ctx) {
|
|
1112
|
+
const resolved = unwrapExpression(expression);
|
|
1113
|
+
const key = resolved.getStart(resolved.getSourceFile());
|
|
1114
|
+
if (ctx.visitedExpressionStarts.has(key)) {
|
|
1115
|
+
return [];
|
|
1116
|
+
}
|
|
1117
|
+
ctx.visitedExpressionStarts.add(key);
|
|
1118
|
+
try {
|
|
1119
|
+
if (import_typescript.default.isIdentifier(resolved)) {
|
|
1120
|
+
const valueExpr = resolveIdentifierExpression(resolved, ctx);
|
|
1121
|
+
if (!valueExpr) return [];
|
|
1122
|
+
return evaluateLeavesFromExpression(valueExpr, ctx);
|
|
1123
|
+
}
|
|
1124
|
+
if (import_typescript.default.isArrayLiteralExpression(resolved)) {
|
|
1125
|
+
const leaves = [];
|
|
1126
|
+
for (const element of resolved.elements) {
|
|
1127
|
+
if (import_typescript.default.isSpreadElement(element)) {
|
|
1128
|
+
leaves.push(...evaluateLeavesFromExpression(element.expression, ctx));
|
|
1129
|
+
continue;
|
|
1130
|
+
}
|
|
1131
|
+
leaves.push(...evaluateLeavesFromExpression(element, ctx));
|
|
1132
|
+
}
|
|
1133
|
+
return leaves;
|
|
1134
|
+
}
|
|
1135
|
+
if (import_typescript.default.isCallExpression(resolved)) {
|
|
1136
|
+
if (import_typescript.default.isIdentifier(resolved.expression)) {
|
|
1137
|
+
const callName = resolved.expression.text;
|
|
1138
|
+
if (callName === "finalize") {
|
|
1139
|
+
const arg = resolved.arguments[0];
|
|
1140
|
+
if (!arg) return [];
|
|
1141
|
+
return evaluateLeavesFromExpression(arg, ctx);
|
|
1142
|
+
}
|
|
1143
|
+
if (callName === "mergeArrays") {
|
|
1144
|
+
return resolved.arguments.flatMap(
|
|
1145
|
+
(arg) => evaluateLeavesFromExpression(arg, ctx)
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
if (import_typescript.default.isPropertyAccessExpression(resolved.expression)) {
|
|
1150
|
+
const prop = resolved.expression.name.text;
|
|
1151
|
+
if (prop === "done") {
|
|
1152
|
+
const branch2 = evaluateBranchExpression(resolved.expression.expression, ctx);
|
|
1153
|
+
return branch2?.leaves ?? [];
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
const branch = evaluateBranchExpression(resolved, ctx);
|
|
1158
|
+
return branch?.leaves ?? [];
|
|
1159
|
+
} finally {
|
|
1160
|
+
ctx.visitedExpressionStarts.delete(key);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
function resolveSchemaMetadata(expression, ctx) {
|
|
1164
|
+
const resolved = unwrapExpression(expression);
|
|
1165
|
+
if (import_typescript.default.isIdentifier(resolved)) {
|
|
1166
|
+
const symbol = ctx.checker.getSymbolAtLocation(resolved);
|
|
1167
|
+
const target = symbol ? getAliasedSymbolIfNeeded(ctx.checker, symbol) : void 0;
|
|
1168
|
+
const declaration = target?.declarations?.[0];
|
|
1169
|
+
const locationNode = declaration ? import_typescript.default.isVariableDeclaration(declaration) ? declaration.name : declaration : resolved;
|
|
1170
|
+
const sourceName = declaration && import_typescript.default.isVariableDeclaration(declaration) && import_typescript.default.isIdentifier(declaration.name) ? declaration.name.text : resolved.text;
|
|
1171
|
+
return {
|
|
1172
|
+
...toLocation(locationNode),
|
|
1173
|
+
sourceName,
|
|
1174
|
+
tag: declaration ? void 0 : "<anonymous>"
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
if (import_typescript.default.isPropertyAccessExpression(resolved) || import_typescript.default.isElementAccessExpression(resolved)) {
|
|
1178
|
+
return {
|
|
1179
|
+
...toLocation(resolved),
|
|
1180
|
+
sourceName: trimPreview(resolved.getText()),
|
|
1181
|
+
tag: "<expression>"
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
return {
|
|
1185
|
+
...toLocation(resolved),
|
|
1186
|
+
sourceName: trimPreview(resolved.getText()),
|
|
1187
|
+
tag: "<inline>"
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
function parseTsConfig(cwd, tsconfigPath) {
|
|
1191
|
+
const resolvedTsconfig = tsconfigPath ? import_node_path.default.resolve(cwd, tsconfigPath) : import_typescript.default.findConfigFile(cwd, import_typescript.default.sys.fileExists, "tsconfig.json");
|
|
1192
|
+
if (!resolvedTsconfig) {
|
|
1193
|
+
return void 0;
|
|
1194
|
+
}
|
|
1195
|
+
const read = import_typescript.default.readConfigFile(resolvedTsconfig, import_typescript.default.sys.readFile);
|
|
1196
|
+
if (read.error) {
|
|
1197
|
+
throw new Error(import_typescript.default.flattenDiagnosticMessageText(read.error.messageText, "\n"));
|
|
1198
|
+
}
|
|
1199
|
+
const parsed = import_typescript.default.parseJsonConfigFileContent(
|
|
1200
|
+
read.config,
|
|
1201
|
+
import_typescript.default.sys,
|
|
1202
|
+
import_node_path.default.dirname(resolvedTsconfig),
|
|
1203
|
+
void 0,
|
|
1204
|
+
resolvedTsconfig
|
|
1205
|
+
);
|
|
1206
|
+
if (parsed.errors.length > 0) {
|
|
1207
|
+
throw new Error(
|
|
1208
|
+
parsed.errors.map((entry) => import_typescript.default.flattenDiagnosticMessageText(entry.messageText, "\n")).join("\n")
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
return { resolvedTsconfig, parsed };
|
|
1212
|
+
}
|
|
1213
|
+
function extractLeafSourceByAst({
|
|
1214
|
+
modulePath,
|
|
1215
|
+
exportName,
|
|
1216
|
+
tsconfigPath,
|
|
1217
|
+
cwd = process.cwd()
|
|
1218
|
+
}) {
|
|
1219
|
+
const parsedConfig = parseTsConfig(cwd, tsconfigPath);
|
|
1220
|
+
if (!parsedConfig) {
|
|
1221
|
+
return { sourceByLeaf: {} };
|
|
1222
|
+
}
|
|
1223
|
+
const program = import_typescript.default.createProgram({
|
|
1224
|
+
rootNames: parsedConfig.parsed.fileNames,
|
|
1225
|
+
options: parsedConfig.parsed.options
|
|
1226
|
+
});
|
|
1227
|
+
const checker = program.getTypeChecker();
|
|
1228
|
+
const moduleFile = findSourceFile(program, import_node_path.default.resolve(cwd, modulePath));
|
|
1229
|
+
if (!moduleFile) {
|
|
1230
|
+
return {
|
|
1231
|
+
sourceByLeaf: {},
|
|
1232
|
+
tsconfigPath: parsedConfig.resolvedTsconfig
|
|
1233
|
+
};
|
|
1234
|
+
}
|
|
1235
|
+
const exportedExpression = resolveExportExpression(moduleFile, exportName, checker);
|
|
1236
|
+
if (!exportedExpression) {
|
|
1237
|
+
return {
|
|
1238
|
+
sourceByLeaf: {},
|
|
1239
|
+
tsconfigPath: parsedConfig.resolvedTsconfig
|
|
1240
|
+
};
|
|
1241
|
+
}
|
|
1242
|
+
const ctx = {
|
|
1243
|
+
checker,
|
|
1244
|
+
visitedExpressionStarts: /* @__PURE__ */ new Set()
|
|
1245
|
+
};
|
|
1246
|
+
const evaluatedLeaves = evaluateLeavesFromExpression(exportedExpression, ctx);
|
|
1247
|
+
const sourceByLeaf = {};
|
|
1248
|
+
for (const leaf of evaluatedLeaves) {
|
|
1249
|
+
const key = buildLeafKey(leaf.method, leaf.path);
|
|
1250
|
+
const definition = {
|
|
1251
|
+
...toLocation(leaf.definitionNode),
|
|
1252
|
+
symbolName: getNearestVariableName(leaf.definitionNode)
|
|
1253
|
+
};
|
|
1254
|
+
const schemas = {};
|
|
1255
|
+
for (const schemaKey of SCHEMA_KEYS) {
|
|
1256
|
+
const schemaExpression = leaf.schemas[schemaKey];
|
|
1257
|
+
if (!schemaExpression) continue;
|
|
1258
|
+
schemas[schemaKey] = resolveSchemaMetadata(schemaExpression, ctx);
|
|
1259
|
+
}
|
|
1260
|
+
sourceByLeaf[key] = { definition, schemas };
|
|
1261
|
+
}
|
|
1262
|
+
return {
|
|
1263
|
+
sourceByLeaf,
|
|
1264
|
+
tsconfigPath: parsedConfig.resolvedTsconfig
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
|
|
817
1268
|
// src/export/exportFinalizedLeaves.ts
|
|
818
1269
|
function isRegistry(value) {
|
|
819
1270
|
return typeof value === "object" && value !== null && "all" in value && "byKey" in value;
|
|
@@ -821,10 +1272,11 @@ function isRegistry(value) {
|
|
|
821
1272
|
function getLeaves(input) {
|
|
822
1273
|
return isRegistry(input) ? input.all : input;
|
|
823
1274
|
}
|
|
824
|
-
function buildMeta() {
|
|
1275
|
+
function buildMeta(sourceExtraction) {
|
|
825
1276
|
return {
|
|
826
1277
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
827
1278
|
description: "Finalized RRRoutes leaves export with serialized schemas and flattened schema paths for downstream processing.",
|
|
1279
|
+
sourceExtraction,
|
|
828
1280
|
fieldCatalog: {
|
|
829
1281
|
leaf: ["key", "method", "path", "cfg"],
|
|
830
1282
|
cfg: [
|
|
@@ -881,20 +1333,20 @@ ${htmlTemplate}`;
|
|
|
881
1333
|
}
|
|
882
1334
|
async function resolveViewerTemplatePath(viewerTemplateFile) {
|
|
883
1335
|
if (viewerTemplateFile) {
|
|
884
|
-
const resolved =
|
|
1336
|
+
const resolved = import_node_path2.default.resolve(viewerTemplateFile);
|
|
885
1337
|
await import_promises.default.access(resolved);
|
|
886
1338
|
return resolved;
|
|
887
1339
|
}
|
|
888
1340
|
const candidates = [
|
|
889
|
-
|
|
1341
|
+
import_node_path2.default.resolve(
|
|
890
1342
|
process.cwd(),
|
|
891
1343
|
"node_modules/@emeryld/rrroutes-contract/tools/finalized-leaves-viewer.html"
|
|
892
1344
|
),
|
|
893
|
-
|
|
1345
|
+
import_node_path2.default.resolve(
|
|
894
1346
|
process.cwd(),
|
|
895
1347
|
"tools/finalized-leaves-viewer.html"
|
|
896
1348
|
),
|
|
897
|
-
|
|
1349
|
+
import_node_path2.default.resolve(
|
|
898
1350
|
process.cwd(),
|
|
899
1351
|
"packages/contract/tools/finalized-leaves-viewer.html"
|
|
900
1352
|
)
|
|
@@ -909,8 +1361,8 @@ async function resolveViewerTemplatePath(viewerTemplateFile) {
|
|
|
909
1361
|
return void 0;
|
|
910
1362
|
}
|
|
911
1363
|
async function writeJsonExport(payload, outFile) {
|
|
912
|
-
const resolved =
|
|
913
|
-
await import_promises.default.mkdir(
|
|
1364
|
+
const resolved = import_node_path2.default.resolve(outFile);
|
|
1365
|
+
await import_promises.default.mkdir(import_node_path2.default.dirname(resolved), { recursive: true });
|
|
914
1366
|
await import_promises.default.writeFile(resolved, `${JSON.stringify(payload, null, 2)}
|
|
915
1367
|
`, "utf8");
|
|
916
1368
|
return resolved;
|
|
@@ -919,13 +1371,13 @@ async function writeBakedHtmlExport(payload, htmlFile, viewerTemplateFile) {
|
|
|
919
1371
|
const templatePath = await resolveViewerTemplatePath(viewerTemplateFile);
|
|
920
1372
|
const template = templatePath ? await import_promises.default.readFile(templatePath, "utf8") : DEFAULT_VIEWER_TEMPLATE;
|
|
921
1373
|
const baked = injectPayloadIntoViewerHtml(template, payload);
|
|
922
|
-
const resolved =
|
|
923
|
-
await import_promises.default.mkdir(
|
|
1374
|
+
const resolved = import_node_path2.default.resolve(htmlFile);
|
|
1375
|
+
await import_promises.default.mkdir(import_node_path2.default.dirname(resolved), { recursive: true });
|
|
924
1376
|
await import_promises.default.writeFile(resolved, baked, "utf8");
|
|
925
1377
|
return resolved;
|
|
926
1378
|
}
|
|
927
1379
|
async function openHtmlInBrowser(filePath) {
|
|
928
|
-
const resolved =
|
|
1380
|
+
const resolved = import_node_path2.default.resolve(filePath);
|
|
929
1381
|
const platform = process.platform;
|
|
930
1382
|
if (platform === "darwin") {
|
|
931
1383
|
(0, import_node_child_process.spawn)("open", [resolved], { detached: true, stdio: "ignore" }).unref();
|
|
@@ -969,10 +1421,46 @@ async function exportFinalizedLeaves(input, options = {}) {
|
|
|
969
1421
|
const schemaFlatByLeaf = Object.fromEntries(
|
|
970
1422
|
serializedLeaves.map((leaf) => [leaf.key, flattenLeafSchemas(leaf)])
|
|
971
1423
|
);
|
|
1424
|
+
const sourceByLeaf = {};
|
|
1425
|
+
let sourceExtraction;
|
|
1426
|
+
if (options.includeSource) {
|
|
1427
|
+
const modulePath = options.sourceModulePath;
|
|
1428
|
+
const exportName = options.sourceExportName ?? "leaves";
|
|
1429
|
+
if (modulePath) {
|
|
1430
|
+
const extracted = extractLeafSourceByAst({
|
|
1431
|
+
modulePath,
|
|
1432
|
+
exportName,
|
|
1433
|
+
tsconfigPath: options.tsconfigPath
|
|
1434
|
+
});
|
|
1435
|
+
const allowedLeafKeys = new Set(serializedLeaves.map((leaf) => leaf.key));
|
|
1436
|
+
for (const [key, source] of Object.entries(extracted.sourceByLeaf)) {
|
|
1437
|
+
if (allowedLeafKeys.has(key)) {
|
|
1438
|
+
sourceByLeaf[key] = source;
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
sourceExtraction = {
|
|
1442
|
+
mode: "ast",
|
|
1443
|
+
enabled: true,
|
|
1444
|
+
modulePath: import_node_path2.default.resolve(modulePath),
|
|
1445
|
+
exportName,
|
|
1446
|
+
tsconfigPath: extracted.tsconfigPath,
|
|
1447
|
+
resolvedLeafCount: Object.keys(sourceByLeaf).length
|
|
1448
|
+
};
|
|
1449
|
+
} else {
|
|
1450
|
+
sourceExtraction = {
|
|
1451
|
+
mode: "ast",
|
|
1452
|
+
enabled: false,
|
|
1453
|
+
exportName,
|
|
1454
|
+
tsconfigPath: options.tsconfigPath ? import_node_path2.default.resolve(options.tsconfigPath) : void 0,
|
|
1455
|
+
resolvedLeafCount: 0
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
972
1459
|
const payload = {
|
|
973
|
-
_meta: buildMeta(),
|
|
1460
|
+
_meta: buildMeta(sourceExtraction),
|
|
974
1461
|
leaves: serializedLeaves,
|
|
975
|
-
schemaFlatByLeaf
|
|
1462
|
+
schemaFlatByLeaf,
|
|
1463
|
+
sourceByLeaf: options.includeSource && Object.keys(sourceByLeaf).length > 0 ? sourceByLeaf : void 0
|
|
976
1464
|
};
|
|
977
1465
|
if (options.outFile || options.htmlFile) {
|
|
978
1466
|
await writeFinalizedLeavesExport(payload, {
|
|
@@ -986,14 +1474,19 @@ async function exportFinalizedLeaves(input, options = {}) {
|
|
|
986
1474
|
}
|
|
987
1475
|
|
|
988
1476
|
// src/export/exportFinalizedLeaves.cli.ts
|
|
989
|
-
var
|
|
1477
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
990
1478
|
var import_node_url = require("url");
|
|
991
1479
|
function parseFinalizedLeavesCliArgs(argv) {
|
|
992
1480
|
const args = /* @__PURE__ */ new Map();
|
|
1481
|
+
let withSource = false;
|
|
993
1482
|
for (let i = 0; i < argv.length; i += 1) {
|
|
994
1483
|
const key = argv[i];
|
|
995
1484
|
if (key === "--") continue;
|
|
996
1485
|
if (!key.startsWith("--")) continue;
|
|
1486
|
+
if (key === "--with-source") {
|
|
1487
|
+
withSource = true;
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
997
1490
|
const value = argv[i + 1];
|
|
998
1491
|
if (!value || value.startsWith("--")) {
|
|
999
1492
|
throw new Error(`Missing value for ${key}`);
|
|
@@ -1008,14 +1501,16 @@ function parseFinalizedLeavesCliArgs(argv) {
|
|
|
1008
1501
|
return {
|
|
1009
1502
|
modulePath,
|
|
1010
1503
|
exportName: args.get("--export") ?? "leaves",
|
|
1011
|
-
outFile: args.get("--out") ?? "finalized-leaves.export.json"
|
|
1504
|
+
outFile: args.get("--out") ?? "finalized-leaves.export.json",
|
|
1505
|
+
withSource,
|
|
1506
|
+
tsconfigPath: args.get("--tsconfig") ?? void 0
|
|
1012
1507
|
};
|
|
1013
1508
|
}
|
|
1014
1509
|
async function loadFinalizedLeavesInput({
|
|
1015
1510
|
modulePath,
|
|
1016
1511
|
exportName
|
|
1017
1512
|
}) {
|
|
1018
|
-
const resolvedModule =
|
|
1513
|
+
const resolvedModule = import_node_path3.default.resolve(process.cwd(), modulePath);
|
|
1019
1514
|
const mod = await import((0, import_node_url.pathToFileURL)(resolvedModule).href);
|
|
1020
1515
|
const value = mod[exportName] ?? (mod.default && mod.default[exportName]);
|
|
1021
1516
|
if (!value) {
|
|
@@ -1026,10 +1521,16 @@ async function loadFinalizedLeavesInput({
|
|
|
1026
1521
|
async function runExportFinalizedLeavesCli(argv) {
|
|
1027
1522
|
const args = parseFinalizedLeavesCliArgs(argv);
|
|
1028
1523
|
const input = await loadFinalizedLeavesInput(args);
|
|
1029
|
-
const payload = await exportFinalizedLeaves(input, {
|
|
1524
|
+
const payload = await exportFinalizedLeaves(input, {
|
|
1525
|
+
outFile: args.outFile,
|
|
1526
|
+
includeSource: args.withSource,
|
|
1527
|
+
tsconfigPath: args.tsconfigPath,
|
|
1528
|
+
sourceModulePath: args.modulePath,
|
|
1529
|
+
sourceExportName: args.exportName
|
|
1530
|
+
});
|
|
1030
1531
|
return {
|
|
1031
1532
|
payload,
|
|
1032
|
-
outFile:
|
|
1533
|
+
outFile: import_node_path3.default.resolve(args.outFile)
|
|
1033
1534
|
};
|
|
1034
1535
|
}
|
|
1035
1536
|
// Annotate the CommonJS export names for ESM import in node:
|
|
@@ -1043,6 +1544,7 @@ async function runExportFinalizedLeavesCli(argv) {
|
|
|
1043
1544
|
createSchemaIntrospector,
|
|
1044
1545
|
defineSocketEvents,
|
|
1045
1546
|
exportFinalizedLeaves,
|
|
1547
|
+
extractLeafSourceByAst,
|
|
1046
1548
|
finalize,
|
|
1047
1549
|
flattenLeafSchemas,
|
|
1048
1550
|
flattenSerializableSchema,
|