spec-gen-cli 1.1.0 → 1.2.0
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 +163 -77
- package/dist/api/analyze.d.ts.map +1 -1
- package/dist/api/analyze.js +56 -27
- package/dist/api/analyze.js.map +1 -1
- package/dist/api/drift.d.ts.map +1 -1
- package/dist/api/drift.js +26 -20
- package/dist/api/drift.js.map +1 -1
- package/dist/api/generate.d.ts.map +1 -1
- package/dist/api/generate.js +19 -43
- package/dist/api/generate.js.map +1 -1
- package/dist/api/init.d.ts.map +1 -1
- package/dist/api/init.js +6 -5
- package/dist/api/init.js.map +1 -1
- package/dist/api/run.d.ts.map +1 -1
- package/dist/api/run.js +67 -51
- package/dist/api/run.js.map +1 -1
- package/dist/api/specs.d.ts.map +1 -1
- package/dist/api/specs.js +5 -4
- package/dist/api/specs.js.map +1 -1
- package/dist/api/types.d.ts +7 -1
- package/dist/api/types.d.ts.map +1 -1
- package/dist/api/verify.d.ts.map +1 -1
- package/dist/api/verify.js +31 -32
- package/dist/api/verify.js.map +1 -1
- package/dist/cli/commands/analyze.d.ts.map +1 -1
- package/dist/cli/commands/analyze.js +41 -62
- package/dist/cli/commands/analyze.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts +9 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +273 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/drift.d.ts.map +1 -1
- package/dist/cli/commands/drift.js +18 -32
- package/dist/cli/commands/drift.js.map +1 -1
- package/dist/cli/commands/generate.d.ts.map +1 -1
- package/dist/cli/commands/generate.js +40 -101
- package/dist/cli/commands/generate.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +17 -15
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/mcp.d.ts.map +1 -1
- package/dist/cli/commands/mcp.js +2 -1
- package/dist/cli/commands/mcp.js.map +1 -1
- package/dist/cli/commands/run.d.ts +0 -15
- package/dist/cli/commands/run.d.ts.map +1 -1
- package/dist/cli/commands/run.js +61 -111
- package/dist/cli/commands/run.js.map +1 -1
- package/dist/cli/commands/verify.d.ts.map +1 -1
- package/dist/cli/commands/verify.js +18 -52
- package/dist/cli/commands/verify.js.map +1 -1
- package/dist/cli/commands/view.d.ts +4 -0
- package/dist/cli/commands/view.d.ts.map +1 -1
- package/dist/cli/commands/view.js +29 -24
- package/dist/cli/commands/view.js.map +1 -1
- package/dist/cli/index.js +22 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/constants.d.ts +254 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +320 -0
- package/dist/constants.js.map +1 -0
- package/dist/core/analyzer/artifact-generator.d.ts +8 -0
- package/dist/core/analyzer/artifact-generator.d.ts.map +1 -1
- package/dist/core/analyzer/artifact-generator.js +59 -11
- package/dist/core/analyzer/artifact-generator.js.map +1 -1
- package/dist/core/analyzer/call-graph.d.ts.map +1 -1
- package/dist/core/analyzer/call-graph.js +135 -1
- package/dist/core/analyzer/call-graph.js.map +1 -1
- package/dist/core/analyzer/dependency-graph.d.ts +32 -0
- package/dist/core/analyzer/dependency-graph.d.ts.map +1 -1
- package/dist/core/analyzer/dependency-graph.js +104 -10
- package/dist/core/analyzer/dependency-graph.js.map +1 -1
- package/dist/core/analyzer/duplicate-detector.d.ts.map +1 -1
- package/dist/core/analyzer/duplicate-detector.js +4 -0
- package/dist/core/analyzer/duplicate-detector.js.map +1 -1
- package/dist/core/analyzer/embedding-service.d.ts +6 -0
- package/dist/core/analyzer/embedding-service.d.ts.map +1 -1
- package/dist/core/analyzer/embedding-service.js +15 -1
- package/dist/core/analyzer/embedding-service.js.map +1 -1
- package/dist/core/analyzer/file-walker.d.ts.map +1 -1
- package/dist/core/analyzer/file-walker.js +4 -3
- package/dist/core/analyzer/file-walker.js.map +1 -1
- package/dist/core/analyzer/http-route-parser.d.ts +111 -0
- package/dist/core/analyzer/http-route-parser.d.ts.map +1 -0
- package/dist/core/analyzer/http-route-parser.js +466 -0
- package/dist/core/analyzer/http-route-parser.js.map +1 -0
- package/dist/core/analyzer/import-parser.d.ts.map +1 -1
- package/dist/core/analyzer/import-parser.js +19 -5
- package/dist/core/analyzer/import-parser.js.map +1 -1
- package/dist/core/analyzer/refactor-analyzer.d.ts.map +1 -1
- package/dist/core/analyzer/refactor-analyzer.js +8 -7
- package/dist/core/analyzer/refactor-analyzer.js.map +1 -1
- package/dist/core/analyzer/repository-mapper.d.ts.map +1 -1
- package/dist/core/analyzer/repository-mapper.js +12 -13
- package/dist/core/analyzer/repository-mapper.js.map +1 -1
- package/dist/core/analyzer/signature-extractor.d.ts +1 -1
- package/dist/core/analyzer/signature-extractor.d.ts.map +1 -1
- package/dist/core/analyzer/signature-extractor.js +69 -1
- package/dist/core/analyzer/signature-extractor.js.map +1 -1
- package/dist/core/analyzer/spec-vector-index.d.ts.map +1 -1
- package/dist/core/analyzer/spec-vector-index.js +4 -3
- package/dist/core/analyzer/spec-vector-index.js.map +1 -1
- package/dist/core/analyzer/vector-index.d.ts.map +1 -1
- package/dist/core/analyzer/vector-index.js +29 -1
- package/dist/core/analyzer/vector-index.js.map +1 -1
- package/dist/core/drift/drift-detector.d.ts.map +1 -1
- package/dist/core/drift/drift-detector.js +7 -6
- package/dist/core/drift/drift-detector.js.map +1 -1
- package/dist/core/drift/git-diff.d.ts.map +1 -1
- package/dist/core/drift/git-diff.js +28 -16
- package/dist/core/drift/git-diff.js.map +1 -1
- package/dist/core/generator/mapping-generator.d.ts.map +1 -1
- package/dist/core/generator/mapping-generator.js +11 -10
- package/dist/core/generator/mapping-generator.js.map +1 -1
- package/dist/core/generator/openspec-compat.d.ts.map +1 -1
- package/dist/core/generator/openspec-compat.js +3 -2
- package/dist/core/generator/openspec-compat.js.map +1 -1
- package/dist/core/generator/openspec-format-generator.js.map +1 -1
- package/dist/core/generator/openspec-writer.d.ts +0 -4
- package/dist/core/generator/openspec-writer.d.ts.map +1 -1
- package/dist/core/generator/openspec-writer.js +30 -41
- package/dist/core/generator/openspec-writer.js.map +1 -1
- package/dist/core/generator/spec-pipeline.d.ts.map +1 -1
- package/dist/core/generator/spec-pipeline.js +4 -4
- package/dist/core/generator/spec-pipeline.js.map +1 -1
- package/dist/core/generator/stages/stage1-survey.d.ts.map +1 -1
- package/dist/core/generator/stages/stage1-survey.js +5 -3
- package/dist/core/generator/stages/stage1-survey.js.map +1 -1
- package/dist/core/generator/stages/stage2-entities.d.ts.map +1 -1
- package/dist/core/generator/stages/stage2-entities.js +10 -9
- package/dist/core/generator/stages/stage2-entities.js.map +1 -1
- package/dist/core/generator/stages/stage3-services.d.ts.map +1 -1
- package/dist/core/generator/stages/stage3-services.js +9 -8
- package/dist/core/generator/stages/stage3-services.js.map +1 -1
- package/dist/core/generator/stages/stage4-api.d.ts.map +1 -1
- package/dist/core/generator/stages/stage4-api.js +10 -9
- package/dist/core/generator/stages/stage4-api.js.map +1 -1
- package/dist/core/generator/stages/stage5-architecture.d.ts.map +1 -1
- package/dist/core/generator/stages/stage5-architecture.js +5 -4
- package/dist/core/generator/stages/stage5-architecture.js.map +1 -1
- package/dist/core/generator/stages/stage6-adr.d.ts.map +1 -1
- package/dist/core/generator/stages/stage6-adr.js +7 -2
- package/dist/core/generator/stages/stage6-adr.js.map +1 -1
- package/dist/core/services/chat-agent.d.ts.map +1 -1
- package/dist/core/services/chat-agent.js +46 -15
- package/dist/core/services/chat-agent.js.map +1 -1
- package/dist/core/services/config-manager.d.ts.map +1 -1
- package/dist/core/services/config-manager.js +32 -26
- package/dist/core/services/config-manager.js.map +1 -1
- package/dist/core/services/gitignore-manager.d.ts.map +1 -1
- package/dist/core/services/gitignore-manager.js +2 -13
- package/dist/core/services/gitignore-manager.js.map +1 -1
- package/dist/core/services/llm-service.d.ts.map +1 -1
- package/dist/core/services/llm-service.js +33 -35
- package/dist/core/services/llm-service.js.map +1 -1
- package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/analysis.js +23 -14
- package/dist/core/services/mcp-handlers/analysis.js.map +1 -1
- package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/graph.js +24 -23
- package/dist/core/services/mcp-handlers/graph.js.map +1 -1
- package/dist/core/services/mcp-handlers/semantic.d.ts +1 -1
- package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/semantic.js +17 -16
- package/dist/core/services/mcp-handlers/semantic.js.map +1 -1
- package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -1
- package/dist/core/services/mcp-handlers/utils.js +4 -3
- package/dist/core/services/mcp-handlers/utils.js.map +1 -1
- package/dist/core/services/project-detector.d.ts.map +1 -1
- package/dist/core/services/project-detector.js +2 -13
- package/dist/core/services/project-detector.js.map +1 -1
- package/dist/core/verifier/verification-engine.d.ts +9 -3
- package/dist/core/verifier/verification-engine.d.ts.map +1 -1
- package/dist/core/verifier/verification-engine.js +25 -13
- package/dist/core/verifier/verification-engine.js.map +1 -1
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/command-helpers.d.ts +38 -0
- package/dist/utils/command-helpers.d.ts.map +1 -0
- package/dist/utils/command-helpers.js +82 -0
- package/dist/utils/command-helpers.js.map +1 -0
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +4 -3
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +14 -3
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/progress.d.ts +1 -1
- package/dist/utils/progress.d.ts.map +1 -1
- package/dist/utils/progress.js +15 -12
- package/dist/utils/progress.js.map +1 -1
- package/dist/utils/shutdown.d.ts.map +1 -1
- package/dist/utils/shutdown.js +4 -3
- package/dist/utils/shutdown.js.map +1 -1
- package/package.json +9 -5
- package/src/viewer/InteractiveGraphViewer.jsx +182 -139
- package/src/viewer/components/ArchitectureView.jsx +19 -19
- package/src/viewer/components/ChatPanel.jsx +40 -40
- package/src/viewer/components/ClusterGraph.jsx +34 -22
- package/src/viewer/components/FilterBar.jsx +26 -26
- package/src/viewer/components/FlatGraph.jsx +22 -15
- package/src/viewer/components/MicroComponents.jsx +14 -12
- package/src/viewer/utils/constants.js +17 -0
- package/src/viewer/utils/graph-helpers.js +7 -3
- package/src/viewer/utils/graph-helpers.test.ts +39 -0
- package/src/viewer/utils/themes.js +170 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Route Parser
|
|
3
|
+
*
|
|
4
|
+
* Extracts two complementary sets of data:
|
|
5
|
+
* 1. HTTP CALLS — fetch/axios/ky/got calls in JS/TS frontend files
|
|
6
|
+
* 2. ROUTE DEFS — FastAPI / Flask / Django route declarations in Python files
|
|
7
|
+
*
|
|
8
|
+
* These are then matched by `buildHttpEdges()` to create cross-language edges
|
|
9
|
+
* between the frontend files that call an endpoint and the Python handlers that
|
|
10
|
+
* serve it — filling the gap that static import analysis cannot reach.
|
|
11
|
+
*
|
|
12
|
+
* Matching strategy
|
|
13
|
+
* -----------------
|
|
14
|
+
* Routes are normalised to a canonical form before comparison:
|
|
15
|
+
* - Path parameters are replaced with a placeholder: /items/{id} → /items/:param
|
|
16
|
+
* - Leading slashes are normalised
|
|
17
|
+
* - Query strings are stripped from call-site URLs
|
|
18
|
+
* - Common API prefixes (/api, /api/v1, /v1, …) are tried both with and
|
|
19
|
+
* without the prefix so that a frontend call to /api/v1/search still matches
|
|
20
|
+
* a FastAPI router mounted at /search.
|
|
21
|
+
*
|
|
22
|
+
* Confidence levels
|
|
23
|
+
* -----------------
|
|
24
|
+
* exact — method + full path match
|
|
25
|
+
* path — path matches, method unknown on one side (e.g. bare fetch)
|
|
26
|
+
* fuzzy — normalised path matches after prefix stripping
|
|
27
|
+
*/
|
|
28
|
+
/** An HTTP call found in a JS/TS source file */
|
|
29
|
+
export interface HttpCall {
|
|
30
|
+
/** Absolute path of the file containing the call */
|
|
31
|
+
file: string;
|
|
32
|
+
/** HTTP method, upper-cased. 'UNKNOWN' when it cannot be determined. */
|
|
33
|
+
method: string;
|
|
34
|
+
/** URL as written in source — may be a template literal or variable ref */
|
|
35
|
+
url: string;
|
|
36
|
+
/** Normalised, static portion of the URL (params stripped, prefix removed) */
|
|
37
|
+
normalizedUrl: string;
|
|
38
|
+
/** 1-based source line */
|
|
39
|
+
line: number;
|
|
40
|
+
/** axios / fetch / ky / got / custom */
|
|
41
|
+
client: string;
|
|
42
|
+
}
|
|
43
|
+
/** A route handler found in a Python source file */
|
|
44
|
+
export interface RouteDefinition {
|
|
45
|
+
/** Absolute path of the file containing the handler */
|
|
46
|
+
file: string;
|
|
47
|
+
/** HTTP method, upper-cased */
|
|
48
|
+
method: string;
|
|
49
|
+
/** Path pattern as declared (may contain {param} or <param> placeholders) */
|
|
50
|
+
path: string;
|
|
51
|
+
/** Normalised path for matching */
|
|
52
|
+
normalizedPath: string;
|
|
53
|
+
/** Name of the handler function */
|
|
54
|
+
handlerName: string;
|
|
55
|
+
/** fastapi / flask / django / starlette */
|
|
56
|
+
framework: string;
|
|
57
|
+
/** 1-based source line */
|
|
58
|
+
line: number;
|
|
59
|
+
}
|
|
60
|
+
/** A resolved cross-language edge */
|
|
61
|
+
export interface HttpEdge {
|
|
62
|
+
/** Absolute path of the JS/TS caller file */
|
|
63
|
+
callerFile: string;
|
|
64
|
+
/** Absolute path of the Python handler file */
|
|
65
|
+
handlerFile: string;
|
|
66
|
+
method: string;
|
|
67
|
+
/** Normalised path used for the match */
|
|
68
|
+
path: string;
|
|
69
|
+
call: HttpCall;
|
|
70
|
+
route: RouteDefinition;
|
|
71
|
+
/** How confident the match is */
|
|
72
|
+
confidence: 'exact' | 'path' | 'fuzzy';
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Reduce a URL/path to a comparable canonical form:
|
|
76
|
+
* - Strip protocol + host if present (https://example.com/foo → /foo)
|
|
77
|
+
* - Strip query string and fragment
|
|
78
|
+
* - Replace path parameters with :param
|
|
79
|
+
* {id}, :id, <int:id>, <id> → :param
|
|
80
|
+
* - Collapse duplicate slashes
|
|
81
|
+
* - Remove trailing slash (except root)
|
|
82
|
+
*/
|
|
83
|
+
export declare function normalizeUrl(raw: string): string;
|
|
84
|
+
/**
|
|
85
|
+
* Extract all HTTP calls from a JavaScript or TypeScript source file.
|
|
86
|
+
*/
|
|
87
|
+
export declare function extractHttpCalls(filePath: string): Promise<HttpCall[]>;
|
|
88
|
+
/**
|
|
89
|
+
* Extract all route definitions from a Python source file.
|
|
90
|
+
* Supports FastAPI, Starlette, Flask, and Django (urls.py path/re_path).
|
|
91
|
+
*/
|
|
92
|
+
export declare function extractRouteDefinitions(filePath: string): Promise<RouteDefinition[]>;
|
|
93
|
+
/**
|
|
94
|
+
* Match HTTP calls from JS/TS files against route definitions from Python files
|
|
95
|
+
* and return cross-language edges.
|
|
96
|
+
*
|
|
97
|
+
* Pass in pre-extracted calls and routes (so callers can cache them across
|
|
98
|
+
* multiple graph builds without re-parsing).
|
|
99
|
+
*/
|
|
100
|
+
export declare function buildHttpEdges(calls: HttpCall[], routes: RouteDefinition[]): HttpEdge[];
|
|
101
|
+
/**
|
|
102
|
+
* Parse all files in a mixed JS+Python codebase and return HTTP edges.
|
|
103
|
+
* Intended to be called once per graph build and its result merged into
|
|
104
|
+
* the DependencyGraphResult edges.
|
|
105
|
+
*/
|
|
106
|
+
export declare function extractAllHttpEdges(filePaths: string[]): Promise<{
|
|
107
|
+
calls: HttpCall[];
|
|
108
|
+
routes: RouteDefinition[];
|
|
109
|
+
edges: HttpEdge[];
|
|
110
|
+
}>;
|
|
111
|
+
//# sourceMappingURL=http-route-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-route-parser.d.ts","sourceRoot":"","sources":["../../../src/core/analyzer/http-route-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AASH,gDAAgD;AAChD,MAAM,WAAW,QAAQ;IACvB,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,8EAA8E;IAC9E,aAAa,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,oDAAoD;AACpD,MAAM,WAAW,eAAe;IAC9B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,6EAA6E;IAC7E,IAAI,EAAE,MAAM,CAAC;IACb,mCAAmC;IACnC,cAAc,EAAE,MAAM,CAAC;IACvB,mCAAmC;IACnC,WAAW,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,SAAS,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qCAAqC;AACrC,MAAM,WAAW,QAAQ;IACvB,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,+CAA+C;IAC/C,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,eAAe,CAAC;IACvB,iCAAiC;IACjC,UAAU,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;CACxC;AAaD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAsBhD;AAuBD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CAuH5E;AAMD;;;GAGG;AACH,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAgI1F;AAMD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,QAAQ,EAAE,EACjB,MAAM,EAAE,eAAe,EAAE,GACxB,QAAQ,EAAE,CAgFZ;AAMD;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;IACtE,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB,CAAC,CAmBD"}
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP Route Parser
|
|
3
|
+
*
|
|
4
|
+
* Extracts two complementary sets of data:
|
|
5
|
+
* 1. HTTP CALLS — fetch/axios/ky/got calls in JS/TS frontend files
|
|
6
|
+
* 2. ROUTE DEFS — FastAPI / Flask / Django route declarations in Python files
|
|
7
|
+
*
|
|
8
|
+
* These are then matched by `buildHttpEdges()` to create cross-language edges
|
|
9
|
+
* between the frontend files that call an endpoint and the Python handlers that
|
|
10
|
+
* serve it — filling the gap that static import analysis cannot reach.
|
|
11
|
+
*
|
|
12
|
+
* Matching strategy
|
|
13
|
+
* -----------------
|
|
14
|
+
* Routes are normalised to a canonical form before comparison:
|
|
15
|
+
* - Path parameters are replaced with a placeholder: /items/{id} → /items/:param
|
|
16
|
+
* - Leading slashes are normalised
|
|
17
|
+
* - Query strings are stripped from call-site URLs
|
|
18
|
+
* - Common API prefixes (/api, /api/v1, /v1, …) are tried both with and
|
|
19
|
+
* without the prefix so that a frontend call to /api/v1/search still matches
|
|
20
|
+
* a FastAPI router mounted at /search.
|
|
21
|
+
*
|
|
22
|
+
* Confidence levels
|
|
23
|
+
* -----------------
|
|
24
|
+
* exact — method + full path match
|
|
25
|
+
* path — path matches, method unknown on one side (e.g. bare fetch)
|
|
26
|
+
* fuzzy — normalised path matches after prefix stripping
|
|
27
|
+
*/
|
|
28
|
+
import { readFile } from 'node:fs/promises';
|
|
29
|
+
import { extname } from 'node:path';
|
|
30
|
+
// ============================================================================
|
|
31
|
+
// NORMALISATION HELPERS
|
|
32
|
+
// ============================================================================
|
|
33
|
+
/** Common API prefixes that frontends add but backends may not declare */
|
|
34
|
+
const API_PREFIXES = [
|
|
35
|
+
'/api/v1', '/api/v2', '/api/v3',
|
|
36
|
+
'/api',
|
|
37
|
+
'/v1', '/v2', '/v3',
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Reduce a URL/path to a comparable canonical form:
|
|
41
|
+
* - Strip protocol + host if present (https://example.com/foo → /foo)
|
|
42
|
+
* - Strip query string and fragment
|
|
43
|
+
* - Replace path parameters with :param
|
|
44
|
+
* {id}, :id, <int:id>, <id> → :param
|
|
45
|
+
* - Collapse duplicate slashes
|
|
46
|
+
* - Remove trailing slash (except root)
|
|
47
|
+
*/
|
|
48
|
+
export function normalizeUrl(raw) {
|
|
49
|
+
// Remove template-literal variable parts: ${...}
|
|
50
|
+
let url = raw.replace(/\$\{[^}]+\}/g, ':param');
|
|
51
|
+
// Strip protocol + host
|
|
52
|
+
url = url.replace(/^https?:\/\/[^/]+/, '');
|
|
53
|
+
// Strip query string and fragment
|
|
54
|
+
url = url.replace(/[?#].*$/, '');
|
|
55
|
+
// Replace FastAPI / Flask style path params
|
|
56
|
+
url = url.replace(/\{[^}]+\}/g, ':param'); // {item_id}
|
|
57
|
+
url = url.replace(/<[^>]+>/g, ':param'); // <int:item_id>
|
|
58
|
+
url = url.replace(/:[\w]+/g, ':param'); // :item_id (Express style)
|
|
59
|
+
// Collapse duplicate slashes, ensure leading slash
|
|
60
|
+
url = ('/' + url).replace(/\/+/g, '/');
|
|
61
|
+
// Remove trailing slash unless it IS the root
|
|
62
|
+
if (url.length > 1 && url.endsWith('/'))
|
|
63
|
+
url = url.slice(0, -1);
|
|
64
|
+
return url.toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Return all candidate normalised paths to try for a frontend URL.
|
|
68
|
+
* We try both the full path and the path with each known prefix stripped,
|
|
69
|
+
* to handle cases where the backend router is mounted without the prefix.
|
|
70
|
+
*/
|
|
71
|
+
function candidatePaths(normalizedUrl) {
|
|
72
|
+
const candidates = new Set([normalizedUrl]);
|
|
73
|
+
for (const prefix of API_PREFIXES) {
|
|
74
|
+
if (normalizedUrl.startsWith(prefix + '/') || normalizedUrl === prefix) {
|
|
75
|
+
candidates.add(normalizedUrl.slice(prefix.length) || '/');
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return Array.from(candidates);
|
|
79
|
+
}
|
|
80
|
+
// ============================================================================
|
|
81
|
+
// HTTP CALL EXTRACTION (JS / TS)
|
|
82
|
+
// ============================================================================
|
|
83
|
+
/**
|
|
84
|
+
* Extract all HTTP calls from a JavaScript or TypeScript source file.
|
|
85
|
+
*/
|
|
86
|
+
export async function extractHttpCalls(filePath) {
|
|
87
|
+
const ext = extname(filePath).toLowerCase();
|
|
88
|
+
if (!['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'].includes(ext))
|
|
89
|
+
return [];
|
|
90
|
+
let content;
|
|
91
|
+
try {
|
|
92
|
+
content = await readFile(filePath, 'utf8');
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
const calls = [];
|
|
98
|
+
// Strip comments to avoid false matches.
|
|
99
|
+
// The line-comment regex must NOT match `://` inside URLs — we only strip
|
|
100
|
+
// `//` that is preceded by whitespace, punctuation, brackets, or the start
|
|
101
|
+
// of the line (i.e. genuine JS/TS comments, not protocol separators).
|
|
102
|
+
// The character class intentionally includes ) and ] so that patterns like
|
|
103
|
+
// `fetch('/api/items') // comment` are correctly stripped.
|
|
104
|
+
const clean = content
|
|
105
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
106
|
+
.replace(/(^|[\s,;()[\]{}])\/\/.*$/gm, '$1');
|
|
107
|
+
const lines = content.split('\n'); // keep original for line numbers
|
|
108
|
+
// ── fetch ──────────────────────────────────────────────────────────────────
|
|
109
|
+
// fetch('/api/search')
|
|
110
|
+
// fetch(`/api/search/${id}`, { method: 'POST' })
|
|
111
|
+
const fetchRegex = /\bfetch\s*\(\s*(`[^`]+`|'[^']+'|"[^"]+")\s*(?:,\s*\{([^}]*)\})?\s*\)/g;
|
|
112
|
+
let m;
|
|
113
|
+
while ((m = fetchRegex.exec(clean)) !== null) {
|
|
114
|
+
const rawUrl = m[1].replace(/^[`'"]/, '').replace(/[`'"]$/, '');
|
|
115
|
+
const optionsBlock = m[2] ?? '';
|
|
116
|
+
const methodMatch = optionsBlock.match(/method\s*:\s*['"`](\w+)['"`]/i);
|
|
117
|
+
const method = methodMatch ? methodMatch[1].toUpperCase() : 'GET';
|
|
118
|
+
calls.push({
|
|
119
|
+
file: filePath,
|
|
120
|
+
method,
|
|
121
|
+
url: rawUrl,
|
|
122
|
+
normalizedUrl: normalizeUrl(rawUrl),
|
|
123
|
+
line: getLine(lines, m.index),
|
|
124
|
+
client: 'fetch',
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
// ── axios (method shorthands + generic) ────────────────────────────────────
|
|
128
|
+
// axios.get('/api/items')
|
|
129
|
+
// axios.post('/api/items', data)
|
|
130
|
+
// axios({ method: 'post', url: '/api/items' })
|
|
131
|
+
// axios.request({ method: 'DELETE', url: '/api/items/1' })
|
|
132
|
+
const axiosMethodRegex = /\baxios\.(get|post|put|patch|delete|head|options)\s*\(\s*(`[^`]+`|'[^']+'|"[^"]+")/g;
|
|
133
|
+
while ((m = axiosMethodRegex.exec(clean)) !== null) {
|
|
134
|
+
const method = m[1].toUpperCase();
|
|
135
|
+
const rawUrl = m[2].replace(/^[`'"]/, '').replace(/[`'"]$/, '');
|
|
136
|
+
calls.push({
|
|
137
|
+
file: filePath,
|
|
138
|
+
method,
|
|
139
|
+
url: rawUrl,
|
|
140
|
+
normalizedUrl: normalizeUrl(rawUrl),
|
|
141
|
+
line: getLine(lines, m.index),
|
|
142
|
+
client: 'axios',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
// axios({ url: '...', method: '...' }) or axios.request({ ... })
|
|
146
|
+
const axiosConfigRegex = /\baxios(?:\.request)?\s*\(\s*\{([^}]{0,400})\}/g;
|
|
147
|
+
while ((m = axiosConfigRegex.exec(clean)) !== null) {
|
|
148
|
+
const block = m[1];
|
|
149
|
+
const urlMatch = block.match(/url\s*:\s*(`[^`]+`|'[^']+'|"[^"]+")/);
|
|
150
|
+
if (!urlMatch)
|
|
151
|
+
continue;
|
|
152
|
+
const rawUrl = urlMatch[1].replace(/^[`'"]/, '').replace(/[`'"]$/, '');
|
|
153
|
+
const methodMatch = block.match(/method\s*:\s*['"`](\w+)['"`]/i);
|
|
154
|
+
const method = methodMatch ? methodMatch[1].toUpperCase() : 'UNKNOWN';
|
|
155
|
+
calls.push({
|
|
156
|
+
file: filePath,
|
|
157
|
+
method,
|
|
158
|
+
url: rawUrl,
|
|
159
|
+
normalizedUrl: normalizeUrl(rawUrl),
|
|
160
|
+
line: getLine(lines, m.index),
|
|
161
|
+
client: 'axios',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
// ── ky ─────────────────────────────────────────────────────────────────────
|
|
165
|
+
// ky.get('/api/items') ky.post('/api/items', { json: data })
|
|
166
|
+
const kyRegex = /\bky\.(get|post|put|patch|delete|head)\s*\(\s*(`[^`]+`|'[^']+'|"[^"]+")/g;
|
|
167
|
+
while ((m = kyRegex.exec(clean)) !== null) {
|
|
168
|
+
const rawUrl = m[2].replace(/^[`'"]/, '').replace(/[`'"]$/, '');
|
|
169
|
+
calls.push({
|
|
170
|
+
file: filePath,
|
|
171
|
+
method: m[1].toUpperCase(),
|
|
172
|
+
url: rawUrl,
|
|
173
|
+
normalizedUrl: normalizeUrl(rawUrl),
|
|
174
|
+
line: getLine(lines, m.index),
|
|
175
|
+
client: 'ky',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
// ── got ────────────────────────────────────────────────────────────────────
|
|
179
|
+
// got.get('/api/items')
|
|
180
|
+
const gotRegex = /\bgot\.(get|post|put|patch|delete|head)\s*\(\s*(`[^`]+`|'[^']+'|"[^"]+")/g;
|
|
181
|
+
while ((m = gotRegex.exec(clean)) !== null) {
|
|
182
|
+
const rawUrl = m[2].replace(/^[`'"]/, '').replace(/[`'"]$/, '');
|
|
183
|
+
calls.push({
|
|
184
|
+
file: filePath,
|
|
185
|
+
method: m[1].toUpperCase(),
|
|
186
|
+
url: rawUrl,
|
|
187
|
+
normalizedUrl: normalizeUrl(rawUrl),
|
|
188
|
+
line: getLine(lines, m.index),
|
|
189
|
+
client: 'got',
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
// ── React Query / SWR convenience wrappers ─────────────────────────────────
|
|
193
|
+
// useQuery(['key', id], () => fetch('/api/items')) — already caught above
|
|
194
|
+
// useMutation(() => axios.post('/api/items')) — already caught above
|
|
195
|
+
return calls;
|
|
196
|
+
}
|
|
197
|
+
// ============================================================================
|
|
198
|
+
// ROUTE DEFINITION EXTRACTION (Python)
|
|
199
|
+
// ============================================================================
|
|
200
|
+
/**
|
|
201
|
+
* Extract all route definitions from a Python source file.
|
|
202
|
+
* Supports FastAPI, Starlette, Flask, and Django (urls.py path/re_path).
|
|
203
|
+
*/
|
|
204
|
+
export async function extractRouteDefinitions(filePath) {
|
|
205
|
+
const ext = extname(filePath).toLowerCase();
|
|
206
|
+
if (!['.py', '.pyw'].includes(ext))
|
|
207
|
+
return [];
|
|
208
|
+
let content;
|
|
209
|
+
try {
|
|
210
|
+
content = await readFile(filePath, 'utf8');
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
return [];
|
|
214
|
+
}
|
|
215
|
+
const routes = [];
|
|
216
|
+
const lines = content.split('\n');
|
|
217
|
+
// Remove comments for cleaner matching
|
|
218
|
+
const clean = content.replace(/#.*$/gm, '');
|
|
219
|
+
// ── FastAPI / Starlette decorators ─────────────────────────────────────────
|
|
220
|
+
// @app.get("/items/{item_id}")
|
|
221
|
+
// @router.post("/search", ...)
|
|
222
|
+
// @app.api_route("/multi", methods=["GET","POST"])
|
|
223
|
+
const fastapiDecoratorRegex = /@(?:app|router|api_router)\.(get|post|put|patch|delete|head|options|trace)\s*\(\s*(['"/][^'")\n]+['"])/gm;
|
|
224
|
+
let m;
|
|
225
|
+
while ((m = fastapiDecoratorRegex.exec(clean)) !== null) {
|
|
226
|
+
const method = m[1].toUpperCase();
|
|
227
|
+
const path = m[2].replace(/^['"]/, '').replace(/['"]$/, '');
|
|
228
|
+
const lineNum = getLine(lines, m.index);
|
|
229
|
+
// The handler name is on the `def` line right after the decorator block
|
|
230
|
+
const handlerName = extractNextDefName(lines, lineNum);
|
|
231
|
+
routes.push({
|
|
232
|
+
file: filePath,
|
|
233
|
+
method,
|
|
234
|
+
path,
|
|
235
|
+
normalizedPath: normalizeUrl(path),
|
|
236
|
+
handlerName,
|
|
237
|
+
framework: 'fastapi',
|
|
238
|
+
line: lineNum,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
// @app.api_route("/path", methods=["GET", "POST"])
|
|
242
|
+
const apiRouteRegex = /@(?:app|router|api_router)\.api_route\s*\(\s*(['"/][^'")\n]+['"]),\s*methods\s*=\s*\[([^\]]+)\]/gm;
|
|
243
|
+
while ((m = apiRouteRegex.exec(clean)) !== null) {
|
|
244
|
+
const path = m[1].replace(/^['"]/, '').replace(/['"]$/, '');
|
|
245
|
+
const lineNum = getLine(lines, m.index);
|
|
246
|
+
const handlerName = extractNextDefName(lines, lineNum);
|
|
247
|
+
// Parse the methods list
|
|
248
|
+
const methodMatches = m[2].matchAll(/['"](\w+)['"]/g);
|
|
249
|
+
for (const mm of methodMatches) {
|
|
250
|
+
routes.push({
|
|
251
|
+
file: filePath,
|
|
252
|
+
method: mm[1].toUpperCase(),
|
|
253
|
+
path,
|
|
254
|
+
normalizedPath: normalizeUrl(path),
|
|
255
|
+
handlerName,
|
|
256
|
+
framework: 'fastapi',
|
|
257
|
+
line: lineNum,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// ── Flask ──────────────────────────────────────────────────────────────────
|
|
262
|
+
// @app.route("/items", methods=["GET", "POST"])
|
|
263
|
+
// @bp.route("/items/<int:item_id>", methods=["DELETE"])
|
|
264
|
+
const flaskRouteRegex = /@(?:\w+)\.route\s*\(\s*(['"/][^'")\n]+['"]),?\s*(?:methods\s*=\s*\[([^\]]*)\])?\s*\)/gm;
|
|
265
|
+
while ((m = flaskRouteRegex.exec(clean)) !== null) {
|
|
266
|
+
const path = m[1].replace(/^['"]/, '').replace(/['"]$/, '');
|
|
267
|
+
const lineNum = getLine(lines, m.index);
|
|
268
|
+
const handlerName = extractNextDefName(lines, lineNum);
|
|
269
|
+
const rawMethods = m[2];
|
|
270
|
+
if (rawMethods) {
|
|
271
|
+
const methodMatches = rawMethods.matchAll(/['"](\w+)['"]/g);
|
|
272
|
+
for (const mm of methodMatches) {
|
|
273
|
+
routes.push({
|
|
274
|
+
file: filePath,
|
|
275
|
+
method: mm[1].toUpperCase(),
|
|
276
|
+
path,
|
|
277
|
+
normalizedPath: normalizeUrl(path),
|
|
278
|
+
handlerName,
|
|
279
|
+
framework: 'flask',
|
|
280
|
+
line: lineNum,
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
// Flask default is GET when no methods specified
|
|
286
|
+
routes.push({
|
|
287
|
+
file: filePath,
|
|
288
|
+
method: 'GET',
|
|
289
|
+
path,
|
|
290
|
+
normalizedPath: normalizeUrl(path),
|
|
291
|
+
handlerName,
|
|
292
|
+
framework: 'flask',
|
|
293
|
+
line: lineNum,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// ── Django urls.py ─────────────────────────────────────────────────────────
|
|
298
|
+
// path('api/items/', views.ItemListView.as_view(), name='item-list'),
|
|
299
|
+
// re_path(r'^api/items/(?P<pk>[0-9]+)/$', views.ItemDetailView.as_view()),
|
|
300
|
+
//
|
|
301
|
+
// NOTE: Django views handle HTTP method dispatch internally (via class-based
|
|
302
|
+
// views or decorators), so no method is declared in urls.py. All Django
|
|
303
|
+
// routes are stored with method='UNKNOWN', which means any frontend call
|
|
304
|
+
// matched against a Django route will receive confidence='path' at best —
|
|
305
|
+
// never 'exact'. This may produce false-positive edges when multiple HTTP
|
|
306
|
+
// methods share the same URL pattern. Filter by confidence if this matters.
|
|
307
|
+
const djangoPathRegex = /\bpath\s*\(\s*r?(['"])(.*?)\1\s*,\s*([\w.]+)/gm;
|
|
308
|
+
while ((m = djangoPathRegex.exec(clean)) !== null) {
|
|
309
|
+
const path = '/' + m[2].replace(/\$$/, '').replace(/^\^/, '');
|
|
310
|
+
const handlerName = m[3].split('.').pop() ?? m[3];
|
|
311
|
+
const lineNum = getLine(lines, m.index);
|
|
312
|
+
routes.push({
|
|
313
|
+
file: filePath,
|
|
314
|
+
method: 'UNKNOWN', // Django views handle method internally
|
|
315
|
+
path,
|
|
316
|
+
normalizedPath: normalizeUrl(path),
|
|
317
|
+
handlerName,
|
|
318
|
+
framework: 'django',
|
|
319
|
+
line: lineNum,
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
return routes;
|
|
323
|
+
}
|
|
324
|
+
// ============================================================================
|
|
325
|
+
// EDGE BUILDER
|
|
326
|
+
// ============================================================================
|
|
327
|
+
/**
|
|
328
|
+
* Match HTTP calls from JS/TS files against route definitions from Python files
|
|
329
|
+
* and return cross-language edges.
|
|
330
|
+
*
|
|
331
|
+
* Pass in pre-extracted calls and routes (so callers can cache them across
|
|
332
|
+
* multiple graph builds without re-parsing).
|
|
333
|
+
*/
|
|
334
|
+
export function buildHttpEdges(calls, routes) {
|
|
335
|
+
const edges = [];
|
|
336
|
+
// Index routes by normalised path for O(1) lookup
|
|
337
|
+
const routesByPath = new Map();
|
|
338
|
+
for (const route of routes) {
|
|
339
|
+
const existing = routesByPath.get(route.normalizedPath) ?? [];
|
|
340
|
+
existing.push(route);
|
|
341
|
+
routesByPath.set(route.normalizedPath, existing);
|
|
342
|
+
}
|
|
343
|
+
for (const call of calls) {
|
|
344
|
+
// Build all candidate paths (handles /api/v1 prefix stripping)
|
|
345
|
+
const candidates = candidatePaths(call.normalizedUrl);
|
|
346
|
+
let matched = false;
|
|
347
|
+
for (const candidate of candidates) {
|
|
348
|
+
const matchingRoutes = routesByPath.get(candidate);
|
|
349
|
+
if (!matchingRoutes)
|
|
350
|
+
continue;
|
|
351
|
+
for (const route of matchingRoutes) {
|
|
352
|
+
// Determine confidence
|
|
353
|
+
let confidence;
|
|
354
|
+
const methodsKnown = call.method !== 'UNKNOWN' && route.method !== 'UNKNOWN';
|
|
355
|
+
const methodsMatch = call.method === route.method;
|
|
356
|
+
if (methodsKnown && methodsMatch && candidate === call.normalizedUrl) {
|
|
357
|
+
confidence = 'exact';
|
|
358
|
+
}
|
|
359
|
+
else if (!methodsKnown || !methodsMatch) {
|
|
360
|
+
confidence = candidate !== call.normalizedUrl ? 'fuzzy' : 'path';
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
confidence = candidate !== call.normalizedUrl ? 'fuzzy' : 'exact';
|
|
364
|
+
}
|
|
365
|
+
edges.push({
|
|
366
|
+
callerFile: call.file,
|
|
367
|
+
handlerFile: route.file,
|
|
368
|
+
method: methodsKnown ? call.method : route.method,
|
|
369
|
+
path: candidate,
|
|
370
|
+
call,
|
|
371
|
+
route,
|
|
372
|
+
confidence,
|
|
373
|
+
});
|
|
374
|
+
matched = true;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
// If no match found via exact/prefix logic, try fuzzy segment comparison
|
|
378
|
+
if (!matched) {
|
|
379
|
+
const callSegments = call.normalizedUrl.replace(/:param/g, '*').split('/');
|
|
380
|
+
for (const [routePath, routeList] of routesByPath) {
|
|
381
|
+
const routeSegments = routePath.replace(/:param/g, '*').split('/');
|
|
382
|
+
if (callSegments.length !== routeSegments.length)
|
|
383
|
+
continue;
|
|
384
|
+
const allMatch = callSegments.every((seg, i) => seg === routeSegments[i] || seg === '*' || routeSegments[i] === '*');
|
|
385
|
+
if (!allMatch)
|
|
386
|
+
continue;
|
|
387
|
+
for (const route of routeList) {
|
|
388
|
+
edges.push({
|
|
389
|
+
callerFile: call.file,
|
|
390
|
+
handlerFile: route.file,
|
|
391
|
+
method: call.method !== 'UNKNOWN' ? call.method : route.method,
|
|
392
|
+
path: routePath,
|
|
393
|
+
call,
|
|
394
|
+
route,
|
|
395
|
+
confidence: 'fuzzy',
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// Deduplicate: same caller file + handler file + method + path
|
|
402
|
+
const seen = new Set();
|
|
403
|
+
return edges.filter(e => {
|
|
404
|
+
const key = `${e.callerFile}|${e.handlerFile}|${e.method}|${e.path}`;
|
|
405
|
+
if (seen.has(key))
|
|
406
|
+
return false;
|
|
407
|
+
seen.add(key);
|
|
408
|
+
return true;
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
// ============================================================================
|
|
412
|
+
// BATCH HELPERS
|
|
413
|
+
// ============================================================================
|
|
414
|
+
/**
|
|
415
|
+
* Parse all files in a mixed JS+Python codebase and return HTTP edges.
|
|
416
|
+
* Intended to be called once per graph build and its result merged into
|
|
417
|
+
* the DependencyGraphResult edges.
|
|
418
|
+
*/
|
|
419
|
+
export async function extractAllHttpEdges(filePaths) {
|
|
420
|
+
const allCalls = [];
|
|
421
|
+
const allRoutes = [];
|
|
422
|
+
await Promise.all(filePaths.map(async (fp) => {
|
|
423
|
+
const ext = extname(fp).toLowerCase();
|
|
424
|
+
if (['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs'].includes(ext)) {
|
|
425
|
+
const calls = await extractHttpCalls(fp);
|
|
426
|
+
allCalls.push(...calls);
|
|
427
|
+
}
|
|
428
|
+
else if (['.py', '.pyw'].includes(ext)) {
|
|
429
|
+
const routes = await extractRouteDefinitions(fp);
|
|
430
|
+
allRoutes.push(...routes);
|
|
431
|
+
}
|
|
432
|
+
}));
|
|
433
|
+
const edges = buildHttpEdges(allCalls, allRoutes);
|
|
434
|
+
return { calls: allCalls, routes: allRoutes, edges };
|
|
435
|
+
}
|
|
436
|
+
// ============================================================================
|
|
437
|
+
// PRIVATE UTILITIES
|
|
438
|
+
// ============================================================================
|
|
439
|
+
/** Convert a character offset in `content` to a 1-based line number */
|
|
440
|
+
function getLine(lines, charOffset) {
|
|
441
|
+
let accumulated = 0;
|
|
442
|
+
for (let i = 0; i < lines.length; i++) {
|
|
443
|
+
accumulated += lines[i].length + 1; // +1 for newline
|
|
444
|
+
if (accumulated > charOffset)
|
|
445
|
+
return i + 1;
|
|
446
|
+
}
|
|
447
|
+
return lines.length;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Given the line of a decorator, scan forward to find the next `def` name.
|
|
451
|
+
* Handles multi-line decorators with up to 10 lines of lookahead.
|
|
452
|
+
*
|
|
453
|
+
* `decoratorLine` is 1-based (from getLine()), so we convert to a 0-based
|
|
454
|
+
* index before indexing into the `lines` array.
|
|
455
|
+
*/
|
|
456
|
+
function extractNextDefName(lines, decoratorLine) {
|
|
457
|
+
const start = decoratorLine - 1; // convert 1-based → 0-based
|
|
458
|
+
const maxLook = Math.min(lines.length, start + 10);
|
|
459
|
+
for (let i = start; i < maxLook; i++) {
|
|
460
|
+
const defMatch = lines[i]?.match(/^\s*(?:async\s+)?def\s+(\w+)/);
|
|
461
|
+
if (defMatch)
|
|
462
|
+
return defMatch[1];
|
|
463
|
+
}
|
|
464
|
+
return 'unknown';
|
|
465
|
+
}
|
|
466
|
+
//# sourceMappingURL=http-route-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-route-parser.js","sourceRoot":"","sources":["../../../src/core/analyzer/http-route-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuDpC,+EAA+E;AAC/E,wBAAwB;AACxB,+EAA+E;AAE/E,0EAA0E;AAC1E,MAAM,YAAY,GAAG;IACnB,SAAS,EAAE,SAAS,EAAE,SAAS;IAC/B,MAAM;IACN,KAAK,EAAE,KAAK,EAAE,KAAK;CACpB,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,iDAAiD;IACjD,IAAI,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;IAEhD,wBAAwB;IACxB,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;IAE3C,kCAAkC;IAClC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAEjC,4CAA4C;IAC5C,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAG,YAAY;IACzD,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC,CAAM,gBAAgB;IAC9D,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAO,4BAA4B;IAE1E,mDAAmD;IACnD,GAAG,GAAG,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAEvC,8CAA8C;IAC9C,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IAEhE,OAAO,GAAG,CAAC,WAAW,EAAE,CAAC;AAC3B,CAAC;AAED;;;;GAIG;AACH,SAAS,cAAc,CAAC,aAAqB;IAC3C,MAAM,UAAU,GAAG,IAAI,GAAG,CAAS,CAAC,aAAa,CAAC,CAAC,CAAC;IACpD,KAAK,MAAM,MAAM,IAAI,YAAY,EAAE,CAAC;QAClC,IAAI,aAAa,CAAC,UAAU,CAAC,MAAM,GAAG,GAAG,CAAC,IAAI,aAAa,KAAK,MAAM,EAAE,CAAC;YACvE,UAAU,CAAC,GAAG,CAAC,aAAa,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;AAChC,CAAC;AAED,+EAA+E;AAC/E,kCAAkC;AAClC,+EAA+E;AAI/E;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,QAAgB;IACrD,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAE7E,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,KAAK,GAAe,EAAE,CAAC;IAE7B,yCAAyC;IACzC,0EAA0E;IAC1E,2EAA2E;IAC3E,sEAAsE;IACtE,2EAA2E;IAC3E,2DAA2D;IAC3D,MAAM,KAAK,GAAG,OAAO;SAClB,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,OAAO,CAAC,4BAA4B,EAAE,IAAI,CAAC,CAAC;IAE/C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,iCAAiC;IAEpE,8EAA8E;IAC9E,uBAAuB;IACvB,iDAAiD;IACjD,MAAM,UAAU,GAAG,uEAAuE,CAAC;IAC3F,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC7C,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAC,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAC,EAAE,CAAC,CAAC;QAC9D,MAAM,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAChC,MAAM,WAAW,GAAG,YAAY,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;QACxE,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;QAElE,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,QAAQ;YACd,MAAM;YACN,GAAG,EAAE,MAAM;YACX,aAAa,EAAE,YAAY,CAAC,MAAM,CAAC;YACnC,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC;YAC7B,MAAM,EAAE,OAAO;SAChB,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,0BAA0B;IAC1B,iCAAiC;IACjC,+CAA+C;IAC/C,2DAA2D;IAC3D,MAAM,gBAAgB,GAAG,qFAAqF,CAAC;IAC/G,OAAO,CAAC,CAAC,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACnD,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAC,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAC,EAAE,CAAC,CAAC;QAC9D,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,QAAQ;YACd,MAAM;YACN,GAAG,EAAE,MAAM;YACX,aAAa,EAAE,YAAY,CAAC,MAAM,CAAC;YACnC,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC;YAC7B,MAAM,EAAE,OAAO;SAChB,CAAC,CAAC;IACL,CAAC;IAED,mEAAmE;IACnE,MAAM,gBAAgB,GAAG,iDAAiD,CAAC;IAC3E,OAAO,CAAC,CAAC,GAAG,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACnD,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACnB,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACpE,IAAI,CAAC,QAAQ;YAAE,SAAS;QACxB,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAC,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAC,EAAE,CAAC,CAAC;QACrE,MAAM,WAAW,GAAG,KAAK,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;QACjE,MAAM,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QACtE,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,QAAQ;YACd,MAAM;YACN,GAAG,EAAE,MAAM;YACX,aAAa,EAAE,YAAY,CAAC,MAAM,CAAC;YACnC,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC;YAC7B,MAAM,EAAE,OAAO;SAChB,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,8DAA8D;IAC9D,MAAM,OAAO,GAAG,0EAA0E,CAAC;IAC3F,OAAO,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC1C,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAC,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAC,EAAE,CAAC,CAAC;QAC9D,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;YAC1B,GAAG,EAAE,MAAM;YACX,aAAa,EAAE,YAAY,CAAC,MAAM,CAAC;YACnC,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC;YAC7B,MAAM,EAAE,IAAI;SACb,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,wBAAwB;IACxB,MAAM,QAAQ,GAAG,2EAA2E,CAAC;IAC7F,OAAO,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC3C,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAC,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAC,EAAE,CAAC,CAAC;QAC9D,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;YAC1B,GAAG,EAAE,MAAM;YACX,aAAa,EAAE,YAAY,CAAC,MAAM,CAAC;YACnC,IAAI,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC;YAC7B,MAAM,EAAE,KAAK;SACd,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAC9E,4EAA4E;IAC5E,4EAA4E;IAE5E,OAAO,KAAK,CAAC;AACf,CAAC;AAED,+EAA+E;AAC/E,wCAAwC;AACxC,+EAA+E;AAE/E;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,QAAgB;IAC5D,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAE9C,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC7C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,MAAM,MAAM,GAAsB,EAAE,CAAC;IACrC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAElC,uCAAuC;IACvC,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;IAE5C,8EAA8E;IAC9E,+BAA+B;IAC/B,+BAA+B;IAC/B,mDAAmD;IACnD,MAAM,qBAAqB,GACzB,0GAA0G,CAAC;IAC7G,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,qBAAqB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACxD,MAAM,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAClC,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC5D,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;QACxC,wEAAwE;QACxE,MAAM,WAAW,GAAG,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,QAAQ;YACd,MAAM;YACN,IAAI;YACJ,cAAc,EAAE,YAAY,CAAC,IAAI,CAAC;YAClC,WAAW;YACX,SAAS,EAAE,SAAS;YACpB,IAAI,EAAE,OAAO;SACd,CAAC,CAAC;IACL,CAAC;IAED,mDAAmD;IACnD,MAAM,aAAa,GACjB,mGAAmG,CAAC;IACtG,OAAO,CAAC,CAAC,GAAG,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAChD,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC5D,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,WAAW,GAAG,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACvD,yBAAyB;QACzB,MAAM,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;QACtD,KAAK,MAAM,EAAE,IAAI,aAAa,EAAE,CAAC;YAC/B,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;gBAC3B,IAAI;gBACJ,cAAc,EAAE,YAAY,CAAC,IAAI,CAAC;gBAClC,WAAW;gBACX,SAAS,EAAE,SAAS;gBACpB,IAAI,EAAE,OAAO;aACd,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,gDAAgD;IAChD,wDAAwD;IACxD,MAAM,eAAe,GACnB,wFAAwF,CAAC;IAC3F,OAAO,CAAC,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAClD,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC5D,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,WAAW,GAAG,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QACvD,MAAM,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,UAAU,EAAE,CAAC;YACf,MAAM,aAAa,GAAG,UAAU,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;YAC5D,KAAK,MAAM,EAAE,IAAI,aAAa,EAAE,CAAC;gBAC/B,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,QAAQ;oBACd,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;oBAC3B,IAAI;oBACJ,cAAc,EAAE,YAAY,CAAC,IAAI,CAAC;oBAClC,WAAW;oBACX,SAAS,EAAE,OAAO;oBAClB,IAAI,EAAE,OAAO;iBACd,CAAC,CAAC;YACL,CAAC;QACH,CAAC;aAAM,CAAC;YACN,iDAAiD;YACjD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,QAAQ;gBACd,MAAM,EAAE,KAAK;gBACb,IAAI;gBACJ,cAAc,EAAE,YAAY,CAAC,IAAI,CAAC;gBAClC,WAAW;gBACX,SAAS,EAAE,OAAO;gBAClB,IAAI,EAAE,OAAO;aACd,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,8EAA8E;IAC9E,sEAAsE;IACtE,2EAA2E;IAC3E,EAAE;IACF,6EAA6E;IAC7E,wEAAwE;IACxE,yEAAyE;IACzE,0EAA0E;IAC1E,0EAA0E;IAC1E,4EAA4E;IAC5E,MAAM,eAAe,GACnB,gDAAgD,CAAC;IACnD,OAAO,CAAC,CAAC,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAClD,MAAM,IAAI,GAAG,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC9D,MAAM,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAClD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,QAAQ;YACd,MAAM,EAAE,SAAS,EAAE,wCAAwC;YAC3D,IAAI;YACJ,cAAc,EAAE,YAAY,CAAC,IAAI,CAAC;YAClC,WAAW;YACX,SAAS,EAAE,QAAQ;YACnB,IAAI,EAAE,OAAO;SACd,CAAC,CAAC;IACL,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,+EAA+E;AAC/E,eAAe;AACf,+EAA+E;AAE/E;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAC5B,KAAiB,EACjB,MAAyB;IAEzB,MAAM,KAAK,GAAe,EAAE,CAAC;IAE7B,kDAAkD;IAClD,MAAM,YAAY,GAAG,IAAI,GAAG,EAA6B,CAAC;IAC1D,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,EAAE,CAAC;QAC9D,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,+DAA+D;QAC/D,MAAM,UAAU,GAAG,cAAc,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACtD,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;YACnC,MAAM,cAAc,GAAG,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;YACnD,IAAI,CAAC,cAAc;gBAAE,SAAS;YAE9B,KAAK,MAAM,KAAK,IAAI,cAAc,EAAE,CAAC;gBACnC,uBAAuB;gBACvB,IAAI,UAAkC,CAAC;gBACvC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,KAAK,SAAS,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC;gBAC7E,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM,CAAC;gBAElD,IAAI,YAAY,IAAI,YAAY,IAAI,SAAS,KAAK,IAAI,CAAC,aAAa,EAAE,CAAC;oBACrE,UAAU,GAAG,OAAO,CAAC;gBACvB,CAAC;qBAAM,IAAI,CAAC,YAAY,IAAI,CAAC,YAAY,EAAE,CAAC;oBAC1C,UAAU,GAAG,SAAS,KAAK,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;gBACnE,CAAC;qBAAM,CAAC;oBACN,UAAU,GAAG,SAAS,KAAK,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;gBACpE,CAAC;gBAED,KAAK,CAAC,IAAI,CAAC;oBACT,UAAU,EAAE,IAAI,CAAC,IAAI;oBACrB,WAAW,EAAE,KAAK,CAAC,IAAI;oBACvB,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM;oBACjD,IAAI,EAAE,SAAS;oBACf,IAAI;oBACJ,KAAK;oBACL,UAAU;iBACX,CAAC,CAAC;gBACH,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;QACH,CAAC;QAED,yEAAyE;QACzE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YAC3E,KAAK,MAAM,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,YAAY,EAAE,CAAC;gBAClD,MAAM,aAAa,GAAG,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBACnE,IAAI,YAAY,CAAC,MAAM,KAAK,aAAa,CAAC,MAAM;oBAAE,SAAS;gBAC3D,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CACjC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,aAAa,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,GAAG,IAAI,aAAa,CAAC,CAAC,CAAC,KAAK,GAAG,CAChF,CAAC;gBACF,IAAI,CAAC,QAAQ;oBAAE,SAAS;gBACxB,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;oBAC9B,KAAK,CAAC,IAAI,CAAC;wBACT,UAAU,EAAE,IAAI,CAAC,IAAI;wBACrB,WAAW,EAAE,KAAK,CAAC,IAAI;wBACvB,MAAM,EAAE,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM;wBAC9D,IAAI,EAAE,SAAS;wBACf,IAAI;wBACJ,KAAK;wBACL,UAAU,EAAE,OAAO;qBACpB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,+DAA+D;IAC/D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE;QACtB,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC;QACrE,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QAChC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,OAAO,IAAI,CAAC;IACd,CAAC,CAAC,CAAC;AACL,CAAC;AAED,+EAA+E;AAC/E,gBAAgB;AAChB,+EAA+E;AAE/E;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,SAAmB;IAK3D,MAAM,QAAQ,GAAe,EAAE,CAAC;IAChC,MAAM,SAAS,GAAsB,EAAE,CAAC;IAExC,MAAM,OAAO,CAAC,GAAG,CACf,SAAS,CAAC,GAAG,CAAC,KAAK,EAAC,EAAE,EAAC,EAAE;QACvB,MAAM,GAAG,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACjE,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,EAAE,CAAC,CAAC;YACzC,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC;QAC1B,CAAC;aAAM,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACzC,MAAM,MAAM,GAAG,MAAM,uBAAuB,CAAC,EAAE,CAAC,CAAC;YACjD,SAAS,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,MAAM,KAAK,GAAG,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAClD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AACvD,CAAC;AAED,+EAA+E;AAC/E,oBAAoB;AACpB,+EAA+E;AAE/E,uEAAuE;AACvE,SAAS,OAAO,CAAC,KAAe,EAAE,UAAkB;IAClD,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,WAAW,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,iBAAiB;QACrD,IAAI,WAAW,GAAG,UAAU;YAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,CAAC;AACtB,CAAC;AAED;;;;;;GAMG;AACH,SAAS,kBAAkB,CAAC,KAAe,EAAE,aAAqB;IAChE,MAAM,KAAK,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,4BAA4B;IAC7D,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,GAAG,EAAE,CAAC,CAAC;IACnD,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,8BAA8B,CAAC,CAAC;QACjE,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-parser.d.ts","sourceRoot":"","sources":["../../../src/core/analyzer/import-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;IACpF,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AA8kBD;;GAEG;AACH,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"import-parser.d.ts","sourceRoot":"","sources":["../../../src/core/analyzer/import-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,SAAS,EAAE,OAAO,CAAC;IACnB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,OAAO,CAAC;IAChB,UAAU,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,GAAG,SAAS,CAAC;IACpF,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IACvC,4CAA4C;IAC5C,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AA8kBD;;GAEG;AACH,wBAAsB,aAAa,CACjC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAqFxB;AAMD;;GAEG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,KAAK,CAAwC;IAErD;;OAEG;IACH,UAAU,IAAI,IAAI;IAIlB;;OAEG;IACH,OAAO,CAAC,WAAW;IAUnB;;OAEG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAsDxD;;OAEG;IACG,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;CAU1E;AAED;;GAEG;AACH,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAGvE;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAGxF"}
|
|
@@ -547,13 +547,15 @@ function parsePythonExports(content) {
|
|
|
547
547
|
* Resolve a relative import to an absolute file path
|
|
548
548
|
*/
|
|
549
549
|
export async function resolveImport(importSource, fromFile, options) {
|
|
550
|
-
|
|
551
|
-
|
|
550
|
+
const fromExt = extname(fromFile).toLowerCase();
|
|
551
|
+
const isPython = fromExt === '.py' || fromExt === '.pyw';
|
|
552
|
+
// For non-Python files, external packages can never resolve to a local file.
|
|
553
|
+
// For Python files we must NOT bail out here: `from services.retriever import X`
|
|
554
|
+
// looks like a package import but may well be a local module under rootDir.
|
|
555
|
+
if (!isRelativeImport(importSource) && !isPython) {
|
|
552
556
|
return null;
|
|
553
557
|
}
|
|
554
558
|
const fromDir = dirname(fromFile);
|
|
555
|
-
const fromExt = extname(fromFile).toLowerCase();
|
|
556
|
-
const isPython = fromExt === '.py' || fromExt === '.pyw';
|
|
557
559
|
// Default extensions depend on the source file type
|
|
558
560
|
const extensions = options.extensions ?? (isPython
|
|
559
561
|
? ['.py', '.pyw']
|
|
@@ -573,7 +575,19 @@ export async function resolveImport(importSource, fromFile, options) {
|
|
|
573
575
|
const prefix = dots === 1 ? './' : '../'.repeat(dots - 1);
|
|
574
576
|
normalizedSource = rest ? prefix + rest : prefix.replace(/\/$/, '') || '.';
|
|
575
577
|
}
|
|
576
|
-
|
|
578
|
+
else if (isPython && !importSource.startsWith('.')) {
|
|
579
|
+
// Absolute-style intra-project import: "services.retriever" or "services.retriever.utils"
|
|
580
|
+
// Convert dotted module path to a filesystem path relative to rootDir.
|
|
581
|
+
// e.g. "services.retriever" → "<rootDir>/services/retriever.py"
|
|
582
|
+
normalizedSource = './' + importSource.replace(/\./g, '/');
|
|
583
|
+
}
|
|
584
|
+
// For Python absolute imports resolve from rootDir, not fromDir,
|
|
585
|
+
// because Python's module system uses sys.path (typically the project root).
|
|
586
|
+
// Fall back to fromDir if baseDir is empty to avoid resolving against cwd.
|
|
587
|
+
const resolveBase = (isPython && !importSource.startsWith('.'))
|
|
588
|
+
? (options.baseDir || fromDir)
|
|
589
|
+
: fromDir;
|
|
590
|
+
const basePath = resolve(resolveBase, normalizedSource);
|
|
577
591
|
// Strip any existing extension from the import source.
|
|
578
592
|
// This handles the TypeScript NodeNext convention where imports are written
|
|
579
593
|
// as `./foo.js` but the actual file on disk is `./foo.ts`.
|