@neat.is/core 0.8.1-dev.20260817 → 0.8.2-dev.20260818
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-3PXRQH53.js → chunk-T4UGEMWN.js} +1176 -524
- package/dist/chunk-T4UGEMWN.js.map +1 -0
- package/dist/{chunk-OJ6BW63H.js → chunk-WLCHTXO3.js} +2 -2
- package/dist/cli.cjs +1710 -1057
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +1398 -745
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +1411 -758
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +1305 -652
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +4 -2
- package/dist/chunk-3PXRQH53.js.map +0 -1
- /package/dist/{chunk-OJ6BW63H.js.map → chunk-WLCHTXO3.js.map} +0 -0
|
@@ -961,7 +961,7 @@ import { promises as fs3 } from "fs";
|
|
|
961
961
|
import path3 from "path";
|
|
962
962
|
import { parse as parseYaml } from "yaml";
|
|
963
963
|
import { extractedEdgeId } from "@neat.is/types";
|
|
964
|
-
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php"]);
|
|
964
|
+
var SERVICE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs", ".cjs", ".ts", ".tsx", ".py", ".go", ".rb", ".php", ".cs", ".java"]);
|
|
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([
|
|
@@ -1301,6 +1301,10 @@ function languageForPath(relPath) {
|
|
|
1301
1301
|
return "ruby";
|
|
1302
1302
|
case ".php":
|
|
1303
1303
|
return "php";
|
|
1304
|
+
case ".cs":
|
|
1305
|
+
return "csharp";
|
|
1306
|
+
case ".java":
|
|
1307
|
+
return "java";
|
|
1304
1308
|
case ".ts":
|
|
1305
1309
|
case ".tsx":
|
|
1306
1310
|
return "typescript";
|
|
@@ -1842,8 +1846,8 @@ function chiRoutesFromSource(source, parser) {
|
|
|
1842
1846
|
chiWalk(tree.rootNode, "", out);
|
|
1843
1847
|
return out;
|
|
1844
1848
|
}
|
|
1845
|
-
function stripChiRegex(
|
|
1846
|
-
return
|
|
1849
|
+
function stripChiRegex(path67) {
|
|
1850
|
+
return path67.replace(/\{([^{}:]+):[^{}]*\}/g, "{$1}");
|
|
1847
1851
|
}
|
|
1848
1852
|
function chiWalk(node, prefix, out) {
|
|
1849
1853
|
for (let i = 0; i < node.namedChildCount; i++) {
|
|
@@ -2515,9 +2519,9 @@ function rubyRocketRoute(args) {
|
|
|
2515
2519
|
if (!pair || pair.type !== "pair") continue;
|
|
2516
2520
|
const k = pair.childForFieldName("key");
|
|
2517
2521
|
if (k?.type !== "string") continue;
|
|
2518
|
-
const
|
|
2519
|
-
if (
|
|
2520
|
-
return { path:
|
|
2522
|
+
const path67 = rubyLiteral(k);
|
|
2523
|
+
if (path67 === null) continue;
|
|
2524
|
+
return { path: path67, target: rubyLiteral(pair.childForFieldName("value")) };
|
|
2521
2525
|
}
|
|
2522
2526
|
return null;
|
|
2523
2527
|
}
|
|
@@ -3794,7 +3798,7 @@ function ensureObservedSymbolNode(graph, fileNodeId, service, relPath, fn, line)
|
|
|
3794
3798
|
}
|
|
3795
3799
|
return sid;
|
|
3796
3800
|
}
|
|
3797
|
-
function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
|
|
3801
|
+
function landObservedSymbol(graph, fileNodeId, service, relPath, callSite, mint = true) {
|
|
3798
3802
|
const line = callSite.line;
|
|
3799
3803
|
if (line === void 0) return fileNodeId;
|
|
3800
3804
|
let sawSymbol = false;
|
|
@@ -3809,7 +3813,7 @@ function landObservedSymbol(graph, fileNodeId, service, relPath, callSite) {
|
|
|
3809
3813
|
}
|
|
3810
3814
|
});
|
|
3811
3815
|
if (candidates.length > 0) return pickContainingSymbol(candidates, callSite.fn);
|
|
3812
|
-
if (sawSymbol && callSite.fn) {
|
|
3816
|
+
if (mint && sawSymbol && callSite.fn) {
|
|
3813
3817
|
return ensureObservedSymbolNode(graph, fileNodeId, service, relPath, callSite.fn, line);
|
|
3814
3818
|
}
|
|
3815
3819
|
return fileNodeId;
|
|
@@ -4215,6 +4219,20 @@ function incidentAffectedNode(span, graph, scanPath) {
|
|
|
4215
4219
|
const callSite = callSiteFromSpan(span, serviceNode, scanPath);
|
|
4216
4220
|
if (callSite) {
|
|
4217
4221
|
const relPath = graph ? reconcileObservedRelPath(graph, span.service, callSite.relPath) : callSite.relPath;
|
|
4222
|
+
const canonicalService = serviceNode && typeof serviceNode.name === "string" ? serviceNode.name : span.service;
|
|
4223
|
+
if (graph) {
|
|
4224
|
+
const fusedFileId = fileId3(canonicalService, relPath);
|
|
4225
|
+
if (graph.hasNode(fusedFileId)) {
|
|
4226
|
+
return landObservedSymbol(
|
|
4227
|
+
graph,
|
|
4228
|
+
fusedFileId,
|
|
4229
|
+
canonicalService,
|
|
4230
|
+
relPath,
|
|
4231
|
+
{ ...callSite, relPath },
|
|
4232
|
+
false
|
|
4233
|
+
);
|
|
4234
|
+
}
|
|
4235
|
+
}
|
|
4218
4236
|
return fileId3(span.service, relPath);
|
|
4219
4237
|
}
|
|
4220
4238
|
return sid;
|
|
@@ -4578,7 +4596,11 @@ async function handleSpan(ctx, span) {
|
|
|
4578
4596
|
...span.exception?.type ? { exceptionType: span.exception.type } : {},
|
|
4579
4597
|
...span.exception?.stacktrace ? { exceptionStacktrace: span.exception.stacktrace } : {},
|
|
4580
4598
|
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
4581
|
-
|
|
4599
|
+
// Attribute to where the failure originated — the symbol / file / service
|
|
4600
|
+
// the throwing span named (incidentAffectedNode, ADR-191) — the same
|
|
4601
|
+
// source-based attribution the durable receiver write uses, not the
|
|
4602
|
+
// outbound edge target this span happened to mint.
|
|
4603
|
+
affectedNode: incidentAffectedNode(span, ctx.graph, ctx.scanPath)
|
|
4582
4604
|
};
|
|
4583
4605
|
await appendErrorEvent(ctx, ev);
|
|
4584
4606
|
}
|
|
@@ -4616,29 +4638,29 @@ function promoteFrontierNodes(graph, opts = {}) {
|
|
|
4616
4638
|
toPromote.push({ frontierId: id, serviceId: target });
|
|
4617
4639
|
});
|
|
4618
4640
|
let promoted = 0;
|
|
4619
|
-
for (const { frontierId: frontierId2, serviceId:
|
|
4641
|
+
for (const { frontierId: frontierId2, serviceId: serviceId12 } of toPromote) {
|
|
4620
4642
|
if (opts.policies && opts.policies.length > 0 && opts.policyCtx) {
|
|
4621
4643
|
const gate = canPromoteFrontier(graph, frontierId2, opts.policies, opts.policyCtx);
|
|
4622
4644
|
if (!gate.allowed) {
|
|
4623
4645
|
continue;
|
|
4624
4646
|
}
|
|
4625
4647
|
}
|
|
4626
|
-
rewireFrontierEdges(graph, frontierId2,
|
|
4648
|
+
rewireFrontierEdges(graph, frontierId2, serviceId12);
|
|
4627
4649
|
graph.dropNode(frontierId2);
|
|
4628
4650
|
promoted++;
|
|
4629
4651
|
}
|
|
4630
4652
|
return promoted;
|
|
4631
4653
|
}
|
|
4632
|
-
function rewireFrontierEdges(graph, frontierId2,
|
|
4654
|
+
function rewireFrontierEdges(graph, frontierId2, serviceId12) {
|
|
4633
4655
|
const inbound = [...graph.inboundEdges(frontierId2)];
|
|
4634
4656
|
const outbound = [...graph.outboundEdges(frontierId2)];
|
|
4635
4657
|
for (const edgeId of inbound) {
|
|
4636
4658
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4637
|
-
rebuildEdge(graph, edge, edge.source,
|
|
4659
|
+
rebuildEdge(graph, edge, edge.source, serviceId12, edgeId);
|
|
4638
4660
|
}
|
|
4639
4661
|
for (const edgeId of outbound) {
|
|
4640
4662
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4641
|
-
rebuildEdge(graph, edge,
|
|
4663
|
+
rebuildEdge(graph, edge, serviceId12, edge.target, edgeId);
|
|
4642
4664
|
}
|
|
4643
4665
|
}
|
|
4644
4666
|
function rebuildEdge(graph, edge, newSource, newTarget, oldEdgeId) {
|
|
@@ -4857,7 +4879,9 @@ import {
|
|
|
4857
4879
|
Provenance as Provenance6,
|
|
4858
4880
|
RelateResultSchema,
|
|
4859
4881
|
RootCauseResultSchema,
|
|
4860
|
-
TransitiveDependenciesResultSchema
|
|
4882
|
+
TransitiveDependenciesResultSchema,
|
|
4883
|
+
fileId as fileId4,
|
|
4884
|
+
parseSymbolId
|
|
4861
4885
|
} from "@neat.is/types";
|
|
4862
4886
|
var ROOT_CAUSE_MAX_DEPTH = 5;
|
|
4863
4887
|
var BLAST_RADIUS_DEFAULT_DEPTH = 10;
|
|
@@ -4968,19 +4992,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
4968
4992
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
4969
4993
|
let best = { path: [start], edges: [] };
|
|
4970
4994
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
4971
|
-
function step(node,
|
|
4972
|
-
if (
|
|
4973
|
-
best = { path: [...
|
|
4995
|
+
function step(node, path67, edges) {
|
|
4996
|
+
if (path67.length > best.path.length) {
|
|
4997
|
+
best = { path: [...path67], edges: [...edges] };
|
|
4974
4998
|
}
|
|
4975
|
-
if (
|
|
4999
|
+
if (path67.length - 1 >= maxDepth) return;
|
|
4976
5000
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
4977
5001
|
for (const [srcId, edge] of incoming) {
|
|
4978
5002
|
if (visited.has(srcId)) continue;
|
|
4979
5003
|
visited.add(srcId);
|
|
4980
|
-
|
|
5004
|
+
path67.push(srcId);
|
|
4981
5005
|
edges.push(edge);
|
|
4982
|
-
step(srcId,
|
|
4983
|
-
|
|
5006
|
+
step(srcId, path67, edges);
|
|
5007
|
+
path67.pop();
|
|
4984
5008
|
edges.pop();
|
|
4985
5009
|
visited.delete(srcId);
|
|
4986
5010
|
}
|
|
@@ -4995,7 +5019,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
|
|
|
4995
5019
|
for (const id of walk9.path) {
|
|
4996
5020
|
const owner = resolveOwningService(graph, id);
|
|
4997
5021
|
if (!owner) continue;
|
|
4998
|
-
const { id:
|
|
5022
|
+
const { id: serviceId12, svc } = owner;
|
|
4999
5023
|
const deps = svc.dependencies ?? {};
|
|
5000
5024
|
for (const pair of candidatePairs) {
|
|
5001
5025
|
const declared = deps[pair.driver];
|
|
@@ -5008,7 +5032,7 @@ function databaseRootCauseShape(graph, origin, walk9) {
|
|
|
5008
5032
|
);
|
|
5009
5033
|
if (!result.compatible) {
|
|
5010
5034
|
return {
|
|
5011
|
-
rootCauseNode:
|
|
5035
|
+
rootCauseNode: serviceId12,
|
|
5012
5036
|
rootCauseReason: result.reason ?? "incompatible driver",
|
|
5013
5037
|
...result.minDriverVersion ? {
|
|
5014
5038
|
fixRecommendation: `Upgrade ${svc.name} ${pair.driver} driver to >= ${result.minDriverVersion}`
|
|
@@ -5023,7 +5047,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
|
|
|
5023
5047
|
for (const id of walk9.path) {
|
|
5024
5048
|
const owner = resolveOwningService(graph, id);
|
|
5025
5049
|
if (!owner) continue;
|
|
5026
|
-
const { id:
|
|
5050
|
+
const { id: serviceId12, svc } = owner;
|
|
5027
5051
|
const deps = svc.dependencies ?? {};
|
|
5028
5052
|
const serviceNodeEngine = svc.nodeEngine;
|
|
5029
5053
|
for (const constraint of nodeEngineConstraints()) {
|
|
@@ -5032,7 +5056,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
|
|
|
5032
5056
|
const result = checkNodeEngineConstraint(constraint, declared, serviceNodeEngine);
|
|
5033
5057
|
if (!result.compatible && result.reason) {
|
|
5034
5058
|
return {
|
|
5035
|
-
rootCauseNode:
|
|
5059
|
+
rootCauseNode: serviceId12,
|
|
5036
5060
|
rootCauseReason: result.reason,
|
|
5037
5061
|
...result.requiredNodeVersion ? {
|
|
5038
5062
|
fixRecommendation: `Bump ${svc.name}'s engines.node to >= ${result.requiredNodeVersion}`
|
|
@@ -5047,7 +5071,7 @@ function serviceRootCauseShape(graph, _origin, walk9) {
|
|
|
5047
5071
|
const result = checkPackageConflict(conflict, declared, requiredDeclared);
|
|
5048
5072
|
if (!result.compatible && result.reason) {
|
|
5049
5073
|
return {
|
|
5050
|
-
rootCauseNode:
|
|
5074
|
+
rootCauseNode: serviceId12,
|
|
5051
5075
|
rootCauseReason: result.reason,
|
|
5052
5076
|
fixRecommendation: `Upgrade ${svc.name}'s ${conflict.requires.name} to >= ${conflict.requires.minVersion}`
|
|
5053
5077
|
};
|
|
@@ -5099,7 +5123,11 @@ function legacyRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
|
5099
5123
|
}
|
|
5100
5124
|
var INCIDENT_ROOT_CAUSE_CONFIDENCE = 0.6;
|
|
5101
5125
|
function incidentMatchesNode(ev, nodeId) {
|
|
5102
|
-
|
|
5126
|
+
if (ev.affectedNode === nodeId) return true;
|
|
5127
|
+
if (ev.service === nodeId.replace(/^service:/, "")) return true;
|
|
5128
|
+
const sym = parseSymbolId(ev.affectedNode);
|
|
5129
|
+
if (sym && fileId4(sym.service, sym.relPath) === nodeId) return true;
|
|
5130
|
+
return false;
|
|
5103
5131
|
}
|
|
5104
5132
|
function localizeFromIncidents(nodeId, incidents, errorEvent) {
|
|
5105
5133
|
const pool = incidents && incidents.length > 0 ? incidents : errorEvent ? [errorEvent] : [];
|
|
@@ -5117,8 +5145,8 @@ function localizeFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
5117
5145
|
const reasonParts = [`${latest.service}: ${latest.errorMessage}`];
|
|
5118
5146
|
if (location) reasonParts.push(`surfaced at ${location}`);
|
|
5119
5147
|
const rootCauseReason = `${reasonParts.join(" \u2014 ")}${tail}`;
|
|
5120
|
-
const
|
|
5121
|
-
const fileNode =
|
|
5148
|
+
const localizesFiner = latest.affectedNode !== nodeId && (latest.affectedNode.startsWith("file:") || latest.affectedNode.startsWith("symbol:"));
|
|
5149
|
+
const fileNode = localizesFiner ? latest.affectedNode : void 0;
|
|
5122
5150
|
const fixRecommendation = location ? `Inspect ${location}${route ? ` handling ${route}` : ""}` : route ? `Inspect ${latest.service}'s handler for ${route}` : void 0;
|
|
5123
5151
|
return {
|
|
5124
5152
|
rootCauseNode: fileNode ?? nodeId,
|
|
@@ -5144,9 +5172,9 @@ function rootCauseFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
5144
5172
|
function isFailingCallEdge(e) {
|
|
5145
5173
|
return e.type === EdgeType6.CALLS && (e.signal?.errorCount ?? 0) > 0;
|
|
5146
5174
|
}
|
|
5147
|
-
function callSourcesForService(graph,
|
|
5148
|
-
const ids = [
|
|
5149
|
-
for (const edgeId of graph.outboundEdges(
|
|
5175
|
+
function callSourcesForService(graph, serviceId12) {
|
|
5176
|
+
const ids = [serviceId12];
|
|
5177
|
+
for (const edgeId of graph.outboundEdges(serviceId12)) {
|
|
5150
5178
|
const e = graph.getEdgeAttributes(edgeId);
|
|
5151
5179
|
if (e.type !== EdgeType6.CONTAINS) continue;
|
|
5152
5180
|
const tgt = graph.getNodeAttributes(e.target);
|
|
@@ -5170,9 +5198,9 @@ function failingCallDominates(e, id, curEdge, curId) {
|
|
|
5170
5198
|
}
|
|
5171
5199
|
return id < curId;
|
|
5172
5200
|
}
|
|
5173
|
-
function dominantFailingCall(graph,
|
|
5201
|
+
function dominantFailingCall(graph, serviceId12, visited) {
|
|
5174
5202
|
let best = null;
|
|
5175
|
-
for (const src of callSourcesForService(graph,
|
|
5203
|
+
for (const src of callSourcesForService(graph, serviceId12)) {
|
|
5176
5204
|
for (const edgeId of graph.outboundEdges(src)) {
|
|
5177
5205
|
const e = graph.getEdgeAttributes(edgeId);
|
|
5178
5206
|
if (!isFailingCallEdge(e)) continue;
|
|
@@ -5187,26 +5215,26 @@ function dominantFailingCall(graph, serviceId9, visited) {
|
|
|
5187
5215
|
return best;
|
|
5188
5216
|
}
|
|
5189
5217
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
5190
|
-
const
|
|
5218
|
+
const path67 = [originServiceId];
|
|
5191
5219
|
const edges = [];
|
|
5192
5220
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
5193
5221
|
let current = originServiceId;
|
|
5194
5222
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
5195
5223
|
const hop = dominantFailingCall(graph, current, visited);
|
|
5196
5224
|
if (!hop) break;
|
|
5197
|
-
|
|
5225
|
+
path67.push(hop.nextService);
|
|
5198
5226
|
edges.push(hop.edge);
|
|
5199
5227
|
visited.add(hop.nextService);
|
|
5200
5228
|
current = hop.nextService;
|
|
5201
5229
|
}
|
|
5202
5230
|
if (edges.length === 0) return null;
|
|
5203
|
-
return { path:
|
|
5231
|
+
return { path: path67, edges, culprit: current };
|
|
5204
5232
|
}
|
|
5205
5233
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
5206
5234
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
5207
5235
|
if (!chain) return null;
|
|
5208
5236
|
const culprit = chain.culprit;
|
|
5209
|
-
const
|
|
5237
|
+
const path67 = [...chain.path];
|
|
5210
5238
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
5211
5239
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
5212
5240
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -5214,14 +5242,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
5214
5242
|
if (loc) {
|
|
5215
5243
|
let rootCauseNode = culprit;
|
|
5216
5244
|
if (loc.fileNode) {
|
|
5217
|
-
|
|
5245
|
+
path67.push(loc.fileNode);
|
|
5218
5246
|
edgeProvenances.push(Provenance6.OBSERVED);
|
|
5219
5247
|
rootCauseNode = loc.fileNode;
|
|
5220
5248
|
}
|
|
5221
5249
|
return RootCauseResultSchema.parse({
|
|
5222
5250
|
rootCauseNode,
|
|
5223
5251
|
rootCauseReason: loc.rootCauseReason,
|
|
5224
|
-
traversalPath:
|
|
5252
|
+
traversalPath: path67,
|
|
5225
5253
|
edgeProvenances,
|
|
5226
5254
|
confidence,
|
|
5227
5255
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -5233,7 +5261,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
5233
5261
|
return RootCauseResultSchema.parse({
|
|
5234
5262
|
rootCauseNode: culprit,
|
|
5235
5263
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
5236
|
-
traversalPath:
|
|
5264
|
+
traversalPath: path67,
|
|
5237
5265
|
edgeProvenances,
|
|
5238
5266
|
confidence,
|
|
5239
5267
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -5603,6 +5631,22 @@ function findLoadOrigin(graph, alertNodeId, incidents, now) {
|
|
|
5603
5631
|
}
|
|
5604
5632
|
return best ? { node: best.node, ctx: best.ctx } : null;
|
|
5605
5633
|
}
|
|
5634
|
+
var LOAD_ORIGIN_CONFIDENCE_CEILING = 0.9;
|
|
5635
|
+
function loadOriginConfidence(originCtx, seedCtx) {
|
|
5636
|
+
const cleanSplit = seedCtx.errorsFromCallers > 0 && seedCtx.errorsEmittedHere === 0;
|
|
5637
|
+
const partialSplit = seedCtx.errorsFromCallers > seedCtx.errorsEmittedHere;
|
|
5638
|
+
let evidence = 0;
|
|
5639
|
+
evidence += cleanSplit ? 0.45 : partialSplit ? 0.2 : 0;
|
|
5640
|
+
evidence += seedCtx.stale ? 0.2 : 0;
|
|
5641
|
+
evidence += isSaturated(seedCtx) ? 0.15 : 0;
|
|
5642
|
+
evidence += (volumeWeight(originCtx.outboundVolume) - 0.5) * 0.4;
|
|
5643
|
+
const anchor = 0.45;
|
|
5644
|
+
const confidence = anchor + Math.min(1, evidence) * (LOAD_ORIGIN_CONFIDENCE_CEILING - anchor);
|
|
5645
|
+
return Math.max(0, Math.min(LOAD_ORIGIN_CONFIDENCE_CEILING, confidence));
|
|
5646
|
+
}
|
|
5647
|
+
function displayNameOf(nodeId) {
|
|
5648
|
+
return nodeId.replace(/^[a-z]+:/, "");
|
|
5649
|
+
}
|
|
5606
5650
|
function getRootCause(graph, errorNodeId, errorEvent, incidents, opts) {
|
|
5607
5651
|
const legacy = legacyRootCause(graph, errorNodeId, errorEvent, incidents);
|
|
5608
5652
|
if (!legacy) return null;
|
|
@@ -5617,20 +5661,20 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
|
|
|
5617
5661
|
const candidates = [];
|
|
5618
5662
|
if (seedCtx && isVictimSeed(seedCtx)) {
|
|
5619
5663
|
const origin = findLoadOrigin(graph, errorNodeId, incidents, now);
|
|
5664
|
+
const staleNote = seedCtx.stale ? "; it has gone STALE under load" : "";
|
|
5665
|
+
const satNote = isSaturated(seedCtx) ? `; its inbound p95 ${Math.round(seedCtx.latencyP95Ms)}ms is saturated` : "";
|
|
5620
5666
|
if (origin) {
|
|
5621
|
-
const
|
|
5622
|
-
const
|
|
5667
|
+
const originName = displayNameOf(origin.node);
|
|
5668
|
+
const seedName = displayNameOf(seedNode);
|
|
5623
5669
|
candidates.push({
|
|
5624
5670
|
node: origin.node,
|
|
5625
5671
|
classification: "primary-failure",
|
|
5626
|
-
reason:
|
|
5672
|
+
reason: `${originName} is the root cause: it overloads the failing subgraph, and the alerting node ${seedName} is a downstream symptom, not the fault \u2014 errors reach ${seedName} from its callers (${seedCtx.errorsFromCallers}), none originate there${staleNote}${satNote}. ${originName} is the upstream source driving that load (${origin.ctx.outboundVolume} observed outbound calls).`,
|
|
5627
5673
|
context: origin.ctx,
|
|
5628
|
-
confidence:
|
|
5674
|
+
confidence: loadOriginConfidence(origin.ctx, seedCtx),
|
|
5629
5675
|
provenance: Provenance6.OBSERVED
|
|
5630
5676
|
});
|
|
5631
5677
|
}
|
|
5632
|
-
const staleNote = seedCtx.stale ? "; the node has gone STALE" : "";
|
|
5633
|
-
const satNote = isSaturated(seedCtx) ? `; inbound p95 ${Math.round(seedCtx.latencyP95Ms)}ms is saturated` : "";
|
|
5634
5678
|
candidates.push({
|
|
5635
5679
|
node: seedNode,
|
|
5636
5680
|
classification: "symptom-only",
|
|
@@ -5653,32 +5697,43 @@ function enrichWithNavigation(graph, errorNodeId, legacy, incidents, now) {
|
|
|
5653
5697
|
let traversalPath = legacy.traversalPath;
|
|
5654
5698
|
let edgeProvenances = legacy.edgeProvenances;
|
|
5655
5699
|
if (top.node !== seedNode) {
|
|
5656
|
-
const
|
|
5657
|
-
if (
|
|
5658
|
-
traversalPath =
|
|
5659
|
-
edgeProvenances =
|
|
5700
|
+
const path67 = findPath(graph, errorNodeId, top.node, "up", ROOT_CAUSE_MAX_DEPTH);
|
|
5701
|
+
if (path67) {
|
|
5702
|
+
traversalPath = path67.nodes;
|
|
5703
|
+
edgeProvenances = path67.edges.map((e) => e.provenance);
|
|
5660
5704
|
} else {
|
|
5661
5705
|
traversalPath = [errorNodeId, top.node];
|
|
5662
5706
|
edgeProvenances = [top.provenance ?? Provenance6.OBSERVED];
|
|
5663
5707
|
}
|
|
5664
5708
|
}
|
|
5709
|
+
const fixRecommendation = fixRecommendationForTop(top, seedNode, legacy);
|
|
5665
5710
|
return RootCauseResultSchema.parse({
|
|
5666
5711
|
rootCauseNode: top.node,
|
|
5667
5712
|
rootCauseReason: top.reason,
|
|
5668
5713
|
traversalPath,
|
|
5669
5714
|
edgeProvenances,
|
|
5670
5715
|
confidence: top.confidence,
|
|
5671
|
-
...
|
|
5716
|
+
...fixRecommendation ? { fixRecommendation } : {},
|
|
5672
5717
|
candidates
|
|
5673
5718
|
});
|
|
5674
5719
|
}
|
|
5720
|
+
function fixRecommendationForTop(top, seedNode, legacy) {
|
|
5721
|
+
if (top.classification === "primary-failure" && top.node === seedNode) {
|
|
5722
|
+
return legacy.fixRecommendation;
|
|
5723
|
+
}
|
|
5724
|
+
const name = top.node.replace(/^service:/, "");
|
|
5725
|
+
if (top.classification === "primary-failure") {
|
|
5726
|
+
return `Reduce or throttle the load from ${name} (or scale the saturated downstream capacity it drives) \u2014 the failure originates at this overloading source, not the starved callee.`;
|
|
5727
|
+
}
|
|
5728
|
+
return `${name} is a saturated/starved downstream victim; investigate its inbound load and upstream callers, not its own handler.`;
|
|
5729
|
+
}
|
|
5675
5730
|
|
|
5676
5731
|
// src/extract/services.ts
|
|
5677
|
-
import { promises as
|
|
5678
|
-
import
|
|
5732
|
+
import { promises as fs15 } from "fs";
|
|
5733
|
+
import path17 from "path";
|
|
5679
5734
|
import ignore from "ignore";
|
|
5680
5735
|
import { minimatch as minimatch2 } from "minimatch";
|
|
5681
|
-
import { NodeType as
|
|
5736
|
+
import { NodeType as NodeType12, serviceId as serviceId8 } from "@neat.is/types";
|
|
5682
5737
|
|
|
5683
5738
|
// src/extract/python.ts
|
|
5684
5739
|
import { promises as fs8 } from "fs";
|
|
@@ -5867,18 +5922,230 @@ async function discoverPhpService(scanPath, dir) {
|
|
|
5867
5922
|
return { pkg, dir, node };
|
|
5868
5923
|
}
|
|
5869
5924
|
|
|
5870
|
-
// src/extract/
|
|
5925
|
+
// src/extract/csharp.ts
|
|
5871
5926
|
import { promises as fs11 } from "fs";
|
|
5872
5927
|
import path13 from "path";
|
|
5928
|
+
import { NodeType as NodeType9, serviceId as serviceId5 } from "@neat.is/types";
|
|
5929
|
+
var CSHARP_PROJECT_EXTS = /* @__PURE__ */ new Set([".csproj", ".sln"]);
|
|
5930
|
+
async function projectFiles(dir) {
|
|
5931
|
+
const entries = await fs11.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
5932
|
+
const out = [];
|
|
5933
|
+
for (const entry of entries) {
|
|
5934
|
+
if (!entry.isFile()) continue;
|
|
5935
|
+
if (CSHARP_PROJECT_EXTS.has(path13.extname(entry.name).toLowerCase())) out.push(entry.name);
|
|
5936
|
+
}
|
|
5937
|
+
return out.sort();
|
|
5938
|
+
}
|
|
5939
|
+
var NESTED_PROJECT_DIR = "src";
|
|
5940
|
+
async function resolveProjectDir(dir) {
|
|
5941
|
+
if ((await projectFiles(dir)).length > 0) return dir;
|
|
5942
|
+
const nested = path13.join(dir, NESTED_PROJECT_DIR);
|
|
5943
|
+
if ((await projectFiles(nested)).length > 0) return nested;
|
|
5944
|
+
return null;
|
|
5945
|
+
}
|
|
5946
|
+
async function hasCsharpProject(dir) {
|
|
5947
|
+
return await resolveProjectDir(dir) !== null;
|
|
5948
|
+
}
|
|
5949
|
+
function parseCsproj(source) {
|
|
5950
|
+
const dependencies = {};
|
|
5951
|
+
const re = /<PackageReference\b([^>]*?)\/?>/g;
|
|
5952
|
+
let m;
|
|
5953
|
+
while ((m = re.exec(source)) !== null) {
|
|
5954
|
+
const attrs = m[1] ?? "";
|
|
5955
|
+
const include = attrs.match(/\bInclude\s*=\s*"([^"]+)"/)?.[1];
|
|
5956
|
+
if (!include) continue;
|
|
5957
|
+
const version = attrs.match(/\bVersion\s*=\s*"([^"]+)"/)?.[1];
|
|
5958
|
+
dependencies[include] = version ?? "";
|
|
5959
|
+
}
|
|
5960
|
+
return { dependencies };
|
|
5961
|
+
}
|
|
5962
|
+
async function discoverCsharpService(scanPath, dir) {
|
|
5963
|
+
const projectDir = await resolveProjectDir(dir);
|
|
5964
|
+
if (projectDir === null) return null;
|
|
5965
|
+
const projects = await projectFiles(projectDir);
|
|
5966
|
+
const csproj = projects.find((p) => p.toLowerCase().endsWith(".csproj"));
|
|
5967
|
+
const name = csproj ? path13.basename(csproj, path13.extname(csproj)) : path13.basename(dir);
|
|
5968
|
+
let dependencies = {};
|
|
5969
|
+
if (csproj) {
|
|
5970
|
+
const raw = await fs11.readFile(path13.join(projectDir, csproj), "utf8").catch(() => "");
|
|
5971
|
+
dependencies = parseCsproj(raw).dependencies;
|
|
5972
|
+
}
|
|
5973
|
+
const pkg = { name, dependencies };
|
|
5974
|
+
const node = {
|
|
5975
|
+
id: serviceId5(name),
|
|
5976
|
+
type: NodeType9.ServiceNode,
|
|
5977
|
+
name,
|
|
5978
|
+
language: "csharp",
|
|
5979
|
+
dependencies,
|
|
5980
|
+
// Anchor on the directory that holds the project and its `.cs` files, so file
|
|
5981
|
+
// attribution reaches the source whether it sits at the root or under `src/`.
|
|
5982
|
+
repoPath: path13.relative(scanPath, projectDir)
|
|
5983
|
+
};
|
|
5984
|
+
return { pkg, dir: projectDir, node };
|
|
5985
|
+
}
|
|
5986
|
+
|
|
5987
|
+
// src/extract/java.ts
|
|
5988
|
+
import { promises as fs12 } from "fs";
|
|
5989
|
+
import path14 from "path";
|
|
5990
|
+
import { NodeType as NodeType10, serviceId as serviceId6 } from "@neat.is/types";
|
|
5991
|
+
var JAVA_MANIFESTS = ["pom.xml", "build.gradle", "build.gradle.kts"];
|
|
5992
|
+
async function hasJavaManifest(dir) {
|
|
5993
|
+
for (const manifest of JAVA_MANIFESTS) {
|
|
5994
|
+
if (await exists(path14.join(dir, manifest))) return true;
|
|
5995
|
+
}
|
|
5996
|
+
return false;
|
|
5997
|
+
}
|
|
5998
|
+
function parsePomXml(source) {
|
|
5999
|
+
const dependencies = {};
|
|
6000
|
+
const depBlock = /<dependency\b[^>]*>([\s\S]*?)<\/dependency>/g;
|
|
6001
|
+
let d;
|
|
6002
|
+
while ((d = depBlock.exec(source)) !== null) {
|
|
6003
|
+
const body = d[1] ?? "";
|
|
6004
|
+
const groupId = body.match(/<groupId>\s*([^<]+?)\s*<\/groupId>/)?.[1];
|
|
6005
|
+
const artifactId = body.match(/<artifactId>\s*([^<]+?)\s*<\/artifactId>/)?.[1];
|
|
6006
|
+
if (!groupId || !artifactId) continue;
|
|
6007
|
+
const version = body.match(/<version>\s*([^<]+?)\s*<\/version>/)?.[1];
|
|
6008
|
+
dependencies[`${groupId}:${artifactId}`] = version ?? "";
|
|
6009
|
+
}
|
|
6010
|
+
const withoutBlocks = source.replace(/<parent\b[^>]*>[\s\S]*?<\/parent>/g, "").replace(/<dependencyManagement\b[^>]*>[\s\S]*?<\/dependencyManagement>/g, "").replace(/<dependencies\b[^>]*>[\s\S]*?<\/dependencies>/g, "").replace(/<build\b[^>]*>[\s\S]*?<\/build>/g, "");
|
|
6011
|
+
const name = withoutBlocks.match(/<artifactId>\s*([^<]+?)\s*<\/artifactId>/)?.[1];
|
|
6012
|
+
return { name, dependencies };
|
|
6013
|
+
}
|
|
6014
|
+
var GRADLE_CONFIGS = "implementation|api|compileOnly|runtimeOnly|compileOnlyApi|testImplementation|testRuntimeOnly|testCompileOnly|annotationProcessor|developmentOnly|runtimeClasspath";
|
|
6015
|
+
function parseGradle(source) {
|
|
6016
|
+
const dependencies = {};
|
|
6017
|
+
const re = new RegExp(`\\b(?:${GRADLE_CONFIGS})\\s*\\(?\\s*(['"])([^'"]+)\\1`, "g");
|
|
6018
|
+
let m;
|
|
6019
|
+
while ((m = re.exec(source)) !== null) {
|
|
6020
|
+
const coord = m[2] ?? "";
|
|
6021
|
+
const parts = coord.split(":");
|
|
6022
|
+
if (parts.length < 2 || !parts[0] || !parts[1]) continue;
|
|
6023
|
+
const key = `${parts[0]}:${parts[1]}`;
|
|
6024
|
+
dependencies[key] = parts.slice(2).join(":");
|
|
6025
|
+
}
|
|
6026
|
+
return { dependencies };
|
|
6027
|
+
}
|
|
6028
|
+
async function discoverJavaService(scanPath, dir) {
|
|
6029
|
+
const pomPath = path14.join(dir, "pom.xml");
|
|
6030
|
+
const gradlePath = path14.join(dir, "build.gradle");
|
|
6031
|
+
const gradleKtsPath = path14.join(dir, "build.gradle.kts");
|
|
6032
|
+
let manifest = null;
|
|
6033
|
+
if (await exists(pomPath)) {
|
|
6034
|
+
manifest = parsePomXml(await fs12.readFile(pomPath, "utf8").catch(() => ""));
|
|
6035
|
+
} else if (await exists(gradlePath)) {
|
|
6036
|
+
manifest = parseGradle(await fs12.readFile(gradlePath, "utf8").catch(() => ""));
|
|
6037
|
+
} else if (await exists(gradleKtsPath)) {
|
|
6038
|
+
manifest = parseGradle(await fs12.readFile(gradleKtsPath, "utf8").catch(() => ""));
|
|
6039
|
+
}
|
|
6040
|
+
if (!manifest) return null;
|
|
6041
|
+
const name = manifest.name?.trim() || path14.basename(dir);
|
|
6042
|
+
const dependencies = manifest.dependencies;
|
|
6043
|
+
const pkg = { name, dependencies };
|
|
6044
|
+
const node = {
|
|
6045
|
+
id: serviceId6(name),
|
|
6046
|
+
type: NodeType10.ServiceNode,
|
|
6047
|
+
name,
|
|
6048
|
+
language: "java",
|
|
6049
|
+
dependencies,
|
|
6050
|
+
repoPath: path14.relative(scanPath, dir)
|
|
6051
|
+
};
|
|
6052
|
+
return { pkg, dir, node };
|
|
6053
|
+
}
|
|
6054
|
+
|
|
6055
|
+
// src/extract/dockerfile-service.ts
|
|
6056
|
+
import { promises as fs13 } from "fs";
|
|
6057
|
+
import path15 from "path";
|
|
6058
|
+
import { NodeType as NodeType11, serviceId as serviceId7 } from "@neat.is/types";
|
|
6059
|
+
function languageForSourceExt(ext) {
|
|
6060
|
+
switch (ext) {
|
|
6061
|
+
case ".py":
|
|
6062
|
+
return "python";
|
|
6063
|
+
case ".go":
|
|
6064
|
+
return "go";
|
|
6065
|
+
case ".rb":
|
|
6066
|
+
return "ruby";
|
|
6067
|
+
case ".php":
|
|
6068
|
+
return "php";
|
|
6069
|
+
case ".cs":
|
|
6070
|
+
return "csharp";
|
|
6071
|
+
case ".java":
|
|
6072
|
+
return "java";
|
|
6073
|
+
case ".ts":
|
|
6074
|
+
case ".tsx":
|
|
6075
|
+
return "typescript";
|
|
6076
|
+
case ".js":
|
|
6077
|
+
case ".mjs":
|
|
6078
|
+
case ".cjs":
|
|
6079
|
+
return "javascript";
|
|
6080
|
+
default:
|
|
6081
|
+
return null;
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
var LANGUAGE_PRECEDENCE = ["typescript", "javascript", "python", "go", "ruby", "php", "csharp", "java"];
|
|
6085
|
+
async function inferServiceLanguage(dir) {
|
|
6086
|
+
const entries = await fs13.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
6087
|
+
const counts = /* @__PURE__ */ new Map();
|
|
6088
|
+
for (const entry of entries) {
|
|
6089
|
+
if (!entry.isFile()) continue;
|
|
6090
|
+
if (isNeatAuthoredSourceFile(entry.name)) continue;
|
|
6091
|
+
const ext = path15.extname(entry.name).toLowerCase();
|
|
6092
|
+
if (!SERVICE_FILE_EXTENSIONS.has(ext)) continue;
|
|
6093
|
+
const lang = languageForSourceExt(ext);
|
|
6094
|
+
if (!lang) continue;
|
|
6095
|
+
counts.set(lang, (counts.get(lang) ?? 0) + 1);
|
|
6096
|
+
}
|
|
6097
|
+
if (counts.size === 0) return null;
|
|
6098
|
+
let best = null;
|
|
6099
|
+
let bestCount = -1;
|
|
6100
|
+
for (const lang of LANGUAGE_PRECEDENCE) {
|
|
6101
|
+
const c = counts.get(lang) ?? 0;
|
|
6102
|
+
if (c > bestCount) {
|
|
6103
|
+
best = lang;
|
|
6104
|
+
bestCount = c;
|
|
6105
|
+
}
|
|
6106
|
+
}
|
|
6107
|
+
return best;
|
|
6108
|
+
}
|
|
6109
|
+
async function hasLanguageManifest(dir) {
|
|
6110
|
+
return await exists(path15.join(dir, "package.json")) || await exists(path15.join(dir, "pyproject.toml")) || await exists(path15.join(dir, "requirements.txt")) || await exists(path15.join(dir, "setup.py")) || await exists(path15.join(dir, "go.mod")) || await exists(path15.join(dir, "Gemfile")) || await exists(path15.join(dir, "composer.json")) || // C#'s marker is a glob (`*.csproj` / `*.sln`), so it reads the directory
|
|
6111
|
+
// rather than probing a fixed filename (ADR-196).
|
|
6112
|
+
await hasCsharpProject(dir) || // Java keys on a Maven / Gradle build manifest (ADR-197).
|
|
6113
|
+
await hasJavaManifest(dir);
|
|
6114
|
+
}
|
|
6115
|
+
async function hasDockerfileDeclaredService(dir) {
|
|
6116
|
+
if (!await exists(path15.join(dir, "Dockerfile"))) return false;
|
|
6117
|
+
return await inferServiceLanguage(dir) !== null;
|
|
6118
|
+
}
|
|
6119
|
+
async function discoverDockerfileService(scanPath, dir) {
|
|
6120
|
+
if (!await exists(path15.join(dir, "Dockerfile"))) return null;
|
|
6121
|
+
if (await hasLanguageManifest(dir)) return null;
|
|
6122
|
+
const language = await inferServiceLanguage(dir);
|
|
6123
|
+
if (!language) return null;
|
|
6124
|
+
const name = path15.basename(dir);
|
|
6125
|
+
const pkg = { name, dependencies: {} };
|
|
6126
|
+
const node = {
|
|
6127
|
+
id: serviceId7(name),
|
|
6128
|
+
type: NodeType11.ServiceNode,
|
|
6129
|
+
name,
|
|
6130
|
+
language,
|
|
6131
|
+
dependencies: {},
|
|
6132
|
+
repoPath: path15.relative(scanPath, dir)
|
|
6133
|
+
};
|
|
6134
|
+
return { pkg, dir, node };
|
|
6135
|
+
}
|
|
6136
|
+
|
|
6137
|
+
// src/extract/owners.ts
|
|
6138
|
+
import { promises as fs14 } from "fs";
|
|
6139
|
+
import path16 from "path";
|
|
5873
6140
|
import { minimatch } from "minimatch";
|
|
5874
6141
|
async function loadCodeowners(scanPath) {
|
|
5875
6142
|
const candidates = [
|
|
5876
|
-
|
|
5877
|
-
|
|
6143
|
+
path16.join(scanPath, "CODEOWNERS"),
|
|
6144
|
+
path16.join(scanPath, ".github", "CODEOWNERS")
|
|
5878
6145
|
];
|
|
5879
6146
|
for (const file of candidates) {
|
|
5880
6147
|
if (await exists(file)) {
|
|
5881
|
-
const raw = await
|
|
6148
|
+
const raw = await fs14.readFile(file, "utf8");
|
|
5882
6149
|
return parseCodeowners(raw);
|
|
5883
6150
|
}
|
|
5884
6151
|
}
|
|
@@ -5896,7 +6163,7 @@ function parseCodeowners(raw) {
|
|
|
5896
6163
|
return { rules };
|
|
5897
6164
|
}
|
|
5898
6165
|
function matchOwner(file, repoPath) {
|
|
5899
|
-
const normalized = repoPath.split(
|
|
6166
|
+
const normalized = repoPath.split(path16.sep).join("/");
|
|
5900
6167
|
for (const rule of file.rules) {
|
|
5901
6168
|
if (matchesPattern(rule.pattern, normalized)) return rule.owners;
|
|
5902
6169
|
}
|
|
@@ -5912,7 +6179,7 @@ function matchesPattern(rawPattern, repoPath) {
|
|
|
5912
6179
|
return false;
|
|
5913
6180
|
}
|
|
5914
6181
|
async function readPackageJsonAuthor(serviceDir) {
|
|
5915
|
-
const pkgPath =
|
|
6182
|
+
const pkgPath = path16.join(serviceDir, "package.json");
|
|
5916
6183
|
if (!await exists(pkgPath)) return null;
|
|
5917
6184
|
try {
|
|
5918
6185
|
const pkg = await readJson(pkgPath);
|
|
@@ -5949,33 +6216,36 @@ function workspaceGlobs(pkg) {
|
|
|
5949
6216
|
return null;
|
|
5950
6217
|
}
|
|
5951
6218
|
async function hasPythonManifest(dir) {
|
|
5952
|
-
return await exists(
|
|
6219
|
+
return await exists(path17.join(dir, "pyproject.toml")) || await exists(path17.join(dir, "requirements.txt")) || await exists(path17.join(dir, "setup.py"));
|
|
5953
6220
|
}
|
|
5954
6221
|
async function hasGoManifest(dir) {
|
|
5955
|
-
return exists(
|
|
6222
|
+
return exists(path17.join(dir, "go.mod"));
|
|
5956
6223
|
}
|
|
5957
6224
|
async function hasRubyManifest(dir) {
|
|
5958
|
-
return exists(
|
|
6225
|
+
return exists(path17.join(dir, "Gemfile"));
|
|
5959
6226
|
}
|
|
5960
6227
|
async function hasPhpManifest(dir) {
|
|
5961
|
-
return exists(
|
|
6228
|
+
return exists(path17.join(dir, "composer.json"));
|
|
6229
|
+
}
|
|
6230
|
+
async function hasCsharpManifest(dir) {
|
|
6231
|
+
return hasCsharpProject(dir);
|
|
5962
6232
|
}
|
|
5963
6233
|
async function loadGitignore(scanPath) {
|
|
5964
|
-
const gitignorePath =
|
|
6234
|
+
const gitignorePath = path17.join(scanPath, ".gitignore");
|
|
5965
6235
|
if (!await exists(gitignorePath)) return null;
|
|
5966
|
-
const raw = await
|
|
6236
|
+
const raw = await fs15.readFile(gitignorePath, "utf8");
|
|
5967
6237
|
return ignore().add(raw);
|
|
5968
6238
|
}
|
|
5969
6239
|
async function walkDirs(start, scanPath, options, visit) {
|
|
5970
6240
|
async function recurse(current, depth) {
|
|
5971
6241
|
if (depth > options.maxDepth) return;
|
|
5972
|
-
const entries = await
|
|
6242
|
+
const entries = await fs15.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
5973
6243
|
for (const entry of entries) {
|
|
5974
6244
|
if (!entry.isDirectory()) continue;
|
|
5975
6245
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
5976
|
-
const child =
|
|
6246
|
+
const child = path17.join(current, entry.name);
|
|
5977
6247
|
if (options.ig) {
|
|
5978
|
-
const rel =
|
|
6248
|
+
const rel = path17.relative(scanPath, child).split(path17.sep).join("/");
|
|
5979
6249
|
if (rel && options.ig.ignores(rel + "/")) continue;
|
|
5980
6250
|
}
|
|
5981
6251
|
if (await isPythonVenvDir(child)) continue;
|
|
@@ -5991,8 +6261,8 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
5991
6261
|
for (const raw of globs) {
|
|
5992
6262
|
const pattern = raw.replace(/^\.\//, "");
|
|
5993
6263
|
if (!pattern.includes("*")) {
|
|
5994
|
-
const candidate =
|
|
5995
|
-
if (await exists(
|
|
6264
|
+
const candidate = path17.join(scanPath, pattern);
|
|
6265
|
+
if (await exists(path17.join(candidate, "package.json"))) found.add(candidate);
|
|
5996
6266
|
continue;
|
|
5997
6267
|
}
|
|
5998
6268
|
const segments = pattern.split("/");
|
|
@@ -6001,13 +6271,13 @@ async function expandWorkspaceGlobs(scanPath, globs) {
|
|
|
6001
6271
|
if (seg.includes("*")) break;
|
|
6002
6272
|
staticSegments.push(seg);
|
|
6003
6273
|
}
|
|
6004
|
-
const start =
|
|
6274
|
+
const start = path17.join(scanPath, ...staticSegments);
|
|
6005
6275
|
if (!await exists(start)) continue;
|
|
6006
6276
|
const hasDoubleStar = pattern.includes("**");
|
|
6007
6277
|
const walkDepth = hasDoubleStar ? scanDepth : Math.max(0, segments.length - staticSegments.length - 1);
|
|
6008
6278
|
await walkDirs(start, scanPath, { maxDepth: walkDepth, ig: null }, async (dir) => {
|
|
6009
|
-
const rel =
|
|
6010
|
-
if (minimatch2(rel, pattern) && await exists(
|
|
6279
|
+
const rel = path17.relative(scanPath, dir).split(path17.sep).join("/");
|
|
6280
|
+
if (minimatch2(rel, pattern) && await exists(path17.join(dir, "package.json"))) {
|
|
6011
6281
|
found.add(dir);
|
|
6012
6282
|
}
|
|
6013
6283
|
});
|
|
@@ -6030,31 +6300,31 @@ function detectJsFramework(pkg) {
|
|
|
6030
6300
|
async function detectJsServiceLanguage(dir, pkg) {
|
|
6031
6301
|
const deps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
6032
6302
|
if (deps["typescript"] !== void 0) return "typescript";
|
|
6033
|
-
const entries = await
|
|
6303
|
+
const entries = await fs15.readdir(dir).catch(() => []);
|
|
6034
6304
|
if (entries.some((name) => /^tsconfig(\..+)?\.json$/.test(name))) return "typescript";
|
|
6035
6305
|
return "javascript";
|
|
6036
6306
|
}
|
|
6037
6307
|
async function discoverNodeService(scanPath, dir) {
|
|
6038
|
-
const pkgPath =
|
|
6308
|
+
const pkgPath = path17.join(dir, "package.json");
|
|
6039
6309
|
if (!await exists(pkgPath)) return null;
|
|
6040
6310
|
let pkg;
|
|
6041
6311
|
try {
|
|
6042
6312
|
pkg = await readJson(pkgPath);
|
|
6043
6313
|
} catch (err) {
|
|
6044
|
-
recordExtractionError("services",
|
|
6314
|
+
recordExtractionError("services", path17.relative(scanPath, pkgPath), err);
|
|
6045
6315
|
return null;
|
|
6046
6316
|
}
|
|
6047
6317
|
if (!pkg.name) return null;
|
|
6048
6318
|
const framework = detectJsFramework(pkg);
|
|
6049
6319
|
const language = await detectJsServiceLanguage(dir, pkg);
|
|
6050
6320
|
const node = {
|
|
6051
|
-
id:
|
|
6052
|
-
type:
|
|
6321
|
+
id: serviceId8(pkg.name),
|
|
6322
|
+
type: NodeType12.ServiceNode,
|
|
6053
6323
|
name: pkg.name,
|
|
6054
6324
|
language,
|
|
6055
6325
|
version: pkg.version,
|
|
6056
6326
|
dependencies: pkg.dependencies ?? {},
|
|
6057
|
-
repoPath:
|
|
6327
|
+
repoPath: path17.relative(scanPath, dir),
|
|
6058
6328
|
...pkg.engines?.node ? { nodeEngine: pkg.engines.node } : {},
|
|
6059
6329
|
...framework ? { framework } : {}
|
|
6060
6330
|
};
|
|
@@ -6065,18 +6335,18 @@ async function discoverPyService(scanPath, dir) {
|
|
|
6065
6335
|
if (!py) return null;
|
|
6066
6336
|
const pkg = pythonToPackage(py);
|
|
6067
6337
|
const node = {
|
|
6068
|
-
id:
|
|
6069
|
-
type:
|
|
6338
|
+
id: serviceId8(py.name),
|
|
6339
|
+
type: NodeType12.ServiceNode,
|
|
6070
6340
|
name: py.name,
|
|
6071
6341
|
language: "python",
|
|
6072
6342
|
version: py.version,
|
|
6073
6343
|
dependencies: py.dependencies,
|
|
6074
|
-
repoPath:
|
|
6344
|
+
repoPath: path17.relative(scanPath, dir)
|
|
6075
6345
|
};
|
|
6076
6346
|
return { pkg, dir, node };
|
|
6077
6347
|
}
|
|
6078
6348
|
async function discoverServices(scanPath) {
|
|
6079
|
-
const rootPkgPath =
|
|
6349
|
+
const rootPkgPath = path17.join(scanPath, "package.json");
|
|
6080
6350
|
let rootPkg = null;
|
|
6081
6351
|
if (await exists(rootPkgPath)) {
|
|
6082
6352
|
try {
|
|
@@ -6084,7 +6354,7 @@ async function discoverServices(scanPath) {
|
|
|
6084
6354
|
} catch (err) {
|
|
6085
6355
|
recordExtractionError(
|
|
6086
6356
|
"services workspaces",
|
|
6087
|
-
|
|
6357
|
+
path17.relative(scanPath, rootPkgPath),
|
|
6088
6358
|
err
|
|
6089
6359
|
);
|
|
6090
6360
|
}
|
|
@@ -6096,7 +6366,7 @@ async function discoverServices(scanPath) {
|
|
|
6096
6366
|
} else {
|
|
6097
6367
|
if (rootPkg && rootPkg.name) {
|
|
6098
6368
|
candidateDirs.push(scanPath);
|
|
6099
|
-
} else if (await hasPythonManifest(scanPath) || await hasGoManifest(scanPath) || await hasRubyManifest(scanPath) || await hasPhpManifest(scanPath)) {
|
|
6369
|
+
} else if (await hasPythonManifest(scanPath) || await hasGoManifest(scanPath) || await hasRubyManifest(scanPath) || await hasPhpManifest(scanPath) || await hasCsharpManifest(scanPath) || await hasJavaManifest(scanPath)) {
|
|
6100
6370
|
candidateDirs.push(scanPath);
|
|
6101
6371
|
}
|
|
6102
6372
|
const ig = await loadGitignore(scanPath);
|
|
@@ -6105,9 +6375,11 @@ async function discoverServices(scanPath) {
|
|
|
6105
6375
|
scanPath,
|
|
6106
6376
|
{ maxDepth: parseScanDepth(), ig },
|
|
6107
6377
|
async (dir) => {
|
|
6108
|
-
if (await exists(
|
|
6378
|
+
if (await exists(path17.join(dir, "package.json"))) {
|
|
6379
|
+
candidateDirs.push(dir);
|
|
6380
|
+
} else if (await hasPythonManifest(dir) || await hasGoManifest(dir) || await hasRubyManifest(dir) || await hasPhpManifest(dir) || await hasCsharpManifest(dir) || await hasJavaManifest(dir)) {
|
|
6109
6381
|
candidateDirs.push(dir);
|
|
6110
|
-
} else if (await
|
|
6382
|
+
} else if (await hasDockerfileDeclaredService(dir)) {
|
|
6111
6383
|
candidateDirs.push(dir);
|
|
6112
6384
|
}
|
|
6113
6385
|
}
|
|
@@ -6115,14 +6387,17 @@ async function discoverServices(scanPath) {
|
|
|
6115
6387
|
}
|
|
6116
6388
|
candidateDirs.sort();
|
|
6117
6389
|
const seen = /* @__PURE__ */ new Map();
|
|
6390
|
+
const seenDirs = /* @__PURE__ */ new Set();
|
|
6118
6391
|
const out = [];
|
|
6119
6392
|
for (const dir of candidateDirs) {
|
|
6120
|
-
const service = await discoverNodeService(scanPath, dir) ?? await discoverPyService(scanPath, dir) ?? await discoverGoService(scanPath, dir) ?? await discoverRubyService(scanPath, dir) ?? await discoverPhpService(scanPath, dir);
|
|
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) ?? await discoverJavaService(scanPath, dir) ?? await discoverDockerfileService(scanPath, dir);
|
|
6121
6394
|
if (!service) continue;
|
|
6395
|
+
if (seenDirs.has(service.dir)) continue;
|
|
6396
|
+
seenDirs.add(service.dir);
|
|
6122
6397
|
const existingDir = seen.get(service.node.name);
|
|
6123
6398
|
if (existingDir !== void 0) {
|
|
6124
|
-
const a =
|
|
6125
|
-
const b =
|
|
6399
|
+
const a = path17.relative(scanPath, existingDir) || ".";
|
|
6400
|
+
const b = path17.relative(scanPath, dir) || ".";
|
|
6126
6401
|
console.warn(
|
|
6127
6402
|
`[neat] duplicate package name "${service.node.name}" \u2014 keeping ${a}, ignoring ${b}`
|
|
6128
6403
|
);
|
|
@@ -6158,20 +6433,20 @@ function addServiceNodes(graph, services) {
|
|
|
6158
6433
|
}
|
|
6159
6434
|
|
|
6160
6435
|
// src/extract/aliases.ts
|
|
6161
|
-
import
|
|
6162
|
-
import { promises as
|
|
6436
|
+
import path18 from "path";
|
|
6437
|
+
import { promises as fs16 } from "fs";
|
|
6163
6438
|
import { parseAllDocuments } from "yaml";
|
|
6164
|
-
import { NodeType as
|
|
6439
|
+
import { NodeType as NodeType13 } from "@neat.is/types";
|
|
6165
6440
|
var K8S_KINDS_WITH_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
6166
6441
|
"Service",
|
|
6167
6442
|
"Deployment",
|
|
6168
6443
|
"StatefulSet",
|
|
6169
6444
|
"DaemonSet"
|
|
6170
6445
|
]);
|
|
6171
|
-
function addAliases(graph,
|
|
6172
|
-
if (!graph.hasNode(
|
|
6173
|
-
const node = graph.getNodeAttributes(
|
|
6174
|
-
if (node.type !==
|
|
6446
|
+
function addAliases(graph, serviceId12, candidates) {
|
|
6447
|
+
if (!graph.hasNode(serviceId12)) return;
|
|
6448
|
+
const node = graph.getNodeAttributes(serviceId12);
|
|
6449
|
+
if (node.type !== NodeType13.ServiceNode) return;
|
|
6175
6450
|
const set = new Set(node.aliases ?? []);
|
|
6176
6451
|
for (const c of candidates) {
|
|
6177
6452
|
if (!c) continue;
|
|
@@ -6180,20 +6455,20 @@ function addAliases(graph, serviceId9, candidates) {
|
|
|
6180
6455
|
}
|
|
6181
6456
|
if (set.size === 0) return;
|
|
6182
6457
|
const updated = { ...node, aliases: [...set].sort() };
|
|
6183
|
-
graph.replaceNodeAttributes(
|
|
6458
|
+
graph.replaceNodeAttributes(serviceId12, updated);
|
|
6184
6459
|
}
|
|
6185
6460
|
function indexServicesByName(services) {
|
|
6186
6461
|
const map = /* @__PURE__ */ new Map();
|
|
6187
6462
|
for (const s of services) {
|
|
6188
6463
|
map.set(s.node.name, s.node.id);
|
|
6189
|
-
map.set(
|
|
6464
|
+
map.set(path18.basename(s.dir), s.node.id);
|
|
6190
6465
|
}
|
|
6191
6466
|
return map;
|
|
6192
6467
|
}
|
|
6193
6468
|
async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
6194
6469
|
let composePath = null;
|
|
6195
6470
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
6196
|
-
const abs =
|
|
6471
|
+
const abs = path18.join(scanPath, name);
|
|
6197
6472
|
if (await exists(abs)) {
|
|
6198
6473
|
composePath = abs;
|
|
6199
6474
|
break;
|
|
@@ -6206,19 +6481,19 @@ async function collectComposeAliases(graph, scanPath, serviceIndex) {
|
|
|
6206
6481
|
} catch (err) {
|
|
6207
6482
|
recordExtractionError(
|
|
6208
6483
|
"aliases compose",
|
|
6209
|
-
|
|
6484
|
+
path18.relative(scanPath, composePath),
|
|
6210
6485
|
err
|
|
6211
6486
|
);
|
|
6212
6487
|
return;
|
|
6213
6488
|
}
|
|
6214
6489
|
if (!compose?.services) return;
|
|
6215
6490
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
6216
|
-
const
|
|
6217
|
-
if (!
|
|
6491
|
+
const serviceId12 = serviceIndex.get(composeName);
|
|
6492
|
+
if (!serviceId12) continue;
|
|
6218
6493
|
const aliases = /* @__PURE__ */ new Set([composeName]);
|
|
6219
6494
|
if (svc.container_name) aliases.add(svc.container_name);
|
|
6220
6495
|
if (svc.hostname) aliases.add(svc.hostname);
|
|
6221
|
-
addAliases(graph,
|
|
6496
|
+
addAliases(graph, serviceId12, aliases);
|
|
6222
6497
|
}
|
|
6223
6498
|
}
|
|
6224
6499
|
var LABEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -6249,11 +6524,11 @@ function parseDockerfileLabels(content) {
|
|
|
6249
6524
|
}
|
|
6250
6525
|
async function collectDockerfileAliases(graph, services) {
|
|
6251
6526
|
for (const service of services) {
|
|
6252
|
-
const dockerfilePath =
|
|
6527
|
+
const dockerfilePath = path18.join(service.dir, "Dockerfile");
|
|
6253
6528
|
if (!await exists(dockerfilePath)) continue;
|
|
6254
6529
|
let content;
|
|
6255
6530
|
try {
|
|
6256
|
-
content = await
|
|
6531
|
+
content = await fs16.readFile(dockerfilePath, "utf8");
|
|
6257
6532
|
} catch (err) {
|
|
6258
6533
|
recordExtractionError("aliases dockerfile", dockerfilePath, err);
|
|
6259
6534
|
continue;
|
|
@@ -6265,15 +6540,15 @@ async function collectDockerfileAliases(graph, services) {
|
|
|
6265
6540
|
async function walkYamlFiles(start, depth = 0, max = 5) {
|
|
6266
6541
|
if (depth > max) return [];
|
|
6267
6542
|
const out = [];
|
|
6268
|
-
const entries = await
|
|
6543
|
+
const entries = await fs16.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
6269
6544
|
for (const entry of entries) {
|
|
6270
6545
|
if (entry.isDirectory()) {
|
|
6271
6546
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6272
|
-
const child =
|
|
6547
|
+
const child = path18.join(start, entry.name);
|
|
6273
6548
|
if (await isPythonVenvDir(child)) continue;
|
|
6274
6549
|
out.push(...await walkYamlFiles(child, depth + 1, max));
|
|
6275
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
6276
|
-
out.push(
|
|
6550
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path18.extname(entry.name))) {
|
|
6551
|
+
out.push(path18.join(start, entry.name));
|
|
6277
6552
|
}
|
|
6278
6553
|
}
|
|
6279
6554
|
return out;
|
|
@@ -6300,7 +6575,7 @@ function k8sServiceTarget(doc, byName) {
|
|
|
6300
6575
|
async function collectK8sAliases(graph, scanPath, serviceIndex) {
|
|
6301
6576
|
const files = await walkYamlFiles(scanPath);
|
|
6302
6577
|
for (const file of files) {
|
|
6303
|
-
const content = await
|
|
6578
|
+
const content = await fs16.readFile(file, "utf8");
|
|
6304
6579
|
let docs;
|
|
6305
6580
|
try {
|
|
6306
6581
|
docs = parseAllDocuments(content).map((d) => d.toJSON());
|
|
@@ -6324,14 +6599,14 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
6324
6599
|
}
|
|
6325
6600
|
|
|
6326
6601
|
// src/extract/files.ts
|
|
6327
|
-
import
|
|
6602
|
+
import path19 from "path";
|
|
6328
6603
|
async function addFiles(graph, services) {
|
|
6329
6604
|
let nodesAdded = 0;
|
|
6330
6605
|
let edgesAdded = 0;
|
|
6331
6606
|
for (const service of services) {
|
|
6332
6607
|
const filePaths = await walkSourceFiles(service.dir);
|
|
6333
6608
|
for (const filePath of filePaths) {
|
|
6334
|
-
const relPath = toPosix(
|
|
6609
|
+
const relPath = toPosix(path19.relative(service.dir, filePath));
|
|
6335
6610
|
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
6336
6611
|
graph,
|
|
6337
6612
|
service.pkg.name,
|
|
@@ -6346,13 +6621,19 @@ async function addFiles(graph, services) {
|
|
|
6346
6621
|
}
|
|
6347
6622
|
|
|
6348
6623
|
// src/extract/symbols.ts
|
|
6349
|
-
import
|
|
6624
|
+
import path20 from "path";
|
|
6350
6625
|
import Parser3 from "tree-sitter";
|
|
6351
6626
|
import JavaScript3 from "tree-sitter-javascript";
|
|
6352
6627
|
import TypeScript from "tree-sitter-typescript";
|
|
6628
|
+
import Python3 from "tree-sitter-python";
|
|
6629
|
+
import Go3 from "tree-sitter-go";
|
|
6630
|
+
import Ruby2 from "tree-sitter-ruby";
|
|
6631
|
+
import Php2 from "tree-sitter-php";
|
|
6632
|
+
import CSharp from "tree-sitter-c-sharp";
|
|
6633
|
+
import Java from "tree-sitter-java";
|
|
6353
6634
|
import {
|
|
6354
6635
|
EdgeType as EdgeType7,
|
|
6355
|
-
NodeType as
|
|
6636
|
+
NodeType as NodeType14,
|
|
6356
6637
|
Provenance as Provenance7,
|
|
6357
6638
|
confidenceForExtracted as confidenceForExtracted4,
|
|
6358
6639
|
extractedEdgeId as extractedEdgeId6,
|
|
@@ -6367,6 +6648,15 @@ var GRAMMAR_BY_EXT = {
|
|
|
6367
6648
|
".mjs": JavaScript3,
|
|
6368
6649
|
".cjs": JavaScript3
|
|
6369
6650
|
};
|
|
6651
|
+
var SYMBOL_GRAMMAR_BY_EXT = {
|
|
6652
|
+
...GRAMMAR_BY_EXT,
|
|
6653
|
+
".py": Python3,
|
|
6654
|
+
".go": Go3,
|
|
6655
|
+
".rb": Ruby2,
|
|
6656
|
+
".php": Php2.php_only,
|
|
6657
|
+
".cs": CSharp,
|
|
6658
|
+
".java": Java
|
|
6659
|
+
};
|
|
6370
6660
|
function parseSource3(parser, source) {
|
|
6371
6661
|
return parser.parse(
|
|
6372
6662
|
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK3)
|
|
@@ -6376,7 +6666,352 @@ function methodName(node) {
|
|
|
6376
6666
|
const name = node.childForFieldName("name");
|
|
6377
6667
|
return name ? name.text : null;
|
|
6378
6668
|
}
|
|
6379
|
-
function collectSymbolDefs(root) {
|
|
6669
|
+
function collectSymbolDefs(root) {
|
|
6670
|
+
const out = [];
|
|
6671
|
+
const push = (kind, qualname, node) => {
|
|
6672
|
+
out.push({
|
|
6673
|
+
kind,
|
|
6674
|
+
qualname,
|
|
6675
|
+
startLine: node.startPosition.row + 1,
|
|
6676
|
+
endLine: node.endPosition.row + 1
|
|
6677
|
+
});
|
|
6678
|
+
};
|
|
6679
|
+
const visit = (node, classCtx) => {
|
|
6680
|
+
switch (node.type) {
|
|
6681
|
+
case "function_declaration":
|
|
6682
|
+
case "generator_function_declaration": {
|
|
6683
|
+
const name = node.childForFieldName("name")?.text;
|
|
6684
|
+
if (name) push("function", name, node);
|
|
6685
|
+
break;
|
|
6686
|
+
}
|
|
6687
|
+
case "class_declaration":
|
|
6688
|
+
case "abstract_class_declaration":
|
|
6689
|
+
case "class": {
|
|
6690
|
+
const name = node.childForFieldName("name")?.text;
|
|
6691
|
+
if (name) push("class", name, node);
|
|
6692
|
+
const body = node.childForFieldName("body");
|
|
6693
|
+
if (body) {
|
|
6694
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
6695
|
+
const child = body.namedChild(i);
|
|
6696
|
+
if (child) visit(child, name ?? classCtx);
|
|
6697
|
+
}
|
|
6698
|
+
}
|
|
6699
|
+
return;
|
|
6700
|
+
}
|
|
6701
|
+
case "method_definition": {
|
|
6702
|
+
const name = methodName(node);
|
|
6703
|
+
if (name) {
|
|
6704
|
+
const kind = name === "constructor" ? "constructor" : "method";
|
|
6705
|
+
push(kind, classCtx ? `${classCtx}.${name}` : name, node);
|
|
6706
|
+
}
|
|
6707
|
+
break;
|
|
6708
|
+
}
|
|
6709
|
+
case "variable_declarator": {
|
|
6710
|
+
const value = node.childForFieldName("value");
|
|
6711
|
+
if (value && (value.type === "arrow_function" || value.type === "function" || value.type === "function_expression" || value.type === "generator_function")) {
|
|
6712
|
+
const nameNode = node.childForFieldName("name");
|
|
6713
|
+
if (nameNode && nameNode.type === "identifier") {
|
|
6714
|
+
push("function", nameNode.text, node);
|
|
6715
|
+
}
|
|
6716
|
+
}
|
|
6717
|
+
break;
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6721
|
+
const child = node.namedChild(i);
|
|
6722
|
+
if (child) visit(child, classCtx);
|
|
6723
|
+
}
|
|
6724
|
+
};
|
|
6725
|
+
visit(root, void 0);
|
|
6726
|
+
return out;
|
|
6727
|
+
}
|
|
6728
|
+
function collectPythonSymbolDefs(root) {
|
|
6729
|
+
const out = [];
|
|
6730
|
+
const push = (kind, qualname, node) => {
|
|
6731
|
+
out.push({
|
|
6732
|
+
kind,
|
|
6733
|
+
qualname,
|
|
6734
|
+
startLine: node.startPosition.row + 1,
|
|
6735
|
+
endLine: node.endPosition.row + 1
|
|
6736
|
+
});
|
|
6737
|
+
};
|
|
6738
|
+
const visit = (node, classCtx) => {
|
|
6739
|
+
switch (node.type) {
|
|
6740
|
+
case "function_definition": {
|
|
6741
|
+
const name = node.childForFieldName("name")?.text;
|
|
6742
|
+
if (name) {
|
|
6743
|
+
if (classCtx) {
|
|
6744
|
+
const kind = name === "__init__" ? "constructor" : "method";
|
|
6745
|
+
push(kind, `${classCtx}.${name}`, node);
|
|
6746
|
+
} else {
|
|
6747
|
+
push("function", name, node);
|
|
6748
|
+
}
|
|
6749
|
+
}
|
|
6750
|
+
const body = node.childForFieldName("body");
|
|
6751
|
+
if (body) {
|
|
6752
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
6753
|
+
const child = body.namedChild(i);
|
|
6754
|
+
if (child) visit(child, void 0);
|
|
6755
|
+
}
|
|
6756
|
+
}
|
|
6757
|
+
return;
|
|
6758
|
+
}
|
|
6759
|
+
case "class_definition": {
|
|
6760
|
+
const name = node.childForFieldName("name")?.text;
|
|
6761
|
+
if (name) push("class", name, node);
|
|
6762
|
+
const body = node.childForFieldName("body");
|
|
6763
|
+
if (body) {
|
|
6764
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
6765
|
+
const child = body.namedChild(i);
|
|
6766
|
+
if (child) visit(child, name ?? classCtx);
|
|
6767
|
+
}
|
|
6768
|
+
}
|
|
6769
|
+
return;
|
|
6770
|
+
}
|
|
6771
|
+
case "decorated_definition": {
|
|
6772
|
+
const def = node.childForFieldName("definition");
|
|
6773
|
+
if (def) visit(def, classCtx);
|
|
6774
|
+
return;
|
|
6775
|
+
}
|
|
6776
|
+
}
|
|
6777
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6778
|
+
const child = node.namedChild(i);
|
|
6779
|
+
if (child) visit(child, classCtx);
|
|
6780
|
+
}
|
|
6781
|
+
};
|
|
6782
|
+
visit(root, void 0);
|
|
6783
|
+
return out;
|
|
6784
|
+
}
|
|
6785
|
+
function goReceiverType(node) {
|
|
6786
|
+
const receiver = node.childForFieldName("receiver");
|
|
6787
|
+
if (!receiver) return void 0;
|
|
6788
|
+
for (let i = 0; i < receiver.namedChildCount; i++) {
|
|
6789
|
+
const param = receiver.namedChild(i);
|
|
6790
|
+
if (param?.type !== "parameter_declaration") continue;
|
|
6791
|
+
let typeNode = param.childForFieldName("type");
|
|
6792
|
+
if (typeNode?.type === "pointer_type") typeNode = typeNode.namedChild(0) ?? typeNode;
|
|
6793
|
+
if (typeNode?.type === "generic_type") {
|
|
6794
|
+
typeNode = typeNode.childForFieldName("type") ?? typeNode.namedChild(0) ?? typeNode;
|
|
6795
|
+
}
|
|
6796
|
+
if (typeNode?.type === "type_identifier") return typeNode.text;
|
|
6797
|
+
return typeNode?.text;
|
|
6798
|
+
}
|
|
6799
|
+
return void 0;
|
|
6800
|
+
}
|
|
6801
|
+
function collectGoSymbolDefs(root) {
|
|
6802
|
+
const out = [];
|
|
6803
|
+
const push = (kind, qualname, node) => {
|
|
6804
|
+
out.push({
|
|
6805
|
+
kind,
|
|
6806
|
+
qualname,
|
|
6807
|
+
startLine: node.startPosition.row + 1,
|
|
6808
|
+
endLine: node.endPosition.row + 1
|
|
6809
|
+
});
|
|
6810
|
+
};
|
|
6811
|
+
const visit = (node) => {
|
|
6812
|
+
switch (node.type) {
|
|
6813
|
+
case "function_declaration": {
|
|
6814
|
+
const name = node.childForFieldName("name")?.text;
|
|
6815
|
+
if (name) push("function", name, node);
|
|
6816
|
+
break;
|
|
6817
|
+
}
|
|
6818
|
+
case "method_declaration": {
|
|
6819
|
+
const name = node.childForFieldName("name")?.text;
|
|
6820
|
+
if (name) {
|
|
6821
|
+
const recv = goReceiverType(node);
|
|
6822
|
+
push("method", recv ? `${recv}.${name}` : name, node);
|
|
6823
|
+
}
|
|
6824
|
+
break;
|
|
6825
|
+
}
|
|
6826
|
+
}
|
|
6827
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6828
|
+
const child = node.namedChild(i);
|
|
6829
|
+
if (child) visit(child);
|
|
6830
|
+
}
|
|
6831
|
+
};
|
|
6832
|
+
visit(root);
|
|
6833
|
+
return out;
|
|
6834
|
+
}
|
|
6835
|
+
function collectRubySymbolDefs(root) {
|
|
6836
|
+
const out = [];
|
|
6837
|
+
const push = (kind, qualname, node) => {
|
|
6838
|
+
out.push({
|
|
6839
|
+
kind,
|
|
6840
|
+
qualname,
|
|
6841
|
+
startLine: node.startPosition.row + 1,
|
|
6842
|
+
endLine: node.endPosition.row + 1
|
|
6843
|
+
});
|
|
6844
|
+
};
|
|
6845
|
+
const nameText = (node) => node.childForFieldName("name")?.text.replace(/::/g, ".");
|
|
6846
|
+
const walkBody = (node, ctx) => {
|
|
6847
|
+
const body = node.childForFieldName("body");
|
|
6848
|
+
if (!body) return;
|
|
6849
|
+
for (let i = 0; i < body.namedChildCount; i++) {
|
|
6850
|
+
const child = body.namedChild(i);
|
|
6851
|
+
if (child) visit(child, ctx);
|
|
6852
|
+
}
|
|
6853
|
+
};
|
|
6854
|
+
const visit = (node, classCtx) => {
|
|
6855
|
+
switch (node.type) {
|
|
6856
|
+
case "method":
|
|
6857
|
+
case "singleton_method": {
|
|
6858
|
+
const name = node.childForFieldName("name")?.text;
|
|
6859
|
+
if (name) {
|
|
6860
|
+
if (classCtx) {
|
|
6861
|
+
const kind = node.type === "method" && name === "initialize" ? "constructor" : "method";
|
|
6862
|
+
push(kind, `${classCtx}.${name}`, node);
|
|
6863
|
+
} else {
|
|
6864
|
+
push("function", name, node);
|
|
6865
|
+
}
|
|
6866
|
+
}
|
|
6867
|
+
walkBody(node, void 0);
|
|
6868
|
+
return;
|
|
6869
|
+
}
|
|
6870
|
+
case "class":
|
|
6871
|
+
case "module": {
|
|
6872
|
+
const name = nameText(node);
|
|
6873
|
+
if (name) push("class", classCtx ? `${classCtx}.${name}` : name, node);
|
|
6874
|
+
walkBody(node, name ? classCtx ? `${classCtx}.${name}` : name : classCtx);
|
|
6875
|
+
return;
|
|
6876
|
+
}
|
|
6877
|
+
}
|
|
6878
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6879
|
+
const child = node.namedChild(i);
|
|
6880
|
+
if (child) visit(child, classCtx);
|
|
6881
|
+
}
|
|
6882
|
+
};
|
|
6883
|
+
visit(root, void 0);
|
|
6884
|
+
return out;
|
|
6885
|
+
}
|
|
6886
|
+
function collectPhpSymbolDefs(root) {
|
|
6887
|
+
const out = [];
|
|
6888
|
+
const push = (kind, qualname, node) => {
|
|
6889
|
+
out.push({
|
|
6890
|
+
kind,
|
|
6891
|
+
qualname,
|
|
6892
|
+
startLine: node.startPosition.row + 1,
|
|
6893
|
+
endLine: node.endPosition.row + 1
|
|
6894
|
+
});
|
|
6895
|
+
};
|
|
6896
|
+
const dot = (s) => s.replace(/\\/g, ".").replace(/::/g, ".").replace(/^\.+/, "");
|
|
6897
|
+
const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
|
|
6898
|
+
const walkChildren = (container, nsCtx, classCtx) => {
|
|
6899
|
+
let ns = nsCtx;
|
|
6900
|
+
for (let i = 0; i < container.namedChildCount; i++) {
|
|
6901
|
+
const child = container.namedChild(i);
|
|
6902
|
+
if (!child) continue;
|
|
6903
|
+
if (child.type === "namespace_definition" && !child.childForFieldName("body")) {
|
|
6904
|
+
const nameNode = child.childForFieldName("name");
|
|
6905
|
+
if (nameNode) ns = dot(nameNode.text);
|
|
6906
|
+
continue;
|
|
6907
|
+
}
|
|
6908
|
+
visit(child, ns, classCtx);
|
|
6909
|
+
}
|
|
6910
|
+
};
|
|
6911
|
+
const walkBody = (node, nsCtx, classCtx) => {
|
|
6912
|
+
const body = node.childForFieldName("body");
|
|
6913
|
+
if (body) walkChildren(body, nsCtx, classCtx);
|
|
6914
|
+
};
|
|
6915
|
+
const visit = (node, nsCtx, classCtx) => {
|
|
6916
|
+
switch (node.type) {
|
|
6917
|
+
case "function_definition": {
|
|
6918
|
+
const name = node.childForFieldName("name")?.text;
|
|
6919
|
+
if (name) push("function", join(nsCtx, name), node);
|
|
6920
|
+
walkBody(node, nsCtx, void 0);
|
|
6921
|
+
return;
|
|
6922
|
+
}
|
|
6923
|
+
case "class_declaration":
|
|
6924
|
+
case "trait_declaration":
|
|
6925
|
+
case "interface_declaration": {
|
|
6926
|
+
const name = node.childForFieldName("name")?.text;
|
|
6927
|
+
const full = name ? join(nsCtx, name) : classCtx;
|
|
6928
|
+
if (name) push("class", full, node);
|
|
6929
|
+
walkBody(node, nsCtx, full);
|
|
6930
|
+
return;
|
|
6931
|
+
}
|
|
6932
|
+
case "method_declaration": {
|
|
6933
|
+
const name = node.childForFieldName("name")?.text;
|
|
6934
|
+
if (name) {
|
|
6935
|
+
const kind = name === "__construct" ? "constructor" : "method";
|
|
6936
|
+
push(kind, join(classCtx ?? nsCtx, name), node);
|
|
6937
|
+
}
|
|
6938
|
+
return;
|
|
6939
|
+
}
|
|
6940
|
+
case "namespace_definition": {
|
|
6941
|
+
const nameNode = node.childForFieldName("name");
|
|
6942
|
+
const ns = nameNode ? dot(nameNode.text) : nsCtx;
|
|
6943
|
+
walkBody(node, ns, classCtx);
|
|
6944
|
+
return;
|
|
6945
|
+
}
|
|
6946
|
+
}
|
|
6947
|
+
walkChildren(node, nsCtx, classCtx);
|
|
6948
|
+
};
|
|
6949
|
+
walkChildren(root, void 0, void 0);
|
|
6950
|
+
return out;
|
|
6951
|
+
}
|
|
6952
|
+
function collectCsharpSymbolDefs(root) {
|
|
6953
|
+
const out = [];
|
|
6954
|
+
const push = (kind, qualname, node) => {
|
|
6955
|
+
out.push({
|
|
6956
|
+
kind,
|
|
6957
|
+
qualname,
|
|
6958
|
+
startLine: node.startPosition.row + 1,
|
|
6959
|
+
endLine: node.endPosition.row + 1
|
|
6960
|
+
});
|
|
6961
|
+
};
|
|
6962
|
+
const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
|
|
6963
|
+
const walkChildren = (container, nsCtx, classCtx) => {
|
|
6964
|
+
let ns = nsCtx;
|
|
6965
|
+
for (let i = 0; i < container.namedChildCount; i++) {
|
|
6966
|
+
const child = container.namedChild(i);
|
|
6967
|
+
if (!child) continue;
|
|
6968
|
+
if (child.type === "file_scoped_namespace_declaration") {
|
|
6969
|
+
const nameNode = child.childForFieldName("name");
|
|
6970
|
+
if (nameNode) ns = join(nsCtx, nameNode.text);
|
|
6971
|
+
continue;
|
|
6972
|
+
}
|
|
6973
|
+
visit(child, ns, classCtx);
|
|
6974
|
+
}
|
|
6975
|
+
};
|
|
6976
|
+
const walkBody = (node, nsCtx, classCtx) => {
|
|
6977
|
+
const body = node.childForFieldName("body");
|
|
6978
|
+
if (body) walkChildren(body, nsCtx, classCtx);
|
|
6979
|
+
};
|
|
6980
|
+
const visit = (node, nsCtx, classCtx) => {
|
|
6981
|
+
switch (node.type) {
|
|
6982
|
+
case "namespace_declaration": {
|
|
6983
|
+
const nameNode = node.childForFieldName("name");
|
|
6984
|
+
const ns = nameNode ? join(nsCtx, nameNode.text) : nsCtx;
|
|
6985
|
+
walkBody(node, ns, classCtx);
|
|
6986
|
+
return;
|
|
6987
|
+
}
|
|
6988
|
+
case "class_declaration":
|
|
6989
|
+
case "interface_declaration":
|
|
6990
|
+
case "struct_declaration":
|
|
6991
|
+
case "record_declaration": {
|
|
6992
|
+
const name = node.childForFieldName("name")?.text;
|
|
6993
|
+
const full = name ? join(classCtx ?? nsCtx, name) : classCtx;
|
|
6994
|
+
if (name) push("class", full, node);
|
|
6995
|
+
walkBody(node, nsCtx, full);
|
|
6996
|
+
return;
|
|
6997
|
+
}
|
|
6998
|
+
case "method_declaration": {
|
|
6999
|
+
const name = node.childForFieldName("name")?.text;
|
|
7000
|
+
if (name) push("method", join(classCtx ?? nsCtx, name), node);
|
|
7001
|
+
return;
|
|
7002
|
+
}
|
|
7003
|
+
case "constructor_declaration": {
|
|
7004
|
+
const name = node.childForFieldName("name")?.text;
|
|
7005
|
+
if (name) push("constructor", join(classCtx ?? nsCtx, name), node);
|
|
7006
|
+
return;
|
|
7007
|
+
}
|
|
7008
|
+
}
|
|
7009
|
+
walkChildren(node, nsCtx, classCtx);
|
|
7010
|
+
};
|
|
7011
|
+
walkChildren(root, void 0, void 0);
|
|
7012
|
+
return out;
|
|
7013
|
+
}
|
|
7014
|
+
function collectJavaSymbolDefs(root) {
|
|
6380
7015
|
const out = [];
|
|
6381
7016
|
const push = (kind, qualname, node) => {
|
|
6382
7017
|
out.push({
|
|
@@ -6386,55 +7021,71 @@ function collectSymbolDefs(root) {
|
|
|
6386
7021
|
endLine: node.endPosition.row + 1
|
|
6387
7022
|
});
|
|
6388
7023
|
};
|
|
7024
|
+
const join = (prefix, name) => prefix ? `${prefix}.${name}` : name;
|
|
7025
|
+
let pkg;
|
|
7026
|
+
for (let i = 0; i < root.namedChildCount; i++) {
|
|
7027
|
+
const child = root.namedChild(i);
|
|
7028
|
+
if (child?.type === "package_declaration") {
|
|
7029
|
+
pkg = child.namedChild(0)?.text;
|
|
7030
|
+
break;
|
|
7031
|
+
}
|
|
7032
|
+
}
|
|
7033
|
+
const walkChildren = (container, classCtx) => {
|
|
7034
|
+
for (let i = 0; i < container.namedChildCount; i++) {
|
|
7035
|
+
const child = container.namedChild(i);
|
|
7036
|
+
if (child) visit(child, classCtx);
|
|
7037
|
+
}
|
|
7038
|
+
};
|
|
7039
|
+
const walkBody = (node, classCtx) => {
|
|
7040
|
+
const body = node.childForFieldName("body");
|
|
7041
|
+
if (body) walkChildren(body, classCtx);
|
|
7042
|
+
};
|
|
6389
7043
|
const visit = (node, classCtx) => {
|
|
6390
7044
|
switch (node.type) {
|
|
6391
|
-
case "function_declaration":
|
|
6392
|
-
case "generator_function_declaration": {
|
|
6393
|
-
const name = node.childForFieldName("name")?.text;
|
|
6394
|
-
if (name) push("function", name, node);
|
|
6395
|
-
break;
|
|
6396
|
-
}
|
|
6397
7045
|
case "class_declaration":
|
|
6398
|
-
case "
|
|
6399
|
-
case "
|
|
7046
|
+
case "interface_declaration":
|
|
7047
|
+
case "enum_declaration":
|
|
7048
|
+
case "record_declaration": {
|
|
6400
7049
|
const name = node.childForFieldName("name")?.text;
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
for (let i = 0; i < body.namedChildCount; i++) {
|
|
6405
|
-
const child = body.namedChild(i);
|
|
6406
|
-
if (child) visit(child, name ?? classCtx);
|
|
6407
|
-
}
|
|
6408
|
-
}
|
|
7050
|
+
const full = name ? join(classCtx ?? pkg, name) : classCtx;
|
|
7051
|
+
if (name) push("class", full, node);
|
|
7052
|
+
walkBody(node, full);
|
|
6409
7053
|
return;
|
|
6410
7054
|
}
|
|
6411
|
-
case "
|
|
6412
|
-
const name =
|
|
6413
|
-
if (name)
|
|
6414
|
-
|
|
6415
|
-
push(kind, classCtx ? `${classCtx}.${name}` : name, node);
|
|
6416
|
-
}
|
|
6417
|
-
break;
|
|
7055
|
+
case "method_declaration": {
|
|
7056
|
+
const name = node.childForFieldName("name")?.text;
|
|
7057
|
+
if (name) push("method", join(classCtx ?? pkg, name), node);
|
|
7058
|
+
return;
|
|
6418
7059
|
}
|
|
6419
|
-
case "
|
|
6420
|
-
const
|
|
6421
|
-
if (
|
|
6422
|
-
|
|
6423
|
-
if (nameNode && nameNode.type === "identifier") {
|
|
6424
|
-
push("function", nameNode.text, node);
|
|
6425
|
-
}
|
|
6426
|
-
}
|
|
6427
|
-
break;
|
|
7060
|
+
case "constructor_declaration": {
|
|
7061
|
+
const name = node.childForFieldName("name")?.text;
|
|
7062
|
+
if (name) push("constructor", join(classCtx ?? pkg, name), node);
|
|
7063
|
+
return;
|
|
6428
7064
|
}
|
|
6429
7065
|
}
|
|
6430
|
-
|
|
6431
|
-
const child = node.namedChild(i);
|
|
6432
|
-
if (child) visit(child, classCtx);
|
|
6433
|
-
}
|
|
7066
|
+
walkChildren(node, classCtx);
|
|
6434
7067
|
};
|
|
6435
|
-
|
|
7068
|
+
walkChildren(root, void 0);
|
|
6436
7069
|
return out;
|
|
6437
7070
|
}
|
|
7071
|
+
function collectSymbolDefsForExt(ext, root) {
|
|
7072
|
+
switch (ext) {
|
|
7073
|
+
case ".py":
|
|
7074
|
+
return collectPythonSymbolDefs(root);
|
|
7075
|
+
case ".go":
|
|
7076
|
+
return collectGoSymbolDefs(root);
|
|
7077
|
+
case ".rb":
|
|
7078
|
+
return collectRubySymbolDefs(root);
|
|
7079
|
+
case ".php":
|
|
7080
|
+
return collectPhpSymbolDefs(root);
|
|
7081
|
+
case ".cs":
|
|
7082
|
+
return collectCsharpSymbolDefs(root);
|
|
7083
|
+
case ".java":
|
|
7084
|
+
return collectJavaSymbolDefs(root);
|
|
7085
|
+
default:
|
|
7086
|
+
return collectSymbolDefs(root);
|
|
7087
|
+
}
|
|
7088
|
+
}
|
|
6438
7089
|
function disambiguate(defs) {
|
|
6439
7090
|
const counts = /* @__PURE__ */ new Map();
|
|
6440
7091
|
for (const def of defs) counts.set(def.qualname, (counts.get(def.qualname) ?? 0) + 1);
|
|
@@ -6449,7 +7100,7 @@ function disambiguate(defs) {
|
|
|
6449
7100
|
async function addSymbols(graph, services) {
|
|
6450
7101
|
const parsers = /* @__PURE__ */ new Map();
|
|
6451
7102
|
const parserForExt5 = (ext) => {
|
|
6452
|
-
const grammar =
|
|
7103
|
+
const grammar = SYMBOL_GRAMMAR_BY_EXT[ext];
|
|
6453
7104
|
if (!grammar) return null;
|
|
6454
7105
|
let parser = parsers.get(ext);
|
|
6455
7106
|
if (!parser) {
|
|
@@ -6464,13 +7115,14 @@ async function addSymbols(graph, services) {
|
|
|
6464
7115
|
for (const service of services) {
|
|
6465
7116
|
const files = await loadSourceFiles(service.dir);
|
|
6466
7117
|
for (const file of files) {
|
|
6467
|
-
const
|
|
7118
|
+
const ext = path20.extname(file.path);
|
|
7119
|
+
const parser = parserForExt5(ext);
|
|
6468
7120
|
if (!parser) continue;
|
|
6469
|
-
const relPath = toPosix(
|
|
7121
|
+
const relPath = toPosix(path20.relative(service.dir, file.path));
|
|
6470
7122
|
let defs;
|
|
6471
7123
|
try {
|
|
6472
7124
|
const tree = parseSource3(parser, file.content);
|
|
6473
|
-
defs =
|
|
7125
|
+
defs = collectSymbolDefsForExt(ext, tree.rootNode);
|
|
6474
7126
|
} catch (err) {
|
|
6475
7127
|
recordExtractionError("symbol extraction", file.path, err);
|
|
6476
7128
|
continue;
|
|
@@ -6489,7 +7141,7 @@ async function addSymbols(graph, services) {
|
|
|
6489
7141
|
if (!graph.hasNode(sid)) {
|
|
6490
7142
|
const node = {
|
|
6491
7143
|
id: sid,
|
|
6492
|
-
type:
|
|
7144
|
+
type: NodeType14.SymbolNode,
|
|
6493
7145
|
kind: def.kind,
|
|
6494
7146
|
qualname: def.qualname,
|
|
6495
7147
|
span: { startLine: def.startLine, endLine: def.endLine },
|
|
@@ -6525,11 +7177,11 @@ async function addSymbols(graph, services) {
|
|
|
6525
7177
|
}
|
|
6526
7178
|
|
|
6527
7179
|
// src/extract/symbol-edges.ts
|
|
6528
|
-
import
|
|
7180
|
+
import path21 from "path";
|
|
6529
7181
|
import Parser4 from "tree-sitter";
|
|
6530
7182
|
import {
|
|
6531
7183
|
EdgeType as EdgeType8,
|
|
6532
|
-
NodeType as
|
|
7184
|
+
NodeType as NodeType15,
|
|
6533
7185
|
Provenance as Provenance8,
|
|
6534
7186
|
confidenceForExtracted as confidenceForExtracted5,
|
|
6535
7187
|
extractedEdgeId as extractedEdgeId7,
|
|
@@ -6639,10 +7291,10 @@ async function addSymbolEdges(graph, services) {
|
|
|
6639
7291
|
const tsPaths = await loadTsPathConfig(service.dir);
|
|
6640
7292
|
const files = await loadSourceFiles(service.dir);
|
|
6641
7293
|
for (const file of files) {
|
|
6642
|
-
const parser = parserForExt5(
|
|
7294
|
+
const parser = parserForExt5(path21.extname(file.path));
|
|
6643
7295
|
if (!parser) continue;
|
|
6644
|
-
const relPath = toPosix(
|
|
6645
|
-
const fileDir =
|
|
7296
|
+
const relPath = toPosix(path21.relative(service.dir, file.path));
|
|
7297
|
+
const fileDir = path21.dirname(file.path);
|
|
6646
7298
|
let root;
|
|
6647
7299
|
try {
|
|
6648
7300
|
root = parseSource3(parser, file.content).rootNode;
|
|
@@ -6684,7 +7336,7 @@ async function addSymbolEdges(graph, services) {
|
|
|
6684
7336
|
const candidate = symbolId3(serviceName, imp.targetRelPath, imp.importedName);
|
|
6685
7337
|
if (graph.hasNode(candidate)) {
|
|
6686
7338
|
const node = graph.getNodeAttributes(candidate);
|
|
6687
|
-
if (node.type ===
|
|
7339
|
+
if (node.type === NodeType15.SymbolNode && node.kind === wantKind) return candidate;
|
|
6688
7340
|
}
|
|
6689
7341
|
}
|
|
6690
7342
|
return null;
|
|
@@ -6776,11 +7428,11 @@ async function addSymbolEdges(graph, services) {
|
|
|
6776
7428
|
}
|
|
6777
7429
|
|
|
6778
7430
|
// src/extract/actions.ts
|
|
6779
|
-
import
|
|
7431
|
+
import path22 from "path";
|
|
6780
7432
|
import Parser5 from "tree-sitter";
|
|
6781
7433
|
import {
|
|
6782
7434
|
EdgeType as EdgeType9,
|
|
6783
|
-
NodeType as
|
|
7435
|
+
NodeType as NodeType16,
|
|
6784
7436
|
Provenance as Provenance9,
|
|
6785
7437
|
confidenceForExtracted as confidenceForExtracted6,
|
|
6786
7438
|
extractedEdgeId as extractedEdgeId8,
|
|
@@ -6930,9 +7582,9 @@ async function addServerActions(graph, services) {
|
|
|
6930
7582
|
const files = await loadSourceFiles(service.dir);
|
|
6931
7583
|
for (const file of files) {
|
|
6932
7584
|
if (isTestPath(file.path)) continue;
|
|
6933
|
-
const parser = parserForExt5(
|
|
7585
|
+
const parser = parserForExt5(path22.extname(file.path));
|
|
6934
7586
|
if (!parser) continue;
|
|
6935
|
-
const relPath = toPosix(
|
|
7587
|
+
const relPath = toPosix(path22.relative(service.dir, file.path));
|
|
6936
7588
|
let root;
|
|
6937
7589
|
try {
|
|
6938
7590
|
root = parseSource3(parser, file.content).rootNode;
|
|
@@ -6959,7 +7611,7 @@ async function addServerActions(graph, services) {
|
|
|
6959
7611
|
if (!graph.hasNode(aid)) {
|
|
6960
7612
|
const node = {
|
|
6961
7613
|
id: aid,
|
|
6962
|
-
type:
|
|
7614
|
+
type: NodeType16.ServerActionNode,
|
|
6963
7615
|
name: action.exportName,
|
|
6964
7616
|
service: service.pkg.name,
|
|
6965
7617
|
module: relPath,
|
|
@@ -6993,10 +7645,10 @@ async function addServerActions(graph, services) {
|
|
|
6993
7645
|
}
|
|
6994
7646
|
for (const file of files) {
|
|
6995
7647
|
if (isTestPath(file.path)) continue;
|
|
6996
|
-
const parser = parserForExt5(
|
|
7648
|
+
const parser = parserForExt5(path22.extname(file.path));
|
|
6997
7649
|
if (!parser) continue;
|
|
6998
|
-
const relPath = toPosix(
|
|
6999
|
-
const fileDir =
|
|
7650
|
+
const relPath = toPosix(path22.relative(service.dir, file.path));
|
|
7651
|
+
const fileDir = path22.dirname(file.path);
|
|
7000
7652
|
let root;
|
|
7001
7653
|
try {
|
|
7002
7654
|
root = parseSource3(parser, file.content).rootNode;
|
|
@@ -7048,10 +7700,10 @@ async function addServerActions(graph, services) {
|
|
|
7048
7700
|
}
|
|
7049
7701
|
|
|
7050
7702
|
// src/extract/databases/index.ts
|
|
7051
|
-
import
|
|
7703
|
+
import path30 from "path";
|
|
7052
7704
|
import {
|
|
7053
7705
|
EdgeType as EdgeType10,
|
|
7054
|
-
NodeType as
|
|
7706
|
+
NodeType as NodeType17,
|
|
7055
7707
|
Provenance as Provenance10,
|
|
7056
7708
|
configId,
|
|
7057
7709
|
databaseId as databaseId2,
|
|
@@ -7059,9 +7711,9 @@ import {
|
|
|
7059
7711
|
} from "@neat.is/types";
|
|
7060
7712
|
|
|
7061
7713
|
// src/extract/databases/db-config-yaml.ts
|
|
7062
|
-
import
|
|
7714
|
+
import path23 from "path";
|
|
7063
7715
|
async function parse(serviceDir) {
|
|
7064
|
-
const yamlPath =
|
|
7716
|
+
const yamlPath = path23.join(serviceDir, "db-config.yaml");
|
|
7065
7717
|
if (!await exists(yamlPath)) return [];
|
|
7066
7718
|
const raw = await readYaml(yamlPath);
|
|
7067
7719
|
return [
|
|
@@ -7078,12 +7730,12 @@ async function parse(serviceDir) {
|
|
|
7078
7730
|
var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
7079
7731
|
|
|
7080
7732
|
// src/extract/databases/dotenv.ts
|
|
7081
|
-
import { promises as
|
|
7082
|
-
import
|
|
7733
|
+
import { promises as fs18 } from "fs";
|
|
7734
|
+
import path25 from "path";
|
|
7083
7735
|
|
|
7084
7736
|
// src/extract/databases/shared.ts
|
|
7085
|
-
import { promises as
|
|
7086
|
-
import
|
|
7737
|
+
import { promises as fs17 } from "fs";
|
|
7738
|
+
import path24 from "path";
|
|
7087
7739
|
function schemeToEngine(scheme) {
|
|
7088
7740
|
const s = scheme.toLowerCase().split("+")[0];
|
|
7089
7741
|
switch (s) {
|
|
@@ -7122,18 +7774,18 @@ function parseConnectionString(url) {
|
|
|
7122
7774
|
}
|
|
7123
7775
|
async function readIfExists(filePath) {
|
|
7124
7776
|
try {
|
|
7125
|
-
return await
|
|
7777
|
+
return await fs17.readFile(filePath, "utf8");
|
|
7126
7778
|
} catch {
|
|
7127
7779
|
return null;
|
|
7128
7780
|
}
|
|
7129
7781
|
}
|
|
7130
7782
|
async function resolveEnvVar(serviceDir, name) {
|
|
7131
|
-
const entries = await
|
|
7783
|
+
const entries = await fs17.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
7132
7784
|
const envNames = entries.filter((e) => e.isFile() && (e.name === ".env" || e.name.startsWith(".env."))).map((e) => e.name);
|
|
7133
7785
|
const rank = (n) => n === ".env.local" ? 0 : n === ".env" ? 1 : 2;
|
|
7134
7786
|
envNames.sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
|
|
7135
7787
|
for (const fileName of envNames) {
|
|
7136
|
-
const content = await readIfExists(
|
|
7788
|
+
const content = await readIfExists(path24.join(serviceDir, fileName));
|
|
7137
7789
|
if (!content) continue;
|
|
7138
7790
|
for (const line of content.split("\n")) {
|
|
7139
7791
|
const trimmed = line.trim();
|
|
@@ -7152,7 +7804,7 @@ async function resolveEnvVar(serviceDir, name) {
|
|
|
7152
7804
|
}
|
|
7153
7805
|
async function findFirst(serviceDir, candidates) {
|
|
7154
7806
|
for (const rel of candidates) {
|
|
7155
|
-
const abs =
|
|
7807
|
+
const abs = path24.join(serviceDir, rel);
|
|
7156
7808
|
const content = await readIfExists(abs);
|
|
7157
7809
|
if (content !== null) return abs;
|
|
7158
7810
|
}
|
|
@@ -7210,15 +7862,15 @@ function parseDotenvLine(line) {
|
|
|
7210
7862
|
return { key, value };
|
|
7211
7863
|
}
|
|
7212
7864
|
async function parse2(serviceDir) {
|
|
7213
|
-
const entries = await
|
|
7865
|
+
const entries = await fs18.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
7214
7866
|
const configs = [];
|
|
7215
7867
|
const seen = /* @__PURE__ */ new Set();
|
|
7216
7868
|
for (const entry of entries) {
|
|
7217
7869
|
if (!entry.isFile()) continue;
|
|
7218
7870
|
const match = isConfigFile(entry.name);
|
|
7219
7871
|
if (!match.match || match.fileType !== "env") continue;
|
|
7220
|
-
const filePath =
|
|
7221
|
-
const content = await
|
|
7872
|
+
const filePath = path25.join(serviceDir, entry.name);
|
|
7873
|
+
const content = await fs18.readFile(filePath, "utf8");
|
|
7222
7874
|
for (const line of content.split("\n")) {
|
|
7223
7875
|
const parsed = parseDotenvLine(line);
|
|
7224
7876
|
if (!parsed) continue;
|
|
@@ -7236,9 +7888,9 @@ async function parse2(serviceDir) {
|
|
|
7236
7888
|
var dotenvParser = { name: ".env", parse: parse2 };
|
|
7237
7889
|
|
|
7238
7890
|
// src/extract/databases/prisma.ts
|
|
7239
|
-
import
|
|
7891
|
+
import path26 from "path";
|
|
7240
7892
|
async function parse3(serviceDir) {
|
|
7241
|
-
const schemaPath =
|
|
7893
|
+
const schemaPath = path26.join(serviceDir, "prisma", "schema.prisma");
|
|
7242
7894
|
const content = await readIfExists(schemaPath);
|
|
7243
7895
|
if (!content) return [];
|
|
7244
7896
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -7393,10 +8045,10 @@ async function parse5(serviceDir) {
|
|
|
7393
8045
|
var knexParser = { name: "knex", parse: parse5 };
|
|
7394
8046
|
|
|
7395
8047
|
// src/extract/databases/ormconfig.ts
|
|
7396
|
-
import
|
|
8048
|
+
import path27 from "path";
|
|
7397
8049
|
async function parse6(serviceDir) {
|
|
7398
8050
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
7399
|
-
const abs =
|
|
8051
|
+
const abs = path27.join(serviceDir, candidate);
|
|
7400
8052
|
if (!await exists(abs)) continue;
|
|
7401
8053
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
7402
8054
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -7454,9 +8106,9 @@ async function parse7(serviceDir) {
|
|
|
7454
8106
|
var typeormParser = { name: "typeorm", parse: parse7 };
|
|
7455
8107
|
|
|
7456
8108
|
// src/extract/databases/sequelize.ts
|
|
7457
|
-
import
|
|
8109
|
+
import path28 from "path";
|
|
7458
8110
|
async function parse8(serviceDir) {
|
|
7459
|
-
const configPath =
|
|
8111
|
+
const configPath = path28.join(serviceDir, "config", "config.json");
|
|
7460
8112
|
if (!await exists(configPath)) return [];
|
|
7461
8113
|
const raw = await readJson(configPath);
|
|
7462
8114
|
const out = [];
|
|
@@ -7482,7 +8134,7 @@ async function parse8(serviceDir) {
|
|
|
7482
8134
|
var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
7483
8135
|
|
|
7484
8136
|
// src/extract/databases/docker-compose.ts
|
|
7485
|
-
import
|
|
8137
|
+
import path29 from "path";
|
|
7486
8138
|
function portFromService(svc) {
|
|
7487
8139
|
for (const raw of svc.ports ?? []) {
|
|
7488
8140
|
const str = String(raw);
|
|
@@ -7509,7 +8161,7 @@ function databaseFromEnv(svc) {
|
|
|
7509
8161
|
}
|
|
7510
8162
|
async function parse9(serviceDir) {
|
|
7511
8163
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
7512
|
-
const abs =
|
|
8164
|
+
const abs = path29.join(serviceDir, name);
|
|
7513
8165
|
if (!await exists(abs)) continue;
|
|
7514
8166
|
const raw = await readYaml(abs);
|
|
7515
8167
|
if (!raw?.services) return [];
|
|
@@ -7551,7 +8203,7 @@ function compatibleDriversFor(engine) {
|
|
|
7551
8203
|
function toDatabaseNode(config) {
|
|
7552
8204
|
return {
|
|
7553
8205
|
id: databaseId2(config.host),
|
|
7554
|
-
type:
|
|
8206
|
+
type: NodeType17.DatabaseNode,
|
|
7555
8207
|
name: config.database || config.host,
|
|
7556
8208
|
engine: config.engine,
|
|
7557
8209
|
engineVersion: config.engineVersion,
|
|
@@ -7681,7 +8333,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
7681
8333
|
discoveredVia: mergedDiscoveredVia
|
|
7682
8334
|
});
|
|
7683
8335
|
}
|
|
7684
|
-
const relConfigFile = toPosix(
|
|
8336
|
+
const relConfigFile = toPosix(path30.relative(service.dir, config.sourceFile));
|
|
7685
8337
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
7686
8338
|
graph,
|
|
7687
8339
|
service.pkg.name,
|
|
@@ -7690,7 +8342,7 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
7690
8342
|
);
|
|
7691
8343
|
nodesAdded += fn;
|
|
7692
8344
|
edgesAdded += fe;
|
|
7693
|
-
const evidenceFile = toPosix(
|
|
8345
|
+
const evidenceFile = toPosix(path30.relative(scanPath, config.sourceFile));
|
|
7694
8346
|
const edge = {
|
|
7695
8347
|
id: extractedEdgeId(fileNodeId, dbNode.id, EdgeType10.CONNECTS_TO),
|
|
7696
8348
|
source: fileNodeId,
|
|
@@ -7708,15 +8360,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
7708
8360
|
if (allConfigs.length === 1) {
|
|
7709
8361
|
const primary = allConfigs[0];
|
|
7710
8362
|
service.node.dbConnectionTarget = primary.port ? `${primary.host}:${primary.port}` : primary.host;
|
|
7711
|
-
const relPath =
|
|
8363
|
+
const relPath = path30.relative(scanPath, primary.sourceFile);
|
|
7712
8364
|
const cfgId = configId(relPath);
|
|
7713
8365
|
if (!graph.hasNode(cfgId)) {
|
|
7714
8366
|
const cfgNode = {
|
|
7715
8367
|
id: cfgId,
|
|
7716
|
-
type:
|
|
7717
|
-
name:
|
|
8368
|
+
type: NodeType17.ConfigNode,
|
|
8369
|
+
name: path30.basename(primary.sourceFile),
|
|
7718
8370
|
path: relPath,
|
|
7719
|
-
fileType: isConfigFile(
|
|
8371
|
+
fileType: isConfigFile(path30.basename(primary.sourceFile)).fileType || "config"
|
|
7720
8372
|
};
|
|
7721
8373
|
graph.addNode(cfgId, cfgNode);
|
|
7722
8374
|
nodesAdded++;
|
|
@@ -7756,11 +8408,11 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
7756
8408
|
}
|
|
7757
8409
|
|
|
7758
8410
|
// src/extract/configs.ts
|
|
7759
|
-
import { promises as
|
|
7760
|
-
import
|
|
8411
|
+
import { promises as fs19 } from "fs";
|
|
8412
|
+
import path31 from "path";
|
|
7761
8413
|
import {
|
|
7762
8414
|
EdgeType as EdgeType11,
|
|
7763
|
-
NodeType as
|
|
8415
|
+
NodeType as NodeType18,
|
|
7764
8416
|
Provenance as Provenance11,
|
|
7765
8417
|
configId as configId2,
|
|
7766
8418
|
confidenceForExtracted as confidenceForExtracted8
|
|
@@ -7768,9 +8420,9 @@ import {
|
|
|
7768
8420
|
async function walkConfigFiles(dir) {
|
|
7769
8421
|
const out = [];
|
|
7770
8422
|
async function walk9(current) {
|
|
7771
|
-
const entries = await
|
|
8423
|
+
const entries = await fs19.readdir(current, { withFileTypes: true });
|
|
7772
8424
|
for (const entry of entries) {
|
|
7773
|
-
const full =
|
|
8425
|
+
const full = path31.join(current, entry.name);
|
|
7774
8426
|
if (entry.isDirectory()) {
|
|
7775
8427
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
7776
8428
|
if (await isPythonVenvDir(full)) continue;
|
|
@@ -7789,19 +8441,19 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
7789
8441
|
for (const service of services) {
|
|
7790
8442
|
const configFiles = await walkConfigFiles(service.dir);
|
|
7791
8443
|
for (const file of configFiles) {
|
|
7792
|
-
const relPath =
|
|
8444
|
+
const relPath = path31.relative(scanPath, file);
|
|
7793
8445
|
const node = {
|
|
7794
8446
|
id: configId2(relPath),
|
|
7795
|
-
type:
|
|
7796
|
-
name:
|
|
8447
|
+
type: NodeType18.ConfigNode,
|
|
8448
|
+
name: path31.basename(file),
|
|
7797
8449
|
path: relPath,
|
|
7798
|
-
fileType: isConfigFile(
|
|
8450
|
+
fileType: isConfigFile(path31.basename(file)).fileType
|
|
7799
8451
|
};
|
|
7800
8452
|
if (!graph.hasNode(node.id)) {
|
|
7801
8453
|
graph.addNode(node.id, node);
|
|
7802
8454
|
nodesAdded++;
|
|
7803
8455
|
}
|
|
7804
|
-
const relToService = toPosix(
|
|
8456
|
+
const relToService = toPosix(path31.relative(service.dir, file));
|
|
7805
8457
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
7806
8458
|
graph,
|
|
7807
8459
|
service.pkg.name,
|
|
@@ -7817,7 +8469,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
7817
8469
|
type: EdgeType11.CONFIGURED_BY,
|
|
7818
8470
|
provenance: Provenance11.EXTRACTED,
|
|
7819
8471
|
confidence: confidenceForExtracted8("structural"),
|
|
7820
|
-
evidence: { file: relPath.split(
|
|
8472
|
+
evidence: { file: relPath.split(path31.sep).join("/") }
|
|
7821
8473
|
};
|
|
7822
8474
|
if (!graph.hasEdge(edge.id)) {
|
|
7823
8475
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -7829,11 +8481,11 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
7829
8481
|
}
|
|
7830
8482
|
|
|
7831
8483
|
// src/extract/proto.ts
|
|
7832
|
-
import { promises as
|
|
7833
|
-
import
|
|
8484
|
+
import { promises as fs20 } from "fs";
|
|
8485
|
+
import path32 from "path";
|
|
7834
8486
|
import {
|
|
7835
8487
|
EdgeType as EdgeType12,
|
|
7836
|
-
NodeType as
|
|
8488
|
+
NodeType as NodeType19,
|
|
7837
8489
|
Provenance as Provenance12,
|
|
7838
8490
|
confidenceForExtracted as confidenceForExtracted9,
|
|
7839
8491
|
extractedEdgeId as extractedEdgeId9,
|
|
@@ -7877,14 +8529,14 @@ function grpcMethodsFromProto(content, fqPackage) {
|
|
|
7877
8529
|
async function walkProtoFiles(dir) {
|
|
7878
8530
|
const out = [];
|
|
7879
8531
|
async function walk9(current) {
|
|
7880
|
-
const entries = await
|
|
8532
|
+
const entries = await fs20.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
7881
8533
|
for (const entry of entries) {
|
|
7882
|
-
const full =
|
|
8534
|
+
const full = path32.join(current, entry.name);
|
|
7883
8535
|
if (entry.isDirectory()) {
|
|
7884
8536
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
7885
8537
|
if (await isPythonVenvDir(full)) continue;
|
|
7886
8538
|
await walk9(full);
|
|
7887
|
-
} else if (entry.isFile() &&
|
|
8539
|
+
} else if (entry.isFile() && path32.extname(entry.name) === PROTO_EXTENSION) {
|
|
7888
8540
|
out.push(full);
|
|
7889
8541
|
}
|
|
7890
8542
|
}
|
|
@@ -7899,10 +8551,10 @@ async function addGrpcMethods(graph, services) {
|
|
|
7899
8551
|
const protoPaths = await walkProtoFiles(service.dir);
|
|
7900
8552
|
for (const protoPath of protoPaths) {
|
|
7901
8553
|
if (isTestPath(protoPath)) continue;
|
|
7902
|
-
const relFile = toPosix(
|
|
8554
|
+
const relFile = toPosix(path32.relative(service.dir, protoPath));
|
|
7903
8555
|
let content;
|
|
7904
8556
|
try {
|
|
7905
|
-
content = await
|
|
8557
|
+
content = await fs20.readFile(protoPath, "utf8");
|
|
7906
8558
|
} catch (err) {
|
|
7907
8559
|
recordExtractionError("proto extraction", protoPath, err);
|
|
7908
8560
|
continue;
|
|
@@ -7920,7 +8572,7 @@ async function addGrpcMethods(graph, services) {
|
|
|
7920
8572
|
if (!graph.hasNode(mid)) {
|
|
7921
8573
|
const node = {
|
|
7922
8574
|
id: mid,
|
|
7923
|
-
type:
|
|
8575
|
+
type: NodeType19.GrpcMethodNode,
|
|
7924
8576
|
name: `${method.rpcService}/${method.rpcMethod}`,
|
|
7925
8577
|
rpcService: method.rpcService,
|
|
7926
8578
|
rpcMethod: method.rpcMethod,
|
|
@@ -7958,18 +8610,18 @@ async function addGrpcMethods(graph, services) {
|
|
|
7958
8610
|
// src/extract/calls/index.ts
|
|
7959
8611
|
import {
|
|
7960
8612
|
EdgeType as EdgeType15,
|
|
7961
|
-
NodeType as
|
|
8613
|
+
NodeType as NodeType21,
|
|
7962
8614
|
Provenance as Provenance15,
|
|
7963
8615
|
confidenceForExtracted as confidenceForExtracted12,
|
|
7964
8616
|
passesExtractedFloor as passesExtractedFloor3
|
|
7965
8617
|
} from "@neat.is/types";
|
|
7966
8618
|
|
|
7967
8619
|
// src/extract/calls/http.ts
|
|
7968
|
-
import
|
|
8620
|
+
import path33 from "path";
|
|
7969
8621
|
import Parser6 from "tree-sitter";
|
|
7970
8622
|
import JavaScript4 from "tree-sitter-javascript";
|
|
7971
8623
|
import TypeScript2 from "tree-sitter-typescript";
|
|
7972
|
-
import
|
|
8624
|
+
import Python4 from "tree-sitter-python";
|
|
7973
8625
|
import {
|
|
7974
8626
|
EdgeType as EdgeType13,
|
|
7975
8627
|
Provenance as Provenance13,
|
|
@@ -8031,7 +8683,7 @@ var GRAMMAR_BY_EXT2 = {
|
|
|
8031
8683
|
".jsx": JavaScript4,
|
|
8032
8684
|
".mjs": JavaScript4,
|
|
8033
8685
|
".cjs": JavaScript4,
|
|
8034
|
-
".py":
|
|
8686
|
+
".py": Python4
|
|
8035
8687
|
};
|
|
8036
8688
|
function parserForExt(ext, cache) {
|
|
8037
8689
|
const grammar = GRAMMAR_BY_EXT2[ext] ?? JavaScript4;
|
|
@@ -8053,7 +8705,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
8053
8705
|
const seen = /* @__PURE__ */ new Set();
|
|
8054
8706
|
for (const file of files) {
|
|
8055
8707
|
if (isTestPath(file.path)) continue;
|
|
8056
|
-
const parser = parserForExt(
|
|
8708
|
+
const parser = parserForExt(path33.extname(file.path), parserCache);
|
|
8057
8709
|
let sites;
|
|
8058
8710
|
try {
|
|
8059
8711
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -8062,7 +8714,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
8062
8714
|
continue;
|
|
8063
8715
|
}
|
|
8064
8716
|
if (sites.length === 0) continue;
|
|
8065
|
-
const relFile = toPosix(
|
|
8717
|
+
const relFile = toPosix(path33.relative(service.dir, file.path));
|
|
8066
8718
|
for (const site of sites) {
|
|
8067
8719
|
const targetId = hostToNodeId.get(site.host);
|
|
8068
8720
|
if (!targetId || targetId === service.node.id) continue;
|
|
@@ -8115,16 +8767,16 @@ async function addHttpCallEdges(graph, services) {
|
|
|
8115
8767
|
}
|
|
8116
8768
|
|
|
8117
8769
|
// src/extract/calls/route-match.ts
|
|
8118
|
-
import
|
|
8770
|
+
import path34 from "path";
|
|
8119
8771
|
import Parser7 from "tree-sitter";
|
|
8120
8772
|
import JavaScript5 from "tree-sitter-javascript";
|
|
8121
8773
|
import {
|
|
8122
8774
|
EdgeType as EdgeType14,
|
|
8123
|
-
NodeType as
|
|
8775
|
+
NodeType as NodeType20,
|
|
8124
8776
|
Provenance as Provenance14,
|
|
8125
8777
|
confidenceForExtracted as confidenceForExtracted11,
|
|
8126
8778
|
passesExtractedFloor as passesExtractedFloor2,
|
|
8127
|
-
serviceId as
|
|
8779
|
+
serviceId as serviceId9
|
|
8128
8780
|
} from "@neat.is/types";
|
|
8129
8781
|
var PARSE_CHUNK5 = 16384;
|
|
8130
8782
|
function parseSource5(parser, source) {
|
|
@@ -8290,9 +8942,9 @@ function buildRouteIndex(graph) {
|
|
|
8290
8942
|
const index = /* @__PURE__ */ new Map();
|
|
8291
8943
|
graph.forEachNode((_id, attrs) => {
|
|
8292
8944
|
const node = attrs;
|
|
8293
|
-
if (node.type !==
|
|
8945
|
+
if (node.type !== NodeType20.RouteNode) return;
|
|
8294
8946
|
const route = attrs;
|
|
8295
|
-
const owner =
|
|
8947
|
+
const owner = serviceId9(route.service);
|
|
8296
8948
|
const entry = {
|
|
8297
8949
|
method: route.method.toUpperCase(),
|
|
8298
8950
|
normalizedPath: normalizePathTemplate(route.pathTemplate),
|
|
@@ -8321,7 +8973,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
8321
8973
|
const seen = /* @__PURE__ */ new Set();
|
|
8322
8974
|
for (const file of files) {
|
|
8323
8975
|
if (isTestPath(file.path)) continue;
|
|
8324
|
-
if (!JS_CLIENT_EXTENSIONS.has(
|
|
8976
|
+
if (!JS_CLIENT_EXTENSIONS.has(path34.extname(file.path))) continue;
|
|
8325
8977
|
let sites;
|
|
8326
8978
|
try {
|
|
8327
8979
|
sites = clientCallSitesFromSource(file.content, jsParser, knownHosts);
|
|
@@ -8330,7 +8982,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
8330
8982
|
continue;
|
|
8331
8983
|
}
|
|
8332
8984
|
if (sites.length === 0) continue;
|
|
8333
|
-
const relFile = toPosix(
|
|
8985
|
+
const relFile = toPosix(path34.relative(service.dir, file.path));
|
|
8334
8986
|
for (const site of sites) {
|
|
8335
8987
|
const serverServiceId = hostToNodeId.get(site.host);
|
|
8336
8988
|
if (!serverServiceId || serverServiceId === service.node.id) continue;
|
|
@@ -8390,7 +9042,7 @@ async function addRouteCallEdges(graph, services) {
|
|
|
8390
9042
|
}
|
|
8391
9043
|
|
|
8392
9044
|
// src/extract/calls/kafka.ts
|
|
8393
|
-
import
|
|
9045
|
+
import path35 from "path";
|
|
8394
9046
|
import { infraId as infraId2 } from "@neat.is/types";
|
|
8395
9047
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
8396
9048
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
@@ -8421,7 +9073,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
8421
9073
|
// tier (ADR-066).
|
|
8422
9074
|
confidenceKind: "verified-call-site",
|
|
8423
9075
|
evidence: {
|
|
8424
|
-
file:
|
|
9076
|
+
file: path35.relative(serviceDir, file.path),
|
|
8425
9077
|
line,
|
|
8426
9078
|
snippet: snippet(file.content, line)
|
|
8427
9079
|
}
|
|
@@ -8433,7 +9085,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
8433
9085
|
}
|
|
8434
9086
|
|
|
8435
9087
|
// src/extract/calls/redis.ts
|
|
8436
|
-
import
|
|
9088
|
+
import path36 from "path";
|
|
8437
9089
|
import { infraId as infraId3 } from "@neat.is/types";
|
|
8438
9090
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
8439
9091
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
@@ -8456,7 +9108,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
8456
9108
|
// support tier (ADR-066).
|
|
8457
9109
|
confidenceKind: "url-with-structural-support",
|
|
8458
9110
|
evidence: {
|
|
8459
|
-
file:
|
|
9111
|
+
file: path36.relative(serviceDir, file.path),
|
|
8460
9112
|
line,
|
|
8461
9113
|
snippet: snippet(file.content, line)
|
|
8462
9114
|
}
|
|
@@ -8466,7 +9118,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
8466
9118
|
}
|
|
8467
9119
|
|
|
8468
9120
|
// src/extract/calls/aws.ts
|
|
8469
|
-
import
|
|
9121
|
+
import path37 from "path";
|
|
8470
9122
|
import { infraId as infraId4 } from "@neat.is/types";
|
|
8471
9123
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
8472
9124
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
@@ -8500,7 +9152,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
8500
9152
|
// (ADR-066).
|
|
8501
9153
|
confidenceKind: "verified-call-site",
|
|
8502
9154
|
evidence: {
|
|
8503
|
-
file:
|
|
9155
|
+
file: path37.relative(serviceDir, file.path),
|
|
8504
9156
|
line,
|
|
8505
9157
|
snippet: snippet(file.content, line)
|
|
8506
9158
|
}
|
|
@@ -8524,7 +9176,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
8524
9176
|
}
|
|
8525
9177
|
|
|
8526
9178
|
// src/extract/calls/grpc.ts
|
|
8527
|
-
import
|
|
9179
|
+
import path38 from "path";
|
|
8528
9180
|
import { infraId as infraId5 } from "@neat.is/types";
|
|
8529
9181
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
8530
9182
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
@@ -8583,7 +9235,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
8583
9235
|
// tier (ADR-066).
|
|
8584
9236
|
confidenceKind: "verified-call-site",
|
|
8585
9237
|
evidence: {
|
|
8586
|
-
file:
|
|
9238
|
+
file: path38.relative(serviceDir, file.path),
|
|
8587
9239
|
line,
|
|
8588
9240
|
snippet: snippet(file.content, line)
|
|
8589
9241
|
}
|
|
@@ -8593,7 +9245,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
8593
9245
|
}
|
|
8594
9246
|
|
|
8595
9247
|
// src/extract/calls/supabase.ts
|
|
8596
|
-
import
|
|
9248
|
+
import path39 from "path";
|
|
8597
9249
|
import { infraId as infraId6 } from "@neat.is/types";
|
|
8598
9250
|
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
8599
9251
|
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
@@ -8652,7 +9304,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
8652
9304
|
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
8653
9305
|
confidenceKind: "verified-call-site",
|
|
8654
9306
|
evidence: {
|
|
8655
|
-
file:
|
|
9307
|
+
file: path39.relative(serviceDir, file.path),
|
|
8656
9308
|
line,
|
|
8657
9309
|
snippet: snippet(file.content, line)
|
|
8658
9310
|
}
|
|
@@ -8679,7 +9331,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
8679
9331
|
edgeType: "CALLS",
|
|
8680
9332
|
confidenceKind: "verified-call-site",
|
|
8681
9333
|
evidence: {
|
|
8682
|
-
file:
|
|
9334
|
+
file: path39.relative(serviceDir, file.path),
|
|
8683
9335
|
line,
|
|
8684
9336
|
snippet: snippet(file.content, line)
|
|
8685
9337
|
}
|
|
@@ -8690,7 +9342,7 @@ function supabaseEndpointsFromFile(file, serviceDir) {
|
|
|
8690
9342
|
}
|
|
8691
9343
|
|
|
8692
9344
|
// src/extract/calls/firestore.ts
|
|
8693
|
-
import
|
|
9345
|
+
import path40 from "path";
|
|
8694
9346
|
import Parser8 from "tree-sitter";
|
|
8695
9347
|
import JavaScript6 from "tree-sitter-javascript";
|
|
8696
9348
|
import { infraId as infraId7 } from "@neat.is/types";
|
|
@@ -8861,7 +9513,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
8861
9513
|
const hasAdmin = FIRESTORE_ADMIN_IMPORT_RE.test(file.content);
|
|
8862
9514
|
if (!hasClient && !hasAdmin) return [];
|
|
8863
9515
|
const fileSdk = hasClient && !hasAdmin ? "client" : hasAdmin && !hasClient ? "admin" : null;
|
|
8864
|
-
const tree = parseSource3(parserForExt2(
|
|
9516
|
+
const tree = parseSource3(parserForExt2(path40.extname(file.path)), file.content);
|
|
8865
9517
|
const clientVars = firestoreClientVars(tree.rootNode);
|
|
8866
9518
|
const collLine = /* @__PURE__ */ new Map();
|
|
8867
9519
|
const writes = /* @__PURE__ */ new Map();
|
|
@@ -8965,7 +9617,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
8965
9617
|
...columnSet.size > 0 ? { columns: [...columnSet] } : {},
|
|
8966
9618
|
...sdkWrites ? { sdkWrites } : {},
|
|
8967
9619
|
evidence: {
|
|
8968
|
-
file:
|
|
9620
|
+
file: path40.relative(serviceDir, file.path),
|
|
8969
9621
|
line,
|
|
8970
9622
|
snippet: snippet(file.content, line)
|
|
8971
9623
|
}
|
|
@@ -8975,7 +9627,7 @@ function firestoreEndpointsFromFile(file, serviceDir) {
|
|
|
8975
9627
|
}
|
|
8976
9628
|
|
|
8977
9629
|
// src/extract/calls/mongoose.ts
|
|
8978
|
-
import
|
|
9630
|
+
import path41 from "path";
|
|
8979
9631
|
import { infraId as infraId8 } from "@neat.is/types";
|
|
8980
9632
|
var MONGOOSE_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongoose['"`]/;
|
|
8981
9633
|
var MONGODB_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])mongodb['"`]/;
|
|
@@ -9126,7 +9778,7 @@ function endpoint(r, file, serviceDir, matchText) {
|
|
|
9126
9778
|
kind: r.kind,
|
|
9127
9779
|
edgeType: "CALLS",
|
|
9128
9780
|
confidenceKind: "verified-call-site",
|
|
9129
|
-
evidence: { file:
|
|
9781
|
+
evidence: { file: path41.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
9130
9782
|
};
|
|
9131
9783
|
}
|
|
9132
9784
|
function mongooseEndpointsFromFile(file, serviceDir) {
|
|
@@ -9216,7 +9868,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
9216
9868
|
const registry = /* @__PURE__ */ new Map();
|
|
9217
9869
|
for (const f of mongooseFiles) {
|
|
9218
9870
|
const fx = fileExportsOf(f.content, pluralizeOn);
|
|
9219
|
-
if (fx) registry.set(toPosix(
|
|
9871
|
+
if (fx) registry.set(toPosix(path41.relative(serviceDir, f.path)), fx);
|
|
9220
9872
|
}
|
|
9221
9873
|
if (registry.size === 0) return [];
|
|
9222
9874
|
const out = [];
|
|
@@ -9227,7 +9879,7 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
9227
9879
|
const directColl = /* @__PURE__ */ new Map();
|
|
9228
9880
|
const nsExports = /* @__PURE__ */ new Map();
|
|
9229
9881
|
for (const b of bindings) {
|
|
9230
|
-
const resolvedRel = await resolveJsImport(b.specifier,
|
|
9882
|
+
const resolvedRel = await resolveJsImport(b.specifier, path41.dirname(f.path), serviceDir, null);
|
|
9231
9883
|
if (!resolvedRel) continue;
|
|
9232
9884
|
const fx = registry.get(resolvedRel);
|
|
9233
9885
|
if (!fx) continue;
|
|
@@ -9270,15 +9922,15 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
9270
9922
|
}
|
|
9271
9923
|
|
|
9272
9924
|
// src/extract/calls/sqlalchemy.ts
|
|
9273
|
-
import
|
|
9925
|
+
import path42 from "path";
|
|
9274
9926
|
import Parser9 from "tree-sitter";
|
|
9275
|
-
import
|
|
9927
|
+
import Python5 from "tree-sitter-python";
|
|
9276
9928
|
import { infraId as infraId9 } from "@neat.is/types";
|
|
9277
9929
|
var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
|
|
9278
9930
|
var PARSE_CHUNK6 = 16384;
|
|
9279
9931
|
function makePyParser3() {
|
|
9280
9932
|
const p = new Parser9();
|
|
9281
|
-
p.setLanguage(
|
|
9933
|
+
p.setLanguage(Python5);
|
|
9282
9934
|
return p;
|
|
9283
9935
|
}
|
|
9284
9936
|
function parseSource6(parser, source) {
|
|
@@ -9409,7 +10061,7 @@ function sqlalchemyForeignKeys(file, serviceDir) {
|
|
|
9409
10061
|
childTable,
|
|
9410
10062
|
parentTable,
|
|
9411
10063
|
evidence: {
|
|
9412
|
-
file:
|
|
10064
|
+
file: path42.relative(serviceDir, file.path),
|
|
9413
10065
|
line,
|
|
9414
10066
|
snippet: snippet(file.content, line)
|
|
9415
10067
|
}
|
|
@@ -9434,7 +10086,7 @@ function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
|
9434
10086
|
confidenceKind: "verified-call-site",
|
|
9435
10087
|
...columns && columns.length > 0 ? { columns } : {},
|
|
9436
10088
|
evidence: {
|
|
9437
|
-
file:
|
|
10089
|
+
file: path42.relative(serviceDir, file.path),
|
|
9438
10090
|
line,
|
|
9439
10091
|
snippet: snippet(file.content, line)
|
|
9440
10092
|
}
|
|
@@ -9541,7 +10193,7 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
|
9541
10193
|
edgeType: "CALLS",
|
|
9542
10194
|
confidenceKind: "verified-call-site",
|
|
9543
10195
|
evidence: {
|
|
9544
|
-
file:
|
|
10196
|
+
file: path42.relative(serviceDir, file.path),
|
|
9545
10197
|
line,
|
|
9546
10198
|
snippet: snippet(file.content, line)
|
|
9547
10199
|
}
|
|
@@ -9552,15 +10204,15 @@ function pythonOrmCrossFileEndpoints(files, serviceDir) {
|
|
|
9552
10204
|
}
|
|
9553
10205
|
|
|
9554
10206
|
// src/extract/calls/django-orm.ts
|
|
9555
|
-
import
|
|
10207
|
+
import path43 from "path";
|
|
9556
10208
|
import Parser10 from "tree-sitter";
|
|
9557
|
-
import
|
|
10209
|
+
import Python6 from "tree-sitter-python";
|
|
9558
10210
|
import { infraId as infraId10 } from "@neat.is/types";
|
|
9559
10211
|
var DJANGO_IMPORT_RE = /(?:from|import)\s+django\b/;
|
|
9560
10212
|
var PARSE_CHUNK7 = 16384;
|
|
9561
10213
|
function makePyParser4() {
|
|
9562
10214
|
const p = new Parser10();
|
|
9563
|
-
p.setLanguage(
|
|
10215
|
+
p.setLanguage(Python6);
|
|
9564
10216
|
return p;
|
|
9565
10217
|
}
|
|
9566
10218
|
function parseSource7(parser, source) {
|
|
@@ -9622,7 +10274,7 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
9622
10274
|
const tree = parseSource7(makePyParser4(), file.content);
|
|
9623
10275
|
const out = [];
|
|
9624
10276
|
const seen = /* @__PURE__ */ new Set();
|
|
9625
|
-
const defaultAppLabel =
|
|
10277
|
+
const defaultAppLabel = path43.basename(path43.dirname(file.path));
|
|
9626
10278
|
walk4(tree.rootNode, (node) => {
|
|
9627
10279
|
if (node.type !== "class_definition") return;
|
|
9628
10280
|
if (!extendsDjangoModel(node)) return;
|
|
@@ -9640,14 +10292,14 @@ function djangoOrmEndpointsFromFile(file, serviceDir) {
|
|
|
9640
10292
|
kind: "sql-table",
|
|
9641
10293
|
edgeType: "CALLS",
|
|
9642
10294
|
confidenceKind: "verified-call-site",
|
|
9643
|
-
evidence: { file:
|
|
10295
|
+
evidence: { file: path43.relative(serviceDir, file.path), line, snippet: snippet(file.content, line) }
|
|
9644
10296
|
});
|
|
9645
10297
|
});
|
|
9646
10298
|
return out;
|
|
9647
10299
|
}
|
|
9648
10300
|
|
|
9649
10301
|
// src/extract/calls/drizzle.ts
|
|
9650
|
-
import
|
|
10302
|
+
import path44 from "path";
|
|
9651
10303
|
import Parser11 from "tree-sitter";
|
|
9652
10304
|
import JavaScript7 from "tree-sitter-javascript";
|
|
9653
10305
|
import { infraId as infraId11 } from "@neat.is/types";
|
|
@@ -9725,7 +10377,7 @@ function columnsFromObject(obj) {
|
|
|
9725
10377
|
}
|
|
9726
10378
|
function drizzleEndpointsFromFile(file, serviceDir) {
|
|
9727
10379
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
9728
|
-
const tree = parseSource3(parserForExt3(
|
|
10380
|
+
const tree = parseSource3(parserForExt3(path44.extname(file.path)), file.content);
|
|
9729
10381
|
const out = [];
|
|
9730
10382
|
const seen = /* @__PURE__ */ new Set();
|
|
9731
10383
|
const walk9 = (node) => {
|
|
@@ -9748,7 +10400,7 @@ function drizzleEndpointsFromFile(file, serviceDir) {
|
|
|
9748
10400
|
confidenceKind: "structural",
|
|
9749
10401
|
columns,
|
|
9750
10402
|
evidence: {
|
|
9751
|
-
file:
|
|
10403
|
+
file: path44.relative(serviceDir, file.path),
|
|
9752
10404
|
line,
|
|
9753
10405
|
snippet: snippet(file.content, line)
|
|
9754
10406
|
}
|
|
@@ -9814,7 +10466,7 @@ function referencesTargetVar(call) {
|
|
|
9814
10466
|
}
|
|
9815
10467
|
function drizzleForeignKeys(file, serviceDir) {
|
|
9816
10468
|
if (!DRIZZLE_IMPORT_RE.test(file.content)) return [];
|
|
9817
|
-
const tree = parseSource3(parserForExt3(
|
|
10469
|
+
const tree = parseSource3(parserForExt3(path44.extname(file.path)), file.content);
|
|
9818
10470
|
const { tables, varToTable } = collectDrizzleTables(tree.rootNode);
|
|
9819
10471
|
const out = [];
|
|
9820
10472
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -9833,7 +10485,7 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
9833
10485
|
childTable: table.tableName,
|
|
9834
10486
|
parentTable,
|
|
9835
10487
|
evidence: {
|
|
9836
|
-
file:
|
|
10488
|
+
file: path44.relative(serviceDir, file.path),
|
|
9837
10489
|
line,
|
|
9838
10490
|
snippet: snippet(file.content, line)
|
|
9839
10491
|
}
|
|
@@ -9849,7 +10501,7 @@ function drizzleForeignKeys(file, serviceDir) {
|
|
|
9849
10501
|
}
|
|
9850
10502
|
|
|
9851
10503
|
// src/extract/calls/prisma.ts
|
|
9852
|
-
import
|
|
10504
|
+
import path45 from "path";
|
|
9853
10505
|
import { infraId as infraId12 } from "@neat.is/types";
|
|
9854
10506
|
var SCALAR_TYPES = /* @__PURE__ */ new Set([
|
|
9855
10507
|
"Int",
|
|
@@ -9905,7 +10557,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
|
|
|
9905
10557
|
confidenceKind: "structural",
|
|
9906
10558
|
columns: b.columns,
|
|
9907
10559
|
evidence: {
|
|
9908
|
-
file:
|
|
10560
|
+
file: path45.relative(serviceDir, file.path),
|
|
9909
10561
|
line: b.startLine,
|
|
9910
10562
|
snippet: snippet(content, b.startLine)
|
|
9911
10563
|
}
|
|
@@ -9966,7 +10618,7 @@ function prismaColumnsFromSchema(file, serviceDir) {
|
|
|
9966
10618
|
}
|
|
9967
10619
|
async function prismaColumnEndpoints(serviceDir) {
|
|
9968
10620
|
const schemaPath = await findFirst(serviceDir, [
|
|
9969
|
-
|
|
10621
|
+
path45.join("prisma", "schema.prisma"),
|
|
9970
10622
|
"schema.prisma"
|
|
9971
10623
|
]);
|
|
9972
10624
|
if (!schemaPath) return [];
|
|
@@ -10041,7 +10693,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
|
10041
10693
|
childTable: current.table,
|
|
10042
10694
|
parentTable,
|
|
10043
10695
|
evidence: {
|
|
10044
|
-
file:
|
|
10696
|
+
file: path45.relative(serviceDir, file.path),
|
|
10045
10697
|
line: lineNo,
|
|
10046
10698
|
snippet: snippet(content, lineNo)
|
|
10047
10699
|
}
|
|
@@ -10056,7 +10708,7 @@ function prismaForeignKeysFromSchema(file, serviceDir) {
|
|
|
10056
10708
|
}
|
|
10057
10709
|
async function prismaForeignKeys(serviceDir) {
|
|
10058
10710
|
const schemaPath = await findFirst(serviceDir, [
|
|
10059
|
-
|
|
10711
|
+
path45.join("prisma", "schema.prisma"),
|
|
10060
10712
|
"schema.prisma"
|
|
10061
10713
|
]);
|
|
10062
10714
|
if (!schemaPath) return [];
|
|
@@ -10066,15 +10718,15 @@ async function prismaForeignKeys(serviceDir) {
|
|
|
10066
10718
|
}
|
|
10067
10719
|
|
|
10068
10720
|
// src/extract/calls/activerecord.ts
|
|
10069
|
-
import
|
|
10721
|
+
import path46 from "path";
|
|
10070
10722
|
import Parser12 from "tree-sitter";
|
|
10071
|
-
import
|
|
10723
|
+
import Ruby3 from "tree-sitter-ruby";
|
|
10072
10724
|
import { infraId as infraId13 } from "@neat.is/types";
|
|
10073
10725
|
var AR_SCHEMA_RE = /ActiveRecord::Schema/;
|
|
10074
10726
|
var PARSE_CHUNK8 = 16384;
|
|
10075
10727
|
function makeRubyParser2() {
|
|
10076
10728
|
const p = new Parser12();
|
|
10077
|
-
p.setLanguage(
|
|
10729
|
+
p.setLanguage(Ruby3);
|
|
10078
10730
|
return p;
|
|
10079
10731
|
}
|
|
10080
10732
|
function parseSource8(parser, source) {
|
|
@@ -10274,7 +10926,7 @@ function railsSchemaEndpointsFromFile(file, serviceDir) {
|
|
|
10274
10926
|
confidenceKind: "structural",
|
|
10275
10927
|
...table.columns.length > 0 ? { columns: table.columns } : {},
|
|
10276
10928
|
evidence: {
|
|
10277
|
-
file:
|
|
10929
|
+
file: path46.relative(serviceDir, file.path),
|
|
10278
10930
|
line: table.line,
|
|
10279
10931
|
snippet: snippet(file.content, table.line)
|
|
10280
10932
|
}
|
|
@@ -10296,7 +10948,7 @@ function railsSchemaForeignKeys(file, serviceDir) {
|
|
|
10296
10948
|
childTable,
|
|
10297
10949
|
parentTable,
|
|
10298
10950
|
evidence: {
|
|
10299
|
-
file:
|
|
10951
|
+
file: path46.relative(serviceDir, file.path),
|
|
10300
10952
|
line,
|
|
10301
10953
|
snippet: snippet(file.content, line)
|
|
10302
10954
|
}
|
|
@@ -10379,7 +11031,7 @@ function railsModelEndpointsFromFile(file, serviceDir) {
|
|
|
10379
11031
|
edgeType: "CALLS",
|
|
10380
11032
|
confidenceKind: "verified-call-site",
|
|
10381
11033
|
evidence: {
|
|
10382
|
-
file:
|
|
11034
|
+
file: path46.relative(serviceDir, file.path),
|
|
10383
11035
|
line,
|
|
10384
11036
|
snippet: snippet(file.content, line)
|
|
10385
11037
|
}
|
|
@@ -10416,7 +11068,7 @@ function railsModelForeignKeys(file, serviceDir) {
|
|
|
10416
11068
|
childTable,
|
|
10417
11069
|
parentTable,
|
|
10418
11070
|
evidence: {
|
|
10419
|
-
file:
|
|
11071
|
+
file: path46.relative(serviceDir, file.path),
|
|
10420
11072
|
line,
|
|
10421
11073
|
snippet: snippet(file.content, line)
|
|
10422
11074
|
}
|
|
@@ -10427,15 +11079,15 @@ function railsModelForeignKeys(file, serviceDir) {
|
|
|
10427
11079
|
}
|
|
10428
11080
|
|
|
10429
11081
|
// src/extract/calls/eloquent.ts
|
|
10430
|
-
import
|
|
11082
|
+
import path47 from "path";
|
|
10431
11083
|
import Parser13 from "tree-sitter";
|
|
10432
|
-
import
|
|
11084
|
+
import Php3 from "tree-sitter-php";
|
|
10433
11085
|
import { infraId as infraId14 } from "@neat.is/types";
|
|
10434
11086
|
var LARAVEL_SCHEMA_RE = /\bSchema::(create|createIfNotExists|table)\b/;
|
|
10435
11087
|
var PARSE_CHUNK9 = 16384;
|
|
10436
11088
|
function makePhpParser2() {
|
|
10437
11089
|
const p = new Parser13();
|
|
10438
|
-
p.setLanguage(
|
|
11090
|
+
p.setLanguage(Php3.php_only);
|
|
10439
11091
|
return p;
|
|
10440
11092
|
}
|
|
10441
11093
|
function parseSource9(parser, source) {
|
|
@@ -10684,7 +11336,7 @@ function laravelMigrationEndpointsFromFile(file, serviceDir) {
|
|
|
10684
11336
|
confidenceKind: "structural",
|
|
10685
11337
|
...columns.length > 0 ? { columns } : {},
|
|
10686
11338
|
evidence: {
|
|
10687
|
-
file:
|
|
11339
|
+
file: path47.relative(serviceDir, file.path),
|
|
10688
11340
|
line: bp.line,
|
|
10689
11341
|
snippet: snippet(file.content, bp.line)
|
|
10690
11342
|
}
|
|
@@ -10706,7 +11358,7 @@ function laravelMigrationForeignKeys(file, serviceDir) {
|
|
|
10706
11358
|
childTable,
|
|
10707
11359
|
parentTable,
|
|
10708
11360
|
evidence: {
|
|
10709
|
-
file:
|
|
11361
|
+
file: path47.relative(serviceDir, file.path),
|
|
10710
11362
|
line,
|
|
10711
11363
|
snippet: snippet(file.content, line)
|
|
10712
11364
|
}
|
|
@@ -10826,7 +11478,7 @@ function laravelModelEndpointsFromFile(file, serviceDir) {
|
|
|
10826
11478
|
edgeType: "CALLS",
|
|
10827
11479
|
confidenceKind: "verified-call-site",
|
|
10828
11480
|
evidence: {
|
|
10829
|
-
file:
|
|
11481
|
+
file: path47.relative(serviceDir, file.path),
|
|
10830
11482
|
line,
|
|
10831
11483
|
snippet: snippet(file.content, line)
|
|
10832
11484
|
}
|
|
@@ -10862,7 +11514,7 @@ function laravelModelForeignKeys(file, serviceDir) {
|
|
|
10862
11514
|
childTable,
|
|
10863
11515
|
parentTable,
|
|
10864
11516
|
evidence: {
|
|
10865
|
-
file:
|
|
11517
|
+
file: path47.relative(serviceDir, file.path),
|
|
10866
11518
|
line,
|
|
10867
11519
|
snippet: snippet(file.content, line)
|
|
10868
11520
|
}
|
|
@@ -10873,9 +11525,9 @@ function laravelModelForeignKeys(file, serviceDir) {
|
|
|
10873
11525
|
}
|
|
10874
11526
|
|
|
10875
11527
|
// src/extract/calls/go.ts
|
|
10876
|
-
import
|
|
11528
|
+
import path48 from "path";
|
|
10877
11529
|
import Parser14 from "tree-sitter";
|
|
10878
|
-
import
|
|
11530
|
+
import Go4 from "tree-sitter-go";
|
|
10879
11531
|
import { infraId as infraId15 } from "@neat.is/types";
|
|
10880
11532
|
var PARSE_CHUNK10 = 16384;
|
|
10881
11533
|
var DATABASE_SQL_METHODS = /* @__PURE__ */ new Set([
|
|
@@ -10904,7 +11556,7 @@ var DATABASE_SQL_IMPORT = "database/sql";
|
|
|
10904
11556
|
var SQLX_IMPORT = "github.com/jmoiron/sqlx";
|
|
10905
11557
|
function makeGoParser3() {
|
|
10906
11558
|
const p = new Parser14();
|
|
10907
|
-
p.setLanguage(
|
|
11559
|
+
p.setLanguage(Go4);
|
|
10908
11560
|
return p;
|
|
10909
11561
|
}
|
|
10910
11562
|
function parseSource10(parser, source) {
|
|
@@ -10947,7 +11599,7 @@ function firstStringLiteralArg(argsNode) {
|
|
|
10947
11599
|
return null;
|
|
10948
11600
|
}
|
|
10949
11601
|
function goSqlEndpointsFromFile(file, serviceDir) {
|
|
10950
|
-
if (
|
|
11602
|
+
if (path48.extname(file.path) !== ".go") return [];
|
|
10951
11603
|
if (!file.content.includes(DATABASE_SQL_IMPORT) && !file.content.includes(SQLX_IMPORT)) return [];
|
|
10952
11604
|
const tree = parseSource10(makeGoParser3(), file.content);
|
|
10953
11605
|
const importsDatabaseSql = goImportsAny(tree.rootNode, /* @__PURE__ */ new Set([DATABASE_SQL_IMPORT]));
|
|
@@ -10976,7 +11628,7 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
10976
11628
|
confidenceKind: "verified-call-site",
|
|
10977
11629
|
...columns.length > 0 ? { columns } : {},
|
|
10978
11630
|
evidence: {
|
|
10979
|
-
file: toPosix(
|
|
11631
|
+
file: toPosix(path48.relative(serviceDir, file.path)),
|
|
10980
11632
|
line,
|
|
10981
11633
|
snippet: snippet(file.content, line)
|
|
10982
11634
|
}
|
|
@@ -10986,15 +11638,15 @@ function goSqlEndpointsFromFile(file, serviceDir) {
|
|
|
10986
11638
|
}
|
|
10987
11639
|
|
|
10988
11640
|
// src/extract/calls/gorm.ts
|
|
10989
|
-
import
|
|
11641
|
+
import path49 from "path";
|
|
10990
11642
|
import Parser15 from "tree-sitter";
|
|
10991
|
-
import
|
|
11643
|
+
import Go5 from "tree-sitter-go";
|
|
10992
11644
|
import { infraId as infraId16 } from "@neat.is/types";
|
|
10993
11645
|
var GORM_IMPORT_RE = /gorm\.io\/gorm/;
|
|
10994
11646
|
var PARSE_CHUNK11 = 16384;
|
|
10995
11647
|
function makeGoParser4() {
|
|
10996
11648
|
const p = new Parser15();
|
|
10997
|
-
p.setLanguage(
|
|
11649
|
+
p.setLanguage(Go5);
|
|
10998
11650
|
return p;
|
|
10999
11651
|
}
|
|
11000
11652
|
function parseSource11(parser, source) {
|
|
@@ -11419,7 +12071,7 @@ function collectColumns(struct, structs, seen, prefix, out, emitted) {
|
|
|
11419
12071
|
seen.delete(struct.name);
|
|
11420
12072
|
}
|
|
11421
12073
|
function gormEndpointsFromFile(file, serviceDir) {
|
|
11422
|
-
if (
|
|
12074
|
+
if (path49.extname(file.path) !== ".go") return [];
|
|
11423
12075
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
11424
12076
|
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11425
12077
|
const { structs, models, tableFor } = analyze(tree);
|
|
@@ -11441,7 +12093,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
11441
12093
|
confidenceKind: "structural",
|
|
11442
12094
|
...columns.length > 0 ? { columns } : {},
|
|
11443
12095
|
evidence: {
|
|
11444
|
-
file: toPosix(
|
|
12096
|
+
file: toPosix(path49.relative(serviceDir, file.path)),
|
|
11445
12097
|
line: struct.line,
|
|
11446
12098
|
snippet: snippet(file.content, struct.line)
|
|
11447
12099
|
}
|
|
@@ -11450,7 +12102,7 @@ function gormEndpointsFromFile(file, serviceDir) {
|
|
|
11450
12102
|
return out;
|
|
11451
12103
|
}
|
|
11452
12104
|
function gormForeignKeys(file, serviceDir) {
|
|
11453
|
-
if (
|
|
12105
|
+
if (path49.extname(file.path) !== ".go") return [];
|
|
11454
12106
|
if (!GORM_IMPORT_RE.test(file.content)) return [];
|
|
11455
12107
|
const tree = parseSource11(makeGoParser4(), file.content);
|
|
11456
12108
|
const { structs, models, tableFor } = analyze(tree);
|
|
@@ -11465,7 +12117,7 @@ function gormForeignKeys(file, serviceDir) {
|
|
|
11465
12117
|
childTable,
|
|
11466
12118
|
parentTable,
|
|
11467
12119
|
evidence: {
|
|
11468
|
-
file: toPosix(
|
|
12120
|
+
file: toPosix(path49.relative(serviceDir, file.path)),
|
|
11469
12121
|
line,
|
|
11470
12122
|
snippet: snippet(file.content, line)
|
|
11471
12123
|
}
|
|
@@ -11568,7 +12220,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
11568
12220
|
if (!graph.hasNode(ep.infraId)) {
|
|
11569
12221
|
const node = {
|
|
11570
12222
|
id: ep.infraId,
|
|
11571
|
-
type:
|
|
12223
|
+
type: NodeType21.InfraNode,
|
|
11572
12224
|
name: ep.name,
|
|
11573
12225
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
11574
12226
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -11581,7 +12233,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
11581
12233
|
}
|
|
11582
12234
|
if (ep.columns && ep.columns.length > 0) {
|
|
11583
12235
|
const node = graph.getNodeAttributes(ep.infraId);
|
|
11584
|
-
if (node.type ===
|
|
12236
|
+
if (node.type === NodeType21.InfraNode) {
|
|
11585
12237
|
graph.replaceNodeAttributes(ep.infraId, {
|
|
11586
12238
|
...node,
|
|
11587
12239
|
columns: foldColumns(
|
|
@@ -11595,7 +12247,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
11595
12247
|
}
|
|
11596
12248
|
if (ep.sdkWrites && Object.keys(ep.sdkWrites).length > 0) {
|
|
11597
12249
|
const node = graph.getNodeAttributes(ep.infraId);
|
|
11598
|
-
if (node.type ===
|
|
12250
|
+
if (node.type === NodeType21.InfraNode) {
|
|
11599
12251
|
graph.replaceNodeAttributes(ep.infraId, {
|
|
11600
12252
|
...node,
|
|
11601
12253
|
columns: foldSdkWrites(node.columns, ep.sdkWrites)
|
|
@@ -11657,7 +12309,7 @@ async function addCallEdges(graph, services) {
|
|
|
11657
12309
|
// src/extract/table-edges.ts
|
|
11658
12310
|
import {
|
|
11659
12311
|
EdgeType as EdgeType16,
|
|
11660
|
-
NodeType as
|
|
12312
|
+
NodeType as NodeType22,
|
|
11661
12313
|
Provenance as Provenance16,
|
|
11662
12314
|
confidenceForExtracted as confidenceForExtracted13,
|
|
11663
12315
|
extractedEdgeId as extractedEdgeId10,
|
|
@@ -11716,7 +12368,7 @@ function ensureTableNode(graph, id, name) {
|
|
|
11716
12368
|
if (graph.hasNode(id)) return 0;
|
|
11717
12369
|
const node = {
|
|
11718
12370
|
id,
|
|
11719
|
-
type:
|
|
12371
|
+
type: NodeType22.InfraNode,
|
|
11720
12372
|
name,
|
|
11721
12373
|
provider: "self",
|
|
11722
12374
|
kind: "sql-table"
|
|
@@ -11726,15 +12378,15 @@ function ensureTableNode(graph, id, name) {
|
|
|
11726
12378
|
}
|
|
11727
12379
|
|
|
11728
12380
|
// src/extract/infra/docker-compose.ts
|
|
11729
|
-
import
|
|
12381
|
+
import path50 from "path";
|
|
11730
12382
|
import { EdgeType as EdgeType17, Provenance as Provenance18, confidenceForExtracted as confidenceForExtracted15 } from "@neat.is/types";
|
|
11731
12383
|
|
|
11732
12384
|
// src/extract/infra/shared.ts
|
|
11733
|
-
import { NodeType as
|
|
12385
|
+
import { NodeType as NodeType23, Provenance as Provenance17, confidenceForExtracted as confidenceForExtracted14, infraId as infraId18 } from "@neat.is/types";
|
|
11734
12386
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
11735
12387
|
return {
|
|
11736
12388
|
id: infraId18(kind, name),
|
|
11737
|
-
type:
|
|
12389
|
+
type: NodeType23.InfraNode,
|
|
11738
12390
|
name,
|
|
11739
12391
|
provider,
|
|
11740
12392
|
kind,
|
|
@@ -11796,7 +12448,7 @@ function dependsOnList(value) {
|
|
|
11796
12448
|
}
|
|
11797
12449
|
function serviceNameToServiceNode(name, services) {
|
|
11798
12450
|
for (const s of services) {
|
|
11799
|
-
if (s.node.name === name ||
|
|
12451
|
+
if (s.node.name === name || path50.basename(s.dir) === name) return s.node.id;
|
|
11800
12452
|
}
|
|
11801
12453
|
return null;
|
|
11802
12454
|
}
|
|
@@ -11805,7 +12457,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
11805
12457
|
let edgesAdded = 0;
|
|
11806
12458
|
let composePath = null;
|
|
11807
12459
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
11808
|
-
const abs =
|
|
12460
|
+
const abs = path50.join(scanPath, name);
|
|
11809
12461
|
if (await exists(abs)) {
|
|
11810
12462
|
composePath = abs;
|
|
11811
12463
|
break;
|
|
@@ -11818,13 +12470,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
11818
12470
|
} catch (err) {
|
|
11819
12471
|
recordExtractionError(
|
|
11820
12472
|
"infra docker-compose",
|
|
11821
|
-
|
|
12473
|
+
path50.relative(scanPath, composePath),
|
|
11822
12474
|
err
|
|
11823
12475
|
);
|
|
11824
12476
|
return { nodesAdded, edgesAdded };
|
|
11825
12477
|
}
|
|
11826
12478
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
11827
|
-
const evidenceFile =
|
|
12479
|
+
const evidenceFile = path50.relative(scanPath, composePath).split(path50.sep).join("/");
|
|
11828
12480
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
11829
12481
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
11830
12482
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -11865,8 +12517,8 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
11865
12517
|
}
|
|
11866
12518
|
|
|
11867
12519
|
// src/extract/infra/dockerfile.ts
|
|
11868
|
-
import
|
|
11869
|
-
import { promises as
|
|
12520
|
+
import path51 from "path";
|
|
12521
|
+
import { promises as fs21 } from "fs";
|
|
11870
12522
|
import { EdgeType as EdgeType18, Provenance as Provenance19, confidenceForExtracted as confidenceForExtracted16 } from "@neat.is/types";
|
|
11871
12523
|
function readDockerfile(content) {
|
|
11872
12524
|
let image = null;
|
|
@@ -11896,15 +12548,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
11896
12548
|
let nodesAdded = 0;
|
|
11897
12549
|
let edgesAdded = 0;
|
|
11898
12550
|
for (const service of services) {
|
|
11899
|
-
const dockerfilePath =
|
|
12551
|
+
const dockerfilePath = path51.join(service.dir, "Dockerfile");
|
|
11900
12552
|
if (!await exists(dockerfilePath)) continue;
|
|
11901
12553
|
let content;
|
|
11902
12554
|
try {
|
|
11903
|
-
content = await
|
|
12555
|
+
content = await fs21.readFile(dockerfilePath, "utf8");
|
|
11904
12556
|
} catch (err) {
|
|
11905
12557
|
recordExtractionError(
|
|
11906
12558
|
"infra dockerfile",
|
|
11907
|
-
|
|
12559
|
+
path51.relative(scanPath, dockerfilePath),
|
|
11908
12560
|
err
|
|
11909
12561
|
);
|
|
11910
12562
|
continue;
|
|
@@ -11916,8 +12568,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
11916
12568
|
graph.addNode(node.id, node);
|
|
11917
12569
|
nodesAdded++;
|
|
11918
12570
|
}
|
|
11919
|
-
const relDockerfile = toPosix(
|
|
11920
|
-
const evidenceFile = toPosix(
|
|
12571
|
+
const relDockerfile = toPosix(path51.relative(service.dir, dockerfilePath));
|
|
12572
|
+
const evidenceFile = toPosix(path51.relative(scanPath, dockerfilePath));
|
|
11921
12573
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
11922
12574
|
graph,
|
|
11923
12575
|
service.pkg.name,
|
|
@@ -11968,23 +12620,23 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
11968
12620
|
}
|
|
11969
12621
|
|
|
11970
12622
|
// src/extract/infra/terraform.ts
|
|
11971
|
-
import { promises as
|
|
11972
|
-
import
|
|
12623
|
+
import { promises as fs22 } from "fs";
|
|
12624
|
+
import path52 from "path";
|
|
11973
12625
|
import { EdgeType as EdgeType19, Provenance as Provenance20, confidenceForExtracted as confidenceForExtracted17 } from "@neat.is/types";
|
|
11974
12626
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
11975
12627
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
11976
12628
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
11977
12629
|
if (depth > max) return [];
|
|
11978
12630
|
const out = [];
|
|
11979
|
-
const entries = await
|
|
12631
|
+
const entries = await fs22.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
11980
12632
|
for (const entry of entries) {
|
|
11981
12633
|
if (entry.isDirectory()) {
|
|
11982
12634
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
11983
|
-
const child =
|
|
12635
|
+
const child = path52.join(start, entry.name);
|
|
11984
12636
|
if (await isPythonVenvDir(child)) continue;
|
|
11985
12637
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
11986
12638
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
11987
|
-
out.push(
|
|
12639
|
+
out.push(path52.join(start, entry.name));
|
|
11988
12640
|
}
|
|
11989
12641
|
}
|
|
11990
12642
|
return out;
|
|
@@ -12015,8 +12667,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
12015
12667
|
let edgesAdded = 0;
|
|
12016
12668
|
const files = await walkTfFiles(scanPath);
|
|
12017
12669
|
for (const file of files) {
|
|
12018
|
-
const content = await
|
|
12019
|
-
const evidenceFile = toPosix(
|
|
12670
|
+
const content = await fs22.readFile(file, "utf8");
|
|
12671
|
+
const evidenceFile = toPosix(path52.relative(scanPath, file));
|
|
12020
12672
|
const resources = [];
|
|
12021
12673
|
const byKey = /* @__PURE__ */ new Map();
|
|
12022
12674
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -12072,8 +12724,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
12072
12724
|
}
|
|
12073
12725
|
|
|
12074
12726
|
// src/extract/infra/k8s.ts
|
|
12075
|
-
import { promises as
|
|
12076
|
-
import
|
|
12727
|
+
import { promises as fs23 } from "fs";
|
|
12728
|
+
import path53 from "path";
|
|
12077
12729
|
import { parseAllDocuments as parseAllDocuments2 } from "yaml";
|
|
12078
12730
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
12079
12731
|
Service: "k8s-service",
|
|
@@ -12087,15 +12739,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
12087
12739
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
12088
12740
|
if (depth > max) return [];
|
|
12089
12741
|
const out = [];
|
|
12090
|
-
const entries = await
|
|
12742
|
+
const entries = await fs23.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
12091
12743
|
for (const entry of entries) {
|
|
12092
12744
|
if (entry.isDirectory()) {
|
|
12093
12745
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
12094
|
-
const child =
|
|
12746
|
+
const child = path53.join(start, entry.name);
|
|
12095
12747
|
if (await isPythonVenvDir(child)) continue;
|
|
12096
12748
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
12097
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
12098
|
-
out.push(
|
|
12749
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path53.extname(entry.name))) {
|
|
12750
|
+
out.push(path53.join(start, entry.name));
|
|
12099
12751
|
}
|
|
12100
12752
|
}
|
|
12101
12753
|
return out;
|
|
@@ -12104,7 +12756,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
12104
12756
|
let nodesAdded = 0;
|
|
12105
12757
|
const files = await walkYamlFiles2(scanPath);
|
|
12106
12758
|
for (const file of files) {
|
|
12107
|
-
const content = await
|
|
12759
|
+
const content = await fs23.readFile(file, "utf8");
|
|
12108
12760
|
let docs;
|
|
12109
12761
|
try {
|
|
12110
12762
|
docs = parseAllDocuments2(content).map((d) => d.toJSON());
|
|
@@ -12127,16 +12779,16 @@ async function addK8sResources(graph, scanPath) {
|
|
|
12127
12779
|
}
|
|
12128
12780
|
|
|
12129
12781
|
// src/extract/infra/cloudflare.ts
|
|
12130
|
-
import { promises as
|
|
12131
|
-
import
|
|
12782
|
+
import { promises as fs24 } from "fs";
|
|
12783
|
+
import path54 from "path";
|
|
12132
12784
|
import { parse as parseToml2 } from "smol-toml";
|
|
12133
12785
|
import { EdgeType as EdgeType20, Provenance as Provenance21, confidenceForExtracted as confidenceForExtracted18 } from "@neat.is/types";
|
|
12134
12786
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
12135
12787
|
async function readWranglerConfig(dir) {
|
|
12136
12788
|
for (const filename of WRANGLER_FILENAMES) {
|
|
12137
|
-
const abs =
|
|
12789
|
+
const abs = path54.join(dir, filename);
|
|
12138
12790
|
if (!await exists(abs)) continue;
|
|
12139
|
-
const raw = await
|
|
12791
|
+
const raw = await fs24.readFile(abs, "utf8");
|
|
12140
12792
|
const config = filename === "wrangler.toml" ? parseToml2(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
12141
12793
|
return { config, relFile: filename, raw };
|
|
12142
12794
|
}
|
|
@@ -12197,11 +12849,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
12197
12849
|
try {
|
|
12198
12850
|
read = await readWranglerConfig(service.dir);
|
|
12199
12851
|
} catch (err) {
|
|
12200
|
-
recordExtractionError("infra cloudflare",
|
|
12852
|
+
recordExtractionError("infra cloudflare", path54.relative(scanPath, service.dir), err);
|
|
12201
12853
|
continue;
|
|
12202
12854
|
}
|
|
12203
12855
|
if (!read || !read.config.name) continue;
|
|
12204
|
-
const evidenceFile = toPosix(
|
|
12856
|
+
const evidenceFile = toPosix(path54.relative(scanPath, path54.join(service.dir, read.relFile)));
|
|
12205
12857
|
discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
|
|
12206
12858
|
}
|
|
12207
12859
|
for (const worker of discovered) {
|
|
@@ -12213,7 +12865,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
12213
12865
|
}
|
|
12214
12866
|
let anchorId = service.node.id;
|
|
12215
12867
|
if (config.main) {
|
|
12216
|
-
const entryRelPath = toPosix(
|
|
12868
|
+
const entryRelPath = toPosix(path54.normalize(config.main));
|
|
12217
12869
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
12218
12870
|
graph,
|
|
12219
12871
|
service.pkg.name,
|
|
@@ -12359,24 +13011,24 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
12359
13011
|
}
|
|
12360
13012
|
|
|
12361
13013
|
// src/extract/infra/vercel.ts
|
|
12362
|
-
import { promises as
|
|
12363
|
-
import
|
|
13014
|
+
import { promises as fs25 } from "fs";
|
|
13015
|
+
import path55 from "path";
|
|
12364
13016
|
import { EdgeType as EdgeType21 } from "@neat.is/types";
|
|
12365
13017
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
12366
13018
|
async function readVercelConfig(dir) {
|
|
12367
13019
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
12368
|
-
const abs =
|
|
13020
|
+
const abs = path55.join(dir, filename);
|
|
12369
13021
|
if (!await exists(abs)) continue;
|
|
12370
|
-
const raw = await
|
|
13022
|
+
const raw = await fs25.readFile(abs, "utf8");
|
|
12371
13023
|
const config = JSON.parse(maskCommentsInSource(raw));
|
|
12372
13024
|
return { config, relFile: filename, raw };
|
|
12373
13025
|
}
|
|
12374
13026
|
return null;
|
|
12375
13027
|
}
|
|
12376
13028
|
async function readLinkedProjectName(dir) {
|
|
12377
|
-
const abs =
|
|
13029
|
+
const abs = path55.join(dir, ".vercel", "project.json");
|
|
12378
13030
|
if (!await exists(abs)) return void 0;
|
|
12379
|
-
const parsed = JSON.parse(await
|
|
13031
|
+
const parsed = JSON.parse(await fs25.readFile(abs, "utf8"));
|
|
12380
13032
|
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
12381
13033
|
}
|
|
12382
13034
|
function routeSource(route) {
|
|
@@ -12392,7 +13044,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
12392
13044
|
read = await readVercelConfig(service.dir);
|
|
12393
13045
|
projectName = await readLinkedProjectName(service.dir);
|
|
12394
13046
|
} catch (err) {
|
|
12395
|
-
recordExtractionError("infra vercel",
|
|
13047
|
+
recordExtractionError("infra vercel", path55.relative(scanPath, service.dir), err);
|
|
12396
13048
|
continue;
|
|
12397
13049
|
}
|
|
12398
13050
|
if (!read && !projectName) continue;
|
|
@@ -12408,7 +13060,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
12408
13060
|
const anchorId = service.node.id;
|
|
12409
13061
|
if (!read) continue;
|
|
12410
13062
|
const { config, relFile, raw } = read;
|
|
12411
|
-
const evidenceFile = toPosix(
|
|
13063
|
+
const evidenceFile = toPosix(path55.relative(scanPath, path55.join(service.dir, relFile)));
|
|
12412
13064
|
const add = (edgeType, kind, name) => {
|
|
12413
13065
|
if (!name) return;
|
|
12414
13066
|
const result = emitPlatformResourceEdge(
|
|
@@ -12436,16 +13088,16 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
12436
13088
|
}
|
|
12437
13089
|
|
|
12438
13090
|
// src/extract/infra/railway.ts
|
|
12439
|
-
import { promises as
|
|
12440
|
-
import
|
|
13091
|
+
import { promises as fs26 } from "fs";
|
|
13092
|
+
import path56 from "path";
|
|
12441
13093
|
import { parse as parseToml3 } from "smol-toml";
|
|
12442
13094
|
import { EdgeType as EdgeType22 } from "@neat.is/types";
|
|
12443
13095
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
12444
13096
|
async function readRailwayConfig(dir) {
|
|
12445
13097
|
for (const filename of RAILWAY_FILENAMES) {
|
|
12446
|
-
const abs =
|
|
13098
|
+
const abs = path56.join(dir, filename);
|
|
12447
13099
|
if (!await exists(abs)) continue;
|
|
12448
|
-
const raw = await
|
|
13100
|
+
const raw = await fs26.readFile(abs, "utf8");
|
|
12449
13101
|
const config = filename === "railway.toml" ? parseToml3(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
12450
13102
|
return { config, relFile: filename, raw };
|
|
12451
13103
|
}
|
|
@@ -12459,7 +13111,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
12459
13111
|
try {
|
|
12460
13112
|
read = await readRailwayConfig(service.dir);
|
|
12461
13113
|
} catch (err) {
|
|
12462
|
-
recordExtractionError("infra railway",
|
|
13114
|
+
recordExtractionError("infra railway", path56.relative(scanPath, service.dir), err);
|
|
12463
13115
|
continue;
|
|
12464
13116
|
}
|
|
12465
13117
|
if (!read) continue;
|
|
@@ -12469,7 +13121,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
12469
13121
|
}
|
|
12470
13122
|
const anchorId = service.node.id;
|
|
12471
13123
|
const { config, relFile, raw } = read;
|
|
12472
|
-
const evidenceFile = toPosix(
|
|
13124
|
+
const evidenceFile = toPosix(path56.relative(scanPath, path56.join(service.dir, relFile)));
|
|
12473
13125
|
const add = (edgeType, kind, name) => {
|
|
12474
13126
|
if (!name) return;
|
|
12475
13127
|
const result = emitPlatformResourceEdge(
|
|
@@ -12493,15 +13145,15 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
12493
13145
|
}
|
|
12494
13146
|
|
|
12495
13147
|
// src/extract/infra/supabase.ts
|
|
12496
|
-
import { promises as
|
|
12497
|
-
import
|
|
13148
|
+
import { promises as fs27 } from "fs";
|
|
13149
|
+
import path57 from "path";
|
|
12498
13150
|
import { parse as parseToml4 } from "smol-toml";
|
|
12499
13151
|
import { EdgeType as EdgeType23 } from "@neat.is/types";
|
|
12500
13152
|
async function readSupabaseConfig(dir) {
|
|
12501
|
-
const relFile =
|
|
12502
|
-
const abs =
|
|
13153
|
+
const relFile = path57.join("supabase", "config.toml");
|
|
13154
|
+
const abs = path57.join(dir, relFile);
|
|
12503
13155
|
if (!await exists(abs)) return null;
|
|
12504
|
-
const raw = await
|
|
13156
|
+
const raw = await fs27.readFile(abs, "utf8");
|
|
12505
13157
|
const config = parseToml4(raw);
|
|
12506
13158
|
return { config, relFile, raw };
|
|
12507
13159
|
}
|
|
@@ -12513,7 +13165,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
12513
13165
|
try {
|
|
12514
13166
|
read = await readSupabaseConfig(service.dir);
|
|
12515
13167
|
} catch (err) {
|
|
12516
|
-
recordExtractionError("infra supabase",
|
|
13168
|
+
recordExtractionError("infra supabase", path57.relative(scanPath, service.dir), err);
|
|
12517
13169
|
continue;
|
|
12518
13170
|
}
|
|
12519
13171
|
if (!read) continue;
|
|
@@ -12528,7 +13180,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
12528
13180
|
});
|
|
12529
13181
|
}
|
|
12530
13182
|
const anchorId = service.node.id;
|
|
12531
|
-
const evidenceFile = toPosix(
|
|
13183
|
+
const evidenceFile = toPosix(path57.relative(scanPath, path57.join(service.dir, relFile)));
|
|
12532
13184
|
const add = (edgeType, kind, name) => {
|
|
12533
13185
|
if (!name) return;
|
|
12534
13186
|
const result = emitPlatformResourceEdge(
|
|
@@ -12569,12 +13221,12 @@ async function addInfra(graph, scanPath, services) {
|
|
|
12569
13221
|
}
|
|
12570
13222
|
|
|
12571
13223
|
// src/extract/zod-shapes.ts
|
|
12572
|
-
import
|
|
13224
|
+
import path58 from "path";
|
|
12573
13225
|
import Parser16 from "tree-sitter";
|
|
12574
13226
|
import JavaScript8 from "tree-sitter-javascript";
|
|
12575
13227
|
import {
|
|
12576
13228
|
EdgeType as EdgeType24,
|
|
12577
|
-
NodeType as
|
|
13229
|
+
NodeType as NodeType24,
|
|
12578
13230
|
Provenance as Provenance22,
|
|
12579
13231
|
confidenceForExtracted as confidenceForExtracted19,
|
|
12580
13232
|
extractedEdgeId as extractedEdgeId11,
|
|
@@ -12671,7 +13323,7 @@ function topLevelSchemas(root) {
|
|
|
12671
13323
|
}
|
|
12672
13324
|
function zodShapesFromFile(file, serviceDir) {
|
|
12673
13325
|
if (!ZOD_IMPORT_RE.test(file.content)) return [];
|
|
12674
|
-
const tree = parseSource3(parserForExt4(
|
|
13326
|
+
const tree = parseSource3(parserForExt4(path58.extname(file.path)), file.content);
|
|
12675
13327
|
const out = [];
|
|
12676
13328
|
const seen = /* @__PURE__ */ new Set();
|
|
12677
13329
|
for (const { name, call } of topLevelSchemas(tree.rootNode)) {
|
|
@@ -12689,7 +13341,7 @@ function zodShapesFromFile(file, serviceDir) {
|
|
|
12689
13341
|
name,
|
|
12690
13342
|
fields,
|
|
12691
13343
|
evidence: {
|
|
12692
|
-
file:
|
|
13344
|
+
file: path58.relative(serviceDir, file.path),
|
|
12693
13345
|
line,
|
|
12694
13346
|
snippet: snippet(file.content, line)
|
|
12695
13347
|
}
|
|
@@ -12720,7 +13372,7 @@ async function addZodShapes(graph, services) {
|
|
|
12720
13372
|
if (!graph.hasNode(shape.infraId)) {
|
|
12721
13373
|
const node = {
|
|
12722
13374
|
id: shape.infraId,
|
|
12723
|
-
type:
|
|
13375
|
+
type: NodeType24.InfraNode,
|
|
12724
13376
|
name: shape.name,
|
|
12725
13377
|
provider: "self",
|
|
12726
13378
|
kind: "zod-schema"
|
|
@@ -12730,7 +13382,7 @@ async function addZodShapes(graph, services) {
|
|
|
12730
13382
|
}
|
|
12731
13383
|
if (shape.fields.length > 0) {
|
|
12732
13384
|
const node = graph.getNodeAttributes(shape.infraId);
|
|
12733
|
-
if (node.type ===
|
|
13385
|
+
if (node.type === NodeType24.InfraNode) {
|
|
12734
13386
|
graph.replaceNodeAttributes(shape.infraId, {
|
|
12735
13387
|
...node,
|
|
12736
13388
|
columns: foldColumns(
|
|
@@ -12772,7 +13424,7 @@ async function addZodShapes(graph, services) {
|
|
|
12772
13424
|
}
|
|
12773
13425
|
|
|
12774
13426
|
// src/extract/firestore-rules.ts
|
|
12775
|
-
import { NodeType as
|
|
13427
|
+
import { NodeType as NodeType25 } from "@neat.is/types";
|
|
12776
13428
|
var FIRESTORE_COLLECTION_KIND = "firestore-collection";
|
|
12777
13429
|
var WRITE_METHODS = /* @__PURE__ */ new Set(["write", "create", "update"]);
|
|
12778
13430
|
function stripComments(src) {
|
|
@@ -12912,7 +13564,7 @@ async function addFirestoreRules(graph, services) {
|
|
|
12912
13564
|
if (guards.size === 0) return { nodesAdded: 0, edgesAdded: 0 };
|
|
12913
13565
|
graph.forEachNode((id, attrs) => {
|
|
12914
13566
|
const node = attrs;
|
|
12915
|
-
if (node.type !==
|
|
13567
|
+
if (node.type !== NodeType25.InfraNode) return;
|
|
12916
13568
|
if (node.kind !== FIRESTORE_COLLECTION_KIND) return;
|
|
12917
13569
|
const fields = guards.get(collectionKeyFromName(node.name));
|
|
12918
13570
|
if (!fields || fields.size === 0) return;
|
|
@@ -12925,16 +13577,16 @@ async function addFirestoreRules(graph, services) {
|
|
|
12925
13577
|
}
|
|
12926
13578
|
|
|
12927
13579
|
// src/extract/index.ts
|
|
12928
|
-
import
|
|
13580
|
+
import path60 from "path";
|
|
12929
13581
|
|
|
12930
13582
|
// src/extract/retire.ts
|
|
12931
13583
|
import { existsSync as existsSync2 } from "fs";
|
|
12932
|
-
import
|
|
12933
|
-
import { NodeType as
|
|
13584
|
+
import path59 from "path";
|
|
13585
|
+
import { NodeType as NodeType26, Provenance as Provenance23 } from "@neat.is/types";
|
|
12934
13586
|
function dropOrphanedFileNodes(graph) {
|
|
12935
13587
|
const orphans = [];
|
|
12936
13588
|
graph.forEachNode((id, attrs) => {
|
|
12937
|
-
if (attrs.type !==
|
|
13589
|
+
if (attrs.type !== NodeType26.FileNode) return;
|
|
12938
13590
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
12939
13591
|
orphans.push(id);
|
|
12940
13592
|
}
|
|
@@ -12963,11 +13615,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
12963
13615
|
if (edge.provenance !== Provenance23.EXTRACTED) return;
|
|
12964
13616
|
const evidenceFile = edge.evidence?.file;
|
|
12965
13617
|
if (!evidenceFile) return;
|
|
12966
|
-
if (
|
|
13618
|
+
if (path59.isAbsolute(evidenceFile)) {
|
|
12967
13619
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
12968
13620
|
return;
|
|
12969
13621
|
}
|
|
12970
|
-
const found = bases.some((base) => existsSync2(
|
|
13622
|
+
const found = bases.some((base) => existsSync2(path59.join(base, evidenceFile)));
|
|
12971
13623
|
if (!found) toDrop.push(id);
|
|
12972
13624
|
});
|
|
12973
13625
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -13024,7 +13676,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
13024
13676
|
}
|
|
13025
13677
|
const droppedEntries = drainDroppedExtracted();
|
|
13026
13678
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
13027
|
-
const rejectedPath =
|
|
13679
|
+
const rejectedPath = path60.join(path60.dirname(opts.errorsPath), "rejected.ndjson");
|
|
13028
13680
|
try {
|
|
13029
13681
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
13030
13682
|
} catch (err) {
|
|
@@ -13061,11 +13713,11 @@ import {
|
|
|
13061
13713
|
databaseId as databaseId3,
|
|
13062
13714
|
DivergenceResultSchema,
|
|
13063
13715
|
EdgeType as EdgeType25,
|
|
13064
|
-
NodeType as
|
|
13716
|
+
NodeType as NodeType27,
|
|
13065
13717
|
parseEdgeId,
|
|
13066
13718
|
parseFileId,
|
|
13067
13719
|
Provenance as Provenance24,
|
|
13068
|
-
serviceId as
|
|
13720
|
+
serviceId as serviceId10
|
|
13069
13721
|
} from "@neat.is/types";
|
|
13070
13722
|
function bucketKey(source, target, type) {
|
|
13071
13723
|
return `${type}|${source}|${target}`;
|
|
@@ -13075,8 +13727,8 @@ function bucketSourceFor(graph, edge) {
|
|
|
13075
13727
|
const parsed = parseFileId(edge.source);
|
|
13076
13728
|
if (!parsed || !graph.hasNode(edge.target)) return edge.source;
|
|
13077
13729
|
const target = graph.getNodeAttributes(edge.target);
|
|
13078
|
-
if (target.type !==
|
|
13079
|
-
return
|
|
13730
|
+
if (target.type !== NodeType27.DatabaseNode) return edge.source;
|
|
13731
|
+
return serviceId10(parsed.service);
|
|
13080
13732
|
}
|
|
13081
13733
|
function bucketEdges(graph) {
|
|
13082
13734
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
@@ -13107,22 +13759,22 @@ function bucketEdges(graph) {
|
|
|
13107
13759
|
function nodeIsFrontier(graph, nodeId) {
|
|
13108
13760
|
if (!graph.hasNode(nodeId)) return false;
|
|
13109
13761
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
13110
|
-
return attrs.type ===
|
|
13762
|
+
return attrs.type === NodeType27.FrontierNode;
|
|
13111
13763
|
}
|
|
13112
13764
|
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
13113
13765
|
if (!graph.hasNode(nodeId)) return false;
|
|
13114
13766
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
13115
|
-
return attrs.type ===
|
|
13767
|
+
return attrs.type === NodeType27.WebSocketChannelNode;
|
|
13116
13768
|
}
|
|
13117
13769
|
function nodeIsServerAction(graph, nodeId) {
|
|
13118
13770
|
if (!graph.hasNode(nodeId)) return false;
|
|
13119
13771
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
13120
|
-
return attrs.type ===
|
|
13772
|
+
return attrs.type === NodeType27.ServerActionNode;
|
|
13121
13773
|
}
|
|
13122
13774
|
function nodeIsSymbol(graph, nodeId) {
|
|
13123
13775
|
if (!graph.hasNode(nodeId)) return false;
|
|
13124
13776
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
13125
|
-
return attrs.type ===
|
|
13777
|
+
return attrs.type === NodeType27.SymbolNode;
|
|
13126
13778
|
}
|
|
13127
13779
|
function clampConfidence(n) {
|
|
13128
13780
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -13207,7 +13859,7 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
13207
13859
|
if (edge.type !== EdgeType25.CONNECTS_TO) continue;
|
|
13208
13860
|
if (edge.provenance !== Provenance24.OBSERVED) continue;
|
|
13209
13861
|
const target = graph.getNodeAttributes(edge.target);
|
|
13210
|
-
if (target.type !==
|
|
13862
|
+
if (target.type !== NodeType27.DatabaseNode) continue;
|
|
13211
13863
|
const observedHost = target.host?.trim();
|
|
13212
13864
|
if (!observedHost) continue;
|
|
13213
13865
|
if (observedHost === declaredHost) continue;
|
|
@@ -13232,7 +13884,7 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
13232
13884
|
if (edge.type !== EdgeType25.CONNECTS_TO) continue;
|
|
13233
13885
|
if (edge.provenance !== Provenance24.OBSERVED) continue;
|
|
13234
13886
|
const target = graph.getNodeAttributes(edge.target);
|
|
13235
|
-
if (target.type !==
|
|
13887
|
+
if (target.type !== NodeType27.DatabaseNode) continue;
|
|
13236
13888
|
for (const pair of compatPairs()) {
|
|
13237
13889
|
if (pair.engine !== target.engine) continue;
|
|
13238
13890
|
const declared = deps[pair.driver];
|
|
@@ -13348,13 +14000,13 @@ function computeDivergences(graph, opts = {}) {
|
|
|
13348
14000
|
}
|
|
13349
14001
|
graph.forEachNode((nodeId, attrs) => {
|
|
13350
14002
|
const n = attrs;
|
|
13351
|
-
if (n.type ===
|
|
14003
|
+
if (n.type === NodeType27.ServiceNode) {
|
|
13352
14004
|
const svc = n;
|
|
13353
14005
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
13354
14006
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
13355
14007
|
return;
|
|
13356
14008
|
}
|
|
13357
|
-
if (n.type ===
|
|
14009
|
+
if (n.type === NodeType27.InfraNode && n.kind === "sql-table") {
|
|
13358
14010
|
for (const d of detectColumnDrift(n)) all.push(d);
|
|
13359
14011
|
}
|
|
13360
14012
|
});
|
|
@@ -13398,9 +14050,9 @@ function computeDivergences(graph, opts = {}) {
|
|
|
13398
14050
|
}
|
|
13399
14051
|
|
|
13400
14052
|
// src/persist.ts
|
|
13401
|
-
import { promises as
|
|
13402
|
-
import
|
|
13403
|
-
import { NodeType as
|
|
14053
|
+
import { promises as fs28 } from "fs";
|
|
14054
|
+
import path61 from "path";
|
|
14055
|
+
import { NodeType as NodeType28, Provenance as Provenance25, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
13404
14056
|
var SCHEMA_VERSION = 7;
|
|
13405
14057
|
function migrateV1ToV2(payload) {
|
|
13406
14058
|
const nodes = payload.graph.nodes;
|
|
@@ -13424,7 +14076,7 @@ function migrateV5ToV6(payload) {
|
|
|
13424
14076
|
if (Array.isArray(nodes)) {
|
|
13425
14077
|
for (const node of nodes) {
|
|
13426
14078
|
const attrs = node.attributes;
|
|
13427
|
-
if (!attrs || attrs.type !==
|
|
14079
|
+
if (!attrs || attrs.type !== NodeType28.InfraNode) continue;
|
|
13428
14080
|
if (attrs.kind !== "sql-table" && attrs.kind !== "supabase-table") continue;
|
|
13429
14081
|
if (!Array.isArray(attrs.columns)) attrs.columns = [];
|
|
13430
14082
|
}
|
|
@@ -13454,7 +14106,7 @@ function migrateV2ToV3(payload) {
|
|
|
13454
14106
|
return { ...payload, schemaVersion: 3 };
|
|
13455
14107
|
}
|
|
13456
14108
|
async function ensureDir(filePath) {
|
|
13457
|
-
await
|
|
14109
|
+
await fs28.mkdir(path61.dirname(filePath), { recursive: true });
|
|
13458
14110
|
}
|
|
13459
14111
|
async function saveGraphToDisk(graph, outPath) {
|
|
13460
14112
|
await ensureDir(outPath);
|
|
@@ -13464,13 +14116,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
13464
14116
|
graph: graph.export()
|
|
13465
14117
|
};
|
|
13466
14118
|
const tmp = `${outPath}.tmp`;
|
|
13467
|
-
await
|
|
13468
|
-
await
|
|
14119
|
+
await fs28.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
14120
|
+
await fs28.rename(tmp, outPath);
|
|
13469
14121
|
}
|
|
13470
14122
|
async function loadGraphFromDisk(graph, outPath) {
|
|
13471
14123
|
let raw;
|
|
13472
14124
|
try {
|
|
13473
|
-
raw = await
|
|
14125
|
+
raw = await fs28.readFile(outPath, "utf8");
|
|
13474
14126
|
} catch (err) {
|
|
13475
14127
|
if (err.code === "ENOENT") return;
|
|
13476
14128
|
throw err;
|
|
@@ -13543,7 +14195,7 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
13543
14195
|
}
|
|
13544
14196
|
|
|
13545
14197
|
// src/diff.ts
|
|
13546
|
-
import { promises as
|
|
14198
|
+
import { promises as fs29 } from "fs";
|
|
13547
14199
|
async function loadSnapshotForDiff(target) {
|
|
13548
14200
|
if (/^https?:\/\//i.test(target)) {
|
|
13549
14201
|
const res = await fetch(target);
|
|
@@ -13552,7 +14204,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
13552
14204
|
}
|
|
13553
14205
|
return await res.json();
|
|
13554
14206
|
}
|
|
13555
|
-
const raw = await
|
|
14207
|
+
const raw = await fs29.readFile(target, "utf8");
|
|
13556
14208
|
return JSON.parse(raw);
|
|
13557
14209
|
}
|
|
13558
14210
|
function indexEntries(entries) {
|
|
@@ -13619,23 +14271,23 @@ function canonicalJson(value) {
|
|
|
13619
14271
|
}
|
|
13620
14272
|
|
|
13621
14273
|
// src/projects.ts
|
|
13622
|
-
import
|
|
14274
|
+
import path62 from "path";
|
|
13623
14275
|
function pathsForProject(project, baseDir) {
|
|
13624
14276
|
if (project === DEFAULT_PROJECT) {
|
|
13625
14277
|
return {
|
|
13626
|
-
snapshotPath:
|
|
13627
|
-
errorsPath:
|
|
13628
|
-
staleEventsPath:
|
|
13629
|
-
embeddingsCachePath:
|
|
13630
|
-
policyViolationsPath:
|
|
14278
|
+
snapshotPath: path62.join(baseDir, "graph.json"),
|
|
14279
|
+
errorsPath: path62.join(baseDir, "errors.ndjson"),
|
|
14280
|
+
staleEventsPath: path62.join(baseDir, "stale-events.ndjson"),
|
|
14281
|
+
embeddingsCachePath: path62.join(baseDir, "embeddings.json"),
|
|
14282
|
+
policyViolationsPath: path62.join(baseDir, "policy-violations.ndjson")
|
|
13631
14283
|
};
|
|
13632
14284
|
}
|
|
13633
14285
|
return {
|
|
13634
|
-
snapshotPath:
|
|
13635
|
-
errorsPath:
|
|
13636
|
-
staleEventsPath:
|
|
13637
|
-
embeddingsCachePath:
|
|
13638
|
-
policyViolationsPath:
|
|
14286
|
+
snapshotPath: path62.join(baseDir, `${project}.json`),
|
|
14287
|
+
errorsPath: path62.join(baseDir, `errors.${project}.ndjson`),
|
|
14288
|
+
staleEventsPath: path62.join(baseDir, `stale-events.${project}.ndjson`),
|
|
14289
|
+
embeddingsCachePath: path62.join(baseDir, `embeddings.${project}.json`),
|
|
14290
|
+
policyViolationsPath: path62.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
13639
14291
|
};
|
|
13640
14292
|
}
|
|
13641
14293
|
var Projects = class {
|
|
@@ -13674,9 +14326,9 @@ function parseExtraProjects(raw) {
|
|
|
13674
14326
|
}
|
|
13675
14327
|
|
|
13676
14328
|
// src/registry.ts
|
|
13677
|
-
import { promises as
|
|
14329
|
+
import { promises as fs30 } from "fs";
|
|
13678
14330
|
import os2 from "os";
|
|
13679
|
-
import
|
|
14331
|
+
import path63 from "path";
|
|
13680
14332
|
import {
|
|
13681
14333
|
RegistryFileSchema
|
|
13682
14334
|
} from "@neat.is/types";
|
|
@@ -13684,20 +14336,20 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
13684
14336
|
var LOCK_RETRY_MS = 50;
|
|
13685
14337
|
function neatHome() {
|
|
13686
14338
|
const override = process.env.NEAT_HOME;
|
|
13687
|
-
if (override && override.length > 0) return
|
|
13688
|
-
return
|
|
14339
|
+
if (override && override.length > 0) return path63.resolve(override);
|
|
14340
|
+
return path63.join(os2.homedir(), ".neat");
|
|
13689
14341
|
}
|
|
13690
14342
|
function registryPath() {
|
|
13691
|
-
return
|
|
14343
|
+
return path63.join(neatHome(), "projects.json");
|
|
13692
14344
|
}
|
|
13693
14345
|
function registryLockPath() {
|
|
13694
|
-
return
|
|
14346
|
+
return path63.join(neatHome(), "projects.json.lock");
|
|
13695
14347
|
}
|
|
13696
14348
|
function daemonPidPath() {
|
|
13697
|
-
return
|
|
14349
|
+
return path63.join(neatHome(), "neatd.pid");
|
|
13698
14350
|
}
|
|
13699
14351
|
function daemonsDir() {
|
|
13700
|
-
return
|
|
14352
|
+
return path63.join(neatHome(), "daemons");
|
|
13701
14353
|
}
|
|
13702
14354
|
function isFiniteInt(v) {
|
|
13703
14355
|
return typeof v === "number" && Number.isFinite(v);
|
|
@@ -13730,7 +14382,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
13730
14382
|
const dir = daemonsDir();
|
|
13731
14383
|
let names;
|
|
13732
14384
|
try {
|
|
13733
|
-
names = await
|
|
14385
|
+
names = await fs30.readdir(dir);
|
|
13734
14386
|
} catch (err) {
|
|
13735
14387
|
if (err.code === "ENOENT") return [];
|
|
13736
14388
|
throw err;
|
|
@@ -13738,10 +14390,10 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
13738
14390
|
const out = [];
|
|
13739
14391
|
for (const name of names) {
|
|
13740
14392
|
if (!name.endsWith(".json")) continue;
|
|
13741
|
-
const file =
|
|
14393
|
+
const file = path63.join(dir, name);
|
|
13742
14394
|
let raw;
|
|
13743
14395
|
try {
|
|
13744
|
-
raw = await
|
|
14396
|
+
raw = await fs30.readFile(file, "utf8");
|
|
13745
14397
|
} catch {
|
|
13746
14398
|
continue;
|
|
13747
14399
|
}
|
|
@@ -13754,7 +14406,7 @@ async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
|
13754
14406
|
return out;
|
|
13755
14407
|
}
|
|
13756
14408
|
async function removeDaemonRecord(source) {
|
|
13757
|
-
await
|
|
14409
|
+
await fs30.unlink(source).catch(() => {
|
|
13758
14410
|
});
|
|
13759
14411
|
}
|
|
13760
14412
|
async function listMachineProjects(probe = defaultDiscoveryProbe) {
|
|
@@ -13811,7 +14463,7 @@ function isPidAliveDefault(pid) {
|
|
|
13811
14463
|
}
|
|
13812
14464
|
async function readPidFile(file) {
|
|
13813
14465
|
try {
|
|
13814
|
-
const raw = await
|
|
14466
|
+
const raw = await fs30.readFile(file, "utf8");
|
|
13815
14467
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
13816
14468
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
13817
14469
|
} catch {
|
|
@@ -13859,32 +14511,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
13859
14511
|
}
|
|
13860
14512
|
}
|
|
13861
14513
|
async function normalizeProjectPath(input) {
|
|
13862
|
-
const resolved =
|
|
14514
|
+
const resolved = path63.resolve(input);
|
|
13863
14515
|
try {
|
|
13864
|
-
return await
|
|
14516
|
+
return await fs30.realpath(resolved);
|
|
13865
14517
|
} catch {
|
|
13866
14518
|
return resolved;
|
|
13867
14519
|
}
|
|
13868
14520
|
}
|
|
13869
14521
|
async function writeAtomically(target, contents) {
|
|
13870
|
-
await
|
|
14522
|
+
await fs30.mkdir(path63.dirname(target), { recursive: true });
|
|
13871
14523
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
13872
|
-
const fd = await
|
|
14524
|
+
const fd = await fs30.open(tmp, "w");
|
|
13873
14525
|
try {
|
|
13874
14526
|
await fd.writeFile(contents, "utf8");
|
|
13875
14527
|
await fd.sync();
|
|
13876
14528
|
} finally {
|
|
13877
14529
|
await fd.close();
|
|
13878
14530
|
}
|
|
13879
|
-
await
|
|
14531
|
+
await fs30.rename(tmp, target);
|
|
13880
14532
|
}
|
|
13881
14533
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
13882
14534
|
const deadline = Date.now() + timeoutMs;
|
|
13883
|
-
await
|
|
14535
|
+
await fs30.mkdir(path63.dirname(lockPath), { recursive: true });
|
|
13884
14536
|
let probedHolder = false;
|
|
13885
14537
|
while (true) {
|
|
13886
14538
|
try {
|
|
13887
|
-
const fd = await
|
|
14539
|
+
const fd = await fs30.open(lockPath, "wx");
|
|
13888
14540
|
try {
|
|
13889
14541
|
await fd.writeFile(`${process.pid}
|
|
13890
14542
|
`, "utf8");
|
|
@@ -13909,7 +14561,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
13909
14561
|
}
|
|
13910
14562
|
}
|
|
13911
14563
|
async function releaseLock(lockPath) {
|
|
13912
|
-
await
|
|
14564
|
+
await fs30.unlink(lockPath).catch(() => {
|
|
13913
14565
|
});
|
|
13914
14566
|
}
|
|
13915
14567
|
async function withLock(fn) {
|
|
@@ -13925,7 +14577,7 @@ async function readRegistry() {
|
|
|
13925
14577
|
const file = registryPath();
|
|
13926
14578
|
let raw;
|
|
13927
14579
|
try {
|
|
13928
|
-
raw = await
|
|
14580
|
+
raw = await fs30.readFile(file, "utf8");
|
|
13929
14581
|
} catch (err) {
|
|
13930
14582
|
if (err.code === "ENOENT") {
|
|
13931
14583
|
return { version: 1, projects: [] };
|
|
@@ -14030,7 +14682,7 @@ function pruneTtlMs() {
|
|
|
14030
14682
|
}
|
|
14031
14683
|
async function statPathStatus(p) {
|
|
14032
14684
|
try {
|
|
14033
|
-
const stat = await
|
|
14685
|
+
const stat = await fs30.stat(p);
|
|
14034
14686
|
return stat.isDirectory() ? "present" : "unknown";
|
|
14035
14687
|
} catch (err) {
|
|
14036
14688
|
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
@@ -14075,14 +14727,14 @@ import cors from "@fastify/cors";
|
|
|
14075
14727
|
import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } from "@neat.is/types";
|
|
14076
14728
|
|
|
14077
14729
|
// src/extend/index.ts
|
|
14078
|
-
import { promises as
|
|
14079
|
-
import
|
|
14730
|
+
import { promises as fs32 } from "fs";
|
|
14731
|
+
import path65 from "path";
|
|
14080
14732
|
import os3 from "os";
|
|
14081
14733
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
14082
14734
|
|
|
14083
14735
|
// src/installers/package-manager.ts
|
|
14084
|
-
import { promises as
|
|
14085
|
-
import
|
|
14736
|
+
import { promises as fs31 } from "fs";
|
|
14737
|
+
import path64 from "path";
|
|
14086
14738
|
import { spawn } from "child_process";
|
|
14087
14739
|
var LOCKFILE_PRIORITY = [
|
|
14088
14740
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -14097,29 +14749,29 @@ var LOCKFILE_PRIORITY = [
|
|
|
14097
14749
|
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
14098
14750
|
async function exists2(p) {
|
|
14099
14751
|
try {
|
|
14100
|
-
await
|
|
14752
|
+
await fs31.access(p);
|
|
14101
14753
|
return true;
|
|
14102
14754
|
} catch {
|
|
14103
14755
|
return false;
|
|
14104
14756
|
}
|
|
14105
14757
|
}
|
|
14106
14758
|
async function detectPackageManager(serviceDir) {
|
|
14107
|
-
let dir =
|
|
14759
|
+
let dir = path64.resolve(serviceDir);
|
|
14108
14760
|
const stops = /* @__PURE__ */ new Set();
|
|
14109
14761
|
for (let i = 0; i < 64; i++) {
|
|
14110
14762
|
if (stops.has(dir)) break;
|
|
14111
14763
|
stops.add(dir);
|
|
14112
14764
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
14113
|
-
const lockPath =
|
|
14765
|
+
const lockPath = path64.join(dir, candidate.lockfile);
|
|
14114
14766
|
if (await exists2(lockPath)) {
|
|
14115
14767
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
14116
14768
|
}
|
|
14117
14769
|
}
|
|
14118
|
-
const parent =
|
|
14770
|
+
const parent = path64.dirname(dir);
|
|
14119
14771
|
if (parent === dir) break;
|
|
14120
14772
|
dir = parent;
|
|
14121
14773
|
}
|
|
14122
|
-
return { pm: "npm", cwd:
|
|
14774
|
+
return { pm: "npm", cwd: path64.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
14123
14775
|
}
|
|
14124
14776
|
async function runPackageManagerInstall(cmd) {
|
|
14125
14777
|
return new Promise((resolve) => {
|
|
@@ -14161,15 +14813,15 @@ ${err.message}`
|
|
|
14161
14813
|
// src/extend/index.ts
|
|
14162
14814
|
async function fileExists2(p) {
|
|
14163
14815
|
try {
|
|
14164
|
-
await
|
|
14816
|
+
await fs32.access(p);
|
|
14165
14817
|
return true;
|
|
14166
14818
|
} catch {
|
|
14167
14819
|
return false;
|
|
14168
14820
|
}
|
|
14169
14821
|
}
|
|
14170
14822
|
async function readPackageJson(scanPath) {
|
|
14171
|
-
const pkgPath =
|
|
14172
|
-
const raw = await
|
|
14823
|
+
const pkgPath = path65.join(scanPath, "package.json");
|
|
14824
|
+
const raw = await fs32.readFile(pkgPath, "utf8");
|
|
14173
14825
|
return JSON.parse(raw);
|
|
14174
14826
|
}
|
|
14175
14827
|
var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
@@ -14183,15 +14835,15 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
14183
14835
|
async function findHookFiles(scanPath) {
|
|
14184
14836
|
const found = [];
|
|
14185
14837
|
const walk9 = async (dir) => {
|
|
14186
|
-
const entries = await
|
|
14838
|
+
const entries = await fs32.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
14187
14839
|
for (const entry of entries) {
|
|
14188
14840
|
if (entry.isDirectory()) {
|
|
14189
14841
|
if (entry.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry.name)) continue;
|
|
14190
|
-
await walk9(
|
|
14842
|
+
await walk9(path65.join(dir, entry.name));
|
|
14191
14843
|
} else if (entry.isFile()) {
|
|
14192
14844
|
if ((entry.name.startsWith("instrumentation") || entry.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry.name)) {
|
|
14193
|
-
const rel =
|
|
14194
|
-
found.push(rel.split(
|
|
14845
|
+
const rel = path65.relative(scanPath, path65.join(dir, entry.name));
|
|
14846
|
+
found.push(rel.split(path65.sep).join("/"));
|
|
14195
14847
|
}
|
|
14196
14848
|
}
|
|
14197
14849
|
}
|
|
@@ -14202,7 +14854,7 @@ async function findHookFiles(scanPath) {
|
|
|
14202
14854
|
async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
14203
14855
|
let fallback = null;
|
|
14204
14856
|
for (const file of hookFiles) {
|
|
14205
|
-
const content = await
|
|
14857
|
+
const content = await fs32.readFile(path65.join(scanPath, file), "utf8");
|
|
14206
14858
|
const patched = splicedContent(content, snippet2);
|
|
14207
14859
|
if (patched !== null) return { file, content, patched };
|
|
14208
14860
|
if (fallback === null) fallback = { file, content };
|
|
@@ -14210,12 +14862,12 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
|
14210
14862
|
return { file: fallback.file, content: fallback.content, patched: null };
|
|
14211
14863
|
}
|
|
14212
14864
|
function extendLogPath() {
|
|
14213
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
14865
|
+
return process.env.NEAT_EXTEND_LOG ?? path65.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
14214
14866
|
}
|
|
14215
14867
|
async function appendExtendLog(entry) {
|
|
14216
14868
|
const logPath = extendLogPath();
|
|
14217
|
-
await
|
|
14218
|
-
await
|
|
14869
|
+
await fs32.mkdir(path65.dirname(logPath), { recursive: true });
|
|
14870
|
+
await fs32.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
14219
14871
|
}
|
|
14220
14872
|
function splicedContent(fileContent, snippet2) {
|
|
14221
14873
|
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
@@ -14273,7 +14925,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
14273
14925
|
}
|
|
14274
14926
|
async function describeProjectInstrumentation(ctx) {
|
|
14275
14927
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
14276
|
-
const envNeat = await fileExists2(
|
|
14928
|
+
const envNeat = await fileExists2(path65.join(ctx.scanPath, ".env.neat"));
|
|
14277
14929
|
const registryInstrPackages = new Set(
|
|
14278
14930
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
14279
14931
|
);
|
|
@@ -14295,7 +14947,7 @@ async function applyExtension(ctx, args, options) {
|
|
|
14295
14947
|
);
|
|
14296
14948
|
}
|
|
14297
14949
|
for (const file of hookFiles) {
|
|
14298
|
-
const content = await
|
|
14950
|
+
const content = await fs32.readFile(path65.join(ctx.scanPath, file), "utf8");
|
|
14299
14951
|
if (content.includes(args.registration_snippet)) {
|
|
14300
14952
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
14301
14953
|
}
|
|
@@ -14307,18 +14959,18 @@ async function applyExtension(ctx, args, options) {
|
|
|
14307
14959
|
);
|
|
14308
14960
|
}
|
|
14309
14961
|
const primaryFile = primary.file;
|
|
14310
|
-
const primaryPath =
|
|
14962
|
+
const primaryPath = path65.join(ctx.scanPath, primaryFile);
|
|
14311
14963
|
const filesTouched = [];
|
|
14312
14964
|
const depsAdded = [];
|
|
14313
|
-
const pkgPath =
|
|
14965
|
+
const pkgPath = path65.join(ctx.scanPath, "package.json");
|
|
14314
14966
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
14315
14967
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
14316
14968
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
14317
|
-
await
|
|
14969
|
+
await fs32.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
14318
14970
|
filesTouched.push("package.json");
|
|
14319
14971
|
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
14320
14972
|
}
|
|
14321
|
-
await
|
|
14973
|
+
await fs32.writeFile(primaryPath, primary.patched, "utf8");
|
|
14322
14974
|
filesTouched.push(primaryFile);
|
|
14323
14975
|
const cmd = await detectPackageManager(ctx.scanPath);
|
|
14324
14976
|
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
@@ -14349,7 +15001,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
14349
15001
|
};
|
|
14350
15002
|
}
|
|
14351
15003
|
for (const file of hookFiles) {
|
|
14352
|
-
const content = await
|
|
15004
|
+
const content = await fs32.readFile(path65.join(ctx.scanPath, file), "utf8");
|
|
14353
15005
|
if (content.includes(args.registration_snippet)) {
|
|
14354
15006
|
return {
|
|
14355
15007
|
library: args.library,
|
|
@@ -14384,28 +15036,28 @@ async function rollbackExtension(ctx, args) {
|
|
|
14384
15036
|
if (!await fileExists2(logPath)) {
|
|
14385
15037
|
return { undone: false, message: "no apply found for library" };
|
|
14386
15038
|
}
|
|
14387
|
-
const raw = await
|
|
15039
|
+
const raw = await fs32.readFile(logPath, "utf8");
|
|
14388
15040
|
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
14389
15041
|
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
14390
15042
|
if (!match) {
|
|
14391
15043
|
return { undone: false, message: "no apply found for library" };
|
|
14392
15044
|
}
|
|
14393
|
-
const pkgPath =
|
|
15045
|
+
const pkgPath = path65.join(ctx.scanPath, "package.json");
|
|
14394
15046
|
if (await fileExists2(pkgPath)) {
|
|
14395
15047
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
14396
15048
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
14397
15049
|
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
14398
15050
|
pkg.dependencies = rest;
|
|
14399
|
-
await
|
|
15051
|
+
await fs32.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
14400
15052
|
}
|
|
14401
15053
|
}
|
|
14402
15054
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
14403
15055
|
for (const file of hookFiles) {
|
|
14404
|
-
const filePath =
|
|
14405
|
-
const content = await
|
|
15056
|
+
const filePath = path65.join(ctx.scanPath, file);
|
|
15057
|
+
const content = await fs32.readFile(filePath, "utf8");
|
|
14406
15058
|
if (content.includes(match.registration_snippet)) {
|
|
14407
15059
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
14408
|
-
await
|
|
15060
|
+
await fs32.writeFile(filePath, filtered, "utf8");
|
|
14409
15061
|
break;
|
|
14410
15062
|
}
|
|
14411
15063
|
}
|
|
@@ -14516,8 +15168,8 @@ data: ${JSON.stringify(envelope.payload)}
|
|
|
14516
15168
|
|
|
14517
15169
|
// src/connectors-config.ts
|
|
14518
15170
|
import os4 from "os";
|
|
14519
|
-
import
|
|
14520
|
-
import { promises as
|
|
15171
|
+
import path66 from "path";
|
|
15172
|
+
import { promises as fs33 } from "fs";
|
|
14521
15173
|
var CONNECTORS_CONFIG_VERSION = 1;
|
|
14522
15174
|
var EnvRefUnsetError = class extends Error {
|
|
14523
15175
|
ref;
|
|
@@ -14531,17 +15183,17 @@ var EnvRefUnsetError = class extends Error {
|
|
|
14531
15183
|
};
|
|
14532
15184
|
function neatHome2() {
|
|
14533
15185
|
const override = process.env.NEAT_HOME;
|
|
14534
|
-
if (override && override.length > 0) return
|
|
14535
|
-
return
|
|
15186
|
+
if (override && override.length > 0) return path66.resolve(override);
|
|
15187
|
+
return path66.join(os4.homedir(), ".neat");
|
|
14536
15188
|
}
|
|
14537
15189
|
function connectorsConfigPath(home = neatHome2()) {
|
|
14538
|
-
return
|
|
15190
|
+
return path66.join(home, "connectors.json");
|
|
14539
15191
|
}
|
|
14540
15192
|
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
14541
15193
|
async function warnIfModeLooserThan0600(file) {
|
|
14542
15194
|
if (process.platform === "win32") return;
|
|
14543
15195
|
try {
|
|
14544
|
-
const stat = await
|
|
15196
|
+
const stat = await fs33.stat(file);
|
|
14545
15197
|
if ((stat.mode & MODE_MASK_LOOSER_THAN_0600) !== 0) {
|
|
14546
15198
|
const mode = (stat.mode & 511).toString(8).padStart(3, "0");
|
|
14547
15199
|
console.warn(
|
|
@@ -14555,7 +15207,7 @@ async function readConnectorsConfig(home = neatHome2()) {
|
|
|
14555
15207
|
const file = connectorsConfigPath(home);
|
|
14556
15208
|
let raw;
|
|
14557
15209
|
try {
|
|
14558
|
-
raw = await
|
|
15210
|
+
raw = await fs33.readFile(file, "utf8");
|
|
14559
15211
|
} catch (err) {
|
|
14560
15212
|
if (err.code === "ENOENT") {
|
|
14561
15213
|
return { version: CONNECTORS_CONFIG_VERSION, connectors: [] };
|
|
@@ -14666,7 +15318,7 @@ function connectorMatchesProject(entry, project) {
|
|
|
14666
15318
|
var CONNECTORS_LOCK_TIMEOUT_MS = 5e3;
|
|
14667
15319
|
var CONNECTORS_LOCK_RETRY_MS = 50;
|
|
14668
15320
|
function connectorsConfigLockPath(home = neatHome2()) {
|
|
14669
|
-
return
|
|
15321
|
+
return path66.join(home, "connectors.json.lock");
|
|
14670
15322
|
}
|
|
14671
15323
|
function isEnvRef(value) {
|
|
14672
15324
|
return value.length > 1 && value.startsWith("$");
|
|
@@ -14679,9 +15331,9 @@ function redactCredentialRef(ref) {
|
|
|
14679
15331
|
return out;
|
|
14680
15332
|
}
|
|
14681
15333
|
async function writeConfigAtomically0600(file, contents) {
|
|
14682
|
-
await
|
|
15334
|
+
await fs33.mkdir(path66.dirname(file), { recursive: true });
|
|
14683
15335
|
const tmp = `${file}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
14684
|
-
const fd = await
|
|
15336
|
+
const fd = await fs33.open(tmp, "w", 384);
|
|
14685
15337
|
try {
|
|
14686
15338
|
await fd.writeFile(contents, "utf8");
|
|
14687
15339
|
await fd.chmod(384);
|
|
@@ -14689,14 +15341,14 @@ async function writeConfigAtomically0600(file, contents) {
|
|
|
14689
15341
|
} finally {
|
|
14690
15342
|
await fd.close();
|
|
14691
15343
|
}
|
|
14692
|
-
await
|
|
15344
|
+
await fs33.rename(tmp, file);
|
|
14693
15345
|
}
|
|
14694
15346
|
async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEOUT_MS) {
|
|
14695
15347
|
const deadline = Date.now() + timeoutMs;
|
|
14696
|
-
await
|
|
15348
|
+
await fs33.mkdir(path66.dirname(lockPath), { recursive: true });
|
|
14697
15349
|
for (; ; ) {
|
|
14698
15350
|
try {
|
|
14699
|
-
const fd = await
|
|
15351
|
+
const fd = await fs33.open(lockPath, "wx");
|
|
14700
15352
|
try {
|
|
14701
15353
|
await fd.writeFile(`${process.pid}
|
|
14702
15354
|
`, "utf8");
|
|
@@ -14716,7 +15368,7 @@ async function acquireConnectorsLock(lockPath, timeoutMs = CONNECTORS_LOCK_TIMEO
|
|
|
14716
15368
|
}
|
|
14717
15369
|
}
|
|
14718
15370
|
async function releaseConnectorsLock(lockPath) {
|
|
14719
|
-
await
|
|
15371
|
+
await fs33.unlink(lockPath).catch(() => {
|
|
14720
15372
|
});
|
|
14721
15373
|
}
|
|
14722
15374
|
async function withConnectorsLock(home, fn) {
|
|
@@ -14841,7 +15493,7 @@ function getConnectorStatus(id, now = Date.now(), thresholdMs = CONNECTOR_STALE_
|
|
|
14841
15493
|
}
|
|
14842
15494
|
|
|
14843
15495
|
// src/connectors/index.ts
|
|
14844
|
-
import { NodeType as
|
|
15496
|
+
import { NodeType as NodeType29, parseFileId as parseFileId2, Provenance as Provenance26 } from "@neat.is/types";
|
|
14845
15497
|
var NO_ENV = "unknown";
|
|
14846
15498
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
14847
15499
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
@@ -14860,7 +15512,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
14860
15512
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
14861
15513
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
14862
15514
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
14863
|
-
if (attrs.type !==
|
|
15515
|
+
if (attrs.type !== NodeType29.RouteNode || !attrs.path) return void 0;
|
|
14864
15516
|
const site = { relPath: attrs.path };
|
|
14865
15517
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
14866
15518
|
return site;
|
|
@@ -15345,10 +15997,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
15345
15997
|
// src/connectors/supabase/map.ts
|
|
15346
15998
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
15347
15999
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
15348
|
-
function targetFromRestPath(
|
|
15349
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
16000
|
+
function targetFromRestPath(path67) {
|
|
16001
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path67);
|
|
15350
16002
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
15351
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
16003
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path67);
|
|
15352
16004
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
15353
16005
|
return null;
|
|
15354
16006
|
}
|
|
@@ -15565,7 +16217,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
15565
16217
|
}
|
|
15566
16218
|
|
|
15567
16219
|
// src/connectors/railway/index.ts
|
|
15568
|
-
import { EdgeType as EdgeType27, NodeType as
|
|
16220
|
+
import { EdgeType as EdgeType27, NodeType as NodeType30, serviceId as serviceId11 } from "@neat.is/types";
|
|
15569
16221
|
|
|
15570
16222
|
// src/connectors/railway/client.ts
|
|
15571
16223
|
var DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
|
|
@@ -15715,7 +16367,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
15715
16367
|
const out = [];
|
|
15716
16368
|
graph.forEachNode((_id, attrs) => {
|
|
15717
16369
|
const node = attrs;
|
|
15718
|
-
if (node.type !==
|
|
16370
|
+
if (node.type !== NodeType30.RouteNode) return;
|
|
15719
16371
|
const route = attrs;
|
|
15720
16372
|
if (route.service !== serviceName) return;
|
|
15721
16373
|
out.push({
|
|
@@ -15824,7 +16476,7 @@ function createRailwayResolveTarget(config) {
|
|
|
15824
16476
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
15825
16477
|
const peerName = config.serviceNameById[signal.targetName];
|
|
15826
16478
|
if (!peerName) return null;
|
|
15827
|
-
return { targetNodeId:
|
|
16479
|
+
return { targetNodeId: serviceId11(peerName), serviceName, edgeType: EdgeType27.CONNECTS_TO };
|
|
15828
16480
|
}
|
|
15829
16481
|
return null;
|
|
15830
16482
|
};
|
|
@@ -15943,9 +16595,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
15943
16595
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
15944
16596
|
if (secondSep === -1) return null;
|
|
15945
16597
|
const method = rest.slice(0, secondSep);
|
|
15946
|
-
const
|
|
15947
|
-
if (!resourceName || !method || !
|
|
15948
|
-
return { resourceName, method, path:
|
|
16598
|
+
const path67 = rest.slice(secondSep + 1);
|
|
16599
|
+
if (!resourceName || !method || !path67) return null;
|
|
16600
|
+
return { resourceName, method, path: path67 };
|
|
15949
16601
|
}
|
|
15950
16602
|
function resourceNameFor(type, labels) {
|
|
15951
16603
|
if (!labels) return null;
|
|
@@ -15983,14 +16635,14 @@ function mapLogEntryToSignal(entry) {
|
|
|
15983
16635
|
if (!req) return null;
|
|
15984
16636
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
15985
16637
|
const method = req.requestMethod.toUpperCase();
|
|
15986
|
-
const
|
|
15987
|
-
if (
|
|
16638
|
+
const path67 = pathFromRequestUrl(req.requestUrl);
|
|
16639
|
+
if (path67 === null) return null;
|
|
15988
16640
|
const timestamp = entry.timestamp;
|
|
15989
16641
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
15990
16642
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD2;
|
|
15991
16643
|
return {
|
|
15992
16644
|
targetKind: resourceType,
|
|
15993
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
16645
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path67 }),
|
|
15994
16646
|
callCount: 1,
|
|
15995
16647
|
errorCount: isError ? 1 : 0,
|
|
15996
16648
|
lastObservedIso: timestamp
|
|
@@ -16006,7 +16658,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
16006
16658
|
}
|
|
16007
16659
|
|
|
16008
16660
|
// src/connectors/firebase/resolve.ts
|
|
16009
|
-
import { NodeType as
|
|
16661
|
+
import { NodeType as NodeType31, EdgeType as EdgeType28 } from "@neat.is/types";
|
|
16010
16662
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
16011
16663
|
switch (resourceType) {
|
|
16012
16664
|
case "cloud_function":
|
|
@@ -16021,7 +16673,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
16021
16673
|
const entries = [];
|
|
16022
16674
|
graph.forEachNode((_id, attrs) => {
|
|
16023
16675
|
const node = attrs;
|
|
16024
|
-
if (node.type !==
|
|
16676
|
+
if (node.type !== NodeType31.RouteNode) return;
|
|
16025
16677
|
const route = attrs;
|
|
16026
16678
|
if (route.service !== serviceName) return;
|
|
16027
16679
|
entries.push({
|
|
@@ -16076,7 +16728,7 @@ function createFirebaseConnector(graph, serviceMap) {
|
|
|
16076
16728
|
}
|
|
16077
16729
|
|
|
16078
16730
|
// src/connectors/cloudflare/connector.ts
|
|
16079
|
-
import { EdgeType as EdgeType29, NodeType as
|
|
16731
|
+
import { EdgeType as EdgeType29, NodeType as NodeType32, fileId as fileId5, infraId as infraId21 } from "@neat.is/types";
|
|
16080
16732
|
|
|
16081
16733
|
// src/connectors/cloudflare/client.ts
|
|
16082
16734
|
import { randomUUID } from "crypto";
|
|
@@ -16187,7 +16839,7 @@ function mapEventToSignal(event) {
|
|
|
16187
16839
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
16188
16840
|
const statusCode = metadata?.statusCode;
|
|
16189
16841
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
16190
|
-
const
|
|
16842
|
+
const path67 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
16191
16843
|
return {
|
|
16192
16844
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
16193
16845
|
targetName: scriptName,
|
|
@@ -16195,7 +16847,7 @@ function mapEventToSignal(event) {
|
|
|
16195
16847
|
errorCount: isError ? 1 : 0,
|
|
16196
16848
|
lastObservedIso: observedAt.toISOString(),
|
|
16197
16849
|
method,
|
|
16198
|
-
...
|
|
16850
|
+
...path67 ? { path: path67 } : {},
|
|
16199
16851
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
16200
16852
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
16201
16853
|
};
|
|
@@ -16235,19 +16887,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
16235
16887
|
graph.forEachNode((id, attrs) => {
|
|
16236
16888
|
if (found) return;
|
|
16237
16889
|
const a = attrs;
|
|
16238
|
-
if (a.type ===
|
|
16890
|
+
if (a.type === NodeType32.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
16239
16891
|
found = id;
|
|
16240
16892
|
}
|
|
16241
16893
|
});
|
|
16242
16894
|
return found;
|
|
16243
16895
|
}
|
|
16244
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
16245
|
-
const normalizedPath = normalizePathTemplate(
|
|
16896
|
+
function findMatchingRouteNode(graph, serviceName, method, path67) {
|
|
16897
|
+
const normalizedPath = normalizePathTemplate(path67);
|
|
16246
16898
|
let found = null;
|
|
16247
16899
|
graph.forEachNode((id, attrs) => {
|
|
16248
16900
|
if (found) return;
|
|
16249
16901
|
const a = attrs;
|
|
16250
|
-
if (a.type !==
|
|
16902
|
+
if (a.type !== NodeType32.RouteNode || a.service !== serviceName) return;
|
|
16251
16903
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
16252
16904
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
16253
16905
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -16259,14 +16911,14 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
16259
16911
|
return (signal) => {
|
|
16260
16912
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
16261
16913
|
const scriptName = signal.targetName;
|
|
16262
|
-
const { method, path:
|
|
16914
|
+
const { method, path: path67 } = signal;
|
|
16263
16915
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
16264
|
-
if (!method || !
|
|
16265
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
16916
|
+
if (!method || !path67) return wholeFileId;
|
|
16917
|
+
return findMatchingRouteNode(graph, serviceName, method, path67) ?? wholeFileId;
|
|
16266
16918
|
};
|
|
16267
16919
|
const mapping = config.workers?.[scriptName];
|
|
16268
16920
|
if (mapping) {
|
|
16269
|
-
const wholeFileId =
|
|
16921
|
+
const wholeFileId = fileId5(mapping.service, mapping.entryFile);
|
|
16270
16922
|
return {
|
|
16271
16923
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
16272
16924
|
serviceName: mapping.service,
|
|
@@ -16592,9 +17244,9 @@ function parseCloudRunTargetName(targetName) {
|
|
|
16592
17244
|
const secondSep = rest.indexOf(FIELD_SEP2);
|
|
16593
17245
|
if (secondSep === -1) return null;
|
|
16594
17246
|
const method = rest.slice(0, secondSep);
|
|
16595
|
-
const
|
|
16596
|
-
if (!serviceName || !method || !
|
|
16597
|
-
return { serviceName, method, path:
|
|
17247
|
+
const path67 = rest.slice(secondSep + 1);
|
|
17248
|
+
if (!serviceName || !method || !path67) return null;
|
|
17249
|
+
return { serviceName, method, path: path67 };
|
|
16598
17250
|
}
|
|
16599
17251
|
|
|
16600
17252
|
// src/connectors/cloud-run/map.ts
|
|
@@ -16623,14 +17275,14 @@ function mapLogEntryToSignal2(entry) {
|
|
|
16623
17275
|
if (!req) return null;
|
|
16624
17276
|
if (typeof req.requestMethod !== "string" || req.requestMethod.length === 0) return null;
|
|
16625
17277
|
const method = req.requestMethod.toUpperCase();
|
|
16626
|
-
const
|
|
16627
|
-
if (
|
|
17278
|
+
const path67 = pathFromRequestUrl2(req.requestUrl);
|
|
17279
|
+
if (path67 === null) return null;
|
|
16628
17280
|
const timestamp = entry.timestamp;
|
|
16629
17281
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
16630
17282
|
const isError = typeof req.status === "number" && req.status >= ERROR_STATUS_THRESHOLD4;
|
|
16631
17283
|
return {
|
|
16632
17284
|
targetKind: CLOUD_RUN_TARGET_KIND,
|
|
16633
|
-
targetName: packCloudRunTargetName({ serviceName, method, path:
|
|
17285
|
+
targetName: packCloudRunTargetName({ serviceName, method, path: path67 }),
|
|
16634
17286
|
callCount: 1,
|
|
16635
17287
|
errorCount: isError ? 1 : 0,
|
|
16636
17288
|
lastObservedIso: timestamp
|
|
@@ -16646,14 +17298,14 @@ function mapLogEntriesToSignals2(entries) {
|
|
|
16646
17298
|
}
|
|
16647
17299
|
|
|
16648
17300
|
// src/connectors/cloud-run/resolve.ts
|
|
16649
|
-
import { EdgeType as EdgeType31, NodeType as
|
|
17301
|
+
import { EdgeType as EdgeType31, NodeType as NodeType33, infraId as infraId23 } from "@neat.is/types";
|
|
16650
17302
|
var CLOUD_RUN_SERVICE_INFRA_KIND = "cloud-run-service";
|
|
16651
17303
|
function findMatchingRouteNode2(graph, serviceName, method, normalizedPath) {
|
|
16652
17304
|
let found = null;
|
|
16653
17305
|
graph.forEachNode((_id, attrs) => {
|
|
16654
17306
|
if (found) return;
|
|
16655
17307
|
const node = attrs;
|
|
16656
|
-
if (node.type !==
|
|
17308
|
+
if (node.type !== NodeType33.RouteNode) return;
|
|
16657
17309
|
const route = attrs;
|
|
16658
17310
|
if (route.service !== serviceName || !route.pathTemplate) return;
|
|
16659
17311
|
if (normalizePathTemplate(route.pathTemplate) !== normalizedPath) return;
|
|
@@ -16668,14 +17320,14 @@ function createCloudRunResolveTarget(graph, config) {
|
|
|
16668
17320
|
if (signal.targetKind !== CLOUD_RUN_TARGET_KIND) return null;
|
|
16669
17321
|
const identity = parseCloudRunTargetName(signal.targetName);
|
|
16670
17322
|
if (!identity) return null;
|
|
16671
|
-
const { serviceName: gcpServiceName, method, path:
|
|
17323
|
+
const { serviceName: gcpServiceName, method, path: path67 } = identity;
|
|
16672
17324
|
const mappedService = config.serviceMap?.[gcpServiceName];
|
|
16673
17325
|
if (mappedService) {
|
|
16674
17326
|
const routeNodeId = findMatchingRouteNode2(
|
|
16675
17327
|
graph,
|
|
16676
17328
|
mappedService,
|
|
16677
17329
|
method,
|
|
16678
|
-
normalizePathTemplate(
|
|
17330
|
+
normalizePathTemplate(path67)
|
|
16679
17331
|
);
|
|
16680
17332
|
if (routeNodeId) {
|
|
16681
17333
|
return { targetNodeId: routeNodeId, serviceName: mappedService, edgeType: EdgeType31.CALLS };
|
|
@@ -16724,7 +17376,7 @@ function createCloudRunConnector(graph, config = {}) {
|
|
|
16724
17376
|
}
|
|
16725
17377
|
|
|
16726
17378
|
// src/connectors/render/index.ts
|
|
16727
|
-
import { EdgeType as EdgeType32, NodeType as
|
|
17379
|
+
import { EdgeType as EdgeType32, NodeType as NodeType34 } from "@neat.is/types";
|
|
16728
17380
|
|
|
16729
17381
|
// src/connectors/render/types.ts
|
|
16730
17382
|
function readRenderToken(credentials) {
|
|
@@ -16800,7 +17452,7 @@ function buildRenderRouteIndex(graph, serviceName) {
|
|
|
16800
17452
|
const out = [];
|
|
16801
17453
|
graph.forEachNode((_id, attrs) => {
|
|
16802
17454
|
const node = attrs;
|
|
16803
|
-
if (node.type !==
|
|
17455
|
+
if (node.type !== NodeType34.RouteNode) return;
|
|
16804
17456
|
const route = attrs;
|
|
16805
17457
|
if (route.service !== serviceName) return;
|
|
16806
17458
|
out.push({
|
|
@@ -17284,7 +17936,7 @@ function mapBuildsToSignals(builds, serviceName) {
|
|
|
17284
17936
|
}
|
|
17285
17937
|
|
|
17286
17938
|
// src/connectors/eas/resolve.ts
|
|
17287
|
-
import { EdgeType as EdgeType34, NodeType as
|
|
17939
|
+
import { EdgeType as EdgeType34, NodeType as NodeType35, parseFileId as parseFileId3 } from "@neat.is/types";
|
|
17288
17940
|
var NO_ENV2 = "unknown";
|
|
17289
17941
|
var EAS_JSON_PHASES = /* @__PURE__ */ new Set(["READ_EAS_JSON"]);
|
|
17290
17942
|
var APP_CONFIG_PHASES = /* @__PURE__ */ new Set([
|
|
@@ -17312,7 +17964,7 @@ function findConfigNode(graph, basenames, serviceName) {
|
|
|
17312
17964
|
graph.forEachNode((id, attrs) => {
|
|
17313
17965
|
if (scoped) return;
|
|
17314
17966
|
const node = attrs;
|
|
17315
|
-
if (node.type !==
|
|
17967
|
+
if (node.type !== NodeType35.ConfigNode) return;
|
|
17316
17968
|
if (typeof node.name !== "string" || !basenames.includes(node.name)) return;
|
|
17317
17969
|
if (anyMatch === null) anyMatch = id;
|
|
17318
17970
|
if (configNodeService(graph, id) === serviceName) scoped = id;
|
|
@@ -18924,4 +19576,4 @@ export {
|
|
|
18924
19576
|
deprovisionConnector,
|
|
18925
19577
|
buildApi
|
|
18926
19578
|
};
|
|
18927
|
-
//# sourceMappingURL=chunk-
|
|
19579
|
+
//# sourceMappingURL=chunk-T4UGEMWN.js.map
|