@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/index.cjs
CHANGED
|
@@ -57,9 +57,9 @@ function mountBearerAuth(app, opts) {
|
|
|
57
57
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
58
58
|
const publicRead = opts.publicRead === true;
|
|
59
59
|
app.addHook("preHandler", (req, reply, done) => {
|
|
60
|
-
const
|
|
60
|
+
const path40 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
61
61
|
for (const suffix of suffixes) {
|
|
62
|
-
if (
|
|
62
|
+
if (path40 === suffix || path40.endsWith(suffix)) {
|
|
63
63
|
done();
|
|
64
64
|
return;
|
|
65
65
|
}
|
|
@@ -185,8 +185,8 @@ function reshapeGrpcRequest(req) {
|
|
|
185
185
|
};
|
|
186
186
|
}
|
|
187
187
|
function resolveProtoRoot() {
|
|
188
|
-
const here =
|
|
189
|
-
return
|
|
188
|
+
const here = import_node_path36.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
189
|
+
return import_node_path36.default.resolve(here, "..", "proto");
|
|
190
190
|
}
|
|
191
191
|
function loadTraceService() {
|
|
192
192
|
const protoRoot = resolveProtoRoot();
|
|
@@ -254,13 +254,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
254
254
|
})
|
|
255
255
|
};
|
|
256
256
|
}
|
|
257
|
-
var import_node_url,
|
|
257
|
+
var import_node_url, import_node_path36, import_node_crypto2, grpc, protoLoader;
|
|
258
258
|
var init_otel_grpc = __esm({
|
|
259
259
|
"src/otel-grpc.ts"() {
|
|
260
260
|
"use strict";
|
|
261
261
|
init_cjs_shims();
|
|
262
262
|
import_node_url = require("url");
|
|
263
|
-
|
|
263
|
+
import_node_path36 = __toESM(require("path"), 1);
|
|
264
264
|
import_node_crypto2 = require("crypto");
|
|
265
265
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
266
266
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -370,10 +370,10 @@ function parseOtlpRequest(body) {
|
|
|
370
370
|
return out;
|
|
371
371
|
}
|
|
372
372
|
function loadProtoRoot() {
|
|
373
|
-
const here =
|
|
374
|
-
const protoRoot =
|
|
373
|
+
const here = import_node_path37.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
374
|
+
const protoRoot = import_node_path37.default.resolve(here, "..", "proto");
|
|
375
375
|
const root = new import_protobufjs.default.Root();
|
|
376
|
-
root.resolvePath = (_origin, target) =>
|
|
376
|
+
root.resolvePath = (_origin, target) => import_node_path37.default.resolve(protoRoot, target);
|
|
377
377
|
root.loadSync(
|
|
378
378
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
379
379
|
{ keepCase: true }
|
|
@@ -574,12 +574,12 @@ function logSpanHandler(span) {
|
|
|
574
574
|
`otel: ${span.service} ${span.name} parent=${parent} status=${status2}${db}`
|
|
575
575
|
);
|
|
576
576
|
}
|
|
577
|
-
var
|
|
577
|
+
var import_node_path37, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
578
578
|
var init_otel = __esm({
|
|
579
579
|
"src/otel.ts"() {
|
|
580
580
|
"use strict";
|
|
581
581
|
init_cjs_shims();
|
|
582
|
-
|
|
582
|
+
import_node_path37 = __toESM(require("path"), 1);
|
|
583
583
|
import_node_url2 = require("url");
|
|
584
584
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
585
585
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -1117,19 +1117,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1117
1117
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1118
1118
|
let best = { path: [start], edges: [] };
|
|
1119
1119
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1120
|
-
function step(node,
|
|
1121
|
-
if (
|
|
1122
|
-
best = { path: [...
|
|
1120
|
+
function step(node, path40, edges) {
|
|
1121
|
+
if (path40.length > best.path.length) {
|
|
1122
|
+
best = { path: [...path40], edges: [...edges] };
|
|
1123
1123
|
}
|
|
1124
|
-
if (
|
|
1124
|
+
if (path40.length - 1 >= maxDepth) return;
|
|
1125
1125
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1126
1126
|
for (const [srcId, edge] of incoming) {
|
|
1127
1127
|
if (visited.has(srcId)) continue;
|
|
1128
1128
|
visited.add(srcId);
|
|
1129
|
-
|
|
1129
|
+
path40.push(srcId);
|
|
1130
1130
|
edges.push(edge);
|
|
1131
|
-
step(srcId,
|
|
1132
|
-
|
|
1131
|
+
step(srcId, path40, edges);
|
|
1132
|
+
path40.pop();
|
|
1133
1133
|
edges.pop();
|
|
1134
1134
|
visited.delete(srcId);
|
|
1135
1135
|
}
|
|
@@ -1961,7 +1961,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
1961
1961
|
graph.addNode(id, node);
|
|
1962
1962
|
return id;
|
|
1963
1963
|
}
|
|
1964
|
-
function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
1964
|
+
function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
|
|
1965
1965
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
1966
1966
|
const id = makeObservedEdgeId(type, source, target);
|
|
1967
1967
|
if (graph.hasEdge(id)) {
|
|
@@ -1998,7 +1998,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
1998
1998
|
confidence: (0, import_types3.confidenceForObservedSignal)(signal),
|
|
1999
1999
|
lastObserved: ts,
|
|
2000
2000
|
callCount: 1,
|
|
2001
|
-
signal
|
|
2001
|
+
signal,
|
|
2002
|
+
// Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
|
|
2003
|
+
// Only set when code.filepath was present on the span — never fabricated.
|
|
2004
|
+
...evidence ? { evidence } : {}
|
|
2002
2005
|
};
|
|
2003
2006
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
2004
2007
|
return { edge, created: true };
|
|
@@ -2092,6 +2095,7 @@ async function handleSpan(ctx, span) {
|
|
|
2092
2095
|
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
2093
2096
|
const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
|
|
2094
2097
|
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
2098
|
+
const callSiteEvidence = callSite ? { file: callSite.relPath, ...callSite.line !== void 0 ? { line: callSite.line } : {} } : void 0;
|
|
2095
2099
|
let affectedNode = sourceId;
|
|
2096
2100
|
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2097
2101
|
if (span.dbSystem) {
|
|
@@ -2105,7 +2109,8 @@ async function handleSpan(ctx, span) {
|
|
|
2105
2109
|
observedSource(),
|
|
2106
2110
|
targetId,
|
|
2107
2111
|
ts,
|
|
2108
|
-
isError
|
|
2112
|
+
isError,
|
|
2113
|
+
callSiteEvidence
|
|
2109
2114
|
);
|
|
2110
2115
|
if (result) affectedNode = targetId;
|
|
2111
2116
|
}
|
|
@@ -2121,7 +2126,8 @@ async function handleSpan(ctx, span) {
|
|
|
2121
2126
|
observedSource(),
|
|
2122
2127
|
targetId,
|
|
2123
2128
|
ts,
|
|
2124
|
-
isError
|
|
2129
|
+
isError,
|
|
2130
|
+
callSiteEvidence
|
|
2125
2131
|
);
|
|
2126
2132
|
affectedNode = targetId;
|
|
2127
2133
|
resolvedViaAddress = true;
|
|
@@ -2133,7 +2139,8 @@ async function handleSpan(ctx, span) {
|
|
|
2133
2139
|
observedSource(),
|
|
2134
2140
|
frontierNodeId,
|
|
2135
2141
|
ts,
|
|
2136
|
-
isError
|
|
2142
|
+
isError,
|
|
2143
|
+
callSiteEvidence
|
|
2137
2144
|
);
|
|
2138
2145
|
affectedNode = frontierNodeId;
|
|
2139
2146
|
resolvedViaAddress = true;
|
|
@@ -3084,14 +3091,111 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
3084
3091
|
|
|
3085
3092
|
// src/extract/databases/index.ts
|
|
3086
3093
|
init_cjs_shims();
|
|
3087
|
-
var
|
|
3094
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
3095
|
+
var import_types8 = require("@neat.is/types");
|
|
3096
|
+
|
|
3097
|
+
// src/extract/calls/shared.ts
|
|
3098
|
+
init_cjs_shims();
|
|
3099
|
+
var import_node_fs10 = require("fs");
|
|
3100
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
3088
3101
|
var import_types7 = require("@neat.is/types");
|
|
3102
|
+
async function walkSourceFiles(dir) {
|
|
3103
|
+
const out = [];
|
|
3104
|
+
async function walk(current) {
|
|
3105
|
+
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3106
|
+
for (const entry of entries) {
|
|
3107
|
+
const full = import_node_path10.default.join(current, entry.name);
|
|
3108
|
+
if (entry.isDirectory()) {
|
|
3109
|
+
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3110
|
+
if (await isPythonVenvDir(full)) continue;
|
|
3111
|
+
await walk(full);
|
|
3112
|
+
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry.name))) {
|
|
3113
|
+
out.push(full);
|
|
3114
|
+
}
|
|
3115
|
+
}
|
|
3116
|
+
}
|
|
3117
|
+
await walk(dir);
|
|
3118
|
+
return out;
|
|
3119
|
+
}
|
|
3120
|
+
async function loadSourceFiles(dir) {
|
|
3121
|
+
const paths = await walkSourceFiles(dir);
|
|
3122
|
+
const out = [];
|
|
3123
|
+
for (const p of paths) {
|
|
3124
|
+
try {
|
|
3125
|
+
const content = await import_node_fs10.promises.readFile(p, "utf8");
|
|
3126
|
+
out.push({ path: p, content });
|
|
3127
|
+
} catch {
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
return out;
|
|
3131
|
+
}
|
|
3132
|
+
function lineOf(text, needle) {
|
|
3133
|
+
const idx = text.indexOf(needle);
|
|
3134
|
+
if (idx < 0) return 1;
|
|
3135
|
+
return text.slice(0, idx).split("\n").length;
|
|
3136
|
+
}
|
|
3137
|
+
function snippet(text, line) {
|
|
3138
|
+
const lines = text.split("\n");
|
|
3139
|
+
return (lines[line - 1] ?? "").trim();
|
|
3140
|
+
}
|
|
3141
|
+
function toPosix2(p) {
|
|
3142
|
+
return p.split("\\").join("/");
|
|
3143
|
+
}
|
|
3144
|
+
function languageForPath(relPath) {
|
|
3145
|
+
switch (import_node_path10.default.extname(relPath).toLowerCase()) {
|
|
3146
|
+
case ".py":
|
|
3147
|
+
return "python";
|
|
3148
|
+
case ".ts":
|
|
3149
|
+
case ".tsx":
|
|
3150
|
+
return "typescript";
|
|
3151
|
+
case ".js":
|
|
3152
|
+
case ".jsx":
|
|
3153
|
+
case ".mjs":
|
|
3154
|
+
case ".cjs":
|
|
3155
|
+
return "javascript";
|
|
3156
|
+
default:
|
|
3157
|
+
return void 0;
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3161
|
+
let nodesAdded = 0;
|
|
3162
|
+
let edgesAdded = 0;
|
|
3163
|
+
const fileNodeId = (0, import_types7.fileId)(serviceName, relPath);
|
|
3164
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3165
|
+
const language = languageForPath(relPath);
|
|
3166
|
+
const node = {
|
|
3167
|
+
id: fileNodeId,
|
|
3168
|
+
type: import_types7.NodeType.FileNode,
|
|
3169
|
+
service: serviceName,
|
|
3170
|
+
path: relPath,
|
|
3171
|
+
...language ? { language } : {},
|
|
3172
|
+
discoveredVia: "static"
|
|
3173
|
+
};
|
|
3174
|
+
graph.addNode(fileNodeId, node);
|
|
3175
|
+
nodesAdded++;
|
|
3176
|
+
}
|
|
3177
|
+
const containsId = (0, import_types7.extractedEdgeId)(serviceNodeId, fileNodeId, import_types7.EdgeType.CONTAINS);
|
|
3178
|
+
if (!graph.hasEdge(containsId)) {
|
|
3179
|
+
const edge = {
|
|
3180
|
+
id: containsId,
|
|
3181
|
+
source: serviceNodeId,
|
|
3182
|
+
target: fileNodeId,
|
|
3183
|
+
type: import_types7.EdgeType.CONTAINS,
|
|
3184
|
+
provenance: import_types7.Provenance.EXTRACTED,
|
|
3185
|
+
confidence: (0, import_types7.confidenceForExtracted)("structural"),
|
|
3186
|
+
evidence: { file: relPath }
|
|
3187
|
+
};
|
|
3188
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3189
|
+
edgesAdded++;
|
|
3190
|
+
}
|
|
3191
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3192
|
+
}
|
|
3089
3193
|
|
|
3090
3194
|
// src/extract/databases/db-config-yaml.ts
|
|
3091
3195
|
init_cjs_shims();
|
|
3092
|
-
var
|
|
3196
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
3093
3197
|
async function parse(serviceDir) {
|
|
3094
|
-
const yamlPath =
|
|
3198
|
+
const yamlPath = import_node_path11.default.join(serviceDir, "db-config.yaml");
|
|
3095
3199
|
if (!await exists(yamlPath)) return [];
|
|
3096
3200
|
const raw = await readYaml(yamlPath);
|
|
3097
3201
|
return [
|
|
@@ -3109,13 +3213,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
3109
3213
|
|
|
3110
3214
|
// src/extract/databases/dotenv.ts
|
|
3111
3215
|
init_cjs_shims();
|
|
3112
|
-
var
|
|
3113
|
-
var
|
|
3216
|
+
var import_node_fs12 = require("fs");
|
|
3217
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
3114
3218
|
|
|
3115
3219
|
// src/extract/databases/shared.ts
|
|
3116
3220
|
init_cjs_shims();
|
|
3117
|
-
var
|
|
3118
|
-
var
|
|
3221
|
+
var import_node_fs11 = require("fs");
|
|
3222
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
3119
3223
|
function schemeToEngine(scheme) {
|
|
3120
3224
|
const s = scheme.toLowerCase().split("+")[0];
|
|
3121
3225
|
switch (s) {
|
|
@@ -3154,14 +3258,14 @@ function parseConnectionString(url) {
|
|
|
3154
3258
|
}
|
|
3155
3259
|
async function readIfExists(filePath) {
|
|
3156
3260
|
try {
|
|
3157
|
-
return await
|
|
3261
|
+
return await import_node_fs11.promises.readFile(filePath, "utf8");
|
|
3158
3262
|
} catch {
|
|
3159
3263
|
return null;
|
|
3160
3264
|
}
|
|
3161
3265
|
}
|
|
3162
3266
|
async function findFirst(serviceDir, candidates) {
|
|
3163
3267
|
for (const rel of candidates) {
|
|
3164
|
-
const abs =
|
|
3268
|
+
const abs = import_node_path12.default.join(serviceDir, rel);
|
|
3165
3269
|
const content = await readIfExists(abs);
|
|
3166
3270
|
if (content !== null) return abs;
|
|
3167
3271
|
}
|
|
@@ -3212,15 +3316,15 @@ function parseDotenvLine(line) {
|
|
|
3212
3316
|
return { key, value };
|
|
3213
3317
|
}
|
|
3214
3318
|
async function parse2(serviceDir) {
|
|
3215
|
-
const entries = await
|
|
3319
|
+
const entries = await import_node_fs12.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
3216
3320
|
const configs = [];
|
|
3217
3321
|
const seen = /* @__PURE__ */ new Set();
|
|
3218
3322
|
for (const entry of entries) {
|
|
3219
3323
|
if (!entry.isFile()) continue;
|
|
3220
3324
|
const match = isConfigFile(entry.name);
|
|
3221
3325
|
if (!match.match || match.fileType !== "env") continue;
|
|
3222
|
-
const filePath =
|
|
3223
|
-
const content = await
|
|
3326
|
+
const filePath = import_node_path13.default.join(serviceDir, entry.name);
|
|
3327
|
+
const content = await import_node_fs12.promises.readFile(filePath, "utf8");
|
|
3224
3328
|
for (const line of content.split("\n")) {
|
|
3225
3329
|
const parsed = parseDotenvLine(line);
|
|
3226
3330
|
if (!parsed) continue;
|
|
@@ -3239,9 +3343,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
3239
3343
|
|
|
3240
3344
|
// src/extract/databases/prisma.ts
|
|
3241
3345
|
init_cjs_shims();
|
|
3242
|
-
var
|
|
3346
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
3243
3347
|
async function parse3(serviceDir) {
|
|
3244
|
-
const schemaPath =
|
|
3348
|
+
const schemaPath = import_node_path14.default.join(serviceDir, "prisma", "schema.prisma");
|
|
3245
3349
|
const content = await readIfExists(schemaPath);
|
|
3246
3350
|
if (!content) return [];
|
|
3247
3351
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -3373,10 +3477,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
3373
3477
|
|
|
3374
3478
|
// src/extract/databases/ormconfig.ts
|
|
3375
3479
|
init_cjs_shims();
|
|
3376
|
-
var
|
|
3480
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
3377
3481
|
async function parse6(serviceDir) {
|
|
3378
3482
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
3379
|
-
const abs =
|
|
3483
|
+
const abs = import_node_path15.default.join(serviceDir, candidate);
|
|
3380
3484
|
if (!await exists(abs)) continue;
|
|
3381
3485
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
3382
3486
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -3436,9 +3540,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
3436
3540
|
|
|
3437
3541
|
// src/extract/databases/sequelize.ts
|
|
3438
3542
|
init_cjs_shims();
|
|
3439
|
-
var
|
|
3543
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
3440
3544
|
async function parse8(serviceDir) {
|
|
3441
|
-
const configPath =
|
|
3545
|
+
const configPath = import_node_path16.default.join(serviceDir, "config", "config.json");
|
|
3442
3546
|
if (!await exists(configPath)) return [];
|
|
3443
3547
|
const raw = await readJson(configPath);
|
|
3444
3548
|
const out = [];
|
|
@@ -3465,7 +3569,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
3465
3569
|
|
|
3466
3570
|
// src/extract/databases/docker-compose.ts
|
|
3467
3571
|
init_cjs_shims();
|
|
3468
|
-
var
|
|
3572
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
3469
3573
|
function portFromService(svc) {
|
|
3470
3574
|
for (const raw of svc.ports ?? []) {
|
|
3471
3575
|
const str = String(raw);
|
|
@@ -3492,7 +3596,7 @@ function databaseFromEnv(svc) {
|
|
|
3492
3596
|
}
|
|
3493
3597
|
async function parse9(serviceDir) {
|
|
3494
3598
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3495
|
-
const abs =
|
|
3599
|
+
const abs = import_node_path17.default.join(serviceDir, name);
|
|
3496
3600
|
if (!await exists(abs)) continue;
|
|
3497
3601
|
const raw = await readYaml(abs);
|
|
3498
3602
|
if (!raw?.services) return [];
|
|
@@ -3533,8 +3637,8 @@ function compatibleDriversFor(engine) {
|
|
|
3533
3637
|
}
|
|
3534
3638
|
function toDatabaseNode(config) {
|
|
3535
3639
|
return {
|
|
3536
|
-
id: (0,
|
|
3537
|
-
type:
|
|
3640
|
+
id: (0, import_types8.databaseId)(config.host),
|
|
3641
|
+
type: import_types8.NodeType.DatabaseNode,
|
|
3538
3642
|
name: config.database || config.host,
|
|
3539
3643
|
engine: config.engine,
|
|
3540
3644
|
engineVersion: config.engineVersion,
|
|
@@ -3664,19 +3768,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3664
3768
|
discoveredVia: mergedDiscoveredVia
|
|
3665
3769
|
});
|
|
3666
3770
|
}
|
|
3771
|
+
const relConfigFile = toPosix2(import_node_path18.default.relative(service.dir, config.sourceFile));
|
|
3772
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
3773
|
+
graph,
|
|
3774
|
+
service.pkg.name,
|
|
3775
|
+
service.node.id,
|
|
3776
|
+
relConfigFile
|
|
3777
|
+
);
|
|
3778
|
+
nodesAdded += fn;
|
|
3779
|
+
edgesAdded += fe;
|
|
3780
|
+
const evidenceFile = toPosix2(import_node_path18.default.relative(scanPath, config.sourceFile));
|
|
3667
3781
|
const edge = {
|
|
3668
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3669
|
-
source:
|
|
3782
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types8.EdgeType.CONNECTS_TO),
|
|
3783
|
+
source: fileNodeId,
|
|
3670
3784
|
target: dbNode.id,
|
|
3671
|
-
type:
|
|
3672
|
-
provenance:
|
|
3673
|
-
confidence: (0,
|
|
3674
|
-
|
|
3675
|
-
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3676
|
-
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
3677
|
-
evidence: {
|
|
3678
|
-
file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
|
|
3679
|
-
}
|
|
3785
|
+
type: import_types8.EdgeType.CONNECTS_TO,
|
|
3786
|
+
provenance: import_types8.Provenance.EXTRACTED,
|
|
3787
|
+
confidence: (0, import_types8.confidenceForExtracted)("structural"),
|
|
3788
|
+
evidence: { file: evidenceFile }
|
|
3680
3789
|
};
|
|
3681
3790
|
if (!graph.hasEdge(edge.id)) {
|
|
3682
3791
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3702,15 +3811,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3702
3811
|
|
|
3703
3812
|
// src/extract/configs.ts
|
|
3704
3813
|
init_cjs_shims();
|
|
3705
|
-
var
|
|
3706
|
-
var
|
|
3707
|
-
var
|
|
3814
|
+
var import_node_fs13 = require("fs");
|
|
3815
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3816
|
+
var import_types9 = require("@neat.is/types");
|
|
3708
3817
|
async function walkConfigFiles(dir) {
|
|
3709
3818
|
const out = [];
|
|
3710
3819
|
async function walk(current) {
|
|
3711
|
-
const entries = await
|
|
3820
|
+
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true });
|
|
3712
3821
|
for (const entry of entries) {
|
|
3713
|
-
const full =
|
|
3822
|
+
const full = import_node_path19.default.join(current, entry.name);
|
|
3714
3823
|
if (entry.isDirectory()) {
|
|
3715
3824
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3716
3825
|
if (await isPythonVenvDir(full)) continue;
|
|
@@ -3729,26 +3838,35 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3729
3838
|
for (const service of services) {
|
|
3730
3839
|
const configFiles = await walkConfigFiles(service.dir);
|
|
3731
3840
|
for (const file of configFiles) {
|
|
3732
|
-
const relPath =
|
|
3841
|
+
const relPath = import_node_path19.default.relative(scanPath, file);
|
|
3733
3842
|
const node = {
|
|
3734
|
-
id: (0,
|
|
3735
|
-
type:
|
|
3736
|
-
name:
|
|
3843
|
+
id: (0, import_types9.configId)(relPath),
|
|
3844
|
+
type: import_types9.NodeType.ConfigNode,
|
|
3845
|
+
name: import_node_path19.default.basename(file),
|
|
3737
3846
|
path: relPath,
|
|
3738
|
-
fileType: isConfigFile(
|
|
3847
|
+
fileType: isConfigFile(import_node_path19.default.basename(file)).fileType
|
|
3739
3848
|
};
|
|
3740
3849
|
if (!graph.hasNode(node.id)) {
|
|
3741
3850
|
graph.addNode(node.id, node);
|
|
3742
3851
|
nodesAdded++;
|
|
3743
3852
|
}
|
|
3853
|
+
const relToService = toPosix2(import_node_path19.default.relative(service.dir, file));
|
|
3854
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
3855
|
+
graph,
|
|
3856
|
+
service.pkg.name,
|
|
3857
|
+
service.node.id,
|
|
3858
|
+
relToService
|
|
3859
|
+
);
|
|
3860
|
+
nodesAdded += fn;
|
|
3861
|
+
edgesAdded += fe;
|
|
3744
3862
|
const edge = {
|
|
3745
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3746
|
-
source:
|
|
3863
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types9.EdgeType.CONFIGURED_BY),
|
|
3864
|
+
source: fileNodeId,
|
|
3747
3865
|
target: node.id,
|
|
3748
|
-
type:
|
|
3749
|
-
provenance:
|
|
3750
|
-
confidence: (0,
|
|
3751
|
-
evidence: { file: relPath.split(
|
|
3866
|
+
type: import_types9.EdgeType.CONFIGURED_BY,
|
|
3867
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
3868
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3869
|
+
evidence: { file: relPath.split(import_node_path19.default.sep).join("/") }
|
|
3752
3870
|
};
|
|
3753
3871
|
if (!graph.hasEdge(edge.id)) {
|
|
3754
3872
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3770,105 +3888,6 @@ var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
|
3770
3888
|
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3771
3889
|
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3772
3890
|
var import_types10 = require("@neat.is/types");
|
|
3773
|
-
|
|
3774
|
-
// src/extract/calls/shared.ts
|
|
3775
|
-
init_cjs_shims();
|
|
3776
|
-
var import_node_fs13 = require("fs");
|
|
3777
|
-
var import_node_path19 = __toESM(require("path"), 1);
|
|
3778
|
-
var import_types9 = require("@neat.is/types");
|
|
3779
|
-
async function walkSourceFiles(dir) {
|
|
3780
|
-
const out = [];
|
|
3781
|
-
async function walk(current) {
|
|
3782
|
-
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3783
|
-
for (const entry of entries) {
|
|
3784
|
-
const full = import_node_path19.default.join(current, entry.name);
|
|
3785
|
-
if (entry.isDirectory()) {
|
|
3786
|
-
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
3787
|
-
if (await isPythonVenvDir(full)) continue;
|
|
3788
|
-
await walk(full);
|
|
3789
|
-
} else if (entry.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry.name))) {
|
|
3790
|
-
out.push(full);
|
|
3791
|
-
}
|
|
3792
|
-
}
|
|
3793
|
-
}
|
|
3794
|
-
await walk(dir);
|
|
3795
|
-
return out;
|
|
3796
|
-
}
|
|
3797
|
-
async function loadSourceFiles(dir) {
|
|
3798
|
-
const paths = await walkSourceFiles(dir);
|
|
3799
|
-
const out = [];
|
|
3800
|
-
for (const p of paths) {
|
|
3801
|
-
try {
|
|
3802
|
-
const content = await import_node_fs13.promises.readFile(p, "utf8");
|
|
3803
|
-
out.push({ path: p, content });
|
|
3804
|
-
} catch {
|
|
3805
|
-
}
|
|
3806
|
-
}
|
|
3807
|
-
return out;
|
|
3808
|
-
}
|
|
3809
|
-
function lineOf(text, needle) {
|
|
3810
|
-
const idx = text.indexOf(needle);
|
|
3811
|
-
if (idx < 0) return 1;
|
|
3812
|
-
return text.slice(0, idx).split("\n").length;
|
|
3813
|
-
}
|
|
3814
|
-
function snippet(text, line) {
|
|
3815
|
-
const lines = text.split("\n");
|
|
3816
|
-
return (lines[line - 1] ?? "").trim();
|
|
3817
|
-
}
|
|
3818
|
-
function toPosix2(p) {
|
|
3819
|
-
return p.split("\\").join("/");
|
|
3820
|
-
}
|
|
3821
|
-
function languageForPath(relPath) {
|
|
3822
|
-
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3823
|
-
case ".py":
|
|
3824
|
-
return "python";
|
|
3825
|
-
case ".ts":
|
|
3826
|
-
case ".tsx":
|
|
3827
|
-
return "typescript";
|
|
3828
|
-
case ".js":
|
|
3829
|
-
case ".jsx":
|
|
3830
|
-
case ".mjs":
|
|
3831
|
-
case ".cjs":
|
|
3832
|
-
return "javascript";
|
|
3833
|
-
default:
|
|
3834
|
-
return void 0;
|
|
3835
|
-
}
|
|
3836
|
-
}
|
|
3837
|
-
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3838
|
-
let nodesAdded = 0;
|
|
3839
|
-
let edgesAdded = 0;
|
|
3840
|
-
const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
|
|
3841
|
-
if (!graph.hasNode(fileNodeId)) {
|
|
3842
|
-
const language = languageForPath(relPath);
|
|
3843
|
-
const node = {
|
|
3844
|
-
id: fileNodeId,
|
|
3845
|
-
type: import_types9.NodeType.FileNode,
|
|
3846
|
-
service: serviceName,
|
|
3847
|
-
path: relPath,
|
|
3848
|
-
...language ? { language } : {},
|
|
3849
|
-
discoveredVia: "static"
|
|
3850
|
-
};
|
|
3851
|
-
graph.addNode(fileNodeId, node);
|
|
3852
|
-
nodesAdded++;
|
|
3853
|
-
}
|
|
3854
|
-
const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
|
|
3855
|
-
if (!graph.hasEdge(containsId)) {
|
|
3856
|
-
const edge = {
|
|
3857
|
-
id: containsId,
|
|
3858
|
-
source: serviceNodeId,
|
|
3859
|
-
target: fileNodeId,
|
|
3860
|
-
type: import_types9.EdgeType.CONTAINS,
|
|
3861
|
-
provenance: import_types9.Provenance.EXTRACTED,
|
|
3862
|
-
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3863
|
-
evidence: { file: relPath }
|
|
3864
|
-
};
|
|
3865
|
-
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3866
|
-
edgesAdded++;
|
|
3867
|
-
}
|
|
3868
|
-
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3869
|
-
}
|
|
3870
|
-
|
|
3871
|
-
// src/extract/calls/http.ts
|
|
3872
3891
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
3873
3892
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
3874
3893
|
function isInsideJsxExternalLink(node) {
|
|
@@ -4152,6 +4171,7 @@ var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-
|
|
|
4152
4171
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
4153
4172
|
function isLikelyAddress(value) {
|
|
4154
4173
|
if (!value) return false;
|
|
4174
|
+
if (/\s/.test(value) || value.startsWith("{")) return false;
|
|
4155
4175
|
return /:\d{2,5}$/.test(value) || value.includes(".");
|
|
4156
4176
|
}
|
|
4157
4177
|
function normaliseForMatch(s) {
|
|
@@ -4463,17 +4483,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4463
4483
|
graph.addNode(node.id, node);
|
|
4464
4484
|
nodesAdded++;
|
|
4465
4485
|
}
|
|
4466
|
-
const
|
|
4486
|
+
const relDockerfile = toPosix2(import_node_path26.default.relative(service.dir, dockerfilePath));
|
|
4487
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4488
|
+
graph,
|
|
4489
|
+
service.pkg.name,
|
|
4490
|
+
service.node.id,
|
|
4491
|
+
relDockerfile
|
|
4492
|
+
);
|
|
4493
|
+
nodesAdded += fn;
|
|
4494
|
+
edgesAdded += fe;
|
|
4495
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types18.EdgeType.RUNS_ON);
|
|
4467
4496
|
if (!graph.hasEdge(edgeId)) {
|
|
4468
4497
|
const edge = {
|
|
4469
4498
|
id: edgeId,
|
|
4470
|
-
source:
|
|
4499
|
+
source: fileNodeId,
|
|
4471
4500
|
target: node.id,
|
|
4472
4501
|
type: import_types18.EdgeType.RUNS_ON,
|
|
4473
4502
|
provenance: import_types18.Provenance.EXTRACTED,
|
|
4474
4503
|
confidence: (0, import_types18.confidenceForExtracted)("structural"),
|
|
4475
4504
|
evidence: {
|
|
4476
|
-
file: import_node_path26.default.relative(scanPath, dockerfilePath)
|
|
4505
|
+
file: toPosix2(import_node_path26.default.relative(scanPath, dockerfilePath))
|
|
4477
4506
|
}
|
|
4478
4507
|
};
|
|
4479
4508
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4812,6 +4841,321 @@ var import_fastify = __toESM(require("fastify"), 1);
|
|
|
4812
4841
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
4813
4842
|
var import_types23 = require("@neat.is/types");
|
|
4814
4843
|
|
|
4844
|
+
// src/extend/index.ts
|
|
4845
|
+
init_cjs_shims();
|
|
4846
|
+
var import_node_fs20 = require("fs");
|
|
4847
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
4848
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
4849
|
+
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
4850
|
+
|
|
4851
|
+
// src/installers/package-manager.ts
|
|
4852
|
+
init_cjs_shims();
|
|
4853
|
+
var import_node_fs19 = require("fs");
|
|
4854
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
4855
|
+
var import_node_child_process = require("child_process");
|
|
4856
|
+
var LOCKFILE_PRIORITY = [
|
|
4857
|
+
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
4858
|
+
{ lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
|
|
4859
|
+
{ lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
|
|
4860
|
+
{
|
|
4861
|
+
lockfile: "package-lock.json",
|
|
4862
|
+
pm: "npm",
|
|
4863
|
+
args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
|
|
4864
|
+
}
|
|
4865
|
+
];
|
|
4866
|
+
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
4867
|
+
async function exists2(p) {
|
|
4868
|
+
try {
|
|
4869
|
+
await import_node_fs19.promises.access(p);
|
|
4870
|
+
return true;
|
|
4871
|
+
} catch {
|
|
4872
|
+
return false;
|
|
4873
|
+
}
|
|
4874
|
+
}
|
|
4875
|
+
async function detectPackageManager(serviceDir) {
|
|
4876
|
+
let dir = import_node_path32.default.resolve(serviceDir);
|
|
4877
|
+
const stops = /* @__PURE__ */ new Set();
|
|
4878
|
+
for (let i = 0; i < 64; i++) {
|
|
4879
|
+
if (stops.has(dir)) break;
|
|
4880
|
+
stops.add(dir);
|
|
4881
|
+
for (const candidate of LOCKFILE_PRIORITY) {
|
|
4882
|
+
const lockPath = import_node_path32.default.join(dir, candidate.lockfile);
|
|
4883
|
+
if (await exists2(lockPath)) {
|
|
4884
|
+
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
4885
|
+
}
|
|
4886
|
+
}
|
|
4887
|
+
const parent = import_node_path32.default.dirname(dir);
|
|
4888
|
+
if (parent === dir) break;
|
|
4889
|
+
dir = parent;
|
|
4890
|
+
}
|
|
4891
|
+
return { pm: "npm", cwd: import_node_path32.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
4892
|
+
}
|
|
4893
|
+
async function runPackageManagerInstall(cmd) {
|
|
4894
|
+
return new Promise((resolve) => {
|
|
4895
|
+
const child = (0, import_node_child_process.spawn)(cmd.pm, cmd.args, {
|
|
4896
|
+
cwd: cmd.cwd,
|
|
4897
|
+
// Inherit PATH + HOME so the user's installed managers resolve.
|
|
4898
|
+
env: process.env,
|
|
4899
|
+
// `false` keeps the parent in control of cleanup if the orchestrator
|
|
4900
|
+
// exits before install finishes. Cross-platform-safe.
|
|
4901
|
+
shell: false,
|
|
4902
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
4903
|
+
});
|
|
4904
|
+
let stderr = "";
|
|
4905
|
+
child.stderr?.on("data", (chunk) => {
|
|
4906
|
+
stderr += chunk.toString("utf8");
|
|
4907
|
+
});
|
|
4908
|
+
child.on("error", (err) => {
|
|
4909
|
+
resolve({
|
|
4910
|
+
pm: cmd.pm,
|
|
4911
|
+
cwd: cmd.cwd,
|
|
4912
|
+
args: cmd.args,
|
|
4913
|
+
exitCode: 127,
|
|
4914
|
+
stderr: stderr + `
|
|
4915
|
+
${err.message}`
|
|
4916
|
+
});
|
|
4917
|
+
});
|
|
4918
|
+
child.on("close", (code) => {
|
|
4919
|
+
resolve({
|
|
4920
|
+
pm: cmd.pm,
|
|
4921
|
+
cwd: cmd.cwd,
|
|
4922
|
+
args: cmd.args,
|
|
4923
|
+
exitCode: code ?? 1,
|
|
4924
|
+
stderr: stderr.trim()
|
|
4925
|
+
});
|
|
4926
|
+
});
|
|
4927
|
+
});
|
|
4928
|
+
}
|
|
4929
|
+
|
|
4930
|
+
// src/extend/index.ts
|
|
4931
|
+
async function fileExists(p) {
|
|
4932
|
+
try {
|
|
4933
|
+
await import_node_fs20.promises.access(p);
|
|
4934
|
+
return true;
|
|
4935
|
+
} catch {
|
|
4936
|
+
return false;
|
|
4937
|
+
}
|
|
4938
|
+
}
|
|
4939
|
+
async function readPackageJson(scanPath) {
|
|
4940
|
+
const pkgPath = import_node_path33.default.join(scanPath, "package.json");
|
|
4941
|
+
const raw = await import_node_fs20.promises.readFile(pkgPath, "utf8");
|
|
4942
|
+
return JSON.parse(raw);
|
|
4943
|
+
}
|
|
4944
|
+
async function findHookFiles(scanPath) {
|
|
4945
|
+
const entries = await import_node_fs20.promises.readdir(scanPath);
|
|
4946
|
+
return entries.filter(
|
|
4947
|
+
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
4948
|
+
).sort();
|
|
4949
|
+
}
|
|
4950
|
+
function extendLogPath() {
|
|
4951
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path33.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
4952
|
+
}
|
|
4953
|
+
async function appendExtendLog(entry) {
|
|
4954
|
+
const logPath = extendLogPath();
|
|
4955
|
+
await import_node_fs20.promises.mkdir(import_node_path33.default.dirname(logPath), { recursive: true });
|
|
4956
|
+
await import_node_fs20.promises.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
4957
|
+
}
|
|
4958
|
+
function splicedContent(fileContent, snippet2) {
|
|
4959
|
+
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
4960
|
+
return fileContent.replace("__INSTRUMENTATION_BLOCK__", `${snippet2}
|
|
4961
|
+
__INSTRUMENTATION_BLOCK__`);
|
|
4962
|
+
}
|
|
4963
|
+
const lines = fileContent.split("\n");
|
|
4964
|
+
let lastPushIdx = -1;
|
|
4965
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4966
|
+
if (lines[i].includes("instrumentations.push(")) lastPushIdx = i;
|
|
4967
|
+
}
|
|
4968
|
+
if (lastPushIdx >= 0) {
|
|
4969
|
+
lines.splice(lastPushIdx + 1, 0, snippet2);
|
|
4970
|
+
return lines.join("\n");
|
|
4971
|
+
}
|
|
4972
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4973
|
+
if (lines[i].includes("new NodeSDK(")) {
|
|
4974
|
+
lines.splice(i, 0, snippet2);
|
|
4975
|
+
return lines.join("\n");
|
|
4976
|
+
}
|
|
4977
|
+
}
|
|
4978
|
+
return null;
|
|
4979
|
+
}
|
|
4980
|
+
async function listUninstrumented(ctx) {
|
|
4981
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
4982
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
4983
|
+
const results = [];
|
|
4984
|
+
for (const [library, installedVersion] of Object.entries(allDeps)) {
|
|
4985
|
+
const entry = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
4986
|
+
if (!entry) continue;
|
|
4987
|
+
if (entry.coverage === "bundled" || entry.coverage === "http-only") continue;
|
|
4988
|
+
results.push({
|
|
4989
|
+
library,
|
|
4990
|
+
coverage: entry.coverage,
|
|
4991
|
+
installedVersion,
|
|
4992
|
+
instrumentation_package: entry.instrumentation_package,
|
|
4993
|
+
package_version: entry.package_version,
|
|
4994
|
+
registration: entry.registration,
|
|
4995
|
+
notes: entry.notes
|
|
4996
|
+
});
|
|
4997
|
+
}
|
|
4998
|
+
return results;
|
|
4999
|
+
}
|
|
5000
|
+
function lookupInstrumentation(library, installedVersion) {
|
|
5001
|
+
const entry = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
5002
|
+
if (!entry) return null;
|
|
5003
|
+
return {
|
|
5004
|
+
library: entry.library,
|
|
5005
|
+
coverage: entry.coverage,
|
|
5006
|
+
instrumentation_package: entry.instrumentation_package,
|
|
5007
|
+
package_version: entry.package_version,
|
|
5008
|
+
registration: entry.registration,
|
|
5009
|
+
notes: entry.notes
|
|
5010
|
+
};
|
|
5011
|
+
}
|
|
5012
|
+
async function describeProjectInstrumentation(ctx) {
|
|
5013
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5014
|
+
const envNeat = await fileExists(import_node_path33.default.join(ctx.scanPath, ".env.neat"));
|
|
5015
|
+
const registryInstrPackages = new Set(
|
|
5016
|
+
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
5017
|
+
);
|
|
5018
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5019
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5020
|
+
const installedDeps = {};
|
|
5021
|
+
for (const [key, version] of Object.entries(allDeps)) {
|
|
5022
|
+
if (key.startsWith("@opentelemetry/") || registryInstrPackages.has(key)) {
|
|
5023
|
+
installedDeps[key] = version;
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
return { hookFiles, envNeat, installedDeps };
|
|
5027
|
+
}
|
|
5028
|
+
async function applyExtension(ctx, args, options) {
|
|
5029
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5030
|
+
if (hookFiles.length === 0) {
|
|
5031
|
+
throw new Error(
|
|
5032
|
+
`No instrumentation hook files found in ${ctx.scanPath}. Run \`neat init\` first.`
|
|
5033
|
+
);
|
|
5034
|
+
}
|
|
5035
|
+
for (const file of hookFiles) {
|
|
5036
|
+
const content = await import_node_fs20.promises.readFile(import_node_path33.default.join(ctx.scanPath, file), "utf8");
|
|
5037
|
+
if (content.includes(args.registration_snippet)) {
|
|
5038
|
+
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
5039
|
+
}
|
|
5040
|
+
}
|
|
5041
|
+
const primaryFile = hookFiles[0];
|
|
5042
|
+
const primaryPath = import_node_path33.default.join(ctx.scanPath, primaryFile);
|
|
5043
|
+
const filesTouched = [];
|
|
5044
|
+
const depsAdded = [];
|
|
5045
|
+
const pkgPath = import_node_path33.default.join(ctx.scanPath, "package.json");
|
|
5046
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5047
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5048
|
+
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
5049
|
+
await import_node_fs20.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5050
|
+
filesTouched.push("package.json");
|
|
5051
|
+
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
5052
|
+
}
|
|
5053
|
+
const hookContent = await import_node_fs20.promises.readFile(primaryPath, "utf8");
|
|
5054
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5055
|
+
if (!patched) {
|
|
5056
|
+
throw new Error(
|
|
5057
|
+
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
5058
|
+
);
|
|
5059
|
+
}
|
|
5060
|
+
await import_node_fs20.promises.writeFile(primaryPath, patched, "utf8");
|
|
5061
|
+
filesTouched.push(primaryFile);
|
|
5062
|
+
const cmd = await detectPackageManager(ctx.scanPath);
|
|
5063
|
+
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
5064
|
+
const install = await installer(cmd);
|
|
5065
|
+
const installOutput = install.exitCode === 0 ? `${cmd.pm} install succeeded` : install.stderr || `${cmd.pm} install failed (exit ${install.exitCode})`;
|
|
5066
|
+
await appendExtendLog({
|
|
5067
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5068
|
+
project: ctx.project,
|
|
5069
|
+
library: args.library,
|
|
5070
|
+
instrumentation_package: args.instrumentation_package,
|
|
5071
|
+
version: args.version,
|
|
5072
|
+
registration_snippet: args.registration_snippet,
|
|
5073
|
+
filesTouched,
|
|
5074
|
+
depsAdded,
|
|
5075
|
+
installOutput
|
|
5076
|
+
});
|
|
5077
|
+
return { library: args.library, filesTouched, depsAdded, installOutput, alreadyApplied: false };
|
|
5078
|
+
}
|
|
5079
|
+
async function dryRunExtension(ctx, args) {
|
|
5080
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5081
|
+
if (hookFiles.length === 0) {
|
|
5082
|
+
return {
|
|
5083
|
+
library: args.library,
|
|
5084
|
+
filesTouched: [],
|
|
5085
|
+
depsToAdd: [],
|
|
5086
|
+
packageJsonPatch: {},
|
|
5087
|
+
templatePatch: "No hook files found. Run 'neat init' first."
|
|
5088
|
+
};
|
|
5089
|
+
}
|
|
5090
|
+
for (const file of hookFiles) {
|
|
5091
|
+
const content = await import_node_fs20.promises.readFile(import_node_path33.default.join(ctx.scanPath, file), "utf8");
|
|
5092
|
+
if (content.includes(args.registration_snippet)) {
|
|
5093
|
+
return {
|
|
5094
|
+
library: args.library,
|
|
5095
|
+
filesTouched: [],
|
|
5096
|
+
depsToAdd: [],
|
|
5097
|
+
packageJsonPatch: {},
|
|
5098
|
+
templatePatch: "Already applied \u2014 no changes would be made."
|
|
5099
|
+
};
|
|
5100
|
+
}
|
|
5101
|
+
}
|
|
5102
|
+
const primaryFile = hookFiles[0];
|
|
5103
|
+
const filesTouched = [];
|
|
5104
|
+
const depsToAdd = [];
|
|
5105
|
+
let packageJsonPatch = {};
|
|
5106
|
+
let templatePatch = "";
|
|
5107
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5108
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5109
|
+
packageJsonPatch = { dependencies: { [args.instrumentation_package]: args.version } };
|
|
5110
|
+
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
5111
|
+
filesTouched.push("package.json");
|
|
5112
|
+
}
|
|
5113
|
+
const hookContent = await import_node_fs20.promises.readFile(import_node_path33.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
5114
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5115
|
+
if (patched) {
|
|
5116
|
+
filesTouched.push(primaryFile);
|
|
5117
|
+
templatePatch = `+ ${args.registration_snippet}`;
|
|
5118
|
+
} else {
|
|
5119
|
+
templatePatch = "Could not find insertion point in hook file.";
|
|
5120
|
+
}
|
|
5121
|
+
return { library: args.library, filesTouched, depsToAdd, packageJsonPatch, templatePatch };
|
|
5122
|
+
}
|
|
5123
|
+
async function rollbackExtension(ctx, args) {
|
|
5124
|
+
const logPath = extendLogPath();
|
|
5125
|
+
if (!await fileExists(logPath)) {
|
|
5126
|
+
return { undone: false, message: "no apply found for library" };
|
|
5127
|
+
}
|
|
5128
|
+
const raw = await import_node_fs20.promises.readFile(logPath, "utf8");
|
|
5129
|
+
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
5130
|
+
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
5131
|
+
if (!match) {
|
|
5132
|
+
return { undone: false, message: "no apply found for library" };
|
|
5133
|
+
}
|
|
5134
|
+
const pkgPath = import_node_path33.default.join(ctx.scanPath, "package.json");
|
|
5135
|
+
if (await fileExists(pkgPath)) {
|
|
5136
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5137
|
+
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
5138
|
+
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
5139
|
+
pkg.dependencies = rest;
|
|
5140
|
+
await import_node_fs20.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5141
|
+
}
|
|
5142
|
+
}
|
|
5143
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5144
|
+
for (const file of hookFiles) {
|
|
5145
|
+
const filePath = import_node_path33.default.join(ctx.scanPath, file);
|
|
5146
|
+
const content = await import_node_fs20.promises.readFile(filePath, "utf8");
|
|
5147
|
+
if (content.includes(match.registration_snippet)) {
|
|
5148
|
+
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
5149
|
+
await import_node_fs20.promises.writeFile(filePath, filtered, "utf8");
|
|
5150
|
+
break;
|
|
5151
|
+
}
|
|
5152
|
+
}
|
|
5153
|
+
return {
|
|
5154
|
+
undone: true,
|
|
5155
|
+
message: `rolled back ${match.library} (${match.instrumentation_package})`
|
|
5156
|
+
};
|
|
5157
|
+
}
|
|
5158
|
+
|
|
4815
5159
|
// src/divergences.ts
|
|
4816
5160
|
init_cjs_shims();
|
|
4817
5161
|
var import_types21 = require("@neat.is/types");
|
|
@@ -5052,7 +5396,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
5052
5396
|
|
|
5053
5397
|
// src/diff.ts
|
|
5054
5398
|
init_cjs_shims();
|
|
5055
|
-
var
|
|
5399
|
+
var import_node_fs21 = require("fs");
|
|
5056
5400
|
async function loadSnapshotForDiff(target) {
|
|
5057
5401
|
if (/^https?:\/\//i.test(target)) {
|
|
5058
5402
|
const res = await fetch(target);
|
|
@@ -5061,7 +5405,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
5061
5405
|
}
|
|
5062
5406
|
return await res.json();
|
|
5063
5407
|
}
|
|
5064
|
-
const raw = await
|
|
5408
|
+
const raw = await import_node_fs21.promises.readFile(target, "utf8");
|
|
5065
5409
|
return JSON.parse(raw);
|
|
5066
5410
|
}
|
|
5067
5411
|
function indexEntries(entries) {
|
|
@@ -5129,23 +5473,23 @@ function canonicalJson(value) {
|
|
|
5129
5473
|
|
|
5130
5474
|
// src/projects.ts
|
|
5131
5475
|
init_cjs_shims();
|
|
5132
|
-
var
|
|
5476
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
5133
5477
|
function pathsForProject(project, baseDir) {
|
|
5134
5478
|
if (project === DEFAULT_PROJECT) {
|
|
5135
5479
|
return {
|
|
5136
|
-
snapshotPath:
|
|
5137
|
-
errorsPath:
|
|
5138
|
-
staleEventsPath:
|
|
5139
|
-
embeddingsCachePath:
|
|
5140
|
-
policyViolationsPath:
|
|
5480
|
+
snapshotPath: import_node_path34.default.join(baseDir, "graph.json"),
|
|
5481
|
+
errorsPath: import_node_path34.default.join(baseDir, "errors.ndjson"),
|
|
5482
|
+
staleEventsPath: import_node_path34.default.join(baseDir, "stale-events.ndjson"),
|
|
5483
|
+
embeddingsCachePath: import_node_path34.default.join(baseDir, "embeddings.json"),
|
|
5484
|
+
policyViolationsPath: import_node_path34.default.join(baseDir, "policy-violations.ndjson")
|
|
5141
5485
|
};
|
|
5142
5486
|
}
|
|
5143
5487
|
return {
|
|
5144
|
-
snapshotPath:
|
|
5145
|
-
errorsPath:
|
|
5146
|
-
staleEventsPath:
|
|
5147
|
-
embeddingsCachePath:
|
|
5148
|
-
policyViolationsPath:
|
|
5488
|
+
snapshotPath: import_node_path34.default.join(baseDir, `${project}.json`),
|
|
5489
|
+
errorsPath: import_node_path34.default.join(baseDir, `errors.${project}.ndjson`),
|
|
5490
|
+
staleEventsPath: import_node_path34.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
5491
|
+
embeddingsCachePath: import_node_path34.default.join(baseDir, `embeddings.${project}.json`),
|
|
5492
|
+
policyViolationsPath: import_node_path34.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
5149
5493
|
};
|
|
5150
5494
|
}
|
|
5151
5495
|
var Projects = class {
|
|
@@ -5181,25 +5525,25 @@ var Projects = class {
|
|
|
5181
5525
|
|
|
5182
5526
|
// src/registry.ts
|
|
5183
5527
|
init_cjs_shims();
|
|
5184
|
-
var
|
|
5185
|
-
var
|
|
5186
|
-
var
|
|
5528
|
+
var import_node_fs22 = require("fs");
|
|
5529
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
5530
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
5187
5531
|
var import_types22 = require("@neat.is/types");
|
|
5188
5532
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
5189
5533
|
var LOCK_RETRY_MS = 50;
|
|
5190
5534
|
function neatHome() {
|
|
5191
5535
|
const override = process.env.NEAT_HOME;
|
|
5192
|
-
if (override && override.length > 0) return
|
|
5193
|
-
return
|
|
5536
|
+
if (override && override.length > 0) return import_node_path35.default.resolve(override);
|
|
5537
|
+
return import_node_path35.default.join(import_node_os3.default.homedir(), ".neat");
|
|
5194
5538
|
}
|
|
5195
5539
|
function registryPath() {
|
|
5196
|
-
return
|
|
5540
|
+
return import_node_path35.default.join(neatHome(), "projects.json");
|
|
5197
5541
|
}
|
|
5198
5542
|
function registryLockPath() {
|
|
5199
|
-
return
|
|
5543
|
+
return import_node_path35.default.join(neatHome(), "projects.json.lock");
|
|
5200
5544
|
}
|
|
5201
5545
|
function daemonPidPath() {
|
|
5202
|
-
return
|
|
5546
|
+
return import_node_path35.default.join(neatHome(), "neatd.pid");
|
|
5203
5547
|
}
|
|
5204
5548
|
function isPidAliveDefault(pid) {
|
|
5205
5549
|
try {
|
|
@@ -5211,7 +5555,7 @@ function isPidAliveDefault(pid) {
|
|
|
5211
5555
|
}
|
|
5212
5556
|
async function readPidFile(file) {
|
|
5213
5557
|
try {
|
|
5214
|
-
const raw = await
|
|
5558
|
+
const raw = await import_node_fs22.promises.readFile(file, "utf8");
|
|
5215
5559
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
5216
5560
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
5217
5561
|
} catch {
|
|
@@ -5259,32 +5603,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
5259
5603
|
}
|
|
5260
5604
|
}
|
|
5261
5605
|
async function normalizeProjectPath(input) {
|
|
5262
|
-
const resolved =
|
|
5606
|
+
const resolved = import_node_path35.default.resolve(input);
|
|
5263
5607
|
try {
|
|
5264
|
-
return await
|
|
5608
|
+
return await import_node_fs22.promises.realpath(resolved);
|
|
5265
5609
|
} catch {
|
|
5266
5610
|
return resolved;
|
|
5267
5611
|
}
|
|
5268
5612
|
}
|
|
5269
5613
|
async function writeAtomically(target, contents) {
|
|
5270
|
-
await
|
|
5614
|
+
await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(target), { recursive: true });
|
|
5271
5615
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
5272
|
-
const fd = await
|
|
5616
|
+
const fd = await import_node_fs22.promises.open(tmp, "w");
|
|
5273
5617
|
try {
|
|
5274
5618
|
await fd.writeFile(contents, "utf8");
|
|
5275
5619
|
await fd.sync();
|
|
5276
5620
|
} finally {
|
|
5277
5621
|
await fd.close();
|
|
5278
5622
|
}
|
|
5279
|
-
await
|
|
5623
|
+
await import_node_fs22.promises.rename(tmp, target);
|
|
5280
5624
|
}
|
|
5281
5625
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
5282
5626
|
const deadline = Date.now() + timeoutMs;
|
|
5283
|
-
await
|
|
5627
|
+
await import_node_fs22.promises.mkdir(import_node_path35.default.dirname(lockPath), { recursive: true });
|
|
5284
5628
|
let probedHolder = false;
|
|
5285
5629
|
while (true) {
|
|
5286
5630
|
try {
|
|
5287
|
-
const fd = await
|
|
5631
|
+
const fd = await import_node_fs22.promises.open(lockPath, "wx");
|
|
5288
5632
|
try {
|
|
5289
5633
|
await fd.writeFile(`${process.pid}
|
|
5290
5634
|
`, "utf8");
|
|
@@ -5309,7 +5653,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
5309
5653
|
}
|
|
5310
5654
|
}
|
|
5311
5655
|
async function releaseLock(lockPath) {
|
|
5312
|
-
await
|
|
5656
|
+
await import_node_fs22.promises.unlink(lockPath).catch(() => {
|
|
5313
5657
|
});
|
|
5314
5658
|
}
|
|
5315
5659
|
async function withLock(fn) {
|
|
@@ -5325,7 +5669,7 @@ async function readRegistry() {
|
|
|
5325
5669
|
const file = registryPath();
|
|
5326
5670
|
let raw;
|
|
5327
5671
|
try {
|
|
5328
|
-
raw = await
|
|
5672
|
+
raw = await import_node_fs22.promises.readFile(file, "utf8");
|
|
5329
5673
|
} catch (err) {
|
|
5330
5674
|
if (err.code === "ENOENT") {
|
|
5331
5675
|
return { version: 1, projects: [] };
|
|
@@ -5875,6 +6219,105 @@ function registerRoutes(scope, ctx) {
|
|
|
5875
6219
|
violations
|
|
5876
6220
|
};
|
|
5877
6221
|
});
|
|
6222
|
+
scope.get("/extend/list-uninstrumented", async (req, reply) => {
|
|
6223
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6224
|
+
if (!proj) return;
|
|
6225
|
+
if (!proj.scanPath) {
|
|
6226
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6227
|
+
}
|
|
6228
|
+
try {
|
|
6229
|
+
const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
|
|
6230
|
+
return { libraries: results };
|
|
6231
|
+
} catch (err) {
|
|
6232
|
+
return reply.code(500).send({ error: err.message });
|
|
6233
|
+
}
|
|
6234
|
+
});
|
|
6235
|
+
scope.get("/extend/lookup", async (req, reply) => {
|
|
6236
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6237
|
+
if (!proj) return;
|
|
6238
|
+
const { library, version } = req.query;
|
|
6239
|
+
if (!library) {
|
|
6240
|
+
return reply.code(400).send({ error: "query parameter `library` is required" });
|
|
6241
|
+
}
|
|
6242
|
+
const result = lookupInstrumentation(library, version);
|
|
6243
|
+
if (!result) {
|
|
6244
|
+
return reply.code(404).send({ error: "library not found in registry", library });
|
|
6245
|
+
}
|
|
6246
|
+
return result;
|
|
6247
|
+
});
|
|
6248
|
+
scope.get("/extend/describe", async (req, reply) => {
|
|
6249
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6250
|
+
if (!proj) return;
|
|
6251
|
+
if (!proj.scanPath) {
|
|
6252
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6253
|
+
}
|
|
6254
|
+
try {
|
|
6255
|
+
const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
|
|
6256
|
+
return state;
|
|
6257
|
+
} catch (err) {
|
|
6258
|
+
return reply.code(500).send({ error: err.message });
|
|
6259
|
+
}
|
|
6260
|
+
});
|
|
6261
|
+
scope.post("/extend/apply", async (req, reply) => {
|
|
6262
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6263
|
+
if (!proj) return;
|
|
6264
|
+
if (!proj.scanPath) {
|
|
6265
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6266
|
+
}
|
|
6267
|
+
const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
|
|
6268
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6269
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6270
|
+
}
|
|
6271
|
+
try {
|
|
6272
|
+
const result = await applyExtension(
|
|
6273
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6274
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6275
|
+
);
|
|
6276
|
+
return result;
|
|
6277
|
+
} catch (err) {
|
|
6278
|
+
return reply.code(500).send({ error: err.message });
|
|
6279
|
+
}
|
|
6280
|
+
});
|
|
6281
|
+
scope.post("/extend/dry-run", async (req, reply) => {
|
|
6282
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6283
|
+
if (!proj) return;
|
|
6284
|
+
if (!proj.scanPath) {
|
|
6285
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6286
|
+
}
|
|
6287
|
+
const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
|
|
6288
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6289
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6290
|
+
}
|
|
6291
|
+
try {
|
|
6292
|
+
const result = await dryRunExtension(
|
|
6293
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6294
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6295
|
+
);
|
|
6296
|
+
return result;
|
|
6297
|
+
} catch (err) {
|
|
6298
|
+
return reply.code(500).send({ error: err.message });
|
|
6299
|
+
}
|
|
6300
|
+
});
|
|
6301
|
+
scope.post("/extend/rollback", async (req, reply) => {
|
|
6302
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6303
|
+
if (!proj) return;
|
|
6304
|
+
if (!proj.scanPath) {
|
|
6305
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6306
|
+
}
|
|
6307
|
+
const { library } = req.body ?? {};
|
|
6308
|
+
if (!library) {
|
|
6309
|
+
return reply.code(400).send({ error: "body must include library" });
|
|
6310
|
+
}
|
|
6311
|
+
try {
|
|
6312
|
+
const result = await rollbackExtension(
|
|
6313
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6314
|
+
{ library }
|
|
6315
|
+
);
|
|
6316
|
+
return result;
|
|
6317
|
+
} catch (err) {
|
|
6318
|
+
return reply.code(500).send({ error: err.message });
|
|
6319
|
+
}
|
|
6320
|
+
});
|
|
5878
6321
|
}
|
|
5879
6322
|
async function buildApi(opts) {
|
|
5880
6323
|
const app = (0, import_fastify.default)({ logger: false });
|
|
@@ -5985,15 +6428,15 @@ init_otel_grpc();
|
|
|
5985
6428
|
|
|
5986
6429
|
// src/daemon.ts
|
|
5987
6430
|
init_cjs_shims();
|
|
5988
|
-
var
|
|
5989
|
-
var
|
|
6431
|
+
var import_node_fs24 = require("fs");
|
|
6432
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
5990
6433
|
init_otel();
|
|
5991
6434
|
init_auth();
|
|
5992
6435
|
|
|
5993
6436
|
// src/unrouted.ts
|
|
5994
6437
|
init_cjs_shims();
|
|
5995
|
-
var
|
|
5996
|
-
var
|
|
6438
|
+
var import_node_fs23 = require("fs");
|
|
6439
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
5997
6440
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
5998
6441
|
return {
|
|
5999
6442
|
timestamp: now.toISOString(),
|
|
@@ -6003,21 +6446,21 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
6003
6446
|
};
|
|
6004
6447
|
}
|
|
6005
6448
|
async function appendUnroutedSpan(neatHome2, record) {
|
|
6006
|
-
const target =
|
|
6007
|
-
await
|
|
6008
|
-
await
|
|
6449
|
+
const target = import_node_path38.default.join(neatHome2, "errors.ndjson");
|
|
6450
|
+
await import_node_fs23.promises.mkdir(neatHome2, { recursive: true });
|
|
6451
|
+
await import_node_fs23.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
6009
6452
|
}
|
|
6010
6453
|
function unroutedErrorsPath(neatHome2) {
|
|
6011
|
-
return
|
|
6454
|
+
return import_node_path38.default.join(neatHome2, "errors.ndjson");
|
|
6012
6455
|
}
|
|
6013
6456
|
|
|
6014
6457
|
// src/daemon.ts
|
|
6015
6458
|
function neatHomeFor(opts) {
|
|
6016
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
6459
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path39.default.resolve(opts.neatHome);
|
|
6017
6460
|
const env = process.env.NEAT_HOME;
|
|
6018
|
-
if (env && env.length > 0) return
|
|
6461
|
+
if (env && env.length > 0) return import_node_path39.default.resolve(env);
|
|
6019
6462
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
6020
|
-
return
|
|
6463
|
+
return import_node_path39.default.join(home, ".neat");
|
|
6021
6464
|
}
|
|
6022
6465
|
function routeSpanToProject(serviceName, projects) {
|
|
6023
6466
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -6052,9 +6495,9 @@ function isTokenContained(needle, haystack) {
|
|
|
6052
6495
|
return tokens.includes(needle);
|
|
6053
6496
|
}
|
|
6054
6497
|
async function bootstrapProject(entry) {
|
|
6055
|
-
const paths = pathsForProject(entry.name,
|
|
6498
|
+
const paths = pathsForProject(entry.name, import_node_path39.default.join(entry.path, "neat-out"));
|
|
6056
6499
|
try {
|
|
6057
|
-
const stat = await
|
|
6500
|
+
const stat = await import_node_fs24.promises.stat(entry.path);
|
|
6058
6501
|
if (!stat.isDirectory()) {
|
|
6059
6502
|
throw new Error(`registered path ${entry.path} is not a directory`);
|
|
6060
6503
|
}
|
|
@@ -6120,13 +6563,13 @@ async function startDaemon(opts = {}) {
|
|
|
6120
6563
|
const home = neatHomeFor(opts);
|
|
6121
6564
|
const regPath = registryPath();
|
|
6122
6565
|
try {
|
|
6123
|
-
await
|
|
6566
|
+
await import_node_fs24.promises.access(regPath);
|
|
6124
6567
|
} catch {
|
|
6125
6568
|
throw new Error(
|
|
6126
6569
|
`neatd: registry not found at ${regPath}. Run \`neat init <path>\` to register a project before starting the daemon.`
|
|
6127
6570
|
);
|
|
6128
6571
|
}
|
|
6129
|
-
const pidPath =
|
|
6572
|
+
const pidPath = import_node_path39.default.join(home, "neatd.pid");
|
|
6130
6573
|
await writeAtomically(pidPath, `${process.pid}
|
|
6131
6574
|
`);
|
|
6132
6575
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -6284,7 +6727,7 @@ async function startDaemon(opts = {}) {
|
|
|
6284
6727
|
}
|
|
6285
6728
|
if (restApp) await restApp.close().catch(() => {
|
|
6286
6729
|
});
|
|
6287
|
-
await
|
|
6730
|
+
await import_node_fs24.promises.unlink(pidPath).catch(() => {
|
|
6288
6731
|
});
|
|
6289
6732
|
throw new Error(
|
|
6290
6733
|
`neatd: failed to bind REST on port ${restPort} \u2014 ${err.message}`
|
|
@@ -6390,7 +6833,7 @@ async function startDaemon(opts = {}) {
|
|
|
6390
6833
|
});
|
|
6391
6834
|
if (otlpApp) await otlpApp.close().catch(() => {
|
|
6392
6835
|
});
|
|
6393
|
-
await
|
|
6836
|
+
await import_node_fs24.promises.unlink(pidPath).catch(() => {
|
|
6394
6837
|
});
|
|
6395
6838
|
throw new Error(
|
|
6396
6839
|
`neatd: failed to bind OTLP on port ${otlpPort} \u2014 ${err.message}`
|
|
@@ -6436,9 +6879,9 @@ async function startDaemon(opts = {}) {
|
|
|
6436
6879
|
let registryWatcher = null;
|
|
6437
6880
|
let reloadTimer = null;
|
|
6438
6881
|
try {
|
|
6439
|
-
const regDir =
|
|
6440
|
-
const regBase =
|
|
6441
|
-
registryWatcher = (0,
|
|
6882
|
+
const regDir = import_node_path39.default.dirname(regPath);
|
|
6883
|
+
const regBase = import_node_path39.default.basename(regPath);
|
|
6884
|
+
registryWatcher = (0, import_node_fs24.watch)(regDir, (_eventType, filename) => {
|
|
6442
6885
|
if (filename !== null && filename !== regBase) return;
|
|
6443
6886
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
6444
6887
|
reloadTimer = setTimeout(() => {
|
|
@@ -6481,7 +6924,7 @@ async function startDaemon(opts = {}) {
|
|
|
6481
6924
|
} catch {
|
|
6482
6925
|
}
|
|
6483
6926
|
}
|
|
6484
|
-
await
|
|
6927
|
+
await import_node_fs24.promises.unlink(pidPath).catch(() => {
|
|
6485
6928
|
});
|
|
6486
6929
|
};
|
|
6487
6930
|
return {
|