depwire-cli 0.9.27 → 0.9.28
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/dist/{chunk-DA5LWNJ4.js → chunk-ITEGMPF7.js} +549 -100
- package/dist/{chunk-RGD3YJYQ.js → chunk-VJLBOFCD.js} +16 -10
- package/dist/index.js +13 -13
- package/dist/mcpb-entry.js +3 -3
- package/dist/sdk.d.ts +31 -2
- package/dist/sdk.js +3 -1
- package/package.json +1 -1
|
@@ -2873,9 +2873,449 @@ async function parseProject(projectRoot, options) {
|
|
|
2873
2873
|
return parsedFiles;
|
|
2874
2874
|
}
|
|
2875
2875
|
|
|
2876
|
+
// src/cross-language/detectors/rest-api.ts
|
|
2877
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
2878
|
+
import { join as join9, resolve as resolve4 } from "path";
|
|
2879
|
+
function getLanguage(filePath) {
|
|
2880
|
+
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
|
|
2881
|
+
if (filePath.endsWith(".js") || filePath.endsWith(".jsx") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs")) return "javascript";
|
|
2882
|
+
if (filePath.endsWith(".py")) return "python";
|
|
2883
|
+
if (filePath.endsWith(".go")) return "go";
|
|
2884
|
+
return "unknown";
|
|
2885
|
+
}
|
|
2886
|
+
function normalizePath(routePath) {
|
|
2887
|
+
return routePath.replace(/:[a-zA-Z_][a-zA-Z0-9_]*/g, "__PARAM__").replace(/\{[a-zA-Z_][a-zA-Z0-9_]*\}/g, "__PARAM__");
|
|
2888
|
+
}
|
|
2889
|
+
function stripTrailingSlash(p) {
|
|
2890
|
+
return p.length > 1 && p.endsWith("/") ? p.slice(0, -1) : p;
|
|
2891
|
+
}
|
|
2892
|
+
function extractHttpCalls(source, filePath) {
|
|
2893
|
+
const calls = [];
|
|
2894
|
+
const lines = source.split("\n");
|
|
2895
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2896
|
+
const line = lines[i];
|
|
2897
|
+
const fetchMatch = line.match(/fetch\s*\(\s*(['"`])([^'"`]+)\1/);
|
|
2898
|
+
if (fetchMatch) {
|
|
2899
|
+
const path6 = fetchMatch[2];
|
|
2900
|
+
if (isLocalApiPath(path6)) {
|
|
2901
|
+
const methodMatch = line.match(/method\s*:\s*['"](\w+)['"]/);
|
|
2902
|
+
const method = methodMatch ? methodMatch[1].toUpperCase() : "GET";
|
|
2903
|
+
calls.push({ method, path: cleanPath(path6), file: filePath, line: i + 1 });
|
|
2904
|
+
}
|
|
2905
|
+
}
|
|
2906
|
+
if (!fetchMatch) {
|
|
2907
|
+
const fetchTemplateMatch = line.match(/fetch\s*\(\s*`([^`]+)`/);
|
|
2908
|
+
if (fetchTemplateMatch) {
|
|
2909
|
+
const path6 = fetchTemplateMatch[1];
|
|
2910
|
+
if (isLocalApiPath(path6)) {
|
|
2911
|
+
const methodMatch = line.match(/method\s*:\s*['"](\w+)['"]/);
|
|
2912
|
+
const method = methodMatch ? methodMatch[1].toUpperCase() : "GET";
|
|
2913
|
+
calls.push({ method, path: cleanPath(path6), file: filePath, line: i + 1 });
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
const axiosMatch = line.match(/axios\s*\.\s*(get|post|put|delete|patch)\s*\(\s*(['"`])([^'"`]+)\2/i);
|
|
2918
|
+
if (axiosMatch) {
|
|
2919
|
+
const path6 = axiosMatch[3];
|
|
2920
|
+
if (isLocalApiPath(path6)) {
|
|
2921
|
+
calls.push({ method: axiosMatch[1].toUpperCase(), path: cleanPath(path6), file: filePath, line: i + 1 });
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
if (!axiosMatch) {
|
|
2925
|
+
const axiosTemplateMatch = line.match(/axios\s*\.\s*(get|post|put|delete|patch)\s*\(\s*`([^`]+)`/i);
|
|
2926
|
+
if (axiosTemplateMatch) {
|
|
2927
|
+
const path6 = axiosTemplateMatch[2];
|
|
2928
|
+
if (isLocalApiPath(path6)) {
|
|
2929
|
+
calls.push({ method: axiosTemplateMatch[1].toUpperCase(), path: cleanPath(path6), file: filePath, line: i + 1 });
|
|
2930
|
+
}
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
const genericMatch = line.match(/\w+\s*\.\s*(get|post|put|delete|patch)\s*\(\s*(['"`])([^'"`]+)\2/i);
|
|
2934
|
+
if (genericMatch && !line.match(/axios/) && !line.match(/app\s*\./) && !line.match(/router\s*\./) && !line.match(/r\s*\./)) {
|
|
2935
|
+
const path6 = genericMatch[3];
|
|
2936
|
+
if (isLocalApiPath(path6)) {
|
|
2937
|
+
calls.push({ method: genericMatch[1].toUpperCase(), path: cleanPath(path6), file: filePath, line: i + 1 });
|
|
2938
|
+
}
|
|
2939
|
+
}
|
|
2940
|
+
}
|
|
2941
|
+
return calls;
|
|
2942
|
+
}
|
|
2943
|
+
function isLocalApiPath(path6) {
|
|
2944
|
+
if (path6.startsWith("http://") || path6.startsWith("https://")) return false;
|
|
2945
|
+
return path6.startsWith("/") || path6.includes("/api/");
|
|
2946
|
+
}
|
|
2947
|
+
function cleanPath(path6) {
|
|
2948
|
+
let cleaned = path6.replace(/\$\{[^}]*\}/g, "");
|
|
2949
|
+
cleaned = stripTrailingSlash(cleaned);
|
|
2950
|
+
return cleaned;
|
|
2951
|
+
}
|
|
2952
|
+
function extractRouteDefinitions(source, filePath) {
|
|
2953
|
+
const routes = [];
|
|
2954
|
+
const lines = source.split("\n");
|
|
2955
|
+
const lang = getLanguage(filePath);
|
|
2956
|
+
for (let i = 0; i < lines.length; i++) {
|
|
2957
|
+
const line = lines[i];
|
|
2958
|
+
if (lang === "typescript" || lang === "javascript") {
|
|
2959
|
+
const expressMatch = line.match(/(?:app|router)\s*\.\s*(get|post|put|delete|patch)\s*\(\s*(['"`])([^'"`]+)\2/i);
|
|
2960
|
+
if (expressMatch) {
|
|
2961
|
+
const path6 = expressMatch[3];
|
|
2962
|
+
if (path6.startsWith("/")) {
|
|
2963
|
+
routes.push({
|
|
2964
|
+
method: expressMatch[1].toUpperCase(),
|
|
2965
|
+
path: path6,
|
|
2966
|
+
normalizedPath: normalizePath(path6),
|
|
2967
|
+
file: filePath,
|
|
2968
|
+
line: i + 1
|
|
2969
|
+
});
|
|
2970
|
+
}
|
|
2971
|
+
}
|
|
2972
|
+
}
|
|
2973
|
+
if (lang === "python") {
|
|
2974
|
+
const pythonMatch = line.match(/@(?:app|router)\s*\.\s*(get|post|put|delete|patch)\s*\(\s*(['"])([^'"]+)\2/i);
|
|
2975
|
+
if (pythonMatch) {
|
|
2976
|
+
const path6 = pythonMatch[3];
|
|
2977
|
+
if (path6.startsWith("/")) {
|
|
2978
|
+
routes.push({
|
|
2979
|
+
method: pythonMatch[1].toUpperCase(),
|
|
2980
|
+
path: path6,
|
|
2981
|
+
normalizedPath: normalizePath(path6),
|
|
2982
|
+
file: filePath,
|
|
2983
|
+
line: i + 1
|
|
2984
|
+
});
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
const flaskMatch = line.match(/@(?:app|blueprint|router)\s*\.\s*route\s*\(\s*(['"])([^'"]+)\1/);
|
|
2988
|
+
if (flaskMatch) {
|
|
2989
|
+
const path6 = flaskMatch[2];
|
|
2990
|
+
if (path6.startsWith("/")) {
|
|
2991
|
+
const methodsMatch = line.match(/methods\s*=\s*\[([^\]]+)\]/);
|
|
2992
|
+
const methods = methodsMatch ? methodsMatch[1].match(/['"](\w+)['"]/g)?.map((m) => m.replace(/['"]/g, "").toUpperCase()) || ["GET"] : ["GET"];
|
|
2993
|
+
for (const method of methods) {
|
|
2994
|
+
routes.push({
|
|
2995
|
+
method,
|
|
2996
|
+
path: path6,
|
|
2997
|
+
normalizedPath: normalizePath(path6),
|
|
2998
|
+
file: filePath,
|
|
2999
|
+
line: i + 1
|
|
3000
|
+
});
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
if (lang === "go") {
|
|
3006
|
+
const goMatch = line.match(/(?:r|router|group)\s*\.\s*(GET|POST|PUT|DELETE|PATCH)\s*\(\s*"([^"]+)"/);
|
|
3007
|
+
if (goMatch) {
|
|
3008
|
+
const path6 = goMatch[2];
|
|
3009
|
+
if (path6.startsWith("/")) {
|
|
3010
|
+
routes.push({
|
|
3011
|
+
method: goMatch[1].toUpperCase(),
|
|
3012
|
+
path: path6,
|
|
3013
|
+
normalizedPath: normalizePath(path6),
|
|
3014
|
+
file: filePath,
|
|
3015
|
+
line: i + 1
|
|
3016
|
+
});
|
|
3017
|
+
}
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
return routes;
|
|
3022
|
+
}
|
|
3023
|
+
function matchPaths(callPath, routeNormalized) {
|
|
3024
|
+
const normalizedCall = normalizePath(stripTrailingSlash(callPath));
|
|
3025
|
+
const normalizedRoute = stripTrailingSlash(routeNormalized);
|
|
3026
|
+
if (normalizedCall === normalizedRoute) return true;
|
|
3027
|
+
if (normalizedRoute.startsWith(normalizedCall) && normalizedRoute[normalizedCall.length] === "/") return true;
|
|
3028
|
+
const callParts = normalizedCall.split("/");
|
|
3029
|
+
const routeParts = normalizedRoute.split("/");
|
|
3030
|
+
if (callParts.length <= routeParts.length) {
|
|
3031
|
+
let match = true;
|
|
3032
|
+
for (let i = 0; i < callParts.length; i++) {
|
|
3033
|
+
if (routeParts[i] === "__PARAM__") continue;
|
|
3034
|
+
if (callParts[i] !== routeParts[i]) {
|
|
3035
|
+
match = false;
|
|
3036
|
+
break;
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
if (match) return true;
|
|
3040
|
+
}
|
|
3041
|
+
return false;
|
|
3042
|
+
}
|
|
3043
|
+
function getConfidence(callPath, callMethod, routePath, routeMethod) {
|
|
3044
|
+
const normalizedCall = normalizePath(stripTrailingSlash(callPath));
|
|
3045
|
+
const normalizedRoute = normalizePath(stripTrailingSlash(routePath));
|
|
3046
|
+
const exactPath = normalizedCall === normalizedRoute;
|
|
3047
|
+
const methodMatch = callMethod === routeMethod;
|
|
3048
|
+
if (exactPath && methodMatch) return "high";
|
|
3049
|
+
if (exactPath) return "medium";
|
|
3050
|
+
if (methodMatch) return "medium";
|
|
3051
|
+
return "low";
|
|
3052
|
+
}
|
|
3053
|
+
function detectRestApiEdges(files, projectRoot) {
|
|
3054
|
+
const edges = [];
|
|
3055
|
+
const allCalls = [];
|
|
3056
|
+
const allRoutes = [];
|
|
3057
|
+
for (const file of files) {
|
|
3058
|
+
const fullPath = join9(projectRoot, file.filePath);
|
|
3059
|
+
if (!resolve4(fullPath).startsWith(resolve4(projectRoot))) continue;
|
|
3060
|
+
let source;
|
|
3061
|
+
try {
|
|
3062
|
+
source = readFileSync6(fullPath, "utf-8");
|
|
3063
|
+
} catch {
|
|
3064
|
+
continue;
|
|
3065
|
+
}
|
|
3066
|
+
const lang = getLanguage(file.filePath);
|
|
3067
|
+
if (lang === "typescript" || lang === "javascript") {
|
|
3068
|
+
allCalls.push(...extractHttpCalls(source, file.filePath));
|
|
3069
|
+
}
|
|
3070
|
+
allRoutes.push(...extractRouteDefinitions(source, file.filePath));
|
|
3071
|
+
}
|
|
3072
|
+
for (const call of allCalls) {
|
|
3073
|
+
for (const route of allRoutes) {
|
|
3074
|
+
if (call.file === route.file) continue;
|
|
3075
|
+
if (matchPaths(call.path, route.normalizedPath)) {
|
|
3076
|
+
const confidence = getConfidence(call.path, call.method, route.path, route.method);
|
|
3077
|
+
edges.push({
|
|
3078
|
+
sourceFile: call.file,
|
|
3079
|
+
targetFile: route.file,
|
|
3080
|
+
edgeType: "rest-api",
|
|
3081
|
+
confidence,
|
|
3082
|
+
sourceLanguage: getLanguage(call.file),
|
|
3083
|
+
targetLanguage: getLanguage(route.file),
|
|
3084
|
+
sourceLine: call.line,
|
|
3085
|
+
targetLine: route.line,
|
|
3086
|
+
metadata: {
|
|
3087
|
+
httpMethod: call.method,
|
|
3088
|
+
path: call.path
|
|
3089
|
+
}
|
|
3090
|
+
});
|
|
3091
|
+
}
|
|
3092
|
+
}
|
|
3093
|
+
}
|
|
3094
|
+
return edges;
|
|
3095
|
+
}
|
|
3096
|
+
|
|
3097
|
+
// src/cross-language/detectors/subprocess.ts
|
|
3098
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
3099
|
+
import { join as join10, resolve as resolve5, basename } from "path";
|
|
3100
|
+
var SCRIPT_EXTENSIONS = [".py", ".js", ".ts", ".go", ".rs"];
|
|
3101
|
+
function getLanguage2(filePath) {
|
|
3102
|
+
if (filePath.endsWith(".ts") || filePath.endsWith(".tsx")) return "typescript";
|
|
3103
|
+
if (filePath.endsWith(".js") || filePath.endsWith(".jsx") || filePath.endsWith(".mjs") || filePath.endsWith(".cjs")) return "javascript";
|
|
3104
|
+
if (filePath.endsWith(".py")) return "python";
|
|
3105
|
+
if (filePath.endsWith(".go")) return "go";
|
|
3106
|
+
if (filePath.endsWith(".rs")) return "rust";
|
|
3107
|
+
return "unknown";
|
|
3108
|
+
}
|
|
3109
|
+
function extractFilenameFromArgs(args) {
|
|
3110
|
+
const tokens = args.split(/[\s,'"[\]]+/).filter(Boolean);
|
|
3111
|
+
for (const token of tokens) {
|
|
3112
|
+
for (const ext of SCRIPT_EXTENSIONS) {
|
|
3113
|
+
if (token.endsWith(ext)) {
|
|
3114
|
+
return token;
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
return null;
|
|
3119
|
+
}
|
|
3120
|
+
function extractSubprocessCalls(source, filePath) {
|
|
3121
|
+
const calls = [];
|
|
3122
|
+
const lines = source.split("\n");
|
|
3123
|
+
const lang = getLanguage2(filePath);
|
|
3124
|
+
for (let i = 0; i < lines.length; i++) {
|
|
3125
|
+
const line = lines[i];
|
|
3126
|
+
if (lang === "typescript" || lang === "javascript") {
|
|
3127
|
+
const execMatch = line.match(/(?:execSync|exec)\s*\(\s*(['"`])([^'"`]+)\1/);
|
|
3128
|
+
if (execMatch) {
|
|
3129
|
+
const command = execMatch[2];
|
|
3130
|
+
const calledFile = extractFilenameFromArgs(command);
|
|
3131
|
+
if (calledFile) {
|
|
3132
|
+
calls.push({ file: filePath, line: i + 1, command, calledFile });
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
if (!execMatch) {
|
|
3136
|
+
const execTemplateMatch = line.match(/(?:execSync|exec)\s*\(\s*`([^`]+)`/);
|
|
3137
|
+
if (execTemplateMatch) {
|
|
3138
|
+
const command = execTemplateMatch[1].replace(/\$\{[^}]*\}/g, "");
|
|
3139
|
+
const calledFile = extractFilenameFromArgs(command);
|
|
3140
|
+
if (calledFile) {
|
|
3141
|
+
calls.push({ file: filePath, line: i + 1, command: execTemplateMatch[1], calledFile });
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
}
|
|
3145
|
+
const spawnMatch = line.match(/(?:spawn|spawnSync)\s*\(\s*['"](\w+)['"]\s*,\s*\[([^\]]*)\]/);
|
|
3146
|
+
if (spawnMatch) {
|
|
3147
|
+
const command = `${spawnMatch[1]} ${spawnMatch[2]}`;
|
|
3148
|
+
const calledFile = extractFilenameFromArgs(spawnMatch[2]);
|
|
3149
|
+
if (calledFile) {
|
|
3150
|
+
calls.push({ file: filePath, line: i + 1, command, calledFile });
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
if (lang === "python") {
|
|
3155
|
+
const subprocessMatch = line.match(/subprocess\s*\.\s*(?:run|call|Popen|check_output|check_call)\s*\(\s*\[([^\]]*)\]/);
|
|
3156
|
+
if (subprocessMatch) {
|
|
3157
|
+
const command = subprocessMatch[1];
|
|
3158
|
+
const calledFile = extractFilenameFromArgs(command);
|
|
3159
|
+
if (calledFile) {
|
|
3160
|
+
calls.push({ file: filePath, line: i + 1, command, calledFile });
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
const osMatch = line.match(/os\s*\.\s*system\s*\(\s*['"]([^'"]+)['"]/);
|
|
3164
|
+
if (osMatch) {
|
|
3165
|
+
const command = osMatch[1];
|
|
3166
|
+
const calledFile = extractFilenameFromArgs(command);
|
|
3167
|
+
if (calledFile) {
|
|
3168
|
+
calls.push({ file: filePath, line: i + 1, command, calledFile });
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
const subprocessStrMatch = line.match(/subprocess\s*\.\s*(?:run|call|Popen|check_output|check_call)\s*\(\s*['"]([^'"]+)['"]/);
|
|
3172
|
+
if (subprocessStrMatch) {
|
|
3173
|
+
const command = subprocessStrMatch[1];
|
|
3174
|
+
const calledFile = extractFilenameFromArgs(command);
|
|
3175
|
+
if (calledFile) {
|
|
3176
|
+
calls.push({ file: filePath, line: i + 1, command, calledFile });
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
if (lang === "go") {
|
|
3181
|
+
const goMatch = line.match(/exec\s*\.\s*Command\s*\(\s*"([^"]+)"\s*,\s*"([^"]+)"/);
|
|
3182
|
+
if (goMatch) {
|
|
3183
|
+
const command = `${goMatch[1]} ${goMatch[2]}`;
|
|
3184
|
+
const calledFile = extractFilenameFromArgs(command);
|
|
3185
|
+
if (calledFile) {
|
|
3186
|
+
calls.push({ file: filePath, line: i + 1, command, calledFile });
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
}
|
|
3190
|
+
}
|
|
3191
|
+
return calls;
|
|
3192
|
+
}
|
|
3193
|
+
function detectSubprocessEdges(files, projectRoot) {
|
|
3194
|
+
const edges = [];
|
|
3195
|
+
const knownFiles = new Set(files.map((f) => f.filePath));
|
|
3196
|
+
const basenameMap = /* @__PURE__ */ new Map();
|
|
3197
|
+
for (const f of files) {
|
|
3198
|
+
const base = basename(f.filePath);
|
|
3199
|
+
if (!basenameMap.has(base)) basenameMap.set(base, []);
|
|
3200
|
+
basenameMap.get(base).push(f.filePath);
|
|
3201
|
+
}
|
|
3202
|
+
for (const file of files) {
|
|
3203
|
+
const fullPath = join10(projectRoot, file.filePath);
|
|
3204
|
+
if (!resolve5(fullPath).startsWith(resolve5(projectRoot))) continue;
|
|
3205
|
+
let source;
|
|
3206
|
+
try {
|
|
3207
|
+
source = readFileSync7(fullPath, "utf-8");
|
|
3208
|
+
} catch {
|
|
3209
|
+
continue;
|
|
3210
|
+
}
|
|
3211
|
+
const calls = extractSubprocessCalls(source, file.filePath);
|
|
3212
|
+
for (const call of calls) {
|
|
3213
|
+
let targetFile = null;
|
|
3214
|
+
let confidence = "high";
|
|
3215
|
+
if (knownFiles.has(call.calledFile)) {
|
|
3216
|
+
targetFile = call.calledFile;
|
|
3217
|
+
confidence = "high";
|
|
3218
|
+
} else {
|
|
3219
|
+
const base = basename(call.calledFile);
|
|
3220
|
+
const candidates = basenameMap.get(base);
|
|
3221
|
+
if (candidates && candidates.length > 0) {
|
|
3222
|
+
const exactCandidate = candidates.find((c) => c.endsWith(call.calledFile));
|
|
3223
|
+
if (exactCandidate) {
|
|
3224
|
+
targetFile = exactCandidate;
|
|
3225
|
+
confidence = "high";
|
|
3226
|
+
} else {
|
|
3227
|
+
targetFile = candidates[0];
|
|
3228
|
+
confidence = "medium";
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3232
|
+
if (!targetFile) continue;
|
|
3233
|
+
if (targetFile === call.file) continue;
|
|
3234
|
+
edges.push({
|
|
3235
|
+
sourceFile: call.file,
|
|
3236
|
+
targetFile,
|
|
3237
|
+
edgeType: "subprocess",
|
|
3238
|
+
confidence,
|
|
3239
|
+
sourceLanguage: getLanguage2(call.file),
|
|
3240
|
+
targetLanguage: getLanguage2(targetFile),
|
|
3241
|
+
sourceLine: call.line,
|
|
3242
|
+
metadata: {
|
|
3243
|
+
command: call.command,
|
|
3244
|
+
calledFile: call.calledFile
|
|
3245
|
+
}
|
|
3246
|
+
});
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3249
|
+
return edges;
|
|
3250
|
+
}
|
|
3251
|
+
|
|
3252
|
+
// src/cross-language/index.ts
|
|
3253
|
+
function detectCrossLanguageEdges(files, projectRoot, graph) {
|
|
3254
|
+
const startTime = Date.now();
|
|
3255
|
+
const restApiEdges = detectRestApiEdges(files, projectRoot);
|
|
3256
|
+
const subprocessEdges = detectSubprocessEdges(files, projectRoot);
|
|
3257
|
+
const allEdges = [...restApiEdges, ...subprocessEdges];
|
|
3258
|
+
for (const edge of allEdges) {
|
|
3259
|
+
const sourceNodeId = `${edge.sourceFile}::__file__`;
|
|
3260
|
+
const targetNodeId = `${edge.targetFile}::__file__`;
|
|
3261
|
+
if (!graph.hasNode(sourceNodeId)) {
|
|
3262
|
+
let hasSourceFile = false;
|
|
3263
|
+
graph.forEachNode((_nodeId, attrs) => {
|
|
3264
|
+
if (attrs.filePath === edge.sourceFile) hasSourceFile = true;
|
|
3265
|
+
});
|
|
3266
|
+
if (!hasSourceFile) continue;
|
|
3267
|
+
graph.addNode(sourceNodeId, {
|
|
3268
|
+
name: "__file__",
|
|
3269
|
+
kind: "import",
|
|
3270
|
+
filePath: edge.sourceFile,
|
|
3271
|
+
startLine: 1,
|
|
3272
|
+
endLine: 1,
|
|
3273
|
+
exported: false
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
if (!graph.hasNode(targetNodeId)) {
|
|
3277
|
+
let hasTargetFile = false;
|
|
3278
|
+
graph.forEachNode((_nodeId, attrs) => {
|
|
3279
|
+
if (attrs.filePath === edge.targetFile) hasTargetFile = true;
|
|
3280
|
+
});
|
|
3281
|
+
if (!hasTargetFile) continue;
|
|
3282
|
+
graph.addNode(targetNodeId, {
|
|
3283
|
+
name: "__file__",
|
|
3284
|
+
kind: "import",
|
|
3285
|
+
filePath: edge.targetFile,
|
|
3286
|
+
startLine: 1,
|
|
3287
|
+
endLine: 1,
|
|
3288
|
+
exported: false
|
|
3289
|
+
});
|
|
3290
|
+
}
|
|
3291
|
+
graph.mergeEdge(sourceNodeId, targetNodeId, {
|
|
3292
|
+
kind: edge.edgeType,
|
|
3293
|
+
filePath: edge.sourceFile,
|
|
3294
|
+
line: edge.sourceLine || 1,
|
|
3295
|
+
crossLanguage: true,
|
|
3296
|
+
confidence: edge.confidence,
|
|
3297
|
+
edgeType: edge.edgeType,
|
|
3298
|
+
httpMethod: edge.metadata.httpMethod,
|
|
3299
|
+
path: edge.metadata.path,
|
|
3300
|
+
command: edge.metadata.command,
|
|
3301
|
+
calledFile: edge.metadata.calledFile
|
|
3302
|
+
});
|
|
3303
|
+
}
|
|
3304
|
+
const detectionTimeMs = Date.now() - startTime;
|
|
3305
|
+
return {
|
|
3306
|
+
edges: allEdges,
|
|
3307
|
+
stats: {
|
|
3308
|
+
restApiEdges: restApiEdges.length,
|
|
3309
|
+
subprocessEdges: subprocessEdges.length,
|
|
3310
|
+
filesAnalyzed: files.length,
|
|
3311
|
+
detectionTimeMs
|
|
3312
|
+
}
|
|
3313
|
+
};
|
|
3314
|
+
}
|
|
3315
|
+
|
|
2876
3316
|
// src/graph/index.ts
|
|
2877
3317
|
import { DirectedGraph } from "graphology";
|
|
2878
|
-
function buildGraph(parsedFiles) {
|
|
3318
|
+
function buildGraph(parsedFiles, projectRoot) {
|
|
2879
3319
|
const graph = new DirectedGraph();
|
|
2880
3320
|
for (const file of parsedFiles) {
|
|
2881
3321
|
for (const symbol of file.symbols) {
|
|
@@ -2932,6 +3372,12 @@ function buildGraph(parsedFiles) {
|
|
|
2932
3372
|
}
|
|
2933
3373
|
}
|
|
2934
3374
|
}
|
|
3375
|
+
if (projectRoot) {
|
|
3376
|
+
const result = detectCrossLanguageEdges(parsedFiles, projectRoot, graph);
|
|
3377
|
+
if (result.stats.restApiEdges > 0 || result.stats.subprocessEdges > 0) {
|
|
3378
|
+
console.error(`Cross-language edges: ${result.stats.restApiEdges} rest-api, ${result.stats.subprocessEdges} subprocess detected`);
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
2935
3381
|
return graph;
|
|
2936
3382
|
}
|
|
2937
3383
|
|
|
@@ -3063,7 +3509,9 @@ function getCrossFileEdges(graph) {
|
|
|
3063
3509
|
target,
|
|
3064
3510
|
sourceFile: sourceAttrs.filePath,
|
|
3065
3511
|
targetFile: targetAttrs.filePath,
|
|
3066
|
-
kind: attrs.kind
|
|
3512
|
+
kind: attrs.kind,
|
|
3513
|
+
crossLanguage: attrs.crossLanguage || false,
|
|
3514
|
+
edgeType: attrs.edgeType
|
|
3067
3515
|
});
|
|
3068
3516
|
}
|
|
3069
3517
|
});
|
|
@@ -3511,8 +3959,8 @@ function calculateDepthScore(graph) {
|
|
|
3511
3959
|
}
|
|
3512
3960
|
|
|
3513
3961
|
// src/health/index.ts
|
|
3514
|
-
import { readFileSync as
|
|
3515
|
-
import { dirname as dirname8, resolve as
|
|
3962
|
+
import { readFileSync as readFileSync8, writeFileSync, existsSync as existsSync8, mkdirSync } from "fs";
|
|
3963
|
+
import { dirname as dirname8, resolve as resolve6 } from "path";
|
|
3516
3964
|
function calculateHealthScore(graph, projectRoot) {
|
|
3517
3965
|
const coupling = calculateCouplingScore(graph);
|
|
3518
3966
|
const cohesion = calculateCohesionScore(graph);
|
|
@@ -3611,8 +4059,8 @@ function getHealthTrend(projectRoot, currentScore) {
|
|
|
3611
4059
|
}
|
|
3612
4060
|
}
|
|
3613
4061
|
function saveHealthHistory(projectRoot, report) {
|
|
3614
|
-
const resolvedRoot =
|
|
3615
|
-
const historyFile =
|
|
4062
|
+
const resolvedRoot = resolve6(projectRoot);
|
|
4063
|
+
const historyFile = resolve6(resolvedRoot, ".depwire", "health-history.json");
|
|
3616
4064
|
if (!historyFile.startsWith(resolvedRoot)) {
|
|
3617
4065
|
return;
|
|
3618
4066
|
}
|
|
@@ -3630,7 +4078,7 @@ function saveHealthHistory(projectRoot, report) {
|
|
|
3630
4078
|
if (existsSync8(historyFile)) {
|
|
3631
4079
|
try {
|
|
3632
4080
|
if (!historyFile.startsWith(resolvedRoot)) return;
|
|
3633
|
-
const content =
|
|
4081
|
+
const content = readFileSync8(historyFile, "utf-8");
|
|
3634
4082
|
history = JSON.parse(content);
|
|
3635
4083
|
} catch {
|
|
3636
4084
|
}
|
|
@@ -3644,14 +4092,14 @@ function saveHealthHistory(projectRoot, report) {
|
|
|
3644
4092
|
writeFileSync(historyFile, JSON.stringify(history, null, 2), "utf-8");
|
|
3645
4093
|
}
|
|
3646
4094
|
function loadHealthHistory(projectRoot) {
|
|
3647
|
-
const resolvedRoot =
|
|
3648
|
-
const historyFile =
|
|
4095
|
+
const resolvedRoot = resolve6(projectRoot);
|
|
4096
|
+
const historyFile = resolve6(resolvedRoot, ".depwire", "health-history.json");
|
|
3649
4097
|
if (!historyFile.startsWith(resolvedRoot) || !existsSync8(historyFile)) {
|
|
3650
4098
|
return [];
|
|
3651
4099
|
}
|
|
3652
4100
|
try {
|
|
3653
4101
|
if (!historyFile.startsWith(resolvedRoot)) return [];
|
|
3654
|
-
const content =
|
|
4102
|
+
const content = readFileSync8(historyFile, "utf-8");
|
|
3655
4103
|
return JSON.parse(content);
|
|
3656
4104
|
} catch {
|
|
3657
4105
|
return [];
|
|
@@ -3660,7 +4108,7 @@ function loadHealthHistory(projectRoot) {
|
|
|
3660
4108
|
|
|
3661
4109
|
// src/dead-code/detector.ts
|
|
3662
4110
|
import path2 from "path";
|
|
3663
|
-
import { readFileSync as
|
|
4111
|
+
import { readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
|
|
3664
4112
|
function findDeadSymbols(graph, projectRoot, includeTests = false, debug = false) {
|
|
3665
4113
|
const deadSymbols = [];
|
|
3666
4114
|
const context = { graph, projectRoot };
|
|
@@ -3795,7 +4243,7 @@ function getPackageEntryPoints(projectRoot) {
|
|
|
3795
4243
|
return entryPoints;
|
|
3796
4244
|
}
|
|
3797
4245
|
try {
|
|
3798
|
-
const packageJson = JSON.parse(
|
|
4246
|
+
const packageJson = JSON.parse(readFileSync9(packageJsonPath, "utf-8"));
|
|
3799
4247
|
if (packageJson.main) {
|
|
3800
4248
|
entryPoints.add(path2.resolve(projectRoot, packageJson.main));
|
|
3801
4249
|
}
|
|
@@ -3936,8 +4384,8 @@ function generateReason(symbol, confidence) {
|
|
|
3936
4384
|
return "Potentially unused";
|
|
3937
4385
|
}
|
|
3938
4386
|
function isBarrelFile(filePath) {
|
|
3939
|
-
const
|
|
3940
|
-
return
|
|
4387
|
+
const basename5 = path3.basename(filePath);
|
|
4388
|
+
return basename5 === "index.ts" || basename5 === "index.js";
|
|
3941
4389
|
}
|
|
3942
4390
|
function isTestFile2(filePath) {
|
|
3943
4391
|
return filePath.includes("__tests__/") || filePath.includes(".test.") || filePath.includes(".spec.") || filePath.includes("/test/") || filePath.includes("/tests/");
|
|
@@ -4117,7 +4565,7 @@ function filterByConfidence(symbols, minConfidence) {
|
|
|
4117
4565
|
|
|
4118
4566
|
// src/docs/generator.ts
|
|
4119
4567
|
import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, existsSync as existsSync12 } from "fs";
|
|
4120
|
-
import { join as
|
|
4568
|
+
import { join as join14 } from "path";
|
|
4121
4569
|
|
|
4122
4570
|
// src/docs/architecture.ts
|
|
4123
4571
|
import { dirname as dirname9 } from "path";
|
|
@@ -4519,7 +4967,7 @@ function detectCycles(graph) {
|
|
|
4519
4967
|
}
|
|
4520
4968
|
|
|
4521
4969
|
// src/docs/conventions.ts
|
|
4522
|
-
import { basename, extname as extname4 } from "path";
|
|
4970
|
+
import { basename as basename2, extname as extname4 } from "path";
|
|
4523
4971
|
function generateConventions(graph, projectRoot, version) {
|
|
4524
4972
|
let output = "";
|
|
4525
4973
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -4557,7 +5005,7 @@ function generateFileOrganization(graph) {
|
|
|
4557
5005
|
graph.forEachNode((node, attrs) => {
|
|
4558
5006
|
if (!files.has(attrs.filePath)) {
|
|
4559
5007
|
files.add(attrs.filePath);
|
|
4560
|
-
const fileName =
|
|
5008
|
+
const fileName = basename2(attrs.filePath);
|
|
4561
5009
|
if (fileName === "index.ts" || fileName === "index.js" || fileName === "index.tsx" || fileName === "index.jsx") {
|
|
4562
5010
|
barrelFileCount++;
|
|
4563
5011
|
}
|
|
@@ -4616,7 +5064,7 @@ function generateNamingPatterns(graph) {
|
|
|
4616
5064
|
graph.forEachNode((node, attrs) => {
|
|
4617
5065
|
if (!files.has(attrs.filePath)) {
|
|
4618
5066
|
files.add(attrs.filePath);
|
|
4619
|
-
const fileName =
|
|
5067
|
+
const fileName = basename2(attrs.filePath, extname4(attrs.filePath));
|
|
4620
5068
|
if (isCamelCase(fileName)) patterns.files.camelCase++;
|
|
4621
5069
|
else if (isPascalCase(fileName)) patterns.files.PascalCase++;
|
|
4622
5070
|
else if (isKebabCase(fileName)) patterns.files.kebabCase++;
|
|
@@ -5681,7 +6129,7 @@ function generateDepwireUsage(projectRoot) {
|
|
|
5681
6129
|
}
|
|
5682
6130
|
|
|
5683
6131
|
// src/docs/files.ts
|
|
5684
|
-
import { dirname as dirname11, basename as
|
|
6132
|
+
import { dirname as dirname11, basename as basename3 } from "path";
|
|
5685
6133
|
function generateFiles(graph, projectRoot, version) {
|
|
5686
6134
|
let output = "";
|
|
5687
6135
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -5800,7 +6248,7 @@ function generateDirectoryBreakdown(graph) {
|
|
|
5800
6248
|
dirStats.symbolCount += file.symbolCount;
|
|
5801
6249
|
if (file.totalConnections > dirStats.maxConnections) {
|
|
5802
6250
|
dirStats.maxConnections = file.totalConnections;
|
|
5803
|
-
dirStats.mostConnectedFile =
|
|
6251
|
+
dirStats.mostConnectedFile = basename3(file.filePath);
|
|
5804
6252
|
}
|
|
5805
6253
|
}
|
|
5806
6254
|
if (dirMap.size === 0) {
|
|
@@ -6428,7 +6876,7 @@ function generateRecommendations(graph) {
|
|
|
6428
6876
|
}
|
|
6429
6877
|
|
|
6430
6878
|
// src/docs/tests.ts
|
|
6431
|
-
import { basename as
|
|
6879
|
+
import { basename as basename4, dirname as dirname12 } from "path";
|
|
6432
6880
|
function generateTests(graph, projectRoot, version) {
|
|
6433
6881
|
let output = "";
|
|
6434
6882
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -6456,7 +6904,7 @@ function getFileCount8(graph) {
|
|
|
6456
6904
|
return files.size;
|
|
6457
6905
|
}
|
|
6458
6906
|
function isTestFile3(filePath) {
|
|
6459
|
-
const fileName =
|
|
6907
|
+
const fileName = basename4(filePath).toLowerCase();
|
|
6460
6908
|
const dirPath = dirname12(filePath).toLowerCase();
|
|
6461
6909
|
if (dirPath.includes("test") || dirPath.includes("spec") || dirPath.includes("__tests__")) {
|
|
6462
6910
|
return true;
|
|
@@ -6515,7 +6963,7 @@ function generateTestFileInventory(graph) {
|
|
|
6515
6963
|
return output;
|
|
6516
6964
|
}
|
|
6517
6965
|
function matchTestToSource(testFile) {
|
|
6518
|
-
const testFileName =
|
|
6966
|
+
const testFileName = basename4(testFile);
|
|
6519
6967
|
const testDir = dirname12(testFile);
|
|
6520
6968
|
let sourceFileName = testFileName.replace(/\.test\./g, ".").replace(/\.spec\./g, ".").replace(/_test\./g, ".").replace(/_spec\./g, ".");
|
|
6521
6969
|
const possiblePaths = [];
|
|
@@ -6677,7 +7125,7 @@ function generateTestCoverageMap(graph) {
|
|
|
6677
7125
|
const rows = mappings.slice(0, 30).map((m) => [
|
|
6678
7126
|
`\`${m.sourceFile}\``,
|
|
6679
7127
|
m.hasTest ? "\u2705" : "\u274C",
|
|
6680
|
-
m.testFile ? `\`${
|
|
7128
|
+
m.testFile ? `\`${basename4(m.testFile)}\`` : "-",
|
|
6681
7129
|
formatNumber(m.symbolCount)
|
|
6682
7130
|
]);
|
|
6683
7131
|
let output = table(headers, rows);
|
|
@@ -7453,8 +7901,8 @@ function getTopLevelDir2(filePath) {
|
|
|
7453
7901
|
}
|
|
7454
7902
|
|
|
7455
7903
|
// src/docs/status.ts
|
|
7456
|
-
import { readFileSync as
|
|
7457
|
-
import { resolve as
|
|
7904
|
+
import { readFileSync as readFileSync10, existsSync as existsSync10 } from "fs";
|
|
7905
|
+
import { resolve as resolve7 } from "path";
|
|
7458
7906
|
function generateStatus(graph, projectRoot, version) {
|
|
7459
7907
|
let output = "";
|
|
7460
7908
|
const now = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
@@ -7487,8 +7935,8 @@ function getFileCount11(graph) {
|
|
|
7487
7935
|
}
|
|
7488
7936
|
function extractComments(projectRoot, filePath) {
|
|
7489
7937
|
const comments = [];
|
|
7490
|
-
const resolvedRoot =
|
|
7491
|
-
const fullPath =
|
|
7938
|
+
const resolvedRoot = resolve7(projectRoot);
|
|
7939
|
+
const fullPath = resolve7(resolvedRoot, filePath);
|
|
7492
7940
|
if (!fullPath.startsWith(resolvedRoot)) {
|
|
7493
7941
|
return comments;
|
|
7494
7942
|
}
|
|
@@ -7496,7 +7944,7 @@ function extractComments(projectRoot, filePath) {
|
|
|
7496
7944
|
return comments;
|
|
7497
7945
|
}
|
|
7498
7946
|
try {
|
|
7499
|
-
const content =
|
|
7947
|
+
const content = readFileSync10(fullPath, "utf-8");
|
|
7500
7948
|
const lines = content.split("\n");
|
|
7501
7949
|
const patterns = [
|
|
7502
7950
|
{ type: "TODO", regex: /(?:\/\/|#|\/\*)\s*TODO:?\s*(.+)/i },
|
|
@@ -8075,16 +8523,16 @@ function generateConfidenceSection(title, description, symbols, projectRoot) {
|
|
|
8075
8523
|
}
|
|
8076
8524
|
|
|
8077
8525
|
// src/docs/metadata.ts
|
|
8078
|
-
import { existsSync as existsSync11, readFileSync as
|
|
8079
|
-
import { resolve as
|
|
8526
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11, writeFileSync as writeFileSync2 } from "fs";
|
|
8527
|
+
import { resolve as resolve8 } from "path";
|
|
8080
8528
|
function loadMetadata(outputDir) {
|
|
8081
|
-
const resolvedDir =
|
|
8082
|
-
const metadataPath =
|
|
8529
|
+
const resolvedDir = resolve8(outputDir);
|
|
8530
|
+
const metadataPath = resolve8(resolvedDir, "metadata.json");
|
|
8083
8531
|
if (!metadataPath.startsWith(resolvedDir) || !existsSync11(metadataPath)) {
|
|
8084
8532
|
return null;
|
|
8085
8533
|
}
|
|
8086
8534
|
try {
|
|
8087
|
-
const content =
|
|
8535
|
+
const content = readFileSync11(metadataPath, "utf-8");
|
|
8088
8536
|
return JSON.parse(content);
|
|
8089
8537
|
} catch (err) {
|
|
8090
8538
|
console.error("Failed to load metadata:", err);
|
|
@@ -8092,8 +8540,8 @@ function loadMetadata(outputDir) {
|
|
|
8092
8540
|
}
|
|
8093
8541
|
}
|
|
8094
8542
|
function saveMetadata(outputDir, metadata) {
|
|
8095
|
-
const resolvedDir =
|
|
8096
|
-
const metadataPath =
|
|
8543
|
+
const resolvedDir = resolve8(outputDir);
|
|
8544
|
+
const metadataPath = resolve8(resolvedDir, "metadata.json");
|
|
8097
8545
|
if (!metadataPath.startsWith(resolvedDir)) {
|
|
8098
8546
|
throw new Error(`Path traversal attempt blocked: ${metadataPath}`);
|
|
8099
8547
|
}
|
|
@@ -8178,7 +8626,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8178
8626
|
try {
|
|
8179
8627
|
if (options.verbose) console.log("Generating ARCHITECTURE.md...");
|
|
8180
8628
|
const content = generateArchitecture(graph, projectRoot, version, parseTime);
|
|
8181
|
-
const filePath =
|
|
8629
|
+
const filePath = join14(options.outputDir, "ARCHITECTURE.md");
|
|
8182
8630
|
writeFileSync3(filePath, content, "utf-8");
|
|
8183
8631
|
generated.push("ARCHITECTURE.md");
|
|
8184
8632
|
} catch (err) {
|
|
@@ -8189,7 +8637,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8189
8637
|
try {
|
|
8190
8638
|
if (options.verbose) console.log("Generating CONVENTIONS.md...");
|
|
8191
8639
|
const content = generateConventions(graph, projectRoot, version);
|
|
8192
|
-
const filePath =
|
|
8640
|
+
const filePath = join14(options.outputDir, "CONVENTIONS.md");
|
|
8193
8641
|
writeFileSync3(filePath, content, "utf-8");
|
|
8194
8642
|
generated.push("CONVENTIONS.md");
|
|
8195
8643
|
} catch (err) {
|
|
@@ -8200,7 +8648,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8200
8648
|
try {
|
|
8201
8649
|
if (options.verbose) console.log("Generating DEPENDENCIES.md...");
|
|
8202
8650
|
const content = generateDependencies(graph, projectRoot, version);
|
|
8203
|
-
const filePath =
|
|
8651
|
+
const filePath = join14(options.outputDir, "DEPENDENCIES.md");
|
|
8204
8652
|
writeFileSync3(filePath, content, "utf-8");
|
|
8205
8653
|
generated.push("DEPENDENCIES.md");
|
|
8206
8654
|
} catch (err) {
|
|
@@ -8211,7 +8659,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8211
8659
|
try {
|
|
8212
8660
|
if (options.verbose) console.log("Generating ONBOARDING.md...");
|
|
8213
8661
|
const content = generateOnboarding(graph, projectRoot, version);
|
|
8214
|
-
const filePath =
|
|
8662
|
+
const filePath = join14(options.outputDir, "ONBOARDING.md");
|
|
8215
8663
|
writeFileSync3(filePath, content, "utf-8");
|
|
8216
8664
|
generated.push("ONBOARDING.md");
|
|
8217
8665
|
} catch (err) {
|
|
@@ -8222,7 +8670,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8222
8670
|
try {
|
|
8223
8671
|
if (options.verbose) console.log("Generating FILES.md...");
|
|
8224
8672
|
const content = generateFiles(graph, projectRoot, version);
|
|
8225
|
-
const filePath =
|
|
8673
|
+
const filePath = join14(options.outputDir, "FILES.md");
|
|
8226
8674
|
writeFileSync3(filePath, content, "utf-8");
|
|
8227
8675
|
generated.push("FILES.md");
|
|
8228
8676
|
} catch (err) {
|
|
@@ -8233,7 +8681,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8233
8681
|
try {
|
|
8234
8682
|
if (options.verbose) console.log("Generating API_SURFACE.md...");
|
|
8235
8683
|
const content = generateApiSurface(graph, projectRoot, version);
|
|
8236
|
-
const filePath =
|
|
8684
|
+
const filePath = join14(options.outputDir, "API_SURFACE.md");
|
|
8237
8685
|
writeFileSync3(filePath, content, "utf-8");
|
|
8238
8686
|
generated.push("API_SURFACE.md");
|
|
8239
8687
|
} catch (err) {
|
|
@@ -8244,7 +8692,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8244
8692
|
try {
|
|
8245
8693
|
if (options.verbose) console.log("Generating ERRORS.md...");
|
|
8246
8694
|
const content = generateErrors(graph, projectRoot, version);
|
|
8247
|
-
const filePath =
|
|
8695
|
+
const filePath = join14(options.outputDir, "ERRORS.md");
|
|
8248
8696
|
writeFileSync3(filePath, content, "utf-8");
|
|
8249
8697
|
generated.push("ERRORS.md");
|
|
8250
8698
|
} catch (err) {
|
|
@@ -8255,7 +8703,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8255
8703
|
try {
|
|
8256
8704
|
if (options.verbose) console.log("Generating TESTS.md...");
|
|
8257
8705
|
const content = generateTests(graph, projectRoot, version);
|
|
8258
|
-
const filePath =
|
|
8706
|
+
const filePath = join14(options.outputDir, "TESTS.md");
|
|
8259
8707
|
writeFileSync3(filePath, content, "utf-8");
|
|
8260
8708
|
generated.push("TESTS.md");
|
|
8261
8709
|
} catch (err) {
|
|
@@ -8266,7 +8714,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8266
8714
|
try {
|
|
8267
8715
|
if (options.verbose) console.log("Generating HISTORY.md...");
|
|
8268
8716
|
const content = generateHistory(graph, projectRoot, version);
|
|
8269
|
-
const filePath =
|
|
8717
|
+
const filePath = join14(options.outputDir, "HISTORY.md");
|
|
8270
8718
|
writeFileSync3(filePath, content, "utf-8");
|
|
8271
8719
|
generated.push("HISTORY.md");
|
|
8272
8720
|
} catch (err) {
|
|
@@ -8277,7 +8725,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8277
8725
|
try {
|
|
8278
8726
|
if (options.verbose) console.log("Generating CURRENT.md...");
|
|
8279
8727
|
const content = generateCurrent(graph, projectRoot, version);
|
|
8280
|
-
const filePath =
|
|
8728
|
+
const filePath = join14(options.outputDir, "CURRENT.md");
|
|
8281
8729
|
writeFileSync3(filePath, content, "utf-8");
|
|
8282
8730
|
generated.push("CURRENT.md");
|
|
8283
8731
|
} catch (err) {
|
|
@@ -8288,7 +8736,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8288
8736
|
try {
|
|
8289
8737
|
if (options.verbose) console.log("Generating STATUS.md...");
|
|
8290
8738
|
const content = generateStatus(graph, projectRoot, version);
|
|
8291
|
-
const filePath =
|
|
8739
|
+
const filePath = join14(options.outputDir, "STATUS.md");
|
|
8292
8740
|
writeFileSync3(filePath, content, "utf-8");
|
|
8293
8741
|
generated.push("STATUS.md");
|
|
8294
8742
|
} catch (err) {
|
|
@@ -8299,7 +8747,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8299
8747
|
try {
|
|
8300
8748
|
if (options.verbose) console.log("Generating HEALTH.md...");
|
|
8301
8749
|
const content = generateHealth(graph, projectRoot, version);
|
|
8302
|
-
const filePath =
|
|
8750
|
+
const filePath = join14(options.outputDir, "HEALTH.md");
|
|
8303
8751
|
writeFileSync3(filePath, content, "utf-8");
|
|
8304
8752
|
generated.push("HEALTH.md");
|
|
8305
8753
|
} catch (err) {
|
|
@@ -8310,7 +8758,7 @@ async function generateDocs(graph, projectRoot, version, parseTime, options) {
|
|
|
8310
8758
|
try {
|
|
8311
8759
|
if (options.verbose) console.log("Generating DEAD_CODE.md...");
|
|
8312
8760
|
const content = generateDeadCode(graph, projectRoot, version);
|
|
8313
|
-
const filePath =
|
|
8761
|
+
const filePath = join14(options.outputDir, "DEAD_CODE.md");
|
|
8314
8762
|
writeFileSync3(filePath, content, "utf-8");
|
|
8315
8763
|
generated.push("DEAD_CODE.md");
|
|
8316
8764
|
} catch (err) {
|
|
@@ -8354,13 +8802,13 @@ function getFileCount13(graph) {
|
|
|
8354
8802
|
}
|
|
8355
8803
|
|
|
8356
8804
|
// src/simulation/engine.ts
|
|
8357
|
-
import { dirname as dirname15, join as
|
|
8358
|
-
function
|
|
8805
|
+
import { dirname as dirname15, join as join15 } from "path";
|
|
8806
|
+
function normalizePath2(p) {
|
|
8359
8807
|
return p.replace(/^\.\//, "").replace(/\/+$/, "");
|
|
8360
8808
|
}
|
|
8361
8809
|
function fileMatch(nodeFilePath, target) {
|
|
8362
|
-
const a =
|
|
8363
|
-
const b =
|
|
8810
|
+
const a = normalizePath2(nodeFilePath);
|
|
8811
|
+
const b = normalizePath2(target);
|
|
8364
8812
|
return a === b || a.endsWith("/" + b) || b.endsWith("/" + a);
|
|
8365
8813
|
}
|
|
8366
8814
|
var SimulationEngine = class {
|
|
@@ -8425,8 +8873,8 @@ var SimulationEngine = class {
|
|
|
8425
8873
|
}
|
|
8426
8874
|
// ── Action implementations ─────────────────────────────────────
|
|
8427
8875
|
applyMove(clone, target, destination, brokenImports) {
|
|
8428
|
-
const normalizedTarget =
|
|
8429
|
-
const normalizedDest =
|
|
8876
|
+
const normalizedTarget = normalizePath2(target);
|
|
8877
|
+
const normalizedDest = normalizePath2(destination);
|
|
8430
8878
|
const nodesToMove = clone.filterNodes(
|
|
8431
8879
|
(_node, attrs) => fileMatch(attrs.filePath, target)
|
|
8432
8880
|
);
|
|
@@ -8485,11 +8933,11 @@ var SimulationEngine = class {
|
|
|
8485
8933
|
}
|
|
8486
8934
|
}
|
|
8487
8935
|
applyRename(clone, target, newName, brokenImports) {
|
|
8488
|
-
const destination =
|
|
8936
|
+
const destination = join15(dirname15(target), newName);
|
|
8489
8937
|
this.applyMove(clone, target, destination, brokenImports);
|
|
8490
8938
|
}
|
|
8491
8939
|
applySplit(clone, target, newFile, symbols, brokenImports) {
|
|
8492
|
-
const normalizedNewFile =
|
|
8940
|
+
const normalizedNewFile = normalizePath2(newFile);
|
|
8493
8941
|
const nodesToSplit = clone.filterNodes((_node, attrs) => {
|
|
8494
8942
|
return fileMatch(attrs.filePath, target) && symbols.includes(attrs.name);
|
|
8495
8943
|
});
|
|
@@ -8525,7 +8973,7 @@ var SimulationEngine = class {
|
|
|
8525
8973
|
}
|
|
8526
8974
|
}
|
|
8527
8975
|
applyMerge(clone, target, source, brokenImports) {
|
|
8528
|
-
const normalizedTarget =
|
|
8976
|
+
const normalizedTarget = normalizePath2(target);
|
|
8529
8977
|
const sourceNodes = clone.filterNodes(
|
|
8530
8978
|
(_node, attrs) => fileMatch(attrs.filePath, source)
|
|
8531
8979
|
);
|
|
@@ -8686,12 +9134,12 @@ var SimulationEngine = class {
|
|
|
8686
9134
|
|
|
8687
9135
|
// src/security/scanner.ts
|
|
8688
9136
|
import { existsSync as existsSync14 } from "fs";
|
|
8689
|
-
import { join as
|
|
9137
|
+
import { join as join25 } from "path";
|
|
8690
9138
|
|
|
8691
9139
|
// src/security/checks/dependencies.ts
|
|
8692
9140
|
import { execSync as execSync2 } from "child_process";
|
|
8693
|
-
import { existsSync as existsSync13, readFileSync as
|
|
8694
|
-
import { join as
|
|
9141
|
+
import { existsSync as existsSync13, readFileSync as readFileSync12, readdirSync as readdirSync5 } from "fs";
|
|
9142
|
+
import { join as join16 } from "path";
|
|
8695
9143
|
function cvssToSeverity(score) {
|
|
8696
9144
|
if (score >= 9) return "critical";
|
|
8697
9145
|
if (score >= 7) return "high";
|
|
@@ -8701,18 +9149,18 @@ function cvssToSeverity(score) {
|
|
|
8701
9149
|
async function checkDependencies(_files, projectRoot) {
|
|
8702
9150
|
const findings = [];
|
|
8703
9151
|
try {
|
|
8704
|
-
if (existsSync13(
|
|
9152
|
+
if (existsSync13(join16(projectRoot, "package.json"))) {
|
|
8705
9153
|
findings.push(...checkNpmAudit(projectRoot));
|
|
8706
9154
|
findings.push(...checkPackageJsonPatterns(projectRoot));
|
|
8707
9155
|
findings.push(...checkPostinstallScripts(projectRoot));
|
|
8708
9156
|
}
|
|
8709
|
-
if (existsSync13(
|
|
9157
|
+
if (existsSync13(join16(projectRoot, "requirements.txt")) || existsSync13(join16(projectRoot, "pyproject.toml"))) {
|
|
8710
9158
|
findings.push(...checkPipAudit(projectRoot));
|
|
8711
9159
|
}
|
|
8712
|
-
if (existsSync13(
|
|
9160
|
+
if (existsSync13(join16(projectRoot, "Cargo.toml"))) {
|
|
8713
9161
|
findings.push(...checkCargoAudit(projectRoot));
|
|
8714
9162
|
}
|
|
8715
|
-
if (existsSync13(
|
|
9163
|
+
if (existsSync13(join16(projectRoot, "go.mod"))) {
|
|
8716
9164
|
findings.push(...checkGoVerify(projectRoot));
|
|
8717
9165
|
}
|
|
8718
9166
|
} catch (err) {
|
|
@@ -8801,8 +9249,8 @@ function checkNpmAudit(projectRoot) {
|
|
|
8801
9249
|
function checkPackageJsonPatterns(projectRoot) {
|
|
8802
9250
|
const findings = [];
|
|
8803
9251
|
try {
|
|
8804
|
-
const pkgPath =
|
|
8805
|
-
const pkg = JSON.parse(
|
|
9252
|
+
const pkgPath = join16(projectRoot, "package.json");
|
|
9253
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
8806
9254
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
8807
9255
|
for (const [name, version] of Object.entries(allDeps)) {
|
|
8808
9256
|
if (version.startsWith("^") || version.startsWith("~")) {
|
|
@@ -8824,15 +9272,15 @@ function checkPackageJsonPatterns(projectRoot) {
|
|
|
8824
9272
|
}
|
|
8825
9273
|
function checkPostinstallScripts(projectRoot) {
|
|
8826
9274
|
const findings = [];
|
|
8827
|
-
const nodeModules =
|
|
9275
|
+
const nodeModules = join16(projectRoot, "node_modules");
|
|
8828
9276
|
if (!existsSync13(nodeModules)) return findings;
|
|
8829
9277
|
try {
|
|
8830
9278
|
const topLevelDeps = readdirSync5(nodeModules).filter((d) => !d.startsWith("."));
|
|
8831
9279
|
for (const dep of topLevelDeps) {
|
|
8832
|
-
const depPkgPath =
|
|
9280
|
+
const depPkgPath = join16(nodeModules, dep, "package.json");
|
|
8833
9281
|
if (!existsSync13(depPkgPath)) continue;
|
|
8834
9282
|
try {
|
|
8835
|
-
const depPkg = JSON.parse(
|
|
9283
|
+
const depPkg = JSON.parse(readFileSync12(depPkgPath, "utf-8"));
|
|
8836
9284
|
const scripts = depPkg.scripts || {};
|
|
8837
9285
|
if (scripts.postinstall || scripts.preinstall || scripts.install) {
|
|
8838
9286
|
const scriptName = scripts.postinstall ? "postinstall" : scripts.preinstall ? "preinstall" : "install";
|
|
@@ -8870,7 +9318,7 @@ function checkPipAudit(projectRoot) {
|
|
|
8870
9318
|
id: "",
|
|
8871
9319
|
severity: cvssToSeverity(vuln.cvss?.score || 5),
|
|
8872
9320
|
vulnerabilityClass: "dependency-cve",
|
|
8873
|
-
file: existsSync13(
|
|
9321
|
+
file: existsSync13(join16(projectRoot, "requirements.txt")) ? "requirements.txt" : "pyproject.toml",
|
|
8874
9322
|
title: `Vulnerable Python dependency: ${vuln.name}`,
|
|
8875
9323
|
description: `${vuln.name}@${vuln.version} \u2014 ${vuln.id}: ${vuln.description || "Known vulnerability"}`,
|
|
8876
9324
|
attackScenario: `An attacker could exploit the vulnerability in ${vuln.name}.`,
|
|
@@ -8967,8 +9415,8 @@ function checkGoVerify(projectRoot) {
|
|
|
8967
9415
|
}
|
|
8968
9416
|
|
|
8969
9417
|
// src/security/checks/injection.ts
|
|
8970
|
-
import { readFileSync as
|
|
8971
|
-
import { join as
|
|
9418
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
9419
|
+
import { join as join17 } from "path";
|
|
8972
9420
|
var SKIP_DIRS = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
8973
9421
|
var TEST_PATTERNS = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__"];
|
|
8974
9422
|
var USER_INPUT_NAMES = /(?:input|user|name|path|query|branch|hash|cmd|command|req\.|params|body|args|url|dir|file|subdirectory)/i;
|
|
@@ -9069,7 +9517,7 @@ async function checkInjection(files, projectRoot) {
|
|
|
9069
9517
|
if (shouldSkip(file.filePath) || isTestFile4(file.filePath)) continue;
|
|
9070
9518
|
let content;
|
|
9071
9519
|
try {
|
|
9072
|
-
content =
|
|
9520
|
+
content = readFileSync13(join17(projectRoot, file.filePath), "utf-8");
|
|
9073
9521
|
} catch {
|
|
9074
9522
|
continue;
|
|
9075
9523
|
}
|
|
@@ -9107,8 +9555,8 @@ async function checkInjection(files, projectRoot) {
|
|
|
9107
9555
|
}
|
|
9108
9556
|
|
|
9109
9557
|
// src/security/checks/secrets.ts
|
|
9110
|
-
import { readFileSync as
|
|
9111
|
-
import { join as
|
|
9558
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
9559
|
+
import { join as join18 } from "path";
|
|
9112
9560
|
var SKIP_DIRS2 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
9113
9561
|
var TEST_PATTERNS2 = ["test", "spec", "fixture", "mock", "__tests__", "__mocks__", ".example", ".sample"];
|
|
9114
9562
|
var SECRET_PATTERNS = [
|
|
@@ -9141,7 +9589,7 @@ async function checkSecrets(files, projectRoot) {
|
|
|
9141
9589
|
if (shouldSkip2(file.filePath) || isTestFile5(file.filePath)) continue;
|
|
9142
9590
|
let content;
|
|
9143
9591
|
try {
|
|
9144
|
-
content =
|
|
9592
|
+
content = readFileSync14(join18(projectRoot, file.filePath), "utf-8");
|
|
9145
9593
|
} catch {
|
|
9146
9594
|
continue;
|
|
9147
9595
|
}
|
|
@@ -9174,8 +9622,8 @@ async function checkSecrets(files, projectRoot) {
|
|
|
9174
9622
|
}
|
|
9175
9623
|
|
|
9176
9624
|
// src/security/checks/path-traversal.ts
|
|
9177
|
-
import { readFileSync as
|
|
9178
|
-
import { join as
|
|
9625
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
9626
|
+
import { join as join19 } from "path";
|
|
9179
9627
|
var SKIP_DIRS3 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
9180
9628
|
var USER_INPUT_VARS = /(?:req\.|params|query|body|input|path|dir|subdirectory|file|userInput|fileName|filePath)/i;
|
|
9181
9629
|
var PATTERNS2 = [
|
|
@@ -9222,7 +9670,7 @@ async function checkPathTraversal(files, projectRoot) {
|
|
|
9222
9670
|
if (shouldSkip3(file.filePath)) continue;
|
|
9223
9671
|
let content;
|
|
9224
9672
|
try {
|
|
9225
|
-
content =
|
|
9673
|
+
content = readFileSync15(join19(projectRoot, file.filePath), "utf-8");
|
|
9226
9674
|
} catch {
|
|
9227
9675
|
continue;
|
|
9228
9676
|
}
|
|
@@ -9264,8 +9712,8 @@ async function checkPathTraversal(files, projectRoot) {
|
|
|
9264
9712
|
}
|
|
9265
9713
|
|
|
9266
9714
|
// src/security/checks/auth.ts
|
|
9267
|
-
import { readFileSync as
|
|
9268
|
-
import { join as
|
|
9715
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
9716
|
+
import { join as join20 } from "path";
|
|
9269
9717
|
var SKIP_DIRS4 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
9270
9718
|
function shouldSkip4(filePath) {
|
|
9271
9719
|
return SKIP_DIRS4.some((d) => filePath.includes(d));
|
|
@@ -9281,7 +9729,7 @@ async function checkAuth(files, projectRoot) {
|
|
|
9281
9729
|
if (shouldSkip4(file.filePath)) continue;
|
|
9282
9730
|
let content;
|
|
9283
9731
|
try {
|
|
9284
|
-
content =
|
|
9732
|
+
content = readFileSync16(join20(projectRoot, file.filePath), "utf-8");
|
|
9285
9733
|
} catch {
|
|
9286
9734
|
continue;
|
|
9287
9735
|
}
|
|
@@ -9372,8 +9820,8 @@ async function checkAuth(files, projectRoot) {
|
|
|
9372
9820
|
}
|
|
9373
9821
|
|
|
9374
9822
|
// src/security/checks/input-validation.ts
|
|
9375
|
-
import { readFileSync as
|
|
9376
|
-
import { join as
|
|
9823
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
9824
|
+
import { join as join21 } from "path";
|
|
9377
9825
|
var SKIP_DIRS5 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
9378
9826
|
function shouldSkip5(filePath) {
|
|
9379
9827
|
return SKIP_DIRS5.some((d) => filePath.includes(d));
|
|
@@ -9385,7 +9833,7 @@ async function checkInputValidation(files, projectRoot) {
|
|
|
9385
9833
|
if (shouldSkip5(file.filePath)) continue;
|
|
9386
9834
|
let content;
|
|
9387
9835
|
try {
|
|
9388
|
-
content =
|
|
9836
|
+
content = readFileSync17(join21(projectRoot, file.filePath), "utf-8");
|
|
9389
9837
|
} catch {
|
|
9390
9838
|
continue;
|
|
9391
9839
|
}
|
|
@@ -9462,8 +9910,8 @@ async function checkInputValidation(files, projectRoot) {
|
|
|
9462
9910
|
}
|
|
9463
9911
|
|
|
9464
9912
|
// src/security/checks/information-disclosure.ts
|
|
9465
|
-
import { readFileSync as
|
|
9466
|
-
import { join as
|
|
9913
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
9914
|
+
import { join as join22 } from "path";
|
|
9467
9915
|
var SKIP_DIRS6 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
9468
9916
|
function shouldSkip6(filePath) {
|
|
9469
9917
|
return SKIP_DIRS6.some((d) => filePath.includes(d));
|
|
@@ -9475,7 +9923,7 @@ async function checkInformationDisclosure(files, projectRoot) {
|
|
|
9475
9923
|
if (shouldSkip6(file.filePath)) continue;
|
|
9476
9924
|
let content;
|
|
9477
9925
|
try {
|
|
9478
|
-
content =
|
|
9926
|
+
content = readFileSync18(join22(projectRoot, file.filePath), "utf-8");
|
|
9479
9927
|
} catch {
|
|
9480
9928
|
continue;
|
|
9481
9929
|
}
|
|
@@ -9545,8 +9993,8 @@ async function checkInformationDisclosure(files, projectRoot) {
|
|
|
9545
9993
|
}
|
|
9546
9994
|
|
|
9547
9995
|
// src/security/checks/cryptography.ts
|
|
9548
|
-
import { readFileSync as
|
|
9549
|
-
import { join as
|
|
9996
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
9997
|
+
import { join as join23 } from "path";
|
|
9550
9998
|
var SKIP_DIRS7 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
9551
9999
|
function shouldSkip7(filePath) {
|
|
9552
10000
|
return SKIP_DIRS7.some((d) => filePath.includes(d));
|
|
@@ -9562,7 +10010,7 @@ async function checkCryptography(files, projectRoot) {
|
|
|
9562
10010
|
if (shouldSkip7(file.filePath)) continue;
|
|
9563
10011
|
let content;
|
|
9564
10012
|
try {
|
|
9565
|
-
content =
|
|
10013
|
+
content = readFileSync19(join23(projectRoot, file.filePath), "utf-8");
|
|
9566
10014
|
} catch {
|
|
9567
10015
|
continue;
|
|
9568
10016
|
}
|
|
@@ -9644,8 +10092,8 @@ async function checkCryptography(files, projectRoot) {
|
|
|
9644
10092
|
}
|
|
9645
10093
|
|
|
9646
10094
|
// src/security/checks/frontend.ts
|
|
9647
|
-
import { readFileSync as
|
|
9648
|
-
import { join as
|
|
10095
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
10096
|
+
import { join as join24 } from "path";
|
|
9649
10097
|
var SKIP_DIRS8 = ["node_modules/", "dist/", ".git/", ".wrangler/", "src/security/checks/"];
|
|
9650
10098
|
function shouldSkip8(filePath) {
|
|
9651
10099
|
return SKIP_DIRS8.some((d) => filePath.includes(d));
|
|
@@ -9661,7 +10109,7 @@ async function checkFrontend(files, projectRoot) {
|
|
|
9661
10109
|
if (!isFrontendFile(file.filePath)) continue;
|
|
9662
10110
|
let content;
|
|
9663
10111
|
try {
|
|
9664
|
-
content =
|
|
10112
|
+
content = readFileSync20(join24(projectRoot, file.filePath), "utf-8");
|
|
9665
10113
|
} catch {
|
|
9666
10114
|
continue;
|
|
9667
10115
|
}
|
|
@@ -10121,11 +10569,11 @@ async function scanSecurity(projectRoot, graph, options = {}) {
|
|
|
10121
10569
|
};
|
|
10122
10570
|
}
|
|
10123
10571
|
function detectPackageManager(projectRoot) {
|
|
10124
|
-
if (existsSync14(
|
|
10125
|
-
if (existsSync14(
|
|
10126
|
-
if (existsSync14(
|
|
10127
|
-
if (existsSync14(
|
|
10128
|
-
if (existsSync14(
|
|
10572
|
+
if (existsSync14(join25(projectRoot, "package.json"))) return "npm";
|
|
10573
|
+
if (existsSync14(join25(projectRoot, "requirements.txt"))) return "pip";
|
|
10574
|
+
if (existsSync14(join25(projectRoot, "pyproject.toml"))) return "pip";
|
|
10575
|
+
if (existsSync14(join25(projectRoot, "Cargo.toml"))) return "cargo";
|
|
10576
|
+
if (existsSync14(join25(projectRoot, "go.mod"))) return "go";
|
|
10129
10577
|
return "unknown";
|
|
10130
10578
|
}
|
|
10131
10579
|
|
|
@@ -10133,6 +10581,7 @@ export {
|
|
|
10133
10581
|
findProjectRoot,
|
|
10134
10582
|
parseTypeScriptFile,
|
|
10135
10583
|
parseProject,
|
|
10584
|
+
detectCrossLanguageEdges,
|
|
10136
10585
|
buildGraph,
|
|
10137
10586
|
findSymbols,
|
|
10138
10587
|
getDependencies,
|