@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/neatd.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", (req2, reply, done) => {
|
|
60
|
-
const
|
|
60
|
+
const path42 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
61
61
|
for (const suffix of suffixes) {
|
|
62
|
-
if (
|
|
62
|
+
if (path42 === suffix || path42.endsWith(suffix)) {
|
|
63
63
|
done();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
@@ -185,8 +185,8 @@ function reshapeGrpcRequest(req2) {
|
|
|
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 }
|
|
@@ -566,12 +566,12 @@ async function buildOtelReceiver(opts) {
|
|
|
566
566
|
};
|
|
567
567
|
return decorated;
|
|
568
568
|
}
|
|
569
|
-
var
|
|
569
|
+
var import_node_path37, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
570
570
|
var init_otel = __esm({
|
|
571
571
|
"src/otel.ts"() {
|
|
572
572
|
"use strict";
|
|
573
573
|
init_cjs_shims();
|
|
574
|
-
|
|
574
|
+
import_node_path37 = __toESM(require("path"), 1);
|
|
575
575
|
import_node_url2 = require("url");
|
|
576
576
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
577
577
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -587,14 +587,14 @@ var init_otel = __esm({
|
|
|
587
587
|
|
|
588
588
|
// src/neatd.ts
|
|
589
589
|
init_cjs_shims();
|
|
590
|
-
var
|
|
591
|
-
var
|
|
590
|
+
var import_node_fs25 = require("fs");
|
|
591
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
592
592
|
var import_node_module = require("module");
|
|
593
593
|
|
|
594
594
|
// src/daemon.ts
|
|
595
595
|
init_cjs_shims();
|
|
596
|
-
var
|
|
597
|
-
var
|
|
596
|
+
var import_node_fs24 = require("fs");
|
|
597
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
598
598
|
|
|
599
599
|
// src/graph.ts
|
|
600
600
|
init_cjs_shims();
|
|
@@ -631,6 +631,7 @@ init_cjs_shims();
|
|
|
631
631
|
init_cjs_shims();
|
|
632
632
|
var import_node_fs3 = require("fs");
|
|
633
633
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
634
|
+
var sourceMapJs = __toESM(require("source-map-js"), 1);
|
|
634
635
|
|
|
635
636
|
// src/policy.ts
|
|
636
637
|
init_cjs_shims();
|
|
@@ -1072,19 +1073,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1072
1073
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1073
1074
|
let best = { path: [start], edges: [] };
|
|
1074
1075
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1075
|
-
function step(node,
|
|
1076
|
-
if (
|
|
1077
|
-
best = { path: [...
|
|
1076
|
+
function step(node, path42, edges) {
|
|
1077
|
+
if (path42.length > best.path.length) {
|
|
1078
|
+
best = { path: [...path42], edges: [...edges] };
|
|
1078
1079
|
}
|
|
1079
|
-
if (
|
|
1080
|
+
if (path42.length - 1 >= maxDepth) return;
|
|
1080
1081
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1081
1082
|
for (const [srcId, edge] of incoming) {
|
|
1082
1083
|
if (visited.has(srcId)) continue;
|
|
1083
1084
|
visited.add(srcId);
|
|
1084
|
-
|
|
1085
|
+
path42.push(srcId);
|
|
1085
1086
|
edges.push(edge);
|
|
1086
|
-
step(srcId,
|
|
1087
|
-
|
|
1087
|
+
step(srcId, path42, edges);
|
|
1088
|
+
path42.pop();
|
|
1088
1089
|
edges.pop();
|
|
1089
1090
|
visited.delete(srcId);
|
|
1090
1091
|
}
|
|
@@ -1613,6 +1614,14 @@ function warnUnidentifiedSpan(project) {
|
|
|
1613
1614
|
`[neatd] span lacked service.name; routed to 'unidentified' in project ${project}; check your OTel SDK config.`
|
|
1614
1615
|
);
|
|
1615
1616
|
}
|
|
1617
|
+
var noSourceMapWarnedServices = /* @__PURE__ */ new Set();
|
|
1618
|
+
function warnNoSourceMaps(serviceName) {
|
|
1619
|
+
if (noSourceMapWarnedServices.has(serviceName)) return;
|
|
1620
|
+
noSourceMapWarnedServices.add(serviceName);
|
|
1621
|
+
console.warn(
|
|
1622
|
+
`[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.`
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1616
1625
|
function pickAttr(span, ...keys) {
|
|
1617
1626
|
for (const k of keys) {
|
|
1618
1627
|
const v = span.attributes[k];
|
|
@@ -1655,8 +1664,13 @@ function languageForExt(relPath) {
|
|
|
1655
1664
|
return void 0;
|
|
1656
1665
|
}
|
|
1657
1666
|
}
|
|
1658
|
-
function relPathForRuntimeFile(filepath, serviceNode) {
|
|
1667
|
+
function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
|
|
1659
1668
|
let p = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1669
|
+
if (scanPath && scanPath.length > 0) {
|
|
1670
|
+
const absRoot = toPosix(import_node_path3.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
|
|
1671
|
+
const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
|
|
1672
|
+
if (p.startsWith(anchor)) return p.slice(anchor.length);
|
|
1673
|
+
}
|
|
1660
1674
|
const root = serviceNode?.repoPath;
|
|
1661
1675
|
if (root && root !== "." && root.length > 0) {
|
|
1662
1676
|
const rootPosix = toPosix(root);
|
|
@@ -1673,16 +1687,65 @@ function relPathForRuntimeFile(filepath, serviceNode) {
|
|
|
1673
1687
|
p = p.replace(/^[A-Za-z]:/, "").replace(/^\/+/, "");
|
|
1674
1688
|
return p.length > 0 ? p : null;
|
|
1675
1689
|
}
|
|
1676
|
-
|
|
1690
|
+
var sourceMapCache = /* @__PURE__ */ new Map();
|
|
1691
|
+
function resolveDistToSrc(absFilepath, line) {
|
|
1692
|
+
if (!absFilepath.endsWith(".js")) return null;
|
|
1693
|
+
let entry2 = sourceMapCache.get(absFilepath);
|
|
1694
|
+
if (entry2 === void 0) {
|
|
1695
|
+
entry2 = null;
|
|
1696
|
+
const mapPath = `${absFilepath}.map`;
|
|
1697
|
+
try {
|
|
1698
|
+
if ((0, import_node_fs3.existsSync)(mapPath)) {
|
|
1699
|
+
const raw = JSON.parse((0, import_node_fs3.readFileSync)(mapPath, "utf8"));
|
|
1700
|
+
const consumer = new sourceMapJs.SourceMapConsumer(raw);
|
|
1701
|
+
entry2 = { consumer, dir: import_node_path3.default.dirname(mapPath) };
|
|
1702
|
+
}
|
|
1703
|
+
} catch {
|
|
1704
|
+
entry2 = null;
|
|
1705
|
+
}
|
|
1706
|
+
sourceMapCache.set(absFilepath, entry2);
|
|
1707
|
+
}
|
|
1708
|
+
if (!entry2) return null;
|
|
1709
|
+
try {
|
|
1710
|
+
const pos = entry2.consumer.originalPositionFor({
|
|
1711
|
+
line: line !== void 0 && Number.isFinite(line) ? line : 1,
|
|
1712
|
+
column: 0
|
|
1713
|
+
});
|
|
1714
|
+
if (!pos || !pos.source) return null;
|
|
1715
|
+
const root = entry2.consumer.sourceRoot ?? "";
|
|
1716
|
+
const resolved = import_node_path3.default.resolve(entry2.dir, root, pos.source);
|
|
1717
|
+
return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
|
|
1718
|
+
} catch {
|
|
1719
|
+
return null;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
1677
1723
|
const filepath = span.attributes[CODE_FILEPATH_ATTR];
|
|
1678
1724
|
if (typeof filepath !== "string" || filepath.length === 0) return null;
|
|
1679
|
-
const relPath = relPathForRuntimeFile(filepath, serviceNode);
|
|
1680
|
-
if (!relPath) return null;
|
|
1681
1725
|
const linenoRaw = span.attributes[CODE_LINENO_ATTR];
|
|
1682
|
-
|
|
1726
|
+
let line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
|
|
1727
|
+
const abs = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1728
|
+
const resolved = resolveDistToSrc(abs, line);
|
|
1729
|
+
let effectivePath = filepath;
|
|
1730
|
+
let originalRelPath;
|
|
1731
|
+
if (resolved) {
|
|
1732
|
+
originalRelPath = relPathForRuntimeFile(filepath, serviceNode, scanPath) ?? void 0;
|
|
1733
|
+
effectivePath = resolved.filepath;
|
|
1734
|
+
if (resolved.line !== void 0) line = resolved.line;
|
|
1735
|
+
}
|
|
1736
|
+
const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
|
|
1737
|
+
if (!relPath) return null;
|
|
1738
|
+
if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
|
|
1739
|
+
warnNoSourceMaps(serviceNode.name);
|
|
1740
|
+
}
|
|
1683
1741
|
const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
|
|
1684
1742
|
const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
|
|
1685
|
-
return {
|
|
1743
|
+
return {
|
|
1744
|
+
relPath,
|
|
1745
|
+
...line !== void 0 ? { line } : {},
|
|
1746
|
+
...fn ? { fn } : {},
|
|
1747
|
+
...originalRelPath && originalRelPath !== relPath ? { originalRelPath } : {}
|
|
1748
|
+
};
|
|
1686
1749
|
}
|
|
1687
1750
|
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
1688
1751
|
const fileNodeId = (0, import_types3.fileId)(serviceName, callSite.relPath);
|
|
@@ -1694,6 +1757,7 @@ function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
|
1694
1757
|
service: serviceName,
|
|
1695
1758
|
path: callSite.relPath,
|
|
1696
1759
|
...language ? { language } : {},
|
|
1760
|
+
...callSite.originalRelPath ? { originalPath: callSite.originalRelPath } : {},
|
|
1697
1761
|
discoveredVia: "otel"
|
|
1698
1762
|
};
|
|
1699
1763
|
graph.addNode(fileNodeId, node);
|
|
@@ -1719,6 +1783,12 @@ function makeInferredEdgeId(type, source, target) {
|
|
|
1719
1783
|
}
|
|
1720
1784
|
var INFERRED_CONFIDENCE = 0.6;
|
|
1721
1785
|
var STITCH_MAX_DEPTH = 2;
|
|
1786
|
+
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
1787
|
+
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
1788
|
+
function spanMintsObservedEdge(kind) {
|
|
1789
|
+
if (kind === void 0 || kind === 0) return true;
|
|
1790
|
+
return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
|
|
1791
|
+
}
|
|
1722
1792
|
var PARENT_SPAN_CACHE_SIZE = 1e4;
|
|
1723
1793
|
var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1724
1794
|
var parentSpanCache = /* @__PURE__ */ new Map();
|
|
@@ -1825,7 +1895,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
1825
1895
|
graph.addNode(id, node);
|
|
1826
1896
|
return id;
|
|
1827
1897
|
}
|
|
1828
|
-
function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
1898
|
+
function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
|
|
1829
1899
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
1830
1900
|
const id = makeObservedEdgeId(type, source, target);
|
|
1831
1901
|
if (graph.hasEdge(id)) {
|
|
@@ -1862,7 +1932,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1862
1932
|
confidence: (0, import_types3.confidenceForObservedSignal)(signal),
|
|
1863
1933
|
lastObserved: ts,
|
|
1864
1934
|
callCount: 1,
|
|
1865
|
-
signal
|
|
1935
|
+
signal,
|
|
1936
|
+
// Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
|
|
1937
|
+
// Only set when code.filepath was present on the span — never fabricated.
|
|
1938
|
+
...evidence ? { evidence } : {}
|
|
1866
1939
|
};
|
|
1867
1940
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
1868
1941
|
return { edge, created: true };
|
|
@@ -1954,12 +2027,14 @@ async function handleSpan(ctx, span) {
|
|
|
1954
2027
|
const isError = span.statusCode === 2;
|
|
1955
2028
|
cacheSpanService(span, nowMs);
|
|
1956
2029
|
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
1957
|
-
const callSite = callSiteFromSpan(span, sourceServiceNode);
|
|
2030
|
+
const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
|
|
1958
2031
|
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
2032
|
+
const callSiteEvidence = callSite ? { file: callSite.relPath, ...callSite.line !== void 0 ? { line: callSite.line } : {} } : void 0;
|
|
1959
2033
|
let affectedNode = sourceId;
|
|
2034
|
+
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
1960
2035
|
if (span.dbSystem) {
|
|
1961
2036
|
const host = pickAddress(span);
|
|
1962
|
-
if (host) {
|
|
2037
|
+
if (mintsFromCallerSide && host) {
|
|
1963
2038
|
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
1964
2039
|
const targetId = (0, import_types3.databaseId)(host);
|
|
1965
2040
|
const result = upsertObservedEdge(
|
|
@@ -1968,14 +2043,15 @@ async function handleSpan(ctx, span) {
|
|
|
1968
2043
|
observedSource(),
|
|
1969
2044
|
targetId,
|
|
1970
2045
|
ts,
|
|
1971
|
-
isError
|
|
2046
|
+
isError,
|
|
2047
|
+
callSiteEvidence
|
|
1972
2048
|
);
|
|
1973
2049
|
if (result) affectedNode = targetId;
|
|
1974
2050
|
}
|
|
1975
2051
|
} else {
|
|
1976
2052
|
const host = pickAddress(span);
|
|
1977
2053
|
let resolvedViaAddress = false;
|
|
1978
|
-
if (host && host !== span.service) {
|
|
2054
|
+
if (mintsFromCallerSide && host && host !== span.service) {
|
|
1979
2055
|
const targetId = resolveServiceId(ctx.graph, host, env);
|
|
1980
2056
|
if (targetId && targetId !== sourceId) {
|
|
1981
2057
|
upsertObservedEdge(
|
|
@@ -1984,7 +2060,8 @@ async function handleSpan(ctx, span) {
|
|
|
1984
2060
|
observedSource(),
|
|
1985
2061
|
targetId,
|
|
1986
2062
|
ts,
|
|
1987
|
-
isError
|
|
2063
|
+
isError,
|
|
2064
|
+
callSiteEvidence
|
|
1988
2065
|
);
|
|
1989
2066
|
affectedNode = targetId;
|
|
1990
2067
|
resolvedViaAddress = true;
|
|
@@ -1996,7 +2073,8 @@ async function handleSpan(ctx, span) {
|
|
|
1996
2073
|
observedSource(),
|
|
1997
2074
|
frontierNodeId,
|
|
1998
2075
|
ts,
|
|
1999
|
-
isError
|
|
2076
|
+
isError,
|
|
2077
|
+
callSiteEvidence
|
|
2000
2078
|
);
|
|
2001
2079
|
affectedNode = frontierNodeId;
|
|
2002
2080
|
resolvedViaAddress = true;
|
|
@@ -2874,14 +2952,111 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
2874
2952
|
|
|
2875
2953
|
// src/extract/databases/index.ts
|
|
2876
2954
|
init_cjs_shims();
|
|
2877
|
-
var
|
|
2955
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
2956
|
+
var import_types8 = require("@neat.is/types");
|
|
2957
|
+
|
|
2958
|
+
// src/extract/calls/shared.ts
|
|
2959
|
+
init_cjs_shims();
|
|
2960
|
+
var import_node_fs10 = require("fs");
|
|
2961
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
2878
2962
|
var import_types7 = require("@neat.is/types");
|
|
2963
|
+
async function walkSourceFiles(dir) {
|
|
2964
|
+
const out = [];
|
|
2965
|
+
async function walk(current) {
|
|
2966
|
+
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
2967
|
+
for (const entry2 of entries) {
|
|
2968
|
+
const full = import_node_path10.default.join(current, entry2.name);
|
|
2969
|
+
if (entry2.isDirectory()) {
|
|
2970
|
+
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
2971
|
+
if (await isPythonVenvDir(full)) continue;
|
|
2972
|
+
await walk(full);
|
|
2973
|
+
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry2.name))) {
|
|
2974
|
+
out.push(full);
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
await walk(dir);
|
|
2979
|
+
return out;
|
|
2980
|
+
}
|
|
2981
|
+
async function loadSourceFiles(dir) {
|
|
2982
|
+
const paths = await walkSourceFiles(dir);
|
|
2983
|
+
const out = [];
|
|
2984
|
+
for (const p of paths) {
|
|
2985
|
+
try {
|
|
2986
|
+
const content = await import_node_fs10.promises.readFile(p, "utf8");
|
|
2987
|
+
out.push({ path: p, content });
|
|
2988
|
+
} catch {
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
return out;
|
|
2992
|
+
}
|
|
2993
|
+
function lineOf(text, needle) {
|
|
2994
|
+
const idx = text.indexOf(needle);
|
|
2995
|
+
if (idx < 0) return 1;
|
|
2996
|
+
return text.slice(0, idx).split("\n").length;
|
|
2997
|
+
}
|
|
2998
|
+
function snippet(text, line) {
|
|
2999
|
+
const lines = text.split("\n");
|
|
3000
|
+
return (lines[line - 1] ?? "").trim();
|
|
3001
|
+
}
|
|
3002
|
+
function toPosix2(p) {
|
|
3003
|
+
return p.split("\\").join("/");
|
|
3004
|
+
}
|
|
3005
|
+
function languageForPath(relPath) {
|
|
3006
|
+
switch (import_node_path10.default.extname(relPath).toLowerCase()) {
|
|
3007
|
+
case ".py":
|
|
3008
|
+
return "python";
|
|
3009
|
+
case ".ts":
|
|
3010
|
+
case ".tsx":
|
|
3011
|
+
return "typescript";
|
|
3012
|
+
case ".js":
|
|
3013
|
+
case ".jsx":
|
|
3014
|
+
case ".mjs":
|
|
3015
|
+
case ".cjs":
|
|
3016
|
+
return "javascript";
|
|
3017
|
+
default:
|
|
3018
|
+
return void 0;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3022
|
+
let nodesAdded = 0;
|
|
3023
|
+
let edgesAdded = 0;
|
|
3024
|
+
const fileNodeId = (0, import_types7.fileId)(serviceName, relPath);
|
|
3025
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3026
|
+
const language = languageForPath(relPath);
|
|
3027
|
+
const node = {
|
|
3028
|
+
id: fileNodeId,
|
|
3029
|
+
type: import_types7.NodeType.FileNode,
|
|
3030
|
+
service: serviceName,
|
|
3031
|
+
path: relPath,
|
|
3032
|
+
...language ? { language } : {},
|
|
3033
|
+
discoveredVia: "static"
|
|
3034
|
+
};
|
|
3035
|
+
graph.addNode(fileNodeId, node);
|
|
3036
|
+
nodesAdded++;
|
|
3037
|
+
}
|
|
3038
|
+
const containsId = (0, import_types7.extractedEdgeId)(serviceNodeId, fileNodeId, import_types7.EdgeType.CONTAINS);
|
|
3039
|
+
if (!graph.hasEdge(containsId)) {
|
|
3040
|
+
const edge = {
|
|
3041
|
+
id: containsId,
|
|
3042
|
+
source: serviceNodeId,
|
|
3043
|
+
target: fileNodeId,
|
|
3044
|
+
type: import_types7.EdgeType.CONTAINS,
|
|
3045
|
+
provenance: import_types7.Provenance.EXTRACTED,
|
|
3046
|
+
confidence: (0, import_types7.confidenceForExtracted)("structural"),
|
|
3047
|
+
evidence: { file: relPath }
|
|
3048
|
+
};
|
|
3049
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3050
|
+
edgesAdded++;
|
|
3051
|
+
}
|
|
3052
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3053
|
+
}
|
|
2879
3054
|
|
|
2880
3055
|
// src/extract/databases/db-config-yaml.ts
|
|
2881
3056
|
init_cjs_shims();
|
|
2882
|
-
var
|
|
3057
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
2883
3058
|
async function parse(serviceDir) {
|
|
2884
|
-
const yamlPath =
|
|
3059
|
+
const yamlPath = import_node_path11.default.join(serviceDir, "db-config.yaml");
|
|
2885
3060
|
if (!await exists(yamlPath)) return [];
|
|
2886
3061
|
const raw = await readYaml(yamlPath);
|
|
2887
3062
|
return [
|
|
@@ -2899,13 +3074,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
2899
3074
|
|
|
2900
3075
|
// src/extract/databases/dotenv.ts
|
|
2901
3076
|
init_cjs_shims();
|
|
2902
|
-
var
|
|
2903
|
-
var
|
|
3077
|
+
var import_node_fs12 = require("fs");
|
|
3078
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
2904
3079
|
|
|
2905
3080
|
// src/extract/databases/shared.ts
|
|
2906
3081
|
init_cjs_shims();
|
|
2907
|
-
var
|
|
2908
|
-
var
|
|
3082
|
+
var import_node_fs11 = require("fs");
|
|
3083
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
2909
3084
|
function schemeToEngine(scheme) {
|
|
2910
3085
|
const s = scheme.toLowerCase().split("+")[0];
|
|
2911
3086
|
switch (s) {
|
|
@@ -2944,14 +3119,14 @@ function parseConnectionString(url) {
|
|
|
2944
3119
|
}
|
|
2945
3120
|
async function readIfExists(filePath) {
|
|
2946
3121
|
try {
|
|
2947
|
-
return await
|
|
3122
|
+
return await import_node_fs11.promises.readFile(filePath, "utf8");
|
|
2948
3123
|
} catch {
|
|
2949
3124
|
return null;
|
|
2950
3125
|
}
|
|
2951
3126
|
}
|
|
2952
3127
|
async function findFirst(serviceDir, candidates) {
|
|
2953
3128
|
for (const rel of candidates) {
|
|
2954
|
-
const abs =
|
|
3129
|
+
const abs = import_node_path12.default.join(serviceDir, rel);
|
|
2955
3130
|
const content = await readIfExists(abs);
|
|
2956
3131
|
if (content !== null) return abs;
|
|
2957
3132
|
}
|
|
@@ -3002,15 +3177,15 @@ function parseDotenvLine(line) {
|
|
|
3002
3177
|
return { key, value };
|
|
3003
3178
|
}
|
|
3004
3179
|
async function parse2(serviceDir) {
|
|
3005
|
-
const entries = await
|
|
3180
|
+
const entries = await import_node_fs12.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
3006
3181
|
const configs = [];
|
|
3007
3182
|
const seen = /* @__PURE__ */ new Set();
|
|
3008
3183
|
for (const entry2 of entries) {
|
|
3009
3184
|
if (!entry2.isFile()) continue;
|
|
3010
3185
|
const match = isConfigFile(entry2.name);
|
|
3011
3186
|
if (!match.match || match.fileType !== "env") continue;
|
|
3012
|
-
const filePath =
|
|
3013
|
-
const content = await
|
|
3187
|
+
const filePath = import_node_path13.default.join(serviceDir, entry2.name);
|
|
3188
|
+
const content = await import_node_fs12.promises.readFile(filePath, "utf8");
|
|
3014
3189
|
for (const line of content.split("\n")) {
|
|
3015
3190
|
const parsed = parseDotenvLine(line);
|
|
3016
3191
|
if (!parsed) continue;
|
|
@@ -3029,9 +3204,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
3029
3204
|
|
|
3030
3205
|
// src/extract/databases/prisma.ts
|
|
3031
3206
|
init_cjs_shims();
|
|
3032
|
-
var
|
|
3207
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
3033
3208
|
async function parse3(serviceDir) {
|
|
3034
|
-
const schemaPath =
|
|
3209
|
+
const schemaPath = import_node_path14.default.join(serviceDir, "prisma", "schema.prisma");
|
|
3035
3210
|
const content = await readIfExists(schemaPath);
|
|
3036
3211
|
if (!content) return [];
|
|
3037
3212
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -3163,10 +3338,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
3163
3338
|
|
|
3164
3339
|
// src/extract/databases/ormconfig.ts
|
|
3165
3340
|
init_cjs_shims();
|
|
3166
|
-
var
|
|
3341
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
3167
3342
|
async function parse6(serviceDir) {
|
|
3168
3343
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
3169
|
-
const abs =
|
|
3344
|
+
const abs = import_node_path15.default.join(serviceDir, candidate);
|
|
3170
3345
|
if (!await exists(abs)) continue;
|
|
3171
3346
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
3172
3347
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -3226,9 +3401,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
3226
3401
|
|
|
3227
3402
|
// src/extract/databases/sequelize.ts
|
|
3228
3403
|
init_cjs_shims();
|
|
3229
|
-
var
|
|
3404
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
3230
3405
|
async function parse8(serviceDir) {
|
|
3231
|
-
const configPath =
|
|
3406
|
+
const configPath = import_node_path16.default.join(serviceDir, "config", "config.json");
|
|
3232
3407
|
if (!await exists(configPath)) return [];
|
|
3233
3408
|
const raw = await readJson(configPath);
|
|
3234
3409
|
const out = [];
|
|
@@ -3255,7 +3430,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
3255
3430
|
|
|
3256
3431
|
// src/extract/databases/docker-compose.ts
|
|
3257
3432
|
init_cjs_shims();
|
|
3258
|
-
var
|
|
3433
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
3259
3434
|
function portFromService(svc) {
|
|
3260
3435
|
for (const raw of svc.ports ?? []) {
|
|
3261
3436
|
const str = String(raw);
|
|
@@ -3282,7 +3457,7 @@ function databaseFromEnv(svc) {
|
|
|
3282
3457
|
}
|
|
3283
3458
|
async function parse9(serviceDir) {
|
|
3284
3459
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3285
|
-
const abs =
|
|
3460
|
+
const abs = import_node_path17.default.join(serviceDir, name);
|
|
3286
3461
|
if (!await exists(abs)) continue;
|
|
3287
3462
|
const raw = await readYaml(abs);
|
|
3288
3463
|
if (!raw?.services) return [];
|
|
@@ -3323,8 +3498,8 @@ function compatibleDriversFor(engine) {
|
|
|
3323
3498
|
}
|
|
3324
3499
|
function toDatabaseNode(config) {
|
|
3325
3500
|
return {
|
|
3326
|
-
id: (0,
|
|
3327
|
-
type:
|
|
3501
|
+
id: (0, import_types8.databaseId)(config.host),
|
|
3502
|
+
type: import_types8.NodeType.DatabaseNode,
|
|
3328
3503
|
name: config.database || config.host,
|
|
3329
3504
|
engine: config.engine,
|
|
3330
3505
|
engineVersion: config.engineVersion,
|
|
@@ -3454,19 +3629,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3454
3629
|
discoveredVia: mergedDiscoveredVia
|
|
3455
3630
|
});
|
|
3456
3631
|
}
|
|
3632
|
+
const relConfigFile = toPosix2(import_node_path18.default.relative(service.dir, config.sourceFile));
|
|
3633
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
3634
|
+
graph,
|
|
3635
|
+
service.pkg.name,
|
|
3636
|
+
service.node.id,
|
|
3637
|
+
relConfigFile
|
|
3638
|
+
);
|
|
3639
|
+
nodesAdded += fn;
|
|
3640
|
+
edgesAdded += fe;
|
|
3641
|
+
const evidenceFile = toPosix2(import_node_path18.default.relative(scanPath, config.sourceFile));
|
|
3457
3642
|
const edge = {
|
|
3458
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3459
|
-
source:
|
|
3643
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types8.EdgeType.CONNECTS_TO),
|
|
3644
|
+
source: fileNodeId,
|
|
3460
3645
|
target: dbNode.id,
|
|
3461
|
-
type:
|
|
3462
|
-
provenance:
|
|
3463
|
-
confidence: (0,
|
|
3464
|
-
|
|
3465
|
-
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3466
|
-
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
3467
|
-
evidence: {
|
|
3468
|
-
file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
|
|
3469
|
-
}
|
|
3646
|
+
type: import_types8.EdgeType.CONNECTS_TO,
|
|
3647
|
+
provenance: import_types8.Provenance.EXTRACTED,
|
|
3648
|
+
confidence: (0, import_types8.confidenceForExtracted)("structural"),
|
|
3649
|
+
evidence: { file: evidenceFile }
|
|
3470
3650
|
};
|
|
3471
3651
|
if (!graph.hasEdge(edge.id)) {
|
|
3472
3652
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3492,15 +3672,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3492
3672
|
|
|
3493
3673
|
// src/extract/configs.ts
|
|
3494
3674
|
init_cjs_shims();
|
|
3495
|
-
var
|
|
3496
|
-
var
|
|
3497
|
-
var
|
|
3675
|
+
var import_node_fs13 = require("fs");
|
|
3676
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3677
|
+
var import_types9 = require("@neat.is/types");
|
|
3498
3678
|
async function walkConfigFiles(dir) {
|
|
3499
3679
|
const out = [];
|
|
3500
3680
|
async function walk(current) {
|
|
3501
|
-
const entries = await
|
|
3681
|
+
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true });
|
|
3502
3682
|
for (const entry2 of entries) {
|
|
3503
|
-
const full =
|
|
3683
|
+
const full = import_node_path19.default.join(current, entry2.name);
|
|
3504
3684
|
if (entry2.isDirectory()) {
|
|
3505
3685
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3506
3686
|
if (await isPythonVenvDir(full)) continue;
|
|
@@ -3519,26 +3699,35 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3519
3699
|
for (const service of services) {
|
|
3520
3700
|
const configFiles = await walkConfigFiles(service.dir);
|
|
3521
3701
|
for (const file of configFiles) {
|
|
3522
|
-
const relPath =
|
|
3702
|
+
const relPath = import_node_path19.default.relative(scanPath, file);
|
|
3523
3703
|
const node = {
|
|
3524
|
-
id: (0,
|
|
3525
|
-
type:
|
|
3526
|
-
name:
|
|
3704
|
+
id: (0, import_types9.configId)(relPath),
|
|
3705
|
+
type: import_types9.NodeType.ConfigNode,
|
|
3706
|
+
name: import_node_path19.default.basename(file),
|
|
3527
3707
|
path: relPath,
|
|
3528
|
-
fileType: isConfigFile(
|
|
3708
|
+
fileType: isConfigFile(import_node_path19.default.basename(file)).fileType
|
|
3529
3709
|
};
|
|
3530
3710
|
if (!graph.hasNode(node.id)) {
|
|
3531
3711
|
graph.addNode(node.id, node);
|
|
3532
3712
|
nodesAdded++;
|
|
3533
3713
|
}
|
|
3714
|
+
const relToService = toPosix2(import_node_path19.default.relative(service.dir, file));
|
|
3715
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
3716
|
+
graph,
|
|
3717
|
+
service.pkg.name,
|
|
3718
|
+
service.node.id,
|
|
3719
|
+
relToService
|
|
3720
|
+
);
|
|
3721
|
+
nodesAdded += fn;
|
|
3722
|
+
edgesAdded += fe;
|
|
3534
3723
|
const edge = {
|
|
3535
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3536
|
-
source:
|
|
3724
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types9.EdgeType.CONFIGURED_BY),
|
|
3725
|
+
source: fileNodeId,
|
|
3537
3726
|
target: node.id,
|
|
3538
|
-
type:
|
|
3539
|
-
provenance:
|
|
3540
|
-
confidence: (0,
|
|
3541
|
-
evidence: { file: relPath.split(
|
|
3727
|
+
type: import_types9.EdgeType.CONFIGURED_BY,
|
|
3728
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
3729
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3730
|
+
evidence: { file: relPath.split(import_node_path19.default.sep).join("/") }
|
|
3542
3731
|
};
|
|
3543
3732
|
if (!graph.hasEdge(edge.id)) {
|
|
3544
3733
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3560,105 +3749,6 @@ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
|
3560
3749
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3561
3750
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3562
3751
|
var import_types10 = require("@neat.is/types");
|
|
3563
|
-
|
|
3564
|
-
// src/extract/calls/shared.ts
|
|
3565
|
-
init_cjs_shims();
|
|
3566
|
-
var import_node_fs13 = require("fs");
|
|
3567
|
-
var import_node_path19 = __toESM(require("path"), 1);
|
|
3568
|
-
var import_types9 = require("@neat.is/types");
|
|
3569
|
-
async function walkSourceFiles(dir) {
|
|
3570
|
-
const out = [];
|
|
3571
|
-
async function walk(current) {
|
|
3572
|
-
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3573
|
-
for (const entry2 of entries) {
|
|
3574
|
-
const full = import_node_path19.default.join(current, entry2.name);
|
|
3575
|
-
if (entry2.isDirectory()) {
|
|
3576
|
-
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3577
|
-
if (await isPythonVenvDir(full)) continue;
|
|
3578
|
-
await walk(full);
|
|
3579
|
-
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry2.name))) {
|
|
3580
|
-
out.push(full);
|
|
3581
|
-
}
|
|
3582
|
-
}
|
|
3583
|
-
}
|
|
3584
|
-
await walk(dir);
|
|
3585
|
-
return out;
|
|
3586
|
-
}
|
|
3587
|
-
async function loadSourceFiles(dir) {
|
|
3588
|
-
const paths = await walkSourceFiles(dir);
|
|
3589
|
-
const out = [];
|
|
3590
|
-
for (const p of paths) {
|
|
3591
|
-
try {
|
|
3592
|
-
const content = await import_node_fs13.promises.readFile(p, "utf8");
|
|
3593
|
-
out.push({ path: p, content });
|
|
3594
|
-
} catch {
|
|
3595
|
-
}
|
|
3596
|
-
}
|
|
3597
|
-
return out;
|
|
3598
|
-
}
|
|
3599
|
-
function lineOf(text, needle) {
|
|
3600
|
-
const idx = text.indexOf(needle);
|
|
3601
|
-
if (idx < 0) return 1;
|
|
3602
|
-
return text.slice(0, idx).split("\n").length;
|
|
3603
|
-
}
|
|
3604
|
-
function snippet(text, line) {
|
|
3605
|
-
const lines = text.split("\n");
|
|
3606
|
-
return (lines[line - 1] ?? "").trim();
|
|
3607
|
-
}
|
|
3608
|
-
function toPosix2(p) {
|
|
3609
|
-
return p.split("\\").join("/");
|
|
3610
|
-
}
|
|
3611
|
-
function languageForPath(relPath) {
|
|
3612
|
-
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3613
|
-
case ".py":
|
|
3614
|
-
return "python";
|
|
3615
|
-
case ".ts":
|
|
3616
|
-
case ".tsx":
|
|
3617
|
-
return "typescript";
|
|
3618
|
-
case ".js":
|
|
3619
|
-
case ".jsx":
|
|
3620
|
-
case ".mjs":
|
|
3621
|
-
case ".cjs":
|
|
3622
|
-
return "javascript";
|
|
3623
|
-
default:
|
|
3624
|
-
return void 0;
|
|
3625
|
-
}
|
|
3626
|
-
}
|
|
3627
|
-
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3628
|
-
let nodesAdded = 0;
|
|
3629
|
-
let edgesAdded = 0;
|
|
3630
|
-
const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
|
|
3631
|
-
if (!graph.hasNode(fileNodeId)) {
|
|
3632
|
-
const language = languageForPath(relPath);
|
|
3633
|
-
const node = {
|
|
3634
|
-
id: fileNodeId,
|
|
3635
|
-
type: import_types9.NodeType.FileNode,
|
|
3636
|
-
service: serviceName,
|
|
3637
|
-
path: relPath,
|
|
3638
|
-
...language ? { language } : {},
|
|
3639
|
-
discoveredVia: "static"
|
|
3640
|
-
};
|
|
3641
|
-
graph.addNode(fileNodeId, node);
|
|
3642
|
-
nodesAdded++;
|
|
3643
|
-
}
|
|
3644
|
-
const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
|
|
3645
|
-
if (!graph.hasEdge(containsId)) {
|
|
3646
|
-
const edge = {
|
|
3647
|
-
id: containsId,
|
|
3648
|
-
source: serviceNodeId,
|
|
3649
|
-
target: fileNodeId,
|
|
3650
|
-
type: import_types9.EdgeType.CONTAINS,
|
|
3651
|
-
provenance: import_types9.Provenance.EXTRACTED,
|
|
3652
|
-
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3653
|
-
evidence: { file: relPath }
|
|
3654
|
-
};
|
|
3655
|
-
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3656
|
-
edgesAdded++;
|
|
3657
|
-
}
|
|
3658
|
-
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3659
|
-
}
|
|
3660
|
-
|
|
3661
|
-
// src/extract/calls/http.ts
|
|
3662
3752
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
3663
3753
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
3664
3754
|
function isInsideJsxExternalLink(node) {
|
|
@@ -3686,8 +3776,14 @@ function collectStringLiterals(node, out) {
|
|
|
3686
3776
|
if (child) collectStringLiterals(child, out);
|
|
3687
3777
|
}
|
|
3688
3778
|
}
|
|
3779
|
+
var PARSE_CHUNK = 16384;
|
|
3780
|
+
function parseSource(parser, source) {
|
|
3781
|
+
return parser.parse(
|
|
3782
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
|
|
3783
|
+
);
|
|
3784
|
+
}
|
|
3689
3785
|
function callsFromSource(source, parser, knownHosts) {
|
|
3690
|
-
const tree = parser
|
|
3786
|
+
const tree = parseSource(parser, source);
|
|
3691
3787
|
const literals = [];
|
|
3692
3788
|
collectStringLiterals(tree.rootNode, literals);
|
|
3693
3789
|
const out = [];
|
|
@@ -3936,6 +4032,7 @@ var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-
|
|
|
3936
4032
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
3937
4033
|
function isLikelyAddress(value) {
|
|
3938
4034
|
if (!value) return false;
|
|
4035
|
+
if (/\s/.test(value) || value.startsWith("{")) return false;
|
|
3939
4036
|
return /:\d{2,5}$/.test(value) || value.includes(".");
|
|
3940
4037
|
}
|
|
3941
4038
|
function normaliseForMatch(s) {
|
|
@@ -4247,17 +4344,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4247
4344
|
graph.addNode(node.id, node);
|
|
4248
4345
|
nodesAdded++;
|
|
4249
4346
|
}
|
|
4250
|
-
const
|
|
4347
|
+
const relDockerfile = toPosix2(import_node_path26.default.relative(service.dir, dockerfilePath));
|
|
4348
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4349
|
+
graph,
|
|
4350
|
+
service.pkg.name,
|
|
4351
|
+
service.node.id,
|
|
4352
|
+
relDockerfile
|
|
4353
|
+
);
|
|
4354
|
+
nodesAdded += fn;
|
|
4355
|
+
edgesAdded += fe;
|
|
4356
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types18.EdgeType.RUNS_ON);
|
|
4251
4357
|
if (!graph.hasEdge(edgeId)) {
|
|
4252
4358
|
const edge = {
|
|
4253
4359
|
id: edgeId,
|
|
4254
|
-
source:
|
|
4360
|
+
source: fileNodeId,
|
|
4255
4361
|
target: node.id,
|
|
4256
4362
|
type: import_types18.EdgeType.RUNS_ON,
|
|
4257
4363
|
provenance: import_types18.Provenance.EXTRACTED,
|
|
4258
4364
|
confidence: (0, import_types18.confidenceForExtracted)("structural"),
|
|
4259
4365
|
evidence: {
|
|
4260
|
-
file: import_node_path26.default.relative(scanPath, dockerfilePath)
|
|
4366
|
+
file: toPosix2(import_node_path26.default.relative(scanPath, dockerfilePath))
|
|
4261
4367
|
}
|
|
4262
4368
|
};
|
|
4263
4369
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4648,6 +4754,321 @@ var import_fastify = __toESM(require("fastify"), 1);
|
|
|
4648
4754
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
4649
4755
|
var import_types23 = require("@neat.is/types");
|
|
4650
4756
|
|
|
4757
|
+
// src/extend/index.ts
|
|
4758
|
+
init_cjs_shims();
|
|
4759
|
+
var import_node_fs20 = require("fs");
|
|
4760
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
4761
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
4762
|
+
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
4763
|
+
|
|
4764
|
+
// src/installers/package-manager.ts
|
|
4765
|
+
init_cjs_shims();
|
|
4766
|
+
var import_node_fs19 = require("fs");
|
|
4767
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
4768
|
+
var import_node_child_process = require("child_process");
|
|
4769
|
+
var LOCKFILE_PRIORITY = [
|
|
4770
|
+
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
4771
|
+
{ lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
|
|
4772
|
+
{ lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
|
|
4773
|
+
{
|
|
4774
|
+
lockfile: "package-lock.json",
|
|
4775
|
+
pm: "npm",
|
|
4776
|
+
args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
|
|
4777
|
+
}
|
|
4778
|
+
];
|
|
4779
|
+
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
4780
|
+
async function exists2(p) {
|
|
4781
|
+
try {
|
|
4782
|
+
await import_node_fs19.promises.access(p);
|
|
4783
|
+
return true;
|
|
4784
|
+
} catch {
|
|
4785
|
+
return false;
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
async function detectPackageManager(serviceDir) {
|
|
4789
|
+
let dir = import_node_path33.default.resolve(serviceDir);
|
|
4790
|
+
const stops = /* @__PURE__ */ new Set();
|
|
4791
|
+
for (let i = 0; i < 64; i++) {
|
|
4792
|
+
if (stops.has(dir)) break;
|
|
4793
|
+
stops.add(dir);
|
|
4794
|
+
for (const candidate of LOCKFILE_PRIORITY) {
|
|
4795
|
+
const lockPath = import_node_path33.default.join(dir, candidate.lockfile);
|
|
4796
|
+
if (await exists2(lockPath)) {
|
|
4797
|
+
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
const parent = import_node_path33.default.dirname(dir);
|
|
4801
|
+
if (parent === dir) break;
|
|
4802
|
+
dir = parent;
|
|
4803
|
+
}
|
|
4804
|
+
return { pm: "npm", cwd: import_node_path33.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
4805
|
+
}
|
|
4806
|
+
async function runPackageManagerInstall(cmd) {
|
|
4807
|
+
return new Promise((resolve) => {
|
|
4808
|
+
const child = (0, import_node_child_process.spawn)(cmd.pm, cmd.args, {
|
|
4809
|
+
cwd: cmd.cwd,
|
|
4810
|
+
// Inherit PATH + HOME so the user's installed managers resolve.
|
|
4811
|
+
env: process.env,
|
|
4812
|
+
// `false` keeps the parent in control of cleanup if the orchestrator
|
|
4813
|
+
// exits before install finishes. Cross-platform-safe.
|
|
4814
|
+
shell: false,
|
|
4815
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
4816
|
+
});
|
|
4817
|
+
let stderr = "";
|
|
4818
|
+
child.stderr?.on("data", (chunk) => {
|
|
4819
|
+
stderr += chunk.toString("utf8");
|
|
4820
|
+
});
|
|
4821
|
+
child.on("error", (err) => {
|
|
4822
|
+
resolve({
|
|
4823
|
+
pm: cmd.pm,
|
|
4824
|
+
cwd: cmd.cwd,
|
|
4825
|
+
args: cmd.args,
|
|
4826
|
+
exitCode: 127,
|
|
4827
|
+
stderr: stderr + `
|
|
4828
|
+
${err.message}`
|
|
4829
|
+
});
|
|
4830
|
+
});
|
|
4831
|
+
child.on("close", (code) => {
|
|
4832
|
+
resolve({
|
|
4833
|
+
pm: cmd.pm,
|
|
4834
|
+
cwd: cmd.cwd,
|
|
4835
|
+
args: cmd.args,
|
|
4836
|
+
exitCode: code ?? 1,
|
|
4837
|
+
stderr: stderr.trim()
|
|
4838
|
+
});
|
|
4839
|
+
});
|
|
4840
|
+
});
|
|
4841
|
+
}
|
|
4842
|
+
|
|
4843
|
+
// src/extend/index.ts
|
|
4844
|
+
async function fileExists(p) {
|
|
4845
|
+
try {
|
|
4846
|
+
await import_node_fs20.promises.access(p);
|
|
4847
|
+
return true;
|
|
4848
|
+
} catch {
|
|
4849
|
+
return false;
|
|
4850
|
+
}
|
|
4851
|
+
}
|
|
4852
|
+
async function readPackageJson(scanPath) {
|
|
4853
|
+
const pkgPath = import_node_path34.default.join(scanPath, "package.json");
|
|
4854
|
+
const raw = await import_node_fs20.promises.readFile(pkgPath, "utf8");
|
|
4855
|
+
return JSON.parse(raw);
|
|
4856
|
+
}
|
|
4857
|
+
async function findHookFiles(scanPath) {
|
|
4858
|
+
const entries = await import_node_fs20.promises.readdir(scanPath);
|
|
4859
|
+
return entries.filter(
|
|
4860
|
+
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
4861
|
+
).sort();
|
|
4862
|
+
}
|
|
4863
|
+
function extendLogPath() {
|
|
4864
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path34.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
4865
|
+
}
|
|
4866
|
+
async function appendExtendLog(entry2) {
|
|
4867
|
+
const logPath = extendLogPath();
|
|
4868
|
+
await import_node_fs20.promises.mkdir(import_node_path34.default.dirname(logPath), { recursive: true });
|
|
4869
|
+
await import_node_fs20.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
|
|
4870
|
+
}
|
|
4871
|
+
function splicedContent(fileContent, snippet2) {
|
|
4872
|
+
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
4873
|
+
return fileContent.replace("__INSTRUMENTATION_BLOCK__", `${snippet2}
|
|
4874
|
+
__INSTRUMENTATION_BLOCK__`);
|
|
4875
|
+
}
|
|
4876
|
+
const lines = fileContent.split("\n");
|
|
4877
|
+
let lastPushIdx = -1;
|
|
4878
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4879
|
+
if (lines[i].includes("instrumentations.push(")) lastPushIdx = i;
|
|
4880
|
+
}
|
|
4881
|
+
if (lastPushIdx >= 0) {
|
|
4882
|
+
lines.splice(lastPushIdx + 1, 0, snippet2);
|
|
4883
|
+
return lines.join("\n");
|
|
4884
|
+
}
|
|
4885
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4886
|
+
if (lines[i].includes("new NodeSDK(")) {
|
|
4887
|
+
lines.splice(i, 0, snippet2);
|
|
4888
|
+
return lines.join("\n");
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
return null;
|
|
4892
|
+
}
|
|
4893
|
+
async function listUninstrumented(ctx) {
|
|
4894
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
4895
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
4896
|
+
const results = [];
|
|
4897
|
+
for (const [library, installedVersion] of Object.entries(allDeps)) {
|
|
4898
|
+
const entry2 = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
4899
|
+
if (!entry2) continue;
|
|
4900
|
+
if (entry2.coverage === "bundled" || entry2.coverage === "http-only") continue;
|
|
4901
|
+
results.push({
|
|
4902
|
+
library,
|
|
4903
|
+
coverage: entry2.coverage,
|
|
4904
|
+
installedVersion,
|
|
4905
|
+
instrumentation_package: entry2.instrumentation_package,
|
|
4906
|
+
package_version: entry2.package_version,
|
|
4907
|
+
registration: entry2.registration,
|
|
4908
|
+
notes: entry2.notes
|
|
4909
|
+
});
|
|
4910
|
+
}
|
|
4911
|
+
return results;
|
|
4912
|
+
}
|
|
4913
|
+
function lookupInstrumentation(library, installedVersion) {
|
|
4914
|
+
const entry2 = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
4915
|
+
if (!entry2) return null;
|
|
4916
|
+
return {
|
|
4917
|
+
library: entry2.library,
|
|
4918
|
+
coverage: entry2.coverage,
|
|
4919
|
+
instrumentation_package: entry2.instrumentation_package,
|
|
4920
|
+
package_version: entry2.package_version,
|
|
4921
|
+
registration: entry2.registration,
|
|
4922
|
+
notes: entry2.notes
|
|
4923
|
+
};
|
|
4924
|
+
}
|
|
4925
|
+
async function describeProjectInstrumentation(ctx) {
|
|
4926
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
4927
|
+
const envNeat = await fileExists(import_node_path34.default.join(ctx.scanPath, ".env.neat"));
|
|
4928
|
+
const registryInstrPackages = new Set(
|
|
4929
|
+
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
4930
|
+
);
|
|
4931
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
4932
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
4933
|
+
const installedDeps = {};
|
|
4934
|
+
for (const [key, version] of Object.entries(allDeps)) {
|
|
4935
|
+
if (key.startsWith("@opentelemetry/") || registryInstrPackages.has(key)) {
|
|
4936
|
+
installedDeps[key] = version;
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
return { hookFiles, envNeat, installedDeps };
|
|
4940
|
+
}
|
|
4941
|
+
async function applyExtension(ctx, args, options) {
|
|
4942
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
4943
|
+
if (hookFiles.length === 0) {
|
|
4944
|
+
throw new Error(
|
|
4945
|
+
`No instrumentation hook files found in ${ctx.scanPath}. Run \`neat init\` first.`
|
|
4946
|
+
);
|
|
4947
|
+
}
|
|
4948
|
+
for (const file of hookFiles) {
|
|
4949
|
+
const content = await import_node_fs20.promises.readFile(import_node_path34.default.join(ctx.scanPath, file), "utf8");
|
|
4950
|
+
if (content.includes(args.registration_snippet)) {
|
|
4951
|
+
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
const primaryFile = hookFiles[0];
|
|
4955
|
+
const primaryPath = import_node_path34.default.join(ctx.scanPath, primaryFile);
|
|
4956
|
+
const filesTouched = [];
|
|
4957
|
+
const depsAdded = [];
|
|
4958
|
+
const pkgPath = import_node_path34.default.join(ctx.scanPath, "package.json");
|
|
4959
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
4960
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
4961
|
+
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
4962
|
+
await import_node_fs20.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
4963
|
+
filesTouched.push("package.json");
|
|
4964
|
+
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
4965
|
+
}
|
|
4966
|
+
const hookContent = await import_node_fs20.promises.readFile(primaryPath, "utf8");
|
|
4967
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
4968
|
+
if (!patched) {
|
|
4969
|
+
throw new Error(
|
|
4970
|
+
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
4971
|
+
);
|
|
4972
|
+
}
|
|
4973
|
+
await import_node_fs20.promises.writeFile(primaryPath, patched, "utf8");
|
|
4974
|
+
filesTouched.push(primaryFile);
|
|
4975
|
+
const cmd = await detectPackageManager(ctx.scanPath);
|
|
4976
|
+
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
4977
|
+
const install = await installer(cmd);
|
|
4978
|
+
const installOutput = install.exitCode === 0 ? `${cmd.pm} install succeeded` : install.stderr || `${cmd.pm} install failed (exit ${install.exitCode})`;
|
|
4979
|
+
await appendExtendLog({
|
|
4980
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4981
|
+
project: ctx.project,
|
|
4982
|
+
library: args.library,
|
|
4983
|
+
instrumentation_package: args.instrumentation_package,
|
|
4984
|
+
version: args.version,
|
|
4985
|
+
registration_snippet: args.registration_snippet,
|
|
4986
|
+
filesTouched,
|
|
4987
|
+
depsAdded,
|
|
4988
|
+
installOutput
|
|
4989
|
+
});
|
|
4990
|
+
return { library: args.library, filesTouched, depsAdded, installOutput, alreadyApplied: false };
|
|
4991
|
+
}
|
|
4992
|
+
async function dryRunExtension(ctx, args) {
|
|
4993
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
4994
|
+
if (hookFiles.length === 0) {
|
|
4995
|
+
return {
|
|
4996
|
+
library: args.library,
|
|
4997
|
+
filesTouched: [],
|
|
4998
|
+
depsToAdd: [],
|
|
4999
|
+
packageJsonPatch: {},
|
|
5000
|
+
templatePatch: "No hook files found. Run 'neat init' first."
|
|
5001
|
+
};
|
|
5002
|
+
}
|
|
5003
|
+
for (const file of hookFiles) {
|
|
5004
|
+
const content = await import_node_fs20.promises.readFile(import_node_path34.default.join(ctx.scanPath, file), "utf8");
|
|
5005
|
+
if (content.includes(args.registration_snippet)) {
|
|
5006
|
+
return {
|
|
5007
|
+
library: args.library,
|
|
5008
|
+
filesTouched: [],
|
|
5009
|
+
depsToAdd: [],
|
|
5010
|
+
packageJsonPatch: {},
|
|
5011
|
+
templatePatch: "Already applied \u2014 no changes would be made."
|
|
5012
|
+
};
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
const primaryFile = hookFiles[0];
|
|
5016
|
+
const filesTouched = [];
|
|
5017
|
+
const depsToAdd = [];
|
|
5018
|
+
let packageJsonPatch = {};
|
|
5019
|
+
let templatePatch = "";
|
|
5020
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5021
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5022
|
+
packageJsonPatch = { dependencies: { [args.instrumentation_package]: args.version } };
|
|
5023
|
+
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
5024
|
+
filesTouched.push("package.json");
|
|
5025
|
+
}
|
|
5026
|
+
const hookContent = await import_node_fs20.promises.readFile(import_node_path34.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
5027
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5028
|
+
if (patched) {
|
|
5029
|
+
filesTouched.push(primaryFile);
|
|
5030
|
+
templatePatch = `+ ${args.registration_snippet}`;
|
|
5031
|
+
} else {
|
|
5032
|
+
templatePatch = "Could not find insertion point in hook file.";
|
|
5033
|
+
}
|
|
5034
|
+
return { library: args.library, filesTouched, depsToAdd, packageJsonPatch, templatePatch };
|
|
5035
|
+
}
|
|
5036
|
+
async function rollbackExtension(ctx, args) {
|
|
5037
|
+
const logPath = extendLogPath();
|
|
5038
|
+
if (!await fileExists(logPath)) {
|
|
5039
|
+
return { undone: false, message: "no apply found for library" };
|
|
5040
|
+
}
|
|
5041
|
+
const raw = await import_node_fs20.promises.readFile(logPath, "utf8");
|
|
5042
|
+
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
5043
|
+
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
5044
|
+
if (!match) {
|
|
5045
|
+
return { undone: false, message: "no apply found for library" };
|
|
5046
|
+
}
|
|
5047
|
+
const pkgPath = import_node_path34.default.join(ctx.scanPath, "package.json");
|
|
5048
|
+
if (await fileExists(pkgPath)) {
|
|
5049
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5050
|
+
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
5051
|
+
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
5052
|
+
pkg.dependencies = rest;
|
|
5053
|
+
await import_node_fs20.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5054
|
+
}
|
|
5055
|
+
}
|
|
5056
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5057
|
+
for (const file of hookFiles) {
|
|
5058
|
+
const filePath = import_node_path34.default.join(ctx.scanPath, file);
|
|
5059
|
+
const content = await import_node_fs20.promises.readFile(filePath, "utf8");
|
|
5060
|
+
if (content.includes(match.registration_snippet)) {
|
|
5061
|
+
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
5062
|
+
await import_node_fs20.promises.writeFile(filePath, filtered, "utf8");
|
|
5063
|
+
break;
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
return {
|
|
5067
|
+
undone: true,
|
|
5068
|
+
message: `rolled back ${match.library} (${match.instrumentation_package})`
|
|
5069
|
+
};
|
|
5070
|
+
}
|
|
5071
|
+
|
|
4651
5072
|
// src/divergences.ts
|
|
4652
5073
|
init_cjs_shims();
|
|
4653
5074
|
var import_types21 = require("@neat.is/types");
|
|
@@ -4888,7 +5309,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4888
5309
|
|
|
4889
5310
|
// src/diff.ts
|
|
4890
5311
|
init_cjs_shims();
|
|
4891
|
-
var
|
|
5312
|
+
var import_node_fs21 = require("fs");
|
|
4892
5313
|
async function loadSnapshotForDiff(target) {
|
|
4893
5314
|
if (/^https?:\/\//i.test(target)) {
|
|
4894
5315
|
const res = await fetch(target);
|
|
@@ -4897,7 +5318,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
4897
5318
|
}
|
|
4898
5319
|
return await res.json();
|
|
4899
5320
|
}
|
|
4900
|
-
const raw = await
|
|
5321
|
+
const raw = await import_node_fs21.promises.readFile(target, "utf8");
|
|
4901
5322
|
return JSON.parse(raw);
|
|
4902
5323
|
}
|
|
4903
5324
|
function indexEntries(entries) {
|
|
@@ -4965,57 +5386,127 @@ function canonicalJson(value) {
|
|
|
4965
5386
|
|
|
4966
5387
|
// src/registry.ts
|
|
4967
5388
|
init_cjs_shims();
|
|
4968
|
-
var
|
|
4969
|
-
var
|
|
4970
|
-
var
|
|
5389
|
+
var import_node_fs22 = require("fs");
|
|
5390
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
5391
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
4971
5392
|
var import_types22 = require("@neat.is/types");
|
|
4972
5393
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
4973
5394
|
var LOCK_RETRY_MS = 50;
|
|
4974
5395
|
function neatHome() {
|
|
4975
5396
|
const override = process.env.NEAT_HOME;
|
|
4976
|
-
if (override && override.length > 0) return
|
|
4977
|
-
return
|
|
5397
|
+
if (override && override.length > 0) return import_node_path35.default.resolve(override);
|
|
5398
|
+
return import_node_path35.default.join(import_node_os3.default.homedir(), ".neat");
|
|
4978
5399
|
}
|
|
4979
5400
|
function registryPath() {
|
|
4980
|
-
return
|
|
5401
|
+
return import_node_path35.default.join(neatHome(), "projects.json");
|
|
4981
5402
|
}
|
|
4982
5403
|
function registryLockPath() {
|
|
4983
|
-
return
|
|
5404
|
+
return import_node_path35.default.join(neatHome(), "projects.json.lock");
|
|
5405
|
+
}
|
|
5406
|
+
function daemonPidPath() {
|
|
5407
|
+
return import_node_path35.default.join(neatHome(), "neatd.pid");
|
|
5408
|
+
}
|
|
5409
|
+
function isPidAliveDefault(pid) {
|
|
5410
|
+
try {
|
|
5411
|
+
process.kill(pid, 0);
|
|
5412
|
+
return true;
|
|
5413
|
+
} catch (err) {
|
|
5414
|
+
return err.code === "EPERM";
|
|
5415
|
+
}
|
|
5416
|
+
}
|
|
5417
|
+
async function readPidFile(file) {
|
|
5418
|
+
try {
|
|
5419
|
+
const raw = await import_node_fs22.promises.readFile(file, "utf8");
|
|
5420
|
+
const pid = Number.parseInt(raw.trim(), 10);
|
|
5421
|
+
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
5422
|
+
} catch {
|
|
5423
|
+
return void 0;
|
|
5424
|
+
}
|
|
5425
|
+
}
|
|
5426
|
+
var defaultLockHolderProbe = {
|
|
5427
|
+
isPidAlive: isPidAliveDefault,
|
|
5428
|
+
daemonPidFromFile: () => readPidFile(daemonPidPath()),
|
|
5429
|
+
async daemonResponds() {
|
|
5430
|
+
const base = process.env.NEAT_API_URL ?? "http://localhost:8080";
|
|
5431
|
+
try {
|
|
5432
|
+
await fetch(`${base}/health`, { signal: AbortSignal.timeout(750) });
|
|
5433
|
+
return true;
|
|
5434
|
+
} catch {
|
|
5435
|
+
return false;
|
|
5436
|
+
}
|
|
5437
|
+
}
|
|
5438
|
+
};
|
|
5439
|
+
async function readLockPid(lockPath) {
|
|
5440
|
+
return readPidFile(lockPath);
|
|
5441
|
+
}
|
|
5442
|
+
async function classifyLockHolder(lockPath, probe = defaultLockHolderProbe) {
|
|
5443
|
+
const lockPid = await readLockPid(lockPath);
|
|
5444
|
+
const daemonPid = await probe.daemonPidFromFile();
|
|
5445
|
+
if (daemonPid !== void 0 && daemonPid !== process.pid && probe.isPidAlive(daemonPid) && // The lock already names the daemon, or the daemon answers on its port.
|
|
5446
|
+
// Either confirms a live daemon is in the picture (the second guards
|
|
5447
|
+
// against a stale pidfile whose PID got reused).
|
|
5448
|
+
(daemonPid === lockPid || await probe.daemonResponds())) {
|
|
5449
|
+
return { kind: "daemon", pid: daemonPid };
|
|
5450
|
+
}
|
|
5451
|
+
if (lockPid !== void 0 && lockPid !== process.pid && probe.isPidAlive(lockPid)) {
|
|
5452
|
+
return { kind: "command", pid: lockPid };
|
|
5453
|
+
}
|
|
5454
|
+
return { kind: "stale" };
|
|
5455
|
+
}
|
|
5456
|
+
function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
5457
|
+
switch (holder.kind) {
|
|
5458
|
+
case "daemon":
|
|
5459
|
+
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\`.`;
|
|
5460
|
+
case "command":
|
|
5461
|
+
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.`;
|
|
5462
|
+
case "stale":
|
|
5463
|
+
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.`;
|
|
5464
|
+
}
|
|
4984
5465
|
}
|
|
4985
5466
|
async function writeAtomically(target, contents) {
|
|
4986
|
-
await
|
|
5467
|
+
await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(target), { recursive: true });
|
|
4987
5468
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
4988
|
-
const fd = await
|
|
5469
|
+
const fd = await import_node_fs22.promises.open(tmp, "w");
|
|
4989
5470
|
try {
|
|
4990
5471
|
await fd.writeFile(contents, "utf8");
|
|
4991
5472
|
await fd.sync();
|
|
4992
5473
|
} finally {
|
|
4993
5474
|
await fd.close();
|
|
4994
5475
|
}
|
|
4995
|
-
await
|
|
5476
|
+
await import_node_fs22.promises.rename(tmp, target);
|
|
4996
5477
|
}
|
|
4997
|
-
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
5478
|
+
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
4998
5479
|
const deadline = Date.now() + timeoutMs;
|
|
4999
|
-
await
|
|
5480
|
+
await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(lockPath), { recursive: true });
|
|
5481
|
+
let probedHolder = false;
|
|
5000
5482
|
while (true) {
|
|
5001
5483
|
try {
|
|
5002
|
-
const fd = await
|
|
5003
|
-
|
|
5484
|
+
const fd = await import_node_fs22.promises.open(lockPath, "wx");
|
|
5485
|
+
try {
|
|
5486
|
+
await fd.writeFile(`${process.pid}
|
|
5487
|
+
`, "utf8");
|
|
5488
|
+
} finally {
|
|
5489
|
+
await fd.close();
|
|
5490
|
+
}
|
|
5004
5491
|
return;
|
|
5005
5492
|
} catch (err) {
|
|
5006
5493
|
const code = err.code;
|
|
5007
5494
|
if (code !== "EEXIST") throw err;
|
|
5495
|
+
if (!probedHolder) {
|
|
5496
|
+
probedHolder = true;
|
|
5497
|
+
const holder = await classifyLockHolder(lockPath, probe);
|
|
5498
|
+
if (holder.kind === "daemon") throw new Error(lockHolderMessage(holder, lockPath, timeoutMs));
|
|
5499
|
+
}
|
|
5008
5500
|
if (Date.now() >= deadline) {
|
|
5009
|
-
|
|
5010
|
-
|
|
5011
|
-
);
|
|
5501
|
+
const holder = await classifyLockHolder(lockPath, probe);
|
|
5502
|
+
throw new Error(lockHolderMessage(holder, lockPath, timeoutMs));
|
|
5012
5503
|
}
|
|
5013
5504
|
await new Promise((r) => setTimeout(r, LOCK_RETRY_MS));
|
|
5014
5505
|
}
|
|
5015
5506
|
}
|
|
5016
5507
|
}
|
|
5017
5508
|
async function releaseLock(lockPath) {
|
|
5018
|
-
await
|
|
5509
|
+
await import_node_fs22.promises.unlink(lockPath).catch(() => {
|
|
5019
5510
|
});
|
|
5020
5511
|
}
|
|
5021
5512
|
async function withLock(fn) {
|
|
@@ -5031,7 +5522,7 @@ async function readRegistry() {
|
|
|
5031
5522
|
const file = registryPath();
|
|
5032
5523
|
let raw;
|
|
5033
5524
|
try {
|
|
5034
|
-
raw = await
|
|
5525
|
+
raw = await import_node_fs22.promises.readFile(file, "utf8");
|
|
5035
5526
|
} catch (err) {
|
|
5036
5527
|
if (err.code === "ENOENT") {
|
|
5037
5528
|
return { version: 1, projects: [] };
|
|
@@ -5531,6 +6022,105 @@ function registerRoutes(scope, ctx) {
|
|
|
5531
6022
|
violations
|
|
5532
6023
|
};
|
|
5533
6024
|
});
|
|
6025
|
+
scope.get("/extend/list-uninstrumented", async (req2, reply) => {
|
|
6026
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6027
|
+
if (!proj) return;
|
|
6028
|
+
if (!proj.scanPath) {
|
|
6029
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6030
|
+
}
|
|
6031
|
+
try {
|
|
6032
|
+
const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
|
|
6033
|
+
return { libraries: results };
|
|
6034
|
+
} catch (err) {
|
|
6035
|
+
return reply.code(500).send({ error: err.message });
|
|
6036
|
+
}
|
|
6037
|
+
});
|
|
6038
|
+
scope.get("/extend/lookup", async (req2, reply) => {
|
|
6039
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6040
|
+
if (!proj) return;
|
|
6041
|
+
const { library, version } = req2.query;
|
|
6042
|
+
if (!library) {
|
|
6043
|
+
return reply.code(400).send({ error: "query parameter `library` is required" });
|
|
6044
|
+
}
|
|
6045
|
+
const result = lookupInstrumentation(library, version);
|
|
6046
|
+
if (!result) {
|
|
6047
|
+
return reply.code(404).send({ error: "library not found in registry", library });
|
|
6048
|
+
}
|
|
6049
|
+
return result;
|
|
6050
|
+
});
|
|
6051
|
+
scope.get("/extend/describe", async (req2, reply) => {
|
|
6052
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6053
|
+
if (!proj) return;
|
|
6054
|
+
if (!proj.scanPath) {
|
|
6055
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6056
|
+
}
|
|
6057
|
+
try {
|
|
6058
|
+
const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
|
|
6059
|
+
return state;
|
|
6060
|
+
} catch (err) {
|
|
6061
|
+
return reply.code(500).send({ error: err.message });
|
|
6062
|
+
}
|
|
6063
|
+
});
|
|
6064
|
+
scope.post("/extend/apply", async (req2, reply) => {
|
|
6065
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6066
|
+
if (!proj) return;
|
|
6067
|
+
if (!proj.scanPath) {
|
|
6068
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6069
|
+
}
|
|
6070
|
+
const { library, instrumentation_package, version, registration_snippet } = req2.body ?? {};
|
|
6071
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6072
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6073
|
+
}
|
|
6074
|
+
try {
|
|
6075
|
+
const result = await applyExtension(
|
|
6076
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6077
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6078
|
+
);
|
|
6079
|
+
return result;
|
|
6080
|
+
} catch (err) {
|
|
6081
|
+
return reply.code(500).send({ error: err.message });
|
|
6082
|
+
}
|
|
6083
|
+
});
|
|
6084
|
+
scope.post("/extend/dry-run", async (req2, reply) => {
|
|
6085
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6086
|
+
if (!proj) return;
|
|
6087
|
+
if (!proj.scanPath) {
|
|
6088
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6089
|
+
}
|
|
6090
|
+
const { library, instrumentation_package, version, registration_snippet } = req2.body ?? {};
|
|
6091
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6092
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6093
|
+
}
|
|
6094
|
+
try {
|
|
6095
|
+
const result = await dryRunExtension(
|
|
6096
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6097
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6098
|
+
);
|
|
6099
|
+
return result;
|
|
6100
|
+
} catch (err) {
|
|
6101
|
+
return reply.code(500).send({ error: err.message });
|
|
6102
|
+
}
|
|
6103
|
+
});
|
|
6104
|
+
scope.post("/extend/rollback", async (req2, reply) => {
|
|
6105
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6106
|
+
if (!proj) return;
|
|
6107
|
+
if (!proj.scanPath) {
|
|
6108
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6109
|
+
}
|
|
6110
|
+
const { library } = req2.body ?? {};
|
|
6111
|
+
if (!library) {
|
|
6112
|
+
return reply.code(400).send({ error: "body must include library" });
|
|
6113
|
+
}
|
|
6114
|
+
try {
|
|
6115
|
+
const result = await rollbackExtension(
|
|
6116
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6117
|
+
{ library }
|
|
6118
|
+
);
|
|
6119
|
+
return result;
|
|
6120
|
+
} catch (err) {
|
|
6121
|
+
return reply.code(500).send({ error: err.message });
|
|
6122
|
+
}
|
|
6123
|
+
});
|
|
5534
6124
|
}
|
|
5535
6125
|
async function buildApi(opts) {
|
|
5536
6126
|
const app = (0, import_fastify.default)({ logger: false });
|
|
@@ -5641,8 +6231,8 @@ init_auth();
|
|
|
5641
6231
|
|
|
5642
6232
|
// src/unrouted.ts
|
|
5643
6233
|
init_cjs_shims();
|
|
5644
|
-
var
|
|
5645
|
-
var
|
|
6234
|
+
var import_node_fs23 = require("fs");
|
|
6235
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
5646
6236
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
5647
6237
|
return {
|
|
5648
6238
|
timestamp: now.toISOString(),
|
|
@@ -5652,21 +6242,21 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
5652
6242
|
};
|
|
5653
6243
|
}
|
|
5654
6244
|
async function appendUnroutedSpan(neatHome3, record) {
|
|
5655
|
-
const target =
|
|
5656
|
-
await
|
|
5657
|
-
await
|
|
6245
|
+
const target = import_node_path38.default.join(neatHome3, "errors.ndjson");
|
|
6246
|
+
await import_node_fs23.promises.mkdir(neatHome3, { recursive: true });
|
|
6247
|
+
await import_node_fs23.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
5658
6248
|
}
|
|
5659
6249
|
function unroutedErrorsPath(neatHome3) {
|
|
5660
|
-
return
|
|
6250
|
+
return import_node_path38.default.join(neatHome3, "errors.ndjson");
|
|
5661
6251
|
}
|
|
5662
6252
|
|
|
5663
6253
|
// src/daemon.ts
|
|
5664
6254
|
function neatHomeFor(opts) {
|
|
5665
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
6255
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path39.default.resolve(opts.neatHome);
|
|
5666
6256
|
const env = process.env.NEAT_HOME;
|
|
5667
|
-
if (env && env.length > 0) return
|
|
6257
|
+
if (env && env.length > 0) return import_node_path39.default.resolve(env);
|
|
5668
6258
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
5669
|
-
return
|
|
6259
|
+
return import_node_path39.default.join(home, ".neat");
|
|
5670
6260
|
}
|
|
5671
6261
|
function routeSpanToProject(serviceName, projects) {
|
|
5672
6262
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -5701,9 +6291,9 @@ function isTokenContained(needle, haystack) {
|
|
|
5701
6291
|
return tokens.includes(needle);
|
|
5702
6292
|
}
|
|
5703
6293
|
async function bootstrapProject(entry2) {
|
|
5704
|
-
const paths = pathsForProject(entry2.name,
|
|
6294
|
+
const paths = pathsForProject(entry2.name, import_node_path39.default.join(entry2.path, "neat-out"));
|
|
5705
6295
|
try {
|
|
5706
|
-
const stat = await
|
|
6296
|
+
const stat = await import_node_fs24.promises.stat(entry2.path);
|
|
5707
6297
|
if (!stat.isDirectory()) {
|
|
5708
6298
|
throw new Error(`registered path ${entry2.path} is not a directory`);
|
|
5709
6299
|
}
|
|
@@ -5769,13 +6359,13 @@ async function startDaemon(opts = {}) {
|
|
|
5769
6359
|
const home = neatHomeFor(opts);
|
|
5770
6360
|
const regPath = registryPath();
|
|
5771
6361
|
try {
|
|
5772
|
-
await
|
|
6362
|
+
await import_node_fs24.promises.access(regPath);
|
|
5773
6363
|
} catch {
|
|
5774
6364
|
throw new Error(
|
|
5775
6365
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
5776
6366
|
);
|
|
5777
6367
|
}
|
|
5778
|
-
const pidPath =
|
|
6368
|
+
const pidPath = import_node_path39.default.join(home, "neatd.pid");
|
|
5779
6369
|
await writeAtomically(pidPath, `${process.pid}
|
|
5780
6370
|
`);
|
|
5781
6371
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -5933,7 +6523,7 @@ async function startDaemon(opts = {}) {
|
|
|
5933
6523
|
}
|
|
5934
6524
|
if (restApp) await restApp.close().catch(() => {
|
|
5935
6525
|
});
|
|
5936
|
-
await
|
|
6526
|
+
await import_node_fs24.promises.unlink(pidPath).catch(() => {
|
|
5937
6527
|
});
|
|
5938
6528
|
throw new Error(
|
|
5939
6529
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
@@ -5989,6 +6579,7 @@ async function startDaemon(opts = {}) {
|
|
|
5989
6579
|
{
|
|
5990
6580
|
graph: slot.graph,
|
|
5991
6581
|
errorsPath: slot.paths.errorsPath,
|
|
6582
|
+
scanPath: slot.entry.path,
|
|
5992
6583
|
project: slot.entry.name,
|
|
5993
6584
|
// Receiver already wrote the error event synchronously below.
|
|
5994
6585
|
writeErrorEventInline: false
|
|
@@ -6012,6 +6603,7 @@ async function startDaemon(opts = {}) {
|
|
|
6012
6603
|
{
|
|
6013
6604
|
graph: slot.graph,
|
|
6014
6605
|
errorsPath: slot.paths.errorsPath,
|
|
6606
|
+
scanPath: slot.entry.path,
|
|
6015
6607
|
project: slot.entry.name,
|
|
6016
6608
|
writeErrorEventInline: false
|
|
6017
6609
|
},
|
|
@@ -6037,7 +6629,7 @@ async function startDaemon(opts = {}) {
|
|
|
6037
6629
|
});
|
|
6038
6630
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
6039
6631
|
});
|
|
6040
|
-
await
|
|
6632
|
+
await import_node_fs24.promises.unlink(pidPath).catch(() => {
|
|
6041
6633
|
});
|
|
6042
6634
|
throw new Error(
|
|
6043
6635
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
@@ -6083,9 +6675,9 @@ async function startDaemon(opts = {}) {
|
|
|
6083
6675
|
let registryWatcher = null;
|
|
6084
6676
|
let reloadTimer = null;
|
|
6085
6677
|
try {
|
|
6086
|
-
const regDir =
|
|
6087
|
-
const regBase =
|
|
6088
|
-
registryWatcher = (0,
|
|
6678
|
+
const regDir = import_node_path39.default.dirname(regPath);
|
|
6679
|
+
const regBase = import_node_path39.default.basename(regPath);
|
|
6680
|
+
registryWatcher = (0, import_node_fs24.watch)(regDir, (_eventType, filename) => {
|
|
6089
6681
|
if (filename !== null && filename !== regBase) return;
|
|
6090
6682
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
6091
6683
|
reloadTimer = setTimeout(() => {
|
|
@@ -6128,7 +6720,7 @@ async function startDaemon(opts = {}) {
|
|
|
6128
6720
|
} catch {
|
|
6129
6721
|
}
|
|
6130
6722
|
}
|
|
6131
|
-
await
|
|
6723
|
+
await import_node_fs24.promises.unlink(pidPath).catch(() => {
|
|
6132
6724
|
});
|
|
6133
6725
|
};
|
|
6134
6726
|
return {
|
|
@@ -6148,9 +6740,9 @@ init_auth();
|
|
|
6148
6740
|
|
|
6149
6741
|
// src/web-spawn.ts
|
|
6150
6742
|
init_cjs_shims();
|
|
6151
|
-
var
|
|
6743
|
+
var import_node_child_process2 = require("child_process");
|
|
6152
6744
|
var import_node_net = __toESM(require("net"), 1);
|
|
6153
|
-
var
|
|
6745
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
6154
6746
|
var DEFAULT_WEB_PORT = 6328;
|
|
6155
6747
|
async function assertPortFree(port) {
|
|
6156
6748
|
await new Promise((resolve, reject) => {
|
|
@@ -6178,10 +6770,10 @@ function resolveWebPackageDir() {
|
|
|
6178
6770
|
eval("require")
|
|
6179
6771
|
);
|
|
6180
6772
|
const pkgJsonPath = req.resolve("@neat.is/web/package.json");
|
|
6181
|
-
return
|
|
6773
|
+
return import_node_path40.default.dirname(pkgJsonPath);
|
|
6182
6774
|
}
|
|
6183
6775
|
function resolveStandaloneServerEntry(webDir) {
|
|
6184
|
-
return
|
|
6776
|
+
return import_node_path40.default.join(webDir, ".next/standalone/packages/web/server.js");
|
|
6185
6777
|
}
|
|
6186
6778
|
async function spawnWebUI(restPort) {
|
|
6187
6779
|
const portRaw = process.env.NEAT_WEB_PORT;
|
|
@@ -6205,8 +6797,8 @@ async function spawnWebUI(restPort) {
|
|
|
6205
6797
|
HOSTNAME: process.env.HOSTNAME ?? "0.0.0.0",
|
|
6206
6798
|
NEAT_API_URL: process.env.NEAT_API_URL ?? `http://localhost:${restPort}`
|
|
6207
6799
|
};
|
|
6208
|
-
const child = (0,
|
|
6209
|
-
cwd:
|
|
6800
|
+
const child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
|
|
6801
|
+
cwd: import_node_path40.default.dirname(serverEntry),
|
|
6210
6802
|
env,
|
|
6211
6803
|
stdio: ["ignore", "inherit", "inherit"],
|
|
6212
6804
|
detached: false
|
|
@@ -6319,14 +6911,14 @@ function localVersion() {
|
|
|
6319
6911
|
}
|
|
6320
6912
|
function neatHome2() {
|
|
6321
6913
|
if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
|
|
6322
|
-
return
|
|
6914
|
+
return import_node_path41.default.resolve(process.env.NEAT_HOME);
|
|
6323
6915
|
}
|
|
6324
6916
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
6325
|
-
return
|
|
6917
|
+
return import_node_path41.default.join(home, ".neat");
|
|
6326
6918
|
}
|
|
6327
6919
|
async function readPid() {
|
|
6328
6920
|
try {
|
|
6329
|
-
const raw = await
|
|
6921
|
+
const raw = await import_node_fs25.promises.readFile(import_node_path41.default.join(neatHome2(), "neatd.pid"), "utf8");
|
|
6330
6922
|
const n = Number.parseInt(raw.trim(), 10);
|
|
6331
6923
|
return Number.isFinite(n) ? n : null;
|
|
6332
6924
|
} catch {
|