@neat.is/core 0.4.5 → 0.4.7
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-RBWL4HRB.js → chunk-7FTK47JQ.js} +277 -103
- package/dist/chunk-7FTK47JQ.js.map +1 -0
- package/dist/{chunk-6GXBAR3M.js → chunk-LS6NS72S.js} +37 -3
- package/dist/chunk-LS6NS72S.js.map +1 -0
- package/dist/cli.cjs +600 -219
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +294 -80
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +314 -115
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +314 -115
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +263 -98
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-6GXBAR3M.js.map +0 -1
- package/dist/chunk-RBWL4HRB.js.map +0 -1
package/dist/neatd.cjs
CHANGED
|
@@ -1607,6 +1607,86 @@ function hostFromUrl(u) {
|
|
|
1607
1607
|
function pickAddress(span) {
|
|
1608
1608
|
return pickAttr(span, "server.address", "net.peer.name", "net.host.name") ?? hostFromUrl(pickAttr(span, "url.full", "http.url"));
|
|
1609
1609
|
}
|
|
1610
|
+
var CODE_FILEPATH_ATTR = "code.filepath";
|
|
1611
|
+
var CODE_LINENO_ATTR = "code.lineno";
|
|
1612
|
+
var CODE_FUNCTION_ATTR = "code.function";
|
|
1613
|
+
function toPosix(p) {
|
|
1614
|
+
return p.split("\\").join("/");
|
|
1615
|
+
}
|
|
1616
|
+
function languageForExt(relPath) {
|
|
1617
|
+
const dot = relPath.lastIndexOf(".");
|
|
1618
|
+
if (dot === -1) return void 0;
|
|
1619
|
+
switch (relPath.slice(dot).toLowerCase()) {
|
|
1620
|
+
case ".py":
|
|
1621
|
+
return "python";
|
|
1622
|
+
case ".ts":
|
|
1623
|
+
case ".tsx":
|
|
1624
|
+
return "typescript";
|
|
1625
|
+
case ".js":
|
|
1626
|
+
case ".jsx":
|
|
1627
|
+
case ".mjs":
|
|
1628
|
+
case ".cjs":
|
|
1629
|
+
return "javascript";
|
|
1630
|
+
default:
|
|
1631
|
+
return void 0;
|
|
1632
|
+
}
|
|
1633
|
+
}
|
|
1634
|
+
function relPathForRuntimeFile(filepath, serviceNode) {
|
|
1635
|
+
let p = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1636
|
+
const root = serviceNode?.repoPath;
|
|
1637
|
+
if (root && root !== "." && root.length > 0) {
|
|
1638
|
+
const rootPosix = toPosix(root);
|
|
1639
|
+
const anchor = `/${rootPosix}/`;
|
|
1640
|
+
const idx = p.lastIndexOf(anchor);
|
|
1641
|
+
if (idx !== -1) return p.slice(idx + anchor.length);
|
|
1642
|
+
const base = rootPosix.split("/").filter(Boolean).pop();
|
|
1643
|
+
if (base) {
|
|
1644
|
+
const baseAnchor = `/${base}/`;
|
|
1645
|
+
const bidx = p.lastIndexOf(baseAnchor);
|
|
1646
|
+
if (bidx !== -1) return p.slice(bidx + baseAnchor.length);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
p = p.replace(/^[A-Za-z]:/, "").replace(/^\/+/, "");
|
|
1650
|
+
return p.length > 0 ? p : null;
|
|
1651
|
+
}
|
|
1652
|
+
function callSiteFromSpan(span, serviceNode) {
|
|
1653
|
+
const filepath = span.attributes[CODE_FILEPATH_ATTR];
|
|
1654
|
+
if (typeof filepath !== "string" || filepath.length === 0) return null;
|
|
1655
|
+
const relPath = relPathForRuntimeFile(filepath, serviceNode);
|
|
1656
|
+
if (!relPath) return null;
|
|
1657
|
+
const linenoRaw = span.attributes[CODE_LINENO_ATTR];
|
|
1658
|
+
const line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
|
|
1659
|
+
const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
|
|
1660
|
+
const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
|
|
1661
|
+
return { relPath, ...line !== void 0 ? { line } : {}, ...fn ? { fn } : {} };
|
|
1662
|
+
}
|
|
1663
|
+
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
1664
|
+
const fileNodeId = (0, import_types3.fileId)(serviceName, callSite.relPath);
|
|
1665
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
1666
|
+
const language = languageForExt(callSite.relPath);
|
|
1667
|
+
const node = {
|
|
1668
|
+
id: fileNodeId,
|
|
1669
|
+
type: import_types3.NodeType.FileNode,
|
|
1670
|
+
service: serviceName,
|
|
1671
|
+
path: callSite.relPath,
|
|
1672
|
+
...language ? { language } : {},
|
|
1673
|
+
discoveredVia: "otel"
|
|
1674
|
+
};
|
|
1675
|
+
graph.addNode(fileNodeId, node);
|
|
1676
|
+
}
|
|
1677
|
+
const containsId = makeObservedEdgeId(import_types3.EdgeType.CONTAINS, serviceNodeId, fileNodeId);
|
|
1678
|
+
if (!graph.hasEdge(containsId)) {
|
|
1679
|
+
const edge = {
|
|
1680
|
+
id: containsId,
|
|
1681
|
+
source: serviceNodeId,
|
|
1682
|
+
target: fileNodeId,
|
|
1683
|
+
type: import_types3.EdgeType.CONTAINS,
|
|
1684
|
+
provenance: import_types3.Provenance.OBSERVED
|
|
1685
|
+
};
|
|
1686
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
1687
|
+
}
|
|
1688
|
+
return fileNodeId;
|
|
1689
|
+
}
|
|
1610
1690
|
function makeObservedEdgeId(type, source, target) {
|
|
1611
1691
|
return (0, import_types3.observedEdgeId)(source, target, type);
|
|
1612
1692
|
}
|
|
@@ -1849,6 +1929,9 @@ async function handleSpan(ctx, span) {
|
|
|
1849
1929
|
const sourceId = ensureServiceNode(ctx.graph, span.service, env);
|
|
1850
1930
|
const isError = span.statusCode === 2;
|
|
1851
1931
|
cacheSpanService(span, nowMs);
|
|
1932
|
+
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
1933
|
+
const callSite = callSiteFromSpan(span, sourceServiceNode);
|
|
1934
|
+
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
1852
1935
|
let affectedNode = sourceId;
|
|
1853
1936
|
if (span.dbSystem) {
|
|
1854
1937
|
const host = pickAddress(span);
|
|
@@ -1858,7 +1941,7 @@ async function handleSpan(ctx, span) {
|
|
|
1858
1941
|
const result = upsertObservedEdge(
|
|
1859
1942
|
ctx.graph,
|
|
1860
1943
|
import_types3.EdgeType.CONNECTS_TO,
|
|
1861
|
-
|
|
1944
|
+
observedSource(),
|
|
1862
1945
|
targetId,
|
|
1863
1946
|
ts,
|
|
1864
1947
|
isError
|
|
@@ -1874,7 +1957,7 @@ async function handleSpan(ctx, span) {
|
|
|
1874
1957
|
upsertObservedEdge(
|
|
1875
1958
|
ctx.graph,
|
|
1876
1959
|
import_types3.EdgeType.CALLS,
|
|
1877
|
-
|
|
1960
|
+
observedSource(),
|
|
1878
1961
|
targetId,
|
|
1879
1962
|
ts,
|
|
1880
1963
|
isError
|
|
@@ -1886,7 +1969,7 @@ async function handleSpan(ctx, span) {
|
|
|
1886
1969
|
upsertObservedEdge(
|
|
1887
1970
|
ctx.graph,
|
|
1888
1971
|
import_types3.EdgeType.CALLS,
|
|
1889
|
-
|
|
1972
|
+
observedSource(),
|
|
1890
1973
|
frontierNodeId,
|
|
1891
1974
|
ts,
|
|
1892
1975
|
isError
|
|
@@ -3444,7 +3527,7 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3444
3527
|
|
|
3445
3528
|
// src/extract/calls/index.ts
|
|
3446
3529
|
init_cjs_shims();
|
|
3447
|
-
var
|
|
3530
|
+
var import_types15 = require("@neat.is/types");
|
|
3448
3531
|
|
|
3449
3532
|
// src/extract/calls/http.ts
|
|
3450
3533
|
init_cjs_shims();
|
|
@@ -3452,12 +3535,13 @@ var import_node_path20 = __toESM(require("path"), 1);
|
|
|
3452
3535
|
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3453
3536
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3454
3537
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3455
|
-
var
|
|
3538
|
+
var import_types10 = require("@neat.is/types");
|
|
3456
3539
|
|
|
3457
3540
|
// src/extract/calls/shared.ts
|
|
3458
3541
|
init_cjs_shims();
|
|
3459
3542
|
var import_node_fs13 = require("fs");
|
|
3460
3543
|
var import_node_path19 = __toESM(require("path"), 1);
|
|
3544
|
+
var import_types9 = require("@neat.is/types");
|
|
3461
3545
|
async function walkSourceFiles(dir) {
|
|
3462
3546
|
const out = [];
|
|
3463
3547
|
async function walk(current) {
|
|
@@ -3497,6 +3581,58 @@ function snippet(text, line) {
|
|
|
3497
3581
|
const lines = text.split("\n");
|
|
3498
3582
|
return (lines[line - 1] ?? "").trim();
|
|
3499
3583
|
}
|
|
3584
|
+
function toPosix2(p) {
|
|
3585
|
+
return p.split("\\").join("/");
|
|
3586
|
+
}
|
|
3587
|
+
function languageForPath(relPath) {
|
|
3588
|
+
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3589
|
+
case ".py":
|
|
3590
|
+
return "python";
|
|
3591
|
+
case ".ts":
|
|
3592
|
+
case ".tsx":
|
|
3593
|
+
return "typescript";
|
|
3594
|
+
case ".js":
|
|
3595
|
+
case ".jsx":
|
|
3596
|
+
case ".mjs":
|
|
3597
|
+
case ".cjs":
|
|
3598
|
+
return "javascript";
|
|
3599
|
+
default:
|
|
3600
|
+
return void 0;
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3604
|
+
let nodesAdded = 0;
|
|
3605
|
+
let edgesAdded = 0;
|
|
3606
|
+
const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
|
|
3607
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3608
|
+
const language = languageForPath(relPath);
|
|
3609
|
+
const node = {
|
|
3610
|
+
id: fileNodeId,
|
|
3611
|
+
type: import_types9.NodeType.FileNode,
|
|
3612
|
+
service: serviceName,
|
|
3613
|
+
path: relPath,
|
|
3614
|
+
...language ? { language } : {},
|
|
3615
|
+
discoveredVia: "static"
|
|
3616
|
+
};
|
|
3617
|
+
graph.addNode(fileNodeId, node);
|
|
3618
|
+
nodesAdded++;
|
|
3619
|
+
}
|
|
3620
|
+
const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
|
|
3621
|
+
if (!graph.hasEdge(containsId)) {
|
|
3622
|
+
const edge = {
|
|
3623
|
+
id: containsId,
|
|
3624
|
+
source: serviceNodeId,
|
|
3625
|
+
target: fileNodeId,
|
|
3626
|
+
type: import_types9.EdgeType.CONTAINS,
|
|
3627
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
3628
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3629
|
+
evidence: { file: relPath }
|
|
3630
|
+
};
|
|
3631
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3632
|
+
edgesAdded++;
|
|
3633
|
+
}
|
|
3634
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3635
|
+
}
|
|
3500
3636
|
|
|
3501
3637
|
// src/extract/calls/http.ts
|
|
3502
3638
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
@@ -3530,16 +3666,16 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
3530
3666
|
const tree = parser.parse(source);
|
|
3531
3667
|
const literals = [];
|
|
3532
3668
|
collectStringLiterals(tree.rootNode, literals);
|
|
3533
|
-
const
|
|
3669
|
+
const out = [];
|
|
3534
3670
|
for (const lit of literals) {
|
|
3535
3671
|
if (isInsideJsxExternalLink(lit.node)) continue;
|
|
3536
3672
|
for (const host of knownHosts) {
|
|
3537
3673
|
if (urlMatchesHost(lit.text, host)) {
|
|
3538
|
-
|
|
3674
|
+
out.push({ host, line: lit.node.startPosition.row + 1 });
|
|
3539
3675
|
}
|
|
3540
3676
|
}
|
|
3541
3677
|
}
|
|
3542
|
-
return
|
|
3678
|
+
return out;
|
|
3543
3679
|
}
|
|
3544
3680
|
function makeJsParser() {
|
|
3545
3681
|
const p = new import_tree_sitter.default();
|
|
@@ -3562,71 +3698,78 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3562
3698
|
hostToNodeId.set(import_node_path20.default.basename(service.dir), service.node.id);
|
|
3563
3699
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3564
3700
|
}
|
|
3701
|
+
let nodesAdded = 0;
|
|
3565
3702
|
let edgesAdded = 0;
|
|
3566
3703
|
for (const service of services) {
|
|
3567
3704
|
const files = await loadSourceFiles(service.dir);
|
|
3568
|
-
const
|
|
3705
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3569
3706
|
for (const file of files) {
|
|
3570
3707
|
if (isTestPath(file.path)) continue;
|
|
3571
3708
|
const parser = import_node_path20.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3572
|
-
let
|
|
3709
|
+
let sites;
|
|
3573
3710
|
try {
|
|
3574
|
-
|
|
3711
|
+
sites = callsFromSource(file.content, parser, knownHosts);
|
|
3575
3712
|
} catch (err) {
|
|
3576
3713
|
recordExtractionError("http call extraction", file.path, err);
|
|
3577
3714
|
continue;
|
|
3578
3715
|
}
|
|
3579
|
-
|
|
3580
|
-
|
|
3716
|
+
if (sites.length === 0) continue;
|
|
3717
|
+
const relFile = toPosix2(import_node_path20.default.relative(service.dir, file.path));
|
|
3718
|
+
for (const site of sites) {
|
|
3719
|
+
const targetId = hostToNodeId.get(site.host);
|
|
3581
3720
|
if (!targetId || targetId === service.node.id) continue;
|
|
3582
|
-
|
|
3583
|
-
|
|
3721
|
+
const dedupKey = `${relFile}|${targetId}`;
|
|
3722
|
+
if (seen.has(dedupKey)) continue;
|
|
3723
|
+
seen.add(dedupKey);
|
|
3724
|
+
const confidence = (0, import_types10.confidenceForExtracted)("hostname-shape-match");
|
|
3725
|
+
const ev = {
|
|
3726
|
+
file: relFile,
|
|
3727
|
+
line: site.line,
|
|
3728
|
+
snippet: snippet(file.content, site.line)
|
|
3729
|
+
};
|
|
3730
|
+
if (!(0, import_types10.passesExtractedFloor)(confidence)) {
|
|
3731
|
+
noteExtractedDropped({
|
|
3732
|
+
source: service.node.id,
|
|
3733
|
+
target: targetId,
|
|
3734
|
+
type: import_types10.EdgeType.CALLS,
|
|
3735
|
+
confidence,
|
|
3736
|
+
confidenceKind: "hostname-shape-match",
|
|
3737
|
+
evidence: ev
|
|
3738
|
+
});
|
|
3739
|
+
continue;
|
|
3740
|
+
}
|
|
3741
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
3742
|
+
graph,
|
|
3743
|
+
service.pkg.name,
|
|
3744
|
+
service.node.id,
|
|
3745
|
+
relFile
|
|
3746
|
+
);
|
|
3747
|
+
nodesAdded += n;
|
|
3748
|
+
edgesAdded += e;
|
|
3749
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types10.EdgeType.CALLS);
|
|
3750
|
+
if (!graph.hasEdge(edgeId)) {
|
|
3751
|
+
const edge = {
|
|
3752
|
+
id: edgeId,
|
|
3753
|
+
source: fileNodeId,
|
|
3754
|
+
target: targetId,
|
|
3755
|
+
type: import_types10.EdgeType.CALLS,
|
|
3756
|
+
provenance: import_types10.Provenance.EXTRACTED,
|
|
3757
|
+
confidence,
|
|
3758
|
+
evidence: ev
|
|
3759
|
+
};
|
|
3760
|
+
graph.addEdgeWithKey(edgeId, fileNodeId, targetId, edge);
|
|
3761
|
+
edgesAdded++;
|
|
3584
3762
|
}
|
|
3585
|
-
}
|
|
3586
|
-
}
|
|
3587
|
-
for (const [targetId, evidenceFile] of seenTargets) {
|
|
3588
|
-
const fileContent = files.find((f) => f.path === evidenceFile.file)?.content ?? "";
|
|
3589
|
-
const line = lineOf(fileContent, `//${evidenceFile.host}`);
|
|
3590
|
-
const confidence = (0, import_types9.confidenceForExtracted)("hostname-shape-match");
|
|
3591
|
-
const ev = {
|
|
3592
|
-
file: import_node_path20.default.relative(service.dir, evidenceFile.file),
|
|
3593
|
-
line,
|
|
3594
|
-
snippet: snippet(fileContent, line)
|
|
3595
|
-
};
|
|
3596
|
-
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, targetId, import_types9.EdgeType.CALLS);
|
|
3597
|
-
if (!(0, import_types9.passesExtractedFloor)(confidence)) {
|
|
3598
|
-
noteExtractedDropped({
|
|
3599
|
-
source: service.node.id,
|
|
3600
|
-
target: targetId,
|
|
3601
|
-
type: import_types9.EdgeType.CALLS,
|
|
3602
|
-
confidence,
|
|
3603
|
-
confidenceKind: "hostname-shape-match",
|
|
3604
|
-
evidence: ev
|
|
3605
|
-
});
|
|
3606
|
-
continue;
|
|
3607
|
-
}
|
|
3608
|
-
const edge = {
|
|
3609
|
-
id: edgeId,
|
|
3610
|
-
source: service.node.id,
|
|
3611
|
-
target: targetId,
|
|
3612
|
-
type: import_types9.EdgeType.CALLS,
|
|
3613
|
-
provenance: import_types9.Provenance.EXTRACTED,
|
|
3614
|
-
confidence,
|
|
3615
|
-
evidence: ev
|
|
3616
|
-
};
|
|
3617
|
-
if (!graph.hasEdge(edge.id)) {
|
|
3618
|
-
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
3619
|
-
edgesAdded++;
|
|
3620
3763
|
}
|
|
3621
3764
|
}
|
|
3622
3765
|
}
|
|
3623
|
-
return edgesAdded;
|
|
3766
|
+
return { nodesAdded, edgesAdded };
|
|
3624
3767
|
}
|
|
3625
3768
|
|
|
3626
3769
|
// src/extract/calls/kafka.ts
|
|
3627
3770
|
init_cjs_shims();
|
|
3628
3771
|
var import_node_path21 = __toESM(require("path"), 1);
|
|
3629
|
-
var
|
|
3772
|
+
var import_types11 = require("@neat.is/types");
|
|
3630
3773
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3631
3774
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
3632
3775
|
function findAll(re, text) {
|
|
@@ -3647,7 +3790,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3647
3790
|
seen.add(key);
|
|
3648
3791
|
const line = lineOf(file.content, topic);
|
|
3649
3792
|
out.push({
|
|
3650
|
-
infraId: (0,
|
|
3793
|
+
infraId: (0, import_types11.infraId)("kafka-topic", topic),
|
|
3651
3794
|
name: topic,
|
|
3652
3795
|
kind: "kafka-topic",
|
|
3653
3796
|
edgeType,
|
|
@@ -3670,7 +3813,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
3670
3813
|
// src/extract/calls/redis.ts
|
|
3671
3814
|
init_cjs_shims();
|
|
3672
3815
|
var import_node_path22 = __toESM(require("path"), 1);
|
|
3673
|
-
var
|
|
3816
|
+
var import_types12 = require("@neat.is/types");
|
|
3674
3817
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
3675
3818
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
3676
3819
|
const out = [];
|
|
@@ -3683,7 +3826,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3683
3826
|
seen.add(host);
|
|
3684
3827
|
const line = lineOf(file.content, host);
|
|
3685
3828
|
out.push({
|
|
3686
|
-
infraId: (0,
|
|
3829
|
+
infraId: (0, import_types12.infraId)("redis", host),
|
|
3687
3830
|
name: host,
|
|
3688
3831
|
kind: "redis",
|
|
3689
3832
|
edgeType: "CALLS",
|
|
@@ -3704,7 +3847,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
3704
3847
|
// src/extract/calls/aws.ts
|
|
3705
3848
|
init_cjs_shims();
|
|
3706
3849
|
var import_node_path23 = __toESM(require("path"), 1);
|
|
3707
|
-
var
|
|
3850
|
+
var import_types13 = require("@neat.is/types");
|
|
3708
3851
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3709
3852
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
3710
3853
|
function hasMarker(text, markers) {
|
|
@@ -3728,7 +3871,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3728
3871
|
seen.add(key);
|
|
3729
3872
|
const line = lineOf(file.content, name);
|
|
3730
3873
|
out.push({
|
|
3731
|
-
infraId: (0,
|
|
3874
|
+
infraId: (0, import_types13.infraId)(kind, name),
|
|
3732
3875
|
name,
|
|
3733
3876
|
kind,
|
|
3734
3877
|
edgeType: "CALLS",
|
|
@@ -3763,7 +3906,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
3763
3906
|
// src/extract/calls/grpc.ts
|
|
3764
3907
|
init_cjs_shims();
|
|
3765
3908
|
var import_node_path24 = __toESM(require("path"), 1);
|
|
3766
|
-
var
|
|
3909
|
+
var import_types14 = require("@neat.is/types");
|
|
3767
3910
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
3768
3911
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
3769
3912
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
@@ -3811,7 +3954,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3811
3954
|
const { kind } = classified;
|
|
3812
3955
|
const line = lineOf(file.content, m[0]);
|
|
3813
3956
|
out.push({
|
|
3814
|
-
infraId: (0,
|
|
3957
|
+
infraId: (0, import_types14.infraId)(kind, name),
|
|
3815
3958
|
name,
|
|
3816
3959
|
kind,
|
|
3817
3960
|
edgeType: "CALLS",
|
|
@@ -3833,11 +3976,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3833
3976
|
function edgeTypeFromEndpoint(ep) {
|
|
3834
3977
|
switch (ep.edgeType) {
|
|
3835
3978
|
case "PUBLISHES_TO":
|
|
3836
|
-
return
|
|
3979
|
+
return import_types15.EdgeType.PUBLISHES_TO;
|
|
3837
3980
|
case "CONSUMES_FROM":
|
|
3838
|
-
return
|
|
3981
|
+
return import_types15.EdgeType.CONSUMES_FROM;
|
|
3839
3982
|
default:
|
|
3840
|
-
return
|
|
3983
|
+
return import_types15.EdgeType.CALLS;
|
|
3841
3984
|
}
|
|
3842
3985
|
}
|
|
3843
3986
|
function isAwsKind(kind) {
|
|
@@ -3864,7 +4007,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3864
4007
|
if (!graph.hasNode(ep.infraId)) {
|
|
3865
4008
|
const node = {
|
|
3866
4009
|
id: ep.infraId,
|
|
3867
|
-
type:
|
|
4010
|
+
type: import_types15.NodeType.InfraNode,
|
|
3868
4011
|
name: ep.name,
|
|
3869
4012
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
3870
4013
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -3876,11 +4019,8 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3876
4019
|
nodesAdded++;
|
|
3877
4020
|
}
|
|
3878
4021
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
3879
|
-
const
|
|
3880
|
-
if (
|
|
3881
|
-
seenEdges.add(edgeId);
|
|
3882
|
-
const confidence = (0, import_types14.confidenceForExtracted)(ep.confidenceKind);
|
|
3883
|
-
if (!(0, import_types14.passesExtractedFloor)(confidence)) {
|
|
4022
|
+
const confidence = (0, import_types15.confidenceForExtracted)(ep.confidenceKind);
|
|
4023
|
+
if (!(0, import_types15.passesExtractedFloor)(confidence)) {
|
|
3884
4024
|
noteExtractedDropped({
|
|
3885
4025
|
source: service.node.id,
|
|
3886
4026
|
target: ep.infraId,
|
|
@@ -3891,13 +4031,25 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3891
4031
|
});
|
|
3892
4032
|
continue;
|
|
3893
4033
|
}
|
|
4034
|
+
const relFile = toPosix2(ep.evidence.file);
|
|
4035
|
+
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
4036
|
+
graph,
|
|
4037
|
+
service.pkg.name,
|
|
4038
|
+
service.node.id,
|
|
4039
|
+
relFile
|
|
4040
|
+
);
|
|
4041
|
+
nodesAdded += n;
|
|
4042
|
+
edgesAdded += e;
|
|
4043
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, ep.infraId, edgeType);
|
|
4044
|
+
if (seenEdges.has(edgeId)) continue;
|
|
4045
|
+
seenEdges.add(edgeId);
|
|
3894
4046
|
if (!graph.hasEdge(edgeId)) {
|
|
3895
4047
|
const edge = {
|
|
3896
4048
|
id: edgeId,
|
|
3897
|
-
source:
|
|
4049
|
+
source: fileNodeId,
|
|
3898
4050
|
target: ep.infraId,
|
|
3899
4051
|
type: edgeType,
|
|
3900
|
-
provenance:
|
|
4052
|
+
provenance: import_types15.Provenance.EXTRACTED,
|
|
3901
4053
|
confidence,
|
|
3902
4054
|
evidence: ep.evidence
|
|
3903
4055
|
};
|
|
@@ -3909,11 +4061,11 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
3909
4061
|
return { nodesAdded, edgesAdded };
|
|
3910
4062
|
}
|
|
3911
4063
|
async function addCallEdges(graph, services) {
|
|
3912
|
-
const
|
|
4064
|
+
const http = await addHttpCallEdges(graph, services);
|
|
3913
4065
|
const ext = await addExternalEndpointEdges(graph, services);
|
|
3914
4066
|
return {
|
|
3915
|
-
nodesAdded: ext.nodesAdded,
|
|
3916
|
-
edgesAdded:
|
|
4067
|
+
nodesAdded: http.nodesAdded + ext.nodesAdded,
|
|
4068
|
+
edgesAdded: http.edgesAdded + ext.edgesAdded
|
|
3917
4069
|
};
|
|
3918
4070
|
}
|
|
3919
4071
|
|
|
@@ -3923,15 +4075,15 @@ init_cjs_shims();
|
|
|
3923
4075
|
// src/extract/infra/docker-compose.ts
|
|
3924
4076
|
init_cjs_shims();
|
|
3925
4077
|
var import_node_path25 = __toESM(require("path"), 1);
|
|
3926
|
-
var
|
|
4078
|
+
var import_types17 = require("@neat.is/types");
|
|
3927
4079
|
|
|
3928
4080
|
// src/extract/infra/shared.ts
|
|
3929
4081
|
init_cjs_shims();
|
|
3930
|
-
var
|
|
4082
|
+
var import_types16 = require("@neat.is/types");
|
|
3931
4083
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
3932
4084
|
return {
|
|
3933
|
-
id: (0,
|
|
3934
|
-
type:
|
|
4085
|
+
id: (0, import_types16.infraId)(kind, name),
|
|
4086
|
+
type: import_types16.NodeType.InfraNode,
|
|
3935
4087
|
name,
|
|
3936
4088
|
provider,
|
|
3937
4089
|
kind,
|
|
@@ -4010,15 +4162,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4010
4162
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
4011
4163
|
const targetId = composeNameToNodeId.get(dep);
|
|
4012
4164
|
if (!targetId) continue;
|
|
4013
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
4165
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types17.EdgeType.DEPENDS_ON);
|
|
4014
4166
|
if (graph.hasEdge(edgeId)) continue;
|
|
4015
4167
|
const edge = {
|
|
4016
4168
|
id: edgeId,
|
|
4017
4169
|
source: sourceId,
|
|
4018
4170
|
target: targetId,
|
|
4019
|
-
type:
|
|
4020
|
-
provenance:
|
|
4021
|
-
confidence: (0,
|
|
4171
|
+
type: import_types17.EdgeType.DEPENDS_ON,
|
|
4172
|
+
provenance: import_types17.Provenance.EXTRACTED,
|
|
4173
|
+
confidence: (0, import_types17.confidenceForExtracted)("structural"),
|
|
4022
4174
|
evidence: { file: evidenceFile }
|
|
4023
4175
|
};
|
|
4024
4176
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4032,7 +4184,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4032
4184
|
init_cjs_shims();
|
|
4033
4185
|
var import_node_path26 = __toESM(require("path"), 1);
|
|
4034
4186
|
var import_node_fs14 = require("fs");
|
|
4035
|
-
var
|
|
4187
|
+
var import_types18 = require("@neat.is/types");
|
|
4036
4188
|
function runtimeImage(content) {
|
|
4037
4189
|
const lines = content.split("\n");
|
|
4038
4190
|
let last = null;
|
|
@@ -4071,15 +4223,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4071
4223
|
graph.addNode(node.id, node);
|
|
4072
4224
|
nodesAdded++;
|
|
4073
4225
|
}
|
|
4074
|
-
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, node.id,
|
|
4226
|
+
const edgeId = (0, import_types4.extractedEdgeId)(service.node.id, node.id, import_types18.EdgeType.RUNS_ON);
|
|
4075
4227
|
if (!graph.hasEdge(edgeId)) {
|
|
4076
4228
|
const edge = {
|
|
4077
4229
|
id: edgeId,
|
|
4078
4230
|
source: service.node.id,
|
|
4079
4231
|
target: node.id,
|
|
4080
|
-
type:
|
|
4081
|
-
provenance:
|
|
4082
|
-
confidence: (0,
|
|
4232
|
+
type: import_types18.EdgeType.RUNS_ON,
|
|
4233
|
+
provenance: import_types18.Provenance.EXTRACTED,
|
|
4234
|
+
confidence: (0, import_types18.confidenceForExtracted)("structural"),
|
|
4083
4235
|
evidence: {
|
|
4084
4236
|
file: import_node_path26.default.relative(scanPath, dockerfilePath).split(import_node_path26.default.sep).join("/")
|
|
4085
4237
|
}
|
|
@@ -4207,13 +4359,24 @@ var import_node_path30 = __toESM(require("path"), 1);
|
|
|
4207
4359
|
init_cjs_shims();
|
|
4208
4360
|
var import_node_fs17 = require("fs");
|
|
4209
4361
|
var import_node_path29 = __toESM(require("path"), 1);
|
|
4210
|
-
var
|
|
4362
|
+
var import_types19 = require("@neat.is/types");
|
|
4363
|
+
function dropOrphanedFileNodes(graph) {
|
|
4364
|
+
const orphans = [];
|
|
4365
|
+
graph.forEachNode((id, attrs) => {
|
|
4366
|
+
if (attrs.type !== import_types19.NodeType.FileNode) return;
|
|
4367
|
+
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
4368
|
+
orphans.push(id);
|
|
4369
|
+
}
|
|
4370
|
+
});
|
|
4371
|
+
for (const id of orphans) graph.dropNode(id);
|
|
4372
|
+
return orphans.length;
|
|
4373
|
+
}
|
|
4211
4374
|
function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
4212
4375
|
const toDrop = [];
|
|
4213
4376
|
const bases = [scanPath, ...serviceDirs];
|
|
4214
4377
|
graph.forEachEdge((id, attrs) => {
|
|
4215
4378
|
const edge = attrs;
|
|
4216
|
-
if (edge.provenance !==
|
|
4379
|
+
if (edge.provenance !== import_types19.Provenance.EXTRACTED) return;
|
|
4217
4380
|
const evidenceFile = edge.evidence?.file;
|
|
4218
4381
|
if (!evidenceFile) return;
|
|
4219
4382
|
if (import_node_path29.default.isAbsolute(evidenceFile)) {
|
|
@@ -4224,6 +4387,7 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
4224
4387
|
if (!found) toDrop.push(id);
|
|
4225
4388
|
});
|
|
4226
4389
|
for (const id of toDrop) graph.dropEdge(id);
|
|
4390
|
+
dropOrphanedFileNodes(graph);
|
|
4227
4391
|
return toDrop.length;
|
|
4228
4392
|
}
|
|
4229
4393
|
|
|
@@ -4293,7 +4457,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4293
4457
|
init_cjs_shims();
|
|
4294
4458
|
var import_node_fs18 = require("fs");
|
|
4295
4459
|
var import_node_path31 = __toESM(require("path"), 1);
|
|
4296
|
-
var
|
|
4460
|
+
var import_types20 = require("@neat.is/types");
|
|
4297
4461
|
var SCHEMA_VERSION = 4;
|
|
4298
4462
|
function migrateV1ToV2(payload) {
|
|
4299
4463
|
const nodes = payload.graph.nodes;
|
|
@@ -4315,12 +4479,12 @@ function migrateV2ToV3(payload) {
|
|
|
4315
4479
|
for (const edge of edges) {
|
|
4316
4480
|
const attrs = edge.attributes;
|
|
4317
4481
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
4318
|
-
attrs.provenance =
|
|
4482
|
+
attrs.provenance = import_types20.Provenance.OBSERVED;
|
|
4319
4483
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
4320
4484
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
4321
4485
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
4322
4486
|
if (type && source && target) {
|
|
4323
|
-
const newId = (0,
|
|
4487
|
+
const newId = (0, import_types20.observedEdgeId)(source, target, type);
|
|
4324
4488
|
attrs.id = newId;
|
|
4325
4489
|
if (edge.key) edge.key = newId;
|
|
4326
4490
|
}
|
|
@@ -4458,11 +4622,11 @@ var Projects = class {
|
|
|
4458
4622
|
init_cjs_shims();
|
|
4459
4623
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
4460
4624
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
4461
|
-
var
|
|
4625
|
+
var import_types23 = require("@neat.is/types");
|
|
4462
4626
|
|
|
4463
4627
|
// src/divergences.ts
|
|
4464
4628
|
init_cjs_shims();
|
|
4465
|
-
var
|
|
4629
|
+
var import_types21 = require("@neat.is/types");
|
|
4466
4630
|
function bucketKey(source, target, type) {
|
|
4467
4631
|
return `${type}|${source}|${target}`;
|
|
4468
4632
|
}
|
|
@@ -4470,22 +4634,22 @@ function bucketEdges(graph) {
|
|
|
4470
4634
|
const buckets = /* @__PURE__ */ new Map();
|
|
4471
4635
|
graph.forEachEdge((id, attrs) => {
|
|
4472
4636
|
const e = attrs;
|
|
4473
|
-
const parsed = (0,
|
|
4637
|
+
const parsed = (0, import_types21.parseEdgeId)(id);
|
|
4474
4638
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
4475
4639
|
const key = bucketKey(e.source, e.target, e.type);
|
|
4476
4640
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
4477
4641
|
switch (provenance) {
|
|
4478
|
-
case
|
|
4642
|
+
case import_types21.Provenance.EXTRACTED:
|
|
4479
4643
|
cur.extracted = e;
|
|
4480
4644
|
break;
|
|
4481
|
-
case
|
|
4645
|
+
case import_types21.Provenance.OBSERVED:
|
|
4482
4646
|
cur.observed = e;
|
|
4483
4647
|
break;
|
|
4484
|
-
case
|
|
4648
|
+
case import_types21.Provenance.INFERRED:
|
|
4485
4649
|
cur.inferred = e;
|
|
4486
4650
|
break;
|
|
4487
4651
|
default:
|
|
4488
|
-
if (e.provenance ===
|
|
4652
|
+
if (e.provenance === import_types21.Provenance.STALE) cur.stale = e;
|
|
4489
4653
|
}
|
|
4490
4654
|
buckets.set(key, cur);
|
|
4491
4655
|
});
|
|
@@ -4494,7 +4658,7 @@ function bucketEdges(graph) {
|
|
|
4494
4658
|
function nodeIsFrontier(graph, nodeId) {
|
|
4495
4659
|
if (!graph.hasNode(nodeId)) return false;
|
|
4496
4660
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4497
|
-
return attrs.type ===
|
|
4661
|
+
return attrs.type === import_types21.NodeType.FrontierNode;
|
|
4498
4662
|
}
|
|
4499
4663
|
function clampConfidence(n) {
|
|
4500
4664
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -4515,6 +4679,7 @@ function gradedConfidence(edge) {
|
|
|
4515
4679
|
}
|
|
4516
4680
|
function detectMissingDivergences(graph, bucket) {
|
|
4517
4681
|
const out = [];
|
|
4682
|
+
if (bucket.type === import_types21.EdgeType.CONTAINS) return out;
|
|
4518
4683
|
if (bucket.extracted && !bucket.observed) {
|
|
4519
4684
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
4520
4685
|
out.push({
|
|
@@ -4555,7 +4720,7 @@ function declaredHostFor(svc) {
|
|
|
4555
4720
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
4556
4721
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4557
4722
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4558
|
-
if (e.type ===
|
|
4723
|
+
if (e.type === import_types21.EdgeType.CONFIGURED_BY && e.provenance === import_types21.Provenance.EXTRACTED) {
|
|
4559
4724
|
return true;
|
|
4560
4725
|
}
|
|
4561
4726
|
}
|
|
@@ -4568,10 +4733,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
4568
4733
|
const out = [];
|
|
4569
4734
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4570
4735
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4571
|
-
if (edge.type !==
|
|
4572
|
-
if (edge.provenance !==
|
|
4736
|
+
if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
|
|
4737
|
+
if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
|
|
4573
4738
|
const target = graph.getNodeAttributes(edge.target);
|
|
4574
|
-
if (target.type !==
|
|
4739
|
+
if (target.type !== import_types21.NodeType.DatabaseNode) continue;
|
|
4575
4740
|
const observedHost = target.host?.trim();
|
|
4576
4741
|
if (!observedHost) continue;
|
|
4577
4742
|
if (observedHost === declaredHost) continue;
|
|
@@ -4593,10 +4758,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
4593
4758
|
const deps = svc.dependencies ?? {};
|
|
4594
4759
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4595
4760
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4596
|
-
if (edge.type !==
|
|
4597
|
-
if (edge.provenance !==
|
|
4761
|
+
if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
|
|
4762
|
+
if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
|
|
4598
4763
|
const target = graph.getNodeAttributes(edge.target);
|
|
4599
|
-
if (target.type !==
|
|
4764
|
+
if (target.type !== import_types21.NodeType.DatabaseNode) continue;
|
|
4600
4765
|
for (const pair of compatPairs()) {
|
|
4601
4766
|
if (pair.engine !== target.engine) continue;
|
|
4602
4767
|
const declared = deps[pair.driver];
|
|
@@ -4657,7 +4822,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4657
4822
|
}
|
|
4658
4823
|
graph.forEachNode((nodeId, attrs) => {
|
|
4659
4824
|
const n = attrs;
|
|
4660
|
-
if (n.type !==
|
|
4825
|
+
if (n.type !== import_types21.NodeType.ServiceNode) return;
|
|
4661
4826
|
const svc = n;
|
|
4662
4827
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
4663
4828
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -4690,7 +4855,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4690
4855
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4691
4856
|
return a.target.localeCompare(b.target);
|
|
4692
4857
|
});
|
|
4693
|
-
return
|
|
4858
|
+
return import_types21.DivergenceResultSchema.parse({
|
|
4694
4859
|
divergences: filtered,
|
|
4695
4860
|
totalAffected: filtered.length,
|
|
4696
4861
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4779,7 +4944,7 @@ init_cjs_shims();
|
|
|
4779
4944
|
var import_node_fs20 = require("fs");
|
|
4780
4945
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
4781
4946
|
var import_node_path33 = __toESM(require("path"), 1);
|
|
4782
|
-
var
|
|
4947
|
+
var import_types22 = require("@neat.is/types");
|
|
4783
4948
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
4784
4949
|
var LOCK_RETRY_MS = 50;
|
|
4785
4950
|
function neatHome() {
|
|
@@ -4850,10 +5015,10 @@ async function readRegistry() {
|
|
|
4850
5015
|
throw err;
|
|
4851
5016
|
}
|
|
4852
5017
|
const parsed = JSON.parse(raw);
|
|
4853
|
-
return
|
|
5018
|
+
return import_types22.RegistryFileSchema.parse(parsed);
|
|
4854
5019
|
}
|
|
4855
5020
|
async function writeRegistry(reg) {
|
|
4856
|
-
const validated =
|
|
5021
|
+
const validated = import_types22.RegistryFileSchema.parse(reg);
|
|
4857
5022
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
4858
5023
|
}
|
|
4859
5024
|
async function getProject(name) {
|
|
@@ -5075,11 +5240,11 @@ function registerRoutes(scope, ctx) {
|
|
|
5075
5240
|
const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
5076
5241
|
const parsed = [];
|
|
5077
5242
|
for (const c of candidates) {
|
|
5078
|
-
const r =
|
|
5243
|
+
const r = import_types23.DivergenceTypeSchema.safeParse(c);
|
|
5079
5244
|
if (!r.success) {
|
|
5080
5245
|
return reply.code(400).send({
|
|
5081
5246
|
error: `unknown divergence type "${c}"`,
|
|
5082
|
-
allowed:
|
|
5247
|
+
allowed: import_types23.DivergenceTypeSchema.options
|
|
5083
5248
|
});
|
|
5084
5249
|
}
|
|
5085
5250
|
parsed.push(r.data);
|
|
@@ -5292,7 +5457,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5292
5457
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
5293
5458
|
let violations = await log.readAll();
|
|
5294
5459
|
if (req2.query.severity) {
|
|
5295
|
-
const sev =
|
|
5460
|
+
const sev = import_types23.PolicySeveritySchema.safeParse(req2.query.severity);
|
|
5296
5461
|
if (!sev.success) {
|
|
5297
5462
|
return reply.code(400).send({
|
|
5298
5463
|
error: "invalid severity",
|
|
@@ -5309,7 +5474,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5309
5474
|
scope.post("/policies/check", async (req2, reply) => {
|
|
5310
5475
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
5311
5476
|
if (!proj) return;
|
|
5312
|
-
const parsed =
|
|
5477
|
+
const parsed = import_types23.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
|
|
5313
5478
|
if (!parsed.success) {
|
|
5314
5479
|
return reply.code(400).send({
|
|
5315
5480
|
error: "invalid /policies/check body",
|
|
@@ -5886,11 +6051,45 @@ async function startDaemon(opts = {}) {
|
|
|
5886
6051
|
});
|
|
5887
6052
|
};
|
|
5888
6053
|
process.on("SIGHUP", sighupHandler);
|
|
6054
|
+
const REGISTRY_RELOAD_DEBOUNCE_MS = 500;
|
|
6055
|
+
let registryWatcher = null;
|
|
6056
|
+
let reloadTimer = null;
|
|
6057
|
+
try {
|
|
6058
|
+
const regDir = import_node_path37.default.dirname(regPath);
|
|
6059
|
+
const regBase = import_node_path37.default.basename(regPath);
|
|
6060
|
+
registryWatcher = (0, import_node_fs22.watch)(regDir, (_eventType, filename) => {
|
|
6061
|
+
if (filename !== null && filename !== regBase) return;
|
|
6062
|
+
if (reloadTimer) clearTimeout(reloadTimer);
|
|
6063
|
+
reloadTimer = setTimeout(() => {
|
|
6064
|
+
reloadTimer = null;
|
|
6065
|
+
void reload().catch((err) => {
|
|
6066
|
+
console.warn(
|
|
6067
|
+
`neatd: registry-watch reload failed \u2014 ${err.message}`
|
|
6068
|
+
);
|
|
6069
|
+
});
|
|
6070
|
+
}, REGISTRY_RELOAD_DEBOUNCE_MS);
|
|
6071
|
+
});
|
|
6072
|
+
} catch (err) {
|
|
6073
|
+
console.warn(
|
|
6074
|
+
`neatd: failed to watch registry at ${regPath} \u2014 ${err.message}. Run \`neatd reload\` (or send SIGHUP) after registering new projects.`
|
|
6075
|
+
);
|
|
6076
|
+
}
|
|
5889
6077
|
let stopped = false;
|
|
5890
6078
|
const stop = async () => {
|
|
5891
6079
|
if (stopped) return;
|
|
5892
6080
|
stopped = true;
|
|
5893
6081
|
process.off("SIGHUP", sighupHandler);
|
|
6082
|
+
if (reloadTimer) {
|
|
6083
|
+
clearTimeout(reloadTimer);
|
|
6084
|
+
reloadTimer = null;
|
|
6085
|
+
}
|
|
6086
|
+
if (registryWatcher) {
|
|
6087
|
+
try {
|
|
6088
|
+
registryWatcher.close();
|
|
6089
|
+
} catch {
|
|
6090
|
+
}
|
|
6091
|
+
registryWatcher = null;
|
|
6092
|
+
}
|
|
5894
6093
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
5895
6094
|
});
|
|
5896
6095
|
if (restApp) await restApp.close().catch(() => {
|