@neat.is/core 0.4.11 → 0.4.13
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-OJHI33LD.js → chunk-5W7H35JJ.js} +1111 -343
- package/dist/chunk-5W7H35JJ.js.map +1 -0
- package/dist/{chunk-W4RNPPB5.js → chunk-TWNJX26R.js} +2 -2
- package/dist/cli.cjs +1471 -682
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +197 -169
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1145 -380
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +1133 -368
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +1267 -502
- 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-TWNJX26R.js.map} +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -58,9 +58,9 @@ function mountBearerAuth(app, opts) {
|
|
|
58
58
|
const suffixes = [...DEFAULT_UNAUTH_SUFFIXES, ...opts.extraUnauthenticatedSuffixes ?? []];
|
|
59
59
|
const publicRead = opts.publicRead === true;
|
|
60
60
|
app.addHook("preHandler", (req, reply, done) => {
|
|
61
|
-
const
|
|
61
|
+
const path49 = (req.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
62
62
|
for (const suffix of suffixes) {
|
|
63
|
-
if (
|
|
63
|
+
if (path49 === suffix || path49.endsWith(suffix)) {
|
|
64
64
|
done();
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
@@ -186,8 +186,8 @@ function reshapeGrpcRequest(req) {
|
|
|
186
186
|
};
|
|
187
187
|
}
|
|
188
188
|
function resolveProtoRoot() {
|
|
189
|
-
const here =
|
|
190
|
-
return
|
|
189
|
+
const here = import_node_path39.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
190
|
+
return import_node_path39.default.resolve(here, "..", "proto");
|
|
191
191
|
}
|
|
192
192
|
function loadTraceService() {
|
|
193
193
|
const protoRoot = resolveProtoRoot();
|
|
@@ -255,13 +255,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
255
255
|
})
|
|
256
256
|
};
|
|
257
257
|
}
|
|
258
|
-
var import_node_url,
|
|
258
|
+
var import_node_url, import_node_path39, import_node_crypto2, grpc, protoLoader;
|
|
259
259
|
var init_otel_grpc = __esm({
|
|
260
260
|
"src/otel-grpc.ts"() {
|
|
261
261
|
"use strict";
|
|
262
262
|
init_cjs_shims();
|
|
263
263
|
import_node_url = require("url");
|
|
264
|
-
|
|
264
|
+
import_node_path39 = __toESM(require("path"), 1);
|
|
265
265
|
import_node_crypto2 = require("crypto");
|
|
266
266
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
267
267
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -371,10 +371,10 @@ function parseOtlpRequest(body) {
|
|
|
371
371
|
return out;
|
|
372
372
|
}
|
|
373
373
|
function loadProtoRoot() {
|
|
374
|
-
const here =
|
|
375
|
-
const protoRoot =
|
|
374
|
+
const here = import_node_path40.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
375
|
+
const protoRoot = import_node_path40.default.resolve(here, "..", "proto");
|
|
376
376
|
const root = new import_protobufjs.default.Root();
|
|
377
|
-
root.resolvePath = (_origin, target) =>
|
|
377
|
+
root.resolvePath = (_origin, target) => import_node_path40.default.resolve(protoRoot, target);
|
|
378
378
|
root.loadSync(
|
|
379
379
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
380
380
|
{ keepCase: true }
|
|
@@ -567,12 +567,12 @@ async function buildOtelReceiver(opts) {
|
|
|
567
567
|
};
|
|
568
568
|
return decorated;
|
|
569
569
|
}
|
|
570
|
-
var
|
|
570
|
+
var import_node_path40, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody;
|
|
571
571
|
var init_otel = __esm({
|
|
572
572
|
"src/otel.ts"() {
|
|
573
573
|
"use strict";
|
|
574
574
|
init_cjs_shims();
|
|
575
|
-
|
|
575
|
+
import_node_path40 = __toESM(require("path"), 1);
|
|
576
576
|
import_node_url2 = require("url");
|
|
577
577
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
578
578
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -600,8 +600,8 @@ __export(cli_exports, {
|
|
|
600
600
|
});
|
|
601
601
|
module.exports = __toCommonJS(cli_exports);
|
|
602
602
|
init_cjs_shims();
|
|
603
|
-
var
|
|
604
|
-
var
|
|
603
|
+
var import_node_path48 = __toESM(require("path"), 1);
|
|
604
|
+
var import_node_fs31 = require("fs");
|
|
605
605
|
var import_node_url3 = require("url");
|
|
606
606
|
|
|
607
607
|
// src/graph.ts
|
|
@@ -1131,19 +1131,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1131
1131
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1132
1132
|
let best = { path: [start], edges: [] };
|
|
1133
1133
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1134
|
-
function step(node,
|
|
1135
|
-
if (
|
|
1136
|
-
best = { path: [...
|
|
1134
|
+
function step(node, path49, edges) {
|
|
1135
|
+
if (path49.length > best.path.length) {
|
|
1136
|
+
best = { path: [...path49], edges: [...edges] };
|
|
1137
1137
|
}
|
|
1138
|
-
if (
|
|
1138
|
+
if (path49.length - 1 >= maxDepth) return;
|
|
1139
1139
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1140
1140
|
for (const [srcId, edge] of incoming) {
|
|
1141
1141
|
if (visited.has(srcId)) continue;
|
|
1142
1142
|
visited.add(srcId);
|
|
1143
|
-
|
|
1143
|
+
path49.push(srcId);
|
|
1144
1144
|
edges.push(edge);
|
|
1145
|
-
step(srcId,
|
|
1146
|
-
|
|
1145
|
+
step(srcId, path49, edges);
|
|
1146
|
+
path49.pop();
|
|
1147
1147
|
edges.pop();
|
|
1148
1148
|
visited.delete(srcId);
|
|
1149
1149
|
}
|
|
@@ -1975,7 +1975,7 @@ function ensureFrontierNode(graph, host, ts) {
|
|
|
1975
1975
|
graph.addNode(id, node);
|
|
1976
1976
|
return id;
|
|
1977
1977
|
}
|
|
1978
|
-
function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
1978
|
+
function upsertObservedEdge(graph, type, source, target, ts, isError = false, evidence) {
|
|
1979
1979
|
if (!graph.hasNode(source) || !graph.hasNode(target)) return null;
|
|
1980
1980
|
const id = makeObservedEdgeId(type, source, target);
|
|
1981
1981
|
if (graph.hasEdge(id)) {
|
|
@@ -2012,7 +2012,10 @@ function upsertObservedEdge(graph, type, source, target, ts, isError = false) {
|
|
|
2012
2012
|
confidence: (0, import_types3.confidenceForObservedSignal)(signal),
|
|
2013
2013
|
lastObserved: ts,
|
|
2014
2014
|
callCount: 1,
|
|
2015
|
-
signal
|
|
2015
|
+
signal,
|
|
2016
|
+
// Call-site evidence from span code.* semconv (file-awareness.md §4 + §6).
|
|
2017
|
+
// Only set when code.filepath was present on the span — never fabricated.
|
|
2018
|
+
...evidence ? { evidence } : {}
|
|
2016
2019
|
};
|
|
2017
2020
|
graph.addEdgeWithKey(id, source, target, edge);
|
|
2018
2021
|
return { edge, created: true };
|
|
@@ -2106,6 +2109,7 @@ async function handleSpan(ctx, span) {
|
|
|
2106
2109
|
const sourceServiceNode = ctx.graph.getNodeAttributes(sourceId);
|
|
2107
2110
|
const callSite = callSiteFromSpan(span, sourceServiceNode, ctx.scanPath);
|
|
2108
2111
|
const observedSource = () => callSite ? ensureObservedFileNode(ctx.graph, span.service, sourceId, callSite) : sourceId;
|
|
2112
|
+
const callSiteEvidence = callSite ? { file: callSite.relPath, ...callSite.line !== void 0 ? { line: callSite.line } : {} } : void 0;
|
|
2109
2113
|
let affectedNode = sourceId;
|
|
2110
2114
|
const mintsFromCallerSide = spanMintsObservedEdge(span.kind);
|
|
2111
2115
|
if (span.dbSystem) {
|
|
@@ -2119,7 +2123,8 @@ async function handleSpan(ctx, span) {
|
|
|
2119
2123
|
observedSource(),
|
|
2120
2124
|
targetId,
|
|
2121
2125
|
ts,
|
|
2122
|
-
isError
|
|
2126
|
+
isError,
|
|
2127
|
+
callSiteEvidence
|
|
2123
2128
|
);
|
|
2124
2129
|
if (result) affectedNode = targetId;
|
|
2125
2130
|
}
|
|
@@ -2135,7 +2140,8 @@ async function handleSpan(ctx, span) {
|
|
|
2135
2140
|
observedSource(),
|
|
2136
2141
|
targetId,
|
|
2137
2142
|
ts,
|
|
2138
|
-
isError
|
|
2143
|
+
isError,
|
|
2144
|
+
callSiteEvidence
|
|
2139
2145
|
);
|
|
2140
2146
|
affectedNode = targetId;
|
|
2141
2147
|
resolvedViaAddress = true;
|
|
@@ -2147,7 +2153,8 @@ async function handleSpan(ctx, span) {
|
|
|
2147
2153
|
observedSource(),
|
|
2148
2154
|
frontierNodeId,
|
|
2149
2155
|
ts,
|
|
2150
|
-
isError
|
|
2156
|
+
isError,
|
|
2157
|
+
callSiteEvidence
|
|
2151
2158
|
);
|
|
2152
2159
|
affectedNode = frontierNodeId;
|
|
2153
2160
|
resolvedViaAddress = true;
|
|
@@ -3108,16 +3115,433 @@ async function addServiceAliases(graph, scanPath, services) {
|
|
|
3108
3115
|
await collectK8sAliases(graph, scanPath, byName);
|
|
3109
3116
|
}
|
|
3110
3117
|
|
|
3111
|
-
// src/extract/
|
|
3118
|
+
// src/extract/files.ts
|
|
3112
3119
|
init_cjs_shims();
|
|
3113
|
-
|
|
3120
|
+
|
|
3121
|
+
// src/extract/calls/shared.ts
|
|
3122
|
+
init_cjs_shims();
|
|
3123
|
+
var import_node_fs10 = require("fs");
|
|
3124
|
+
var import_node_path10 = __toESM(require("path"), 1);
|
|
3114
3125
|
var import_types7 = require("@neat.is/types");
|
|
3126
|
+
async function walkSourceFiles(dir) {
|
|
3127
|
+
const out = [];
|
|
3128
|
+
async function walk(current) {
|
|
3129
|
+
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3130
|
+
for (const entry2 of entries) {
|
|
3131
|
+
const full = import_node_path10.default.join(current, entry2.name);
|
|
3132
|
+
if (entry2.isDirectory()) {
|
|
3133
|
+
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3134
|
+
if (await isPythonVenvDir(full)) continue;
|
|
3135
|
+
await walk(full);
|
|
3136
|
+
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry2.name))) {
|
|
3137
|
+
out.push(full);
|
|
3138
|
+
}
|
|
3139
|
+
}
|
|
3140
|
+
}
|
|
3141
|
+
await walk(dir);
|
|
3142
|
+
return out;
|
|
3143
|
+
}
|
|
3144
|
+
async function loadSourceFiles(dir) {
|
|
3145
|
+
const paths = await walkSourceFiles(dir);
|
|
3146
|
+
const out = [];
|
|
3147
|
+
for (const p of paths) {
|
|
3148
|
+
try {
|
|
3149
|
+
const content = await import_node_fs10.promises.readFile(p, "utf8");
|
|
3150
|
+
out.push({ path: p, content });
|
|
3151
|
+
} catch {
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
return out;
|
|
3155
|
+
}
|
|
3156
|
+
function lineOf(text, needle) {
|
|
3157
|
+
const idx = text.indexOf(needle);
|
|
3158
|
+
if (idx < 0) return 1;
|
|
3159
|
+
return text.slice(0, idx).split("\n").length;
|
|
3160
|
+
}
|
|
3161
|
+
function snippet(text, line) {
|
|
3162
|
+
const lines = text.split("\n");
|
|
3163
|
+
return (lines[line - 1] ?? "").trim();
|
|
3164
|
+
}
|
|
3165
|
+
function toPosix2(p) {
|
|
3166
|
+
return p.split("\\").join("/");
|
|
3167
|
+
}
|
|
3168
|
+
function languageForPath(relPath) {
|
|
3169
|
+
switch (import_node_path10.default.extname(relPath).toLowerCase()) {
|
|
3170
|
+
case ".py":
|
|
3171
|
+
return "python";
|
|
3172
|
+
case ".ts":
|
|
3173
|
+
case ".tsx":
|
|
3174
|
+
return "typescript";
|
|
3175
|
+
case ".js":
|
|
3176
|
+
case ".jsx":
|
|
3177
|
+
case ".mjs":
|
|
3178
|
+
case ".cjs":
|
|
3179
|
+
return "javascript";
|
|
3180
|
+
default:
|
|
3181
|
+
return void 0;
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3184
|
+
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3185
|
+
let nodesAdded = 0;
|
|
3186
|
+
let edgesAdded = 0;
|
|
3187
|
+
const fileNodeId = (0, import_types7.fileId)(serviceName, relPath);
|
|
3188
|
+
if (!graph.hasNode(fileNodeId)) {
|
|
3189
|
+
const language = languageForPath(relPath);
|
|
3190
|
+
const node = {
|
|
3191
|
+
id: fileNodeId,
|
|
3192
|
+
type: import_types7.NodeType.FileNode,
|
|
3193
|
+
service: serviceName,
|
|
3194
|
+
path: relPath,
|
|
3195
|
+
...language ? { language } : {},
|
|
3196
|
+
discoveredVia: "static"
|
|
3197
|
+
};
|
|
3198
|
+
graph.addNode(fileNodeId, node);
|
|
3199
|
+
nodesAdded++;
|
|
3200
|
+
}
|
|
3201
|
+
const containsId = (0, import_types7.extractedEdgeId)(serviceNodeId, fileNodeId, import_types7.EdgeType.CONTAINS);
|
|
3202
|
+
if (!graph.hasEdge(containsId)) {
|
|
3203
|
+
const edge = {
|
|
3204
|
+
id: containsId,
|
|
3205
|
+
source: serviceNodeId,
|
|
3206
|
+
target: fileNodeId,
|
|
3207
|
+
type: import_types7.EdgeType.CONTAINS,
|
|
3208
|
+
provenance: import_types7.Provenance.EXTRACTED,
|
|
3209
|
+
confidence: (0, import_types7.confidenceForExtracted)("structural"),
|
|
3210
|
+
evidence: { file: relPath }
|
|
3211
|
+
};
|
|
3212
|
+
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3213
|
+
edgesAdded++;
|
|
3214
|
+
}
|
|
3215
|
+
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3216
|
+
}
|
|
3217
|
+
|
|
3218
|
+
// src/extract/files.ts
|
|
3219
|
+
var import_node_path11 = __toESM(require("path"), 1);
|
|
3220
|
+
async function addFiles(graph, services) {
|
|
3221
|
+
let nodesAdded = 0;
|
|
3222
|
+
let edgesAdded = 0;
|
|
3223
|
+
for (const service of services) {
|
|
3224
|
+
const filePaths = await walkSourceFiles(service.dir);
|
|
3225
|
+
for (const filePath of filePaths) {
|
|
3226
|
+
const relPath = toPosix2(import_node_path11.default.relative(service.dir, filePath));
|
|
3227
|
+
const { nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
3228
|
+
graph,
|
|
3229
|
+
service.pkg.name,
|
|
3230
|
+
service.node.id,
|
|
3231
|
+
relPath
|
|
3232
|
+
);
|
|
3233
|
+
nodesAdded += n;
|
|
3234
|
+
edgesAdded += e;
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
return { nodesAdded, edgesAdded };
|
|
3238
|
+
}
|
|
3239
|
+
|
|
3240
|
+
// src/extract/imports.ts
|
|
3241
|
+
init_cjs_shims();
|
|
3242
|
+
var import_node_path12 = __toESM(require("path"), 1);
|
|
3243
|
+
var import_node_fs11 = require("fs");
|
|
3244
|
+
var import_tree_sitter = __toESM(require("tree-sitter"), 1);
|
|
3245
|
+
var import_tree_sitter_javascript = __toESM(require("tree-sitter-javascript"), 1);
|
|
3246
|
+
var import_tree_sitter_python = __toESM(require("tree-sitter-python"), 1);
|
|
3247
|
+
var import_types8 = require("@neat.is/types");
|
|
3248
|
+
var PARSE_CHUNK = 16384;
|
|
3249
|
+
function parseSource(parser, source) {
|
|
3250
|
+
return parser.parse(
|
|
3251
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK)
|
|
3252
|
+
);
|
|
3253
|
+
}
|
|
3254
|
+
function makeJsParser() {
|
|
3255
|
+
const p = new import_tree_sitter.default();
|
|
3256
|
+
p.setLanguage(import_tree_sitter_javascript.default);
|
|
3257
|
+
return p;
|
|
3258
|
+
}
|
|
3259
|
+
function makePyParser() {
|
|
3260
|
+
const p = new import_tree_sitter.default();
|
|
3261
|
+
p.setLanguage(import_tree_sitter_python.default);
|
|
3262
|
+
return p;
|
|
3263
|
+
}
|
|
3264
|
+
function stringLiteralText(node) {
|
|
3265
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3266
|
+
const child = node.child(i);
|
|
3267
|
+
if (child?.type === "string_fragment") return child.text;
|
|
3268
|
+
}
|
|
3269
|
+
const raw = node.text;
|
|
3270
|
+
if (raw.length >= 2) return raw.slice(1, -1);
|
|
3271
|
+
return raw.length === 0 ? null : "";
|
|
3272
|
+
}
|
|
3273
|
+
function clipSnippet(text) {
|
|
3274
|
+
const oneLine = text.split("\n")[0] ?? text;
|
|
3275
|
+
return oneLine.length > 120 ? oneLine.slice(0, 120) : oneLine;
|
|
3276
|
+
}
|
|
3277
|
+
function collectJsImports(node, out) {
|
|
3278
|
+
if (node.type === "import_statement") {
|
|
3279
|
+
const source = node.childForFieldName("source");
|
|
3280
|
+
if (source) {
|
|
3281
|
+
const specifier = stringLiteralText(source);
|
|
3282
|
+
if (specifier) {
|
|
3283
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
return;
|
|
3287
|
+
}
|
|
3288
|
+
if (node.type === "call_expression") {
|
|
3289
|
+
const fn = node.childForFieldName("function");
|
|
3290
|
+
if (fn?.type === "identifier" && fn.text === "require") {
|
|
3291
|
+
const args = node.childForFieldName("arguments");
|
|
3292
|
+
const firstArg = args?.namedChild(0);
|
|
3293
|
+
if (firstArg?.type === "string") {
|
|
3294
|
+
const specifier = stringLiteralText(firstArg);
|
|
3295
|
+
if (specifier) {
|
|
3296
|
+
out.push({ specifier, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
3297
|
+
}
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3302
|
+
const child = node.namedChild(i);
|
|
3303
|
+
if (child) collectJsImports(child, out);
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
function collectPyImports(node, out) {
|
|
3307
|
+
if (node.type === "import_from_statement") {
|
|
3308
|
+
let level = 0;
|
|
3309
|
+
let modulePath = "";
|
|
3310
|
+
let pastFrom = false;
|
|
3311
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
3312
|
+
const child = node.child(i);
|
|
3313
|
+
if (!child) continue;
|
|
3314
|
+
if (!pastFrom) {
|
|
3315
|
+
if (child.type === "from") pastFrom = true;
|
|
3316
|
+
continue;
|
|
3317
|
+
}
|
|
3318
|
+
if (child.type === "import") break;
|
|
3319
|
+
if (child.type === "relative_import") {
|
|
3320
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
3321
|
+
const rc = child.child(j);
|
|
3322
|
+
if (!rc) continue;
|
|
3323
|
+
if (rc.type === "import_prefix") {
|
|
3324
|
+
for (let k = 0; k < rc.childCount; k++) {
|
|
3325
|
+
if (rc.child(k)?.type === ".") level++;
|
|
3326
|
+
}
|
|
3327
|
+
} else if (rc.type === "dotted_name") modulePath = rc.text;
|
|
3328
|
+
}
|
|
3329
|
+
break;
|
|
3330
|
+
}
|
|
3331
|
+
if (child.type === "dotted_name") {
|
|
3332
|
+
modulePath = child.text;
|
|
3333
|
+
break;
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
if (level > 0 || modulePath) {
|
|
3337
|
+
out.push({ modulePath, level, line: node.startPosition.row + 1, snippet: clipSnippet(node.text) });
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
3341
|
+
const child = node.namedChild(i);
|
|
3342
|
+
if (child) collectPyImports(child, out);
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
async function fileExists(p) {
|
|
3346
|
+
try {
|
|
3347
|
+
await import_node_fs11.promises.access(p);
|
|
3348
|
+
return true;
|
|
3349
|
+
} catch {
|
|
3350
|
+
return false;
|
|
3351
|
+
}
|
|
3352
|
+
}
|
|
3353
|
+
function isWithinServiceDir(candidate, serviceDir) {
|
|
3354
|
+
const rel = import_node_path12.default.relative(serviceDir, candidate);
|
|
3355
|
+
return rel !== "" && !rel.startsWith("..") && !import_node_path12.default.isAbsolute(rel);
|
|
3356
|
+
}
|
|
3357
|
+
var JS_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
3358
|
+
var JS_INDEX_FILES = JS_EXTENSIONS.map((ext) => `index${ext}`);
|
|
3359
|
+
async function firstExistingCandidate(base, serviceDir) {
|
|
3360
|
+
for (const ext of JS_EXTENSIONS) {
|
|
3361
|
+
const candidate = base + ext;
|
|
3362
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
3363
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
|
|
3364
|
+
}
|
|
3365
|
+
}
|
|
3366
|
+
for (const indexFile of JS_INDEX_FILES) {
|
|
3367
|
+
const candidate = import_node_path12.default.join(base, indexFile);
|
|
3368
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
3369
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
return null;
|
|
3373
|
+
}
|
|
3374
|
+
async function loadTsPathConfig(serviceDir) {
|
|
3375
|
+
const tsconfigPath = import_node_path12.default.join(serviceDir, "tsconfig.json");
|
|
3376
|
+
let raw;
|
|
3377
|
+
try {
|
|
3378
|
+
raw = await import_node_fs11.promises.readFile(tsconfigPath, "utf8");
|
|
3379
|
+
} catch {
|
|
3380
|
+
return null;
|
|
3381
|
+
}
|
|
3382
|
+
try {
|
|
3383
|
+
const parsed = JSON.parse(raw);
|
|
3384
|
+
const paths = parsed.compilerOptions?.paths;
|
|
3385
|
+
if (!paths || Object.keys(paths).length === 0) return null;
|
|
3386
|
+
const baseUrl = parsed.compilerOptions?.baseUrl;
|
|
3387
|
+
return { paths, baseDir: baseUrl ? import_node_path12.default.resolve(serviceDir, baseUrl) : serviceDir };
|
|
3388
|
+
} catch (err) {
|
|
3389
|
+
recordExtractionError("import alias resolution", tsconfigPath, err);
|
|
3390
|
+
return null;
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
async function resolveTsAlias(specifier, config, serviceDir) {
|
|
3394
|
+
for (const [pattern, targets] of Object.entries(config.paths)) {
|
|
3395
|
+
let suffix = null;
|
|
3396
|
+
if (pattern === specifier) {
|
|
3397
|
+
suffix = "";
|
|
3398
|
+
} else if (pattern.endsWith("/*")) {
|
|
3399
|
+
const prefix = pattern.slice(0, -1);
|
|
3400
|
+
if (specifier.startsWith(prefix)) suffix = specifier.slice(prefix.length);
|
|
3401
|
+
}
|
|
3402
|
+
if (suffix === null) continue;
|
|
3403
|
+
for (const target of targets) {
|
|
3404
|
+
const targetBase = target.endsWith("/*") ? target.slice(0, -2) : target.replace(/\*$/, "");
|
|
3405
|
+
const resolvedBase = import_node_path12.default.resolve(config.baseDir, targetBase, suffix);
|
|
3406
|
+
const hit = await firstExistingCandidate(resolvedBase, serviceDir);
|
|
3407
|
+
if (hit) return hit;
|
|
3408
|
+
if (isWithinServiceDir(resolvedBase, serviceDir) && await fileExists(resolvedBase)) {
|
|
3409
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, resolvedBase));
|
|
3410
|
+
}
|
|
3411
|
+
}
|
|
3412
|
+
}
|
|
3413
|
+
return null;
|
|
3414
|
+
}
|
|
3415
|
+
async function resolveJsImport(specifier, importerDir, serviceDir, tsPaths) {
|
|
3416
|
+
if (!specifier) return null;
|
|
3417
|
+
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
3418
|
+
const base = import_node_path12.default.resolve(importerDir, specifier);
|
|
3419
|
+
const ext = import_node_path12.default.extname(specifier);
|
|
3420
|
+
if (ext) {
|
|
3421
|
+
if (ext === ".js" || ext === ".jsx") {
|
|
3422
|
+
const tsExt = ext === ".jsx" ? ".tsx" : ".ts";
|
|
3423
|
+
const tsSibling = base.slice(0, -ext.length) + tsExt;
|
|
3424
|
+
if (isWithinServiceDir(tsSibling, serviceDir) && await fileExists(tsSibling)) {
|
|
3425
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, tsSibling));
|
|
3426
|
+
}
|
|
3427
|
+
}
|
|
3428
|
+
if (isWithinServiceDir(base, serviceDir) && await fileExists(base)) {
|
|
3429
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, base));
|
|
3430
|
+
}
|
|
3431
|
+
return null;
|
|
3432
|
+
}
|
|
3433
|
+
return firstExistingCandidate(base, serviceDir);
|
|
3434
|
+
}
|
|
3435
|
+
if (tsPaths) return resolveTsAlias(specifier, tsPaths, serviceDir);
|
|
3436
|
+
return null;
|
|
3437
|
+
}
|
|
3438
|
+
async function resolvePyImport(imp, importerPath, serviceDir) {
|
|
3439
|
+
if (!imp.modulePath) return null;
|
|
3440
|
+
const relPath = imp.modulePath.split(".").join("/");
|
|
3441
|
+
let baseDir;
|
|
3442
|
+
if (imp.level > 0) {
|
|
3443
|
+
baseDir = import_node_path12.default.dirname(importerPath);
|
|
3444
|
+
for (let i = 1; i < imp.level; i++) baseDir = import_node_path12.default.dirname(baseDir);
|
|
3445
|
+
} else {
|
|
3446
|
+
baseDir = serviceDir;
|
|
3447
|
+
}
|
|
3448
|
+
const candidates = [import_node_path12.default.join(baseDir, `${relPath}.py`), import_node_path12.default.join(baseDir, relPath, "__init__.py")];
|
|
3449
|
+
for (const candidate of candidates) {
|
|
3450
|
+
if (isWithinServiceDir(candidate, serviceDir) && await fileExists(candidate)) {
|
|
3451
|
+
return toPosix2(import_node_path12.default.relative(serviceDir, candidate));
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
3454
|
+
return null;
|
|
3455
|
+
}
|
|
3456
|
+
function emitImportEdge(graph, serviceName, importerFileId, importerRelPath, importeeRelPath, line, snippet2) {
|
|
3457
|
+
const importeeFileId = (0, import_types8.fileId)(serviceName, importeeRelPath);
|
|
3458
|
+
if (!graph.hasNode(importeeFileId)) return 0;
|
|
3459
|
+
const edgeId = (0, import_types8.extractedEdgeId)(importerFileId, importeeFileId, import_types8.EdgeType.IMPORTS);
|
|
3460
|
+
if (graph.hasEdge(edgeId)) return 0;
|
|
3461
|
+
const edge = {
|
|
3462
|
+
id: edgeId,
|
|
3463
|
+
source: importerFileId,
|
|
3464
|
+
target: importeeFileId,
|
|
3465
|
+
type: import_types8.EdgeType.IMPORTS,
|
|
3466
|
+
provenance: import_types8.Provenance.EXTRACTED,
|
|
3467
|
+
confidence: (0, import_types8.confidenceForExtracted)("structural"),
|
|
3468
|
+
evidence: { file: importerRelPath, line, snippet: snippet2 }
|
|
3469
|
+
};
|
|
3470
|
+
graph.addEdgeWithKey(edgeId, importerFileId, importeeFileId, edge);
|
|
3471
|
+
return 1;
|
|
3472
|
+
}
|
|
3473
|
+
async function addImports(graph, services) {
|
|
3474
|
+
const jsParser = makeJsParser();
|
|
3475
|
+
const pyParser = makePyParser();
|
|
3476
|
+
let edgesAdded = 0;
|
|
3477
|
+
for (const service of services) {
|
|
3478
|
+
const tsPaths = await loadTsPathConfig(service.dir);
|
|
3479
|
+
const files = await loadSourceFiles(service.dir);
|
|
3480
|
+
for (const file of files) {
|
|
3481
|
+
if (isTestPath(file.path)) continue;
|
|
3482
|
+
const relFile = toPosix2(import_node_path12.default.relative(service.dir, file.path));
|
|
3483
|
+
const importerFileId = (0, import_types8.fileId)(service.pkg.name, relFile);
|
|
3484
|
+
const isPython = import_node_path12.default.extname(file.path) === ".py";
|
|
3485
|
+
if (isPython) {
|
|
3486
|
+
let pyImports = [];
|
|
3487
|
+
try {
|
|
3488
|
+
const tree = parseSource(pyParser, file.content);
|
|
3489
|
+
collectPyImports(tree.rootNode, pyImports);
|
|
3490
|
+
} catch (err) {
|
|
3491
|
+
recordExtractionError("import extraction", file.path, err);
|
|
3492
|
+
continue;
|
|
3493
|
+
}
|
|
3494
|
+
for (const imp of pyImports) {
|
|
3495
|
+
const resolved = await resolvePyImport(imp, file.path, service.dir);
|
|
3496
|
+
if (!resolved) continue;
|
|
3497
|
+
edgesAdded += emitImportEdge(
|
|
3498
|
+
graph,
|
|
3499
|
+
service.pkg.name,
|
|
3500
|
+
importerFileId,
|
|
3501
|
+
relFile,
|
|
3502
|
+
resolved,
|
|
3503
|
+
imp.line,
|
|
3504
|
+
imp.snippet
|
|
3505
|
+
);
|
|
3506
|
+
}
|
|
3507
|
+
continue;
|
|
3508
|
+
}
|
|
3509
|
+
let jsImports = [];
|
|
3510
|
+
try {
|
|
3511
|
+
const tree = parseSource(jsParser, file.content);
|
|
3512
|
+
collectJsImports(tree.rootNode, jsImports);
|
|
3513
|
+
} catch (err) {
|
|
3514
|
+
recordExtractionError("import extraction", file.path, err);
|
|
3515
|
+
continue;
|
|
3516
|
+
}
|
|
3517
|
+
for (const imp of jsImports) {
|
|
3518
|
+
const resolved = await resolveJsImport(imp.specifier, import_node_path12.default.dirname(file.path), service.dir, tsPaths);
|
|
3519
|
+
if (!resolved) continue;
|
|
3520
|
+
edgesAdded += emitImportEdge(
|
|
3521
|
+
graph,
|
|
3522
|
+
service.pkg.name,
|
|
3523
|
+
importerFileId,
|
|
3524
|
+
relFile,
|
|
3525
|
+
resolved,
|
|
3526
|
+
imp.line,
|
|
3527
|
+
imp.snippet
|
|
3528
|
+
);
|
|
3529
|
+
}
|
|
3530
|
+
}
|
|
3531
|
+
}
|
|
3532
|
+
return { nodesAdded: 0, edgesAdded };
|
|
3533
|
+
}
|
|
3534
|
+
|
|
3535
|
+
// src/extract/databases/index.ts
|
|
3536
|
+
init_cjs_shims();
|
|
3537
|
+
var import_node_path20 = __toESM(require("path"), 1);
|
|
3538
|
+
var import_types9 = require("@neat.is/types");
|
|
3115
3539
|
|
|
3116
3540
|
// src/extract/databases/db-config-yaml.ts
|
|
3117
3541
|
init_cjs_shims();
|
|
3118
|
-
var
|
|
3542
|
+
var import_node_path13 = __toESM(require("path"), 1);
|
|
3119
3543
|
async function parse(serviceDir) {
|
|
3120
|
-
const yamlPath =
|
|
3544
|
+
const yamlPath = import_node_path13.default.join(serviceDir, "db-config.yaml");
|
|
3121
3545
|
if (!await exists(yamlPath)) return [];
|
|
3122
3546
|
const raw = await readYaml(yamlPath);
|
|
3123
3547
|
return [
|
|
@@ -3135,13 +3559,13 @@ var dbConfigYamlParser = { name: "db-config.yaml", parse };
|
|
|
3135
3559
|
|
|
3136
3560
|
// src/extract/databases/dotenv.ts
|
|
3137
3561
|
init_cjs_shims();
|
|
3138
|
-
var
|
|
3139
|
-
var
|
|
3562
|
+
var import_node_fs13 = require("fs");
|
|
3563
|
+
var import_node_path15 = __toESM(require("path"), 1);
|
|
3140
3564
|
|
|
3141
3565
|
// src/extract/databases/shared.ts
|
|
3142
3566
|
init_cjs_shims();
|
|
3143
|
-
var
|
|
3144
|
-
var
|
|
3567
|
+
var import_node_fs12 = require("fs");
|
|
3568
|
+
var import_node_path14 = __toESM(require("path"), 1);
|
|
3145
3569
|
function schemeToEngine(scheme) {
|
|
3146
3570
|
const s = scheme.toLowerCase().split("+")[0];
|
|
3147
3571
|
switch (s) {
|
|
@@ -3180,14 +3604,14 @@ function parseConnectionString(url) {
|
|
|
3180
3604
|
}
|
|
3181
3605
|
async function readIfExists(filePath) {
|
|
3182
3606
|
try {
|
|
3183
|
-
return await
|
|
3607
|
+
return await import_node_fs12.promises.readFile(filePath, "utf8");
|
|
3184
3608
|
} catch {
|
|
3185
3609
|
return null;
|
|
3186
3610
|
}
|
|
3187
3611
|
}
|
|
3188
3612
|
async function findFirst(serviceDir, candidates) {
|
|
3189
3613
|
for (const rel of candidates) {
|
|
3190
|
-
const abs =
|
|
3614
|
+
const abs = import_node_path14.default.join(serviceDir, rel);
|
|
3191
3615
|
const content = await readIfExists(abs);
|
|
3192
3616
|
if (content !== null) return abs;
|
|
3193
3617
|
}
|
|
@@ -3238,15 +3662,15 @@ function parseDotenvLine(line) {
|
|
|
3238
3662
|
return { key, value };
|
|
3239
3663
|
}
|
|
3240
3664
|
async function parse2(serviceDir) {
|
|
3241
|
-
const entries = await
|
|
3665
|
+
const entries = await import_node_fs13.promises.readdir(serviceDir, { withFileTypes: true }).catch(() => []);
|
|
3242
3666
|
const configs = [];
|
|
3243
3667
|
const seen = /* @__PURE__ */ new Set();
|
|
3244
3668
|
for (const entry2 of entries) {
|
|
3245
3669
|
if (!entry2.isFile()) continue;
|
|
3246
3670
|
const match = isConfigFile(entry2.name);
|
|
3247
3671
|
if (!match.match || match.fileType !== "env") continue;
|
|
3248
|
-
const filePath =
|
|
3249
|
-
const content = await
|
|
3672
|
+
const filePath = import_node_path15.default.join(serviceDir, entry2.name);
|
|
3673
|
+
const content = await import_node_fs13.promises.readFile(filePath, "utf8");
|
|
3250
3674
|
for (const line of content.split("\n")) {
|
|
3251
3675
|
const parsed = parseDotenvLine(line);
|
|
3252
3676
|
if (!parsed) continue;
|
|
@@ -3265,9 +3689,9 @@ var dotenvParser = { name: ".env", parse: parse2 };
|
|
|
3265
3689
|
|
|
3266
3690
|
// src/extract/databases/prisma.ts
|
|
3267
3691
|
init_cjs_shims();
|
|
3268
|
-
var
|
|
3692
|
+
var import_node_path16 = __toESM(require("path"), 1);
|
|
3269
3693
|
async function parse3(serviceDir) {
|
|
3270
|
-
const schemaPath =
|
|
3694
|
+
const schemaPath = import_node_path16.default.join(serviceDir, "prisma", "schema.prisma");
|
|
3271
3695
|
const content = await readIfExists(schemaPath);
|
|
3272
3696
|
if (!content) return [];
|
|
3273
3697
|
const block = content.match(/datasource\s+\w+\s*\{([^}]*)\}/s);
|
|
@@ -3399,10 +3823,10 @@ var knexParser = { name: "knex", parse: parse5 };
|
|
|
3399
3823
|
|
|
3400
3824
|
// src/extract/databases/ormconfig.ts
|
|
3401
3825
|
init_cjs_shims();
|
|
3402
|
-
var
|
|
3826
|
+
var import_node_path17 = __toESM(require("path"), 1);
|
|
3403
3827
|
async function parse6(serviceDir) {
|
|
3404
3828
|
for (const candidate of ["ormconfig.json", "ormconfig.yaml", "ormconfig.yml"]) {
|
|
3405
|
-
const abs =
|
|
3829
|
+
const abs = import_node_path17.default.join(serviceDir, candidate);
|
|
3406
3830
|
if (!await exists(abs)) continue;
|
|
3407
3831
|
const raw = candidate.endsWith(".json") ? await readJson(abs) : await readYaml(abs);
|
|
3408
3832
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -3462,9 +3886,9 @@ var typeormParser = { name: "typeorm", parse: parse7 };
|
|
|
3462
3886
|
|
|
3463
3887
|
// src/extract/databases/sequelize.ts
|
|
3464
3888
|
init_cjs_shims();
|
|
3465
|
-
var
|
|
3889
|
+
var import_node_path18 = __toESM(require("path"), 1);
|
|
3466
3890
|
async function parse8(serviceDir) {
|
|
3467
|
-
const configPath =
|
|
3891
|
+
const configPath = import_node_path18.default.join(serviceDir, "config", "config.json");
|
|
3468
3892
|
if (!await exists(configPath)) return [];
|
|
3469
3893
|
const raw = await readJson(configPath);
|
|
3470
3894
|
const out = [];
|
|
@@ -3491,7 +3915,7 @@ var sequelizeParser = { name: "sequelize", parse: parse8 };
|
|
|
3491
3915
|
|
|
3492
3916
|
// src/extract/databases/docker-compose.ts
|
|
3493
3917
|
init_cjs_shims();
|
|
3494
|
-
var
|
|
3918
|
+
var import_node_path19 = __toESM(require("path"), 1);
|
|
3495
3919
|
function portFromService(svc) {
|
|
3496
3920
|
for (const raw of svc.ports ?? []) {
|
|
3497
3921
|
const str = String(raw);
|
|
@@ -3518,7 +3942,7 @@ function databaseFromEnv(svc) {
|
|
|
3518
3942
|
}
|
|
3519
3943
|
async function parse9(serviceDir) {
|
|
3520
3944
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
3521
|
-
const abs =
|
|
3945
|
+
const abs = import_node_path19.default.join(serviceDir, name);
|
|
3522
3946
|
if (!await exists(abs)) continue;
|
|
3523
3947
|
const raw = await readYaml(abs);
|
|
3524
3948
|
if (!raw?.services) return [];
|
|
@@ -3559,8 +3983,8 @@ function compatibleDriversFor(engine) {
|
|
|
3559
3983
|
}
|
|
3560
3984
|
function toDatabaseNode(config) {
|
|
3561
3985
|
return {
|
|
3562
|
-
id: (0,
|
|
3563
|
-
type:
|
|
3986
|
+
id: (0, import_types9.databaseId)(config.host),
|
|
3987
|
+
type: import_types9.NodeType.DatabaseNode,
|
|
3564
3988
|
name: config.database || config.host,
|
|
3565
3989
|
engine: config.engine,
|
|
3566
3990
|
engineVersion: config.engineVersion,
|
|
@@ -3690,19 +4114,24 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3690
4114
|
discoveredVia: mergedDiscoveredVia
|
|
3691
4115
|
});
|
|
3692
4116
|
}
|
|
4117
|
+
const relConfigFile = toPosix2(import_node_path20.default.relative(service.dir, config.sourceFile));
|
|
4118
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4119
|
+
graph,
|
|
4120
|
+
service.pkg.name,
|
|
4121
|
+
service.node.id,
|
|
4122
|
+
relConfigFile
|
|
4123
|
+
);
|
|
4124
|
+
nodesAdded += fn;
|
|
4125
|
+
edgesAdded += fe;
|
|
4126
|
+
const evidenceFile = toPosix2(import_node_path20.default.relative(scanPath, config.sourceFile));
|
|
3693
4127
|
const edge = {
|
|
3694
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3695
|
-
source:
|
|
4128
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, dbNode.id, import_types9.EdgeType.CONNECTS_TO),
|
|
4129
|
+
source: fileNodeId,
|
|
3696
4130
|
target: dbNode.id,
|
|
3697
|
-
type:
|
|
3698
|
-
provenance:
|
|
3699
|
-
confidence: (0,
|
|
3700
|
-
|
|
3701
|
-
// Ghost-edge cleanup keys retirement on this; the conditional
|
|
3702
|
-
// sourceFile spread that used to live here was a v0.1.x leftover.
|
|
3703
|
-
evidence: {
|
|
3704
|
-
file: import_node_path17.default.relative(scanPath, config.sourceFile).split(import_node_path17.default.sep).join("/")
|
|
3705
|
-
}
|
|
4131
|
+
type: import_types9.EdgeType.CONNECTS_TO,
|
|
4132
|
+
provenance: import_types9.Provenance.EXTRACTED,
|
|
4133
|
+
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
4134
|
+
evidence: { file: evidenceFile }
|
|
3706
4135
|
};
|
|
3707
4136
|
if (!graph.hasEdge(edge.id)) {
|
|
3708
4137
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3728,15 +4157,15 @@ async function addDatabasesAndCompat(graph, services, scanPath) {
|
|
|
3728
4157
|
|
|
3729
4158
|
// src/extract/configs.ts
|
|
3730
4159
|
init_cjs_shims();
|
|
3731
|
-
var
|
|
3732
|
-
var
|
|
3733
|
-
var
|
|
4160
|
+
var import_node_fs14 = require("fs");
|
|
4161
|
+
var import_node_path21 = __toESM(require("path"), 1);
|
|
4162
|
+
var import_types10 = require("@neat.is/types");
|
|
3734
4163
|
async function walkConfigFiles(dir) {
|
|
3735
4164
|
const out = [];
|
|
3736
4165
|
async function walk(current) {
|
|
3737
|
-
const entries = await
|
|
4166
|
+
const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
|
|
3738
4167
|
for (const entry2 of entries) {
|
|
3739
|
-
const full =
|
|
4168
|
+
const full = import_node_path21.default.join(current, entry2.name);
|
|
3740
4169
|
if (entry2.isDirectory()) {
|
|
3741
4170
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3742
4171
|
if (await isPythonVenvDir(full)) continue;
|
|
@@ -3755,26 +4184,35 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3755
4184
|
for (const service of services) {
|
|
3756
4185
|
const configFiles = await walkConfigFiles(service.dir);
|
|
3757
4186
|
for (const file of configFiles) {
|
|
3758
|
-
const relPath =
|
|
4187
|
+
const relPath = import_node_path21.default.relative(scanPath, file);
|
|
3759
4188
|
const node = {
|
|
3760
|
-
id: (0,
|
|
3761
|
-
type:
|
|
3762
|
-
name:
|
|
4189
|
+
id: (0, import_types10.configId)(relPath),
|
|
4190
|
+
type: import_types10.NodeType.ConfigNode,
|
|
4191
|
+
name: import_node_path21.default.basename(file),
|
|
3763
4192
|
path: relPath,
|
|
3764
|
-
fileType: isConfigFile(
|
|
4193
|
+
fileType: isConfigFile(import_node_path21.default.basename(file)).fileType
|
|
3765
4194
|
};
|
|
3766
4195
|
if (!graph.hasNode(node.id)) {
|
|
3767
4196
|
graph.addNode(node.id, node);
|
|
3768
4197
|
nodesAdded++;
|
|
3769
4198
|
}
|
|
4199
|
+
const relToService = toPosix2(import_node_path21.default.relative(service.dir, file));
|
|
4200
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4201
|
+
graph,
|
|
4202
|
+
service.pkg.name,
|
|
4203
|
+
service.node.id,
|
|
4204
|
+
relToService
|
|
4205
|
+
);
|
|
4206
|
+
nodesAdded += fn;
|
|
4207
|
+
edgesAdded += fe;
|
|
3770
4208
|
const edge = {
|
|
3771
|
-
id: (0, import_types4.extractedEdgeId)(
|
|
3772
|
-
source:
|
|
4209
|
+
id: (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types10.EdgeType.CONFIGURED_BY),
|
|
4210
|
+
source: fileNodeId,
|
|
3773
4211
|
target: node.id,
|
|
3774
|
-
type:
|
|
3775
|
-
provenance:
|
|
3776
|
-
confidence: (0,
|
|
3777
|
-
evidence: { file: relPath.split(
|
|
4212
|
+
type: import_types10.EdgeType.CONFIGURED_BY,
|
|
4213
|
+
provenance: import_types10.Provenance.EXTRACTED,
|
|
4214
|
+
confidence: (0, import_types10.confidenceForExtracted)("structural"),
|
|
4215
|
+
evidence: { file: relPath.split(import_node_path21.default.sep).join("/") }
|
|
3778
4216
|
};
|
|
3779
4217
|
if (!graph.hasEdge(edge.id)) {
|
|
3780
4218
|
graph.addEdgeWithKey(edge.id, edge.source, edge.target, edge);
|
|
@@ -3787,114 +4225,15 @@ async function addConfigNodes(graph, services, scanPath) {
|
|
|
3787
4225
|
|
|
3788
4226
|
// src/extract/calls/index.ts
|
|
3789
4227
|
init_cjs_shims();
|
|
3790
|
-
var
|
|
4228
|
+
var import_types16 = require("@neat.is/types");
|
|
3791
4229
|
|
|
3792
4230
|
// src/extract/calls/http.ts
|
|
3793
4231
|
init_cjs_shims();
|
|
3794
|
-
var
|
|
3795
|
-
var
|
|
3796
|
-
var
|
|
3797
|
-
var
|
|
3798
|
-
var
|
|
3799
|
-
|
|
3800
|
-
// src/extract/calls/shared.ts
|
|
3801
|
-
init_cjs_shims();
|
|
3802
|
-
var import_node_fs13 = require("fs");
|
|
3803
|
-
var import_node_path19 = __toESM(require("path"), 1);
|
|
3804
|
-
var import_types9 = require("@neat.is/types");
|
|
3805
|
-
async function walkSourceFiles(dir) {
|
|
3806
|
-
const out = [];
|
|
3807
|
-
async function walk(current) {
|
|
3808
|
-
const entries = await import_node_fs13.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
3809
|
-
for (const entry2 of entries) {
|
|
3810
|
-
const full = import_node_path19.default.join(current, entry2.name);
|
|
3811
|
-
if (entry2.isDirectory()) {
|
|
3812
|
-
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
3813
|
-
if (await isPythonVenvDir(full)) continue;
|
|
3814
|
-
await walk(full);
|
|
3815
|
-
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path19.default.extname(entry2.name))) {
|
|
3816
|
-
out.push(full);
|
|
3817
|
-
}
|
|
3818
|
-
}
|
|
3819
|
-
}
|
|
3820
|
-
await walk(dir);
|
|
3821
|
-
return out;
|
|
3822
|
-
}
|
|
3823
|
-
async function loadSourceFiles(dir) {
|
|
3824
|
-
const paths = await walkSourceFiles(dir);
|
|
3825
|
-
const out = [];
|
|
3826
|
-
for (const p of paths) {
|
|
3827
|
-
try {
|
|
3828
|
-
const content = await import_node_fs13.promises.readFile(p, "utf8");
|
|
3829
|
-
out.push({ path: p, content });
|
|
3830
|
-
} catch {
|
|
3831
|
-
}
|
|
3832
|
-
}
|
|
3833
|
-
return out;
|
|
3834
|
-
}
|
|
3835
|
-
function lineOf(text, needle) {
|
|
3836
|
-
const idx = text.indexOf(needle);
|
|
3837
|
-
if (idx < 0) return 1;
|
|
3838
|
-
return text.slice(0, idx).split("\n").length;
|
|
3839
|
-
}
|
|
3840
|
-
function snippet(text, line) {
|
|
3841
|
-
const lines = text.split("\n");
|
|
3842
|
-
return (lines[line - 1] ?? "").trim();
|
|
3843
|
-
}
|
|
3844
|
-
function toPosix2(p) {
|
|
3845
|
-
return p.split("\\").join("/");
|
|
3846
|
-
}
|
|
3847
|
-
function languageForPath(relPath) {
|
|
3848
|
-
switch (import_node_path19.default.extname(relPath).toLowerCase()) {
|
|
3849
|
-
case ".py":
|
|
3850
|
-
return "python";
|
|
3851
|
-
case ".ts":
|
|
3852
|
-
case ".tsx":
|
|
3853
|
-
return "typescript";
|
|
3854
|
-
case ".js":
|
|
3855
|
-
case ".jsx":
|
|
3856
|
-
case ".mjs":
|
|
3857
|
-
case ".cjs":
|
|
3858
|
-
return "javascript";
|
|
3859
|
-
default:
|
|
3860
|
-
return void 0;
|
|
3861
|
-
}
|
|
3862
|
-
}
|
|
3863
|
-
function ensureFileNode(graph, serviceName, serviceNodeId, relPath) {
|
|
3864
|
-
let nodesAdded = 0;
|
|
3865
|
-
let edgesAdded = 0;
|
|
3866
|
-
const fileNodeId = (0, import_types9.fileId)(serviceName, relPath);
|
|
3867
|
-
if (!graph.hasNode(fileNodeId)) {
|
|
3868
|
-
const language = languageForPath(relPath);
|
|
3869
|
-
const node = {
|
|
3870
|
-
id: fileNodeId,
|
|
3871
|
-
type: import_types9.NodeType.FileNode,
|
|
3872
|
-
service: serviceName,
|
|
3873
|
-
path: relPath,
|
|
3874
|
-
...language ? { language } : {},
|
|
3875
|
-
discoveredVia: "static"
|
|
3876
|
-
};
|
|
3877
|
-
graph.addNode(fileNodeId, node);
|
|
3878
|
-
nodesAdded++;
|
|
3879
|
-
}
|
|
3880
|
-
const containsId = (0, import_types9.extractedEdgeId)(serviceNodeId, fileNodeId, import_types9.EdgeType.CONTAINS);
|
|
3881
|
-
if (!graph.hasEdge(containsId)) {
|
|
3882
|
-
const edge = {
|
|
3883
|
-
id: containsId,
|
|
3884
|
-
source: serviceNodeId,
|
|
3885
|
-
target: fileNodeId,
|
|
3886
|
-
type: import_types9.EdgeType.CONTAINS,
|
|
3887
|
-
provenance: import_types9.Provenance.EXTRACTED,
|
|
3888
|
-
confidence: (0, import_types9.confidenceForExtracted)("structural"),
|
|
3889
|
-
evidence: { file: relPath }
|
|
3890
|
-
};
|
|
3891
|
-
graph.addEdgeWithKey(containsId, serviceNodeId, fileNodeId, edge);
|
|
3892
|
-
edgesAdded++;
|
|
3893
|
-
}
|
|
3894
|
-
return { fileNodeId, nodesAdded, edgesAdded };
|
|
3895
|
-
}
|
|
3896
|
-
|
|
3897
|
-
// src/extract/calls/http.ts
|
|
4232
|
+
var import_node_path22 = __toESM(require("path"), 1);
|
|
4233
|
+
var import_tree_sitter2 = __toESM(require("tree-sitter"), 1);
|
|
4234
|
+
var import_tree_sitter_javascript2 = __toESM(require("tree-sitter-javascript"), 1);
|
|
4235
|
+
var import_tree_sitter_python2 = __toESM(require("tree-sitter-python"), 1);
|
|
4236
|
+
var import_types11 = require("@neat.is/types");
|
|
3898
4237
|
var STRING_LITERAL_NODE_TYPES = /* @__PURE__ */ new Set(["string_fragment", "string_content"]);
|
|
3899
4238
|
var JSX_EXTERNAL_LINK_TAGS = /* @__PURE__ */ new Set(["a", "Link", "NavLink", "ExternalLink", "Anchor"]);
|
|
3900
4239
|
function isInsideJsxExternalLink(node) {
|
|
@@ -3922,14 +4261,14 @@ function collectStringLiterals(node, out) {
|
|
|
3922
4261
|
if (child) collectStringLiterals(child, out);
|
|
3923
4262
|
}
|
|
3924
4263
|
}
|
|
3925
|
-
var
|
|
3926
|
-
function
|
|
4264
|
+
var PARSE_CHUNK2 = 16384;
|
|
4265
|
+
function parseSource2(parser, source) {
|
|
3927
4266
|
return parser.parse(
|
|
3928
|
-
(index) => index >= source.length ? "" : source.slice(index, index +
|
|
4267
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK2)
|
|
3929
4268
|
);
|
|
3930
4269
|
}
|
|
3931
4270
|
function callsFromSource(source, parser, knownHosts) {
|
|
3932
|
-
const tree =
|
|
4271
|
+
const tree = parseSource2(parser, source);
|
|
3933
4272
|
const literals = [];
|
|
3934
4273
|
collectStringLiterals(tree.rootNode, literals);
|
|
3935
4274
|
const out = [];
|
|
@@ -3943,25 +4282,25 @@ function callsFromSource(source, parser, knownHosts) {
|
|
|
3943
4282
|
}
|
|
3944
4283
|
return out;
|
|
3945
4284
|
}
|
|
3946
|
-
function
|
|
3947
|
-
const p = new
|
|
3948
|
-
p.setLanguage(
|
|
4285
|
+
function makeJsParser2() {
|
|
4286
|
+
const p = new import_tree_sitter2.default();
|
|
4287
|
+
p.setLanguage(import_tree_sitter_javascript2.default);
|
|
3949
4288
|
return p;
|
|
3950
4289
|
}
|
|
3951
|
-
function
|
|
3952
|
-
const p = new
|
|
3953
|
-
p.setLanguage(
|
|
4290
|
+
function makePyParser2() {
|
|
4291
|
+
const p = new import_tree_sitter2.default();
|
|
4292
|
+
p.setLanguage(import_tree_sitter_python2.default);
|
|
3954
4293
|
return p;
|
|
3955
4294
|
}
|
|
3956
4295
|
async function addHttpCallEdges(graph, services) {
|
|
3957
|
-
const jsParser =
|
|
3958
|
-
const pyParser =
|
|
4296
|
+
const jsParser = makeJsParser2();
|
|
4297
|
+
const pyParser = makePyParser2();
|
|
3959
4298
|
const knownHosts = /* @__PURE__ */ new Set();
|
|
3960
4299
|
const hostToNodeId = /* @__PURE__ */ new Map();
|
|
3961
4300
|
for (const service of services) {
|
|
3962
|
-
knownHosts.add(
|
|
4301
|
+
knownHosts.add(import_node_path22.default.basename(service.dir));
|
|
3963
4302
|
knownHosts.add(service.pkg.name);
|
|
3964
|
-
hostToNodeId.set(
|
|
4303
|
+
hostToNodeId.set(import_node_path22.default.basename(service.dir), service.node.id);
|
|
3965
4304
|
hostToNodeId.set(service.pkg.name, service.node.id);
|
|
3966
4305
|
}
|
|
3967
4306
|
let nodesAdded = 0;
|
|
@@ -3971,7 +4310,7 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3971
4310
|
const seen = /* @__PURE__ */ new Set();
|
|
3972
4311
|
for (const file of files) {
|
|
3973
4312
|
if (isTestPath(file.path)) continue;
|
|
3974
|
-
const parser =
|
|
4313
|
+
const parser = import_node_path22.default.extname(file.path) === ".py" ? pyParser : jsParser;
|
|
3975
4314
|
let sites;
|
|
3976
4315
|
try {
|
|
3977
4316
|
sites = callsFromSource(file.content, parser, knownHosts);
|
|
@@ -3980,14 +4319,14 @@ async function addHttpCallEdges(graph, services) {
|
|
|
3980
4319
|
continue;
|
|
3981
4320
|
}
|
|
3982
4321
|
if (sites.length === 0) continue;
|
|
3983
|
-
const relFile = toPosix2(
|
|
4322
|
+
const relFile = toPosix2(import_node_path22.default.relative(service.dir, file.path));
|
|
3984
4323
|
for (const site of sites) {
|
|
3985
4324
|
const targetId = hostToNodeId.get(site.host);
|
|
3986
4325
|
if (!targetId || targetId === service.node.id) continue;
|
|
3987
4326
|
const dedupKey = `${relFile}|${targetId}`;
|
|
3988
4327
|
if (seen.has(dedupKey)) continue;
|
|
3989
4328
|
seen.add(dedupKey);
|
|
3990
|
-
const confidence = (0,
|
|
4329
|
+
const confidence = (0, import_types11.confidenceForExtracted)("hostname-shape-match");
|
|
3991
4330
|
const ev = {
|
|
3992
4331
|
file: relFile,
|
|
3993
4332
|
line: site.line,
|
|
@@ -4001,25 +4340,25 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4001
4340
|
);
|
|
4002
4341
|
nodesAdded += n;
|
|
4003
4342
|
edgesAdded += e;
|
|
4004
|
-
if (!(0,
|
|
4343
|
+
if (!(0, import_types11.passesExtractedFloor)(confidence)) {
|
|
4005
4344
|
noteExtractedDropped({
|
|
4006
4345
|
source: fileNodeId,
|
|
4007
4346
|
target: targetId,
|
|
4008
|
-
type:
|
|
4347
|
+
type: import_types11.EdgeType.CALLS,
|
|
4009
4348
|
confidence,
|
|
4010
4349
|
confidenceKind: "hostname-shape-match",
|
|
4011
4350
|
evidence: ev
|
|
4012
4351
|
});
|
|
4013
4352
|
continue;
|
|
4014
4353
|
}
|
|
4015
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId,
|
|
4354
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, targetId, import_types11.EdgeType.CALLS);
|
|
4016
4355
|
if (!graph.hasEdge(edgeId)) {
|
|
4017
4356
|
const edge = {
|
|
4018
4357
|
id: edgeId,
|
|
4019
4358
|
source: fileNodeId,
|
|
4020
4359
|
target: targetId,
|
|
4021
|
-
type:
|
|
4022
|
-
provenance:
|
|
4360
|
+
type: import_types11.EdgeType.CALLS,
|
|
4361
|
+
provenance: import_types11.Provenance.EXTRACTED,
|
|
4023
4362
|
confidence,
|
|
4024
4363
|
evidence: ev
|
|
4025
4364
|
};
|
|
@@ -4034,8 +4373,8 @@ async function addHttpCallEdges(graph, services) {
|
|
|
4034
4373
|
|
|
4035
4374
|
// src/extract/calls/kafka.ts
|
|
4036
4375
|
init_cjs_shims();
|
|
4037
|
-
var
|
|
4038
|
-
var
|
|
4376
|
+
var import_node_path23 = __toESM(require("path"), 1);
|
|
4377
|
+
var import_types12 = require("@neat.is/types");
|
|
4039
4378
|
var PRODUCER_TOPIC_RE = /(?:producer|kafkaProducer)[\s\S]{0,40}?\.send\s*\(\s*\{[\s\S]{0,200}?topic\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4040
4379
|
var CONSUMER_TOPIC_RE = /(?:consumer|kafkaConsumer)[\s\S]{0,40}?\.(?:subscribe|run)\s*\(\s*\{[\s\S]{0,200}?topic[s]?\s*:\s*(?:\[\s*)?['"`]([^'"`]+)['"`]/g;
|
|
4041
4380
|
function findAll(re, text) {
|
|
@@ -4056,7 +4395,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4056
4395
|
seen.add(key);
|
|
4057
4396
|
const line = lineOf(file.content, topic);
|
|
4058
4397
|
out.push({
|
|
4059
|
-
infraId: (0,
|
|
4398
|
+
infraId: (0, import_types12.infraId)("kafka-topic", topic),
|
|
4060
4399
|
name: topic,
|
|
4061
4400
|
kind: "kafka-topic",
|
|
4062
4401
|
edgeType,
|
|
@@ -4065,7 +4404,7 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4065
4404
|
// tier (ADR-066).
|
|
4066
4405
|
confidenceKind: "verified-call-site",
|
|
4067
4406
|
evidence: {
|
|
4068
|
-
file:
|
|
4407
|
+
file: import_node_path23.default.relative(serviceDir, file.path),
|
|
4069
4408
|
line,
|
|
4070
4409
|
snippet: snippet(file.content, line)
|
|
4071
4410
|
}
|
|
@@ -4078,8 +4417,8 @@ function kafkaEndpointsFromFile(file, serviceDir) {
|
|
|
4078
4417
|
|
|
4079
4418
|
// src/extract/calls/redis.ts
|
|
4080
4419
|
init_cjs_shims();
|
|
4081
|
-
var
|
|
4082
|
-
var
|
|
4420
|
+
var import_node_path24 = __toESM(require("path"), 1);
|
|
4421
|
+
var import_types13 = require("@neat.is/types");
|
|
4083
4422
|
var REDIS_URL_RE = /redis(?:s)?:\/\/(?:[^@'"`\s]+@)?([^:/'"`\s]+)(?::(\d+))?/g;
|
|
4084
4423
|
function redisEndpointsFromFile(file, serviceDir) {
|
|
4085
4424
|
const out = [];
|
|
@@ -4092,7 +4431,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4092
4431
|
seen.add(host);
|
|
4093
4432
|
const line = lineOf(file.content, host);
|
|
4094
4433
|
out.push({
|
|
4095
|
-
infraId: (0,
|
|
4434
|
+
infraId: (0, import_types13.infraId)("redis", host),
|
|
4096
4435
|
name: host,
|
|
4097
4436
|
kind: "redis",
|
|
4098
4437
|
edgeType: "CALLS",
|
|
@@ -4101,7 +4440,7 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4101
4440
|
// support tier (ADR-066).
|
|
4102
4441
|
confidenceKind: "url-with-structural-support",
|
|
4103
4442
|
evidence: {
|
|
4104
|
-
file:
|
|
4443
|
+
file: import_node_path24.default.relative(serviceDir, file.path),
|
|
4105
4444
|
line,
|
|
4106
4445
|
snippet: snippet(file.content, line)
|
|
4107
4446
|
}
|
|
@@ -4112,8 +4451,8 @@ function redisEndpointsFromFile(file, serviceDir) {
|
|
|
4112
4451
|
|
|
4113
4452
|
// src/extract/calls/aws.ts
|
|
4114
4453
|
init_cjs_shims();
|
|
4115
|
-
var
|
|
4116
|
-
var
|
|
4454
|
+
var import_node_path25 = __toESM(require("path"), 1);
|
|
4455
|
+
var import_types14 = require("@neat.is/types");
|
|
4117
4456
|
var S3_BUCKET_RE = /Bucket\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4118
4457
|
var DYNAMO_TABLE_RE = /TableName\s*:\s*['"`]([^'"`]+)['"`]/g;
|
|
4119
4458
|
function hasMarker(text, markers) {
|
|
@@ -4137,7 +4476,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4137
4476
|
seen.add(key);
|
|
4138
4477
|
const line = lineOf(file.content, name);
|
|
4139
4478
|
out.push({
|
|
4140
|
-
infraId: (0,
|
|
4479
|
+
infraId: (0, import_types14.infraId)(kind, name),
|
|
4141
4480
|
name,
|
|
4142
4481
|
kind,
|
|
4143
4482
|
edgeType: "CALLS",
|
|
@@ -4146,7 +4485,7 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4146
4485
|
// (ADR-066).
|
|
4147
4486
|
confidenceKind: "verified-call-site",
|
|
4148
4487
|
evidence: {
|
|
4149
|
-
file:
|
|
4488
|
+
file: import_node_path25.default.relative(serviceDir, file.path),
|
|
4150
4489
|
line,
|
|
4151
4490
|
snippet: snippet(file.content, line)
|
|
4152
4491
|
}
|
|
@@ -4171,13 +4510,14 @@ function awsEndpointsFromFile(file, serviceDir) {
|
|
|
4171
4510
|
|
|
4172
4511
|
// src/extract/calls/grpc.ts
|
|
4173
4512
|
init_cjs_shims();
|
|
4174
|
-
var
|
|
4175
|
-
var
|
|
4513
|
+
var import_node_path26 = __toESM(require("path"), 1);
|
|
4514
|
+
var import_types15 = require("@neat.is/types");
|
|
4176
4515
|
var GRPC_CLIENT_RE = /new\s+([A-Z][A-Za-z0-9_]*)Client\s*\(\s*['"`]?([^,'"`)]+)?/g;
|
|
4177
4516
|
var AWS_SDK_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@aws-sdk\/client-([a-z0-9-]+)['"`]/g;
|
|
4178
4517
|
var GRPC_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@grpc\/grpc-js['"`]|_grpc_pb['"`]/;
|
|
4179
4518
|
function isLikelyAddress(value) {
|
|
4180
4519
|
if (!value) return false;
|
|
4520
|
+
if (/\s/.test(value) || value.startsWith("{")) return false;
|
|
4181
4521
|
return /:\d{2,5}$/.test(value) || value.includes(".");
|
|
4182
4522
|
}
|
|
4183
4523
|
function normaliseForMatch(s) {
|
|
@@ -4220,7 +4560,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4220
4560
|
const { kind } = classified;
|
|
4221
4561
|
const line = lineOf(file.content, m[0]);
|
|
4222
4562
|
out.push({
|
|
4223
|
-
infraId: (0,
|
|
4563
|
+
infraId: (0, import_types15.infraId)(kind, name),
|
|
4224
4564
|
name,
|
|
4225
4565
|
kind,
|
|
4226
4566
|
edgeType: "CALLS",
|
|
@@ -4229,7 +4569,7 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4229
4569
|
// tier (ADR-066).
|
|
4230
4570
|
confidenceKind: "verified-call-site",
|
|
4231
4571
|
evidence: {
|
|
4232
|
-
file:
|
|
4572
|
+
file: import_node_path26.default.relative(serviceDir, file.path),
|
|
4233
4573
|
line,
|
|
4234
4574
|
snippet: snippet(file.content, line)
|
|
4235
4575
|
}
|
|
@@ -4242,11 +4582,11 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
4242
4582
|
function edgeTypeFromEndpoint(ep) {
|
|
4243
4583
|
switch (ep.edgeType) {
|
|
4244
4584
|
case "PUBLISHES_TO":
|
|
4245
|
-
return
|
|
4585
|
+
return import_types16.EdgeType.PUBLISHES_TO;
|
|
4246
4586
|
case "CONSUMES_FROM":
|
|
4247
|
-
return
|
|
4587
|
+
return import_types16.EdgeType.CONSUMES_FROM;
|
|
4248
4588
|
default:
|
|
4249
|
-
return
|
|
4589
|
+
return import_types16.EdgeType.CALLS;
|
|
4250
4590
|
}
|
|
4251
4591
|
}
|
|
4252
4592
|
function isAwsKind(kind) {
|
|
@@ -4273,7 +4613,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4273
4613
|
if (!graph.hasNode(ep.infraId)) {
|
|
4274
4614
|
const node = {
|
|
4275
4615
|
id: ep.infraId,
|
|
4276
|
-
type:
|
|
4616
|
+
type: import_types16.NodeType.InfraNode,
|
|
4277
4617
|
name: ep.name,
|
|
4278
4618
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
4279
4619
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -4285,7 +4625,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4285
4625
|
nodesAdded++;
|
|
4286
4626
|
}
|
|
4287
4627
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
4288
|
-
const confidence = (0,
|
|
4628
|
+
const confidence = (0, import_types16.confidenceForExtracted)(ep.confidenceKind);
|
|
4289
4629
|
const relFile = toPosix2(ep.evidence.file);
|
|
4290
4630
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
4291
4631
|
graph,
|
|
@@ -4295,7 +4635,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4295
4635
|
);
|
|
4296
4636
|
nodesAdded += n;
|
|
4297
4637
|
edgesAdded += e;
|
|
4298
|
-
if (!(0,
|
|
4638
|
+
if (!(0, import_types16.passesExtractedFloor)(confidence)) {
|
|
4299
4639
|
noteExtractedDropped({
|
|
4300
4640
|
source: fileNodeId,
|
|
4301
4641
|
target: ep.infraId,
|
|
@@ -4315,7 +4655,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4315
4655
|
source: fileNodeId,
|
|
4316
4656
|
target: ep.infraId,
|
|
4317
4657
|
type: edgeType,
|
|
4318
|
-
provenance:
|
|
4658
|
+
provenance: import_types16.Provenance.EXTRACTED,
|
|
4319
4659
|
confidence,
|
|
4320
4660
|
evidence: ep.evidence
|
|
4321
4661
|
};
|
|
@@ -4340,16 +4680,16 @@ init_cjs_shims();
|
|
|
4340
4680
|
|
|
4341
4681
|
// src/extract/infra/docker-compose.ts
|
|
4342
4682
|
init_cjs_shims();
|
|
4343
|
-
var
|
|
4344
|
-
var
|
|
4683
|
+
var import_node_path27 = __toESM(require("path"), 1);
|
|
4684
|
+
var import_types18 = require("@neat.is/types");
|
|
4345
4685
|
|
|
4346
4686
|
// src/extract/infra/shared.ts
|
|
4347
4687
|
init_cjs_shims();
|
|
4348
|
-
var
|
|
4688
|
+
var import_types17 = require("@neat.is/types");
|
|
4349
4689
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
4350
4690
|
return {
|
|
4351
|
-
id: (0,
|
|
4352
|
-
type:
|
|
4691
|
+
id: (0, import_types17.infraId)(kind, name),
|
|
4692
|
+
type: import_types17.NodeType.InfraNode,
|
|
4353
4693
|
name,
|
|
4354
4694
|
provider,
|
|
4355
4695
|
kind,
|
|
@@ -4378,7 +4718,7 @@ function dependsOnList(value) {
|
|
|
4378
4718
|
}
|
|
4379
4719
|
function serviceNameToServiceNode(name, services) {
|
|
4380
4720
|
for (const s of services) {
|
|
4381
|
-
if (s.node.name === name ||
|
|
4721
|
+
if (s.node.name === name || import_node_path27.default.basename(s.dir) === name) return s.node.id;
|
|
4382
4722
|
}
|
|
4383
4723
|
return null;
|
|
4384
4724
|
}
|
|
@@ -4387,7 +4727,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4387
4727
|
let edgesAdded = 0;
|
|
4388
4728
|
let composePath = null;
|
|
4389
4729
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
4390
|
-
const abs =
|
|
4730
|
+
const abs = import_node_path27.default.join(scanPath, name);
|
|
4391
4731
|
if (await exists(abs)) {
|
|
4392
4732
|
composePath = abs;
|
|
4393
4733
|
break;
|
|
@@ -4400,13 +4740,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4400
4740
|
} catch (err) {
|
|
4401
4741
|
recordExtractionError(
|
|
4402
4742
|
"infra docker-compose",
|
|
4403
|
-
|
|
4743
|
+
import_node_path27.default.relative(scanPath, composePath),
|
|
4404
4744
|
err
|
|
4405
4745
|
);
|
|
4406
4746
|
return { nodesAdded, edgesAdded };
|
|
4407
4747
|
}
|
|
4408
4748
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
4409
|
-
const evidenceFile =
|
|
4749
|
+
const evidenceFile = import_node_path27.default.relative(scanPath, composePath).split(import_node_path27.default.sep).join("/");
|
|
4410
4750
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
4411
4751
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
4412
4752
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -4428,15 +4768,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4428
4768
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
4429
4769
|
const targetId = composeNameToNodeId.get(dep);
|
|
4430
4770
|
if (!targetId) continue;
|
|
4431
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
4771
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types18.EdgeType.DEPENDS_ON);
|
|
4432
4772
|
if (graph.hasEdge(edgeId)) continue;
|
|
4433
4773
|
const edge = {
|
|
4434
4774
|
id: edgeId,
|
|
4435
4775
|
source: sourceId,
|
|
4436
4776
|
target: targetId,
|
|
4437
|
-
type:
|
|
4438
|
-
provenance:
|
|
4439
|
-
confidence: (0,
|
|
4777
|
+
type: import_types18.EdgeType.DEPENDS_ON,
|
|
4778
|
+
provenance: import_types18.Provenance.EXTRACTED,
|
|
4779
|
+
confidence: (0, import_types18.confidenceForExtracted)("structural"),
|
|
4440
4780
|
evidence: { file: evidenceFile }
|
|
4441
4781
|
};
|
|
4442
4782
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4448,9 +4788,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4448
4788
|
|
|
4449
4789
|
// src/extract/infra/dockerfile.ts
|
|
4450
4790
|
init_cjs_shims();
|
|
4451
|
-
var
|
|
4452
|
-
var
|
|
4453
|
-
var
|
|
4791
|
+
var import_node_path28 = __toESM(require("path"), 1);
|
|
4792
|
+
var import_node_fs15 = require("fs");
|
|
4793
|
+
var import_types19 = require("@neat.is/types");
|
|
4454
4794
|
function runtimeImage(content) {
|
|
4455
4795
|
const lines = content.split("\n");
|
|
4456
4796
|
let last = null;
|
|
@@ -4469,15 +4809,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4469
4809
|
let nodesAdded = 0;
|
|
4470
4810
|
let edgesAdded = 0;
|
|
4471
4811
|
for (const service of services) {
|
|
4472
|
-
const dockerfilePath =
|
|
4812
|
+
const dockerfilePath = import_node_path28.default.join(service.dir, "Dockerfile");
|
|
4473
4813
|
if (!await exists(dockerfilePath)) continue;
|
|
4474
4814
|
let content;
|
|
4475
4815
|
try {
|
|
4476
|
-
content = await
|
|
4816
|
+
content = await import_node_fs15.promises.readFile(dockerfilePath, "utf8");
|
|
4477
4817
|
} catch (err) {
|
|
4478
4818
|
recordExtractionError(
|
|
4479
4819
|
"infra dockerfile",
|
|
4480
|
-
|
|
4820
|
+
import_node_path28.default.relative(scanPath, dockerfilePath),
|
|
4481
4821
|
err
|
|
4482
4822
|
);
|
|
4483
4823
|
continue;
|
|
@@ -4489,17 +4829,26 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4489
4829
|
graph.addNode(node.id, node);
|
|
4490
4830
|
nodesAdded++;
|
|
4491
4831
|
}
|
|
4492
|
-
const
|
|
4832
|
+
const relDockerfile = toPosix2(import_node_path28.default.relative(service.dir, dockerfilePath));
|
|
4833
|
+
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4834
|
+
graph,
|
|
4835
|
+
service.pkg.name,
|
|
4836
|
+
service.node.id,
|
|
4837
|
+
relDockerfile
|
|
4838
|
+
);
|
|
4839
|
+
nodesAdded += fn;
|
|
4840
|
+
edgesAdded += fe;
|
|
4841
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types19.EdgeType.RUNS_ON);
|
|
4493
4842
|
if (!graph.hasEdge(edgeId)) {
|
|
4494
4843
|
const edge = {
|
|
4495
4844
|
id: edgeId,
|
|
4496
|
-
source:
|
|
4845
|
+
source: fileNodeId,
|
|
4497
4846
|
target: node.id,
|
|
4498
|
-
type:
|
|
4499
|
-
provenance:
|
|
4500
|
-
confidence: (0,
|
|
4847
|
+
type: import_types19.EdgeType.RUNS_ON,
|
|
4848
|
+
provenance: import_types19.Provenance.EXTRACTED,
|
|
4849
|
+
confidence: (0, import_types19.confidenceForExtracted)("structural"),
|
|
4501
4850
|
evidence: {
|
|
4502
|
-
file:
|
|
4851
|
+
file: toPosix2(import_node_path28.default.relative(scanPath, dockerfilePath))
|
|
4503
4852
|
}
|
|
4504
4853
|
};
|
|
4505
4854
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4511,21 +4860,21 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4511
4860
|
|
|
4512
4861
|
// src/extract/infra/terraform.ts
|
|
4513
4862
|
init_cjs_shims();
|
|
4514
|
-
var
|
|
4515
|
-
var
|
|
4863
|
+
var import_node_fs16 = require("fs");
|
|
4864
|
+
var import_node_path29 = __toESM(require("path"), 1);
|
|
4516
4865
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
4517
4866
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
4518
4867
|
if (depth > max) return [];
|
|
4519
4868
|
const out = [];
|
|
4520
|
-
const entries = await
|
|
4869
|
+
const entries = await import_node_fs16.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4521
4870
|
for (const entry2 of entries) {
|
|
4522
4871
|
if (entry2.isDirectory()) {
|
|
4523
4872
|
if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
|
|
4524
|
-
const child =
|
|
4873
|
+
const child = import_node_path29.default.join(start, entry2.name);
|
|
4525
4874
|
if (await isPythonVenvDir(child)) continue;
|
|
4526
4875
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
4527
4876
|
} else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
|
|
4528
|
-
out.push(
|
|
4877
|
+
out.push(import_node_path29.default.join(start, entry2.name));
|
|
4529
4878
|
}
|
|
4530
4879
|
}
|
|
4531
4880
|
return out;
|
|
@@ -4534,7 +4883,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4534
4883
|
let nodesAdded = 0;
|
|
4535
4884
|
const files = await walkTfFiles(scanPath);
|
|
4536
4885
|
for (const file of files) {
|
|
4537
|
-
const content = await
|
|
4886
|
+
const content = await import_node_fs16.promises.readFile(file, "utf8");
|
|
4538
4887
|
RESOURCE_RE.lastIndex = 0;
|
|
4539
4888
|
let m;
|
|
4540
4889
|
while ((m = RESOURCE_RE.exec(content)) !== null) {
|
|
@@ -4552,8 +4901,8 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4552
4901
|
|
|
4553
4902
|
// src/extract/infra/k8s.ts
|
|
4554
4903
|
init_cjs_shims();
|
|
4555
|
-
var
|
|
4556
|
-
var
|
|
4904
|
+
var import_node_fs17 = require("fs");
|
|
4905
|
+
var import_node_path30 = __toESM(require("path"), 1);
|
|
4557
4906
|
var import_yaml3 = require("yaml");
|
|
4558
4907
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
4559
4908
|
Service: "k8s-service",
|
|
@@ -4567,15 +4916,15 @@ var K8S_KIND_TO_INFRA_KIND = {
|
|
|
4567
4916
|
async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
4568
4917
|
if (depth > max) return [];
|
|
4569
4918
|
const out = [];
|
|
4570
|
-
const entries = await
|
|
4919
|
+
const entries = await import_node_fs17.promises.readdir(start, { withFileTypes: true }).catch(() => []);
|
|
4571
4920
|
for (const entry2 of entries) {
|
|
4572
4921
|
if (entry2.isDirectory()) {
|
|
4573
4922
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
4574
|
-
const child =
|
|
4923
|
+
const child = import_node_path30.default.join(start, entry2.name);
|
|
4575
4924
|
if (await isPythonVenvDir(child)) continue;
|
|
4576
4925
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
4577
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
4578
|
-
out.push(
|
|
4926
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path30.default.extname(entry2.name))) {
|
|
4927
|
+
out.push(import_node_path30.default.join(start, entry2.name));
|
|
4579
4928
|
}
|
|
4580
4929
|
}
|
|
4581
4930
|
return out;
|
|
@@ -4584,7 +4933,7 @@ async function addK8sResources(graph, scanPath) {
|
|
|
4584
4933
|
let nodesAdded = 0;
|
|
4585
4934
|
const files = await walkYamlFiles2(scanPath);
|
|
4586
4935
|
for (const file of files) {
|
|
4587
|
-
const content = await
|
|
4936
|
+
const content = await import_node_fs17.promises.readFile(file, "utf8");
|
|
4588
4937
|
let docs;
|
|
4589
4938
|
try {
|
|
4590
4939
|
docs = (0, import_yaml3.parseAllDocuments)(content).map((d) => d.toJSON());
|
|
@@ -4619,17 +4968,17 @@ async function addInfra(graph, scanPath, services) {
|
|
|
4619
4968
|
}
|
|
4620
4969
|
|
|
4621
4970
|
// src/extract/index.ts
|
|
4622
|
-
var
|
|
4971
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
4623
4972
|
|
|
4624
4973
|
// src/extract/retire.ts
|
|
4625
4974
|
init_cjs_shims();
|
|
4626
|
-
var
|
|
4627
|
-
var
|
|
4628
|
-
var
|
|
4975
|
+
var import_node_fs18 = require("fs");
|
|
4976
|
+
var import_node_path31 = __toESM(require("path"), 1);
|
|
4977
|
+
var import_types20 = require("@neat.is/types");
|
|
4629
4978
|
function dropOrphanedFileNodes(graph) {
|
|
4630
4979
|
const orphans = [];
|
|
4631
4980
|
graph.forEachNode((id, attrs) => {
|
|
4632
|
-
if (attrs.type !==
|
|
4981
|
+
if (attrs.type !== import_types20.NodeType.FileNode) return;
|
|
4633
4982
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
4634
4983
|
orphans.push(id);
|
|
4635
4984
|
}
|
|
@@ -4642,7 +4991,7 @@ function retireEdgesByFile(graph, file) {
|
|
|
4642
4991
|
const toDrop = [];
|
|
4643
4992
|
graph.forEachEdge((id, attrs) => {
|
|
4644
4993
|
const edge = attrs;
|
|
4645
|
-
if (edge.provenance !==
|
|
4994
|
+
if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
|
|
4646
4995
|
if (!edge.evidence?.file) return;
|
|
4647
4996
|
if (edge.evidence.file === normalized) toDrop.push(id);
|
|
4648
4997
|
});
|
|
@@ -4655,14 +5004,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
4655
5004
|
const bases = [scanPath, ...serviceDirs];
|
|
4656
5005
|
graph.forEachEdge((id, attrs) => {
|
|
4657
5006
|
const edge = attrs;
|
|
4658
|
-
if (edge.provenance !==
|
|
5007
|
+
if (edge.provenance !== import_types20.Provenance.EXTRACTED) return;
|
|
4659
5008
|
const evidenceFile = edge.evidence?.file;
|
|
4660
5009
|
if (!evidenceFile) return;
|
|
4661
|
-
if (
|
|
4662
|
-
if (!(0,
|
|
5010
|
+
if (import_node_path31.default.isAbsolute(evidenceFile)) {
|
|
5011
|
+
if (!(0, import_node_fs18.existsSync)(evidenceFile)) toDrop.push(id);
|
|
4663
5012
|
return;
|
|
4664
5013
|
}
|
|
4665
|
-
const found = bases.some((base) => (0,
|
|
5014
|
+
const found = bases.some((base) => (0, import_node_fs18.existsSync)(import_node_path31.default.join(base, evidenceFile)));
|
|
4666
5015
|
if (!found) toDrop.push(id);
|
|
4667
5016
|
});
|
|
4668
5017
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -4677,6 +5026,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4677
5026
|
const services = await discoverServices(scanPath);
|
|
4678
5027
|
const phase1Nodes = addServiceNodes(graph, services);
|
|
4679
5028
|
await addServiceAliases(graph, scanPath, services);
|
|
5029
|
+
const fileEnum = await addFiles(graph, services);
|
|
5030
|
+
const importGraph = await addImports(graph, services);
|
|
4680
5031
|
const phase2 = await addDatabasesAndCompat(graph, services, scanPath);
|
|
4681
5032
|
const phase3 = await addConfigNodes(graph, services, scanPath);
|
|
4682
5033
|
const phase4 = await addCallEdges(graph, services);
|
|
@@ -4700,7 +5051,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4700
5051
|
}
|
|
4701
5052
|
const droppedEntries = drainDroppedExtracted();
|
|
4702
5053
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
4703
|
-
const rejectedPath =
|
|
5054
|
+
const rejectedPath = import_node_path32.default.join(import_node_path32.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
4704
5055
|
try {
|
|
4705
5056
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
4706
5057
|
} catch (err) {
|
|
@@ -4710,8 +5061,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4710
5061
|
}
|
|
4711
5062
|
}
|
|
4712
5063
|
const result = {
|
|
4713
|
-
nodesAdded: phase1Nodes + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
4714
|
-
edgesAdded: phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
5064
|
+
nodesAdded: phase1Nodes + fileEnum.nodesAdded + importGraph.nodesAdded + phase2.nodesAdded + phase3.nodesAdded + phase4.nodesAdded + phase5.nodesAdded,
|
|
5065
|
+
edgesAdded: fileEnum.edgesAdded + importGraph.edgesAdded + phase2.edgesAdded + phase3.edgesAdded + phase4.edgesAdded + phase5.edgesAdded,
|
|
4715
5066
|
frontiersPromoted,
|
|
4716
5067
|
extractionErrors: errorEntries.length,
|
|
4717
5068
|
errorEntries,
|
|
@@ -4734,7 +5085,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4734
5085
|
|
|
4735
5086
|
// src/divergences.ts
|
|
4736
5087
|
init_cjs_shims();
|
|
4737
|
-
var
|
|
5088
|
+
var import_types21 = require("@neat.is/types");
|
|
4738
5089
|
function bucketKey(source, target, type) {
|
|
4739
5090
|
return `${type}|${source}|${target}`;
|
|
4740
5091
|
}
|
|
@@ -4742,22 +5093,22 @@ function bucketEdges(graph) {
|
|
|
4742
5093
|
const buckets = /* @__PURE__ */ new Map();
|
|
4743
5094
|
graph.forEachEdge((id, attrs) => {
|
|
4744
5095
|
const e = attrs;
|
|
4745
|
-
const parsed = (0,
|
|
5096
|
+
const parsed = (0, import_types21.parseEdgeId)(id);
|
|
4746
5097
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
4747
5098
|
const key = bucketKey(e.source, e.target, e.type);
|
|
4748
5099
|
const cur = buckets.get(key) ?? { source: e.source, target: e.target, type: e.type };
|
|
4749
5100
|
switch (provenance) {
|
|
4750
|
-
case
|
|
5101
|
+
case import_types21.Provenance.EXTRACTED:
|
|
4751
5102
|
cur.extracted = e;
|
|
4752
5103
|
break;
|
|
4753
|
-
case
|
|
5104
|
+
case import_types21.Provenance.OBSERVED:
|
|
4754
5105
|
cur.observed = e;
|
|
4755
5106
|
break;
|
|
4756
|
-
case
|
|
5107
|
+
case import_types21.Provenance.INFERRED:
|
|
4757
5108
|
cur.inferred = e;
|
|
4758
5109
|
break;
|
|
4759
5110
|
default:
|
|
4760
|
-
if (e.provenance ===
|
|
5111
|
+
if (e.provenance === import_types21.Provenance.STALE) cur.stale = e;
|
|
4761
5112
|
}
|
|
4762
5113
|
buckets.set(key, cur);
|
|
4763
5114
|
});
|
|
@@ -4766,7 +5117,7 @@ function bucketEdges(graph) {
|
|
|
4766
5117
|
function nodeIsFrontier(graph, nodeId) {
|
|
4767
5118
|
if (!graph.hasNode(nodeId)) return false;
|
|
4768
5119
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
4769
|
-
return attrs.type ===
|
|
5120
|
+
return attrs.type === import_types21.NodeType.FrontierNode;
|
|
4770
5121
|
}
|
|
4771
5122
|
function clampConfidence(n) {
|
|
4772
5123
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -4787,7 +5138,7 @@ function gradedConfidence(edge) {
|
|
|
4787
5138
|
}
|
|
4788
5139
|
function detectMissingDivergences(graph, bucket) {
|
|
4789
5140
|
const out = [];
|
|
4790
|
-
if (bucket.type ===
|
|
5141
|
+
if (bucket.type === import_types21.EdgeType.CONTAINS) return out;
|
|
4791
5142
|
if (bucket.extracted && !bucket.observed) {
|
|
4792
5143
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
4793
5144
|
out.push({
|
|
@@ -4828,7 +5179,7 @@ function declaredHostFor(svc) {
|
|
|
4828
5179
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
4829
5180
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4830
5181
|
const e = graph.getEdgeAttributes(edgeId);
|
|
4831
|
-
if (e.type ===
|
|
5182
|
+
if (e.type === import_types21.EdgeType.CONFIGURED_BY && e.provenance === import_types21.Provenance.EXTRACTED) {
|
|
4832
5183
|
return true;
|
|
4833
5184
|
}
|
|
4834
5185
|
}
|
|
@@ -4841,10 +5192,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
4841
5192
|
const out = [];
|
|
4842
5193
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4843
5194
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4844
|
-
if (edge.type !==
|
|
4845
|
-
if (edge.provenance !==
|
|
5195
|
+
if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
|
|
5196
|
+
if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
|
|
4846
5197
|
const target = graph.getNodeAttributes(edge.target);
|
|
4847
|
-
if (target.type !==
|
|
5198
|
+
if (target.type !== import_types21.NodeType.DatabaseNode) continue;
|
|
4848
5199
|
const observedHost = target.host?.trim();
|
|
4849
5200
|
if (!observedHost) continue;
|
|
4850
5201
|
if (observedHost === declaredHost) continue;
|
|
@@ -4866,10 +5217,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
4866
5217
|
const deps = svc.dependencies ?? {};
|
|
4867
5218
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
4868
5219
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
4869
|
-
if (edge.type !==
|
|
4870
|
-
if (edge.provenance !==
|
|
5220
|
+
if (edge.type !== import_types21.EdgeType.CONNECTS_TO) continue;
|
|
5221
|
+
if (edge.provenance !== import_types21.Provenance.OBSERVED) continue;
|
|
4871
5222
|
const target = graph.getNodeAttributes(edge.target);
|
|
4872
|
-
if (target.type !==
|
|
5223
|
+
if (target.type !== import_types21.NodeType.DatabaseNode) continue;
|
|
4873
5224
|
for (const pair of compatPairs()) {
|
|
4874
5225
|
if (pair.engine !== target.engine) continue;
|
|
4875
5226
|
const declared = deps[pair.driver];
|
|
@@ -4930,7 +5281,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4930
5281
|
}
|
|
4931
5282
|
graph.forEachNode((nodeId, attrs) => {
|
|
4932
5283
|
const n = attrs;
|
|
4933
|
-
if (n.type !==
|
|
5284
|
+
if (n.type !== import_types21.NodeType.ServiceNode) return;
|
|
4934
5285
|
const svc = n;
|
|
4935
5286
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
4936
5287
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -4963,7 +5314,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4963
5314
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
4964
5315
|
return a.target.localeCompare(b.target);
|
|
4965
5316
|
});
|
|
4966
|
-
return
|
|
5317
|
+
return import_types21.DivergenceResultSchema.parse({
|
|
4967
5318
|
divergences: filtered,
|
|
4968
5319
|
totalAffected: filtered.length,
|
|
4969
5320
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -4972,9 +5323,9 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4972
5323
|
|
|
4973
5324
|
// src/persist.ts
|
|
4974
5325
|
init_cjs_shims();
|
|
4975
|
-
var
|
|
4976
|
-
var
|
|
4977
|
-
var
|
|
5326
|
+
var import_node_fs19 = require("fs");
|
|
5327
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
5328
|
+
var import_types22 = require("@neat.is/types");
|
|
4978
5329
|
var SCHEMA_VERSION = 4;
|
|
4979
5330
|
function migrateV1ToV2(payload) {
|
|
4980
5331
|
const nodes = payload.graph.nodes;
|
|
@@ -4996,12 +5347,12 @@ function migrateV2ToV3(payload) {
|
|
|
4996
5347
|
for (const edge of edges) {
|
|
4997
5348
|
const attrs = edge.attributes;
|
|
4998
5349
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
4999
|
-
attrs.provenance =
|
|
5350
|
+
attrs.provenance = import_types22.Provenance.OBSERVED;
|
|
5000
5351
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
5001
5352
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
5002
5353
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
5003
5354
|
if (type && source && target) {
|
|
5004
|
-
const newId = (0,
|
|
5355
|
+
const newId = (0, import_types22.observedEdgeId)(source, target, type);
|
|
5005
5356
|
attrs.id = newId;
|
|
5006
5357
|
if (edge.key) edge.key = newId;
|
|
5007
5358
|
}
|
|
@@ -5010,7 +5361,7 @@ function migrateV2ToV3(payload) {
|
|
|
5010
5361
|
return { ...payload, schemaVersion: 3 };
|
|
5011
5362
|
}
|
|
5012
5363
|
async function ensureDir(filePath) {
|
|
5013
|
-
await
|
|
5364
|
+
await import_node_fs19.promises.mkdir(import_node_path33.default.dirname(filePath), { recursive: true });
|
|
5014
5365
|
}
|
|
5015
5366
|
async function saveGraphToDisk(graph, outPath) {
|
|
5016
5367
|
await ensureDir(outPath);
|
|
@@ -5020,13 +5371,13 @@ async function saveGraphToDisk(graph, outPath) {
|
|
|
5020
5371
|
graph: graph.export()
|
|
5021
5372
|
};
|
|
5022
5373
|
const tmp = `${outPath}.tmp`;
|
|
5023
|
-
await
|
|
5024
|
-
await
|
|
5374
|
+
await import_node_fs19.promises.writeFile(tmp, JSON.stringify(payload), "utf8");
|
|
5375
|
+
await import_node_fs19.promises.rename(tmp, outPath);
|
|
5025
5376
|
}
|
|
5026
5377
|
async function loadGraphFromDisk(graph, outPath) {
|
|
5027
5378
|
let raw;
|
|
5028
5379
|
try {
|
|
5029
|
-
raw = await
|
|
5380
|
+
raw = await import_node_fs19.promises.readFile(outPath, "utf8");
|
|
5030
5381
|
} catch (err) {
|
|
5031
5382
|
if (err.code === "ENOENT") return;
|
|
5032
5383
|
throw err;
|
|
@@ -5085,8 +5436,8 @@ function startPersistLoop(graph, outPath, intervalMs = 6e4) {
|
|
|
5085
5436
|
|
|
5086
5437
|
// src/gitignore.ts
|
|
5087
5438
|
init_cjs_shims();
|
|
5088
|
-
var
|
|
5089
|
-
var
|
|
5439
|
+
var import_node_fs20 = require("fs");
|
|
5440
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
5090
5441
|
var NEAT_OUT_LINE = "neat-out/";
|
|
5091
5442
|
var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
|
|
5092
5443
|
function isNeatOutLine(line) {
|
|
@@ -5094,15 +5445,15 @@ function isNeatOutLine(line) {
|
|
|
5094
5445
|
return trimmed === "neat-out/" || trimmed === "neat-out";
|
|
5095
5446
|
}
|
|
5096
5447
|
async function ensureNeatOutIgnored(projectDir) {
|
|
5097
|
-
const file =
|
|
5448
|
+
const file = import_node_path34.default.join(projectDir, ".gitignore");
|
|
5098
5449
|
let existing = null;
|
|
5099
5450
|
try {
|
|
5100
|
-
existing = await
|
|
5451
|
+
existing = await import_node_fs20.promises.readFile(file, "utf8");
|
|
5101
5452
|
} catch (err) {
|
|
5102
5453
|
if (err.code !== "ENOENT") throw err;
|
|
5103
5454
|
}
|
|
5104
5455
|
if (existing === null) {
|
|
5105
|
-
await
|
|
5456
|
+
await import_node_fs20.promises.writeFile(file, `${NEAT_HEADER}
|
|
5106
5457
|
${NEAT_OUT_LINE}
|
|
5107
5458
|
`, "utf8");
|
|
5108
5459
|
return { action: "created", file };
|
|
@@ -5115,13 +5466,13 @@ ${NEAT_OUT_LINE}
|
|
|
5115
5466
|
${NEAT_HEADER}
|
|
5116
5467
|
${NEAT_OUT_LINE}
|
|
5117
5468
|
`;
|
|
5118
|
-
await
|
|
5469
|
+
await import_node_fs20.promises.writeFile(file, existing + appended, "utf8");
|
|
5119
5470
|
return { action: "added", file };
|
|
5120
5471
|
}
|
|
5121
5472
|
|
|
5122
5473
|
// src/summary.ts
|
|
5123
5474
|
init_cjs_shims();
|
|
5124
|
-
var
|
|
5475
|
+
var import_types23 = require("@neat.is/types");
|
|
5125
5476
|
function renderOtelEnvBlock() {
|
|
5126
5477
|
return [
|
|
5127
5478
|
"for prod OTel routing, set these in your deploy platform's env:",
|
|
@@ -5131,19 +5482,19 @@ function renderOtelEnvBlock() {
|
|
|
5131
5482
|
}
|
|
5132
5483
|
function findIncompatServices(nodes) {
|
|
5133
5484
|
return nodes.filter(
|
|
5134
|
-
(n) => n.type ===
|
|
5485
|
+
(n) => n.type === import_types23.NodeType.ServiceNode && Array.isArray(n.incompatibilities) && (n.incompatibilities ?? []).length > 0
|
|
5135
5486
|
);
|
|
5136
5487
|
}
|
|
5137
5488
|
function servicesWithoutObserved(nodes, edges) {
|
|
5138
5489
|
const seen = /* @__PURE__ */ new Set();
|
|
5139
5490
|
for (const e of edges) {
|
|
5140
|
-
if (e.provenance ===
|
|
5491
|
+
if (e.provenance === import_types23.Provenance.OBSERVED) {
|
|
5141
5492
|
seen.add(e.source);
|
|
5142
5493
|
seen.add(e.target);
|
|
5143
5494
|
}
|
|
5144
5495
|
}
|
|
5145
5496
|
return nodes.filter(
|
|
5146
|
-
(n) => n.type ===
|
|
5497
|
+
(n) => n.type === import_types23.NodeType.ServiceNode && !seen.has(n.id)
|
|
5147
5498
|
);
|
|
5148
5499
|
}
|
|
5149
5500
|
function formatDivergence(d) {
|
|
@@ -5205,31 +5556,346 @@ function formatIncompat(inc) {
|
|
|
5205
5556
|
const range = inc.declaredNodeEngine ? ` (engines.node="${inc.declaredNodeEngine}")` : "";
|
|
5206
5557
|
return `${inc.package}@${inc.packageVersion ?? "?"} requires Node ${inc.requiredNodeVersion}${range} \u2014 ${inc.reason}`;
|
|
5207
5558
|
}
|
|
5208
|
-
if (inc.kind === "package-conflict") {
|
|
5209
|
-
const found = inc.foundVersion ? `@${inc.foundVersion}` : " (missing)";
|
|
5210
|
-
return `${inc.package}@${inc.packageVersion ?? "?"} requires ${inc.requires.name}>=${inc.requires.minVersion}; found ${inc.requires.name}${found} \u2014 ${inc.reason}`;
|
|
5559
|
+
if (inc.kind === "package-conflict") {
|
|
5560
|
+
const found = inc.foundVersion ? `@${inc.foundVersion}` : " (missing)";
|
|
5561
|
+
return `${inc.package}@${inc.packageVersion ?? "?"} requires ${inc.requires.name}>=${inc.requires.minVersion}; found ${inc.requires.name}${found} \u2014 ${inc.reason}`;
|
|
5562
|
+
}
|
|
5563
|
+
if (inc.kind === "deprecated-api") {
|
|
5564
|
+
return `${inc.package}@${inc.packageVersion ?? "?"} is deprecated \u2014 ${inc.reason}`;
|
|
5565
|
+
}
|
|
5566
|
+
return `${inc.driver}@${inc.driverVersion} vs ${inc.engine} ${inc.engineVersion} \u2014 ${inc.reason}`;
|
|
5567
|
+
}
|
|
5568
|
+
|
|
5569
|
+
// src/watch.ts
|
|
5570
|
+
init_cjs_shims();
|
|
5571
|
+
var import_node_fs26 = __toESM(require("fs"), 1);
|
|
5572
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
5573
|
+
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
5574
|
+
|
|
5575
|
+
// src/api.ts
|
|
5576
|
+
init_cjs_shims();
|
|
5577
|
+
var import_fastify = __toESM(require("fastify"), 1);
|
|
5578
|
+
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
5579
|
+
var import_types25 = require("@neat.is/types");
|
|
5580
|
+
|
|
5581
|
+
// src/extend/index.ts
|
|
5582
|
+
init_cjs_shims();
|
|
5583
|
+
var import_node_fs22 = require("fs");
|
|
5584
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
5585
|
+
var import_node_os2 = __toESM(require("os"), 1);
|
|
5586
|
+
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
5587
|
+
|
|
5588
|
+
// src/installers/package-manager.ts
|
|
5589
|
+
init_cjs_shims();
|
|
5590
|
+
var import_node_fs21 = require("fs");
|
|
5591
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
5592
|
+
var import_node_child_process = require("child_process");
|
|
5593
|
+
var LOCKFILE_PRIORITY = [
|
|
5594
|
+
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
5595
|
+
{ lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
|
|
5596
|
+
{ lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
|
|
5597
|
+
{
|
|
5598
|
+
lockfile: "package-lock.json",
|
|
5599
|
+
pm: "npm",
|
|
5600
|
+
args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
|
|
5601
|
+
}
|
|
5602
|
+
];
|
|
5603
|
+
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
5604
|
+
async function exists2(p) {
|
|
5605
|
+
try {
|
|
5606
|
+
await import_node_fs21.promises.access(p);
|
|
5607
|
+
return true;
|
|
5608
|
+
} catch {
|
|
5609
|
+
return false;
|
|
5610
|
+
}
|
|
5611
|
+
}
|
|
5612
|
+
async function detectPackageManager(serviceDir) {
|
|
5613
|
+
let dir = import_node_path35.default.resolve(serviceDir);
|
|
5614
|
+
const stops = /* @__PURE__ */ new Set();
|
|
5615
|
+
for (let i = 0; i < 64; i++) {
|
|
5616
|
+
if (stops.has(dir)) break;
|
|
5617
|
+
stops.add(dir);
|
|
5618
|
+
for (const candidate of LOCKFILE_PRIORITY) {
|
|
5619
|
+
const lockPath = import_node_path35.default.join(dir, candidate.lockfile);
|
|
5620
|
+
if (await exists2(lockPath)) {
|
|
5621
|
+
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
5622
|
+
}
|
|
5623
|
+
}
|
|
5624
|
+
const parent = import_node_path35.default.dirname(dir);
|
|
5625
|
+
if (parent === dir) break;
|
|
5626
|
+
dir = parent;
|
|
5627
|
+
}
|
|
5628
|
+
return { pm: "npm", cwd: import_node_path35.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
5629
|
+
}
|
|
5630
|
+
async function runPackageManagerInstall(cmd) {
|
|
5631
|
+
return new Promise((resolve) => {
|
|
5632
|
+
const child = (0, import_node_child_process.spawn)(cmd.pm, cmd.args, {
|
|
5633
|
+
cwd: cmd.cwd,
|
|
5634
|
+
// Inherit PATH + HOME so the user's installed managers resolve.
|
|
5635
|
+
env: process.env,
|
|
5636
|
+
// `false` keeps the parent in control of cleanup if the orchestrator
|
|
5637
|
+
// exits before install finishes. Cross-platform-safe.
|
|
5638
|
+
shell: false,
|
|
5639
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
5640
|
+
});
|
|
5641
|
+
let stderr = "";
|
|
5642
|
+
child.stderr?.on("data", (chunk) => {
|
|
5643
|
+
stderr += chunk.toString("utf8");
|
|
5644
|
+
});
|
|
5645
|
+
child.on("error", (err) => {
|
|
5646
|
+
resolve({
|
|
5647
|
+
pm: cmd.pm,
|
|
5648
|
+
cwd: cmd.cwd,
|
|
5649
|
+
args: cmd.args,
|
|
5650
|
+
exitCode: 127,
|
|
5651
|
+
stderr: stderr + `
|
|
5652
|
+
${err.message}`
|
|
5653
|
+
});
|
|
5654
|
+
});
|
|
5655
|
+
child.on("close", (code) => {
|
|
5656
|
+
resolve({
|
|
5657
|
+
pm: cmd.pm,
|
|
5658
|
+
cwd: cmd.cwd,
|
|
5659
|
+
args: cmd.args,
|
|
5660
|
+
exitCode: code ?? 1,
|
|
5661
|
+
stderr: stderr.trim()
|
|
5662
|
+
});
|
|
5663
|
+
});
|
|
5664
|
+
});
|
|
5665
|
+
}
|
|
5666
|
+
|
|
5667
|
+
// src/extend/index.ts
|
|
5668
|
+
async function fileExists2(p) {
|
|
5669
|
+
try {
|
|
5670
|
+
await import_node_fs22.promises.access(p);
|
|
5671
|
+
return true;
|
|
5672
|
+
} catch {
|
|
5673
|
+
return false;
|
|
5674
|
+
}
|
|
5675
|
+
}
|
|
5676
|
+
async function readPackageJson(scanPath) {
|
|
5677
|
+
const pkgPath = import_node_path36.default.join(scanPath, "package.json");
|
|
5678
|
+
const raw = await import_node_fs22.promises.readFile(pkgPath, "utf8");
|
|
5679
|
+
return JSON.parse(raw);
|
|
5680
|
+
}
|
|
5681
|
+
async function findHookFiles(scanPath) {
|
|
5682
|
+
const entries = await import_node_fs22.promises.readdir(scanPath);
|
|
5683
|
+
return entries.filter(
|
|
5684
|
+
(e) => (e.startsWith("instrumentation") || e.startsWith("otel-init")) && /\.(ts|js)$/.test(e)
|
|
5685
|
+
).sort();
|
|
5686
|
+
}
|
|
5687
|
+
function extendLogPath() {
|
|
5688
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path36.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
5689
|
+
}
|
|
5690
|
+
async function appendExtendLog(entry2) {
|
|
5691
|
+
const logPath = extendLogPath();
|
|
5692
|
+
await import_node_fs22.promises.mkdir(import_node_path36.default.dirname(logPath), { recursive: true });
|
|
5693
|
+
await import_node_fs22.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
|
|
5694
|
+
}
|
|
5695
|
+
function splicedContent(fileContent, snippet2) {
|
|
5696
|
+
if (fileContent.includes("__INSTRUMENTATION_BLOCK__")) {
|
|
5697
|
+
return fileContent.replace("__INSTRUMENTATION_BLOCK__", `${snippet2}
|
|
5698
|
+
__INSTRUMENTATION_BLOCK__`);
|
|
5699
|
+
}
|
|
5700
|
+
const lines = fileContent.split("\n");
|
|
5701
|
+
let lastPushIdx = -1;
|
|
5702
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5703
|
+
if (lines[i].includes("instrumentations.push(")) lastPushIdx = i;
|
|
5704
|
+
}
|
|
5705
|
+
if (lastPushIdx >= 0) {
|
|
5706
|
+
lines.splice(lastPushIdx + 1, 0, snippet2);
|
|
5707
|
+
return lines.join("\n");
|
|
5708
|
+
}
|
|
5709
|
+
for (let i = 0; i < lines.length; i++) {
|
|
5710
|
+
if (lines[i].includes("new NodeSDK(")) {
|
|
5711
|
+
lines.splice(i, 0, snippet2);
|
|
5712
|
+
return lines.join("\n");
|
|
5713
|
+
}
|
|
5714
|
+
}
|
|
5715
|
+
return null;
|
|
5716
|
+
}
|
|
5717
|
+
async function listUninstrumented(ctx) {
|
|
5718
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5719
|
+
const allDeps2 = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5720
|
+
const results = [];
|
|
5721
|
+
for (const [library, installedVersion] of Object.entries(allDeps2)) {
|
|
5722
|
+
const entry2 = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
5723
|
+
if (!entry2) continue;
|
|
5724
|
+
if (entry2.coverage === "bundled" || entry2.coverage === "http-only") continue;
|
|
5725
|
+
results.push({
|
|
5726
|
+
library,
|
|
5727
|
+
coverage: entry2.coverage,
|
|
5728
|
+
installedVersion,
|
|
5729
|
+
instrumentation_package: entry2.instrumentation_package,
|
|
5730
|
+
package_version: entry2.package_version,
|
|
5731
|
+
registration: entry2.registration,
|
|
5732
|
+
notes: entry2.notes
|
|
5733
|
+
});
|
|
5734
|
+
}
|
|
5735
|
+
return results;
|
|
5736
|
+
}
|
|
5737
|
+
function lookupInstrumentation(library, installedVersion) {
|
|
5738
|
+
const entry2 = (0, import_instrumentation_registry.resolve)(library, installedVersion);
|
|
5739
|
+
if (!entry2) return null;
|
|
5740
|
+
return {
|
|
5741
|
+
library: entry2.library,
|
|
5742
|
+
coverage: entry2.coverage,
|
|
5743
|
+
instrumentation_package: entry2.instrumentation_package,
|
|
5744
|
+
package_version: entry2.package_version,
|
|
5745
|
+
registration: entry2.registration,
|
|
5746
|
+
notes: entry2.notes
|
|
5747
|
+
};
|
|
5748
|
+
}
|
|
5749
|
+
async function describeProjectInstrumentation(ctx) {
|
|
5750
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5751
|
+
const envNeat = await fileExists2(import_node_path36.default.join(ctx.scanPath, ".env.neat"));
|
|
5752
|
+
const registryInstrPackages = new Set(
|
|
5753
|
+
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
5754
|
+
);
|
|
5755
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5756
|
+
const allDeps2 = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
5757
|
+
const installedDeps = {};
|
|
5758
|
+
for (const [key, version] of Object.entries(allDeps2)) {
|
|
5759
|
+
if (key.startsWith("@opentelemetry/") || registryInstrPackages.has(key)) {
|
|
5760
|
+
installedDeps[key] = version;
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
return { hookFiles, envNeat, installedDeps };
|
|
5764
|
+
}
|
|
5765
|
+
async function applyExtension(ctx, args, options) {
|
|
5766
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5767
|
+
if (hookFiles.length === 0) {
|
|
5768
|
+
throw new Error(
|
|
5769
|
+
`No instrumentation hook files found in ${ctx.scanPath}. Run \`neat init\` first.`
|
|
5770
|
+
);
|
|
5771
|
+
}
|
|
5772
|
+
for (const file of hookFiles) {
|
|
5773
|
+
const content = await import_node_fs22.promises.readFile(import_node_path36.default.join(ctx.scanPath, file), "utf8");
|
|
5774
|
+
if (content.includes(args.registration_snippet)) {
|
|
5775
|
+
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
5776
|
+
}
|
|
5777
|
+
}
|
|
5778
|
+
const primaryFile = hookFiles[0];
|
|
5779
|
+
const primaryPath = import_node_path36.default.join(ctx.scanPath, primaryFile);
|
|
5780
|
+
const filesTouched = [];
|
|
5781
|
+
const depsAdded = [];
|
|
5782
|
+
const pkgPath = import_node_path36.default.join(ctx.scanPath, "package.json");
|
|
5783
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5784
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5785
|
+
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
5786
|
+
await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5787
|
+
filesTouched.push("package.json");
|
|
5788
|
+
depsAdded.push(`${args.instrumentation_package}@${args.version}`);
|
|
5789
|
+
}
|
|
5790
|
+
const hookContent = await import_node_fs22.promises.readFile(primaryPath, "utf8");
|
|
5791
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5792
|
+
if (!patched) {
|
|
5793
|
+
throw new Error(
|
|
5794
|
+
`Could not find instrumentation insertion point in ${primaryFile}. Expected __INSTRUMENTATION_BLOCK__, instrumentations.push(, or new NodeSDK(.`
|
|
5795
|
+
);
|
|
5796
|
+
}
|
|
5797
|
+
await import_node_fs22.promises.writeFile(primaryPath, patched, "utf8");
|
|
5798
|
+
filesTouched.push(primaryFile);
|
|
5799
|
+
const cmd = await detectPackageManager(ctx.scanPath);
|
|
5800
|
+
const installer = options?.runInstall ?? runPackageManagerInstall;
|
|
5801
|
+
const install = await installer(cmd);
|
|
5802
|
+
const installOutput = install.exitCode === 0 ? `${cmd.pm} install succeeded` : install.stderr || `${cmd.pm} install failed (exit ${install.exitCode})`;
|
|
5803
|
+
await appendExtendLog({
|
|
5804
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5805
|
+
project: ctx.project,
|
|
5806
|
+
library: args.library,
|
|
5807
|
+
instrumentation_package: args.instrumentation_package,
|
|
5808
|
+
version: args.version,
|
|
5809
|
+
registration_snippet: args.registration_snippet,
|
|
5810
|
+
filesTouched,
|
|
5811
|
+
depsAdded,
|
|
5812
|
+
installOutput
|
|
5813
|
+
});
|
|
5814
|
+
return { library: args.library, filesTouched, depsAdded, installOutput, alreadyApplied: false };
|
|
5815
|
+
}
|
|
5816
|
+
async function dryRunExtension(ctx, args) {
|
|
5817
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5818
|
+
if (hookFiles.length === 0) {
|
|
5819
|
+
return {
|
|
5820
|
+
library: args.library,
|
|
5821
|
+
filesTouched: [],
|
|
5822
|
+
depsToAdd: [],
|
|
5823
|
+
packageJsonPatch: {},
|
|
5824
|
+
templatePatch: "No hook files found. Run 'neat init' first."
|
|
5825
|
+
};
|
|
5211
5826
|
}
|
|
5212
|
-
|
|
5213
|
-
|
|
5827
|
+
for (const file of hookFiles) {
|
|
5828
|
+
const content = await import_node_fs22.promises.readFile(import_node_path36.default.join(ctx.scanPath, file), "utf8");
|
|
5829
|
+
if (content.includes(args.registration_snippet)) {
|
|
5830
|
+
return {
|
|
5831
|
+
library: args.library,
|
|
5832
|
+
filesTouched: [],
|
|
5833
|
+
depsToAdd: [],
|
|
5834
|
+
packageJsonPatch: {},
|
|
5835
|
+
templatePatch: "Already applied \u2014 no changes would be made."
|
|
5836
|
+
};
|
|
5837
|
+
}
|
|
5214
5838
|
}
|
|
5215
|
-
|
|
5839
|
+
const primaryFile = hookFiles[0];
|
|
5840
|
+
const filesTouched = [];
|
|
5841
|
+
const depsToAdd = [];
|
|
5842
|
+
let packageJsonPatch = {};
|
|
5843
|
+
let templatePatch = "";
|
|
5844
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5845
|
+
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5846
|
+
packageJsonPatch = { dependencies: { [args.instrumentation_package]: args.version } };
|
|
5847
|
+
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
5848
|
+
filesTouched.push("package.json");
|
|
5849
|
+
}
|
|
5850
|
+
const hookContent = await import_node_fs22.promises.readFile(import_node_path36.default.join(ctx.scanPath, primaryFile), "utf8");
|
|
5851
|
+
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5852
|
+
if (patched) {
|
|
5853
|
+
filesTouched.push(primaryFile);
|
|
5854
|
+
templatePatch = `+ ${args.registration_snippet}`;
|
|
5855
|
+
} else {
|
|
5856
|
+
templatePatch = "Could not find insertion point in hook file.";
|
|
5857
|
+
}
|
|
5858
|
+
return { library: args.library, filesTouched, depsToAdd, packageJsonPatch, templatePatch };
|
|
5859
|
+
}
|
|
5860
|
+
async function rollbackExtension(ctx, args) {
|
|
5861
|
+
const logPath = extendLogPath();
|
|
5862
|
+
if (!await fileExists2(logPath)) {
|
|
5863
|
+
return { undone: false, message: "no apply found for library" };
|
|
5864
|
+
}
|
|
5865
|
+
const raw = await import_node_fs22.promises.readFile(logPath, "utf8");
|
|
5866
|
+
const entries = raw.trim().split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
5867
|
+
const match = [...entries].reverse().find((e) => e.project === ctx.project && e.library === args.library);
|
|
5868
|
+
if (!match) {
|
|
5869
|
+
return { undone: false, message: "no apply found for library" };
|
|
5870
|
+
}
|
|
5871
|
+
const pkgPath = import_node_path36.default.join(ctx.scanPath, "package.json");
|
|
5872
|
+
if (await fileExists2(pkgPath)) {
|
|
5873
|
+
const pkg = await readPackageJson(ctx.scanPath);
|
|
5874
|
+
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
5875
|
+
const { [match.instrumentation_package]: _removed, ...rest } = pkg.dependencies;
|
|
5876
|
+
pkg.dependencies = rest;
|
|
5877
|
+
await import_node_fs22.promises.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5880
|
+
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5881
|
+
for (const file of hookFiles) {
|
|
5882
|
+
const filePath = import_node_path36.default.join(ctx.scanPath, file);
|
|
5883
|
+
const content = await import_node_fs22.promises.readFile(filePath, "utf8");
|
|
5884
|
+
if (content.includes(match.registration_snippet)) {
|
|
5885
|
+
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
5886
|
+
await import_node_fs22.promises.writeFile(filePath, filtered, "utf8");
|
|
5887
|
+
break;
|
|
5888
|
+
}
|
|
5889
|
+
}
|
|
5890
|
+
return {
|
|
5891
|
+
undone: true,
|
|
5892
|
+
message: `rolled back ${match.library} (${match.instrumentation_package})`
|
|
5893
|
+
};
|
|
5216
5894
|
}
|
|
5217
5895
|
|
|
5218
|
-
// src/watch.ts
|
|
5219
|
-
init_cjs_shims();
|
|
5220
|
-
var import_node_fs23 = __toESM(require("fs"), 1);
|
|
5221
|
-
var import_node_path38 = __toESM(require("path"), 1);
|
|
5222
|
-
var import_chokidar = __toESM(require("chokidar"), 1);
|
|
5223
|
-
|
|
5224
|
-
// src/api.ts
|
|
5225
|
-
init_cjs_shims();
|
|
5226
|
-
var import_fastify = __toESM(require("fastify"), 1);
|
|
5227
|
-
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
5228
|
-
var import_types24 = require("@neat.is/types");
|
|
5229
|
-
|
|
5230
5896
|
// src/diff.ts
|
|
5231
5897
|
init_cjs_shims();
|
|
5232
|
-
var
|
|
5898
|
+
var import_node_fs23 = require("fs");
|
|
5233
5899
|
async function loadSnapshotForDiff(target) {
|
|
5234
5900
|
if (/^https?:\/\//i.test(target)) {
|
|
5235
5901
|
const res = await fetch(target);
|
|
@@ -5238,7 +5904,7 @@ async function loadSnapshotForDiff(target) {
|
|
|
5238
5904
|
}
|
|
5239
5905
|
return await res.json();
|
|
5240
5906
|
}
|
|
5241
|
-
const raw = await
|
|
5907
|
+
const raw = await import_node_fs23.promises.readFile(target, "utf8");
|
|
5242
5908
|
return JSON.parse(raw);
|
|
5243
5909
|
}
|
|
5244
5910
|
function indexEntries(entries) {
|
|
@@ -5306,23 +5972,23 @@ function canonicalJson(value) {
|
|
|
5306
5972
|
|
|
5307
5973
|
// src/projects.ts
|
|
5308
5974
|
init_cjs_shims();
|
|
5309
|
-
var
|
|
5975
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
5310
5976
|
function pathsForProject(project, baseDir) {
|
|
5311
5977
|
if (project === DEFAULT_PROJECT) {
|
|
5312
5978
|
return {
|
|
5313
|
-
snapshotPath:
|
|
5314
|
-
errorsPath:
|
|
5315
|
-
staleEventsPath:
|
|
5316
|
-
embeddingsCachePath:
|
|
5317
|
-
policyViolationsPath:
|
|
5979
|
+
snapshotPath: import_node_path37.default.join(baseDir, "graph.json"),
|
|
5980
|
+
errorsPath: import_node_path37.default.join(baseDir, "errors.ndjson"),
|
|
5981
|
+
staleEventsPath: import_node_path37.default.join(baseDir, "stale-events.ndjson"),
|
|
5982
|
+
embeddingsCachePath: import_node_path37.default.join(baseDir, "embeddings.json"),
|
|
5983
|
+
policyViolationsPath: import_node_path37.default.join(baseDir, "policy-violations.ndjson")
|
|
5318
5984
|
};
|
|
5319
5985
|
}
|
|
5320
5986
|
return {
|
|
5321
|
-
snapshotPath:
|
|
5322
|
-
errorsPath:
|
|
5323
|
-
staleEventsPath:
|
|
5324
|
-
embeddingsCachePath:
|
|
5325
|
-
policyViolationsPath:
|
|
5987
|
+
snapshotPath: import_node_path37.default.join(baseDir, `${project}.json`),
|
|
5988
|
+
errorsPath: import_node_path37.default.join(baseDir, `errors.${project}.ndjson`),
|
|
5989
|
+
staleEventsPath: import_node_path37.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
5990
|
+
embeddingsCachePath: import_node_path37.default.join(baseDir, `embeddings.${project}.json`),
|
|
5991
|
+
policyViolationsPath: import_node_path37.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
5326
5992
|
};
|
|
5327
5993
|
}
|
|
5328
5994
|
var Projects = class {
|
|
@@ -5358,25 +6024,25 @@ var Projects = class {
|
|
|
5358
6024
|
|
|
5359
6025
|
// src/registry.ts
|
|
5360
6026
|
init_cjs_shims();
|
|
5361
|
-
var
|
|
5362
|
-
var
|
|
5363
|
-
var
|
|
5364
|
-
var
|
|
6027
|
+
var import_node_fs24 = require("fs");
|
|
6028
|
+
var import_node_os3 = __toESM(require("os"), 1);
|
|
6029
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
6030
|
+
var import_types24 = require("@neat.is/types");
|
|
5365
6031
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
5366
6032
|
var LOCK_RETRY_MS = 50;
|
|
5367
6033
|
function neatHome() {
|
|
5368
6034
|
const override = process.env.NEAT_HOME;
|
|
5369
|
-
if (override && override.length > 0) return
|
|
5370
|
-
return
|
|
6035
|
+
if (override && override.length > 0) return import_node_path38.default.resolve(override);
|
|
6036
|
+
return import_node_path38.default.join(import_node_os3.default.homedir(), ".neat");
|
|
5371
6037
|
}
|
|
5372
6038
|
function registryPath() {
|
|
5373
|
-
return
|
|
6039
|
+
return import_node_path38.default.join(neatHome(), "projects.json");
|
|
5374
6040
|
}
|
|
5375
6041
|
function registryLockPath() {
|
|
5376
|
-
return
|
|
6042
|
+
return import_node_path38.default.join(neatHome(), "projects.json.lock");
|
|
5377
6043
|
}
|
|
5378
6044
|
function daemonPidPath() {
|
|
5379
|
-
return
|
|
6045
|
+
return import_node_path38.default.join(neatHome(), "neatd.pid");
|
|
5380
6046
|
}
|
|
5381
6047
|
function isPidAliveDefault(pid) {
|
|
5382
6048
|
try {
|
|
@@ -5388,7 +6054,7 @@ function isPidAliveDefault(pid) {
|
|
|
5388
6054
|
}
|
|
5389
6055
|
async function readPidFile(file) {
|
|
5390
6056
|
try {
|
|
5391
|
-
const raw = await
|
|
6057
|
+
const raw = await import_node_fs24.promises.readFile(file, "utf8");
|
|
5392
6058
|
const pid = Number.parseInt(raw.trim(), 10);
|
|
5393
6059
|
return Number.isInteger(pid) && pid > 0 ? pid : void 0;
|
|
5394
6060
|
} catch {
|
|
@@ -5436,32 +6102,32 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
5436
6102
|
}
|
|
5437
6103
|
}
|
|
5438
6104
|
async function normalizeProjectPath(input) {
|
|
5439
|
-
const resolved =
|
|
6105
|
+
const resolved = import_node_path38.default.resolve(input);
|
|
5440
6106
|
try {
|
|
5441
|
-
return await
|
|
6107
|
+
return await import_node_fs24.promises.realpath(resolved);
|
|
5442
6108
|
} catch {
|
|
5443
6109
|
return resolved;
|
|
5444
6110
|
}
|
|
5445
6111
|
}
|
|
5446
6112
|
async function writeAtomically(target, contents) {
|
|
5447
|
-
await
|
|
6113
|
+
await import_node_fs24.promises.mkdir(import_node_path38.default.dirname(target), { recursive: true });
|
|
5448
6114
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
5449
|
-
const fd = await
|
|
6115
|
+
const fd = await import_node_fs24.promises.open(tmp, "w");
|
|
5450
6116
|
try {
|
|
5451
6117
|
await fd.writeFile(contents, "utf8");
|
|
5452
6118
|
await fd.sync();
|
|
5453
6119
|
} finally {
|
|
5454
6120
|
await fd.close();
|
|
5455
6121
|
}
|
|
5456
|
-
await
|
|
6122
|
+
await import_node_fs24.promises.rename(tmp, target);
|
|
5457
6123
|
}
|
|
5458
6124
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
5459
6125
|
const deadline = Date.now() + timeoutMs;
|
|
5460
|
-
await
|
|
6126
|
+
await import_node_fs24.promises.mkdir(import_node_path38.default.dirname(lockPath), { recursive: true });
|
|
5461
6127
|
let probedHolder = false;
|
|
5462
6128
|
while (true) {
|
|
5463
6129
|
try {
|
|
5464
|
-
const fd = await
|
|
6130
|
+
const fd = await import_node_fs24.promises.open(lockPath, "wx");
|
|
5465
6131
|
try {
|
|
5466
6132
|
await fd.writeFile(`${process.pid}
|
|
5467
6133
|
`, "utf8");
|
|
@@ -5486,7 +6152,7 @@ async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaul
|
|
|
5486
6152
|
}
|
|
5487
6153
|
}
|
|
5488
6154
|
async function releaseLock(lockPath) {
|
|
5489
|
-
await
|
|
6155
|
+
await import_node_fs24.promises.unlink(lockPath).catch(() => {
|
|
5490
6156
|
});
|
|
5491
6157
|
}
|
|
5492
6158
|
async function withLock(fn) {
|
|
@@ -5502,7 +6168,7 @@ async function readRegistry() {
|
|
|
5502
6168
|
const file = registryPath();
|
|
5503
6169
|
let raw;
|
|
5504
6170
|
try {
|
|
5505
|
-
raw = await
|
|
6171
|
+
raw = await import_node_fs24.promises.readFile(file, "utf8");
|
|
5506
6172
|
} catch (err) {
|
|
5507
6173
|
if (err.code === "ENOENT") {
|
|
5508
6174
|
return { version: 1, projects: [] };
|
|
@@ -5510,10 +6176,10 @@ async function readRegistry() {
|
|
|
5510
6176
|
throw err;
|
|
5511
6177
|
}
|
|
5512
6178
|
const parsed = JSON.parse(raw);
|
|
5513
|
-
return
|
|
6179
|
+
return import_types24.RegistryFileSchema.parse(parsed);
|
|
5514
6180
|
}
|
|
5515
6181
|
async function writeRegistry(reg) {
|
|
5516
|
-
const validated =
|
|
6182
|
+
const validated = import_types24.RegistryFileSchema.parse(reg);
|
|
5517
6183
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
5518
6184
|
}
|
|
5519
6185
|
var ProjectNameCollisionError = class extends Error {
|
|
@@ -5776,11 +6442,11 @@ function registerRoutes(scope, ctx) {
|
|
|
5776
6442
|
const candidates = req.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
5777
6443
|
const parsed = [];
|
|
5778
6444
|
for (const c of candidates) {
|
|
5779
|
-
const r =
|
|
6445
|
+
const r = import_types25.DivergenceTypeSchema.safeParse(c);
|
|
5780
6446
|
if (!r.success) {
|
|
5781
6447
|
return reply.code(400).send({
|
|
5782
6448
|
error: `unknown divergence type "${c}"`,
|
|
5783
|
-
allowed:
|
|
6449
|
+
allowed: import_types25.DivergenceTypeSchema.options
|
|
5784
6450
|
});
|
|
5785
6451
|
}
|
|
5786
6452
|
parsed.push(r.data);
|
|
@@ -5993,7 +6659,7 @@ function registerRoutes(scope, ctx) {
|
|
|
5993
6659
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
5994
6660
|
let violations = await log.readAll();
|
|
5995
6661
|
if (req.query.severity) {
|
|
5996
|
-
const sev =
|
|
6662
|
+
const sev = import_types25.PolicySeveritySchema.safeParse(req.query.severity);
|
|
5997
6663
|
if (!sev.success) {
|
|
5998
6664
|
return reply.code(400).send({
|
|
5999
6665
|
error: "invalid severity",
|
|
@@ -6010,7 +6676,7 @@ function registerRoutes(scope, ctx) {
|
|
|
6010
6676
|
scope.post("/policies/check", async (req, reply) => {
|
|
6011
6677
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6012
6678
|
if (!proj) return;
|
|
6013
|
-
const parsed =
|
|
6679
|
+
const parsed = import_types25.PoliciesCheckBodySchema.safeParse(req.body ?? {});
|
|
6014
6680
|
if (!parsed.success) {
|
|
6015
6681
|
return reply.code(400).send({
|
|
6016
6682
|
error: "invalid /policies/check body",
|
|
@@ -6043,6 +6709,105 @@ function registerRoutes(scope, ctx) {
|
|
|
6043
6709
|
violations
|
|
6044
6710
|
};
|
|
6045
6711
|
});
|
|
6712
|
+
scope.get("/extend/list-uninstrumented", async (req, reply) => {
|
|
6713
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6714
|
+
if (!proj) return;
|
|
6715
|
+
if (!proj.scanPath) {
|
|
6716
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6717
|
+
}
|
|
6718
|
+
try {
|
|
6719
|
+
const results = await listUninstrumented({ project: proj.name, scanPath: proj.scanPath });
|
|
6720
|
+
return { libraries: results };
|
|
6721
|
+
} catch (err) {
|
|
6722
|
+
return reply.code(500).send({ error: err.message });
|
|
6723
|
+
}
|
|
6724
|
+
});
|
|
6725
|
+
scope.get("/extend/lookup", async (req, reply) => {
|
|
6726
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6727
|
+
if (!proj) return;
|
|
6728
|
+
const { library, version } = req.query;
|
|
6729
|
+
if (!library) {
|
|
6730
|
+
return reply.code(400).send({ error: "query parameter `library` is required" });
|
|
6731
|
+
}
|
|
6732
|
+
const result = lookupInstrumentation(library, version);
|
|
6733
|
+
if (!result) {
|
|
6734
|
+
return reply.code(404).send({ error: "library not found in registry", library });
|
|
6735
|
+
}
|
|
6736
|
+
return result;
|
|
6737
|
+
});
|
|
6738
|
+
scope.get("/extend/describe", async (req, reply) => {
|
|
6739
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6740
|
+
if (!proj) return;
|
|
6741
|
+
if (!proj.scanPath) {
|
|
6742
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6743
|
+
}
|
|
6744
|
+
try {
|
|
6745
|
+
const state = await describeProjectInstrumentation({ project: proj.name, scanPath: proj.scanPath });
|
|
6746
|
+
return state;
|
|
6747
|
+
} catch (err) {
|
|
6748
|
+
return reply.code(500).send({ error: err.message });
|
|
6749
|
+
}
|
|
6750
|
+
});
|
|
6751
|
+
scope.post("/extend/apply", async (req, reply) => {
|
|
6752
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6753
|
+
if (!proj) return;
|
|
6754
|
+
if (!proj.scanPath) {
|
|
6755
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6756
|
+
}
|
|
6757
|
+
const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
|
|
6758
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6759
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6760
|
+
}
|
|
6761
|
+
try {
|
|
6762
|
+
const result = await applyExtension(
|
|
6763
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6764
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6765
|
+
);
|
|
6766
|
+
return result;
|
|
6767
|
+
} catch (err) {
|
|
6768
|
+
return reply.code(500).send({ error: err.message });
|
|
6769
|
+
}
|
|
6770
|
+
});
|
|
6771
|
+
scope.post("/extend/dry-run", async (req, reply) => {
|
|
6772
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6773
|
+
if (!proj) return;
|
|
6774
|
+
if (!proj.scanPath) {
|
|
6775
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6776
|
+
}
|
|
6777
|
+
const { library, instrumentation_package, version, registration_snippet } = req.body ?? {};
|
|
6778
|
+
if (!library || !instrumentation_package || !version || !registration_snippet) {
|
|
6779
|
+
return reply.code(400).send({ error: "body must include library, instrumentation_package, version, registration_snippet" });
|
|
6780
|
+
}
|
|
6781
|
+
try {
|
|
6782
|
+
const result = await dryRunExtension(
|
|
6783
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6784
|
+
{ library, instrumentation_package, version, registration_snippet }
|
|
6785
|
+
);
|
|
6786
|
+
return result;
|
|
6787
|
+
} catch (err) {
|
|
6788
|
+
return reply.code(500).send({ error: err.message });
|
|
6789
|
+
}
|
|
6790
|
+
});
|
|
6791
|
+
scope.post("/extend/rollback", async (req, reply) => {
|
|
6792
|
+
const proj = resolveProject(registry, req, reply, ctx.bootstrap);
|
|
6793
|
+
if (!proj) return;
|
|
6794
|
+
if (!proj.scanPath) {
|
|
6795
|
+
return reply.code(409).send({ error: "scan path not configured for this project", project: proj.name });
|
|
6796
|
+
}
|
|
6797
|
+
const { library } = req.body ?? {};
|
|
6798
|
+
if (!library) {
|
|
6799
|
+
return reply.code(400).send({ error: "body must include library" });
|
|
6800
|
+
}
|
|
6801
|
+
try {
|
|
6802
|
+
const result = await rollbackExtension(
|
|
6803
|
+
{ project: proj.name, scanPath: proj.scanPath },
|
|
6804
|
+
{ library }
|
|
6805
|
+
);
|
|
6806
|
+
return result;
|
|
6807
|
+
} catch (err) {
|
|
6808
|
+
return reply.code(500).send({ error: err.message });
|
|
6809
|
+
}
|
|
6810
|
+
});
|
|
6046
6811
|
}
|
|
6047
6812
|
async function buildApi(opts) {
|
|
6048
6813
|
const app = (0, import_fastify.default)({ logger: false });
|
|
@@ -6154,8 +6919,8 @@ init_otel_grpc();
|
|
|
6154
6919
|
|
|
6155
6920
|
// src/search.ts
|
|
6156
6921
|
init_cjs_shims();
|
|
6157
|
-
var
|
|
6158
|
-
var
|
|
6922
|
+
var import_node_fs25 = require("fs");
|
|
6923
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
6159
6924
|
var import_node_crypto3 = require("crypto");
|
|
6160
6925
|
var DEFAULT_LIMIT = 10;
|
|
6161
6926
|
var NOMIC_DIM = 768;
|
|
@@ -6285,7 +7050,7 @@ async function pickEmbedder() {
|
|
|
6285
7050
|
}
|
|
6286
7051
|
async function readCache(cachePath) {
|
|
6287
7052
|
try {
|
|
6288
|
-
const raw = await
|
|
7053
|
+
const raw = await import_node_fs25.promises.readFile(cachePath, "utf8");
|
|
6289
7054
|
const parsed = JSON.parse(raw);
|
|
6290
7055
|
if (parsed.version !== 1) return null;
|
|
6291
7056
|
return parsed;
|
|
@@ -6294,8 +7059,8 @@ async function readCache(cachePath) {
|
|
|
6294
7059
|
}
|
|
6295
7060
|
}
|
|
6296
7061
|
async function writeCache(cachePath, cache) {
|
|
6297
|
-
await
|
|
6298
|
-
await
|
|
7062
|
+
await import_node_fs25.promises.mkdir(import_node_path41.default.dirname(cachePath), { recursive: true });
|
|
7063
|
+
await import_node_fs25.promises.writeFile(cachePath, JSON.stringify(cache));
|
|
6299
7064
|
}
|
|
6300
7065
|
var VectorIndex = class {
|
|
6301
7066
|
constructor(embedder, cachePath) {
|
|
@@ -6443,6 +7208,7 @@ async function buildSearchIndex(graph, options = {}) {
|
|
|
6443
7208
|
var ALL_PHASES = [
|
|
6444
7209
|
"services",
|
|
6445
7210
|
"aliases",
|
|
7211
|
+
"imports",
|
|
6446
7212
|
"databases",
|
|
6447
7213
|
"configs",
|
|
6448
7214
|
"calls",
|
|
@@ -6450,8 +7216,8 @@ var ALL_PHASES = [
|
|
|
6450
7216
|
];
|
|
6451
7217
|
function classifyChange(relPath) {
|
|
6452
7218
|
const phases = /* @__PURE__ */ new Set();
|
|
6453
|
-
const base =
|
|
6454
|
-
const segments = relPath.split(
|
|
7219
|
+
const base = import_node_path42.default.basename(relPath).toLowerCase();
|
|
7220
|
+
const segments = relPath.split(import_node_path42.default.sep).map((s) => s.toLowerCase());
|
|
6455
7221
|
if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
|
|
6456
7222
|
phases.add("services");
|
|
6457
7223
|
phases.add("aliases");
|
|
@@ -6466,6 +7232,7 @@ function classifyChange(relPath) {
|
|
|
6466
7232
|
phases.add("aliases");
|
|
6467
7233
|
}
|
|
6468
7234
|
if (/\.(?:js|jsx|mjs|cjs|ts|tsx|py)$/.test(base)) {
|
|
7235
|
+
phases.add("imports");
|
|
6469
7236
|
phases.add("calls");
|
|
6470
7237
|
}
|
|
6471
7238
|
if (/\.ya?ml$/.test(base) && !/^docker-compose.*\.ya?ml$/.test(base)) {
|
|
@@ -6487,6 +7254,11 @@ async function runExtractPhases(graph, scanPath, phases, project = DEFAULT_PROJE
|
|
|
6487
7254
|
if (phases.has("aliases")) {
|
|
6488
7255
|
await addServiceAliases(graph, scanPath, services);
|
|
6489
7256
|
}
|
|
7257
|
+
if (phases.has("imports")) {
|
|
7258
|
+
const r = await addImports(graph, services);
|
|
7259
|
+
nodesAdded += r.nodesAdded;
|
|
7260
|
+
edgesAdded += r.edgesAdded;
|
|
7261
|
+
}
|
|
6490
7262
|
if (phases.has("databases")) {
|
|
6491
7263
|
const r = await addDatabasesAndCompat(graph, services, scanPath);
|
|
6492
7264
|
nodesAdded += r.nodesAdded;
|
|
@@ -6559,16 +7331,16 @@ function countWatchableDirs(scanPath, limit) {
|
|
|
6559
7331
|
if (count >= limit) return;
|
|
6560
7332
|
let entries;
|
|
6561
7333
|
try {
|
|
6562
|
-
entries =
|
|
7334
|
+
entries = import_node_fs26.default.readdirSync(dir, { withFileTypes: true });
|
|
6563
7335
|
} catch {
|
|
6564
7336
|
return;
|
|
6565
7337
|
}
|
|
6566
7338
|
for (const e of entries) {
|
|
6567
7339
|
if (count >= limit) return;
|
|
6568
7340
|
if (!e.isDirectory()) continue;
|
|
6569
|
-
if (IGNORED_WATCH_PATHS.some((re) => re.test(
|
|
7341
|
+
if (IGNORED_WATCH_PATHS.some((re) => re.test(import_node_path42.default.join(dir, e.name) + import_node_path42.default.sep))) continue;
|
|
6570
7342
|
count++;
|
|
6571
|
-
if (depth < 2) visit(
|
|
7343
|
+
if (depth < 2) visit(import_node_path42.default.join(dir, e.name), depth + 1);
|
|
6572
7344
|
}
|
|
6573
7345
|
};
|
|
6574
7346
|
visit(scanPath, 0);
|
|
@@ -6586,8 +7358,8 @@ async function startWatch(graph, opts) {
|
|
|
6586
7358
|
const projectName = opts.project ?? DEFAULT_PROJECT;
|
|
6587
7359
|
await loadGraphFromDisk(graph, opts.outPath);
|
|
6588
7360
|
const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
|
|
6589
|
-
const policyFilePath =
|
|
6590
|
-
const policyViolationsPath =
|
|
7361
|
+
const policyFilePath = import_node_path42.default.join(opts.scanPath, "policy.json");
|
|
7362
|
+
const policyViolationsPath = import_node_path42.default.join(import_node_path42.default.dirname(opts.outPath), "policy-violations.ndjson");
|
|
6591
7363
|
let policies = [];
|
|
6592
7364
|
try {
|
|
6593
7365
|
policies = await loadPolicyFile(policyFilePath);
|
|
@@ -6638,7 +7410,7 @@ async function startWatch(graph, opts) {
|
|
|
6638
7410
|
assertBindAuthority(host, auth.authToken);
|
|
6639
7411
|
const port = opts.port ?? 8080;
|
|
6640
7412
|
const otelPort = opts.otelPort ?? 4318;
|
|
6641
|
-
const cachePath = opts.embeddingsCachePath ??
|
|
7413
|
+
const cachePath = opts.embeddingsCachePath ?? import_node_path42.default.join(import_node_path42.default.dirname(opts.outPath), "embeddings.json");
|
|
6642
7414
|
let searchIndex;
|
|
6643
7415
|
try {
|
|
6644
7416
|
searchIndex = await buildSearchIndex(graph, { cachePath });
|
|
@@ -6656,7 +7428,7 @@ async function startWatch(graph, opts) {
|
|
|
6656
7428
|
// Paths are derived from the explicit options the watch caller passes
|
|
6657
7429
|
// — pathsForProject is only used to fill in the embeddings/snapshot
|
|
6658
7430
|
// fields so the registry shape is complete.
|
|
6659
|
-
...pathsForProject(projectName,
|
|
7431
|
+
...pathsForProject(projectName, import_node_path42.default.dirname(opts.outPath)),
|
|
6660
7432
|
snapshotPath: opts.outPath,
|
|
6661
7433
|
errorsPath: opts.errorsPath,
|
|
6662
7434
|
staleEventsPath: opts.staleEventsPath
|
|
@@ -6743,9 +7515,9 @@ async function startWatch(graph, opts) {
|
|
|
6743
7515
|
};
|
|
6744
7516
|
const onPath = (absPath) => {
|
|
6745
7517
|
if (shouldIgnore(absPath)) return;
|
|
6746
|
-
const rel =
|
|
7518
|
+
const rel = import_node_path42.default.relative(opts.scanPath, absPath);
|
|
6747
7519
|
if (!rel || rel.startsWith("..")) return;
|
|
6748
|
-
pendingPaths.add(rel.split(
|
|
7520
|
+
pendingPaths.add(rel.split(import_node_path42.default.sep).join("/"));
|
|
6749
7521
|
const phases = classifyChange(rel);
|
|
6750
7522
|
if (phases.size === 0) {
|
|
6751
7523
|
for (const p of ALL_PHASES) pending.add(p);
|
|
@@ -6799,16 +7571,16 @@ async function startWatch(graph, opts) {
|
|
|
6799
7571
|
|
|
6800
7572
|
// src/deploy/detect.ts
|
|
6801
7573
|
init_cjs_shims();
|
|
6802
|
-
var
|
|
6803
|
-
var
|
|
6804
|
-
var
|
|
7574
|
+
var import_node_fs27 = require("fs");
|
|
7575
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
7576
|
+
var import_node_child_process2 = require("child_process");
|
|
6805
7577
|
var import_node_crypto4 = require("crypto");
|
|
6806
7578
|
function generateToken() {
|
|
6807
7579
|
return (0, import_node_crypto4.randomBytes)(32).toString("base64url");
|
|
6808
7580
|
}
|
|
6809
7581
|
async function probeBinary(binary, arg) {
|
|
6810
7582
|
return new Promise((resolve) => {
|
|
6811
|
-
const child = (0,
|
|
7583
|
+
const child = (0, import_node_child_process2.spawn)(binary, [arg], { stdio: "ignore" });
|
|
6812
7584
|
const timer = setTimeout(() => {
|
|
6813
7585
|
child.kill("SIGKILL");
|
|
6814
7586
|
resolve(false);
|
|
@@ -6900,21 +7672,21 @@ async function runDeploy(opts = {}) {
|
|
|
6900
7672
|
const token = generateToken();
|
|
6901
7673
|
switch (substrate) {
|
|
6902
7674
|
case "docker-compose": {
|
|
6903
|
-
const artifactPath =
|
|
7675
|
+
const artifactPath = import_node_path43.default.join(cwd, "docker-compose.neat.yml");
|
|
6904
7676
|
const contents = emitDockerCompose(cwd);
|
|
6905
|
-
await
|
|
7677
|
+
await import_node_fs27.promises.writeFile(artifactPath, contents, "utf8");
|
|
6906
7678
|
return {
|
|
6907
7679
|
substrate,
|
|
6908
7680
|
artifactPath,
|
|
6909
7681
|
token,
|
|
6910
7682
|
contents,
|
|
6911
|
-
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${
|
|
7683
|
+
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${import_node_path43.default.basename(artifactPath)} up -d`
|
|
6912
7684
|
};
|
|
6913
7685
|
}
|
|
6914
7686
|
case "systemd": {
|
|
6915
|
-
const artifactPath =
|
|
7687
|
+
const artifactPath = import_node_path43.default.join(cwd, "neat.service");
|
|
6916
7688
|
const contents = emitSystemdUnit(cwd);
|
|
6917
|
-
await
|
|
7689
|
+
await import_node_fs27.promises.writeFile(artifactPath, contents, "utf8");
|
|
6918
7690
|
return {
|
|
6919
7691
|
substrate,
|
|
6920
7692
|
artifactPath,
|
|
@@ -6947,8 +7719,9 @@ init_cjs_shims();
|
|
|
6947
7719
|
|
|
6948
7720
|
// src/installers/javascript.ts
|
|
6949
7721
|
init_cjs_shims();
|
|
6950
|
-
var
|
|
6951
|
-
var
|
|
7722
|
+
var import_node_fs28 = require("fs");
|
|
7723
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
7724
|
+
var import_semver2 = __toESM(require("semver"), 1);
|
|
6952
7725
|
|
|
6953
7726
|
// src/installers/templates.ts
|
|
6954
7727
|
init_cjs_shims();
|
|
@@ -7485,15 +8258,15 @@ var OTEL_ENV = {
|
|
|
7485
8258
|
value: "http://localhost:4318/projects/<project>/v1/traces"
|
|
7486
8259
|
};
|
|
7487
8260
|
function serviceNodeName(pkg, serviceDir) {
|
|
7488
|
-
return pkg.name ??
|
|
8261
|
+
return pkg.name ?? import_node_path44.default.basename(serviceDir);
|
|
7489
8262
|
}
|
|
7490
8263
|
function projectToken(pkg, serviceDir, project) {
|
|
7491
8264
|
if (project && project.length > 0) return project;
|
|
7492
|
-
return pkg.name ??
|
|
8265
|
+
return pkg.name ?? import_node_path44.default.basename(serviceDir);
|
|
7493
8266
|
}
|
|
7494
8267
|
async function readJsonFile(p) {
|
|
7495
8268
|
try {
|
|
7496
|
-
const raw = await
|
|
8269
|
+
const raw = await import_node_fs28.promises.readFile(p, "utf8");
|
|
7497
8270
|
return JSON.parse(raw);
|
|
7498
8271
|
} catch {
|
|
7499
8272
|
return null;
|
|
@@ -7502,26 +8275,33 @@ async function readJsonFile(p) {
|
|
|
7502
8275
|
async function detectRuntimeKind(pkgRoot, pkg) {
|
|
7503
8276
|
const deps = allDeps(pkg);
|
|
7504
8277
|
if ("react-native" in deps || "expo" in deps) return "react-native";
|
|
7505
|
-
const appJson = await readJsonFile(
|
|
8278
|
+
const appJson = await readJsonFile(import_node_path44.default.join(pkgRoot, "app.json"));
|
|
7506
8279
|
if (appJson && typeof appJson === "object" && "expo" in appJson) {
|
|
7507
8280
|
return "react-native";
|
|
7508
8281
|
}
|
|
7509
|
-
if (await
|
|
8282
|
+
if (await exists3(import_node_path44.default.join(pkgRoot, "vite.config.js")) || await exists3(import_node_path44.default.join(pkgRoot, "vite.config.ts")) || await exists3(import_node_path44.default.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
|
|
7510
8283
|
return "browser-bundle";
|
|
7511
8284
|
}
|
|
8285
|
+
if (await exists3(import_node_path44.default.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
|
|
8286
|
+
if (await exists3(import_node_path44.default.join(pkgRoot, "bun.lockb"))) return "bun";
|
|
8287
|
+
if (await exists3(import_node_path44.default.join(pkgRoot, "deno.json")) || await exists3(import_node_path44.default.join(pkgRoot, "deno.lock"))) {
|
|
8288
|
+
return "deno";
|
|
8289
|
+
}
|
|
8290
|
+
const engines = pkg.engines ?? {};
|
|
8291
|
+
if ("electron" in engines) return "electron";
|
|
7512
8292
|
return "node";
|
|
7513
8293
|
}
|
|
7514
|
-
async function
|
|
8294
|
+
async function readPackageJson2(serviceDir) {
|
|
7515
8295
|
try {
|
|
7516
|
-
const raw = await
|
|
8296
|
+
const raw = await import_node_fs28.promises.readFile(import_node_path44.default.join(serviceDir, "package.json"), "utf8");
|
|
7517
8297
|
return JSON.parse(raw);
|
|
7518
8298
|
} catch {
|
|
7519
8299
|
return null;
|
|
7520
8300
|
}
|
|
7521
8301
|
}
|
|
7522
|
-
async function
|
|
8302
|
+
async function exists3(p) {
|
|
7523
8303
|
try {
|
|
7524
|
-
await
|
|
8304
|
+
await import_node_fs28.promises.stat(p);
|
|
7525
8305
|
return true;
|
|
7526
8306
|
} catch {
|
|
7527
8307
|
return false;
|
|
@@ -7529,7 +8309,7 @@ async function exists2(p) {
|
|
|
7529
8309
|
}
|
|
7530
8310
|
async function readFileMaybe(p) {
|
|
7531
8311
|
try {
|
|
7532
|
-
return await
|
|
8312
|
+
return await import_node_fs28.promises.readFile(p, "utf8");
|
|
7533
8313
|
} catch {
|
|
7534
8314
|
return null;
|
|
7535
8315
|
}
|
|
@@ -7545,14 +8325,20 @@ async function planOtelInitGeneration(file, contents) {
|
|
|
7545
8325
|
return null;
|
|
7546
8326
|
}
|
|
7547
8327
|
async function detect(serviceDir) {
|
|
7548
|
-
const pkg = await
|
|
8328
|
+
const pkg = await readPackageJson2(serviceDir);
|
|
7549
8329
|
return pkg !== null && typeof pkg.name === "string";
|
|
7550
8330
|
}
|
|
8331
|
+
function needsVersionUpgrade(installed, expected) {
|
|
8332
|
+
return !import_semver2.default.satisfies(
|
|
8333
|
+
import_semver2.default.minVersion(installed)?.version ?? installed,
|
|
8334
|
+
expected
|
|
8335
|
+
) && !import_semver2.default.intersects(installed, expected);
|
|
8336
|
+
}
|
|
7551
8337
|
var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
|
|
7552
8338
|
async function findNextConfig(serviceDir) {
|
|
7553
8339
|
for (const name of NEXT_CONFIG_CANDIDATES) {
|
|
7554
|
-
const candidate =
|
|
7555
|
-
if (await
|
|
8340
|
+
const candidate = import_node_path44.default.join(serviceDir, name);
|
|
8341
|
+
if (await exists3(candidate)) return candidate;
|
|
7556
8342
|
}
|
|
7557
8343
|
return null;
|
|
7558
8344
|
}
|
|
@@ -7578,8 +8364,8 @@ function hasRemixDependency(pkg) {
|
|
|
7578
8364
|
}
|
|
7579
8365
|
async function findRemixEntry(serviceDir) {
|
|
7580
8366
|
for (const rel of REMIX_ENTRY_CANDIDATES) {
|
|
7581
|
-
const candidate =
|
|
7582
|
-
if (await
|
|
8367
|
+
const candidate = import_node_path44.default.join(serviceDir, rel);
|
|
8368
|
+
if (await exists3(candidate)) return candidate;
|
|
7583
8369
|
}
|
|
7584
8370
|
return null;
|
|
7585
8371
|
}
|
|
@@ -7590,15 +8376,15 @@ function hasSvelteKitDependency(pkg) {
|
|
|
7590
8376
|
}
|
|
7591
8377
|
async function findSvelteKitHooks(serviceDir) {
|
|
7592
8378
|
for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
|
|
7593
|
-
const candidate =
|
|
7594
|
-
if (await
|
|
8379
|
+
const candidate = import_node_path44.default.join(serviceDir, rel);
|
|
8380
|
+
if (await exists3(candidate)) return candidate;
|
|
7595
8381
|
}
|
|
7596
8382
|
return null;
|
|
7597
8383
|
}
|
|
7598
8384
|
async function findSvelteKitConfig(serviceDir) {
|
|
7599
8385
|
for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
|
|
7600
|
-
const candidate =
|
|
7601
|
-
if (await
|
|
8386
|
+
const candidate = import_node_path44.default.join(serviceDir, rel);
|
|
8387
|
+
if (await exists3(candidate)) return candidate;
|
|
7602
8388
|
}
|
|
7603
8389
|
return null;
|
|
7604
8390
|
}
|
|
@@ -7608,8 +8394,8 @@ function hasNuxtDependency(pkg) {
|
|
|
7608
8394
|
}
|
|
7609
8395
|
async function findNuxtConfig(serviceDir) {
|
|
7610
8396
|
for (const name of NUXT_CONFIG_CANDIDATES) {
|
|
7611
|
-
const candidate =
|
|
7612
|
-
if (await
|
|
8397
|
+
const candidate = import_node_path44.default.join(serviceDir, name);
|
|
8398
|
+
if (await exists3(candidate)) return candidate;
|
|
7613
8399
|
}
|
|
7614
8400
|
return null;
|
|
7615
8401
|
}
|
|
@@ -7619,8 +8405,8 @@ function hasAstroDependency(pkg) {
|
|
|
7619
8405
|
}
|
|
7620
8406
|
async function findAstroConfig(serviceDir) {
|
|
7621
8407
|
for (const name of ASTRO_CONFIG_CANDIDATES) {
|
|
7622
|
-
const candidate =
|
|
7623
|
-
if (await
|
|
8408
|
+
const candidate = import_node_path44.default.join(serviceDir, name);
|
|
8409
|
+
if (await exists3(candidate)) return candidate;
|
|
7624
8410
|
}
|
|
7625
8411
|
return null;
|
|
7626
8412
|
}
|
|
@@ -7633,7 +8419,7 @@ function parseNextMajor(range) {
|
|
|
7633
8419
|
return Number.isFinite(n) ? n : null;
|
|
7634
8420
|
}
|
|
7635
8421
|
async function isTypeScriptProject(serviceDir) {
|
|
7636
|
-
return
|
|
8422
|
+
return exists3(import_node_path44.default.join(serviceDir, "tsconfig.json"));
|
|
7637
8423
|
}
|
|
7638
8424
|
var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
|
|
7639
8425
|
var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
|
|
@@ -7679,8 +8465,8 @@ function entryFromScript(script) {
|
|
|
7679
8465
|
}
|
|
7680
8466
|
async function resolveEntry(serviceDir, pkg) {
|
|
7681
8467
|
if (typeof pkg.main === "string" && pkg.main.length > 0) {
|
|
7682
|
-
const candidate =
|
|
7683
|
-
if (await
|
|
8468
|
+
const candidate = import_node_path44.default.resolve(serviceDir, pkg.main);
|
|
8469
|
+
if (await exists3(candidate)) return candidate;
|
|
7684
8470
|
}
|
|
7685
8471
|
if (pkg.bin) {
|
|
7686
8472
|
let binEntry;
|
|
@@ -7693,36 +8479,36 @@ async function resolveEntry(serviceDir, pkg) {
|
|
|
7693
8479
|
if (typeof first === "string") binEntry = first;
|
|
7694
8480
|
}
|
|
7695
8481
|
if (binEntry) {
|
|
7696
|
-
const candidate =
|
|
7697
|
-
if (await
|
|
8482
|
+
const candidate = import_node_path44.default.resolve(serviceDir, binEntry);
|
|
8483
|
+
if (await exists3(candidate)) return candidate;
|
|
7698
8484
|
}
|
|
7699
8485
|
}
|
|
7700
8486
|
const startEntry = entryFromScript(pkg.scripts?.start);
|
|
7701
8487
|
if (startEntry) {
|
|
7702
|
-
const candidate =
|
|
7703
|
-
if (await
|
|
8488
|
+
const candidate = import_node_path44.default.resolve(serviceDir, startEntry);
|
|
8489
|
+
if (await exists3(candidate)) return candidate;
|
|
7704
8490
|
}
|
|
7705
8491
|
const devEntry = entryFromScript(pkg.scripts?.dev);
|
|
7706
8492
|
if (devEntry) {
|
|
7707
|
-
const candidate =
|
|
7708
|
-
if (await
|
|
8493
|
+
const candidate = import_node_path44.default.resolve(serviceDir, devEntry);
|
|
8494
|
+
if (await exists3(candidate)) return candidate;
|
|
7709
8495
|
}
|
|
7710
8496
|
for (const rel of SRC_INDEX_CANDIDATES) {
|
|
7711
|
-
const candidate =
|
|
7712
|
-
if (await
|
|
8497
|
+
const candidate = import_node_path44.default.join(serviceDir, rel);
|
|
8498
|
+
if (await exists3(candidate)) return candidate;
|
|
7713
8499
|
}
|
|
7714
8500
|
for (const rel of SRC_NAMED_CANDIDATES) {
|
|
7715
|
-
const candidate =
|
|
7716
|
-
if (await
|
|
8501
|
+
const candidate = import_node_path44.default.join(serviceDir, rel);
|
|
8502
|
+
if (await exists3(candidate)) return candidate;
|
|
7717
8503
|
}
|
|
7718
8504
|
for (const name of INDEX_CANDIDATES) {
|
|
7719
|
-
const candidate =
|
|
7720
|
-
if (await
|
|
8505
|
+
const candidate = import_node_path44.default.join(serviceDir, name);
|
|
8506
|
+
if (await exists3(candidate)) return candidate;
|
|
7721
8507
|
}
|
|
7722
8508
|
return null;
|
|
7723
8509
|
}
|
|
7724
8510
|
function dispatchEntry(entryFile, pkg) {
|
|
7725
|
-
const ext =
|
|
8511
|
+
const ext = import_node_path44.default.extname(entryFile).toLowerCase();
|
|
7726
8512
|
if (ext === ".ts" || ext === ".tsx") return "ts";
|
|
7727
8513
|
if (ext === ".mjs") return "esm";
|
|
7728
8514
|
if (ext === ".cjs") return "cjs";
|
|
@@ -7739,9 +8525,9 @@ function otelInitContents(flavor) {
|
|
|
7739
8525
|
return OTEL_INIT_CJS;
|
|
7740
8526
|
}
|
|
7741
8527
|
function injectionLine(flavor, entryFile, otelInitFile) {
|
|
7742
|
-
let rel =
|
|
8528
|
+
let rel = import_node_path44.default.relative(import_node_path44.default.dirname(entryFile), otelInitFile);
|
|
7743
8529
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
7744
|
-
rel = rel.split(
|
|
8530
|
+
rel = rel.split(import_node_path44.default.sep).join("/");
|
|
7745
8531
|
if (flavor === "cjs") return `require('${rel}')`;
|
|
7746
8532
|
if (flavor === "esm") return `import '${rel}'`;
|
|
7747
8533
|
const tsRel = rel.replace(/\.ts$/, "");
|
|
@@ -7754,56 +8540,56 @@ function lineIsOtelInjection(line) {
|
|
|
7754
8540
|
}
|
|
7755
8541
|
async function detectsSrcLayout(serviceDir) {
|
|
7756
8542
|
const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
|
|
7760
|
-
|
|
8543
|
+
exists3(import_node_path44.default.join(serviceDir, "src", "app")),
|
|
8544
|
+
exists3(import_node_path44.default.join(serviceDir, "src", "pages")),
|
|
8545
|
+
exists3(import_node_path44.default.join(serviceDir, "app")),
|
|
8546
|
+
exists3(import_node_path44.default.join(serviceDir, "pages"))
|
|
7761
8547
|
]);
|
|
7762
8548
|
return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
|
|
7763
8549
|
}
|
|
7764
8550
|
async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
|
|
7765
8551
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
7766
8552
|
const srcLayout = await detectsSrcLayout(serviceDir);
|
|
7767
|
-
const baseDir = srcLayout ?
|
|
7768
|
-
const instrumentationFile =
|
|
7769
|
-
const instrumentationNodeFile =
|
|
8553
|
+
const baseDir = srcLayout ? import_node_path44.default.join(serviceDir, "src") : serviceDir;
|
|
8554
|
+
const instrumentationFile = import_node_path44.default.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
|
|
8555
|
+
const instrumentationNodeFile = import_node_path44.default.join(
|
|
7770
8556
|
baseDir,
|
|
7771
8557
|
useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
|
|
7772
8558
|
);
|
|
7773
|
-
const envNeatFile =
|
|
8559
|
+
const envNeatFile = import_node_path44.default.join(baseDir, ".env.neat");
|
|
7774
8560
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
7775
8561
|
const dependencyEdits = [];
|
|
7776
8562
|
for (const sdk of SDK_PACKAGES) {
|
|
7777
|
-
if (sdk.name in existingDeps)
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
7783
|
-
});
|
|
8563
|
+
if (sdk.name in existingDeps) {
|
|
8564
|
+
if (needsVersionUpgrade(existingDeps[sdk.name], sdk.version)) {
|
|
8565
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: sdk.name, version: sdk.version, fromVersion: existingDeps[sdk.name] });
|
|
8566
|
+
}
|
|
8567
|
+
continue;
|
|
8568
|
+
}
|
|
8569
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
|
|
7784
8570
|
}
|
|
7785
8571
|
const nonBundled = detectNonBundledInstrumentations(pkg);
|
|
7786
8572
|
for (const inst of nonBundled) {
|
|
7787
|
-
if (inst.pkg in existingDeps)
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
7791
|
-
|
|
7792
|
-
|
|
7793
|
-
});
|
|
8573
|
+
if (inst.pkg in existingDeps) {
|
|
8574
|
+
if (needsVersionUpgrade(existingDeps[inst.pkg], inst.version)) {
|
|
8575
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: inst.pkg, version: inst.version, fromVersion: existingDeps[inst.pkg] });
|
|
8576
|
+
}
|
|
8577
|
+
continue;
|
|
8578
|
+
}
|
|
8579
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: inst.pkg, version: inst.version });
|
|
7794
8580
|
}
|
|
7795
8581
|
const svcName = serviceNodeName(pkg, serviceDir);
|
|
7796
8582
|
const projectName = projectToken(pkg, serviceDir, project);
|
|
7797
8583
|
const registrations = nonBundled.map((i) => i.registration);
|
|
7798
8584
|
const generatedFiles = [];
|
|
7799
|
-
if (!await
|
|
8585
|
+
if (!await exists3(instrumentationFile)) {
|
|
7800
8586
|
generatedFiles.push({
|
|
7801
8587
|
file: instrumentationFile,
|
|
7802
8588
|
contents: useTs ? NEXT_INSTRUMENTATION_TS : NEXT_INSTRUMENTATION_JS,
|
|
7803
8589
|
skipIfExists: true
|
|
7804
8590
|
});
|
|
7805
8591
|
}
|
|
7806
|
-
if (!await
|
|
8592
|
+
if (!await exists3(instrumentationNodeFile)) {
|
|
7807
8593
|
generatedFiles.push({
|
|
7808
8594
|
file: instrumentationNodeFile,
|
|
7809
8595
|
contents: renderNextInstrumentationNode(
|
|
@@ -7815,7 +8601,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
7815
8601
|
skipIfExists: true
|
|
7816
8602
|
});
|
|
7817
8603
|
}
|
|
7818
|
-
if (!await
|
|
8604
|
+
if (!await exists3(envNeatFile)) {
|
|
7819
8605
|
generatedFiles.push({
|
|
7820
8606
|
file: envNeatFile,
|
|
7821
8607
|
contents: renderEnvNeat(svcName, projectName),
|
|
@@ -7827,7 +8613,7 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
7827
8613
|
const nextMajor = parseNextMajor(nextRange);
|
|
7828
8614
|
if (nextMajor !== null && nextMajor < 15) {
|
|
7829
8615
|
try {
|
|
7830
|
-
const raw = await
|
|
8616
|
+
const raw = await import_node_fs28.promises.readFile(nextConfigPath, "utf8");
|
|
7831
8617
|
if (!raw.includes("instrumentationHook")) {
|
|
7832
8618
|
nextConfigEdit = {
|
|
7833
8619
|
file: nextConfigPath,
|
|
@@ -7875,8 +8661,8 @@ function buildDependencyEdits(pkg, manifestPath) {
|
|
|
7875
8661
|
return edits;
|
|
7876
8662
|
}
|
|
7877
8663
|
async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
|
|
7878
|
-
const envNeatFile =
|
|
7879
|
-
if (!await
|
|
8664
|
+
const envNeatFile = import_node_path44.default.join(serviceDir, ".env.neat");
|
|
8665
|
+
if (!await exists3(envNeatFile)) {
|
|
7880
8666
|
generatedFiles.push({
|
|
7881
8667
|
file: envNeatFile,
|
|
7882
8668
|
contents: renderEnvNeat(
|
|
@@ -7910,13 +8696,13 @@ function fileImportsOtelHook(raw, specifiers) {
|
|
|
7910
8696
|
}
|
|
7911
8697
|
async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
7912
8698
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
7913
|
-
const otelServerFile =
|
|
8699
|
+
const otelServerFile = import_node_path44.default.join(
|
|
7914
8700
|
serviceDir,
|
|
7915
8701
|
useTs ? "app/otel.server.ts" : "app/otel.server.js"
|
|
7916
8702
|
);
|
|
7917
8703
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
7918
8704
|
const generatedFiles = [];
|
|
7919
|
-
if (!await
|
|
8705
|
+
if (!await exists3(otelServerFile)) {
|
|
7920
8706
|
generatedFiles.push({
|
|
7921
8707
|
file: otelServerFile,
|
|
7922
8708
|
contents: renderFrameworkOtelInitForPkg(
|
|
@@ -7931,7 +8717,7 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
7931
8717
|
await queueEnvNeat(serviceDir, pkg, project, generatedFiles);
|
|
7932
8718
|
const entrypointEdits = [];
|
|
7933
8719
|
try {
|
|
7934
|
-
const raw = await
|
|
8720
|
+
const raw = await import_node_fs28.promises.readFile(entryFile, "utf8");
|
|
7935
8721
|
if (!fileImportsOtelHook(raw, ["./otel.server"])) {
|
|
7936
8722
|
const lines = raw.split(/\r?\n/);
|
|
7937
8723
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -7967,15 +8753,15 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
7967
8753
|
}
|
|
7968
8754
|
async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
|
|
7969
8755
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
7970
|
-
const otelInitFile =
|
|
8756
|
+
const otelInitFile = import_node_path44.default.join(
|
|
7971
8757
|
serviceDir,
|
|
7972
8758
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
7973
8759
|
);
|
|
7974
|
-
const resolvedHooksFile = hooksFile ??
|
|
8760
|
+
const resolvedHooksFile = hooksFile ?? import_node_path44.default.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
|
|
7975
8761
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
7976
8762
|
const generatedFiles = [];
|
|
7977
8763
|
const entrypointEdits = [];
|
|
7978
|
-
if (!await
|
|
8764
|
+
if (!await exists3(otelInitFile)) {
|
|
7979
8765
|
generatedFiles.push({
|
|
7980
8766
|
file: otelInitFile,
|
|
7981
8767
|
contents: renderFrameworkOtelInitForPkg(
|
|
@@ -7996,7 +8782,7 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
7996
8782
|
});
|
|
7997
8783
|
} else {
|
|
7998
8784
|
try {
|
|
7999
|
-
const raw = await
|
|
8785
|
+
const raw = await import_node_fs28.promises.readFile(hooksFile, "utf8");
|
|
8000
8786
|
if (!fileImportsOtelHook(raw, ["./otel-init"])) {
|
|
8001
8787
|
const lines = raw.split(/\r?\n/);
|
|
8002
8788
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -8033,17 +8819,17 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
8033
8819
|
}
|
|
8034
8820
|
async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
8035
8821
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
8036
|
-
const otelPluginFile =
|
|
8822
|
+
const otelPluginFile = import_node_path44.default.join(
|
|
8037
8823
|
serviceDir,
|
|
8038
8824
|
useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
|
|
8039
8825
|
);
|
|
8040
|
-
const otelInitFile =
|
|
8826
|
+
const otelInitFile = import_node_path44.default.join(
|
|
8041
8827
|
serviceDir,
|
|
8042
8828
|
useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
|
|
8043
8829
|
);
|
|
8044
8830
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
8045
8831
|
const generatedFiles = [];
|
|
8046
|
-
if (!await
|
|
8832
|
+
if (!await exists3(otelInitFile)) {
|
|
8047
8833
|
generatedFiles.push({
|
|
8048
8834
|
file: otelInitFile,
|
|
8049
8835
|
contents: renderFrameworkOtelInitForPkg(
|
|
@@ -8055,7 +8841,7 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
|
8055
8841
|
skipIfExists: true
|
|
8056
8842
|
});
|
|
8057
8843
|
}
|
|
8058
|
-
if (!await
|
|
8844
|
+
if (!await exists3(otelPluginFile)) {
|
|
8059
8845
|
generatedFiles.push({
|
|
8060
8846
|
file: otelPluginFile,
|
|
8061
8847
|
contents: useTs ? NUXT_OTEL_PLUGIN_TS : NUXT_OTEL_PLUGIN_JS,
|
|
@@ -8088,23 +8874,23 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
|
8088
8874
|
var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
|
|
8089
8875
|
async function findAstroMiddleware(serviceDir) {
|
|
8090
8876
|
for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
|
|
8091
|
-
const candidate =
|
|
8092
|
-
if (await
|
|
8877
|
+
const candidate = import_node_path44.default.join(serviceDir, rel);
|
|
8878
|
+
if (await exists3(candidate)) return candidate;
|
|
8093
8879
|
}
|
|
8094
8880
|
return null;
|
|
8095
8881
|
}
|
|
8096
8882
|
async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
8097
8883
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
8098
|
-
const otelInitFile =
|
|
8884
|
+
const otelInitFile = import_node_path44.default.join(
|
|
8099
8885
|
serviceDir,
|
|
8100
8886
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
8101
8887
|
);
|
|
8102
8888
|
const existingMiddleware = await findAstroMiddleware(serviceDir);
|
|
8103
|
-
const middlewareFile = existingMiddleware ??
|
|
8889
|
+
const middlewareFile = existingMiddleware ?? import_node_path44.default.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
|
|
8104
8890
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
8105
8891
|
const generatedFiles = [];
|
|
8106
8892
|
const entrypointEdits = [];
|
|
8107
|
-
if (!await
|
|
8893
|
+
if (!await exists3(otelInitFile)) {
|
|
8108
8894
|
generatedFiles.push({
|
|
8109
8895
|
file: otelInitFile,
|
|
8110
8896
|
contents: renderFrameworkOtelInitForPkg(
|
|
@@ -8125,7 +8911,7 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
|
8125
8911
|
});
|
|
8126
8912
|
} else {
|
|
8127
8913
|
try {
|
|
8128
|
-
const raw = await
|
|
8914
|
+
const raw = await import_node_fs28.promises.readFile(existingMiddleware, "utf8");
|
|
8129
8915
|
if (!fileImportsOtelHook(raw, ["./otel-init"])) {
|
|
8130
8916
|
const lines = raw.split(/\r?\n/);
|
|
8131
8917
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
@@ -8195,8 +8981,8 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
|
|
|
8195
8981
|
return null;
|
|
8196
8982
|
}
|
|
8197
8983
|
async function plan(serviceDir, opts) {
|
|
8198
|
-
const pkg = await
|
|
8199
|
-
const manifestPath =
|
|
8984
|
+
const pkg = await readPackageJson2(serviceDir);
|
|
8985
|
+
const manifestPath = import_node_path44.default.join(serviceDir, "package.json");
|
|
8200
8986
|
const project = opts?.project;
|
|
8201
8987
|
const empty = {
|
|
8202
8988
|
language: "javascript",
|
|
@@ -8231,32 +9017,32 @@ async function plan(serviceDir, opts) {
|
|
|
8231
9017
|
return { ...empty, libOnly: true };
|
|
8232
9018
|
}
|
|
8233
9019
|
const flavor = dispatchEntry(entryFile, pkg);
|
|
8234
|
-
const otelInitFile =
|
|
8235
|
-
const envNeatFile =
|
|
9020
|
+
const otelInitFile = import_node_path44.default.join(import_node_path44.default.dirname(entryFile), otelInitFilename(flavor));
|
|
9021
|
+
const envNeatFile = import_node_path44.default.join(serviceDir, ".env.neat");
|
|
8236
9022
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
8237
9023
|
const dependencyEdits = [];
|
|
8238
9024
|
for (const sdk of SDK_PACKAGES) {
|
|
8239
|
-
if (sdk.name in existingDeps)
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
});
|
|
9025
|
+
if (sdk.name in existingDeps) {
|
|
9026
|
+
if (needsVersionUpgrade(existingDeps[sdk.name], sdk.version)) {
|
|
9027
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: sdk.name, version: sdk.version, fromVersion: existingDeps[sdk.name] });
|
|
9028
|
+
}
|
|
9029
|
+
continue;
|
|
9030
|
+
}
|
|
9031
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
|
|
8246
9032
|
}
|
|
8247
9033
|
const nonBundled = detectNonBundledInstrumentations(pkg);
|
|
8248
9034
|
for (const inst of nonBundled) {
|
|
8249
|
-
if (inst.pkg in existingDeps)
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
});
|
|
9035
|
+
if (inst.pkg in existingDeps) {
|
|
9036
|
+
if (needsVersionUpgrade(existingDeps[inst.pkg], inst.version)) {
|
|
9037
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: inst.pkg, version: inst.version, fromVersion: existingDeps[inst.pkg] });
|
|
9038
|
+
}
|
|
9039
|
+
continue;
|
|
9040
|
+
}
|
|
9041
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: inst.pkg, version: inst.version });
|
|
8256
9042
|
}
|
|
8257
9043
|
const entrypointEdits = [];
|
|
8258
9044
|
try {
|
|
8259
|
-
const raw = await
|
|
9045
|
+
const raw = await import_node_fs28.promises.readFile(entryFile, "utf8");
|
|
8260
9046
|
const lines = raw.split(/\r?\n/);
|
|
8261
9047
|
const firstReal = lines[0]?.startsWith("#!") ? lines[1] ?? "" : lines[0] ?? "";
|
|
8262
9048
|
if (!lineIsOtelInjection(firstReal)) {
|
|
@@ -8279,7 +9065,7 @@ async function plan(serviceDir, opts) {
|
|
|
8279
9065
|
renderNodeOtelInit(otelInitContents(flavor), svcName, projectName, registrations)
|
|
8280
9066
|
);
|
|
8281
9067
|
if (otelInitGen) generatedFiles.push(otelInitGen);
|
|
8282
|
-
if (!await
|
|
9068
|
+
if (!await exists3(envNeatFile)) {
|
|
8283
9069
|
generatedFiles.push({
|
|
8284
9070
|
file: envNeatFile,
|
|
8285
9071
|
contents: renderEnvNeat(svcName, projectName),
|
|
@@ -8301,13 +9087,13 @@ async function plan(serviceDir, opts) {
|
|
|
8301
9087
|
};
|
|
8302
9088
|
}
|
|
8303
9089
|
function isAllowedWritePath(serviceDir, target) {
|
|
8304
|
-
const rel =
|
|
9090
|
+
const rel = import_node_path44.default.relative(serviceDir, target);
|
|
8305
9091
|
if (rel.startsWith("..")) return false;
|
|
8306
|
-
const base =
|
|
9092
|
+
const base = import_node_path44.default.basename(target);
|
|
8307
9093
|
if (base === "package.json") return true;
|
|
8308
9094
|
if (base === ".env.neat") return true;
|
|
8309
9095
|
if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
|
|
8310
|
-
const relPosix = rel.split(
|
|
9096
|
+
const relPosix = rel.split(import_node_path44.default.sep).join("/");
|
|
8311
9097
|
if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
|
|
8312
9098
|
if (relPosix === base) return true;
|
|
8313
9099
|
if (relPosix === `src/${base}`) return true;
|
|
@@ -8324,10 +9110,10 @@ function isAllowedWritePath(serviceDir, target) {
|
|
|
8324
9110
|
return false;
|
|
8325
9111
|
}
|
|
8326
9112
|
async function writeAtomic(file, contents) {
|
|
8327
|
-
await
|
|
9113
|
+
await import_node_fs28.promises.mkdir(import_node_path44.default.dirname(file), { recursive: true });
|
|
8328
9114
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
8329
|
-
await
|
|
8330
|
-
await
|
|
9115
|
+
await import_node_fs28.promises.writeFile(tmp, contents, "utf8");
|
|
9116
|
+
await import_node_fs28.promises.rename(tmp, file);
|
|
8331
9117
|
}
|
|
8332
9118
|
async function apply(installPlan) {
|
|
8333
9119
|
const { serviceDir } = installPlan;
|
|
@@ -8355,6 +9141,28 @@ async function apply(installPlan) {
|
|
|
8355
9141
|
writtenFiles: []
|
|
8356
9142
|
};
|
|
8357
9143
|
}
|
|
9144
|
+
if (installPlan.runtimeKind === "bun") {
|
|
9145
|
+
return { serviceDir, outcome: "bun", reason: "Bun runtime; use BYO-OTel", writtenFiles: [] };
|
|
9146
|
+
}
|
|
9147
|
+
if (installPlan.runtimeKind === "deno") {
|
|
9148
|
+
return { serviceDir, outcome: "deno", reason: "Deno runtime; use BYO-OTel", writtenFiles: [] };
|
|
9149
|
+
}
|
|
9150
|
+
if (installPlan.runtimeKind === "cloudflare-workers") {
|
|
9151
|
+
return {
|
|
9152
|
+
serviceDir,
|
|
9153
|
+
outcome: "cloudflare-workers",
|
|
9154
|
+
reason: "Cloudflare Workers runtime; use BYO-OTel",
|
|
9155
|
+
writtenFiles: []
|
|
9156
|
+
};
|
|
9157
|
+
}
|
|
9158
|
+
if (installPlan.runtimeKind === "electron") {
|
|
9159
|
+
return {
|
|
9160
|
+
serviceDir,
|
|
9161
|
+
outcome: "electron",
|
|
9162
|
+
reason: "Electron; use BYO-OTel",
|
|
9163
|
+
writtenFiles: []
|
|
9164
|
+
};
|
|
9165
|
+
}
|
|
8358
9166
|
if (installPlan.dependencyEdits.length === 0 && installPlan.entrypointEdits.length === 0 && (installPlan.generatedFiles?.length ?? 0) === 0 && installPlan.nextConfigEdit === void 0) {
|
|
8359
9167
|
return {
|
|
8360
9168
|
serviceDir,
|
|
@@ -8379,9 +9187,9 @@ async function apply(installPlan) {
|
|
|
8379
9187
|
const originals = /* @__PURE__ */ new Map();
|
|
8380
9188
|
const createdFiles = [];
|
|
8381
9189
|
for (const target of allTargets) {
|
|
8382
|
-
if (await
|
|
9190
|
+
if (await exists3(target)) {
|
|
8383
9191
|
try {
|
|
8384
|
-
originals.set(target, await
|
|
9192
|
+
originals.set(target, await import_node_fs28.promises.readFile(target, "utf8"));
|
|
8385
9193
|
} catch {
|
|
8386
9194
|
}
|
|
8387
9195
|
}
|
|
@@ -8405,6 +9213,8 @@ async function apply(installPlan) {
|
|
|
8405
9213
|
if (!(dep.name in (pkg.dependencies ?? {}))) {
|
|
8406
9214
|
pkg.dependencies[dep.name] = dep.version;
|
|
8407
9215
|
}
|
|
9216
|
+
} else if (dep.kind === "upgrade") {
|
|
9217
|
+
pkg.dependencies[dep.name] = dep.version;
|
|
8408
9218
|
} else {
|
|
8409
9219
|
delete pkg.dependencies[dep.name];
|
|
8410
9220
|
}
|
|
@@ -8414,7 +9224,7 @@ async function apply(installPlan) {
|
|
|
8414
9224
|
writtenFiles.push(file);
|
|
8415
9225
|
}
|
|
8416
9226
|
for (const gen of installPlan.generatedFiles ?? []) {
|
|
8417
|
-
if (gen.skipIfExists && await
|
|
9227
|
+
if (gen.skipIfExists && await exists3(gen.file)) {
|
|
8418
9228
|
continue;
|
|
8419
9229
|
}
|
|
8420
9230
|
await writeAtomic(gen.file, gen.contents);
|
|
@@ -8462,14 +9272,14 @@ async function rollback(installPlan, originals, createdFiles) {
|
|
|
8462
9272
|
const removed = [];
|
|
8463
9273
|
for (const [file, raw] of originals.entries()) {
|
|
8464
9274
|
try {
|
|
8465
|
-
await
|
|
9275
|
+
await import_node_fs28.promises.writeFile(file, raw, "utf8");
|
|
8466
9276
|
restored.push(file);
|
|
8467
9277
|
} catch {
|
|
8468
9278
|
}
|
|
8469
9279
|
}
|
|
8470
9280
|
for (const file of createdFiles) {
|
|
8471
9281
|
try {
|
|
8472
|
-
await
|
|
9282
|
+
await import_node_fs28.promises.unlink(file);
|
|
8473
9283
|
removed.push(file);
|
|
8474
9284
|
} catch {
|
|
8475
9285
|
}
|
|
@@ -8484,8 +9294,8 @@ async function rollback(installPlan, originals, createdFiles) {
|
|
|
8484
9294
|
...removed.map((f) => `removed: ${f}`),
|
|
8485
9295
|
""
|
|
8486
9296
|
];
|
|
8487
|
-
const rollbackPath =
|
|
8488
|
-
await
|
|
9297
|
+
const rollbackPath = import_node_path44.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
9298
|
+
await import_node_fs28.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
8489
9299
|
}
|
|
8490
9300
|
function injectInstrumentationHook(raw) {
|
|
8491
9301
|
if (raw.includes("instrumentationHook")) return raw;
|
|
@@ -8514,8 +9324,8 @@ var javascriptInstaller = {
|
|
|
8514
9324
|
|
|
8515
9325
|
// src/installers/python.ts
|
|
8516
9326
|
init_cjs_shims();
|
|
8517
|
-
var
|
|
8518
|
-
var
|
|
9327
|
+
var import_node_fs29 = require("fs");
|
|
9328
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
8519
9329
|
var SDK_PACKAGES2 = [
|
|
8520
9330
|
{ name: "opentelemetry-distro", version: ">=0.49b0" },
|
|
8521
9331
|
{ name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
|
|
@@ -8525,9 +9335,9 @@ var OTEL_ENV2 = {
|
|
|
8525
9335
|
key: "OTEL_EXPORTER_OTLP_ENDPOINT",
|
|
8526
9336
|
value: "http://localhost:4318"
|
|
8527
9337
|
};
|
|
8528
|
-
async function
|
|
9338
|
+
async function exists4(p) {
|
|
8529
9339
|
try {
|
|
8530
|
-
await
|
|
9340
|
+
await import_node_fs29.promises.stat(p);
|
|
8531
9341
|
return true;
|
|
8532
9342
|
} catch {
|
|
8533
9343
|
return false;
|
|
@@ -8536,7 +9346,7 @@ async function exists3(p) {
|
|
|
8536
9346
|
async function detect2(serviceDir) {
|
|
8537
9347
|
const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
|
|
8538
9348
|
for (const m of markers) {
|
|
8539
|
-
if (await
|
|
9349
|
+
if (await exists4(import_node_path45.default.join(serviceDir, m))) return true;
|
|
8540
9350
|
}
|
|
8541
9351
|
return false;
|
|
8542
9352
|
}
|
|
@@ -8546,9 +9356,9 @@ function reqPackageName(line) {
|
|
|
8546
9356
|
return head.replace(/[<>=!~].*$/, "").toLowerCase();
|
|
8547
9357
|
}
|
|
8548
9358
|
async function planRequirementsTxtEdits(serviceDir) {
|
|
8549
|
-
const file =
|
|
8550
|
-
if (!await
|
|
8551
|
-
const raw = await
|
|
9359
|
+
const file = import_node_path45.default.join(serviceDir, "requirements.txt");
|
|
9360
|
+
if (!await exists4(file)) return null;
|
|
9361
|
+
const raw = await import_node_fs29.promises.readFile(file, "utf8");
|
|
8552
9362
|
const presentNames = new Set(
|
|
8553
9363
|
raw.split(/\r?\n/).map(reqPackageName).filter((n) => n.length > 0)
|
|
8554
9364
|
);
|
|
@@ -8556,9 +9366,9 @@ async function planRequirementsTxtEdits(serviceDir) {
|
|
|
8556
9366
|
return { manifest: file, missing: [...missing] };
|
|
8557
9367
|
}
|
|
8558
9368
|
async function planProcfileEdits(serviceDir) {
|
|
8559
|
-
const procfile =
|
|
8560
|
-
if (!await
|
|
8561
|
-
const raw = await
|
|
9369
|
+
const procfile = import_node_path45.default.join(serviceDir, "Procfile");
|
|
9370
|
+
if (!await exists4(procfile)) return [];
|
|
9371
|
+
const raw = await import_node_fs29.promises.readFile(procfile, "utf8");
|
|
8562
9372
|
const edits = [];
|
|
8563
9373
|
for (const line of raw.split(/\r?\n/)) {
|
|
8564
9374
|
if (line.length === 0) continue;
|
|
@@ -8610,8 +9420,8 @@ async function applyRequirementsTxt(manifest, edits, original) {
|
|
|
8610
9420
|
const next = `${original}${trailing}${newlines.join("\n")}
|
|
8611
9421
|
`;
|
|
8612
9422
|
const tmp = `${manifest}.${process.pid}.${Date.now()}.tmp`;
|
|
8613
|
-
await
|
|
8614
|
-
await
|
|
9423
|
+
await import_node_fs29.promises.writeFile(tmp, next, "utf8");
|
|
9424
|
+
await import_node_fs29.promises.rename(tmp, manifest);
|
|
8615
9425
|
}
|
|
8616
9426
|
async function applyProcfile(procfile, edits, original) {
|
|
8617
9427
|
let next = original;
|
|
@@ -8620,8 +9430,8 @@ async function applyProcfile(procfile, edits, original) {
|
|
|
8620
9430
|
next = next.replace(e.before, e.after);
|
|
8621
9431
|
}
|
|
8622
9432
|
const tmp = `${procfile}.${process.pid}.${Date.now()}.tmp`;
|
|
8623
|
-
await
|
|
8624
|
-
await
|
|
9433
|
+
await import_node_fs29.promises.writeFile(tmp, next, "utf8");
|
|
9434
|
+
await import_node_fs29.promises.rename(tmp, procfile);
|
|
8625
9435
|
}
|
|
8626
9436
|
async function apply2(installPlan) {
|
|
8627
9437
|
const { serviceDir } = installPlan;
|
|
@@ -8634,7 +9444,7 @@ async function apply2(installPlan) {
|
|
|
8634
9444
|
const originals = /* @__PURE__ */ new Map();
|
|
8635
9445
|
for (const file of touched) {
|
|
8636
9446
|
try {
|
|
8637
|
-
originals.set(file, await
|
|
9447
|
+
originals.set(file, await import_node_fs29.promises.readFile(file, "utf8"));
|
|
8638
9448
|
} catch {
|
|
8639
9449
|
}
|
|
8640
9450
|
}
|
|
@@ -8645,7 +9455,7 @@ async function apply2(installPlan) {
|
|
|
8645
9455
|
if (raw === void 0) {
|
|
8646
9456
|
throw new Error(`python installer: cannot read ${file} during apply`);
|
|
8647
9457
|
}
|
|
8648
|
-
const base =
|
|
9458
|
+
const base = import_node_path45.default.basename(file);
|
|
8649
9459
|
if (base === "requirements.txt") {
|
|
8650
9460
|
const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
|
|
8651
9461
|
if (edits.length > 0) {
|
|
@@ -8670,7 +9480,7 @@ async function rollback2(installPlan, originals) {
|
|
|
8670
9480
|
const restored = [];
|
|
8671
9481
|
for (const [file, raw] of originals.entries()) {
|
|
8672
9482
|
try {
|
|
8673
|
-
await
|
|
9483
|
+
await import_node_fs29.promises.writeFile(file, raw, "utf8");
|
|
8674
9484
|
restored.push(file);
|
|
8675
9485
|
} catch {
|
|
8676
9486
|
}
|
|
@@ -8684,8 +9494,8 @@ async function rollback2(installPlan, originals) {
|
|
|
8684
9494
|
...restored.map((f) => `restored: ${f}`),
|
|
8685
9495
|
""
|
|
8686
9496
|
];
|
|
8687
|
-
const rollbackPath =
|
|
8688
|
-
await
|
|
9497
|
+
const rollbackPath = import_node_path45.default.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
9498
|
+
await import_node_fs29.promises.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
8689
9499
|
}
|
|
8690
9500
|
var pythonInstaller = {
|
|
8691
9501
|
name: "python",
|
|
@@ -8808,100 +9618,19 @@ function renderPatch(sections) {
|
|
|
8808
9618
|
|
|
8809
9619
|
// src/orchestrator.ts
|
|
8810
9620
|
init_cjs_shims();
|
|
8811
|
-
var
|
|
9621
|
+
var import_node_fs30 = require("fs");
|
|
8812
9622
|
var import_node_http = __toESM(require("http"), 1);
|
|
8813
9623
|
var import_node_net = __toESM(require("net"), 1);
|
|
8814
|
-
var
|
|
9624
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
8815
9625
|
var import_node_child_process3 = require("child_process");
|
|
8816
9626
|
var import_node_readline = __toESM(require("readline"), 1);
|
|
8817
|
-
|
|
8818
|
-
// src/installers/package-manager.ts
|
|
8819
|
-
init_cjs_shims();
|
|
8820
|
-
var import_node_fs27 = require("fs");
|
|
8821
|
-
var import_node_path42 = __toESM(require("path"), 1);
|
|
8822
|
-
var import_node_child_process2 = require("child_process");
|
|
8823
|
-
var LOCKFILE_PRIORITY = [
|
|
8824
|
-
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
8825
|
-
{ lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
|
|
8826
|
-
{ lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
|
|
8827
|
-
{
|
|
8828
|
-
lockfile: "package-lock.json",
|
|
8829
|
-
pm: "npm",
|
|
8830
|
-
args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
|
|
8831
|
-
}
|
|
8832
|
-
];
|
|
8833
|
-
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
8834
|
-
async function exists4(p) {
|
|
8835
|
-
try {
|
|
8836
|
-
await import_node_fs27.promises.access(p);
|
|
8837
|
-
return true;
|
|
8838
|
-
} catch {
|
|
8839
|
-
return false;
|
|
8840
|
-
}
|
|
8841
|
-
}
|
|
8842
|
-
async function detectPackageManager(serviceDir) {
|
|
8843
|
-
let dir = import_node_path42.default.resolve(serviceDir);
|
|
8844
|
-
const stops = /* @__PURE__ */ new Set();
|
|
8845
|
-
for (let i = 0; i < 64; i++) {
|
|
8846
|
-
if (stops.has(dir)) break;
|
|
8847
|
-
stops.add(dir);
|
|
8848
|
-
for (const candidate of LOCKFILE_PRIORITY) {
|
|
8849
|
-
const lockPath = import_node_path42.default.join(dir, candidate.lockfile);
|
|
8850
|
-
if (await exists4(lockPath)) {
|
|
8851
|
-
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
8852
|
-
}
|
|
8853
|
-
}
|
|
8854
|
-
const parent = import_node_path42.default.dirname(dir);
|
|
8855
|
-
if (parent === dir) break;
|
|
8856
|
-
dir = parent;
|
|
8857
|
-
}
|
|
8858
|
-
return { pm: "npm", cwd: import_node_path42.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
8859
|
-
}
|
|
8860
|
-
async function runPackageManagerInstall(cmd) {
|
|
8861
|
-
return new Promise((resolve) => {
|
|
8862
|
-
const child = (0, import_node_child_process2.spawn)(cmd.pm, cmd.args, {
|
|
8863
|
-
cwd: cmd.cwd,
|
|
8864
|
-
// Inherit PATH + HOME so the user's installed managers resolve.
|
|
8865
|
-
env: process.env,
|
|
8866
|
-
// `false` keeps the parent in control of cleanup if the orchestrator
|
|
8867
|
-
// exits before install finishes. Cross-platform-safe.
|
|
8868
|
-
shell: false,
|
|
8869
|
-
stdio: ["ignore", "ignore", "pipe"]
|
|
8870
|
-
});
|
|
8871
|
-
let stderr = "";
|
|
8872
|
-
child.stderr?.on("data", (chunk) => {
|
|
8873
|
-
stderr += chunk.toString("utf8");
|
|
8874
|
-
});
|
|
8875
|
-
child.on("error", (err) => {
|
|
8876
|
-
resolve({
|
|
8877
|
-
pm: cmd.pm,
|
|
8878
|
-
cwd: cmd.cwd,
|
|
8879
|
-
args: cmd.args,
|
|
8880
|
-
exitCode: 127,
|
|
8881
|
-
stderr: stderr + `
|
|
8882
|
-
${err.message}`
|
|
8883
|
-
});
|
|
8884
|
-
});
|
|
8885
|
-
child.on("close", (code) => {
|
|
8886
|
-
resolve({
|
|
8887
|
-
pm: cmd.pm,
|
|
8888
|
-
cwd: cmd.cwd,
|
|
8889
|
-
args: cmd.args,
|
|
8890
|
-
exitCode: code ?? 1,
|
|
8891
|
-
stderr: stderr.trim()
|
|
8892
|
-
});
|
|
8893
|
-
});
|
|
8894
|
-
});
|
|
8895
|
-
}
|
|
8896
|
-
|
|
8897
|
-
// src/orchestrator.ts
|
|
8898
9627
|
async function extractAndPersist(opts) {
|
|
8899
9628
|
const services = await discoverServices(opts.scanPath);
|
|
8900
9629
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
8901
9630
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
8902
9631
|
resetGraph(graphKey);
|
|
8903
9632
|
const graph = getGraph(graphKey);
|
|
8904
|
-
const projectPaths = pathsForProject(graphKey,
|
|
9633
|
+
const projectPaths = pathsForProject(graphKey, import_node_path46.default.join(opts.scanPath, "neat-out"));
|
|
8905
9634
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
8906
9635
|
errorsPath: projectPaths.errorsPath
|
|
8907
9636
|
});
|
|
@@ -8927,6 +9656,10 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
8927
9656
|
let libOnly = 0;
|
|
8928
9657
|
let browserBundle = 0;
|
|
8929
9658
|
let reactNative = 0;
|
|
9659
|
+
let bun = 0;
|
|
9660
|
+
let deno = 0;
|
|
9661
|
+
let cloudflareWorkers = 0;
|
|
9662
|
+
let electron = 0;
|
|
8930
9663
|
const installPlans = /* @__PURE__ */ new Map();
|
|
8931
9664
|
for (const svc of services) {
|
|
8932
9665
|
const installer = await pickInstaller(svc.dir);
|
|
@@ -8951,7 +9684,59 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
8951
9684
|
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
8952
9685
|
} else if (outcome.outcome === "react-native") {
|
|
8953
9686
|
reactNative++;
|
|
8954
|
-
|
|
9687
|
+
const svcName = import_node_path46.default.basename(svc.dir);
|
|
9688
|
+
console.log(
|
|
9689
|
+
`neat: ${svc.dir} detected as React Native / Expo
|
|
9690
|
+
The installer doesn't cover this runtime deterministically.
|
|
9691
|
+
Configure your OTel binding to send spans to:
|
|
9692
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
9693
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
9694
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
9695
|
+
);
|
|
9696
|
+
} else if (outcome.outcome === "bun") {
|
|
9697
|
+
bun++;
|
|
9698
|
+
const svcName = import_node_path46.default.basename(svc.dir);
|
|
9699
|
+
console.log(
|
|
9700
|
+
`neat: ${svc.dir} detected as Bun
|
|
9701
|
+
The installer doesn't cover this runtime deterministically.
|
|
9702
|
+
Configure your OTel binding to send spans to:
|
|
9703
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
9704
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
9705
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
9706
|
+
);
|
|
9707
|
+
} else if (outcome.outcome === "deno") {
|
|
9708
|
+
deno++;
|
|
9709
|
+
const svcName = import_node_path46.default.basename(svc.dir);
|
|
9710
|
+
console.log(
|
|
9711
|
+
`neat: ${svc.dir} detected as Deno
|
|
9712
|
+
The installer doesn't cover this runtime deterministically.
|
|
9713
|
+
Configure your OTel binding to send spans to:
|
|
9714
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
9715
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
9716
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
9717
|
+
);
|
|
9718
|
+
} else if (outcome.outcome === "cloudflare-workers") {
|
|
9719
|
+
cloudflareWorkers++;
|
|
9720
|
+
const svcName = import_node_path46.default.basename(svc.dir);
|
|
9721
|
+
console.log(
|
|
9722
|
+
`neat: ${svc.dir} detected as Cloudflare Workers
|
|
9723
|
+
The installer doesn't cover this runtime deterministically.
|
|
9724
|
+
Configure your OTel binding to send spans to:
|
|
9725
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
9726
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
9727
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
9728
|
+
);
|
|
9729
|
+
} else if (outcome.outcome === "electron") {
|
|
9730
|
+
electron++;
|
|
9731
|
+
const svcName = import_node_path46.default.basename(svc.dir);
|
|
9732
|
+
console.log(
|
|
9733
|
+
`neat: ${svc.dir} detected as Electron
|
|
9734
|
+
The installer doesn't cover this runtime deterministically.
|
|
9735
|
+
Configure your OTel binding to send spans to:
|
|
9736
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
9737
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
9738
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
9739
|
+
);
|
|
8955
9740
|
}
|
|
8956
9741
|
}
|
|
8957
9742
|
const packageManagerInstalls = [];
|
|
@@ -8976,6 +9761,10 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
8976
9761
|
libOnly,
|
|
8977
9762
|
browserBundle,
|
|
8978
9763
|
reactNative,
|
|
9764
|
+
bun,
|
|
9765
|
+
deno,
|
|
9766
|
+
cloudflareWorkers,
|
|
9767
|
+
electron,
|
|
8979
9768
|
packageManagerInstalls
|
|
8980
9769
|
};
|
|
8981
9770
|
}
|
|
@@ -9113,10 +9902,10 @@ function formatPortCollisionMessage(port) {
|
|
|
9113
9902
|
];
|
|
9114
9903
|
}
|
|
9115
9904
|
function spawnDaemonDetached() {
|
|
9116
|
-
const here =
|
|
9905
|
+
const here = import_node_path46.default.dirname(new URL(importMetaUrl).pathname);
|
|
9117
9906
|
const candidates = [
|
|
9118
|
-
|
|
9119
|
-
|
|
9907
|
+
import_node_path46.default.join(here, "neatd.cjs"),
|
|
9908
|
+
import_node_path46.default.join(here, "neatd.js")
|
|
9120
9909
|
];
|
|
9121
9910
|
let entry2 = null;
|
|
9122
9911
|
const fsSync = require("fs");
|
|
@@ -9174,7 +9963,7 @@ async function runOrchestrator(opts) {
|
|
|
9174
9963
|
browser: "skipped"
|
|
9175
9964
|
}
|
|
9176
9965
|
};
|
|
9177
|
-
const stat = await
|
|
9966
|
+
const stat = await import_node_fs30.promises.stat(opts.scanPath).catch(() => null);
|
|
9178
9967
|
if (!stat || !stat.isDirectory()) {
|
|
9179
9968
|
console.error(`neat: ${opts.scanPath} is not a directory`);
|
|
9180
9969
|
result.exitCode = 2;
|
|
@@ -9317,11 +10106,11 @@ function printSummary(result, graph, dashboardUrl) {
|
|
|
9317
10106
|
|
|
9318
10107
|
// src/cli-verbs.ts
|
|
9319
10108
|
init_cjs_shims();
|
|
9320
|
-
var
|
|
10109
|
+
var import_node_path47 = __toESM(require("path"), 1);
|
|
9321
10110
|
|
|
9322
10111
|
// src/cli-client.ts
|
|
9323
10112
|
init_cjs_shims();
|
|
9324
|
-
var
|
|
10113
|
+
var import_types26 = require("@neat.is/types");
|
|
9325
10114
|
var HttpError = class extends Error {
|
|
9326
10115
|
constructor(status2, message, responseBody = "") {
|
|
9327
10116
|
super(message);
|
|
@@ -9346,10 +10135,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
9346
10135
|
const root = baseUrl.replace(/\/$/, "");
|
|
9347
10136
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
9348
10137
|
return {
|
|
9349
|
-
async get(
|
|
10138
|
+
async get(path49) {
|
|
9350
10139
|
let res;
|
|
9351
10140
|
try {
|
|
9352
|
-
res = await fetch(`${root}${
|
|
10141
|
+
res = await fetch(`${root}${path49}`, {
|
|
9353
10142
|
headers: { ...authHeader }
|
|
9354
10143
|
});
|
|
9355
10144
|
} catch (err) {
|
|
@@ -9361,16 +10150,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
9361
10150
|
const body = await res.text().catch(() => "");
|
|
9362
10151
|
throw new HttpError(
|
|
9363
10152
|
res.status,
|
|
9364
|
-
`${res.status} ${res.statusText} on GET ${
|
|
10153
|
+
`${res.status} ${res.statusText} on GET ${path49}: ${body}`,
|
|
9365
10154
|
body
|
|
9366
10155
|
);
|
|
9367
10156
|
}
|
|
9368
10157
|
return await res.json();
|
|
9369
10158
|
},
|
|
9370
|
-
async post(
|
|
10159
|
+
async post(path49, body) {
|
|
9371
10160
|
let res;
|
|
9372
10161
|
try {
|
|
9373
|
-
res = await fetch(`${root}${
|
|
10162
|
+
res = await fetch(`${root}${path49}`, {
|
|
9374
10163
|
method: "POST",
|
|
9375
10164
|
headers: { "content-type": "application/json", ...authHeader },
|
|
9376
10165
|
body: JSON.stringify(body)
|
|
@@ -9384,7 +10173,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
9384
10173
|
const text = await res.text().catch(() => "");
|
|
9385
10174
|
throw new HttpError(
|
|
9386
10175
|
res.status,
|
|
9387
|
-
`${res.status} ${res.statusText} on POST ${
|
|
10176
|
+
`${res.status} ${res.statusText} on POST ${path49}: ${text}`,
|
|
9388
10177
|
text
|
|
9389
10178
|
);
|
|
9390
10179
|
}
|
|
@@ -9398,12 +10187,12 @@ function projectPath(project, suffix) {
|
|
|
9398
10187
|
}
|
|
9399
10188
|
async function runRootCause(client, input) {
|
|
9400
10189
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
9401
|
-
const
|
|
10190
|
+
const path49 = projectPath(
|
|
9402
10191
|
input.project,
|
|
9403
10192
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
9404
10193
|
);
|
|
9405
10194
|
try {
|
|
9406
|
-
const result = await client.get(
|
|
10195
|
+
const result = await client.get(path49);
|
|
9407
10196
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
9408
10197
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
9409
10198
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -9429,12 +10218,12 @@ async function runRootCause(client, input) {
|
|
|
9429
10218
|
}
|
|
9430
10219
|
async function runBlastRadius(client, input) {
|
|
9431
10220
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
9432
|
-
const
|
|
10221
|
+
const path49 = projectPath(
|
|
9433
10222
|
input.project,
|
|
9434
10223
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
9435
10224
|
);
|
|
9436
10225
|
try {
|
|
9437
|
-
const result = await client.get(
|
|
10226
|
+
const result = await client.get(path49);
|
|
9438
10227
|
if (result.totalAffected === 0) {
|
|
9439
10228
|
return {
|
|
9440
10229
|
summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
|
|
@@ -9463,17 +10252,17 @@ async function runBlastRadius(client, input) {
|
|
|
9463
10252
|
}
|
|
9464
10253
|
}
|
|
9465
10254
|
function formatBlastEntry(n) {
|
|
9466
|
-
const tag = n.edgeProvenance ===
|
|
10255
|
+
const tag = n.edgeProvenance === import_types26.Provenance.STALE ? " [STALE \u2014 last seen too long ago]" : "";
|
|
9467
10256
|
return ` \u2022 ${n.nodeId} (distance ${n.distance}, ${n.edgeProvenance})${tag}`;
|
|
9468
10257
|
}
|
|
9469
10258
|
async function runDependencies(client, input) {
|
|
9470
10259
|
const depth = input.depth ?? 3;
|
|
9471
|
-
const
|
|
10260
|
+
const path49 = projectPath(
|
|
9472
10261
|
input.project,
|
|
9473
10262
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
9474
10263
|
);
|
|
9475
10264
|
try {
|
|
9476
|
-
const result = await client.get(
|
|
10265
|
+
const result = await client.get(path49);
|
|
9477
10266
|
if (result.total === 0) {
|
|
9478
10267
|
return {
|
|
9479
10268
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -9509,9 +10298,9 @@ async function runObservedDependencies(client, input) {
|
|
|
9509
10298
|
const edges = await client.get(
|
|
9510
10299
|
projectPath(input.project, `/graph/edges/${encodeURIComponent(input.nodeId)}`)
|
|
9511
10300
|
);
|
|
9512
|
-
const observed = edges.outbound.filter((e) => e.provenance ===
|
|
10301
|
+
const observed = edges.outbound.filter((e) => e.provenance === import_types26.Provenance.OBSERVED);
|
|
9513
10302
|
if (observed.length === 0) {
|
|
9514
|
-
const hasExtracted = edges.outbound.some((e) => e.provenance ===
|
|
10303
|
+
const hasExtracted = edges.outbound.some((e) => e.provenance === import_types26.Provenance.EXTRACTED);
|
|
9515
10304
|
const note = hasExtracted ? " Static (EXTRACTED) dependencies exist but no runtime traffic has been seen \u2014 is OTel running?" : "";
|
|
9516
10305
|
return { summary: `No OBSERVED dependencies for ${input.nodeId}.${note}` };
|
|
9517
10306
|
}
|
|
@@ -9519,7 +10308,7 @@ async function runObservedDependencies(client, input) {
|
|
|
9519
10308
|
return {
|
|
9520
10309
|
summary: `${input.nodeId} has ${observed.length} runtime dependenc${observed.length === 1 ? "y" : "ies"} confirmed by OTel.`,
|
|
9521
10310
|
block: blockLines.join("\n"),
|
|
9522
|
-
provenance:
|
|
10311
|
+
provenance: import_types26.Provenance.OBSERVED
|
|
9523
10312
|
};
|
|
9524
10313
|
} catch (err) {
|
|
9525
10314
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -9554,9 +10343,9 @@ function formatDuration(ms) {
|
|
|
9554
10343
|
return `${Math.round(h / 24)}d`;
|
|
9555
10344
|
}
|
|
9556
10345
|
async function runIncidents(client, input) {
|
|
9557
|
-
const
|
|
10346
|
+
const path49 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
9558
10347
|
try {
|
|
9559
|
-
const body = await client.get(
|
|
10348
|
+
const body = await client.get(path49);
|
|
9560
10349
|
const events = body.events;
|
|
9561
10350
|
if (events.length === 0) {
|
|
9562
10351
|
return {
|
|
@@ -9573,7 +10362,7 @@ async function runIncidents(client, input) {
|
|
|
9573
10362
|
return {
|
|
9574
10363
|
summary: `${target} has ${body.total} recorded incident${body.total === 1 ? "" : "s"}; showing the ${ordered.length} most recent.`,
|
|
9575
10364
|
block: blockLines.join("\n"),
|
|
9576
|
-
provenance:
|
|
10365
|
+
provenance: import_types26.Provenance.OBSERVED
|
|
9577
10366
|
};
|
|
9578
10367
|
} catch (err) {
|
|
9579
10368
|
if (err instanceof HttpError && err.status === 404) {
|
|
@@ -9682,7 +10471,7 @@ async function runStaleEdges(client, input) {
|
|
|
9682
10471
|
return {
|
|
9683
10472
|
summary: `${events.length} stale-edge transition${events.length === 1 ? "" : "s"} recorded${input.edgeType ? ` for ${input.edgeType}` : ""}.`,
|
|
9684
10473
|
block: blockLines.join("\n"),
|
|
9685
|
-
provenance:
|
|
10474
|
+
provenance: import_types26.Provenance.STALE
|
|
9686
10475
|
};
|
|
9687
10476
|
}
|
|
9688
10477
|
async function runPolicies(client, input) {
|
|
@@ -9846,7 +10635,7 @@ async function resolveProjectEntry(opts) {
|
|
|
9846
10635
|
const cwd = opts.cwd ?? process.cwd();
|
|
9847
10636
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
9848
10637
|
for (const entry2 of entries) {
|
|
9849
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
10638
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${import_node_path47.default.sep}`)) {
|
|
9850
10639
|
return entry2;
|
|
9851
10640
|
}
|
|
9852
10641
|
}
|
|
@@ -9996,7 +10785,7 @@ async function runSync(opts) {
|
|
|
9996
10785
|
}
|
|
9997
10786
|
|
|
9998
10787
|
// src/cli.ts
|
|
9999
|
-
var
|
|
10788
|
+
var import_types27 = require("@neat.is/types");
|
|
10000
10789
|
function usage() {
|
|
10001
10790
|
console.log("usage: neat <command> [args] [--project <name>]");
|
|
10002
10791
|
console.log("");
|
|
@@ -10206,14 +10995,14 @@ function assignFlag(out, field, value) {
|
|
|
10206
10995
|
out[field] = value;
|
|
10207
10996
|
}
|
|
10208
10997
|
function readPackageVersion() {
|
|
10209
|
-
const here = typeof __dirname !== "undefined" ? __dirname :
|
|
10998
|
+
const here = typeof __dirname !== "undefined" ? __dirname : import_node_path48.default.dirname((0, import_node_url3.fileURLToPath)(importMetaUrl));
|
|
10210
10999
|
const candidates = [
|
|
10211
|
-
|
|
10212
|
-
|
|
11000
|
+
import_node_path48.default.resolve(here, "../package.json"),
|
|
11001
|
+
import_node_path48.default.resolve(here, "../../package.json")
|
|
10213
11002
|
];
|
|
10214
11003
|
for (const candidate of candidates) {
|
|
10215
11004
|
try {
|
|
10216
|
-
const raw = (0,
|
|
11005
|
+
const raw = (0, import_node_fs31.readFileSync)(candidate, "utf8");
|
|
10217
11006
|
const parsed = JSON.parse(raw);
|
|
10218
11007
|
if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
|
|
10219
11008
|
return parsed.version;
|
|
@@ -10277,7 +11066,7 @@ async function buildPatchSections(services, project) {
|
|
|
10277
11066
|
}
|
|
10278
11067
|
async function runInit(opts) {
|
|
10279
11068
|
const written = [];
|
|
10280
|
-
const stat = await
|
|
11069
|
+
const stat = await import_node_fs31.promises.stat(opts.scanPath).catch(() => null);
|
|
10281
11070
|
if (!stat || !stat.isDirectory()) {
|
|
10282
11071
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
10283
11072
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -10286,13 +11075,13 @@ async function runInit(opts) {
|
|
|
10286
11075
|
printDiscoveryReport(opts, services);
|
|
10287
11076
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
10288
11077
|
const patch = renderPatch(sections);
|
|
10289
|
-
const patchPath =
|
|
11078
|
+
const patchPath = import_node_path48.default.join(opts.scanPath, "neat.patch");
|
|
10290
11079
|
if (opts.dryRun) {
|
|
10291
|
-
await
|
|
11080
|
+
await import_node_fs31.promises.writeFile(patchPath, patch, "utf8");
|
|
10292
11081
|
written.push(patchPath);
|
|
10293
11082
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
10294
|
-
const gitignorePath =
|
|
10295
|
-
const gitignoreExists = await
|
|
11083
|
+
const gitignorePath = import_node_path48.default.join(opts.scanPath, ".gitignore");
|
|
11084
|
+
const gitignoreExists = await import_node_fs31.promises.stat(gitignorePath).then(() => true).catch(() => false);
|
|
10296
11085
|
const verb = gitignoreExists ? "append" : "create";
|
|
10297
11086
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
10298
11087
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -10303,9 +11092,9 @@ async function runInit(opts) {
|
|
|
10303
11092
|
const graph = getGraph(graphKey);
|
|
10304
11093
|
const projectPaths = pathsForProject(
|
|
10305
11094
|
graphKey,
|
|
10306
|
-
|
|
11095
|
+
import_node_path48.default.join(opts.scanPath, "neat-out")
|
|
10307
11096
|
);
|
|
10308
|
-
const errorsPath =
|
|
11097
|
+
const errorsPath = import_node_path48.default.join(import_node_path48.default.dirname(opts.outPath), import_node_path48.default.basename(projectPaths.errorsPath));
|
|
10309
11098
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
10310
11099
|
await saveGraphToDisk(graph, opts.outPath);
|
|
10311
11100
|
written.push(opts.outPath);
|
|
@@ -10384,7 +11173,7 @@ async function runInit(opts) {
|
|
|
10384
11173
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
10385
11174
|
}
|
|
10386
11175
|
} else {
|
|
10387
|
-
await
|
|
11176
|
+
await import_node_fs31.promises.writeFile(patchPath, patch, "utf8");
|
|
10388
11177
|
written.push(patchPath);
|
|
10389
11178
|
}
|
|
10390
11179
|
}
|
|
@@ -10424,9 +11213,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
10424
11213
|
};
|
|
10425
11214
|
function claudeConfigPath() {
|
|
10426
11215
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
10427
|
-
if (override && override.length > 0) return
|
|
11216
|
+
if (override && override.length > 0) return import_node_path48.default.resolve(override);
|
|
10428
11217
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
10429
|
-
return
|
|
11218
|
+
return import_node_path48.default.join(home, ".claude.json");
|
|
10430
11219
|
}
|
|
10431
11220
|
async function runSkill(opts) {
|
|
10432
11221
|
const snippet2 = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -10438,7 +11227,7 @@ async function runSkill(opts) {
|
|
|
10438
11227
|
const target = claudeConfigPath();
|
|
10439
11228
|
let existing = {};
|
|
10440
11229
|
try {
|
|
10441
|
-
existing = JSON.parse(await
|
|
11230
|
+
existing = JSON.parse(await import_node_fs31.promises.readFile(target, "utf8"));
|
|
10442
11231
|
} catch (err) {
|
|
10443
11232
|
if (err.code !== "ENOENT") {
|
|
10444
11233
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -10450,8 +11239,8 @@ async function runSkill(opts) {
|
|
|
10450
11239
|
...existing,
|
|
10451
11240
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
10452
11241
|
};
|
|
10453
|
-
await
|
|
10454
|
-
await
|
|
11242
|
+
await import_node_fs31.promises.mkdir(import_node_path48.default.dirname(target), { recursive: true });
|
|
11243
|
+
await import_node_fs31.promises.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
10455
11244
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
10456
11245
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
10457
11246
|
return { exitCode: 0 };
|
|
@@ -10489,12 +11278,12 @@ async function main() {
|
|
|
10489
11278
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
10490
11279
|
process.exit(2);
|
|
10491
11280
|
}
|
|
10492
|
-
const scanPath =
|
|
11281
|
+
const scanPath = import_node_path48.default.resolve(target);
|
|
10493
11282
|
const projectExplicit = parsed.project !== null;
|
|
10494
|
-
const projectName = projectExplicit ? project :
|
|
11283
|
+
const projectName = projectExplicit ? project : import_node_path48.default.basename(scanPath);
|
|
10495
11284
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
10496
|
-
const fallback = pathsForProject(projectKey,
|
|
10497
|
-
const outPath =
|
|
11285
|
+
const fallback = pathsForProject(projectKey, import_node_path48.default.join(scanPath, "neat-out")).snapshotPath;
|
|
11286
|
+
const outPath = import_node_path48.default.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
10498
11287
|
const result = await runInit({
|
|
10499
11288
|
scanPath,
|
|
10500
11289
|
outPath,
|
|
@@ -10515,21 +11304,21 @@ async function main() {
|
|
|
10515
11304
|
usage();
|
|
10516
11305
|
process.exit(2);
|
|
10517
11306
|
}
|
|
10518
|
-
const scanPath =
|
|
10519
|
-
const stat = await
|
|
11307
|
+
const scanPath = import_node_path48.default.resolve(target);
|
|
11308
|
+
const stat = await import_node_fs31.promises.stat(scanPath).catch(() => null);
|
|
10520
11309
|
if (!stat || !stat.isDirectory()) {
|
|
10521
11310
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
10522
11311
|
process.exit(2);
|
|
10523
11312
|
}
|
|
10524
|
-
const projectPaths = pathsForProject(project,
|
|
10525
|
-
const outPath =
|
|
10526
|
-
const errorsPath =
|
|
10527
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
11313
|
+
const projectPaths = pathsForProject(project, import_node_path48.default.join(scanPath, "neat-out"));
|
|
11314
|
+
const outPath = import_node_path48.default.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
11315
|
+
const errorsPath = import_node_path48.default.resolve(
|
|
11316
|
+
process.env.NEAT_ERRORS_PATH ?? import_node_path48.default.join(import_node_path48.default.dirname(outPath), import_node_path48.default.basename(projectPaths.errorsPath))
|
|
10528
11317
|
);
|
|
10529
|
-
const staleEventsPath =
|
|
10530
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
11318
|
+
const staleEventsPath = import_node_path48.default.resolve(
|
|
11319
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? import_node_path48.default.join(import_node_path48.default.dirname(outPath), import_node_path48.default.basename(projectPaths.staleEventsPath))
|
|
10531
11320
|
);
|
|
10532
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
11321
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? import_node_path48.default.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
10533
11322
|
const handle = await startWatch(getGraph(project), {
|
|
10534
11323
|
scanPath,
|
|
10535
11324
|
outPath,
|
|
@@ -10675,11 +11464,11 @@ async function main() {
|
|
|
10675
11464
|
process.exit(1);
|
|
10676
11465
|
}
|
|
10677
11466
|
async function tryOrchestrator(cmd, parsed) {
|
|
10678
|
-
const scanPath =
|
|
10679
|
-
const stat = await
|
|
11467
|
+
const scanPath = import_node_path48.default.resolve(cmd);
|
|
11468
|
+
const stat = await import_node_fs31.promises.stat(scanPath).catch(() => null);
|
|
10680
11469
|
if (!stat || !stat.isDirectory()) return null;
|
|
10681
11470
|
const projectExplicit = parsed.project !== null;
|
|
10682
|
-
const projectName = projectExplicit ? parsed.project :
|
|
11471
|
+
const projectName = projectExplicit ? parsed.project : import_node_path48.default.basename(scanPath);
|
|
10683
11472
|
const result = await runOrchestrator({
|
|
10684
11473
|
scanPath,
|
|
10685
11474
|
project: projectName,
|
|
@@ -10829,10 +11618,10 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
10829
11618
|
const parts = parsed.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
10830
11619
|
const out = [];
|
|
10831
11620
|
for (const p of parts) {
|
|
10832
|
-
const r =
|
|
11621
|
+
const r = import_types27.DivergenceTypeSchema.safeParse(p);
|
|
10833
11622
|
if (!r.success) {
|
|
10834
11623
|
console.error(
|
|
10835
|
-
`neat divergences: unknown --type "${p}". allowed: ${
|
|
11624
|
+
`neat divergences: unknown --type "${p}". allowed: ${import_types27.DivergenceTypeSchema.options.join(", ")}`
|
|
10836
11625
|
);
|
|
10837
11626
|
return 2;
|
|
10838
11627
|
}
|