@neat.is/core 0.8.1 → 0.9.0-rc.1
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-ZGZYZ33B.js → chunk-RDLDSIEY.js} +1595 -530
- package/dist/chunk-RDLDSIEY.js.map +1 -0
- package/dist/{chunk-RQTW7IV3.js → chunk-WMYVJXUF.js} +2 -2
- package/dist/cli.cjs +2201 -1130
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +1840 -769
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +1846 -775
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +1749 -678
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +7 -2
- package/dist/chunk-ZGZYZ33B.js.map +0 -1
- /package/dist/{chunk-RQTW7IV3.js.map → chunk-WMYVJXUF.js.map} +0 -0
|
@@ -961,7 +961,7 @@ import { promises as fs3 } from "fs";
|
|
|
961
961
|
import path3 from "path";
|
|
962
962
|
import { parse as parseYaml } from "yaml";
|
|
963
963
|
import { extractedEdgeId } from "@neat.is/types";
|
|
964
|
-
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php"]);
|
|
964
|
+
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php", ".cs", ".java", ".kt", ".rs", ".cpp", ".cc", ".cxx", ".c++", ".hpp", ".hh", ".hxx", ".h++"]);
|
|
965
965
|
var CONFIG_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml"]);
|
|
966
966
|
var JSON_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["app.json", "app.config.json", "eas.json"]);
|
|
967
967
|
var IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -1247,7 +1247,8 @@ function buildServiceHostIndex(services) {
|
|
|
1247
1247
|
}
|
|
1248
1248
|
return { knownHosts, hostToNodeId };
|
|
1249
1249
|
}
|
|
1250
|
-
async function walkSourceFiles(dir) {
|
|
1250
|
+
async function walkSourceFiles(dir, excludeDirs = []) {
|
|
1251
|
+
const excluded = new Set(excludeDirs.map((d) => path5.resolve(d)));
|
|
1251
1252
|
const out = [];
|
|
1252
1253
|
async function walk9(current) {
|
|
1253
1254
|
const entries = await fs5.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
@@ -1255,6 +1256,7 @@ async function walkSourceFiles(dir) {
|
|
|
1255
1256
|
const full = path5.join(current, entry.name);
|
|
1256
1257
|
if (entry.isDirectory()) {
|
|
1257
1258
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
1259
|
+
if (excluded.has(path5.resolve(full))) continue;
|
|
1258
1260
|
if (await isPythonVenvDir(full)) continue;
|
|
1259
1261
|
await walk9(full);
|
|
1260
1262
|
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(path5.extname(entry.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
|
|
@@ -1267,8 +1269,8 @@ async function walkSourceFiles(dir) {
|
|
|
1267
1269
|
await walk9(dir);
|
|
1268
1270
|
return out;
|
|
1269
1271
|
}
|
|
1270
|
-
async function loadSourceFiles(dir) {
|
|
1271
|
-
const paths = await walkSourceFiles(dir);
|
|
1272
|
+
async function loadSourceFiles(dir, excludeDirs = []) {
|
|
1273
|
+
const paths = await walkSourceFiles(dir, excludeDirs);
|
|
1272
1274
|
const out = [];
|
|
1273
1275
|
for (const p of paths) {
|
|
1274
1276
|
try {
|
|
@@ -1301,6 +1303,23 @@ function languageForPath(relPath) {
|
|
|
1301
1303
|
return "ruby";
|
|
1302
1304
|
case ".php":
|
|
1303
1305
|
return "php";
|
|
1306
|
+
case ".cs":
|
|
1307
|
+
return "csharp";
|
|
1308
|
+
case ".java":
|
|
1309
|
+
return "java";
|
|
1310
|
+
case ".kt":
|
|
1311
|
+
return "kotlin";
|
|
1312
|
+
case ".rs":
|
|
1313
|
+
return "rust";
|
|
1314
|
+
case ".cpp":
|
|
1315
|
+
case ".cc":
|
|
1316
|
+
case ".cxx":
|
|
1317
|
+
case ".c++":
|
|
1318
|
+
case ".hpp":
|
|
1319
|
+
case ".hh":
|
|
1320
|
+
case ".hxx":
|
|
1321
|
+
case ".h++":
|
|
1322
|
+
return "cpp";
|
|
1304
1323
|
case ".ts":
|
|
1305
1324
|
case ".tsx":
|
|
1306
1325
|
return "typescript";
|
|
@@ -1639,6 +1658,7 @@ async function resolveGoImport(specifier, modulePath, serviceDir) {
|
|
|
1639
1658
|
function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
|
|
1640
1659
|
const importeeFileId = fileId2(serviceName, importeeRelPath);
|
|
1641
1660
|
if (!graph.hasNode(importeeFileId)) return 0;
|
|
1661
|
+
if (importerFileId === importeeFileId) return 0;
|
|
1642
1662
|
const edgeId = extractedEdgeId3(importerFileId, importeeFileId, EdgeType3.IMPORTS);
|
|
1643
1663
|
if (graph.hasEdge(edgeId)) return 0;
|
|
1644
1664
|
const edge = {
|
|
@@ -1660,7 +1680,7 @@ async function addImports(graph, services) {
|
|
|
1660
1680
|
let edgesAdded = 0;
|
|
1661
1681
|
for (const service of services) {
|
|
1662
1682
|
const tsPaths = await loadTsPathConfig(service.dir);
|
|
1663
|
-
const files = await loadSourceFiles(service.dir);
|
|
1683
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
1664
1684
|
for (const file of files) {
|
|
1665
1685
|
if (isTestPath(file.path)) continue;
|
|
1666
1686
|
const relFile = toPosix(path6.relative(service.dir, file.path));
|
|
@@ -1842,8 +1862,8 @@ function chiRoutesFromSource(source, parser) {
|
|
|
1842
1862
|
chiWalk(tree.rootNode, "", out);
|
|
1843
1863
|
return out;
|
|
1844
1864
|
}
|
|
1845
|
-
function stripChiRegex(
|
|
1846
|
-
return
|
|
1865
|
+
function stripChiRegex(path70) {
|
|
1866
|
+
return path70.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
|
|
1847
1867
|
}
|
|
1848
1868
|
function chiWalk(node, prefix, out) {
|
|
1849
1869
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -2515,9 +2535,9 @@ function rubyRocketRoute(args) {
|
|
|
2515
2535
|
if (!pair || pair.type !== "pair") continue;
|
|
2516
2536
|
const k = pair.childForFieldName("key");
|
|
2517
2537
|
if (k?.type !== "string") continue;
|
|
2518
|
-
const
|
|
2519
|
-
if (
|
|
2520
|
-
return { path:
|
|
2538
|
+
const path70 = rubyLiteral(k);
|
|
2539
|
+
if (path70 === null) continue;
|
|
2540
|
+
return { path: path70, target: rubyLiteral(pair.childForFieldName("value")) };
|
|
2521
2541
|
}
|
|
2522
2542
|
return null;
|
|
2523
2543
|
}
|
|
@@ -3202,7 +3222,7 @@ async function addRoutes(graph, services) {
|
|
|
3202
3222
|
const hasLaravel = deps["laravel/framework"] !== void 0;
|
|
3203
3223
|
if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasChi && !isGoService && !hasRails && !hasLaravel)
|
|
3204
3224
|
continue;
|
|
3205
|
-
const files = await loadSourceFiles(service.dir);
|
|
3225
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
3206
3226
|
const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
|
|
3207
3227
|
for (const file of files) {
|
|
3208
3228
|
if (isTestPath(file.path)) continue;
|
|
@@ -4634,29 +4654,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
4634
4654
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
4635
4655
|
});
|
|
4636
4656
|
let promoted = 0;
|
|
4637
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
4657
|
+
for (const { frontierId: frontierId2, serviceId: serviceId15 } of toPromote) {
|
|
4638
4658
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
4639
4659
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
4640
4660
|
if (!gate.allowed) {
|
|
4641
4661
|
continue;
|
|
4642
4662
|
}
|
|
4643
4663
|
}
|
|
4644
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
4664
|
+
rewireFrontierEdges(graph, frontierId2, serviceId15);
|
|
4645
4665
|
graph.dropNode(frontierId2);
|
|
4646
4666
|
promoted++;
|
|
4647
4667
|
}
|
|
4648
4668
|
return promoted;
|
|
4649
4669
|
}
|
|
4650
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
4670
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId15) {
|
|
4651
4671
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
4652
4672
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
4653
4673
|
for (const edgeId of inbound) {
|
|
4654
4674
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4655
|
-
rebuildEdge(graph, edge, edge.source,
|
|
4675
|
+
rebuildEdge(graph, edge, edge.source, serviceId15, edgeId);
|
|
4656
4676
|
}
|
|
4657
4677
|
for (const edgeId of outbound) {
|
|
4658
4678
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4659
|
-
rebuildEdge(graph, edge,
|
|
4679
|
+
rebuildEdge(graph, edge, serviceId15, edge.target, edgeId);
|
|
4660
4680
|
}
|
|
4661
4681
|
}
|
|
4662
4682
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -4988,19 +5008,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
4988
5008
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
4989
5009
|
let best = { path: [start], edges: [] };
|
|
4990
5010
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
4991
|
-
function step(node,
|
|
4992
|
-
if (
|
|
4993
|
-
best = { path: [...
|
|
5011
|
+
function step(node, path70, edges) {
|
|
5012
|
+
if (path70.length > best.path.length) {
|
|
5013
|
+
best = { path: [...path70], edges: [...edges] };
|
|
4994
5014
|
}
|
|
4995
|
-
if (
|
|
5015
|
+
if (path70.length - 1 >= maxDepth) return;
|
|
4996
5016
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
4997
5017
|
for (const [srcId, edge] of incoming) {
|
|
4998
5018
|
if (visited.has(srcId)) continue;
|
|
4999
5019
|
visited.add(srcId);
|
|
5000
|
-
|
|
5020
|
+
path70.push(srcId);
|
|
5001
5021
|
edges.push(edge);
|
|
5002
|
-
step(srcId,
|
|
5003
|
-
|
|
5022
|
+
step(srcId, path70, edges);
|
|
5023
|
+
path70.pop();
|
|
5004
5024
|
edges.pop();
|
|
5005
5025
|
visited.delete(srcId);
|
|
5006
5026
|
}
|
|
@@ -5015,7 +5035,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
|
|
|
5015
5035
|
for (const id of walk9.path) {
|
|
5016
5036
|
const owner = resolveOwningService(graph, id);
|
|
5017
5037
|
if (!owner) continue;
|
|
5018
|
-
const { id:
|
|
5038
|
+
const { id: serviceId15, svc } = owner;
|
|
5019
5039
|
const deps = svc.dependencies ?? {};
|
|
5020
5040
|
for (const pair of candidatePairs) {
|
|
5021
5041
|
const declared = deps[pair.driver];
|
|
@@ -5028,7 +5048,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
|
|
|
5028
5048
|
);
|
|
5029
5049
|
if (!result.compatible) {
|
|
5030
5050
|
return {
|
|
5031
|
-
rootCauseNode:
|
|
5051
|
+
rootCauseNode: serviceId15,
|
|
5032
5052
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
5033
5053
|
...result.minDriverVersion ? {
|
|
5034
5054
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -5043,7 +5063,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
|
|
|
5043
5063
|
for (const id of walk9.path) {
|
|
5044
5064
|
const owner = resolveOwningService(graph, id);
|
|
5045
5065
|
if (!owner) continue;
|
|
5046
|
-
const { id:
|
|
5066
|
+
const { id: serviceId15, svc } = owner;
|
|
5047
5067
|
const deps = svc.dependencies ?? {};
|
|
5048
5068
|
const serviceNodeEngine = svc.nodeEngine;
|
|
5049
5069
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -5052,7 +5072,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
|
|
|
5052
5072
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
5053
5073
|
if (!result.compatible && result.reason) {
|
|
5054
5074
|
return {
|
|
5055
|
-
rootCauseNode:
|
|
5075
|
+
rootCauseNode: serviceId15,
|
|
5056
5076
|
rootCauseReason: result.reason,
|
|
5057
5077
|
...result.requiredNodeVersion ? {
|
|
5058
5078
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -5067,7 +5087,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
|
|
|
5067
5087
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
5068
5088
|
if (!result.compatible && result.reason) {
|
|
5069
5089
|
return {
|
|
5070
|
-
rootCauseNode:
|
|
5090
|
+
rootCauseNode: serviceId15,
|
|
5071
5091
|
rootCauseReason: result.reason,
|
|
5072
5092
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
5073
5093
|
};
|
|
@@ -5168,9 +5188,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
5168
5188
|
function isFailingCallEdge(e) {
|
|
5169
5189
|
return e.type === EdgeType6.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
5170
5190
|
}
|
|
5171
|
-
function callSourcesForService(graph,
|
|
5172
|
-
const ids = [
|
|
5173
|
-
for (const edgeId of graph.outboundEdges(
|
|
5191
|
+
function callSourcesForService(graph, serviceId15) {
|
|
5192
|
+
const ids = [serviceId15];
|
|
5193
|
+
for (const edgeId of graph.outboundEdges(serviceId15)) {
|
|
5174
5194
|
const e = graph.getEdgeAttributes(edgeId);
|
|
5175
5195
|
if (e.type !== EdgeType6.CONTAINS) continue;
|
|
5176
5196
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -5194,9 +5214,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
5194
5214
|
}
|
|
5195
5215
|
return id < curId;
|
|
5196
5216
|
}
|
|
5197
|
-
function dominantFailingCall(graph,
|
|
5217
|
+
function dominantFailingCall(graph, serviceId15, visited) {
|
|
5198
5218
|
let best = null;
|
|
5199
|
-
for (const src of callSourcesForService(graph,
|
|
5219
|
+
for (const src of callSourcesForService(graph, serviceId15)) {
|
|
5200
5220
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
5201
5221
|
const e = graph.getEdgeAttributes(edgeId);
|
|
5202
5222
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -5211,26 +5231,26 @@ function dominantFailingCall(graph, serviceId9, visited) {
|
|
|
5211
5231
|
return best;
|
|
5212
5232
|
}
|
|
5213
5233
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
5214
|
-
const
|
|
5234
|
+
const path70 = [originServiceId];
|
|
5215
5235
|
const edges = [];
|
|
5216
5236
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
5217
5237
|
let current = originServiceId;
|
|
5218
5238
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
5219
5239
|
const hop = dominantFailingCall(graph, current, visited);
|
|
5220
5240
|
if (!hop) break;
|
|
5221
|
-
|
|
5241
|
+
path70.push(hop.nextService);
|
|
5222
5242
|
edges.push(hop.edge);
|
|
5223
5243
|
visited.add(hop.nextService);
|
|
5224
5244
|
current = hop.nextService;
|
|
5225
5245
|
}
|
|
5226
5246
|
if (edges.length === 0) return null;
|
|
5227
|
-
return { path:
|
|
5247
|
+
return { path: path70, edges, culprit: current };
|
|
5228
5248
|
}
|
|
5229
5249
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
5230
5250
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
5231
5251
|
if (!chain) return null;
|
|
5232
5252
|
const culprit = chain.culprit;
|
|
5233
|
-
const
|
|
5253
|
+
const path70 = [...chain.path];
|
|
5234
5254
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
5235
5255
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
5236
5256
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -5238,14 +5258,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
5238
5258
|
if (loc) {
|
|
5239
5259
|
let rootCauseNode = culprit;
|
|
5240
5260
|
if (loc.fileNode) {
|
|
5241
|
-
|
|
5261
|
+
path70.push(loc.fileNode);
|
|
5242
5262
|
edgeProvenances.push(Provenance6.OBSERVED);
|
|
5243
5263
|
rootCauseNode = loc.fileNode;
|
|
5244
5264
|
}
|
|
5245
5265
|
return RootCauseResultSchema.parse({
|
|
5246
5266
|
rootCauseNode,
|
|
5247
5267
|
rootCauseReason: loc.rootCauseReason,
|
|
5248
|
-
traversalPath:
|
|
5268
|
+
traversalPath: path70,
|
|
5249
5269
|
edgeProvenances,
|
|
5250
5270
|
confidence,
|
|
5251
5271
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -5257,7 +5277,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
5257
5277
|
return RootCauseResultSchema.parse({
|
|
5258
5278
|
rootCauseNode: culprit,
|
|
5259
5279
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
5260
|
-
traversalPath:
|
|
5280
|
+
traversalPath: path70,
|
|
5261
5281
|
edgeProvenances,
|
|
5262
5282
|
confidence,
|
|
5263
5283
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -5693,10 +5713,10 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
|
|
|
5693
5713
|
let traversalPath = legacy.traversalPath;
|
|
5694
5714
|
let edgeProvenances = legacy.edgeProvenances;
|
|
5695
5715
|
if (top.node !== seedNode) {
|
|
5696
|
-
const
|
|
5697
|
-
if (
|
|
5698
|
-
traversalPath =
|
|
5699
|
-
edgeProvenances =
|
|
5716
|
+
const path70 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
|
|
5717
|
+
if (path70) {
|
|
5718
|
+
traversalPath = path70.nodes;
|
|
5719
|
+
edgeProvenances = path70.edges.map((e) => e.provenance);
|
|
5700
5720
|
} else {
|
|
5701
5721
|
traversalPath = [errorNodeId, top.node];
|
|
5702
5722
|
edgeProvenances = [top.provenance ?? Provenance6.OBSERVED];
|
|
@@ -5725,11 +5745,11 @@ function fixRecommendationForTop(top, seedNode, legacy) {
|
|
|
5725
5745
|
}
|
|
5726
5746
|
|
|
5727
5747
|
// src/extract/services.ts
|
|
5728
|
-
import { promises as
|
|
5729
|
-
import
|
|
5748
|
+
import { promises as fs18 } from "fs";
|
|
5749
|
+
import path20 from "path";
|
|
5730
5750
|
import ignore from "ignore";
|
|
5731
5751
|
import { minimatch as minimatch2 } from "minimatch";
|
|
5732
|
-
import { NodeType as
|
|
5752
|
+
import { NodeType as NodeType15, serviceId as serviceId11 } from "@neat.is/types";
|
|
5733
5753
|
|
|
5734
5754
|
// src/extract/python.ts
|
|
5735
5755
|
import { promises as fs8 } from "fs";
|
|
@@ -5918,18 +5938,391 @@ async function discoverPhpService(scanPath, dir) {
|
|
|
5918
5938
|
return { pkg, dir, node };
|
|
5919
5939
|
}
|
|
5920
5940
|
|
|
5921
|
-
// src/extract/
|
|
5941
|
+
// src/extract/csharp.ts
|
|
5922
5942
|
import { promises as fs11 } from "fs";
|
|
5923
5943
|
import path13 from "path";
|
|
5944
|
+
import { NodeType as NodeType9, serviceId as serviceId5 } from "@neat.is/types";
|
|
5945
|
+
var CSHARP_PROJECT_EXTS = /* @__PURE__ */ new Set([".csproj", ".sln"]);
|
|
5946
|
+
async function projectFiles(dir) {
|
|
5947
|
+
const entries = await fs11.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
5948
|
+
const out = [];
|
|
5949
|
+
for (const entry of entries) {
|
|
5950
|
+
if (!entry.isFile()) continue;
|
|
5951
|
+
if (CSHARP_PROJECT_EXTS.has(path13.extname(entry.name).toLowerCase())) out.push(entry.name);
|
|
5952
|
+
}
|
|
5953
|
+
return out.sort();
|
|
5954
|
+
}
|
|
5955
|
+
var NESTED_PROJECT_DIR = "src";
|
|
5956
|
+
async function resolveProjectDir(dir) {
|
|
5957
|
+
if ((await projectFiles(dir)).length > 0) return dir;
|
|
5958
|
+
const nested = path13.join(dir, NESTED_PROJECT_DIR);
|
|
5959
|
+
if ((await projectFiles(nested)).length > 0) return nested;
|
|
5960
|
+
return null;
|
|
5961
|
+
}
|
|
5962
|
+
async function hasCsharpProject(dir) {
|
|
5963
|
+
return await resolveProjectDir(dir) !== null;
|
|
5964
|
+
}
|
|
5965
|
+
function parseCsproj(source) {
|
|
5966
|
+
const dependencies = {};
|
|
5967
|
+
const re = /<PackageReference\b([^>]*?)\/?>/g;
|
|
5968
|
+
let m;
|
|
5969
|
+
while ((m = re.exec(source)) !== null) {
|
|
5970
|
+
const attrs = m[1] ?? "";
|
|
5971
|
+
const include = attrs.match(/\bInclude\s*=\s*"([^"]+)"/)?.[1];
|
|
5972
|
+
if (!include) continue;
|
|
5973
|
+
const version = attrs.match(/\bVersion\s*=\s*"([^"]+)"/)?.[1];
|
|
5974
|
+
dependencies[include] = version ?? "";
|
|
5975
|
+
}
|
|
5976
|
+
return { dependencies };
|
|
5977
|
+
}
|
|
5978
|
+
async function discoverCsharpService(scanPath, dir) {
|
|
5979
|
+
const projectDir = await resolveProjectDir(dir);
|
|
5980
|
+
if (projectDir === null) return null;
|
|
5981
|
+
const projects = await projectFiles(projectDir);
|
|
5982
|
+
const csproj = projects.find((p) => p.toLowerCase().endsWith(".csproj"));
|
|
5983
|
+
const name = csproj ? path13.basename(csproj, path13.extname(csproj)) : path13.basename(dir);
|
|
5984
|
+
let dependencies = {};
|
|
5985
|
+
if (csproj) {
|
|
5986
|
+
const raw = await fs11.readFile(path13.join(projectDir, csproj), "utf8").catch(() => "");
|
|
5987
|
+
dependencies = parseCsproj(raw).dependencies;
|
|
5988
|
+
}
|
|
5989
|
+
const pkg = { name, dependencies };
|
|
5990
|
+
const node = {
|
|
5991
|
+
id: serviceId5(name),
|
|
5992
|
+
type: NodeType9.ServiceNode,
|
|
5993
|
+
name,
|
|
5994
|
+
language: "csharp",
|
|
5995
|
+
dependencies,
|
|
5996
|
+
// Anchor on the directory that holds the project and its `.cs` files, so file
|
|
5997
|
+
// attribution reaches the source whether it sits at the root or under `src/`.
|
|
5998
|
+
repoPath: path13.relative(scanPath, projectDir)
|
|
5999
|
+
};
|
|
6000
|
+
return { pkg, dir: projectDir, node };
|
|
6001
|
+
}
|
|
6002
|
+
|
|
6003
|
+
// src/extract/java.ts
|
|
6004
|
+
import { promises as fs12 } from "fs";
|
|
6005
|
+
import path14 from "path";
|
|
6006
|
+
import { NodeType as NodeType10, serviceId as serviceId6 } from "@neat.is/types";
|
|
6007
|
+
var JAVA_MANIFESTS = ["pom.xml", "build.gradle", "build.gradle.kts"];
|
|
6008
|
+
async function hasJavaManifest(dir) {
|
|
6009
|
+
for (const manifest of JAVA_MANIFESTS) {
|
|
6010
|
+
if (await exists(path14.join(dir, manifest))) return true;
|
|
6011
|
+
}
|
|
6012
|
+
return false;
|
|
6013
|
+
}
|
|
6014
|
+
function parsePomXml(source) {
|
|
6015
|
+
const dependencies = {};
|
|
6016
|
+
const depBlock = /<dependency\b[^>]*>([\s\S]*?)<\/dependency>/g;
|
|
6017
|
+
let d;
|
|
6018
|
+
while ((d = depBlock.exec(source)) !== null) {
|
|
6019
|
+
const body = d[1] ?? "";
|
|
6020
|
+
const groupId = body.match(/<groupId>\s*([^<]+?)\s*<\/groupId>/)?.[1];
|
|
6021
|
+
const artifactId = body.match(/<artifactId>\s*([^<]+?)\s*<\/artifactId>/)?.[1];
|
|
6022
|
+
if (!groupId || !artifactId) continue;
|
|
6023
|
+
const version = body.match(/<version>\s*([^<]+?)\s*<\/version>/)?.[1];
|
|
6024
|
+
dependencies[`${groupId}:${artifactId}`] = version ?? "";
|
|
6025
|
+
}
|
|
6026
|
+
const withoutBlocks = source.replace(/<parent\b[^>]*>[\s\S]*?<\/parent>/g, "").replace(/<dependencyManagement\b[^>]*>[\s\S]*?<\/dependencyManagement>/g, "").replace(/<dependencies\b[^>]*>[\s\S]*?<\/dependencies>/g, "").replace(/<build\b[^>]*>[\s\S]*?<\/build>/g, "");
|
|
6027
|
+
const name = withoutBlocks.match(/<artifactId>\s*([^<]+?)\s*<\/artifactId>/)?.[1];
|
|
6028
|
+
return { name, dependencies };
|
|
6029
|
+
}
|
|
6030
|
+
var GRADLE_CONFIGS = "implementation|api|compileOnly|runtimeOnly|compileOnlyApi|testImplementation|testRuntimeOnly|testCompileOnly|annotationProcessor|developmentOnly|runtimeClasspath";
|
|
6031
|
+
function parseGradle(source) {
|
|
6032
|
+
const dependencies = {};
|
|
6033
|
+
const re = new RegExp(`\\b(?:${GRADLE_CONFIGS})\\s*\\(?\\s*(['"])([^'"]+)\\1`, "g");
|
|
6034
|
+
let m;
|
|
6035
|
+
while ((m = re.exec(source)) !== null) {
|
|
6036
|
+
const coord = m[2] ?? "";
|
|
6037
|
+
const parts = coord.split(":");
|
|
6038
|
+
if (parts.length < 2 || !parts[0] || !parts[1]) continue;
|
|
6039
|
+
const key = `${parts[0]}:${parts[1]}`;
|
|
6040
|
+
dependencies[key] = parts.slice(2).join(":");
|
|
6041
|
+
}
|
|
6042
|
+
return { dependencies };
|
|
6043
|
+
}
|
|
6044
|
+
async function discoverJavaService(scanPath, dir) {
|
|
6045
|
+
const pomPath = path14.join(dir, "pom.xml");
|
|
6046
|
+
const gradlePath = path14.join(dir, "build.gradle");
|
|
6047
|
+
const gradleKtsPath = path14.join(dir, "build.gradle.kts");
|
|
6048
|
+
let manifest = null;
|
|
6049
|
+
if (await exists(pomPath)) {
|
|
6050
|
+
manifest = parsePomXml(await fs12.readFile(pomPath, "utf8").catch(() => ""));
|
|
6051
|
+
} else if (await exists(gradlePath)) {
|
|
6052
|
+
manifest = parseGradle(await fs12.readFile(gradlePath, "utf8").catch(() => ""));
|
|
6053
|
+
} else if (await exists(gradleKtsPath)) {
|
|
6054
|
+
manifest = parseGradle(await fs12.readFile(gradleKtsPath, "utf8").catch(() => ""));
|
|
6055
|
+
}
|
|
6056
|
+
if (!manifest) return null;
|
|
6057
|
+
const name = manifest.name?.trim() || path14.basename(dir);
|
|
6058
|
+
const dependencies = manifest.dependencies;
|
|
6059
|
+
const pkg = { name, dependencies };
|
|
6060
|
+
const node = {
|
|
6061
|
+
id: serviceId6(name),
|
|
6062
|
+
type: NodeType10.ServiceNode,
|
|
6063
|
+
name,
|
|
6064
|
+
language: "java",
|
|
6065
|
+
dependencies,
|
|
6066
|
+
repoPath: path14.relative(scanPath, dir)
|
|
6067
|
+
};
|
|
6068
|
+
return { pkg, dir, node };
|
|
6069
|
+
}
|
|
6070
|
+
|
|
6071
|
+
// src/extract/kotlin.ts
|
|
6072
|
+
import { promises as fs13 } from "fs";
|
|
6073
|
+
import path15 from "path";
|
|
6074
|
+
import { NodeType as NodeType11, serviceId as serviceId7 } from "@neat.is/types";
|
|
6075
|
+
var KOTLIN_SOURCE_EXT = ".kt";
|
|
6076
|
+
var JAVA_SOURCE_EXT = ".java";
|
|
6077
|
+
var SOURCE_PROBE_MAX_DEPTH = 8;
|
|
6078
|
+
async function dominantJvmSourceLanguage(dir) {
|
|
6079
|
+
let kt = 0;
|
|
6080
|
+
let java = 0;
|
|
6081
|
+
async function recurse(current, depth) {
|
|
6082
|
+
if (depth > SOURCE_PROBE_MAX_DEPTH) return;
|
|
6083
|
+
const entries = await fs13.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
6084
|
+
for (const entry of entries) {
|
|
6085
|
+
if (entry.isDirectory()) {
|
|
6086
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6087
|
+
await recurse(path15.join(current, entry.name), depth + 1);
|
|
6088
|
+
continue;
|
|
6089
|
+
}
|
|
6090
|
+
if (!entry.isFile()) continue;
|
|
6091
|
+
const ext = path15.extname(entry.name).toLowerCase();
|
|
6092
|
+
if (ext === KOTLIN_SOURCE_EXT) kt++;
|
|
6093
|
+
else if (ext === JAVA_SOURCE_EXT) java++;
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
await recurse(dir, 0);
|
|
6097
|
+
if (kt > 0 && kt >= java) return "kotlin";
|
|
6098
|
+
if (java > 0) return "java";
|
|
6099
|
+
return void 0;
|
|
6100
|
+
}
|
|
6101
|
+
async function readJvmManifest(dir) {
|
|
6102
|
+
const pomPath = path15.join(dir, JAVA_MANIFESTS[0]);
|
|
6103
|
+
const gradlePath = path15.join(dir, JAVA_MANIFESTS[1]);
|
|
6104
|
+
const gradleKtsPath = path15.join(dir, JAVA_MANIFESTS[2]);
|
|
6105
|
+
if (await exists(pomPath)) {
|
|
6106
|
+
return parsePomXml(await fs13.readFile(pomPath, "utf8").catch(() => ""));
|
|
6107
|
+
}
|
|
6108
|
+
if (await exists(gradlePath)) {
|
|
6109
|
+
return parseGradle(await fs13.readFile(gradlePath, "utf8").catch(() => ""));
|
|
6110
|
+
}
|
|
6111
|
+
if (await exists(gradleKtsPath)) {
|
|
6112
|
+
return parseGradle(await fs13.readFile(gradleKtsPath, "utf8").catch(() => ""));
|
|
6113
|
+
}
|
|
6114
|
+
return null;
|
|
6115
|
+
}
|
|
6116
|
+
async function discoverKotlinService(scanPath, dir) {
|
|
6117
|
+
if (!await hasJavaManifest(dir)) return null;
|
|
6118
|
+
if (await dominantJvmSourceLanguage(dir) !== "kotlin") return null;
|
|
6119
|
+
const manifest = await readJvmManifest(dir);
|
|
6120
|
+
if (!manifest) return null;
|
|
6121
|
+
const name = manifest.name?.trim() || path15.basename(dir);
|
|
6122
|
+
const dependencies = manifest.dependencies;
|
|
6123
|
+
const pkg = { name, dependencies };
|
|
6124
|
+
const node = {
|
|
6125
|
+
id: serviceId7(name),
|
|
6126
|
+
type: NodeType11.ServiceNode,
|
|
6127
|
+
name,
|
|
6128
|
+
language: "kotlin",
|
|
6129
|
+
dependencies,
|
|
6130
|
+
repoPath: path15.relative(scanPath, dir)
|
|
6131
|
+
};
|
|
6132
|
+
return { pkg, dir, node };
|
|
6133
|
+
}
|
|
6134
|
+
|
|
6135
|
+
// src/extract/rust.ts
|
|
6136
|
+
import { promises as fs14 } from "fs";
|
|
6137
|
+
import path16 from "path";
|
|
6138
|
+
import { parse as parseToml2 } from "smol-toml";
|
|
6139
|
+
import { NodeType as NodeType12, serviceId as serviceId8 } from "@neat.is/types";
|
|
6140
|
+
var CARGO_MANIFEST = "Cargo.toml";
|
|
6141
|
+
async function hasRustManifest(dir) {
|
|
6142
|
+
return exists(path16.join(dir, CARGO_MANIFEST));
|
|
6143
|
+
}
|
|
6144
|
+
function parseCargoToml(source) {
|
|
6145
|
+
const dependencies = {};
|
|
6146
|
+
let name;
|
|
6147
|
+
let parsed;
|
|
6148
|
+
try {
|
|
6149
|
+
parsed = parseToml2(source);
|
|
6150
|
+
} catch {
|
|
6151
|
+
return { dependencies };
|
|
6152
|
+
}
|
|
6153
|
+
if (typeof parsed.package?.name === "string") name = parsed.package.name;
|
|
6154
|
+
for (const [crate, spec] of Object.entries(parsed.dependencies ?? {})) {
|
|
6155
|
+
if (typeof spec === "string") {
|
|
6156
|
+
dependencies[crate] = spec;
|
|
6157
|
+
} else if (spec && typeof spec === "object") {
|
|
6158
|
+
const version = spec.version;
|
|
6159
|
+
dependencies[crate] = typeof version === "string" ? version : "";
|
|
6160
|
+
} else {
|
|
6161
|
+
dependencies[crate] = "";
|
|
6162
|
+
}
|
|
6163
|
+
}
|
|
6164
|
+
return { name, dependencies };
|
|
6165
|
+
}
|
|
6166
|
+
async function discoverRustService(scanPath, dir) {
|
|
6167
|
+
const cargoPath = path16.join(dir, CARGO_MANIFEST);
|
|
6168
|
+
if (!await exists(cargoPath)) return null;
|
|
6169
|
+
const manifest = parseCargoToml(await fs14.readFile(cargoPath, "utf8").catch(() => ""));
|
|
6170
|
+
const name = manifest.name?.trim() || path16.basename(dir);
|
|
6171
|
+
const dependencies = manifest.dependencies;
|
|
6172
|
+
const pkg = { name, dependencies };
|
|
6173
|
+
const node = {
|
|
6174
|
+
id: serviceId8(name),
|
|
6175
|
+
type: NodeType12.ServiceNode,
|
|
6176
|
+
name,
|
|
6177
|
+
language: "rust",
|
|
6178
|
+
dependencies,
|
|
6179
|
+
repoPath: path16.relative(scanPath, dir)
|
|
6180
|
+
};
|
|
6181
|
+
return { pkg, dir, node };
|
|
6182
|
+
}
|
|
6183
|
+
|
|
6184
|
+
// src/extract/cpp.ts
|
|
6185
|
+
import { promises as fs15 } from "fs";
|
|
6186
|
+
import path17 from "path";
|
|
6187
|
+
import { NodeType as NodeType13, serviceId as serviceId9 } from "@neat.is/types";
|
|
6188
|
+
var CMAKE_MANIFEST = "CMakeLists.txt";
|
|
6189
|
+
async function hasCppManifest(dir) {
|
|
6190
|
+
return exists(path17.join(dir, CMAKE_MANIFEST));
|
|
6191
|
+
}
|
|
6192
|
+
function parseCMakeProjectName(source) {
|
|
6193
|
+
const withoutComments = source.replace(/#.*$/gm, "");
|
|
6194
|
+
const m = withoutComments.match(/\bproject\s*\(\s*"?([A-Za-z0-9_.+-]+)"?/i);
|
|
6195
|
+
return m?.[1];
|
|
6196
|
+
}
|
|
6197
|
+
async function discoverCppService(scanPath, dir) {
|
|
6198
|
+
const cmakePath = path17.join(dir, CMAKE_MANIFEST);
|
|
6199
|
+
if (!await exists(cmakePath)) return null;
|
|
6200
|
+
const source = await fs15.readFile(cmakePath, "utf8").catch(() => "");
|
|
6201
|
+
const name = parseCMakeProjectName(source)?.trim() || path17.basename(dir);
|
|
6202
|
+
const dependencies = {};
|
|
6203
|
+
const pkg = { name, dependencies };
|
|
6204
|
+
const node = {
|
|
6205
|
+
id: serviceId9(name),
|
|
6206
|
+
type: NodeType13.ServiceNode,
|
|
6207
|
+
name,
|
|
6208
|
+
language: "cpp",
|
|
6209
|
+
dependencies,
|
|
6210
|
+
repoPath: path17.relative(scanPath, dir)
|
|
6211
|
+
};
|
|
6212
|
+
return { pkg, dir, node };
|
|
6213
|
+
}
|
|
6214
|
+
|
|
6215
|
+
// src/extract/dockerfile-service.ts
|
|
6216
|
+
import { promises as fs16 } from "fs";
|
|
6217
|
+
import path18 from "path";
|
|
6218
|
+
import { NodeType as NodeType14, serviceId as serviceId10 } from "@neat.is/types";
|
|
6219
|
+
function languageForSourceExt(ext) {
|
|
6220
|
+
switch (ext) {
|
|
6221
|
+
case ".py":
|
|
6222
|
+
return "python";
|
|
6223
|
+
case ".go":
|
|
6224
|
+
return "go";
|
|
6225
|
+
case ".rb":
|
|
6226
|
+
return "ruby";
|
|
6227
|
+
case ".php":
|
|
6228
|
+
return "php";
|
|
6229
|
+
case ".cs":
|
|
6230
|
+
return "csharp";
|
|
6231
|
+
case ".java":
|
|
6232
|
+
return "java";
|
|
6233
|
+
case ".kt":
|
|
6234
|
+
return "kotlin";
|
|
6235
|
+
case ".rs":
|
|
6236
|
+
return "rust";
|
|
6237
|
+
case ".cpp":
|
|
6238
|
+
case ".cc":
|
|
6239
|
+
case ".cxx":
|
|
6240
|
+
case ".c++":
|
|
6241
|
+
case ".hpp":
|
|
6242
|
+
case ".hh":
|
|
6243
|
+
case ".hxx":
|
|
6244
|
+
case ".h++":
|
|
6245
|
+
return "cpp";
|
|
6246
|
+
case ".ts":
|
|
6247
|
+
case ".tsx":
|
|
6248
|
+
return "typescript";
|
|
6249
|
+
case ".js":
|
|
6250
|
+
case ".mjs":
|
|
6251
|
+
case ".cjs":
|
|
6252
|
+
return "javascript";
|
|
6253
|
+
default:
|
|
6254
|
+
return null;
|
|
6255
|
+
}
|
|
6256
|
+
}
|
|
6257
|
+
var LANGUAGE_PRECEDENCE = ["typescript", "javascript", "python", "go", "ruby", "php", "csharp", "java", "kotlin", "rust", "cpp"];
|
|
6258
|
+
async function inferServiceLanguage(dir) {
|
|
6259
|
+
const entries = await fs16.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
6260
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6261
|
+
for (const entry of entries) {
|
|
6262
|
+
if (!entry.isFile()) continue;
|
|
6263
|
+
if (isNeatAuthoredSourceFile(entry.name)) continue;
|
|
6264
|
+
const ext = path18.extname(entry.name).toLowerCase();
|
|
6265
|
+
if (!SERVICE_FILE_EXTENSIONS.has(ext)) continue;
|
|
6266
|
+
const lang = languageForSourceExt(ext);
|
|
6267
|
+
if (!lang) continue;
|
|
6268
|
+
counts.set(lang, (counts.get(lang) ?? 0) + 1);
|
|
6269
|
+
}
|
|
6270
|
+
if (counts.size === 0) return null;
|
|
6271
|
+
let best = null;
|
|
6272
|
+
let bestCount = -1;
|
|
6273
|
+
for (const lang of LANGUAGE_PRECEDENCE) {
|
|
6274
|
+
const c = counts.get(lang) ?? 0;
|
|
6275
|
+
if (c > bestCount) {
|
|
6276
|
+
best = lang;
|
|
6277
|
+
bestCount = c;
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
return best;
|
|
6281
|
+
}
|
|
6282
|
+
async function hasLanguageManifest(dir) {
|
|
6283
|
+
return await exists(path18.join(dir, "package.json")) || await exists(path18.join(dir, "pyproject.toml")) || await exists(path18.join(dir, "requirements.txt")) || await exists(path18.join(dir, "setup.py")) || await exists(path18.join(dir, "go.mod")) || await exists(path18.join(dir, "Gemfile")) || await exists(path18.join(dir, "composer.json")) || // C#'s marker is a glob (`*.csproj` / `*.sln`), so it reads the directory
|
|
6284
|
+
// rather than probing a fixed filename (ADR-196).
|
|
6285
|
+
await hasCsharpProject(dir) || // Java (and its JVM sibling Kotlin, ADR-199) key on the same Maven / Gradle
|
|
6286
|
+
// build manifest (ADR-197), so this one check stands the Dockerfile fallback
|
|
6287
|
+
// down for both.
|
|
6288
|
+
await hasJavaManifest(dir) || // Rust's marker is a fixed `Cargo.toml` (ADR-201).
|
|
6289
|
+
await hasRustManifest(dir) || // C++'s marker is a fixed `CMakeLists.txt` (ADR-202).
|
|
6290
|
+
await hasCppManifest(dir);
|
|
6291
|
+
}
|
|
6292
|
+
async function hasDockerfileDeclaredService(dir) {
|
|
6293
|
+
if (!await exists(path18.join(dir, "Dockerfile"))) return false;
|
|
6294
|
+
return await inferServiceLanguage(dir) !== null;
|
|
6295
|
+
}
|
|
6296
|
+
async function discoverDockerfileService(scanPath, dir) {
|
|
6297
|
+
if (!await exists(path18.join(dir, "Dockerfile"))) return null;
|
|
6298
|
+
if (await hasLanguageManifest(dir)) return null;
|
|
6299
|
+
const language = await inferServiceLanguage(dir);
|
|
6300
|
+
if (!language) return null;
|
|
6301
|
+
const name = path18.basename(dir);
|
|
6302
|
+
const pkg = { name, dependencies: {} };
|
|
6303
|
+
const node = {
|
|
6304
|
+
id: serviceId10(name),
|
|
6305
|
+
type: NodeType14.ServiceNode,
|
|
6306
|
+
name,
|
|
6307
|
+
language,
|
|
6308
|
+
dependencies: {},
|
|
6309
|
+
repoPath: path18.relative(scanPath, dir)
|
|
6310
|
+
};
|
|
6311
|
+
return { pkg, dir, node };
|
|
6312
|
+
}
|
|
6313
|
+
|
|
6314
|
+
// src/extract/owners.ts
|
|
6315
|
+
import { promises as fs17 } from "fs";
|
|
6316
|
+
import path19 from "path";
|
|
5924
6317
|
import { minimatch } from "minimatch";
|
|
5925
6318
|
async function loadCodeowners(scanPath) {
|
|
5926
6319
|
const candidates = [
|
|
5927
|
-
|
|
5928
|
-
|
|
6320
|
+
path19.join(scanPath, "CODEOWNERS"),
|
|
6321
|
+
path19.join(scanPath, ".github", "CODEOWNERS")
|
|
5929
6322
|
];
|
|
5930
6323
|
for (const file of candidates) {
|
|
5931
6324
|
if (await exists(file)) {
|
|
5932
|
-
const raw = await
|
|
6325
|
+
const raw = await fs17.readFile(file, "utf8");
|
|
5933
6326
|
return parseCodeowners(raw);
|
|
5934
6327
|
}
|
|
5935
6328
|
}
|
|
@@ -5947,7 +6340,7 @@ function parseCodeowners(raw) {
|
|
|
5947
6340
|
return { rules };
|
|
5948
6341
|
}
|
|
5949
6342
|
function matchOwner(file, repoPath) {
|
|
5950
|
-
const normalized = repoPath.split(
|
|
6343
|
+
const normalized = repoPath.split(path19.sep).join("/");
|
|
5951
6344
|
for (const rule of file.rules) {
|
|
5952
6345
|
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
5953
6346
|
}
|
|
@@ -5963,7 +6356,7 @@ function matchesPattern(rawPattern, repoPath) {
|
|
|
5963
6356
|
return false;
|
|
5964
6357
|
}
|
|
5965
6358
|
async function readPackageJsonAuthor(serviceDir) {
|
|
5966
|
-
const pkgPath =
|
|
6359
|
+
const pkgPath = path19.join(serviceDir, "package.json");
|
|
5967
6360
|
if (!await exists(pkgPath)) return null;
|
|
5968
6361
|
try {
|
|
5969
6362
|
const pkg = await readJson(pkgPath);
|
|
@@ -6000,33 +6393,36 @@ function workspaceGlobs(pkg) {
|
|
|
6000
6393
|
return null;
|
|
6001
6394
|
}
|
|
6002
6395
|
async function hasPythonManifest(dir) {
|
|
6003
|
-
return await exists(
|
|
6396
|
+
return await exists(path20.join(dir, "pyproject.toml")) || await exists(path20.join(dir, "requirements.txt")) || await exists(path20.join(dir, "setup.py"));
|
|
6004
6397
|
}
|
|
6005
6398
|
async function hasGoManifest(dir) {
|
|
6006
|
-
return exists(
|
|
6399
|
+
return exists(path20.join(dir, "go.mod"));
|
|
6007
6400
|
}
|
|
6008
6401
|
async function hasRubyManifest(dir) {
|
|
6009
|
-
return exists(
|
|
6402
|
+
return exists(path20.join(dir, "Gemfile"));
|
|
6010
6403
|
}
|
|
6011
6404
|
async function hasPhpManifest(dir) {
|
|
6012
|
-
return exists(
|
|
6405
|
+
return exists(path20.join(dir, "composer.json"));
|
|
6406
|
+
}
|
|
6407
|
+
async function hasCsharpManifest(dir) {
|
|
6408
|
+
return hasCsharpProject(dir);
|
|
6013
6409
|
}
|
|
6014
6410
|
async function loadGitignore(scanPath) {
|
|
6015
|
-
const gitignorePath =
|
|
6411
|
+
const gitignorePath = path20.join(scanPath, ".gitignore");
|
|
6016
6412
|
if (!await exists(gitignorePath)) return null;
|
|
6017
|
-
const raw = await
|
|
6413
|
+
const raw = await fs18.readFile(gitignorePath, "utf8");
|
|
6018
6414
|
return ignore().add(raw);
|
|
6019
6415
|
}
|
|
6020
6416
|
async function walkDirs(start, scanPath, options, visit) {
|
|
6021
6417
|
async function recurse(current, depth) {
|
|
6022
6418
|
if (depth > options.maxDepth) return;
|
|
6023
|
-
const entries = await
|
|
6419
|
+
const entries = await fs18.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
6024
6420
|
for (const entry of entries) {
|
|
6025
6421
|
if (!entry.isDirectory()) continue;
|
|
6026
6422
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6027
|
-
const child =
|
|
6423
|
+
const child = path20.join(current, entry.name);
|
|
6028
6424
|
if (options.ig) {
|
|
6029
|
-
const rel =
|
|
6425
|
+
const rel = path20.relative(scanPath, child).split(path20.sep).join("/");
|
|
6030
6426
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
6031
6427
|
}
|
|
6032
6428
|
if (await isPythonVenvDir(child)) continue;
|
|
@@ -6042,8 +6438,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
6042
6438
|
for (const raw of globs) {
|
|
6043
6439
|
const pattern = raw.replace(/^\.\//, "");
|
|
6044
6440
|
if (!pattern.includes("*")) {
|
|
6045
|
-
const candidate =
|
|
6046
|
-
if (await exists(
|
|
6441
|
+
const candidate = path20.join(scanPath, pattern);
|
|
6442
|
+
if (await exists(path20.join(candidate, "package.json"))) found.add(candidate);
|
|
6047
6443
|
continue;
|
|
6048
6444
|
}
|
|
6049
6445
|
const segments = pattern.split("/");
|
|
@@ -6052,13 +6448,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
6052
6448
|
if (seg.includes("*")) break;
|
|
6053
6449
|
staticSegments.push(seg);
|
|
6054
6450
|
}
|
|
6055
|
-
const start =
|
|
6451
|
+
const start = path20.join(scanPath, ...staticSegments);
|
|
6056
6452
|
if (!await exists(start)) continue;
|
|
6057
6453
|
const hasDoubleStar = pattern.includes("**");
|
|
6058
6454
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
6059
6455
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
6060
|
-
const rel =
|
|
6061
|
-
if (minimatch2(rel, pattern) && await exists(
|
|
6456
|
+
const rel = path20.relative(scanPath, dir).split(path20.sep).join("/");
|
|
6457
|
+
if (minimatch2(rel, pattern) && await exists(path20.join(dir, "package.json"))) {
|
|
6062
6458
|
found.add(dir);
|
|
6063
6459
|
}
|
|
6064
6460
|
});
|
|
@@ -6081,31 +6477,31 @@ function detectJsFramework(pkg) {
|
|
|
6081
6477
|
async function detectJsServiceLanguage(dir, pkg) {
|
|
6082
6478
|
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
6083
6479
|
if (deps["typescript"] !== void 0) return "typescript";
|
|
6084
|
-
const entries = await
|
|
6480
|
+
const entries = await fs18.readdir(dir).catch(() => []);
|
|
6085
6481
|
if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
|
|
6086
6482
|
return "javascript";
|
|
6087
6483
|
}
|
|
6088
6484
|
async function discoverNodeService(scanPath, dir) {
|
|
6089
|
-
const pkgPath =
|
|
6485
|
+
const pkgPath = path20.join(dir, "package.json");
|
|
6090
6486
|
if (!await exists(pkgPath)) return null;
|
|
6091
6487
|
let pkg;
|
|
6092
6488
|
try {
|
|
6093
6489
|
pkg = await readJson(pkgPath);
|
|
6094
6490
|
} catch (err) {
|
|
6095
|
-
recordExtractionError("services",
|
|
6491
|
+
recordExtractionError("services", path20.relative(scanPath, pkgPath), err);
|
|
6096
6492
|
return null;
|
|
6097
6493
|
}
|
|
6098
6494
|
if (!pkg.name) return null;
|
|
6099
6495
|
const framework = detectJsFramework(pkg);
|
|
6100
6496
|
const language = await detectJsServiceLanguage(dir, pkg);
|
|
6101
6497
|
const node = {
|
|
6102
|
-
id:
|
|
6103
|
-
type:
|
|
6498
|
+
id: serviceId11(pkg.name),
|
|
6499
|
+
type: NodeType15.ServiceNode,
|
|
6104
6500
|
name: pkg.name,
|
|
6105
6501
|
language,
|
|
6106
6502
|
version: pkg.version,
|
|
6107
6503
|
dependencies: pkg.dependencies ?? {},
|
|
6108
|
-
repoPath:
|
|
6504
|
+
repoPath: path20.relative(scanPath, dir),
|
|
6109
6505
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
|
|
6110
6506
|
...framework ? { framework } : {}
|
|
6111
6507
|
};
|
|
@@ -6116,18 +6512,18 @@ async function discoverPyService(scanPath, dir) {
|
|
|
6116
6512
|
if (!py) return null;
|
|
6117
6513
|
const pkg = pythonToPackage(py);
|
|
6118
6514
|
const node = {
|
|
6119
|
-
id:
|
|
6120
|
-
type:
|
|
6515
|
+
id: serviceId11(py.name),
|
|
6516
|
+
type: NodeType15.ServiceNode,
|
|
6121
6517
|
name: py.name,
|
|
6122
6518
|
language: "python",
|
|
6123
6519
|
version: py.version,
|
|
6124
6520
|
dependencies: py.dependencies,
|
|
6125
|
-
repoPath:
|
|
6521
|
+
repoPath: path20.relative(scanPath, dir)
|
|
6126
6522
|
};
|
|
6127
6523
|
return { pkg, dir, node };
|
|
6128
6524
|
}
|
|
6129
6525
|
async function discoverServices(scanPath) {
|
|
6130
|
-
const rootPkgPath =
|
|
6526
|
+
const rootPkgPath = path20.join(scanPath, "package.json");
|
|
6131
6527
|
let rootPkg = null;
|
|
6132
6528
|
if (await exists(rootPkgPath)) {
|
|
6133
6529
|
try {
|
|
@@ -6135,7 +6531,7 @@ async function discoverServices(scanPath) {
|
|
|
6135
6531
|
} catch (err) {
|
|
6136
6532
|
recordExtractionError(
|
|
6137
6533
|
"services workspaces",
|
|
6138
|
-
|
|
6534
|
+
path20.relative(scanPath, rootPkgPath),
|
|
6139
6535
|
err
|
|
6140
6536
|
);
|
|
6141
6537
|
}
|
|
@@ -6147,7 +6543,7 @@ async function discoverServices(scanPath) {
|
|
|
6147
6543
|
} else {
|
|
6148
6544
|
if (rootPkg && rootPkg.name) {
|
|
6149
6545
|
candidateDirs.push(scanPath);
|
|
6150
|
-
} else if (await hasPythonManifest(scanPath) || await hasGoManifest(scanPath) || await hasRubyManifest(scanPath) || await hasPhpManifest(scanPath)) {
|
|
6546
|
+
} else if (await hasPythonManifest(scanPath) || await hasGoManifest(scanPath) || await hasRubyManifest(scanPath) || await hasPhpManifest(scanPath) || await hasCsharpManifest(scanPath) || await hasJavaManifest(scanPath) || await hasRustManifest(scanPath) || await hasCppManifest(scanPath)) {
|
|
6151
6547
|
candidateDirs.push(scanPath);
|
|
6152
6548
|
}
|
|
6153
6549
|
const ig = await loadGitignore(scanPath);
|
|
@@ -6156,9 +6552,11 @@ async function discoverServices(scanPath) {
|
|
|
6156
6552
|
scanPath,
|
|
6157
6553
|
{ maxDepth: parseScanDepth(), ig },
|
|
6158
6554
|
async (dir) => {
|
|
6159
|
-
if (await exists(
|
|
6555
|
+
if (await exists(path20.join(dir, "package.json"))) {
|
|
6160
6556
|
candidateDirs.push(dir);
|
|
6161
|
-
} else if (await hasPythonManifest(dir) || await hasGoManifest(dir) || await hasRubyManifest(dir) || await hasPhpManifest(dir)) {
|
|
6557
|
+
} else if (await hasPythonManifest(dir) || await hasGoManifest(dir) || await hasRubyManifest(dir) || await hasPhpManifest(dir) || await hasCsharpManifest(dir) || await hasJavaManifest(dir) || await hasRustManifest(dir) || await hasCppManifest(dir)) {
|
|
6558
|
+
candidateDirs.push(dir);
|
|
6559
|
+
} else if (await hasDockerfileDeclaredService(dir)) {
|
|
6162
6560
|
candidateDirs.push(dir);
|
|
6163
6561
|
}
|
|
6164
6562
|
}
|
|
@@ -6166,14 +6564,24 @@ async function discoverServices(scanPath) {
|
|
|
6166
6564
|
}
|
|
6167
6565
|
candidateDirs.sort();
|
|
6168
6566
|
const seen = /* @__PURE__ */ new Map();
|
|
6567
|
+
const seenDirs = /* @__PURE__ */ new Set();
|
|
6169
6568
|
const out = [];
|
|
6170
6569
|
for (const dir of candidateDirs) {
|
|
6171
|
-
const service = await discoverNodeService(scanPath, dir) ?? await discoverPyService(scanPath, dir) ?? await discoverGoService(scanPath, dir) ?? await discoverRubyService(scanPath, dir) ?? await discoverPhpService(scanPath, dir)
|
|
6570
|
+
const service = await discoverNodeService(scanPath, dir) ?? await discoverPyService(scanPath, dir) ?? await discoverGoService(scanPath, dir) ?? await discoverRubyService(scanPath, dir) ?? await discoverPhpService(scanPath, dir) ?? await discoverCsharpService(scanPath, dir) ?? // Kotlin runs ahead of Java: both mark a service with the same Maven/Gradle
|
|
6571
|
+
// manifest, so Kotlin claims a JVM-manifest dir whose source is `.kt` (ADR-199)
|
|
6572
|
+
// and a `.java`-dominant one falls through to the Java reader unchanged.
|
|
6573
|
+
await discoverKotlinService(scanPath, dir) ?? await discoverJavaService(scanPath, dir) ?? // Rust keys on a `Cargo.toml`, a manifest no other language shares (ADR-201),
|
|
6574
|
+
// so its order among the distinct-manifest discoverers doesn't matter.
|
|
6575
|
+
await discoverRustService(scanPath, dir) ?? // C++ keys on a `CMakeLists.txt`, likewise a manifest no other language shares
|
|
6576
|
+
// (ADR-202), so it too sits anywhere among the distinct-manifest discoverers.
|
|
6577
|
+
await discoverCppService(scanPath, dir) ?? await discoverDockerfileService(scanPath, dir);
|
|
6172
6578
|
if (!service) continue;
|
|
6579
|
+
if (seenDirs.has(service.dir)) continue;
|
|
6580
|
+
seenDirs.add(service.dir);
|
|
6173
6581
|
const existingDir = seen.get(service.node.name);
|
|
6174
6582
|
if (existingDir !== void 0) {
|
|
6175
|
-
const a =
|
|
6176
|
-
const b =
|
|
6583
|
+
const a = path20.relative(scanPath, existingDir) || ".";
|
|
6584
|
+
const b = path20.relative(scanPath, dir) || ".";
|
|
6177
6585
|
console.warn(
|
|
6178
6586
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
6179
6587
|
);
|
|
@@ -6187,6 +6595,17 @@ async function discoverServices(scanPath) {
|
|
|
6187
6595
|
const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
|
|
6188
6596
|
if (owner !== void 0) service.node.owner = owner;
|
|
6189
6597
|
}
|
|
6598
|
+
const resolvedDirs = out.map((s) => path20.resolve(s.dir));
|
|
6599
|
+
for (let i = 0; i < out.length; i++) {
|
|
6600
|
+
const self = resolvedDirs[i];
|
|
6601
|
+
const nested = [];
|
|
6602
|
+
for (let j = 0; j < out.length; j++) {
|
|
6603
|
+
if (i === j) continue;
|
|
6604
|
+
const other = resolvedDirs[j];
|
|
6605
|
+
if (other.startsWith(self + path20.sep)) nested.push(other);
|
|
6606
|
+
}
|
|
6607
|
+
out[i].excludeDirs = nested;
|
|
6608
|
+
}
|
|
6190
6609
|
return out;
|
|
6191
6610
|
}
|
|
6192
6611
|
function addServiceNodes(graph, services) {
|
|
@@ -6209,20 +6628,20 @@ function addServiceNodes(graph, services) {
|
|
|
6209
6628
|
}
|
|
6210
6629
|
|
|
6211
6630
|
// src/extract/aliases.ts
|
|
6212
|
-
import
|
|
6213
|
-
import { promises as
|
|
6631
|
+
import path21 from "path";
|
|
6632
|
+
import { promises as fs19 } from "fs";
|
|
6214
6633
|
import { parseAllDocuments } from "yaml";
|
|
6215
|
-
import { NodeType as
|
|
6634
|
+
import { NodeType as NodeType16 } from "@neat.is/types";
|
|
6216
6635
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
6217
6636
|
"Service",
|
|
6218
6637
|
"Deployment",
|
|
6219
6638
|
"StatefulSet",
|
|
6220
6639
|
"DaemonSet"
|
|
6221
6640
|
]);
|
|
6222
|
-
function addAliases(graph,
|
|
6223
|
-
if (!graph.hasNode(
|
|
6224
|
-
const node = graph.getNodeAttributes(
|
|
6225
|
-
if (node.type !==
|
|
6641
|
+
function addAliases(graph, serviceId15, candidates) {
|
|
6642
|
+
if (!graph.hasNode(serviceId15)) return;
|
|
6643
|
+
const node = graph.getNodeAttributes(serviceId15);
|
|
6644
|
+
if (node.type !== NodeType16.ServiceNode) return;
|
|
6226
6645
|
const set = new Set(node.aliases ?? []);
|
|
6227
6646
|
for (const c of candidates) {
|
|
6228
6647
|
if (!c) continue;
|
|
@@ -6231,20 +6650,20 @@ function addAliases(graph, serviceId9, candidates) {
|
|
|
6231
6650
|
}
|
|
6232
6651
|
if (set.size === 0) return;
|
|
6233
6652
|
const updated = { ...node, aliases: [...set].sort() };
|
|
6234
|
-
graph.replaceNodeAttributes(
|
|
6653
|
+
graph.replaceNodeAttributes(serviceId15, updated);
|
|
6235
6654
|
}
|
|
6236
6655
|
function indexServicesByName(services) {
|
|
6237
6656
|
const map = /* @__PURE__ */ new Map();
|
|
6238
6657
|
for (const s of services) {
|
|
6239
6658
|
map.set(s.node.name, s.node.id);
|
|
6240
|
-
map.set(
|
|
6659
|
+
map.set(path21.basename(s.dir), s.node.id);
|
|
6241
6660
|
}
|
|
6242
6661
|
return map;
|
|
6243
6662
|
}
|
|
6244
6663
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
6245
6664
|
let composePath = null;
|
|
6246
6665
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
6247
|
-
const abs =
|
|
6666
|
+
const abs = path21.join(scanPath, name);
|
|
6248
6667
|
if (await exists(abs)) {
|
|
6249
6668
|
composePath = abs;
|
|
6250
6669
|
break;
|
|
@@ -6257,19 +6676,19 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
6257
6676
|
} catch (err) {
|
|
6258
6677
|
recordExtractionError(
|
|
6259
6678
|
"aliases compose",
|
|
6260
|
-
|
|
6679
|
+
path21.relative(scanPath, composePath),
|
|
6261
6680
|
err
|
|
6262
6681
|
);
|
|
6263
6682
|
return;
|
|
6264
6683
|
}
|
|
6265
6684
|
if (!compose?.services) return;
|
|
6266
6685
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
6267
|
-
const
|
|
6268
|
-
if (!
|
|
6686
|
+
const serviceId15 = serviceIndex.get(composeName);
|
|
6687
|
+
if (!serviceId15) continue;
|
|
6269
6688
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
6270
6689
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
6271
6690
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
6272
|
-
addAliases(graph,
|
|
6691
|
+
addAliases(graph, serviceId15, aliases);
|
|
6273
6692
|
}
|
|
6274
6693
|
}
|
|
6275
6694
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -6300,11 +6719,11 @@ function parseDockerfileLabels(content) {
|
|
|
6300
6719
|
}
|
|
6301
6720
|
async function collectDockerfileAliases(graph, services) {
|
|
6302
6721
|
for (const service of services) {
|
|
6303
|
-
const dockerfilePath =
|
|
6722
|
+
const dockerfilePath = path21.join(service.dir, "Dockerfile");
|
|
6304
6723
|
if (!await exists(dockerfilePath)) continue;
|
|
6305
6724
|
let content;
|
|
6306
6725
|
try {
|
|
6307
|
-
content = await
|
|
6726
|
+
content = await fs19.readFile(dockerfilePath, "utf8");
|
|
6308
6727
|
} catch (err) {
|
|
6309
6728
|
recordExtractionError("aliases dockerfile", dockerfilePath, err);
|
|
6310
6729
|
continue;
|
|
@@ -6316,15 +6735,15 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
6316
6735
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
6317
6736
|
if (depth > max) return [];
|
|
6318
6737
|
const out = [];
|
|
6319
|
-
const entries = await
|
|
6738
|
+
const entries = await fs19.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
6320
6739
|
for (const entry of entries) {
|
|
6321
6740
|
if (entry.isDirectory()) {
|
|
6322
6741
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6323
|
-
const child =
|
|
6742
|
+
const child = path21.join(start, entry.name);
|
|
6324
6743
|
if (await isPythonVenvDir(child)) continue;
|
|
6325
6744
|
out.push(...await walkYamlFiles(child, depth + 1, max));
|
|
6326
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
6327
|
-
out.push(
|
|
6745
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path21.extname(entry.name))) {
|
|
6746
|
+
out.push(path21.join(start, entry.name));
|
|
6328
6747
|
}
|
|
6329
6748
|
}
|
|
6330
6749
|
return out;
|
|
@@ -6351,7 +6770,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
6351
6770
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
6352
6771
|
const files = await walkYamlFiles(scanPath);
|
|
6353
6772
|
for (const file of files) {
|
|
6354
|
-
const content = await
|
|
6773
|
+
const content = await fs19.readFile(file, "utf8");
|
|
6355
6774
|
let docs;
|
|
6356
6775
|
try {
|
|
6357
6776
|
docs = parseAllDocuments(content).map((d) => d.toJSON());
|
|
@@ -6375,14 +6794,14 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
6375
6794
|
}
|
|
6376
6795
|
|
|
6377
6796
|
// src/extract/files.ts
|
|
6378
|
-
import
|
|
6797
|
+
import path22 from "path";
|
|
6379
6798
|
async function addFiles(graph, services) {
|
|
6380
6799
|
let nodesAdded = 0;
|
|
6381
6800
|
let edgesAdded = 0;
|
|
6382
6801
|
for (const service of services) {
|
|
6383
|
-
const filePaths = await walkSourceFiles(service.dir);
|
|
6802
|
+
const filePaths = await walkSourceFiles(service.dir, service.excludeDirs);
|
|
6384
6803
|
for (const filePath of filePaths) {
|
|
6385
|
-
const relPath = toPosix(
|
|
6804
|
+
const relPath = toPosix(path22.relative(service.dir, filePath));
|
|
6386
6805
|
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
6387
6806
|
graph,
|
|
6388
6807
|
service.pkg.name,
|
|
@@ -6397,13 +6816,22 @@ async function addFiles(graph, services) {
|
|
|
6397
6816
|
}
|
|
6398
6817
|
|
|
6399
6818
|
// src/extract/symbols.ts
|
|
6400
|
-
import
|
|
6819
|
+
import path23 from "path";
|
|
6401
6820
|
import Parser3 from "tree-sitter";
|
|
6402
6821
|
import JavaScript3 from "tree-sitter-javascript";
|
|
6403
6822
|
import TypeScript from "tree-sitter-typescript";
|
|
6823
|
+
import Python3 from "tree-sitter-python";
|
|
6824
|
+
import Go3 from "tree-sitter-go";
|
|
6825
|
+
import Ruby2 from "tree-sitter-ruby";
|
|
6826
|
+
import Php2 from "tree-sitter-php";
|
|
6827
|
+
import CSharp from "tree-sitter-c-sharp";
|
|
6828
|
+
import Java from "tree-sitter-java";
|
|
6829
|
+
import Kotlin from "tree-sitter-kotlin";
|
|
6830
|
+
import Rust from "tree-sitter-rust";
|
|
6831
|
+
import Cpp from "tree-sitter-cpp";
|
|
6404
6832
|
import {
|
|
6405
6833
|
EdgeType as EdgeType7,
|
|
6406
|
-
NodeType as
|
|
6834
|
+
NodeType as NodeType17,
|
|
6407
6835
|
Provenance as Provenance7,
|
|
6408
6836
|
confidenceForExtracted as confidenceForExtracted4,
|
|
6409
6837
|
extractedEdgeId as extractedEdgeId6,
|
|
@@ -6418,6 +6846,33 @@ var GRAMMAR_BY_EXT = {
|
|
|
6418
6846
|
".mjs": JavaScript3,
|
|
6419
6847
|
".cjs": JavaScript3
|
|
6420
6848
|
};
|
|
6849
|
+
var SYMBOL_GRAMMAR_BY_EXT = {
|
|
6850
|
+
...GRAMMAR_BY_EXT,
|
|
6851
|
+
".py": Python3,
|
|
6852
|
+
".go": Go3,
|
|
6853
|
+
".rb": Ruby2,
|
|
6854
|
+
".php": Php2.php_only,
|
|
6855
|
+
".cs": CSharp,
|
|
6856
|
+
".java": Java,
|
|
6857
|
+
".kt": Kotlin,
|
|
6858
|
+
".rs": Rust,
|
|
6859
|
+
// C++ (ADR-202) — only the UNAMBIGUOUS extensions. `.cpp` / `.cc` / `.cxx` /
|
|
6860
|
+
// `.c++` are implementation files; `.hpp` / `.hh` / `.hxx` / `.h++` are C++-only
|
|
6861
|
+
// headers. `.h` and `.c` are deliberately absent: they are shared with C (a
|
|
6862
|
+
// language NEAT does not add), and this map is global — the symbol grain
|
|
6863
|
+
// dispatches on extension, not on the owning service's language — so claiming
|
|
6864
|
+
// `.h` would parse every pure-C header in any repo with the C++ grammar and
|
|
6865
|
+
// mislabel it. The otel-demo `currency` target's executable code lives in a
|
|
6866
|
+
// `.cpp`, so nothing is lost.
|
|
6867
|
+
".cpp": Cpp,
|
|
6868
|
+
".cc": Cpp,
|
|
6869
|
+
".cxx": Cpp,
|
|
6870
|
+
".c++": Cpp,
|
|
6871
|
+
".hpp": Cpp,
|
|
6872
|
+
".hh": Cpp,
|
|
6873
|
+
".hxx": Cpp,
|
|
6874
|
+
".h++": Cpp
|
|
6875
|
+
};
|
|
6421
6876
|
function parseSource3(parser, source) {
|
|
6422
6877
|
return parser.parse(
|
|
6423
6878
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
|
|
@@ -6427,7 +6882,479 @@ function methodName(node) {
|
|
|
6427
6882
|
const name = node.childForFieldName("name");
|
|
6428
6883
|
return name ? name.text : null;
|
|
6429
6884
|
}
|
|
6430
|
-
function collectSymbolDefs(root) {
|
|
6885
|
+
function collectSymbolDefs(root) {
|
|
6886
|
+
const out = [];
|
|
6887
|
+
const push = (kind, qualname, node) => {
|
|
6888
|
+
out.push({
|
|
6889
|
+
kind,
|
|
6890
|
+
qualname,
|
|
6891
|
+
startLine: node.startPosition.row + 1,
|
|
6892
|
+
endLine: node.endPosition.row + 1
|
|
6893
|
+
});
|
|
6894
|
+
};
|
|
6895
|
+
const visit = (node, classCtx) => {
|
|
6896
|
+
switch (node.type) {
|
|
6897
|
+
case "function_declaration":
|
|
6898
|
+
case "generator_function_declaration": {
|
|
6899
|
+
const name = node.childForFieldName("name")?.text;
|
|
6900
|
+
if (name) push("function", name, node);
|
|
6901
|
+
break;
|
|
6902
|
+
}
|
|
6903
|
+
case "class_declaration":
|
|
6904
|
+
case "abstract_class_declaration":
|
|
6905
|
+
case "class": {
|
|
6906
|
+
const name = node.childForFieldName("name")?.text;
|
|
6907
|
+
if (name) push("class", name, node);
|
|
6908
|
+
const body = node.childForFieldName("body");
|
|
6909
|
+
if (body) {
|
|
6910
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
6911
|
+
const child = body.namedChild(i);
|
|
6912
|
+
if (child) visit(child, name ?? classCtx);
|
|
6913
|
+
}
|
|
6914
|
+
}
|
|
6915
|
+
return;
|
|
6916
|
+
}
|
|
6917
|
+
case "method_definition": {
|
|
6918
|
+
const name = methodName(node);
|
|
6919
|
+
if (name) {
|
|
6920
|
+
const kind = name === "constructor" ? "constructor" : "method";
|
|
6921
|
+
push(kind, classCtx ? `${classCtx}.${name}` : name, node);
|
|
6922
|
+
}
|
|
6923
|
+
break;
|
|
6924
|
+
}
|
|
6925
|
+
case "variable_declarator": {
|
|
6926
|
+
const value = node.childForFieldName("value");
|
|
6927
|
+
if (value && (value.type === "arrow_function" || value.type === "function" || value.type === "function_expression" || value.type === "generator_function")) {
|
|
6928
|
+
const nameNode = node.childForFieldName("name");
|
|
6929
|
+
if (nameNode && nameNode.type === "identifier") {
|
|
6930
|
+
push("function", nameNode.text, node);
|
|
6931
|
+
}
|
|
6932
|
+
}
|
|
6933
|
+
break;
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6936
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6937
|
+
const child = node.namedChild(i);
|
|
6938
|
+
if (child) visit(child, classCtx);
|
|
6939
|
+
}
|
|
6940
|
+
};
|
|
6941
|
+
visit(root, void 0);
|
|
6942
|
+
return out;
|
|
6943
|
+
}
|
|
6944
|
+
function collectPythonSymbolDefs(root) {
|
|
6945
|
+
const out = [];
|
|
6946
|
+
const push = (kind, qualname, node) => {
|
|
6947
|
+
out.push({
|
|
6948
|
+
kind,
|
|
6949
|
+
qualname,
|
|
6950
|
+
startLine: node.startPosition.row + 1,
|
|
6951
|
+
endLine: node.endPosition.row + 1
|
|
6952
|
+
});
|
|
6953
|
+
};
|
|
6954
|
+
const visit = (node, classCtx) => {
|
|
6955
|
+
switch (node.type) {
|
|
6956
|
+
case "function_definition": {
|
|
6957
|
+
const name = node.childForFieldName("name")?.text;
|
|
6958
|
+
if (name) {
|
|
6959
|
+
if (classCtx) {
|
|
6960
|
+
const kind = name === "__init__" ? "constructor" : "method";
|
|
6961
|
+
push(kind, `${classCtx}.${name}`, node);
|
|
6962
|
+
} else {
|
|
6963
|
+
push("function", name, node);
|
|
6964
|
+
}
|
|
6965
|
+
}
|
|
6966
|
+
const body = node.childForFieldName("body");
|
|
6967
|
+
if (body) {
|
|
6968
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
6969
|
+
const child = body.namedChild(i);
|
|
6970
|
+
if (child) visit(child, void 0);
|
|
6971
|
+
}
|
|
6972
|
+
}
|
|
6973
|
+
return;
|
|
6974
|
+
}
|
|
6975
|
+
case "class_definition": {
|
|
6976
|
+
const name = node.childForFieldName("name")?.text;
|
|
6977
|
+
if (name) push("class", name, node);
|
|
6978
|
+
const body = node.childForFieldName("body");
|
|
6979
|
+
if (body) {
|
|
6980
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
6981
|
+
const child = body.namedChild(i);
|
|
6982
|
+
if (child) visit(child, name ?? classCtx);
|
|
6983
|
+
}
|
|
6984
|
+
}
|
|
6985
|
+
return;
|
|
6986
|
+
}
|
|
6987
|
+
case "decorated_definition": {
|
|
6988
|
+
const def = node.childForFieldName("definition");
|
|
6989
|
+
if (def) visit(def, classCtx);
|
|
6990
|
+
return;
|
|
6991
|
+
}
|
|
6992
|
+
}
|
|
6993
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6994
|
+
const child = node.namedChild(i);
|
|
6995
|
+
if (child) visit(child, classCtx);
|
|
6996
|
+
}
|
|
6997
|
+
};
|
|
6998
|
+
visit(root, void 0);
|
|
6999
|
+
return out;
|
|
7000
|
+
}
|
|
7001
|
+
function goReceiverType(node) {
|
|
7002
|
+
const receiver = node.childForFieldName("receiver");
|
|
7003
|
+
if (!receiver) return void 0;
|
|
7004
|
+
for (let i = 0; i < receiver.namedChildCount; i++) {
|
|
7005
|
+
const param = receiver.namedChild(i);
|
|
7006
|
+
if (param?.type !== "parameter_declaration") continue;
|
|
7007
|
+
let typeNode = param.childForFieldName("type");
|
|
7008
|
+
if (typeNode?.type === "pointer_type") typeNode = typeNode.namedChild(0) ?? typeNode;
|
|
7009
|
+
if (typeNode?.type === "generic_type") {
|
|
7010
|
+
typeNode = typeNode.childForFieldName("type") ?? typeNode.namedChild(0) ?? typeNode;
|
|
7011
|
+
}
|
|
7012
|
+
if (typeNode?.type === "type_identifier") return typeNode.text;
|
|
7013
|
+
return typeNode?.text;
|
|
7014
|
+
}
|
|
7015
|
+
return void 0;
|
|
7016
|
+
}
|
|
7017
|
+
function collectGoSymbolDefs(root) {
|
|
7018
|
+
const out = [];
|
|
7019
|
+
const push = (kind, qualname, node) => {
|
|
7020
|
+
out.push({
|
|
7021
|
+
kind,
|
|
7022
|
+
qualname,
|
|
7023
|
+
startLine: node.startPosition.row + 1,
|
|
7024
|
+
endLine: node.endPosition.row + 1
|
|
7025
|
+
});
|
|
7026
|
+
};
|
|
7027
|
+
const visit = (node) => {
|
|
7028
|
+
switch (node.type) {
|
|
7029
|
+
case "function_declaration": {
|
|
7030
|
+
const name = node.childForFieldName("name")?.text;
|
|
7031
|
+
if (name) push("function", name, node);
|
|
7032
|
+
break;
|
|
7033
|
+
}
|
|
7034
|
+
case "method_declaration": {
|
|
7035
|
+
const name = node.childForFieldName("name")?.text;
|
|
7036
|
+
if (name) {
|
|
7037
|
+
const recv = goReceiverType(node);
|
|
7038
|
+
push("method", recv ? `${recv}.${name}` : name, node);
|
|
7039
|
+
}
|
|
7040
|
+
break;
|
|
7041
|
+
}
|
|
7042
|
+
}
|
|
7043
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
7044
|
+
const child = node.namedChild(i);
|
|
7045
|
+
if (child) visit(child);
|
|
7046
|
+
}
|
|
7047
|
+
};
|
|
7048
|
+
visit(root);
|
|
7049
|
+
return out;
|
|
7050
|
+
}
|
|
7051
|
+
function collectRubySymbolDefs(root) {
|
|
7052
|
+
const out = [];
|
|
7053
|
+
const push = (kind, qualname, node) => {
|
|
7054
|
+
out.push({
|
|
7055
|
+
kind,
|
|
7056
|
+
qualname,
|
|
7057
|
+
startLine: node.startPosition.row + 1,
|
|
7058
|
+
endLine: node.endPosition.row + 1
|
|
7059
|
+
});
|
|
7060
|
+
};
|
|
7061
|
+
const nameText = (node) => node.childForFieldName("name")?.text.replace(/::/g, ".");
|
|
7062
|
+
const walkBody = (node, ctx) => {
|
|
7063
|
+
const body = node.childForFieldName("body");
|
|
7064
|
+
if (!body) return;
|
|
7065
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
7066
|
+
const child = body.namedChild(i);
|
|
7067
|
+
if (child) visit(child, ctx);
|
|
7068
|
+
}
|
|
7069
|
+
};
|
|
7070
|
+
const visit = (node, classCtx) => {
|
|
7071
|
+
switch (node.type) {
|
|
7072
|
+
case "method":
|
|
7073
|
+
case "singleton_method": {
|
|
7074
|
+
const name = node.childForFieldName("name")?.text;
|
|
7075
|
+
if (name) {
|
|
7076
|
+
if (classCtx) {
|
|
7077
|
+
const kind = node.type === "method" && name === "initialize" ? "constructor" : "method";
|
|
7078
|
+
push(kind, `${classCtx}.${name}`, node);
|
|
7079
|
+
} else {
|
|
7080
|
+
push("function", name, node);
|
|
7081
|
+
}
|
|
7082
|
+
}
|
|
7083
|
+
walkBody(node, void 0);
|
|
7084
|
+
return;
|
|
7085
|
+
}
|
|
7086
|
+
case "class":
|
|
7087
|
+
case "module": {
|
|
7088
|
+
const name = nameText(node);
|
|
7089
|
+
if (name) push("class", classCtx ? `${classCtx}.${name}` : name, node);
|
|
7090
|
+
walkBody(node, name ? classCtx ? `${classCtx}.${name}` : name : classCtx);
|
|
7091
|
+
return;
|
|
7092
|
+
}
|
|
7093
|
+
}
|
|
7094
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
7095
|
+
const child = node.namedChild(i);
|
|
7096
|
+
if (child) visit(child, classCtx);
|
|
7097
|
+
}
|
|
7098
|
+
};
|
|
7099
|
+
visit(root, void 0);
|
|
7100
|
+
return out;
|
|
7101
|
+
}
|
|
7102
|
+
function collectPhpSymbolDefs(root) {
|
|
7103
|
+
const out = [];
|
|
7104
|
+
const push = (kind, qualname, node) => {
|
|
7105
|
+
out.push({
|
|
7106
|
+
kind,
|
|
7107
|
+
qualname,
|
|
7108
|
+
startLine: node.startPosition.row + 1,
|
|
7109
|
+
endLine: node.endPosition.row + 1
|
|
7110
|
+
});
|
|
7111
|
+
};
|
|
7112
|
+
const dot = (s) => s.replace(/\\/g, ".").replace(/::/g, ".").replace(/^\.+/, "");
|
|
7113
|
+
const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
|
|
7114
|
+
const walkChildren = (container, nsCtx, classCtx) => {
|
|
7115
|
+
let ns = nsCtx;
|
|
7116
|
+
for (let i = 0; i < container.namedChildCount; i++) {
|
|
7117
|
+
const child = container.namedChild(i);
|
|
7118
|
+
if (!child) continue;
|
|
7119
|
+
if (child.type === "namespace_definition" && !child.childForFieldName("body")) {
|
|
7120
|
+
const nameNode = child.childForFieldName("name");
|
|
7121
|
+
if (nameNode) ns = dot(nameNode.text);
|
|
7122
|
+
continue;
|
|
7123
|
+
}
|
|
7124
|
+
visit(child, ns, classCtx);
|
|
7125
|
+
}
|
|
7126
|
+
};
|
|
7127
|
+
const walkBody = (node, nsCtx, classCtx) => {
|
|
7128
|
+
const body = node.childForFieldName("body");
|
|
7129
|
+
if (body) walkChildren(body, nsCtx, classCtx);
|
|
7130
|
+
};
|
|
7131
|
+
const visit = (node, nsCtx, classCtx) => {
|
|
7132
|
+
switch (node.type) {
|
|
7133
|
+
case "function_definition": {
|
|
7134
|
+
const name = node.childForFieldName("name")?.text;
|
|
7135
|
+
if (name) push("function", join(nsCtx, name), node);
|
|
7136
|
+
walkBody(node, nsCtx, void 0);
|
|
7137
|
+
return;
|
|
7138
|
+
}
|
|
7139
|
+
case "class_declaration":
|
|
7140
|
+
case "trait_declaration":
|
|
7141
|
+
case "interface_declaration": {
|
|
7142
|
+
const name = node.childForFieldName("name")?.text;
|
|
7143
|
+
const full = name ? join(nsCtx, name) : classCtx;
|
|
7144
|
+
if (name) push("class", full, node);
|
|
7145
|
+
walkBody(node, nsCtx, full);
|
|
7146
|
+
return;
|
|
7147
|
+
}
|
|
7148
|
+
case "method_declaration": {
|
|
7149
|
+
const name = node.childForFieldName("name")?.text;
|
|
7150
|
+
if (name) {
|
|
7151
|
+
const kind = name === "__construct" ? "constructor" : "method";
|
|
7152
|
+
push(kind, join(classCtx ?? nsCtx, name), node);
|
|
7153
|
+
}
|
|
7154
|
+
return;
|
|
7155
|
+
}
|
|
7156
|
+
case "namespace_definition": {
|
|
7157
|
+
const nameNode = node.childForFieldName("name");
|
|
7158
|
+
const ns = nameNode ? dot(nameNode.text) : nsCtx;
|
|
7159
|
+
walkBody(node, ns, classCtx);
|
|
7160
|
+
return;
|
|
7161
|
+
}
|
|
7162
|
+
}
|
|
7163
|
+
walkChildren(node, nsCtx, classCtx);
|
|
7164
|
+
};
|
|
7165
|
+
walkChildren(root, void 0, void 0);
|
|
7166
|
+
return out;
|
|
7167
|
+
}
|
|
7168
|
+
function collectCsharpSymbolDefs(root) {
|
|
7169
|
+
const out = [];
|
|
7170
|
+
const push = (kind, qualname, node) => {
|
|
7171
|
+
out.push({
|
|
7172
|
+
kind,
|
|
7173
|
+
qualname,
|
|
7174
|
+
startLine: node.startPosition.row + 1,
|
|
7175
|
+
endLine: node.endPosition.row + 1
|
|
7176
|
+
});
|
|
7177
|
+
};
|
|
7178
|
+
const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
|
|
7179
|
+
const walkChildren = (container, nsCtx, classCtx) => {
|
|
7180
|
+
let ns = nsCtx;
|
|
7181
|
+
for (let i = 0; i < container.namedChildCount; i++) {
|
|
7182
|
+
const child = container.namedChild(i);
|
|
7183
|
+
if (!child) continue;
|
|
7184
|
+
if (child.type === "file_scoped_namespace_declaration") {
|
|
7185
|
+
const nameNode = child.childForFieldName("name");
|
|
7186
|
+
if (nameNode) ns = join(nsCtx, nameNode.text);
|
|
7187
|
+
continue;
|
|
7188
|
+
}
|
|
7189
|
+
visit(child, ns, classCtx);
|
|
7190
|
+
}
|
|
7191
|
+
};
|
|
7192
|
+
const walkBody = (node, nsCtx, classCtx) => {
|
|
7193
|
+
const body = node.childForFieldName("body");
|
|
7194
|
+
if (body) walkChildren(body, nsCtx, classCtx);
|
|
7195
|
+
};
|
|
7196
|
+
const visit = (node, nsCtx, classCtx) => {
|
|
7197
|
+
switch (node.type) {
|
|
7198
|
+
case "namespace_declaration": {
|
|
7199
|
+
const nameNode = node.childForFieldName("name");
|
|
7200
|
+
const ns = nameNode ? join(nsCtx, nameNode.text) : nsCtx;
|
|
7201
|
+
walkBody(node, ns, classCtx);
|
|
7202
|
+
return;
|
|
7203
|
+
}
|
|
7204
|
+
case "class_declaration":
|
|
7205
|
+
case "interface_declaration":
|
|
7206
|
+
case "struct_declaration":
|
|
7207
|
+
case "record_declaration": {
|
|
7208
|
+
const name = node.childForFieldName("name")?.text;
|
|
7209
|
+
const full = name ? join(classCtx ?? nsCtx, name) : classCtx;
|
|
7210
|
+
if (name) push("class", full, node);
|
|
7211
|
+
walkBody(node, nsCtx, full);
|
|
7212
|
+
return;
|
|
7213
|
+
}
|
|
7214
|
+
case "method_declaration": {
|
|
7215
|
+
const name = node.childForFieldName("name")?.text;
|
|
7216
|
+
if (name) push("method", join(classCtx ?? nsCtx, name), node);
|
|
7217
|
+
return;
|
|
7218
|
+
}
|
|
7219
|
+
case "constructor_declaration": {
|
|
7220
|
+
const name = node.childForFieldName("name")?.text;
|
|
7221
|
+
if (name) push("constructor", join(classCtx ?? nsCtx, name), node);
|
|
7222
|
+
return;
|
|
7223
|
+
}
|
|
7224
|
+
}
|
|
7225
|
+
walkChildren(node, nsCtx, classCtx);
|
|
7226
|
+
};
|
|
7227
|
+
walkChildren(root, void 0, void 0);
|
|
7228
|
+
return out;
|
|
7229
|
+
}
|
|
7230
|
+
function collectJavaSymbolDefs(root) {
|
|
7231
|
+
const out = [];
|
|
7232
|
+
const push = (kind, qualname, node) => {
|
|
7233
|
+
out.push({
|
|
7234
|
+
kind,
|
|
7235
|
+
qualname,
|
|
7236
|
+
startLine: node.startPosition.row + 1,
|
|
7237
|
+
endLine: node.endPosition.row + 1
|
|
7238
|
+
});
|
|
7239
|
+
};
|
|
7240
|
+
const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
|
|
7241
|
+
let pkg;
|
|
7242
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
7243
|
+
const child = root.namedChild(i);
|
|
7244
|
+
if (child?.type === "package_declaration") {
|
|
7245
|
+
pkg = child.namedChild(0)?.text;
|
|
7246
|
+
break;
|
|
7247
|
+
}
|
|
7248
|
+
}
|
|
7249
|
+
const walkChildren = (container, classCtx) => {
|
|
7250
|
+
for (let i = 0; i < container.namedChildCount; i++) {
|
|
7251
|
+
const child = container.namedChild(i);
|
|
7252
|
+
if (child) visit(child, classCtx);
|
|
7253
|
+
}
|
|
7254
|
+
};
|
|
7255
|
+
const walkBody = (node, classCtx) => {
|
|
7256
|
+
const body = node.childForFieldName("body");
|
|
7257
|
+
if (body) walkChildren(body, classCtx);
|
|
7258
|
+
};
|
|
7259
|
+
const visit = (node, classCtx) => {
|
|
7260
|
+
switch (node.type) {
|
|
7261
|
+
case "class_declaration":
|
|
7262
|
+
case "interface_declaration":
|
|
7263
|
+
case "enum_declaration":
|
|
7264
|
+
case "record_declaration": {
|
|
7265
|
+
const name = node.childForFieldName("name")?.text;
|
|
7266
|
+
const full = name ? join(classCtx ?? pkg, name) : classCtx;
|
|
7267
|
+
if (name) push("class", full, node);
|
|
7268
|
+
walkBody(node, full);
|
|
7269
|
+
return;
|
|
7270
|
+
}
|
|
7271
|
+
case "method_declaration": {
|
|
7272
|
+
const name = node.childForFieldName("name")?.text;
|
|
7273
|
+
if (name) push("method", join(classCtx ?? pkg, name), node);
|
|
7274
|
+
return;
|
|
7275
|
+
}
|
|
7276
|
+
case "constructor_declaration": {
|
|
7277
|
+
const name = node.childForFieldName("name")?.text;
|
|
7278
|
+
if (name) push("constructor", join(classCtx ?? pkg, name), node);
|
|
7279
|
+
return;
|
|
7280
|
+
}
|
|
7281
|
+
}
|
|
7282
|
+
walkChildren(node, classCtx);
|
|
7283
|
+
};
|
|
7284
|
+
walkChildren(root, void 0);
|
|
7285
|
+
return out;
|
|
7286
|
+
}
|
|
7287
|
+
function collectKotlinSymbolDefs(root) {
|
|
7288
|
+
const out = [];
|
|
7289
|
+
const push = (kind, qualname, node) => {
|
|
7290
|
+
out.push({
|
|
7291
|
+
kind,
|
|
7292
|
+
qualname,
|
|
7293
|
+
startLine: node.startPosition.row + 1,
|
|
7294
|
+
endLine: node.endPosition.row + 1
|
|
7295
|
+
});
|
|
7296
|
+
};
|
|
7297
|
+
const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
|
|
7298
|
+
const firstChildOfType = (node, types) => {
|
|
7299
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
7300
|
+
const child = node.namedChild(i);
|
|
7301
|
+
if (child && types.includes(child.type)) return child;
|
|
7302
|
+
}
|
|
7303
|
+
return void 0;
|
|
7304
|
+
};
|
|
7305
|
+
const nameOf = (node, ...types) => firstChildOfType(node, types)?.text;
|
|
7306
|
+
const bodyOf = (node) => firstChildOfType(node, ["class_body", "enum_class_body"]);
|
|
7307
|
+
let pkg;
|
|
7308
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
7309
|
+
const child = root.namedChild(i);
|
|
7310
|
+
if (child?.type === "package_header") {
|
|
7311
|
+
pkg = child.namedChild(0)?.text;
|
|
7312
|
+
break;
|
|
7313
|
+
}
|
|
7314
|
+
}
|
|
7315
|
+
const walkChildren = (container, classCtx) => {
|
|
7316
|
+
for (let i = 0; i < container.namedChildCount; i++) {
|
|
7317
|
+
const child = container.namedChild(i);
|
|
7318
|
+
if (child) visit(child, classCtx);
|
|
7319
|
+
}
|
|
7320
|
+
};
|
|
7321
|
+
const visit = (node, classCtx) => {
|
|
7322
|
+
switch (node.type) {
|
|
7323
|
+
case "class_declaration":
|
|
7324
|
+
case "object_declaration": {
|
|
7325
|
+
const name = nameOf(node, "type_identifier");
|
|
7326
|
+
const full = name ? join(classCtx ?? pkg, name) : classCtx;
|
|
7327
|
+
if (name) push("class", full, node);
|
|
7328
|
+
const body = bodyOf(node);
|
|
7329
|
+
if (body) walkChildren(body, full);
|
|
7330
|
+
return;
|
|
7331
|
+
}
|
|
7332
|
+
case "companion_object": {
|
|
7333
|
+
const name = nameOf(node, "type_identifier");
|
|
7334
|
+
const full = name ? join(classCtx ?? pkg, name) : classCtx ?? pkg;
|
|
7335
|
+
const body = bodyOf(node);
|
|
7336
|
+
if (body) walkChildren(body, full);
|
|
7337
|
+
return;
|
|
7338
|
+
}
|
|
7339
|
+
case "function_declaration": {
|
|
7340
|
+
const name = nameOf(node, "simple_identifier");
|
|
7341
|
+
if (name) push(classCtx ? "method" : "function", join(classCtx ?? pkg, name), node);
|
|
7342
|
+
return;
|
|
7343
|
+
}
|
|
7344
|
+
case "secondary_constructor": {
|
|
7345
|
+
if (classCtx) {
|
|
7346
|
+
const typeName = classCtx.slice(classCtx.lastIndexOf(".") + 1);
|
|
7347
|
+
push("constructor", join(classCtx, typeName), node);
|
|
7348
|
+
}
|
|
7349
|
+
return;
|
|
7350
|
+
}
|
|
7351
|
+
}
|
|
7352
|
+
walkChildren(node, classCtx);
|
|
7353
|
+
};
|
|
7354
|
+
walkChildren(root, void 0);
|
|
7355
|
+
return out;
|
|
7356
|
+
}
|
|
7357
|
+
function collectRustSymbolDefs(root) {
|
|
6431
7358
|
const out = [];
|
|
6432
7359
|
const push = (kind, qualname, node) => {
|
|
6433
7360
|
out.push({
|
|
@@ -6437,55 +7364,188 @@ function collectSymbolDefs(root) {
|
|
|
6437
7364
|
endLine: node.endPosition.row + 1
|
|
6438
7365
|
});
|
|
6439
7366
|
};
|
|
6440
|
-
const
|
|
7367
|
+
const join = (prefix, name) => prefix ? `${prefix}::${name}` : name;
|
|
7368
|
+
const walkTypeBody = (body, typeCtx) => {
|
|
7369
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
7370
|
+
const child = body.namedChild(i);
|
|
7371
|
+
if (!child) continue;
|
|
7372
|
+
if (child.type === "function_item" || child.type === "function_signature_item") {
|
|
7373
|
+
const name = child.childForFieldName("name")?.text;
|
|
7374
|
+
if (name) push("method", join(typeCtx, name), child);
|
|
7375
|
+
}
|
|
7376
|
+
}
|
|
7377
|
+
};
|
|
7378
|
+
const walkChildren = (container, modCtx) => {
|
|
7379
|
+
for (let i = 0; i < container.namedChildCount; i++) {
|
|
7380
|
+
const child = container.namedChild(i);
|
|
7381
|
+
if (child) visit(child, modCtx);
|
|
7382
|
+
}
|
|
7383
|
+
};
|
|
7384
|
+
const visit = (node, modCtx) => {
|
|
6441
7385
|
switch (node.type) {
|
|
6442
|
-
case "
|
|
6443
|
-
case "generator_function_declaration": {
|
|
7386
|
+
case "mod_item": {
|
|
6444
7387
|
const name = node.childForFieldName("name")?.text;
|
|
6445
|
-
|
|
6446
|
-
|
|
7388
|
+
const full = name ? join(modCtx, name) : modCtx;
|
|
7389
|
+
const body = node.childForFieldName("body");
|
|
7390
|
+
if (body) walkChildren(body, full);
|
|
7391
|
+
return;
|
|
6447
7392
|
}
|
|
6448
|
-
case "
|
|
6449
|
-
case "
|
|
6450
|
-
case "class": {
|
|
7393
|
+
case "struct_item":
|
|
7394
|
+
case "enum_item": {
|
|
6451
7395
|
const name = node.childForFieldName("name")?.text;
|
|
6452
|
-
if (name) push("class", name, node);
|
|
7396
|
+
if (name) push("class", join(modCtx, name), node);
|
|
7397
|
+
return;
|
|
7398
|
+
}
|
|
7399
|
+
case "trait_item": {
|
|
7400
|
+
const name = node.childForFieldName("name")?.text;
|
|
7401
|
+
const full = name ? join(modCtx, name) : modCtx;
|
|
7402
|
+
if (name) push("class", full, node);
|
|
6453
7403
|
const body = node.childForFieldName("body");
|
|
6454
|
-
if (body)
|
|
6455
|
-
for (let i = 0; i < body.namedChildCount; i++) {
|
|
6456
|
-
const child = body.namedChild(i);
|
|
6457
|
-
if (child) visit(child, name ?? classCtx);
|
|
6458
|
-
}
|
|
6459
|
-
}
|
|
7404
|
+
if (body && full) walkTypeBody(body, full);
|
|
6460
7405
|
return;
|
|
6461
7406
|
}
|
|
6462
|
-
case "
|
|
6463
|
-
const
|
|
6464
|
-
|
|
6465
|
-
|
|
6466
|
-
|
|
6467
|
-
|
|
6468
|
-
break;
|
|
7407
|
+
case "impl_item": {
|
|
7408
|
+
const typeName = node.childForFieldName("type")?.text;
|
|
7409
|
+
const full = typeName ? join(modCtx, typeName) : modCtx;
|
|
7410
|
+
const body = node.childForFieldName("body");
|
|
7411
|
+
if (body && full) walkTypeBody(body, full);
|
|
7412
|
+
return;
|
|
6469
7413
|
}
|
|
6470
|
-
case "
|
|
6471
|
-
const
|
|
6472
|
-
if (
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
6476
|
-
|
|
7414
|
+
case "function_item": {
|
|
7415
|
+
const name = node.childForFieldName("name")?.text;
|
|
7416
|
+
if (name) push("function", join(modCtx, name), node);
|
|
7417
|
+
return;
|
|
7418
|
+
}
|
|
7419
|
+
}
|
|
7420
|
+
walkChildren(node, modCtx);
|
|
7421
|
+
};
|
|
7422
|
+
walkChildren(root, void 0);
|
|
7423
|
+
return out;
|
|
7424
|
+
}
|
|
7425
|
+
function collectCppSymbolDefs(root) {
|
|
7426
|
+
const out = [];
|
|
7427
|
+
const push = (kind, qualname, node) => {
|
|
7428
|
+
out.push({
|
|
7429
|
+
kind,
|
|
7430
|
+
qualname,
|
|
7431
|
+
startLine: node.startPosition.row + 1,
|
|
7432
|
+
endLine: node.endPosition.row + 1
|
|
7433
|
+
});
|
|
7434
|
+
};
|
|
7435
|
+
const join = (prefix, name) => prefix ? `${prefix}::${name}` : name;
|
|
7436
|
+
const DECLARATOR_WRAPPERS = /* @__PURE__ */ new Set([
|
|
7437
|
+
"function_declarator",
|
|
7438
|
+
"pointer_declarator",
|
|
7439
|
+
"reference_declarator",
|
|
7440
|
+
"parenthesized_declarator"
|
|
7441
|
+
]);
|
|
7442
|
+
const functionNameNode = (node) => {
|
|
7443
|
+
let decl = node.childForFieldName("declarator");
|
|
7444
|
+
for (let guard = 0; decl && guard < 8; guard++) {
|
|
7445
|
+
if (decl.type === "function_declarator") {
|
|
7446
|
+
return decl.childForFieldName("declarator") ?? void 0;
|
|
7447
|
+
}
|
|
7448
|
+
let next;
|
|
7449
|
+
for (let i = 0; i < decl.namedChildCount; i++) {
|
|
7450
|
+
const child = decl.namedChild(i);
|
|
7451
|
+
if (child && DECLARATOR_WRAPPERS.has(child.type)) {
|
|
7452
|
+
next = child;
|
|
7453
|
+
break;
|
|
6477
7454
|
}
|
|
6478
|
-
break;
|
|
6479
7455
|
}
|
|
7456
|
+
decl = next;
|
|
6480
7457
|
}
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
7458
|
+
return void 0;
|
|
7459
|
+
};
|
|
7460
|
+
const classify = (nameText, prefix, enclosingClass) => {
|
|
7461
|
+
const qualname = join(prefix, nameText);
|
|
7462
|
+
let className;
|
|
7463
|
+
let memberName;
|
|
7464
|
+
const idx = nameText.lastIndexOf("::");
|
|
7465
|
+
if (idx >= 0) {
|
|
7466
|
+
memberName = nameText.slice(idx + 2);
|
|
7467
|
+
const before = nameText.slice(0, idx);
|
|
7468
|
+
const j = before.lastIndexOf("::");
|
|
7469
|
+
className = j >= 0 ? before.slice(j + 2) : before;
|
|
7470
|
+
} else {
|
|
7471
|
+
memberName = nameText;
|
|
7472
|
+
className = enclosingClass;
|
|
7473
|
+
}
|
|
7474
|
+
let kind;
|
|
7475
|
+
if (className && memberName === className) kind = "constructor";
|
|
7476
|
+
else if (className) kind = "method";
|
|
7477
|
+
else kind = "function";
|
|
7478
|
+
return { kind, qualname };
|
|
7479
|
+
};
|
|
7480
|
+
const walkChildren = (container, prefix, enclosingClass) => {
|
|
7481
|
+
for (let i = 0; i < container.namedChildCount; i++) {
|
|
7482
|
+
const child = container.namedChild(i);
|
|
7483
|
+
if (child) visit(child, prefix, enclosingClass);
|
|
6484
7484
|
}
|
|
6485
7485
|
};
|
|
6486
|
-
visit(
|
|
7486
|
+
const visit = (node, prefix, enclosingClass) => {
|
|
7487
|
+
switch (node.type) {
|
|
7488
|
+
case "namespace_definition": {
|
|
7489
|
+
const name = node.childForFieldName("name")?.text;
|
|
7490
|
+
const newPrefix = name ? join(prefix, name) : prefix;
|
|
7491
|
+
const body = node.childForFieldName("body");
|
|
7492
|
+
if (body) walkChildren(body, newPrefix, void 0);
|
|
7493
|
+
return;
|
|
7494
|
+
}
|
|
7495
|
+
case "class_specifier":
|
|
7496
|
+
case "struct_specifier": {
|
|
7497
|
+
const name = node.childForFieldName("name")?.text;
|
|
7498
|
+
const full = name ? join(prefix, name) : prefix;
|
|
7499
|
+
if (name) push("class", full, node);
|
|
7500
|
+
const body = node.childForFieldName("body");
|
|
7501
|
+
if (body && name) walkChildren(body, full, name);
|
|
7502
|
+
return;
|
|
7503
|
+
}
|
|
7504
|
+
case "function_definition": {
|
|
7505
|
+
const nameNode = functionNameNode(node);
|
|
7506
|
+
if (nameNode) {
|
|
7507
|
+
const { kind, qualname } = classify(nameNode.text, prefix, enclosingClass);
|
|
7508
|
+
push(kind, qualname, node);
|
|
7509
|
+
}
|
|
7510
|
+
return;
|
|
7511
|
+
}
|
|
7512
|
+
}
|
|
7513
|
+
walkChildren(node, prefix, enclosingClass);
|
|
7514
|
+
};
|
|
7515
|
+
walkChildren(root, void 0, void 0);
|
|
6487
7516
|
return out;
|
|
6488
7517
|
}
|
|
7518
|
+
function collectSymbolDefsForExt(ext, root) {
|
|
7519
|
+
switch (ext) {
|
|
7520
|
+
case ".py":
|
|
7521
|
+
return collectPythonSymbolDefs(root);
|
|
7522
|
+
case ".go":
|
|
7523
|
+
return collectGoSymbolDefs(root);
|
|
7524
|
+
case ".rb":
|
|
7525
|
+
return collectRubySymbolDefs(root);
|
|
7526
|
+
case ".php":
|
|
7527
|
+
return collectPhpSymbolDefs(root);
|
|
7528
|
+
case ".cs":
|
|
7529
|
+
return collectCsharpSymbolDefs(root);
|
|
7530
|
+
case ".java":
|
|
7531
|
+
return collectJavaSymbolDefs(root);
|
|
7532
|
+
case ".kt":
|
|
7533
|
+
return collectKotlinSymbolDefs(root);
|
|
7534
|
+
case ".rs":
|
|
7535
|
+
return collectRustSymbolDefs(root);
|
|
7536
|
+
case ".cpp":
|
|
7537
|
+
case ".cc":
|
|
7538
|
+
case ".cxx":
|
|
7539
|
+
case ".c++":
|
|
7540
|
+
case ".hpp":
|
|
7541
|
+
case ".hh":
|
|
7542
|
+
case ".hxx":
|
|
7543
|
+
case ".h++":
|
|
7544
|
+
return collectCppSymbolDefs(root);
|
|
7545
|
+
default:
|
|
7546
|
+
return collectSymbolDefs(root);
|
|
7547
|
+
}
|
|
7548
|
+
}
|
|
6489
7549
|
function disambiguate(defs) {
|
|
6490
7550
|
const counts = /* @__PURE__ */ new Map();
|
|
6491
7551
|
for (const def of defs) counts.set(def.qualname, (counts.get(def.qualname) ?? 0) + 1);
|
|
@@ -6500,7 +7560,7 @@ function disambiguate(defs) {
|
|
|
6500
7560
|
async function addSymbols(graph, services) {
|
|
6501
7561
|
const parsers = /* @__PURE__ */ new Map();
|
|
6502
7562
|
const parserForExt5 = (ext) => {
|
|
6503
|
-
const grammar =
|
|
7563
|
+
const grammar = SYMBOL_GRAMMAR_BY_EXT[ext];
|
|
6504
7564
|
if (!grammar) return null;
|
|
6505
7565
|
let parser = parsers.get(ext);
|
|
6506
7566
|
if (!parser) {
|
|
@@ -6513,15 +7573,16 @@ async function addSymbols(graph, services) {
|
|
|
6513
7573
|
let nodesAdded = 0;
|
|
6514
7574
|
let edgesAdded = 0;
|
|
6515
7575
|
for (const service of services) {
|
|
6516
|
-
const files = await loadSourceFiles(service.dir);
|
|
7576
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
6517
7577
|
for (const file of files) {
|
|
6518
|
-
const
|
|
7578
|
+
const ext = path23.extname(file.path);
|
|
7579
|
+
const parser = parserForExt5(ext);
|
|
6519
7580
|
if (!parser) continue;
|
|
6520
|
-
const relPath = toPosix(
|
|
7581
|
+
const relPath = toPosix(path23.relative(service.dir, file.path));
|
|
6521
7582
|
let defs;
|
|
6522
7583
|
try {
|
|
6523
7584
|
const tree = parseSource3(parser, file.content);
|
|
6524
|
-
defs =
|
|
7585
|
+
defs = collectSymbolDefsForExt(ext, tree.rootNode);
|
|
6525
7586
|
} catch (err) {
|
|
6526
7587
|
recordExtractionError("symbol extraction", file.path, err);
|
|
6527
7588
|
continue;
|
|
@@ -6540,7 +7601,7 @@ async function addSymbols(graph, services) {
|
|
|
6540
7601
|
if (!graph.hasNode(sid)) {
|
|
6541
7602
|
const node = {
|
|
6542
7603
|
id: sid,
|
|
6543
|
-
type:
|
|
7604
|
+
type: NodeType17.SymbolNode,
|
|
6544
7605
|
kind: def.kind,
|
|
6545
7606
|
qualname: def.qualname,
|
|
6546
7607
|
span: { startLine: def.startLine, endLine: def.endLine },
|
|
@@ -6576,11 +7637,11 @@ async function addSymbols(graph, services) {
|
|
|
6576
7637
|
}
|
|
6577
7638
|
|
|
6578
7639
|
// src/extract/symbol-edges.ts
|
|
6579
|
-
import
|
|
7640
|
+
import path24 from "path";
|
|
6580
7641
|
import Parser4 from "tree-sitter";
|
|
6581
7642
|
import {
|
|
6582
7643
|
EdgeType as EdgeType8,
|
|
6583
|
-
NodeType as
|
|
7644
|
+
NodeType as NodeType18,
|
|
6584
7645
|
Provenance as Provenance8,
|
|
6585
7646
|
confidenceForExtracted as confidenceForExtracted5,
|
|
6586
7647
|
extractedEdgeId as extractedEdgeId7,
|
|
@@ -6688,12 +7749,12 @@ async function addSymbolEdges(graph, services) {
|
|
|
6688
7749
|
for (const service of services) {
|
|
6689
7750
|
const serviceName = service.pkg.name;
|
|
6690
7751
|
const tsPaths = await loadTsPathConfig(service.dir);
|
|
6691
|
-
const files = await loadSourceFiles(service.dir);
|
|
7752
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
6692
7753
|
for (const file of files) {
|
|
6693
|
-
const parser = parserForExt5(
|
|
7754
|
+
const parser = parserForExt5(path24.extname(file.path));
|
|
6694
7755
|
if (!parser) continue;
|
|
6695
|
-
const relPath = toPosix(
|
|
6696
|
-
const fileDir =
|
|
7756
|
+
const relPath = toPosix(path24.relative(service.dir, file.path));
|
|
7757
|
+
const fileDir = path24.dirname(file.path);
|
|
6697
7758
|
let root;
|
|
6698
7759
|
try {
|
|
6699
7760
|
root = parseSource3(parser, file.content).rootNode;
|
|
@@ -6735,7 +7796,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
6735
7796
|
const candidate = symbolId3(serviceName, imp.targetRelPath, imp.importedName);
|
|
6736
7797
|
if (graph.hasNode(candidate)) {
|
|
6737
7798
|
const node = graph.getNodeAttributes(candidate);
|
|
6738
|
-
if (node.type ===
|
|
7799
|
+
if (node.type === NodeType18.SymbolNode && node.kind === wantKind) return candidate;
|
|
6739
7800
|
}
|
|
6740
7801
|
}
|
|
6741
7802
|
return null;
|
|
@@ -6827,11 +7888,11 @@ async function addSymbolEdges(graph, services) {
|
|
|
6827
7888
|
}
|
|
6828
7889
|
|
|
6829
7890
|
// src/extract/actions.ts
|
|
6830
|
-
import
|
|
7891
|
+
import path25 from "path";
|
|
6831
7892
|
import Parser5 from "tree-sitter";
|
|
6832
7893
|
import {
|
|
6833
7894
|
EdgeType as EdgeType9,
|
|
6834
|
-
NodeType as
|
|
7895
|
+
NodeType as NodeType19,
|
|
6835
7896
|
Provenance as Provenance9,
|
|
6836
7897
|
confidenceForExtracted as confidenceForExtracted6,
|
|
6837
7898
|
extractedEdgeId as extractedEdgeId8,
|
|
@@ -6978,12 +8039,12 @@ async function addServerActions(graph, services) {
|
|
|
6978
8039
|
};
|
|
6979
8040
|
if (deps["next"] === void 0) continue;
|
|
6980
8041
|
const tsPaths = await loadTsPathConfig(service.dir);
|
|
6981
|
-
const files = await loadSourceFiles(service.dir);
|
|
8042
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
6982
8043
|
for (const file of files) {
|
|
6983
8044
|
if (isTestPath(file.path)) continue;
|
|
6984
|
-
const parser = parserForExt5(
|
|
8045
|
+
const parser = parserForExt5(path25.extname(file.path));
|
|
6985
8046
|
if (!parser) continue;
|
|
6986
|
-
const relPath = toPosix(
|
|
8047
|
+
const relPath = toPosix(path25.relative(service.dir, file.path));
|
|
6987
8048
|
let root;
|
|
6988
8049
|
try {
|
|
6989
8050
|
root = parseSource3(parser, file.content).rootNode;
|
|
@@ -7010,7 +8071,7 @@ async function addServerActions(graph, services) {
|
|
|
7010
8071
|
if (!graph.hasNode(aid)) {
|
|
7011
8072
|
const node = {
|
|
7012
8073
|
id: aid,
|
|
7013
|
-
type:
|
|
8074
|
+
type: NodeType19.ServerActionNode,
|
|
7014
8075
|
name: action.exportName,
|
|
7015
8076
|
service: service.pkg.name,
|
|
7016
8077
|
module: relPath,
|
|
@@ -7044,10 +8105,10 @@ async function addServerActions(graph, services) {
|
|
|
7044
8105
|
}
|
|
7045
8106
|
for (const file of files) {
|
|
7046
8107
|
if (isTestPath(file.path)) continue;
|
|
7047
|
-
const parser = parserForExt5(
|
|
8108
|
+
const parser = parserForExt5(path25.extname(file.path));
|
|
7048
8109
|
if (!parser) continue;
|
|
7049
|
-
const relPath = toPosix(
|
|
7050
|
-
const fileDir =
|
|
8110
|
+
const relPath = toPosix(path25.relative(service.dir, file.path));
|
|
8111
|
+
const fileDir = path25.dirname(file.path);
|
|
7051
8112
|
let root;
|
|
7052
8113
|
try {
|
|
7053
8114
|
root = parseSource3(parser, file.content).rootNode;
|
|
@@ -7099,10 +8160,10 @@ async function addServerActions(graph, services) {
|
|
|
7099
8160
|
}
|
|
7100
8161
|
|
|
7101
8162
|
// src/extract/databases/index.ts
|
|
7102
|
-
import
|
|
8163
|
+
import path33 from "path";
|
|
7103
8164
|
import {
|
|
7104
8165
|
EdgeType as EdgeType10,
|
|
7105
|
-
NodeType as
|
|
8166
|
+
NodeType as NodeType20,
|
|
7106
8167
|
Provenance as Provenance10,
|
|
7107
8168
|
configId,
|
|
7108
8169
|
databaseId as databaseId2,
|
|
@@ -7110,9 +8171,9 @@ import {
|
|
|
7110
8171
|
} from "@neat.is/types";
|
|
7111
8172
|
|
|
7112
8173
|
// src/extract/databases/db-config-yaml.ts
|
|
7113
|
-
import
|
|
8174
|
+
import path26 from "path";
|
|
7114
8175
|
async function parse(serviceDir) {
|
|
7115
|
-
const yamlPath =
|
|
8176
|
+
const yamlPath = path26.join(serviceDir, "db-config.yaml");
|
|
7116
8177
|
if (!await exists(yamlPath)) return [];
|
|
7117
8178
|
const raw = await readYaml(yamlPath);
|
|
7118
8179
|
return [
|
|
@@ -7129,12 +8190,12 @@ async function parse(serviceDir) {
|
|
|
7129
8190
|
var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
7130
8191
|
|
|
7131
8192
|
// src/extract/databases/dotenv.ts
|
|
7132
|
-
import { promises as
|
|
7133
|
-
import
|
|
8193
|
+
import { promises as fs21 } from "fs";
|
|
8194
|
+
import path28 from "path";
|
|
7134
8195
|
|
|
7135
8196
|
// src/extract/databases/shared.ts
|
|
7136
|
-
import { promises as
|
|
7137
|
-
import
|
|
8197
|
+
import { promises as fs20 } from "fs";
|
|
8198
|
+
import path27 from "path";
|
|
7138
8199
|
function schemeToEngine(scheme) {
|
|
7139
8200
|
const s = scheme.toLowerCase().split("+")[0];
|
|
7140
8201
|
switch (s) {
|
|
@@ -7173,18 +8234,18 @@ function parseConnectionString(url) {
|
|
|
7173
8234
|
}
|
|
7174
8235
|
async function readIfExists(filePath) {
|
|
7175
8236
|
try {
|
|
7176
|
-
return await
|
|
8237
|
+
return await fs20.readFile(filePath, "utf8");
|
|
7177
8238
|
} catch {
|
|
7178
8239
|
return null;
|
|
7179
8240
|
}
|
|
7180
8241
|
}
|
|
7181
8242
|
async function resolveEnvVar(serviceDir, name) {
|
|
7182
|
-
const entries = await
|
|
8243
|
+
const entries = await fs20.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
7183
8244
|
const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
|
|
7184
8245
|
const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
|
|
7185
8246
|
envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
|
|
7186
8247
|
for (const fileName of envNames) {
|
|
7187
|
-
const content = await readIfExists(
|
|
8248
|
+
const content = await readIfExists(path27.join(serviceDir, fileName));
|
|
7188
8249
|
if (!content) continue;
|
|
7189
8250
|
for (const line of content.split("\n")) {
|
|
7190
8251
|
const trimmed = line.trim();
|
|
@@ -7203,7 +8264,7 @@ async function resolveEnvVar(serviceDir, name) {
|
|
|
7203
8264
|
}
|
|
7204
8265
|
async function findFirst(serviceDir, candidates) {
|
|
7205
8266
|
for (const rel of candidates) {
|
|
7206
|
-
const abs =
|
|
8267
|
+
const abs = path27.join(serviceDir, rel);
|
|
7207
8268
|
const content = await readIfExists(abs);
|
|
7208
8269
|
if (content !== null) return abs;
|
|
7209
8270
|
}
|
|
@@ -7261,15 +8322,15 @@ function parseDotenvLine(line) {
|
|
|
7261
8322
|
return { key, value };
|
|
7262
8323
|
}
|
|
7263
8324
|
async function parse2(serviceDir) {
|
|
7264
|
-
const entries = await
|
|
8325
|
+
const entries = await fs21.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
7265
8326
|
const configs = [];
|
|
7266
8327
|
const seen = /* @__PURE__ */ new Set();
|
|
7267
8328
|
for (const entry of entries) {
|
|
7268
8329
|
if (!entry.isFile()) continue;
|
|
7269
8330
|
const match = isConfigFile(entry.name);
|
|
7270
8331
|
if (!match.match || match.fileType !== "env") continue;
|
|
7271
|
-
const filePath =
|
|
7272
|
-
const content = await
|
|
8332
|
+
const filePath = path28.join(serviceDir, entry.name);
|
|
8333
|
+
const content = await fs21.readFile(filePath, "utf8");
|
|
7273
8334
|
for (const line of content.split("\n")) {
|
|
7274
8335
|
const parsed = parseDotenvLine(line);
|
|
7275
8336
|
if (!parsed) continue;
|
|
@@ -7287,9 +8348,9 @@ async function parse2(serviceDir) {
|
|
|
7287
8348
|
var dotenvParser = { name: ".env", parse: parse2 };
|
|
7288
8349
|
|
|
7289
8350
|
// src/extract/databases/prisma.ts
|
|
7290
|
-
import
|
|
8351
|
+
import path29 from "path";
|
|
7291
8352
|
async function parse3(serviceDir) {
|
|
7292
|
-
const schemaPath =
|
|
8353
|
+
const schemaPath = path29.join(serviceDir, "prisma", "schema.prisma");
|
|
7293
8354
|
const content = await readIfExists(schemaPath);
|
|
7294
8355
|
if (!content) return [];
|
|
7295
8356
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -7444,10 +8505,10 @@ async function parse5(serviceDir) {
|
|
|
7444
8505
|
var knexParser = { name: "knex", parse: parse5 };
|
|
7445
8506
|
|
|
7446
8507
|
// src/extract/databases/ormconfig.ts
|
|
7447
|
-
import
|
|
8508
|
+
import path30 from "path";
|
|
7448
8509
|
async function parse6(serviceDir) {
|
|
7449
8510
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
7450
|
-
const abs =
|
|
8511
|
+
const abs = path30.join(serviceDir, candidate);
|
|
7451
8512
|
if (!await exists(abs)) continue;
|
|
7452
8513
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
7453
8514
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -7505,9 +8566,9 @@ async function parse7(serviceDir) {
|
|
|
7505
8566
|
var typeormParser = { name: "typeorm", parse: parse7 };
|
|
7506
8567
|
|
|
7507
8568
|
// src/extract/databases/sequelize.ts
|
|
7508
|
-
import
|
|
8569
|
+
import path31 from "path";
|
|
7509
8570
|
async function parse8(serviceDir) {
|
|
7510
|
-
const configPath =
|
|
8571
|
+
const configPath = path31.join(serviceDir, "config", "config.json");
|
|
7511
8572
|
if (!await exists(configPath)) return [];
|
|
7512
8573
|
const raw = await readJson(configPath);
|
|
7513
8574
|
const out = [];
|
|
@@ -7533,7 +8594,7 @@ async function parse8(serviceDir) {
|
|
|
7533
8594
|
var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
7534
8595
|
|
|
7535
8596
|
// src/extract/databases/docker-compose.ts
|
|
7536
|
-
import
|
|
8597
|
+
import path32 from "path";
|
|
7537
8598
|
function portFromService(svc) {
|
|
7538
8599
|
for (const raw of svc.ports ?? []) {
|
|
7539
8600
|
const str = String(raw);
|
|
@@ -7560,7 +8621,7 @@ function databaseFromEnv(svc) {
|
|
|
7560
8621
|
}
|
|
7561
8622
|
async function parse9(serviceDir) {
|
|
7562
8623
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
7563
|
-
const abs =
|
|
8624
|
+
const abs = path32.join(serviceDir, name);
|
|
7564
8625
|
if (!await exists(abs)) continue;
|
|
7565
8626
|
const raw = await readYaml(abs);
|
|
7566
8627
|
if (!raw?.services) return [];
|
|
@@ -7602,7 +8663,7 @@ function compatibleDriversFor(engine) {
|
|
|
7602
8663
|
function toDatabaseNode(config) {
|
|
7603
8664
|
return {
|
|
7604
8665
|
id: databaseId2(config.host),
|
|
7605
|
-
type:
|
|
8666
|
+
type: NodeType20.DatabaseNode,
|
|
7606
8667
|
name: config.database || config.host,
|
|
7607
8668
|
engine: config.engine,
|
|
7608
8669
|
engineVersion: config.engineVersion,
|
|
@@ -7732,7 +8793,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
7732
8793
|
discoveredVia: mergedDiscoveredVia
|
|
7733
8794
|
});
|
|
7734
8795
|
}
|
|
7735
|
-
const relConfigFile = toPosix(
|
|
8796
|
+
const relConfigFile = toPosix(path33.relative(service.dir, config.sourceFile));
|
|
7736
8797
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
7737
8798
|
graph,
|
|
7738
8799
|
service.pkg.name,
|
|
@@ -7741,7 +8802,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
7741
8802
|
);
|
|
7742
8803
|
nodesAdded += fn;
|
|
7743
8804
|
edgesAdded += fe;
|
|
7744
|
-
const evidenceFile = toPosix(
|
|
8805
|
+
const evidenceFile = toPosix(path33.relative(scanPath, config.sourceFile));
|
|
7745
8806
|
const edge = {
|
|
7746
8807
|
id: extractedEdgeId(fileNodeId, dbNode.id, EdgeType10.CONNECTS_TO),
|
|
7747
8808
|
source: fileNodeId,
|
|
@@ -7759,15 +8820,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
7759
8820
|
if (allConfigs.length === 1) {
|
|
7760
8821
|
const primary = allConfigs[0];
|
|
7761
8822
|
service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
|
|
7762
|
-
const relPath =
|
|
8823
|
+
const relPath = path33.relative(scanPath, primary.sourceFile);
|
|
7763
8824
|
const cfgId = configId(relPath);
|
|
7764
8825
|
if (!graph.hasNode(cfgId)) {
|
|
7765
8826
|
const cfgNode = {
|
|
7766
8827
|
id: cfgId,
|
|
7767
|
-
type:
|
|
7768
|
-
name:
|
|
8828
|
+
type: NodeType20.ConfigNode,
|
|
8829
|
+
name: path33.basename(primary.sourceFile),
|
|
7769
8830
|
path: relPath,
|
|
7770
|
-
fileType: isConfigFile(
|
|
8831
|
+
fileType: isConfigFile(path33.basename(primary.sourceFile)).fileType || "config"
|
|
7771
8832
|
};
|
|
7772
8833
|
graph.addNode(cfgId, cfgNode);
|
|
7773
8834
|
nodesAdded++;
|
|
@@ -7807,23 +8868,25 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
7807
8868
|
}
|
|
7808
8869
|
|
|
7809
8870
|
// src/extract/configs.ts
|
|
7810
|
-
import { promises as
|
|
7811
|
-
import
|
|
8871
|
+
import { promises as fs22 } from "fs";
|
|
8872
|
+
import path34 from "path";
|
|
7812
8873
|
import {
|
|
7813
8874
|
EdgeType as EdgeType11,
|
|
7814
|
-
NodeType as
|
|
8875
|
+
NodeType as NodeType21,
|
|
7815
8876
|
Provenance as Provenance11,
|
|
7816
8877
|
configId as configId2,
|
|
7817
8878
|
confidenceForExtracted as confidenceForExtracted8
|
|
7818
8879
|
} from "@neat.is/types";
|
|
7819
|
-
async function walkConfigFiles(dir) {
|
|
8880
|
+
async function walkConfigFiles(dir, excludeDirs = []) {
|
|
8881
|
+
const excluded = new Set(excludeDirs.map((d) => path34.resolve(d)));
|
|
7820
8882
|
const out = [];
|
|
7821
8883
|
async function walk9(current) {
|
|
7822
|
-
const entries = await
|
|
8884
|
+
const entries = await fs22.readdir(current, { withFileTypes: true });
|
|
7823
8885
|
for (const entry of entries) {
|
|
7824
|
-
const full =
|
|
8886
|
+
const full = path34.join(current, entry.name);
|
|
7825
8887
|
if (entry.isDirectory()) {
|
|
7826
8888
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
8889
|
+
if (excluded.has(path34.resolve(full))) continue;
|
|
7827
8890
|
if (await isPythonVenvDir(full)) continue;
|
|
7828
8891
|
await walk9(full);
|
|
7829
8892
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
@@ -7838,21 +8901,21 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
7838
8901
|
let nodesAdded = 0;
|
|
7839
8902
|
let edgesAdded = 0;
|
|
7840
8903
|
for (const service of services) {
|
|
7841
|
-
const configFiles = await walkConfigFiles(service.dir);
|
|
8904
|
+
const configFiles = await walkConfigFiles(service.dir, service.excludeDirs);
|
|
7842
8905
|
for (const file of configFiles) {
|
|
7843
|
-
const relPath =
|
|
8906
|
+
const relPath = path34.relative(scanPath, file);
|
|
7844
8907
|
const node = {
|
|
7845
8908
|
id: configId2(relPath),
|
|
7846
|
-
type:
|
|
7847
|
-
name:
|
|
8909
|
+
type: NodeType21.ConfigNode,
|
|
8910
|
+
name: path34.basename(file),
|
|
7848
8911
|
path: relPath,
|
|
7849
|
-
fileType: isConfigFile(
|
|
8912
|
+
fileType: isConfigFile(path34.basename(file)).fileType
|
|
7850
8913
|
};
|
|
7851
8914
|
if (!graph.hasNode(node.id)) {
|
|
7852
8915
|
graph.addNode(node.id, node);
|
|
7853
8916
|
nodesAdded++;
|
|
7854
8917
|
}
|
|
7855
|
-
const relToService = toPosix(
|
|
8918
|
+
const relToService = toPosix(path34.relative(service.dir, file));
|
|
7856
8919
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
7857
8920
|
graph,
|
|
7858
8921
|
service.pkg.name,
|
|
@@ -7868,7 +8931,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
7868
8931
|
type: EdgeType11.CONFIGURED_BY,
|
|
7869
8932
|
provenance: Provenance11.EXTRACTED,
|
|
7870
8933
|
confidence: confidenceForExtracted8("structural"),
|
|
7871
|
-
evidence: { file: relPath.split(
|
|
8934
|
+
evidence: { file: relPath.split(path34.sep).join("/") }
|
|
7872
8935
|
};
|
|
7873
8936
|
if (!graph.hasEdge(edge.id)) {
|
|
7874
8937
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -7880,11 +8943,11 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
7880
8943
|
}
|
|
7881
8944
|
|
|
7882
8945
|
// src/extract/proto.ts
|
|
7883
|
-
import { promises as
|
|
7884
|
-
import
|
|
8946
|
+
import { promises as fs23 } from "fs";
|
|
8947
|
+
import path35 from "path";
|
|
7885
8948
|
import {
|
|
7886
8949
|
EdgeType as EdgeType12,
|
|
7887
|
-
NodeType as
|
|
8950
|
+
NodeType as NodeType22,
|
|
7888
8951
|
Provenance as Provenance12,
|
|
7889
8952
|
confidenceForExtracted as confidenceForExtracted9,
|
|
7890
8953
|
extractedEdgeId as extractedEdgeId9,
|
|
@@ -7925,17 +8988,19 @@ function grpcMethodsFromProto(content, fqPackage) {
|
|
|
7925
8988
|
}
|
|
7926
8989
|
return out;
|
|
7927
8990
|
}
|
|
7928
|
-
async function walkProtoFiles(dir) {
|
|
8991
|
+
async function walkProtoFiles(dir, excludeDirs = []) {
|
|
8992
|
+
const excluded = new Set(excludeDirs.map((d) => path35.resolve(d)));
|
|
7929
8993
|
const out = [];
|
|
7930
8994
|
async function walk9(current) {
|
|
7931
|
-
const entries = await
|
|
8995
|
+
const entries = await fs23.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
7932
8996
|
for (const entry of entries) {
|
|
7933
|
-
const full =
|
|
8997
|
+
const full = path35.join(current, entry.name);
|
|
7934
8998
|
if (entry.isDirectory()) {
|
|
7935
8999
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
9000
|
+
if (excluded.has(path35.resolve(full))) continue;
|
|
7936
9001
|
if (await isPythonVenvDir(full)) continue;
|
|
7937
9002
|
await walk9(full);
|
|
7938
|
-
} else if (entry.isFile() &&
|
|
9003
|
+
} else if (entry.isFile() && path35.extname(entry.name) === PROTO_EXTENSION) {
|
|
7939
9004
|
out.push(full);
|
|
7940
9005
|
}
|
|
7941
9006
|
}
|
|
@@ -7947,13 +9012,13 @@ async function addGrpcMethods(graph, services) {
|
|
|
7947
9012
|
let nodesAdded = 0;
|
|
7948
9013
|
let edgesAdded = 0;
|
|
7949
9014
|
for (const service of services) {
|
|
7950
|
-
const protoPaths = await walkProtoFiles(service.dir);
|
|
9015
|
+
const protoPaths = await walkProtoFiles(service.dir, service.excludeDirs);
|
|
7951
9016
|
for (const protoPath of protoPaths) {
|
|
7952
9017
|
if (isTestPath(protoPath)) continue;
|
|
7953
|
-
const relFile = toPosix(
|
|
9018
|
+
const relFile = toPosix(path35.relative(service.dir, protoPath));
|
|
7954
9019
|
let content;
|
|
7955
9020
|
try {
|
|
7956
|
-
content = await
|
|
9021
|
+
content = await fs23.readFile(protoPath, "utf8");
|
|
7957
9022
|
} catch (err) {
|
|
7958
9023
|
recordExtractionError("proto extraction", protoPath, err);
|
|
7959
9024
|
continue;
|
|
@@ -7971,7 +9036,7 @@ async function addGrpcMethods(graph, services) {
|
|
|
7971
9036
|
if (!graph.hasNode(mid)) {
|
|
7972
9037
|
const node = {
|
|
7973
9038
|
id: mid,
|
|
7974
|
-
type:
|
|
9039
|
+
type: NodeType22.GrpcMethodNode,
|
|
7975
9040
|
name: `${method.rpcService}/${method.rpcMethod}`,
|
|
7976
9041
|
rpcService: method.rpcService,
|
|
7977
9042
|
rpcMethod: method.rpcMethod,
|
|
@@ -8009,18 +9074,18 @@ async function addGrpcMethods(graph, services) {
|
|
|
8009
9074
|
// src/extract/calls/index.ts
|
|
8010
9075
|
import {
|
|
8011
9076
|
EdgeType as EdgeType15,
|
|
8012
|
-
NodeType as
|
|
9077
|
+
NodeType as NodeType24,
|
|
8013
9078
|
Provenance as Provenance15,
|
|
8014
9079
|
confidenceForExtracted as confidenceForExtracted12,
|
|
8015
9080
|
passesExtractedFloor as passesExtractedFloor3
|
|
8016
9081
|
} from "@neat.is/types";
|
|
8017
9082
|
|
|
8018
9083
|
// src/extract/calls/http.ts
|
|
8019
|
-
import
|
|
9084
|
+
import path36 from "path";
|
|
8020
9085
|
import Parser6 from "tree-sitter";
|
|
8021
9086
|
import JavaScript4 from "tree-sitter-javascript";
|
|
8022
9087
|
import TypeScript2 from "tree-sitter-typescript";
|
|
8023
|
-
import
|
|
9088
|
+
import Python4 from "tree-sitter-python";
|
|
8024
9089
|
import {
|
|
8025
9090
|
EdgeType as EdgeType13,
|
|
8026
9091
|
Provenance as Provenance13,
|
|
@@ -8082,7 +9147,7 @@ var GRAMMAR_BY_EXT2 = {
|
|
|
8082
9147
|
".jsx": JavaScript4,
|
|
8083
9148
|
".mjs": JavaScript4,
|
|
8084
9149
|
".cjs": JavaScript4,
|
|
8085
|
-
".py":
|
|
9150
|
+
".py": Python4
|
|
8086
9151
|
};
|
|
8087
9152
|
function parserForExt(ext, cache) {
|
|
8088
9153
|
const grammar = GRAMMAR_BY_EXT2[ext] ?? JavaScript4;
|
|
@@ -8100,11 +9165,11 @@ async function addHttpCallEdges(graph, services) {
|
|
|
8100
9165
|
let nodesAdded = 0;
|
|
8101
9166
|
let edgesAdded = 0;
|
|
8102
9167
|
for (const service of services) {
|
|
8103
|
-
const files = await loadSourceFiles(service.dir);
|
|
9168
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
8104
9169
|
const seen = /* @__PURE__ */ new Set();
|
|
8105
9170
|
for (const file of files) {
|
|
8106
9171
|
if (isTestPath(file.path)) continue;
|
|
8107
|
-
const parser = parserForExt(
|
|
9172
|
+
const parser = parserForExt(path36.extname(file.path), parserCache);
|
|
8108
9173
|
let sites;
|
|
8109
9174
|
try {
|
|
8110
9175
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -8113,7 +9178,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
8113
9178
|
continue;
|
|
8114
9179
|
}
|
|
8115
9180
|
if (sites.length === 0) continue;
|
|
8116
|
-
const relFile = toPosix(
|
|
9181
|
+
const relFile = toPosix(path36.relative(service.dir, file.path));
|
|
8117
9182
|
for (const site of sites) {
|
|
8118
9183
|
const targetId = hostToNodeId.get(site.host);
|
|
8119
9184
|
if (!targetId || targetId === service.node.id) continue;
|
|
@@ -8166,16 +9231,16 @@ async function addHttpCallEdges(graph, services) {
|
|
|
8166
9231
|
}
|
|
8167
9232
|
|
|
8168
9233
|
// src/extract/calls/route-match.ts
|
|
8169
|
-
import
|
|
9234
|
+
import path37 from "path";
|
|
8170
9235
|
import Parser7 from "tree-sitter";
|
|
8171
9236
|
import JavaScript5 from "tree-sitter-javascript";
|
|
8172
9237
|
import {
|
|
8173
9238
|
EdgeType as EdgeType14,
|
|
8174
|
-
NodeType as
|
|
9239
|
+
NodeType as NodeType23,
|
|
8175
9240
|
Provenance as Provenance14,
|
|
8176
9241
|
confidenceForExtracted as confidenceForExtracted11,
|
|
8177
9242
|
passesExtractedFloor as passesExtractedFloor2,
|
|
8178
|
-
serviceId as
|
|
9243
|
+
serviceId as serviceId12
|
|
8179
9244
|
} from "@neat.is/types";
|
|
8180
9245
|
var PARSE_CHUNK5 = 16384;
|
|
8181
9246
|
function parseSource5(parser, source) {
|
|
@@ -8341,9 +9406,9 @@ function buildRouteIndex(graph) {
|
|
|
8341
9406
|
const index = /* @__PURE__ */ new Map();
|
|
8342
9407
|
graph.forEachNode((_id, attrs) => {
|
|
8343
9408
|
const node = attrs;
|
|
8344
|
-
if (node.type !==
|
|
9409
|
+
if (node.type !== NodeType23.RouteNode) return;
|
|
8345
9410
|
const route = attrs;
|
|
8346
|
-
const owner =
|
|
9411
|
+
const owner = serviceId12(route.service);
|
|
8347
9412
|
const entry = {
|
|
8348
9413
|
method: route.method.toUpperCase(),
|
|
8349
9414
|
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
@@ -8368,11 +9433,11 @@ async function addRouteCallEdges(graph, services) {
|
|
|
8368
9433
|
let nodesAdded = 0;
|
|
8369
9434
|
let edgesAdded = 0;
|
|
8370
9435
|
for (const service of services) {
|
|
8371
|
-
const files = await loadSourceFiles(service.dir);
|
|
9436
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
8372
9437
|
const seen = /* @__PURE__ */ new Set();
|
|
8373
9438
|
for (const file of files) {
|
|
8374
9439
|
if (isTestPath(file.path)) continue;
|
|
8375
|
-
if (!JS_CLIENT_EXTENSIONS.has(
|
|
9440
|
+
if (!JS_CLIENT_EXTENSIONS.has(path37.extname(file.path))) continue;
|
|
8376
9441
|
let sites;
|
|
8377
9442
|
try {
|
|
8378
9443
|
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
@@ -8381,7 +9446,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
8381
9446
|
continue;
|
|
8382
9447
|
}
|
|
8383
9448
|
if (sites.length === 0) continue;
|
|
8384
|
-
const relFile = toPosix(
|
|
9449
|
+
const relFile = toPosix(path37.relative(service.dir, file.path));
|
|
8385
9450
|
for (const site of sites) {
|
|
8386
9451
|
const serverServiceId = hostToNodeId.get(site.host);
|
|
8387
9452
|
if (!serverServiceId || serverServiceId === service.node.id) continue;
|
|
@@ -8441,7 +9506,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
8441
9506
|
}
|
|
8442
9507
|
|
|
8443
9508
|
// src/extract/calls/kafka.ts
|
|
8444
|
-
import
|
|
9509
|
+
import path38 from "path";
|
|
8445
9510
|
import { infraId as infraId2 } from "@neat.is/types";
|
|
8446
9511
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
8447
9512
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -8472,7 +9537,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
8472
9537
|
// tier (ADR-066).
|
|
8473
9538
|
confidenceKind: "verified-call-site",
|
|
8474
9539
|
evidence: {
|
|
8475
|
-
file:
|
|
9540
|
+
file: path38.relative(serviceDir, file.path),
|
|
8476
9541
|
line,
|
|
8477
9542
|
snippet: snippet(file.content, line)
|
|
8478
9543
|
}
|
|
@@ -8484,7 +9549,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
8484
9549
|
}
|
|
8485
9550
|
|
|
8486
9551
|
// src/extract/calls/redis.ts
|
|
8487
|
-
import
|
|
9552
|
+
import path39 from "path";
|
|
8488
9553
|
import { infraId as infraId3 } from "@neat.is/types";
|
|
8489
9554
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
8490
9555
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -8507,7 +9572,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
8507
9572
|
// support tier (ADR-066).
|
|
8508
9573
|
confidenceKind: "url-with-structural-support",
|
|
8509
9574
|
evidence: {
|
|
8510
|
-
file:
|
|
9575
|
+
file: path39.relative(serviceDir, file.path),
|
|
8511
9576
|
line,
|
|
8512
9577
|
snippet: snippet(file.content, line)
|
|
8513
9578
|
}
|
|
@@ -8517,7 +9582,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
8517
9582
|
}
|
|
8518
9583
|
|
|
8519
9584
|
// src/extract/calls/aws.ts
|
|
8520
|
-
import
|
|
9585
|
+
import path40 from "path";
|
|
8521
9586
|
import { infraId as infraId4 } from "@neat.is/types";
|
|
8522
9587
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
8523
9588
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -8551,7 +9616,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
8551
9616
|
// (ADR-066).
|
|
8552
9617
|
confidenceKind: "verified-call-site",
|
|
8553
9618
|
evidence: {
|
|
8554
|
-
file:
|
|
9619
|
+
file: path40.relative(serviceDir, file.path),
|
|
8555
9620
|
line,
|
|
8556
9621
|
snippet: snippet(file.content, line)
|
|
8557
9622
|
}
|
|
@@ -8575,7 +9640,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
8575
9640
|
}
|
|
8576
9641
|
|
|
8577
9642
|
// src/extract/calls/grpc.ts
|
|
8578
|
-
import
|
|
9643
|
+
import path41 from "path";
|
|
8579
9644
|
import { infraId as infraId5 } from "@neat.is/types";
|
|
8580
9645
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
8581
9646
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
@@ -8634,7 +9699,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
8634
9699
|
// tier (ADR-066).
|
|
8635
9700
|
confidenceKind: "verified-call-site",
|
|
8636
9701
|
evidence: {
|
|
8637
|
-
file:
|
|
9702
|
+
file: path41.relative(serviceDir, file.path),
|
|
8638
9703
|
line,
|
|
8639
9704
|
snippet: snippet(file.content, line)
|
|
8640
9705
|
}
|
|
@@ -8644,7 +9709,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
8644
9709
|
}
|
|
8645
9710
|
|
|
8646
9711
|
// src/extract/calls/supabase.ts
|
|
8647
|
-
import
|
|
9712
|
+
import path42 from "path";
|
|
8648
9713
|
import { infraId as infraId6 } from "@neat.is/types";
|
|
8649
9714
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
8650
9715
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
@@ -8703,7 +9768,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
8703
9768
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
8704
9769
|
confidenceKind: "verified-call-site",
|
|
8705
9770
|
evidence: {
|
|
8706
|
-
file:
|
|
9771
|
+
file: path42.relative(serviceDir, file.path),
|
|
8707
9772
|
line,
|
|
8708
9773
|
snippet: snippet(file.content, line)
|
|
8709
9774
|
}
|
|
@@ -8730,7 +9795,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
8730
9795
|
edgeType: "CALLS",
|
|
8731
9796
|
confidenceKind: "verified-call-site",
|
|
8732
9797
|
evidence: {
|
|
8733
|
-
file:
|
|
9798
|
+
file: path42.relative(serviceDir, file.path),
|
|
8734
9799
|
line,
|
|
8735
9800
|
snippet: snippet(file.content, line)
|
|
8736
9801
|
}
|
|
@@ -8741,7 +9806,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
8741
9806
|
}
|
|
8742
9807
|
|
|
8743
9808
|
// src/extract/calls/firestore.ts
|
|
8744
|
-
import
|
|
9809
|
+
import path43 from "path";
|
|
8745
9810
|
import Parser8 from "tree-sitter";
|
|
8746
9811
|
import JavaScript6 from "tree-sitter-javascript";
|
|
8747
9812
|
import { infraId as infraId7 } from "@neat.is/types";
|
|
@@ -8912,7 +9977,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
8912
9977
|
const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
|
|
8913
9978
|
if (!hasClient && !hasAdmin) return [];
|
|
8914
9979
|
const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
|
|
8915
|
-
const tree = parseSource3(parserForExt2(
|
|
9980
|
+
const tree = parseSource3(parserForExt2(path43.extname(file.path)), file.content);
|
|
8916
9981
|
const clientVars = firestoreClientVars(tree.rootNode);
|
|
8917
9982
|
const collLine = /* @__PURE__ */ new Map();
|
|
8918
9983
|
const writes = /* @__PURE__ */ new Map();
|
|
@@ -9016,7 +10081,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
9016
10081
|
...columnSet.size > 0 ? { columns: [...columnSet] } : {},
|
|
9017
10082
|
...sdkWrites ? { sdkWrites } : {},
|
|
9018
10083
|
evidence: {
|
|
9019
|
-
file:
|
|
10084
|
+
file: path43.relative(serviceDir, file.path),
|
|
9020
10085
|
line,
|
|
9021
10086
|
snippet: snippet(file.content, line)
|
|
9022
10087
|
}
|
|
@@ -9026,7 +10091,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
9026
10091
|
}
|
|
9027
10092
|
|
|
9028
10093
|
// src/extract/calls/mongoose.ts
|
|
9029
|
-
import
|
|
10094
|
+
import path44 from "path";
|
|
9030
10095
|
import { infraId as infraId8 } from "@neat.is/types";
|
|
9031
10096
|
var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
|
|
9032
10097
|
var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
|
|
@@ -9177,7 +10242,7 @@ function endpoint(r, file, serviceDir, matchText) {
|
|
|
9177
10242
|
kind: r.kind,
|
|
9178
10243
|
edgeType: "CALLS",
|
|
9179
10244
|
confidenceKind: "verified-call-site",
|
|
9180
|
-
evidence: { file:
|
|
10245
|
+
evidence: { file: path44.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
9181
10246
|
};
|
|
9182
10247
|
}
|
|
9183
10248
|
function mongooseEndpointsFromFile(file, serviceDir) {
|
|
@@ -9267,7 +10332,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
9267
10332
|
const registry = /* @__PURE__ */ new Map();
|
|
9268
10333
|
for (const f of mongooseFiles) {
|
|
9269
10334
|
const fx = fileExportsOf(f.content, pluralizeOn);
|
|
9270
|
-
if (fx) registry.set(toPosix(
|
|
10335
|
+
if (fx) registry.set(toPosix(path44.relative(serviceDir, f.path)), fx);
|
|
9271
10336
|
}
|
|
9272
10337
|
if (registry.size === 0) return [];
|
|
9273
10338
|
const out = [];
|
|
@@ -9278,7 +10343,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
9278
10343
|
const directColl = /* @__PURE__ */ new Map();
|
|
9279
10344
|
const nsExports = /* @__PURE__ */ new Map();
|
|
9280
10345
|
for (const b of bindings) {
|
|
9281
|
-
const resolvedRel = await resolveJsImport(b.specifier,
|
|
10346
|
+
const resolvedRel = await resolveJsImport(b.specifier, path44.dirname(f.path), serviceDir, null);
|
|
9282
10347
|
if (!resolvedRel) continue;
|
|
9283
10348
|
const fx = registry.get(resolvedRel);
|
|
9284
10349
|
if (!fx) continue;
|
|
@@ -9321,15 +10386,15 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
9321
10386
|
}
|
|
9322
10387
|
|
|
9323
10388
|
// src/extract/calls/sqlalchemy.ts
|
|
9324
|
-
import
|
|
10389
|
+
import path45 from "path";
|
|
9325
10390
|
import Parser9 from "tree-sitter";
|
|
9326
|
-
import
|
|
10391
|
+
import Python5 from "tree-sitter-python";
|
|
9327
10392
|
import { infraId as infraId9 } from "@neat.is/types";
|
|
9328
10393
|
var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
|
|
9329
10394
|
var PARSE_CHUNK6 = 16384;
|
|
9330
10395
|
function makePyParser3() {
|
|
9331
10396
|
const p = new Parser9();
|
|
9332
|
-
p.setLanguage(
|
|
10397
|
+
p.setLanguage(Python5);
|
|
9333
10398
|
return p;
|
|
9334
10399
|
}
|
|
9335
10400
|
function parseSource6(parser, source) {
|
|
@@ -9460,7 +10525,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
|
|
|
9460
10525
|
childTable,
|
|
9461
10526
|
parentTable,
|
|
9462
10527
|
evidence: {
|
|
9463
|
-
file:
|
|
10528
|
+
file: path45.relative(serviceDir, file.path),
|
|
9464
10529
|
line,
|
|
9465
10530
|
snippet: snippet(file.content, line)
|
|
9466
10531
|
}
|
|
@@ -9485,7 +10550,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
|
9485
10550
|
confidenceKind: "verified-call-site",
|
|
9486
10551
|
...columns && columns.length > 0 ? { columns } : {},
|
|
9487
10552
|
evidence: {
|
|
9488
|
-
file:
|
|
10553
|
+
file: path45.relative(serviceDir, file.path),
|
|
9489
10554
|
line,
|
|
9490
10555
|
snippet: snippet(file.content, line)
|
|
9491
10556
|
}
|
|
@@ -9592,7 +10657,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
|
9592
10657
|
edgeType: "CALLS",
|
|
9593
10658
|
confidenceKind: "verified-call-site",
|
|
9594
10659
|
evidence: {
|
|
9595
|
-
file:
|
|
10660
|
+
file: path45.relative(serviceDir, file.path),
|
|
9596
10661
|
line,
|
|
9597
10662
|
snippet: snippet(file.content, line)
|
|
9598
10663
|
}
|
|
@@ -9603,15 +10668,15 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
|
9603
10668
|
}
|
|
9604
10669
|
|
|
9605
10670
|
// src/extract/calls/django-orm.ts
|
|
9606
|
-
import
|
|
10671
|
+
import path46 from "path";
|
|
9607
10672
|
import Parser10 from "tree-sitter";
|
|
9608
|
-
import
|
|
10673
|
+
import Python6 from "tree-sitter-python";
|
|
9609
10674
|
import { infraId as infraId10 } from "@neat.is/types";
|
|
9610
10675
|
var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
|
|
9611
10676
|
var PARSE_CHUNK7 = 16384;
|
|
9612
10677
|
function makePyParser4() {
|
|
9613
10678
|
const p = new Parser10();
|
|
9614
|
-
p.setLanguage(
|
|
10679
|
+
p.setLanguage(Python6);
|
|
9615
10680
|
return p;
|
|
9616
10681
|
}
|
|
9617
10682
|
function parseSource7(parser, source) {
|
|
@@ -9673,7 +10738,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
9673
10738
|
const tree = parseSource7(makePyParser4(), file.content);
|
|
9674
10739
|
const out = [];
|
|
9675
10740
|
const seen = /* @__PURE__ */ new Set();
|
|
9676
|
-
const defaultAppLabel =
|
|
10741
|
+
const defaultAppLabel = path46.basename(path46.dirname(file.path));
|
|
9677
10742
|
walk4(tree.rootNode, (node) => {
|
|
9678
10743
|
if (node.type !== "class_definition") return;
|
|
9679
10744
|
if (!extendsDjangoModel(node)) return;
|
|
@@ -9691,14 +10756,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
9691
10756
|
kind: "sql-table",
|
|
9692
10757
|
edgeType: "CALLS",
|
|
9693
10758
|
confidenceKind: "verified-call-site",
|
|
9694
|
-
evidence: { file:
|
|
10759
|
+
evidence: { file: path46.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
9695
10760
|
});
|
|
9696
10761
|
});
|
|
9697
10762
|
return out;
|
|
9698
10763
|
}
|
|
9699
10764
|
|
|
9700
10765
|
// src/extract/calls/drizzle.ts
|
|
9701
|
-
import
|
|
10766
|
+
import path47 from "path";
|
|
9702
10767
|
import Parser11 from "tree-sitter";
|
|
9703
10768
|
import JavaScript7 from "tree-sitter-javascript";
|
|
9704
10769
|
import { infraId as infraId11 } from "@neat.is/types";
|
|
@@ -9776,7 +10841,7 @@ function columnsFromObject(obj) {
|
|
|
9776
10841
|
}
|
|
9777
10842
|
function drizzleEndpointsFromFile(file, serviceDir) {
|
|
9778
10843
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
9779
|
-
const tree = parseSource3(parserForExt3(
|
|
10844
|
+
const tree = parseSource3(parserForExt3(path47.extname(file.path)), file.content);
|
|
9780
10845
|
const out = [];
|
|
9781
10846
|
const seen = /* @__PURE__ */ new Set();
|
|
9782
10847
|
const walk9 = (node) => {
|
|
@@ -9799,7 +10864,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
9799
10864
|
confidenceKind: "structural",
|
|
9800
10865
|
columns,
|
|
9801
10866
|
evidence: {
|
|
9802
|
-
file:
|
|
10867
|
+
file: path47.relative(serviceDir, file.path),
|
|
9803
10868
|
line,
|
|
9804
10869
|
snippet: snippet(file.content, line)
|
|
9805
10870
|
}
|
|
@@ -9865,7 +10930,7 @@ function referencesTargetVar(call) {
|
|
|
9865
10930
|
}
|
|
9866
10931
|
function drizzleForeignKeys(file, serviceDir) {
|
|
9867
10932
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
9868
|
-
const tree = parseSource3(parserForExt3(
|
|
10933
|
+
const tree = parseSource3(parserForExt3(path47.extname(file.path)), file.content);
|
|
9869
10934
|
const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
|
|
9870
10935
|
const out = [];
|
|
9871
10936
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -9884,7 +10949,7 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
9884
10949
|
childTable: table.tableName,
|
|
9885
10950
|
parentTable,
|
|
9886
10951
|
evidence: {
|
|
9887
|
-
file:
|
|
10952
|
+
file: path47.relative(serviceDir, file.path),
|
|
9888
10953
|
line,
|
|
9889
10954
|
snippet: snippet(file.content, line)
|
|
9890
10955
|
}
|
|
@@ -9900,7 +10965,7 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
9900
10965
|
}
|
|
9901
10966
|
|
|
9902
10967
|
// src/extract/calls/prisma.ts
|
|
9903
|
-
import
|
|
10968
|
+
import path48 from "path";
|
|
9904
10969
|
import { infraId as infraId12 } from "@neat.is/types";
|
|
9905
10970
|
var SCALAR_TYPES = /* @__PURE__ */ new Set([
|
|
9906
10971
|
"Int",
|
|
@@ -9956,7 +11021,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
|
|
|
9956
11021
|
confidenceKind: "structural",
|
|
9957
11022
|
columns: b.columns,
|
|
9958
11023
|
evidence: {
|
|
9959
|
-
file:
|
|
11024
|
+
file: path48.relative(serviceDir, file.path),
|
|
9960
11025
|
line: b.startLine,
|
|
9961
11026
|
snippet: snippet(content, b.startLine)
|
|
9962
11027
|
}
|
|
@@ -10017,7 +11082,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
|
|
|
10017
11082
|
}
|
|
10018
11083
|
async function prismaColumnEndpoints(serviceDir) {
|
|
10019
11084
|
const schemaPath = await findFirst(serviceDir, [
|
|
10020
|
-
|
|
11085
|
+
path48.join("prisma", "schema.prisma"),
|
|
10021
11086
|
"schema.prisma"
|
|
10022
11087
|
]);
|
|
10023
11088
|
if (!schemaPath) return [];
|
|
@@ -10092,7 +11157,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
|
10092
11157
|
childTable: current.table,
|
|
10093
11158
|
parentTable,
|
|
10094
11159
|
evidence: {
|
|
10095
|
-
file:
|
|
11160
|
+
file: path48.relative(serviceDir, file.path),
|
|
10096
11161
|
line: lineNo,
|
|
10097
11162
|
snippet: snippet(content, lineNo)
|
|
10098
11163
|
}
|
|
@@ -10107,7 +11172,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
|
10107
11172
|
}
|
|
10108
11173
|
async function prismaForeignKeys(serviceDir) {
|
|
10109
11174
|
const schemaPath = await findFirst(serviceDir, [
|
|
10110
|
-
|
|
11175
|
+
path48.join("prisma", "schema.prisma"),
|
|
10111
11176
|
"schema.prisma"
|
|
10112
11177
|
]);
|
|
10113
11178
|
if (!schemaPath) return [];
|
|
@@ -10117,15 +11182,15 @@ async function prismaForeignKeys(serviceDir) {
|
|
|
10117
11182
|
}
|
|
10118
11183
|
|
|
10119
11184
|
// src/extract/calls/activerecord.ts
|
|
10120
|
-
import
|
|
11185
|
+
import path49 from "path";
|
|
10121
11186
|
import Parser12 from "tree-sitter";
|
|
10122
|
-
import
|
|
11187
|
+
import Ruby3 from "tree-sitter-ruby";
|
|
10123
11188
|
import { infraId as infraId13 } from "@neat.is/types";
|
|
10124
11189
|
var AR_SCHEMA_RE = /ActiveRecord::Schema/;
|
|
10125
11190
|
var PARSE_CHUNK8 = 16384;
|
|
10126
11191
|
function makeRubyParser2() {
|
|
10127
11192
|
const p = new Parser12();
|
|
10128
|
-
p.setLanguage(
|
|
11193
|
+
p.setLanguage(Ruby3);
|
|
10129
11194
|
return p;
|
|
10130
11195
|
}
|
|
10131
11196
|
function parseSource8(parser, source) {
|
|
@@ -10325,7 +11390,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
|
|
|
10325
11390
|
confidenceKind: "structural",
|
|
10326
11391
|
...table.columns.length > 0 ? { columns: table.columns } : {},
|
|
10327
11392
|
evidence: {
|
|
10328
|
-
file:
|
|
11393
|
+
file: path49.relative(serviceDir, file.path),
|
|
10329
11394
|
line: table.line,
|
|
10330
11395
|
snippet: snippet(file.content, table.line)
|
|
10331
11396
|
}
|
|
@@ -10347,7 +11412,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
|
|
|
10347
11412
|
childTable,
|
|
10348
11413
|
parentTable,
|
|
10349
11414
|
evidence: {
|
|
10350
|
-
file:
|
|
11415
|
+
file: path49.relative(serviceDir, file.path),
|
|
10351
11416
|
line,
|
|
10352
11417
|
snippet: snippet(file.content, line)
|
|
10353
11418
|
}
|
|
@@ -10430,7 +11495,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
|
|
|
10430
11495
|
edgeType: "CALLS",
|
|
10431
11496
|
confidenceKind: "verified-call-site",
|
|
10432
11497
|
evidence: {
|
|
10433
|
-
file:
|
|
11498
|
+
file: path49.relative(serviceDir, file.path),
|
|
10434
11499
|
line,
|
|
10435
11500
|
snippet: snippet(file.content, line)
|
|
10436
11501
|
}
|
|
@@ -10467,7 +11532,7 @@ function railsModelForeignKeys(file, serviceDir) {
|
|
|
10467
11532
|
childTable,
|
|
10468
11533
|
parentTable,
|
|
10469
11534
|
evidence: {
|
|
10470
|
-
file:
|
|
11535
|
+
file: path49.relative(serviceDir, file.path),
|
|
10471
11536
|
line,
|
|
10472
11537
|
snippet: snippet(file.content, line)
|
|
10473
11538
|
}
|
|
@@ -10478,15 +11543,15 @@ function railsModelForeignKeys(file, serviceDir) {
|
|
|
10478
11543
|
}
|
|
10479
11544
|
|
|
10480
11545
|
// src/extract/calls/eloquent.ts
|
|
10481
|
-
import
|
|
11546
|
+
import path50 from "path";
|
|
10482
11547
|
import Parser13 from "tree-sitter";
|
|
10483
|
-
import
|
|
11548
|
+
import Php3 from "tree-sitter-php";
|
|
10484
11549
|
import { infraId as infraId14 } from "@neat.is/types";
|
|
10485
11550
|
var LARAVEL_SCHEMA_RE = /\bSchema::(create|createIfNotExists|table)\b/;
|
|
10486
11551
|
var PARSE_CHUNK9 = 16384;
|
|
10487
11552
|
function makePhpParser2() {
|
|
10488
11553
|
const p = new Parser13();
|
|
10489
|
-
p.setLanguage(
|
|
11554
|
+
p.setLanguage(Php3.php_only);
|
|
10490
11555
|
return p;
|
|
10491
11556
|
}
|
|
10492
11557
|
function parseSource9(parser, source) {
|
|
@@ -10735,7 +11800,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
|
|
|
10735
11800
|
confidenceKind: "structural",
|
|
10736
11801
|
...columns.length > 0 ? { columns } : {},
|
|
10737
11802
|
evidence: {
|
|
10738
|
-
file:
|
|
11803
|
+
file: path50.relative(serviceDir, file.path),
|
|
10739
11804
|
line: bp.line,
|
|
10740
11805
|
snippet: snippet(file.content, bp.line)
|
|
10741
11806
|
}
|
|
@@ -10757,7 +11822,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
|
|
|
10757
11822
|
childTable,
|
|
10758
11823
|
parentTable,
|
|
10759
11824
|
evidence: {
|
|
10760
|
-
file:
|
|
11825
|
+
file: path50.relative(serviceDir, file.path),
|
|
10761
11826
|
line,
|
|
10762
11827
|
snippet: snippet(file.content, line)
|
|
10763
11828
|
}
|
|
@@ -10877,7 +11942,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
|
|
|
10877
11942
|
edgeType: "CALLS",
|
|
10878
11943
|
confidenceKind: "verified-call-site",
|
|
10879
11944
|
evidence: {
|
|
10880
|
-
file:
|
|
11945
|
+
file: path50.relative(serviceDir, file.path),
|
|
10881
11946
|
line,
|
|
10882
11947
|
snippet: snippet(file.content, line)
|
|
10883
11948
|
}
|
|
@@ -10913,7 +11978,7 @@ function laravelModelForeignKeys(file, serviceDir) {
|
|
|
10913
11978
|
childTable,
|
|
10914
11979
|
parentTable,
|
|
10915
11980
|
evidence: {
|
|
10916
|
-
file:
|
|
11981
|
+
file: path50.relative(serviceDir, file.path),
|
|
10917
11982
|
line,
|
|
10918
11983
|
snippet: snippet(file.content, line)
|
|
10919
11984
|
}
|
|
@@ -10924,9 +11989,9 @@ function laravelModelForeignKeys(file, serviceDir) {
|
|
|
10924
11989
|
}
|
|
10925
11990
|
|
|
10926
11991
|
// src/extract/calls/go.ts
|
|
10927
|
-
import
|
|
11992
|
+
import path51 from "path";
|
|
10928
11993
|
import Parser14 from "tree-sitter";
|
|
10929
|
-
import
|
|
11994
|
+
import Go4 from "tree-sitter-go";
|
|
10930
11995
|
import { infraId as infraId15 } from "@neat.is/types";
|
|
10931
11996
|
var PARSE_CHUNK10 = 16384;
|
|
10932
11997
|
var DATABASE_SQL_METHODS = /* @__PURE__ */ new Set([
|
|
@@ -10955,7 +12020,7 @@ var DATABASE_SQL_IMPORT = "database/sql";
|
|
|
10955
12020
|
var SQLX_IMPORT = "github.com/jmoiron/sqlx";
|
|
10956
12021
|
function makeGoParser3() {
|
|
10957
12022
|
const p = new Parser14();
|
|
10958
|
-
p.setLanguage(
|
|
12023
|
+
p.setLanguage(Go4);
|
|
10959
12024
|
return p;
|
|
10960
12025
|
}
|
|
10961
12026
|
function parseSource10(parser, source) {
|
|
@@ -10998,7 +12063,7 @@ function firstStringLiteralArg(argsNode) {
|
|
|
10998
12063
|
return null;
|
|
10999
12064
|
}
|
|
11000
12065
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
11001
|
-
if (
|
|
12066
|
+
if (path51.extname(file.path) !== ".go") return [];
|
|
11002
12067
|
if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
|
|
11003
12068
|
const tree = parseSource10(makeGoParser3(), file.content);
|
|
11004
12069
|
const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
|
|
@@ -11027,7 +12092,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
11027
12092
|
confidenceKind: "verified-call-site",
|
|
11028
12093
|
...columns.length > 0 ? { columns } : {},
|
|
11029
12094
|
evidence: {
|
|
11030
|
-
file: toPosix(
|
|
12095
|
+
file: toPosix(path51.relative(serviceDir, file.path)),
|
|
11031
12096
|
line,
|
|
11032
12097
|
snippet: snippet(file.content, line)
|
|
11033
12098
|
}
|
|
@@ -11037,15 +12102,15 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
11037
12102
|
}
|
|
11038
12103
|
|
|
11039
12104
|
// src/extract/calls/gorm.ts
|
|
11040
|
-
import
|
|
12105
|
+
import path52 from "path";
|
|
11041
12106
|
import Parser15 from "tree-sitter";
|
|
11042
|
-
import
|
|
12107
|
+
import Go5 from "tree-sitter-go";
|
|
11043
12108
|
import { infraId as infraId16 } from "@neat.is/types";
|
|
11044
12109
|
var GORM_IMPORT_RE = /gorm\.io\/gorm/;
|
|
11045
12110
|
var PARSE_CHUNK11 = 16384;
|
|
11046
12111
|
function makeGoParser4() {
|
|
11047
12112
|
const p = new Parser15();
|
|
11048
|
-
p.setLanguage(
|
|
12113
|
+
p.setLanguage(Go5);
|
|
11049
12114
|
return p;
|
|
11050
12115
|
}
|
|
11051
12116
|
function parseSource11(parser, source) {
|
|
@@ -11470,7 +12535,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
|
11470
12535
|
seen.delete(struct.name);
|
|
11471
12536
|
}
|
|
11472
12537
|
function gormEndpointsFromFile(file, serviceDir) {
|
|
11473
|
-
if (
|
|
12538
|
+
if (path52.extname(file.path) !== ".go") return [];
|
|
11474
12539
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
11475
12540
|
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11476
12541
|
const { structs, models, tableFor } = analyze(tree);
|
|
@@ -11492,7 +12557,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
11492
12557
|
confidenceKind: "structural",
|
|
11493
12558
|
...columns.length > 0 ? { columns } : {},
|
|
11494
12559
|
evidence: {
|
|
11495
|
-
file: toPosix(
|
|
12560
|
+
file: toPosix(path52.relative(serviceDir, file.path)),
|
|
11496
12561
|
line: struct.line,
|
|
11497
12562
|
snippet: snippet(file.content, struct.line)
|
|
11498
12563
|
}
|
|
@@ -11501,7 +12566,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
11501
12566
|
return out;
|
|
11502
12567
|
}
|
|
11503
12568
|
function gormForeignKeys(file, serviceDir) {
|
|
11504
|
-
if (
|
|
12569
|
+
if (path52.extname(file.path) !== ".go") return [];
|
|
11505
12570
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
11506
12571
|
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11507
12572
|
const { structs, models, tableFor } = analyze(tree);
|
|
@@ -11516,7 +12581,7 @@ function gormForeignKeys(file, serviceDir) {
|
|
|
11516
12581
|
childTable,
|
|
11517
12582
|
parentTable,
|
|
11518
12583
|
evidence: {
|
|
11519
|
-
file: toPosix(
|
|
12584
|
+
file: toPosix(path52.relative(serviceDir, file.path)),
|
|
11520
12585
|
line,
|
|
11521
12586
|
snippet: snippet(file.content, line)
|
|
11522
12587
|
}
|
|
@@ -11569,7 +12634,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
11569
12634
|
let nodesAdded = 0;
|
|
11570
12635
|
let edgesAdded = 0;
|
|
11571
12636
|
for (const service of services) {
|
|
11572
|
-
const files = await loadSourceFiles(service.dir);
|
|
12637
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
11573
12638
|
const endpoints = [];
|
|
11574
12639
|
const maskedFiles = [];
|
|
11575
12640
|
for (const file of files) {
|
|
@@ -11619,7 +12684,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
11619
12684
|
if (!graph.hasNode(ep.infraId)) {
|
|
11620
12685
|
const node = {
|
|
11621
12686
|
id: ep.infraId,
|
|
11622
|
-
type:
|
|
12687
|
+
type: NodeType24.InfraNode,
|
|
11623
12688
|
name: ep.name,
|
|
11624
12689
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
11625
12690
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -11632,7 +12697,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
11632
12697
|
}
|
|
11633
12698
|
if (ep.columns && ep.columns.length > 0) {
|
|
11634
12699
|
const node = graph.getNodeAttributes(ep.infraId);
|
|
11635
|
-
if (node.type ===
|
|
12700
|
+
if (node.type === NodeType24.InfraNode) {
|
|
11636
12701
|
graph.replaceNodeAttributes(ep.infraId, {
|
|
11637
12702
|
...node,
|
|
11638
12703
|
columns: foldColumns(
|
|
@@ -11646,7 +12711,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
11646
12711
|
}
|
|
11647
12712
|
if (ep.sdkWrites && Object.keys(ep.sdkWrites).length > 0) {
|
|
11648
12713
|
const node = graph.getNodeAttributes(ep.infraId);
|
|
11649
|
-
if (node.type ===
|
|
12714
|
+
if (node.type === NodeType24.InfraNode) {
|
|
11650
12715
|
graph.replaceNodeAttributes(ep.infraId, {
|
|
11651
12716
|
...node,
|
|
11652
12717
|
columns: foldSdkWrites(node.columns, ep.sdkWrites)
|
|
@@ -11708,7 +12773,7 @@ async function addCallEdges(graph, services) {
|
|
|
11708
12773
|
// src/extract/table-edges.ts
|
|
11709
12774
|
import {
|
|
11710
12775
|
EdgeType as EdgeType16,
|
|
11711
|
-
NodeType as
|
|
12776
|
+
NodeType as NodeType25,
|
|
11712
12777
|
Provenance as Provenance16,
|
|
11713
12778
|
confidenceForExtracted as confidenceForExtracted13,
|
|
11714
12779
|
extractedEdgeId as extractedEdgeId10,
|
|
@@ -11718,7 +12783,7 @@ async function addTableEdges(graph, services) {
|
|
|
11718
12783
|
let nodesAdded = 0;
|
|
11719
12784
|
let edgesAdded = 0;
|
|
11720
12785
|
for (const service of services) {
|
|
11721
|
-
const files = await loadSourceFiles(service.dir);
|
|
12786
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
11722
12787
|
const refs = [];
|
|
11723
12788
|
const modelRefs = [];
|
|
11724
12789
|
for (const file of files) {
|
|
@@ -11767,7 +12832,7 @@ function ensureTableNode(graph, id, name) {
|
|
|
11767
12832
|
if (graph.hasNode(id)) return 0;
|
|
11768
12833
|
const node = {
|
|
11769
12834
|
id,
|
|
11770
|
-
type:
|
|
12835
|
+
type: NodeType25.InfraNode,
|
|
11771
12836
|
name,
|
|
11772
12837
|
provider: "self",
|
|
11773
12838
|
kind: "sql-table"
|
|
@@ -11777,15 +12842,15 @@ function ensureTableNode(graph, id, name) {
|
|
|
11777
12842
|
}
|
|
11778
12843
|
|
|
11779
12844
|
// src/extract/infra/docker-compose.ts
|
|
11780
|
-
import
|
|
12845
|
+
import path53 from "path";
|
|
11781
12846
|
import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
|
|
11782
12847
|
|
|
11783
12848
|
// src/extract/infra/shared.ts
|
|
11784
|
-
import { NodeType as
|
|
12849
|
+
import { NodeType as NodeType26, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId18 } from "@neat.is/types";
|
|
11785
12850
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
11786
12851
|
return {
|
|
11787
12852
|
id: infraId18(kind, name),
|
|
11788
|
-
type:
|
|
12853
|
+
type: NodeType26.InfraNode,
|
|
11789
12854
|
name,
|
|
11790
12855
|
provider,
|
|
11791
12856
|
kind,
|
|
@@ -11847,7 +12912,7 @@ function dependsOnList(value) {
|
|
|
11847
12912
|
}
|
|
11848
12913
|
function serviceNameToServiceNode(name, services) {
|
|
11849
12914
|
for (const s of services) {
|
|
11850
|
-
if (s.node.name === name ||
|
|
12915
|
+
if (s.node.name === name || path53.basename(s.dir) === name) return s.node.id;
|
|
11851
12916
|
}
|
|
11852
12917
|
return null;
|
|
11853
12918
|
}
|
|
@@ -11856,7 +12921,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
11856
12921
|
let edgesAdded = 0;
|
|
11857
12922
|
let composePath = null;
|
|
11858
12923
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
11859
|
-
const abs =
|
|
12924
|
+
const abs = path53.join(scanPath, name);
|
|
11860
12925
|
if (await exists(abs)) {
|
|
11861
12926
|
composePath = abs;
|
|
11862
12927
|
break;
|
|
@@ -11869,13 +12934,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
11869
12934
|
} catch (err) {
|
|
11870
12935
|
recordExtractionError(
|
|
11871
12936
|
"infra docker-compose",
|
|
11872
|
-
|
|
12937
|
+
path53.relative(scanPath, composePath),
|
|
11873
12938
|
err
|
|
11874
12939
|
);
|
|
11875
12940
|
return { nodesAdded, edgesAdded };
|
|
11876
12941
|
}
|
|
11877
12942
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
11878
|
-
const evidenceFile =
|
|
12943
|
+
const evidenceFile = path53.relative(scanPath, composePath).split(path53.sep).join("/");
|
|
11879
12944
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
11880
12945
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
11881
12946
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -11916,8 +12981,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
11916
12981
|
}
|
|
11917
12982
|
|
|
11918
12983
|
// src/extract/infra/dockerfile.ts
|
|
11919
|
-
import
|
|
11920
|
-
import { promises as
|
|
12984
|
+
import path54 from "path";
|
|
12985
|
+
import { promises as fs24 } from "fs";
|
|
11921
12986
|
import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
|
|
11922
12987
|
function readDockerfile(content) {
|
|
11923
12988
|
let image = null;
|
|
@@ -11947,15 +13012,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
11947
13012
|
let nodesAdded = 0;
|
|
11948
13013
|
let edgesAdded = 0;
|
|
11949
13014
|
for (const service of services) {
|
|
11950
|
-
const dockerfilePath =
|
|
13015
|
+
const dockerfilePath = path54.join(service.dir, "Dockerfile");
|
|
11951
13016
|
if (!await exists(dockerfilePath)) continue;
|
|
11952
13017
|
let content;
|
|
11953
13018
|
try {
|
|
11954
|
-
content = await
|
|
13019
|
+
content = await fs24.readFile(dockerfilePath, "utf8");
|
|
11955
13020
|
} catch (err) {
|
|
11956
13021
|
recordExtractionError(
|
|
11957
13022
|
"infra dockerfile",
|
|
11958
|
-
|
|
13023
|
+
path54.relative(scanPath, dockerfilePath),
|
|
11959
13024
|
err
|
|
11960
13025
|
);
|
|
11961
13026
|
continue;
|
|
@@ -11967,8 +13032,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
11967
13032
|
graph.addNode(node.id, node);
|
|
11968
13033
|
nodesAdded++;
|
|
11969
13034
|
}
|
|
11970
|
-
const relDockerfile = toPosix(
|
|
11971
|
-
const evidenceFile = toPosix(
|
|
13035
|
+
const relDockerfile = toPosix(path54.relative(service.dir, dockerfilePath));
|
|
13036
|
+
const evidenceFile = toPosix(path54.relative(scanPath, dockerfilePath));
|
|
11972
13037
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
11973
13038
|
graph,
|
|
11974
13039
|
service.pkg.name,
|
|
@@ -12019,23 +13084,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
12019
13084
|
}
|
|
12020
13085
|
|
|
12021
13086
|
// src/extract/infra/terraform.ts
|
|
12022
|
-
import { promises as
|
|
12023
|
-
import
|
|
13087
|
+
import { promises as fs25 } from "fs";
|
|
13088
|
+
import path55 from "path";
|
|
12024
13089
|
import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
|
|
12025
13090
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
12026
13091
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
12027
13092
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
12028
13093
|
if (depth > max) return [];
|
|
12029
13094
|
const out = [];
|
|
12030
|
-
const entries = await
|
|
13095
|
+
const entries = await fs25.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
12031
13096
|
for (const entry of entries) {
|
|
12032
13097
|
if (entry.isDirectory()) {
|
|
12033
13098
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
12034
|
-
const child =
|
|
13099
|
+
const child = path55.join(start, entry.name);
|
|
12035
13100
|
if (await isPythonVenvDir(child)) continue;
|
|
12036
13101
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
12037
13102
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
12038
|
-
out.push(
|
|
13103
|
+
out.push(path55.join(start, entry.name));
|
|
12039
13104
|
}
|
|
12040
13105
|
}
|
|
12041
13106
|
return out;
|
|
@@ -12066,8 +13131,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
12066
13131
|
let edgesAdded = 0;
|
|
12067
13132
|
const files = await walkTfFiles(scanPath);
|
|
12068
13133
|
for (const file of files) {
|
|
12069
|
-
const content = await
|
|
12070
|
-
const evidenceFile = toPosix(
|
|
13134
|
+
const content = await fs25.readFile(file, "utf8");
|
|
13135
|
+
const evidenceFile = toPosix(path55.relative(scanPath, file));
|
|
12071
13136
|
const resources = [];
|
|
12072
13137
|
const byKey = /* @__PURE__ */ new Map();
|
|
12073
13138
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -12123,8 +13188,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
12123
13188
|
}
|
|
12124
13189
|
|
|
12125
13190
|
// src/extract/infra/k8s.ts
|
|
12126
|
-
import { promises as
|
|
12127
|
-
import
|
|
13191
|
+
import { promises as fs26 } from "fs";
|
|
13192
|
+
import path56 from "path";
|
|
12128
13193
|
import { parseAllDocuments as parseAllDocuments2 } from "yaml";
|
|
12129
13194
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
12130
13195
|
Service: "k8s-service",
|
|
@@ -12138,15 +13203,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
12138
13203
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
12139
13204
|
if (depth > max) return [];
|
|
12140
13205
|
const out = [];
|
|
12141
|
-
const entries = await
|
|
13206
|
+
const entries = await fs26.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
12142
13207
|
for (const entry of entries) {
|
|
12143
13208
|
if (entry.isDirectory()) {
|
|
12144
13209
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
12145
|
-
const child =
|
|
13210
|
+
const child = path56.join(start, entry.name);
|
|
12146
13211
|
if (await isPythonVenvDir(child)) continue;
|
|
12147
13212
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
12148
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
12149
|
-
out.push(
|
|
13213
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path56.extname(entry.name))) {
|
|
13214
|
+
out.push(path56.join(start, entry.name));
|
|
12150
13215
|
}
|
|
12151
13216
|
}
|
|
12152
13217
|
return out;
|
|
@@ -12155,7 +13220,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
12155
13220
|
let nodesAdded = 0;
|
|
12156
13221
|
const files = await walkYamlFiles2(scanPath);
|
|
12157
13222
|
for (const file of files) {
|
|
12158
|
-
const content = await
|
|
13223
|
+
const content = await fs26.readFile(file, "utf8");
|
|
12159
13224
|
let docs;
|
|
12160
13225
|
try {
|
|
12161
13226
|
docs = parseAllDocuments2(content).map((d) => d.toJSON());
|
|
@@ -12178,17 +13243,17 @@ async function addK8sResources(graph, scanPath) {
|
|
|
12178
13243
|
}
|
|
12179
13244
|
|
|
12180
13245
|
// src/extract/infra/cloudflare.ts
|
|
12181
|
-
import { promises as
|
|
12182
|
-
import
|
|
12183
|
-
import { parse as
|
|
13246
|
+
import { promises as fs27 } from "fs";
|
|
13247
|
+
import path57 from "path";
|
|
13248
|
+
import { parse as parseToml3 } from "smol-toml";
|
|
12184
13249
|
import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
|
|
12185
13250
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
12186
13251
|
async function readWranglerConfig(dir) {
|
|
12187
13252
|
for (const filename of WRANGLER_FILENAMES) {
|
|
12188
|
-
const abs =
|
|
13253
|
+
const abs = path57.join(dir, filename);
|
|
12189
13254
|
if (!await exists(abs)) continue;
|
|
12190
|
-
const raw = await
|
|
12191
|
-
const config = filename === "wrangler.toml" ?
|
|
13255
|
+
const raw = await fs27.readFile(abs, "utf8");
|
|
13256
|
+
const config = filename === "wrangler.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
12192
13257
|
return { config, relFile: filename, raw };
|
|
12193
13258
|
}
|
|
12194
13259
|
return null;
|
|
@@ -12248,11 +13313,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
12248
13313
|
try {
|
|
12249
13314
|
read = await readWranglerConfig(service.dir);
|
|
12250
13315
|
} catch (err) {
|
|
12251
|
-
recordExtractionError("infra cloudflare",
|
|
13316
|
+
recordExtractionError("infra cloudflare", path57.relative(scanPath, service.dir), err);
|
|
12252
13317
|
continue;
|
|
12253
13318
|
}
|
|
12254
13319
|
if (!read || !read.config.name) continue;
|
|
12255
|
-
const evidenceFile = toPosix(
|
|
13320
|
+
const evidenceFile = toPosix(path57.relative(scanPath, path57.join(service.dir, read.relFile)));
|
|
12256
13321
|
discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
|
|
12257
13322
|
}
|
|
12258
13323
|
for (const worker of discovered) {
|
|
@@ -12264,7 +13329,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
12264
13329
|
}
|
|
12265
13330
|
let anchorId = service.node.id;
|
|
12266
13331
|
if (config.main) {
|
|
12267
|
-
const entryRelPath = toPosix(
|
|
13332
|
+
const entryRelPath = toPosix(path57.normalize(config.main));
|
|
12268
13333
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
12269
13334
|
graph,
|
|
12270
13335
|
service.pkg.name,
|
|
@@ -12410,24 +13475,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
12410
13475
|
}
|
|
12411
13476
|
|
|
12412
13477
|
// src/extract/infra/vercel.ts
|
|
12413
|
-
import { promises as
|
|
12414
|
-
import
|
|
13478
|
+
import { promises as fs28 } from "fs";
|
|
13479
|
+
import path58 from "path";
|
|
12415
13480
|
import { EdgeType as EdgeType21 } from "@neat.is/types";
|
|
12416
13481
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
12417
13482
|
async function readVercelConfig(dir) {
|
|
12418
13483
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
12419
|
-
const abs =
|
|
13484
|
+
const abs = path58.join(dir, filename);
|
|
12420
13485
|
if (!await exists(abs)) continue;
|
|
12421
|
-
const raw = await
|
|
13486
|
+
const raw = await fs28.readFile(abs, "utf8");
|
|
12422
13487
|
const config = JSON.parse(maskCommentsInSource(raw));
|
|
12423
13488
|
return { config, relFile: filename, raw };
|
|
12424
13489
|
}
|
|
12425
13490
|
return null;
|
|
12426
13491
|
}
|
|
12427
13492
|
async function readLinkedProjectName(dir) {
|
|
12428
|
-
const abs =
|
|
13493
|
+
const abs = path58.join(dir, ".vercel", "project.json");
|
|
12429
13494
|
if (!await exists(abs)) return void 0;
|
|
12430
|
-
const parsed = JSON.parse(await
|
|
13495
|
+
const parsed = JSON.parse(await fs28.readFile(abs, "utf8"));
|
|
12431
13496
|
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
12432
13497
|
}
|
|
12433
13498
|
function routeSource(route) {
|
|
@@ -12443,7 +13508,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
12443
13508
|
read = await readVercelConfig(service.dir);
|
|
12444
13509
|
projectName = await readLinkedProjectName(service.dir);
|
|
12445
13510
|
} catch (err) {
|
|
12446
|
-
recordExtractionError("infra vercel",
|
|
13511
|
+
recordExtractionError("infra vercel", path58.relative(scanPath, service.dir), err);
|
|
12447
13512
|
continue;
|
|
12448
13513
|
}
|
|
12449
13514
|
if (!read && !projectName) continue;
|
|
@@ -12459,7 +13524,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
12459
13524
|
const anchorId = service.node.id;
|
|
12460
13525
|
if (!read) continue;
|
|
12461
13526
|
const { config, relFile, raw } = read;
|
|
12462
|
-
const evidenceFile = toPosix(
|
|
13527
|
+
const evidenceFile = toPosix(path58.relative(scanPath, path58.join(service.dir, relFile)));
|
|
12463
13528
|
const add = (edgeType, kind, name) => {
|
|
12464
13529
|
if (!name) return;
|
|
12465
13530
|
const result = emitPlatformResourceEdge(
|
|
@@ -12487,17 +13552,17 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
12487
13552
|
}
|
|
12488
13553
|
|
|
12489
13554
|
// src/extract/infra/railway.ts
|
|
12490
|
-
import { promises as
|
|
12491
|
-
import
|
|
12492
|
-
import { parse as
|
|
13555
|
+
import { promises as fs29 } from "fs";
|
|
13556
|
+
import path59 from "path";
|
|
13557
|
+
import { parse as parseToml4 } from "smol-toml";
|
|
12493
13558
|
import { EdgeType as EdgeType22 } from "@neat.is/types";
|
|
12494
13559
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
12495
13560
|
async function readRailwayConfig(dir) {
|
|
12496
13561
|
for (const filename of RAILWAY_FILENAMES) {
|
|
12497
|
-
const abs =
|
|
13562
|
+
const abs = path59.join(dir, filename);
|
|
12498
13563
|
if (!await exists(abs)) continue;
|
|
12499
|
-
const raw = await
|
|
12500
|
-
const config = filename === "railway.toml" ?
|
|
13564
|
+
const raw = await fs29.readFile(abs, "utf8");
|
|
13565
|
+
const config = filename === "railway.toml" ? parseToml4(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
12501
13566
|
return { config, relFile: filename, raw };
|
|
12502
13567
|
}
|
|
12503
13568
|
return null;
|
|
@@ -12510,7 +13575,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
12510
13575
|
try {
|
|
12511
13576
|
read = await readRailwayConfig(service.dir);
|
|
12512
13577
|
} catch (err) {
|
|
12513
|
-
recordExtractionError("infra railway",
|
|
13578
|
+
recordExtractionError("infra railway", path59.relative(scanPath, service.dir), err);
|
|
12514
13579
|
continue;
|
|
12515
13580
|
}
|
|
12516
13581
|
if (!read) continue;
|
|
@@ -12520,7 +13585,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
12520
13585
|
}
|
|
12521
13586
|
const anchorId = service.node.id;
|
|
12522
13587
|
const { config, relFile, raw } = read;
|
|
12523
|
-
const evidenceFile = toPosix(
|
|
13588
|
+
const evidenceFile = toPosix(path59.relative(scanPath, path59.join(service.dir, relFile)));
|
|
12524
13589
|
const add = (edgeType, kind, name) => {
|
|
12525
13590
|
if (!name) return;
|
|
12526
13591
|
const result = emitPlatformResourceEdge(
|
|
@@ -12544,16 +13609,16 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
12544
13609
|
}
|
|
12545
13610
|
|
|
12546
13611
|
// src/extract/infra/supabase.ts
|
|
12547
|
-
import { promises as
|
|
12548
|
-
import
|
|
12549
|
-
import { parse as
|
|
13612
|
+
import { promises as fs30 } from "fs";
|
|
13613
|
+
import path60 from "path";
|
|
13614
|
+
import { parse as parseToml5 } from "smol-toml";
|
|
12550
13615
|
import { EdgeType as EdgeType23 } from "@neat.is/types";
|
|
12551
13616
|
async function readSupabaseConfig(dir) {
|
|
12552
|
-
const relFile =
|
|
12553
|
-
const abs =
|
|
13617
|
+
const relFile = path60.join("supabase", "config.toml");
|
|
13618
|
+
const abs = path60.join(dir, relFile);
|
|
12554
13619
|
if (!await exists(abs)) return null;
|
|
12555
|
-
const raw = await
|
|
12556
|
-
const config =
|
|
13620
|
+
const raw = await fs30.readFile(abs, "utf8");
|
|
13621
|
+
const config = parseToml5(raw);
|
|
12557
13622
|
return { config, relFile, raw };
|
|
12558
13623
|
}
|
|
12559
13624
|
async function addSupabaseProjects(graph, services, scanPath) {
|
|
@@ -12564,7 +13629,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
12564
13629
|
try {
|
|
12565
13630
|
read = await readSupabaseConfig(service.dir);
|
|
12566
13631
|
} catch (err) {
|
|
12567
|
-
recordExtractionError("infra supabase",
|
|
13632
|
+
recordExtractionError("infra supabase", path60.relative(scanPath, service.dir), err);
|
|
12568
13633
|
continue;
|
|
12569
13634
|
}
|
|
12570
13635
|
if (!read) continue;
|
|
@@ -12579,7 +13644,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
12579
13644
|
});
|
|
12580
13645
|
}
|
|
12581
13646
|
const anchorId = service.node.id;
|
|
12582
|
-
const evidenceFile = toPosix(
|
|
13647
|
+
const evidenceFile = toPosix(path60.relative(scanPath, path60.join(service.dir, relFile)));
|
|
12583
13648
|
const add = (edgeType, kind, name) => {
|
|
12584
13649
|
if (!name) return;
|
|
12585
13650
|
const result = emitPlatformResourceEdge(
|
|
@@ -12620,12 +13685,12 @@ async function addInfra(graph, scanPath, services) {
|
|
|
12620
13685
|
}
|
|
12621
13686
|
|
|
12622
13687
|
// src/extract/zod-shapes.ts
|
|
12623
|
-
import
|
|
13688
|
+
import path61 from "path";
|
|
12624
13689
|
import Parser16 from "tree-sitter";
|
|
12625
13690
|
import JavaScript8 from "tree-sitter-javascript";
|
|
12626
13691
|
import {
|
|
12627
13692
|
EdgeType as EdgeType24,
|
|
12628
|
-
NodeType as
|
|
13693
|
+
NodeType as NodeType27,
|
|
12629
13694
|
Provenance as Provenance22,
|
|
12630
13695
|
confidenceForExtracted as confidenceForExtracted19,
|
|
12631
13696
|
extractedEdgeId as extractedEdgeId11,
|
|
@@ -12722,7 +13787,7 @@ function topLevelSchemas(root) {
|
|
|
12722
13787
|
}
|
|
12723
13788
|
function zodShapesFromFile(file, serviceDir) {
|
|
12724
13789
|
if (!ZOD_IMPORT_RE.test(file.content)) return [];
|
|
12725
|
-
const tree = parseSource3(parserForExt4(
|
|
13790
|
+
const tree = parseSource3(parserForExt4(path61.extname(file.path)), file.content);
|
|
12726
13791
|
const out = [];
|
|
12727
13792
|
const seen = /* @__PURE__ */ new Set();
|
|
12728
13793
|
for (const { name, call } of topLevelSchemas(tree.rootNode)) {
|
|
@@ -12740,7 +13805,7 @@ function zodShapesFromFile(file, serviceDir) {
|
|
|
12740
13805
|
name,
|
|
12741
13806
|
fields,
|
|
12742
13807
|
evidence: {
|
|
12743
|
-
file:
|
|
13808
|
+
file: path61.relative(serviceDir, file.path),
|
|
12744
13809
|
line,
|
|
12745
13810
|
snippet: snippet(file.content, line)
|
|
12746
13811
|
}
|
|
@@ -12757,7 +13822,7 @@ async function addZodShapes(graph, services) {
|
|
|
12757
13822
|
...service.pkg.devDependencies ?? {}
|
|
12758
13823
|
};
|
|
12759
13824
|
if (deps["zod"] === void 0) continue;
|
|
12760
|
-
const files = await loadSourceFiles(service.dir);
|
|
13825
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
12761
13826
|
for (const file of files) {
|
|
12762
13827
|
if (isTestPath(file.path)) continue;
|
|
12763
13828
|
let shapes;
|
|
@@ -12771,7 +13836,7 @@ async function addZodShapes(graph, services) {
|
|
|
12771
13836
|
if (!graph.hasNode(shape.infraId)) {
|
|
12772
13837
|
const node = {
|
|
12773
13838
|
id: shape.infraId,
|
|
12774
|
-
type:
|
|
13839
|
+
type: NodeType27.InfraNode,
|
|
12775
13840
|
name: shape.name,
|
|
12776
13841
|
provider: "self",
|
|
12777
13842
|
kind: "zod-schema"
|
|
@@ -12781,7 +13846,7 @@ async function addZodShapes(graph, services) {
|
|
|
12781
13846
|
}
|
|
12782
13847
|
if (shape.fields.length > 0) {
|
|
12783
13848
|
const node = graph.getNodeAttributes(shape.infraId);
|
|
12784
|
-
if (node.type ===
|
|
13849
|
+
if (node.type === NodeType27.InfraNode) {
|
|
12785
13850
|
graph.replaceNodeAttributes(shape.infraId, {
|
|
12786
13851
|
...node,
|
|
12787
13852
|
columns: foldColumns(
|
|
@@ -12823,7 +13888,7 @@ async function addZodShapes(graph, services) {
|
|
|
12823
13888
|
}
|
|
12824
13889
|
|
|
12825
13890
|
// src/extract/firestore-rules.ts
|
|
12826
|
-
import { NodeType as
|
|
13891
|
+
import { NodeType as NodeType28 } from "@neat.is/types";
|
|
12827
13892
|
var FIRESTORE_COLLECTION_KIND = "firestore-collection";
|
|
12828
13893
|
var WRITE_METHODS = /* @__PURE__ */ new Set(["write", "create", "update"]);
|
|
12829
13894
|
function stripComments(src) {
|
|
@@ -12963,7 +14028,7 @@ async function addFirestoreRules(graph, services) {
|
|
|
12963
14028
|
if (guards.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
|
|
12964
14029
|
graph.forEachNode((id, attrs) => {
|
|
12965
14030
|
const node = attrs;
|
|
12966
|
-
if (node.type !==
|
|
14031
|
+
if (node.type !== NodeType28.InfraNode) return;
|
|
12967
14032
|
if (node.kind !== FIRESTORE_COLLECTION_KIND) return;
|
|
12968
14033
|
const fields = guards.get(collectionKeyFromName(node.name));
|
|
12969
14034
|
if (!fields || fields.size === 0) return;
|
|
@@ -12976,16 +14041,16 @@ async function addFirestoreRules(graph, services) {
|
|
|
12976
14041
|
}
|
|
12977
14042
|
|
|
12978
14043
|
// src/extract/index.ts
|
|
12979
|
-
import
|
|
14044
|
+
import path63 from "path";
|
|
12980
14045
|
|
|
12981
14046
|
// src/extract/retire.ts
|
|
12982
14047
|
import { existsSync as existsSync2 } from "fs";
|
|
12983
|
-
import
|
|
12984
|
-
import { NodeType as
|
|
14048
|
+
import path62 from "path";
|
|
14049
|
+
import { NodeType as NodeType29, Provenance as Provenance23 } from "@neat.is/types";
|
|
12985
14050
|
function dropOrphanedFileNodes(graph) {
|
|
12986
14051
|
const orphans = [];
|
|
12987
14052
|
graph.forEachNode((id, attrs) => {
|
|
12988
|
-
if (attrs.type !==
|
|
14053
|
+
if (attrs.type !== NodeType29.FileNode) return;
|
|
12989
14054
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
12990
14055
|
orphans.push(id);
|
|
12991
14056
|
}
|
|
@@ -13014,11 +14079,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
13014
14079
|
if (edge.provenance !== Provenance23.EXTRACTED) return;
|
|
13015
14080
|
const evidenceFile = edge.evidence?.file;
|
|
13016
14081
|
if (!evidenceFile) return;
|
|
13017
|
-
if (
|
|
14082
|
+
if (path62.isAbsolute(evidenceFile)) {
|
|
13018
14083
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
13019
14084
|
return;
|
|
13020
14085
|
}
|
|
13021
|
-
const found = bases.some((base) => existsSync2(
|
|
14086
|
+
const found = bases.some((base) => existsSync2(path62.join(base, evidenceFile)));
|
|
13022
14087
|
if (!found) toDrop.push(id);
|
|
13023
14088
|
});
|
|
13024
14089
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -13075,7 +14140,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
13075
14140
|
}
|
|
13076
14141
|
const droppedEntries = drainDroppedExtracted();
|
|
13077
14142
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
13078
|
-
const rejectedPath =
|
|
14143
|
+
const rejectedPath = path63.join(path63.dirname(opts.errorsPath), "rejected.ndjson");
|
|
13079
14144
|
try {
|
|
13080
14145
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
13081
14146
|
} catch (err) {
|
|
@@ -13112,11 +14177,11 @@ import {
|
|
|
13112
14177
|
databaseId as databaseId3,
|
|
13113
14178
|
DivergenceResultSchema,
|
|
13114
14179
|
EdgeType as EdgeType25,
|
|
13115
|
-
NodeType as
|
|
14180
|
+
NodeType as NodeType30,
|
|
13116
14181
|
parseEdgeId,
|
|
13117
14182
|
parseFileId,
|
|
13118
14183
|
Provenance as Provenance24,
|
|
13119
|
-
serviceId as
|
|
14184
|
+
serviceId as serviceId13
|
|
13120
14185
|
} from "@neat.is/types";
|
|
13121
14186
|
function bucketKey(source, target, type) {
|
|
13122
14187
|
return `${type}|${source}|${target}`;
|
|
@@ -13126,8 +14191,8 @@ function bucketSourceFor(graph, edge) {
|
|
|
13126
14191
|
const parsed = parseFileId(edge.source);
|
|
13127
14192
|
if (!parsed || !graph.hasNode(edge.target)) return edge.source;
|
|
13128
14193
|
const target = graph.getNodeAttributes(edge.target);
|
|
13129
|
-
if (target.type !==
|
|
13130
|
-
return
|
|
14194
|
+
if (target.type !== NodeType30.DatabaseNode) return edge.source;
|
|
14195
|
+
return serviceId13(parsed.service);
|
|
13131
14196
|
}
|
|
13132
14197
|
function bucketEdges(graph) {
|
|
13133
14198
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
@@ -13158,22 +14223,22 @@ function bucketEdges(graph) {
|
|
|
13158
14223
|
function nodeIsFrontier(graph, nodeId) {
|
|
13159
14224
|
if (!graph.hasNode(nodeId)) return false;
|
|
13160
14225
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
13161
|
-
return attrs.type ===
|
|
14226
|
+
return attrs.type === NodeType30.FrontierNode;
|
|
13162
14227
|
}
|
|
13163
14228
|
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
13164
14229
|
if (!graph.hasNode(nodeId)) return false;
|
|
13165
14230
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
13166
|
-
return attrs.type ===
|
|
14231
|
+
return attrs.type === NodeType30.WebSocketChannelNode;
|
|
13167
14232
|
}
|
|
13168
14233
|
function nodeIsServerAction(graph, nodeId) {
|
|
13169
14234
|
if (!graph.hasNode(nodeId)) return false;
|
|
13170
14235
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
13171
|
-
return attrs.type ===
|
|
14236
|
+
return attrs.type === NodeType30.ServerActionNode;
|
|
13172
14237
|
}
|
|
13173
14238
|
function nodeIsSymbol(graph, nodeId) {
|
|
13174
14239
|
if (!graph.hasNode(nodeId)) return false;
|
|
13175
14240
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
13176
|
-
return attrs.type ===
|
|
14241
|
+
return attrs.type === NodeType30.SymbolNode;
|
|
13177
14242
|
}
|
|
13178
14243
|
function clampConfidence(n) {
|
|
13179
14244
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -13258,7 +14323,7 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
13258
14323
|
if (edge.type !== EdgeType25.CONNECTS_TO) continue;
|
|
13259
14324
|
if (edge.provenance !== Provenance24.OBSERVED) continue;
|
|
13260
14325
|
const target = graph.getNodeAttributes(edge.target);
|
|
13261
|
-
if (target.type !==
|
|
14326
|
+
if (target.type !== NodeType30.DatabaseNode) continue;
|
|
13262
14327
|
const observedHost = target.host?.trim();
|
|
13263
14328
|
if (!observedHost) continue;
|
|
13264
14329
|
if (observedHost === declaredHost) continue;
|
|
@@ -13283,7 +14348,7 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
13283
14348
|
if (edge.type !== EdgeType25.CONNECTS_TO) continue;
|
|
13284
14349
|
if (edge.provenance !== Provenance24.OBSERVED) continue;
|
|
13285
14350
|
const target = graph.getNodeAttributes(edge.target);
|
|
13286
|
-
if (target.type !==
|
|
14351
|
+
if (target.type !== NodeType30.DatabaseNode) continue;
|
|
13287
14352
|
for (const pair of compatPairs()) {
|
|
13288
14353
|
if (pair.engine !== target.engine) continue;
|
|
13289
14354
|
const declared = deps[pair.driver];
|
|
@@ -13399,13 +14464,13 @@ function computeDivergences(graph, opts = {}) {
|
|
|
13399
14464
|
}
|
|
13400
14465
|
graph.forEachNode((nodeId, attrs) => {
|
|
13401
14466
|
const n = attrs;
|
|
13402
|
-
if (n.type ===
|
|
14467
|
+
if (n.type === NodeType30.ServiceNode) {
|
|
13403
14468
|
const svc = n;
|
|
13404
14469
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
13405
14470
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
13406
14471
|
return;
|
|
13407
14472
|
}
|
|
13408
|
-
if (n.type ===
|
|
14473
|
+
if (n.type === NodeType30.InfraNode && n.kind === "sql-table") {
|
|
13409
14474
|
for (const d of detectColumnDrift(n)) all.push(d);
|
|
13410
14475
|
}
|
|
13411
14476
|
});
|
|
@@ -13449,9 +14514,9 @@ function computeDivergences(graph, opts = {}) {
|
|
|
13449
14514
|
}
|
|
13450
14515
|
|
|
13451
14516
|
// src/persist.ts
|
|
13452
|
-
import { promises as
|
|
13453
|
-
import
|
|
13454
|
-
import { NodeType as
|
|
14517
|
+
import { promises as fs31 } from "fs";
|
|
14518
|
+
import path64 from "path";
|
|
14519
|
+
import { NodeType as NodeType31, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
13455
14520
|
var SCHEMA_VERSION = 7;
|
|
13456
14521
|
function migrateV1ToV2(payload) {
|
|
13457
14522
|
const nodes = payload.graph.nodes;
|
|
@@ -13475,7 +14540,7 @@ function migrateV5ToV6(payload) {
|
|
|
13475
14540
|
if (Array.isArray(nodes)) {
|
|
13476
14541
|
for (const node of nodes) {
|
|
13477
14542
|
const attrs = node.attributes;
|
|
13478
|
-
if (!attrs || attrs.type !==
|
|
14543
|
+
if (!attrs || attrs.type !== NodeType31.InfraNode) continue;
|
|
13479
14544
|
if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
|
|
13480
14545
|
if (!Array.isArray(attrs.columns)) attrs.columns = [];
|
|
13481
14546
|
}
|
|
@@ -13505,7 +14570,7 @@ function migrateV2ToV3(payload) {
|
|
|
13505
14570
|
return { ...payload, schemaVersion: 3 };
|
|
13506
14571
|
}
|
|
13507
14572
|
async function ensureDir(filePath) {
|
|
13508
|
-
await
|
|
14573
|
+
await fs31.mkdir(path64.dirname(filePath), { recursive: true });
|
|
13509
14574
|
}
|
|
13510
14575
|
async function saveGraphToDisk(graph, outPath) {
|
|
13511
14576
|
await ensureDir(outPath);
|
|
@@ -13515,13 +14580,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
13515
14580
|
graph: graph.export()
|
|
13516
14581
|
};
|
|
13517
14582
|
const tmp = `${outPath}.tmp`;
|
|
13518
|
-
await
|
|
13519
|
-
await
|
|
14583
|
+
await fs31.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
14584
|
+
await fs31.rename(tmp, outPath);
|
|
13520
14585
|
}
|
|
13521
14586
|
async function loadGraphFromDisk(graph, outPath) {
|
|
13522
14587
|
let raw;
|
|
13523
14588
|
try {
|
|
13524
|
-
raw = await
|
|
14589
|
+
raw = await fs31.readFile(outPath, "utf8");
|
|
13525
14590
|
} catch (err) {
|
|
13526
14591
|
if (err.code === "ENOENT") return;
|
|
13527
14592
|
throw err;
|
|
@@ -13594,7 +14659,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
13594
14659
|
}
|
|
13595
14660
|
|
|
13596
14661
|
// src/diff.ts
|
|
13597
|
-
import { promises as
|
|
14662
|
+
import { promises as fs32 } from "fs";
|
|
13598
14663
|
async function loadSnapshotForDiff(target) {
|
|
13599
14664
|
if (/^https?:\/\//i.test(target)) {
|
|
13600
14665
|
const res = await fetch(target);
|
|
@@ -13603,7 +14668,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
13603
14668
|
}
|
|
13604
14669
|
return await res.json();
|
|
13605
14670
|
}
|
|
13606
|
-
const raw = await
|
|
14671
|
+
const raw = await fs32.readFile(target, "utf8");
|
|
13607
14672
|
return JSON.parse(raw);
|
|
13608
14673
|
}
|
|
13609
14674
|
function indexEntries(entries) {
|
|
@@ -13670,23 +14735,23 @@ function canonicalJson(value) {
|
|
|
13670
14735
|
}
|
|
13671
14736
|
|
|
13672
14737
|
// src/projects.ts
|
|
13673
|
-
import
|
|
14738
|
+
import path65 from "path";
|
|
13674
14739
|
function pathsForProject(project, baseDir) {
|
|
13675
14740
|
if (project === DEFAULT_PROJECT) {
|
|
13676
14741
|
return {
|
|
13677
|
-
snapshotPath:
|
|
13678
|
-
errorsPath:
|
|
13679
|
-
staleEventsPath:
|
|
13680
|
-
embeddingsCachePath:
|
|
13681
|
-
policyViolationsPath:
|
|
14742
|
+
snapshotPath: path65.join(baseDir, "graph.json"),
|
|
14743
|
+
errorsPath: path65.join(baseDir, "errors.ndjson"),
|
|
14744
|
+
staleEventsPath: path65.join(baseDir, "stale-events.ndjson"),
|
|
14745
|
+
embeddingsCachePath: path65.join(baseDir, "embeddings.json"),
|
|
14746
|
+
policyViolationsPath: path65.join(baseDir, "policy-violations.ndjson")
|
|
13682
14747
|
};
|
|
13683
14748
|
}
|
|
13684
14749
|
return {
|
|
13685
|
-
snapshotPath:
|
|
13686
|
-
errorsPath:
|
|
13687
|
-
staleEventsPath:
|
|
13688
|
-
embeddingsCachePath:
|
|
13689
|
-
policyViolationsPath:
|
|
14750
|
+
snapshotPath: path65.join(baseDir, `${project}.json`),
|
|
14751
|
+
errorsPath: path65.join(baseDir, `errors.${project}.ndjson`),
|
|
14752
|
+
staleEventsPath: path65.join(baseDir, `stale-events.${project}.ndjson`),
|
|
14753
|
+
embeddingsCachePath: path65.join(baseDir, `embeddings.${project}.json`),
|
|
14754
|
+
policyViolationsPath: path65.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
13690
14755
|
};
|
|
13691
14756
|
}
|
|
13692
14757
|
var Projects = class {
|
|
@@ -13725,9 +14790,9 @@ function parseExtraProjects(raw) {
|
|
|
13725
14790
|
}
|
|
13726
14791
|
|
|
13727
14792
|
// src/registry.ts
|
|
13728
|
-
import { promises as
|
|
14793
|
+
import { promises as fs33 } from "fs";
|
|
13729
14794
|
import os2 from "os";
|
|
13730
|
-
import
|
|
14795
|
+
import path66 from "path";
|
|
13731
14796
|
import {
|
|
13732
14797
|
RegistryFileSchema
|
|
13733
14798
|
} from "@neat.is/types";
|
|
@@ -13735,20 +14800,20 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
13735
14800
|
var LOCK_RETRY_MS = 50;
|
|
13736
14801
|
function neatHome() {
|
|
13737
14802
|
const override = process.env.NEAT_HOME;
|
|
13738
|
-
if (override && override.length > 0) return
|
|
13739
|
-
return
|
|
14803
|
+
if (override && override.length > 0) return path66.resolve(override);
|
|
14804
|
+
return path66.join(os2.homedir(), ".neat");
|
|
13740
14805
|
}
|
|
13741
14806
|
function registryPath() {
|
|
13742
|
-
return
|
|
14807
|
+
return path66.join(neatHome(), "projects.json");
|
|
13743
14808
|
}
|
|
13744
14809
|
function registryLockPath() {
|
|
13745
|
-
return
|
|
14810
|
+
return path66.join(neatHome(), "projects.json.lock");
|
|
13746
14811
|
}
|
|
13747
14812
|
function daemonPidPath() {
|
|
13748
|
-
return
|
|
14813
|
+
return path66.join(neatHome(), "neatd.pid");
|
|
13749
14814
|
}
|
|
13750
14815
|
function daemonsDir() {
|
|
13751
|
-
return
|
|
14816
|
+
return path66.join(neatHome(), "daemons");
|
|
13752
14817
|
}
|
|
13753
14818
|
function isFiniteInt(v) {
|
|
13754
14819
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -13781,7 +14846,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
13781
14846
|
const dir = daemonsDir();
|
|
13782
14847
|
let names;
|
|
13783
14848
|
try {
|
|
13784
|
-
names = await
|
|
14849
|
+
names = await fs33.readdir(dir);
|
|
13785
14850
|
} catch (err) {
|
|
13786
14851
|
if (err.code === "ENOENT") return [];
|
|
13787
14852
|
throw err;
|
|
@@ -13789,10 +14854,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
13789
14854
|
const out = [];
|
|
13790
14855
|
for (const name of names) {
|
|
13791
14856
|
if (!name.endsWith(".json")) continue;
|
|
13792
|
-
const file =
|
|
14857
|
+
const file = path66.join(dir, name);
|
|
13793
14858
|
let raw;
|
|
13794
14859
|
try {
|
|
13795
|
-
raw = await
|
|
14860
|
+
raw = await fs33.readFile(file, "utf8");
|
|
13796
14861
|
} catch {
|
|
13797
14862
|
continue;
|
|
13798
14863
|
}
|
|
@@ -13805,7 +14870,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
13805
14870
|
return out;
|
|
13806
14871
|
}
|
|
13807
14872
|
async function removeDaemonRecord(source) {
|
|
13808
|
-
await
|
|
14873
|
+
await fs33.unlink(source).catch(() => {
|
|
13809
14874
|
});
|
|
13810
14875
|
}
|
|
13811
14876
|
async function listMachineProjects(probe = defaultDiscoveryProbe) {
|
|
@@ -13862,7 +14927,7 @@ function isPidAliveDefault(pid) {
|
|
|
13862
14927
|
}
|
|
13863
14928
|
async function readPidFile(file) {
|
|
13864
14929
|
try {
|
|
13865
|
-
const raw = await
|
|
14930
|
+
const raw = await fs33.readFile(file, "utf8");
|
|
13866
14931
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
13867
14932
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
13868
14933
|
} catch {
|
|
@@ -13910,32 +14975,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
13910
14975
|
}
|
|
13911
14976
|
}
|
|
13912
14977
|
async function normalizeProjectPath(input) {
|
|
13913
|
-
const resolved =
|
|
14978
|
+
const resolved = path66.resolve(input);
|
|
13914
14979
|
try {
|
|
13915
|
-
return await
|
|
14980
|
+
return await fs33.realpath(resolved);
|
|
13916
14981
|
} catch {
|
|
13917
14982
|
return resolved;
|
|
13918
14983
|
}
|
|
13919
14984
|
}
|
|
13920
14985
|
async function writeAtomically(target, contents) {
|
|
13921
|
-
await
|
|
14986
|
+
await fs33.mkdir(path66.dirname(target), { recursive: true });
|
|
13922
14987
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
13923
|
-
const fd = await
|
|
14988
|
+
const fd = await fs33.open(tmp, "w");
|
|
13924
14989
|
try {
|
|
13925
14990
|
await fd.writeFile(contents, "utf8");
|
|
13926
14991
|
await fd.sync();
|
|
13927
14992
|
} finally {
|
|
13928
14993
|
await fd.close();
|
|
13929
14994
|
}
|
|
13930
|
-
await
|
|
14995
|
+
await fs33.rename(tmp, target);
|
|
13931
14996
|
}
|
|
13932
14997
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
13933
14998
|
const deadline = Date.now() + timeoutMs;
|
|
13934
|
-
await
|
|
14999
|
+
await fs33.mkdir(path66.dirname(lockPath), { recursive: true });
|
|
13935
15000
|
let probedHolder = false;
|
|
13936
15001
|
while (true) {
|
|
13937
15002
|
try {
|
|
13938
|
-
const fd = await
|
|
15003
|
+
const fd = await fs33.open(lockPath, "wx");
|
|
13939
15004
|
try {
|
|
13940
15005
|
await fd.writeFile(`${process.pid}
|
|
13941
15006
|
`, "utf8");
|
|
@@ -13960,7 +15025,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
13960
15025
|
}
|
|
13961
15026
|
}
|
|
13962
15027
|
async function releaseLock(lockPath) {
|
|
13963
|
-
await
|
|
15028
|
+
await fs33.unlink(lockPath).catch(() => {
|
|
13964
15029
|
});
|
|
13965
15030
|
}
|
|
13966
15031
|
async function withLock(fn) {
|
|
@@ -13976,7 +15041,7 @@ async function readRegistry() {
|
|
|
13976
15041
|
const file = registryPath();
|
|
13977
15042
|
let raw;
|
|
13978
15043
|
try {
|
|
13979
|
-
raw = await
|
|
15044
|
+
raw = await fs33.readFile(file, "utf8");
|
|
13980
15045
|
} catch (err) {
|
|
13981
15046
|
if (err.code === "ENOENT") {
|
|
13982
15047
|
return { version: 1, projects: [] };
|
|
@@ -14081,7 +15146,7 @@ function pruneTtlMs() {
|
|
|
14081
15146
|
}
|
|
14082
15147
|
async function statPathStatus(p) {
|
|
14083
15148
|
try {
|
|
14084
|
-
const stat = await
|
|
15149
|
+
const stat = await fs33.stat(p);
|
|
14085
15150
|
return stat.isDirectory() ? "present" : "unknown";
|
|
14086
15151
|
} catch (err) {
|
|
14087
15152
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -14126,14 +15191,14 @@ import cors from "@fastify/cors";
|
|
|
14126
15191
|
import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
|
|
14127
15192
|
|
|
14128
15193
|
// src/extend/index.ts
|
|
14129
|
-
import { promises as
|
|
14130
|
-
import
|
|
15194
|
+
import { promises as fs35 } from "fs";
|
|
15195
|
+
import path68 from "path";
|
|
14131
15196
|
import os3 from "os";
|
|
14132
15197
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
14133
15198
|
|
|
14134
15199
|
// src/installers/package-manager.ts
|
|
14135
|
-
import { promises as
|
|
14136
|
-
import
|
|
15200
|
+
import { promises as fs34 } from "fs";
|
|
15201
|
+
import path67 from "path";
|
|
14137
15202
|
import { spawn } from "child_process";
|
|
14138
15203
|
var LOCKFILE_PRIORITY = [
|
|
14139
15204
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -14148,29 +15213,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
14148
15213
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
14149
15214
|
async function exists2(p) {
|
|
14150
15215
|
try {
|
|
14151
|
-
await
|
|
15216
|
+
await fs34.access(p);
|
|
14152
15217
|
return true;
|
|
14153
15218
|
} catch {
|
|
14154
15219
|
return false;
|
|
14155
15220
|
}
|
|
14156
15221
|
}
|
|
14157
15222
|
async function detectPackageManager(serviceDir) {
|
|
14158
|
-
let dir =
|
|
15223
|
+
let dir = path67.resolve(serviceDir);
|
|
14159
15224
|
const stops = /* @__PURE__ */ new Set();
|
|
14160
15225
|
for (let i = 0; i < 64; i++) {
|
|
14161
15226
|
if (stops.has(dir)) break;
|
|
14162
15227
|
stops.add(dir);
|
|
14163
15228
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
14164
|
-
const lockPath =
|
|
15229
|
+
const lockPath = path67.join(dir, candidate.lockfile);
|
|
14165
15230
|
if (await exists2(lockPath)) {
|
|
14166
15231
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
14167
15232
|
}
|
|
14168
15233
|
}
|
|
14169
|
-
const parent =
|
|
15234
|
+
const parent = path67.dirname(dir);
|
|
14170
15235
|
if (parent === dir) break;
|
|
14171
15236
|
dir = parent;
|
|
14172
15237
|
}
|
|
14173
|
-
return { pm: "npm", cwd:
|
|
15238
|
+
return { pm: "npm", cwd: path67.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
14174
15239
|
}
|
|
14175
15240
|
async function runPackageManagerInstall(cmd) {
|
|
14176
15241
|
return new Promise((resolve) => {
|
|
@@ -14212,15 +15277,15 @@ ${err.message}`
|
|
|
14212
15277
|
// src/extend/index.ts
|
|
14213
15278
|
async function fileExists2(p) {
|
|
14214
15279
|
try {
|
|
14215
|
-
await
|
|
15280
|
+
await fs35.access(p);
|
|
14216
15281
|
return true;
|
|
14217
15282
|
} catch {
|
|
14218
15283
|
return false;
|
|
14219
15284
|
}
|
|
14220
15285
|
}
|
|
14221
15286
|
async function readPackageJson(scanPath) {
|
|
14222
|
-
const pkgPath =
|
|
14223
|
-
const raw = await
|
|
15287
|
+
const pkgPath = path68.join(scanPath, "package.json");
|
|
15288
|
+
const raw = await fs35.readFile(pkgPath, "utf8");
|
|
14224
15289
|
return JSON.parse(raw);
|
|
14225
15290
|
}
|
|
14226
15291
|
var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -14234,15 +15299,15 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
14234
15299
|
async function findHookFiles(scanPath) {
|
|
14235
15300
|
const found = [];
|
|
14236
15301
|
const walk9 = async (dir) => {
|
|
14237
|
-
const entries = await
|
|
15302
|
+
const entries = await fs35.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
14238
15303
|
for (const entry of entries) {
|
|
14239
15304
|
if (entry.isDirectory()) {
|
|
14240
15305
|
if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
|
|
14241
|
-
await walk9(
|
|
15306
|
+
await walk9(path68.join(dir, entry.name));
|
|
14242
15307
|
} else if (entry.isFile()) {
|
|
14243
15308
|
if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
|
|
14244
|
-
const rel =
|
|
14245
|
-
found.push(rel.split(
|
|
15309
|
+
const rel = path68.relative(scanPath, path68.join(dir, entry.name));
|
|
15310
|
+
found.push(rel.split(path68.sep).join("/"));
|
|
14246
15311
|
}
|
|
14247
15312
|
}
|
|
14248
15313
|
}
|
|
@@ -14253,7 +15318,7 @@ async function findHookFiles(scanPath) {
|
|
|
14253
15318
|
async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
14254
15319
|
let fallback = null;
|
|
14255
15320
|
for (const file of hookFiles) {
|
|
14256
|
-
const content = await
|
|
15321
|
+
const content = await fs35.readFile(path68.join(scanPath, file), "utf8");
|
|
14257
15322
|
const patched = splicedContent(content, snippet2);
|
|
14258
15323
|
if (patched !== null) return { file, content, patched };
|
|
14259
15324
|
if (fallback === null) fallback = { file, content };
|
|
@@ -14261,12 +15326,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
|
14261
15326
|
return { file: fallback.file, content: fallback.content, patched: null };
|
|
14262
15327
|
}
|
|
14263
15328
|
function extendLogPath() {
|
|
14264
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
15329
|
+
return process.env.NEAT_EXTEND_LOG ?? path68.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
14265
15330
|
}
|
|
14266
15331
|
async function appendExtendLog(entry) {
|
|
14267
15332
|
const logPath = extendLogPath();
|
|
14268
|
-
await
|
|
14269
|
-
await
|
|
15333
|
+
await fs35.mkdir(path68.dirname(logPath), { recursive: true });
|
|
15334
|
+
await fs35.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
14270
15335
|
}
|
|
14271
15336
|
function splicedContent(fileContent, snippet2) {
|
|
14272
15337
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -14324,7 +15389,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
14324
15389
|
}
|
|
14325
15390
|
async function describeProjectInstrumentation(ctx) {
|
|
14326
15391
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
14327
|
-
const envNeat = await fileExists2(
|
|
15392
|
+
const envNeat = await fileExists2(path68.join(ctx.scanPath, ".env.neat"));
|
|
14328
15393
|
const registryInstrPackages = new Set(
|
|
14329
15394
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
14330
15395
|
);
|
|
@@ -14346,7 +15411,7 @@ async function applyExtension(ctx, args, options) {
|
|
|
14346
15411
|
);
|
|
14347
15412
|
}
|
|
14348
15413
|
for (const file of hookFiles) {
|
|
14349
|
-
const content = await
|
|
15414
|
+
const content = await fs35.readFile(path68.join(ctx.scanPath, file), "utf8");
|
|
14350
15415
|
if (content.includes(args.registration_snippet)) {
|
|
14351
15416
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
14352
15417
|
}
|
|
@@ -14358,18 +15423,18 @@ async function applyExtension(ctx, args, options) {
|
|
|
14358
15423
|
);
|
|
14359
15424
|
}
|
|
14360
15425
|
const primaryFile = primary.file;
|
|
14361
|
-
const primaryPath =
|
|
15426
|
+
const primaryPath = path68.join(ctx.scanPath, primaryFile);
|
|
14362
15427
|
const filesTouched = [];
|
|
14363
15428
|
const depsAdded = [];
|
|
14364
|
-
const pkgPath =
|
|
15429
|
+
const pkgPath = path68.join(ctx.scanPath, "package.json");
|
|
14365
15430
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
14366
15431
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
14367
15432
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
14368
|
-
await
|
|
15433
|
+
await fs35.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
14369
15434
|
filesTouched.push("package.json");
|
|
14370
15435
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
14371
15436
|
}
|
|
14372
|
-
await
|
|
15437
|
+
await fs35.writeFile(primaryPath, primary.patched, "utf8");
|
|
14373
15438
|
filesTouched.push(primaryFile);
|
|
14374
15439
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
14375
15440
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -14400,7 +15465,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
14400
15465
|
};
|
|
14401
15466
|
}
|
|
14402
15467
|
for (const file of hookFiles) {
|
|
14403
|
-
const content = await
|
|
15468
|
+
const content = await fs35.readFile(path68.join(ctx.scanPath, file), "utf8");
|
|
14404
15469
|
if (content.includes(args.registration_snippet)) {
|
|
14405
15470
|
return {
|
|
14406
15471
|
library: args.library,
|
|
@@ -14435,28 +15500,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
14435
15500
|
if (!await fileExists2(logPath)) {
|
|
14436
15501
|
return { undone: false, message: "no apply found for library" };
|
|
14437
15502
|
}
|
|
14438
|
-
const raw = await
|
|
15503
|
+
const raw = await fs35.readFile(logPath, "utf8");
|
|
14439
15504
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
14440
15505
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
14441
15506
|
if (!match) {
|
|
14442
15507
|
return { undone: false, message: "no apply found for library" };
|
|
14443
15508
|
}
|
|
14444
|
-
const pkgPath =
|
|
15509
|
+
const pkgPath = path68.join(ctx.scanPath, "package.json");
|
|
14445
15510
|
if (await fileExists2(pkgPath)) {
|
|
14446
15511
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
14447
15512
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
14448
15513
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
14449
15514
|
pkg.dependencies = rest;
|
|
14450
|
-
await
|
|
15515
|
+
await fs35.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
14451
15516
|
}
|
|
14452
15517
|
}
|
|
14453
15518
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
14454
15519
|
for (const file of hookFiles) {
|
|
14455
|
-
const filePath =
|
|
14456
|
-
const content = await
|
|
15520
|
+
const filePath = path68.join(ctx.scanPath, file);
|
|
15521
|
+
const content = await fs35.readFile(filePath, "utf8");
|
|
14457
15522
|
if (content.includes(match.registration_snippet)) {
|
|
14458
15523
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
14459
|
-
await
|
|
15524
|
+
await fs35.writeFile(filePath, filtered, "utf8");
|
|
14460
15525
|
break;
|
|
14461
15526
|
}
|
|
14462
15527
|
}
|
|
@@ -14567,8 +15632,8 @@ data: ${JSON.stringify(envelope.payload)}
|
|
|
14567
15632
|
|
|
14568
15633
|
// src/connectors-config.ts
|
|
14569
15634
|
import os4 from "os";
|
|
14570
|
-
import
|
|
14571
|
-
import { promises as
|
|
15635
|
+
import path69 from "path";
|
|
15636
|
+
import { promises as fs36 } from "fs";
|
|
14572
15637
|
var CONNECTORS_CONFIG_VERSION = 1;
|
|
14573
15638
|
var EnvRefUnsetError = class extends Error {
|
|
14574
15639
|
ref;
|
|
@@ -14582,17 +15647,17 @@ var EnvRefUnsetError = class extends Error {
|
|
|
14582
15647
|
};
|
|
14583
15648
|
function neatHome2() {
|
|
14584
15649
|
const override = process.env.NEAT_HOME;
|
|
14585
|
-
if (override && override.length > 0) return
|
|
14586
|
-
return
|
|
15650
|
+
if (override && override.length > 0) return path69.resolve(override);
|
|
15651
|
+
return path69.join(os4.homedir(), ".neat");
|
|
14587
15652
|
}
|
|
14588
15653
|
function connectorsConfigPath(home = neatHome2()) {
|
|
14589
|
-
return
|
|
15654
|
+
return path69.join(home, "connectors.json");
|
|
14590
15655
|
}
|
|
14591
15656
|
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
14592
15657
|
async function warnIfModeLooserThan0600(file) {
|
|
14593
15658
|
if (process.platform === "win32") return;
|
|
14594
15659
|
try {
|
|
14595
|
-
const stat = await
|
|
15660
|
+
const stat = await fs36.stat(file);
|
|
14596
15661
|
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
14597
15662
|
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
14598
15663
|
console.warn(
|
|
@@ -14606,7 +15671,7 @@ async function readConnectorsConfig(home = neatHome2()) {
|
|
|
14606
15671
|
const file = connectorsConfigPath(home);
|
|
14607
15672
|
let raw;
|
|
14608
15673
|
try {
|
|
14609
|
-
raw = await
|
|
15674
|
+
raw = await fs36.readFile(file, "utf8");
|
|
14610
15675
|
} catch (err) {
|
|
14611
15676
|
if (err.code === "ENOENT") {
|
|
14612
15677
|
return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
|
|
@@ -14717,7 +15782,7 @@ function connectorMatchesProject(entry, project) {
|
|
|
14717
15782
|
var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
|
|
14718
15783
|
var CONNECTORS_LOCK_RETRY_MS = 50;
|
|
14719
15784
|
function connectorsConfigLockPath(home = neatHome2()) {
|
|
14720
|
-
return
|
|
15785
|
+
return path69.join(home, "connectors.json.lock");
|
|
14721
15786
|
}
|
|
14722
15787
|
function isEnvRef(value) {
|
|
14723
15788
|
return value.length > 1 && value.startsWith("$");
|
|
@@ -14730,9 +15795,9 @@ function redactCredentialRef(ref) {
|
|
|
14730
15795
|
return out;
|
|
14731
15796
|
}
|
|
14732
15797
|
async function writeConfigAtomically0600(file, contents) {
|
|
14733
|
-
await
|
|
15798
|
+
await fs36.mkdir(path69.dirname(file), { recursive: true });
|
|
14734
15799
|
const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
14735
|
-
const fd = await
|
|
15800
|
+
const fd = await fs36.open(tmp, "w", 384);
|
|
14736
15801
|
try {
|
|
14737
15802
|
await fd.writeFile(contents, "utf8");
|
|
14738
15803
|
await fd.chmod(384);
|
|
@@ -14740,14 +15805,14 @@ async function writeConfigAtomically0600(file, contents) {
|
|
|
14740
15805
|
} finally {
|
|
14741
15806
|
await fd.close();
|
|
14742
15807
|
}
|
|
14743
|
-
await
|
|
15808
|
+
await fs36.rename(tmp, file);
|
|
14744
15809
|
}
|
|
14745
15810
|
async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
|
|
14746
15811
|
const deadline = Date.now() + timeoutMs;
|
|
14747
|
-
await
|
|
15812
|
+
await fs36.mkdir(path69.dirname(lockPath), { recursive: true });
|
|
14748
15813
|
for (; ; ) {
|
|
14749
15814
|
try {
|
|
14750
|
-
const fd = await
|
|
15815
|
+
const fd = await fs36.open(lockPath, "wx");
|
|
14751
15816
|
try {
|
|
14752
15817
|
await fd.writeFile(`${process.pid}
|
|
14753
15818
|
`, "utf8");
|
|
@@ -14767,7 +15832,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
|
|
|
14767
15832
|
}
|
|
14768
15833
|
}
|
|
14769
15834
|
async function releaseConnectorsLock(lockPath) {
|
|
14770
|
-
await
|
|
15835
|
+
await fs36.unlink(lockPath).catch(() => {
|
|
14771
15836
|
});
|
|
14772
15837
|
}
|
|
14773
15838
|
async function withConnectorsLock(home, fn) {
|
|
@@ -14892,7 +15957,7 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
|
|
|
14892
15957
|
}
|
|
14893
15958
|
|
|
14894
15959
|
// src/connectors/index.ts
|
|
14895
|
-
import { NodeType as
|
|
15960
|
+
import { NodeType as NodeType32, parseFileId as parseFileId2, Provenance as Provenance26 } from "@neat.is/types";
|
|
14896
15961
|
var NO_ENV = "unknown";
|
|
14897
15962
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
14898
15963
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
@@ -14911,7 +15976,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
14911
15976
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
14912
15977
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
14913
15978
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
14914
|
-
if (attrs.type !==
|
|
15979
|
+
if (attrs.type !== NodeType32.RouteNode || !attrs.path) return void 0;
|
|
14915
15980
|
const site = { relPath: attrs.path };
|
|
14916
15981
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
14917
15982
|
return site;
|
|
@@ -15396,10 +16461,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
15396
16461
|
// src/connectors/supabase/map.ts
|
|
15397
16462
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
15398
16463
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
15399
|
-
function targetFromRestPath(
|
|
15400
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
16464
|
+
function targetFromRestPath(path70) {
|
|
16465
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path70);
|
|
15401
16466
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
15402
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
16467
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path70);
|
|
15403
16468
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
15404
16469
|
return null;
|
|
15405
16470
|
}
|
|
@@ -15616,7 +16681,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
15616
16681
|
}
|
|
15617
16682
|
|
|
15618
16683
|
// src/connectors/railway/index.ts
|
|
15619
|
-
import { EdgeType as EdgeType27, NodeType as
|
|
16684
|
+
import { EdgeType as EdgeType27, NodeType as NodeType33, serviceId as serviceId14 } from "@neat.is/types";
|
|
15620
16685
|
|
|
15621
16686
|
// src/connectors/railway/client.ts
|
|
15622
16687
|
var DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
|
|
@@ -15766,7 +16831,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
15766
16831
|
const out = [];
|
|
15767
16832
|
graph.forEachNode((_id, attrs) => {
|
|
15768
16833
|
const node = attrs;
|
|
15769
|
-
if (node.type !==
|
|
16834
|
+
if (node.type !== NodeType33.RouteNode) return;
|
|
15770
16835
|
const route = attrs;
|
|
15771
16836
|
if (route.service !== serviceName) return;
|
|
15772
16837
|
out.push({
|
|
@@ -15875,7 +16940,7 @@ function createRailwayResolveTarget(config) {
|
|
|
15875
16940
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
15876
16941
|
const peerName = config.serviceNameById[signal.targetName];
|
|
15877
16942
|
if (!peerName) return null;
|
|
15878
|
-
return { targetNodeId:
|
|
16943
|
+
return { targetNodeId: serviceId14(peerName), serviceName, edgeType: EdgeType27.CONNECTS_TO };
|
|
15879
16944
|
}
|
|
15880
16945
|
return null;
|
|
15881
16946
|
};
|
|
@@ -15994,9 +17059,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
15994
17059
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
15995
17060
|
if (secondSep === -1) return null;
|
|
15996
17061
|
const method = rest.slice(0, secondSep);
|
|
15997
|
-
const
|
|
15998
|
-
if (!resourceName || !method || !
|
|
15999
|
-
return { resourceName, method, path:
|
|
17062
|
+
const path70 = rest.slice(secondSep + 1);
|
|
17063
|
+
if (!resourceName || !method || !path70) return null;
|
|
17064
|
+
return { resourceName, method, path: path70 };
|
|
16000
17065
|
}
|
|
16001
17066
|
function resourceNameFor(type, labels) {
|
|
16002
17067
|
if (!labels) return null;
|
|
@@ -16034,14 +17099,14 @@ function mapLogEntryToSignal(entry) {
|
|
|
16034
17099
|
if (!req) return null;
|
|
16035
17100
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
16036
17101
|
const method = req.requestMethod.toUpperCase();
|
|
16037
|
-
const
|
|
16038
|
-
if (
|
|
17102
|
+
const path70 = pathFromRequestUrl(req.requestUrl);
|
|
17103
|
+
if (path70 === null) return null;
|
|
16039
17104
|
const timestamp = entry.timestamp;
|
|
16040
17105
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
16041
17106
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
16042
17107
|
return {
|
|
16043
17108
|
targetKind: resourceType,
|
|
16044
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
17109
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path70 }),
|
|
16045
17110
|
callCount: 1,
|
|
16046
17111
|
errorCount: isError ? 1 : 0,
|
|
16047
17112
|
lastObservedIso: timestamp
|
|
@@ -16057,7 +17122,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
16057
17122
|
}
|
|
16058
17123
|
|
|
16059
17124
|
// src/connectors/firebase/resolve.ts
|
|
16060
|
-
import { NodeType as
|
|
17125
|
+
import { NodeType as NodeType34, EdgeType as EdgeType28 } from "@neat.is/types";
|
|
16061
17126
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
16062
17127
|
switch (resourceType) {
|
|
16063
17128
|
case "cloud_function":
|
|
@@ -16072,7 +17137,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
16072
17137
|
const entries = [];
|
|
16073
17138
|
graph.forEachNode((_id, attrs) => {
|
|
16074
17139
|
const node = attrs;
|
|
16075
|
-
if (node.type !==
|
|
17140
|
+
if (node.type !== NodeType34.RouteNode) return;
|
|
16076
17141
|
const route = attrs;
|
|
16077
17142
|
if (route.service !== serviceName) return;
|
|
16078
17143
|
entries.push({
|
|
@@ -16127,7 +17192,7 @@ function createFirebaseConnector(graph, serviceMap) {
|
|
|
16127
17192
|
}
|
|
16128
17193
|
|
|
16129
17194
|
// src/connectors/cloudflare/connector.ts
|
|
16130
|
-
import { EdgeType as EdgeType29, NodeType as
|
|
17195
|
+
import { EdgeType as EdgeType29, NodeType as NodeType35, fileId as fileId5, infraId as infraId21 } from "@neat.is/types";
|
|
16131
17196
|
|
|
16132
17197
|
// src/connectors/cloudflare/client.ts
|
|
16133
17198
|
import { randomUUID } from "crypto";
|
|
@@ -16238,7 +17303,7 @@ function mapEventToSignal(event) {
|
|
|
16238
17303
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
16239
17304
|
const statusCode = metadata?.statusCode;
|
|
16240
17305
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
16241
|
-
const
|
|
17306
|
+
const path70 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
16242
17307
|
return {
|
|
16243
17308
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
16244
17309
|
targetName: scriptName,
|
|
@@ -16246,7 +17311,7 @@ function mapEventToSignal(event) {
|
|
|
16246
17311
|
errorCount: isError ? 1 : 0,
|
|
16247
17312
|
lastObservedIso: observedAt.toISOString(),
|
|
16248
17313
|
method,
|
|
16249
|
-
...
|
|
17314
|
+
...path70 ? { path: path70 } : {},
|
|
16250
17315
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
16251
17316
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
16252
17317
|
};
|
|
@@ -16286,19 +17351,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
16286
17351
|
graph.forEachNode((id, attrs) => {
|
|
16287
17352
|
if (found) return;
|
|
16288
17353
|
const a = attrs;
|
|
16289
|
-
if (a.type ===
|
|
17354
|
+
if (a.type === NodeType35.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
16290
17355
|
found = id;
|
|
16291
17356
|
}
|
|
16292
17357
|
});
|
|
16293
17358
|
return found;
|
|
16294
17359
|
}
|
|
16295
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
16296
|
-
const normalizedPath = normalizePathTemplate(
|
|
17360
|
+
function findMatchingRouteNode(graph, serviceName, method, path70) {
|
|
17361
|
+
const normalizedPath = normalizePathTemplate(path70);
|
|
16297
17362
|
let found = null;
|
|
16298
17363
|
graph.forEachNode((id, attrs) => {
|
|
16299
17364
|
if (found) return;
|
|
16300
17365
|
const a = attrs;
|
|
16301
|
-
if (a.type !==
|
|
17366
|
+
if (a.type !== NodeType35.RouteNode || a.service !== serviceName) return;
|
|
16302
17367
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
16303
17368
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
16304
17369
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -16310,10 +17375,10 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
16310
17375
|
return (signal) => {
|
|
16311
17376
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
16312
17377
|
const scriptName = signal.targetName;
|
|
16313
|
-
const { method, path:
|
|
17378
|
+
const { method, path: path70 } = signal;
|
|
16314
17379
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
16315
|
-
if (!method || !
|
|
16316
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
17380
|
+
if (!method || !path70) return wholeFileId;
|
|
17381
|
+
return findMatchingRouteNode(graph, serviceName, method, path70) ?? wholeFileId;
|
|
16317
17382
|
};
|
|
16318
17383
|
const mapping = config.workers?.[scriptName];
|
|
16319
17384
|
if (mapping) {
|
|
@@ -16643,9 +17708,9 @@ function parseCloudRunTargetName(targetName) {
|
|
|
16643
17708
|
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
16644
17709
|
if (secondSep === -1) return null;
|
|
16645
17710
|
const method = rest.slice(0, secondSep);
|
|
16646
|
-
const
|
|
16647
|
-
if (!serviceName || !method || !
|
|
16648
|
-
return { serviceName, method, path:
|
|
17711
|
+
const path70 = rest.slice(secondSep + 1);
|
|
17712
|
+
if (!serviceName || !method || !path70) return null;
|
|
17713
|
+
return { serviceName, method, path: path70 };
|
|
16649
17714
|
}
|
|
16650
17715
|
|
|
16651
17716
|
// src/connectors/cloud-run/map.ts
|
|
@@ -16674,14 +17739,14 @@ function mapLogEntryToSignal2(entry) {
|
|
|
16674
17739
|
if (!req) return null;
|
|
16675
17740
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
16676
17741
|
const method = req.requestMethod.toUpperCase();
|
|
16677
|
-
const
|
|
16678
|
-
if (
|
|
17742
|
+
const path70 = pathFromRequestUrl2(req.requestUrl);
|
|
17743
|
+
if (path70 === null) return null;
|
|
16679
17744
|
const timestamp = entry.timestamp;
|
|
16680
17745
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
16681
17746
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
16682
17747
|
return {
|
|
16683
17748
|
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
16684
|
-
targetName: packCloudRunTargetName({ serviceName, method, path:
|
|
17749
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path70 }),
|
|
16685
17750
|
callCount: 1,
|
|
16686
17751
|
errorCount: isError ? 1 : 0,
|
|
16687
17752
|
lastObservedIso: timestamp
|
|
@@ -16697,14 +17762,14 @@ function mapLogEntriesToSignals2(entries) {
|
|
|
16697
17762
|
}
|
|
16698
17763
|
|
|
16699
17764
|
// src/connectors/cloud-run/resolve.ts
|
|
16700
|
-
import { EdgeType as EdgeType31, NodeType as
|
|
17765
|
+
import { EdgeType as EdgeType31, NodeType as NodeType36, infraId as infraId23 } from "@neat.is/types";
|
|
16701
17766
|
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
16702
17767
|
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
16703
17768
|
let found = null;
|
|
16704
17769
|
graph.forEachNode((_id, attrs) => {
|
|
16705
17770
|
if (found) return;
|
|
16706
17771
|
const node = attrs;
|
|
16707
|
-
if (node.type !==
|
|
17772
|
+
if (node.type !== NodeType36.RouteNode) return;
|
|
16708
17773
|
const route = attrs;
|
|
16709
17774
|
if (route.service !== serviceName || !route.pathTemplate) return;
|
|
16710
17775
|
if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
|
|
@@ -16719,14 +17784,14 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
16719
17784
|
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
16720
17785
|
const identity = parseCloudRunTargetName(signal.targetName);
|
|
16721
17786
|
if (!identity) return null;
|
|
16722
|
-
const { serviceName: gcpServiceName, method, path:
|
|
17787
|
+
const { serviceName: gcpServiceName, method, path: path70 } = identity;
|
|
16723
17788
|
const mappedService = config.serviceMap?.[gcpServiceName];
|
|
16724
17789
|
if (mappedService) {
|
|
16725
17790
|
const routeNodeId = findMatchingRouteNode2(
|
|
16726
17791
|
graph,
|
|
16727
17792
|
mappedService,
|
|
16728
17793
|
method,
|
|
16729
|
-
normalizePathTemplate(
|
|
17794
|
+
normalizePathTemplate(path70)
|
|
16730
17795
|
);
|
|
16731
17796
|
if (routeNodeId) {
|
|
16732
17797
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
|
|
@@ -16775,7 +17840,7 @@ function createCloudRunConnector(graph, config = {}) {
|
|
|
16775
17840
|
}
|
|
16776
17841
|
|
|
16777
17842
|
// src/connectors/render/index.ts
|
|
16778
|
-
import { EdgeType as EdgeType32, NodeType as
|
|
17843
|
+
import { EdgeType as EdgeType32, NodeType as NodeType37 } from "@neat.is/types";
|
|
16779
17844
|
|
|
16780
17845
|
// src/connectors/render/types.ts
|
|
16781
17846
|
function readRenderToken(credentials) {
|
|
@@ -16851,7 +17916,7 @@ function buildRenderRouteIndex(graph, serviceName) {
|
|
|
16851
17916
|
const out = [];
|
|
16852
17917
|
graph.forEachNode((_id, attrs) => {
|
|
16853
17918
|
const node = attrs;
|
|
16854
|
-
if (node.type !==
|
|
17919
|
+
if (node.type !== NodeType37.RouteNode) return;
|
|
16855
17920
|
const route = attrs;
|
|
16856
17921
|
if (route.service !== serviceName) return;
|
|
16857
17922
|
out.push({
|
|
@@ -17335,7 +18400,7 @@ function mapBuildsToSignals(builds, serviceName) {
|
|
|
17335
18400
|
}
|
|
17336
18401
|
|
|
17337
18402
|
// src/connectors/eas/resolve.ts
|
|
17338
|
-
import { EdgeType as EdgeType34, NodeType as
|
|
18403
|
+
import { EdgeType as EdgeType34, NodeType as NodeType38, parseFileId as parseFileId3 } from "@neat.is/types";
|
|
17339
18404
|
var NO_ENV2 = "unknown";
|
|
17340
18405
|
var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
|
|
17341
18406
|
var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
|
|
@@ -17363,7 +18428,7 @@ function findConfigNode(graph, basenames, serviceName) {
|
|
|
17363
18428
|
graph.forEachNode((id, attrs) => {
|
|
17364
18429
|
if (scoped) return;
|
|
17365
18430
|
const node = attrs;
|
|
17366
|
-
if (node.type !==
|
|
18431
|
+
if (node.type !== NodeType38.ConfigNode) return;
|
|
17367
18432
|
if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
|
|
17368
18433
|
if (anyMatch === null) anyMatch = id;
|
|
17369
18434
|
if (configNodeService(graph, id) === serviceName) scoped = id;
|
|
@@ -18975,4 +20040,4 @@ export {
|
|
|
18975
20040
|
deprovisionConnector,
|
|
18976
20041
|
buildApi
|
|
18977
20042
|
};
|
|
18978
|
-
//# sourceMappingURL=chunk-
|
|
20043
|
+
//# sourceMappingURL=chunk-RDLDSIEY.js.map
|