@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/cli.cjs
CHANGED
|
@@ -58,9 +58,9 @@ function mountBearerAuth(app, opts) {
|
|
|
58
58
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
59
59
|
const publicRead = opts.publicRead === true;
|
|
60
60
|
app.addHook("preHandler", (req, reply, done) => {
|
|
61
|
-
const
|
|
61
|
+
const path47 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
62
62
|
for (const suffix of suffixes) {
|
|
63
|
-
if (
|
|
63
|
+
if (path47 === suffix || path47.endsWith(suffix)) {
|
|
64
64
|
done();
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
@@ -186,8 +186,8 @@ function reshapeGrpcRequest(req) {
|
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
188
|
function resolveProtoRoot() {
|
|
189
|
-
const here =
|
|
190
|
-
return
|
|
189
|
+
const here = import_node_path37.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
190
|
+
return import_node_path37.default.resolve(here, "..", "proto");
|
|
191
191
|
}
|
|
192
192
|
function loadTraceService() {
|
|
193
193
|
const protoRoot = resolveProtoRoot();
|
|
@@ -255,13 +255,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
255
255
|
})
|
|
256
256
|
};
|
|
257
257
|
}
|
|
258
|
-
var import_node_url,
|
|
258
|
+
var import_node_url, import_node_path37, import_node_crypto2, grpc, protoLoader;
|
|
259
259
|
var init_otel_grpc = __esm({
|
|
260
260
|
"src/otel-grpc.ts"() {
|
|
261
261
|
"use strict";
|
|
262
262
|
init_cjs_shims();
|
|
263
263
|
import_node_url = require("url");
|
|
264
|
-
|
|
264
|
+
import_node_path37 = __toESM(require("path"), 1);
|
|
265
265
|
import_node_crypto2 = require("crypto");
|
|
266
266
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
267
267
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -371,10 +371,10 @@ function parseOtlpRequest(body) {
|
|
|
371
371
|
return out;
|
|
372
372
|
}
|
|
373
373
|
function loadProtoRoot() {
|
|
374
|
-
const here =
|
|
375
|
-
const protoRoot =
|
|
374
|
+
const here = import_node_path38.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
375
|
+
const protoRoot = import_node_path38.default.resolve(here, "..", "proto");
|
|
376
376
|
const root = new import_protobufjs.default.Root();
|
|
377
|
-
root.resolvePath = (_origin, target) =>
|
|
377
|
+
root.resolvePath = (_origin, target) => import_node_path38.default.resolve(protoRoot, target);
|
|
378
378
|
root.loadSync(
|
|
379
379
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
380
380
|
{ keepCase: true }
|
|
@@ -567,12 +567,12 @@ async function buildOtelReceiver(opts) {
|
|
|
567
567
|
};
|
|
568
568
|
return decorated;
|
|
569
569
|
}
|
|
570
|
-
var
|
|
570
|
+
var import_node_path38, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
571
571
|
var init_otel = __esm({
|
|
572
572
|
"src/otel.ts"() {
|
|
573
573
|
"use strict";
|
|
574
574
|
init_cjs_shims();
|
|
575
|
-
|
|
575
|
+
import_node_path38 = __toESM(require("path"), 1);
|
|
576
576
|
import_node_url2 = require("url");
|
|
577
577
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
578
578
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -600,8 +600,8 @@ __export(cli_exports, {
|
|
|
600
600
|
});
|
|
601
601
|
module.exports = __toCommonJS(cli_exports);
|
|
602
602
|
init_cjs_shims();
|
|
603
|
-
var
|
|
604
|
-
var
|
|
603
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
604
|
+
var import_node_fs30 = require("fs");
|
|
605
605
|
var import_node_url3 = require("url");
|
|
606
606
|
|
|
607
607
|
// src/graph.ts
|
|
@@ -639,6 +639,7 @@ init_cjs_shims();
|
|
|
639
639
|
init_cjs_shims();
|
|
640
640
|
var import_node_fs3 = require("fs");
|
|
641
641
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
642
|
+
var sourceMapJs = __toESM(require("source-map-js"), 1);
|
|
642
643
|
|
|
643
644
|
// src/policy.ts
|
|
644
645
|
init_cjs_shims();
|
|
@@ -1130,19 +1131,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1130
1131
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1131
1132
|
let best = { path: [start], edges: [] };
|
|
1132
1133
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1133
|
-
function step(node,
|
|
1134
|
-
if (
|
|
1135
|
-
best = { path: [...
|
|
1134
|
+
function step(node, path47, edges) {
|
|
1135
|
+
if (path47.length > best.path.length) {
|
|
1136
|
+
best = { path: [...path47], edges: [...edges] };
|
|
1136
1137
|
}
|
|
1137
|
-
if (
|
|
1138
|
+
if (path47.length - 1 >= maxDepth) return;
|
|
1138
1139
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1139
1140
|
for (const [srcId, edge] of incoming) {
|
|
1140
1141
|
if (visited.has(srcId)) continue;
|
|
1141
1142
|
visited.add(srcId);
|
|
1142
|
-
|
|
1143
|
+
path47.push(srcId);
|
|
1143
1144
|
edges.push(edge);
|
|
1144
|
-
step(srcId,
|
|
1145
|
-
|
|
1145
|
+
step(srcId, path47, edges);
|
|
1146
|
+
path47.pop();
|
|
1146
1147
|
edges.pop();
|
|
1147
1148
|
visited.delete(srcId);
|
|
1148
1149
|
}
|
|
@@ -1693,6 +1694,14 @@ function warnUnidentifiedSpan(project) {
|
|
|
1693
1694
|
`[neatd] span lacked service.name; routed to 'unidentified' in project ${project}; check your OTel SDK config.`
|
|
1694
1695
|
);
|
|
1695
1696
|
}
|
|
1697
|
+
var noSourceMapWarnedServices = /* @__PURE__ */ new Set();
|
|
1698
|
+
function warnNoSourceMaps(serviceName) {
|
|
1699
|
+
if (noSourceMapWarnedServices.has(serviceName)) return;
|
|
1700
|
+
noSourceMapWarnedServices.add(serviceName);
|
|
1701
|
+
console.warn(
|
|
1702
|
+
`[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.`
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1696
1705
|
function pickAttr(span, ...keys) {
|
|
1697
1706
|
for (const k of keys) {
|
|
1698
1707
|
const v = span.attributes[k];
|
|
@@ -1735,8 +1744,13 @@ function languageForExt(relPath) {
|
|
|
1735
1744
|
return void 0;
|
|
1736
1745
|
}
|
|
1737
1746
|
}
|
|
1738
|
-
function relPathForRuntimeFile(filepath, serviceNode) {
|
|
1747
|
+
function relPathForRuntimeFile(filepath, serviceNode, scanPath) {
|
|
1739
1748
|
let p = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1749
|
+
if (scanPath && scanPath.length > 0) {
|
|
1750
|
+
const absRoot = toPosix(import_node_path3.default.resolve(scanPath, serviceNode?.repoPath ?? ""));
|
|
1751
|
+
const anchor = absRoot.endsWith("/") ? absRoot : `${absRoot}/`;
|
|
1752
|
+
if (p.startsWith(anchor)) return p.slice(anchor.length);
|
|
1753
|
+
}
|
|
1740
1754
|
const root = serviceNode?.repoPath;
|
|
1741
1755
|
if (root && root !== "." && root.length > 0) {
|
|
1742
1756
|
const rootPosix = toPosix(root);
|
|
@@ -1753,16 +1767,65 @@ function relPathForRuntimeFile(filepath, serviceNode) {
|
|
|
1753
1767
|
p = p.replace(/^[A-Za-z]:/, "").replace(/^\/+/, "");
|
|
1754
1768
|
return p.length > 0 ? p : null;
|
|
1755
1769
|
}
|
|
1756
|
-
|
|
1770
|
+
var sourceMapCache = /* @__PURE__ */ new Map();
|
|
1771
|
+
function resolveDistToSrc(absFilepath, line) {
|
|
1772
|
+
if (!absFilepath.endsWith(".js")) return null;
|
|
1773
|
+
let entry2 = sourceMapCache.get(absFilepath);
|
|
1774
|
+
if (entry2 === void 0) {
|
|
1775
|
+
entry2 = null;
|
|
1776
|
+
const mapPath = `${absFilepath}.map`;
|
|
1777
|
+
try {
|
|
1778
|
+
if ((0, import_node_fs3.existsSync)(mapPath)) {
|
|
1779
|
+
const raw = JSON.parse((0, import_node_fs3.readFileSync)(mapPath, "utf8"));
|
|
1780
|
+
const consumer = new sourceMapJs.SourceMapConsumer(raw);
|
|
1781
|
+
entry2 = { consumer, dir: import_node_path3.default.dirname(mapPath) };
|
|
1782
|
+
}
|
|
1783
|
+
} catch {
|
|
1784
|
+
entry2 = null;
|
|
1785
|
+
}
|
|
1786
|
+
sourceMapCache.set(absFilepath, entry2);
|
|
1787
|
+
}
|
|
1788
|
+
if (!entry2) return null;
|
|
1789
|
+
try {
|
|
1790
|
+
const pos = entry2.consumer.originalPositionFor({
|
|
1791
|
+
line: line !== void 0 && Number.isFinite(line) ? line : 1,
|
|
1792
|
+
column: 0
|
|
1793
|
+
});
|
|
1794
|
+
if (!pos || !pos.source) return null;
|
|
1795
|
+
const root = entry2.consumer.sourceRoot ?? "";
|
|
1796
|
+
const resolved = import_node_path3.default.resolve(entry2.dir, root, pos.source);
|
|
1797
|
+
return { filepath: resolved, ...pos.line ? { line: pos.line } : {} };
|
|
1798
|
+
} catch {
|
|
1799
|
+
return null;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
1757
1803
|
const filepath = span.attributes[CODE_FILEPATH_ATTR];
|
|
1758
1804
|
if (typeof filepath !== "string" || filepath.length === 0) return null;
|
|
1759
|
-
const relPath = relPathForRuntimeFile(filepath, serviceNode);
|
|
1760
|
-
if (!relPath) return null;
|
|
1761
1805
|
const linenoRaw = span.attributes[CODE_LINENO_ATTR];
|
|
1762
|
-
|
|
1806
|
+
let line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
|
|
1807
|
+
const abs = toPosix(filepath).replace(/^file:\/\//, "");
|
|
1808
|
+
const resolved = resolveDistToSrc(abs, line);
|
|
1809
|
+
let effectivePath = filepath;
|
|
1810
|
+
let originalRelPath;
|
|
1811
|
+
if (resolved) {
|
|
1812
|
+
originalRelPath = relPathForRuntimeFile(filepath, serviceNode, scanPath) ?? void 0;
|
|
1813
|
+
effectivePath = resolved.filepath;
|
|
1814
|
+
if (resolved.line !== void 0) line = resolved.line;
|
|
1815
|
+
}
|
|
1816
|
+
const relPath = relPathForRuntimeFile(effectivePath, serviceNode, scanPath);
|
|
1817
|
+
if (!relPath) return null;
|
|
1818
|
+
if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
|
|
1819
|
+
warnNoSourceMaps(serviceNode.name);
|
|
1820
|
+
}
|
|
1763
1821
|
const fnRaw = span.attributes[CODE_FUNCTION_ATTR];
|
|
1764
1822
|
const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
|
|
1765
|
-
return {
|
|
1823
|
+
return {
|
|
1824
|
+
relPath,
|
|
1825
|
+
...line !== void 0 ? { line } : {},
|
|
1826
|
+
...fn ? { fn } : {},
|
|
1827
|
+
...originalRelPath && originalRelPath !== relPath ? { originalRelPath } : {}
|
|
1828
|
+
};
|
|
1766
1829
|
}
|
|
1767
1830
|
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
1768
1831
|
const fileNodeId = (0, import_types3.fileId)(serviceName, callSite.relPath);
|
|
@@ -1774,6 +1837,7 @@ function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
|
1774
1837
|
service: serviceName,
|
|
1775
1838
|
path: callSite.relPath,
|
|
1776
1839
|
...language ? { language } : {},
|
|
1840
|
+
...callSite.originalRelPath ? { originalPath: callSite.originalRelPath } : {},
|
|
1777
1841
|
discoveredVia: "otel"
|
|
1778
1842
|
};
|
|
1779
1843
|
graph.addNode(fileNodeId, node);
|
|
@@ -1799,6 +1863,12 @@ function makeInferredEdgeId(type, source, target) {
|
|
|
1799
1863
|
}
|
|
1800
1864
|
var INFERRED_CONFIDENCE = 0.6;
|
|
1801
1865
|
var STITCH_MAX_DEPTH = 2;
|
|
1866
|
+
var WIRE_SPAN_KIND_CLIENT = 3;
|
|
1867
|
+
var WIRE_SPAN_KIND_PRODUCER = 4;
|
|
1868
|
+
function spanMintsObservedEdge(kind) {
|
|
1869
|
+
if (kind === void 0 || kind === 0) return true;
|
|
1870
|
+
return kind === WIRE_SPAN_KIND_CLIENT || kind === WIRE_SPAN_KIND_PRODUCER;
|
|
1871
|
+
}
|
|
1802
1872
|
var PARENT_SPAN_CACHE_SIZE = 1e4;
|
|
1803
1873
|
var PARENT_SPAN_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
1804
1874
|
var parentSpanCache = /* @__PURE__ */ new Map();
|
|
@@ -1905,7 +1975,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
1905
1975
|
graph.addNode(id, node);
|
|
1906
1976
|
return id;
|
|
1907
1977
|
}
|
|
1908
|
-
function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
1978
|
+
function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
|
|
1909
1979
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
1910
1980
|
const id = makeObservedEdgeId(type, source, target);
|
|
1911
1981
|
if (graph.hasEdge(id)) {
|
|
@@ -1942,7 +2012,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1942
2012
|
confidence: (0, import_types3.confidenceForObservedSignal)(signal),
|
|
1943
2013
|
lastObserved: ts,
|
|
1944
2014
|
callCount: 1,
|
|
1945
|
-
signal
|
|
2015
|
+
signal,
|
|
2016
|
+
// Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
|
|
2017
|
+
// Only set when code.filepath was present on the span — never fabricated.
|
|
2018
|
+
...evidence ? { evidence } : {}
|
|
1946
2019
|
};
|
|
1947
2020
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
1948
2021
|
return { edge, created: true };
|
|
@@ -2034,12 +2107,14 @@ async function handleSpan(ctx, span) {
|
|
|
2034
2107
|
const isError = span.statusCode === 2;
|
|
2035
2108
|
cacheSpanService(span, nowMs);
|
|
2036
2109
|
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
2037
|
-
const callSite = callSiteFromSpan(span, sourceServiceNode);
|
|
2110
|
+
const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
|
|
2038
2111
|
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
2112
|
+
const callSiteEvidence = callSite ? { file: callSite.relPath, ...callSite.line !== void 0 ? { line: callSite.line } : {} } : void 0;
|
|
2039
2113
|
let affectedNode = sourceId;
|
|
2114
|
+
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2040
2115
|
if (span.dbSystem) {
|
|
2041
2116
|
const host = pickAddress(span);
|
|
2042
|
-
if (host) {
|
|
2117
|
+
if (mintsFromCallerSide && host) {
|
|
2043
2118
|
ensureDatabaseNode(ctx.graph, host, span.dbSystem);
|
|
2044
2119
|
const targetId = (0, import_types3.databaseId)(host);
|
|
2045
2120
|
const result = upsertObservedEdge(
|
|
@@ -2048,14 +2123,15 @@ async function handleSpan(ctx, span) {
|
|
|
2048
2123
|
observedSource(),
|
|
2049
2124
|
targetId,
|
|
2050
2125
|
ts,
|
|
2051
|
-
isError
|
|
2126
|
+
isError,
|
|
2127
|
+
callSiteEvidence
|
|
2052
2128
|
);
|
|
2053
2129
|
if (result) affectedNode = targetId;
|
|
2054
2130
|
}
|
|
2055
2131
|
} else {
|
|
2056
2132
|
const host = pickAddress(span);
|
|
2057
2133
|
let resolvedViaAddress = false;
|
|
2058
|
-
if (host && host !== span.service) {
|
|
2134
|
+
if (mintsFromCallerSide && host && host !== span.service) {
|
|
2059
2135
|
const targetId = resolveServiceId(ctx.graph, host, env);
|
|
2060
2136
|
if (targetId && targetId !== sourceId) {
|
|
2061
2137
|
upsertObservedEdge(
|
|
@@ -2064,7 +2140,8 @@ async function handleSpan(ctx, span) {
|
|
|
2064
2140
|
observedSource(),
|
|
2065
2141
|
targetId,
|
|
2066
2142
|
ts,
|
|
2067
|
-
isError
|
|
2143
|
+
isError,
|
|
2144
|
+
callSiteEvidence
|
|
2068
2145
|
);
|
|
2069
2146
|
affectedNode = targetId;
|
|
2070
2147
|
resolvedViaAddress = true;
|
|
@@ -2076,7 +2153,8 @@ async function handleSpan(ctx, span) {
|
|
|
2076
2153
|
observedSource(),
|
|
2077
2154
|
frontierNodeId,
|
|
2078
2155
|
ts,
|
|
2079
|
-
isError
|
|
2156
|
+
isError,
|
|
2157
|
+
callSiteEvidence
|
|
2080
2158
|
);
|
|
2081
2159
|
affectedNode = frontierNodeId;
|
|
2082
2160
|
resolvedViaAddress = true;
|
|
@@ -3039,14 +3117,111 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
3039
3117
|
|
|
3040
3118
|
// src/extract/databases/index.ts
|
|
3041
3119
|
init_cjs_shims();
|
|
3042
|
-
var
|
|
3120
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
3121
|
+
var import_types8 = require("@neat.is/types");
|
|
3122
|
+
|
|
3123
|
+
// src/extract/calls/shared.ts
|
|
3124
|
+
init_cjs_shims();
|
|
3125
|
+
var import_node_fs10 = require("fs");
|
|
3126
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
3043
3127
|
var import_types7 = require("@neat.is/types");
|
|
3128
|
+
async function walkSourceFiles(dir) {
|
|
3129
|
+
const out = [];
|
|
3130
|
+
async function walk(current) {
|
|
3131
|
+
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3132
|
+
for (const entry2 of entries) {
|
|
3133
|
+
const full = import_node_path10.default.join(current, entry2.name);
|
|
3134
|
+
if (entry2.isDirectory()) {
|
|
3135
|
+
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3136
|
+
if (await isPythonVenvDir(full)) continue;
|
|
3137
|
+
await walk(full);
|
|
3138
|
+
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry2.name))) {
|
|
3139
|
+
out.push(full);
|
|
3140
|
+
}
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
await walk(dir);
|
|
3144
|
+
return out;
|
|
3145
|
+
}
|
|
3146
|
+
async function loadSourceFiles(dir) {
|
|
3147
|
+
const paths = await walkSourceFiles(dir);
|
|
3148
|
+
const out = [];
|
|
3149
|
+
for (const p of paths) {
|
|
3150
|
+
try {
|
|
3151
|
+
const content = await import_node_fs10.promises.readFile(p, "utf8");
|
|
3152
|
+
out.push({ path: p, content });
|
|
3153
|
+
} catch {
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
return out;
|
|
3157
|
+
}
|
|
3158
|
+
function lineOf(text, needle) {
|
|
3159
|
+
const idx = text.indexOf(needle);
|
|
3160
|
+
if (idx < 0) return 1;
|
|
3161
|
+
return text.slice(0, idx).split("\n").length;
|
|
3162
|
+
}
|
|
3163
|
+
function snippet(text, line) {
|
|
3164
|
+
const lines = text.split("\n");
|
|
3165
|
+
return (lines[line - 1] ?? "").trim();
|
|
3166
|
+
}
|
|
3167
|
+
function toPosix2(p) {
|
|
3168
|
+
return p.split("\\").join("/");
|
|
3169
|
+
}
|
|
3170
|
+
function languageForPath(relPath) {
|
|
3171
|
+
switch (import_node_path10.default.extname(relPath).toLowerCase()) {
|
|
3172
|
+
case ".py":
|
|
3173
|
+
return "python";
|
|
3174
|
+
case ".ts":
|
|
3175
|
+
case ".tsx":
|
|
3176
|
+
return "typescript";
|
|
3177
|
+
case ".js":
|
|
3178
|
+
case ".jsx":
|
|
3179
|
+
case ".mjs":
|
|
3180
|
+
case ".cjs":
|
|
3181
|
+
return "javascript";
|
|
3182
|
+
default:
|
|
3183
|
+
return void 0;
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3187
|
+
let nodesAdded = 0;
|
|
3188
|
+
let edgesAdded = 0;
|
|
3189
|
+
const fileNodeId = (0, import_types7.fileId)(serviceName, relPath);
|
|
3190
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3191
|
+
const language = languageForPath(relPath);
|
|
3192
|
+
const node = {
|
|
3193
|
+
id: fileNodeId,
|
|
3194
|
+
type: import_types7.NodeType.FileNode,
|
|
3195
|
+
service: serviceName,
|
|
3196
|
+
path: relPath,
|
|
3197
|
+
...language ? { language } : {},
|
|
3198
|
+
discoveredVia: "static"
|
|
3199
|
+
};
|
|
3200
|
+
graph.addNode(fileNodeId, node);
|
|
3201
|
+
nodesAdded++;
|
|
3202
|
+
}
|
|
3203
|
+
const containsId = (0, import_types7.extractedEdgeId)(serviceNodeId, fileNodeId, import_types7.EdgeType.CONTAINS);
|
|
3204
|
+
if (!graph.hasEdge(containsId)) {
|
|
3205
|
+
const edge = {
|
|
3206
|
+
id: containsId,
|
|
3207
|
+
source: serviceNodeId,
|
|
3208
|
+
target: fileNodeId,
|
|
3209
|
+
type: import_types7.EdgeType.CONTAINS,
|
|
3210
|
+
provenance: import_types7.Provenance.EXTRACTED,
|
|
3211
|
+
confidence: (0, import_types7.confidenceForExtracted)("structural"),
|
|
3212
|
+
evidence: { file: relPath }
|
|
3213
|
+
};
|
|
3214
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3215
|
+
edgesAdded++;
|
|
3216
|
+
}
|
|
3217
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3218
|
+
}
|
|
3044
3219
|
|
|
3045
3220
|
// src/extract/databases/db-config-yaml.ts
|
|
3046
3221
|
init_cjs_shims();
|
|
3047
|
-
var
|
|
3222
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
3048
3223
|
async function parse(serviceDir) {
|
|
3049
|
-
const yamlPath =
|
|
3224
|
+
const yamlPath = import_node_path11.default.join(serviceDir, "db-config.yaml");
|
|
3050
3225
|
if (!await exists(yamlPath)) return [];
|
|
3051
3226
|
const raw = await readYaml(yamlPath);
|
|
3052
3227
|
return [
|
|
@@ -3064,13 +3239,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
3064
3239
|
|
|
3065
3240
|
// src/extract/databases/dotenv.ts
|
|
3066
3241
|
init_cjs_shims();
|
|
3067
|
-
var
|
|
3068
|
-
var
|
|
3242
|
+
var import_node_fs12 = require("fs");
|
|
3243
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
3069
3244
|
|
|
3070
3245
|
// src/extract/databases/shared.ts
|
|
3071
3246
|
init_cjs_shims();
|
|
3072
|
-
var
|
|
3073
|
-
var
|
|
3247
|
+
var import_node_fs11 = require("fs");
|
|
3248
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
3074
3249
|
function schemeToEngine(scheme) {
|
|
3075
3250
|
const s = scheme.toLowerCase().split("+")[0];
|
|
3076
3251
|
switch (s) {
|
|
@@ -3109,14 +3284,14 @@ function parseConnectionString(url) {
|
|
|
3109
3284
|
}
|
|
3110
3285
|
async function readIfExists(filePath) {
|
|
3111
3286
|
try {
|
|
3112
|
-
return await
|
|
3287
|
+
return await import_node_fs11.promises.readFile(filePath, "utf8");
|
|
3113
3288
|
} catch {
|
|
3114
3289
|
return null;
|
|
3115
3290
|
}
|
|
3116
3291
|
}
|
|
3117
3292
|
async function findFirst(serviceDir, candidates) {
|
|
3118
3293
|
for (const rel of candidates) {
|
|
3119
|
-
const abs =
|
|
3294
|
+
const abs = import_node_path12.default.join(serviceDir, rel);
|
|
3120
3295
|
const content = await readIfExists(abs);
|
|
3121
3296
|
if (content !== null) return abs;
|
|
3122
3297
|
}
|
|
@@ -3167,15 +3342,15 @@ function parseDotenvLine(line) {
|
|
|
3167
3342
|
return { key, value };
|
|
3168
3343
|
}
|
|
3169
3344
|
async function parse2(serviceDir) {
|
|
3170
|
-
const entries = await
|
|
3345
|
+
const entries = await import_node_fs12.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
3171
3346
|
const configs = [];
|
|
3172
3347
|
const seen = /* @__PURE__ */ new Set();
|
|
3173
3348
|
for (const entry2 of entries) {
|
|
3174
3349
|
if (!entry2.isFile()) continue;
|
|
3175
3350
|
const match = isConfigFile(entry2.name);
|
|
3176
3351
|
if (!match.match || match.fileType !== "env") continue;
|
|
3177
|
-
const filePath =
|
|
3178
|
-
const content = await
|
|
3352
|
+
const filePath = import_node_path13.default.join(serviceDir, entry2.name);
|
|
3353
|
+
const content = await import_node_fs12.promises.readFile(filePath, "utf8");
|
|
3179
3354
|
for (const line of content.split("\n")) {
|
|
3180
3355
|
const parsed = parseDotenvLine(line);
|
|
3181
3356
|
if (!parsed) continue;
|
|
@@ -3194,9 +3369,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
3194
3369
|
|
|
3195
3370
|
// src/extract/databases/prisma.ts
|
|
3196
3371
|
init_cjs_shims();
|
|
3197
|
-
var
|
|
3372
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
3198
3373
|
async function parse3(serviceDir) {
|
|
3199
|
-
const schemaPath =
|
|
3374
|
+
const schemaPath = import_node_path14.default.join(serviceDir, "prisma", "schema.prisma");
|
|
3200
3375
|
const content = await readIfExists(schemaPath);
|
|
3201
3376
|
if (!content) return [];
|
|
3202
3377
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -3328,10 +3503,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
3328
3503
|
|
|
3329
3504
|
// src/extract/databases/ormconfig.ts
|
|
3330
3505
|
init_cjs_shims();
|
|
3331
|
-
var
|
|
3506
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
3332
3507
|
async function parse6(serviceDir) {
|
|
3333
3508
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
3334
|
-
const abs =
|
|
3509
|
+
const abs = import_node_path15.default.join(serviceDir, candidate);
|
|
3335
3510
|
if (!await exists(abs)) continue;
|
|
3336
3511
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
3337
3512
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -3391,9 +3566,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
3391
3566
|
|
|
3392
3567
|
// src/extract/databases/sequelize.ts
|
|
3393
3568
|
init_cjs_shims();
|
|
3394
|
-
var
|
|
3569
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
3395
3570
|
async function parse8(serviceDir) {
|
|
3396
|
-
const configPath =
|
|
3571
|
+
const configPath = import_node_path16.default.join(serviceDir, "config", "config.json");
|
|
3397
3572
|
if (!await exists(configPath)) return [];
|
|
3398
3573
|
const raw = await readJson(configPath);
|
|
3399
3574
|
const out = [];
|
|
@@ -3420,7 +3595,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
3420
3595
|
|
|
3421
3596
|
// src/extract/databases/docker-compose.ts
|
|
3422
3597
|
init_cjs_shims();
|
|
3423
|
-
var
|
|
3598
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
3424
3599
|
function portFromService(svc) {
|
|
3425
3600
|
for (const raw of svc.ports ?? []) {
|
|
3426
3601
|
const str = String(raw);
|
|
@@ -3447,7 +3622,7 @@ function databaseFromEnv(svc) {
|
|
|
3447
3622
|
}
|
|
3448
3623
|
async function parse9(serviceDir) {
|
|
3449
3624
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3450
|
-
const abs =
|
|
3625
|
+
const abs = import_node_path17.default.join(serviceDir, name);
|
|
3451
3626
|
if (!await exists(abs)) continue;
|
|
3452
3627
|
const raw = await readYaml(abs);
|
|
3453
3628
|
if (!raw?.services) return [];
|
|
@@ -3488,8 +3663,8 @@ function compatibleDriversFor(engine) {
|
|
|
3488
3663
|
}
|
|
3489
3664
|
function toDatabaseNode(config) {
|
|
3490
3665
|
return {
|
|
3491
|
-
id: (0,
|
|
3492
|
-
type:
|
|
3666
|
+
id: (0, import_types8.databaseId)(config.host),
|
|
3667
|
+
type: import_types8.NodeType.DatabaseNode,
|
|
3493
3668
|
name: config.database || config.host,
|
|
3494
3669
|
engine: config.engine,
|
|
3495
3670
|
engineVersion: config.engineVersion,
|
|
@@ -3619,19 +3794,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3619
3794
|
discoveredVia: mergedDiscoveredVia
|
|
3620
3795
|
});
|
|
3621
3796
|
}
|
|
3797
|
+
const relConfigFile = toPosix2(import_node_path18.default.relative(service.dir, config.sourceFile));
|
|
3798
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
3799
|
+
graph,
|
|
3800
|
+
service.pkg.name,
|
|
3801
|
+
service.node.id,
|
|
3802
|
+
relConfigFile
|
|
3803
|
+
);
|
|
3804
|
+
nodesAdded += fn;
|
|
3805
|
+
edgesAdded += fe;
|
|
3806
|
+
const evidenceFile = toPosix2(import_node_path18.default.relative(scanPath, config.sourceFile));
|
|
3622
3807
|
const edge = {
|
|
3623
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3624
|
-
source:
|
|
3808
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types8.EdgeType.CONNECTS_TO),
|
|
3809
|
+
source: fileNodeId,
|
|
3625
3810
|
target: dbNode.id,
|
|
3626
|
-
type:
|
|
3627
|
-
provenance:
|
|
3628
|
-
confidence: (0,
|
|
3629
|
-
|
|
3630
|
-
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3631
|
-
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
3632
|
-
evidence: {
|
|
3633
|
-
file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
|
|
3634
|
-
}
|
|
3811
|
+
type: import_types8.EdgeType.CONNECTS_TO,
|
|
3812
|
+
provenance: import_types8.Provenance.EXTRACTED,
|
|
3813
|
+
confidence: (0, import_types8.confidenceForExtracted)("structural"),
|
|
3814
|
+
evidence: { file: evidenceFile }
|
|
3635
3815
|
};
|
|
3636
3816
|
if (!graph.hasEdge(edge.id)) {
|
|
3637
3817
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3657,15 +3837,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3657
3837
|
|
|
3658
3838
|
// src/extract/configs.ts
|
|
3659
3839
|
init_cjs_shims();
|
|
3660
|
-
var
|
|
3661
|
-
var
|
|
3662
|
-
var
|
|
3840
|
+
var import_node_fs13 = require("fs");
|
|
3841
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3842
|
+
var import_types9 = require("@neat.is/types");
|
|
3663
3843
|
async function walkConfigFiles(dir) {
|
|
3664
3844
|
const out = [];
|
|
3665
3845
|
async function walk(current) {
|
|
3666
|
-
const entries = await
|
|
3846
|
+
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true });
|
|
3667
3847
|
for (const entry2 of entries) {
|
|
3668
|
-
const full =
|
|
3848
|
+
const full = import_node_path19.default.join(current, entry2.name);
|
|
3669
3849
|
if (entry2.isDirectory()) {
|
|
3670
3850
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3671
3851
|
if (await isPythonVenvDir(full)) continue;
|
|
@@ -3684,26 +3864,35 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3684
3864
|
for (const service of services) {
|
|
3685
3865
|
const configFiles = await walkConfigFiles(service.dir);
|
|
3686
3866
|
for (const file of configFiles) {
|
|
3687
|
-
const relPath =
|
|
3867
|
+
const relPath = import_node_path19.default.relative(scanPath, file);
|
|
3688
3868
|
const node = {
|
|
3689
|
-
id: (0,
|
|
3690
|
-
type:
|
|
3691
|
-
name:
|
|
3869
|
+
id: (0, import_types9.configId)(relPath),
|
|
3870
|
+
type: import_types9.NodeType.ConfigNode,
|
|
3871
|
+
name: import_node_path19.default.basename(file),
|
|
3692
3872
|
path: relPath,
|
|
3693
|
-
fileType: isConfigFile(
|
|
3873
|
+
fileType: isConfigFile(import_node_path19.default.basename(file)).fileType
|
|
3694
3874
|
};
|
|
3695
3875
|
if (!graph.hasNode(node.id)) {
|
|
3696
3876
|
graph.addNode(node.id, node);
|
|
3697
3877
|
nodesAdded++;
|
|
3698
3878
|
}
|
|
3879
|
+
const relToService = toPosix2(import_node_path19.default.relative(service.dir, file));
|
|
3880
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
3881
|
+
graph,
|
|
3882
|
+
service.pkg.name,
|
|
3883
|
+
service.node.id,
|
|
3884
|
+
relToService
|
|
3885
|
+
);
|
|
3886
|
+
nodesAdded += fn;
|
|
3887
|
+
edgesAdded += fe;
|
|
3699
3888
|
const edge = {
|
|
3700
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3701
|
-
source:
|
|
3889
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types9.EdgeType.CONFIGURED_BY),
|
|
3890
|
+
source: fileNodeId,
|
|
3702
3891
|
target: node.id,
|
|
3703
|
-
type:
|
|
3704
|
-
provenance:
|
|
3705
|
-
confidence: (0,
|
|
3706
|
-
evidence: { file: relPath.split(
|
|
3892
|
+
type: import_types9.EdgeType.CONFIGURED_BY,
|
|
3893
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
3894
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3895
|
+
evidence: { file: relPath.split(import_node_path19.default.sep).join("/") }
|
|
3707
3896
|
};
|
|
3708
3897
|
if (!graph.hasEdge(edge.id)) {
|
|
3709
3898
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3725,105 +3914,6 @@ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
|
3725
3914
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3726
3915
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3727
3916
|
var import_types10 = require("@neat.is/types");
|
|
3728
|
-
|
|
3729
|
-
// src/extract/calls/shared.ts
|
|
3730
|
-
init_cjs_shims();
|
|
3731
|
-
var import_node_fs13 = require("fs");
|
|
3732
|
-
var import_node_path19 = __toESM(require("path"), 1);
|
|
3733
|
-
var import_types9 = require("@neat.is/types");
|
|
3734
|
-
async function walkSourceFiles(dir) {
|
|
3735
|
-
const out = [];
|
|
3736
|
-
async function walk(current) {
|
|
3737
|
-
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3738
|
-
for (const entry2 of entries) {
|
|
3739
|
-
const full = import_node_path19.default.join(current, entry2.name);
|
|
3740
|
-
if (entry2.isDirectory()) {
|
|
3741
|
-
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3742
|
-
if (await isPythonVenvDir(full)) continue;
|
|
3743
|
-
await walk(full);
|
|
3744
|
-
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry2.name))) {
|
|
3745
|
-
out.push(full);
|
|
3746
|
-
}
|
|
3747
|
-
}
|
|
3748
|
-
}
|
|
3749
|
-
await walk(dir);
|
|
3750
|
-
return out;
|
|
3751
|
-
}
|
|
3752
|
-
async function loadSourceFiles(dir) {
|
|
3753
|
-
const paths = await walkSourceFiles(dir);
|
|
3754
|
-
const out = [];
|
|
3755
|
-
for (const p of paths) {
|
|
3756
|
-
try {
|
|
3757
|
-
const content = await import_node_fs13.promises.readFile(p, "utf8");
|
|
3758
|
-
out.push({ path: p, content });
|
|
3759
|
-
} catch {
|
|
3760
|
-
}
|
|
3761
|
-
}
|
|
3762
|
-
return out;
|
|
3763
|
-
}
|
|
3764
|
-
function lineOf(text, needle) {
|
|
3765
|
-
const idx = text.indexOf(needle);
|
|
3766
|
-
if (idx < 0) return 1;
|
|
3767
|
-
return text.slice(0, idx).split("\n").length;
|
|
3768
|
-
}
|
|
3769
|
-
function snippet(text, line) {
|
|
3770
|
-
const lines = text.split("\n");
|
|
3771
|
-
return (lines[line - 1] ?? "").trim();
|
|
3772
|
-
}
|
|
3773
|
-
function toPosix2(p) {
|
|
3774
|
-
return p.split("\\").join("/");
|
|
3775
|
-
}
|
|
3776
|
-
function languageForPath(relPath) {
|
|
3777
|
-
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3778
|
-
case ".py":
|
|
3779
|
-
return "python";
|
|
3780
|
-
case ".ts":
|
|
3781
|
-
case ".tsx":
|
|
3782
|
-
return "typescript";
|
|
3783
|
-
case ".js":
|
|
3784
|
-
case ".jsx":
|
|
3785
|
-
case ".mjs":
|
|
3786
|
-
case ".cjs":
|
|
3787
|
-
return "javascript";
|
|
3788
|
-
default:
|
|
3789
|
-
return void 0;
|
|
3790
|
-
}
|
|
3791
|
-
}
|
|
3792
|
-
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3793
|
-
let nodesAdded = 0;
|
|
3794
|
-
let edgesAdded = 0;
|
|
3795
|
-
const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
|
|
3796
|
-
if (!graph.hasNode(fileNodeId)) {
|
|
3797
|
-
const language = languageForPath(relPath);
|
|
3798
|
-
const node = {
|
|
3799
|
-
id: fileNodeId,
|
|
3800
|
-
type: import_types9.NodeType.FileNode,
|
|
3801
|
-
service: serviceName,
|
|
3802
|
-
path: relPath,
|
|
3803
|
-
...language ? { language } : {},
|
|
3804
|
-
discoveredVia: "static"
|
|
3805
|
-
};
|
|
3806
|
-
graph.addNode(fileNodeId, node);
|
|
3807
|
-
nodesAdded++;
|
|
3808
|
-
}
|
|
3809
|
-
const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
|
|
3810
|
-
if (!graph.hasEdge(containsId)) {
|
|
3811
|
-
const edge = {
|
|
3812
|
-
id: containsId,
|
|
3813
|
-
source: serviceNodeId,
|
|
3814
|
-
target: fileNodeId,
|
|
3815
|
-
type: import_types9.EdgeType.CONTAINS,
|
|
3816
|
-
provenance: import_types9.Provenance.EXTRACTED,
|
|
3817
|
-
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3818
|
-
evidence: { file: relPath }
|
|
3819
|
-
};
|
|
3820
|
-
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3821
|
-
edgesAdded++;
|
|
3822
|
-
}
|
|
3823
|
-
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3824
|
-
}
|
|
3825
|
-
|
|
3826
|
-
// src/extract/calls/http.ts
|
|
3827
3917
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
3828
3918
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
3829
3919
|
function isInsideJsxExternalLink(node) {
|
|
@@ -3851,8 +3941,14 @@ function collectStringLiterals(node, out) {
|
|
|
3851
3941
|
if (child) collectStringLiterals(child, out);
|
|
3852
3942
|
}
|
|
3853
3943
|
}
|
|
3944
|
+
var PARSE_CHUNK = 16384;
|
|
3945
|
+
function parseSource(parser, source) {
|
|
3946
|
+
return parser.parse(
|
|
3947
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
|
|
3948
|
+
);
|
|
3949
|
+
}
|
|
3854
3950
|
function callsFromSource(source, parser, knownHosts) {
|
|
3855
|
-
const tree = parser
|
|
3951
|
+
const tree = parseSource(parser, source);
|
|
3856
3952
|
const literals = [];
|
|
3857
3953
|
collectStringLiterals(tree.rootNode, literals);
|
|
3858
3954
|
const out = [];
|
|
@@ -4101,6 +4197,7 @@ var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-
|
|
|
4101
4197
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
4102
4198
|
function isLikelyAddress(value) {
|
|
4103
4199
|
if (!value) return false;
|
|
4200
|
+
if (/\s/.test(value) || value.startsWith("{")) return false;
|
|
4104
4201
|
return /:\d{2,5}$/.test(value) || value.includes(".");
|
|
4105
4202
|
}
|
|
4106
4203
|
function normaliseForMatch(s) {
|
|
@@ -4412,17 +4509,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4412
4509
|
graph.addNode(node.id, node);
|
|
4413
4510
|
nodesAdded++;
|
|
4414
4511
|
}
|
|
4415
|
-
const
|
|
4512
|
+
const relDockerfile = toPosix2(import_node_path26.default.relative(service.dir, dockerfilePath));
|
|
4513
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4514
|
+
graph,
|
|
4515
|
+
service.pkg.name,
|
|
4516
|
+
service.node.id,
|
|
4517
|
+
relDockerfile
|
|
4518
|
+
);
|
|
4519
|
+
nodesAdded += fn;
|
|
4520
|
+
edgesAdded += fe;
|
|
4521
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types18.EdgeType.RUNS_ON);
|
|
4416
4522
|
if (!graph.hasEdge(edgeId)) {
|
|
4417
4523
|
const edge = {
|
|
4418
4524
|
id: edgeId,
|
|
4419
|
-
source:
|
|
4525
|
+
source: fileNodeId,
|
|
4420
4526
|
target: node.id,
|
|
4421
4527
|
type: import_types18.EdgeType.RUNS_ON,
|
|
4422
4528
|
provenance: import_types18.Provenance.EXTRACTED,
|
|
4423
4529
|
confidence: (0, import_types18.confidenceForExtracted)("structural"),
|
|
4424
4530
|
evidence: {
|
|
4425
|
-
file: import_node_path26.default.relative(scanPath, dockerfilePath)
|
|
4531
|
+
file: toPosix2(import_node_path26.default.relative(scanPath, dockerfilePath))
|
|
4426
4532
|
}
|
|
4427
4533
|
};
|
|
4428
4534
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -5140,8 +5246,8 @@ function formatIncompat(inc) {
|
|
|
5140
5246
|
|
|
5141
5247
|
// src/watch.ts
|
|
5142
5248
|
init_cjs_shims();
|
|
5143
|
-
var
|
|
5144
|
-
var
|
|
5249
|
+
var import_node_fs25 = __toESM(require("fs"), 1);
|
|
5250
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
5145
5251
|
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
5146
5252
|
|
|
5147
5253
|
// src/api.ts
|
|
@@ -5150,10 +5256,325 @@ var import_fastify = __toESM(require("fastify"), 1);
|
|
|
5150
5256
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
5151
5257
|
var import_types24 = require("@neat.is/types");
|
|
5152
5258
|
|
|
5153
|
-
// src/
|
|
5259
|
+
// src/extend/index.ts
|
|
5260
|
+
init_cjs_shims();
|
|
5261
|
+
var import_node_fs21 = require("fs");
|
|
5262
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
5263
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
5264
|
+
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
5265
|
+
|
|
5266
|
+
// src/installers/package-manager.ts
|
|
5154
5267
|
init_cjs_shims();
|
|
5155
5268
|
var import_node_fs20 = require("fs");
|
|
5156
|
-
|
|
5269
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
5270
|
+
var import_node_child_process = require("child_process");
|
|
5271
|
+
var LOCKFILE_PRIORITY = [
|
|
5272
|
+
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
5273
|
+
{ lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
|
|
5274
|
+
{ lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
|
|
5275
|
+
{
|
|
5276
|
+
lockfile: "package-lock.json",
|
|
5277
|
+
pm: "npm",
|
|
5278
|
+
args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
|
|
5279
|
+
}
|
|
5280
|
+
];
|
|
5281
|
+
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
5282
|
+
async function exists2(p) {
|
|
5283
|
+
try {
|
|
5284
|
+
await import_node_fs20.promises.access(p);
|
|
5285
|
+
return true;
|
|
5286
|
+
} catch {
|
|
5287
|
+
return false;
|
|
5288
|
+
}
|
|
5289
|
+
}
|
|
5290
|
+
async function detectPackageManager(serviceDir) {
|
|
5291
|
+
let dir = import_node_path33.default.resolve(serviceDir);
|
|
5292
|
+
const stops = /* @__PURE__ */ new Set();
|
|
5293
|
+
for (let i = 0; i < 64; i++) {
|
|
5294
|
+
if (stops.has(dir)) break;
|
|
5295
|
+
stops.add(dir);
|
|
5296
|
+
for (const candidate of LOCKFILE_PRIORITY) {
|
|
5297
|
+
const lockPath = import_node_path33.default.join(dir, candidate.lockfile);
|
|
5298
|
+
if (await exists2(lockPath)) {
|
|
5299
|
+
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5302
|
+
const parent = import_node_path33.default.dirname(dir);
|
|
5303
|
+
if (parent === dir) break;
|
|
5304
|
+
dir = parent;
|
|
5305
|
+
}
|
|
5306
|
+
return { pm: "npm", cwd: import_node_path33.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
5307
|
+
}
|
|
5308
|
+
async function runPackageManagerInstall(cmd) {
|
|
5309
|
+
return new Promise((resolve) => {
|
|
5310
|
+
const child = (0, import_node_child_process.spawn)(cmd.pm, cmd.args, {
|
|
5311
|
+
cwd: cmd.cwd,
|
|
5312
|
+
// Inherit PATH + HOME so the user's installed managers resolve.
|
|
5313
|
+
env: process.env,
|
|
5314
|
+
// `false` keeps the parent in control of cleanup if the orchestrator
|
|
5315
|
+
// exits before install finishes. Cross-platform-safe.
|
|
5316
|
+
shell: false,
|
|
5317
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
5318
|
+
});
|
|
5319
|
+
let stderr = "";
|
|
5320
|
+
child.stderr?.on("data", (chunk) => {
|
|
5321
|
+
stderr += chunk.toString("utf8");
|
|
5322
|
+
});
|
|
5323
|
+
child.on("error", (err) => {
|
|
5324
|
+
resolve({
|
|
5325
|
+
pm: cmd.pm,
|
|
5326
|
+
cwd: cmd.cwd,
|
|
5327
|
+
args: cmd.args,
|
|
5328
|
+
exitCode: 127,
|
|
5329
|
+
stderr: stderr + `
|
|
5330
|
+
${err.message}`
|
|
5331
|
+
});
|
|
5332
|
+
});
|
|
5333
|
+
child.on("close", (code) => {
|
|
5334
|
+
resolve({
|
|
5335
|
+
pm: cmd.pm,
|
|
5336
|
+
cwd: cmd.cwd,
|
|
5337
|
+
args: cmd.args,
|
|
5338
|
+
exitCode: code ?? 1,
|
|
5339
|
+
stderr: stderr.trim()
|
|
5340
|
+
});
|
|
5341
|
+
});
|
|
5342
|
+
});
|
|
5343
|
+
}
|
|
5344
|
+
|
|
5345
|
+
// src/extend/index.ts
|
|
5346
|
+
async function fileExists(p) {
|
|
5347
|
+
try {
|
|
5348
|
+
await import_node_fs21.promises.access(p);
|
|
5349
|
+
return true;
|
|
5350
|
+
} catch {
|
|
5351
|
+
return false;
|
|
5352
|
+
}
|
|
5353
|
+
}
|
|
5354
|
+
async function readPackageJson(scanPath) {
|
|
5355
|
+
const pkgPath = import_node_path34.default.join(scanPath, "package.json");
|
|
5356
|
+
const raw = await import_node_fs21.promises.readFile(pkgPath, "utf8");
|
|
5357
|
+
return JSON.parse(raw);
|
|
5358
|
+
}
|
|
5359
|
+
async function findHookFiles(scanPath) {
|
|
5360
|
+
const entries = await import_node_fs21.promises.readdir(scanPath);
|
|
5361
|
+
return entries.filter(
|
|
5362
|
+
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
5363
|
+
).sort();
|
|
5364
|
+
}
|
|
5365
|
+
function extendLogPath() {
|
|
5366
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path34.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
5367
|
+
}
|
|
5368
|
+
async function appendExtendLog(entry2) {
|
|
5369
|
+
const logPath = extendLogPath();
|
|
5370
|
+
await import_node_fs21.promises.mkdir(import_node_path34.default.dirname(logPath), { recursive: true });
|
|
5371
|
+
await import_node_fs21.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
|
|
5372
|
+
}
|
|
5373
|
+
function splicedContent(fileContent, snippet2) {
|
|
5374
|
+
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
5375
|
+
return fileContent.replace("__INSTRUMENTATION_BLOCK__", `${snippet2}
|
|
5376
|
+
__INSTRUMENTATION_BLOCK__`);
|
|
5377
|
+
}
|
|
5378
|
+
const lines = fileContent.split("\n");
|
|
5379
|
+
let lastPushIdx = -1;
|
|
5380
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5381
|
+
if (lines[i].includes("instrumentations.push(")) lastPushIdx = i;
|
|
5382
|
+
}
|
|
5383
|
+
if (lastPushIdx >= 0) {
|
|
5384
|
+
lines.splice(lastPushIdx + 1, 0, snippet2);
|
|
5385
|
+
return lines.join("\n");
|
|
5386
|
+
}
|
|
5387
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5388
|
+
if (lines[i].includes("new NodeSDK(")) {
|
|
5389
|
+
lines.splice(i, 0, snippet2);
|
|
5390
|
+
return lines.join("\n");
|
|
5391
|
+
}
|
|
5392
|
+
}
|
|
5393
|
+
return null;
|
|
5394
|
+
}
|
|
5395
|
+
async function listUninstrumented(ctx) {
|
|
5396
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5397
|
+
const allDeps2 = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5398
|
+
const results = [];
|
|
5399
|
+
for (const [library, installedVersion] of Object.entries(allDeps2)) {
|
|
5400
|
+
const entry2 = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
5401
|
+
if (!entry2) continue;
|
|
5402
|
+
if (entry2.coverage === "bundled" || entry2.coverage === "http-only") continue;
|
|
5403
|
+
results.push({
|
|
5404
|
+
library,
|
|
5405
|
+
coverage: entry2.coverage,
|
|
5406
|
+
installedVersion,
|
|
5407
|
+
instrumentation_package: entry2.instrumentation_package,
|
|
5408
|
+
package_version: entry2.package_version,
|
|
5409
|
+
registration: entry2.registration,
|
|
5410
|
+
notes: entry2.notes
|
|
5411
|
+
});
|
|
5412
|
+
}
|
|
5413
|
+
return results;
|
|
5414
|
+
}
|
|
5415
|
+
function lookupInstrumentation(library, installedVersion) {
|
|
5416
|
+
const entry2 = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
5417
|
+
if (!entry2) return null;
|
|
5418
|
+
return {
|
|
5419
|
+
library: entry2.library,
|
|
5420
|
+
coverage: entry2.coverage,
|
|
5421
|
+
instrumentation_package: entry2.instrumentation_package,
|
|
5422
|
+
package_version: entry2.package_version,
|
|
5423
|
+
registration: entry2.registration,
|
|
5424
|
+
notes: entry2.notes
|
|
5425
|
+
};
|
|
5426
|
+
}
|
|
5427
|
+
async function describeProjectInstrumentation(ctx) {
|
|
5428
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5429
|
+
const envNeat = await fileExists(import_node_path34.default.join(ctx.scanPath, ".env.neat"));
|
|
5430
|
+
const registryInstrPackages = new Set(
|
|
5431
|
+
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
5432
|
+
);
|
|
5433
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5434
|
+
const allDeps2 = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5435
|
+
const installedDeps = {};
|
|
5436
|
+
for (const [key, version] of Object.entries(allDeps2)) {
|
|
5437
|
+
if (key.startsWith("@opentelemetry/") || registryInstrPackages.has(key)) {
|
|
5438
|
+
installedDeps[key] = version;
|
|
5439
|
+
}
|
|
5440
|
+
}
|
|
5441
|
+
return { hookFiles, envNeat, installedDeps };
|
|
5442
|
+
}
|
|
5443
|
+
async function applyExtension(ctx, args, options) {
|
|
5444
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5445
|
+
if (hookFiles.length === 0) {
|
|
5446
|
+
throw new Error(
|
|
5447
|
+
`No instrumentation hook files found in ${ctx.scanPath}. Run \`neat init\` first.`
|
|
5448
|
+
);
|
|
5449
|
+
}
|
|
5450
|
+
for (const file of hookFiles) {
|
|
5451
|
+
const content = await import_node_fs21.promises.readFile(import_node_path34.default.join(ctx.scanPath, file), "utf8");
|
|
5452
|
+
if (content.includes(args.registration_snippet)) {
|
|
5453
|
+
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
5454
|
+
}
|
|
5455
|
+
}
|
|
5456
|
+
const primaryFile = hookFiles[0];
|
|
5457
|
+
const primaryPath = import_node_path34.default.join(ctx.scanPath, primaryFile);
|
|
5458
|
+
const filesTouched = [];
|
|
5459
|
+
const depsAdded = [];
|
|
5460
|
+
const pkgPath = import_node_path34.default.join(ctx.scanPath, "package.json");
|
|
5461
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5462
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5463
|
+
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
5464
|
+
await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5465
|
+
filesTouched.push("package.json");
|
|
5466
|
+
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
5467
|
+
}
|
|
5468
|
+
const hookContent = await import_node_fs21.promises.readFile(primaryPath, "utf8");
|
|
5469
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5470
|
+
if (!patched) {
|
|
5471
|
+
throw new Error(
|
|
5472
|
+
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
5473
|
+
);
|
|
5474
|
+
}
|
|
5475
|
+
await import_node_fs21.promises.writeFile(primaryPath, patched, "utf8");
|
|
5476
|
+
filesTouched.push(primaryFile);
|
|
5477
|
+
const cmd = await detectPackageManager(ctx.scanPath);
|
|
5478
|
+
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
5479
|
+
const install = await installer(cmd);
|
|
5480
|
+
const installOutput = install.exitCode === 0 ? `${cmd.pm} install succeeded` : install.stderr || `${cmd.pm} install failed (exit ${install.exitCode})`;
|
|
5481
|
+
await appendExtendLog({
|
|
5482
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5483
|
+
project: ctx.project,
|
|
5484
|
+
library: args.library,
|
|
5485
|
+
instrumentation_package: args.instrumentation_package,
|
|
5486
|
+
version: args.version,
|
|
5487
|
+
registration_snippet: args.registration_snippet,
|
|
5488
|
+
filesTouched,
|
|
5489
|
+
depsAdded,
|
|
5490
|
+
installOutput
|
|
5491
|
+
});
|
|
5492
|
+
return { library: args.library, filesTouched, depsAdded, installOutput, alreadyApplied: false };
|
|
5493
|
+
}
|
|
5494
|
+
async function dryRunExtension(ctx, args) {
|
|
5495
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5496
|
+
if (hookFiles.length === 0) {
|
|
5497
|
+
return {
|
|
5498
|
+
library: args.library,
|
|
5499
|
+
filesTouched: [],
|
|
5500
|
+
depsToAdd: [],
|
|
5501
|
+
packageJsonPatch: {},
|
|
5502
|
+
templatePatch: "No hook files found. Run 'neat init' first."
|
|
5503
|
+
};
|
|
5504
|
+
}
|
|
5505
|
+
for (const file of hookFiles) {
|
|
5506
|
+
const content = await import_node_fs21.promises.readFile(import_node_path34.default.join(ctx.scanPath, file), "utf8");
|
|
5507
|
+
if (content.includes(args.registration_snippet)) {
|
|
5508
|
+
return {
|
|
5509
|
+
library: args.library,
|
|
5510
|
+
filesTouched: [],
|
|
5511
|
+
depsToAdd: [],
|
|
5512
|
+
packageJsonPatch: {},
|
|
5513
|
+
templatePatch: "Already applied \u2014 no changes would be made."
|
|
5514
|
+
};
|
|
5515
|
+
}
|
|
5516
|
+
}
|
|
5517
|
+
const primaryFile = hookFiles[0];
|
|
5518
|
+
const filesTouched = [];
|
|
5519
|
+
const depsToAdd = [];
|
|
5520
|
+
let packageJsonPatch = {};
|
|
5521
|
+
let templatePatch = "";
|
|
5522
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5523
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5524
|
+
packageJsonPatch = { dependencies: { [args.instrumentation_package]: args.version } };
|
|
5525
|
+
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
5526
|
+
filesTouched.push("package.json");
|
|
5527
|
+
}
|
|
5528
|
+
const hookContent = await import_node_fs21.promises.readFile(import_node_path34.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
5529
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5530
|
+
if (patched) {
|
|
5531
|
+
filesTouched.push(primaryFile);
|
|
5532
|
+
templatePatch = `+ ${args.registration_snippet}`;
|
|
5533
|
+
} else {
|
|
5534
|
+
templatePatch = "Could not find insertion point in hook file.";
|
|
5535
|
+
}
|
|
5536
|
+
return { library: args.library, filesTouched, depsToAdd, packageJsonPatch, templatePatch };
|
|
5537
|
+
}
|
|
5538
|
+
async function rollbackExtension(ctx, args) {
|
|
5539
|
+
const logPath = extendLogPath();
|
|
5540
|
+
if (!await fileExists(logPath)) {
|
|
5541
|
+
return { undone: false, message: "no apply found for library" };
|
|
5542
|
+
}
|
|
5543
|
+
const raw = await import_node_fs21.promises.readFile(logPath, "utf8");
|
|
5544
|
+
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
5545
|
+
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
5546
|
+
if (!match) {
|
|
5547
|
+
return { undone: false, message: "no apply found for library" };
|
|
5548
|
+
}
|
|
5549
|
+
const pkgPath = import_node_path34.default.join(ctx.scanPath, "package.json");
|
|
5550
|
+
if (await fileExists(pkgPath)) {
|
|
5551
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5552
|
+
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
5553
|
+
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
5554
|
+
pkg.dependencies = rest;
|
|
5555
|
+
await import_node_fs21.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5556
|
+
}
|
|
5557
|
+
}
|
|
5558
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5559
|
+
for (const file of hookFiles) {
|
|
5560
|
+
const filePath = import_node_path34.default.join(ctx.scanPath, file);
|
|
5561
|
+
const content = await import_node_fs21.promises.readFile(filePath, "utf8");
|
|
5562
|
+
if (content.includes(match.registration_snippet)) {
|
|
5563
|
+
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
5564
|
+
await import_node_fs21.promises.writeFile(filePath, filtered, "utf8");
|
|
5565
|
+
break;
|
|
5566
|
+
}
|
|
5567
|
+
}
|
|
5568
|
+
return {
|
|
5569
|
+
undone: true,
|
|
5570
|
+
message: `rolled back ${match.library} (${match.instrumentation_package})`
|
|
5571
|
+
};
|
|
5572
|
+
}
|
|
5573
|
+
|
|
5574
|
+
// src/diff.ts
|
|
5575
|
+
init_cjs_shims();
|
|
5576
|
+
var import_node_fs22 = require("fs");
|
|
5577
|
+
async function loadSnapshotForDiff(target) {
|
|
5157
5578
|
if (/^https?:\/\//i.test(target)) {
|
|
5158
5579
|
const res = await fetch(target);
|
|
5159
5580
|
if (!res.ok) {
|
|
@@ -5161,7 +5582,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
5161
5582
|
}
|
|
5162
5583
|
return await res.json();
|
|
5163
5584
|
}
|
|
5164
|
-
const raw = await
|
|
5585
|
+
const raw = await import_node_fs22.promises.readFile(target, "utf8");
|
|
5165
5586
|
return JSON.parse(raw);
|
|
5166
5587
|
}
|
|
5167
5588
|
function indexEntries(entries) {
|
|
@@ -5229,23 +5650,23 @@ function canonicalJson(value) {
|
|
|
5229
5650
|
|
|
5230
5651
|
// src/projects.ts
|
|
5231
5652
|
init_cjs_shims();
|
|
5232
|
-
var
|
|
5653
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
5233
5654
|
function pathsForProject(project, baseDir) {
|
|
5234
5655
|
if (project === DEFAULT_PROJECT) {
|
|
5235
5656
|
return {
|
|
5236
|
-
snapshotPath:
|
|
5237
|
-
errorsPath:
|
|
5238
|
-
staleEventsPath:
|
|
5239
|
-
embeddingsCachePath:
|
|
5240
|
-
policyViolationsPath:
|
|
5657
|
+
snapshotPath: import_node_path35.default.join(baseDir, "graph.json"),
|
|
5658
|
+
errorsPath: import_node_path35.default.join(baseDir, "errors.ndjson"),
|
|
5659
|
+
staleEventsPath: import_node_path35.default.join(baseDir, "stale-events.ndjson"),
|
|
5660
|
+
embeddingsCachePath: import_node_path35.default.join(baseDir, "embeddings.json"),
|
|
5661
|
+
policyViolationsPath: import_node_path35.default.join(baseDir, "policy-violations.ndjson")
|
|
5241
5662
|
};
|
|
5242
5663
|
}
|
|
5243
5664
|
return {
|
|
5244
|
-
snapshotPath:
|
|
5245
|
-
errorsPath:
|
|
5246
|
-
staleEventsPath:
|
|
5247
|
-
embeddingsCachePath:
|
|
5248
|
-
policyViolationsPath:
|
|
5665
|
+
snapshotPath: import_node_path35.default.join(baseDir, `${project}.json`),
|
|
5666
|
+
errorsPath: import_node_path35.default.join(baseDir, `errors.${project}.ndjson`),
|
|
5667
|
+
staleEventsPath: import_node_path35.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
5668
|
+
embeddingsCachePath: import_node_path35.default.join(baseDir, `embeddings.${project}.json`),
|
|
5669
|
+
policyViolationsPath: import_node_path35.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
5249
5670
|
};
|
|
5250
5671
|
}
|
|
5251
5672
|
var Projects = class {
|
|
@@ -5281,65 +5702,135 @@ var Projects = class {
|
|
|
5281
5702
|
|
|
5282
5703
|
// src/registry.ts
|
|
5283
5704
|
init_cjs_shims();
|
|
5284
|
-
var
|
|
5285
|
-
var
|
|
5286
|
-
var
|
|
5705
|
+
var import_node_fs23 = require("fs");
|
|
5706
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
5707
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
5287
5708
|
var import_types23 = require("@neat.is/types");
|
|
5288
5709
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
5289
5710
|
var LOCK_RETRY_MS = 50;
|
|
5290
5711
|
function neatHome() {
|
|
5291
5712
|
const override = process.env.NEAT_HOME;
|
|
5292
|
-
if (override && override.length > 0) return
|
|
5293
|
-
return
|
|
5713
|
+
if (override && override.length > 0) return import_node_path36.default.resolve(override);
|
|
5714
|
+
return import_node_path36.default.join(import_node_os3.default.homedir(), ".neat");
|
|
5294
5715
|
}
|
|
5295
5716
|
function registryPath() {
|
|
5296
|
-
return
|
|
5717
|
+
return import_node_path36.default.join(neatHome(), "projects.json");
|
|
5297
5718
|
}
|
|
5298
5719
|
function registryLockPath() {
|
|
5299
|
-
return
|
|
5720
|
+
return import_node_path36.default.join(neatHome(), "projects.json.lock");
|
|
5721
|
+
}
|
|
5722
|
+
function daemonPidPath() {
|
|
5723
|
+
return import_node_path36.default.join(neatHome(), "neatd.pid");
|
|
5724
|
+
}
|
|
5725
|
+
function isPidAliveDefault(pid) {
|
|
5726
|
+
try {
|
|
5727
|
+
process.kill(pid, 0);
|
|
5728
|
+
return true;
|
|
5729
|
+
} catch (err) {
|
|
5730
|
+
return err.code === "EPERM";
|
|
5731
|
+
}
|
|
5732
|
+
}
|
|
5733
|
+
async function readPidFile(file) {
|
|
5734
|
+
try {
|
|
5735
|
+
const raw = await import_node_fs23.promises.readFile(file, "utf8");
|
|
5736
|
+
const pid = Number.parseInt(raw.trim(), 10);
|
|
5737
|
+
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
5738
|
+
} catch {
|
|
5739
|
+
return void 0;
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
5742
|
+
var defaultLockHolderProbe = {
|
|
5743
|
+
isPidAlive: isPidAliveDefault,
|
|
5744
|
+
daemonPidFromFile: () => readPidFile(daemonPidPath()),
|
|
5745
|
+
async daemonResponds() {
|
|
5746
|
+
const base = process.env.NEAT_API_URL ?? "http://localhost:8080";
|
|
5747
|
+
try {
|
|
5748
|
+
await fetch(`${base}/health`, { signal: AbortSignal.timeout(750) });
|
|
5749
|
+
return true;
|
|
5750
|
+
} catch {
|
|
5751
|
+
return false;
|
|
5752
|
+
}
|
|
5753
|
+
}
|
|
5754
|
+
};
|
|
5755
|
+
async function readLockPid(lockPath) {
|
|
5756
|
+
return readPidFile(lockPath);
|
|
5757
|
+
}
|
|
5758
|
+
async function classifyLockHolder(lockPath, probe = defaultLockHolderProbe) {
|
|
5759
|
+
const lockPid = await readLockPid(lockPath);
|
|
5760
|
+
const daemonPid = await probe.daemonPidFromFile();
|
|
5761
|
+
if (daemonPid !== void 0 && daemonPid !== process.pid && probe.isPidAlive(daemonPid) && // The lock already names the daemon, or the daemon answers on its port.
|
|
5762
|
+
// Either confirms a live daemon is in the picture (the second guards
|
|
5763
|
+
// against a stale pidfile whose PID got reused).
|
|
5764
|
+
(daemonPid === lockPid || await probe.daemonResponds())) {
|
|
5765
|
+
return { kind: "daemon", pid: daemonPid };
|
|
5766
|
+
}
|
|
5767
|
+
if (lockPid !== void 0 && lockPid !== process.pid && probe.isPidAlive(lockPid)) {
|
|
5768
|
+
return { kind: "command", pid: lockPid };
|
|
5769
|
+
}
|
|
5770
|
+
return { kind: "stale" };
|
|
5771
|
+
}
|
|
5772
|
+
function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
5773
|
+
switch (holder.kind) {
|
|
5774
|
+
case "daemon":
|
|
5775
|
+
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\`.`;
|
|
5776
|
+
case "command":
|
|
5777
|
+
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.`;
|
|
5778
|
+
case "stale":
|
|
5779
|
+
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.`;
|
|
5780
|
+
}
|
|
5300
5781
|
}
|
|
5301
5782
|
async function normalizeProjectPath(input) {
|
|
5302
|
-
const resolved =
|
|
5783
|
+
const resolved = import_node_path36.default.resolve(input);
|
|
5303
5784
|
try {
|
|
5304
|
-
return await
|
|
5785
|
+
return await import_node_fs23.promises.realpath(resolved);
|
|
5305
5786
|
} catch {
|
|
5306
5787
|
return resolved;
|
|
5307
5788
|
}
|
|
5308
5789
|
}
|
|
5309
5790
|
async function writeAtomically(target, contents) {
|
|
5310
|
-
await
|
|
5791
|
+
await import_node_fs23.promises.mkdir(import_node_path36.default.dirname(target), { recursive: true });
|
|
5311
5792
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
5312
|
-
const fd = await
|
|
5793
|
+
const fd = await import_node_fs23.promises.open(tmp, "w");
|
|
5313
5794
|
try {
|
|
5314
5795
|
await fd.writeFile(contents, "utf8");
|
|
5315
5796
|
await fd.sync();
|
|
5316
5797
|
} finally {
|
|
5317
5798
|
await fd.close();
|
|
5318
5799
|
}
|
|
5319
|
-
await
|
|
5800
|
+
await import_node_fs23.promises.rename(tmp, target);
|
|
5320
5801
|
}
|
|
5321
|
-
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS) {
|
|
5802
|
+
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
5322
5803
|
const deadline = Date.now() + timeoutMs;
|
|
5323
|
-
await
|
|
5804
|
+
await import_node_fs23.promises.mkdir(import_node_path36.default.dirname(lockPath), { recursive: true });
|
|
5805
|
+
let probedHolder = false;
|
|
5324
5806
|
while (true) {
|
|
5325
5807
|
try {
|
|
5326
|
-
const fd = await
|
|
5327
|
-
|
|
5808
|
+
const fd = await import_node_fs23.promises.open(lockPath, "wx");
|
|
5809
|
+
try {
|
|
5810
|
+
await fd.writeFile(`${process.pid}
|
|
5811
|
+
`, "utf8");
|
|
5812
|
+
} finally {
|
|
5813
|
+
await fd.close();
|
|
5814
|
+
}
|
|
5328
5815
|
return;
|
|
5329
5816
|
} catch (err) {
|
|
5330
5817
|
const code = err.code;
|
|
5331
5818
|
if (code !== "EEXIST") throw err;
|
|
5819
|
+
if (!probedHolder) {
|
|
5820
|
+
probedHolder = true;
|
|
5821
|
+
const holder = await classifyLockHolder(lockPath, probe);
|
|
5822
|
+
if (holder.kind === "daemon") throw new Error(lockHolderMessage(holder, lockPath, timeoutMs));
|
|
5823
|
+
}
|
|
5332
5824
|
if (Date.now() >= deadline) {
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
);
|
|
5825
|
+
const holder = await classifyLockHolder(lockPath, probe);
|
|
5826
|
+
throw new Error(lockHolderMessage(holder, lockPath, timeoutMs));
|
|
5336
5827
|
}
|
|
5337
5828
|
await new Promise((r) => setTimeout(r, LOCK_RETRY_MS));
|
|
5338
5829
|
}
|
|
5339
5830
|
}
|
|
5340
5831
|
}
|
|
5341
5832
|
async function releaseLock(lockPath) {
|
|
5342
|
-
await
|
|
5833
|
+
await import_node_fs23.promises.unlink(lockPath).catch(() => {
|
|
5343
5834
|
});
|
|
5344
5835
|
}
|
|
5345
5836
|
async function withLock(fn) {
|
|
@@ -5355,7 +5846,7 @@ async function readRegistry() {
|
|
|
5355
5846
|
const file = registryPath();
|
|
5356
5847
|
let raw;
|
|
5357
5848
|
try {
|
|
5358
|
-
raw = await
|
|
5849
|
+
raw = await import_node_fs23.promises.readFile(file, "utf8");
|
|
5359
5850
|
} catch (err) {
|
|
5360
5851
|
if (err.code === "ENOENT") {
|
|
5361
5852
|
return { version: 1, projects: [] };
|
|
@@ -5896,6 +6387,105 @@ function registerRoutes(scope, ctx) {
|
|
|
5896
6387
|
violations
|
|
5897
6388
|
};
|
|
5898
6389
|
});
|
|
6390
|
+
scope.get("/extend/list-uninstrumented", async (req, reply) => {
|
|
6391
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6392
|
+
if (!proj) return;
|
|
6393
|
+
if (!proj.scanPath) {
|
|
6394
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6395
|
+
}
|
|
6396
|
+
try {
|
|
6397
|
+
const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
|
|
6398
|
+
return { libraries: results };
|
|
6399
|
+
} catch (err) {
|
|
6400
|
+
return reply.code(500).send({ error: err.message });
|
|
6401
|
+
}
|
|
6402
|
+
});
|
|
6403
|
+
scope.get("/extend/lookup", async (req, reply) => {
|
|
6404
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6405
|
+
if (!proj) return;
|
|
6406
|
+
const { library, version } = req.query;
|
|
6407
|
+
if (!library) {
|
|
6408
|
+
return reply.code(400).send({ error: "query parameter `library` is required" });
|
|
6409
|
+
}
|
|
6410
|
+
const result = lookupInstrumentation(library, version);
|
|
6411
|
+
if (!result) {
|
|
6412
|
+
return reply.code(404).send({ error: "library not found in registry", library });
|
|
6413
|
+
}
|
|
6414
|
+
return result;
|
|
6415
|
+
});
|
|
6416
|
+
scope.get("/extend/describe", async (req, reply) => {
|
|
6417
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6418
|
+
if (!proj) return;
|
|
6419
|
+
if (!proj.scanPath) {
|
|
6420
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6421
|
+
}
|
|
6422
|
+
try {
|
|
6423
|
+
const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
|
|
6424
|
+
return state;
|
|
6425
|
+
} catch (err) {
|
|
6426
|
+
return reply.code(500).send({ error: err.message });
|
|
6427
|
+
}
|
|
6428
|
+
});
|
|
6429
|
+
scope.post("/extend/apply", async (req, reply) => {
|
|
6430
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6431
|
+
if (!proj) return;
|
|
6432
|
+
if (!proj.scanPath) {
|
|
6433
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6434
|
+
}
|
|
6435
|
+
const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
|
|
6436
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6437
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6438
|
+
}
|
|
6439
|
+
try {
|
|
6440
|
+
const result = await applyExtension(
|
|
6441
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6442
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6443
|
+
);
|
|
6444
|
+
return result;
|
|
6445
|
+
} catch (err) {
|
|
6446
|
+
return reply.code(500).send({ error: err.message });
|
|
6447
|
+
}
|
|
6448
|
+
});
|
|
6449
|
+
scope.post("/extend/dry-run", async (req, reply) => {
|
|
6450
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6451
|
+
if (!proj) return;
|
|
6452
|
+
if (!proj.scanPath) {
|
|
6453
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6454
|
+
}
|
|
6455
|
+
const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
|
|
6456
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6457
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6458
|
+
}
|
|
6459
|
+
try {
|
|
6460
|
+
const result = await dryRunExtension(
|
|
6461
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6462
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6463
|
+
);
|
|
6464
|
+
return result;
|
|
6465
|
+
} catch (err) {
|
|
6466
|
+
return reply.code(500).send({ error: err.message });
|
|
6467
|
+
}
|
|
6468
|
+
});
|
|
6469
|
+
scope.post("/extend/rollback", async (req, reply) => {
|
|
6470
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6471
|
+
if (!proj) return;
|
|
6472
|
+
if (!proj.scanPath) {
|
|
6473
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6474
|
+
}
|
|
6475
|
+
const { library } = req.body ?? {};
|
|
6476
|
+
if (!library) {
|
|
6477
|
+
return reply.code(400).send({ error: "body must include library" });
|
|
6478
|
+
}
|
|
6479
|
+
try {
|
|
6480
|
+
const result = await rollbackExtension(
|
|
6481
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6482
|
+
{ library }
|
|
6483
|
+
);
|
|
6484
|
+
return result;
|
|
6485
|
+
} catch (err) {
|
|
6486
|
+
return reply.code(500).send({ error: err.message });
|
|
6487
|
+
}
|
|
6488
|
+
});
|
|
5899
6489
|
}
|
|
5900
6490
|
async function buildApi(opts) {
|
|
5901
6491
|
const app = (0, import_fastify.default)({ logger: false });
|
|
@@ -6007,8 +6597,8 @@ init_otel_grpc();
|
|
|
6007
6597
|
|
|
6008
6598
|
// src/search.ts
|
|
6009
6599
|
init_cjs_shims();
|
|
6010
|
-
var
|
|
6011
|
-
var
|
|
6600
|
+
var import_node_fs24 = require("fs");
|
|
6601
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
6012
6602
|
var import_node_crypto3 = require("crypto");
|
|
6013
6603
|
var DEFAULT_LIMIT = 10;
|
|
6014
6604
|
var NOMIC_DIM = 768;
|
|
@@ -6138,7 +6728,7 @@ async function pickEmbedder() {
|
|
|
6138
6728
|
}
|
|
6139
6729
|
async function readCache(cachePath) {
|
|
6140
6730
|
try {
|
|
6141
|
-
const raw = await
|
|
6731
|
+
const raw = await import_node_fs24.promises.readFile(cachePath, "utf8");
|
|
6142
6732
|
const parsed = JSON.parse(raw);
|
|
6143
6733
|
if (parsed.version !== 1) return null;
|
|
6144
6734
|
return parsed;
|
|
@@ -6147,8 +6737,8 @@ async function readCache(cachePath) {
|
|
|
6147
6737
|
}
|
|
6148
6738
|
}
|
|
6149
6739
|
async function writeCache(cachePath, cache) {
|
|
6150
|
-
await
|
|
6151
|
-
await
|
|
6740
|
+
await import_node_fs24.promises.mkdir(import_node_path39.default.dirname(cachePath), { recursive: true });
|
|
6741
|
+
await import_node_fs24.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
6152
6742
|
}
|
|
6153
6743
|
var VectorIndex = class {
|
|
6154
6744
|
constructor(embedder, cachePath) {
|
|
@@ -6303,8 +6893,8 @@ var ALL_PHASES = [
|
|
|
6303
6893
|
];
|
|
6304
6894
|
function classifyChange(relPath) {
|
|
6305
6895
|
const phases = /* @__PURE__ */ new Set();
|
|
6306
|
-
const base =
|
|
6307
|
-
const segments = relPath.split(
|
|
6896
|
+
const base = import_node_path40.default.basename(relPath).toLowerCase();
|
|
6897
|
+
const segments = relPath.split(import_node_path40.default.sep).map((s) => s.toLowerCase());
|
|
6308
6898
|
if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
|
|
6309
6899
|
phases.add("services");
|
|
6310
6900
|
phases.add("aliases");
|
|
@@ -6412,16 +7002,16 @@ function countWatchableDirs(scanPath, limit) {
|
|
|
6412
7002
|
if (count >= limit) return;
|
|
6413
7003
|
let entries;
|
|
6414
7004
|
try {
|
|
6415
|
-
entries =
|
|
7005
|
+
entries = import_node_fs25.default.readdirSync(dir, { withFileTypes: true });
|
|
6416
7006
|
} catch {
|
|
6417
7007
|
return;
|
|
6418
7008
|
}
|
|
6419
7009
|
for (const e of entries) {
|
|
6420
7010
|
if (count >= limit) return;
|
|
6421
7011
|
if (!e.isDirectory()) continue;
|
|
6422
|
-
if (IGNORED_WATCH_PATHS.some((re) => re.test(
|
|
7012
|
+
if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path40.default.join(dir, e.name) + import_node_path40.default.sep))) continue;
|
|
6423
7013
|
count++;
|
|
6424
|
-
if (depth < 2) visit(
|
|
7014
|
+
if (depth < 2) visit(import_node_path40.default.join(dir, e.name), depth + 1);
|
|
6425
7015
|
}
|
|
6426
7016
|
};
|
|
6427
7017
|
visit(scanPath, 0);
|
|
@@ -6439,8 +7029,8 @@ async function startWatch(graph, opts) {
|
|
|
6439
7029
|
const projectName = opts.project ?? DEFAULT_PROJECT;
|
|
6440
7030
|
await loadGraphFromDisk(graph, opts.outPath);
|
|
6441
7031
|
const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
|
|
6442
|
-
const policyFilePath =
|
|
6443
|
-
const policyViolationsPath =
|
|
7032
|
+
const policyFilePath = import_node_path40.default.join(opts.scanPath, "policy.json");
|
|
7033
|
+
const policyViolationsPath = import_node_path40.default.join(import_node_path40.default.dirname(opts.outPath), "policy-violations.ndjson");
|
|
6444
7034
|
let policies = [];
|
|
6445
7035
|
try {
|
|
6446
7036
|
policies = await loadPolicyFile(policyFilePath);
|
|
@@ -6491,7 +7081,7 @@ async function startWatch(graph, opts) {
|
|
|
6491
7081
|
assertBindAuthority(host, auth.authToken);
|
|
6492
7082
|
const port = opts.port ?? 8080;
|
|
6493
7083
|
const otelPort = opts.otelPort ?? 4318;
|
|
6494
|
-
const cachePath = opts.embeddingsCachePath ??
|
|
7084
|
+
const cachePath = opts.embeddingsCachePath ?? import_node_path40.default.join(import_node_path40.default.dirname(opts.outPath), "embeddings.json");
|
|
6495
7085
|
let searchIndex;
|
|
6496
7086
|
try {
|
|
6497
7087
|
searchIndex = await buildSearchIndex(graph, { cachePath });
|
|
@@ -6509,7 +7099,7 @@ async function startWatch(graph, opts) {
|
|
|
6509
7099
|
// Paths are derived from the explicit options the watch caller passes
|
|
6510
7100
|
// — pathsForProject is only used to fill in the embeddings/snapshot
|
|
6511
7101
|
// fields so the registry shape is complete.
|
|
6512
|
-
...pathsForProject(projectName,
|
|
7102
|
+
...pathsForProject(projectName, import_node_path40.default.dirname(opts.outPath)),
|
|
6513
7103
|
snapshotPath: opts.outPath,
|
|
6514
7104
|
errorsPath: opts.errorsPath,
|
|
6515
7105
|
staleEventsPath: opts.staleEventsPath
|
|
@@ -6525,6 +7115,7 @@ async function startWatch(graph, opts) {
|
|
|
6525
7115
|
const onSpan = makeSpanHandler({
|
|
6526
7116
|
graph,
|
|
6527
7117
|
errorsPath: opts.errorsPath,
|
|
7118
|
+
scanPath: opts.scanPath,
|
|
6528
7119
|
project: projectName,
|
|
6529
7120
|
writeErrorEventInline: false,
|
|
6530
7121
|
onPolicyTrigger
|
|
@@ -6539,6 +7130,7 @@ async function startWatch(graph, opts) {
|
|
|
6539
7130
|
const onSpanGrpc = makeSpanHandler({
|
|
6540
7131
|
graph,
|
|
6541
7132
|
errorsPath: opts.errorsPath,
|
|
7133
|
+
scanPath: opts.scanPath,
|
|
6542
7134
|
project: projectName,
|
|
6543
7135
|
onPolicyTrigger
|
|
6544
7136
|
});
|
|
@@ -6594,9 +7186,9 @@ async function startWatch(graph, opts) {
|
|
|
6594
7186
|
};
|
|
6595
7187
|
const onPath = (absPath) => {
|
|
6596
7188
|
if (shouldIgnore(absPath)) return;
|
|
6597
|
-
const rel =
|
|
7189
|
+
const rel = import_node_path40.default.relative(opts.scanPath, absPath);
|
|
6598
7190
|
if (!rel || rel.startsWith("..")) return;
|
|
6599
|
-
pendingPaths.add(rel.split(
|
|
7191
|
+
pendingPaths.add(rel.split(import_node_path40.default.sep).join("/"));
|
|
6600
7192
|
const phases = classifyChange(rel);
|
|
6601
7193
|
if (phases.size === 0) {
|
|
6602
7194
|
for (const p of ALL_PHASES) pending.add(p);
|
|
@@ -6650,16 +7242,16 @@ async function startWatch(graph, opts) {
|
|
|
6650
7242
|
|
|
6651
7243
|
// src/deploy/detect.ts
|
|
6652
7244
|
init_cjs_shims();
|
|
6653
|
-
var
|
|
6654
|
-
var
|
|
6655
|
-
var
|
|
7245
|
+
var import_node_fs26 = require("fs");
|
|
7246
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
7247
|
+
var import_node_child_process2 = require("child_process");
|
|
6656
7248
|
var import_node_crypto4 = require("crypto");
|
|
6657
7249
|
function generateToken() {
|
|
6658
7250
|
return (0, import_node_crypto4.randomBytes)(32).toString("base64url");
|
|
6659
7251
|
}
|
|
6660
7252
|
async function probeBinary(binary, arg) {
|
|
6661
7253
|
return new Promise((resolve) => {
|
|
6662
|
-
const child = (0,
|
|
7254
|
+
const child = (0, import_node_child_process2.spawn)(binary, [arg], { stdio: "ignore" });
|
|
6663
7255
|
const timer = setTimeout(() => {
|
|
6664
7256
|
child.kill("SIGKILL");
|
|
6665
7257
|
resolve(false);
|
|
@@ -6751,21 +7343,21 @@ async function runDeploy(opts = {}) {
|
|
|
6751
7343
|
const token = generateToken();
|
|
6752
7344
|
switch (substrate) {
|
|
6753
7345
|
case "docker-compose": {
|
|
6754
|
-
const artifactPath =
|
|
7346
|
+
const artifactPath = import_node_path41.default.join(cwd, "docker-compose.neat.yml");
|
|
6755
7347
|
const contents = emitDockerCompose(cwd);
|
|
6756
|
-
await
|
|
7348
|
+
await import_node_fs26.promises.writeFile(artifactPath, contents, "utf8");
|
|
6757
7349
|
return {
|
|
6758
7350
|
substrate,
|
|
6759
7351
|
artifactPath,
|
|
6760
7352
|
token,
|
|
6761
7353
|
contents,
|
|
6762
|
-
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${
|
|
7354
|
+
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path41.default.basename(artifactPath)} up -d`
|
|
6763
7355
|
};
|
|
6764
7356
|
}
|
|
6765
7357
|
case "systemd": {
|
|
6766
|
-
const artifactPath =
|
|
7358
|
+
const artifactPath = import_node_path41.default.join(cwd, "neat.service");
|
|
6767
7359
|
const contents = emitSystemdUnit(cwd);
|
|
6768
|
-
await
|
|
7360
|
+
await import_node_fs26.promises.writeFile(artifactPath, contents, "utf8");
|
|
6769
7361
|
return {
|
|
6770
7362
|
substrate,
|
|
6771
7363
|
artifactPath,
|
|
@@ -6798,19 +7390,27 @@ init_cjs_shims();
|
|
|
6798
7390
|
|
|
6799
7391
|
// src/installers/javascript.ts
|
|
6800
7392
|
init_cjs_shims();
|
|
6801
|
-
var
|
|
6802
|
-
var
|
|
7393
|
+
var import_node_fs27 = require("fs");
|
|
7394
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
7395
|
+
var import_semver2 = __toESM(require("semver"), 1);
|
|
6803
7396
|
|
|
6804
7397
|
// src/installers/templates.ts
|
|
6805
7398
|
init_cjs_shims();
|
|
6806
7399
|
var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
|
|
6807
|
-
var OTEL_INIT_STAMP = "// neat-template-version:
|
|
7400
|
+
var OTEL_INIT_STAMP = "// neat-template-version: 4 \u2014 layered file-first capture (ADR-090): stack walk + handler-entry + off-stack facades.";
|
|
6808
7401
|
var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer ' + process.env.NEAT_OTEL_TOKEN";
|
|
6809
|
-
function
|
|
6810
|
-
const stackT = ts ? ": string" : "";
|
|
7402
|
+
function neatCaptureSource(ts) {
|
|
6811
7403
|
const spanT = ts ? ": any" : "";
|
|
6812
7404
|
const strOpt = ts ? ": string | undefined" : "";
|
|
6813
|
-
|
|
7405
|
+
const anyT = ts ? ": any" : "";
|
|
7406
|
+
const fnT = ts ? ": any" : "";
|
|
7407
|
+
const arrAny = ts ? ": any[]" : "";
|
|
7408
|
+
return `// Context key shared by the facade/handler wraps (writers) and the
|
|
7409
|
+
// processor fallback (reader). Symbol.for keeps it stable even if the wraps
|
|
7410
|
+
// and the processor end up in different module instances.
|
|
7411
|
+
const NEAT_USER_FRAME = Symbol.for('neat.user-frame')
|
|
7412
|
+
|
|
7413
|
+
function __neatPickUserFrame(stack${strOpt}) {
|
|
6814
7414
|
const lines = String(stack || '').split('\\n')
|
|
6815
7415
|
for (let i = 0; i < lines.length; i++) {
|
|
6816
7416
|
const raw = lines[i].trim()
|
|
@@ -6819,6 +7419,9 @@ function neatCallsiteProcessorSource(ts) {
|
|
|
6819
7419
|
if (raw.indexOf('@opentelemetry') !== -1) continue
|
|
6820
7420
|
if (raw.indexOf('node:') !== -1) continue
|
|
6821
7421
|
if (raw.indexOf('NeatCallSiteSpanProcessor') !== -1) continue
|
|
7422
|
+
// Skip NEAT's own inlined wraps/helpers (all carry the __neat prefix) so a
|
|
7423
|
+
// facade frame never masquerades as the user's call site.
|
|
7424
|
+
if (raw.indexOf('__neat') !== -1) continue
|
|
6822
7425
|
const bodyText = raw.slice(3)
|
|
6823
7426
|
const loc = bodyText.match(/:(\\d+):(\\d+)\\)?$/)
|
|
6824
7427
|
if (!loc) continue
|
|
@@ -6840,32 +7443,245 @@ function neatCallsiteProcessorSource(ts) {
|
|
|
6840
7443
|
return null
|
|
6841
7444
|
}
|
|
6842
7445
|
|
|
7446
|
+
// Layer 2/3 fallback source: the frame a handler-entry or facade wrap pushed
|
|
7447
|
+
// into context. Reads the span's own parent context first, then the active
|
|
7448
|
+
// context \u2014 the capture spike confirmed both return the value for undici.
|
|
7449
|
+
function __neatFrameFromContext(parentContext${anyT}) {
|
|
7450
|
+
try {
|
|
7451
|
+
const fromParent =
|
|
7452
|
+
parentContext && typeof parentContext.getValue === 'function'
|
|
7453
|
+
? parentContext.getValue(NEAT_USER_FRAME)
|
|
7454
|
+
: undefined
|
|
7455
|
+
return fromParent || context.active().getValue(NEAT_USER_FRAME) || null
|
|
7456
|
+
} catch (_e) {
|
|
7457
|
+
return null
|
|
7458
|
+
}
|
|
7459
|
+
}
|
|
7460
|
+
|
|
7461
|
+
function __neatSetCodeAttrs(span${spanT}, frame${anyT}) {
|
|
7462
|
+
span.setAttribute('code.filepath', frame.filepath)
|
|
7463
|
+
if (typeof frame.lineno === 'number') span.setAttribute('code.lineno', frame.lineno)
|
|
7464
|
+
if (frame.function) span.setAttribute('code.function', frame.function)
|
|
7465
|
+
}
|
|
7466
|
+
|
|
6843
7467
|
class NeatCallSiteSpanProcessor {
|
|
6844
|
-
onStart(span${spanT}) {
|
|
7468
|
+
onStart(span${spanT}, parentContext${anyT}) {
|
|
6845
7469
|
if (!span || (span.kind !== 2 && span.kind !== 3)) return
|
|
6846
|
-
|
|
7470
|
+
// Layer 1 \u2014 synchronous stack walk (sync-wrapper instrumentations).
|
|
7471
|
+
let frame = __neatPickUserFrame(new Error().stack)
|
|
7472
|
+
// Layer 2/3 \u2014 the handler-entry or off-stack-facade frame from context.
|
|
7473
|
+
if (!frame) frame = __neatFrameFromContext(parentContext)
|
|
6847
7474
|
if (!frame) return
|
|
6848
|
-
span
|
|
6849
|
-
if (typeof frame.lineno === 'number') span.setAttribute('code.lineno', frame.lineno)
|
|
6850
|
-
if (frame.function) span.setAttribute('code.function', frame.function)
|
|
7475
|
+
__neatSetCodeAttrs(span, frame)
|
|
6851
7476
|
}
|
|
6852
7477
|
onEnd() {}
|
|
6853
7478
|
forceFlush() { return Promise.resolve() }
|
|
6854
7479
|
shutdown() { return Promise.resolve() }
|
|
7480
|
+
}
|
|
7481
|
+
|
|
7482
|
+
// Capture the caller's synchronous frame at the wrap point and run \`fn\` with
|
|
7483
|
+
// that frame pushed into the active context (file-awareness.md \xA74 layer 3). An
|
|
7484
|
+
// off-stack instrumentation that creates its span inside \`fn\` inherits the
|
|
7485
|
+
// frame; the processor reads it via __neatFrameFromContext.
|
|
7486
|
+
function __neatRunWithUserFrame(fn${fnT}) {
|
|
7487
|
+
const frame = __neatPickUserFrame(new Error().stack)
|
|
7488
|
+
if (!frame) return fn()
|
|
7489
|
+
return context.with(context.active().setValue(NEAT_USER_FRAME, frame), fn)
|
|
7490
|
+
}
|
|
7491
|
+
|
|
7492
|
+
// Handler-entry attribution (file-awareness.md \xA74 layer 2). Stamp the framework
|
|
7493
|
+
// SERVER span with the handler frame captured at route registration, and push
|
|
7494
|
+
// the same frame into context so downstream CLIENT/PRODUCER spans inherit the
|
|
7495
|
+
// handler-file floor when their own stack carries none.
|
|
7496
|
+
function __neatStampHandler(frame${anyT}, run${fnT}) {
|
|
7497
|
+
try {
|
|
7498
|
+
const active = trace.getActiveSpan()
|
|
7499
|
+
if (active && active.kind === 1 && typeof active.setAttribute === 'function') {
|
|
7500
|
+
__neatSetCodeAttrs(active, frame)
|
|
7501
|
+
}
|
|
7502
|
+
} catch (_e) {}
|
|
7503
|
+
try {
|
|
7504
|
+
return context.with(context.active().setValue(NEAT_USER_FRAME, frame), run)
|
|
7505
|
+
} catch (_e) {
|
|
7506
|
+
return run()
|
|
7507
|
+
}
|
|
7508
|
+
}
|
|
7509
|
+
|
|
7510
|
+
// require-in-the-middle is a transitive dependency of the OTel instrumentation
|
|
7511
|
+
// packages NEAT installs, so it resolves in any instrumented CJS service.
|
|
7512
|
+
// Guarded: when it's absent (or the host runs as pure ESM, where require isn't
|
|
7513
|
+
// defined) the off-stack/handler wraps degrade to the stack walk + context
|
|
7514
|
+
// floor, never to a crash.
|
|
7515
|
+
function __neatHook(modules${arrAny}, onload${fnT}) {
|
|
7516
|
+
try {
|
|
7517
|
+
const RITM = require('require-in-the-middle')
|
|
7518
|
+
const Hook = RITM && RITM.Hook ? RITM.Hook : RITM
|
|
7519
|
+
new Hook(modules, { internals: false }, onload)
|
|
7520
|
+
return true
|
|
7521
|
+
} catch (_e) {
|
|
7522
|
+
return false
|
|
7523
|
+
}
|
|
7524
|
+
}
|
|
7525
|
+
|
|
7526
|
+
// Off-stack facade: Node's built-in fetch / undici. The instrumentation creates
|
|
7527
|
+
// the CLIENT span inside a diagnostics_channel handler detached from the
|
|
7528
|
+
// caller's stack, so wrap the global so the user frame is in context when the
|
|
7529
|
+
// span is created. The capture spike (2026-05-28) validated this on real
|
|
7530
|
+
// undici. .name is restored to 'fetch' for ecosystem compatibility; the inner
|
|
7531
|
+
// __neat name is what the frame skip keys on.
|
|
7532
|
+
function __neatWrapFetch() {
|
|
7533
|
+
try {
|
|
7534
|
+
const g${anyT} = globalThis
|
|
7535
|
+
if (typeof g.fetch === 'function' && !g.fetch.__neatWrapped) {
|
|
7536
|
+
const realFetch = g.fetch
|
|
7537
|
+
// The wrapper keeps its __neat-prefixed name so the frame skip in
|
|
7538
|
+
// __neatPickUserFrame never mistakes the wrapper's own frame for the
|
|
7539
|
+
// user's call site (renaming it to 'fetch' would defeat the skip).
|
|
7540
|
+
const __neatFetch${anyT} = function (input${anyT}, init${anyT}) {
|
|
7541
|
+
return __neatRunWithUserFrame(function () { return realFetch(input, init) })
|
|
7542
|
+
}
|
|
7543
|
+
__neatFetch.__neatWrapped = true
|
|
7544
|
+
g.fetch = __neatFetch
|
|
7545
|
+
}
|
|
7546
|
+
} catch (_e) {}
|
|
7547
|
+
}
|
|
7548
|
+
|
|
7549
|
+
// Off-stack facade: @prisma/client. Prisma's query engine backdates its spans
|
|
7550
|
+
// from Rust, off the caller's stack. Wrap the model methods on the client
|
|
7551
|
+
// prototype so the call-site frame (still synchronous at \`prisma.user.find\`)
|
|
7552
|
+
// is pushed into context for the engine dispatch.
|
|
7553
|
+
function __neatWrapPrisma() {
|
|
7554
|
+
__neatHook(['@prisma/client'], function (exports${anyT}) {
|
|
7555
|
+
try {
|
|
7556
|
+
const Client = exports && exports.PrismaClient
|
|
7557
|
+
if (typeof Client === 'function' && !Client.__neatWrapped) {
|
|
7558
|
+
const ops = ['findUnique','findUniqueOrThrow','findFirst','findFirstOrThrow','findMany','create','createMany','update','updateMany','upsert','delete','deleteMany','count','aggregate','groupBy']
|
|
7559
|
+
const __neatPrismaWrapModel = function (model${anyT}) {
|
|
7560
|
+
if (!model || model.__neatWrapped) return model
|
|
7561
|
+
for (let i = 0; i < ops.length; i++) {
|
|
7562
|
+
const op = ops[i]
|
|
7563
|
+
const orig = model[op]
|
|
7564
|
+
if (typeof orig !== 'function') continue
|
|
7565
|
+
model[op] = function __neatPrismaOp(${ts ? "...args: any[]" : "...args"}) {
|
|
7566
|
+
const self = this
|
|
7567
|
+
return __neatRunWithUserFrame(function () { return orig.apply(self, args) })
|
|
7568
|
+
}
|
|
7569
|
+
}
|
|
7570
|
+
model.__neatWrapped = true
|
|
7571
|
+
return model
|
|
7572
|
+
}
|
|
7573
|
+
const proto = Client.prototype
|
|
7574
|
+
const handler = function (model${anyT}) { return __neatPrismaWrapModel(model) }
|
|
7575
|
+
// Model accessors are lazily created getters on the instance; wrap the
|
|
7576
|
+
// \`$extends\`-free common path by trapping property access via a proxy
|
|
7577
|
+
// on each constructed client.
|
|
7578
|
+
exports.PrismaClient = new Proxy(Client, {
|
|
7579
|
+
construct(Target${anyT}, argList${anyT}, NewTarget${anyT}) {
|
|
7580
|
+
const instance = Reflect.construct(Target, argList, NewTarget)
|
|
7581
|
+
return new Proxy(instance, {
|
|
7582
|
+
get(target${anyT}, prop${anyT}, receiver${anyT}) {
|
|
7583
|
+
const value = Reflect.get(target, prop, receiver)
|
|
7584
|
+
if (value && typeof value === 'object' && typeof prop === 'string' && prop[0] !== '$' && prop[0] !== '_') {
|
|
7585
|
+
return handler(value)
|
|
7586
|
+
}
|
|
7587
|
+
return value
|
|
7588
|
+
},
|
|
7589
|
+
})
|
|
7590
|
+
},
|
|
7591
|
+
})
|
|
7592
|
+
exports.PrismaClient.__neatWrapped = true
|
|
7593
|
+
void proto
|
|
7594
|
+
}
|
|
7595
|
+
} catch (_e) {}
|
|
7596
|
+
return exports
|
|
7597
|
+
})
|
|
7598
|
+
}
|
|
7599
|
+
|
|
7600
|
+
// Handler-entry facades. express / connect share the Layer model (each route
|
|
7601
|
+
// handler is a function registered on a Router); the wrap captures the
|
|
7602
|
+
// registration frame and stamps + propagates it when the handler runs. The
|
|
7603
|
+
// registry is the extensibility seam \u2014 koa, fastify (via @fastify/otel),
|
|
7604
|
+
// nestjs, restify, and hapi add an entry as their patch surface is wired.
|
|
7605
|
+
function __neatWrapConnectStyle(mod${anyT}) {
|
|
7606
|
+
try {
|
|
7607
|
+
// express() returns an app whose route verbs live on Router.prototype and
|
|
7608
|
+
// the application proto; connect apps expose \`use\`. Wrap the registration
|
|
7609
|
+
// verbs so the user handler is wound with its registration frame.
|
|
7610
|
+
const verbs = ['use','get','post','put','delete','patch','all','options','head']
|
|
7611
|
+
const wrapTarget = function (target${anyT}) {
|
|
7612
|
+
if (!target || target.__neatVerbsWrapped) return
|
|
7613
|
+
for (let i = 0; i < verbs.length; i++) {
|
|
7614
|
+
const verb = verbs[i]
|
|
7615
|
+
const orig = target[verb]
|
|
7616
|
+
if (typeof orig !== 'function') continue
|
|
7617
|
+
target[verb] = function __neatVerb(${ts ? "...args: any[]" : "...args"}) {
|
|
7618
|
+
const frame = __neatPickUserFrame(new Error().stack)
|
|
7619
|
+
if (frame) {
|
|
7620
|
+
for (let a = 0; a < args.length; a++) {
|
|
7621
|
+
const h = args[a]
|
|
7622
|
+
if (typeof h === 'function' && !h.__neatHandlerWrapped && h.length <= 4) {
|
|
7623
|
+
const inner = h
|
|
7624
|
+
const __neatHandler${anyT} = function (${ts ? "...hargs: any[]" : "...hargs"}) {
|
|
7625
|
+
const self = this
|
|
7626
|
+
return __neatStampHandler(frame, function () { return inner.apply(self, hargs) })
|
|
7627
|
+
}
|
|
7628
|
+
__neatHandler.__neatHandlerWrapped = true
|
|
7629
|
+
args[a] = __neatHandler
|
|
7630
|
+
}
|
|
7631
|
+
}
|
|
7632
|
+
}
|
|
7633
|
+
return orig.apply(this, args)
|
|
7634
|
+
}
|
|
7635
|
+
}
|
|
7636
|
+
target.__neatVerbsWrapped = true
|
|
7637
|
+
}
|
|
7638
|
+
if (mod && mod.Router && mod.Router.prototype) wrapTarget(mod.Router.prototype)
|
|
7639
|
+
if (mod && mod.application) wrapTarget(mod.application)
|
|
7640
|
+
if (mod && mod.prototype) wrapTarget(mod.prototype)
|
|
7641
|
+
} catch (_e) {}
|
|
7642
|
+
}
|
|
7643
|
+
|
|
7644
|
+
function __neatInstallHandlerEntry() {
|
|
7645
|
+
__neatHook(['express'], function (exports${anyT}) { __neatWrapConnectStyle(exports); return exports })
|
|
7646
|
+
__neatHook(['connect'], function (exports${anyT}) { __neatWrapConnectStyle(exports); return exports })
|
|
7647
|
+
}
|
|
7648
|
+
|
|
7649
|
+
// Install every off-stack and handler-entry wrap. Called once after sdk.start()
|
|
7650
|
+
// \u2014 before user code requires the framework / Prisma modules, so the hooks land
|
|
7651
|
+
// on first require. Each wrap is independently guarded.
|
|
7652
|
+
function __neatInstallFacades() {
|
|
7653
|
+
__neatWrapFetch()
|
|
7654
|
+
__neatWrapPrisma()
|
|
7655
|
+
__neatInstallHandlerEntry()
|
|
6855
7656
|
}`;
|
|
6856
7657
|
}
|
|
6857
|
-
var CALLSITE_PROCESSOR_JS =
|
|
6858
|
-
var CALLSITE_PROCESSOR_TS =
|
|
6859
|
-
function
|
|
7658
|
+
var CALLSITE_PROCESSOR_JS = neatCaptureSource(false);
|
|
7659
|
+
var CALLSITE_PROCESSOR_TS = neatCaptureSource(true);
|
|
7660
|
+
function neatWireCaptureSource(ts) {
|
|
6860
7661
|
const providerT = ts ? ": any" : "";
|
|
6861
7662
|
return `try {
|
|
6862
7663
|
const provider${providerT} = trace.getTracerProvider()
|
|
6863
7664
|
const delegate = provider && typeof provider.getDelegate === 'function' ? provider.getDelegate() : provider
|
|
6864
|
-
if (delegate
|
|
6865
|
-
|
|
6866
|
-
}
|
|
6867
|
-
|
|
6868
|
-
|
|
7665
|
+
if (!delegate || typeof delegate.addSpanProcessor !== 'function') {
|
|
7666
|
+
throw new Error('[neat] could not resolve a TracerProvider to attach the call-site processor; file-first OBSERVED capture would be silent (file-awareness.md \xA74)')
|
|
7667
|
+
}
|
|
7668
|
+
const __neatProcessor = new NeatCallSiteSpanProcessor()
|
|
7669
|
+
delegate.addSpanProcessor(__neatProcessor)
|
|
7670
|
+
// Post-init assertion: confirm attachment on providers that expose their
|
|
7671
|
+
// processor list. An un-introspectable provider is trusted (we just called
|
|
7672
|
+
// its addSpanProcessor); a list that exists and lacks our processor throws.
|
|
7673
|
+
const registered = delegate._registeredSpanProcessors
|
|
7674
|
+
if (Array.isArray(registered) && registered.indexOf(__neatProcessor) === -1) {
|
|
7675
|
+
throw new Error('[neat] call-site processor did not attach to the active TracerProvider (file-awareness.md \xA74)')
|
|
7676
|
+
}
|
|
7677
|
+
} catch (err) {
|
|
7678
|
+
throw err
|
|
7679
|
+
}
|
|
7680
|
+
try {
|
|
7681
|
+
__neatInstallFacades()
|
|
7682
|
+
} catch (_e) {
|
|
7683
|
+
// Facade install is best-effort: the stack-walk + handler-entry floor still
|
|
7684
|
+
// attribute the sync-wrapper majority even if an off-stack wrap fails.
|
|
6869
7685
|
}`;
|
|
6870
7686
|
}
|
|
6871
7687
|
var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
|
|
@@ -6876,7 +7692,7 @@ ${OTEL_OTLP_HEADERS_JS}
|
|
|
6876
7692
|
|
|
6877
7693
|
const { NodeSDK } = require('@opentelemetry/sdk-node')
|
|
6878
7694
|
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
|
|
6879
|
-
const { trace } = require('@opentelemetry/api')
|
|
7695
|
+
const { trace, context } = require('@opentelemetry/api')
|
|
6880
7696
|
|
|
6881
7697
|
${CALLSITE_PROCESSOR_JS}
|
|
6882
7698
|
|
|
@@ -6884,7 +7700,7 @@ const instrumentations = [getNodeAutoInstrumentations()]
|
|
|
6884
7700
|
__INSTRUMENTATION_BLOCK__
|
|
6885
7701
|
const sdk = new NodeSDK({ instrumentations })
|
|
6886
7702
|
sdk.start()
|
|
6887
|
-
${
|
|
7703
|
+
${neatWireCaptureSource(false)}
|
|
6888
7704
|
`;
|
|
6889
7705
|
var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
|
|
6890
7706
|
${OTEL_INIT_STAMP}
|
|
@@ -6894,7 +7710,7 @@ ${OTEL_OTLP_HEADERS_JS}
|
|
|
6894
7710
|
|
|
6895
7711
|
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6896
7712
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
6897
|
-
import { trace } from '@opentelemetry/api'
|
|
7713
|
+
import { trace, context } from '@opentelemetry/api'
|
|
6898
7714
|
|
|
6899
7715
|
${CALLSITE_PROCESSOR_JS}
|
|
6900
7716
|
|
|
@@ -6902,17 +7718,22 @@ const instrumentations = [getNodeAutoInstrumentations()]
|
|
|
6902
7718
|
__INSTRUMENTATION_BLOCK__
|
|
6903
7719
|
const sdk = new NodeSDK({ instrumentations })
|
|
6904
7720
|
sdk.start()
|
|
6905
|
-
${
|
|
7721
|
+
${neatWireCaptureSource(false)}
|
|
6906
7722
|
`;
|
|
6907
7723
|
var OTEL_INIT_TS = `${OTEL_INIT_HEADER}
|
|
6908
7724
|
${OTEL_INIT_STAMP}
|
|
7725
|
+
// @ts-nocheck \u2014 generated runtime shim. The layered capture mechanism uses
|
|
7726
|
+
// dynamic patterns (facade wraps, Proxy traps, a createRequire bridge) that a
|
|
7727
|
+
// strict user tsconfig would reject; suppressing type-checking here keeps the
|
|
7728
|
+
// generated file from breaking the host project's \`tsc\` gate (#427) without
|
|
7729
|
+
// constraining the runtime logic. The file is regenerated, never hand-edited.
|
|
6909
7730
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
6910
7731
|
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
6911
7732
|
${OTEL_OTLP_HEADERS_JS}
|
|
6912
7733
|
|
|
6913
7734
|
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
6914
7735
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
6915
|
-
import { trace } from '@opentelemetry/api'
|
|
7736
|
+
import { trace, context } from '@opentelemetry/api'
|
|
6916
7737
|
|
|
6917
7738
|
${CALLSITE_PROCESSOR_TS}
|
|
6918
7739
|
|
|
@@ -6920,7 +7741,7 @@ const instrumentations = [getNodeAutoInstrumentations()]
|
|
|
6920
7741
|
__INSTRUMENTATION_BLOCK__
|
|
6921
7742
|
const sdk = new NodeSDK({ instrumentations })
|
|
6922
7743
|
sdk.start()
|
|
6923
|
-
${
|
|
7744
|
+
${neatWireCaptureSource(true)}
|
|
6924
7745
|
`;
|
|
6925
7746
|
function renderNodeOtelInit(template, serviceName, projectName, registrations = []) {
|
|
6926
7747
|
const block = registrations.length === 0 ? "" : `
|
|
@@ -7108,15 +7929,15 @@ var OTEL_ENV = {
|
|
|
7108
7929
|
value: "http://localhost:4318/projects/<project>/v1/traces"
|
|
7109
7930
|
};
|
|
7110
7931
|
function serviceNodeName(pkg, serviceDir) {
|
|
7111
|
-
return pkg.name ??
|
|
7932
|
+
return pkg.name ?? import_node_path42.default.basename(serviceDir);
|
|
7112
7933
|
}
|
|
7113
7934
|
function projectToken(pkg, serviceDir, project) {
|
|
7114
7935
|
if (project && project.length > 0) return project;
|
|
7115
|
-
return pkg.name ??
|
|
7936
|
+
return pkg.name ?? import_node_path42.default.basename(serviceDir);
|
|
7116
7937
|
}
|
|
7117
7938
|
async function readJsonFile(p) {
|
|
7118
7939
|
try {
|
|
7119
|
-
const raw = await
|
|
7940
|
+
const raw = await import_node_fs27.promises.readFile(p, "utf8");
|
|
7120
7941
|
return JSON.parse(raw);
|
|
7121
7942
|
} catch {
|
|
7122
7943
|
return null;
|
|
@@ -7125,26 +7946,33 @@ async function readJsonFile(p) {
|
|
|
7125
7946
|
async function detectRuntimeKind(pkgRoot, pkg) {
|
|
7126
7947
|
const deps = allDeps(pkg);
|
|
7127
7948
|
if ("react-native" in deps || "expo" in deps) return "react-native";
|
|
7128
|
-
const appJson = await readJsonFile(
|
|
7949
|
+
const appJson = await readJsonFile(import_node_path42.default.join(pkgRoot, "app.json"));
|
|
7129
7950
|
if (appJson && typeof appJson === "object" && "expo" in appJson) {
|
|
7130
7951
|
return "react-native";
|
|
7131
7952
|
}
|
|
7132
|
-
if (await
|
|
7953
|
+
if (await exists3(import_node_path42.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path42.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path42.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
|
|
7133
7954
|
return "browser-bundle";
|
|
7134
7955
|
}
|
|
7956
|
+
if (await exists3(import_node_path42.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
|
|
7957
|
+
if (await exists3(import_node_path42.default.join(pkgRoot, "bun.lockb"))) return "bun";
|
|
7958
|
+
if (await exists3(import_node_path42.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path42.default.join(pkgRoot, "deno.lock"))) {
|
|
7959
|
+
return "deno";
|
|
7960
|
+
}
|
|
7961
|
+
const engines = pkg.engines ?? {};
|
|
7962
|
+
if ("electron" in engines) return "electron";
|
|
7135
7963
|
return "node";
|
|
7136
7964
|
}
|
|
7137
|
-
async function
|
|
7965
|
+
async function readPackageJson2(serviceDir) {
|
|
7138
7966
|
try {
|
|
7139
|
-
const raw = await
|
|
7967
|
+
const raw = await import_node_fs27.promises.readFile(import_node_path42.default.join(serviceDir, "package.json"), "utf8");
|
|
7140
7968
|
return JSON.parse(raw);
|
|
7141
7969
|
} catch {
|
|
7142
7970
|
return null;
|
|
7143
7971
|
}
|
|
7144
7972
|
}
|
|
7145
|
-
async function
|
|
7973
|
+
async function exists3(p) {
|
|
7146
7974
|
try {
|
|
7147
|
-
await
|
|
7975
|
+
await import_node_fs27.promises.stat(p);
|
|
7148
7976
|
return true;
|
|
7149
7977
|
} catch {
|
|
7150
7978
|
return false;
|
|
@@ -7152,7 +7980,7 @@ async function exists2(p) {
|
|
|
7152
7980
|
}
|
|
7153
7981
|
async function readFileMaybe(p) {
|
|
7154
7982
|
try {
|
|
7155
|
-
return await
|
|
7983
|
+
return await import_node_fs27.promises.readFile(p, "utf8");
|
|
7156
7984
|
} catch {
|
|
7157
7985
|
return null;
|
|
7158
7986
|
}
|
|
@@ -7168,14 +7996,20 @@ async function planOtelInitGeneration(file, contents) {
|
|
|
7168
7996
|
return null;
|
|
7169
7997
|
}
|
|
7170
7998
|
async function detect(serviceDir) {
|
|
7171
|
-
const pkg = await
|
|
7999
|
+
const pkg = await readPackageJson2(serviceDir);
|
|
7172
8000
|
return pkg !== null && typeof pkg.name === "string";
|
|
7173
8001
|
}
|
|
8002
|
+
function needsVersionUpgrade(installed, expected) {
|
|
8003
|
+
return !import_semver2.default.satisfies(
|
|
8004
|
+
import_semver2.default.minVersion(installed)?.version ?? installed,
|
|
8005
|
+
expected
|
|
8006
|
+
) && !import_semver2.default.intersects(installed, expected);
|
|
8007
|
+
}
|
|
7174
8008
|
var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
|
|
7175
8009
|
async function findNextConfig(serviceDir) {
|
|
7176
8010
|
for (const name of NEXT_CONFIG_CANDIDATES) {
|
|
7177
|
-
const candidate =
|
|
7178
|
-
if (await
|
|
8011
|
+
const candidate = import_node_path42.default.join(serviceDir, name);
|
|
8012
|
+
if (await exists3(candidate)) return candidate;
|
|
7179
8013
|
}
|
|
7180
8014
|
return null;
|
|
7181
8015
|
}
|
|
@@ -7201,8 +8035,8 @@ function hasRemixDependency(pkg) {
|
|
|
7201
8035
|
}
|
|
7202
8036
|
async function findRemixEntry(serviceDir) {
|
|
7203
8037
|
for (const rel of REMIX_ENTRY_CANDIDATES) {
|
|
7204
|
-
const candidate =
|
|
7205
|
-
if (await
|
|
8038
|
+
const candidate = import_node_path42.default.join(serviceDir, rel);
|
|
8039
|
+
if (await exists3(candidate)) return candidate;
|
|
7206
8040
|
}
|
|
7207
8041
|
return null;
|
|
7208
8042
|
}
|
|
@@ -7213,15 +8047,15 @@ function hasSvelteKitDependency(pkg) {
|
|
|
7213
8047
|
}
|
|
7214
8048
|
async function findSvelteKitHooks(serviceDir) {
|
|
7215
8049
|
for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
|
|
7216
|
-
const candidate =
|
|
7217
|
-
if (await
|
|
8050
|
+
const candidate = import_node_path42.default.join(serviceDir, rel);
|
|
8051
|
+
if (await exists3(candidate)) return candidate;
|
|
7218
8052
|
}
|
|
7219
8053
|
return null;
|
|
7220
8054
|
}
|
|
7221
8055
|
async function findSvelteKitConfig(serviceDir) {
|
|
7222
8056
|
for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
|
|
7223
|
-
const candidate =
|
|
7224
|
-
if (await
|
|
8057
|
+
const candidate = import_node_path42.default.join(serviceDir, rel);
|
|
8058
|
+
if (await exists3(candidate)) return candidate;
|
|
7225
8059
|
}
|
|
7226
8060
|
return null;
|
|
7227
8061
|
}
|
|
@@ -7231,8 +8065,8 @@ function hasNuxtDependency(pkg) {
|
|
|
7231
8065
|
}
|
|
7232
8066
|
async function findNuxtConfig(serviceDir) {
|
|
7233
8067
|
for (const name of NUXT_CONFIG_CANDIDATES) {
|
|
7234
|
-
const candidate =
|
|
7235
|
-
if (await
|
|
8068
|
+
const candidate = import_node_path42.default.join(serviceDir, name);
|
|
8069
|
+
if (await exists3(candidate)) return candidate;
|
|
7236
8070
|
}
|
|
7237
8071
|
return null;
|
|
7238
8072
|
}
|
|
@@ -7242,8 +8076,8 @@ function hasAstroDependency(pkg) {
|
|
|
7242
8076
|
}
|
|
7243
8077
|
async function findAstroConfig(serviceDir) {
|
|
7244
8078
|
for (const name of ASTRO_CONFIG_CANDIDATES) {
|
|
7245
|
-
const candidate =
|
|
7246
|
-
if (await
|
|
8079
|
+
const candidate = import_node_path42.default.join(serviceDir, name);
|
|
8080
|
+
if (await exists3(candidate)) return candidate;
|
|
7247
8081
|
}
|
|
7248
8082
|
return null;
|
|
7249
8083
|
}
|
|
@@ -7256,7 +8090,7 @@ function parseNextMajor(range) {
|
|
|
7256
8090
|
return Number.isFinite(n) ? n : null;
|
|
7257
8091
|
}
|
|
7258
8092
|
async function isTypeScriptProject(serviceDir) {
|
|
7259
|
-
return
|
|
8093
|
+
return exists3(import_node_path42.default.join(serviceDir, "tsconfig.json"));
|
|
7260
8094
|
}
|
|
7261
8095
|
var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
|
|
7262
8096
|
var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
|
|
@@ -7302,8 +8136,8 @@ function entryFromScript(script) {
|
|
|
7302
8136
|
}
|
|
7303
8137
|
async function resolveEntry(serviceDir, pkg) {
|
|
7304
8138
|
if (typeof pkg.main === "string" && pkg.main.length > 0) {
|
|
7305
|
-
const candidate =
|
|
7306
|
-
if (await
|
|
8139
|
+
const candidate = import_node_path42.default.resolve(serviceDir, pkg.main);
|
|
8140
|
+
if (await exists3(candidate)) return candidate;
|
|
7307
8141
|
}
|
|
7308
8142
|
if (pkg.bin) {
|
|
7309
8143
|
let binEntry;
|
|
@@ -7316,36 +8150,36 @@ async function resolveEntry(serviceDir, pkg) {
|
|
|
7316
8150
|
if (typeof first === "string") binEntry = first;
|
|
7317
8151
|
}
|
|
7318
8152
|
if (binEntry) {
|
|
7319
|
-
const candidate =
|
|
7320
|
-
if (await
|
|
8153
|
+
const candidate = import_node_path42.default.resolve(serviceDir, binEntry);
|
|
8154
|
+
if (await exists3(candidate)) return candidate;
|
|
7321
8155
|
}
|
|
7322
8156
|
}
|
|
7323
8157
|
const startEntry = entryFromScript(pkg.scripts?.start);
|
|
7324
8158
|
if (startEntry) {
|
|
7325
|
-
const candidate =
|
|
7326
|
-
if (await
|
|
8159
|
+
const candidate = import_node_path42.default.resolve(serviceDir, startEntry);
|
|
8160
|
+
if (await exists3(candidate)) return candidate;
|
|
7327
8161
|
}
|
|
7328
8162
|
const devEntry = entryFromScript(pkg.scripts?.dev);
|
|
7329
8163
|
if (devEntry) {
|
|
7330
|
-
const candidate =
|
|
7331
|
-
if (await
|
|
8164
|
+
const candidate = import_node_path42.default.resolve(serviceDir, devEntry);
|
|
8165
|
+
if (await exists3(candidate)) return candidate;
|
|
7332
8166
|
}
|
|
7333
8167
|
for (const rel of SRC_INDEX_CANDIDATES) {
|
|
7334
|
-
const candidate =
|
|
7335
|
-
if (await
|
|
8168
|
+
const candidate = import_node_path42.default.join(serviceDir, rel);
|
|
8169
|
+
if (await exists3(candidate)) return candidate;
|
|
7336
8170
|
}
|
|
7337
8171
|
for (const rel of SRC_NAMED_CANDIDATES) {
|
|
7338
|
-
const candidate =
|
|
7339
|
-
if (await
|
|
8172
|
+
const candidate = import_node_path42.default.join(serviceDir, rel);
|
|
8173
|
+
if (await exists3(candidate)) return candidate;
|
|
7340
8174
|
}
|
|
7341
8175
|
for (const name of INDEX_CANDIDATES) {
|
|
7342
|
-
const candidate =
|
|
7343
|
-
if (await
|
|
8176
|
+
const candidate = import_node_path42.default.join(serviceDir, name);
|
|
8177
|
+
if (await exists3(candidate)) return candidate;
|
|
7344
8178
|
}
|
|
7345
8179
|
return null;
|
|
7346
8180
|
}
|
|
7347
8181
|
function dispatchEntry(entryFile, pkg) {
|
|
7348
|
-
const ext =
|
|
8182
|
+
const ext = import_node_path42.default.extname(entryFile).toLowerCase();
|
|
7349
8183
|
if (ext === ".ts" || ext === ".tsx") return "ts";
|
|
7350
8184
|
if (ext === ".mjs") return "esm";
|
|
7351
8185
|
if (ext === ".cjs") return "cjs";
|
|
@@ -7362,9 +8196,9 @@ function otelInitContents(flavor) {
|
|
|
7362
8196
|
return OTEL_INIT_CJS;
|
|
7363
8197
|
}
|
|
7364
8198
|
function injectionLine(flavor, entryFile, otelInitFile) {
|
|
7365
|
-
let rel =
|
|
8199
|
+
let rel = import_node_path42.default.relative(import_node_path42.default.dirname(entryFile), otelInitFile);
|
|
7366
8200
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
7367
|
-
rel = rel.split(
|
|
8201
|
+
rel = rel.split(import_node_path42.default.sep).join("/");
|
|
7368
8202
|
if (flavor === "cjs") return `require('${rel}')`;
|
|
7369
8203
|
if (flavor === "esm") return `import '${rel}'`;
|
|
7370
8204
|
const tsRel = rel.replace(/\.ts$/, "");
|
|
@@ -7377,56 +8211,56 @@ function lineIsOtelInjection(line) {
|
|
|
7377
8211
|
}
|
|
7378
8212
|
async function detectsSrcLayout(serviceDir) {
|
|
7379
8213
|
const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
|
|
7380
|
-
|
|
7381
|
-
|
|
7382
|
-
|
|
7383
|
-
|
|
8214
|
+
exists3(import_node_path42.default.join(serviceDir, "src", "app")),
|
|
8215
|
+
exists3(import_node_path42.default.join(serviceDir, "src", "pages")),
|
|
8216
|
+
exists3(import_node_path42.default.join(serviceDir, "app")),
|
|
8217
|
+
exists3(import_node_path42.default.join(serviceDir, "pages"))
|
|
7384
8218
|
]);
|
|
7385
8219
|
return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
|
|
7386
8220
|
}
|
|
7387
8221
|
async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
|
|
7388
8222
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
7389
8223
|
const srcLayout = await detectsSrcLayout(serviceDir);
|
|
7390
|
-
const baseDir = srcLayout ?
|
|
7391
|
-
const instrumentationFile =
|
|
7392
|
-
const instrumentationNodeFile =
|
|
8224
|
+
const baseDir = srcLayout ? import_node_path42.default.join(serviceDir, "src") : serviceDir;
|
|
8225
|
+
const instrumentationFile = import_node_path42.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
|
|
8226
|
+
const instrumentationNodeFile = import_node_path42.default.join(
|
|
7393
8227
|
baseDir,
|
|
7394
8228
|
useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
|
|
7395
8229
|
);
|
|
7396
|
-
const envNeatFile =
|
|
8230
|
+
const envNeatFile = import_node_path42.default.join(baseDir, ".env.neat");
|
|
7397
8231
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
7398
8232
|
const dependencyEdits = [];
|
|
7399
8233
|
for (const sdk of SDK_PACKAGES) {
|
|
7400
|
-
if (sdk.name in existingDeps)
|
|
7401
|
-
|
|
7402
|
-
|
|
7403
|
-
|
|
7404
|
-
|
|
7405
|
-
|
|
7406
|
-
});
|
|
8234
|
+
if (sdk.name in existingDeps) {
|
|
8235
|
+
if (needsVersionUpgrade(existingDeps[sdk.name], sdk.version)) {
|
|
8236
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: sdk.name, version: sdk.version, fromVersion: existingDeps[sdk.name] });
|
|
8237
|
+
}
|
|
8238
|
+
continue;
|
|
8239
|
+
}
|
|
8240
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
|
|
7407
8241
|
}
|
|
7408
8242
|
const nonBundled = detectNonBundledInstrumentations(pkg);
|
|
7409
8243
|
for (const inst of nonBundled) {
|
|
7410
|
-
if (inst.pkg in existingDeps)
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
});
|
|
8244
|
+
if (inst.pkg in existingDeps) {
|
|
8245
|
+
if (needsVersionUpgrade(existingDeps[inst.pkg], inst.version)) {
|
|
8246
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: inst.pkg, version: inst.version, fromVersion: existingDeps[inst.pkg] });
|
|
8247
|
+
}
|
|
8248
|
+
continue;
|
|
8249
|
+
}
|
|
8250
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: inst.pkg, version: inst.version });
|
|
7417
8251
|
}
|
|
7418
8252
|
const svcName = serviceNodeName(pkg, serviceDir);
|
|
7419
8253
|
const projectName = projectToken(pkg, serviceDir, project);
|
|
7420
8254
|
const registrations = nonBundled.map((i) => i.registration);
|
|
7421
8255
|
const generatedFiles = [];
|
|
7422
|
-
if (!await
|
|
8256
|
+
if (!await exists3(instrumentationFile)) {
|
|
7423
8257
|
generatedFiles.push({
|
|
7424
8258
|
file: instrumentationFile,
|
|
7425
8259
|
contents: useTs ? NEXT_INSTRUMENTATION_TS : NEXT_INSTRUMENTATION_JS,
|
|
7426
8260
|
skipIfExists: true
|
|
7427
8261
|
});
|
|
7428
8262
|
}
|
|
7429
|
-
if (!await
|
|
8263
|
+
if (!await exists3(instrumentationNodeFile)) {
|
|
7430
8264
|
generatedFiles.push({
|
|
7431
8265
|
file: instrumentationNodeFile,
|
|
7432
8266
|
contents: renderNextInstrumentationNode(
|
|
@@ -7438,7 +8272,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
7438
8272
|
skipIfExists: true
|
|
7439
8273
|
});
|
|
7440
8274
|
}
|
|
7441
|
-
if (!await
|
|
8275
|
+
if (!await exists3(envNeatFile)) {
|
|
7442
8276
|
generatedFiles.push({
|
|
7443
8277
|
file: envNeatFile,
|
|
7444
8278
|
contents: renderEnvNeat(svcName, projectName),
|
|
@@ -7450,7 +8284,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
7450
8284
|
const nextMajor = parseNextMajor(nextRange);
|
|
7451
8285
|
if (nextMajor !== null && nextMajor < 15) {
|
|
7452
8286
|
try {
|
|
7453
|
-
const raw = await
|
|
8287
|
+
const raw = await import_node_fs27.promises.readFile(nextConfigPath, "utf8");
|
|
7454
8288
|
if (!raw.includes("instrumentationHook")) {
|
|
7455
8289
|
nextConfigEdit = {
|
|
7456
8290
|
file: nextConfigPath,
|
|
@@ -7498,8 +8332,8 @@ function buildDependencyEdits(pkg, manifestPath) {
|
|
|
7498
8332
|
return edits;
|
|
7499
8333
|
}
|
|
7500
8334
|
async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
|
|
7501
|
-
const envNeatFile =
|
|
7502
|
-
if (!await
|
|
8335
|
+
const envNeatFile = import_node_path42.default.join(serviceDir, ".env.neat");
|
|
8336
|
+
if (!await exists3(envNeatFile)) {
|
|
7503
8337
|
generatedFiles.push({
|
|
7504
8338
|
file: envNeatFile,
|
|
7505
8339
|
contents: renderEnvNeat(
|
|
@@ -7533,13 +8367,13 @@ function fileImportsOtelHook(raw, specifiers) {
|
|
|
7533
8367
|
}
|
|
7534
8368
|
async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
7535
8369
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
7536
|
-
const otelServerFile =
|
|
8370
|
+
const otelServerFile = import_node_path42.default.join(
|
|
7537
8371
|
serviceDir,
|
|
7538
8372
|
useTs ? "app/otel.server.ts" : "app/otel.server.js"
|
|
7539
8373
|
);
|
|
7540
8374
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
7541
8375
|
const generatedFiles = [];
|
|
7542
|
-
if (!await
|
|
8376
|
+
if (!await exists3(otelServerFile)) {
|
|
7543
8377
|
generatedFiles.push({
|
|
7544
8378
|
file: otelServerFile,
|
|
7545
8379
|
contents: renderFrameworkOtelInitForPkg(
|
|
@@ -7554,7 +8388,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
7554
8388
|
await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
|
|
7555
8389
|
const entrypointEdits = [];
|
|
7556
8390
|
try {
|
|
7557
|
-
const raw = await
|
|
8391
|
+
const raw = await import_node_fs27.promises.readFile(entryFile, "utf8");
|
|
7558
8392
|
if (!fileImportsOtelHook(raw, ["./otel.server"])) {
|
|
7559
8393
|
const lines = raw.split(/\r?\n/);
|
|
7560
8394
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -7590,15 +8424,15 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
7590
8424
|
}
|
|
7591
8425
|
async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
|
|
7592
8426
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
7593
|
-
const otelInitFile =
|
|
8427
|
+
const otelInitFile = import_node_path42.default.join(
|
|
7594
8428
|
serviceDir,
|
|
7595
8429
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
7596
8430
|
);
|
|
7597
|
-
const resolvedHooksFile = hooksFile ??
|
|
8431
|
+
const resolvedHooksFile = hooksFile ?? import_node_path42.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
|
|
7598
8432
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
7599
8433
|
const generatedFiles = [];
|
|
7600
8434
|
const entrypointEdits = [];
|
|
7601
|
-
if (!await
|
|
8435
|
+
if (!await exists3(otelInitFile)) {
|
|
7602
8436
|
generatedFiles.push({
|
|
7603
8437
|
file: otelInitFile,
|
|
7604
8438
|
contents: renderFrameworkOtelInitForPkg(
|
|
@@ -7619,7 +8453,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
7619
8453
|
});
|
|
7620
8454
|
} else {
|
|
7621
8455
|
try {
|
|
7622
|
-
const raw = await
|
|
8456
|
+
const raw = await import_node_fs27.promises.readFile(hooksFile, "utf8");
|
|
7623
8457
|
if (!fileImportsOtelHook(raw, ["./otel-init"])) {
|
|
7624
8458
|
const lines = raw.split(/\r?\n/);
|
|
7625
8459
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -7656,17 +8490,17 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
7656
8490
|
}
|
|
7657
8491
|
async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
7658
8492
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
7659
|
-
const otelPluginFile =
|
|
8493
|
+
const otelPluginFile = import_node_path42.default.join(
|
|
7660
8494
|
serviceDir,
|
|
7661
8495
|
useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
|
|
7662
8496
|
);
|
|
7663
|
-
const otelInitFile =
|
|
8497
|
+
const otelInitFile = import_node_path42.default.join(
|
|
7664
8498
|
serviceDir,
|
|
7665
8499
|
useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
|
|
7666
8500
|
);
|
|
7667
8501
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
7668
8502
|
const generatedFiles = [];
|
|
7669
|
-
if (!await
|
|
8503
|
+
if (!await exists3(otelInitFile)) {
|
|
7670
8504
|
generatedFiles.push({
|
|
7671
8505
|
file: otelInitFile,
|
|
7672
8506
|
contents: renderFrameworkOtelInitForPkg(
|
|
@@ -7678,7 +8512,7 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
|
7678
8512
|
skipIfExists: true
|
|
7679
8513
|
});
|
|
7680
8514
|
}
|
|
7681
|
-
if (!await
|
|
8515
|
+
if (!await exists3(otelPluginFile)) {
|
|
7682
8516
|
generatedFiles.push({
|
|
7683
8517
|
file: otelPluginFile,
|
|
7684
8518
|
contents: useTs ? NUXT_OTEL_PLUGIN_TS : NUXT_OTEL_PLUGIN_JS,
|
|
@@ -7711,23 +8545,23 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
|
7711
8545
|
var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
|
|
7712
8546
|
async function findAstroMiddleware(serviceDir) {
|
|
7713
8547
|
for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
|
|
7714
|
-
const candidate =
|
|
7715
|
-
if (await
|
|
8548
|
+
const candidate = import_node_path42.default.join(serviceDir, rel);
|
|
8549
|
+
if (await exists3(candidate)) return candidate;
|
|
7716
8550
|
}
|
|
7717
8551
|
return null;
|
|
7718
8552
|
}
|
|
7719
8553
|
async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
7720
8554
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
7721
|
-
const otelInitFile =
|
|
8555
|
+
const otelInitFile = import_node_path42.default.join(
|
|
7722
8556
|
serviceDir,
|
|
7723
8557
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
7724
8558
|
);
|
|
7725
8559
|
const existingMiddleware = await findAstroMiddleware(serviceDir);
|
|
7726
|
-
const middlewareFile = existingMiddleware ??
|
|
8560
|
+
const middlewareFile = existingMiddleware ?? import_node_path42.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
|
|
7727
8561
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
7728
8562
|
const generatedFiles = [];
|
|
7729
8563
|
const entrypointEdits = [];
|
|
7730
|
-
if (!await
|
|
8564
|
+
if (!await exists3(otelInitFile)) {
|
|
7731
8565
|
generatedFiles.push({
|
|
7732
8566
|
file: otelInitFile,
|
|
7733
8567
|
contents: renderFrameworkOtelInitForPkg(
|
|
@@ -7748,7 +8582,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
|
7748
8582
|
});
|
|
7749
8583
|
} else {
|
|
7750
8584
|
try {
|
|
7751
|
-
const raw = await
|
|
8585
|
+
const raw = await import_node_fs27.promises.readFile(existingMiddleware, "utf8");
|
|
7752
8586
|
if (!fileImportsOtelHook(raw, ["./otel-init"])) {
|
|
7753
8587
|
const lines = raw.split(/\r?\n/);
|
|
7754
8588
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -7818,8 +8652,8 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
|
|
|
7818
8652
|
return null;
|
|
7819
8653
|
}
|
|
7820
8654
|
async function plan(serviceDir, opts) {
|
|
7821
|
-
const pkg = await
|
|
7822
|
-
const manifestPath =
|
|
8655
|
+
const pkg = await readPackageJson2(serviceDir);
|
|
8656
|
+
const manifestPath = import_node_path42.default.join(serviceDir, "package.json");
|
|
7823
8657
|
const project = opts?.project;
|
|
7824
8658
|
const empty = {
|
|
7825
8659
|
language: "javascript",
|
|
@@ -7854,32 +8688,32 @@ async function plan(serviceDir, opts) {
|
|
|
7854
8688
|
return { ...empty, libOnly: true };
|
|
7855
8689
|
}
|
|
7856
8690
|
const flavor = dispatchEntry(entryFile, pkg);
|
|
7857
|
-
const otelInitFile =
|
|
7858
|
-
const envNeatFile =
|
|
8691
|
+
const otelInitFile = import_node_path42.default.join(import_node_path42.default.dirname(entryFile), otelInitFilename(flavor));
|
|
8692
|
+
const envNeatFile = import_node_path42.default.join(serviceDir, ".env.neat");
|
|
7859
8693
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
7860
8694
|
const dependencyEdits = [];
|
|
7861
8695
|
for (const sdk of SDK_PACKAGES) {
|
|
7862
|
-
if (sdk.name in existingDeps)
|
|
7863
|
-
|
|
7864
|
-
|
|
7865
|
-
|
|
7866
|
-
|
|
7867
|
-
|
|
7868
|
-
});
|
|
8696
|
+
if (sdk.name in existingDeps) {
|
|
8697
|
+
if (needsVersionUpgrade(existingDeps[sdk.name], sdk.version)) {
|
|
8698
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: sdk.name, version: sdk.version, fromVersion: existingDeps[sdk.name] });
|
|
8699
|
+
}
|
|
8700
|
+
continue;
|
|
8701
|
+
}
|
|
8702
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
|
|
7869
8703
|
}
|
|
7870
8704
|
const nonBundled = detectNonBundledInstrumentations(pkg);
|
|
7871
8705
|
for (const inst of nonBundled) {
|
|
7872
|
-
if (inst.pkg in existingDeps)
|
|
7873
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
7876
|
-
|
|
7877
|
-
|
|
7878
|
-
});
|
|
8706
|
+
if (inst.pkg in existingDeps) {
|
|
8707
|
+
if (needsVersionUpgrade(existingDeps[inst.pkg], inst.version)) {
|
|
8708
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: inst.pkg, version: inst.version, fromVersion: existingDeps[inst.pkg] });
|
|
8709
|
+
}
|
|
8710
|
+
continue;
|
|
8711
|
+
}
|
|
8712
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: inst.pkg, version: inst.version });
|
|
7879
8713
|
}
|
|
7880
8714
|
const entrypointEdits = [];
|
|
7881
8715
|
try {
|
|
7882
|
-
const raw = await
|
|
8716
|
+
const raw = await import_node_fs27.promises.readFile(entryFile, "utf8");
|
|
7883
8717
|
const lines = raw.split(/\r?\n/);
|
|
7884
8718
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
7885
8719
|
if (!lineIsOtelInjection(firstReal)) {
|
|
@@ -7902,7 +8736,7 @@ async function plan(serviceDir, opts) {
|
|
|
7902
8736
|
renderNodeOtelInit(otelInitContents(flavor), svcName, projectName, registrations)
|
|
7903
8737
|
);
|
|
7904
8738
|
if (otelInitGen) generatedFiles.push(otelInitGen);
|
|
7905
|
-
if (!await
|
|
8739
|
+
if (!await exists3(envNeatFile)) {
|
|
7906
8740
|
generatedFiles.push({
|
|
7907
8741
|
file: envNeatFile,
|
|
7908
8742
|
contents: renderEnvNeat(svcName, projectName),
|
|
@@ -7924,13 +8758,13 @@ async function plan(serviceDir, opts) {
|
|
|
7924
8758
|
};
|
|
7925
8759
|
}
|
|
7926
8760
|
function isAllowedWritePath(serviceDir, target) {
|
|
7927
|
-
const rel =
|
|
8761
|
+
const rel = import_node_path42.default.relative(serviceDir, target);
|
|
7928
8762
|
if (rel.startsWith("..")) return false;
|
|
7929
|
-
const base =
|
|
8763
|
+
const base = import_node_path42.default.basename(target);
|
|
7930
8764
|
if (base === "package.json") return true;
|
|
7931
8765
|
if (base === ".env.neat") return true;
|
|
7932
8766
|
if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
|
|
7933
|
-
const relPosix = rel.split(
|
|
8767
|
+
const relPosix = rel.split(import_node_path42.default.sep).join("/");
|
|
7934
8768
|
if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
|
|
7935
8769
|
if (relPosix === base) return true;
|
|
7936
8770
|
if (relPosix === `src/${base}`) return true;
|
|
@@ -7947,10 +8781,10 @@ function isAllowedWritePath(serviceDir, target) {
|
|
|
7947
8781
|
return false;
|
|
7948
8782
|
}
|
|
7949
8783
|
async function writeAtomic(file, contents) {
|
|
7950
|
-
await
|
|
8784
|
+
await import_node_fs27.promises.mkdir(import_node_path42.default.dirname(file), { recursive: true });
|
|
7951
8785
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
7952
|
-
await
|
|
7953
|
-
await
|
|
8786
|
+
await import_node_fs27.promises.writeFile(tmp, contents, "utf8");
|
|
8787
|
+
await import_node_fs27.promises.rename(tmp, file);
|
|
7954
8788
|
}
|
|
7955
8789
|
async function apply(installPlan) {
|
|
7956
8790
|
const { serviceDir } = installPlan;
|
|
@@ -7978,6 +8812,28 @@ async function apply(installPlan) {
|
|
|
7978
8812
|
writtenFiles: []
|
|
7979
8813
|
};
|
|
7980
8814
|
}
|
|
8815
|
+
if (installPlan.runtimeKind === "bun") {
|
|
8816
|
+
return { serviceDir, outcome: "bun", reason: "Bun runtime; use BYO-OTel", writtenFiles: [] };
|
|
8817
|
+
}
|
|
8818
|
+
if (installPlan.runtimeKind === "deno") {
|
|
8819
|
+
return { serviceDir, outcome: "deno", reason: "Deno runtime; use BYO-OTel", writtenFiles: [] };
|
|
8820
|
+
}
|
|
8821
|
+
if (installPlan.runtimeKind === "cloudflare-workers") {
|
|
8822
|
+
return {
|
|
8823
|
+
serviceDir,
|
|
8824
|
+
outcome: "cloudflare-workers",
|
|
8825
|
+
reason: "Cloudflare Workers runtime; use BYO-OTel",
|
|
8826
|
+
writtenFiles: []
|
|
8827
|
+
};
|
|
8828
|
+
}
|
|
8829
|
+
if (installPlan.runtimeKind === "electron") {
|
|
8830
|
+
return {
|
|
8831
|
+
serviceDir,
|
|
8832
|
+
outcome: "electron",
|
|
8833
|
+
reason: "Electron; use BYO-OTel",
|
|
8834
|
+
writtenFiles: []
|
|
8835
|
+
};
|
|
8836
|
+
}
|
|
7981
8837
|
if (installPlan.dependencyEdits.length === 0 && installPlan.entrypointEdits.length === 0 && (installPlan.generatedFiles?.length ?? 0) === 0 && installPlan.nextConfigEdit === void 0) {
|
|
7982
8838
|
return {
|
|
7983
8839
|
serviceDir,
|
|
@@ -8002,9 +8858,9 @@ async function apply(installPlan) {
|
|
|
8002
8858
|
const originals = /* @__PURE__ */ new Map();
|
|
8003
8859
|
const createdFiles = [];
|
|
8004
8860
|
for (const target of allTargets) {
|
|
8005
|
-
if (await
|
|
8861
|
+
if (await exists3(target)) {
|
|
8006
8862
|
try {
|
|
8007
|
-
originals.set(target, await
|
|
8863
|
+
originals.set(target, await import_node_fs27.promises.readFile(target, "utf8"));
|
|
8008
8864
|
} catch {
|
|
8009
8865
|
}
|
|
8010
8866
|
}
|
|
@@ -8028,6 +8884,8 @@ async function apply(installPlan) {
|
|
|
8028
8884
|
if (!(dep.name in (pkg.dependencies ?? {}))) {
|
|
8029
8885
|
pkg.dependencies[dep.name] = dep.version;
|
|
8030
8886
|
}
|
|
8887
|
+
} else if (dep.kind === "upgrade") {
|
|
8888
|
+
pkg.dependencies[dep.name] = dep.version;
|
|
8031
8889
|
} else {
|
|
8032
8890
|
delete pkg.dependencies[dep.name];
|
|
8033
8891
|
}
|
|
@@ -8037,7 +8895,7 @@ async function apply(installPlan) {
|
|
|
8037
8895
|
writtenFiles.push(file);
|
|
8038
8896
|
}
|
|
8039
8897
|
for (const gen of installPlan.generatedFiles ?? []) {
|
|
8040
|
-
if (gen.skipIfExists && await
|
|
8898
|
+
if (gen.skipIfExists && await exists3(gen.file)) {
|
|
8041
8899
|
continue;
|
|
8042
8900
|
}
|
|
8043
8901
|
await writeAtomic(gen.file, gen.contents);
|
|
@@ -8085,14 +8943,14 @@ async function rollback(installPlan, originals, createdFiles) {
|
|
|
8085
8943
|
const removed = [];
|
|
8086
8944
|
for (const [file, raw] of originals.entries()) {
|
|
8087
8945
|
try {
|
|
8088
|
-
await
|
|
8946
|
+
await import_node_fs27.promises.writeFile(file, raw, "utf8");
|
|
8089
8947
|
restored.push(file);
|
|
8090
8948
|
} catch {
|
|
8091
8949
|
}
|
|
8092
8950
|
}
|
|
8093
8951
|
for (const file of createdFiles) {
|
|
8094
8952
|
try {
|
|
8095
|
-
await
|
|
8953
|
+
await import_node_fs27.promises.unlink(file);
|
|
8096
8954
|
removed.push(file);
|
|
8097
8955
|
} catch {
|
|
8098
8956
|
}
|
|
@@ -8107,8 +8965,8 @@ async function rollback(installPlan, originals, createdFiles) {
|
|
|
8107
8965
|
...removed.map((f) => `removed: ${f}`),
|
|
8108
8966
|
""
|
|
8109
8967
|
];
|
|
8110
|
-
const rollbackPath =
|
|
8111
|
-
await
|
|
8968
|
+
const rollbackPath = import_node_path42.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
8969
|
+
await import_node_fs27.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
8112
8970
|
}
|
|
8113
8971
|
function injectInstrumentationHook(raw) {
|
|
8114
8972
|
if (raw.includes("instrumentationHook")) return raw;
|
|
@@ -8137,8 +8995,8 @@ var javascriptInstaller = {
|
|
|
8137
8995
|
|
|
8138
8996
|
// src/installers/python.ts
|
|
8139
8997
|
init_cjs_shims();
|
|
8140
|
-
var
|
|
8141
|
-
var
|
|
8998
|
+
var import_node_fs28 = require("fs");
|
|
8999
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
8142
9000
|
var SDK_PACKAGES2 = [
|
|
8143
9001
|
{ name: "opentelemetry-distro", version: ">=0.49b0" },
|
|
8144
9002
|
{ name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
|
|
@@ -8148,9 +9006,9 @@ var OTEL_ENV2 = {
|
|
|
8148
9006
|
key: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
8149
9007
|
value: "http://localhost:4318"
|
|
8150
9008
|
};
|
|
8151
|
-
async function
|
|
9009
|
+
async function exists4(p) {
|
|
8152
9010
|
try {
|
|
8153
|
-
await
|
|
9011
|
+
await import_node_fs28.promises.stat(p);
|
|
8154
9012
|
return true;
|
|
8155
9013
|
} catch {
|
|
8156
9014
|
return false;
|
|
@@ -8159,7 +9017,7 @@ async function exists3(p) {
|
|
|
8159
9017
|
async function detect2(serviceDir) {
|
|
8160
9018
|
const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
|
|
8161
9019
|
for (const m of markers) {
|
|
8162
|
-
if (await
|
|
9020
|
+
if (await exists4(import_node_path43.default.join(serviceDir, m))) return true;
|
|
8163
9021
|
}
|
|
8164
9022
|
return false;
|
|
8165
9023
|
}
|
|
@@ -8169,9 +9027,9 @@ function reqPackageName(line) {
|
|
|
8169
9027
|
return head.replace(/[<>=!~].*$/, "").toLowerCase();
|
|
8170
9028
|
}
|
|
8171
9029
|
async function planRequirementsTxtEdits(serviceDir) {
|
|
8172
|
-
const file =
|
|
8173
|
-
if (!await
|
|
8174
|
-
const raw = await
|
|
9030
|
+
const file = import_node_path43.default.join(serviceDir, "requirements.txt");
|
|
9031
|
+
if (!await exists4(file)) return null;
|
|
9032
|
+
const raw = await import_node_fs28.promises.readFile(file, "utf8");
|
|
8175
9033
|
const presentNames = new Set(
|
|
8176
9034
|
raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
|
|
8177
9035
|
);
|
|
@@ -8179,9 +9037,9 @@ async function planRequirementsTxtEdits(serviceDir) {
|
|
|
8179
9037
|
return { manifest: file, missing: [...missing] };
|
|
8180
9038
|
}
|
|
8181
9039
|
async function planProcfileEdits(serviceDir) {
|
|
8182
|
-
const procfile =
|
|
8183
|
-
if (!await
|
|
8184
|
-
const raw = await
|
|
9040
|
+
const procfile = import_node_path43.default.join(serviceDir, "Procfile");
|
|
9041
|
+
if (!await exists4(procfile)) return [];
|
|
9042
|
+
const raw = await import_node_fs28.promises.readFile(procfile, "utf8");
|
|
8185
9043
|
const edits = [];
|
|
8186
9044
|
for (const line of raw.split(/\r?\n/)) {
|
|
8187
9045
|
if (line.length === 0) continue;
|
|
@@ -8233,8 +9091,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
|
|
|
8233
9091
|
const next = `${original}${trailing}${newlines.join("\n")}
|
|
8234
9092
|
`;
|
|
8235
9093
|
const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
|
|
8236
|
-
await
|
|
8237
|
-
await
|
|
9094
|
+
await import_node_fs28.promises.writeFile(tmp, next, "utf8");
|
|
9095
|
+
await import_node_fs28.promises.rename(tmp, manifest);
|
|
8238
9096
|
}
|
|
8239
9097
|
async function applyProcfile(procfile, edits, original) {
|
|
8240
9098
|
let next = original;
|
|
@@ -8243,8 +9101,8 @@ async function applyProcfile(procfile, edits, original) {
|
|
|
8243
9101
|
next = next.replace(e.before, e.after);
|
|
8244
9102
|
}
|
|
8245
9103
|
const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
|
|
8246
|
-
await
|
|
8247
|
-
await
|
|
9104
|
+
await import_node_fs28.promises.writeFile(tmp, next, "utf8");
|
|
9105
|
+
await import_node_fs28.promises.rename(tmp, procfile);
|
|
8248
9106
|
}
|
|
8249
9107
|
async function apply2(installPlan) {
|
|
8250
9108
|
const { serviceDir } = installPlan;
|
|
@@ -8257,7 +9115,7 @@ async function apply2(installPlan) {
|
|
|
8257
9115
|
const originals = /* @__PURE__ */ new Map();
|
|
8258
9116
|
for (const file of touched) {
|
|
8259
9117
|
try {
|
|
8260
|
-
originals.set(file, await
|
|
9118
|
+
originals.set(file, await import_node_fs28.promises.readFile(file, "utf8"));
|
|
8261
9119
|
} catch {
|
|
8262
9120
|
}
|
|
8263
9121
|
}
|
|
@@ -8268,7 +9126,7 @@ async function apply2(installPlan) {
|
|
|
8268
9126
|
if (raw === void 0) {
|
|
8269
9127
|
throw new Error(`python installer: cannot read ${file} during apply`);
|
|
8270
9128
|
}
|
|
8271
|
-
const base =
|
|
9129
|
+
const base = import_node_path43.default.basename(file);
|
|
8272
9130
|
if (base === "requirements.txt") {
|
|
8273
9131
|
const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
|
|
8274
9132
|
if (edits.length > 0) {
|
|
@@ -8293,7 +9151,7 @@ async function rollback2(installPlan, originals) {
|
|
|
8293
9151
|
const restored = [];
|
|
8294
9152
|
for (const [file, raw] of originals.entries()) {
|
|
8295
9153
|
try {
|
|
8296
|
-
await
|
|
9154
|
+
await import_node_fs28.promises.writeFile(file, raw, "utf8");
|
|
8297
9155
|
restored.push(file);
|
|
8298
9156
|
} catch {
|
|
8299
9157
|
}
|
|
@@ -8307,8 +9165,8 @@ async function rollback2(installPlan, originals) {
|
|
|
8307
9165
|
...restored.map((f) => `restored: ${f}`),
|
|
8308
9166
|
""
|
|
8309
9167
|
];
|
|
8310
|
-
const rollbackPath =
|
|
8311
|
-
await
|
|
9168
|
+
const rollbackPath = import_node_path43.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
9169
|
+
await import_node_fs28.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
8312
9170
|
}
|
|
8313
9171
|
var pythonInstaller = {
|
|
8314
9172
|
name: "python",
|
|
@@ -8431,100 +9289,19 @@ function renderPatch(sections) {
|
|
|
8431
9289
|
|
|
8432
9290
|
// src/orchestrator.ts
|
|
8433
9291
|
init_cjs_shims();
|
|
8434
|
-
var
|
|
9292
|
+
var import_node_fs29 = require("fs");
|
|
8435
9293
|
var import_node_http = __toESM(require("http"), 1);
|
|
8436
9294
|
var import_node_net = __toESM(require("net"), 1);
|
|
8437
|
-
var
|
|
9295
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
8438
9296
|
var import_node_child_process3 = require("child_process");
|
|
8439
9297
|
var import_node_readline = __toESM(require("readline"), 1);
|
|
8440
|
-
|
|
8441
|
-
// src/installers/package-manager.ts
|
|
8442
|
-
init_cjs_shims();
|
|
8443
|
-
var import_node_fs27 = require("fs");
|
|
8444
|
-
var import_node_path42 = __toESM(require("path"), 1);
|
|
8445
|
-
var import_node_child_process2 = require("child_process");
|
|
8446
|
-
var LOCKFILE_PRIORITY = [
|
|
8447
|
-
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
8448
|
-
{ lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
|
|
8449
|
-
{ lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
|
|
8450
|
-
{
|
|
8451
|
-
lockfile: "package-lock.json",
|
|
8452
|
-
pm: "npm",
|
|
8453
|
-
args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
|
|
8454
|
-
}
|
|
8455
|
-
];
|
|
8456
|
-
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
8457
|
-
async function exists4(p) {
|
|
8458
|
-
try {
|
|
8459
|
-
await import_node_fs27.promises.access(p);
|
|
8460
|
-
return true;
|
|
8461
|
-
} catch {
|
|
8462
|
-
return false;
|
|
8463
|
-
}
|
|
8464
|
-
}
|
|
8465
|
-
async function detectPackageManager(serviceDir) {
|
|
8466
|
-
let dir = import_node_path42.default.resolve(serviceDir);
|
|
8467
|
-
const stops = /* @__PURE__ */ new Set();
|
|
8468
|
-
for (let i = 0; i < 64; i++) {
|
|
8469
|
-
if (stops.has(dir)) break;
|
|
8470
|
-
stops.add(dir);
|
|
8471
|
-
for (const candidate of LOCKFILE_PRIORITY) {
|
|
8472
|
-
const lockPath = import_node_path42.default.join(dir, candidate.lockfile);
|
|
8473
|
-
if (await exists4(lockPath)) {
|
|
8474
|
-
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
8475
|
-
}
|
|
8476
|
-
}
|
|
8477
|
-
const parent = import_node_path42.default.dirname(dir);
|
|
8478
|
-
if (parent === dir) break;
|
|
8479
|
-
dir = parent;
|
|
8480
|
-
}
|
|
8481
|
-
return { pm: "npm", cwd: import_node_path42.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
8482
|
-
}
|
|
8483
|
-
async function runPackageManagerInstall(cmd) {
|
|
8484
|
-
return new Promise((resolve) => {
|
|
8485
|
-
const child = (0, import_node_child_process2.spawn)(cmd.pm, cmd.args, {
|
|
8486
|
-
cwd: cmd.cwd,
|
|
8487
|
-
// Inherit PATH + HOME so the user's installed managers resolve.
|
|
8488
|
-
env: process.env,
|
|
8489
|
-
// `false` keeps the parent in control of cleanup if the orchestrator
|
|
8490
|
-
// exits before install finishes. Cross-platform-safe.
|
|
8491
|
-
shell: false,
|
|
8492
|
-
stdio: ["ignore", "ignore", "pipe"]
|
|
8493
|
-
});
|
|
8494
|
-
let stderr = "";
|
|
8495
|
-
child.stderr?.on("data", (chunk) => {
|
|
8496
|
-
stderr += chunk.toString("utf8");
|
|
8497
|
-
});
|
|
8498
|
-
child.on("error", (err) => {
|
|
8499
|
-
resolve({
|
|
8500
|
-
pm: cmd.pm,
|
|
8501
|
-
cwd: cmd.cwd,
|
|
8502
|
-
args: cmd.args,
|
|
8503
|
-
exitCode: 127,
|
|
8504
|
-
stderr: stderr + `
|
|
8505
|
-
${err.message}`
|
|
8506
|
-
});
|
|
8507
|
-
});
|
|
8508
|
-
child.on("close", (code) => {
|
|
8509
|
-
resolve({
|
|
8510
|
-
pm: cmd.pm,
|
|
8511
|
-
cwd: cmd.cwd,
|
|
8512
|
-
args: cmd.args,
|
|
8513
|
-
exitCode: code ?? 1,
|
|
8514
|
-
stderr: stderr.trim()
|
|
8515
|
-
});
|
|
8516
|
-
});
|
|
8517
|
-
});
|
|
8518
|
-
}
|
|
8519
|
-
|
|
8520
|
-
// src/orchestrator.ts
|
|
8521
9298
|
async function extractAndPersist(opts) {
|
|
8522
9299
|
const services = await discoverServices(opts.scanPath);
|
|
8523
9300
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
8524
9301
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
8525
9302
|
resetGraph(graphKey);
|
|
8526
9303
|
const graph = getGraph(graphKey);
|
|
8527
|
-
const projectPaths = pathsForProject(graphKey,
|
|
9304
|
+
const projectPaths = pathsForProject(graphKey, import_node_path44.default.join(opts.scanPath, "neat-out"));
|
|
8528
9305
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
8529
9306
|
errorsPath: projectPaths.errorsPath
|
|
8530
9307
|
});
|
|
@@ -8550,6 +9327,10 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
8550
9327
|
let libOnly = 0;
|
|
8551
9328
|
let browserBundle = 0;
|
|
8552
9329
|
let reactNative = 0;
|
|
9330
|
+
let bun = 0;
|
|
9331
|
+
let deno = 0;
|
|
9332
|
+
let cloudflareWorkers = 0;
|
|
9333
|
+
let electron = 0;
|
|
8553
9334
|
const installPlans = /* @__PURE__ */ new Map();
|
|
8554
9335
|
for (const svc of services) {
|
|
8555
9336
|
const installer = await pickInstaller(svc.dir);
|
|
@@ -8574,7 +9355,59 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
8574
9355
|
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
8575
9356
|
} else if (outcome.outcome === "react-native") {
|
|
8576
9357
|
reactNative++;
|
|
8577
|
-
|
|
9358
|
+
const svcName = import_node_path44.default.basename(svc.dir);
|
|
9359
|
+
console.log(
|
|
9360
|
+
`neat: ${svc.dir} detected as React Native / Expo
|
|
9361
|
+
The installer doesn't cover this runtime deterministically.
|
|
9362
|
+
Configure your OTel binding to send spans to:
|
|
9363
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
9364
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
9365
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
9366
|
+
);
|
|
9367
|
+
} else if (outcome.outcome === "bun") {
|
|
9368
|
+
bun++;
|
|
9369
|
+
const svcName = import_node_path44.default.basename(svc.dir);
|
|
9370
|
+
console.log(
|
|
9371
|
+
`neat: ${svc.dir} detected as Bun
|
|
9372
|
+
The installer doesn't cover this runtime deterministically.
|
|
9373
|
+
Configure your OTel binding to send spans to:
|
|
9374
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
9375
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
9376
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
9377
|
+
);
|
|
9378
|
+
} else if (outcome.outcome === "deno") {
|
|
9379
|
+
deno++;
|
|
9380
|
+
const svcName = import_node_path44.default.basename(svc.dir);
|
|
9381
|
+
console.log(
|
|
9382
|
+
`neat: ${svc.dir} detected as Deno
|
|
9383
|
+
The installer doesn't cover this runtime deterministically.
|
|
9384
|
+
Configure your OTel binding to send spans to:
|
|
9385
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
9386
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
9387
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
9388
|
+
);
|
|
9389
|
+
} else if (outcome.outcome === "cloudflare-workers") {
|
|
9390
|
+
cloudflareWorkers++;
|
|
9391
|
+
const svcName = import_node_path44.default.basename(svc.dir);
|
|
9392
|
+
console.log(
|
|
9393
|
+
`neat: ${svc.dir} detected as Cloudflare Workers
|
|
9394
|
+
The installer doesn't cover this runtime deterministically.
|
|
9395
|
+
Configure your OTel binding to send spans to:
|
|
9396
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
9397
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
9398
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
9399
|
+
);
|
|
9400
|
+
} else if (outcome.outcome === "electron") {
|
|
9401
|
+
electron++;
|
|
9402
|
+
const svcName = import_node_path44.default.basename(svc.dir);
|
|
9403
|
+
console.log(
|
|
9404
|
+
`neat: ${svc.dir} detected as Electron
|
|
9405
|
+
The installer doesn't cover this runtime deterministically.
|
|
9406
|
+
Configure your OTel binding to send spans to:
|
|
9407
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
9408
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
9409
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
9410
|
+
);
|
|
8578
9411
|
}
|
|
8579
9412
|
}
|
|
8580
9413
|
const packageManagerInstalls = [];
|
|
@@ -8599,6 +9432,10 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
8599
9432
|
libOnly,
|
|
8600
9433
|
browserBundle,
|
|
8601
9434
|
reactNative,
|
|
9435
|
+
bun,
|
|
9436
|
+
deno,
|
|
9437
|
+
cloudflareWorkers,
|
|
9438
|
+
electron,
|
|
8602
9439
|
packageManagerInstalls
|
|
8603
9440
|
};
|
|
8604
9441
|
}
|
|
@@ -8736,10 +9573,10 @@ function formatPortCollisionMessage(port) {
|
|
|
8736
9573
|
];
|
|
8737
9574
|
}
|
|
8738
9575
|
function spawnDaemonDetached() {
|
|
8739
|
-
const here =
|
|
9576
|
+
const here = import_node_path44.default.dirname(new URL(importMetaUrl).pathname);
|
|
8740
9577
|
const candidates = [
|
|
8741
|
-
|
|
8742
|
-
|
|
9578
|
+
import_node_path44.default.join(here, "neatd.cjs"),
|
|
9579
|
+
import_node_path44.default.join(here, "neatd.js")
|
|
8743
9580
|
];
|
|
8744
9581
|
let entry2 = null;
|
|
8745
9582
|
const fsSync = require("fs");
|
|
@@ -8797,7 +9634,7 @@ async function runOrchestrator(opts) {
|
|
|
8797
9634
|
browser: "skipped"
|
|
8798
9635
|
}
|
|
8799
9636
|
};
|
|
8800
|
-
const stat = await
|
|
9637
|
+
const stat = await import_node_fs29.promises.stat(opts.scanPath).catch(() => null);
|
|
8801
9638
|
if (!stat || !stat.isDirectory()) {
|
|
8802
9639
|
console.error(`neat: ${opts.scanPath} is not a directory`);
|
|
8803
9640
|
result.exitCode = 2;
|
|
@@ -8940,7 +9777,7 @@ function printSummary(result, graph, dashboardUrl) {
|
|
|
8940
9777
|
|
|
8941
9778
|
// src/cli-verbs.ts
|
|
8942
9779
|
init_cjs_shims();
|
|
8943
|
-
var
|
|
9780
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
8944
9781
|
|
|
8945
9782
|
// src/cli-client.ts
|
|
8946
9783
|
init_cjs_shims();
|
|
@@ -8969,10 +9806,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
8969
9806
|
const root = baseUrl.replace(/\/$/, "");
|
|
8970
9807
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
8971
9808
|
return {
|
|
8972
|
-
async get(
|
|
9809
|
+
async get(path47) {
|
|
8973
9810
|
let res;
|
|
8974
9811
|
try {
|
|
8975
|
-
res = await fetch(`${root}${
|
|
9812
|
+
res = await fetch(`${root}${path47}`, {
|
|
8976
9813
|
headers: { ...authHeader }
|
|
8977
9814
|
});
|
|
8978
9815
|
} catch (err) {
|
|
@@ -8984,16 +9821,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
8984
9821
|
const body = await res.text().catch(() => "");
|
|
8985
9822
|
throw new HttpError(
|
|
8986
9823
|
res.status,
|
|
8987
|
-
`${res.status} ${res.statusText} on GET ${
|
|
9824
|
+
`${res.status} ${res.statusText} on GET ${path47}: ${body}`,
|
|
8988
9825
|
body
|
|
8989
9826
|
);
|
|
8990
9827
|
}
|
|
8991
9828
|
return await res.json();
|
|
8992
9829
|
},
|
|
8993
|
-
async post(
|
|
9830
|
+
async post(path47, body) {
|
|
8994
9831
|
let res;
|
|
8995
9832
|
try {
|
|
8996
|
-
res = await fetch(`${root}${
|
|
9833
|
+
res = await fetch(`${root}${path47}`, {
|
|
8997
9834
|
method: "POST",
|
|
8998
9835
|
headers: { "content-type": "application/json", ...authHeader },
|
|
8999
9836
|
body: JSON.stringify(body)
|
|
@@ -9007,7 +9844,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
9007
9844
|
const text = await res.text().catch(() => "");
|
|
9008
9845
|
throw new HttpError(
|
|
9009
9846
|
res.status,
|
|
9010
|
-
`${res.status} ${res.statusText} on POST ${
|
|
9847
|
+
`${res.status} ${res.statusText} on POST ${path47}: ${text}`,
|
|
9011
9848
|
text
|
|
9012
9849
|
);
|
|
9013
9850
|
}
|
|
@@ -9021,12 +9858,12 @@ function projectPath(project, suffix) {
|
|
|
9021
9858
|
}
|
|
9022
9859
|
async function runRootCause(client, input) {
|
|
9023
9860
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
9024
|
-
const
|
|
9861
|
+
const path47 = projectPath(
|
|
9025
9862
|
input.project,
|
|
9026
9863
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
9027
9864
|
);
|
|
9028
9865
|
try {
|
|
9029
|
-
const result = await client.get(
|
|
9866
|
+
const result = await client.get(path47);
|
|
9030
9867
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
9031
9868
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
9032
9869
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -9052,12 +9889,12 @@ async function runRootCause(client, input) {
|
|
|
9052
9889
|
}
|
|
9053
9890
|
async function runBlastRadius(client, input) {
|
|
9054
9891
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
9055
|
-
const
|
|
9892
|
+
const path47 = projectPath(
|
|
9056
9893
|
input.project,
|
|
9057
9894
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
9058
9895
|
);
|
|
9059
9896
|
try {
|
|
9060
|
-
const result = await client.get(
|
|
9897
|
+
const result = await client.get(path47);
|
|
9061
9898
|
if (result.totalAffected === 0) {
|
|
9062
9899
|
return {
|
|
9063
9900
|
summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
|
|
@@ -9091,12 +9928,12 @@ function formatBlastEntry(n) {
|
|
|
9091
9928
|
}
|
|
9092
9929
|
async function runDependencies(client, input) {
|
|
9093
9930
|
const depth = input.depth ?? 3;
|
|
9094
|
-
const
|
|
9931
|
+
const path47 = projectPath(
|
|
9095
9932
|
input.project,
|
|
9096
9933
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
9097
9934
|
);
|
|
9098
9935
|
try {
|
|
9099
|
-
const result = await client.get(
|
|
9936
|
+
const result = await client.get(path47);
|
|
9100
9937
|
if (result.total === 0) {
|
|
9101
9938
|
return {
|
|
9102
9939
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -9177,9 +10014,9 @@ function formatDuration(ms) {
|
|
|
9177
10014
|
return `${Math.round(h / 24)}d`;
|
|
9178
10015
|
}
|
|
9179
10016
|
async function runIncidents(client, input) {
|
|
9180
|
-
const
|
|
10017
|
+
const path47 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
9181
10018
|
try {
|
|
9182
|
-
const body = await client.get(
|
|
10019
|
+
const body = await client.get(path47);
|
|
9183
10020
|
const events = body.events;
|
|
9184
10021
|
if (events.length === 0) {
|
|
9185
10022
|
return {
|
|
@@ -9469,7 +10306,7 @@ async function resolveProjectEntry(opts) {
|
|
|
9469
10306
|
const cwd = opts.cwd ?? process.cwd();
|
|
9470
10307
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
9471
10308
|
for (const entry2 of entries) {
|
|
9472
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
10309
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path45.default.sep}`)) {
|
|
9473
10310
|
return entry2;
|
|
9474
10311
|
}
|
|
9475
10312
|
}
|
|
@@ -9829,14 +10666,14 @@ function assignFlag(out, field, value) {
|
|
|
9829
10666
|
out[field] = value;
|
|
9830
10667
|
}
|
|
9831
10668
|
function readPackageVersion() {
|
|
9832
|
-
const here = typeof __dirname !== "undefined" ? __dirname :
|
|
10669
|
+
const here = typeof __dirname !== "undefined" ? __dirname : import_node_path46.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
|
|
9833
10670
|
const candidates = [
|
|
9834
|
-
|
|
9835
|
-
|
|
10671
|
+
import_node_path46.default.resolve(here, "../package.json"),
|
|
10672
|
+
import_node_path46.default.resolve(here, "../../package.json")
|
|
9836
10673
|
];
|
|
9837
10674
|
for (const candidate of candidates) {
|
|
9838
10675
|
try {
|
|
9839
|
-
const raw = (0,
|
|
10676
|
+
const raw = (0, import_node_fs30.readFileSync)(candidate, "utf8");
|
|
9840
10677
|
const parsed = JSON.parse(raw);
|
|
9841
10678
|
if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
|
|
9842
10679
|
return parsed.version;
|
|
@@ -9900,7 +10737,7 @@ async function buildPatchSections(services, project) {
|
|
|
9900
10737
|
}
|
|
9901
10738
|
async function runInit(opts) {
|
|
9902
10739
|
const written = [];
|
|
9903
|
-
const stat = await
|
|
10740
|
+
const stat = await import_node_fs30.promises.stat(opts.scanPath).catch(() => null);
|
|
9904
10741
|
if (!stat || !stat.isDirectory()) {
|
|
9905
10742
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
9906
10743
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -9909,13 +10746,13 @@ async function runInit(opts) {
|
|
|
9909
10746
|
printDiscoveryReport(opts, services);
|
|
9910
10747
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
9911
10748
|
const patch = renderPatch(sections);
|
|
9912
|
-
const patchPath =
|
|
10749
|
+
const patchPath = import_node_path46.default.join(opts.scanPath, "neat.patch");
|
|
9913
10750
|
if (opts.dryRun) {
|
|
9914
|
-
await
|
|
10751
|
+
await import_node_fs30.promises.writeFile(patchPath, patch, "utf8");
|
|
9915
10752
|
written.push(patchPath);
|
|
9916
10753
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
9917
|
-
const gitignorePath =
|
|
9918
|
-
const gitignoreExists = await
|
|
10754
|
+
const gitignorePath = import_node_path46.default.join(opts.scanPath, ".gitignore");
|
|
10755
|
+
const gitignoreExists = await import_node_fs30.promises.stat(gitignorePath).then(() => true).catch(() => false);
|
|
9919
10756
|
const verb = gitignoreExists ? "append" : "create";
|
|
9920
10757
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
9921
10758
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -9926,9 +10763,9 @@ async function runInit(opts) {
|
|
|
9926
10763
|
const graph = getGraph(graphKey);
|
|
9927
10764
|
const projectPaths = pathsForProject(
|
|
9928
10765
|
graphKey,
|
|
9929
|
-
|
|
10766
|
+
import_node_path46.default.join(opts.scanPath, "neat-out")
|
|
9930
10767
|
);
|
|
9931
|
-
const errorsPath =
|
|
10768
|
+
const errorsPath = import_node_path46.default.join(import_node_path46.default.dirname(opts.outPath), import_node_path46.default.basename(projectPaths.errorsPath));
|
|
9932
10769
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
9933
10770
|
await saveGraphToDisk(graph, opts.outPath);
|
|
9934
10771
|
written.push(opts.outPath);
|
|
@@ -10007,7 +10844,7 @@ async function runInit(opts) {
|
|
|
10007
10844
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
10008
10845
|
}
|
|
10009
10846
|
} else {
|
|
10010
|
-
await
|
|
10847
|
+
await import_node_fs30.promises.writeFile(patchPath, patch, "utf8");
|
|
10011
10848
|
written.push(patchPath);
|
|
10012
10849
|
}
|
|
10013
10850
|
}
|
|
@@ -10047,9 +10884,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
10047
10884
|
};
|
|
10048
10885
|
function claudeConfigPath() {
|
|
10049
10886
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
10050
|
-
if (override && override.length > 0) return
|
|
10887
|
+
if (override && override.length > 0) return import_node_path46.default.resolve(override);
|
|
10051
10888
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
10052
|
-
return
|
|
10889
|
+
return import_node_path46.default.join(home, ".claude.json");
|
|
10053
10890
|
}
|
|
10054
10891
|
async function runSkill(opts) {
|
|
10055
10892
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -10061,7 +10898,7 @@ async function runSkill(opts) {
|
|
|
10061
10898
|
const target = claudeConfigPath();
|
|
10062
10899
|
let existing = {};
|
|
10063
10900
|
try {
|
|
10064
|
-
existing = JSON.parse(await
|
|
10901
|
+
existing = JSON.parse(await import_node_fs30.promises.readFile(target, "utf8"));
|
|
10065
10902
|
} catch (err) {
|
|
10066
10903
|
if (err.code !== "ENOENT") {
|
|
10067
10904
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -10073,8 +10910,8 @@ async function runSkill(opts) {
|
|
|
10073
10910
|
...existing,
|
|
10074
10911
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
10075
10912
|
};
|
|
10076
|
-
await
|
|
10077
|
-
await
|
|
10913
|
+
await import_node_fs30.promises.mkdir(import_node_path46.default.dirname(target), { recursive: true });
|
|
10914
|
+
await import_node_fs30.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
10078
10915
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
10079
10916
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
10080
10917
|
return { exitCode: 0 };
|
|
@@ -10112,12 +10949,12 @@ async function main() {
|
|
|
10112
10949
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
10113
10950
|
process.exit(2);
|
|
10114
10951
|
}
|
|
10115
|
-
const scanPath =
|
|
10952
|
+
const scanPath = import_node_path46.default.resolve(target);
|
|
10116
10953
|
const projectExplicit = parsed.project !== null;
|
|
10117
|
-
const projectName = projectExplicit ? project :
|
|
10954
|
+
const projectName = projectExplicit ? project : import_node_path46.default.basename(scanPath);
|
|
10118
10955
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
10119
|
-
const fallback = pathsForProject(projectKey,
|
|
10120
|
-
const outPath =
|
|
10956
|
+
const fallback = pathsForProject(projectKey, import_node_path46.default.join(scanPath, "neat-out")).snapshotPath;
|
|
10957
|
+
const outPath = import_node_path46.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
10121
10958
|
const result = await runInit({
|
|
10122
10959
|
scanPath,
|
|
10123
10960
|
outPath,
|
|
@@ -10138,21 +10975,21 @@ async function main() {
|
|
|
10138
10975
|
usage();
|
|
10139
10976
|
process.exit(2);
|
|
10140
10977
|
}
|
|
10141
|
-
const scanPath =
|
|
10142
|
-
const stat = await
|
|
10978
|
+
const scanPath = import_node_path46.default.resolve(target);
|
|
10979
|
+
const stat = await import_node_fs30.promises.stat(scanPath).catch(() => null);
|
|
10143
10980
|
if (!stat || !stat.isDirectory()) {
|
|
10144
10981
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
10145
10982
|
process.exit(2);
|
|
10146
10983
|
}
|
|
10147
|
-
const projectPaths = pathsForProject(project,
|
|
10148
|
-
const outPath =
|
|
10149
|
-
const errorsPath =
|
|
10150
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
10984
|
+
const projectPaths = pathsForProject(project, import_node_path46.default.join(scanPath, "neat-out"));
|
|
10985
|
+
const outPath = import_node_path46.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
10986
|
+
const errorsPath = import_node_path46.default.resolve(
|
|
10987
|
+
process.env.NEAT_ERRORS_PATH ?? import_node_path46.default.join(import_node_path46.default.dirname(outPath), import_node_path46.default.basename(projectPaths.errorsPath))
|
|
10151
10988
|
);
|
|
10152
|
-
const staleEventsPath =
|
|
10153
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
10989
|
+
const staleEventsPath = import_node_path46.default.resolve(
|
|
10990
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path46.default.join(import_node_path46.default.dirname(outPath), import_node_path46.default.basename(projectPaths.staleEventsPath))
|
|
10154
10991
|
);
|
|
10155
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
10992
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path46.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
10156
10993
|
const handle = await startWatch(getGraph(project), {
|
|
10157
10994
|
scanPath,
|
|
10158
10995
|
outPath,
|
|
@@ -10298,11 +11135,11 @@ async function main() {
|
|
|
10298
11135
|
process.exit(1);
|
|
10299
11136
|
}
|
|
10300
11137
|
async function tryOrchestrator(cmd, parsed) {
|
|
10301
|
-
const scanPath =
|
|
10302
|
-
const stat = await
|
|
11138
|
+
const scanPath = import_node_path46.default.resolve(cmd);
|
|
11139
|
+
const stat = await import_node_fs30.promises.stat(scanPath).catch(() => null);
|
|
10303
11140
|
if (!stat || !stat.isDirectory()) return null;
|
|
10304
11141
|
const projectExplicit = parsed.project !== null;
|
|
10305
|
-
const projectName = projectExplicit ? parsed.project :
|
|
11142
|
+
const projectName = projectExplicit ? parsed.project : import_node_path46.default.basename(scanPath);
|
|
10306
11143
|
const result = await runOrchestrator({
|
|
10307
11144
|
scanPath,
|
|
10308
11145
|
project: projectName,
|