@neat.is/core 0.4.11 → 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-W4RNPPB5.js → chunk-7JY6F7BY.js} +2 -2
- package/dist/{chunk-OJHI33LD.js → chunk-WDG4QEFO.js} +616 -173
- package/dist/chunk-WDG4QEFO.js.map +1 -0
- package/dist/cli.cjs +994 -534
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +189 -169
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +669 -226
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +667 -224
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +802 -359
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +4 -2
- package/dist/chunk-OJHI33LD.js.map +0 -1
- /package/dist/{chunk-W4RNPPB5.js.map → chunk-7JY6F7BY.js.map} +0 -0
package/dist/neatd.cjs
CHANGED
|
@@ -57,9 +57,9 @@ function mountBearerAuth(app, opts) {
|
|
|
57
57
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
58
58
|
const publicRead = opts.publicRead === true;
|
|
59
59
|
app.addHook("preHandler", (req2, reply, done) => {
|
|
60
|
-
const
|
|
60
|
+
const path42 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
61
61
|
for (const suffix of suffixes) {
|
|
62
|
-
if (
|
|
62
|
+
if (path42 === suffix || path42.endsWith(suffix)) {
|
|
63
63
|
done();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
@@ -185,8 +185,8 @@ function reshapeGrpcRequest(req2) {
|
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
187
|
function resolveProtoRoot() {
|
|
188
|
-
const here =
|
|
189
|
-
return
|
|
188
|
+
const here = import_node_path36.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
189
|
+
return import_node_path36.default.resolve(here, "..", "proto");
|
|
190
190
|
}
|
|
191
191
|
function loadTraceService() {
|
|
192
192
|
const protoRoot = resolveProtoRoot();
|
|
@@ -254,13 +254,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
254
254
|
})
|
|
255
255
|
};
|
|
256
256
|
}
|
|
257
|
-
var import_node_url,
|
|
257
|
+
var import_node_url, import_node_path36, import_node_crypto2, grpc, protoLoader;
|
|
258
258
|
var init_otel_grpc = __esm({
|
|
259
259
|
"src/otel-grpc.ts"() {
|
|
260
260
|
"use strict";
|
|
261
261
|
init_cjs_shims();
|
|
262
262
|
import_node_url = require("url");
|
|
263
|
-
|
|
263
|
+
import_node_path36 = __toESM(require("path"), 1);
|
|
264
264
|
import_node_crypto2 = require("crypto");
|
|
265
265
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
266
266
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -370,10 +370,10 @@ function parseOtlpRequest(body) {
|
|
|
370
370
|
return out;
|
|
371
371
|
}
|
|
372
372
|
function loadProtoRoot() {
|
|
373
|
-
const here =
|
|
374
|
-
const protoRoot =
|
|
373
|
+
const here = import_node_path37.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
374
|
+
const protoRoot = import_node_path37.default.resolve(here, "..", "proto");
|
|
375
375
|
const root = new import_protobufjs.default.Root();
|
|
376
|
-
root.resolvePath = (_origin, target) =>
|
|
376
|
+
root.resolvePath = (_origin, target) => import_node_path37.default.resolve(protoRoot, target);
|
|
377
377
|
root.loadSync(
|
|
378
378
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
379
379
|
{ keepCase: true }
|
|
@@ -566,12 +566,12 @@ async function buildOtelReceiver(opts) {
|
|
|
566
566
|
};
|
|
567
567
|
return decorated;
|
|
568
568
|
}
|
|
569
|
-
var
|
|
569
|
+
var import_node_path37, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
570
570
|
var init_otel = __esm({
|
|
571
571
|
"src/otel.ts"() {
|
|
572
572
|
"use strict";
|
|
573
573
|
init_cjs_shims();
|
|
574
|
-
|
|
574
|
+
import_node_path37 = __toESM(require("path"), 1);
|
|
575
575
|
import_node_url2 = require("url");
|
|
576
576
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
577
577
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -587,14 +587,14 @@ var init_otel = __esm({
|
|
|
587
587
|
|
|
588
588
|
// src/neatd.ts
|
|
589
589
|
init_cjs_shims();
|
|
590
|
-
var
|
|
591
|
-
var
|
|
590
|
+
var import_node_fs25 = require("fs");
|
|
591
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
592
592
|
var import_node_module = require("module");
|
|
593
593
|
|
|
594
594
|
// src/daemon.ts
|
|
595
595
|
init_cjs_shims();
|
|
596
|
-
var
|
|
597
|
-
var
|
|
596
|
+
var import_node_fs24 = require("fs");
|
|
597
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
598
598
|
|
|
599
599
|
// src/graph.ts
|
|
600
600
|
init_cjs_shims();
|
|
@@ -1073,19 +1073,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1073
1073
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1074
1074
|
let best = { path: [start], edges: [] };
|
|
1075
1075
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1076
|
-
function step(node,
|
|
1077
|
-
if (
|
|
1078
|
-
best = { path: [...
|
|
1076
|
+
function step(node, path42, edges) {
|
|
1077
|
+
if (path42.length > best.path.length) {
|
|
1078
|
+
best = { path: [...path42], edges: [...edges] };
|
|
1079
1079
|
}
|
|
1080
|
-
if (
|
|
1080
|
+
if (path42.length - 1 >= maxDepth) return;
|
|
1081
1081
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1082
1082
|
for (const [srcId, edge] of incoming) {
|
|
1083
1083
|
if (visited.has(srcId)) continue;
|
|
1084
1084
|
visited.add(srcId);
|
|
1085
|
-
|
|
1085
|
+
path42.push(srcId);
|
|
1086
1086
|
edges.push(edge);
|
|
1087
|
-
step(srcId,
|
|
1088
|
-
|
|
1087
|
+
step(srcId, path42, edges);
|
|
1088
|
+
path42.pop();
|
|
1089
1089
|
edges.pop();
|
|
1090
1090
|
visited.delete(srcId);
|
|
1091
1091
|
}
|
|
@@ -1895,7 +1895,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
1895
1895
|
graph.addNode(id, node);
|
|
1896
1896
|
return id;
|
|
1897
1897
|
}
|
|
1898
|
-
function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
1898
|
+
function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
|
|
1899
1899
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
1900
1900
|
const id = makeObservedEdgeId(type, source, target);
|
|
1901
1901
|
if (graph.hasEdge(id)) {
|
|
@@ -1932,7 +1932,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1932
1932
|
confidence: (0, import_types3.confidenceForObservedSignal)(signal),
|
|
1933
1933
|
lastObserved: ts,
|
|
1934
1934
|
callCount: 1,
|
|
1935
|
-
signal
|
|
1935
|
+
signal,
|
|
1936
|
+
// Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
|
|
1937
|
+
// Only set when code.filepath was present on the span — never fabricated.
|
|
1938
|
+
...evidence ? { evidence } : {}
|
|
1936
1939
|
};
|
|
1937
1940
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
1938
1941
|
return { edge, created: true };
|
|
@@ -2026,6 +2029,7 @@ async function handleSpan(ctx, span) {
|
|
|
2026
2029
|
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
2027
2030
|
const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
|
|
2028
2031
|
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
2032
|
+
const callSiteEvidence = callSite ? { file: callSite.relPath, ...callSite.line !== void 0 ? { line: callSite.line } : {} } : void 0;
|
|
2029
2033
|
let affectedNode = sourceId;
|
|
2030
2034
|
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2031
2035
|
if (span.dbSystem) {
|
|
@@ -2039,7 +2043,8 @@ async function handleSpan(ctx, span) {
|
|
|
2039
2043
|
observedSource(),
|
|
2040
2044
|
targetId,
|
|
2041
2045
|
ts,
|
|
2042
|
-
isError
|
|
2046
|
+
isError,
|
|
2047
|
+
callSiteEvidence
|
|
2043
2048
|
);
|
|
2044
2049
|
if (result) affectedNode = targetId;
|
|
2045
2050
|
}
|
|
@@ -2055,7 +2060,8 @@ async function handleSpan(ctx, span) {
|
|
|
2055
2060
|
observedSource(),
|
|
2056
2061
|
targetId,
|
|
2057
2062
|
ts,
|
|
2058
|
-
isError
|
|
2063
|
+
isError,
|
|
2064
|
+
callSiteEvidence
|
|
2059
2065
|
);
|
|
2060
2066
|
affectedNode = targetId;
|
|
2061
2067
|
resolvedViaAddress = true;
|
|
@@ -2067,7 +2073,8 @@ async function handleSpan(ctx, span) {
|
|
|
2067
2073
|
observedSource(),
|
|
2068
2074
|
frontierNodeId,
|
|
2069
2075
|
ts,
|
|
2070
|
-
isError
|
|
2076
|
+
isError,
|
|
2077
|
+
callSiteEvidence
|
|
2071
2078
|
);
|
|
2072
2079
|
affectedNode = frontierNodeId;
|
|
2073
2080
|
resolvedViaAddress = true;
|
|
@@ -2945,14 +2952,111 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
2945
2952
|
|
|
2946
2953
|
// src/extract/databases/index.ts
|
|
2947
2954
|
init_cjs_shims();
|
|
2948
|
-
var
|
|
2955
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
2956
|
+
var import_types8 = require("@neat.is/types");
|
|
2957
|
+
|
|
2958
|
+
// src/extract/calls/shared.ts
|
|
2959
|
+
init_cjs_shims();
|
|
2960
|
+
var import_node_fs10 = require("fs");
|
|
2961
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
2949
2962
|
var import_types7 = require("@neat.is/types");
|
|
2963
|
+
async function walkSourceFiles(dir) {
|
|
2964
|
+
const out = [];
|
|
2965
|
+
async function walk(current) {
|
|
2966
|
+
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
2967
|
+
for (const entry2 of entries) {
|
|
2968
|
+
const full = import_node_path10.default.join(current, entry2.name);
|
|
2969
|
+
if (entry2.isDirectory()) {
|
|
2970
|
+
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
2971
|
+
if (await isPythonVenvDir(full)) continue;
|
|
2972
|
+
await walk(full);
|
|
2973
|
+
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry2.name))) {
|
|
2974
|
+
out.push(full);
|
|
2975
|
+
}
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
await walk(dir);
|
|
2979
|
+
return out;
|
|
2980
|
+
}
|
|
2981
|
+
async function loadSourceFiles(dir) {
|
|
2982
|
+
const paths = await walkSourceFiles(dir);
|
|
2983
|
+
const out = [];
|
|
2984
|
+
for (const p of paths) {
|
|
2985
|
+
try {
|
|
2986
|
+
const content = await import_node_fs10.promises.readFile(p, "utf8");
|
|
2987
|
+
out.push({ path: p, content });
|
|
2988
|
+
} catch {
|
|
2989
|
+
}
|
|
2990
|
+
}
|
|
2991
|
+
return out;
|
|
2992
|
+
}
|
|
2993
|
+
function lineOf(text, needle) {
|
|
2994
|
+
const idx = text.indexOf(needle);
|
|
2995
|
+
if (idx < 0) return 1;
|
|
2996
|
+
return text.slice(0, idx).split("\n").length;
|
|
2997
|
+
}
|
|
2998
|
+
function snippet(text, line) {
|
|
2999
|
+
const lines = text.split("\n");
|
|
3000
|
+
return (lines[line - 1] ?? "").trim();
|
|
3001
|
+
}
|
|
3002
|
+
function toPosix2(p) {
|
|
3003
|
+
return p.split("\\").join("/");
|
|
3004
|
+
}
|
|
3005
|
+
function languageForPath(relPath) {
|
|
3006
|
+
switch (import_node_path10.default.extname(relPath).toLowerCase()) {
|
|
3007
|
+
case ".py":
|
|
3008
|
+
return "python";
|
|
3009
|
+
case ".ts":
|
|
3010
|
+
case ".tsx":
|
|
3011
|
+
return "typescript";
|
|
3012
|
+
case ".js":
|
|
3013
|
+
case ".jsx":
|
|
3014
|
+
case ".mjs":
|
|
3015
|
+
case ".cjs":
|
|
3016
|
+
return "javascript";
|
|
3017
|
+
default:
|
|
3018
|
+
return void 0;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3022
|
+
let nodesAdded = 0;
|
|
3023
|
+
let edgesAdded = 0;
|
|
3024
|
+
const fileNodeId = (0, import_types7.fileId)(serviceName, relPath);
|
|
3025
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3026
|
+
const language = languageForPath(relPath);
|
|
3027
|
+
const node = {
|
|
3028
|
+
id: fileNodeId,
|
|
3029
|
+
type: import_types7.NodeType.FileNode,
|
|
3030
|
+
service: serviceName,
|
|
3031
|
+
path: relPath,
|
|
3032
|
+
...language ? { language } : {},
|
|
3033
|
+
discoveredVia: "static"
|
|
3034
|
+
};
|
|
3035
|
+
graph.addNode(fileNodeId, node);
|
|
3036
|
+
nodesAdded++;
|
|
3037
|
+
}
|
|
3038
|
+
const containsId = (0, import_types7.extractedEdgeId)(serviceNodeId, fileNodeId, import_types7.EdgeType.CONTAINS);
|
|
3039
|
+
if (!graph.hasEdge(containsId)) {
|
|
3040
|
+
const edge = {
|
|
3041
|
+
id: containsId,
|
|
3042
|
+
source: serviceNodeId,
|
|
3043
|
+
target: fileNodeId,
|
|
3044
|
+
type: import_types7.EdgeType.CONTAINS,
|
|
3045
|
+
provenance: import_types7.Provenance.EXTRACTED,
|
|
3046
|
+
confidence: (0, import_types7.confidenceForExtracted)("structural"),
|
|
3047
|
+
evidence: { file: relPath }
|
|
3048
|
+
};
|
|
3049
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3050
|
+
edgesAdded++;
|
|
3051
|
+
}
|
|
3052
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3053
|
+
}
|
|
2950
3054
|
|
|
2951
3055
|
// src/extract/databases/db-config-yaml.ts
|
|
2952
3056
|
init_cjs_shims();
|
|
2953
|
-
var
|
|
3057
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
2954
3058
|
async function parse(serviceDir) {
|
|
2955
|
-
const yamlPath =
|
|
3059
|
+
const yamlPath = import_node_path11.default.join(serviceDir, "db-config.yaml");
|
|
2956
3060
|
if (!await exists(yamlPath)) return [];
|
|
2957
3061
|
const raw = await readYaml(yamlPath);
|
|
2958
3062
|
return [
|
|
@@ -2970,13 +3074,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
2970
3074
|
|
|
2971
3075
|
// src/extract/databases/dotenv.ts
|
|
2972
3076
|
init_cjs_shims();
|
|
2973
|
-
var
|
|
2974
|
-
var
|
|
3077
|
+
var import_node_fs12 = require("fs");
|
|
3078
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
2975
3079
|
|
|
2976
3080
|
// src/extract/databases/shared.ts
|
|
2977
3081
|
init_cjs_shims();
|
|
2978
|
-
var
|
|
2979
|
-
var
|
|
3082
|
+
var import_node_fs11 = require("fs");
|
|
3083
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
2980
3084
|
function schemeToEngine(scheme) {
|
|
2981
3085
|
const s = scheme.toLowerCase().split("+")[0];
|
|
2982
3086
|
switch (s) {
|
|
@@ -3015,14 +3119,14 @@ function parseConnectionString(url) {
|
|
|
3015
3119
|
}
|
|
3016
3120
|
async function readIfExists(filePath) {
|
|
3017
3121
|
try {
|
|
3018
|
-
return await
|
|
3122
|
+
return await import_node_fs11.promises.readFile(filePath, "utf8");
|
|
3019
3123
|
} catch {
|
|
3020
3124
|
return null;
|
|
3021
3125
|
}
|
|
3022
3126
|
}
|
|
3023
3127
|
async function findFirst(serviceDir, candidates) {
|
|
3024
3128
|
for (const rel of candidates) {
|
|
3025
|
-
const abs =
|
|
3129
|
+
const abs = import_node_path12.default.join(serviceDir, rel);
|
|
3026
3130
|
const content = await readIfExists(abs);
|
|
3027
3131
|
if (content !== null) return abs;
|
|
3028
3132
|
}
|
|
@@ -3073,15 +3177,15 @@ function parseDotenvLine(line) {
|
|
|
3073
3177
|
return { key, value };
|
|
3074
3178
|
}
|
|
3075
3179
|
async function parse2(serviceDir) {
|
|
3076
|
-
const entries = await
|
|
3180
|
+
const entries = await import_node_fs12.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
3077
3181
|
const configs = [];
|
|
3078
3182
|
const seen = /* @__PURE__ */ new Set();
|
|
3079
3183
|
for (const entry2 of entries) {
|
|
3080
3184
|
if (!entry2.isFile()) continue;
|
|
3081
3185
|
const match = isConfigFile(entry2.name);
|
|
3082
3186
|
if (!match.match || match.fileType !== "env") continue;
|
|
3083
|
-
const filePath =
|
|
3084
|
-
const content = await
|
|
3187
|
+
const filePath = import_node_path13.default.join(serviceDir, entry2.name);
|
|
3188
|
+
const content = await import_node_fs12.promises.readFile(filePath, "utf8");
|
|
3085
3189
|
for (const line of content.split("\n")) {
|
|
3086
3190
|
const parsed = parseDotenvLine(line);
|
|
3087
3191
|
if (!parsed) continue;
|
|
@@ -3100,9 +3204,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
3100
3204
|
|
|
3101
3205
|
// src/extract/databases/prisma.ts
|
|
3102
3206
|
init_cjs_shims();
|
|
3103
|
-
var
|
|
3207
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
3104
3208
|
async function parse3(serviceDir) {
|
|
3105
|
-
const schemaPath =
|
|
3209
|
+
const schemaPath = import_node_path14.default.join(serviceDir, "prisma", "schema.prisma");
|
|
3106
3210
|
const content = await readIfExists(schemaPath);
|
|
3107
3211
|
if (!content) return [];
|
|
3108
3212
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -3234,10 +3338,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
3234
3338
|
|
|
3235
3339
|
// src/extract/databases/ormconfig.ts
|
|
3236
3340
|
init_cjs_shims();
|
|
3237
|
-
var
|
|
3341
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
3238
3342
|
async function parse6(serviceDir) {
|
|
3239
3343
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
3240
|
-
const abs =
|
|
3344
|
+
const abs = import_node_path15.default.join(serviceDir, candidate);
|
|
3241
3345
|
if (!await exists(abs)) continue;
|
|
3242
3346
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
3243
3347
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -3297,9 +3401,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
3297
3401
|
|
|
3298
3402
|
// src/extract/databases/sequelize.ts
|
|
3299
3403
|
init_cjs_shims();
|
|
3300
|
-
var
|
|
3404
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
3301
3405
|
async function parse8(serviceDir) {
|
|
3302
|
-
const configPath =
|
|
3406
|
+
const configPath = import_node_path16.default.join(serviceDir, "config", "config.json");
|
|
3303
3407
|
if (!await exists(configPath)) return [];
|
|
3304
3408
|
const raw = await readJson(configPath);
|
|
3305
3409
|
const out = [];
|
|
@@ -3326,7 +3430,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
3326
3430
|
|
|
3327
3431
|
// src/extract/databases/docker-compose.ts
|
|
3328
3432
|
init_cjs_shims();
|
|
3329
|
-
var
|
|
3433
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
3330
3434
|
function portFromService(svc) {
|
|
3331
3435
|
for (const raw of svc.ports ?? []) {
|
|
3332
3436
|
const str = String(raw);
|
|
@@ -3353,7 +3457,7 @@ function databaseFromEnv(svc) {
|
|
|
3353
3457
|
}
|
|
3354
3458
|
async function parse9(serviceDir) {
|
|
3355
3459
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3356
|
-
const abs =
|
|
3460
|
+
const abs = import_node_path17.default.join(serviceDir, name);
|
|
3357
3461
|
if (!await exists(abs)) continue;
|
|
3358
3462
|
const raw = await readYaml(abs);
|
|
3359
3463
|
if (!raw?.services) return [];
|
|
@@ -3394,8 +3498,8 @@ function compatibleDriversFor(engine) {
|
|
|
3394
3498
|
}
|
|
3395
3499
|
function toDatabaseNode(config) {
|
|
3396
3500
|
return {
|
|
3397
|
-
id: (0,
|
|
3398
|
-
type:
|
|
3501
|
+
id: (0, import_types8.databaseId)(config.host),
|
|
3502
|
+
type: import_types8.NodeType.DatabaseNode,
|
|
3399
3503
|
name: config.database || config.host,
|
|
3400
3504
|
engine: config.engine,
|
|
3401
3505
|
engineVersion: config.engineVersion,
|
|
@@ -3525,19 +3629,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3525
3629
|
discoveredVia: mergedDiscoveredVia
|
|
3526
3630
|
});
|
|
3527
3631
|
}
|
|
3632
|
+
const relConfigFile = toPosix2(import_node_path18.default.relative(service.dir, config.sourceFile));
|
|
3633
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
3634
|
+
graph,
|
|
3635
|
+
service.pkg.name,
|
|
3636
|
+
service.node.id,
|
|
3637
|
+
relConfigFile
|
|
3638
|
+
);
|
|
3639
|
+
nodesAdded += fn;
|
|
3640
|
+
edgesAdded += fe;
|
|
3641
|
+
const evidenceFile = toPosix2(import_node_path18.default.relative(scanPath, config.sourceFile));
|
|
3528
3642
|
const edge = {
|
|
3529
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3530
|
-
source:
|
|
3643
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types8.EdgeType.CONNECTS_TO),
|
|
3644
|
+
source: fileNodeId,
|
|
3531
3645
|
target: dbNode.id,
|
|
3532
|
-
type:
|
|
3533
|
-
provenance:
|
|
3534
|
-
confidence: (0,
|
|
3535
|
-
|
|
3536
|
-
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3537
|
-
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
3538
|
-
evidence: {
|
|
3539
|
-
file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
|
|
3540
|
-
}
|
|
3646
|
+
type: import_types8.EdgeType.CONNECTS_TO,
|
|
3647
|
+
provenance: import_types8.Provenance.EXTRACTED,
|
|
3648
|
+
confidence: (0, import_types8.confidenceForExtracted)("structural"),
|
|
3649
|
+
evidence: { file: evidenceFile }
|
|
3541
3650
|
};
|
|
3542
3651
|
if (!graph.hasEdge(edge.id)) {
|
|
3543
3652
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3563,15 +3672,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3563
3672
|
|
|
3564
3673
|
// src/extract/configs.ts
|
|
3565
3674
|
init_cjs_shims();
|
|
3566
|
-
var
|
|
3567
|
-
var
|
|
3568
|
-
var
|
|
3675
|
+
var import_node_fs13 = require("fs");
|
|
3676
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3677
|
+
var import_types9 = require("@neat.is/types");
|
|
3569
3678
|
async function walkConfigFiles(dir) {
|
|
3570
3679
|
const out = [];
|
|
3571
3680
|
async function walk(current) {
|
|
3572
|
-
const entries = await
|
|
3681
|
+
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true });
|
|
3573
3682
|
for (const entry2 of entries) {
|
|
3574
|
-
const full =
|
|
3683
|
+
const full = import_node_path19.default.join(current, entry2.name);
|
|
3575
3684
|
if (entry2.isDirectory()) {
|
|
3576
3685
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3577
3686
|
if (await isPythonVenvDir(full)) continue;
|
|
@@ -3590,26 +3699,35 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3590
3699
|
for (const service of services) {
|
|
3591
3700
|
const configFiles = await walkConfigFiles(service.dir);
|
|
3592
3701
|
for (const file of configFiles) {
|
|
3593
|
-
const relPath =
|
|
3702
|
+
const relPath = import_node_path19.default.relative(scanPath, file);
|
|
3594
3703
|
const node = {
|
|
3595
|
-
id: (0,
|
|
3596
|
-
type:
|
|
3597
|
-
name:
|
|
3704
|
+
id: (0, import_types9.configId)(relPath),
|
|
3705
|
+
type: import_types9.NodeType.ConfigNode,
|
|
3706
|
+
name: import_node_path19.default.basename(file),
|
|
3598
3707
|
path: relPath,
|
|
3599
|
-
fileType: isConfigFile(
|
|
3708
|
+
fileType: isConfigFile(import_node_path19.default.basename(file)).fileType
|
|
3600
3709
|
};
|
|
3601
3710
|
if (!graph.hasNode(node.id)) {
|
|
3602
3711
|
graph.addNode(node.id, node);
|
|
3603
3712
|
nodesAdded++;
|
|
3604
3713
|
}
|
|
3714
|
+
const relToService = toPosix2(import_node_path19.default.relative(service.dir, file));
|
|
3715
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
3716
|
+
graph,
|
|
3717
|
+
service.pkg.name,
|
|
3718
|
+
service.node.id,
|
|
3719
|
+
relToService
|
|
3720
|
+
);
|
|
3721
|
+
nodesAdded += fn;
|
|
3722
|
+
edgesAdded += fe;
|
|
3605
3723
|
const edge = {
|
|
3606
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3607
|
-
source:
|
|
3724
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types9.EdgeType.CONFIGURED_BY),
|
|
3725
|
+
source: fileNodeId,
|
|
3608
3726
|
target: node.id,
|
|
3609
|
-
type:
|
|
3610
|
-
provenance:
|
|
3611
|
-
confidence: (0,
|
|
3612
|
-
evidence: { file: relPath.split(
|
|
3727
|
+
type: import_types9.EdgeType.CONFIGURED_BY,
|
|
3728
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
3729
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3730
|
+
evidence: { file: relPath.split(import_node_path19.default.sep).join("/") }
|
|
3613
3731
|
};
|
|
3614
3732
|
if (!graph.hasEdge(edge.id)) {
|
|
3615
3733
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3631,105 +3749,6 @@ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
|
3631
3749
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3632
3750
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3633
3751
|
var import_types10 = require("@neat.is/types");
|
|
3634
|
-
|
|
3635
|
-
// src/extract/calls/shared.ts
|
|
3636
|
-
init_cjs_shims();
|
|
3637
|
-
var import_node_fs13 = require("fs");
|
|
3638
|
-
var import_node_path19 = __toESM(require("path"), 1);
|
|
3639
|
-
var import_types9 = require("@neat.is/types");
|
|
3640
|
-
async function walkSourceFiles(dir) {
|
|
3641
|
-
const out = [];
|
|
3642
|
-
async function walk(current) {
|
|
3643
|
-
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3644
|
-
for (const entry2 of entries) {
|
|
3645
|
-
const full = import_node_path19.default.join(current, entry2.name);
|
|
3646
|
-
if (entry2.isDirectory()) {
|
|
3647
|
-
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3648
|
-
if (await isPythonVenvDir(full)) continue;
|
|
3649
|
-
await walk(full);
|
|
3650
|
-
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry2.name))) {
|
|
3651
|
-
out.push(full);
|
|
3652
|
-
}
|
|
3653
|
-
}
|
|
3654
|
-
}
|
|
3655
|
-
await walk(dir);
|
|
3656
|
-
return out;
|
|
3657
|
-
}
|
|
3658
|
-
async function loadSourceFiles(dir) {
|
|
3659
|
-
const paths = await walkSourceFiles(dir);
|
|
3660
|
-
const out = [];
|
|
3661
|
-
for (const p of paths) {
|
|
3662
|
-
try {
|
|
3663
|
-
const content = await import_node_fs13.promises.readFile(p, "utf8");
|
|
3664
|
-
out.push({ path: p, content });
|
|
3665
|
-
} catch {
|
|
3666
|
-
}
|
|
3667
|
-
}
|
|
3668
|
-
return out;
|
|
3669
|
-
}
|
|
3670
|
-
function lineOf(text, needle) {
|
|
3671
|
-
const idx = text.indexOf(needle);
|
|
3672
|
-
if (idx < 0) return 1;
|
|
3673
|
-
return text.slice(0, idx).split("\n").length;
|
|
3674
|
-
}
|
|
3675
|
-
function snippet(text, line) {
|
|
3676
|
-
const lines = text.split("\n");
|
|
3677
|
-
return (lines[line - 1] ?? "").trim();
|
|
3678
|
-
}
|
|
3679
|
-
function toPosix2(p) {
|
|
3680
|
-
return p.split("\\").join("/");
|
|
3681
|
-
}
|
|
3682
|
-
function languageForPath(relPath) {
|
|
3683
|
-
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3684
|
-
case ".py":
|
|
3685
|
-
return "python";
|
|
3686
|
-
case ".ts":
|
|
3687
|
-
case ".tsx":
|
|
3688
|
-
return "typescript";
|
|
3689
|
-
case ".js":
|
|
3690
|
-
case ".jsx":
|
|
3691
|
-
case ".mjs":
|
|
3692
|
-
case ".cjs":
|
|
3693
|
-
return "javascript";
|
|
3694
|
-
default:
|
|
3695
|
-
return void 0;
|
|
3696
|
-
}
|
|
3697
|
-
}
|
|
3698
|
-
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3699
|
-
let nodesAdded = 0;
|
|
3700
|
-
let edgesAdded = 0;
|
|
3701
|
-
const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
|
|
3702
|
-
if (!graph.hasNode(fileNodeId)) {
|
|
3703
|
-
const language = languageForPath(relPath);
|
|
3704
|
-
const node = {
|
|
3705
|
-
id: fileNodeId,
|
|
3706
|
-
type: import_types9.NodeType.FileNode,
|
|
3707
|
-
service: serviceName,
|
|
3708
|
-
path: relPath,
|
|
3709
|
-
...language ? { language } : {},
|
|
3710
|
-
discoveredVia: "static"
|
|
3711
|
-
};
|
|
3712
|
-
graph.addNode(fileNodeId, node);
|
|
3713
|
-
nodesAdded++;
|
|
3714
|
-
}
|
|
3715
|
-
const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
|
|
3716
|
-
if (!graph.hasEdge(containsId)) {
|
|
3717
|
-
const edge = {
|
|
3718
|
-
id: containsId,
|
|
3719
|
-
source: serviceNodeId,
|
|
3720
|
-
target: fileNodeId,
|
|
3721
|
-
type: import_types9.EdgeType.CONTAINS,
|
|
3722
|
-
provenance: import_types9.Provenance.EXTRACTED,
|
|
3723
|
-
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3724
|
-
evidence: { file: relPath }
|
|
3725
|
-
};
|
|
3726
|
-
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3727
|
-
edgesAdded++;
|
|
3728
|
-
}
|
|
3729
|
-
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3730
|
-
}
|
|
3731
|
-
|
|
3732
|
-
// src/extract/calls/http.ts
|
|
3733
3752
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
3734
3753
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
3735
3754
|
function isInsideJsxExternalLink(node) {
|
|
@@ -4013,6 +4032,7 @@ var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-
|
|
|
4013
4032
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
4014
4033
|
function isLikelyAddress(value) {
|
|
4015
4034
|
if (!value) return false;
|
|
4035
|
+
if (/\s/.test(value) || value.startsWith("{")) return false;
|
|
4016
4036
|
return /:\d{2,5}$/.test(value) || value.includes(".");
|
|
4017
4037
|
}
|
|
4018
4038
|
function normaliseForMatch(s) {
|
|
@@ -4324,17 +4344,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4324
4344
|
graph.addNode(node.id, node);
|
|
4325
4345
|
nodesAdded++;
|
|
4326
4346
|
}
|
|
4327
|
-
const
|
|
4347
|
+
const relDockerfile = toPosix2(import_node_path26.default.relative(service.dir, dockerfilePath));
|
|
4348
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4349
|
+
graph,
|
|
4350
|
+
service.pkg.name,
|
|
4351
|
+
service.node.id,
|
|
4352
|
+
relDockerfile
|
|
4353
|
+
);
|
|
4354
|
+
nodesAdded += fn;
|
|
4355
|
+
edgesAdded += fe;
|
|
4356
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types18.EdgeType.RUNS_ON);
|
|
4328
4357
|
if (!graph.hasEdge(edgeId)) {
|
|
4329
4358
|
const edge = {
|
|
4330
4359
|
id: edgeId,
|
|
4331
|
-
source:
|
|
4360
|
+
source: fileNodeId,
|
|
4332
4361
|
target: node.id,
|
|
4333
4362
|
type: import_types18.EdgeType.RUNS_ON,
|
|
4334
4363
|
provenance: import_types18.Provenance.EXTRACTED,
|
|
4335
4364
|
confidence: (0, import_types18.confidenceForExtracted)("structural"),
|
|
4336
4365
|
evidence: {
|
|
4337
|
-
file: import_node_path26.default.relative(scanPath, dockerfilePath)
|
|
4366
|
+
file: toPosix2(import_node_path26.default.relative(scanPath, dockerfilePath))
|
|
4338
4367
|
}
|
|
4339
4368
|
};
|
|
4340
4369
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4725,6 +4754,321 @@ var import_fastify = __toESM(require("fastify"), 1);
|
|
|
4725
4754
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
4726
4755
|
var import_types23 = require("@neat.is/types");
|
|
4727
4756
|
|
|
4757
|
+
// src/extend/index.ts
|
|
4758
|
+
init_cjs_shims();
|
|
4759
|
+
var import_node_fs20 = require("fs");
|
|
4760
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
4761
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
4762
|
+
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
4763
|
+
|
|
4764
|
+
// src/installers/package-manager.ts
|
|
4765
|
+
init_cjs_shims();
|
|
4766
|
+
var import_node_fs19 = require("fs");
|
|
4767
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
4768
|
+
var import_node_child_process = require("child_process");
|
|
4769
|
+
var LOCKFILE_PRIORITY = [
|
|
4770
|
+
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
4771
|
+
{ lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
|
|
4772
|
+
{ lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
|
|
4773
|
+
{
|
|
4774
|
+
lockfile: "package-lock.json",
|
|
4775
|
+
pm: "npm",
|
|
4776
|
+
args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
|
|
4777
|
+
}
|
|
4778
|
+
];
|
|
4779
|
+
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
4780
|
+
async function exists2(p) {
|
|
4781
|
+
try {
|
|
4782
|
+
await import_node_fs19.promises.access(p);
|
|
4783
|
+
return true;
|
|
4784
|
+
} catch {
|
|
4785
|
+
return false;
|
|
4786
|
+
}
|
|
4787
|
+
}
|
|
4788
|
+
async function detectPackageManager(serviceDir) {
|
|
4789
|
+
let dir = import_node_path33.default.resolve(serviceDir);
|
|
4790
|
+
const stops = /* @__PURE__ */ new Set();
|
|
4791
|
+
for (let i = 0; i < 64; i++) {
|
|
4792
|
+
if (stops.has(dir)) break;
|
|
4793
|
+
stops.add(dir);
|
|
4794
|
+
for (const candidate of LOCKFILE_PRIORITY) {
|
|
4795
|
+
const lockPath = import_node_path33.default.join(dir, candidate.lockfile);
|
|
4796
|
+
if (await exists2(lockPath)) {
|
|
4797
|
+
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
4798
|
+
}
|
|
4799
|
+
}
|
|
4800
|
+
const parent = import_node_path33.default.dirname(dir);
|
|
4801
|
+
if (parent === dir) break;
|
|
4802
|
+
dir = parent;
|
|
4803
|
+
}
|
|
4804
|
+
return { pm: "npm", cwd: import_node_path33.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
4805
|
+
}
|
|
4806
|
+
async function runPackageManagerInstall(cmd) {
|
|
4807
|
+
return new Promise((resolve) => {
|
|
4808
|
+
const child = (0, import_node_child_process.spawn)(cmd.pm, cmd.args, {
|
|
4809
|
+
cwd: cmd.cwd,
|
|
4810
|
+
// Inherit PATH + HOME so the user's installed managers resolve.
|
|
4811
|
+
env: process.env,
|
|
4812
|
+
// `false` keeps the parent in control of cleanup if the orchestrator
|
|
4813
|
+
// exits before install finishes. Cross-platform-safe.
|
|
4814
|
+
shell: false,
|
|
4815
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
4816
|
+
});
|
|
4817
|
+
let stderr = "";
|
|
4818
|
+
child.stderr?.on("data", (chunk) => {
|
|
4819
|
+
stderr += chunk.toString("utf8");
|
|
4820
|
+
});
|
|
4821
|
+
child.on("error", (err) => {
|
|
4822
|
+
resolve({
|
|
4823
|
+
pm: cmd.pm,
|
|
4824
|
+
cwd: cmd.cwd,
|
|
4825
|
+
args: cmd.args,
|
|
4826
|
+
exitCode: 127,
|
|
4827
|
+
stderr: stderr + `
|
|
4828
|
+
${err.message}`
|
|
4829
|
+
});
|
|
4830
|
+
});
|
|
4831
|
+
child.on("close", (code) => {
|
|
4832
|
+
resolve({
|
|
4833
|
+
pm: cmd.pm,
|
|
4834
|
+
cwd: cmd.cwd,
|
|
4835
|
+
args: cmd.args,
|
|
4836
|
+
exitCode: code ?? 1,
|
|
4837
|
+
stderr: stderr.trim()
|
|
4838
|
+
});
|
|
4839
|
+
});
|
|
4840
|
+
});
|
|
4841
|
+
}
|
|
4842
|
+
|
|
4843
|
+
// src/extend/index.ts
|
|
4844
|
+
async function fileExists(p) {
|
|
4845
|
+
try {
|
|
4846
|
+
await import_node_fs20.promises.access(p);
|
|
4847
|
+
return true;
|
|
4848
|
+
} catch {
|
|
4849
|
+
return false;
|
|
4850
|
+
}
|
|
4851
|
+
}
|
|
4852
|
+
async function readPackageJson(scanPath) {
|
|
4853
|
+
const pkgPath = import_node_path34.default.join(scanPath, "package.json");
|
|
4854
|
+
const raw = await import_node_fs20.promises.readFile(pkgPath, "utf8");
|
|
4855
|
+
return JSON.parse(raw);
|
|
4856
|
+
}
|
|
4857
|
+
async function findHookFiles(scanPath) {
|
|
4858
|
+
const entries = await import_node_fs20.promises.readdir(scanPath);
|
|
4859
|
+
return entries.filter(
|
|
4860
|
+
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
4861
|
+
).sort();
|
|
4862
|
+
}
|
|
4863
|
+
function extendLogPath() {
|
|
4864
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path34.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
4865
|
+
}
|
|
4866
|
+
async function appendExtendLog(entry2) {
|
|
4867
|
+
const logPath = extendLogPath();
|
|
4868
|
+
await import_node_fs20.promises.mkdir(import_node_path34.default.dirname(logPath), { recursive: true });
|
|
4869
|
+
await import_node_fs20.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
|
|
4870
|
+
}
|
|
4871
|
+
function splicedContent(fileContent, snippet2) {
|
|
4872
|
+
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
4873
|
+
return fileContent.replace("__INSTRUMENTATION_BLOCK__", `${snippet2}
|
|
4874
|
+
__INSTRUMENTATION_BLOCK__`);
|
|
4875
|
+
}
|
|
4876
|
+
const lines = fileContent.split("\n");
|
|
4877
|
+
let lastPushIdx = -1;
|
|
4878
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4879
|
+
if (lines[i].includes("instrumentations.push(")) lastPushIdx = i;
|
|
4880
|
+
}
|
|
4881
|
+
if (lastPushIdx >= 0) {
|
|
4882
|
+
lines.splice(lastPushIdx + 1, 0, snippet2);
|
|
4883
|
+
return lines.join("\n");
|
|
4884
|
+
}
|
|
4885
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4886
|
+
if (lines[i].includes("new NodeSDK(")) {
|
|
4887
|
+
lines.splice(i, 0, snippet2);
|
|
4888
|
+
return lines.join("\n");
|
|
4889
|
+
}
|
|
4890
|
+
}
|
|
4891
|
+
return null;
|
|
4892
|
+
}
|
|
4893
|
+
async function listUninstrumented(ctx) {
|
|
4894
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
4895
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
4896
|
+
const results = [];
|
|
4897
|
+
for (const [library, installedVersion] of Object.entries(allDeps)) {
|
|
4898
|
+
const entry2 = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
4899
|
+
if (!entry2) continue;
|
|
4900
|
+
if (entry2.coverage === "bundled" || entry2.coverage === "http-only") continue;
|
|
4901
|
+
results.push({
|
|
4902
|
+
library,
|
|
4903
|
+
coverage: entry2.coverage,
|
|
4904
|
+
installedVersion,
|
|
4905
|
+
instrumentation_package: entry2.instrumentation_package,
|
|
4906
|
+
package_version: entry2.package_version,
|
|
4907
|
+
registration: entry2.registration,
|
|
4908
|
+
notes: entry2.notes
|
|
4909
|
+
});
|
|
4910
|
+
}
|
|
4911
|
+
return results;
|
|
4912
|
+
}
|
|
4913
|
+
function lookupInstrumentation(library, installedVersion) {
|
|
4914
|
+
const entry2 = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
4915
|
+
if (!entry2) return null;
|
|
4916
|
+
return {
|
|
4917
|
+
library: entry2.library,
|
|
4918
|
+
coverage: entry2.coverage,
|
|
4919
|
+
instrumentation_package: entry2.instrumentation_package,
|
|
4920
|
+
package_version: entry2.package_version,
|
|
4921
|
+
registration: entry2.registration,
|
|
4922
|
+
notes: entry2.notes
|
|
4923
|
+
};
|
|
4924
|
+
}
|
|
4925
|
+
async function describeProjectInstrumentation(ctx) {
|
|
4926
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
4927
|
+
const envNeat = await fileExists(import_node_path34.default.join(ctx.scanPath, ".env.neat"));
|
|
4928
|
+
const registryInstrPackages = new Set(
|
|
4929
|
+
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
4930
|
+
);
|
|
4931
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
4932
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
4933
|
+
const installedDeps = {};
|
|
4934
|
+
for (const [key, version] of Object.entries(allDeps)) {
|
|
4935
|
+
if (key.startsWith("@opentelemetry/") || registryInstrPackages.has(key)) {
|
|
4936
|
+
installedDeps[key] = version;
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
return { hookFiles, envNeat, installedDeps };
|
|
4940
|
+
}
|
|
4941
|
+
async function applyExtension(ctx, args, options) {
|
|
4942
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
4943
|
+
if (hookFiles.length === 0) {
|
|
4944
|
+
throw new Error(
|
|
4945
|
+
`No instrumentation hook files found in ${ctx.scanPath}. Run \`neat init\` first.`
|
|
4946
|
+
);
|
|
4947
|
+
}
|
|
4948
|
+
for (const file of hookFiles) {
|
|
4949
|
+
const content = await import_node_fs20.promises.readFile(import_node_path34.default.join(ctx.scanPath, file), "utf8");
|
|
4950
|
+
if (content.includes(args.registration_snippet)) {
|
|
4951
|
+
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
4952
|
+
}
|
|
4953
|
+
}
|
|
4954
|
+
const primaryFile = hookFiles[0];
|
|
4955
|
+
const primaryPath = import_node_path34.default.join(ctx.scanPath, primaryFile);
|
|
4956
|
+
const filesTouched = [];
|
|
4957
|
+
const depsAdded = [];
|
|
4958
|
+
const pkgPath = import_node_path34.default.join(ctx.scanPath, "package.json");
|
|
4959
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
4960
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
4961
|
+
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
4962
|
+
await import_node_fs20.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
4963
|
+
filesTouched.push("package.json");
|
|
4964
|
+
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
4965
|
+
}
|
|
4966
|
+
const hookContent = await import_node_fs20.promises.readFile(primaryPath, "utf8");
|
|
4967
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
4968
|
+
if (!patched) {
|
|
4969
|
+
throw new Error(
|
|
4970
|
+
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
4971
|
+
);
|
|
4972
|
+
}
|
|
4973
|
+
await import_node_fs20.promises.writeFile(primaryPath, patched, "utf8");
|
|
4974
|
+
filesTouched.push(primaryFile);
|
|
4975
|
+
const cmd = await detectPackageManager(ctx.scanPath);
|
|
4976
|
+
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
4977
|
+
const install = await installer(cmd);
|
|
4978
|
+
const installOutput = install.exitCode === 0 ? `${cmd.pm} install succeeded` : install.stderr || `${cmd.pm} install failed (exit ${install.exitCode})`;
|
|
4979
|
+
await appendExtendLog({
|
|
4980
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4981
|
+
project: ctx.project,
|
|
4982
|
+
library: args.library,
|
|
4983
|
+
instrumentation_package: args.instrumentation_package,
|
|
4984
|
+
version: args.version,
|
|
4985
|
+
registration_snippet: args.registration_snippet,
|
|
4986
|
+
filesTouched,
|
|
4987
|
+
depsAdded,
|
|
4988
|
+
installOutput
|
|
4989
|
+
});
|
|
4990
|
+
return { library: args.library, filesTouched, depsAdded, installOutput, alreadyApplied: false };
|
|
4991
|
+
}
|
|
4992
|
+
async function dryRunExtension(ctx, args) {
|
|
4993
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
4994
|
+
if (hookFiles.length === 0) {
|
|
4995
|
+
return {
|
|
4996
|
+
library: args.library,
|
|
4997
|
+
filesTouched: [],
|
|
4998
|
+
depsToAdd: [],
|
|
4999
|
+
packageJsonPatch: {},
|
|
5000
|
+
templatePatch: "No hook files found. Run 'neat init' first."
|
|
5001
|
+
};
|
|
5002
|
+
}
|
|
5003
|
+
for (const file of hookFiles) {
|
|
5004
|
+
const content = await import_node_fs20.promises.readFile(import_node_path34.default.join(ctx.scanPath, file), "utf8");
|
|
5005
|
+
if (content.includes(args.registration_snippet)) {
|
|
5006
|
+
return {
|
|
5007
|
+
library: args.library,
|
|
5008
|
+
filesTouched: [],
|
|
5009
|
+
depsToAdd: [],
|
|
5010
|
+
packageJsonPatch: {},
|
|
5011
|
+
templatePatch: "Already applied \u2014 no changes would be made."
|
|
5012
|
+
};
|
|
5013
|
+
}
|
|
5014
|
+
}
|
|
5015
|
+
const primaryFile = hookFiles[0];
|
|
5016
|
+
const filesTouched = [];
|
|
5017
|
+
const depsToAdd = [];
|
|
5018
|
+
let packageJsonPatch = {};
|
|
5019
|
+
let templatePatch = "";
|
|
5020
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5021
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5022
|
+
packageJsonPatch = { dependencies: { [args.instrumentation_package]: args.version } };
|
|
5023
|
+
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
5024
|
+
filesTouched.push("package.json");
|
|
5025
|
+
}
|
|
5026
|
+
const hookContent = await import_node_fs20.promises.readFile(import_node_path34.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
5027
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5028
|
+
if (patched) {
|
|
5029
|
+
filesTouched.push(primaryFile);
|
|
5030
|
+
templatePatch = `+ ${args.registration_snippet}`;
|
|
5031
|
+
} else {
|
|
5032
|
+
templatePatch = "Could not find insertion point in hook file.";
|
|
5033
|
+
}
|
|
5034
|
+
return { library: args.library, filesTouched, depsToAdd, packageJsonPatch, templatePatch };
|
|
5035
|
+
}
|
|
5036
|
+
async function rollbackExtension(ctx, args) {
|
|
5037
|
+
const logPath = extendLogPath();
|
|
5038
|
+
if (!await fileExists(logPath)) {
|
|
5039
|
+
return { undone: false, message: "no apply found for library" };
|
|
5040
|
+
}
|
|
5041
|
+
const raw = await import_node_fs20.promises.readFile(logPath, "utf8");
|
|
5042
|
+
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
5043
|
+
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
5044
|
+
if (!match) {
|
|
5045
|
+
return { undone: false, message: "no apply found for library" };
|
|
5046
|
+
}
|
|
5047
|
+
const pkgPath = import_node_path34.default.join(ctx.scanPath, "package.json");
|
|
5048
|
+
if (await fileExists(pkgPath)) {
|
|
5049
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5050
|
+
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
5051
|
+
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
5052
|
+
pkg.dependencies = rest;
|
|
5053
|
+
await import_node_fs20.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5054
|
+
}
|
|
5055
|
+
}
|
|
5056
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5057
|
+
for (const file of hookFiles) {
|
|
5058
|
+
const filePath = import_node_path34.default.join(ctx.scanPath, file);
|
|
5059
|
+
const content = await import_node_fs20.promises.readFile(filePath, "utf8");
|
|
5060
|
+
if (content.includes(match.registration_snippet)) {
|
|
5061
|
+
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
5062
|
+
await import_node_fs20.promises.writeFile(filePath, filtered, "utf8");
|
|
5063
|
+
break;
|
|
5064
|
+
}
|
|
5065
|
+
}
|
|
5066
|
+
return {
|
|
5067
|
+
undone: true,
|
|
5068
|
+
message: `rolled back ${match.library} (${match.instrumentation_package})`
|
|
5069
|
+
};
|
|
5070
|
+
}
|
|
5071
|
+
|
|
4728
5072
|
// src/divergences.ts
|
|
4729
5073
|
init_cjs_shims();
|
|
4730
5074
|
var import_types21 = require("@neat.is/types");
|
|
@@ -4965,7 +5309,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4965
5309
|
|
|
4966
5310
|
// src/diff.ts
|
|
4967
5311
|
init_cjs_shims();
|
|
4968
|
-
var
|
|
5312
|
+
var import_node_fs21 = require("fs");
|
|
4969
5313
|
async function loadSnapshotForDiff(target) {
|
|
4970
5314
|
if (/^https?:\/\//i.test(target)) {
|
|
4971
5315
|
const res = await fetch(target);
|
|
@@ -4974,7 +5318,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
4974
5318
|
}
|
|
4975
5319
|
return await res.json();
|
|
4976
5320
|
}
|
|
4977
|
-
const raw = await
|
|
5321
|
+
const raw = await import_node_fs21.promises.readFile(target, "utf8");
|
|
4978
5322
|
return JSON.parse(raw);
|
|
4979
5323
|
}
|
|
4980
5324
|
function indexEntries(entries) {
|
|
@@ -5042,25 +5386,25 @@ function canonicalJson(value) {
|
|
|
5042
5386
|
|
|
5043
5387
|
// src/registry.ts
|
|
5044
5388
|
init_cjs_shims();
|
|
5045
|
-
var
|
|
5046
|
-
var
|
|
5047
|
-
var
|
|
5389
|
+
var import_node_fs22 = require("fs");
|
|
5390
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
5391
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
5048
5392
|
var import_types22 = require("@neat.is/types");
|
|
5049
5393
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
5050
5394
|
var LOCK_RETRY_MS = 50;
|
|
5051
5395
|
function neatHome() {
|
|
5052
5396
|
const override = process.env.NEAT_HOME;
|
|
5053
|
-
if (override && override.length > 0) return
|
|
5054
|
-
return
|
|
5397
|
+
if (override && override.length > 0) return import_node_path35.default.resolve(override);
|
|
5398
|
+
return import_node_path35.default.join(import_node_os3.default.homedir(), ".neat");
|
|
5055
5399
|
}
|
|
5056
5400
|
function registryPath() {
|
|
5057
|
-
return
|
|
5401
|
+
return import_node_path35.default.join(neatHome(), "projects.json");
|
|
5058
5402
|
}
|
|
5059
5403
|
function registryLockPath() {
|
|
5060
|
-
return
|
|
5404
|
+
return import_node_path35.default.join(neatHome(), "projects.json.lock");
|
|
5061
5405
|
}
|
|
5062
5406
|
function daemonPidPath() {
|
|
5063
|
-
return
|
|
5407
|
+
return import_node_path35.default.join(neatHome(), "neatd.pid");
|
|
5064
5408
|
}
|
|
5065
5409
|
function isPidAliveDefault(pid) {
|
|
5066
5410
|
try {
|
|
@@ -5072,7 +5416,7 @@ function isPidAliveDefault(pid) {
|
|
|
5072
5416
|
}
|
|
5073
5417
|
async function readPidFile(file) {
|
|
5074
5418
|
try {
|
|
5075
|
-
const raw = await
|
|
5419
|
+
const raw = await import_node_fs22.promises.readFile(file, "utf8");
|
|
5076
5420
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
5077
5421
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
5078
5422
|
} catch {
|
|
@@ -5120,24 +5464,24 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
5120
5464
|
}
|
|
5121
5465
|
}
|
|
5122
5466
|
async function writeAtomically(target, contents) {
|
|
5123
|
-
await
|
|
5467
|
+
await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(target), { recursive: true });
|
|
5124
5468
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
5125
|
-
const fd = await
|
|
5469
|
+
const fd = await import_node_fs22.promises.open(tmp, "w");
|
|
5126
5470
|
try {
|
|
5127
5471
|
await fd.writeFile(contents, "utf8");
|
|
5128
5472
|
await fd.sync();
|
|
5129
5473
|
} finally {
|
|
5130
5474
|
await fd.close();
|
|
5131
5475
|
}
|
|
5132
|
-
await
|
|
5476
|
+
await import_node_fs22.promises.rename(tmp, target);
|
|
5133
5477
|
}
|
|
5134
5478
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
5135
5479
|
const deadline = Date.now() + timeoutMs;
|
|
5136
|
-
await
|
|
5480
|
+
await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(lockPath), { recursive: true });
|
|
5137
5481
|
let probedHolder = false;
|
|
5138
5482
|
while (true) {
|
|
5139
5483
|
try {
|
|
5140
|
-
const fd = await
|
|
5484
|
+
const fd = await import_node_fs22.promises.open(lockPath, "wx");
|
|
5141
5485
|
try {
|
|
5142
5486
|
await fd.writeFile(`${process.pid}
|
|
5143
5487
|
`, "utf8");
|
|
@@ -5162,7 +5506,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
5162
5506
|
}
|
|
5163
5507
|
}
|
|
5164
5508
|
async function releaseLock(lockPath) {
|
|
5165
|
-
await
|
|
5509
|
+
await import_node_fs22.promises.unlink(lockPath).catch(() => {
|
|
5166
5510
|
});
|
|
5167
5511
|
}
|
|
5168
5512
|
async function withLock(fn) {
|
|
@@ -5178,7 +5522,7 @@ async function readRegistry() {
|
|
|
5178
5522
|
const file = registryPath();
|
|
5179
5523
|
let raw;
|
|
5180
5524
|
try {
|
|
5181
|
-
raw = await
|
|
5525
|
+
raw = await import_node_fs22.promises.readFile(file, "utf8");
|
|
5182
5526
|
} catch (err) {
|
|
5183
5527
|
if (err.code === "ENOENT") {
|
|
5184
5528
|
return { version: 1, projects: [] };
|
|
@@ -5678,6 +6022,105 @@ function registerRoutes(scope, ctx) {
|
|
|
5678
6022
|
violations
|
|
5679
6023
|
};
|
|
5680
6024
|
});
|
|
6025
|
+
scope.get("/extend/list-uninstrumented", async (req2, reply) => {
|
|
6026
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6027
|
+
if (!proj) return;
|
|
6028
|
+
if (!proj.scanPath) {
|
|
6029
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6030
|
+
}
|
|
6031
|
+
try {
|
|
6032
|
+
const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
|
|
6033
|
+
return { libraries: results };
|
|
6034
|
+
} catch (err) {
|
|
6035
|
+
return reply.code(500).send({ error: err.message });
|
|
6036
|
+
}
|
|
6037
|
+
});
|
|
6038
|
+
scope.get("/extend/lookup", async (req2, reply) => {
|
|
6039
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6040
|
+
if (!proj) return;
|
|
6041
|
+
const { library, version } = req2.query;
|
|
6042
|
+
if (!library) {
|
|
6043
|
+
return reply.code(400).send({ error: "query parameter `library` is required" });
|
|
6044
|
+
}
|
|
6045
|
+
const result = lookupInstrumentation(library, version);
|
|
6046
|
+
if (!result) {
|
|
6047
|
+
return reply.code(404).send({ error: "library not found in registry", library });
|
|
6048
|
+
}
|
|
6049
|
+
return result;
|
|
6050
|
+
});
|
|
6051
|
+
scope.get("/extend/describe", async (req2, reply) => {
|
|
6052
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6053
|
+
if (!proj) return;
|
|
6054
|
+
if (!proj.scanPath) {
|
|
6055
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6056
|
+
}
|
|
6057
|
+
try {
|
|
6058
|
+
const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
|
|
6059
|
+
return state;
|
|
6060
|
+
} catch (err) {
|
|
6061
|
+
return reply.code(500).send({ error: err.message });
|
|
6062
|
+
}
|
|
6063
|
+
});
|
|
6064
|
+
scope.post("/extend/apply", async (req2, reply) => {
|
|
6065
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6066
|
+
if (!proj) return;
|
|
6067
|
+
if (!proj.scanPath) {
|
|
6068
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6069
|
+
}
|
|
6070
|
+
const { library, instrumentation_package, version, registration_snippet } = req2.body ?? {};
|
|
6071
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6072
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6073
|
+
}
|
|
6074
|
+
try {
|
|
6075
|
+
const result = await applyExtension(
|
|
6076
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6077
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6078
|
+
);
|
|
6079
|
+
return result;
|
|
6080
|
+
} catch (err) {
|
|
6081
|
+
return reply.code(500).send({ error: err.message });
|
|
6082
|
+
}
|
|
6083
|
+
});
|
|
6084
|
+
scope.post("/extend/dry-run", async (req2, reply) => {
|
|
6085
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6086
|
+
if (!proj) return;
|
|
6087
|
+
if (!proj.scanPath) {
|
|
6088
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6089
|
+
}
|
|
6090
|
+
const { library, instrumentation_package, version, registration_snippet } = req2.body ?? {};
|
|
6091
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6092
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6093
|
+
}
|
|
6094
|
+
try {
|
|
6095
|
+
const result = await dryRunExtension(
|
|
6096
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6097
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6098
|
+
);
|
|
6099
|
+
return result;
|
|
6100
|
+
} catch (err) {
|
|
6101
|
+
return reply.code(500).send({ error: err.message });
|
|
6102
|
+
}
|
|
6103
|
+
});
|
|
6104
|
+
scope.post("/extend/rollback", async (req2, reply) => {
|
|
6105
|
+
const proj = resolveProject(registry, req2, reply, ctx.bootstrap);
|
|
6106
|
+
if (!proj) return;
|
|
6107
|
+
if (!proj.scanPath) {
|
|
6108
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6109
|
+
}
|
|
6110
|
+
const { library } = req2.body ?? {};
|
|
6111
|
+
if (!library) {
|
|
6112
|
+
return reply.code(400).send({ error: "body must include library" });
|
|
6113
|
+
}
|
|
6114
|
+
try {
|
|
6115
|
+
const result = await rollbackExtension(
|
|
6116
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6117
|
+
{ library }
|
|
6118
|
+
);
|
|
6119
|
+
return result;
|
|
6120
|
+
} catch (err) {
|
|
6121
|
+
return reply.code(500).send({ error: err.message });
|
|
6122
|
+
}
|
|
6123
|
+
});
|
|
5681
6124
|
}
|
|
5682
6125
|
async function buildApi(opts) {
|
|
5683
6126
|
const app = (0, import_fastify.default)({ logger: false });
|
|
@@ -5788,8 +6231,8 @@ init_auth();
|
|
|
5788
6231
|
|
|
5789
6232
|
// src/unrouted.ts
|
|
5790
6233
|
init_cjs_shims();
|
|
5791
|
-
var
|
|
5792
|
-
var
|
|
6234
|
+
var import_node_fs23 = require("fs");
|
|
6235
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
5793
6236
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
5794
6237
|
return {
|
|
5795
6238
|
timestamp: now.toISOString(),
|
|
@@ -5799,21 +6242,21 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
5799
6242
|
};
|
|
5800
6243
|
}
|
|
5801
6244
|
async function appendUnroutedSpan(neatHome3, record) {
|
|
5802
|
-
const target =
|
|
5803
|
-
await
|
|
5804
|
-
await
|
|
6245
|
+
const target = import_node_path38.default.join(neatHome3, "errors.ndjson");
|
|
6246
|
+
await import_node_fs23.promises.mkdir(neatHome3, { recursive: true });
|
|
6247
|
+
await import_node_fs23.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
5805
6248
|
}
|
|
5806
6249
|
function unroutedErrorsPath(neatHome3) {
|
|
5807
|
-
return
|
|
6250
|
+
return import_node_path38.default.join(neatHome3, "errors.ndjson");
|
|
5808
6251
|
}
|
|
5809
6252
|
|
|
5810
6253
|
// src/daemon.ts
|
|
5811
6254
|
function neatHomeFor(opts) {
|
|
5812
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
6255
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path39.default.resolve(opts.neatHome);
|
|
5813
6256
|
const env = process.env.NEAT_HOME;
|
|
5814
|
-
if (env && env.length > 0) return
|
|
6257
|
+
if (env && env.length > 0) return import_node_path39.default.resolve(env);
|
|
5815
6258
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
5816
|
-
return
|
|
6259
|
+
return import_node_path39.default.join(home, ".neat");
|
|
5817
6260
|
}
|
|
5818
6261
|
function routeSpanToProject(serviceName, projects) {
|
|
5819
6262
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -5848,9 +6291,9 @@ function isTokenContained(needle, haystack) {
|
|
|
5848
6291
|
return tokens.includes(needle);
|
|
5849
6292
|
}
|
|
5850
6293
|
async function bootstrapProject(entry2) {
|
|
5851
|
-
const paths = pathsForProject(entry2.name,
|
|
6294
|
+
const paths = pathsForProject(entry2.name, import_node_path39.default.join(entry2.path, "neat-out"));
|
|
5852
6295
|
try {
|
|
5853
|
-
const stat = await
|
|
6296
|
+
const stat = await import_node_fs24.promises.stat(entry2.path);
|
|
5854
6297
|
if (!stat.isDirectory()) {
|
|
5855
6298
|
throw new Error(`registered path ${entry2.path} is not a directory`);
|
|
5856
6299
|
}
|
|
@@ -5916,13 +6359,13 @@ async function startDaemon(opts = {}) {
|
|
|
5916
6359
|
const home = neatHomeFor(opts);
|
|
5917
6360
|
const regPath = registryPath();
|
|
5918
6361
|
try {
|
|
5919
|
-
await
|
|
6362
|
+
await import_node_fs24.promises.access(regPath);
|
|
5920
6363
|
} catch {
|
|
5921
6364
|
throw new Error(
|
|
5922
6365
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
5923
6366
|
);
|
|
5924
6367
|
}
|
|
5925
|
-
const pidPath =
|
|
6368
|
+
const pidPath = import_node_path39.default.join(home, "neatd.pid");
|
|
5926
6369
|
await writeAtomically(pidPath, `${process.pid}
|
|
5927
6370
|
`);
|
|
5928
6371
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -6080,7 +6523,7 @@ async function startDaemon(opts = {}) {
|
|
|
6080
6523
|
}
|
|
6081
6524
|
if (restApp) await restApp.close().catch(() => {
|
|
6082
6525
|
});
|
|
6083
|
-
await
|
|
6526
|
+
await import_node_fs24.promises.unlink(pidPath).catch(() => {
|
|
6084
6527
|
});
|
|
6085
6528
|
throw new Error(
|
|
6086
6529
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
@@ -6186,7 +6629,7 @@ async function startDaemon(opts = {}) {
|
|
|
6186
6629
|
});
|
|
6187
6630
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
6188
6631
|
});
|
|
6189
|
-
await
|
|
6632
|
+
await import_node_fs24.promises.unlink(pidPath).catch(() => {
|
|
6190
6633
|
});
|
|
6191
6634
|
throw new Error(
|
|
6192
6635
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
@@ -6232,9 +6675,9 @@ async function startDaemon(opts = {}) {
|
|
|
6232
6675
|
let registryWatcher = null;
|
|
6233
6676
|
let reloadTimer = null;
|
|
6234
6677
|
try {
|
|
6235
|
-
const regDir =
|
|
6236
|
-
const regBase =
|
|
6237
|
-
registryWatcher = (0,
|
|
6678
|
+
const regDir = import_node_path39.default.dirname(regPath);
|
|
6679
|
+
const regBase = import_node_path39.default.basename(regPath);
|
|
6680
|
+
registryWatcher = (0, import_node_fs24.watch)(regDir, (_eventType, filename) => {
|
|
6238
6681
|
if (filename !== null && filename !== regBase) return;
|
|
6239
6682
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
6240
6683
|
reloadTimer = setTimeout(() => {
|
|
@@ -6277,7 +6720,7 @@ async function startDaemon(opts = {}) {
|
|
|
6277
6720
|
} catch {
|
|
6278
6721
|
}
|
|
6279
6722
|
}
|
|
6280
|
-
await
|
|
6723
|
+
await import_node_fs24.promises.unlink(pidPath).catch(() => {
|
|
6281
6724
|
});
|
|
6282
6725
|
};
|
|
6283
6726
|
return {
|
|
@@ -6297,9 +6740,9 @@ init_auth();
|
|
|
6297
6740
|
|
|
6298
6741
|
// src/web-spawn.ts
|
|
6299
6742
|
init_cjs_shims();
|
|
6300
|
-
var
|
|
6743
|
+
var import_node_child_process2 = require("child_process");
|
|
6301
6744
|
var import_node_net = __toESM(require("net"), 1);
|
|
6302
|
-
var
|
|
6745
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
6303
6746
|
var DEFAULT_WEB_PORT = 6328;
|
|
6304
6747
|
async function assertPortFree(port) {
|
|
6305
6748
|
await new Promise((resolve, reject) => {
|
|
@@ -6327,10 +6770,10 @@ function resolveWebPackageDir() {
|
|
|
6327
6770
|
eval("require")
|
|
6328
6771
|
);
|
|
6329
6772
|
const pkgJsonPath = req.resolve("@neat.is/web/package.json");
|
|
6330
|
-
return
|
|
6773
|
+
return import_node_path40.default.dirname(pkgJsonPath);
|
|
6331
6774
|
}
|
|
6332
6775
|
function resolveStandaloneServerEntry(webDir) {
|
|
6333
|
-
return
|
|
6776
|
+
return import_node_path40.default.join(webDir, ".next/standalone/packages/web/server.js");
|
|
6334
6777
|
}
|
|
6335
6778
|
async function spawnWebUI(restPort) {
|
|
6336
6779
|
const portRaw = process.env.NEAT_WEB_PORT;
|
|
@@ -6354,8 +6797,8 @@ async function spawnWebUI(restPort) {
|
|
|
6354
6797
|
HOSTNAME: process.env.HOSTNAME ?? "0.0.0.0",
|
|
6355
6798
|
NEAT_API_URL: process.env.NEAT_API_URL ?? `http://localhost:${restPort}`
|
|
6356
6799
|
};
|
|
6357
|
-
const child = (0,
|
|
6358
|
-
cwd:
|
|
6800
|
+
const child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
|
|
6801
|
+
cwd: import_node_path40.default.dirname(serverEntry),
|
|
6359
6802
|
env,
|
|
6360
6803
|
stdio: ["ignore", "inherit", "inherit"],
|
|
6361
6804
|
detached: false
|
|
@@ -6468,14 +6911,14 @@ function localVersion() {
|
|
|
6468
6911
|
}
|
|
6469
6912
|
function neatHome2() {
|
|
6470
6913
|
if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
|
|
6471
|
-
return
|
|
6914
|
+
return import_node_path41.default.resolve(process.env.NEAT_HOME);
|
|
6472
6915
|
}
|
|
6473
6916
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
6474
|
-
return
|
|
6917
|
+
return import_node_path41.default.join(home, ".neat");
|
|
6475
6918
|
}
|
|
6476
6919
|
async function readPid() {
|
|
6477
6920
|
try {
|
|
6478
|
-
const raw = await
|
|
6921
|
+
const raw = await import_node_fs25.promises.readFile(import_node_path41.default.join(neatHome2(), "neatd.pid"), "utf8");
|
|
6479
6922
|
const n = Number.parseInt(raw.trim(), 10);
|
|
6480
6923
|
return Number.isFinite(n) ? n : null;
|
|
6481
6924
|
} catch {
|