@neat.is/core 0.4.10 → 0.4.12
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-RC3CIDZO.js → chunk-7JY6F7BY.js} +4 -2
- package/dist/{chunk-RC3CIDZO.js.map → chunk-7JY6F7BY.js.map} +1 -1
- package/dist/{chunk-J5CEKCTR.js → chunk-WDG4QEFO.js} +782 -192
- package/dist/chunk-WDG4QEFO.js.map +1 -0
- package/dist/cli.cjs +1387 -550
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +442 -192
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +831 -239
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +829 -237
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +884 -364
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +5 -2
- package/dist/chunk-J5CEKCTR.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -57,9 +57,9 @@ function mountBearerAuth(app, opts) {
|
|
|
57
57
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
58
58
|
const publicRead = opts.publicRead === true;
|
|
59
59
|
app.addHook("preHandler", (req, reply, done) => {
|
|
60
|
-
const
|
|
60
|
+
const path40 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
61
61
|
for (const suffix of suffixes) {
|
|
62
|
-
if (
|
|
62
|
+
if (path40 === suffix || path40.endsWith(suffix)) {
|
|
63
63
|
done();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
@@ -185,8 +185,8 @@ function reshapeGrpcRequest(req) {
|
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
187
|
function resolveProtoRoot() {
|
|
188
|
-
const here =
|
|
189
|
-
return
|
|
188
|
+
const here = import_node_path36.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
189
|
+
return import_node_path36.default.resolve(here, "..", "proto");
|
|
190
190
|
}
|
|
191
191
|
function loadTraceService() {
|
|
192
192
|
const protoRoot = resolveProtoRoot();
|
|
@@ -254,13 +254,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
254
254
|
})
|
|
255
255
|
};
|
|
256
256
|
}
|
|
257
|
-
var import_node_url,
|
|
257
|
+
var import_node_url, import_node_path36, import_node_crypto2, grpc, protoLoader;
|
|
258
258
|
var init_otel_grpc = __esm({
|
|
259
259
|
"src/otel-grpc.ts"() {
|
|
260
260
|
"use strict";
|
|
261
261
|
init_cjs_shims();
|
|
262
262
|
import_node_url = require("url");
|
|
263
|
-
|
|
263
|
+
import_node_path36 = __toESM(require("path"), 1);
|
|
264
264
|
import_node_crypto2 = require("crypto");
|
|
265
265
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
266
266
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -370,10 +370,10 @@ function parseOtlpRequest(body) {
|
|
|
370
370
|
return out;
|
|
371
371
|
}
|
|
372
372
|
function loadProtoRoot() {
|
|
373
|
-
const here =
|
|
374
|
-
const protoRoot =
|
|
373
|
+
const here = import_node_path37.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
374
|
+
const protoRoot = import_node_path37.default.resolve(here, "..", "proto");
|
|
375
375
|
const root = new import_protobufjs.default.Root();
|
|
376
|
-
root.resolvePath = (_origin, target) =>
|
|
376
|
+
root.resolvePath = (_origin, target) => import_node_path37.default.resolve(protoRoot, target);
|
|
377
377
|
root.loadSync(
|
|
378
378
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
379
379
|
{ keepCase: true }
|
|
@@ -574,12 +574,12 @@ function logSpanHandler(span) {
|
|
|
574
574
|
`otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
|
|
575
575
|
);
|
|
576
576
|
}
|
|
577
|
-
var
|
|
577
|
+
var import_node_path37, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
578
578
|
var init_otel = __esm({
|
|
579
579
|
"src/otel.ts"() {
|
|
580
580
|
"use strict";
|
|
581
581
|
init_cjs_shims();
|
|
582
|
-
|
|
582
|
+
import_node_path37 = __toESM(require("path"), 1);
|
|
583
583
|
import_node_url2 = require("url");
|
|
584
584
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
585
585
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -675,6 +675,7 @@ init_cjs_shims();
|
|
|
675
675
|
init_cjs_shims();
|
|
676
676
|
var import_node_fs3 = require("fs");
|
|
677
677
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
678
|
+
var sourceMapJs = __toESM(require("source-map-js"), 1);
|
|
678
679
|
|
|
679
680
|
// src/policy.ts
|
|
680
681
|
init_cjs_shims();
|
|
@@ -1116,19 +1117,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1116
1117
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1117
1118
|
let best = { path: [start], edges: [] };
|
|
1118
1119
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1119
|
-
function step(node,
|
|
1120
|
-
if (
|
|
1121
|
-
best = { path: [...
|
|
1120
|
+
function step(node, path40, edges) {
|
|
1121
|
+
if (path40.length > best.path.length) {
|
|
1122
|
+
best = { path: [...path40], edges: [...edges] };
|
|
1122
1123
|
}
|
|
1123
|
-
if (
|
|
1124
|
+
if (path40.length - 1 >= maxDepth) return;
|
|
1124
1125
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1125
1126
|
for (const [srcId, edge] of incoming) {
|
|
1126
1127
|
if (visited.has(srcId)) continue;
|
|
1127
1128
|
visited.add(srcId);
|
|
1128
|
-
|
|
1129
|
+
path40.push(srcId);
|
|
1129
1130
|
edges.push(edge);
|
|
1130
|
-
step(srcId,
|
|
1131
|
-
|
|
1131
|
+
step(srcId, path40, edges);
|
|
1132
|
+
path40.pop();
|
|
1132
1133
|
edges.pop();
|
|
1133
1134
|
visited.delete(srcId);
|
|
1134
1135
|
}
|
|
@@ -1679,6 +1680,14 @@ function warnUnidentifiedSpan(project) {
|
|
|
1679
1680
|
`[neatd] span lacked service.name; routed to 'unidentified' in project ${project}; check your OTel SDK config.`
|
|
1680
1681
|
);
|
|
1681
1682
|
}
|
|
1683
|
+
var noSourceMapWarnedServices = /* @__PURE__ */ new Set();
|
|
1684
|
+
function warnNoSourceMaps(serviceName) {
|
|
1685
|
+
if (noSourceMapWarnedServices.has(serviceName)) return;
|
|
1686
|
+
noSourceMapWarnedServices.add(serviceName);
|
|
1687
|
+
console.warn(
|
|
1688
|
+
`[neat] ${serviceName}: no .map files found under dist/; observed file edges will land on dist paths, not src. Set sourceMap: true in tsconfig to enable file-level reconciliation.`
|
|
1689
|
+
);
|
|
1690
|
+
}
|
|
1682
1691
|
function pickAttr(span, ...keys) {
|
|
1683
1692
|
for (const k of keys) {
|
|
1684
1693
|
const v = span.attributes[k];
|
|
@@ -1721,8 +1730,13 @@ function languageForExt(relPath) {
|
|
|
1721
1730
|
return void 0;
|
|
1722
1731
|
}
|
|
1723
1732
|
}
|
|
1724
|
-
function relPathForRuntimeFile(filepath, serviceNode) {
|
|
1733
|
+
function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
|
|
1725
1734
|
let p = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1735
|
+
if (scanPath && scanPath.length > 0) {
|
|
1736
|
+
const absRoot = toPosix(import_node_path3.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
|
|
1737
|
+
const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
|
|
1738
|
+
if (p.startsWith(anchor)) return p.slice(anchor.length);
|
|
1739
|
+
}
|
|
1726
1740
|
const root = serviceNode?.repoPath;
|
|
1727
1741
|
if (root && root !== "." && root.length > 0) {
|
|
1728
1742
|
const rootPosix = toPosix(root);
|
|
@@ -1739,16 +1753,65 @@ function relPathForRuntimeFile(filepath, serviceNode) {
|
|
|
1739
1753
|
p = p.replace(/^[A-Za-z]:/, "").replace(/^\/+/, "");
|
|
1740
1754
|
return p.length > 0 ? p : null;
|
|
1741
1755
|
}
|
|
1742
|
-
|
|
1756
|
+
var sourceMapCache = /* @__PURE__ */ new Map();
|
|
1757
|
+
function resolveDistToSrc(absFilepath, line) {
|
|
1758
|
+
if (!absFilepath.endsWith(".js")) return null;
|
|
1759
|
+
let entry = sourceMapCache.get(absFilepath);
|
|
1760
|
+
if (entry === void 0) {
|
|
1761
|
+
entry = null;
|
|
1762
|
+
const mapPath = `${absFilepath}.map`;
|
|
1763
|
+
try {
|
|
1764
|
+
if ((0, import_node_fs3.existsSync)(mapPath)) {
|
|
1765
|
+
const raw = JSON.parse((0, import_node_fs3.readFileSync)(mapPath, "utf8"));
|
|
1766
|
+
const consumer = new sourceMapJs.SourceMapConsumer(raw);
|
|
1767
|
+
entry = { consumer, dir: import_node_path3.default.dirname(mapPath) };
|
|
1768
|
+
}
|
|
1769
|
+
} catch {
|
|
1770
|
+
entry = null;
|
|
1771
|
+
}
|
|
1772
|
+
sourceMapCache.set(absFilepath, entry);
|
|
1773
|
+
}
|
|
1774
|
+
if (!entry) return null;
|
|
1775
|
+
try {
|
|
1776
|
+
const pos = entry.consumer.originalPositionFor({
|
|
1777
|
+
line: line !== void 0 && Number.isFinite(line) ? line : 1,
|
|
1778
|
+
column: 0
|
|
1779
|
+
});
|
|
1780
|
+
if (!pos || !pos.source) return null;
|
|
1781
|
+
const root = entry.consumer.sourceRoot ?? "";
|
|
1782
|
+
const resolved = import_node_path3.default.resolve(entry.dir, root, pos.source);
|
|
1783
|
+
return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
|
|
1784
|
+
} catch {
|
|
1785
|
+
return null;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
1743
1789
|
const filepath = span.attributes[CODE_FILEPATH_ATTR];
|
|
1744
1790
|
if (typeof filepath !== "string" || filepath.length === 0) return null;
|
|
1745
|
-
const relPath = relPathForRuntimeFile(filepath, serviceNode);
|
|
1746
|
-
if (!relPath) return null;
|
|
1747
1791
|
const linenoRaw = span.attributes[CODE_LINENO_ATTR];
|
|
1748
|
-
|
|
1792
|
+
let line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
|
|
1793
|
+
const abs = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1794
|
+
const resolved = resolveDistToSrc(abs, line);
|
|
1795
|
+
let effectivePath = filepath;
|
|
1796
|
+
let originalRelPath;
|
|
1797
|
+
if (resolved) {
|
|
1798
|
+
originalRelPath = relPathForRuntimeFile(filepath, serviceNode, scanPath) ?? void 0;
|
|
1799
|
+
effectivePath = resolved.filepath;
|
|
1800
|
+
if (resolved.line !== void 0) line = resolved.line;
|
|
1801
|
+
}
|
|
1802
|
+
const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
|
|
1803
|
+
if (!relPath) return null;
|
|
1804
|
+
if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
|
|
1805
|
+
warnNoSourceMaps(serviceNode.name);
|
|
1806
|
+
}
|
|
1749
1807
|
const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
|
|
1750
1808
|
const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
|
|
1751
|
-
return {
|
|
1809
|
+
return {
|
|
1810
|
+
relPath,
|
|
1811
|
+
...line !== void 0 ? { line } : {},
|
|
1812
|
+
...fn ? { fn } : {},
|
|
1813
|
+
...originalRelPath && originalRelPath !== relPath ? { originalRelPath } : {}
|
|
1814
|
+
};
|
|
1752
1815
|
}
|
|
1753
1816
|
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
1754
1817
|
const fileNodeId = (0, import_types3.fileId)(serviceName, callSite.relPath);
|
|
@@ -1760,6 +1823,7 @@ function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
|
1760
1823
|
service: serviceName,
|
|
1761
1824
|
path: callSite.relPath,
|
|
1762
1825
|
...language ? { language } : {},
|
|
1826
|
+
...callSite.originalRelPath ? { originalPath: callSite.originalRelPath } : {},
|
|
1763
1827
|
discoveredVia: "otel"
|
|
1764
1828
|
};
|
|
1765
1829
|
graph.addNode(fileNodeId, node);
|
|
@@ -1785,6 +1849,12 @@ function makeInferredEdgeId(type, source, target) {
|
|
|
1785
1849
|
}
|
|
1786
1850
|
var INFERRED_CONFIDENCE = 0.6;
|
|
1787
1851
|
var STITCH_MAX_DEPTH = 2;
|
|
1852
|
+
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
1853
|
+
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
1854
|
+
function spanMintsObservedEdge(kind) {
|
|
1855
|
+
if (kind === void 0 || kind === 0) return true;
|
|
1856
|
+
return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
|
|
1857
|
+
}
|
|
1788
1858
|
var PARENT_SPAN_CACHE_SIZE = 1e4;
|
|
1789
1859
|
var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1790
1860
|
var parentSpanCache = /* @__PURE__ */ new Map();
|
|
@@ -1891,7 +1961,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
1891
1961
|
graph.addNode(id, node);
|
|
1892
1962
|
return id;
|
|
1893
1963
|
}
|
|
1894
|
-
function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
1964
|
+
function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
|
|
1895
1965
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
1896
1966
|
const id = makeObservedEdgeId(type, source, target);
|
|
1897
1967
|
if (graph.hasEdge(id)) {
|
|
@@ -1928,7 +1998,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1928
1998
|
confidence: (0, import_types3.confidenceForObservedSignal)(signal),
|
|
1929
1999
|
lastObserved: ts,
|
|
1930
2000
|
callCount: 1,
|
|
1931
|
-
signal
|
|
2001
|
+
signal,
|
|
2002
|
+
// Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
|
|
2003
|
+
// Only set when code.filepath was present on the span — never fabricated.
|
|
2004
|
+
...evidence ? { evidence } : {}
|
|
1932
2005
|
};
|
|
1933
2006
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
1934
2007
|
return { edge, created: true };
|
|
@@ -2020,12 +2093,14 @@ async function handleSpan(ctx, span) {
|
|
|
2020
2093
|
const isError = span.statusCode === 2;
|
|
2021
2094
|
cacheSpanService(span, nowMs);
|
|
2022
2095
|
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
2023
|
-
const callSite = callSiteFromSpan(span, sourceServiceNode);
|
|
2096
|
+
const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
|
|
2024
2097
|
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
2098
|
+
const callSiteEvidence = callSite ? { file: callSite.relPath, ...callSite.line !== void 0 ? { line: callSite.line } : {} } : void 0;
|
|
2025
2099
|
let affectedNode = sourceId;
|
|
2100
|
+
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2026
2101
|
if (span.dbSystem) {
|
|
2027
2102
|
const host = pickAddress(span);
|
|
2028
|
-
if (host) {
|
|
2103
|
+
if (mintsFromCallerSide && host) {
|
|
2029
2104
|
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
2030
2105
|
const targetId = (0, import_types3.databaseId)(host);
|
|
2031
2106
|
const result = upsertObservedEdge(
|
|
@@ -2034,14 +2109,15 @@ async function handleSpan(ctx, span) {
|
|
|
2034
2109
|
observedSource(),
|
|
2035
2110
|
targetId,
|
|
2036
2111
|
ts,
|
|
2037
|
-
isError
|
|
2112
|
+
isError,
|
|
2113
|
+
callSiteEvidence
|
|
2038
2114
|
);
|
|
2039
2115
|
if (result) affectedNode = targetId;
|
|
2040
2116
|
}
|
|
2041
2117
|
} else {
|
|
2042
2118
|
const host = pickAddress(span);
|
|
2043
2119
|
let resolvedViaAddress = false;
|
|
2044
|
-
if (host && host !== span.service) {
|
|
2120
|
+
if (mintsFromCallerSide && host && host !== span.service) {
|
|
2045
2121
|
const targetId = resolveServiceId(ctx.graph, host, env);
|
|
2046
2122
|
if (targetId && targetId !== sourceId) {
|
|
2047
2123
|
upsertObservedEdge(
|
|
@@ -2050,7 +2126,8 @@ async function handleSpan(ctx, span) {
|
|
|
2050
2126
|
observedSource(),
|
|
2051
2127
|
targetId,
|
|
2052
2128
|
ts,
|
|
2053
|
-
isError
|
|
2129
|
+
isError,
|
|
2130
|
+
callSiteEvidence
|
|
2054
2131
|
);
|
|
2055
2132
|
affectedNode = targetId;
|
|
2056
2133
|
resolvedViaAddress = true;
|
|
@@ -2062,7 +2139,8 @@ async function handleSpan(ctx, span) {
|
|
|
2062
2139
|
observedSource(),
|
|
2063
2140
|
frontierNodeId,
|
|
2064
2141
|
ts,
|
|
2065
|
-
isError
|
|
2142
|
+
isError,
|
|
2143
|
+
callSiteEvidence
|
|
2066
2144
|
);
|
|
2067
2145
|
affectedNode = frontierNodeId;
|
|
2068
2146
|
resolvedViaAddress = true;
|
|
@@ -3013,14 +3091,111 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
3013
3091
|
|
|
3014
3092
|
// src/extract/databases/index.ts
|
|
3015
3093
|
init_cjs_shims();
|
|
3016
|
-
var
|
|
3094
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
3095
|
+
var import_types8 = require("@neat.is/types");
|
|
3096
|
+
|
|
3097
|
+
// src/extract/calls/shared.ts
|
|
3098
|
+
init_cjs_shims();
|
|
3099
|
+
var import_node_fs10 = require("fs");
|
|
3100
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
3017
3101
|
var import_types7 = require("@neat.is/types");
|
|
3102
|
+
async function walkSourceFiles(dir) {
|
|
3103
|
+
const out = [];
|
|
3104
|
+
async function walk(current) {
|
|
3105
|
+
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3106
|
+
for (const entry of entries) {
|
|
3107
|
+
const full = import_node_path10.default.join(current, entry.name);
|
|
3108
|
+
if (entry.isDirectory()) {
|
|
3109
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3110
|
+
if (await isPythonVenvDir(full)) continue;
|
|
3111
|
+
await walk(full);
|
|
3112
|
+
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry.name))) {
|
|
3113
|
+
out.push(full);
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
await walk(dir);
|
|
3118
|
+
return out;
|
|
3119
|
+
}
|
|
3120
|
+
async function loadSourceFiles(dir) {
|
|
3121
|
+
const paths = await walkSourceFiles(dir);
|
|
3122
|
+
const out = [];
|
|
3123
|
+
for (const p of paths) {
|
|
3124
|
+
try {
|
|
3125
|
+
const content = await import_node_fs10.promises.readFile(p, "utf8");
|
|
3126
|
+
out.push({ path: p, content });
|
|
3127
|
+
} catch {
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
return out;
|
|
3131
|
+
}
|
|
3132
|
+
function lineOf(text, needle) {
|
|
3133
|
+
const idx = text.indexOf(needle);
|
|
3134
|
+
if (idx < 0) return 1;
|
|
3135
|
+
return text.slice(0, idx).split("\n").length;
|
|
3136
|
+
}
|
|
3137
|
+
function snippet(text, line) {
|
|
3138
|
+
const lines = text.split("\n");
|
|
3139
|
+
return (lines[line - 1] ?? "").trim();
|
|
3140
|
+
}
|
|
3141
|
+
function toPosix2(p) {
|
|
3142
|
+
return p.split("\\").join("/");
|
|
3143
|
+
}
|
|
3144
|
+
function languageForPath(relPath) {
|
|
3145
|
+
switch (import_node_path10.default.extname(relPath).toLowerCase()) {
|
|
3146
|
+
case ".py":
|
|
3147
|
+
return "python";
|
|
3148
|
+
case ".ts":
|
|
3149
|
+
case ".tsx":
|
|
3150
|
+
return "typescript";
|
|
3151
|
+
case ".js":
|
|
3152
|
+
case ".jsx":
|
|
3153
|
+
case ".mjs":
|
|
3154
|
+
case ".cjs":
|
|
3155
|
+
return "javascript";
|
|
3156
|
+
default:
|
|
3157
|
+
return void 0;
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3161
|
+
let nodesAdded = 0;
|
|
3162
|
+
let edgesAdded = 0;
|
|
3163
|
+
const fileNodeId = (0, import_types7.fileId)(serviceName, relPath);
|
|
3164
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3165
|
+
const language = languageForPath(relPath);
|
|
3166
|
+
const node = {
|
|
3167
|
+
id: fileNodeId,
|
|
3168
|
+
type: import_types7.NodeType.FileNode,
|
|
3169
|
+
service: serviceName,
|
|
3170
|
+
path: relPath,
|
|
3171
|
+
...language ? { language } : {},
|
|
3172
|
+
discoveredVia: "static"
|
|
3173
|
+
};
|
|
3174
|
+
graph.addNode(fileNodeId, node);
|
|
3175
|
+
nodesAdded++;
|
|
3176
|
+
}
|
|
3177
|
+
const containsId = (0, import_types7.extractedEdgeId)(serviceNodeId, fileNodeId, import_types7.EdgeType.CONTAINS);
|
|
3178
|
+
if (!graph.hasEdge(containsId)) {
|
|
3179
|
+
const edge = {
|
|
3180
|
+
id: containsId,
|
|
3181
|
+
source: serviceNodeId,
|
|
3182
|
+
target: fileNodeId,
|
|
3183
|
+
type: import_types7.EdgeType.CONTAINS,
|
|
3184
|
+
provenance: import_types7.Provenance.EXTRACTED,
|
|
3185
|
+
confidence: (0, import_types7.confidenceForExtracted)("structural"),
|
|
3186
|
+
evidence: { file: relPath }
|
|
3187
|
+
};
|
|
3188
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3189
|
+
edgesAdded++;
|
|
3190
|
+
}
|
|
3191
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3192
|
+
}
|
|
3018
3193
|
|
|
3019
3194
|
// src/extract/databases/db-config-yaml.ts
|
|
3020
3195
|
init_cjs_shims();
|
|
3021
|
-
var
|
|
3196
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
3022
3197
|
async function parse(serviceDir) {
|
|
3023
|
-
const yamlPath =
|
|
3198
|
+
const yamlPath = import_node_path11.default.join(serviceDir, "db-config.yaml");
|
|
3024
3199
|
if (!await exists(yamlPath)) return [];
|
|
3025
3200
|
const raw = await readYaml(yamlPath);
|
|
3026
3201
|
return [
|
|
@@ -3038,13 +3213,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
3038
3213
|
|
|
3039
3214
|
// src/extract/databases/dotenv.ts
|
|
3040
3215
|
init_cjs_shims();
|
|
3041
|
-
var
|
|
3042
|
-
var
|
|
3216
|
+
var import_node_fs12 = require("fs");
|
|
3217
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
3043
3218
|
|
|
3044
3219
|
// src/extract/databases/shared.ts
|
|
3045
3220
|
init_cjs_shims();
|
|
3046
|
-
var
|
|
3047
|
-
var
|
|
3221
|
+
var import_node_fs11 = require("fs");
|
|
3222
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
3048
3223
|
function schemeToEngine(scheme) {
|
|
3049
3224
|
const s = scheme.toLowerCase().split("+")[0];
|
|
3050
3225
|
switch (s) {
|
|
@@ -3083,14 +3258,14 @@ function parseConnectionString(url) {
|
|
|
3083
3258
|
}
|
|
3084
3259
|
async function readIfExists(filePath) {
|
|
3085
3260
|
try {
|
|
3086
|
-
return await
|
|
3261
|
+
return await import_node_fs11.promises.readFile(filePath, "utf8");
|
|
3087
3262
|
} catch {
|
|
3088
3263
|
return null;
|
|
3089
3264
|
}
|
|
3090
3265
|
}
|
|
3091
3266
|
async function findFirst(serviceDir, candidates) {
|
|
3092
3267
|
for (const rel of candidates) {
|
|
3093
|
-
const abs =
|
|
3268
|
+
const abs = import_node_path12.default.join(serviceDir, rel);
|
|
3094
3269
|
const content = await readIfExists(abs);
|
|
3095
3270
|
if (content !== null) return abs;
|
|
3096
3271
|
}
|
|
@@ -3141,15 +3316,15 @@ function parseDotenvLine(line) {
|
|
|
3141
3316
|
return { key, value };
|
|
3142
3317
|
}
|
|
3143
3318
|
async function parse2(serviceDir) {
|
|
3144
|
-
const entries = await
|
|
3319
|
+
const entries = await import_node_fs12.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
3145
3320
|
const configs = [];
|
|
3146
3321
|
const seen = /* @__PURE__ */ new Set();
|
|
3147
3322
|
for (const entry of entries) {
|
|
3148
3323
|
if (!entry.isFile()) continue;
|
|
3149
3324
|
const match = isConfigFile(entry.name);
|
|
3150
3325
|
if (!match.match || match.fileType !== "env") continue;
|
|
3151
|
-
const filePath =
|
|
3152
|
-
const content = await
|
|
3326
|
+
const filePath = import_node_path13.default.join(serviceDir, entry.name);
|
|
3327
|
+
const content = await import_node_fs12.promises.readFile(filePath, "utf8");
|
|
3153
3328
|
for (const line of content.split("\n")) {
|
|
3154
3329
|
const parsed = parseDotenvLine(line);
|
|
3155
3330
|
if (!parsed) continue;
|
|
@@ -3168,9 +3343,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
3168
3343
|
|
|
3169
3344
|
// src/extract/databases/prisma.ts
|
|
3170
3345
|
init_cjs_shims();
|
|
3171
|
-
var
|
|
3346
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
3172
3347
|
async function parse3(serviceDir) {
|
|
3173
|
-
const schemaPath =
|
|
3348
|
+
const schemaPath = import_node_path14.default.join(serviceDir, "prisma", "schema.prisma");
|
|
3174
3349
|
const content = await readIfExists(schemaPath);
|
|
3175
3350
|
if (!content) return [];
|
|
3176
3351
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -3302,10 +3477,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
3302
3477
|
|
|
3303
3478
|
// src/extract/databases/ormconfig.ts
|
|
3304
3479
|
init_cjs_shims();
|
|
3305
|
-
var
|
|
3480
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
3306
3481
|
async function parse6(serviceDir) {
|
|
3307
3482
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
3308
|
-
const abs =
|
|
3483
|
+
const abs = import_node_path15.default.join(serviceDir, candidate);
|
|
3309
3484
|
if (!await exists(abs)) continue;
|
|
3310
3485
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
3311
3486
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -3365,9 +3540,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
3365
3540
|
|
|
3366
3541
|
// src/extract/databases/sequelize.ts
|
|
3367
3542
|
init_cjs_shims();
|
|
3368
|
-
var
|
|
3543
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
3369
3544
|
async function parse8(serviceDir) {
|
|
3370
|
-
const configPath =
|
|
3545
|
+
const configPath = import_node_path16.default.join(serviceDir, "config", "config.json");
|
|
3371
3546
|
if (!await exists(configPath)) return [];
|
|
3372
3547
|
const raw = await readJson(configPath);
|
|
3373
3548
|
const out = [];
|
|
@@ -3394,7 +3569,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
3394
3569
|
|
|
3395
3570
|
// src/extract/databases/docker-compose.ts
|
|
3396
3571
|
init_cjs_shims();
|
|
3397
|
-
var
|
|
3572
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
3398
3573
|
function portFromService(svc) {
|
|
3399
3574
|
for (const raw of svc.ports ?? []) {
|
|
3400
3575
|
const str = String(raw);
|
|
@@ -3421,7 +3596,7 @@ function databaseFromEnv(svc) {
|
|
|
3421
3596
|
}
|
|
3422
3597
|
async function parse9(serviceDir) {
|
|
3423
3598
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3424
|
-
const abs =
|
|
3599
|
+
const abs = import_node_path17.default.join(serviceDir, name);
|
|
3425
3600
|
if (!await exists(abs)) continue;
|
|
3426
3601
|
const raw = await readYaml(abs);
|
|
3427
3602
|
if (!raw?.services) return [];
|
|
@@ -3462,8 +3637,8 @@ function compatibleDriversFor(engine) {
|
|
|
3462
3637
|
}
|
|
3463
3638
|
function toDatabaseNode(config) {
|
|
3464
3639
|
return {
|
|
3465
|
-
id: (0,
|
|
3466
|
-
type:
|
|
3640
|
+
id: (0, import_types8.databaseId)(config.host),
|
|
3641
|
+
type: import_types8.NodeType.DatabaseNode,
|
|
3467
3642
|
name: config.database || config.host,
|
|
3468
3643
|
engine: config.engine,
|
|
3469
3644
|
engineVersion: config.engineVersion,
|
|
@@ -3593,19 +3768,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3593
3768
|
discoveredVia: mergedDiscoveredVia
|
|
3594
3769
|
});
|
|
3595
3770
|
}
|
|
3771
|
+
const relConfigFile = toPosix2(import_node_path18.default.relative(service.dir, config.sourceFile));
|
|
3772
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
3773
|
+
graph,
|
|
3774
|
+
service.pkg.name,
|
|
3775
|
+
service.node.id,
|
|
3776
|
+
relConfigFile
|
|
3777
|
+
);
|
|
3778
|
+
nodesAdded += fn;
|
|
3779
|
+
edgesAdded += fe;
|
|
3780
|
+
const evidenceFile = toPosix2(import_node_path18.default.relative(scanPath, config.sourceFile));
|
|
3596
3781
|
const edge = {
|
|
3597
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3598
|
-
source:
|
|
3782
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types8.EdgeType.CONNECTS_TO),
|
|
3783
|
+
source: fileNodeId,
|
|
3599
3784
|
target: dbNode.id,
|
|
3600
|
-
type:
|
|
3601
|
-
provenance:
|
|
3602
|
-
confidence: (0,
|
|
3603
|
-
|
|
3604
|
-
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3605
|
-
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
3606
|
-
evidence: {
|
|
3607
|
-
file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
|
|
3608
|
-
}
|
|
3785
|
+
type: import_types8.EdgeType.CONNECTS_TO,
|
|
3786
|
+
provenance: import_types8.Provenance.EXTRACTED,
|
|
3787
|
+
confidence: (0, import_types8.confidenceForExtracted)("structural"),
|
|
3788
|
+
evidence: { file: evidenceFile }
|
|
3609
3789
|
};
|
|
3610
3790
|
if (!graph.hasEdge(edge.id)) {
|
|
3611
3791
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3631,15 +3811,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3631
3811
|
|
|
3632
3812
|
// src/extract/configs.ts
|
|
3633
3813
|
init_cjs_shims();
|
|
3634
|
-
var
|
|
3635
|
-
var
|
|
3636
|
-
var
|
|
3814
|
+
var import_node_fs13 = require("fs");
|
|
3815
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3816
|
+
var import_types9 = require("@neat.is/types");
|
|
3637
3817
|
async function walkConfigFiles(dir) {
|
|
3638
3818
|
const out = [];
|
|
3639
3819
|
async function walk(current) {
|
|
3640
|
-
const entries = await
|
|
3820
|
+
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true });
|
|
3641
3821
|
for (const entry of entries) {
|
|
3642
|
-
const full =
|
|
3822
|
+
const full = import_node_path19.default.join(current, entry.name);
|
|
3643
3823
|
if (entry.isDirectory()) {
|
|
3644
3824
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3645
3825
|
if (await isPythonVenvDir(full)) continue;
|
|
@@ -3658,26 +3838,35 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3658
3838
|
for (const service of services) {
|
|
3659
3839
|
const configFiles = await walkConfigFiles(service.dir);
|
|
3660
3840
|
for (const file of configFiles) {
|
|
3661
|
-
const relPath =
|
|
3841
|
+
const relPath = import_node_path19.default.relative(scanPath, file);
|
|
3662
3842
|
const node = {
|
|
3663
|
-
id: (0,
|
|
3664
|
-
type:
|
|
3665
|
-
name:
|
|
3843
|
+
id: (0, import_types9.configId)(relPath),
|
|
3844
|
+
type: import_types9.NodeType.ConfigNode,
|
|
3845
|
+
name: import_node_path19.default.basename(file),
|
|
3666
3846
|
path: relPath,
|
|
3667
|
-
fileType: isConfigFile(
|
|
3847
|
+
fileType: isConfigFile(import_node_path19.default.basename(file)).fileType
|
|
3668
3848
|
};
|
|
3669
3849
|
if (!graph.hasNode(node.id)) {
|
|
3670
3850
|
graph.addNode(node.id, node);
|
|
3671
3851
|
nodesAdded++;
|
|
3672
3852
|
}
|
|
3853
|
+
const relToService = toPosix2(import_node_path19.default.relative(service.dir, file));
|
|
3854
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
3855
|
+
graph,
|
|
3856
|
+
service.pkg.name,
|
|
3857
|
+
service.node.id,
|
|
3858
|
+
relToService
|
|
3859
|
+
);
|
|
3860
|
+
nodesAdded += fn;
|
|
3861
|
+
edgesAdded += fe;
|
|
3673
3862
|
const edge = {
|
|
3674
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3675
|
-
source:
|
|
3863
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types9.EdgeType.CONFIGURED_BY),
|
|
3864
|
+
source: fileNodeId,
|
|
3676
3865
|
target: node.id,
|
|
3677
|
-
type:
|
|
3678
|
-
provenance:
|
|
3679
|
-
confidence: (0,
|
|
3680
|
-
evidence: { file: relPath.split(
|
|
3866
|
+
type: import_types9.EdgeType.CONFIGURED_BY,
|
|
3867
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
3868
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3869
|
+
evidence: { file: relPath.split(import_node_path19.default.sep).join("/") }
|
|
3681
3870
|
};
|
|
3682
3871
|
if (!graph.hasEdge(edge.id)) {
|
|
3683
3872
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3699,105 +3888,6 @@ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
|
3699
3888
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3700
3889
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3701
3890
|
var import_types10 = require("@neat.is/types");
|
|
3702
|
-
|
|
3703
|
-
// src/extract/calls/shared.ts
|
|
3704
|
-
init_cjs_shims();
|
|
3705
|
-
var import_node_fs13 = require("fs");
|
|
3706
|
-
var import_node_path19 = __toESM(require("path"), 1);
|
|
3707
|
-
var import_types9 = require("@neat.is/types");
|
|
3708
|
-
async function walkSourceFiles(dir) {
|
|
3709
|
-
const out = [];
|
|
3710
|
-
async function walk(current) {
|
|
3711
|
-
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3712
|
-
for (const entry of entries) {
|
|
3713
|
-
const full = import_node_path19.default.join(current, entry.name);
|
|
3714
|
-
if (entry.isDirectory()) {
|
|
3715
|
-
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3716
|
-
if (await isPythonVenvDir(full)) continue;
|
|
3717
|
-
await walk(full);
|
|
3718
|
-
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry.name))) {
|
|
3719
|
-
out.push(full);
|
|
3720
|
-
}
|
|
3721
|
-
}
|
|
3722
|
-
}
|
|
3723
|
-
await walk(dir);
|
|
3724
|
-
return out;
|
|
3725
|
-
}
|
|
3726
|
-
async function loadSourceFiles(dir) {
|
|
3727
|
-
const paths = await walkSourceFiles(dir);
|
|
3728
|
-
const out = [];
|
|
3729
|
-
for (const p of paths) {
|
|
3730
|
-
try {
|
|
3731
|
-
const content = await import_node_fs13.promises.readFile(p, "utf8");
|
|
3732
|
-
out.push({ path: p, content });
|
|
3733
|
-
} catch {
|
|
3734
|
-
}
|
|
3735
|
-
}
|
|
3736
|
-
return out;
|
|
3737
|
-
}
|
|
3738
|
-
function lineOf(text, needle) {
|
|
3739
|
-
const idx = text.indexOf(needle);
|
|
3740
|
-
if (idx < 0) return 1;
|
|
3741
|
-
return text.slice(0, idx).split("\n").length;
|
|
3742
|
-
}
|
|
3743
|
-
function snippet(text, line) {
|
|
3744
|
-
const lines = text.split("\n");
|
|
3745
|
-
return (lines[line - 1] ?? "").trim();
|
|
3746
|
-
}
|
|
3747
|
-
function toPosix2(p) {
|
|
3748
|
-
return p.split("\\").join("/");
|
|
3749
|
-
}
|
|
3750
|
-
function languageForPath(relPath) {
|
|
3751
|
-
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3752
|
-
case ".py":
|
|
3753
|
-
return "python";
|
|
3754
|
-
case ".ts":
|
|
3755
|
-
case ".tsx":
|
|
3756
|
-
return "typescript";
|
|
3757
|
-
case ".js":
|
|
3758
|
-
case ".jsx":
|
|
3759
|
-
case ".mjs":
|
|
3760
|
-
case ".cjs":
|
|
3761
|
-
return "javascript";
|
|
3762
|
-
default:
|
|
3763
|
-
return void 0;
|
|
3764
|
-
}
|
|
3765
|
-
}
|
|
3766
|
-
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3767
|
-
let nodesAdded = 0;
|
|
3768
|
-
let edgesAdded = 0;
|
|
3769
|
-
const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
|
|
3770
|
-
if (!graph.hasNode(fileNodeId)) {
|
|
3771
|
-
const language = languageForPath(relPath);
|
|
3772
|
-
const node = {
|
|
3773
|
-
id: fileNodeId,
|
|
3774
|
-
type: import_types9.NodeType.FileNode,
|
|
3775
|
-
service: serviceName,
|
|
3776
|
-
path: relPath,
|
|
3777
|
-
...language ? { language } : {},
|
|
3778
|
-
discoveredVia: "static"
|
|
3779
|
-
};
|
|
3780
|
-
graph.addNode(fileNodeId, node);
|
|
3781
|
-
nodesAdded++;
|
|
3782
|
-
}
|
|
3783
|
-
const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
|
|
3784
|
-
if (!graph.hasEdge(containsId)) {
|
|
3785
|
-
const edge = {
|
|
3786
|
-
id: containsId,
|
|
3787
|
-
source: serviceNodeId,
|
|
3788
|
-
target: fileNodeId,
|
|
3789
|
-
type: import_types9.EdgeType.CONTAINS,
|
|
3790
|
-
provenance: import_types9.Provenance.EXTRACTED,
|
|
3791
|
-
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3792
|
-
evidence: { file: relPath }
|
|
3793
|
-
};
|
|
3794
|
-
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3795
|
-
edgesAdded++;
|
|
3796
|
-
}
|
|
3797
|
-
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3798
|
-
}
|
|
3799
|
-
|
|
3800
|
-
// src/extract/calls/http.ts
|
|
3801
3891
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
3802
3892
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
3803
3893
|
function isInsideJsxExternalLink(node) {
|
|
@@ -3825,8 +3915,14 @@ function collectStringLiterals(node, out) {
|
|
|
3825
3915
|
if (child) collectStringLiterals(child, out);
|
|
3826
3916
|
}
|
|
3827
3917
|
}
|
|
3918
|
+
var PARSE_CHUNK = 16384;
|
|
3919
|
+
function parseSource(parser, source) {
|
|
3920
|
+
return parser.parse(
|
|
3921
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
|
|
3922
|
+
);
|
|
3923
|
+
}
|
|
3828
3924
|
function callsFromSource(source, parser, knownHosts) {
|
|
3829
|
-
const tree = parser
|
|
3925
|
+
const tree = parseSource(parser, source);
|
|
3830
3926
|
const literals = [];
|
|
3831
3927
|
collectStringLiterals(tree.rootNode, literals);
|
|
3832
3928
|
const out = [];
|
|
@@ -4075,6 +4171,7 @@ var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-
|
|
|
4075
4171
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
4076
4172
|
function isLikelyAddress(value) {
|
|
4077
4173
|
if (!value) return false;
|
|
4174
|
+
if (/\s/.test(value) || value.startsWith("{")) return false;
|
|
4078
4175
|
return /:\d{2,5}$/.test(value) || value.includes(".");
|
|
4079
4176
|
}
|
|
4080
4177
|
function normaliseForMatch(s) {
|
|
@@ -4386,17 +4483,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4386
4483
|
graph.addNode(node.id, node);
|
|
4387
4484
|
nodesAdded++;
|
|
4388
4485
|
}
|
|
4389
|
-
const
|
|
4486
|
+
const relDockerfile = toPosix2(import_node_path26.default.relative(service.dir, dockerfilePath));
|
|
4487
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4488
|
+
graph,
|
|
4489
|
+
service.pkg.name,
|
|
4490
|
+
service.node.id,
|
|
4491
|
+
relDockerfile
|
|
4492
|
+
);
|
|
4493
|
+
nodesAdded += fn;
|
|
4494
|
+
edgesAdded += fe;
|
|
4495
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types18.EdgeType.RUNS_ON);
|
|
4390
4496
|
if (!graph.hasEdge(edgeId)) {
|
|
4391
4497
|
const edge = {
|
|
4392
4498
|
id: edgeId,
|
|
4393
|
-
source:
|
|
4499
|
+
source: fileNodeId,
|
|
4394
4500
|
target: node.id,
|
|
4395
4501
|
type: import_types18.EdgeType.RUNS_ON,
|
|
4396
4502
|
provenance: import_types18.Provenance.EXTRACTED,
|
|
4397
4503
|
confidence: (0, import_types18.confidenceForExtracted)("structural"),
|
|
4398
4504
|
evidence: {
|
|
4399
|
-
file: import_node_path26.default.relative(scanPath, dockerfilePath)
|
|
4505
|
+
file: toPosix2(import_node_path26.default.relative(scanPath, dockerfilePath))
|
|
4400
4506
|
}
|
|
4401
4507
|
};
|
|
4402
4508
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4735,6 +4841,321 @@ var import_fastify = __toESM(require("fastify"), 1);
|
|
|
4735
4841
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
4736
4842
|
var import_types23 = require("@neat.is/types");
|
|
4737
4843
|
|
|
4844
|
+
// src/extend/index.ts
|
|
4845
|
+
init_cjs_shims();
|
|
4846
|
+
var import_node_fs20 = require("fs");
|
|
4847
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
4848
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
4849
|
+
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
4850
|
+
|
|
4851
|
+
// src/installers/package-manager.ts
|
|
4852
|
+
init_cjs_shims();
|
|
4853
|
+
var import_node_fs19 = require("fs");
|
|
4854
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
4855
|
+
var import_node_child_process = require("child_process");
|
|
4856
|
+
var LOCKFILE_PRIORITY = [
|
|
4857
|
+
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
4858
|
+
{ lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
|
|
4859
|
+
{ lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
|
|
4860
|
+
{
|
|
4861
|
+
lockfile: "package-lock.json",
|
|
4862
|
+
pm: "npm",
|
|
4863
|
+
args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
|
|
4864
|
+
}
|
|
4865
|
+
];
|
|
4866
|
+
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
4867
|
+
async function exists2(p) {
|
|
4868
|
+
try {
|
|
4869
|
+
await import_node_fs19.promises.access(p);
|
|
4870
|
+
return true;
|
|
4871
|
+
} catch {
|
|
4872
|
+
return false;
|
|
4873
|
+
}
|
|
4874
|
+
}
|
|
4875
|
+
async function detectPackageManager(serviceDir) {
|
|
4876
|
+
let dir = import_node_path32.default.resolve(serviceDir);
|
|
4877
|
+
const stops = /* @__PURE__ */ new Set();
|
|
4878
|
+
for (let i = 0; i < 64; i++) {
|
|
4879
|
+
if (stops.has(dir)) break;
|
|
4880
|
+
stops.add(dir);
|
|
4881
|
+
for (const candidate of LOCKFILE_PRIORITY) {
|
|
4882
|
+
const lockPath = import_node_path32.default.join(dir, candidate.lockfile);
|
|
4883
|
+
if (await exists2(lockPath)) {
|
|
4884
|
+
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
4885
|
+
}
|
|
4886
|
+
}
|
|
4887
|
+
const parent = import_node_path32.default.dirname(dir);
|
|
4888
|
+
if (parent === dir) break;
|
|
4889
|
+
dir = parent;
|
|
4890
|
+
}
|
|
4891
|
+
return { pm: "npm", cwd: import_node_path32.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
4892
|
+
}
|
|
4893
|
+
async function runPackageManagerInstall(cmd) {
|
|
4894
|
+
return new Promise((resolve) => {
|
|
4895
|
+
const child = (0, import_node_child_process.spawn)(cmd.pm, cmd.args, {
|
|
4896
|
+
cwd: cmd.cwd,
|
|
4897
|
+
// Inherit PATH + HOME so the user's installed managers resolve.
|
|
4898
|
+
env: process.env,
|
|
4899
|
+
// `false` keeps the parent in control of cleanup if the orchestrator
|
|
4900
|
+
// exits before install finishes. Cross-platform-safe.
|
|
4901
|
+
shell: false,
|
|
4902
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
4903
|
+
});
|
|
4904
|
+
let stderr = "";
|
|
4905
|
+
child.stderr?.on("data", (chunk) => {
|
|
4906
|
+
stderr += chunk.toString("utf8");
|
|
4907
|
+
});
|
|
4908
|
+
child.on("error", (err) => {
|
|
4909
|
+
resolve({
|
|
4910
|
+
pm: cmd.pm,
|
|
4911
|
+
cwd: cmd.cwd,
|
|
4912
|
+
args: cmd.args,
|
|
4913
|
+
exitCode: 127,
|
|
4914
|
+
stderr: stderr + `
|
|
4915
|
+
${err.message}`
|
|
4916
|
+
});
|
|
4917
|
+
});
|
|
4918
|
+
child.on("close", (code) => {
|
|
4919
|
+
resolve({
|
|
4920
|
+
pm: cmd.pm,
|
|
4921
|
+
cwd: cmd.cwd,
|
|
4922
|
+
args: cmd.args,
|
|
4923
|
+
exitCode: code ?? 1,
|
|
4924
|
+
stderr: stderr.trim()
|
|
4925
|
+
});
|
|
4926
|
+
});
|
|
4927
|
+
});
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4930
|
+
// src/extend/index.ts
|
|
4931
|
+
async function fileExists(p) {
|
|
4932
|
+
try {
|
|
4933
|
+
await import_node_fs20.promises.access(p);
|
|
4934
|
+
return true;
|
|
4935
|
+
} catch {
|
|
4936
|
+
return false;
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
async function readPackageJson(scanPath) {
|
|
4940
|
+
const pkgPath = import_node_path33.default.join(scanPath, "package.json");
|
|
4941
|
+
const raw = await import_node_fs20.promises.readFile(pkgPath, "utf8");
|
|
4942
|
+
return JSON.parse(raw);
|
|
4943
|
+
}
|
|
4944
|
+
async function findHookFiles(scanPath) {
|
|
4945
|
+
const entries = await import_node_fs20.promises.readdir(scanPath);
|
|
4946
|
+
return entries.filter(
|
|
4947
|
+
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
4948
|
+
).sort();
|
|
4949
|
+
}
|
|
4950
|
+
function extendLogPath() {
|
|
4951
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path33.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
4952
|
+
}
|
|
4953
|
+
async function appendExtendLog(entry) {
|
|
4954
|
+
const logPath = extendLogPath();
|
|
4955
|
+
await import_node_fs20.promises.mkdir(import_node_path33.default.dirname(logPath), { recursive: true });
|
|
4956
|
+
await import_node_fs20.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
4957
|
+
}
|
|
4958
|
+
function splicedContent(fileContent, snippet2) {
|
|
4959
|
+
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
4960
|
+
return fileContent.replace("__INSTRUMENTATION_BLOCK__", `${snippet2}
|
|
4961
|
+
__INSTRUMENTATION_BLOCK__`);
|
|
4962
|
+
}
|
|
4963
|
+
const lines = fileContent.split("\n");
|
|
4964
|
+
let lastPushIdx = -1;
|
|
4965
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4966
|
+
if (lines[i].includes("instrumentations.push(")) lastPushIdx = i;
|
|
4967
|
+
}
|
|
4968
|
+
if (lastPushIdx >= 0) {
|
|
4969
|
+
lines.splice(lastPushIdx + 1, 0, snippet2);
|
|
4970
|
+
return lines.join("\n");
|
|
4971
|
+
}
|
|
4972
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4973
|
+
if (lines[i].includes("new NodeSDK(")) {
|
|
4974
|
+
lines.splice(i, 0, snippet2);
|
|
4975
|
+
return lines.join("\n");
|
|
4976
|
+
}
|
|
4977
|
+
}
|
|
4978
|
+
return null;
|
|
4979
|
+
}
|
|
4980
|
+
async function listUninstrumented(ctx) {
|
|
4981
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
4982
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
4983
|
+
const results = [];
|
|
4984
|
+
for (const [library, installedVersion] of Object.entries(allDeps)) {
|
|
4985
|
+
const entry = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
4986
|
+
if (!entry) continue;
|
|
4987
|
+
if (entry.coverage === "bundled" || entry.coverage === "http-only") continue;
|
|
4988
|
+
results.push({
|
|
4989
|
+
library,
|
|
4990
|
+
coverage: entry.coverage,
|
|
4991
|
+
installedVersion,
|
|
4992
|
+
instrumentation_package: entry.instrumentation_package,
|
|
4993
|
+
package_version: entry.package_version,
|
|
4994
|
+
registration: entry.registration,
|
|
4995
|
+
notes: entry.notes
|
|
4996
|
+
});
|
|
4997
|
+
}
|
|
4998
|
+
return results;
|
|
4999
|
+
}
|
|
5000
|
+
function lookupInstrumentation(library, installedVersion) {
|
|
5001
|
+
const entry = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
5002
|
+
if (!entry) return null;
|
|
5003
|
+
return {
|
|
5004
|
+
library: entry.library,
|
|
5005
|
+
coverage: entry.coverage,
|
|
5006
|
+
instrumentation_package: entry.instrumentation_package,
|
|
5007
|
+
package_version: entry.package_version,
|
|
5008
|
+
registration: entry.registration,
|
|
5009
|
+
notes: entry.notes
|
|
5010
|
+
};
|
|
5011
|
+
}
|
|
5012
|
+
async function describeProjectInstrumentation(ctx) {
|
|
5013
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5014
|
+
const envNeat = await fileExists(import_node_path33.default.join(ctx.scanPath, ".env.neat"));
|
|
5015
|
+
const registryInstrPackages = new Set(
|
|
5016
|
+
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
5017
|
+
);
|
|
5018
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5019
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5020
|
+
const installedDeps = {};
|
|
5021
|
+
for (const [key, version] of Object.entries(allDeps)) {
|
|
5022
|
+
if (key.startsWith("@opentelemetry/") || registryInstrPackages.has(key)) {
|
|
5023
|
+
installedDeps[key] = version;
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
return { hookFiles, envNeat, installedDeps };
|
|
5027
|
+
}
|
|
5028
|
+
async function applyExtension(ctx, args, options) {
|
|
5029
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5030
|
+
if (hookFiles.length === 0) {
|
|
5031
|
+
throw new Error(
|
|
5032
|
+
`No instrumentation hook files found in ${ctx.scanPath}. Run \`neat init\` first.`
|
|
5033
|
+
);
|
|
5034
|
+
}
|
|
5035
|
+
for (const file of hookFiles) {
|
|
5036
|
+
const content = await import_node_fs20.promises.readFile(import_node_path33.default.join(ctx.scanPath, file), "utf8");
|
|
5037
|
+
if (content.includes(args.registration_snippet)) {
|
|
5038
|
+
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
5039
|
+
}
|
|
5040
|
+
}
|
|
5041
|
+
const primaryFile = hookFiles[0];
|
|
5042
|
+
const primaryPath = import_node_path33.default.join(ctx.scanPath, primaryFile);
|
|
5043
|
+
const filesTouched = [];
|
|
5044
|
+
const depsAdded = [];
|
|
5045
|
+
const pkgPath = import_node_path33.default.join(ctx.scanPath, "package.json");
|
|
5046
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5047
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5048
|
+
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
5049
|
+
await import_node_fs20.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5050
|
+
filesTouched.push("package.json");
|
|
5051
|
+
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
5052
|
+
}
|
|
5053
|
+
const hookContent = await import_node_fs20.promises.readFile(primaryPath, "utf8");
|
|
5054
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5055
|
+
if (!patched) {
|
|
5056
|
+
throw new Error(
|
|
5057
|
+
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
5058
|
+
);
|
|
5059
|
+
}
|
|
5060
|
+
await import_node_fs20.promises.writeFile(primaryPath, patched, "utf8");
|
|
5061
|
+
filesTouched.push(primaryFile);
|
|
5062
|
+
const cmd = await detectPackageManager(ctx.scanPath);
|
|
5063
|
+
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
5064
|
+
const install = await installer(cmd);
|
|
5065
|
+
const installOutput = install.exitCode === 0 ? `${cmd.pm} install succeeded` : install.stderr || `${cmd.pm} install failed (exit ${install.exitCode})`;
|
|
5066
|
+
await appendExtendLog({
|
|
5067
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5068
|
+
project: ctx.project,
|
|
5069
|
+
library: args.library,
|
|
5070
|
+
instrumentation_package: args.instrumentation_package,
|
|
5071
|
+
version: args.version,
|
|
5072
|
+
registration_snippet: args.registration_snippet,
|
|
5073
|
+
filesTouched,
|
|
5074
|
+
depsAdded,
|
|
5075
|
+
installOutput
|
|
5076
|
+
});
|
|
5077
|
+
return { library: args.library, filesTouched, depsAdded, installOutput, alreadyApplied: false };
|
|
5078
|
+
}
|
|
5079
|
+
async function dryRunExtension(ctx, args) {
|
|
5080
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5081
|
+
if (hookFiles.length === 0) {
|
|
5082
|
+
return {
|
|
5083
|
+
library: args.library,
|
|
5084
|
+
filesTouched: [],
|
|
5085
|
+
depsToAdd: [],
|
|
5086
|
+
packageJsonPatch: {},
|
|
5087
|
+
templatePatch: "No hook files found. Run 'neat init' first."
|
|
5088
|
+
};
|
|
5089
|
+
}
|
|
5090
|
+
for (const file of hookFiles) {
|
|
5091
|
+
const content = await import_node_fs20.promises.readFile(import_node_path33.default.join(ctx.scanPath, file), "utf8");
|
|
5092
|
+
if (content.includes(args.registration_snippet)) {
|
|
5093
|
+
return {
|
|
5094
|
+
library: args.library,
|
|
5095
|
+
filesTouched: [],
|
|
5096
|
+
depsToAdd: [],
|
|
5097
|
+
packageJsonPatch: {},
|
|
5098
|
+
templatePatch: "Already applied \u2014 no changes would be made."
|
|
5099
|
+
};
|
|
5100
|
+
}
|
|
5101
|
+
}
|
|
5102
|
+
const primaryFile = hookFiles[0];
|
|
5103
|
+
const filesTouched = [];
|
|
5104
|
+
const depsToAdd = [];
|
|
5105
|
+
let packageJsonPatch = {};
|
|
5106
|
+
let templatePatch = "";
|
|
5107
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5108
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5109
|
+
packageJsonPatch = { dependencies: { [args.instrumentation_package]: args.version } };
|
|
5110
|
+
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
5111
|
+
filesTouched.push("package.json");
|
|
5112
|
+
}
|
|
5113
|
+
const hookContent = await import_node_fs20.promises.readFile(import_node_path33.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
5114
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5115
|
+
if (patched) {
|
|
5116
|
+
filesTouched.push(primaryFile);
|
|
5117
|
+
templatePatch = `+ ${args.registration_snippet}`;
|
|
5118
|
+
} else {
|
|
5119
|
+
templatePatch = "Could not find insertion point in hook file.";
|
|
5120
|
+
}
|
|
5121
|
+
return { library: args.library, filesTouched, depsToAdd, packageJsonPatch, templatePatch };
|
|
5122
|
+
}
|
|
5123
|
+
async function rollbackExtension(ctx, args) {
|
|
5124
|
+
const logPath = extendLogPath();
|
|
5125
|
+
if (!await fileExists(logPath)) {
|
|
5126
|
+
return { undone: false, message: "no apply found for library" };
|
|
5127
|
+
}
|
|
5128
|
+
const raw = await import_node_fs20.promises.readFile(logPath, "utf8");
|
|
5129
|
+
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
5130
|
+
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
5131
|
+
if (!match) {
|
|
5132
|
+
return { undone: false, message: "no apply found for library" };
|
|
5133
|
+
}
|
|
5134
|
+
const pkgPath = import_node_path33.default.join(ctx.scanPath, "package.json");
|
|
5135
|
+
if (await fileExists(pkgPath)) {
|
|
5136
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5137
|
+
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
5138
|
+
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
5139
|
+
pkg.dependencies = rest;
|
|
5140
|
+
await import_node_fs20.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5144
|
+
for (const file of hookFiles) {
|
|
5145
|
+
const filePath = import_node_path33.default.join(ctx.scanPath, file);
|
|
5146
|
+
const content = await import_node_fs20.promises.readFile(filePath, "utf8");
|
|
5147
|
+
if (content.includes(match.registration_snippet)) {
|
|
5148
|
+
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
5149
|
+
await import_node_fs20.promises.writeFile(filePath, filtered, "utf8");
|
|
5150
|
+
break;
|
|
5151
|
+
}
|
|
5152
|
+
}
|
|
5153
|
+
return {
|
|
5154
|
+
undone: true,
|
|
5155
|
+
message: `rolled back ${match.library} (${match.instrumentation_package})`
|
|
5156
|
+
};
|
|
5157
|
+
}
|
|
5158
|
+
|
|
4738
5159
|
// src/divergences.ts
|
|
4739
5160
|
init_cjs_shims();
|
|
4740
5161
|
var import_types21 = require("@neat.is/types");
|
|
@@ -4975,7 +5396,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4975
5396
|
|
|
4976
5397
|
// src/diff.ts
|
|
4977
5398
|
init_cjs_shims();
|
|
4978
|
-
var
|
|
5399
|
+
var import_node_fs21 = require("fs");
|
|
4979
5400
|
async function loadSnapshotForDiff(target) {
|
|
4980
5401
|
if (/^https?:\/\//i.test(target)) {
|
|
4981
5402
|
const res = await fetch(target);
|
|
@@ -4984,7 +5405,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
4984
5405
|
}
|
|
4985
5406
|
return await res.json();
|
|
4986
5407
|
}
|
|
4987
|
-
const raw = await
|
|
5408
|
+
const raw = await import_node_fs21.promises.readFile(target, "utf8");
|
|
4988
5409
|
return JSON.parse(raw);
|
|
4989
5410
|
}
|
|
4990
5411
|
function indexEntries(entries) {
|
|
@@ -5052,23 +5473,23 @@ function canonicalJson(value) {
|
|
|
5052
5473
|
|
|
5053
5474
|
// src/projects.ts
|
|
5054
5475
|
init_cjs_shims();
|
|
5055
|
-
var
|
|
5476
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
5056
5477
|
function pathsForProject(project, baseDir) {
|
|
5057
5478
|
if (project === DEFAULT_PROJECT) {
|
|
5058
5479
|
return {
|
|
5059
|
-
snapshotPath:
|
|
5060
|
-
errorsPath:
|
|
5061
|
-
staleEventsPath:
|
|
5062
|
-
embeddingsCachePath:
|
|
5063
|
-
policyViolationsPath:
|
|
5480
|
+
snapshotPath: import_node_path34.default.join(baseDir, "graph.json"),
|
|
5481
|
+
errorsPath: import_node_path34.default.join(baseDir, "errors.ndjson"),
|
|
5482
|
+
staleEventsPath: import_node_path34.default.join(baseDir, "stale-events.ndjson"),
|
|
5483
|
+
embeddingsCachePath: import_node_path34.default.join(baseDir, "embeddings.json"),
|
|
5484
|
+
policyViolationsPath: import_node_path34.default.join(baseDir, "policy-violations.ndjson")
|
|
5064
5485
|
};
|
|
5065
5486
|
}
|
|
5066
5487
|
return {
|
|
5067
|
-
snapshotPath:
|
|
5068
|
-
errorsPath:
|
|
5069
|
-
staleEventsPath:
|
|
5070
|
-
embeddingsCachePath:
|
|
5071
|
-
policyViolationsPath:
|
|
5488
|
+
snapshotPath: import_node_path34.default.join(baseDir, `${project}.json`),
|
|
5489
|
+
errorsPath: import_node_path34.default.join(baseDir, `errors.${project}.ndjson`),
|
|
5490
|
+
staleEventsPath: import_node_path34.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
5491
|
+
embeddingsCachePath: import_node_path34.default.join(baseDir, `embeddings.${project}.json`),
|
|
5492
|
+
policyViolationsPath: import_node_path34.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
5072
5493
|
};
|
|
5073
5494
|
}
|
|
5074
5495
|
var Projects = class {
|
|
@@ -5104,65 +5525,135 @@ var Projects = class {
|
|
|
5104
5525
|
|
|
5105
5526
|
// src/registry.ts
|
|
5106
5527
|
init_cjs_shims();
|
|
5107
|
-
var
|
|
5108
|
-
var
|
|
5109
|
-
var
|
|
5528
|
+
var import_node_fs22 = require("fs");
|
|
5529
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
5530
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
5110
5531
|
var import_types22 = require("@neat.is/types");
|
|
5111
5532
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
5112
5533
|
var LOCK_RETRY_MS = 50;
|
|
5113
5534
|
function neatHome() {
|
|
5114
5535
|
const override = process.env.NEAT_HOME;
|
|
5115
|
-
if (override && override.length > 0) return
|
|
5116
|
-
return
|
|
5536
|
+
if (override && override.length > 0) return import_node_path35.default.resolve(override);
|
|
5537
|
+
return import_node_path35.default.join(import_node_os3.default.homedir(), ".neat");
|
|
5117
5538
|
}
|
|
5118
5539
|
function registryPath() {
|
|
5119
|
-
return
|
|
5540
|
+
return import_node_path35.default.join(neatHome(), "projects.json");
|
|
5120
5541
|
}
|
|
5121
5542
|
function registryLockPath() {
|
|
5122
|
-
return
|
|
5543
|
+
return import_node_path35.default.join(neatHome(), "projects.json.lock");
|
|
5544
|
+
}
|
|
5545
|
+
function daemonPidPath() {
|
|
5546
|
+
return import_node_path35.default.join(neatHome(), "neatd.pid");
|
|
5547
|
+
}
|
|
5548
|
+
function isPidAliveDefault(pid) {
|
|
5549
|
+
try {
|
|
5550
|
+
process.kill(pid, 0);
|
|
5551
|
+
return true;
|
|
5552
|
+
} catch (err) {
|
|
5553
|
+
return err.code === "EPERM";
|
|
5554
|
+
}
|
|
5555
|
+
}
|
|
5556
|
+
async function readPidFile(file) {
|
|
5557
|
+
try {
|
|
5558
|
+
const raw = await import_node_fs22.promises.readFile(file, "utf8");
|
|
5559
|
+
const pid = Number.parseInt(raw.trim(), 10);
|
|
5560
|
+
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
5561
|
+
} catch {
|
|
5562
|
+
return void 0;
|
|
5563
|
+
}
|
|
5564
|
+
}
|
|
5565
|
+
var defaultLockHolderProbe = {
|
|
5566
|
+
isPidAlive: isPidAliveDefault,
|
|
5567
|
+
daemonPidFromFile: () => readPidFile(daemonPidPath()),
|
|
5568
|
+
async daemonResponds() {
|
|
5569
|
+
const base = process.env.NEAT_API_URL ?? "http://localhost:8080";
|
|
5570
|
+
try {
|
|
5571
|
+
await fetch(`${base}/health`, { signal: AbortSignal.timeout(750) });
|
|
5572
|
+
return true;
|
|
5573
|
+
} catch {
|
|
5574
|
+
return false;
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
};
|
|
5578
|
+
async function readLockPid(lockPath) {
|
|
5579
|
+
return readPidFile(lockPath);
|
|
5580
|
+
}
|
|
5581
|
+
async function classifyLockHolder(lockPath, probe = defaultLockHolderProbe) {
|
|
5582
|
+
const lockPid = await readLockPid(lockPath);
|
|
5583
|
+
const daemonPid = await probe.daemonPidFromFile();
|
|
5584
|
+
if (daemonPid !== void 0 && daemonPid !== process.pid && probe.isPidAlive(daemonPid) && // The lock already names the daemon, or the daemon answers on its port.
|
|
5585
|
+
// Either confirms a live daemon is in the picture (the second guards
|
|
5586
|
+
// against a stale pidfile whose PID got reused).
|
|
5587
|
+
(daemonPid === lockPid || await probe.daemonResponds())) {
|
|
5588
|
+
return { kind: "daemon", pid: daemonPid };
|
|
5589
|
+
}
|
|
5590
|
+
if (lockPid !== void 0 && lockPid !== process.pid && probe.isPidAlive(lockPid)) {
|
|
5591
|
+
return { kind: "command", pid: lockPid };
|
|
5592
|
+
}
|
|
5593
|
+
return { kind: "stale" };
|
|
5594
|
+
}
|
|
5595
|
+
function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
5596
|
+
switch (holder.kind) {
|
|
5597
|
+
case "daemon":
|
|
5598
|
+
return `The neat daemon (pid ${holder.pid}) is holding the registry lock. Register this project through the daemon, or stop neatd and re-run \`neat init\`.`;
|
|
5599
|
+
case "command":
|
|
5600
|
+
return `Another neat command (pid ${holder.pid}) is holding the registry lock. Wait for it to finish, or check \`ps\` if you're not sure what's running.`;
|
|
5601
|
+
case "stale":
|
|
5602
|
+
return `neat registry: timed out after ${timeoutMs}ms waiting for ${lockPath}. Another neat process is holding the lock; if no such process exists, remove the file by hand.`;
|
|
5603
|
+
}
|
|
5123
5604
|
}
|
|
5124
5605
|
async function normalizeProjectPath(input) {
|
|
5125
|
-
const resolved =
|
|
5606
|
+
const resolved = import_node_path35.default.resolve(input);
|
|
5126
5607
|
try {
|
|
5127
|
-
return await
|
|
5608
|
+
return await import_node_fs22.promises.realpath(resolved);
|
|
5128
5609
|
} catch {
|
|
5129
5610
|
return resolved;
|
|
5130
5611
|
}
|
|
5131
5612
|
}
|
|
5132
5613
|
async function writeAtomically(target, contents) {
|
|
5133
|
-
await
|
|
5614
|
+
await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(target), { recursive: true });
|
|
5134
5615
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
5135
|
-
const fd = await
|
|
5616
|
+
const fd = await import_node_fs22.promises.open(tmp, "w");
|
|
5136
5617
|
try {
|
|
5137
5618
|
await fd.writeFile(contents, "utf8");
|
|
5138
5619
|
await fd.sync();
|
|
5139
5620
|
} finally {
|
|
5140
5621
|
await fd.close();
|
|
5141
5622
|
}
|
|
5142
|
-
await
|
|
5623
|
+
await import_node_fs22.promises.rename(tmp, target);
|
|
5143
5624
|
}
|
|
5144
|
-
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
5625
|
+
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
5145
5626
|
const deadline = Date.now() + timeoutMs;
|
|
5146
|
-
await
|
|
5627
|
+
await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(lockPath), { recursive: true });
|
|
5628
|
+
let probedHolder = false;
|
|
5147
5629
|
while (true) {
|
|
5148
5630
|
try {
|
|
5149
|
-
const fd = await
|
|
5150
|
-
|
|
5631
|
+
const fd = await import_node_fs22.promises.open(lockPath, "wx");
|
|
5632
|
+
try {
|
|
5633
|
+
await fd.writeFile(`${process.pid}
|
|
5634
|
+
`, "utf8");
|
|
5635
|
+
} finally {
|
|
5636
|
+
await fd.close();
|
|
5637
|
+
}
|
|
5151
5638
|
return;
|
|
5152
5639
|
} catch (err) {
|
|
5153
5640
|
const code = err.code;
|
|
5154
5641
|
if (code !== "EEXIST") throw err;
|
|
5642
|
+
if (!probedHolder) {
|
|
5643
|
+
probedHolder = true;
|
|
5644
|
+
const holder = await classifyLockHolder(lockPath, probe);
|
|
5645
|
+
if (holder.kind === "daemon") throw new Error(lockHolderMessage(holder, lockPath, timeoutMs));
|
|
5646
|
+
}
|
|
5155
5647
|
if (Date.now() >= deadline) {
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
);
|
|
5648
|
+
const holder = await classifyLockHolder(lockPath, probe);
|
|
5649
|
+
throw new Error(lockHolderMessage(holder, lockPath, timeoutMs));
|
|
5159
5650
|
}
|
|
5160
5651
|
await new Promise((r) => setTimeout(r, LOCK_RETRY_MS));
|
|
5161
5652
|
}
|
|
5162
5653
|
}
|
|
5163
5654
|
}
|
|
5164
5655
|
async function releaseLock(lockPath) {
|
|
5165
|
-
await
|
|
5656
|
+
await import_node_fs22.promises.unlink(lockPath).catch(() => {
|
|
5166
5657
|
});
|
|
5167
5658
|
}
|
|
5168
5659
|
async function withLock(fn) {
|
|
@@ -5178,7 +5669,7 @@ async function readRegistry() {
|
|
|
5178
5669
|
const file = registryPath();
|
|
5179
5670
|
let raw;
|
|
5180
5671
|
try {
|
|
5181
|
-
raw = await
|
|
5672
|
+
raw = await import_node_fs22.promises.readFile(file, "utf8");
|
|
5182
5673
|
} catch (err) {
|
|
5183
5674
|
if (err.code === "ENOENT") {
|
|
5184
5675
|
return { version: 1, projects: [] };
|
|
@@ -5728,6 +6219,105 @@ function registerRoutes(scope, ctx) {
|
|
|
5728
6219
|
violations
|
|
5729
6220
|
};
|
|
5730
6221
|
});
|
|
6222
|
+
scope.get("/extend/list-uninstrumented", async (req, reply) => {
|
|
6223
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6224
|
+
if (!proj) return;
|
|
6225
|
+
if (!proj.scanPath) {
|
|
6226
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6227
|
+
}
|
|
6228
|
+
try {
|
|
6229
|
+
const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
|
|
6230
|
+
return { libraries: results };
|
|
6231
|
+
} catch (err) {
|
|
6232
|
+
return reply.code(500).send({ error: err.message });
|
|
6233
|
+
}
|
|
6234
|
+
});
|
|
6235
|
+
scope.get("/extend/lookup", async (req, reply) => {
|
|
6236
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6237
|
+
if (!proj) return;
|
|
6238
|
+
const { library, version } = req.query;
|
|
6239
|
+
if (!library) {
|
|
6240
|
+
return reply.code(400).send({ error: "query parameter `library` is required" });
|
|
6241
|
+
}
|
|
6242
|
+
const result = lookupInstrumentation(library, version);
|
|
6243
|
+
if (!result) {
|
|
6244
|
+
return reply.code(404).send({ error: "library not found in registry", library });
|
|
6245
|
+
}
|
|
6246
|
+
return result;
|
|
6247
|
+
});
|
|
6248
|
+
scope.get("/extend/describe", async (req, reply) => {
|
|
6249
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6250
|
+
if (!proj) return;
|
|
6251
|
+
if (!proj.scanPath) {
|
|
6252
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6253
|
+
}
|
|
6254
|
+
try {
|
|
6255
|
+
const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
|
|
6256
|
+
return state;
|
|
6257
|
+
} catch (err) {
|
|
6258
|
+
return reply.code(500).send({ error: err.message });
|
|
6259
|
+
}
|
|
6260
|
+
});
|
|
6261
|
+
scope.post("/extend/apply", async (req, reply) => {
|
|
6262
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6263
|
+
if (!proj) return;
|
|
6264
|
+
if (!proj.scanPath) {
|
|
6265
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6266
|
+
}
|
|
6267
|
+
const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
|
|
6268
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6269
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6270
|
+
}
|
|
6271
|
+
try {
|
|
6272
|
+
const result = await applyExtension(
|
|
6273
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6274
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6275
|
+
);
|
|
6276
|
+
return result;
|
|
6277
|
+
} catch (err) {
|
|
6278
|
+
return reply.code(500).send({ error: err.message });
|
|
6279
|
+
}
|
|
6280
|
+
});
|
|
6281
|
+
scope.post("/extend/dry-run", async (req, reply) => {
|
|
6282
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6283
|
+
if (!proj) return;
|
|
6284
|
+
if (!proj.scanPath) {
|
|
6285
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6286
|
+
}
|
|
6287
|
+
const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
|
|
6288
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6289
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6290
|
+
}
|
|
6291
|
+
try {
|
|
6292
|
+
const result = await dryRunExtension(
|
|
6293
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6294
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6295
|
+
);
|
|
6296
|
+
return result;
|
|
6297
|
+
} catch (err) {
|
|
6298
|
+
return reply.code(500).send({ error: err.message });
|
|
6299
|
+
}
|
|
6300
|
+
});
|
|
6301
|
+
scope.post("/extend/rollback", async (req, reply) => {
|
|
6302
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6303
|
+
if (!proj) return;
|
|
6304
|
+
if (!proj.scanPath) {
|
|
6305
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6306
|
+
}
|
|
6307
|
+
const { library } = req.body ?? {};
|
|
6308
|
+
if (!library) {
|
|
6309
|
+
return reply.code(400).send({ error: "body must include library" });
|
|
6310
|
+
}
|
|
6311
|
+
try {
|
|
6312
|
+
const result = await rollbackExtension(
|
|
6313
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6314
|
+
{ library }
|
|
6315
|
+
);
|
|
6316
|
+
return result;
|
|
6317
|
+
} catch (err) {
|
|
6318
|
+
return reply.code(500).send({ error: err.message });
|
|
6319
|
+
}
|
|
6320
|
+
});
|
|
5731
6321
|
}
|
|
5732
6322
|
async function buildApi(opts) {
|
|
5733
6323
|
const app = (0, import_fastify.default)({ logger: false });
|
|
@@ -5838,15 +6428,15 @@ init_otel_grpc();
|
|
|
5838
6428
|
|
|
5839
6429
|
// src/daemon.ts
|
|
5840
6430
|
init_cjs_shims();
|
|
5841
|
-
var
|
|
5842
|
-
var
|
|
6431
|
+
var import_node_fs24 = require("fs");
|
|
6432
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
5843
6433
|
init_otel();
|
|
5844
6434
|
init_auth();
|
|
5845
6435
|
|
|
5846
6436
|
// src/unrouted.ts
|
|
5847
6437
|
init_cjs_shims();
|
|
5848
|
-
var
|
|
5849
|
-
var
|
|
6438
|
+
var import_node_fs23 = require("fs");
|
|
6439
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
5850
6440
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
5851
6441
|
return {
|
|
5852
6442
|
timestamp: now.toISOString(),
|
|
@@ -5856,21 +6446,21 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
5856
6446
|
};
|
|
5857
6447
|
}
|
|
5858
6448
|
async function appendUnroutedSpan(neatHome2, record) {
|
|
5859
|
-
const target =
|
|
5860
|
-
await
|
|
5861
|
-
await
|
|
6449
|
+
const target = import_node_path38.default.join(neatHome2, "errors.ndjson");
|
|
6450
|
+
await import_node_fs23.promises.mkdir(neatHome2, { recursive: true });
|
|
6451
|
+
await import_node_fs23.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
5862
6452
|
}
|
|
5863
6453
|
function unroutedErrorsPath(neatHome2) {
|
|
5864
|
-
return
|
|
6454
|
+
return import_node_path38.default.join(neatHome2, "errors.ndjson");
|
|
5865
6455
|
}
|
|
5866
6456
|
|
|
5867
6457
|
// src/daemon.ts
|
|
5868
6458
|
function neatHomeFor(opts) {
|
|
5869
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
6459
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path39.default.resolve(opts.neatHome);
|
|
5870
6460
|
const env = process.env.NEAT_HOME;
|
|
5871
|
-
if (env && env.length > 0) return
|
|
6461
|
+
if (env && env.length > 0) return import_node_path39.default.resolve(env);
|
|
5872
6462
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
5873
|
-
return
|
|
6463
|
+
return import_node_path39.default.join(home, ".neat");
|
|
5874
6464
|
}
|
|
5875
6465
|
function routeSpanToProject(serviceName, projects) {
|
|
5876
6466
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -5905,9 +6495,9 @@ function isTokenContained(needle, haystack) {
|
|
|
5905
6495
|
return tokens.includes(needle);
|
|
5906
6496
|
}
|
|
5907
6497
|
async function bootstrapProject(entry) {
|
|
5908
|
-
const paths = pathsForProject(entry.name,
|
|
6498
|
+
const paths = pathsForProject(entry.name, import_node_path39.default.join(entry.path, "neat-out"));
|
|
5909
6499
|
try {
|
|
5910
|
-
const stat = await
|
|
6500
|
+
const stat = await import_node_fs24.promises.stat(entry.path);
|
|
5911
6501
|
if (!stat.isDirectory()) {
|
|
5912
6502
|
throw new Error(`registered path ${entry.path} is not a directory`);
|
|
5913
6503
|
}
|
|
@@ -5973,13 +6563,13 @@ async function startDaemon(opts = {}) {
|
|
|
5973
6563
|
const home = neatHomeFor(opts);
|
|
5974
6564
|
const regPath = registryPath();
|
|
5975
6565
|
try {
|
|
5976
|
-
await
|
|
6566
|
+
await import_node_fs24.promises.access(regPath);
|
|
5977
6567
|
} catch {
|
|
5978
6568
|
throw new Error(
|
|
5979
6569
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
5980
6570
|
);
|
|
5981
6571
|
}
|
|
5982
|
-
const pidPath =
|
|
6572
|
+
const pidPath = import_node_path39.default.join(home, "neatd.pid");
|
|
5983
6573
|
await writeAtomically(pidPath, `${process.pid}
|
|
5984
6574
|
`);
|
|
5985
6575
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -6137,7 +6727,7 @@ async function startDaemon(opts = {}) {
|
|
|
6137
6727
|
}
|
|
6138
6728
|
if (restApp) await restApp.close().catch(() => {
|
|
6139
6729
|
});
|
|
6140
|
-
await
|
|
6730
|
+
await import_node_fs24.promises.unlink(pidPath).catch(() => {
|
|
6141
6731
|
});
|
|
6142
6732
|
throw new Error(
|
|
6143
6733
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
@@ -6193,6 +6783,7 @@ async function startDaemon(opts = {}) {
|
|
|
6193
6783
|
{
|
|
6194
6784
|
graph: slot.graph,
|
|
6195
6785
|
errorsPath: slot.paths.errorsPath,
|
|
6786
|
+
scanPath: slot.entry.path,
|
|
6196
6787
|
project: slot.entry.name,
|
|
6197
6788
|
// Receiver already wrote the error event synchronously below.
|
|
6198
6789
|
writeErrorEventInline: false
|
|
@@ -6216,6 +6807,7 @@ async function startDaemon(opts = {}) {
|
|
|
6216
6807
|
{
|
|
6217
6808
|
graph: slot.graph,
|
|
6218
6809
|
errorsPath: slot.paths.errorsPath,
|
|
6810
|
+
scanPath: slot.entry.path,
|
|
6219
6811
|
project: slot.entry.name,
|
|
6220
6812
|
writeErrorEventInline: false
|
|
6221
6813
|
},
|
|
@@ -6241,7 +6833,7 @@ async function startDaemon(opts = {}) {
|
|
|
6241
6833
|
});
|
|
6242
6834
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
6243
6835
|
});
|
|
6244
|
-
await
|
|
6836
|
+
await import_node_fs24.promises.unlink(pidPath).catch(() => {
|
|
6245
6837
|
});
|
|
6246
6838
|
throw new Error(
|
|
6247
6839
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
@@ -6287,9 +6879,9 @@ async function startDaemon(opts = {}) {
|
|
|
6287
6879
|
let registryWatcher = null;
|
|
6288
6880
|
let reloadTimer = null;
|
|
6289
6881
|
try {
|
|
6290
|
-
const regDir =
|
|
6291
|
-
const regBase =
|
|
6292
|
-
registryWatcher = (0,
|
|
6882
|
+
const regDir = import_node_path39.default.dirname(regPath);
|
|
6883
|
+
const regBase = import_node_path39.default.basename(regPath);
|
|
6884
|
+
registryWatcher = (0, import_node_fs24.watch)(regDir, (_eventType, filename) => {
|
|
6293
6885
|
if (filename !== null && filename !== regBase) return;
|
|
6294
6886
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
6295
6887
|
reloadTimer = setTimeout(() => {
|
|
@@ -6332,7 +6924,7 @@ async function startDaemon(opts = {}) {
|
|
|
6332
6924
|
} catch {
|
|
6333
6925
|
}
|
|
6334
6926
|
}
|
|
6335
|
-
await
|
|
6927
|
+
await import_node_fs24.promises.unlink(pidPath).catch(() => {
|
|
6336
6928
|
});
|
|
6337
6929
|
};
|
|
6338
6930
|
return {
|