@neat.is/core 0.8.2-dev.20260818 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-T4UGEMWN.js → chunk-RDLDSIEY.js} +958 -494
- package/dist/chunk-RDLDSIEY.js.map +1 -0
- package/dist/{chunk-WLCHTXO3.js → chunk-WMYVJXUF.js} +2 -2
- package/dist/cli.cjs +1538 -1071
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +1222 -755
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +1235 -768
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +1124 -657
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +5 -2
- package/dist/chunk-T4UGEMWN.js.map +0 -1
- /package/dist/{chunk-WLCHTXO3.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", ".cs", ".java"]);
|
|
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 {
|
|
@@ -1305,6 +1307,19 @@ function languageForPath(relPath) {
|
|
|
1305
1307
|
return "csharp";
|
|
1306
1308
|
case ".java":
|
|
1307
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";
|
|
1308
1323
|
case ".ts":
|
|
1309
1324
|
case ".tsx":
|
|
1310
1325
|
return "typescript";
|
|
@@ -1643,6 +1658,7 @@ async function resolveGoImport(specifier, modulePath, serviceDir) {
|
|
|
1643
1658
|
function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
|
|
1644
1659
|
const importeeFileId = fileId2(serviceName, importeeRelPath);
|
|
1645
1660
|
if (!graph.hasNode(importeeFileId)) return 0;
|
|
1661
|
+
if (importerFileId === importeeFileId) return 0;
|
|
1646
1662
|
const edgeId = extractedEdgeId3(importerFileId, importeeFileId, EdgeType3.IMPORTS);
|
|
1647
1663
|
if (graph.hasEdge(edgeId)) return 0;
|
|
1648
1664
|
const edge = {
|
|
@@ -1664,7 +1680,7 @@ async function addImports(graph, services) {
|
|
|
1664
1680
|
let edgesAdded = 0;
|
|
1665
1681
|
for (const service of services) {
|
|
1666
1682
|
const tsPaths = await loadTsPathConfig(service.dir);
|
|
1667
|
-
const files = await loadSourceFiles(service.dir);
|
|
1683
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
1668
1684
|
for (const file of files) {
|
|
1669
1685
|
if (isTestPath(file.path)) continue;
|
|
1670
1686
|
const relFile = toPosix(path6.relative(service.dir, file.path));
|
|
@@ -1846,8 +1862,8 @@ function chiRoutesFromSource(source, parser) {
|
|
|
1846
1862
|
chiWalk(tree.rootNode, "", out);
|
|
1847
1863
|
return out;
|
|
1848
1864
|
}
|
|
1849
|
-
function stripChiRegex(
|
|
1850
|
-
return
|
|
1865
|
+
function stripChiRegex(path70) {
|
|
1866
|
+
return path70.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
|
|
1851
1867
|
}
|
|
1852
1868
|
function chiWalk(node, prefix, out) {
|
|
1853
1869
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -2519,9 +2535,9 @@ function rubyRocketRoute(args) {
|
|
|
2519
2535
|
if (!pair || pair.type !== "pair") continue;
|
|
2520
2536
|
const k = pair.childForFieldName("key");
|
|
2521
2537
|
if (k?.type !== "string") continue;
|
|
2522
|
-
const
|
|
2523
|
-
if (
|
|
2524
|
-
return { path:
|
|
2538
|
+
const path70 = rubyLiteral(k);
|
|
2539
|
+
if (path70 === null) continue;
|
|
2540
|
+
return { path: path70, target: rubyLiteral(pair.childForFieldName("value")) };
|
|
2525
2541
|
}
|
|
2526
2542
|
return null;
|
|
2527
2543
|
}
|
|
@@ -3206,7 +3222,7 @@ async function addRoutes(graph, services) {
|
|
|
3206
3222
|
const hasLaravel = deps["laravel/framework"] !== void 0;
|
|
3207
3223
|
if (!hasExpress && !hasFastify && !hasHono && !hasNext && !hasNestjs && !hasFastapi && !hasFlask && !hasDjango && !hasGin && !hasEcho && !hasFiber && !hasChi && !isGoService && !hasRails && !hasLaravel)
|
|
3208
3224
|
continue;
|
|
3209
|
-
const files = await loadSourceFiles(service.dir);
|
|
3225
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
3210
3226
|
const mountPrefixes = hasExpress ? await expressMountPrefixes(files, service.dir, await loadTsPathConfig(service.dir)) : /* @__PURE__ */ new Map();
|
|
3211
3227
|
for (const file of files) {
|
|
3212
3228
|
if (isTestPath(file.path)) continue;
|
|
@@ -4638,29 +4654,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
4638
4654
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
4639
4655
|
});
|
|
4640
4656
|
let promoted = 0;
|
|
4641
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
4657
|
+
for (const { frontierId: frontierId2, serviceId: serviceId15 } of toPromote) {
|
|
4642
4658
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
4643
4659
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
4644
4660
|
if (!gate.allowed) {
|
|
4645
4661
|
continue;
|
|
4646
4662
|
}
|
|
4647
4663
|
}
|
|
4648
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
4664
|
+
rewireFrontierEdges(graph, frontierId2, serviceId15);
|
|
4649
4665
|
graph.dropNode(frontierId2);
|
|
4650
4666
|
promoted++;
|
|
4651
4667
|
}
|
|
4652
4668
|
return promoted;
|
|
4653
4669
|
}
|
|
4654
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
4670
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId15) {
|
|
4655
4671
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
4656
4672
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
4657
4673
|
for (const edgeId of inbound) {
|
|
4658
4674
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4659
|
-
rebuildEdge(graph, edge, edge.source,
|
|
4675
|
+
rebuildEdge(graph, edge, edge.source, serviceId15, edgeId);
|
|
4660
4676
|
}
|
|
4661
4677
|
for (const edgeId of outbound) {
|
|
4662
4678
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4663
|
-
rebuildEdge(graph, edge,
|
|
4679
|
+
rebuildEdge(graph, edge, serviceId15, edge.target, edgeId);
|
|
4664
4680
|
}
|
|
4665
4681
|
}
|
|
4666
4682
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -4992,19 +5008,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
4992
5008
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
4993
5009
|
let best = { path: [start], edges: [] };
|
|
4994
5010
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
4995
|
-
function step(node,
|
|
4996
|
-
if (
|
|
4997
|
-
best = { path: [...
|
|
5011
|
+
function step(node, path70, edges) {
|
|
5012
|
+
if (path70.length > best.path.length) {
|
|
5013
|
+
best = { path: [...path70], edges: [...edges] };
|
|
4998
5014
|
}
|
|
4999
|
-
if (
|
|
5015
|
+
if (path70.length - 1 >= maxDepth) return;
|
|
5000
5016
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
5001
5017
|
for (const [srcId, edge] of incoming) {
|
|
5002
5018
|
if (visited.has(srcId)) continue;
|
|
5003
5019
|
visited.add(srcId);
|
|
5004
|
-
|
|
5020
|
+
path70.push(srcId);
|
|
5005
5021
|
edges.push(edge);
|
|
5006
|
-
step(srcId,
|
|
5007
|
-
|
|
5022
|
+
step(srcId, path70, edges);
|
|
5023
|
+
path70.pop();
|
|
5008
5024
|
edges.pop();
|
|
5009
5025
|
visited.delete(srcId);
|
|
5010
5026
|
}
|
|
@@ -5019,7 +5035,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
|
|
|
5019
5035
|
for (const id of walk9.path) {
|
|
5020
5036
|
const owner = resolveOwningService(graph, id);
|
|
5021
5037
|
if (!owner) continue;
|
|
5022
|
-
const { id:
|
|
5038
|
+
const { id: serviceId15, svc } = owner;
|
|
5023
5039
|
const deps = svc.dependencies ?? {};
|
|
5024
5040
|
for (const pair of candidatePairs) {
|
|
5025
5041
|
const declared = deps[pair.driver];
|
|
@@ -5032,7 +5048,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
|
|
|
5032
5048
|
);
|
|
5033
5049
|
if (!result.compatible) {
|
|
5034
5050
|
return {
|
|
5035
|
-
rootCauseNode:
|
|
5051
|
+
rootCauseNode: serviceId15,
|
|
5036
5052
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
5037
5053
|
...result.minDriverVersion ? {
|
|
5038
5054
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -5047,7 +5063,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
|
|
|
5047
5063
|
for (const id of walk9.path) {
|
|
5048
5064
|
const owner = resolveOwningService(graph, id);
|
|
5049
5065
|
if (!owner) continue;
|
|
5050
|
-
const { id:
|
|
5066
|
+
const { id: serviceId15, svc } = owner;
|
|
5051
5067
|
const deps = svc.dependencies ?? {};
|
|
5052
5068
|
const serviceNodeEngine = svc.nodeEngine;
|
|
5053
5069
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -5056,7 +5072,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
|
|
|
5056
5072
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
5057
5073
|
if (!result.compatible && result.reason) {
|
|
5058
5074
|
return {
|
|
5059
|
-
rootCauseNode:
|
|
5075
|
+
rootCauseNode: serviceId15,
|
|
5060
5076
|
rootCauseReason: result.reason,
|
|
5061
5077
|
...result.requiredNodeVersion ? {
|
|
5062
5078
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -5071,7 +5087,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
|
|
|
5071
5087
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
5072
5088
|
if (!result.compatible && result.reason) {
|
|
5073
5089
|
return {
|
|
5074
|
-
rootCauseNode:
|
|
5090
|
+
rootCauseNode: serviceId15,
|
|
5075
5091
|
rootCauseReason: result.reason,
|
|
5076
5092
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
5077
5093
|
};
|
|
@@ -5172,9 +5188,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
5172
5188
|
function isFailingCallEdge(e) {
|
|
5173
5189
|
return e.type === EdgeType6.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
5174
5190
|
}
|
|
5175
|
-
function callSourcesForService(graph,
|
|
5176
|
-
const ids = [
|
|
5177
|
-
for (const edgeId of graph.outboundEdges(
|
|
5191
|
+
function callSourcesForService(graph, serviceId15) {
|
|
5192
|
+
const ids = [serviceId15];
|
|
5193
|
+
for (const edgeId of graph.outboundEdges(serviceId15)) {
|
|
5178
5194
|
const e = graph.getEdgeAttributes(edgeId);
|
|
5179
5195
|
if (e.type !== EdgeType6.CONTAINS) continue;
|
|
5180
5196
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -5198,9 +5214,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
5198
5214
|
}
|
|
5199
5215
|
return id < curId;
|
|
5200
5216
|
}
|
|
5201
|
-
function dominantFailingCall(graph,
|
|
5217
|
+
function dominantFailingCall(graph, serviceId15, visited) {
|
|
5202
5218
|
let best = null;
|
|
5203
|
-
for (const src of callSourcesForService(graph,
|
|
5219
|
+
for (const src of callSourcesForService(graph, serviceId15)) {
|
|
5204
5220
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
5205
5221
|
const e = graph.getEdgeAttributes(edgeId);
|
|
5206
5222
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -5215,26 +5231,26 @@ function dominantFailingCall(graph, serviceId12, visited) {
|
|
|
5215
5231
|
return best;
|
|
5216
5232
|
}
|
|
5217
5233
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
5218
|
-
const
|
|
5234
|
+
const path70 = [originServiceId];
|
|
5219
5235
|
const edges = [];
|
|
5220
5236
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
5221
5237
|
let current = originServiceId;
|
|
5222
5238
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
5223
5239
|
const hop = dominantFailingCall(graph, current, visited);
|
|
5224
5240
|
if (!hop) break;
|
|
5225
|
-
|
|
5241
|
+
path70.push(hop.nextService);
|
|
5226
5242
|
edges.push(hop.edge);
|
|
5227
5243
|
visited.add(hop.nextService);
|
|
5228
5244
|
current = hop.nextService;
|
|
5229
5245
|
}
|
|
5230
5246
|
if (edges.length === 0) return null;
|
|
5231
|
-
return { path:
|
|
5247
|
+
return { path: path70, edges, culprit: current };
|
|
5232
5248
|
}
|
|
5233
5249
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
5234
5250
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
5235
5251
|
if (!chain) return null;
|
|
5236
5252
|
const culprit = chain.culprit;
|
|
5237
|
-
const
|
|
5253
|
+
const path70 = [...chain.path];
|
|
5238
5254
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
5239
5255
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
5240
5256
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -5242,14 +5258,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
5242
5258
|
if (loc) {
|
|
5243
5259
|
let rootCauseNode = culprit;
|
|
5244
5260
|
if (loc.fileNode) {
|
|
5245
|
-
|
|
5261
|
+
path70.push(loc.fileNode);
|
|
5246
5262
|
edgeProvenances.push(Provenance6.OBSERVED);
|
|
5247
5263
|
rootCauseNode = loc.fileNode;
|
|
5248
5264
|
}
|
|
5249
5265
|
return RootCauseResultSchema.parse({
|
|
5250
5266
|
rootCauseNode,
|
|
5251
5267
|
rootCauseReason: loc.rootCauseReason,
|
|
5252
|
-
traversalPath:
|
|
5268
|
+
traversalPath: path70,
|
|
5253
5269
|
edgeProvenances,
|
|
5254
5270
|
confidence,
|
|
5255
5271
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -5261,7 +5277,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
5261
5277
|
return RootCauseResultSchema.parse({
|
|
5262
5278
|
rootCauseNode: culprit,
|
|
5263
5279
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
5264
|
-
traversalPath:
|
|
5280
|
+
traversalPath: path70,
|
|
5265
5281
|
edgeProvenances,
|
|
5266
5282
|
confidence,
|
|
5267
5283
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -5697,10 +5713,10 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
|
|
|
5697
5713
|
let traversalPath = legacy.traversalPath;
|
|
5698
5714
|
let edgeProvenances = legacy.edgeProvenances;
|
|
5699
5715
|
if (top.node !== seedNode) {
|
|
5700
|
-
const
|
|
5701
|
-
if (
|
|
5702
|
-
traversalPath =
|
|
5703
|
-
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);
|
|
5704
5720
|
} else {
|
|
5705
5721
|
traversalPath = [errorNodeId, top.node];
|
|
5706
5722
|
edgeProvenances = [top.provenance ?? Provenance6.OBSERVED];
|
|
@@ -5729,11 +5745,11 @@ function fixRecommendationForTop(top, seedNode, legacy) {
|
|
|
5729
5745
|
}
|
|
5730
5746
|
|
|
5731
5747
|
// src/extract/services.ts
|
|
5732
|
-
import { promises as
|
|
5733
|
-
import
|
|
5748
|
+
import { promises as fs18 } from "fs";
|
|
5749
|
+
import path20 from "path";
|
|
5734
5750
|
import ignore from "ignore";
|
|
5735
5751
|
import { minimatch as minimatch2 } from "minimatch";
|
|
5736
|
-
import { NodeType as
|
|
5752
|
+
import { NodeType as NodeType15, serviceId as serviceId11 } from "@neat.is/types";
|
|
5737
5753
|
|
|
5738
5754
|
// src/extract/python.ts
|
|
5739
5755
|
import { promises as fs8 } from "fs";
|
|
@@ -6052,10 +6068,154 @@ async function discoverJavaService(scanPath, dir) {
|
|
|
6052
6068
|
return { pkg, dir, node };
|
|
6053
6069
|
}
|
|
6054
6070
|
|
|
6055
|
-
// src/extract/
|
|
6071
|
+
// src/extract/kotlin.ts
|
|
6056
6072
|
import { promises as fs13 } from "fs";
|
|
6057
6073
|
import path15 from "path";
|
|
6058
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";
|
|
6059
6219
|
function languageForSourceExt(ext) {
|
|
6060
6220
|
switch (ext) {
|
|
6061
6221
|
case ".py":
|
|
@@ -6070,6 +6230,19 @@ function languageForSourceExt(ext) {
|
|
|
6070
6230
|
return "csharp";
|
|
6071
6231
|
case ".java":
|
|
6072
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";
|
|
6073
6246
|
case ".ts":
|
|
6074
6247
|
case ".tsx":
|
|
6075
6248
|
return "typescript";
|
|
@@ -6081,14 +6254,14 @@ function languageForSourceExt(ext) {
|
|
|
6081
6254
|
return null;
|
|
6082
6255
|
}
|
|
6083
6256
|
}
|
|
6084
|
-
var LANGUAGE_PRECEDENCE = ["typescript", "javascript", "python", "go", "ruby", "php", "csharp", "java"];
|
|
6257
|
+
var LANGUAGE_PRECEDENCE = ["typescript", "javascript", "python", "go", "ruby", "php", "csharp", "java", "kotlin", "rust", "cpp"];
|
|
6085
6258
|
async function inferServiceLanguage(dir) {
|
|
6086
|
-
const entries = await
|
|
6259
|
+
const entries = await fs16.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
6087
6260
|
const counts = /* @__PURE__ */ new Map();
|
|
6088
6261
|
for (const entry of entries) {
|
|
6089
6262
|
if (!entry.isFile()) continue;
|
|
6090
6263
|
if (isNeatAuthoredSourceFile(entry.name)) continue;
|
|
6091
|
-
const ext =
|
|
6264
|
+
const ext = path18.extname(entry.name).toLowerCase();
|
|
6092
6265
|
if (!SERVICE_FILE_EXTENSIONS.has(ext)) continue;
|
|
6093
6266
|
const lang = languageForSourceExt(ext);
|
|
6094
6267
|
if (!lang) continue;
|
|
@@ -6107,45 +6280,49 @@ async function inferServiceLanguage(dir) {
|
|
|
6107
6280
|
return best;
|
|
6108
6281
|
}
|
|
6109
6282
|
async function hasLanguageManifest(dir) {
|
|
6110
|
-
return await exists(
|
|
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
|
|
6111
6284
|
// rather than probing a fixed filename (ADR-196).
|
|
6112
|
-
await hasCsharpProject(dir) || // Java
|
|
6113
|
-
|
|
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);
|
|
6114
6291
|
}
|
|
6115
6292
|
async function hasDockerfileDeclaredService(dir) {
|
|
6116
|
-
if (!await exists(
|
|
6293
|
+
if (!await exists(path18.join(dir, "Dockerfile"))) return false;
|
|
6117
6294
|
return await inferServiceLanguage(dir) !== null;
|
|
6118
6295
|
}
|
|
6119
6296
|
async function discoverDockerfileService(scanPath, dir) {
|
|
6120
|
-
if (!await exists(
|
|
6297
|
+
if (!await exists(path18.join(dir, "Dockerfile"))) return null;
|
|
6121
6298
|
if (await hasLanguageManifest(dir)) return null;
|
|
6122
6299
|
const language = await inferServiceLanguage(dir);
|
|
6123
6300
|
if (!language) return null;
|
|
6124
|
-
const name =
|
|
6301
|
+
const name = path18.basename(dir);
|
|
6125
6302
|
const pkg = { name, dependencies: {} };
|
|
6126
6303
|
const node = {
|
|
6127
|
-
id:
|
|
6128
|
-
type:
|
|
6304
|
+
id: serviceId10(name),
|
|
6305
|
+
type: NodeType14.ServiceNode,
|
|
6129
6306
|
name,
|
|
6130
6307
|
language,
|
|
6131
6308
|
dependencies: {},
|
|
6132
|
-
repoPath:
|
|
6309
|
+
repoPath: path18.relative(scanPath, dir)
|
|
6133
6310
|
};
|
|
6134
6311
|
return { pkg, dir, node };
|
|
6135
6312
|
}
|
|
6136
6313
|
|
|
6137
6314
|
// src/extract/owners.ts
|
|
6138
|
-
import { promises as
|
|
6139
|
-
import
|
|
6315
|
+
import { promises as fs17 } from "fs";
|
|
6316
|
+
import path19 from "path";
|
|
6140
6317
|
import { minimatch } from "minimatch";
|
|
6141
6318
|
async function loadCodeowners(scanPath) {
|
|
6142
6319
|
const candidates = [
|
|
6143
|
-
|
|
6144
|
-
|
|
6320
|
+
path19.join(scanPath, "CODEOWNERS"),
|
|
6321
|
+
path19.join(scanPath, ".github", "CODEOWNERS")
|
|
6145
6322
|
];
|
|
6146
6323
|
for (const file of candidates) {
|
|
6147
6324
|
if (await exists(file)) {
|
|
6148
|
-
const raw = await
|
|
6325
|
+
const raw = await fs17.readFile(file, "utf8");
|
|
6149
6326
|
return parseCodeowners(raw);
|
|
6150
6327
|
}
|
|
6151
6328
|
}
|
|
@@ -6163,7 +6340,7 @@ function parseCodeowners(raw) {
|
|
|
6163
6340
|
return { rules };
|
|
6164
6341
|
}
|
|
6165
6342
|
function matchOwner(file, repoPath) {
|
|
6166
|
-
const normalized = repoPath.split(
|
|
6343
|
+
const normalized = repoPath.split(path19.sep).join("/");
|
|
6167
6344
|
for (const rule of file.rules) {
|
|
6168
6345
|
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
6169
6346
|
}
|
|
@@ -6179,7 +6356,7 @@ function matchesPattern(rawPattern, repoPath) {
|
|
|
6179
6356
|
return false;
|
|
6180
6357
|
}
|
|
6181
6358
|
async function readPackageJsonAuthor(serviceDir) {
|
|
6182
|
-
const pkgPath =
|
|
6359
|
+
const pkgPath = path19.join(serviceDir, "package.json");
|
|
6183
6360
|
if (!await exists(pkgPath)) return null;
|
|
6184
6361
|
try {
|
|
6185
6362
|
const pkg = await readJson(pkgPath);
|
|
@@ -6216,36 +6393,36 @@ function workspaceGlobs(pkg) {
|
|
|
6216
6393
|
return null;
|
|
6217
6394
|
}
|
|
6218
6395
|
async function hasPythonManifest(dir) {
|
|
6219
|
-
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"));
|
|
6220
6397
|
}
|
|
6221
6398
|
async function hasGoManifest(dir) {
|
|
6222
|
-
return exists(
|
|
6399
|
+
return exists(path20.join(dir, "go.mod"));
|
|
6223
6400
|
}
|
|
6224
6401
|
async function hasRubyManifest(dir) {
|
|
6225
|
-
return exists(
|
|
6402
|
+
return exists(path20.join(dir, "Gemfile"));
|
|
6226
6403
|
}
|
|
6227
6404
|
async function hasPhpManifest(dir) {
|
|
6228
|
-
return exists(
|
|
6405
|
+
return exists(path20.join(dir, "composer.json"));
|
|
6229
6406
|
}
|
|
6230
6407
|
async function hasCsharpManifest(dir) {
|
|
6231
6408
|
return hasCsharpProject(dir);
|
|
6232
6409
|
}
|
|
6233
6410
|
async function loadGitignore(scanPath) {
|
|
6234
|
-
const gitignorePath =
|
|
6411
|
+
const gitignorePath = path20.join(scanPath, ".gitignore");
|
|
6235
6412
|
if (!await exists(gitignorePath)) return null;
|
|
6236
|
-
const raw = await
|
|
6413
|
+
const raw = await fs18.readFile(gitignorePath, "utf8");
|
|
6237
6414
|
return ignore().add(raw);
|
|
6238
6415
|
}
|
|
6239
6416
|
async function walkDirs(start, scanPath, options, visit) {
|
|
6240
6417
|
async function recurse(current, depth) {
|
|
6241
6418
|
if (depth > options.maxDepth) return;
|
|
6242
|
-
const entries = await
|
|
6419
|
+
const entries = await fs18.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
6243
6420
|
for (const entry of entries) {
|
|
6244
6421
|
if (!entry.isDirectory()) continue;
|
|
6245
6422
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6246
|
-
const child =
|
|
6423
|
+
const child = path20.join(current, entry.name);
|
|
6247
6424
|
if (options.ig) {
|
|
6248
|
-
const rel =
|
|
6425
|
+
const rel = path20.relative(scanPath, child).split(path20.sep).join("/");
|
|
6249
6426
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
6250
6427
|
}
|
|
6251
6428
|
if (await isPythonVenvDir(child)) continue;
|
|
@@ -6261,8 +6438,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
6261
6438
|
for (const raw of globs) {
|
|
6262
6439
|
const pattern = raw.replace(/^\.\//, "");
|
|
6263
6440
|
if (!pattern.includes("*")) {
|
|
6264
|
-
const candidate =
|
|
6265
|
-
if (await exists(
|
|
6441
|
+
const candidate = path20.join(scanPath, pattern);
|
|
6442
|
+
if (await exists(path20.join(candidate, "package.json"))) found.add(candidate);
|
|
6266
6443
|
continue;
|
|
6267
6444
|
}
|
|
6268
6445
|
const segments = pattern.split("/");
|
|
@@ -6271,13 +6448,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
6271
6448
|
if (seg.includes("*")) break;
|
|
6272
6449
|
staticSegments.push(seg);
|
|
6273
6450
|
}
|
|
6274
|
-
const start =
|
|
6451
|
+
const start = path20.join(scanPath, ...staticSegments);
|
|
6275
6452
|
if (!await exists(start)) continue;
|
|
6276
6453
|
const hasDoubleStar = pattern.includes("**");
|
|
6277
6454
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
6278
6455
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
6279
|
-
const rel =
|
|
6280
|
-
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"))) {
|
|
6281
6458
|
found.add(dir);
|
|
6282
6459
|
}
|
|
6283
6460
|
});
|
|
@@ -6300,31 +6477,31 @@ function detectJsFramework(pkg) {
|
|
|
6300
6477
|
async function detectJsServiceLanguage(dir, pkg) {
|
|
6301
6478
|
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
6302
6479
|
if (deps["typescript"] !== void 0) return "typescript";
|
|
6303
|
-
const entries = await
|
|
6480
|
+
const entries = await fs18.readdir(dir).catch(() => []);
|
|
6304
6481
|
if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
|
|
6305
6482
|
return "javascript";
|
|
6306
6483
|
}
|
|
6307
6484
|
async function discoverNodeService(scanPath, dir) {
|
|
6308
|
-
const pkgPath =
|
|
6485
|
+
const pkgPath = path20.join(dir, "package.json");
|
|
6309
6486
|
if (!await exists(pkgPath)) return null;
|
|
6310
6487
|
let pkg;
|
|
6311
6488
|
try {
|
|
6312
6489
|
pkg = await readJson(pkgPath);
|
|
6313
6490
|
} catch (err) {
|
|
6314
|
-
recordExtractionError("services",
|
|
6491
|
+
recordExtractionError("services", path20.relative(scanPath, pkgPath), err);
|
|
6315
6492
|
return null;
|
|
6316
6493
|
}
|
|
6317
6494
|
if (!pkg.name) return null;
|
|
6318
6495
|
const framework = detectJsFramework(pkg);
|
|
6319
6496
|
const language = await detectJsServiceLanguage(dir, pkg);
|
|
6320
6497
|
const node = {
|
|
6321
|
-
id:
|
|
6322
|
-
type:
|
|
6498
|
+
id: serviceId11(pkg.name),
|
|
6499
|
+
type: NodeType15.ServiceNode,
|
|
6323
6500
|
name: pkg.name,
|
|
6324
6501
|
language,
|
|
6325
6502
|
version: pkg.version,
|
|
6326
6503
|
dependencies: pkg.dependencies ?? {},
|
|
6327
|
-
repoPath:
|
|
6504
|
+
repoPath: path20.relative(scanPath, dir),
|
|
6328
6505
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
|
|
6329
6506
|
...framework ? { framework } : {}
|
|
6330
6507
|
};
|
|
@@ -6335,18 +6512,18 @@ async function discoverPyService(scanPath, dir) {
|
|
|
6335
6512
|
if (!py) return null;
|
|
6336
6513
|
const pkg = pythonToPackage(py);
|
|
6337
6514
|
const node = {
|
|
6338
|
-
id:
|
|
6339
|
-
type:
|
|
6515
|
+
id: serviceId11(py.name),
|
|
6516
|
+
type: NodeType15.ServiceNode,
|
|
6340
6517
|
name: py.name,
|
|
6341
6518
|
language: "python",
|
|
6342
6519
|
version: py.version,
|
|
6343
6520
|
dependencies: py.dependencies,
|
|
6344
|
-
repoPath:
|
|
6521
|
+
repoPath: path20.relative(scanPath, dir)
|
|
6345
6522
|
};
|
|
6346
6523
|
return { pkg, dir, node };
|
|
6347
6524
|
}
|
|
6348
6525
|
async function discoverServices(scanPath) {
|
|
6349
|
-
const rootPkgPath =
|
|
6526
|
+
const rootPkgPath = path20.join(scanPath, "package.json");
|
|
6350
6527
|
let rootPkg = null;
|
|
6351
6528
|
if (await exists(rootPkgPath)) {
|
|
6352
6529
|
try {
|
|
@@ -6354,7 +6531,7 @@ async function discoverServices(scanPath) {
|
|
|
6354
6531
|
} catch (err) {
|
|
6355
6532
|
recordExtractionError(
|
|
6356
6533
|
"services workspaces",
|
|
6357
|
-
|
|
6534
|
+
path20.relative(scanPath, rootPkgPath),
|
|
6358
6535
|
err
|
|
6359
6536
|
);
|
|
6360
6537
|
}
|
|
@@ -6366,7 +6543,7 @@ async function discoverServices(scanPath) {
|
|
|
6366
6543
|
} else {
|
|
6367
6544
|
if (rootPkg && rootPkg.name) {
|
|
6368
6545
|
candidateDirs.push(scanPath);
|
|
6369
|
-
} else if (await hasPythonManifest(scanPath) || await hasGoManifest(scanPath) || await hasRubyManifest(scanPath) || await hasPhpManifest(scanPath) || await hasCsharpManifest(scanPath) || await hasJavaManifest(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)) {
|
|
6370
6547
|
candidateDirs.push(scanPath);
|
|
6371
6548
|
}
|
|
6372
6549
|
const ig = await loadGitignore(scanPath);
|
|
@@ -6375,9 +6552,9 @@ async function discoverServices(scanPath) {
|
|
|
6375
6552
|
scanPath,
|
|
6376
6553
|
{ maxDepth: parseScanDepth(), ig },
|
|
6377
6554
|
async (dir) => {
|
|
6378
|
-
if (await exists(
|
|
6555
|
+
if (await exists(path20.join(dir, "package.json"))) {
|
|
6379
6556
|
candidateDirs.push(dir);
|
|
6380
|
-
} else if (await hasPythonManifest(dir) || await hasGoManifest(dir) || await hasRubyManifest(dir) || await hasPhpManifest(dir) || await hasCsharpManifest(dir) || await hasJavaManifest(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)) {
|
|
6381
6558
|
candidateDirs.push(dir);
|
|
6382
6559
|
} else if (await hasDockerfileDeclaredService(dir)) {
|
|
6383
6560
|
candidateDirs.push(dir);
|
|
@@ -6390,14 +6567,21 @@ async function discoverServices(scanPath) {
|
|
|
6390
6567
|
const seenDirs = /* @__PURE__ */ new Set();
|
|
6391
6568
|
const out = [];
|
|
6392
6569
|
for (const dir of candidateDirs) {
|
|
6393
|
-
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) ??
|
|
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);
|
|
6394
6578
|
if (!service) continue;
|
|
6395
6579
|
if (seenDirs.has(service.dir)) continue;
|
|
6396
6580
|
seenDirs.add(service.dir);
|
|
6397
6581
|
const existingDir = seen.get(service.node.name);
|
|
6398
6582
|
if (existingDir !== void 0) {
|
|
6399
|
-
const a =
|
|
6400
|
-
const b =
|
|
6583
|
+
const a = path20.relative(scanPath, existingDir) || ".";
|
|
6584
|
+
const b = path20.relative(scanPath, dir) || ".";
|
|
6401
6585
|
console.warn(
|
|
6402
6586
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
6403
6587
|
);
|
|
@@ -6411,6 +6595,17 @@ async function discoverServices(scanPath) {
|
|
|
6411
6595
|
const owner = await computeServiceOwner(codeowners, service.node.repoPath, service.dir);
|
|
6412
6596
|
if (owner !== void 0) service.node.owner = owner;
|
|
6413
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
|
+
}
|
|
6414
6609
|
return out;
|
|
6415
6610
|
}
|
|
6416
6611
|
function addServiceNodes(graph, services) {
|
|
@@ -6433,20 +6628,20 @@ function addServiceNodes(graph, services) {
|
|
|
6433
6628
|
}
|
|
6434
6629
|
|
|
6435
6630
|
// src/extract/aliases.ts
|
|
6436
|
-
import
|
|
6437
|
-
import { promises as
|
|
6631
|
+
import path21 from "path";
|
|
6632
|
+
import { promises as fs19 } from "fs";
|
|
6438
6633
|
import { parseAllDocuments } from "yaml";
|
|
6439
|
-
import { NodeType as
|
|
6634
|
+
import { NodeType as NodeType16 } from "@neat.is/types";
|
|
6440
6635
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
6441
6636
|
"Service",
|
|
6442
6637
|
"Deployment",
|
|
6443
6638
|
"StatefulSet",
|
|
6444
6639
|
"DaemonSet"
|
|
6445
6640
|
]);
|
|
6446
|
-
function addAliases(graph,
|
|
6447
|
-
if (!graph.hasNode(
|
|
6448
|
-
const node = graph.getNodeAttributes(
|
|
6449
|
-
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;
|
|
6450
6645
|
const set = new Set(node.aliases ?? []);
|
|
6451
6646
|
for (const c of candidates) {
|
|
6452
6647
|
if (!c) continue;
|
|
@@ -6455,20 +6650,20 @@ function addAliases(graph, serviceId12, candidates) {
|
|
|
6455
6650
|
}
|
|
6456
6651
|
if (set.size === 0) return;
|
|
6457
6652
|
const updated = { ...node, aliases: [...set].sort() };
|
|
6458
|
-
graph.replaceNodeAttributes(
|
|
6653
|
+
graph.replaceNodeAttributes(serviceId15, updated);
|
|
6459
6654
|
}
|
|
6460
6655
|
function indexServicesByName(services) {
|
|
6461
6656
|
const map = /* @__PURE__ */ new Map();
|
|
6462
6657
|
for (const s of services) {
|
|
6463
6658
|
map.set(s.node.name, s.node.id);
|
|
6464
|
-
map.set(
|
|
6659
|
+
map.set(path21.basename(s.dir), s.node.id);
|
|
6465
6660
|
}
|
|
6466
6661
|
return map;
|
|
6467
6662
|
}
|
|
6468
6663
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
6469
6664
|
let composePath = null;
|
|
6470
6665
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
6471
|
-
const abs =
|
|
6666
|
+
const abs = path21.join(scanPath, name);
|
|
6472
6667
|
if (await exists(abs)) {
|
|
6473
6668
|
composePath = abs;
|
|
6474
6669
|
break;
|
|
@@ -6481,19 +6676,19 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
6481
6676
|
} catch (err) {
|
|
6482
6677
|
recordExtractionError(
|
|
6483
6678
|
"aliases compose",
|
|
6484
|
-
|
|
6679
|
+
path21.relative(scanPath, composePath),
|
|
6485
6680
|
err
|
|
6486
6681
|
);
|
|
6487
6682
|
return;
|
|
6488
6683
|
}
|
|
6489
6684
|
if (!compose?.services) return;
|
|
6490
6685
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
6491
|
-
const
|
|
6492
|
-
if (!
|
|
6686
|
+
const serviceId15 = serviceIndex.get(composeName);
|
|
6687
|
+
if (!serviceId15) continue;
|
|
6493
6688
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
6494
6689
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
6495
6690
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
6496
|
-
addAliases(graph,
|
|
6691
|
+
addAliases(graph, serviceId15, aliases);
|
|
6497
6692
|
}
|
|
6498
6693
|
}
|
|
6499
6694
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -6524,11 +6719,11 @@ function parseDockerfileLabels(content) {
|
|
|
6524
6719
|
}
|
|
6525
6720
|
async function collectDockerfileAliases(graph, services) {
|
|
6526
6721
|
for (const service of services) {
|
|
6527
|
-
const dockerfilePath =
|
|
6722
|
+
const dockerfilePath = path21.join(service.dir, "Dockerfile");
|
|
6528
6723
|
if (!await exists(dockerfilePath)) continue;
|
|
6529
6724
|
let content;
|
|
6530
6725
|
try {
|
|
6531
|
-
content = await
|
|
6726
|
+
content = await fs19.readFile(dockerfilePath, "utf8");
|
|
6532
6727
|
} catch (err) {
|
|
6533
6728
|
recordExtractionError("aliases dockerfile", dockerfilePath, err);
|
|
6534
6729
|
continue;
|
|
@@ -6540,15 +6735,15 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
6540
6735
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
6541
6736
|
if (depth > max) return [];
|
|
6542
6737
|
const out = [];
|
|
6543
|
-
const entries = await
|
|
6738
|
+
const entries = await fs19.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
6544
6739
|
for (const entry of entries) {
|
|
6545
6740
|
if (entry.isDirectory()) {
|
|
6546
6741
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6547
|
-
const child =
|
|
6742
|
+
const child = path21.join(start, entry.name);
|
|
6548
6743
|
if (await isPythonVenvDir(child)) continue;
|
|
6549
6744
|
out.push(...await walkYamlFiles(child, depth + 1, max));
|
|
6550
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
6551
|
-
out.push(
|
|
6745
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path21.extname(entry.name))) {
|
|
6746
|
+
out.push(path21.join(start, entry.name));
|
|
6552
6747
|
}
|
|
6553
6748
|
}
|
|
6554
6749
|
return out;
|
|
@@ -6575,7 +6770,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
6575
6770
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
6576
6771
|
const files = await walkYamlFiles(scanPath);
|
|
6577
6772
|
for (const file of files) {
|
|
6578
|
-
const content = await
|
|
6773
|
+
const content = await fs19.readFile(file, "utf8");
|
|
6579
6774
|
let docs;
|
|
6580
6775
|
try {
|
|
6581
6776
|
docs = parseAllDocuments(content).map((d) => d.toJSON());
|
|
@@ -6599,14 +6794,14 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
6599
6794
|
}
|
|
6600
6795
|
|
|
6601
6796
|
// src/extract/files.ts
|
|
6602
|
-
import
|
|
6797
|
+
import path22 from "path";
|
|
6603
6798
|
async function addFiles(graph, services) {
|
|
6604
6799
|
let nodesAdded = 0;
|
|
6605
6800
|
let edgesAdded = 0;
|
|
6606
6801
|
for (const service of services) {
|
|
6607
|
-
const filePaths = await walkSourceFiles(service.dir);
|
|
6802
|
+
const filePaths = await walkSourceFiles(service.dir, service.excludeDirs);
|
|
6608
6803
|
for (const filePath of filePaths) {
|
|
6609
|
-
const relPath = toPosix(
|
|
6804
|
+
const relPath = toPosix(path22.relative(service.dir, filePath));
|
|
6610
6805
|
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
6611
6806
|
graph,
|
|
6612
6807
|
service.pkg.name,
|
|
@@ -6621,7 +6816,7 @@ async function addFiles(graph, services) {
|
|
|
6621
6816
|
}
|
|
6622
6817
|
|
|
6623
6818
|
// src/extract/symbols.ts
|
|
6624
|
-
import
|
|
6819
|
+
import path23 from "path";
|
|
6625
6820
|
import Parser3 from "tree-sitter";
|
|
6626
6821
|
import JavaScript3 from "tree-sitter-javascript";
|
|
6627
6822
|
import TypeScript from "tree-sitter-typescript";
|
|
@@ -6631,9 +6826,12 @@ import Ruby2 from "tree-sitter-ruby";
|
|
|
6631
6826
|
import Php2 from "tree-sitter-php";
|
|
6632
6827
|
import CSharp from "tree-sitter-c-sharp";
|
|
6633
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";
|
|
6634
6832
|
import {
|
|
6635
6833
|
EdgeType as EdgeType7,
|
|
6636
|
-
NodeType as
|
|
6834
|
+
NodeType as NodeType17,
|
|
6637
6835
|
Provenance as Provenance7,
|
|
6638
6836
|
confidenceForExtracted as confidenceForExtracted4,
|
|
6639
6837
|
extractedEdgeId as extractedEdgeId6,
|
|
@@ -6655,7 +6853,25 @@ var SYMBOL_GRAMMAR_BY_EXT = {
|
|
|
6655
6853
|
".rb": Ruby2,
|
|
6656
6854
|
".php": Php2.php_only,
|
|
6657
6855
|
".cs": CSharp,
|
|
6658
|
-
".java": Java
|
|
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
|
|
6659
6875
|
};
|
|
6660
6876
|
function parseSource3(parser, source) {
|
|
6661
6877
|
return parser.parse(
|
|
@@ -7068,6 +7284,237 @@ function collectJavaSymbolDefs(root) {
|
|
|
7068
7284
|
walkChildren(root, void 0);
|
|
7069
7285
|
return out;
|
|
7070
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) {
|
|
7358
|
+
const out = [];
|
|
7359
|
+
const push = (kind, qualname, node) => {
|
|
7360
|
+
out.push({
|
|
7361
|
+
kind,
|
|
7362
|
+
qualname,
|
|
7363
|
+
startLine: node.startPosition.row + 1,
|
|
7364
|
+
endLine: node.endPosition.row + 1
|
|
7365
|
+
});
|
|
7366
|
+
};
|
|
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) => {
|
|
7385
|
+
switch (node.type) {
|
|
7386
|
+
case "mod_item": {
|
|
7387
|
+
const name = node.childForFieldName("name")?.text;
|
|
7388
|
+
const full = name ? join(modCtx, name) : modCtx;
|
|
7389
|
+
const body = node.childForFieldName("body");
|
|
7390
|
+
if (body) walkChildren(body, full);
|
|
7391
|
+
return;
|
|
7392
|
+
}
|
|
7393
|
+
case "struct_item":
|
|
7394
|
+
case "enum_item": {
|
|
7395
|
+
const name = node.childForFieldName("name")?.text;
|
|
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);
|
|
7403
|
+
const body = node.childForFieldName("body");
|
|
7404
|
+
if (body && full) walkTypeBody(body, full);
|
|
7405
|
+
return;
|
|
7406
|
+
}
|
|
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;
|
|
7413
|
+
}
|
|
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;
|
|
7454
|
+
}
|
|
7455
|
+
}
|
|
7456
|
+
decl = next;
|
|
7457
|
+
}
|
|
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);
|
|
7484
|
+
}
|
|
7485
|
+
};
|
|
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);
|
|
7516
|
+
return out;
|
|
7517
|
+
}
|
|
7071
7518
|
function collectSymbolDefsForExt(ext, root) {
|
|
7072
7519
|
switch (ext) {
|
|
7073
7520
|
case ".py":
|
|
@@ -7082,6 +7529,19 @@ function collectSymbolDefsForExt(ext, root) {
|
|
|
7082
7529
|
return collectCsharpSymbolDefs(root);
|
|
7083
7530
|
case ".java":
|
|
7084
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);
|
|
7085
7545
|
default:
|
|
7086
7546
|
return collectSymbolDefs(root);
|
|
7087
7547
|
}
|
|
@@ -7113,12 +7573,12 @@ async function addSymbols(graph, services) {
|
|
|
7113
7573
|
let nodesAdded = 0;
|
|
7114
7574
|
let edgesAdded = 0;
|
|
7115
7575
|
for (const service of services) {
|
|
7116
|
-
const files = await loadSourceFiles(service.dir);
|
|
7576
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
7117
7577
|
for (const file of files) {
|
|
7118
|
-
const ext =
|
|
7578
|
+
const ext = path23.extname(file.path);
|
|
7119
7579
|
const parser = parserForExt5(ext);
|
|
7120
7580
|
if (!parser) continue;
|
|
7121
|
-
const relPath = toPosix(
|
|
7581
|
+
const relPath = toPosix(path23.relative(service.dir, file.path));
|
|
7122
7582
|
let defs;
|
|
7123
7583
|
try {
|
|
7124
7584
|
const tree = parseSource3(parser, file.content);
|
|
@@ -7141,7 +7601,7 @@ async function addSymbols(graph, services) {
|
|
|
7141
7601
|
if (!graph.hasNode(sid)) {
|
|
7142
7602
|
const node = {
|
|
7143
7603
|
id: sid,
|
|
7144
|
-
type:
|
|
7604
|
+
type: NodeType17.SymbolNode,
|
|
7145
7605
|
kind: def.kind,
|
|
7146
7606
|
qualname: def.qualname,
|
|
7147
7607
|
span: { startLine: def.startLine, endLine: def.endLine },
|
|
@@ -7177,11 +7637,11 @@ async function addSymbols(graph, services) {
|
|
|
7177
7637
|
}
|
|
7178
7638
|
|
|
7179
7639
|
// src/extract/symbol-edges.ts
|
|
7180
|
-
import
|
|
7640
|
+
import path24 from "path";
|
|
7181
7641
|
import Parser4 from "tree-sitter";
|
|
7182
7642
|
import {
|
|
7183
7643
|
EdgeType as EdgeType8,
|
|
7184
|
-
NodeType as
|
|
7644
|
+
NodeType as NodeType18,
|
|
7185
7645
|
Provenance as Provenance8,
|
|
7186
7646
|
confidenceForExtracted as confidenceForExtracted5,
|
|
7187
7647
|
extractedEdgeId as extractedEdgeId7,
|
|
@@ -7289,12 +7749,12 @@ async function addSymbolEdges(graph, services) {
|
|
|
7289
7749
|
for (const service of services) {
|
|
7290
7750
|
const serviceName = service.pkg.name;
|
|
7291
7751
|
const tsPaths = await loadTsPathConfig(service.dir);
|
|
7292
|
-
const files = await loadSourceFiles(service.dir);
|
|
7752
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
7293
7753
|
for (const file of files) {
|
|
7294
|
-
const parser = parserForExt5(
|
|
7754
|
+
const parser = parserForExt5(path24.extname(file.path));
|
|
7295
7755
|
if (!parser) continue;
|
|
7296
|
-
const relPath = toPosix(
|
|
7297
|
-
const fileDir =
|
|
7756
|
+
const relPath = toPosix(path24.relative(service.dir, file.path));
|
|
7757
|
+
const fileDir = path24.dirname(file.path);
|
|
7298
7758
|
let root;
|
|
7299
7759
|
try {
|
|
7300
7760
|
root = parseSource3(parser, file.content).rootNode;
|
|
@@ -7336,7 +7796,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
7336
7796
|
const candidate = symbolId3(serviceName, imp.targetRelPath, imp.importedName);
|
|
7337
7797
|
if (graph.hasNode(candidate)) {
|
|
7338
7798
|
const node = graph.getNodeAttributes(candidate);
|
|
7339
|
-
if (node.type ===
|
|
7799
|
+
if (node.type === NodeType18.SymbolNode && node.kind === wantKind) return candidate;
|
|
7340
7800
|
}
|
|
7341
7801
|
}
|
|
7342
7802
|
return null;
|
|
@@ -7428,11 +7888,11 @@ async function addSymbolEdges(graph, services) {
|
|
|
7428
7888
|
}
|
|
7429
7889
|
|
|
7430
7890
|
// src/extract/actions.ts
|
|
7431
|
-
import
|
|
7891
|
+
import path25 from "path";
|
|
7432
7892
|
import Parser5 from "tree-sitter";
|
|
7433
7893
|
import {
|
|
7434
7894
|
EdgeType as EdgeType9,
|
|
7435
|
-
NodeType as
|
|
7895
|
+
NodeType as NodeType19,
|
|
7436
7896
|
Provenance as Provenance9,
|
|
7437
7897
|
confidenceForExtracted as confidenceForExtracted6,
|
|
7438
7898
|
extractedEdgeId as extractedEdgeId8,
|
|
@@ -7579,12 +8039,12 @@ async function addServerActions(graph, services) {
|
|
|
7579
8039
|
};
|
|
7580
8040
|
if (deps["next"] === void 0) continue;
|
|
7581
8041
|
const tsPaths = await loadTsPathConfig(service.dir);
|
|
7582
|
-
const files = await loadSourceFiles(service.dir);
|
|
8042
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
7583
8043
|
for (const file of files) {
|
|
7584
8044
|
if (isTestPath(file.path)) continue;
|
|
7585
|
-
const parser = parserForExt5(
|
|
8045
|
+
const parser = parserForExt5(path25.extname(file.path));
|
|
7586
8046
|
if (!parser) continue;
|
|
7587
|
-
const relPath = toPosix(
|
|
8047
|
+
const relPath = toPosix(path25.relative(service.dir, file.path));
|
|
7588
8048
|
let root;
|
|
7589
8049
|
try {
|
|
7590
8050
|
root = parseSource3(parser, file.content).rootNode;
|
|
@@ -7611,7 +8071,7 @@ async function addServerActions(graph, services) {
|
|
|
7611
8071
|
if (!graph.hasNode(aid)) {
|
|
7612
8072
|
const node = {
|
|
7613
8073
|
id: aid,
|
|
7614
|
-
type:
|
|
8074
|
+
type: NodeType19.ServerActionNode,
|
|
7615
8075
|
name: action.exportName,
|
|
7616
8076
|
service: service.pkg.name,
|
|
7617
8077
|
module: relPath,
|
|
@@ -7645,10 +8105,10 @@ async function addServerActions(graph, services) {
|
|
|
7645
8105
|
}
|
|
7646
8106
|
for (const file of files) {
|
|
7647
8107
|
if (isTestPath(file.path)) continue;
|
|
7648
|
-
const parser = parserForExt5(
|
|
8108
|
+
const parser = parserForExt5(path25.extname(file.path));
|
|
7649
8109
|
if (!parser) continue;
|
|
7650
|
-
const relPath = toPosix(
|
|
7651
|
-
const fileDir =
|
|
8110
|
+
const relPath = toPosix(path25.relative(service.dir, file.path));
|
|
8111
|
+
const fileDir = path25.dirname(file.path);
|
|
7652
8112
|
let root;
|
|
7653
8113
|
try {
|
|
7654
8114
|
root = parseSource3(parser, file.content).rootNode;
|
|
@@ -7700,10 +8160,10 @@ async function addServerActions(graph, services) {
|
|
|
7700
8160
|
}
|
|
7701
8161
|
|
|
7702
8162
|
// src/extract/databases/index.ts
|
|
7703
|
-
import
|
|
8163
|
+
import path33 from "path";
|
|
7704
8164
|
import {
|
|
7705
8165
|
EdgeType as EdgeType10,
|
|
7706
|
-
NodeType as
|
|
8166
|
+
NodeType as NodeType20,
|
|
7707
8167
|
Provenance as Provenance10,
|
|
7708
8168
|
configId,
|
|
7709
8169
|
databaseId as databaseId2,
|
|
@@ -7711,9 +8171,9 @@ import {
|
|
|
7711
8171
|
} from "@neat.is/types";
|
|
7712
8172
|
|
|
7713
8173
|
// src/extract/databases/db-config-yaml.ts
|
|
7714
|
-
import
|
|
8174
|
+
import path26 from "path";
|
|
7715
8175
|
async function parse(serviceDir) {
|
|
7716
|
-
const yamlPath =
|
|
8176
|
+
const yamlPath = path26.join(serviceDir, "db-config.yaml");
|
|
7717
8177
|
if (!await exists(yamlPath)) return [];
|
|
7718
8178
|
const raw = await readYaml(yamlPath);
|
|
7719
8179
|
return [
|
|
@@ -7730,12 +8190,12 @@ async function parse(serviceDir) {
|
|
|
7730
8190
|
var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
7731
8191
|
|
|
7732
8192
|
// src/extract/databases/dotenv.ts
|
|
7733
|
-
import { promises as
|
|
7734
|
-
import
|
|
8193
|
+
import { promises as fs21 } from "fs";
|
|
8194
|
+
import path28 from "path";
|
|
7735
8195
|
|
|
7736
8196
|
// src/extract/databases/shared.ts
|
|
7737
|
-
import { promises as
|
|
7738
|
-
import
|
|
8197
|
+
import { promises as fs20 } from "fs";
|
|
8198
|
+
import path27 from "path";
|
|
7739
8199
|
function schemeToEngine(scheme) {
|
|
7740
8200
|
const s = scheme.toLowerCase().split("+")[0];
|
|
7741
8201
|
switch (s) {
|
|
@@ -7774,18 +8234,18 @@ function parseConnectionString(url) {
|
|
|
7774
8234
|
}
|
|
7775
8235
|
async function readIfExists(filePath) {
|
|
7776
8236
|
try {
|
|
7777
|
-
return await
|
|
8237
|
+
return await fs20.readFile(filePath, "utf8");
|
|
7778
8238
|
} catch {
|
|
7779
8239
|
return null;
|
|
7780
8240
|
}
|
|
7781
8241
|
}
|
|
7782
8242
|
async function resolveEnvVar(serviceDir, name) {
|
|
7783
|
-
const entries = await
|
|
8243
|
+
const entries = await fs20.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
7784
8244
|
const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
|
|
7785
8245
|
const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
|
|
7786
8246
|
envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
|
|
7787
8247
|
for (const fileName of envNames) {
|
|
7788
|
-
const content = await readIfExists(
|
|
8248
|
+
const content = await readIfExists(path27.join(serviceDir, fileName));
|
|
7789
8249
|
if (!content) continue;
|
|
7790
8250
|
for (const line of content.split("\n")) {
|
|
7791
8251
|
const trimmed = line.trim();
|
|
@@ -7804,7 +8264,7 @@ async function resolveEnvVar(serviceDir, name) {
|
|
|
7804
8264
|
}
|
|
7805
8265
|
async function findFirst(serviceDir, candidates) {
|
|
7806
8266
|
for (const rel of candidates) {
|
|
7807
|
-
const abs =
|
|
8267
|
+
const abs = path27.join(serviceDir, rel);
|
|
7808
8268
|
const content = await readIfExists(abs);
|
|
7809
8269
|
if (content !== null) return abs;
|
|
7810
8270
|
}
|
|
@@ -7862,15 +8322,15 @@ function parseDotenvLine(line) {
|
|
|
7862
8322
|
return { key, value };
|
|
7863
8323
|
}
|
|
7864
8324
|
async function parse2(serviceDir) {
|
|
7865
|
-
const entries = await
|
|
8325
|
+
const entries = await fs21.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
7866
8326
|
const configs = [];
|
|
7867
8327
|
const seen = /* @__PURE__ */ new Set();
|
|
7868
8328
|
for (const entry of entries) {
|
|
7869
8329
|
if (!entry.isFile()) continue;
|
|
7870
8330
|
const match = isConfigFile(entry.name);
|
|
7871
8331
|
if (!match.match || match.fileType !== "env") continue;
|
|
7872
|
-
const filePath =
|
|
7873
|
-
const content = await
|
|
8332
|
+
const filePath = path28.join(serviceDir, entry.name);
|
|
8333
|
+
const content = await fs21.readFile(filePath, "utf8");
|
|
7874
8334
|
for (const line of content.split("\n")) {
|
|
7875
8335
|
const parsed = parseDotenvLine(line);
|
|
7876
8336
|
if (!parsed) continue;
|
|
@@ -7888,9 +8348,9 @@ async function parse2(serviceDir) {
|
|
|
7888
8348
|
var dotenvParser = { name: ".env", parse: parse2 };
|
|
7889
8349
|
|
|
7890
8350
|
// src/extract/databases/prisma.ts
|
|
7891
|
-
import
|
|
8351
|
+
import path29 from "path";
|
|
7892
8352
|
async function parse3(serviceDir) {
|
|
7893
|
-
const schemaPath =
|
|
8353
|
+
const schemaPath = path29.join(serviceDir, "prisma", "schema.prisma");
|
|
7894
8354
|
const content = await readIfExists(schemaPath);
|
|
7895
8355
|
if (!content) return [];
|
|
7896
8356
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -8045,10 +8505,10 @@ async function parse5(serviceDir) {
|
|
|
8045
8505
|
var knexParser = { name: "knex", parse: parse5 };
|
|
8046
8506
|
|
|
8047
8507
|
// src/extract/databases/ormconfig.ts
|
|
8048
|
-
import
|
|
8508
|
+
import path30 from "path";
|
|
8049
8509
|
async function parse6(serviceDir) {
|
|
8050
8510
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
8051
|
-
const abs =
|
|
8511
|
+
const abs = path30.join(serviceDir, candidate);
|
|
8052
8512
|
if (!await exists(abs)) continue;
|
|
8053
8513
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
8054
8514
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -8106,9 +8566,9 @@ async function parse7(serviceDir) {
|
|
|
8106
8566
|
var typeormParser = { name: "typeorm", parse: parse7 };
|
|
8107
8567
|
|
|
8108
8568
|
// src/extract/databases/sequelize.ts
|
|
8109
|
-
import
|
|
8569
|
+
import path31 from "path";
|
|
8110
8570
|
async function parse8(serviceDir) {
|
|
8111
|
-
const configPath =
|
|
8571
|
+
const configPath = path31.join(serviceDir, "config", "config.json");
|
|
8112
8572
|
if (!await exists(configPath)) return [];
|
|
8113
8573
|
const raw = await readJson(configPath);
|
|
8114
8574
|
const out = [];
|
|
@@ -8134,7 +8594,7 @@ async function parse8(serviceDir) {
|
|
|
8134
8594
|
var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
8135
8595
|
|
|
8136
8596
|
// src/extract/databases/docker-compose.ts
|
|
8137
|
-
import
|
|
8597
|
+
import path32 from "path";
|
|
8138
8598
|
function portFromService(svc) {
|
|
8139
8599
|
for (const raw of svc.ports ?? []) {
|
|
8140
8600
|
const str = String(raw);
|
|
@@ -8161,7 +8621,7 @@ function databaseFromEnv(svc) {
|
|
|
8161
8621
|
}
|
|
8162
8622
|
async function parse9(serviceDir) {
|
|
8163
8623
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
8164
|
-
const abs =
|
|
8624
|
+
const abs = path32.join(serviceDir, name);
|
|
8165
8625
|
if (!await exists(abs)) continue;
|
|
8166
8626
|
const raw = await readYaml(abs);
|
|
8167
8627
|
if (!raw?.services) return [];
|
|
@@ -8203,7 +8663,7 @@ function compatibleDriversFor(engine) {
|
|
|
8203
8663
|
function toDatabaseNode(config) {
|
|
8204
8664
|
return {
|
|
8205
8665
|
id: databaseId2(config.host),
|
|
8206
|
-
type:
|
|
8666
|
+
type: NodeType20.DatabaseNode,
|
|
8207
8667
|
name: config.database || config.host,
|
|
8208
8668
|
engine: config.engine,
|
|
8209
8669
|
engineVersion: config.engineVersion,
|
|
@@ -8333,7 +8793,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
8333
8793
|
discoveredVia: mergedDiscoveredVia
|
|
8334
8794
|
});
|
|
8335
8795
|
}
|
|
8336
|
-
const relConfigFile = toPosix(
|
|
8796
|
+
const relConfigFile = toPosix(path33.relative(service.dir, config.sourceFile));
|
|
8337
8797
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
8338
8798
|
graph,
|
|
8339
8799
|
service.pkg.name,
|
|
@@ -8342,7 +8802,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
8342
8802
|
);
|
|
8343
8803
|
nodesAdded += fn;
|
|
8344
8804
|
edgesAdded += fe;
|
|
8345
|
-
const evidenceFile = toPosix(
|
|
8805
|
+
const evidenceFile = toPosix(path33.relative(scanPath, config.sourceFile));
|
|
8346
8806
|
const edge = {
|
|
8347
8807
|
id: extractedEdgeId(fileNodeId, dbNode.id, EdgeType10.CONNECTS_TO),
|
|
8348
8808
|
source: fileNodeId,
|
|
@@ -8360,15 +8820,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
8360
8820
|
if (allConfigs.length === 1) {
|
|
8361
8821
|
const primary = allConfigs[0];
|
|
8362
8822
|
service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
|
|
8363
|
-
const relPath =
|
|
8823
|
+
const relPath = path33.relative(scanPath, primary.sourceFile);
|
|
8364
8824
|
const cfgId = configId(relPath);
|
|
8365
8825
|
if (!graph.hasNode(cfgId)) {
|
|
8366
8826
|
const cfgNode = {
|
|
8367
8827
|
id: cfgId,
|
|
8368
|
-
type:
|
|
8369
|
-
name:
|
|
8828
|
+
type: NodeType20.ConfigNode,
|
|
8829
|
+
name: path33.basename(primary.sourceFile),
|
|
8370
8830
|
path: relPath,
|
|
8371
|
-
fileType: isConfigFile(
|
|
8831
|
+
fileType: isConfigFile(path33.basename(primary.sourceFile)).fileType || "config"
|
|
8372
8832
|
};
|
|
8373
8833
|
graph.addNode(cfgId, cfgNode);
|
|
8374
8834
|
nodesAdded++;
|
|
@@ -8408,23 +8868,25 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
8408
8868
|
}
|
|
8409
8869
|
|
|
8410
8870
|
// src/extract/configs.ts
|
|
8411
|
-
import { promises as
|
|
8412
|
-
import
|
|
8871
|
+
import { promises as fs22 } from "fs";
|
|
8872
|
+
import path34 from "path";
|
|
8413
8873
|
import {
|
|
8414
8874
|
EdgeType as EdgeType11,
|
|
8415
|
-
NodeType as
|
|
8875
|
+
NodeType as NodeType21,
|
|
8416
8876
|
Provenance as Provenance11,
|
|
8417
8877
|
configId as configId2,
|
|
8418
8878
|
confidenceForExtracted as confidenceForExtracted8
|
|
8419
8879
|
} from "@neat.is/types";
|
|
8420
|
-
async function walkConfigFiles(dir) {
|
|
8880
|
+
async function walkConfigFiles(dir, excludeDirs = []) {
|
|
8881
|
+
const excluded = new Set(excludeDirs.map((d) => path34.resolve(d)));
|
|
8421
8882
|
const out = [];
|
|
8422
8883
|
async function walk9(current) {
|
|
8423
|
-
const entries = await
|
|
8884
|
+
const entries = await fs22.readdir(current, { withFileTypes: true });
|
|
8424
8885
|
for (const entry of entries) {
|
|
8425
|
-
const full =
|
|
8886
|
+
const full = path34.join(current, entry.name);
|
|
8426
8887
|
if (entry.isDirectory()) {
|
|
8427
8888
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
8889
|
+
if (excluded.has(path34.resolve(full))) continue;
|
|
8428
8890
|
if (await isPythonVenvDir(full)) continue;
|
|
8429
8891
|
await walk9(full);
|
|
8430
8892
|
} else if (entry.isFile() && isConfigFile(entry.name).match) {
|
|
@@ -8439,21 +8901,21 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
8439
8901
|
let nodesAdded = 0;
|
|
8440
8902
|
let edgesAdded = 0;
|
|
8441
8903
|
for (const service of services) {
|
|
8442
|
-
const configFiles = await walkConfigFiles(service.dir);
|
|
8904
|
+
const configFiles = await walkConfigFiles(service.dir, service.excludeDirs);
|
|
8443
8905
|
for (const file of configFiles) {
|
|
8444
|
-
const relPath =
|
|
8906
|
+
const relPath = path34.relative(scanPath, file);
|
|
8445
8907
|
const node = {
|
|
8446
8908
|
id: configId2(relPath),
|
|
8447
|
-
type:
|
|
8448
|
-
name:
|
|
8909
|
+
type: NodeType21.ConfigNode,
|
|
8910
|
+
name: path34.basename(file),
|
|
8449
8911
|
path: relPath,
|
|
8450
|
-
fileType: isConfigFile(
|
|
8912
|
+
fileType: isConfigFile(path34.basename(file)).fileType
|
|
8451
8913
|
};
|
|
8452
8914
|
if (!graph.hasNode(node.id)) {
|
|
8453
8915
|
graph.addNode(node.id, node);
|
|
8454
8916
|
nodesAdded++;
|
|
8455
8917
|
}
|
|
8456
|
-
const relToService = toPosix(
|
|
8918
|
+
const relToService = toPosix(path34.relative(service.dir, file));
|
|
8457
8919
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
8458
8920
|
graph,
|
|
8459
8921
|
service.pkg.name,
|
|
@@ -8469,7 +8931,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
8469
8931
|
type: EdgeType11.CONFIGURED_BY,
|
|
8470
8932
|
provenance: Provenance11.EXTRACTED,
|
|
8471
8933
|
confidence: confidenceForExtracted8("structural"),
|
|
8472
|
-
evidence: { file: relPath.split(
|
|
8934
|
+
evidence: { file: relPath.split(path34.sep).join("/") }
|
|
8473
8935
|
};
|
|
8474
8936
|
if (!graph.hasEdge(edge.id)) {
|
|
8475
8937
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -8481,11 +8943,11 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
8481
8943
|
}
|
|
8482
8944
|
|
|
8483
8945
|
// src/extract/proto.ts
|
|
8484
|
-
import { promises as
|
|
8485
|
-
import
|
|
8946
|
+
import { promises as fs23 } from "fs";
|
|
8947
|
+
import path35 from "path";
|
|
8486
8948
|
import {
|
|
8487
8949
|
EdgeType as EdgeType12,
|
|
8488
|
-
NodeType as
|
|
8950
|
+
NodeType as NodeType22,
|
|
8489
8951
|
Provenance as Provenance12,
|
|
8490
8952
|
confidenceForExtracted as confidenceForExtracted9,
|
|
8491
8953
|
extractedEdgeId as extractedEdgeId9,
|
|
@@ -8526,17 +8988,19 @@ function grpcMethodsFromProto(content, fqPackage) {
|
|
|
8526
8988
|
}
|
|
8527
8989
|
return out;
|
|
8528
8990
|
}
|
|
8529
|
-
async function walkProtoFiles(dir) {
|
|
8991
|
+
async function walkProtoFiles(dir, excludeDirs = []) {
|
|
8992
|
+
const excluded = new Set(excludeDirs.map((d) => path35.resolve(d)));
|
|
8530
8993
|
const out = [];
|
|
8531
8994
|
async function walk9(current) {
|
|
8532
|
-
const entries = await
|
|
8995
|
+
const entries = await fs23.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
8533
8996
|
for (const entry of entries) {
|
|
8534
|
-
const full =
|
|
8997
|
+
const full = path35.join(current, entry.name);
|
|
8535
8998
|
if (entry.isDirectory()) {
|
|
8536
8999
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
9000
|
+
if (excluded.has(path35.resolve(full))) continue;
|
|
8537
9001
|
if (await isPythonVenvDir(full)) continue;
|
|
8538
9002
|
await walk9(full);
|
|
8539
|
-
} else if (entry.isFile() &&
|
|
9003
|
+
} else if (entry.isFile() && path35.extname(entry.name) === PROTO_EXTENSION) {
|
|
8540
9004
|
out.push(full);
|
|
8541
9005
|
}
|
|
8542
9006
|
}
|
|
@@ -8548,13 +9012,13 @@ async function addGrpcMethods(graph, services) {
|
|
|
8548
9012
|
let nodesAdded = 0;
|
|
8549
9013
|
let edgesAdded = 0;
|
|
8550
9014
|
for (const service of services) {
|
|
8551
|
-
const protoPaths = await walkProtoFiles(service.dir);
|
|
9015
|
+
const protoPaths = await walkProtoFiles(service.dir, service.excludeDirs);
|
|
8552
9016
|
for (const protoPath of protoPaths) {
|
|
8553
9017
|
if (isTestPath(protoPath)) continue;
|
|
8554
|
-
const relFile = toPosix(
|
|
9018
|
+
const relFile = toPosix(path35.relative(service.dir, protoPath));
|
|
8555
9019
|
let content;
|
|
8556
9020
|
try {
|
|
8557
|
-
content = await
|
|
9021
|
+
content = await fs23.readFile(protoPath, "utf8");
|
|
8558
9022
|
} catch (err) {
|
|
8559
9023
|
recordExtractionError("proto extraction", protoPath, err);
|
|
8560
9024
|
continue;
|
|
@@ -8572,7 +9036,7 @@ async function addGrpcMethods(graph, services) {
|
|
|
8572
9036
|
if (!graph.hasNode(mid)) {
|
|
8573
9037
|
const node = {
|
|
8574
9038
|
id: mid,
|
|
8575
|
-
type:
|
|
9039
|
+
type: NodeType22.GrpcMethodNode,
|
|
8576
9040
|
name: `${method.rpcService}/${method.rpcMethod}`,
|
|
8577
9041
|
rpcService: method.rpcService,
|
|
8578
9042
|
rpcMethod: method.rpcMethod,
|
|
@@ -8610,14 +9074,14 @@ async function addGrpcMethods(graph, services) {
|
|
|
8610
9074
|
// src/extract/calls/index.ts
|
|
8611
9075
|
import {
|
|
8612
9076
|
EdgeType as EdgeType15,
|
|
8613
|
-
NodeType as
|
|
9077
|
+
NodeType as NodeType24,
|
|
8614
9078
|
Provenance as Provenance15,
|
|
8615
9079
|
confidenceForExtracted as confidenceForExtracted12,
|
|
8616
9080
|
passesExtractedFloor as passesExtractedFloor3
|
|
8617
9081
|
} from "@neat.is/types";
|
|
8618
9082
|
|
|
8619
9083
|
// src/extract/calls/http.ts
|
|
8620
|
-
import
|
|
9084
|
+
import path36 from "path";
|
|
8621
9085
|
import Parser6 from "tree-sitter";
|
|
8622
9086
|
import JavaScript4 from "tree-sitter-javascript";
|
|
8623
9087
|
import TypeScript2 from "tree-sitter-typescript";
|
|
@@ -8701,11 +9165,11 @@ async function addHttpCallEdges(graph, services) {
|
|
|
8701
9165
|
let nodesAdded = 0;
|
|
8702
9166
|
let edgesAdded = 0;
|
|
8703
9167
|
for (const service of services) {
|
|
8704
|
-
const files = await loadSourceFiles(service.dir);
|
|
9168
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
8705
9169
|
const seen = /* @__PURE__ */ new Set();
|
|
8706
9170
|
for (const file of files) {
|
|
8707
9171
|
if (isTestPath(file.path)) continue;
|
|
8708
|
-
const parser = parserForExt(
|
|
9172
|
+
const parser = parserForExt(path36.extname(file.path), parserCache);
|
|
8709
9173
|
let sites;
|
|
8710
9174
|
try {
|
|
8711
9175
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -8714,7 +9178,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
8714
9178
|
continue;
|
|
8715
9179
|
}
|
|
8716
9180
|
if (sites.length === 0) continue;
|
|
8717
|
-
const relFile = toPosix(
|
|
9181
|
+
const relFile = toPosix(path36.relative(service.dir, file.path));
|
|
8718
9182
|
for (const site of sites) {
|
|
8719
9183
|
const targetId = hostToNodeId.get(site.host);
|
|
8720
9184
|
if (!targetId || targetId === service.node.id) continue;
|
|
@@ -8767,16 +9231,16 @@ async function addHttpCallEdges(graph, services) {
|
|
|
8767
9231
|
}
|
|
8768
9232
|
|
|
8769
9233
|
// src/extract/calls/route-match.ts
|
|
8770
|
-
import
|
|
9234
|
+
import path37 from "path";
|
|
8771
9235
|
import Parser7 from "tree-sitter";
|
|
8772
9236
|
import JavaScript5 from "tree-sitter-javascript";
|
|
8773
9237
|
import {
|
|
8774
9238
|
EdgeType as EdgeType14,
|
|
8775
|
-
NodeType as
|
|
9239
|
+
NodeType as NodeType23,
|
|
8776
9240
|
Provenance as Provenance14,
|
|
8777
9241
|
confidenceForExtracted as confidenceForExtracted11,
|
|
8778
9242
|
passesExtractedFloor as passesExtractedFloor2,
|
|
8779
|
-
serviceId as
|
|
9243
|
+
serviceId as serviceId12
|
|
8780
9244
|
} from "@neat.is/types";
|
|
8781
9245
|
var PARSE_CHUNK5 = 16384;
|
|
8782
9246
|
function parseSource5(parser, source) {
|
|
@@ -8942,9 +9406,9 @@ function buildRouteIndex(graph) {
|
|
|
8942
9406
|
const index = /* @__PURE__ */ new Map();
|
|
8943
9407
|
graph.forEachNode((_id, attrs) => {
|
|
8944
9408
|
const node = attrs;
|
|
8945
|
-
if (node.type !==
|
|
9409
|
+
if (node.type !== NodeType23.RouteNode) return;
|
|
8946
9410
|
const route = attrs;
|
|
8947
|
-
const owner =
|
|
9411
|
+
const owner = serviceId12(route.service);
|
|
8948
9412
|
const entry = {
|
|
8949
9413
|
method: route.method.toUpperCase(),
|
|
8950
9414
|
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
@@ -8969,11 +9433,11 @@ async function addRouteCallEdges(graph, services) {
|
|
|
8969
9433
|
let nodesAdded = 0;
|
|
8970
9434
|
let edgesAdded = 0;
|
|
8971
9435
|
for (const service of services) {
|
|
8972
|
-
const files = await loadSourceFiles(service.dir);
|
|
9436
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
8973
9437
|
const seen = /* @__PURE__ */ new Set();
|
|
8974
9438
|
for (const file of files) {
|
|
8975
9439
|
if (isTestPath(file.path)) continue;
|
|
8976
|
-
if (!JS_CLIENT_EXTENSIONS.has(
|
|
9440
|
+
if (!JS_CLIENT_EXTENSIONS.has(path37.extname(file.path))) continue;
|
|
8977
9441
|
let sites;
|
|
8978
9442
|
try {
|
|
8979
9443
|
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
@@ -8982,7 +9446,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
8982
9446
|
continue;
|
|
8983
9447
|
}
|
|
8984
9448
|
if (sites.length === 0) continue;
|
|
8985
|
-
const relFile = toPosix(
|
|
9449
|
+
const relFile = toPosix(path37.relative(service.dir, file.path));
|
|
8986
9450
|
for (const site of sites) {
|
|
8987
9451
|
const serverServiceId = hostToNodeId.get(site.host);
|
|
8988
9452
|
if (!serverServiceId || serverServiceId === service.node.id) continue;
|
|
@@ -9042,7 +9506,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
9042
9506
|
}
|
|
9043
9507
|
|
|
9044
9508
|
// src/extract/calls/kafka.ts
|
|
9045
|
-
import
|
|
9509
|
+
import path38 from "path";
|
|
9046
9510
|
import { infraId as infraId2 } from "@neat.is/types";
|
|
9047
9511
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
9048
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;
|
|
@@ -9073,7 +9537,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
9073
9537
|
// tier (ADR-066).
|
|
9074
9538
|
confidenceKind: "verified-call-site",
|
|
9075
9539
|
evidence: {
|
|
9076
|
-
file:
|
|
9540
|
+
file: path38.relative(serviceDir, file.path),
|
|
9077
9541
|
line,
|
|
9078
9542
|
snippet: snippet(file.content, line)
|
|
9079
9543
|
}
|
|
@@ -9085,7 +9549,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
9085
9549
|
}
|
|
9086
9550
|
|
|
9087
9551
|
// src/extract/calls/redis.ts
|
|
9088
|
-
import
|
|
9552
|
+
import path39 from "path";
|
|
9089
9553
|
import { infraId as infraId3 } from "@neat.is/types";
|
|
9090
9554
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
9091
9555
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -9108,7 +9572,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
9108
9572
|
// support tier (ADR-066).
|
|
9109
9573
|
confidenceKind: "url-with-structural-support",
|
|
9110
9574
|
evidence: {
|
|
9111
|
-
file:
|
|
9575
|
+
file: path39.relative(serviceDir, file.path),
|
|
9112
9576
|
line,
|
|
9113
9577
|
snippet: snippet(file.content, line)
|
|
9114
9578
|
}
|
|
@@ -9118,7 +9582,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
9118
9582
|
}
|
|
9119
9583
|
|
|
9120
9584
|
// src/extract/calls/aws.ts
|
|
9121
|
-
import
|
|
9585
|
+
import path40 from "path";
|
|
9122
9586
|
import { infraId as infraId4 } from "@neat.is/types";
|
|
9123
9587
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
9124
9588
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -9152,7 +9616,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
9152
9616
|
// (ADR-066).
|
|
9153
9617
|
confidenceKind: "verified-call-site",
|
|
9154
9618
|
evidence: {
|
|
9155
|
-
file:
|
|
9619
|
+
file: path40.relative(serviceDir, file.path),
|
|
9156
9620
|
line,
|
|
9157
9621
|
snippet: snippet(file.content, line)
|
|
9158
9622
|
}
|
|
@@ -9176,7 +9640,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
9176
9640
|
}
|
|
9177
9641
|
|
|
9178
9642
|
// src/extract/calls/grpc.ts
|
|
9179
|
-
import
|
|
9643
|
+
import path41 from "path";
|
|
9180
9644
|
import { infraId as infraId5 } from "@neat.is/types";
|
|
9181
9645
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
9182
9646
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
@@ -9235,7 +9699,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
9235
9699
|
// tier (ADR-066).
|
|
9236
9700
|
confidenceKind: "verified-call-site",
|
|
9237
9701
|
evidence: {
|
|
9238
|
-
file:
|
|
9702
|
+
file: path41.relative(serviceDir, file.path),
|
|
9239
9703
|
line,
|
|
9240
9704
|
snippet: snippet(file.content, line)
|
|
9241
9705
|
}
|
|
@@ -9245,7 +9709,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
9245
9709
|
}
|
|
9246
9710
|
|
|
9247
9711
|
// src/extract/calls/supabase.ts
|
|
9248
|
-
import
|
|
9712
|
+
import path42 from "path";
|
|
9249
9713
|
import { infraId as infraId6 } from "@neat.is/types";
|
|
9250
9714
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
9251
9715
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
@@ -9304,7 +9768,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
9304
9768
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
9305
9769
|
confidenceKind: "verified-call-site",
|
|
9306
9770
|
evidence: {
|
|
9307
|
-
file:
|
|
9771
|
+
file: path42.relative(serviceDir, file.path),
|
|
9308
9772
|
line,
|
|
9309
9773
|
snippet: snippet(file.content, line)
|
|
9310
9774
|
}
|
|
@@ -9331,7 +9795,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
9331
9795
|
edgeType: "CALLS",
|
|
9332
9796
|
confidenceKind: "verified-call-site",
|
|
9333
9797
|
evidence: {
|
|
9334
|
-
file:
|
|
9798
|
+
file: path42.relative(serviceDir, file.path),
|
|
9335
9799
|
line,
|
|
9336
9800
|
snippet: snippet(file.content, line)
|
|
9337
9801
|
}
|
|
@@ -9342,7 +9806,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
9342
9806
|
}
|
|
9343
9807
|
|
|
9344
9808
|
// src/extract/calls/firestore.ts
|
|
9345
|
-
import
|
|
9809
|
+
import path43 from "path";
|
|
9346
9810
|
import Parser8 from "tree-sitter";
|
|
9347
9811
|
import JavaScript6 from "tree-sitter-javascript";
|
|
9348
9812
|
import { infraId as infraId7 } from "@neat.is/types";
|
|
@@ -9513,7 +9977,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
9513
9977
|
const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
|
|
9514
9978
|
if (!hasClient && !hasAdmin) return [];
|
|
9515
9979
|
const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
|
|
9516
|
-
const tree = parseSource3(parserForExt2(
|
|
9980
|
+
const tree = parseSource3(parserForExt2(path43.extname(file.path)), file.content);
|
|
9517
9981
|
const clientVars = firestoreClientVars(tree.rootNode);
|
|
9518
9982
|
const collLine = /* @__PURE__ */ new Map();
|
|
9519
9983
|
const writes = /* @__PURE__ */ new Map();
|
|
@@ -9617,7 +10081,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
9617
10081
|
...columnSet.size > 0 ? { columns: [...columnSet] } : {},
|
|
9618
10082
|
...sdkWrites ? { sdkWrites } : {},
|
|
9619
10083
|
evidence: {
|
|
9620
|
-
file:
|
|
10084
|
+
file: path43.relative(serviceDir, file.path),
|
|
9621
10085
|
line,
|
|
9622
10086
|
snippet: snippet(file.content, line)
|
|
9623
10087
|
}
|
|
@@ -9627,7 +10091,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
9627
10091
|
}
|
|
9628
10092
|
|
|
9629
10093
|
// src/extract/calls/mongoose.ts
|
|
9630
|
-
import
|
|
10094
|
+
import path44 from "path";
|
|
9631
10095
|
import { infraId as infraId8 } from "@neat.is/types";
|
|
9632
10096
|
var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
|
|
9633
10097
|
var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
|
|
@@ -9778,7 +10242,7 @@ function endpoint(r, file, serviceDir, matchText) {
|
|
|
9778
10242
|
kind: r.kind,
|
|
9779
10243
|
edgeType: "CALLS",
|
|
9780
10244
|
confidenceKind: "verified-call-site",
|
|
9781
|
-
evidence: { file:
|
|
10245
|
+
evidence: { file: path44.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
9782
10246
|
};
|
|
9783
10247
|
}
|
|
9784
10248
|
function mongooseEndpointsFromFile(file, serviceDir) {
|
|
@@ -9868,7 +10332,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
9868
10332
|
const registry = /* @__PURE__ */ new Map();
|
|
9869
10333
|
for (const f of mongooseFiles) {
|
|
9870
10334
|
const fx = fileExportsOf(f.content, pluralizeOn);
|
|
9871
|
-
if (fx) registry.set(toPosix(
|
|
10335
|
+
if (fx) registry.set(toPosix(path44.relative(serviceDir, f.path)), fx);
|
|
9872
10336
|
}
|
|
9873
10337
|
if (registry.size === 0) return [];
|
|
9874
10338
|
const out = [];
|
|
@@ -9879,7 +10343,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
9879
10343
|
const directColl = /* @__PURE__ */ new Map();
|
|
9880
10344
|
const nsExports = /* @__PURE__ */ new Map();
|
|
9881
10345
|
for (const b of bindings) {
|
|
9882
|
-
const resolvedRel = await resolveJsImport(b.specifier,
|
|
10346
|
+
const resolvedRel = await resolveJsImport(b.specifier, path44.dirname(f.path), serviceDir, null);
|
|
9883
10347
|
if (!resolvedRel) continue;
|
|
9884
10348
|
const fx = registry.get(resolvedRel);
|
|
9885
10349
|
if (!fx) continue;
|
|
@@ -9922,7 +10386,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
9922
10386
|
}
|
|
9923
10387
|
|
|
9924
10388
|
// src/extract/calls/sqlalchemy.ts
|
|
9925
|
-
import
|
|
10389
|
+
import path45 from "path";
|
|
9926
10390
|
import Parser9 from "tree-sitter";
|
|
9927
10391
|
import Python5 from "tree-sitter-python";
|
|
9928
10392
|
import { infraId as infraId9 } from "@neat.is/types";
|
|
@@ -10061,7 +10525,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
|
|
|
10061
10525
|
childTable,
|
|
10062
10526
|
parentTable,
|
|
10063
10527
|
evidence: {
|
|
10064
|
-
file:
|
|
10528
|
+
file: path45.relative(serviceDir, file.path),
|
|
10065
10529
|
line,
|
|
10066
10530
|
snippet: snippet(file.content, line)
|
|
10067
10531
|
}
|
|
@@ -10086,7 +10550,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
|
10086
10550
|
confidenceKind: "verified-call-site",
|
|
10087
10551
|
...columns && columns.length > 0 ? { columns } : {},
|
|
10088
10552
|
evidence: {
|
|
10089
|
-
file:
|
|
10553
|
+
file: path45.relative(serviceDir, file.path),
|
|
10090
10554
|
line,
|
|
10091
10555
|
snippet: snippet(file.content, line)
|
|
10092
10556
|
}
|
|
@@ -10193,7 +10657,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
|
10193
10657
|
edgeType: "CALLS",
|
|
10194
10658
|
confidenceKind: "verified-call-site",
|
|
10195
10659
|
evidence: {
|
|
10196
|
-
file:
|
|
10660
|
+
file: path45.relative(serviceDir, file.path),
|
|
10197
10661
|
line,
|
|
10198
10662
|
snippet: snippet(file.content, line)
|
|
10199
10663
|
}
|
|
@@ -10204,7 +10668,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
|
10204
10668
|
}
|
|
10205
10669
|
|
|
10206
10670
|
// src/extract/calls/django-orm.ts
|
|
10207
|
-
import
|
|
10671
|
+
import path46 from "path";
|
|
10208
10672
|
import Parser10 from "tree-sitter";
|
|
10209
10673
|
import Python6 from "tree-sitter-python";
|
|
10210
10674
|
import { infraId as infraId10 } from "@neat.is/types";
|
|
@@ -10274,7 +10738,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
10274
10738
|
const tree = parseSource7(makePyParser4(), file.content);
|
|
10275
10739
|
const out = [];
|
|
10276
10740
|
const seen = /* @__PURE__ */ new Set();
|
|
10277
|
-
const defaultAppLabel =
|
|
10741
|
+
const defaultAppLabel = path46.basename(path46.dirname(file.path));
|
|
10278
10742
|
walk4(tree.rootNode, (node) => {
|
|
10279
10743
|
if (node.type !== "class_definition") return;
|
|
10280
10744
|
if (!extendsDjangoModel(node)) return;
|
|
@@ -10292,14 +10756,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
10292
10756
|
kind: "sql-table",
|
|
10293
10757
|
edgeType: "CALLS",
|
|
10294
10758
|
confidenceKind: "verified-call-site",
|
|
10295
|
-
evidence: { file:
|
|
10759
|
+
evidence: { file: path46.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
10296
10760
|
});
|
|
10297
10761
|
});
|
|
10298
10762
|
return out;
|
|
10299
10763
|
}
|
|
10300
10764
|
|
|
10301
10765
|
// src/extract/calls/drizzle.ts
|
|
10302
|
-
import
|
|
10766
|
+
import path47 from "path";
|
|
10303
10767
|
import Parser11 from "tree-sitter";
|
|
10304
10768
|
import JavaScript7 from "tree-sitter-javascript";
|
|
10305
10769
|
import { infraId as infraId11 } from "@neat.is/types";
|
|
@@ -10377,7 +10841,7 @@ function columnsFromObject(obj) {
|
|
|
10377
10841
|
}
|
|
10378
10842
|
function drizzleEndpointsFromFile(file, serviceDir) {
|
|
10379
10843
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
10380
|
-
const tree = parseSource3(parserForExt3(
|
|
10844
|
+
const tree = parseSource3(parserForExt3(path47.extname(file.path)), file.content);
|
|
10381
10845
|
const out = [];
|
|
10382
10846
|
const seen = /* @__PURE__ */ new Set();
|
|
10383
10847
|
const walk9 = (node) => {
|
|
@@ -10400,7 +10864,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
10400
10864
|
confidenceKind: "structural",
|
|
10401
10865
|
columns,
|
|
10402
10866
|
evidence: {
|
|
10403
|
-
file:
|
|
10867
|
+
file: path47.relative(serviceDir, file.path),
|
|
10404
10868
|
line,
|
|
10405
10869
|
snippet: snippet(file.content, line)
|
|
10406
10870
|
}
|
|
@@ -10466,7 +10930,7 @@ function referencesTargetVar(call) {
|
|
|
10466
10930
|
}
|
|
10467
10931
|
function drizzleForeignKeys(file, serviceDir) {
|
|
10468
10932
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
10469
|
-
const tree = parseSource3(parserForExt3(
|
|
10933
|
+
const tree = parseSource3(parserForExt3(path47.extname(file.path)), file.content);
|
|
10470
10934
|
const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
|
|
10471
10935
|
const out = [];
|
|
10472
10936
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -10485,7 +10949,7 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
10485
10949
|
childTable: table.tableName,
|
|
10486
10950
|
parentTable,
|
|
10487
10951
|
evidence: {
|
|
10488
|
-
file:
|
|
10952
|
+
file: path47.relative(serviceDir, file.path),
|
|
10489
10953
|
line,
|
|
10490
10954
|
snippet: snippet(file.content, line)
|
|
10491
10955
|
}
|
|
@@ -10501,7 +10965,7 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
10501
10965
|
}
|
|
10502
10966
|
|
|
10503
10967
|
// src/extract/calls/prisma.ts
|
|
10504
|
-
import
|
|
10968
|
+
import path48 from "path";
|
|
10505
10969
|
import { infraId as infraId12 } from "@neat.is/types";
|
|
10506
10970
|
var SCALAR_TYPES = /* @__PURE__ */ new Set([
|
|
10507
10971
|
"Int",
|
|
@@ -10557,7 +11021,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
|
|
|
10557
11021
|
confidenceKind: "structural",
|
|
10558
11022
|
columns: b.columns,
|
|
10559
11023
|
evidence: {
|
|
10560
|
-
file:
|
|
11024
|
+
file: path48.relative(serviceDir, file.path),
|
|
10561
11025
|
line: b.startLine,
|
|
10562
11026
|
snippet: snippet(content, b.startLine)
|
|
10563
11027
|
}
|
|
@@ -10618,7 +11082,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
|
|
|
10618
11082
|
}
|
|
10619
11083
|
async function prismaColumnEndpoints(serviceDir) {
|
|
10620
11084
|
const schemaPath = await findFirst(serviceDir, [
|
|
10621
|
-
|
|
11085
|
+
path48.join("prisma", "schema.prisma"),
|
|
10622
11086
|
"schema.prisma"
|
|
10623
11087
|
]);
|
|
10624
11088
|
if (!schemaPath) return [];
|
|
@@ -10693,7 +11157,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
|
10693
11157
|
childTable: current.table,
|
|
10694
11158
|
parentTable,
|
|
10695
11159
|
evidence: {
|
|
10696
|
-
file:
|
|
11160
|
+
file: path48.relative(serviceDir, file.path),
|
|
10697
11161
|
line: lineNo,
|
|
10698
11162
|
snippet: snippet(content, lineNo)
|
|
10699
11163
|
}
|
|
@@ -10708,7 +11172,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
|
10708
11172
|
}
|
|
10709
11173
|
async function prismaForeignKeys(serviceDir) {
|
|
10710
11174
|
const schemaPath = await findFirst(serviceDir, [
|
|
10711
|
-
|
|
11175
|
+
path48.join("prisma", "schema.prisma"),
|
|
10712
11176
|
"schema.prisma"
|
|
10713
11177
|
]);
|
|
10714
11178
|
if (!schemaPath) return [];
|
|
@@ -10718,7 +11182,7 @@ async function prismaForeignKeys(serviceDir) {
|
|
|
10718
11182
|
}
|
|
10719
11183
|
|
|
10720
11184
|
// src/extract/calls/activerecord.ts
|
|
10721
|
-
import
|
|
11185
|
+
import path49 from "path";
|
|
10722
11186
|
import Parser12 from "tree-sitter";
|
|
10723
11187
|
import Ruby3 from "tree-sitter-ruby";
|
|
10724
11188
|
import { infraId as infraId13 } from "@neat.is/types";
|
|
@@ -10926,7 +11390,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
|
|
|
10926
11390
|
confidenceKind: "structural",
|
|
10927
11391
|
...table.columns.length > 0 ? { columns: table.columns } : {},
|
|
10928
11392
|
evidence: {
|
|
10929
|
-
file:
|
|
11393
|
+
file: path49.relative(serviceDir, file.path),
|
|
10930
11394
|
line: table.line,
|
|
10931
11395
|
snippet: snippet(file.content, table.line)
|
|
10932
11396
|
}
|
|
@@ -10948,7 +11412,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
|
|
|
10948
11412
|
childTable,
|
|
10949
11413
|
parentTable,
|
|
10950
11414
|
evidence: {
|
|
10951
|
-
file:
|
|
11415
|
+
file: path49.relative(serviceDir, file.path),
|
|
10952
11416
|
line,
|
|
10953
11417
|
snippet: snippet(file.content, line)
|
|
10954
11418
|
}
|
|
@@ -11031,7 +11495,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
|
|
|
11031
11495
|
edgeType: "CALLS",
|
|
11032
11496
|
confidenceKind: "verified-call-site",
|
|
11033
11497
|
evidence: {
|
|
11034
|
-
file:
|
|
11498
|
+
file: path49.relative(serviceDir, file.path),
|
|
11035
11499
|
line,
|
|
11036
11500
|
snippet: snippet(file.content, line)
|
|
11037
11501
|
}
|
|
@@ -11068,7 +11532,7 @@ function railsModelForeignKeys(file, serviceDir) {
|
|
|
11068
11532
|
childTable,
|
|
11069
11533
|
parentTable,
|
|
11070
11534
|
evidence: {
|
|
11071
|
-
file:
|
|
11535
|
+
file: path49.relative(serviceDir, file.path),
|
|
11072
11536
|
line,
|
|
11073
11537
|
snippet: snippet(file.content, line)
|
|
11074
11538
|
}
|
|
@@ -11079,7 +11543,7 @@ function railsModelForeignKeys(file, serviceDir) {
|
|
|
11079
11543
|
}
|
|
11080
11544
|
|
|
11081
11545
|
// src/extract/calls/eloquent.ts
|
|
11082
|
-
import
|
|
11546
|
+
import path50 from "path";
|
|
11083
11547
|
import Parser13 from "tree-sitter";
|
|
11084
11548
|
import Php3 from "tree-sitter-php";
|
|
11085
11549
|
import { infraId as infraId14 } from "@neat.is/types";
|
|
@@ -11336,7 +11800,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
|
|
|
11336
11800
|
confidenceKind: "structural",
|
|
11337
11801
|
...columns.length > 0 ? { columns } : {},
|
|
11338
11802
|
evidence: {
|
|
11339
|
-
file:
|
|
11803
|
+
file: path50.relative(serviceDir, file.path),
|
|
11340
11804
|
line: bp.line,
|
|
11341
11805
|
snippet: snippet(file.content, bp.line)
|
|
11342
11806
|
}
|
|
@@ -11358,7 +11822,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
|
|
|
11358
11822
|
childTable,
|
|
11359
11823
|
parentTable,
|
|
11360
11824
|
evidence: {
|
|
11361
|
-
file:
|
|
11825
|
+
file: path50.relative(serviceDir, file.path),
|
|
11362
11826
|
line,
|
|
11363
11827
|
snippet: snippet(file.content, line)
|
|
11364
11828
|
}
|
|
@@ -11478,7 +11942,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
|
|
|
11478
11942
|
edgeType: "CALLS",
|
|
11479
11943
|
confidenceKind: "verified-call-site",
|
|
11480
11944
|
evidence: {
|
|
11481
|
-
file:
|
|
11945
|
+
file: path50.relative(serviceDir, file.path),
|
|
11482
11946
|
line,
|
|
11483
11947
|
snippet: snippet(file.content, line)
|
|
11484
11948
|
}
|
|
@@ -11514,7 +11978,7 @@ function laravelModelForeignKeys(file, serviceDir) {
|
|
|
11514
11978
|
childTable,
|
|
11515
11979
|
parentTable,
|
|
11516
11980
|
evidence: {
|
|
11517
|
-
file:
|
|
11981
|
+
file: path50.relative(serviceDir, file.path),
|
|
11518
11982
|
line,
|
|
11519
11983
|
snippet: snippet(file.content, line)
|
|
11520
11984
|
}
|
|
@@ -11525,7 +11989,7 @@ function laravelModelForeignKeys(file, serviceDir) {
|
|
|
11525
11989
|
}
|
|
11526
11990
|
|
|
11527
11991
|
// src/extract/calls/go.ts
|
|
11528
|
-
import
|
|
11992
|
+
import path51 from "path";
|
|
11529
11993
|
import Parser14 from "tree-sitter";
|
|
11530
11994
|
import Go4 from "tree-sitter-go";
|
|
11531
11995
|
import { infraId as infraId15 } from "@neat.is/types";
|
|
@@ -11599,7 +12063,7 @@ function firstStringLiteralArg(argsNode) {
|
|
|
11599
12063
|
return null;
|
|
11600
12064
|
}
|
|
11601
12065
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
11602
|
-
if (
|
|
12066
|
+
if (path51.extname(file.path) !== ".go") return [];
|
|
11603
12067
|
if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
|
|
11604
12068
|
const tree = parseSource10(makeGoParser3(), file.content);
|
|
11605
12069
|
const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
|
|
@@ -11628,7 +12092,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
11628
12092
|
confidenceKind: "verified-call-site",
|
|
11629
12093
|
...columns.length > 0 ? { columns } : {},
|
|
11630
12094
|
evidence: {
|
|
11631
|
-
file: toPosix(
|
|
12095
|
+
file: toPosix(path51.relative(serviceDir, file.path)),
|
|
11632
12096
|
line,
|
|
11633
12097
|
snippet: snippet(file.content, line)
|
|
11634
12098
|
}
|
|
@@ -11638,7 +12102,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
11638
12102
|
}
|
|
11639
12103
|
|
|
11640
12104
|
// src/extract/calls/gorm.ts
|
|
11641
|
-
import
|
|
12105
|
+
import path52 from "path";
|
|
11642
12106
|
import Parser15 from "tree-sitter";
|
|
11643
12107
|
import Go5 from "tree-sitter-go";
|
|
11644
12108
|
import { infraId as infraId16 } from "@neat.is/types";
|
|
@@ -12071,7 +12535,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
|
12071
12535
|
seen.delete(struct.name);
|
|
12072
12536
|
}
|
|
12073
12537
|
function gormEndpointsFromFile(file, serviceDir) {
|
|
12074
|
-
if (
|
|
12538
|
+
if (path52.extname(file.path) !== ".go") return [];
|
|
12075
12539
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
12076
12540
|
const tree = parseSource11(makeGoParser4(), file.content);
|
|
12077
12541
|
const { structs, models, tableFor } = analyze(tree);
|
|
@@ -12093,7 +12557,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
12093
12557
|
confidenceKind: "structural",
|
|
12094
12558
|
...columns.length > 0 ? { columns } : {},
|
|
12095
12559
|
evidence: {
|
|
12096
|
-
file: toPosix(
|
|
12560
|
+
file: toPosix(path52.relative(serviceDir, file.path)),
|
|
12097
12561
|
line: struct.line,
|
|
12098
12562
|
snippet: snippet(file.content, struct.line)
|
|
12099
12563
|
}
|
|
@@ -12102,7 +12566,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
12102
12566
|
return out;
|
|
12103
12567
|
}
|
|
12104
12568
|
function gormForeignKeys(file, serviceDir) {
|
|
12105
|
-
if (
|
|
12569
|
+
if (path52.extname(file.path) !== ".go") return [];
|
|
12106
12570
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
12107
12571
|
const tree = parseSource11(makeGoParser4(), file.content);
|
|
12108
12572
|
const { structs, models, tableFor } = analyze(tree);
|
|
@@ -12117,7 +12581,7 @@ function gormForeignKeys(file, serviceDir) {
|
|
|
12117
12581
|
childTable,
|
|
12118
12582
|
parentTable,
|
|
12119
12583
|
evidence: {
|
|
12120
|
-
file: toPosix(
|
|
12584
|
+
file: toPosix(path52.relative(serviceDir, file.path)),
|
|
12121
12585
|
line,
|
|
12122
12586
|
snippet: snippet(file.content, line)
|
|
12123
12587
|
}
|
|
@@ -12170,7 +12634,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
12170
12634
|
let nodesAdded = 0;
|
|
12171
12635
|
let edgesAdded = 0;
|
|
12172
12636
|
for (const service of services) {
|
|
12173
|
-
const files = await loadSourceFiles(service.dir);
|
|
12637
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
12174
12638
|
const endpoints = [];
|
|
12175
12639
|
const maskedFiles = [];
|
|
12176
12640
|
for (const file of files) {
|
|
@@ -12220,7 +12684,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
12220
12684
|
if (!graph.hasNode(ep.infraId)) {
|
|
12221
12685
|
const node = {
|
|
12222
12686
|
id: ep.infraId,
|
|
12223
|
-
type:
|
|
12687
|
+
type: NodeType24.InfraNode,
|
|
12224
12688
|
name: ep.name,
|
|
12225
12689
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
12226
12690
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -12233,7 +12697,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
12233
12697
|
}
|
|
12234
12698
|
if (ep.columns && ep.columns.length > 0) {
|
|
12235
12699
|
const node = graph.getNodeAttributes(ep.infraId);
|
|
12236
|
-
if (node.type ===
|
|
12700
|
+
if (node.type === NodeType24.InfraNode) {
|
|
12237
12701
|
graph.replaceNodeAttributes(ep.infraId, {
|
|
12238
12702
|
...node,
|
|
12239
12703
|
columns: foldColumns(
|
|
@@ -12247,7 +12711,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
12247
12711
|
}
|
|
12248
12712
|
if (ep.sdkWrites && Object.keys(ep.sdkWrites).length > 0) {
|
|
12249
12713
|
const node = graph.getNodeAttributes(ep.infraId);
|
|
12250
|
-
if (node.type ===
|
|
12714
|
+
if (node.type === NodeType24.InfraNode) {
|
|
12251
12715
|
graph.replaceNodeAttributes(ep.infraId, {
|
|
12252
12716
|
...node,
|
|
12253
12717
|
columns: foldSdkWrites(node.columns, ep.sdkWrites)
|
|
@@ -12309,7 +12773,7 @@ async function addCallEdges(graph, services) {
|
|
|
12309
12773
|
// src/extract/table-edges.ts
|
|
12310
12774
|
import {
|
|
12311
12775
|
EdgeType as EdgeType16,
|
|
12312
|
-
NodeType as
|
|
12776
|
+
NodeType as NodeType25,
|
|
12313
12777
|
Provenance as Provenance16,
|
|
12314
12778
|
confidenceForExtracted as confidenceForExtracted13,
|
|
12315
12779
|
extractedEdgeId as extractedEdgeId10,
|
|
@@ -12319,7 +12783,7 @@ async function addTableEdges(graph, services) {
|
|
|
12319
12783
|
let nodesAdded = 0;
|
|
12320
12784
|
let edgesAdded = 0;
|
|
12321
12785
|
for (const service of services) {
|
|
12322
|
-
const files = await loadSourceFiles(service.dir);
|
|
12786
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
12323
12787
|
const refs = [];
|
|
12324
12788
|
const modelRefs = [];
|
|
12325
12789
|
for (const file of files) {
|
|
@@ -12368,7 +12832,7 @@ function ensureTableNode(graph, id, name) {
|
|
|
12368
12832
|
if (graph.hasNode(id)) return 0;
|
|
12369
12833
|
const node = {
|
|
12370
12834
|
id,
|
|
12371
|
-
type:
|
|
12835
|
+
type: NodeType25.InfraNode,
|
|
12372
12836
|
name,
|
|
12373
12837
|
provider: "self",
|
|
12374
12838
|
kind: "sql-table"
|
|
@@ -12378,15 +12842,15 @@ function ensureTableNode(graph, id, name) {
|
|
|
12378
12842
|
}
|
|
12379
12843
|
|
|
12380
12844
|
// src/extract/infra/docker-compose.ts
|
|
12381
|
-
import
|
|
12845
|
+
import path53 from "path";
|
|
12382
12846
|
import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
|
|
12383
12847
|
|
|
12384
12848
|
// src/extract/infra/shared.ts
|
|
12385
|
-
import { NodeType as
|
|
12849
|
+
import { NodeType as NodeType26, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId18 } from "@neat.is/types";
|
|
12386
12850
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
12387
12851
|
return {
|
|
12388
12852
|
id: infraId18(kind, name),
|
|
12389
|
-
type:
|
|
12853
|
+
type: NodeType26.InfraNode,
|
|
12390
12854
|
name,
|
|
12391
12855
|
provider,
|
|
12392
12856
|
kind,
|
|
@@ -12448,7 +12912,7 @@ function dependsOnList(value) {
|
|
|
12448
12912
|
}
|
|
12449
12913
|
function serviceNameToServiceNode(name, services) {
|
|
12450
12914
|
for (const s of services) {
|
|
12451
|
-
if (s.node.name === name ||
|
|
12915
|
+
if (s.node.name === name || path53.basename(s.dir) === name) return s.node.id;
|
|
12452
12916
|
}
|
|
12453
12917
|
return null;
|
|
12454
12918
|
}
|
|
@@ -12457,7 +12921,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
12457
12921
|
let edgesAdded = 0;
|
|
12458
12922
|
let composePath = null;
|
|
12459
12923
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
12460
|
-
const abs =
|
|
12924
|
+
const abs = path53.join(scanPath, name);
|
|
12461
12925
|
if (await exists(abs)) {
|
|
12462
12926
|
composePath = abs;
|
|
12463
12927
|
break;
|
|
@@ -12470,13 +12934,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
12470
12934
|
} catch (err) {
|
|
12471
12935
|
recordExtractionError(
|
|
12472
12936
|
"infra docker-compose",
|
|
12473
|
-
|
|
12937
|
+
path53.relative(scanPath, composePath),
|
|
12474
12938
|
err
|
|
12475
12939
|
);
|
|
12476
12940
|
return { nodesAdded, edgesAdded };
|
|
12477
12941
|
}
|
|
12478
12942
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
12479
|
-
const evidenceFile =
|
|
12943
|
+
const evidenceFile = path53.relative(scanPath, composePath).split(path53.sep).join("/");
|
|
12480
12944
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
12481
12945
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
12482
12946
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -12517,8 +12981,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
12517
12981
|
}
|
|
12518
12982
|
|
|
12519
12983
|
// src/extract/infra/dockerfile.ts
|
|
12520
|
-
import
|
|
12521
|
-
import { promises as
|
|
12984
|
+
import path54 from "path";
|
|
12985
|
+
import { promises as fs24 } from "fs";
|
|
12522
12986
|
import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
|
|
12523
12987
|
function readDockerfile(content) {
|
|
12524
12988
|
let image = null;
|
|
@@ -12548,15 +13012,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
12548
13012
|
let nodesAdded = 0;
|
|
12549
13013
|
let edgesAdded = 0;
|
|
12550
13014
|
for (const service of services) {
|
|
12551
|
-
const dockerfilePath =
|
|
13015
|
+
const dockerfilePath = path54.join(service.dir, "Dockerfile");
|
|
12552
13016
|
if (!await exists(dockerfilePath)) continue;
|
|
12553
13017
|
let content;
|
|
12554
13018
|
try {
|
|
12555
|
-
content = await
|
|
13019
|
+
content = await fs24.readFile(dockerfilePath, "utf8");
|
|
12556
13020
|
} catch (err) {
|
|
12557
13021
|
recordExtractionError(
|
|
12558
13022
|
"infra dockerfile",
|
|
12559
|
-
|
|
13023
|
+
path54.relative(scanPath, dockerfilePath),
|
|
12560
13024
|
err
|
|
12561
13025
|
);
|
|
12562
13026
|
continue;
|
|
@@ -12568,8 +13032,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
12568
13032
|
graph.addNode(node.id, node);
|
|
12569
13033
|
nodesAdded++;
|
|
12570
13034
|
}
|
|
12571
|
-
const relDockerfile = toPosix(
|
|
12572
|
-
const evidenceFile = toPosix(
|
|
13035
|
+
const relDockerfile = toPosix(path54.relative(service.dir, dockerfilePath));
|
|
13036
|
+
const evidenceFile = toPosix(path54.relative(scanPath, dockerfilePath));
|
|
12573
13037
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
12574
13038
|
graph,
|
|
12575
13039
|
service.pkg.name,
|
|
@@ -12620,23 +13084,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
12620
13084
|
}
|
|
12621
13085
|
|
|
12622
13086
|
// src/extract/infra/terraform.ts
|
|
12623
|
-
import { promises as
|
|
12624
|
-
import
|
|
13087
|
+
import { promises as fs25 } from "fs";
|
|
13088
|
+
import path55 from "path";
|
|
12625
13089
|
import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
|
|
12626
13090
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
12627
13091
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
12628
13092
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
12629
13093
|
if (depth > max) return [];
|
|
12630
13094
|
const out = [];
|
|
12631
|
-
const entries = await
|
|
13095
|
+
const entries = await fs25.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
12632
13096
|
for (const entry of entries) {
|
|
12633
13097
|
if (entry.isDirectory()) {
|
|
12634
13098
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
12635
|
-
const child =
|
|
13099
|
+
const child = path55.join(start, entry.name);
|
|
12636
13100
|
if (await isPythonVenvDir(child)) continue;
|
|
12637
13101
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
12638
13102
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
12639
|
-
out.push(
|
|
13103
|
+
out.push(path55.join(start, entry.name));
|
|
12640
13104
|
}
|
|
12641
13105
|
}
|
|
12642
13106
|
return out;
|
|
@@ -12667,8 +13131,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
12667
13131
|
let edgesAdded = 0;
|
|
12668
13132
|
const files = await walkTfFiles(scanPath);
|
|
12669
13133
|
for (const file of files) {
|
|
12670
|
-
const content = await
|
|
12671
|
-
const evidenceFile = toPosix(
|
|
13134
|
+
const content = await fs25.readFile(file, "utf8");
|
|
13135
|
+
const evidenceFile = toPosix(path55.relative(scanPath, file));
|
|
12672
13136
|
const resources = [];
|
|
12673
13137
|
const byKey = /* @__PURE__ */ new Map();
|
|
12674
13138
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -12724,8 +13188,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
12724
13188
|
}
|
|
12725
13189
|
|
|
12726
13190
|
// src/extract/infra/k8s.ts
|
|
12727
|
-
import { promises as
|
|
12728
|
-
import
|
|
13191
|
+
import { promises as fs26 } from "fs";
|
|
13192
|
+
import path56 from "path";
|
|
12729
13193
|
import { parseAllDocuments as parseAllDocuments2 } from "yaml";
|
|
12730
13194
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
12731
13195
|
Service: "k8s-service",
|
|
@@ -12739,15 +13203,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
12739
13203
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
12740
13204
|
if (depth > max) return [];
|
|
12741
13205
|
const out = [];
|
|
12742
|
-
const entries = await
|
|
13206
|
+
const entries = await fs26.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
12743
13207
|
for (const entry of entries) {
|
|
12744
13208
|
if (entry.isDirectory()) {
|
|
12745
13209
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
12746
|
-
const child =
|
|
13210
|
+
const child = path56.join(start, entry.name);
|
|
12747
13211
|
if (await isPythonVenvDir(child)) continue;
|
|
12748
13212
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
12749
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
12750
|
-
out.push(
|
|
13213
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path56.extname(entry.name))) {
|
|
13214
|
+
out.push(path56.join(start, entry.name));
|
|
12751
13215
|
}
|
|
12752
13216
|
}
|
|
12753
13217
|
return out;
|
|
@@ -12756,7 +13220,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
12756
13220
|
let nodesAdded = 0;
|
|
12757
13221
|
const files = await walkYamlFiles2(scanPath);
|
|
12758
13222
|
for (const file of files) {
|
|
12759
|
-
const content = await
|
|
13223
|
+
const content = await fs26.readFile(file, "utf8");
|
|
12760
13224
|
let docs;
|
|
12761
13225
|
try {
|
|
12762
13226
|
docs = parseAllDocuments2(content).map((d) => d.toJSON());
|
|
@@ -12779,17 +13243,17 @@ async function addK8sResources(graph, scanPath) {
|
|
|
12779
13243
|
}
|
|
12780
13244
|
|
|
12781
13245
|
// src/extract/infra/cloudflare.ts
|
|
12782
|
-
import { promises as
|
|
12783
|
-
import
|
|
12784
|
-
import { parse as
|
|
13246
|
+
import { promises as fs27 } from "fs";
|
|
13247
|
+
import path57 from "path";
|
|
13248
|
+
import { parse as parseToml3 } from "smol-toml";
|
|
12785
13249
|
import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
|
|
12786
13250
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
12787
13251
|
async function readWranglerConfig(dir) {
|
|
12788
13252
|
for (const filename of WRANGLER_FILENAMES) {
|
|
12789
|
-
const abs =
|
|
13253
|
+
const abs = path57.join(dir, filename);
|
|
12790
13254
|
if (!await exists(abs)) continue;
|
|
12791
|
-
const raw = await
|
|
12792
|
-
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));
|
|
12793
13257
|
return { config, relFile: filename, raw };
|
|
12794
13258
|
}
|
|
12795
13259
|
return null;
|
|
@@ -12849,11 +13313,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
12849
13313
|
try {
|
|
12850
13314
|
read = await readWranglerConfig(service.dir);
|
|
12851
13315
|
} catch (err) {
|
|
12852
|
-
recordExtractionError("infra cloudflare",
|
|
13316
|
+
recordExtractionError("infra cloudflare", path57.relative(scanPath, service.dir), err);
|
|
12853
13317
|
continue;
|
|
12854
13318
|
}
|
|
12855
13319
|
if (!read || !read.config.name) continue;
|
|
12856
|
-
const evidenceFile = toPosix(
|
|
13320
|
+
const evidenceFile = toPosix(path57.relative(scanPath, path57.join(service.dir, read.relFile)));
|
|
12857
13321
|
discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
|
|
12858
13322
|
}
|
|
12859
13323
|
for (const worker of discovered) {
|
|
@@ -12865,7 +13329,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
12865
13329
|
}
|
|
12866
13330
|
let anchorId = service.node.id;
|
|
12867
13331
|
if (config.main) {
|
|
12868
|
-
const entryRelPath = toPosix(
|
|
13332
|
+
const entryRelPath = toPosix(path57.normalize(config.main));
|
|
12869
13333
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
12870
13334
|
graph,
|
|
12871
13335
|
service.pkg.name,
|
|
@@ -13011,24 +13475,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
13011
13475
|
}
|
|
13012
13476
|
|
|
13013
13477
|
// src/extract/infra/vercel.ts
|
|
13014
|
-
import { promises as
|
|
13015
|
-
import
|
|
13478
|
+
import { promises as fs28 } from "fs";
|
|
13479
|
+
import path58 from "path";
|
|
13016
13480
|
import { EdgeType as EdgeType21 } from "@neat.is/types";
|
|
13017
13481
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
13018
13482
|
async function readVercelConfig(dir) {
|
|
13019
13483
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
13020
|
-
const abs =
|
|
13484
|
+
const abs = path58.join(dir, filename);
|
|
13021
13485
|
if (!await exists(abs)) continue;
|
|
13022
|
-
const raw = await
|
|
13486
|
+
const raw = await fs28.readFile(abs, "utf8");
|
|
13023
13487
|
const config = JSON.parse(maskCommentsInSource(raw));
|
|
13024
13488
|
return { config, relFile: filename, raw };
|
|
13025
13489
|
}
|
|
13026
13490
|
return null;
|
|
13027
13491
|
}
|
|
13028
13492
|
async function readLinkedProjectName(dir) {
|
|
13029
|
-
const abs =
|
|
13493
|
+
const abs = path58.join(dir, ".vercel", "project.json");
|
|
13030
13494
|
if (!await exists(abs)) return void 0;
|
|
13031
|
-
const parsed = JSON.parse(await
|
|
13495
|
+
const parsed = JSON.parse(await fs28.readFile(abs, "utf8"));
|
|
13032
13496
|
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
13033
13497
|
}
|
|
13034
13498
|
function routeSource(route) {
|
|
@@ -13044,7 +13508,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
13044
13508
|
read = await readVercelConfig(service.dir);
|
|
13045
13509
|
projectName = await readLinkedProjectName(service.dir);
|
|
13046
13510
|
} catch (err) {
|
|
13047
|
-
recordExtractionError("infra vercel",
|
|
13511
|
+
recordExtractionError("infra vercel", path58.relative(scanPath, service.dir), err);
|
|
13048
13512
|
continue;
|
|
13049
13513
|
}
|
|
13050
13514
|
if (!read && !projectName) continue;
|
|
@@ -13060,7 +13524,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
13060
13524
|
const anchorId = service.node.id;
|
|
13061
13525
|
if (!read) continue;
|
|
13062
13526
|
const { config, relFile, raw } = read;
|
|
13063
|
-
const evidenceFile = toPosix(
|
|
13527
|
+
const evidenceFile = toPosix(path58.relative(scanPath, path58.join(service.dir, relFile)));
|
|
13064
13528
|
const add = (edgeType, kind, name) => {
|
|
13065
13529
|
if (!name) return;
|
|
13066
13530
|
const result = emitPlatformResourceEdge(
|
|
@@ -13088,17 +13552,17 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
13088
13552
|
}
|
|
13089
13553
|
|
|
13090
13554
|
// src/extract/infra/railway.ts
|
|
13091
|
-
import { promises as
|
|
13092
|
-
import
|
|
13093
|
-
import { parse as
|
|
13555
|
+
import { promises as fs29 } from "fs";
|
|
13556
|
+
import path59 from "path";
|
|
13557
|
+
import { parse as parseToml4 } from "smol-toml";
|
|
13094
13558
|
import { EdgeType as EdgeType22 } from "@neat.is/types";
|
|
13095
13559
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
13096
13560
|
async function readRailwayConfig(dir) {
|
|
13097
13561
|
for (const filename of RAILWAY_FILENAMES) {
|
|
13098
|
-
const abs =
|
|
13562
|
+
const abs = path59.join(dir, filename);
|
|
13099
13563
|
if (!await exists(abs)) continue;
|
|
13100
|
-
const raw = await
|
|
13101
|
-
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));
|
|
13102
13566
|
return { config, relFile: filename, raw };
|
|
13103
13567
|
}
|
|
13104
13568
|
return null;
|
|
@@ -13111,7 +13575,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
13111
13575
|
try {
|
|
13112
13576
|
read = await readRailwayConfig(service.dir);
|
|
13113
13577
|
} catch (err) {
|
|
13114
|
-
recordExtractionError("infra railway",
|
|
13578
|
+
recordExtractionError("infra railway", path59.relative(scanPath, service.dir), err);
|
|
13115
13579
|
continue;
|
|
13116
13580
|
}
|
|
13117
13581
|
if (!read) continue;
|
|
@@ -13121,7 +13585,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
13121
13585
|
}
|
|
13122
13586
|
const anchorId = service.node.id;
|
|
13123
13587
|
const { config, relFile, raw } = read;
|
|
13124
|
-
const evidenceFile = toPosix(
|
|
13588
|
+
const evidenceFile = toPosix(path59.relative(scanPath, path59.join(service.dir, relFile)));
|
|
13125
13589
|
const add = (edgeType, kind, name) => {
|
|
13126
13590
|
if (!name) return;
|
|
13127
13591
|
const result = emitPlatformResourceEdge(
|
|
@@ -13145,16 +13609,16 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
13145
13609
|
}
|
|
13146
13610
|
|
|
13147
13611
|
// src/extract/infra/supabase.ts
|
|
13148
|
-
import { promises as
|
|
13149
|
-
import
|
|
13150
|
-
import { parse as
|
|
13612
|
+
import { promises as fs30 } from "fs";
|
|
13613
|
+
import path60 from "path";
|
|
13614
|
+
import { parse as parseToml5 } from "smol-toml";
|
|
13151
13615
|
import { EdgeType as EdgeType23 } from "@neat.is/types";
|
|
13152
13616
|
async function readSupabaseConfig(dir) {
|
|
13153
|
-
const relFile =
|
|
13154
|
-
const abs =
|
|
13617
|
+
const relFile = path60.join("supabase", "config.toml");
|
|
13618
|
+
const abs = path60.join(dir, relFile);
|
|
13155
13619
|
if (!await exists(abs)) return null;
|
|
13156
|
-
const raw = await
|
|
13157
|
-
const config =
|
|
13620
|
+
const raw = await fs30.readFile(abs, "utf8");
|
|
13621
|
+
const config = parseToml5(raw);
|
|
13158
13622
|
return { config, relFile, raw };
|
|
13159
13623
|
}
|
|
13160
13624
|
async function addSupabaseProjects(graph, services, scanPath) {
|
|
@@ -13165,7 +13629,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
13165
13629
|
try {
|
|
13166
13630
|
read = await readSupabaseConfig(service.dir);
|
|
13167
13631
|
} catch (err) {
|
|
13168
|
-
recordExtractionError("infra supabase",
|
|
13632
|
+
recordExtractionError("infra supabase", path60.relative(scanPath, service.dir), err);
|
|
13169
13633
|
continue;
|
|
13170
13634
|
}
|
|
13171
13635
|
if (!read) continue;
|
|
@@ -13180,7 +13644,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
13180
13644
|
});
|
|
13181
13645
|
}
|
|
13182
13646
|
const anchorId = service.node.id;
|
|
13183
|
-
const evidenceFile = toPosix(
|
|
13647
|
+
const evidenceFile = toPosix(path60.relative(scanPath, path60.join(service.dir, relFile)));
|
|
13184
13648
|
const add = (edgeType, kind, name) => {
|
|
13185
13649
|
if (!name) return;
|
|
13186
13650
|
const result = emitPlatformResourceEdge(
|
|
@@ -13221,12 +13685,12 @@ async function addInfra(graph, scanPath, services) {
|
|
|
13221
13685
|
}
|
|
13222
13686
|
|
|
13223
13687
|
// src/extract/zod-shapes.ts
|
|
13224
|
-
import
|
|
13688
|
+
import path61 from "path";
|
|
13225
13689
|
import Parser16 from "tree-sitter";
|
|
13226
13690
|
import JavaScript8 from "tree-sitter-javascript";
|
|
13227
13691
|
import {
|
|
13228
13692
|
EdgeType as EdgeType24,
|
|
13229
|
-
NodeType as
|
|
13693
|
+
NodeType as NodeType27,
|
|
13230
13694
|
Provenance as Provenance22,
|
|
13231
13695
|
confidenceForExtracted as confidenceForExtracted19,
|
|
13232
13696
|
extractedEdgeId as extractedEdgeId11,
|
|
@@ -13323,7 +13787,7 @@ function topLevelSchemas(root) {
|
|
|
13323
13787
|
}
|
|
13324
13788
|
function zodShapesFromFile(file, serviceDir) {
|
|
13325
13789
|
if (!ZOD_IMPORT_RE.test(file.content)) return [];
|
|
13326
|
-
const tree = parseSource3(parserForExt4(
|
|
13790
|
+
const tree = parseSource3(parserForExt4(path61.extname(file.path)), file.content);
|
|
13327
13791
|
const out = [];
|
|
13328
13792
|
const seen = /* @__PURE__ */ new Set();
|
|
13329
13793
|
for (const { name, call } of topLevelSchemas(tree.rootNode)) {
|
|
@@ -13341,7 +13805,7 @@ function zodShapesFromFile(file, serviceDir) {
|
|
|
13341
13805
|
name,
|
|
13342
13806
|
fields,
|
|
13343
13807
|
evidence: {
|
|
13344
|
-
file:
|
|
13808
|
+
file: path61.relative(serviceDir, file.path),
|
|
13345
13809
|
line,
|
|
13346
13810
|
snippet: snippet(file.content, line)
|
|
13347
13811
|
}
|
|
@@ -13358,7 +13822,7 @@ async function addZodShapes(graph, services) {
|
|
|
13358
13822
|
...service.pkg.devDependencies ?? {}
|
|
13359
13823
|
};
|
|
13360
13824
|
if (deps["zod"] === void 0) continue;
|
|
13361
|
-
const files = await loadSourceFiles(service.dir);
|
|
13825
|
+
const files = await loadSourceFiles(service.dir, service.excludeDirs);
|
|
13362
13826
|
for (const file of files) {
|
|
13363
13827
|
if (isTestPath(file.path)) continue;
|
|
13364
13828
|
let shapes;
|
|
@@ -13372,7 +13836,7 @@ async function addZodShapes(graph, services) {
|
|
|
13372
13836
|
if (!graph.hasNode(shape.infraId)) {
|
|
13373
13837
|
const node = {
|
|
13374
13838
|
id: shape.infraId,
|
|
13375
|
-
type:
|
|
13839
|
+
type: NodeType27.InfraNode,
|
|
13376
13840
|
name: shape.name,
|
|
13377
13841
|
provider: "self",
|
|
13378
13842
|
kind: "zod-schema"
|
|
@@ -13382,7 +13846,7 @@ async function addZodShapes(graph, services) {
|
|
|
13382
13846
|
}
|
|
13383
13847
|
if (shape.fields.length > 0) {
|
|
13384
13848
|
const node = graph.getNodeAttributes(shape.infraId);
|
|
13385
|
-
if (node.type ===
|
|
13849
|
+
if (node.type === NodeType27.InfraNode) {
|
|
13386
13850
|
graph.replaceNodeAttributes(shape.infraId, {
|
|
13387
13851
|
...node,
|
|
13388
13852
|
columns: foldColumns(
|
|
@@ -13424,7 +13888,7 @@ async function addZodShapes(graph, services) {
|
|
|
13424
13888
|
}
|
|
13425
13889
|
|
|
13426
13890
|
// src/extract/firestore-rules.ts
|
|
13427
|
-
import { NodeType as
|
|
13891
|
+
import { NodeType as NodeType28 } from "@neat.is/types";
|
|
13428
13892
|
var FIRESTORE_COLLECTION_KIND = "firestore-collection";
|
|
13429
13893
|
var WRITE_METHODS = /* @__PURE__ */ new Set(["write", "create", "update"]);
|
|
13430
13894
|
function stripComments(src) {
|
|
@@ -13564,7 +14028,7 @@ async function addFirestoreRules(graph, services) {
|
|
|
13564
14028
|
if (guards.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
|
|
13565
14029
|
graph.forEachNode((id, attrs) => {
|
|
13566
14030
|
const node = attrs;
|
|
13567
|
-
if (node.type !==
|
|
14031
|
+
if (node.type !== NodeType28.InfraNode) return;
|
|
13568
14032
|
if (node.kind !== FIRESTORE_COLLECTION_KIND) return;
|
|
13569
14033
|
const fields = guards.get(collectionKeyFromName(node.name));
|
|
13570
14034
|
if (!fields || fields.size === 0) return;
|
|
@@ -13577,16 +14041,16 @@ async function addFirestoreRules(graph, services) {
|
|
|
13577
14041
|
}
|
|
13578
14042
|
|
|
13579
14043
|
// src/extract/index.ts
|
|
13580
|
-
import
|
|
14044
|
+
import path63 from "path";
|
|
13581
14045
|
|
|
13582
14046
|
// src/extract/retire.ts
|
|
13583
14047
|
import { existsSync as existsSync2 } from "fs";
|
|
13584
|
-
import
|
|
13585
|
-
import { NodeType as
|
|
14048
|
+
import path62 from "path";
|
|
14049
|
+
import { NodeType as NodeType29, Provenance as Provenance23 } from "@neat.is/types";
|
|
13586
14050
|
function dropOrphanedFileNodes(graph) {
|
|
13587
14051
|
const orphans = [];
|
|
13588
14052
|
graph.forEachNode((id, attrs) => {
|
|
13589
|
-
if (attrs.type !==
|
|
14053
|
+
if (attrs.type !== NodeType29.FileNode) return;
|
|
13590
14054
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
13591
14055
|
orphans.push(id);
|
|
13592
14056
|
}
|
|
@@ -13615,11 +14079,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
13615
14079
|
if (edge.provenance !== Provenance23.EXTRACTED) return;
|
|
13616
14080
|
const evidenceFile = edge.evidence?.file;
|
|
13617
14081
|
if (!evidenceFile) return;
|
|
13618
|
-
if (
|
|
14082
|
+
if (path62.isAbsolute(evidenceFile)) {
|
|
13619
14083
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
13620
14084
|
return;
|
|
13621
14085
|
}
|
|
13622
|
-
const found = bases.some((base) => existsSync2(
|
|
14086
|
+
const found = bases.some((base) => existsSync2(path62.join(base, evidenceFile)));
|
|
13623
14087
|
if (!found) toDrop.push(id);
|
|
13624
14088
|
});
|
|
13625
14089
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -13676,7 +14140,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
13676
14140
|
}
|
|
13677
14141
|
const droppedEntries = drainDroppedExtracted();
|
|
13678
14142
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
13679
|
-
const rejectedPath =
|
|
14143
|
+
const rejectedPath = path63.join(path63.dirname(opts.errorsPath), "rejected.ndjson");
|
|
13680
14144
|
try {
|
|
13681
14145
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
13682
14146
|
} catch (err) {
|
|
@@ -13713,11 +14177,11 @@ import {
|
|
|
13713
14177
|
databaseId as databaseId3,
|
|
13714
14178
|
DivergenceResultSchema,
|
|
13715
14179
|
EdgeType as EdgeType25,
|
|
13716
|
-
NodeType as
|
|
14180
|
+
NodeType as NodeType30,
|
|
13717
14181
|
parseEdgeId,
|
|
13718
14182
|
parseFileId,
|
|
13719
14183
|
Provenance as Provenance24,
|
|
13720
|
-
serviceId as
|
|
14184
|
+
serviceId as serviceId13
|
|
13721
14185
|
} from "@neat.is/types";
|
|
13722
14186
|
function bucketKey(source, target, type) {
|
|
13723
14187
|
return `${type}|${source}|${target}`;
|
|
@@ -13727,8 +14191,8 @@ function bucketSourceFor(graph, edge) {
|
|
|
13727
14191
|
const parsed = parseFileId(edge.source);
|
|
13728
14192
|
if (!parsed || !graph.hasNode(edge.target)) return edge.source;
|
|
13729
14193
|
const target = graph.getNodeAttributes(edge.target);
|
|
13730
|
-
if (target.type !==
|
|
13731
|
-
return
|
|
14194
|
+
if (target.type !== NodeType30.DatabaseNode) return edge.source;
|
|
14195
|
+
return serviceId13(parsed.service);
|
|
13732
14196
|
}
|
|
13733
14197
|
function bucketEdges(graph) {
|
|
13734
14198
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
@@ -13759,22 +14223,22 @@ function bucketEdges(graph) {
|
|
|
13759
14223
|
function nodeIsFrontier(graph, nodeId) {
|
|
13760
14224
|
if (!graph.hasNode(nodeId)) return false;
|
|
13761
14225
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
13762
|
-
return attrs.type ===
|
|
14226
|
+
return attrs.type === NodeType30.FrontierNode;
|
|
13763
14227
|
}
|
|
13764
14228
|
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
13765
14229
|
if (!graph.hasNode(nodeId)) return false;
|
|
13766
14230
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
13767
|
-
return attrs.type ===
|
|
14231
|
+
return attrs.type === NodeType30.WebSocketChannelNode;
|
|
13768
14232
|
}
|
|
13769
14233
|
function nodeIsServerAction(graph, nodeId) {
|
|
13770
14234
|
if (!graph.hasNode(nodeId)) return false;
|
|
13771
14235
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
13772
|
-
return attrs.type ===
|
|
14236
|
+
return attrs.type === NodeType30.ServerActionNode;
|
|
13773
14237
|
}
|
|
13774
14238
|
function nodeIsSymbol(graph, nodeId) {
|
|
13775
14239
|
if (!graph.hasNode(nodeId)) return false;
|
|
13776
14240
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
13777
|
-
return attrs.type ===
|
|
14241
|
+
return attrs.type === NodeType30.SymbolNode;
|
|
13778
14242
|
}
|
|
13779
14243
|
function clampConfidence(n) {
|
|
13780
14244
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -13859,7 +14323,7 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
13859
14323
|
if (edge.type !== EdgeType25.CONNECTS_TO) continue;
|
|
13860
14324
|
if (edge.provenance !== Provenance24.OBSERVED) continue;
|
|
13861
14325
|
const target = graph.getNodeAttributes(edge.target);
|
|
13862
|
-
if (target.type !==
|
|
14326
|
+
if (target.type !== NodeType30.DatabaseNode) continue;
|
|
13863
14327
|
const observedHost = target.host?.trim();
|
|
13864
14328
|
if (!observedHost) continue;
|
|
13865
14329
|
if (observedHost === declaredHost) continue;
|
|
@@ -13884,7 +14348,7 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
13884
14348
|
if (edge.type !== EdgeType25.CONNECTS_TO) continue;
|
|
13885
14349
|
if (edge.provenance !== Provenance24.OBSERVED) continue;
|
|
13886
14350
|
const target = graph.getNodeAttributes(edge.target);
|
|
13887
|
-
if (target.type !==
|
|
14351
|
+
if (target.type !== NodeType30.DatabaseNode) continue;
|
|
13888
14352
|
for (const pair of compatPairs()) {
|
|
13889
14353
|
if (pair.engine !== target.engine) continue;
|
|
13890
14354
|
const declared = deps[pair.driver];
|
|
@@ -14000,13 +14464,13 @@ function computeDivergences(graph, opts = {}) {
|
|
|
14000
14464
|
}
|
|
14001
14465
|
graph.forEachNode((nodeId, attrs) => {
|
|
14002
14466
|
const n = attrs;
|
|
14003
|
-
if (n.type ===
|
|
14467
|
+
if (n.type === NodeType30.ServiceNode) {
|
|
14004
14468
|
const svc = n;
|
|
14005
14469
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
14006
14470
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
14007
14471
|
return;
|
|
14008
14472
|
}
|
|
14009
|
-
if (n.type ===
|
|
14473
|
+
if (n.type === NodeType30.InfraNode && n.kind === "sql-table") {
|
|
14010
14474
|
for (const d of detectColumnDrift(n)) all.push(d);
|
|
14011
14475
|
}
|
|
14012
14476
|
});
|
|
@@ -14050,9 +14514,9 @@ function computeDivergences(graph, opts = {}) {
|
|
|
14050
14514
|
}
|
|
14051
14515
|
|
|
14052
14516
|
// src/persist.ts
|
|
14053
|
-
import { promises as
|
|
14054
|
-
import
|
|
14055
|
-
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";
|
|
14056
14520
|
var SCHEMA_VERSION = 7;
|
|
14057
14521
|
function migrateV1ToV2(payload) {
|
|
14058
14522
|
const nodes = payload.graph.nodes;
|
|
@@ -14076,7 +14540,7 @@ function migrateV5ToV6(payload) {
|
|
|
14076
14540
|
if (Array.isArray(nodes)) {
|
|
14077
14541
|
for (const node of nodes) {
|
|
14078
14542
|
const attrs = node.attributes;
|
|
14079
|
-
if (!attrs || attrs.type !==
|
|
14543
|
+
if (!attrs || attrs.type !== NodeType31.InfraNode) continue;
|
|
14080
14544
|
if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
|
|
14081
14545
|
if (!Array.isArray(attrs.columns)) attrs.columns = [];
|
|
14082
14546
|
}
|
|
@@ -14106,7 +14570,7 @@ function migrateV2ToV3(payload) {
|
|
|
14106
14570
|
return { ...payload, schemaVersion: 3 };
|
|
14107
14571
|
}
|
|
14108
14572
|
async function ensureDir(filePath) {
|
|
14109
|
-
await
|
|
14573
|
+
await fs31.mkdir(path64.dirname(filePath), { recursive: true });
|
|
14110
14574
|
}
|
|
14111
14575
|
async function saveGraphToDisk(graph, outPath) {
|
|
14112
14576
|
await ensureDir(outPath);
|
|
@@ -14116,13 +14580,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
14116
14580
|
graph: graph.export()
|
|
14117
14581
|
};
|
|
14118
14582
|
const tmp = `${outPath}.tmp`;
|
|
14119
|
-
await
|
|
14120
|
-
await
|
|
14583
|
+
await fs31.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
14584
|
+
await fs31.rename(tmp, outPath);
|
|
14121
14585
|
}
|
|
14122
14586
|
async function loadGraphFromDisk(graph, outPath) {
|
|
14123
14587
|
let raw;
|
|
14124
14588
|
try {
|
|
14125
|
-
raw = await
|
|
14589
|
+
raw = await fs31.readFile(outPath, "utf8");
|
|
14126
14590
|
} catch (err) {
|
|
14127
14591
|
if (err.code === "ENOENT") return;
|
|
14128
14592
|
throw err;
|
|
@@ -14195,7 +14659,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
14195
14659
|
}
|
|
14196
14660
|
|
|
14197
14661
|
// src/diff.ts
|
|
14198
|
-
import { promises as
|
|
14662
|
+
import { promises as fs32 } from "fs";
|
|
14199
14663
|
async function loadSnapshotForDiff(target) {
|
|
14200
14664
|
if (/^https?:\/\//i.test(target)) {
|
|
14201
14665
|
const res = await fetch(target);
|
|
@@ -14204,7 +14668,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
14204
14668
|
}
|
|
14205
14669
|
return await res.json();
|
|
14206
14670
|
}
|
|
14207
|
-
const raw = await
|
|
14671
|
+
const raw = await fs32.readFile(target, "utf8");
|
|
14208
14672
|
return JSON.parse(raw);
|
|
14209
14673
|
}
|
|
14210
14674
|
function indexEntries(entries) {
|
|
@@ -14271,23 +14735,23 @@ function canonicalJson(value) {
|
|
|
14271
14735
|
}
|
|
14272
14736
|
|
|
14273
14737
|
// src/projects.ts
|
|
14274
|
-
import
|
|
14738
|
+
import path65 from "path";
|
|
14275
14739
|
function pathsForProject(project, baseDir) {
|
|
14276
14740
|
if (project === DEFAULT_PROJECT) {
|
|
14277
14741
|
return {
|
|
14278
|
-
snapshotPath:
|
|
14279
|
-
errorsPath:
|
|
14280
|
-
staleEventsPath:
|
|
14281
|
-
embeddingsCachePath:
|
|
14282
|
-
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")
|
|
14283
14747
|
};
|
|
14284
14748
|
}
|
|
14285
14749
|
return {
|
|
14286
|
-
snapshotPath:
|
|
14287
|
-
errorsPath:
|
|
14288
|
-
staleEventsPath:
|
|
14289
|
-
embeddingsCachePath:
|
|
14290
|
-
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`)
|
|
14291
14755
|
};
|
|
14292
14756
|
}
|
|
14293
14757
|
var Projects = class {
|
|
@@ -14326,9 +14790,9 @@ function parseExtraProjects(raw) {
|
|
|
14326
14790
|
}
|
|
14327
14791
|
|
|
14328
14792
|
// src/registry.ts
|
|
14329
|
-
import { promises as
|
|
14793
|
+
import { promises as fs33 } from "fs";
|
|
14330
14794
|
import os2 from "os";
|
|
14331
|
-
import
|
|
14795
|
+
import path66 from "path";
|
|
14332
14796
|
import {
|
|
14333
14797
|
RegistryFileSchema
|
|
14334
14798
|
} from "@neat.is/types";
|
|
@@ -14336,20 +14800,20 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
14336
14800
|
var LOCK_RETRY_MS = 50;
|
|
14337
14801
|
function neatHome() {
|
|
14338
14802
|
const override = process.env.NEAT_HOME;
|
|
14339
|
-
if (override && override.length > 0) return
|
|
14340
|
-
return
|
|
14803
|
+
if (override && override.length > 0) return path66.resolve(override);
|
|
14804
|
+
return path66.join(os2.homedir(), ".neat");
|
|
14341
14805
|
}
|
|
14342
14806
|
function registryPath() {
|
|
14343
|
-
return
|
|
14807
|
+
return path66.join(neatHome(), "projects.json");
|
|
14344
14808
|
}
|
|
14345
14809
|
function registryLockPath() {
|
|
14346
|
-
return
|
|
14810
|
+
return path66.join(neatHome(), "projects.json.lock");
|
|
14347
14811
|
}
|
|
14348
14812
|
function daemonPidPath() {
|
|
14349
|
-
return
|
|
14813
|
+
return path66.join(neatHome(), "neatd.pid");
|
|
14350
14814
|
}
|
|
14351
14815
|
function daemonsDir() {
|
|
14352
|
-
return
|
|
14816
|
+
return path66.join(neatHome(), "daemons");
|
|
14353
14817
|
}
|
|
14354
14818
|
function isFiniteInt(v) {
|
|
14355
14819
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -14382,7 +14846,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
14382
14846
|
const dir = daemonsDir();
|
|
14383
14847
|
let names;
|
|
14384
14848
|
try {
|
|
14385
|
-
names = await
|
|
14849
|
+
names = await fs33.readdir(dir);
|
|
14386
14850
|
} catch (err) {
|
|
14387
14851
|
if (err.code === "ENOENT") return [];
|
|
14388
14852
|
throw err;
|
|
@@ -14390,10 +14854,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
14390
14854
|
const out = [];
|
|
14391
14855
|
for (const name of names) {
|
|
14392
14856
|
if (!name.endsWith(".json")) continue;
|
|
14393
|
-
const file =
|
|
14857
|
+
const file = path66.join(dir, name);
|
|
14394
14858
|
let raw;
|
|
14395
14859
|
try {
|
|
14396
|
-
raw = await
|
|
14860
|
+
raw = await fs33.readFile(file, "utf8");
|
|
14397
14861
|
} catch {
|
|
14398
14862
|
continue;
|
|
14399
14863
|
}
|
|
@@ -14406,7 +14870,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
14406
14870
|
return out;
|
|
14407
14871
|
}
|
|
14408
14872
|
async function removeDaemonRecord(source) {
|
|
14409
|
-
await
|
|
14873
|
+
await fs33.unlink(source).catch(() => {
|
|
14410
14874
|
});
|
|
14411
14875
|
}
|
|
14412
14876
|
async function listMachineProjects(probe = defaultDiscoveryProbe) {
|
|
@@ -14463,7 +14927,7 @@ function isPidAliveDefault(pid) {
|
|
|
14463
14927
|
}
|
|
14464
14928
|
async function readPidFile(file) {
|
|
14465
14929
|
try {
|
|
14466
|
-
const raw = await
|
|
14930
|
+
const raw = await fs33.readFile(file, "utf8");
|
|
14467
14931
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
14468
14932
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
14469
14933
|
} catch {
|
|
@@ -14511,32 +14975,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
14511
14975
|
}
|
|
14512
14976
|
}
|
|
14513
14977
|
async function normalizeProjectPath(input) {
|
|
14514
|
-
const resolved =
|
|
14978
|
+
const resolved = path66.resolve(input);
|
|
14515
14979
|
try {
|
|
14516
|
-
return await
|
|
14980
|
+
return await fs33.realpath(resolved);
|
|
14517
14981
|
} catch {
|
|
14518
14982
|
return resolved;
|
|
14519
14983
|
}
|
|
14520
14984
|
}
|
|
14521
14985
|
async function writeAtomically(target, contents) {
|
|
14522
|
-
await
|
|
14986
|
+
await fs33.mkdir(path66.dirname(target), { recursive: true });
|
|
14523
14987
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
14524
|
-
const fd = await
|
|
14988
|
+
const fd = await fs33.open(tmp, "w");
|
|
14525
14989
|
try {
|
|
14526
14990
|
await fd.writeFile(contents, "utf8");
|
|
14527
14991
|
await fd.sync();
|
|
14528
14992
|
} finally {
|
|
14529
14993
|
await fd.close();
|
|
14530
14994
|
}
|
|
14531
|
-
await
|
|
14995
|
+
await fs33.rename(tmp, target);
|
|
14532
14996
|
}
|
|
14533
14997
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
14534
14998
|
const deadline = Date.now() + timeoutMs;
|
|
14535
|
-
await
|
|
14999
|
+
await fs33.mkdir(path66.dirname(lockPath), { recursive: true });
|
|
14536
15000
|
let probedHolder = false;
|
|
14537
15001
|
while (true) {
|
|
14538
15002
|
try {
|
|
14539
|
-
const fd = await
|
|
15003
|
+
const fd = await fs33.open(lockPath, "wx");
|
|
14540
15004
|
try {
|
|
14541
15005
|
await fd.writeFile(`${process.pid}
|
|
14542
15006
|
`, "utf8");
|
|
@@ -14561,7 +15025,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
14561
15025
|
}
|
|
14562
15026
|
}
|
|
14563
15027
|
async function releaseLock(lockPath) {
|
|
14564
|
-
await
|
|
15028
|
+
await fs33.unlink(lockPath).catch(() => {
|
|
14565
15029
|
});
|
|
14566
15030
|
}
|
|
14567
15031
|
async function withLock(fn) {
|
|
@@ -14577,7 +15041,7 @@ async function readRegistry() {
|
|
|
14577
15041
|
const file = registryPath();
|
|
14578
15042
|
let raw;
|
|
14579
15043
|
try {
|
|
14580
|
-
raw = await
|
|
15044
|
+
raw = await fs33.readFile(file, "utf8");
|
|
14581
15045
|
} catch (err) {
|
|
14582
15046
|
if (err.code === "ENOENT") {
|
|
14583
15047
|
return { version: 1, projects: [] };
|
|
@@ -14682,7 +15146,7 @@ function pruneTtlMs() {
|
|
|
14682
15146
|
}
|
|
14683
15147
|
async function statPathStatus(p) {
|
|
14684
15148
|
try {
|
|
14685
|
-
const stat = await
|
|
15149
|
+
const stat = await fs33.stat(p);
|
|
14686
15150
|
return stat.isDirectory() ? "present" : "unknown";
|
|
14687
15151
|
} catch (err) {
|
|
14688
15152
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -14727,14 +15191,14 @@ import cors from "@fastify/cors";
|
|
|
14727
15191
|
import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
|
|
14728
15192
|
|
|
14729
15193
|
// src/extend/index.ts
|
|
14730
|
-
import { promises as
|
|
14731
|
-
import
|
|
15194
|
+
import { promises as fs35 } from "fs";
|
|
15195
|
+
import path68 from "path";
|
|
14732
15196
|
import os3 from "os";
|
|
14733
15197
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
14734
15198
|
|
|
14735
15199
|
// src/installers/package-manager.ts
|
|
14736
|
-
import { promises as
|
|
14737
|
-
import
|
|
15200
|
+
import { promises as fs34 } from "fs";
|
|
15201
|
+
import path67 from "path";
|
|
14738
15202
|
import { spawn } from "child_process";
|
|
14739
15203
|
var LOCKFILE_PRIORITY = [
|
|
14740
15204
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -14749,29 +15213,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
14749
15213
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
14750
15214
|
async function exists2(p) {
|
|
14751
15215
|
try {
|
|
14752
|
-
await
|
|
15216
|
+
await fs34.access(p);
|
|
14753
15217
|
return true;
|
|
14754
15218
|
} catch {
|
|
14755
15219
|
return false;
|
|
14756
15220
|
}
|
|
14757
15221
|
}
|
|
14758
15222
|
async function detectPackageManager(serviceDir) {
|
|
14759
|
-
let dir =
|
|
15223
|
+
let dir = path67.resolve(serviceDir);
|
|
14760
15224
|
const stops = /* @__PURE__ */ new Set();
|
|
14761
15225
|
for (let i = 0; i < 64; i++) {
|
|
14762
15226
|
if (stops.has(dir)) break;
|
|
14763
15227
|
stops.add(dir);
|
|
14764
15228
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
14765
|
-
const lockPath =
|
|
15229
|
+
const lockPath = path67.join(dir, candidate.lockfile);
|
|
14766
15230
|
if (await exists2(lockPath)) {
|
|
14767
15231
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
14768
15232
|
}
|
|
14769
15233
|
}
|
|
14770
|
-
const parent =
|
|
15234
|
+
const parent = path67.dirname(dir);
|
|
14771
15235
|
if (parent === dir) break;
|
|
14772
15236
|
dir = parent;
|
|
14773
15237
|
}
|
|
14774
|
-
return { pm: "npm", cwd:
|
|
15238
|
+
return { pm: "npm", cwd: path67.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
14775
15239
|
}
|
|
14776
15240
|
async function runPackageManagerInstall(cmd) {
|
|
14777
15241
|
return new Promise((resolve) => {
|
|
@@ -14813,15 +15277,15 @@ ${err.message}`
|
|
|
14813
15277
|
// src/extend/index.ts
|
|
14814
15278
|
async function fileExists2(p) {
|
|
14815
15279
|
try {
|
|
14816
|
-
await
|
|
15280
|
+
await fs35.access(p);
|
|
14817
15281
|
return true;
|
|
14818
15282
|
} catch {
|
|
14819
15283
|
return false;
|
|
14820
15284
|
}
|
|
14821
15285
|
}
|
|
14822
15286
|
async function readPackageJson(scanPath) {
|
|
14823
|
-
const pkgPath =
|
|
14824
|
-
const raw = await
|
|
15287
|
+
const pkgPath = path68.join(scanPath, "package.json");
|
|
15288
|
+
const raw = await fs35.readFile(pkgPath, "utf8");
|
|
14825
15289
|
return JSON.parse(raw);
|
|
14826
15290
|
}
|
|
14827
15291
|
var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -14835,15 +15299,15 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
14835
15299
|
async function findHookFiles(scanPath) {
|
|
14836
15300
|
const found = [];
|
|
14837
15301
|
const walk9 = async (dir) => {
|
|
14838
|
-
const entries = await
|
|
15302
|
+
const entries = await fs35.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
14839
15303
|
for (const entry of entries) {
|
|
14840
15304
|
if (entry.isDirectory()) {
|
|
14841
15305
|
if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
|
|
14842
|
-
await walk9(
|
|
15306
|
+
await walk9(path68.join(dir, entry.name));
|
|
14843
15307
|
} else if (entry.isFile()) {
|
|
14844
15308
|
if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
|
|
14845
|
-
const rel =
|
|
14846
|
-
found.push(rel.split(
|
|
15309
|
+
const rel = path68.relative(scanPath, path68.join(dir, entry.name));
|
|
15310
|
+
found.push(rel.split(path68.sep).join("/"));
|
|
14847
15311
|
}
|
|
14848
15312
|
}
|
|
14849
15313
|
}
|
|
@@ -14854,7 +15318,7 @@ async function findHookFiles(scanPath) {
|
|
|
14854
15318
|
async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
14855
15319
|
let fallback = null;
|
|
14856
15320
|
for (const file of hookFiles) {
|
|
14857
|
-
const content = await
|
|
15321
|
+
const content = await fs35.readFile(path68.join(scanPath, file), "utf8");
|
|
14858
15322
|
const patched = splicedContent(content, snippet2);
|
|
14859
15323
|
if (patched !== null) return { file, content, patched };
|
|
14860
15324
|
if (fallback === null) fallback = { file, content };
|
|
@@ -14862,12 +15326,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
|
14862
15326
|
return { file: fallback.file, content: fallback.content, patched: null };
|
|
14863
15327
|
}
|
|
14864
15328
|
function extendLogPath() {
|
|
14865
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
15329
|
+
return process.env.NEAT_EXTEND_LOG ?? path68.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
14866
15330
|
}
|
|
14867
15331
|
async function appendExtendLog(entry) {
|
|
14868
15332
|
const logPath = extendLogPath();
|
|
14869
|
-
await
|
|
14870
|
-
await
|
|
15333
|
+
await fs35.mkdir(path68.dirname(logPath), { recursive: true });
|
|
15334
|
+
await fs35.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
14871
15335
|
}
|
|
14872
15336
|
function splicedContent(fileContent, snippet2) {
|
|
14873
15337
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -14925,7 +15389,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
14925
15389
|
}
|
|
14926
15390
|
async function describeProjectInstrumentation(ctx) {
|
|
14927
15391
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
14928
|
-
const envNeat = await fileExists2(
|
|
15392
|
+
const envNeat = await fileExists2(path68.join(ctx.scanPath, ".env.neat"));
|
|
14929
15393
|
const registryInstrPackages = new Set(
|
|
14930
15394
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
14931
15395
|
);
|
|
@@ -14947,7 +15411,7 @@ async function applyExtension(ctx, args, options) {
|
|
|
14947
15411
|
);
|
|
14948
15412
|
}
|
|
14949
15413
|
for (const file of hookFiles) {
|
|
14950
|
-
const content = await
|
|
15414
|
+
const content = await fs35.readFile(path68.join(ctx.scanPath, file), "utf8");
|
|
14951
15415
|
if (content.includes(args.registration_snippet)) {
|
|
14952
15416
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
14953
15417
|
}
|
|
@@ -14959,18 +15423,18 @@ async function applyExtension(ctx, args, options) {
|
|
|
14959
15423
|
);
|
|
14960
15424
|
}
|
|
14961
15425
|
const primaryFile = primary.file;
|
|
14962
|
-
const primaryPath =
|
|
15426
|
+
const primaryPath = path68.join(ctx.scanPath, primaryFile);
|
|
14963
15427
|
const filesTouched = [];
|
|
14964
15428
|
const depsAdded = [];
|
|
14965
|
-
const pkgPath =
|
|
15429
|
+
const pkgPath = path68.join(ctx.scanPath, "package.json");
|
|
14966
15430
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
14967
15431
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
14968
15432
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
14969
|
-
await
|
|
15433
|
+
await fs35.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
14970
15434
|
filesTouched.push("package.json");
|
|
14971
15435
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
14972
15436
|
}
|
|
14973
|
-
await
|
|
15437
|
+
await fs35.writeFile(primaryPath, primary.patched, "utf8");
|
|
14974
15438
|
filesTouched.push(primaryFile);
|
|
14975
15439
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
14976
15440
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -15001,7 +15465,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
15001
15465
|
};
|
|
15002
15466
|
}
|
|
15003
15467
|
for (const file of hookFiles) {
|
|
15004
|
-
const content = await
|
|
15468
|
+
const content = await fs35.readFile(path68.join(ctx.scanPath, file), "utf8");
|
|
15005
15469
|
if (content.includes(args.registration_snippet)) {
|
|
15006
15470
|
return {
|
|
15007
15471
|
library: args.library,
|
|
@@ -15036,28 +15500,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
15036
15500
|
if (!await fileExists2(logPath)) {
|
|
15037
15501
|
return { undone: false, message: "no apply found for library" };
|
|
15038
15502
|
}
|
|
15039
|
-
const raw = await
|
|
15503
|
+
const raw = await fs35.readFile(logPath, "utf8");
|
|
15040
15504
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
15041
15505
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
15042
15506
|
if (!match) {
|
|
15043
15507
|
return { undone: false, message: "no apply found for library" };
|
|
15044
15508
|
}
|
|
15045
|
-
const pkgPath =
|
|
15509
|
+
const pkgPath = path68.join(ctx.scanPath, "package.json");
|
|
15046
15510
|
if (await fileExists2(pkgPath)) {
|
|
15047
15511
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
15048
15512
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
15049
15513
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
15050
15514
|
pkg.dependencies = rest;
|
|
15051
|
-
await
|
|
15515
|
+
await fs35.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
15052
15516
|
}
|
|
15053
15517
|
}
|
|
15054
15518
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
15055
15519
|
for (const file of hookFiles) {
|
|
15056
|
-
const filePath =
|
|
15057
|
-
const content = await
|
|
15520
|
+
const filePath = path68.join(ctx.scanPath, file);
|
|
15521
|
+
const content = await fs35.readFile(filePath, "utf8");
|
|
15058
15522
|
if (content.includes(match.registration_snippet)) {
|
|
15059
15523
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
15060
|
-
await
|
|
15524
|
+
await fs35.writeFile(filePath, filtered, "utf8");
|
|
15061
15525
|
break;
|
|
15062
15526
|
}
|
|
15063
15527
|
}
|
|
@@ -15168,8 +15632,8 @@ data: ${JSON.stringify(envelope.payload)}
|
|
|
15168
15632
|
|
|
15169
15633
|
// src/connectors-config.ts
|
|
15170
15634
|
import os4 from "os";
|
|
15171
|
-
import
|
|
15172
|
-
import { promises as
|
|
15635
|
+
import path69 from "path";
|
|
15636
|
+
import { promises as fs36 } from "fs";
|
|
15173
15637
|
var CONNECTORS_CONFIG_VERSION = 1;
|
|
15174
15638
|
var EnvRefUnsetError = class extends Error {
|
|
15175
15639
|
ref;
|
|
@@ -15183,17 +15647,17 @@ var EnvRefUnsetError = class extends Error {
|
|
|
15183
15647
|
};
|
|
15184
15648
|
function neatHome2() {
|
|
15185
15649
|
const override = process.env.NEAT_HOME;
|
|
15186
|
-
if (override && override.length > 0) return
|
|
15187
|
-
return
|
|
15650
|
+
if (override && override.length > 0) return path69.resolve(override);
|
|
15651
|
+
return path69.join(os4.homedir(), ".neat");
|
|
15188
15652
|
}
|
|
15189
15653
|
function connectorsConfigPath(home = neatHome2()) {
|
|
15190
|
-
return
|
|
15654
|
+
return path69.join(home, "connectors.json");
|
|
15191
15655
|
}
|
|
15192
15656
|
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
15193
15657
|
async function warnIfModeLooserThan0600(file) {
|
|
15194
15658
|
if (process.platform === "win32") return;
|
|
15195
15659
|
try {
|
|
15196
|
-
const stat = await
|
|
15660
|
+
const stat = await fs36.stat(file);
|
|
15197
15661
|
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
15198
15662
|
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
15199
15663
|
console.warn(
|
|
@@ -15207,7 +15671,7 @@ async function readConnectorsConfig(home = neatHome2()) {
|
|
|
15207
15671
|
const file = connectorsConfigPath(home);
|
|
15208
15672
|
let raw;
|
|
15209
15673
|
try {
|
|
15210
|
-
raw = await
|
|
15674
|
+
raw = await fs36.readFile(file, "utf8");
|
|
15211
15675
|
} catch (err) {
|
|
15212
15676
|
if (err.code === "ENOENT") {
|
|
15213
15677
|
return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
|
|
@@ -15318,7 +15782,7 @@ function connectorMatchesProject(entry, project) {
|
|
|
15318
15782
|
var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
|
|
15319
15783
|
var CONNECTORS_LOCK_RETRY_MS = 50;
|
|
15320
15784
|
function connectorsConfigLockPath(home = neatHome2()) {
|
|
15321
|
-
return
|
|
15785
|
+
return path69.join(home, "connectors.json.lock");
|
|
15322
15786
|
}
|
|
15323
15787
|
function isEnvRef(value) {
|
|
15324
15788
|
return value.length > 1 && value.startsWith("$");
|
|
@@ -15331,9 +15795,9 @@ function redactCredentialRef(ref) {
|
|
|
15331
15795
|
return out;
|
|
15332
15796
|
}
|
|
15333
15797
|
async function writeConfigAtomically0600(file, contents) {
|
|
15334
|
-
await
|
|
15798
|
+
await fs36.mkdir(path69.dirname(file), { recursive: true });
|
|
15335
15799
|
const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
15336
|
-
const fd = await
|
|
15800
|
+
const fd = await fs36.open(tmp, "w", 384);
|
|
15337
15801
|
try {
|
|
15338
15802
|
await fd.writeFile(contents, "utf8");
|
|
15339
15803
|
await fd.chmod(384);
|
|
@@ -15341,14 +15805,14 @@ async function writeConfigAtomically0600(file, contents) {
|
|
|
15341
15805
|
} finally {
|
|
15342
15806
|
await fd.close();
|
|
15343
15807
|
}
|
|
15344
|
-
await
|
|
15808
|
+
await fs36.rename(tmp, file);
|
|
15345
15809
|
}
|
|
15346
15810
|
async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
|
|
15347
15811
|
const deadline = Date.now() + timeoutMs;
|
|
15348
|
-
await
|
|
15812
|
+
await fs36.mkdir(path69.dirname(lockPath), { recursive: true });
|
|
15349
15813
|
for (; ; ) {
|
|
15350
15814
|
try {
|
|
15351
|
-
const fd = await
|
|
15815
|
+
const fd = await fs36.open(lockPath, "wx");
|
|
15352
15816
|
try {
|
|
15353
15817
|
await fd.writeFile(`${process.pid}
|
|
15354
15818
|
`, "utf8");
|
|
@@ -15368,7 +15832,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
|
|
|
15368
15832
|
}
|
|
15369
15833
|
}
|
|
15370
15834
|
async function releaseConnectorsLock(lockPath) {
|
|
15371
|
-
await
|
|
15835
|
+
await fs36.unlink(lockPath).catch(() => {
|
|
15372
15836
|
});
|
|
15373
15837
|
}
|
|
15374
15838
|
async function withConnectorsLock(home, fn) {
|
|
@@ -15493,7 +15957,7 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
|
|
|
15493
15957
|
}
|
|
15494
15958
|
|
|
15495
15959
|
// src/connectors/index.ts
|
|
15496
|
-
import { NodeType as
|
|
15960
|
+
import { NodeType as NodeType32, parseFileId as parseFileId2, Provenance as Provenance26 } from "@neat.is/types";
|
|
15497
15961
|
var NO_ENV = "unknown";
|
|
15498
15962
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
15499
15963
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
@@ -15512,7 +15976,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
15512
15976
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
15513
15977
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
15514
15978
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
15515
|
-
if (attrs.type !==
|
|
15979
|
+
if (attrs.type !== NodeType32.RouteNode || !attrs.path) return void 0;
|
|
15516
15980
|
const site = { relPath: attrs.path };
|
|
15517
15981
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
15518
15982
|
return site;
|
|
@@ -15997,10 +16461,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
15997
16461
|
// src/connectors/supabase/map.ts
|
|
15998
16462
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
15999
16463
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
16000
|
-
function targetFromRestPath(
|
|
16001
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
16464
|
+
function targetFromRestPath(path70) {
|
|
16465
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path70);
|
|
16002
16466
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
16003
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
16467
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path70);
|
|
16004
16468
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
16005
16469
|
return null;
|
|
16006
16470
|
}
|
|
@@ -16217,7 +16681,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
16217
16681
|
}
|
|
16218
16682
|
|
|
16219
16683
|
// src/connectors/railway/index.ts
|
|
16220
|
-
import { EdgeType as EdgeType27, NodeType as
|
|
16684
|
+
import { EdgeType as EdgeType27, NodeType as NodeType33, serviceId as serviceId14 } from "@neat.is/types";
|
|
16221
16685
|
|
|
16222
16686
|
// src/connectors/railway/client.ts
|
|
16223
16687
|
var DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
|
|
@@ -16367,7 +16831,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
16367
16831
|
const out = [];
|
|
16368
16832
|
graph.forEachNode((_id, attrs) => {
|
|
16369
16833
|
const node = attrs;
|
|
16370
|
-
if (node.type !==
|
|
16834
|
+
if (node.type !== NodeType33.RouteNode) return;
|
|
16371
16835
|
const route = attrs;
|
|
16372
16836
|
if (route.service !== serviceName) return;
|
|
16373
16837
|
out.push({
|
|
@@ -16476,7 +16940,7 @@ function createRailwayResolveTarget(config) {
|
|
|
16476
16940
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
16477
16941
|
const peerName = config.serviceNameById[signal.targetName];
|
|
16478
16942
|
if (!peerName) return null;
|
|
16479
|
-
return { targetNodeId:
|
|
16943
|
+
return { targetNodeId: serviceId14(peerName), serviceName, edgeType: EdgeType27.CONNECTS_TO };
|
|
16480
16944
|
}
|
|
16481
16945
|
return null;
|
|
16482
16946
|
};
|
|
@@ -16595,9 +17059,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
16595
17059
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
16596
17060
|
if (secondSep === -1) return null;
|
|
16597
17061
|
const method = rest.slice(0, secondSep);
|
|
16598
|
-
const
|
|
16599
|
-
if (!resourceName || !method || !
|
|
16600
|
-
return { resourceName, method, path:
|
|
17062
|
+
const path70 = rest.slice(secondSep + 1);
|
|
17063
|
+
if (!resourceName || !method || !path70) return null;
|
|
17064
|
+
return { resourceName, method, path: path70 };
|
|
16601
17065
|
}
|
|
16602
17066
|
function resourceNameFor(type, labels) {
|
|
16603
17067
|
if (!labels) return null;
|
|
@@ -16635,14 +17099,14 @@ function mapLogEntryToSignal(entry) {
|
|
|
16635
17099
|
if (!req) return null;
|
|
16636
17100
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
16637
17101
|
const method = req.requestMethod.toUpperCase();
|
|
16638
|
-
const
|
|
16639
|
-
if (
|
|
17102
|
+
const path70 = pathFromRequestUrl(req.requestUrl);
|
|
17103
|
+
if (path70 === null) return null;
|
|
16640
17104
|
const timestamp = entry.timestamp;
|
|
16641
17105
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
16642
17106
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
16643
17107
|
return {
|
|
16644
17108
|
targetKind: resourceType,
|
|
16645
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
17109
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path70 }),
|
|
16646
17110
|
callCount: 1,
|
|
16647
17111
|
errorCount: isError ? 1 : 0,
|
|
16648
17112
|
lastObservedIso: timestamp
|
|
@@ -16658,7 +17122,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
16658
17122
|
}
|
|
16659
17123
|
|
|
16660
17124
|
// src/connectors/firebase/resolve.ts
|
|
16661
|
-
import { NodeType as
|
|
17125
|
+
import { NodeType as NodeType34, EdgeType as EdgeType28 } from "@neat.is/types";
|
|
16662
17126
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
16663
17127
|
switch (resourceType) {
|
|
16664
17128
|
case "cloud_function":
|
|
@@ -16673,7 +17137,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
16673
17137
|
const entries = [];
|
|
16674
17138
|
graph.forEachNode((_id, attrs) => {
|
|
16675
17139
|
const node = attrs;
|
|
16676
|
-
if (node.type !==
|
|
17140
|
+
if (node.type !== NodeType34.RouteNode) return;
|
|
16677
17141
|
const route = attrs;
|
|
16678
17142
|
if (route.service !== serviceName) return;
|
|
16679
17143
|
entries.push({
|
|
@@ -16728,7 +17192,7 @@ function createFirebaseConnector(graph, serviceMap) {
|
|
|
16728
17192
|
}
|
|
16729
17193
|
|
|
16730
17194
|
// src/connectors/cloudflare/connector.ts
|
|
16731
|
-
import { EdgeType as EdgeType29, NodeType as
|
|
17195
|
+
import { EdgeType as EdgeType29, NodeType as NodeType35, fileId as fileId5, infraId as infraId21 } from "@neat.is/types";
|
|
16732
17196
|
|
|
16733
17197
|
// src/connectors/cloudflare/client.ts
|
|
16734
17198
|
import { randomUUID } from "crypto";
|
|
@@ -16839,7 +17303,7 @@ function mapEventToSignal(event) {
|
|
|
16839
17303
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
16840
17304
|
const statusCode = metadata?.statusCode;
|
|
16841
17305
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
16842
|
-
const
|
|
17306
|
+
const path70 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
16843
17307
|
return {
|
|
16844
17308
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
16845
17309
|
targetName: scriptName,
|
|
@@ -16847,7 +17311,7 @@ function mapEventToSignal(event) {
|
|
|
16847
17311
|
errorCount: isError ? 1 : 0,
|
|
16848
17312
|
lastObservedIso: observedAt.toISOString(),
|
|
16849
17313
|
method,
|
|
16850
|
-
...
|
|
17314
|
+
...path70 ? { path: path70 } : {},
|
|
16851
17315
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
16852
17316
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
16853
17317
|
};
|
|
@@ -16887,19 +17351,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
16887
17351
|
graph.forEachNode((id, attrs) => {
|
|
16888
17352
|
if (found) return;
|
|
16889
17353
|
const a = attrs;
|
|
16890
|
-
if (a.type ===
|
|
17354
|
+
if (a.type === NodeType35.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
16891
17355
|
found = id;
|
|
16892
17356
|
}
|
|
16893
17357
|
});
|
|
16894
17358
|
return found;
|
|
16895
17359
|
}
|
|
16896
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
16897
|
-
const normalizedPath = normalizePathTemplate(
|
|
17360
|
+
function findMatchingRouteNode(graph, serviceName, method, path70) {
|
|
17361
|
+
const normalizedPath = normalizePathTemplate(path70);
|
|
16898
17362
|
let found = null;
|
|
16899
17363
|
graph.forEachNode((id, attrs) => {
|
|
16900
17364
|
if (found) return;
|
|
16901
17365
|
const a = attrs;
|
|
16902
|
-
if (a.type !==
|
|
17366
|
+
if (a.type !== NodeType35.RouteNode || a.service !== serviceName) return;
|
|
16903
17367
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
16904
17368
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
16905
17369
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -16911,10 +17375,10 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
16911
17375
|
return (signal) => {
|
|
16912
17376
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
16913
17377
|
const scriptName = signal.targetName;
|
|
16914
|
-
const { method, path:
|
|
17378
|
+
const { method, path: path70 } = signal;
|
|
16915
17379
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
16916
|
-
if (!method || !
|
|
16917
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
17380
|
+
if (!method || !path70) return wholeFileId;
|
|
17381
|
+
return findMatchingRouteNode(graph, serviceName, method, path70) ?? wholeFileId;
|
|
16918
17382
|
};
|
|
16919
17383
|
const mapping = config.workers?.[scriptName];
|
|
16920
17384
|
if (mapping) {
|
|
@@ -17244,9 +17708,9 @@ function parseCloudRunTargetName(targetName) {
|
|
|
17244
17708
|
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
17245
17709
|
if (secondSep === -1) return null;
|
|
17246
17710
|
const method = rest.slice(0, secondSep);
|
|
17247
|
-
const
|
|
17248
|
-
if (!serviceName || !method || !
|
|
17249
|
-
return { serviceName, method, path:
|
|
17711
|
+
const path70 = rest.slice(secondSep + 1);
|
|
17712
|
+
if (!serviceName || !method || !path70) return null;
|
|
17713
|
+
return { serviceName, method, path: path70 };
|
|
17250
17714
|
}
|
|
17251
17715
|
|
|
17252
17716
|
// src/connectors/cloud-run/map.ts
|
|
@@ -17275,14 +17739,14 @@ function mapLogEntryToSignal2(entry) {
|
|
|
17275
17739
|
if (!req) return null;
|
|
17276
17740
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
17277
17741
|
const method = req.requestMethod.toUpperCase();
|
|
17278
|
-
const
|
|
17279
|
-
if (
|
|
17742
|
+
const path70 = pathFromRequestUrl2(req.requestUrl);
|
|
17743
|
+
if (path70 === null) return null;
|
|
17280
17744
|
const timestamp = entry.timestamp;
|
|
17281
17745
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
17282
17746
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
17283
17747
|
return {
|
|
17284
17748
|
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
17285
|
-
targetName: packCloudRunTargetName({ serviceName, method, path:
|
|
17749
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path70 }),
|
|
17286
17750
|
callCount: 1,
|
|
17287
17751
|
errorCount: isError ? 1 : 0,
|
|
17288
17752
|
lastObservedIso: timestamp
|
|
@@ -17298,14 +17762,14 @@ function mapLogEntriesToSignals2(entries) {
|
|
|
17298
17762
|
}
|
|
17299
17763
|
|
|
17300
17764
|
// src/connectors/cloud-run/resolve.ts
|
|
17301
|
-
import { EdgeType as EdgeType31, NodeType as
|
|
17765
|
+
import { EdgeType as EdgeType31, NodeType as NodeType36, infraId as infraId23 } from "@neat.is/types";
|
|
17302
17766
|
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
17303
17767
|
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
17304
17768
|
let found = null;
|
|
17305
17769
|
graph.forEachNode((_id, attrs) => {
|
|
17306
17770
|
if (found) return;
|
|
17307
17771
|
const node = attrs;
|
|
17308
|
-
if (node.type !==
|
|
17772
|
+
if (node.type !== NodeType36.RouteNode) return;
|
|
17309
17773
|
const route = attrs;
|
|
17310
17774
|
if (route.service !== serviceName || !route.pathTemplate) return;
|
|
17311
17775
|
if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
|
|
@@ -17320,14 +17784,14 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
17320
17784
|
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
17321
17785
|
const identity = parseCloudRunTargetName(signal.targetName);
|
|
17322
17786
|
if (!identity) return null;
|
|
17323
|
-
const { serviceName: gcpServiceName, method, path:
|
|
17787
|
+
const { serviceName: gcpServiceName, method, path: path70 } = identity;
|
|
17324
17788
|
const mappedService = config.serviceMap?.[gcpServiceName];
|
|
17325
17789
|
if (mappedService) {
|
|
17326
17790
|
const routeNodeId = findMatchingRouteNode2(
|
|
17327
17791
|
graph,
|
|
17328
17792
|
mappedService,
|
|
17329
17793
|
method,
|
|
17330
|
-
normalizePathTemplate(
|
|
17794
|
+
normalizePathTemplate(path70)
|
|
17331
17795
|
);
|
|
17332
17796
|
if (routeNodeId) {
|
|
17333
17797
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
|
|
@@ -17376,7 +17840,7 @@ function createCloudRunConnector(graph, config = {}) {
|
|
|
17376
17840
|
}
|
|
17377
17841
|
|
|
17378
17842
|
// src/connectors/render/index.ts
|
|
17379
|
-
import { EdgeType as EdgeType32, NodeType as
|
|
17843
|
+
import { EdgeType as EdgeType32, NodeType as NodeType37 } from "@neat.is/types";
|
|
17380
17844
|
|
|
17381
17845
|
// src/connectors/render/types.ts
|
|
17382
17846
|
function readRenderToken(credentials) {
|
|
@@ -17452,7 +17916,7 @@ function buildRenderRouteIndex(graph, serviceName) {
|
|
|
17452
17916
|
const out = [];
|
|
17453
17917
|
graph.forEachNode((_id, attrs) => {
|
|
17454
17918
|
const node = attrs;
|
|
17455
|
-
if (node.type !==
|
|
17919
|
+
if (node.type !== NodeType37.RouteNode) return;
|
|
17456
17920
|
const route = attrs;
|
|
17457
17921
|
if (route.service !== serviceName) return;
|
|
17458
17922
|
out.push({
|
|
@@ -17936,7 +18400,7 @@ function mapBuildsToSignals(builds, serviceName) {
|
|
|
17936
18400
|
}
|
|
17937
18401
|
|
|
17938
18402
|
// src/connectors/eas/resolve.ts
|
|
17939
|
-
import { EdgeType as EdgeType34, NodeType as
|
|
18403
|
+
import { EdgeType as EdgeType34, NodeType as NodeType38, parseFileId as parseFileId3 } from "@neat.is/types";
|
|
17940
18404
|
var NO_ENV2 = "unknown";
|
|
17941
18405
|
var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
|
|
17942
18406
|
var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
|
|
@@ -17964,7 +18428,7 @@ function findConfigNode(graph, basenames, serviceName) {
|
|
|
17964
18428
|
graph.forEachNode((id, attrs) => {
|
|
17965
18429
|
if (scoped) return;
|
|
17966
18430
|
const node = attrs;
|
|
17967
|
-
if (node.type !==
|
|
18431
|
+
if (node.type !== NodeType38.ConfigNode) return;
|
|
17968
18432
|
if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
|
|
17969
18433
|
if (anyMatch === null) anyMatch = id;
|
|
17970
18434
|
if (configNodeService(graph, id) === serviceName) scoped = id;
|
|
@@ -19576,4 +20040,4 @@ export {
|
|
|
19576
20040
|
deprovisionConnector,
|
|
19577
20041
|
buildApi
|
|
19578
20042
|
};
|
|
19579
|
-
//# sourceMappingURL=chunk-
|
|
20043
|
+
//# sourceMappingURL=chunk-RDLDSIEY.js.map
|