@neat.is/core 0.6.1 → 0.6.2-dev.20260723
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-BZ3AJVAC.js → chunk-2MAGU6RB.js} +11 -2
- package/dist/chunk-2MAGU6RB.js.map +1 -0
- package/dist/{chunk-3YUNROLT.js → chunk-64JJOXES.js} +3 -3
- package/dist/{chunk-MVINCLQM.js → chunk-OY7FGJAX.js} +2 -2
- package/dist/{chunk-7C6ZULNU.js → chunk-ZZ3VUCWL.js} +1492 -1346
- package/dist/chunk-ZZ3VUCWL.js.map +1 -0
- package/dist/cli.cjs +783 -468
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +170 -11
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +466 -310
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +475 -319
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-XS45HBET.js → otel-grpc-AHNPM5LX.js} +3 -3
- package/dist/server.cjs +2400 -2240
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-7C6ZULNU.js.map +0 -1
- package/dist/chunk-BZ3AJVAC.js.map +0 -1
- /package/dist/{chunk-3YUNROLT.js.map → chunk-64JJOXES.js.map} +0 -0
- /package/dist/{chunk-MVINCLQM.js.map → chunk-OY7FGJAX.js.map} +0 -0
- /package/dist/{otel-grpc-XS45HBET.js.map → otel-grpc-AHNPM5LX.js.map} +0 -0
package/dist/neatd.cjs
CHANGED
|
@@ -61,8 +61,8 @@ function mountBearerAuth(app, opts) {
|
|
|
61
61
|
]);
|
|
62
62
|
const publicRead = opts.publicRead === true;
|
|
63
63
|
app.addHook("preHandler", (req2, reply, done) => {
|
|
64
|
-
const
|
|
65
|
-
if (exactUnauthPaths.has(
|
|
64
|
+
const path55 = (req2.url.split("?")[0] ?? "").replace(/\/+$/, "");
|
|
65
|
+
if (exactUnauthPaths.has(path55) || PROJECT_SCOPED_UNAUTH_PATTERN.test(path55)) {
|
|
66
66
|
done();
|
|
67
67
|
return;
|
|
68
68
|
}
|
|
@@ -194,8 +194,8 @@ function reshapeGrpcRequest(req2) {
|
|
|
194
194
|
};
|
|
195
195
|
}
|
|
196
196
|
function resolveProtoRoot() {
|
|
197
|
-
const here =
|
|
198
|
-
return
|
|
197
|
+
const here = import_node_path49.default.dirname((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
198
|
+
return import_node_path49.default.resolve(here, "..", "proto");
|
|
199
199
|
}
|
|
200
200
|
function loadTraceService() {
|
|
201
201
|
const protoRoot = resolveProtoRoot();
|
|
@@ -263,13 +263,13 @@ async function startOtelGrpcReceiver(opts) {
|
|
|
263
263
|
})
|
|
264
264
|
};
|
|
265
265
|
}
|
|
266
|
-
var import_node_url,
|
|
266
|
+
var import_node_url, import_node_path49, import_node_crypto2, grpc, protoLoader;
|
|
267
267
|
var init_otel_grpc = __esm({
|
|
268
268
|
"src/otel-grpc.ts"() {
|
|
269
269
|
"use strict";
|
|
270
270
|
init_cjs_shims();
|
|
271
271
|
import_node_url = require("url");
|
|
272
|
-
|
|
272
|
+
import_node_path49 = __toESM(require("path"), 1);
|
|
273
273
|
import_node_crypto2 = require("crypto");
|
|
274
274
|
grpc = __toESM(require("@grpc/grpc-js"), 1);
|
|
275
275
|
protoLoader = __toESM(require("@grpc/proto-loader"), 1);
|
|
@@ -347,6 +347,14 @@ function normalizeDbSystem(attrs) {
|
|
|
347
347
|
if (typeof raw !== "string") return void 0;
|
|
348
348
|
return raw === "mongoose" ? "mongodb" : raw;
|
|
349
349
|
}
|
|
350
|
+
function tableFromSqlStatement(sql) {
|
|
351
|
+
if (typeof sql !== "string" || sql.length === 0) return null;
|
|
352
|
+
if (/\bjoin\b/i.test(sql)) return null;
|
|
353
|
+
const froms = sql.match(/\bfrom\b/gi);
|
|
354
|
+
if (froms && froms.length > 1) return null;
|
|
355
|
+
const m = /\b(?:from|into|update)\s+(?:"?[\w$]+"?\s*\.\s*)?"?([a-zA-Z_][\w$]*)"?/i.exec(sql);
|
|
356
|
+
return m ? m[1] : null;
|
|
357
|
+
}
|
|
350
358
|
function messagingDestinationOf(attrs) {
|
|
351
359
|
for (const key of ["messaging.destination.name", "messaging.destination"]) {
|
|
352
360
|
const v = attrs[key];
|
|
@@ -367,8 +375,8 @@ function websocketChannelPathOf(attrs) {
|
|
|
367
375
|
const v = attrs[key];
|
|
368
376
|
if (typeof v === "string" && v.length > 0) {
|
|
369
377
|
const q = v.indexOf("?");
|
|
370
|
-
const
|
|
371
|
-
if (
|
|
378
|
+
const path55 = q === -1 ? v : v.slice(0, q);
|
|
379
|
+
if (path55.length > 0) return path55;
|
|
372
380
|
}
|
|
373
381
|
}
|
|
374
382
|
return void 0;
|
|
@@ -404,6 +412,7 @@ function parseOtlpRequest(body) {
|
|
|
404
412
|
dbSystem: normalizeDbSystem(attrs),
|
|
405
413
|
dbName: typeof attrs["db.name"] === "string" ? attrs["db.name"] : void 0,
|
|
406
414
|
dbCollection: typeof attrs["db.collection.name"] === "string" ? attrs["db.collection.name"] : typeof attrs["db.mongodb.collection"] === "string" ? attrs["db.mongodb.collection"] : void 0,
|
|
415
|
+
dbTable: typeof attrs["db.statement"] === "string" ? tableFromSqlStatement(attrs["db.statement"]) ?? void 0 : void 0,
|
|
407
416
|
messagingSystem: typeof attrs["messaging.system"] === "string" ? attrs["messaging.system"] : void 0,
|
|
408
417
|
messagingDestination: messagingDestinationOf(attrs),
|
|
409
418
|
graphqlOperationName: typeof attrs["graphql.operation.name"] === "string" && attrs["graphql.operation.name"].length > 0 ? attrs["graphql.operation.name"] : void 0,
|
|
@@ -423,10 +432,10 @@ function parseOtlpRequest(body) {
|
|
|
423
432
|
return out;
|
|
424
433
|
}
|
|
425
434
|
function loadProtoRoot() {
|
|
426
|
-
const here =
|
|
427
|
-
const protoRoot =
|
|
435
|
+
const here = import_node_path50.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
436
|
+
const protoRoot = import_node_path50.default.resolve(here, "..", "proto");
|
|
428
437
|
const root = new import_protobufjs.default.Root();
|
|
429
|
-
root.resolvePath = (_origin, target) =>
|
|
438
|
+
root.resolvePath = (_origin, target) => import_node_path50.default.resolve(protoRoot, target);
|
|
430
439
|
root.loadSync(
|
|
431
440
|
"opentelemetry/proto/collector/trace/v1/trace_service.proto",
|
|
432
441
|
{ keepCase: true }
|
|
@@ -654,12 +663,12 @@ async function listenSteppingOtlp(app, requestedPort, host) {
|
|
|
654
663
|
}
|
|
655
664
|
}
|
|
656
665
|
}
|
|
657
|
-
var
|
|
666
|
+
var import_node_path50, import_node_url2, import_fastify2, import_protobufjs, ENV_ATTR_CANONICAL, ENV_ATTR_COMPAT, ENV_FALLBACK, exportTraceServiceRequestType, exportTraceServiceResponseType, cachedProtobufResponseBody, OTLP_STEP_ATTEMPTS, OTLP_STEP_STRIDE;
|
|
658
667
|
var init_otel = __esm({
|
|
659
668
|
"src/otel.ts"() {
|
|
660
669
|
"use strict";
|
|
661
670
|
init_cjs_shims();
|
|
662
|
-
|
|
671
|
+
import_node_path50 = __toESM(require("path"), 1);
|
|
663
672
|
import_node_url2 = require("url");
|
|
664
673
|
import_fastify2 = __toESM(require("fastify"), 1);
|
|
665
674
|
import_protobufjs = __toESM(require("protobufjs"), 1);
|
|
@@ -683,13 +692,13 @@ __export(neatd_exports, {
|
|
|
683
692
|
module.exports = __toCommonJS(neatd_exports);
|
|
684
693
|
init_cjs_shims();
|
|
685
694
|
var import_node_fs33 = require("fs");
|
|
686
|
-
var
|
|
695
|
+
var import_node_path54 = __toESM(require("path"), 1);
|
|
687
696
|
var import_node_module2 = require("module");
|
|
688
697
|
|
|
689
698
|
// src/daemon.ts
|
|
690
699
|
init_cjs_shims();
|
|
691
700
|
var import_node_fs31 = require("fs");
|
|
692
|
-
var
|
|
701
|
+
var import_node_path52 = __toESM(require("path"), 1);
|
|
693
702
|
var import_node_module = require("module");
|
|
694
703
|
|
|
695
704
|
// src/graph.ts
|
|
@@ -1219,19 +1228,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
1219
1228
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
1220
1229
|
let best = { path: [start], edges: [] };
|
|
1221
1230
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
1222
|
-
function step(node,
|
|
1223
|
-
if (
|
|
1224
|
-
best = { path: [...
|
|
1231
|
+
function step(node, path55, edges) {
|
|
1232
|
+
if (path55.length > best.path.length) {
|
|
1233
|
+
best = { path: [...path55], edges: [...edges] };
|
|
1225
1234
|
}
|
|
1226
|
-
if (
|
|
1235
|
+
if (path55.length - 1 >= maxDepth) return;
|
|
1227
1236
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
1228
1237
|
for (const [srcId, edge] of incoming) {
|
|
1229
1238
|
if (visited.has(srcId)) continue;
|
|
1230
1239
|
visited.add(srcId);
|
|
1231
|
-
|
|
1240
|
+
path55.push(srcId);
|
|
1232
1241
|
edges.push(edge);
|
|
1233
|
-
step(srcId,
|
|
1234
|
-
|
|
1242
|
+
step(srcId, path55, edges);
|
|
1243
|
+
path55.pop();
|
|
1235
1244
|
edges.pop();
|
|
1236
1245
|
visited.delete(srcId);
|
|
1237
1246
|
}
|
|
@@ -1239,11 +1248,11 @@ function longestIncomingWalk(graph, start, maxDepth) {
|
|
|
1239
1248
|
step(start, [start], []);
|
|
1240
1249
|
return best;
|
|
1241
1250
|
}
|
|
1242
|
-
function databaseRootCauseShape(graph, origin,
|
|
1251
|
+
function databaseRootCauseShape(graph, origin, walk4) {
|
|
1243
1252
|
const targetDb = origin;
|
|
1244
1253
|
const candidatePairs = compatPairs().filter((p) => p.engine === targetDb.engine);
|
|
1245
1254
|
if (candidatePairs.length === 0) return null;
|
|
1246
|
-
for (const id of
|
|
1255
|
+
for (const id of walk4.path) {
|
|
1247
1256
|
const owner = resolveOwningService(graph, id);
|
|
1248
1257
|
if (!owner) continue;
|
|
1249
1258
|
const { id: serviceId6, svc } = owner;
|
|
@@ -1270,8 +1279,8 @@ function databaseRootCauseShape(graph, origin, walk3) {
|
|
|
1270
1279
|
}
|
|
1271
1280
|
return null;
|
|
1272
1281
|
}
|
|
1273
|
-
function serviceRootCauseShape(graph, _origin,
|
|
1274
|
-
for (const id of
|
|
1282
|
+
function serviceRootCauseShape(graph, _origin, walk4) {
|
|
1283
|
+
for (const id of walk4.path) {
|
|
1275
1284
|
const owner = resolveOwningService(graph, id);
|
|
1276
1285
|
if (!owner) continue;
|
|
1277
1286
|
const { id: serviceId6, svc } = owner;
|
|
@@ -1307,10 +1316,10 @@ function serviceRootCauseShape(graph, _origin, walk3) {
|
|
|
1307
1316
|
}
|
|
1308
1317
|
return null;
|
|
1309
1318
|
}
|
|
1310
|
-
function fileRootCauseShape(graph, origin,
|
|
1319
|
+
function fileRootCauseShape(graph, origin, walk4) {
|
|
1311
1320
|
const owner = resolveOwningService(graph, origin.id);
|
|
1312
1321
|
if (!owner) return null;
|
|
1313
|
-
return serviceRootCauseShape(graph, owner.svc,
|
|
1322
|
+
return serviceRootCauseShape(graph, owner.svc, walk4);
|
|
1314
1323
|
}
|
|
1315
1324
|
var rootCauseShapes = {
|
|
1316
1325
|
[import_types.NodeType.DatabaseNode]: databaseRootCauseShape,
|
|
@@ -1322,16 +1331,16 @@ function getRootCause(graph, errorNodeId, errorEvent, incidents) {
|
|
|
1322
1331
|
const origin = graph.getNodeAttributes(errorNodeId);
|
|
1323
1332
|
const shape = rootCauseShapes[origin.type];
|
|
1324
1333
|
if (shape) {
|
|
1325
|
-
const
|
|
1326
|
-
const match = shape(graph, origin,
|
|
1334
|
+
const walk4 = longestIncomingWalk(graph, errorNodeId, ROOT_CAUSE_MAX_DEPTH);
|
|
1335
|
+
const match = shape(graph, origin, walk4);
|
|
1327
1336
|
if (match) {
|
|
1328
1337
|
const reason = errorEvent ? `${match.rootCauseReason} (observed error: ${errorEvent.errorMessage})` : match.rootCauseReason;
|
|
1329
1338
|
return import_types.RootCauseResultSchema.parse({
|
|
1330
1339
|
rootCauseNode: match.rootCauseNode,
|
|
1331
1340
|
rootCauseReason: reason,
|
|
1332
|
-
traversalPath:
|
|
1333
|
-
edgeProvenances:
|
|
1334
|
-
confidence: confidenceFromMix(
|
|
1341
|
+
traversalPath: walk4.path,
|
|
1342
|
+
edgeProvenances: walk4.edges.map((e) => e.provenance),
|
|
1343
|
+
confidence: confidenceFromMix(walk4.edges),
|
|
1335
1344
|
fixRecommendation: match.fixRecommendation
|
|
1336
1345
|
});
|
|
1337
1346
|
}
|
|
@@ -1352,8 +1361,8 @@ function localizeFromIncidents(nodeId, incidents, errorEvent) {
|
|
|
1352
1361
|
if (relevant.length === 0) return null;
|
|
1353
1362
|
const latest = [...relevant].sort((a, b) => b.timestamp.localeCompare(a.timestamp))[0];
|
|
1354
1363
|
const attrs = latest.attributes ?? {};
|
|
1355
|
-
const filepath =
|
|
1356
|
-
const lineno =
|
|
1364
|
+
const filepath = codeFilepathOf(attrs);
|
|
1365
|
+
const lineno = codeLinenoOf(attrs);
|
|
1357
1366
|
const route = typeof attrs["http.route"] === "string" ? attrs["http.route"] : void 0;
|
|
1358
1367
|
const location = filepath ? `${filepath}${lineno !== void 0 ? `:${lineno}` : ""}` : void 0;
|
|
1359
1368
|
const sameMode = relevant.filter((ev) => ev.errorMessage === latest.errorMessage);
|
|
@@ -1425,26 +1434,26 @@ function dominantFailingCall(graph, serviceId6, visited) {
|
|
|
1425
1434
|
return best;
|
|
1426
1435
|
}
|
|
1427
1436
|
function followFailingCallChain(graph, originServiceId, maxDepth) {
|
|
1428
|
-
const
|
|
1437
|
+
const path55 = [originServiceId];
|
|
1429
1438
|
const edges = [];
|
|
1430
1439
|
const visited = /* @__PURE__ */ new Set([originServiceId]);
|
|
1431
1440
|
let current = originServiceId;
|
|
1432
1441
|
for (let depth = 0; depth < maxDepth; depth++) {
|
|
1433
1442
|
const hop = dominantFailingCall(graph, current, visited);
|
|
1434
1443
|
if (!hop) break;
|
|
1435
|
-
|
|
1444
|
+
path55.push(hop.nextService);
|
|
1436
1445
|
edges.push(hop.edge);
|
|
1437
1446
|
visited.add(hop.nextService);
|
|
1438
1447
|
current = hop.nextService;
|
|
1439
1448
|
}
|
|
1440
1449
|
if (edges.length === 0) return null;
|
|
1441
|
-
return { path:
|
|
1450
|
+
return { path: path55, edges, culprit: current };
|
|
1442
1451
|
}
|
|
1443
1452
|
function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
1444
1453
|
const chain = followFailingCallChain(graph, originId, ROOT_CAUSE_MAX_DEPTH);
|
|
1445
1454
|
if (!chain) return null;
|
|
1446
1455
|
const culprit = chain.culprit;
|
|
1447
|
-
const
|
|
1456
|
+
const path55 = [...chain.path];
|
|
1448
1457
|
const edgeProvenances = chain.edges.map((e) => e.provenance);
|
|
1449
1458
|
const baseConfidence = confidenceFromMix(chain.edges);
|
|
1450
1459
|
const confidence = Math.max(0, Math.min(1, baseConfidence * INCIDENT_ROOT_CAUSE_CONFIDENCE));
|
|
@@ -1452,14 +1461,14 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1452
1461
|
if (loc) {
|
|
1453
1462
|
let rootCauseNode = culprit;
|
|
1454
1463
|
if (loc.fileNode) {
|
|
1455
|
-
|
|
1464
|
+
path55.push(loc.fileNode);
|
|
1456
1465
|
edgeProvenances.push(import_types.Provenance.OBSERVED);
|
|
1457
1466
|
rootCauseNode = loc.fileNode;
|
|
1458
1467
|
}
|
|
1459
1468
|
return import_types.RootCauseResultSchema.parse({
|
|
1460
1469
|
rootCauseNode,
|
|
1461
1470
|
rootCauseReason: loc.rootCauseReason,
|
|
1462
|
-
traversalPath:
|
|
1471
|
+
traversalPath: path55,
|
|
1463
1472
|
edgeProvenances,
|
|
1464
1473
|
confidence,
|
|
1465
1474
|
...loc.fixRecommendation ? { fixRecommendation: loc.fixRecommendation } : {}
|
|
@@ -1471,7 +1480,7 @@ function crossServiceRootCause(graph, originId, incidents, errorEvent) {
|
|
|
1471
1480
|
return import_types.RootCauseResultSchema.parse({
|
|
1472
1481
|
rootCauseNode: culprit,
|
|
1473
1482
|
rootCauseReason: `${culpritName} is failing downstream calls (${errs} observed error${errs === 1 ? "" : "s"})`,
|
|
1474
|
-
traversalPath:
|
|
1483
|
+
traversalPath: path55,
|
|
1475
1484
|
edgeProvenances,
|
|
1476
1485
|
confidence,
|
|
1477
1486
|
fixRecommendation: `Inspect ${culpritName}'s failing handler`
|
|
@@ -2229,9 +2238,30 @@ function isLoopbackHost(host) {
|
|
|
2229
2238
|
const h = host.toLowerCase();
|
|
2230
2239
|
return h === "localhost" || h === "ip6-localhost" || h === "::1" || h === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(h);
|
|
2231
2240
|
}
|
|
2241
|
+
var CODE_FILE_PATH_ATTR = "code.file.path";
|
|
2232
2242
|
var CODE_FILEPATH_ATTR = "code.filepath";
|
|
2243
|
+
var CODE_LINE_NUMBER_ATTR = "code.line.number";
|
|
2233
2244
|
var CODE_LINENO_ATTR = "code.lineno";
|
|
2245
|
+
var CODE_FUNCTION_NAME_ATTR = "code.function.name";
|
|
2234
2246
|
var CODE_FUNCTION_ATTR = "code.function";
|
|
2247
|
+
function codeFilepathOf(attrs) {
|
|
2248
|
+
const stable = attrs[CODE_FILE_PATH_ATTR];
|
|
2249
|
+
if (typeof stable === "string" && stable.length > 0) return stable;
|
|
2250
|
+
const prior = attrs[CODE_FILEPATH_ATTR];
|
|
2251
|
+
return typeof prior === "string" && prior.length > 0 ? prior : void 0;
|
|
2252
|
+
}
|
|
2253
|
+
function codeLinenoOf(attrs) {
|
|
2254
|
+
const stable = attrs[CODE_LINE_NUMBER_ATTR];
|
|
2255
|
+
if (typeof stable === "number" && Number.isFinite(stable)) return stable;
|
|
2256
|
+
const prior = attrs[CODE_LINENO_ATTR];
|
|
2257
|
+
return typeof prior === "number" && Number.isFinite(prior) ? prior : void 0;
|
|
2258
|
+
}
|
|
2259
|
+
function codeFunctionOf(attrs) {
|
|
2260
|
+
const stable = attrs[CODE_FUNCTION_NAME_ATTR];
|
|
2261
|
+
if (typeof stable === "string" && stable.length > 0) return stable;
|
|
2262
|
+
const prior = attrs[CODE_FUNCTION_ATTR];
|
|
2263
|
+
return typeof prior === "string" && prior.length > 0 ? prior : void 0;
|
|
2264
|
+
}
|
|
2235
2265
|
function toPosix(p) {
|
|
2236
2266
|
return p.split("\\").join("/");
|
|
2237
2267
|
}
|
|
@@ -2309,10 +2339,9 @@ function resolveDistToSrc(absFilepath, line) {
|
|
|
2309
2339
|
}
|
|
2310
2340
|
}
|
|
2311
2341
|
function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
2312
|
-
const filepath = span.attributes
|
|
2313
|
-
if (
|
|
2314
|
-
|
|
2315
|
-
let line = typeof linenoRaw === "number" && Number.isFinite(linenoRaw) ? linenoRaw : void 0;
|
|
2342
|
+
const filepath = codeFilepathOf(span.attributes);
|
|
2343
|
+
if (filepath === void 0) return null;
|
|
2344
|
+
let line = codeLinenoOf(span.attributes);
|
|
2316
2345
|
const abs = toPosix(filepath).replace(/^file:\/\//, "");
|
|
2317
2346
|
const resolved = resolveDistToSrc(abs, line);
|
|
2318
2347
|
let effectivePath = filepath;
|
|
@@ -2327,8 +2356,7 @@ function callSiteFromSpan(span, serviceNode, scanPath) {
|
|
|
2327
2356
|
if (!resolved && abs.endsWith(".js") && relPath.startsWith("dist/") && serviceNode?.name) {
|
|
2328
2357
|
warnNoSourceMaps(serviceNode.name);
|
|
2329
2358
|
}
|
|
2330
|
-
const
|
|
2331
|
-
const fn = typeof fnRaw === "string" && fnRaw.length > 0 ? fnRaw : void 0;
|
|
2359
|
+
const fn = codeFunctionOf(span.attributes);
|
|
2332
2360
|
return {
|
|
2333
2361
|
relPath,
|
|
2334
2362
|
...line !== void 0 ? { line } : {},
|
|
@@ -2910,6 +2938,18 @@ async function handleSpan(ctx, span) {
|
|
|
2910
2938
|
callSiteEvidence
|
|
2911
2939
|
);
|
|
2912
2940
|
}
|
|
2941
|
+
if (span.dbTable) {
|
|
2942
|
+
const tableId = ensureInfraNode(ctx.graph, "sql-table", span.dbTable, "self");
|
|
2943
|
+
upsertObservedEdge(
|
|
2944
|
+
ctx.graph,
|
|
2945
|
+
import_types3.EdgeType.CALLS,
|
|
2946
|
+
observedSource(),
|
|
2947
|
+
tableId,
|
|
2948
|
+
ts,
|
|
2949
|
+
isError,
|
|
2950
|
+
callSiteEvidence
|
|
2951
|
+
);
|
|
2952
|
+
}
|
|
2913
2953
|
}
|
|
2914
2954
|
} else if (span.messagingSystem && span.messagingDestination && spanMintsMessagingEdge(span.kind)) {
|
|
2915
2955
|
const targetId = ensureMessagingDestinationNode(
|
|
@@ -4074,14 +4114,14 @@ function buildServiceHostIndex(services) {
|
|
|
4074
4114
|
}
|
|
4075
4115
|
async function walkSourceFiles(dir) {
|
|
4076
4116
|
const out = [];
|
|
4077
|
-
async function
|
|
4117
|
+
async function walk4(current) {
|
|
4078
4118
|
const entries = await import_node_fs10.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
4079
4119
|
for (const entry2 of entries) {
|
|
4080
4120
|
const full = import_node_path10.default.join(current, entry2.name);
|
|
4081
4121
|
if (entry2.isDirectory()) {
|
|
4082
4122
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
4083
4123
|
if (await isPythonVenvDir(full)) continue;
|
|
4084
|
-
await
|
|
4124
|
+
await walk4(full);
|
|
4085
4125
|
} else if (entry2.isFile() && SERVICE_FILE_EXTENSIONS.has(import_node_path10.default.extname(entry2.name)) && // Skip NEAT's own generated `otel-init.*` bootstrap — extracting it
|
|
4086
4126
|
// would attribute our instrumentation imports to the user's service.
|
|
4087
4127
|
!isNeatAuthoredSourceFile(entry2.name)) {
|
|
@@ -4089,7 +4129,7 @@ async function walkSourceFiles(dir) {
|
|
|
4089
4129
|
}
|
|
4090
4130
|
}
|
|
4091
4131
|
}
|
|
4092
|
-
await
|
|
4132
|
+
await walk4(dir);
|
|
4093
4133
|
return out;
|
|
4094
4134
|
}
|
|
4095
4135
|
async function loadSourceFiles(dir) {
|
|
@@ -5239,20 +5279,20 @@ var import_node_path21 = __toESM(require("path"), 1);
|
|
|
5239
5279
|
var import_types10 = require("@neat.is/types");
|
|
5240
5280
|
async function walkConfigFiles(dir) {
|
|
5241
5281
|
const out = [];
|
|
5242
|
-
async function
|
|
5282
|
+
async function walk4(current) {
|
|
5243
5283
|
const entries = await import_node_fs14.promises.readdir(current, { withFileTypes: true });
|
|
5244
5284
|
for (const entry2 of entries) {
|
|
5245
5285
|
const full = import_node_path21.default.join(current, entry2.name);
|
|
5246
5286
|
if (entry2.isDirectory()) {
|
|
5247
5287
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
5248
5288
|
if (await isPythonVenvDir(full)) continue;
|
|
5249
|
-
await
|
|
5289
|
+
await walk4(full);
|
|
5250
5290
|
} else if (entry2.isFile() && isConfigFile(entry2.name).match) {
|
|
5251
5291
|
out.push(full);
|
|
5252
5292
|
}
|
|
5253
5293
|
}
|
|
5254
5294
|
}
|
|
5255
|
-
await
|
|
5295
|
+
await walk4(dir);
|
|
5256
5296
|
return out;
|
|
5257
5297
|
}
|
|
5258
5298
|
async function addConfigNodes(graph, services, scanPath) {
|
|
@@ -5779,20 +5819,20 @@ function grpcMethodsFromProto(content, fqPackage) {
|
|
|
5779
5819
|
}
|
|
5780
5820
|
async function walkProtoFiles(dir) {
|
|
5781
5821
|
const out = [];
|
|
5782
|
-
async function
|
|
5822
|
+
async function walk4(current) {
|
|
5783
5823
|
const entries = await import_node_fs15.promises.readdir(current, { withFileTypes: true }).catch(() => []);
|
|
5784
5824
|
for (const entry2 of entries) {
|
|
5785
5825
|
const full = import_node_path23.default.join(current, entry2.name);
|
|
5786
5826
|
if (entry2.isDirectory()) {
|
|
5787
5827
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
5788
5828
|
if (await isPythonVenvDir(full)) continue;
|
|
5789
|
-
await
|
|
5829
|
+
await walk4(full);
|
|
5790
5830
|
} else if (entry2.isFile() && import_node_path23.default.extname(entry2.name) === PROTO_EXTENSION) {
|
|
5791
5831
|
out.push(full);
|
|
5792
5832
|
}
|
|
5793
5833
|
}
|
|
5794
5834
|
}
|
|
5795
|
-
await
|
|
5835
|
+
await walk4(dir);
|
|
5796
5836
|
return out;
|
|
5797
5837
|
}
|
|
5798
5838
|
async function addGrpcMethods(graph, services) {
|
|
@@ -5860,7 +5900,7 @@ async function addGrpcMethods(graph, services) {
|
|
|
5860
5900
|
|
|
5861
5901
|
// src/extract/calls/index.ts
|
|
5862
5902
|
init_cjs_shims();
|
|
5863
|
-
var
|
|
5903
|
+
var import_types22 = require("@neat.is/types");
|
|
5864
5904
|
|
|
5865
5905
|
// src/extract/calls/http.ts
|
|
5866
5906
|
init_cjs_shims();
|
|
@@ -6869,15 +6909,130 @@ async function mongooseCrossFileEndpoints(files, serviceDir) {
|
|
|
6869
6909
|
return out;
|
|
6870
6910
|
}
|
|
6871
6911
|
|
|
6912
|
+
// src/extract/calls/sqlalchemy.ts
|
|
6913
|
+
init_cjs_shims();
|
|
6914
|
+
var import_node_path32 = __toESM(require("path"), 1);
|
|
6915
|
+
var import_tree_sitter5 = __toESM(require("tree-sitter"), 1);
|
|
6916
|
+
var import_tree_sitter_python4 = __toESM(require("tree-sitter-python"), 1);
|
|
6917
|
+
var import_types21 = require("@neat.is/types");
|
|
6918
|
+
var SQLALCHEMY_IMPORT_RE = /(?:from|import)\s+(?:flask_sqlalchemy|sqlalchemy)\b/;
|
|
6919
|
+
var PARSE_CHUNK5 = 16384;
|
|
6920
|
+
function makePyParser4() {
|
|
6921
|
+
const p = new import_tree_sitter5.default();
|
|
6922
|
+
p.setLanguage(import_tree_sitter_python4.default);
|
|
6923
|
+
return p;
|
|
6924
|
+
}
|
|
6925
|
+
function parseSource5(parser, source) {
|
|
6926
|
+
return parser.parse(
|
|
6927
|
+
(index) => index >= source.length ? "" : source.slice(index, index + PARSE_CHUNK5)
|
|
6928
|
+
);
|
|
6929
|
+
}
|
|
6930
|
+
function flaskSqlalchemyTableName(className) {
|
|
6931
|
+
return className.replace(/((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))/g, "_$1").toLowerCase().replace(/^_+/, "");
|
|
6932
|
+
}
|
|
6933
|
+
function namedChildren(node) {
|
|
6934
|
+
const out = [];
|
|
6935
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
6936
|
+
const c = node.namedChild(i);
|
|
6937
|
+
if (c) out.push(c);
|
|
6938
|
+
}
|
|
6939
|
+
return out;
|
|
6940
|
+
}
|
|
6941
|
+
function pyStaticStringText2(node) {
|
|
6942
|
+
if (node.type !== "string") return null;
|
|
6943
|
+
for (const child of namedChildren(node)) {
|
|
6944
|
+
if (child.type === "interpolation") return null;
|
|
6945
|
+
if (child.type === "string_content") return child.text;
|
|
6946
|
+
}
|
|
6947
|
+
return "";
|
|
6948
|
+
}
|
|
6949
|
+
function explicitTablename(body) {
|
|
6950
|
+
for (const stmt of namedChildren(body)) {
|
|
6951
|
+
if (stmt.type !== "expression_statement") continue;
|
|
6952
|
+
const assign = stmt.namedChild(0);
|
|
6953
|
+
if (assign?.type !== "assignment") continue;
|
|
6954
|
+
if (assign.childForFieldName("left")?.text !== "__tablename__") continue;
|
|
6955
|
+
const right = assign.childForFieldName("right");
|
|
6956
|
+
if (right?.type === "string") {
|
|
6957
|
+
const s = pyStaticStringText2(right);
|
|
6958
|
+
return s ? { name: s } : "computed";
|
|
6959
|
+
}
|
|
6960
|
+
return "computed";
|
|
6961
|
+
}
|
|
6962
|
+
return null;
|
|
6963
|
+
}
|
|
6964
|
+
function extendsFlaskModel(cls) {
|
|
6965
|
+
const supers = cls.childForFieldName("superclasses");
|
|
6966
|
+
if (!supers) return false;
|
|
6967
|
+
for (const a of namedChildren(supers)) {
|
|
6968
|
+
const t = a.text;
|
|
6969
|
+
if (t === "db.Model" || t === "Model" || t.endsWith(".Model")) return true;
|
|
6970
|
+
}
|
|
6971
|
+
return false;
|
|
6972
|
+
}
|
|
6973
|
+
function walk3(node, visit) {
|
|
6974
|
+
visit(node);
|
|
6975
|
+
for (const c of namedChildren(node)) walk3(c, visit);
|
|
6976
|
+
}
|
|
6977
|
+
function sqlalchemyEndpointsFromFile(file, serviceDir) {
|
|
6978
|
+
if (!SQLALCHEMY_IMPORT_RE.test(file.content)) return [];
|
|
6979
|
+
const tree = parseSource5(makePyParser4(), file.content);
|
|
6980
|
+
const out = [];
|
|
6981
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6982
|
+
const push = (name, line) => {
|
|
6983
|
+
if (seen.has(name)) return;
|
|
6984
|
+
seen.add(name);
|
|
6985
|
+
out.push({
|
|
6986
|
+
infraId: (0, import_types21.infraId)("sql-table", name),
|
|
6987
|
+
name,
|
|
6988
|
+
kind: "sql-table",
|
|
6989
|
+
edgeType: "CALLS",
|
|
6990
|
+
confidenceKind: "verified-call-site",
|
|
6991
|
+
evidence: {
|
|
6992
|
+
file: import_node_path32.default.relative(serviceDir, file.path),
|
|
6993
|
+
line,
|
|
6994
|
+
snippet: snippet(file.content, line)
|
|
6995
|
+
}
|
|
6996
|
+
});
|
|
6997
|
+
};
|
|
6998
|
+
walk3(tree.rootNode, (node) => {
|
|
6999
|
+
if (node.type === "class_definition") {
|
|
7000
|
+
const body = node.childForFieldName("body");
|
|
7001
|
+
const nameNode = node.childForFieldName("name");
|
|
7002
|
+
if (!body || !nameNode) return;
|
|
7003
|
+
const line = node.startPosition.row + 1;
|
|
7004
|
+
const explicit = explicitTablename(body);
|
|
7005
|
+
if (explicit === "computed") return;
|
|
7006
|
+
if (explicit) {
|
|
7007
|
+
push(explicit.name, line);
|
|
7008
|
+
return;
|
|
7009
|
+
}
|
|
7010
|
+
if (extendsFlaskModel(node)) push(flaskSqlalchemyTableName(nameNode.text), line);
|
|
7011
|
+
return;
|
|
7012
|
+
}
|
|
7013
|
+
if (node.type === "call") {
|
|
7014
|
+
const fn = node.childForFieldName("function");
|
|
7015
|
+
const fnText = fn?.text;
|
|
7016
|
+
if (fnText !== "Table" && fnText !== "sa.Table" && fnText !== "sqlalchemy.Table") return;
|
|
7017
|
+
const first = node.childForFieldName("arguments")?.namedChild(0);
|
|
7018
|
+
if (first?.type === "string") {
|
|
7019
|
+
const s = pyStaticStringText2(first);
|
|
7020
|
+
if (s) push(s, node.startPosition.row + 1);
|
|
7021
|
+
}
|
|
7022
|
+
}
|
|
7023
|
+
});
|
|
7024
|
+
return out;
|
|
7025
|
+
}
|
|
7026
|
+
|
|
6872
7027
|
// src/extract/calls/index.ts
|
|
6873
7028
|
function edgeTypeFromEndpoint(ep) {
|
|
6874
7029
|
switch (ep.edgeType) {
|
|
6875
7030
|
case "PUBLISHES_TO":
|
|
6876
|
-
return
|
|
7031
|
+
return import_types22.EdgeType.PUBLISHES_TO;
|
|
6877
7032
|
case "CONSUMES_FROM":
|
|
6878
|
-
return
|
|
7033
|
+
return import_types22.EdgeType.CONSUMES_FROM;
|
|
6879
7034
|
default:
|
|
6880
|
-
return
|
|
7035
|
+
return import_types22.EdgeType.CALLS;
|
|
6881
7036
|
}
|
|
6882
7037
|
}
|
|
6883
7038
|
function isAwsKind(kind) {
|
|
@@ -6901,6 +7056,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
6901
7056
|
endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
|
|
6902
7057
|
endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
|
|
6903
7058
|
endpoints.push(...mongooseEndpointsFromFile(maskedFile, service.dir));
|
|
7059
|
+
endpoints.push(...sqlalchemyEndpointsFromFile(maskedFile, service.dir));
|
|
6904
7060
|
}
|
|
6905
7061
|
endpoints.push(...await mongooseCrossFileEndpoints(maskedFiles, service.dir));
|
|
6906
7062
|
if (endpoints.length === 0) continue;
|
|
@@ -6909,7 +7065,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
6909
7065
|
if (!graph.hasNode(ep.infraId)) {
|
|
6910
7066
|
const node = {
|
|
6911
7067
|
id: ep.infraId,
|
|
6912
|
-
type:
|
|
7068
|
+
type: import_types22.NodeType.InfraNode,
|
|
6913
7069
|
name: ep.name,
|
|
6914
7070
|
// #238 — `aws-*` covers AWS-SDK client kinds (aws-s3, aws-dynamodb,
|
|
6915
7071
|
// aws-cognito-identity-provider, …); `s3-` / `dynamodb-` cover the
|
|
@@ -6921,7 +7077,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
6921
7077
|
nodesAdded++;
|
|
6922
7078
|
}
|
|
6923
7079
|
const edgeType = edgeTypeFromEndpoint(ep);
|
|
6924
|
-
const confidence = (0,
|
|
7080
|
+
const confidence = (0, import_types22.confidenceForExtracted)(ep.confidenceKind);
|
|
6925
7081
|
const relFile = toPosix2(ep.evidence.file);
|
|
6926
7082
|
const { fileNodeId, nodesAdded: n, edgesAdded: e } = ensureFileNode(
|
|
6927
7083
|
graph,
|
|
@@ -6931,7 +7087,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
6931
7087
|
);
|
|
6932
7088
|
nodesAdded += n;
|
|
6933
7089
|
edgesAdded += e;
|
|
6934
|
-
if (!(0,
|
|
7090
|
+
if (!(0, import_types22.passesExtractedFloor)(confidence)) {
|
|
6935
7091
|
noteExtractedDropped({
|
|
6936
7092
|
source: fileNodeId,
|
|
6937
7093
|
target: ep.infraId,
|
|
@@ -6951,7 +7107,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
6951
7107
|
source: fileNodeId,
|
|
6952
7108
|
target: ep.infraId,
|
|
6953
7109
|
type: edgeType,
|
|
6954
|
-
provenance:
|
|
7110
|
+
provenance: import_types22.Provenance.EXTRACTED,
|
|
6955
7111
|
confidence,
|
|
6956
7112
|
evidence: ep.evidence
|
|
6957
7113
|
};
|
|
@@ -6977,16 +7133,16 @@ init_cjs_shims();
|
|
|
6977
7133
|
|
|
6978
7134
|
// src/extract/infra/docker-compose.ts
|
|
6979
7135
|
init_cjs_shims();
|
|
6980
|
-
var
|
|
6981
|
-
var
|
|
7136
|
+
var import_node_path33 = __toESM(require("path"), 1);
|
|
7137
|
+
var import_types24 = require("@neat.is/types");
|
|
6982
7138
|
|
|
6983
7139
|
// src/extract/infra/shared.ts
|
|
6984
7140
|
init_cjs_shims();
|
|
6985
|
-
var
|
|
7141
|
+
var import_types23 = require("@neat.is/types");
|
|
6986
7142
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
6987
7143
|
return {
|
|
6988
|
-
id: (0,
|
|
6989
|
-
type:
|
|
7144
|
+
id: (0, import_types23.infraId)(kind, name),
|
|
7145
|
+
type: import_types23.NodeType.InfraNode,
|
|
6990
7146
|
name,
|
|
6991
7147
|
provider,
|
|
6992
7148
|
kind,
|
|
@@ -7030,8 +7186,8 @@ function emitPlatformResourceEdge(graph, anchorId, edgeType, kind, name, provide
|
|
|
7030
7186
|
source: anchorId,
|
|
7031
7187
|
target: node.id,
|
|
7032
7188
|
type: edgeType,
|
|
7033
|
-
provenance:
|
|
7034
|
-
confidence: (0,
|
|
7189
|
+
provenance: import_types23.Provenance.EXTRACTED,
|
|
7190
|
+
confidence: (0, import_types23.confidenceForExtracted)("structural"),
|
|
7035
7191
|
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
7036
7192
|
};
|
|
7037
7193
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -7048,7 +7204,7 @@ function dependsOnList(value) {
|
|
|
7048
7204
|
}
|
|
7049
7205
|
function serviceNameToServiceNode(name, services) {
|
|
7050
7206
|
for (const s of services) {
|
|
7051
|
-
if (s.node.name === name ||
|
|
7207
|
+
if (s.node.name === name || import_node_path33.default.basename(s.dir) === name) return s.node.id;
|
|
7052
7208
|
}
|
|
7053
7209
|
return null;
|
|
7054
7210
|
}
|
|
@@ -7057,7 +7213,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
7057
7213
|
let edgesAdded = 0;
|
|
7058
7214
|
let composePath = null;
|
|
7059
7215
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
7060
|
-
const abs =
|
|
7216
|
+
const abs = import_node_path33.default.join(scanPath, name);
|
|
7061
7217
|
if (await exists(abs)) {
|
|
7062
7218
|
composePath = abs;
|
|
7063
7219
|
break;
|
|
@@ -7070,13 +7226,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
7070
7226
|
} catch (err) {
|
|
7071
7227
|
recordExtractionError(
|
|
7072
7228
|
"infra docker-compose",
|
|
7073
|
-
|
|
7229
|
+
import_node_path33.default.relative(scanPath, composePath),
|
|
7074
7230
|
err
|
|
7075
7231
|
);
|
|
7076
7232
|
return { nodesAdded, edgesAdded };
|
|
7077
7233
|
}
|
|
7078
7234
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
7079
|
-
const evidenceFile =
|
|
7235
|
+
const evidenceFile = import_node_path33.default.relative(scanPath, composePath).split(import_node_path33.default.sep).join("/");
|
|
7080
7236
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
7081
7237
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
7082
7238
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -7098,15 +7254,15 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
7098
7254
|
for (const dep of dependsOnList(svc.depends_on)) {
|
|
7099
7255
|
const targetId = composeNameToNodeId.get(dep);
|
|
7100
7256
|
if (!targetId) continue;
|
|
7101
|
-
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId,
|
|
7257
|
+
const edgeId = (0, import_types4.extractedEdgeId)(sourceId, targetId, import_types24.EdgeType.DEPENDS_ON);
|
|
7102
7258
|
if (graph.hasEdge(edgeId)) continue;
|
|
7103
7259
|
const edge = {
|
|
7104
7260
|
id: edgeId,
|
|
7105
7261
|
source: sourceId,
|
|
7106
7262
|
target: targetId,
|
|
7107
|
-
type:
|
|
7108
|
-
provenance:
|
|
7109
|
-
confidence: (0,
|
|
7263
|
+
type: import_types24.EdgeType.DEPENDS_ON,
|
|
7264
|
+
provenance: import_types24.Provenance.EXTRACTED,
|
|
7265
|
+
confidence: (0, import_types24.confidenceForExtracted)("structural"),
|
|
7110
7266
|
evidence: { file: evidenceFile }
|
|
7111
7267
|
};
|
|
7112
7268
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -7118,9 +7274,9 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
7118
7274
|
|
|
7119
7275
|
// src/extract/infra/dockerfile.ts
|
|
7120
7276
|
init_cjs_shims();
|
|
7121
|
-
var
|
|
7277
|
+
var import_node_path34 = __toESM(require("path"), 1);
|
|
7122
7278
|
var import_node_fs16 = require("fs");
|
|
7123
|
-
var
|
|
7279
|
+
var import_types25 = require("@neat.is/types");
|
|
7124
7280
|
function readDockerfile(content) {
|
|
7125
7281
|
let image = null;
|
|
7126
7282
|
const ports = [];
|
|
@@ -7149,7 +7305,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
7149
7305
|
let nodesAdded = 0;
|
|
7150
7306
|
let edgesAdded = 0;
|
|
7151
7307
|
for (const service of services) {
|
|
7152
|
-
const dockerfilePath =
|
|
7308
|
+
const dockerfilePath = import_node_path34.default.join(service.dir, "Dockerfile");
|
|
7153
7309
|
if (!await exists(dockerfilePath)) continue;
|
|
7154
7310
|
let content;
|
|
7155
7311
|
try {
|
|
@@ -7157,7 +7313,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
7157
7313
|
} catch (err) {
|
|
7158
7314
|
recordExtractionError(
|
|
7159
7315
|
"infra dockerfile",
|
|
7160
|
-
|
|
7316
|
+
import_node_path34.default.relative(scanPath, dockerfilePath),
|
|
7161
7317
|
err
|
|
7162
7318
|
);
|
|
7163
7319
|
continue;
|
|
@@ -7169,8 +7325,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
7169
7325
|
graph.addNode(node.id, node);
|
|
7170
7326
|
nodesAdded++;
|
|
7171
7327
|
}
|
|
7172
|
-
const relDockerfile = toPosix2(
|
|
7173
|
-
const evidenceFile = toPosix2(
|
|
7328
|
+
const relDockerfile = toPosix2(import_node_path34.default.relative(service.dir, dockerfilePath));
|
|
7329
|
+
const evidenceFile = toPosix2(import_node_path34.default.relative(scanPath, dockerfilePath));
|
|
7174
7330
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
7175
7331
|
graph,
|
|
7176
7332
|
service.pkg.name,
|
|
@@ -7179,15 +7335,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
7179
7335
|
);
|
|
7180
7336
|
nodesAdded += fn;
|
|
7181
7337
|
edgesAdded += fe;
|
|
7182
|
-
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id,
|
|
7338
|
+
const edgeId = (0, import_types4.extractedEdgeId)(fileNodeId, node.id, import_types25.EdgeType.RUNS_ON);
|
|
7183
7339
|
if (!graph.hasEdge(edgeId)) {
|
|
7184
7340
|
const edge = {
|
|
7185
7341
|
id: edgeId,
|
|
7186
7342
|
source: fileNodeId,
|
|
7187
7343
|
target: node.id,
|
|
7188
|
-
type:
|
|
7189
|
-
provenance:
|
|
7190
|
-
confidence: (0,
|
|
7344
|
+
type: import_types25.EdgeType.RUNS_ON,
|
|
7345
|
+
provenance: import_types25.Provenance.EXTRACTED,
|
|
7346
|
+
confidence: (0, import_types25.confidenceForExtracted)("structural"),
|
|
7191
7347
|
evidence: {
|
|
7192
7348
|
file: evidenceFile,
|
|
7193
7349
|
...facts.entrypoint ? { snippet: facts.entrypoint.slice(0, 120) } : {}
|
|
@@ -7202,15 +7358,15 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
7202
7358
|
graph.addNode(portNode.id, portNode);
|
|
7203
7359
|
nodesAdded++;
|
|
7204
7360
|
}
|
|
7205
|
-
const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id,
|
|
7361
|
+
const portEdgeId = (0, import_types4.extractedEdgeId)(fileNodeId, portNode.id, import_types25.EdgeType.CONNECTS_TO);
|
|
7206
7362
|
if (graph.hasEdge(portEdgeId)) continue;
|
|
7207
7363
|
const portEdge = {
|
|
7208
7364
|
id: portEdgeId,
|
|
7209
7365
|
source: fileNodeId,
|
|
7210
7366
|
target: portNode.id,
|
|
7211
|
-
type:
|
|
7212
|
-
provenance:
|
|
7213
|
-
confidence: (0,
|
|
7367
|
+
type: import_types25.EdgeType.CONNECTS_TO,
|
|
7368
|
+
provenance: import_types25.Provenance.EXTRACTED,
|
|
7369
|
+
confidence: (0, import_types25.confidenceForExtracted)("structural"),
|
|
7214
7370
|
evidence: { file: evidenceFile, snippet: `EXPOSE ${port}` }
|
|
7215
7371
|
};
|
|
7216
7372
|
graph.addEdgeWithKey(portEdgeId, portEdge.source, portEdge.target, portEdge);
|
|
@@ -7223,8 +7379,8 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
7223
7379
|
// src/extract/infra/terraform.ts
|
|
7224
7380
|
init_cjs_shims();
|
|
7225
7381
|
var import_node_fs17 = require("fs");
|
|
7226
|
-
var
|
|
7227
|
-
var
|
|
7382
|
+
var import_node_path35 = __toESM(require("path"), 1);
|
|
7383
|
+
var import_types26 = require("@neat.is/types");
|
|
7228
7384
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
7229
7385
|
var REFERENCE_RE = /(?<![\w.])(aws_[A-Za-z0-9_]+)\.([A-Za-z0-9_-]+)/g;
|
|
7230
7386
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
@@ -7234,11 +7390,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
|
|
|
7234
7390
|
for (const entry2 of entries) {
|
|
7235
7391
|
if (entry2.isDirectory()) {
|
|
7236
7392
|
if (IGNORED_DIRS.has(entry2.name) || entry2.name === ".terraform") continue;
|
|
7237
|
-
const child =
|
|
7393
|
+
const child = import_node_path35.default.join(start, entry2.name);
|
|
7238
7394
|
if (await isPythonVenvDir(child)) continue;
|
|
7239
7395
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
7240
7396
|
} else if (entry2.isFile() && entry2.name.endsWith(".tf")) {
|
|
7241
|
-
out.push(
|
|
7397
|
+
out.push(import_node_path35.default.join(start, entry2.name));
|
|
7242
7398
|
}
|
|
7243
7399
|
}
|
|
7244
7400
|
return out;
|
|
@@ -7270,7 +7426,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
7270
7426
|
const files = await walkTfFiles(scanPath);
|
|
7271
7427
|
for (const file of files) {
|
|
7272
7428
|
const content = await import_node_fs17.promises.readFile(file, "utf8");
|
|
7273
|
-
const evidenceFile = toPosix2(
|
|
7429
|
+
const evidenceFile = toPosix2(import_node_path35.default.relative(scanPath, file));
|
|
7274
7430
|
const resources = [];
|
|
7275
7431
|
const byKey = /* @__PURE__ */ new Map();
|
|
7276
7432
|
RESOURCE_RE.lastIndex = 0;
|
|
@@ -7305,16 +7461,16 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
7305
7461
|
if (!target) continue;
|
|
7306
7462
|
if (seen.has(target.nodeId)) continue;
|
|
7307
7463
|
seen.add(target.nodeId);
|
|
7308
|
-
const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId,
|
|
7464
|
+
const edgeId = (0, import_types4.extractedEdgeId)(resource.nodeId, target.nodeId, import_types26.EdgeType.DEPENDS_ON);
|
|
7309
7465
|
if (graph.hasEdge(edgeId)) continue;
|
|
7310
7466
|
const line = lineAt2(content, resource.bodyOffset + ref.index);
|
|
7311
7467
|
const edge = {
|
|
7312
7468
|
id: edgeId,
|
|
7313
7469
|
source: resource.nodeId,
|
|
7314
7470
|
target: target.nodeId,
|
|
7315
|
-
type:
|
|
7316
|
-
provenance:
|
|
7317
|
-
confidence: (0,
|
|
7471
|
+
type: import_types26.EdgeType.DEPENDS_ON,
|
|
7472
|
+
provenance: import_types26.Provenance.EXTRACTED,
|
|
7473
|
+
confidence: (0, import_types26.confidenceForExtracted)("structural"),
|
|
7318
7474
|
evidence: { file: evidenceFile, line, snippet: key }
|
|
7319
7475
|
};
|
|
7320
7476
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -7328,7 +7484,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
7328
7484
|
// src/extract/infra/k8s.ts
|
|
7329
7485
|
init_cjs_shims();
|
|
7330
7486
|
var import_node_fs18 = require("fs");
|
|
7331
|
-
var
|
|
7487
|
+
var import_node_path36 = __toESM(require("path"), 1);
|
|
7332
7488
|
var import_yaml3 = require("yaml");
|
|
7333
7489
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
7334
7490
|
Service: "k8s-service",
|
|
@@ -7346,11 +7502,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
|
7346
7502
|
for (const entry2 of entries) {
|
|
7347
7503
|
if (entry2.isDirectory()) {
|
|
7348
7504
|
if (IGNORED_DIRS.has(entry2.name)) continue;
|
|
7349
|
-
const child =
|
|
7505
|
+
const child = import_node_path36.default.join(start, entry2.name);
|
|
7350
7506
|
if (await isPythonVenvDir(child)) continue;
|
|
7351
7507
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
7352
|
-
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
7353
|
-
out.push(
|
|
7508
|
+
} else if (entry2.isFile() && CONFIG_FILE_EXTENSIONS.has(import_node_path36.default.extname(entry2.name))) {
|
|
7509
|
+
out.push(import_node_path36.default.join(start, entry2.name));
|
|
7354
7510
|
}
|
|
7355
7511
|
}
|
|
7356
7512
|
return out;
|
|
@@ -7384,13 +7540,13 @@ async function addK8sResources(graph, scanPath) {
|
|
|
7384
7540
|
// src/extract/infra/cloudflare.ts
|
|
7385
7541
|
init_cjs_shims();
|
|
7386
7542
|
var import_node_fs19 = require("fs");
|
|
7387
|
-
var
|
|
7543
|
+
var import_node_path37 = __toESM(require("path"), 1);
|
|
7388
7544
|
var import_smol_toml2 = require("smol-toml");
|
|
7389
|
-
var
|
|
7545
|
+
var import_types27 = require("@neat.is/types");
|
|
7390
7546
|
var WRANGLER_FILENAMES = ["wrangler.toml", "wrangler.jsonc", "wrangler.json"];
|
|
7391
7547
|
async function readWranglerConfig(dir) {
|
|
7392
7548
|
for (const filename of WRANGLER_FILENAMES) {
|
|
7393
|
-
const abs =
|
|
7549
|
+
const abs = import_node_path37.default.join(dir, filename);
|
|
7394
7550
|
if (!await exists(abs)) continue;
|
|
7395
7551
|
const raw = await import_node_fs19.promises.readFile(abs, "utf8");
|
|
7396
7552
|
const config = filename === "wrangler.toml" ? (0, import_smol_toml2.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
@@ -7434,8 +7590,8 @@ function addResourceEdge(graph, anchorId, edgeType, kind, name, evidenceFile, li
|
|
|
7434
7590
|
source: anchorId,
|
|
7435
7591
|
target: node.id,
|
|
7436
7592
|
type: edgeType,
|
|
7437
|
-
provenance:
|
|
7438
|
-
confidence: (0,
|
|
7593
|
+
provenance: import_types27.Provenance.EXTRACTED,
|
|
7594
|
+
confidence: (0, import_types27.confidenceForExtracted)("structural"),
|
|
7439
7595
|
evidence: { file: evidenceFile, ...line !== void 0 ? { line } : {} }
|
|
7440
7596
|
};
|
|
7441
7597
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -7453,11 +7609,11 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7453
7609
|
try {
|
|
7454
7610
|
read = await readWranglerConfig(service.dir);
|
|
7455
7611
|
} catch (err) {
|
|
7456
|
-
recordExtractionError("infra cloudflare",
|
|
7612
|
+
recordExtractionError("infra cloudflare", import_node_path37.default.relative(scanPath, service.dir), err);
|
|
7457
7613
|
continue;
|
|
7458
7614
|
}
|
|
7459
7615
|
if (!read || !read.config.name) continue;
|
|
7460
|
-
const evidenceFile = toPosix2(
|
|
7616
|
+
const evidenceFile = toPosix2(import_node_path37.default.relative(scanPath, import_node_path37.default.join(service.dir, read.relFile)));
|
|
7461
7617
|
discovered.push({ service, config: read.config, relFile: read.relFile, raw: read.raw, evidenceFile });
|
|
7462
7618
|
}
|
|
7463
7619
|
for (const worker of discovered) {
|
|
@@ -7469,7 +7625,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7469
7625
|
}
|
|
7470
7626
|
let anchorId = service.node.id;
|
|
7471
7627
|
if (config.main) {
|
|
7472
|
-
const entryRelPath = toPosix2(
|
|
7628
|
+
const entryRelPath = toPosix2(import_node_path37.default.normalize(config.main));
|
|
7473
7629
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
7474
7630
|
graph,
|
|
7475
7631
|
service.pkg.name,
|
|
@@ -7496,15 +7652,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7496
7652
|
nodesAdded++;
|
|
7497
7653
|
}
|
|
7498
7654
|
if (runtimeNode.id !== anchorId) {
|
|
7499
|
-
const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id,
|
|
7655
|
+
const runsOnId = (0, import_types4.extractedEdgeId)(anchorId, runtimeNode.id, import_types27.EdgeType.RUNS_ON);
|
|
7500
7656
|
if (!graph.hasEdge(runsOnId)) {
|
|
7501
7657
|
const edge = {
|
|
7502
7658
|
id: runsOnId,
|
|
7503
7659
|
source: anchorId,
|
|
7504
7660
|
target: runtimeNode.id,
|
|
7505
|
-
type:
|
|
7506
|
-
provenance:
|
|
7507
|
-
confidence: (0,
|
|
7661
|
+
type: import_types27.EdgeType.RUNS_ON,
|
|
7662
|
+
provenance: import_types27.Provenance.EXTRACTED,
|
|
7663
|
+
confidence: (0, import_types27.confidenceForExtracted)("structural"),
|
|
7508
7664
|
evidence: {
|
|
7509
7665
|
file: evidenceFile,
|
|
7510
7666
|
...config.compatibility_date ? { snippet: `compatibility_date = ${config.compatibility_date}`.slice(0, 120) } : {}
|
|
@@ -7518,7 +7674,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7518
7674
|
const result = addResourceEdge(
|
|
7519
7675
|
graph,
|
|
7520
7676
|
anchorId,
|
|
7521
|
-
|
|
7677
|
+
import_types27.EdgeType.CONNECTS_TO,
|
|
7522
7678
|
"cloudflare-route",
|
|
7523
7679
|
route,
|
|
7524
7680
|
evidenceFile,
|
|
@@ -7542,7 +7698,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7542
7698
|
const result = addResourceEdge(
|
|
7543
7699
|
graph,
|
|
7544
7700
|
anchorId,
|
|
7545
|
-
|
|
7701
|
+
import_types27.EdgeType.DEPENDS_ON,
|
|
7546
7702
|
group.kind,
|
|
7547
7703
|
name,
|
|
7548
7704
|
evidenceFile,
|
|
@@ -7556,7 +7712,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7556
7712
|
const result = addResourceEdge(
|
|
7557
7713
|
graph,
|
|
7558
7714
|
anchorId,
|
|
7559
|
-
|
|
7715
|
+
import_types27.EdgeType.DEPENDS_ON,
|
|
7560
7716
|
"cloudflare-cron",
|
|
7561
7717
|
cron,
|
|
7562
7718
|
evidenceFile,
|
|
@@ -7569,7 +7725,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7569
7725
|
const result = addResourceEdge(
|
|
7570
7726
|
graph,
|
|
7571
7727
|
anchorId,
|
|
7572
|
-
|
|
7728
|
+
import_types27.EdgeType.DEPENDS_ON,
|
|
7573
7729
|
"cloudflare-env-var",
|
|
7574
7730
|
varName,
|
|
7575
7731
|
evidenceFile,
|
|
@@ -7582,15 +7738,15 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7582
7738
|
if (!svc.service) continue;
|
|
7583
7739
|
const target = workerIndex.get(svc.service);
|
|
7584
7740
|
if (target && target.anchorId !== anchorId) {
|
|
7585
|
-
const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId,
|
|
7741
|
+
const edgeId = (0, import_types4.extractedEdgeId)(anchorId, target.anchorId, import_types27.EdgeType.CALLS);
|
|
7586
7742
|
if (!graph.hasEdge(edgeId)) {
|
|
7587
7743
|
const edge = {
|
|
7588
7744
|
id: edgeId,
|
|
7589
7745
|
source: anchorId,
|
|
7590
7746
|
target: target.anchorId,
|
|
7591
|
-
type:
|
|
7592
|
-
provenance:
|
|
7593
|
-
confidence: (0,
|
|
7747
|
+
type: import_types27.EdgeType.CALLS,
|
|
7748
|
+
provenance: import_types27.Provenance.EXTRACTED,
|
|
7749
|
+
confidence: (0, import_types27.confidenceForExtracted)("structural"),
|
|
7594
7750
|
evidence: { file: evidenceFile, line: lineContaining2(raw, svc.service) }
|
|
7595
7751
|
};
|
|
7596
7752
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -7601,7 +7757,7 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7601
7757
|
const result = addResourceEdge(
|
|
7602
7758
|
graph,
|
|
7603
7759
|
anchorId,
|
|
7604
|
-
|
|
7760
|
+
import_types27.EdgeType.DEPENDS_ON,
|
|
7605
7761
|
"cloudflare-service-binding",
|
|
7606
7762
|
svc.service,
|
|
7607
7763
|
evidenceFile,
|
|
@@ -7617,12 +7773,12 @@ async function addCloudflareWorkers(graph, services, scanPath) {
|
|
|
7617
7773
|
// src/extract/infra/vercel.ts
|
|
7618
7774
|
init_cjs_shims();
|
|
7619
7775
|
var import_node_fs20 = require("fs");
|
|
7620
|
-
var
|
|
7621
|
-
var
|
|
7776
|
+
var import_node_path38 = __toESM(require("path"), 1);
|
|
7777
|
+
var import_types28 = require("@neat.is/types");
|
|
7622
7778
|
var VERCEL_CONFIG_FILENAMES = ["vercel.json", "vercel.jsonc"];
|
|
7623
7779
|
async function readVercelConfig(dir) {
|
|
7624
7780
|
for (const filename of VERCEL_CONFIG_FILENAMES) {
|
|
7625
|
-
const abs =
|
|
7781
|
+
const abs = import_node_path38.default.join(dir, filename);
|
|
7626
7782
|
if (!await exists(abs)) continue;
|
|
7627
7783
|
const raw = await import_node_fs20.promises.readFile(abs, "utf8");
|
|
7628
7784
|
const config = JSON.parse(maskCommentsInSource(raw));
|
|
@@ -7631,7 +7787,7 @@ async function readVercelConfig(dir) {
|
|
|
7631
7787
|
return null;
|
|
7632
7788
|
}
|
|
7633
7789
|
async function readLinkedProjectName(dir) {
|
|
7634
|
-
const abs =
|
|
7790
|
+
const abs = import_node_path38.default.join(dir, ".vercel", "project.json");
|
|
7635
7791
|
if (!await exists(abs)) return void 0;
|
|
7636
7792
|
const parsed = JSON.parse(await import_node_fs20.promises.readFile(abs, "utf8"));
|
|
7637
7793
|
return typeof parsed.projectName === "string" ? parsed.projectName : void 0;
|
|
@@ -7649,7 +7805,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
7649
7805
|
read = await readVercelConfig(service.dir);
|
|
7650
7806
|
projectName = await readLinkedProjectName(service.dir);
|
|
7651
7807
|
} catch (err) {
|
|
7652
|
-
recordExtractionError("infra vercel",
|
|
7808
|
+
recordExtractionError("infra vercel", import_node_path38.default.relative(scanPath, service.dir), err);
|
|
7653
7809
|
continue;
|
|
7654
7810
|
}
|
|
7655
7811
|
if (!read && !projectName) continue;
|
|
@@ -7665,7 +7821,7 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
7665
7821
|
const anchorId = service.node.id;
|
|
7666
7822
|
if (!read) continue;
|
|
7667
7823
|
const { config, relFile, raw } = read;
|
|
7668
|
-
const evidenceFile = toPosix2(
|
|
7824
|
+
const evidenceFile = toPosix2(import_node_path38.default.relative(scanPath, import_node_path38.default.join(service.dir, relFile)));
|
|
7669
7825
|
const add = (edgeType, kind, name) => {
|
|
7670
7826
|
if (!name) return;
|
|
7671
7827
|
const result = emitPlatformResourceEdge(
|
|
@@ -7681,12 +7837,12 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
7681
7837
|
nodesAdded += result.nodesAdded;
|
|
7682
7838
|
edgesAdded += result.edgesAdded;
|
|
7683
7839
|
};
|
|
7684
|
-
add(
|
|
7685
|
-
for (const cron of config.crons ?? []) add(
|
|
7686
|
-
for (const varName of Object.keys(config.env ?? {})) add(
|
|
7687
|
-
for (const varName of Object.keys(config.build?.env ?? {})) add(
|
|
7840
|
+
add(import_types28.EdgeType.RUNS_ON, "vercel", "vercel");
|
|
7841
|
+
for (const cron of config.crons ?? []) add(import_types28.EdgeType.DEPENDS_ON, "vercel-cron", cron.path ?? cron.schedule);
|
|
7842
|
+
for (const varName of Object.keys(config.env ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
7843
|
+
for (const varName of Object.keys(config.build?.env ?? {})) add(import_types28.EdgeType.DEPENDS_ON, "vercel-env-var", varName);
|
|
7688
7844
|
for (const route of [...config.rewrites ?? [], ...config.redirects ?? [], ...config.routes ?? []]) {
|
|
7689
|
-
add(
|
|
7845
|
+
add(import_types28.EdgeType.CONNECTS_TO, "vercel-route", routeSource(route));
|
|
7690
7846
|
}
|
|
7691
7847
|
}
|
|
7692
7848
|
return { nodesAdded, edgesAdded };
|
|
@@ -7695,13 +7851,13 @@ async function addVercelServices(graph, services, scanPath) {
|
|
|
7695
7851
|
// src/extract/infra/railway.ts
|
|
7696
7852
|
init_cjs_shims();
|
|
7697
7853
|
var import_node_fs21 = require("fs");
|
|
7698
|
-
var
|
|
7854
|
+
var import_node_path39 = __toESM(require("path"), 1);
|
|
7699
7855
|
var import_smol_toml3 = require("smol-toml");
|
|
7700
|
-
var
|
|
7856
|
+
var import_types29 = require("@neat.is/types");
|
|
7701
7857
|
var RAILWAY_FILENAMES = ["railway.toml", "railway.json", "railway.jsonc"];
|
|
7702
7858
|
async function readRailwayConfig(dir) {
|
|
7703
7859
|
for (const filename of RAILWAY_FILENAMES) {
|
|
7704
|
-
const abs =
|
|
7860
|
+
const abs = import_node_path39.default.join(dir, filename);
|
|
7705
7861
|
if (!await exists(abs)) continue;
|
|
7706
7862
|
const raw = await import_node_fs21.promises.readFile(abs, "utf8");
|
|
7707
7863
|
const config = filename === "railway.toml" ? (0, import_smol_toml3.parse)(raw) : JSON.parse(maskCommentsInSource(raw));
|
|
@@ -7717,7 +7873,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
7717
7873
|
try {
|
|
7718
7874
|
read = await readRailwayConfig(service.dir);
|
|
7719
7875
|
} catch (err) {
|
|
7720
|
-
recordExtractionError("infra railway",
|
|
7876
|
+
recordExtractionError("infra railway", import_node_path39.default.relative(scanPath, service.dir), err);
|
|
7721
7877
|
continue;
|
|
7722
7878
|
}
|
|
7723
7879
|
if (!read) continue;
|
|
@@ -7727,7 +7883,7 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
7727
7883
|
}
|
|
7728
7884
|
const anchorId = service.node.id;
|
|
7729
7885
|
const { config, relFile, raw } = read;
|
|
7730
|
-
const evidenceFile = toPosix2(
|
|
7886
|
+
const evidenceFile = toPosix2(import_node_path39.default.relative(scanPath, import_node_path39.default.join(service.dir, relFile)));
|
|
7731
7887
|
const add = (edgeType, kind, name) => {
|
|
7732
7888
|
if (!name) return;
|
|
7733
7889
|
const result = emitPlatformResourceEdge(
|
|
@@ -7743,9 +7899,9 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
7743
7899
|
nodesAdded += result.nodesAdded;
|
|
7744
7900
|
edgesAdded += result.edgesAdded;
|
|
7745
7901
|
};
|
|
7746
|
-
add(
|
|
7747
|
-
add(
|
|
7748
|
-
add(
|
|
7902
|
+
add(import_types29.EdgeType.RUNS_ON, "railway", "railway");
|
|
7903
|
+
add(import_types29.EdgeType.CONNECTS_TO, "railway-route", config.deploy?.healthcheckPath);
|
|
7904
|
+
add(import_types29.EdgeType.DEPENDS_ON, "railway-cron", config.deploy?.cronSchedule);
|
|
7749
7905
|
}
|
|
7750
7906
|
return { nodesAdded, edgesAdded };
|
|
7751
7907
|
}
|
|
@@ -7753,12 +7909,12 @@ async function addRailwayServices(graph, services, scanPath) {
|
|
|
7753
7909
|
// src/extract/infra/supabase.ts
|
|
7754
7910
|
init_cjs_shims();
|
|
7755
7911
|
var import_node_fs22 = require("fs");
|
|
7756
|
-
var
|
|
7912
|
+
var import_node_path40 = __toESM(require("path"), 1);
|
|
7757
7913
|
var import_smol_toml4 = require("smol-toml");
|
|
7758
|
-
var
|
|
7914
|
+
var import_types30 = require("@neat.is/types");
|
|
7759
7915
|
async function readSupabaseConfig(dir) {
|
|
7760
|
-
const relFile =
|
|
7761
|
-
const abs =
|
|
7916
|
+
const relFile = import_node_path40.default.join("supabase", "config.toml");
|
|
7917
|
+
const abs = import_node_path40.default.join(dir, relFile);
|
|
7762
7918
|
if (!await exists(abs)) return null;
|
|
7763
7919
|
const raw = await import_node_fs22.promises.readFile(abs, "utf8");
|
|
7764
7920
|
const config = (0, import_smol_toml4.parse)(raw);
|
|
@@ -7772,7 +7928,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
7772
7928
|
try {
|
|
7773
7929
|
read = await readSupabaseConfig(service.dir);
|
|
7774
7930
|
} catch (err) {
|
|
7775
|
-
recordExtractionError("infra supabase",
|
|
7931
|
+
recordExtractionError("infra supabase", import_node_path40.default.relative(scanPath, service.dir), err);
|
|
7776
7932
|
continue;
|
|
7777
7933
|
}
|
|
7778
7934
|
if (!read) continue;
|
|
@@ -7787,7 +7943,7 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
7787
7943
|
});
|
|
7788
7944
|
}
|
|
7789
7945
|
const anchorId = service.node.id;
|
|
7790
|
-
const evidenceFile = toPosix2(
|
|
7946
|
+
const evidenceFile = toPosix2(import_node_path40.default.relative(scanPath, import_node_path40.default.join(service.dir, relFile)));
|
|
7791
7947
|
const add = (edgeType, kind, name) => {
|
|
7792
7948
|
if (!name) return;
|
|
7793
7949
|
const result = emitPlatformResourceEdge(
|
|
@@ -7803,10 +7959,10 @@ async function addSupabaseProjects(graph, services, scanPath) {
|
|
|
7803
7959
|
nodesAdded += result.nodesAdded;
|
|
7804
7960
|
edgesAdded += result.edgesAdded;
|
|
7805
7961
|
};
|
|
7806
|
-
add(
|
|
7807
|
-
for (const fn of Object.keys(config.functions ?? {})) add(
|
|
7808
|
-
if (config.storage) add(
|
|
7809
|
-
if (config.auth) add(
|
|
7962
|
+
add(import_types30.EdgeType.RUNS_ON, "supabase", "supabase");
|
|
7963
|
+
for (const fn of Object.keys(config.functions ?? {})) add(import_types30.EdgeType.DEPENDS_ON, "supabase-function", fn);
|
|
7964
|
+
if (config.storage) add(import_types30.EdgeType.DEPENDS_ON, "supabase-storage", "storage");
|
|
7965
|
+
if (config.auth) add(import_types30.EdgeType.DEPENDS_ON, "supabase-auth", "auth");
|
|
7810
7966
|
}
|
|
7811
7967
|
return { nodesAdded, edgesAdded };
|
|
7812
7968
|
}
|
|
@@ -7828,17 +7984,17 @@ async function addInfra(graph, scanPath, services) {
|
|
|
7828
7984
|
}
|
|
7829
7985
|
|
|
7830
7986
|
// src/extract/index.ts
|
|
7831
|
-
var
|
|
7987
|
+
var import_node_path42 = __toESM(require("path"), 1);
|
|
7832
7988
|
|
|
7833
7989
|
// src/extract/retire.ts
|
|
7834
7990
|
init_cjs_shims();
|
|
7835
7991
|
var import_node_fs23 = require("fs");
|
|
7836
|
-
var
|
|
7837
|
-
var
|
|
7992
|
+
var import_node_path41 = __toESM(require("path"), 1);
|
|
7993
|
+
var import_types31 = require("@neat.is/types");
|
|
7838
7994
|
function dropOrphanedFileNodes(graph) {
|
|
7839
7995
|
const orphans = [];
|
|
7840
7996
|
graph.forEachNode((id, attrs) => {
|
|
7841
|
-
if (attrs.type !==
|
|
7997
|
+
if (attrs.type !== import_types31.NodeType.FileNode) return;
|
|
7842
7998
|
if (graph.inboundEdges(id).length === 0 && graph.outboundEdges(id).length === 0) {
|
|
7843
7999
|
orphans.push(id);
|
|
7844
8000
|
}
|
|
@@ -7851,14 +8007,14 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
7851
8007
|
const bases = [scanPath, ...serviceDirs];
|
|
7852
8008
|
graph.forEachEdge((id, attrs) => {
|
|
7853
8009
|
const edge = attrs;
|
|
7854
|
-
if (edge.provenance !==
|
|
8010
|
+
if (edge.provenance !== import_types31.Provenance.EXTRACTED) return;
|
|
7855
8011
|
const evidenceFile = edge.evidence?.file;
|
|
7856
8012
|
if (!evidenceFile) return;
|
|
7857
|
-
if (
|
|
8013
|
+
if (import_node_path41.default.isAbsolute(evidenceFile)) {
|
|
7858
8014
|
if (!(0, import_node_fs23.existsSync)(evidenceFile)) toDrop.push(id);
|
|
7859
8015
|
return;
|
|
7860
8016
|
}
|
|
7861
|
-
const found = bases.some((base) => (0, import_node_fs23.existsSync)(
|
|
8017
|
+
const found = bases.some((base) => (0, import_node_fs23.existsSync)(import_node_path41.default.join(base, evidenceFile)));
|
|
7862
8018
|
if (!found) toDrop.push(id);
|
|
7863
8019
|
});
|
|
7864
8020
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -7900,7 +8056,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
7900
8056
|
}
|
|
7901
8057
|
const droppedEntries = drainDroppedExtracted();
|
|
7902
8058
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
7903
|
-
const rejectedPath =
|
|
8059
|
+
const rejectedPath = import_node_path42.default.join(import_node_path42.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
7904
8060
|
try {
|
|
7905
8061
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
7906
8062
|
} catch (err) {
|
|
@@ -7935,8 +8091,8 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
7935
8091
|
// src/persist.ts
|
|
7936
8092
|
init_cjs_shims();
|
|
7937
8093
|
var import_node_fs24 = require("fs");
|
|
7938
|
-
var
|
|
7939
|
-
var
|
|
8094
|
+
var import_node_path43 = __toESM(require("path"), 1);
|
|
8095
|
+
var import_types32 = require("@neat.is/types");
|
|
7940
8096
|
var SCHEMA_VERSION = 4;
|
|
7941
8097
|
function migrateV1ToV2(payload) {
|
|
7942
8098
|
const nodes = payload.graph.nodes;
|
|
@@ -7958,12 +8114,12 @@ function migrateV2ToV3(payload) {
|
|
|
7958
8114
|
for (const edge of edges) {
|
|
7959
8115
|
const attrs = edge.attributes;
|
|
7960
8116
|
if (!attrs || attrs.provenance !== "FRONTIER") continue;
|
|
7961
|
-
attrs.provenance =
|
|
8117
|
+
attrs.provenance = import_types32.Provenance.OBSERVED;
|
|
7962
8118
|
const type = typeof attrs.type === "string" ? attrs.type : void 0;
|
|
7963
8119
|
const source = typeof attrs.source === "string" ? attrs.source : void 0;
|
|
7964
8120
|
const target = typeof attrs.target === "string" ? attrs.target : void 0;
|
|
7965
8121
|
if (type && source && target) {
|
|
7966
|
-
const newId = (0,
|
|
8122
|
+
const newId = (0, import_types32.observedEdgeId)(source, target, type);
|
|
7967
8123
|
attrs.id = newId;
|
|
7968
8124
|
if (edge.key) edge.key = newId;
|
|
7969
8125
|
}
|
|
@@ -7972,7 +8128,7 @@ function migrateV2ToV3(payload) {
|
|
|
7972
8128
|
return { ...payload, schemaVersion: 3 };
|
|
7973
8129
|
}
|
|
7974
8130
|
async function ensureDir(filePath) {
|
|
7975
|
-
await import_node_fs24.promises.mkdir(
|
|
8131
|
+
await import_node_fs24.promises.mkdir(import_node_path43.default.dirname(filePath), { recursive: true });
|
|
7976
8132
|
}
|
|
7977
8133
|
async function saveGraphToDisk(graph, outPath) {
|
|
7978
8134
|
await ensureDir(outPath);
|
|
@@ -8053,23 +8209,23 @@ function startPersistLoop(graph, outPath, opts = {}) {
|
|
|
8053
8209
|
|
|
8054
8210
|
// src/projects.ts
|
|
8055
8211
|
init_cjs_shims();
|
|
8056
|
-
var
|
|
8212
|
+
var import_node_path44 = __toESM(require("path"), 1);
|
|
8057
8213
|
function pathsForProject(project, baseDir) {
|
|
8058
8214
|
if (project === DEFAULT_PROJECT) {
|
|
8059
8215
|
return {
|
|
8060
|
-
snapshotPath:
|
|
8061
|
-
errorsPath:
|
|
8062
|
-
staleEventsPath:
|
|
8063
|
-
embeddingsCachePath:
|
|
8064
|
-
policyViolationsPath:
|
|
8216
|
+
snapshotPath: import_node_path44.default.join(baseDir, "graph.json"),
|
|
8217
|
+
errorsPath: import_node_path44.default.join(baseDir, "errors.ndjson"),
|
|
8218
|
+
staleEventsPath: import_node_path44.default.join(baseDir, "stale-events.ndjson"),
|
|
8219
|
+
embeddingsCachePath: import_node_path44.default.join(baseDir, "embeddings.json"),
|
|
8220
|
+
policyViolationsPath: import_node_path44.default.join(baseDir, "policy-violations.ndjson")
|
|
8065
8221
|
};
|
|
8066
8222
|
}
|
|
8067
8223
|
return {
|
|
8068
|
-
snapshotPath:
|
|
8069
|
-
errorsPath:
|
|
8070
|
-
staleEventsPath:
|
|
8071
|
-
embeddingsCachePath:
|
|
8072
|
-
policyViolationsPath:
|
|
8224
|
+
snapshotPath: import_node_path44.default.join(baseDir, `${project}.json`),
|
|
8225
|
+
errorsPath: import_node_path44.default.join(baseDir, `errors.${project}.ndjson`),
|
|
8226
|
+
staleEventsPath: import_node_path44.default.join(baseDir, `stale-events.${project}.ndjson`),
|
|
8227
|
+
embeddingsCachePath: import_node_path44.default.join(baseDir, `embeddings.${project}.json`),
|
|
8228
|
+
policyViolationsPath: import_node_path44.default.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
8073
8229
|
};
|
|
8074
8230
|
}
|
|
8075
8231
|
var Projects = class {
|
|
@@ -8107,19 +8263,19 @@ var Projects = class {
|
|
|
8107
8263
|
init_cjs_shims();
|
|
8108
8264
|
var import_fastify = __toESM(require("fastify"), 1);
|
|
8109
8265
|
var import_cors = __toESM(require("@fastify/cors"), 1);
|
|
8110
|
-
var
|
|
8266
|
+
var import_types35 = require("@neat.is/types");
|
|
8111
8267
|
|
|
8112
8268
|
// src/extend/index.ts
|
|
8113
8269
|
init_cjs_shims();
|
|
8114
8270
|
var import_node_fs26 = require("fs");
|
|
8115
|
-
var
|
|
8271
|
+
var import_node_path46 = __toESM(require("path"), 1);
|
|
8116
8272
|
var import_node_os2 = __toESM(require("os"), 1);
|
|
8117
8273
|
var import_instrumentation_registry = require("@neat.is/instrumentation-registry");
|
|
8118
8274
|
|
|
8119
8275
|
// src/installers/package-manager.ts
|
|
8120
8276
|
init_cjs_shims();
|
|
8121
8277
|
var import_node_fs25 = require("fs");
|
|
8122
|
-
var
|
|
8278
|
+
var import_node_path45 = __toESM(require("path"), 1);
|
|
8123
8279
|
var import_node_child_process = require("child_process");
|
|
8124
8280
|
var LOCKFILE_PRIORITY = [
|
|
8125
8281
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -8141,22 +8297,22 @@ async function exists2(p) {
|
|
|
8141
8297
|
}
|
|
8142
8298
|
}
|
|
8143
8299
|
async function detectPackageManager(serviceDir) {
|
|
8144
|
-
let dir =
|
|
8300
|
+
let dir = import_node_path45.default.resolve(serviceDir);
|
|
8145
8301
|
const stops = /* @__PURE__ */ new Set();
|
|
8146
8302
|
for (let i = 0; i < 64; i++) {
|
|
8147
8303
|
if (stops.has(dir)) break;
|
|
8148
8304
|
stops.add(dir);
|
|
8149
8305
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
8150
|
-
const lockPath =
|
|
8306
|
+
const lockPath = import_node_path45.default.join(dir, candidate.lockfile);
|
|
8151
8307
|
if (await exists2(lockPath)) {
|
|
8152
8308
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
8153
8309
|
}
|
|
8154
8310
|
}
|
|
8155
|
-
const parent =
|
|
8311
|
+
const parent = import_node_path45.default.dirname(dir);
|
|
8156
8312
|
if (parent === dir) break;
|
|
8157
8313
|
dir = parent;
|
|
8158
8314
|
}
|
|
8159
|
-
return { pm: "npm", cwd:
|
|
8315
|
+
return { pm: "npm", cwd: import_node_path45.default.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
8160
8316
|
}
|
|
8161
8317
|
async function runPackageManagerInstall(cmd) {
|
|
8162
8318
|
return new Promise((resolve) => {
|
|
@@ -8205,7 +8361,7 @@ async function fileExists2(p) {
|
|
|
8205
8361
|
}
|
|
8206
8362
|
}
|
|
8207
8363
|
async function readPackageJson(scanPath) {
|
|
8208
|
-
const pkgPath =
|
|
8364
|
+
const pkgPath = import_node_path46.default.join(scanPath, "package.json");
|
|
8209
8365
|
const raw = await import_node_fs26.promises.readFile(pkgPath, "utf8");
|
|
8210
8366
|
return JSON.parse(raw);
|
|
8211
8367
|
}
|
|
@@ -8219,27 +8375,27 @@ var HOOK_WALK_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
|
8219
8375
|
]);
|
|
8220
8376
|
async function findHookFiles(scanPath) {
|
|
8221
8377
|
const found = [];
|
|
8222
|
-
const
|
|
8378
|
+
const walk4 = async (dir) => {
|
|
8223
8379
|
const entries = await import_node_fs26.promises.readdir(dir, { withFileTypes: true }).catch(() => []);
|
|
8224
8380
|
for (const entry2 of entries) {
|
|
8225
8381
|
if (entry2.isDirectory()) {
|
|
8226
8382
|
if (entry2.name.startsWith(".") || HOOK_WALK_SKIP_DIRS.has(entry2.name)) continue;
|
|
8227
|
-
await
|
|
8383
|
+
await walk4(import_node_path46.default.join(dir, entry2.name));
|
|
8228
8384
|
} else if (entry2.isFile()) {
|
|
8229
8385
|
if ((entry2.name.startsWith("instrumentation") || entry2.name.startsWith("otel-init")) && /\.(ts|js|cjs|mjs)$/.test(entry2.name)) {
|
|
8230
|
-
const rel =
|
|
8231
|
-
found.push(rel.split(
|
|
8386
|
+
const rel = import_node_path46.default.relative(scanPath, import_node_path46.default.join(dir, entry2.name));
|
|
8387
|
+
found.push(rel.split(import_node_path46.default.sep).join("/"));
|
|
8232
8388
|
}
|
|
8233
8389
|
}
|
|
8234
8390
|
}
|
|
8235
8391
|
};
|
|
8236
|
-
await
|
|
8392
|
+
await walk4(scanPath);
|
|
8237
8393
|
return found.sort();
|
|
8238
8394
|
}
|
|
8239
8395
|
async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
8240
8396
|
let fallback = null;
|
|
8241
8397
|
for (const file of hookFiles) {
|
|
8242
|
-
const content = await import_node_fs26.promises.readFile(
|
|
8398
|
+
const content = await import_node_fs26.promises.readFile(import_node_path46.default.join(scanPath, file), "utf8");
|
|
8243
8399
|
const patched = splicedContent(content, snippet2);
|
|
8244
8400
|
if (patched !== null) return { file, content, patched };
|
|
8245
8401
|
if (fallback === null) fallback = { file, content };
|
|
@@ -8247,11 +8403,11 @@ async function pickPrimaryHookFile(scanPath, hookFiles, snippet2) {
|
|
|
8247
8403
|
return { file: fallback.file, content: fallback.content, patched: null };
|
|
8248
8404
|
}
|
|
8249
8405
|
function extendLogPath() {
|
|
8250
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
8406
|
+
return process.env.NEAT_EXTEND_LOG ?? import_node_path46.default.join(import_node_os2.default.homedir(), ".neat", "extend-log.ndjson");
|
|
8251
8407
|
}
|
|
8252
8408
|
async function appendExtendLog(entry2) {
|
|
8253
8409
|
const logPath = extendLogPath();
|
|
8254
|
-
await import_node_fs26.promises.mkdir(
|
|
8410
|
+
await import_node_fs26.promises.mkdir(import_node_path46.default.dirname(logPath), { recursive: true });
|
|
8255
8411
|
await import_node_fs26.promises.appendFile(logPath, JSON.stringify(entry2) + "\n", "utf8");
|
|
8256
8412
|
}
|
|
8257
8413
|
function splicedContent(fileContent, snippet2) {
|
|
@@ -8310,7 +8466,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
8310
8466
|
}
|
|
8311
8467
|
async function describeProjectInstrumentation(ctx) {
|
|
8312
8468
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
8313
|
-
const envNeat = await fileExists2(
|
|
8469
|
+
const envNeat = await fileExists2(import_node_path46.default.join(ctx.scanPath, ".env.neat"));
|
|
8314
8470
|
const registryInstrPackages = new Set(
|
|
8315
8471
|
(0, import_instrumentation_registry.list)().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
8316
8472
|
);
|
|
@@ -8332,7 +8488,7 @@ async function applyExtension(ctx, args, options) {
|
|
|
8332
8488
|
);
|
|
8333
8489
|
}
|
|
8334
8490
|
for (const file of hookFiles) {
|
|
8335
|
-
const content = await import_node_fs26.promises.readFile(
|
|
8491
|
+
const content = await import_node_fs26.promises.readFile(import_node_path46.default.join(ctx.scanPath, file), "utf8");
|
|
8336
8492
|
if (content.includes(args.registration_snippet)) {
|
|
8337
8493
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
8338
8494
|
}
|
|
@@ -8344,10 +8500,10 @@ async function applyExtension(ctx, args, options) {
|
|
|
8344
8500
|
);
|
|
8345
8501
|
}
|
|
8346
8502
|
const primaryFile = primary.file;
|
|
8347
|
-
const primaryPath =
|
|
8503
|
+
const primaryPath = import_node_path46.default.join(ctx.scanPath, primaryFile);
|
|
8348
8504
|
const filesTouched = [];
|
|
8349
8505
|
const depsAdded = [];
|
|
8350
|
-
const pkgPath =
|
|
8506
|
+
const pkgPath = import_node_path46.default.join(ctx.scanPath, "package.json");
|
|
8351
8507
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
8352
8508
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
8353
8509
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
@@ -8386,7 +8542,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
8386
8542
|
};
|
|
8387
8543
|
}
|
|
8388
8544
|
for (const file of hookFiles) {
|
|
8389
|
-
const content = await import_node_fs26.promises.readFile(
|
|
8545
|
+
const content = await import_node_fs26.promises.readFile(import_node_path46.default.join(ctx.scanPath, file), "utf8");
|
|
8390
8546
|
if (content.includes(args.registration_snippet)) {
|
|
8391
8547
|
return {
|
|
8392
8548
|
library: args.library,
|
|
@@ -8427,7 +8583,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
8427
8583
|
if (!match) {
|
|
8428
8584
|
return { undone: false, message: "no apply found for library" };
|
|
8429
8585
|
}
|
|
8430
|
-
const pkgPath =
|
|
8586
|
+
const pkgPath = import_node_path46.default.join(ctx.scanPath, "package.json");
|
|
8431
8587
|
if (await fileExists2(pkgPath)) {
|
|
8432
8588
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
8433
8589
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
@@ -8438,7 +8594,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
8438
8594
|
}
|
|
8439
8595
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
8440
8596
|
for (const file of hookFiles) {
|
|
8441
|
-
const filePath =
|
|
8597
|
+
const filePath = import_node_path46.default.join(ctx.scanPath, file);
|
|
8442
8598
|
const content = await import_node_fs26.promises.readFile(filePath, "utf8");
|
|
8443
8599
|
if (content.includes(match.registration_snippet)) {
|
|
8444
8600
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
@@ -8454,39 +8610,39 @@ async function rollbackExtension(ctx, args) {
|
|
|
8454
8610
|
|
|
8455
8611
|
// src/divergences.ts
|
|
8456
8612
|
init_cjs_shims();
|
|
8457
|
-
var
|
|
8613
|
+
var import_types33 = require("@neat.is/types");
|
|
8458
8614
|
function bucketKey(source, target, type) {
|
|
8459
8615
|
return `${type}|${source}|${target}`;
|
|
8460
8616
|
}
|
|
8461
8617
|
function bucketSourceFor(graph, edge) {
|
|
8462
|
-
if (edge.type !==
|
|
8463
|
-
const parsed = (0,
|
|
8618
|
+
if (edge.type !== import_types33.EdgeType.CONNECTS_TO) return edge.source;
|
|
8619
|
+
const parsed = (0, import_types33.parseFileId)(edge.source);
|
|
8464
8620
|
if (!parsed || !graph.hasNode(edge.target)) return edge.source;
|
|
8465
8621
|
const target = graph.getNodeAttributes(edge.target);
|
|
8466
|
-
if (target.type !==
|
|
8467
|
-
return (0,
|
|
8622
|
+
if (target.type !== import_types33.NodeType.DatabaseNode) return edge.source;
|
|
8623
|
+
return (0, import_types33.serviceId)(parsed.service);
|
|
8468
8624
|
}
|
|
8469
8625
|
function bucketEdges(graph) {
|
|
8470
8626
|
const buckets2 = /* @__PURE__ */ new Map();
|
|
8471
8627
|
graph.forEachEdge((id, attrs) => {
|
|
8472
8628
|
const e = attrs;
|
|
8473
|
-
const parsed = (0,
|
|
8629
|
+
const parsed = (0, import_types33.parseEdgeId)(id);
|
|
8474
8630
|
const provenance = parsed?.provenance ?? e.provenance;
|
|
8475
8631
|
const source = bucketSourceFor(graph, e);
|
|
8476
8632
|
const key = bucketKey(source, e.target, e.type);
|
|
8477
8633
|
const cur = buckets2.get(key) ?? { source, target: e.target, type: e.type };
|
|
8478
8634
|
switch (provenance) {
|
|
8479
|
-
case
|
|
8635
|
+
case import_types33.Provenance.EXTRACTED:
|
|
8480
8636
|
cur.extracted = e;
|
|
8481
8637
|
break;
|
|
8482
|
-
case
|
|
8638
|
+
case import_types33.Provenance.OBSERVED:
|
|
8483
8639
|
cur.observed = e;
|
|
8484
8640
|
break;
|
|
8485
|
-
case
|
|
8641
|
+
case import_types33.Provenance.INFERRED:
|
|
8486
8642
|
cur.inferred = e;
|
|
8487
8643
|
break;
|
|
8488
8644
|
default:
|
|
8489
|
-
if (e.provenance ===
|
|
8645
|
+
if (e.provenance === import_types33.Provenance.STALE) cur.stale = e;
|
|
8490
8646
|
}
|
|
8491
8647
|
buckets2.set(key, cur);
|
|
8492
8648
|
});
|
|
@@ -8495,12 +8651,12 @@ function bucketEdges(graph) {
|
|
|
8495
8651
|
function nodeIsFrontier(graph, nodeId) {
|
|
8496
8652
|
if (!graph.hasNode(nodeId)) return false;
|
|
8497
8653
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
8498
|
-
return attrs.type ===
|
|
8654
|
+
return attrs.type === import_types33.NodeType.FrontierNode;
|
|
8499
8655
|
}
|
|
8500
8656
|
function nodeIsWebsocketChannel(graph, nodeId) {
|
|
8501
8657
|
if (!graph.hasNode(nodeId)) return false;
|
|
8502
8658
|
const attrs = graph.getNodeAttributes(nodeId);
|
|
8503
|
-
return attrs.type ===
|
|
8659
|
+
return attrs.type === import_types33.NodeType.WebSocketChannelNode;
|
|
8504
8660
|
}
|
|
8505
8661
|
function clampConfidence(n) {
|
|
8506
8662
|
if (!Number.isFinite(n)) return 0;
|
|
@@ -8520,14 +8676,14 @@ function gradedConfidence(edge) {
|
|
|
8520
8676
|
return clampConfidence(confidenceForEdge(edge));
|
|
8521
8677
|
}
|
|
8522
8678
|
var OBSERVABLE_EDGE_TYPES = /* @__PURE__ */ new Set([
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8679
|
+
import_types33.EdgeType.CALLS,
|
|
8680
|
+
import_types33.EdgeType.CONNECTS_TO,
|
|
8681
|
+
import_types33.EdgeType.PUBLISHES_TO,
|
|
8682
|
+
import_types33.EdgeType.CONSUMES_FROM
|
|
8527
8683
|
]);
|
|
8528
8684
|
function detectMissingDivergences(graph, bucket) {
|
|
8529
8685
|
const out = [];
|
|
8530
|
-
if (bucket.type ===
|
|
8686
|
+
if (bucket.type === import_types33.EdgeType.CONTAINS) return out;
|
|
8531
8687
|
if (bucket.extracted && !bucket.observed && OBSERVABLE_EDGE_TYPES.has(bucket.type)) {
|
|
8532
8688
|
if (!nodeIsFrontier(graph, bucket.target)) {
|
|
8533
8689
|
out.push({
|
|
@@ -8568,7 +8724,7 @@ function declaredHostFor(svc) {
|
|
|
8568
8724
|
function hasExtractedConfiguredBy(graph, svcId) {
|
|
8569
8725
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
8570
8726
|
const e = graph.getEdgeAttributes(edgeId);
|
|
8571
|
-
if (e.type ===
|
|
8727
|
+
if (e.type === import_types33.EdgeType.CONFIGURED_BY && e.provenance === import_types33.Provenance.EXTRACTED) {
|
|
8572
8728
|
return true;
|
|
8573
8729
|
}
|
|
8574
8730
|
}
|
|
@@ -8581,10 +8737,10 @@ function detectHostMismatch(graph, svcId, svc) {
|
|
|
8581
8737
|
const out = [];
|
|
8582
8738
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
8583
8739
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
8584
|
-
if (edge.type !==
|
|
8585
|
-
if (edge.provenance !==
|
|
8740
|
+
if (edge.type !== import_types33.EdgeType.CONNECTS_TO) continue;
|
|
8741
|
+
if (edge.provenance !== import_types33.Provenance.OBSERVED) continue;
|
|
8586
8742
|
const target = graph.getNodeAttributes(edge.target);
|
|
8587
|
-
if (target.type !==
|
|
8743
|
+
if (target.type !== import_types33.NodeType.DatabaseNode) continue;
|
|
8588
8744
|
const observedHost = target.host?.trim();
|
|
8589
8745
|
if (!observedHost) continue;
|
|
8590
8746
|
if (observedHost === declaredHost) continue;
|
|
@@ -8606,10 +8762,10 @@ function detectCompatDivergences(graph, svcId, svc) {
|
|
|
8606
8762
|
const deps = svc.dependencies ?? {};
|
|
8607
8763
|
for (const edgeId of graph.outboundEdges(svcId)) {
|
|
8608
8764
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
8609
|
-
if (edge.type !==
|
|
8610
|
-
if (edge.provenance !==
|
|
8765
|
+
if (edge.type !== import_types33.EdgeType.CONNECTS_TO) continue;
|
|
8766
|
+
if (edge.provenance !== import_types33.Provenance.OBSERVED) continue;
|
|
8611
8767
|
const target = graph.getNodeAttributes(edge.target);
|
|
8612
|
-
if (target.type !==
|
|
8768
|
+
if (target.type !== import_types33.NodeType.DatabaseNode) continue;
|
|
8613
8769
|
for (const pair of compatPairs()) {
|
|
8614
8770
|
if (pair.engine !== target.engine) continue;
|
|
8615
8771
|
const declared = deps[pair.driver];
|
|
@@ -8668,7 +8824,7 @@ function suppressHostMismatchHalves(all) {
|
|
|
8668
8824
|
for (const d of all) {
|
|
8669
8825
|
if (d.type !== "host-mismatch") continue;
|
|
8670
8826
|
observedHalf.add(`${d.source}->${d.target}`);
|
|
8671
|
-
declaredHalf.add((0,
|
|
8827
|
+
declaredHalf.add((0, import_types33.databaseId)(d.extractedHost));
|
|
8672
8828
|
}
|
|
8673
8829
|
if (observedHalf.size === 0) return all;
|
|
8674
8830
|
return all.filter((d) => {
|
|
@@ -8687,7 +8843,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
8687
8843
|
}
|
|
8688
8844
|
graph.forEachNode((nodeId, attrs) => {
|
|
8689
8845
|
const n = attrs;
|
|
8690
|
-
if (n.type !==
|
|
8846
|
+
if (n.type !== import_types33.NodeType.ServiceNode) return;
|
|
8691
8847
|
const svc = n;
|
|
8692
8848
|
for (const d of detectHostMismatch(graph, nodeId, svc)) all.push(d);
|
|
8693
8849
|
for (const d of detectCompatDivergences(graph, nodeId, svc)) all.push(d);
|
|
@@ -8721,7 +8877,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
8721
8877
|
if (a.source !== b.source) return a.source.localeCompare(b.source);
|
|
8722
8878
|
return a.target.localeCompare(b.target);
|
|
8723
8879
|
});
|
|
8724
|
-
return
|
|
8880
|
+
return import_types33.DivergenceResultSchema.parse({
|
|
8725
8881
|
divergences: filtered,
|
|
8726
8882
|
totalAffected: filtered.length,
|
|
8727
8883
|
computedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -8854,23 +9010,23 @@ function canonicalJson(value) {
|
|
|
8854
9010
|
init_cjs_shims();
|
|
8855
9011
|
var import_node_fs28 = require("fs");
|
|
8856
9012
|
var import_node_os3 = __toESM(require("os"), 1);
|
|
8857
|
-
var
|
|
8858
|
-
var
|
|
9013
|
+
var import_node_path47 = __toESM(require("path"), 1);
|
|
9014
|
+
var import_types34 = require("@neat.is/types");
|
|
8859
9015
|
var LOCK_TIMEOUT_MS = 5e3;
|
|
8860
9016
|
var LOCK_RETRY_MS = 50;
|
|
8861
9017
|
function neatHome() {
|
|
8862
9018
|
const override = process.env.NEAT_HOME;
|
|
8863
|
-
if (override && override.length > 0) return
|
|
8864
|
-
return
|
|
9019
|
+
if (override && override.length > 0) return import_node_path47.default.resolve(override);
|
|
9020
|
+
return import_node_path47.default.join(import_node_os3.default.homedir(), ".neat");
|
|
8865
9021
|
}
|
|
8866
9022
|
function registryPath() {
|
|
8867
|
-
return
|
|
9023
|
+
return import_node_path47.default.join(neatHome(), "projects.json");
|
|
8868
9024
|
}
|
|
8869
9025
|
function registryLockPath() {
|
|
8870
|
-
return
|
|
9026
|
+
return import_node_path47.default.join(neatHome(), "projects.json.lock");
|
|
8871
9027
|
}
|
|
8872
9028
|
function daemonPidPath() {
|
|
8873
|
-
return
|
|
9029
|
+
return import_node_path47.default.join(neatHome(), "neatd.pid");
|
|
8874
9030
|
}
|
|
8875
9031
|
function isPidAliveDefault(pid) {
|
|
8876
9032
|
try {
|
|
@@ -8930,7 +9086,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
8930
9086
|
}
|
|
8931
9087
|
}
|
|
8932
9088
|
async function writeAtomically(target, contents) {
|
|
8933
|
-
await import_node_fs28.promises.mkdir(
|
|
9089
|
+
await import_node_fs28.promises.mkdir(import_node_path47.default.dirname(target), { recursive: true });
|
|
8934
9090
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
8935
9091
|
const fd = await import_node_fs28.promises.open(tmp, "w");
|
|
8936
9092
|
try {
|
|
@@ -8943,7 +9099,7 @@ async function writeAtomically(target, contents) {
|
|
|
8943
9099
|
}
|
|
8944
9100
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
8945
9101
|
const deadline = Date.now() + timeoutMs;
|
|
8946
|
-
await import_node_fs28.promises.mkdir(
|
|
9102
|
+
await import_node_fs28.promises.mkdir(import_node_path47.default.dirname(lockPath), { recursive: true });
|
|
8947
9103
|
let probedHolder = false;
|
|
8948
9104
|
while (true) {
|
|
8949
9105
|
try {
|
|
@@ -8996,10 +9152,10 @@ async function readRegistry() {
|
|
|
8996
9152
|
throw err;
|
|
8997
9153
|
}
|
|
8998
9154
|
const parsed = JSON.parse(raw);
|
|
8999
|
-
return
|
|
9155
|
+
return import_types34.RegistryFileSchema.parse(parsed);
|
|
9000
9156
|
}
|
|
9001
9157
|
async function writeRegistry(reg) {
|
|
9002
|
-
const validated =
|
|
9158
|
+
const validated = import_types34.RegistryFileSchema.parse(reg);
|
|
9003
9159
|
await writeAtomically(registryPath(), JSON.stringify(validated, null, 2) + "\n");
|
|
9004
9160
|
}
|
|
9005
9161
|
async function getProject(name) {
|
|
@@ -9144,7 +9300,7 @@ init_auth();
|
|
|
9144
9300
|
// src/connectors-config.ts
|
|
9145
9301
|
init_cjs_shims();
|
|
9146
9302
|
var import_node_os4 = __toESM(require("os"), 1);
|
|
9147
|
-
var
|
|
9303
|
+
var import_node_path48 = __toESM(require("path"), 1);
|
|
9148
9304
|
var import_node_fs29 = require("fs");
|
|
9149
9305
|
var CONNECTORS_CONFIG_VERSION = 1;
|
|
9150
9306
|
var EnvRefUnsetError = class extends Error {
|
|
@@ -9159,11 +9315,11 @@ var EnvRefUnsetError = class extends Error {
|
|
|
9159
9315
|
};
|
|
9160
9316
|
function neatHome2() {
|
|
9161
9317
|
const override = process.env.NEAT_HOME;
|
|
9162
|
-
if (override && override.length > 0) return
|
|
9163
|
-
return
|
|
9318
|
+
if (override && override.length > 0) return import_node_path48.default.resolve(override);
|
|
9319
|
+
return import_node_path48.default.join(import_node_os4.default.homedir(), ".neat");
|
|
9164
9320
|
}
|
|
9165
9321
|
function connectorsConfigPath(home = neatHome2()) {
|
|
9166
|
-
return
|
|
9322
|
+
return import_node_path48.default.join(home, "connectors.json");
|
|
9167
9323
|
}
|
|
9168
9324
|
var MODE_MASK_LOOSER_THAN_0600 = 63;
|
|
9169
9325
|
async function warnIfModeLooserThan0600(file) {
|
|
@@ -9499,11 +9655,11 @@ function registerRoutes(scope, ctx) {
|
|
|
9499
9655
|
const candidates = req2.query.type.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
9500
9656
|
const parsed = [];
|
|
9501
9657
|
for (const c of candidates) {
|
|
9502
|
-
const r =
|
|
9658
|
+
const r = import_types35.DivergenceTypeSchema.safeParse(c);
|
|
9503
9659
|
if (!r.success) {
|
|
9504
9660
|
return reply.code(400).send({
|
|
9505
9661
|
error: `unknown divergence type "${c}"`,
|
|
9506
|
-
allowed:
|
|
9662
|
+
allowed: import_types35.DivergenceTypeSchema.options
|
|
9507
9663
|
});
|
|
9508
9664
|
}
|
|
9509
9665
|
parsed.push(r.data);
|
|
@@ -9774,7 +9930,7 @@ function registerRoutes(scope, ctx) {
|
|
|
9774
9930
|
const log = new PolicyViolationsLog(proj.paths.policyViolationsPath);
|
|
9775
9931
|
let violations = await log.readAll();
|
|
9776
9932
|
if (req2.query.severity) {
|
|
9777
|
-
const sev =
|
|
9933
|
+
const sev = import_types35.PolicySeveritySchema.safeParse(req2.query.severity);
|
|
9778
9934
|
if (!sev.success) {
|
|
9779
9935
|
return reply.code(400).send({
|
|
9780
9936
|
error: "invalid severity",
|
|
@@ -9813,7 +9969,7 @@ function registerRoutes(scope, ctx) {
|
|
|
9813
9969
|
scope.post("/policies/check", async (req2, reply) => {
|
|
9814
9970
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
9815
9971
|
if (!proj) return;
|
|
9816
|
-
const parsed =
|
|
9972
|
+
const parsed = import_types35.PoliciesCheckBodySchema.safeParse(req2.body ?? {});
|
|
9817
9973
|
if (!parsed.success) {
|
|
9818
9974
|
return reply.code(400).send({
|
|
9819
9975
|
error: "invalid /policies/check body",
|
|
@@ -10113,15 +10269,15 @@ init_otel();
|
|
|
10113
10269
|
|
|
10114
10270
|
// src/connectors/index.ts
|
|
10115
10271
|
init_cjs_shims();
|
|
10116
|
-
var
|
|
10272
|
+
var import_types36 = require("@neat.is/types");
|
|
10117
10273
|
var NO_ENV = "unknown";
|
|
10118
10274
|
function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
10119
10275
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
10120
10276
|
const sites = [];
|
|
10121
10277
|
for (const edgeId of graph.inboundEdges(targetNodeId)) {
|
|
10122
10278
|
const edge = graph.getEdgeAttributes(edgeId);
|
|
10123
|
-
if (edge.provenance !==
|
|
10124
|
-
const parsed = (0,
|
|
10279
|
+
if (edge.provenance !== import_types36.Provenance.EXTRACTED) continue;
|
|
10280
|
+
const parsed = (0, import_types36.parseFileId)(edge.source);
|
|
10125
10281
|
if (!parsed || parsed.service !== serviceName || !edge.evidence) continue;
|
|
10126
10282
|
const site = { relPath: edge.evidence.file };
|
|
10127
10283
|
if (edge.evidence.line !== void 0) site.line = edge.evidence.line;
|
|
@@ -10132,7 +10288,7 @@ function staticCallSiteFor(graph, serviceName, targetNodeId) {
|
|
|
10132
10288
|
function routeCallSiteFor(graph, targetNodeId) {
|
|
10133
10289
|
if (!graph.hasNode(targetNodeId)) return void 0;
|
|
10134
10290
|
const attrs = graph.getNodeAttributes(targetNodeId);
|
|
10135
|
-
if (attrs.type !==
|
|
10291
|
+
if (attrs.type !== import_types36.NodeType.RouteNode || !attrs.path) return void 0;
|
|
10136
10292
|
const site = { relPath: attrs.path };
|
|
10137
10293
|
if (attrs.line !== void 0) site.line = attrs.line;
|
|
10138
10294
|
return site;
|
|
@@ -10603,10 +10759,10 @@ var SUPABASE_RPC_TARGET_KIND = "supabase-rpc";
|
|
|
10603
10759
|
// src/connectors/supabase/map.ts
|
|
10604
10760
|
var REST_RPC_PATH_RE = /^\/rest\/v1\/rpc\/([^/?]+)/;
|
|
10605
10761
|
var REST_TABLE_PATH_RE = /^\/rest\/v1\/([^/?]+)/;
|
|
10606
|
-
function targetFromRestPath(
|
|
10607
|
-
const rpcMatch = REST_RPC_PATH_RE.exec(
|
|
10762
|
+
function targetFromRestPath(path55) {
|
|
10763
|
+
const rpcMatch = REST_RPC_PATH_RE.exec(path55);
|
|
10608
10764
|
if (rpcMatch) return { targetKind: SUPABASE_RPC_TARGET_KIND, name: rpcMatch[1] };
|
|
10609
|
-
const tableMatch = REST_TABLE_PATH_RE.exec(
|
|
10765
|
+
const tableMatch = REST_TABLE_PATH_RE.exec(path55);
|
|
10610
10766
|
if (tableMatch) return { targetKind: SUPABASE_TABLE_TARGET_KIND, name: tableMatch[1] };
|
|
10611
10767
|
return null;
|
|
10612
10768
|
}
|
|
@@ -10715,23 +10871,23 @@ async function fetchPgStatStatements(connectionString, limit = DEFAULT_STATEMENT
|
|
|
10715
10871
|
|
|
10716
10872
|
// src/connectors/supabase/resolve.ts
|
|
10717
10873
|
init_cjs_shims();
|
|
10718
|
-
var
|
|
10874
|
+
var import_types38 = require("@neat.is/types");
|
|
10719
10875
|
function createSupabaseResolveTarget(graph, config) {
|
|
10720
10876
|
return (signal, _ctx) => {
|
|
10721
10877
|
if (signal.targetKind !== SUPABASE_TABLE_TARGET_KIND && signal.targetKind !== SUPABASE_RPC_TARGET_KIND) {
|
|
10722
10878
|
return null;
|
|
10723
10879
|
}
|
|
10724
|
-
const subResourceId = (0,
|
|
10880
|
+
const subResourceId = (0, import_types38.infraId)(signal.targetKind, `${config.nodeRef}/${signal.targetName}`);
|
|
10725
10881
|
if (graph.hasNode(subResourceId)) {
|
|
10726
|
-
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType:
|
|
10882
|
+
return { targetNodeId: subResourceId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
|
|
10727
10883
|
}
|
|
10728
|
-
const bareResourceId = (0,
|
|
10884
|
+
const bareResourceId = (0, import_types38.infraId)(signal.targetKind, signal.targetName);
|
|
10729
10885
|
if (graph.hasNode(bareResourceId)) {
|
|
10730
|
-
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType:
|
|
10886
|
+
return { targetNodeId: bareResourceId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
|
|
10731
10887
|
}
|
|
10732
|
-
const projectLevelId = (0,
|
|
10888
|
+
const projectLevelId = (0, import_types38.infraId)("supabase", config.nodeRef);
|
|
10733
10889
|
if (graph.hasNode(projectLevelId)) {
|
|
10734
|
-
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType:
|
|
10890
|
+
return { targetNodeId: projectLevelId, serviceName: config.serviceName, edgeType: import_types38.EdgeType.CALLS };
|
|
10735
10891
|
}
|
|
10736
10892
|
return null;
|
|
10737
10893
|
};
|
|
@@ -10824,7 +10980,7 @@ function createSupabaseConnector(graph, config, deps = {}) {
|
|
|
10824
10980
|
|
|
10825
10981
|
// src/connectors/railway/index.ts
|
|
10826
10982
|
init_cjs_shims();
|
|
10827
|
-
var
|
|
10983
|
+
var import_types42 = require("@neat.is/types");
|
|
10828
10984
|
|
|
10829
10985
|
// src/connectors/railway/client.ts
|
|
10830
10986
|
init_cjs_shims();
|
|
@@ -10950,7 +11106,7 @@ function buildRailwayRouteIndex(graph, serviceName) {
|
|
|
10950
11106
|
const out = [];
|
|
10951
11107
|
graph.forEachNode((_id, attrs) => {
|
|
10952
11108
|
const node = attrs;
|
|
10953
|
-
if (node.type !==
|
|
11109
|
+
if (node.type !== import_types42.NodeType.RouteNode) return;
|
|
10954
11110
|
const route = attrs;
|
|
10955
11111
|
if (route.service !== serviceName) return;
|
|
10956
11112
|
out.push({
|
|
@@ -11054,12 +11210,12 @@ function createRailwayResolveTarget(config) {
|
|
|
11054
11210
|
const serviceName = config.serviceNameById[config.serviceId];
|
|
11055
11211
|
if (!serviceName) return null;
|
|
11056
11212
|
if (signal.targetKind === ROUTE_TARGET_KIND) {
|
|
11057
|
-
return { targetNodeId: signal.targetName, serviceName, edgeType:
|
|
11213
|
+
return { targetNodeId: signal.targetName, serviceName, edgeType: import_types42.EdgeType.CALLS };
|
|
11058
11214
|
}
|
|
11059
11215
|
if (signal.targetKind === PEER_SERVICE_TARGET_KIND) {
|
|
11060
11216
|
const peerName = config.serviceNameById[signal.targetName];
|
|
11061
11217
|
if (!peerName) return null;
|
|
11062
|
-
return { targetNodeId: (0,
|
|
11218
|
+
return { targetNodeId: (0, import_types42.serviceId)(peerName), serviceName, edgeType: import_types42.EdgeType.CONNECTS_TO };
|
|
11063
11219
|
}
|
|
11064
11220
|
return null;
|
|
11065
11221
|
};
|
|
@@ -11183,9 +11339,9 @@ function parseFirebaseTargetName(targetName) {
|
|
|
11183
11339
|
const secondSep = rest.indexOf(FIELD_SEP);
|
|
11184
11340
|
if (secondSep === -1) return null;
|
|
11185
11341
|
const method = rest.slice(0, secondSep);
|
|
11186
|
-
const
|
|
11187
|
-
if (!resourceName || !method || !
|
|
11188
|
-
return { resourceName, method, path:
|
|
11342
|
+
const path55 = rest.slice(secondSep + 1);
|
|
11343
|
+
if (!resourceName || !method || !path55) return null;
|
|
11344
|
+
return { resourceName, method, path: path55 };
|
|
11189
11345
|
}
|
|
11190
11346
|
function resourceNameFor(type, labels) {
|
|
11191
11347
|
if (!labels) return null;
|
|
@@ -11223,14 +11379,14 @@ function mapLogEntryToSignal(entry2) {
|
|
|
11223
11379
|
if (!req2) return null;
|
|
11224
11380
|
if (typeof req2.requestMethod !== "string" || req2.requestMethod.length === 0) return null;
|
|
11225
11381
|
const method = req2.requestMethod.toUpperCase();
|
|
11226
|
-
const
|
|
11227
|
-
if (
|
|
11382
|
+
const path55 = pathFromRequestUrl(req2.requestUrl);
|
|
11383
|
+
if (path55 === null) return null;
|
|
11228
11384
|
const timestamp = entry2.timestamp;
|
|
11229
11385
|
if (typeof timestamp !== "string" || timestamp.length === 0) return null;
|
|
11230
11386
|
const isError = typeof req2.status === "number" && req2.status >= ERROR_STATUS_THRESHOLD2;
|
|
11231
11387
|
return {
|
|
11232
11388
|
targetKind: resourceType,
|
|
11233
|
-
targetName: packFirebaseTargetName({ resourceName, method, path:
|
|
11389
|
+
targetName: packFirebaseTargetName({ resourceName, method, path: path55 }),
|
|
11234
11390
|
callCount: 1,
|
|
11235
11391
|
errorCount: isError ? 1 : 0,
|
|
11236
11392
|
lastObservedIso: timestamp
|
|
@@ -11247,7 +11403,7 @@ function mapLogEntriesToSignals(entries) {
|
|
|
11247
11403
|
|
|
11248
11404
|
// src/connectors/firebase/resolve.ts
|
|
11249
11405
|
init_cjs_shims();
|
|
11250
|
-
var
|
|
11406
|
+
var import_types43 = require("@neat.is/types");
|
|
11251
11407
|
function neatServiceNameFor(resourceType, resourceName, serviceMap) {
|
|
11252
11408
|
switch (resourceType) {
|
|
11253
11409
|
case "cloud_function":
|
|
@@ -11262,7 +11418,7 @@ function routeEntriesFor(graph, serviceName) {
|
|
|
11262
11418
|
const entries = [];
|
|
11263
11419
|
graph.forEachNode((_id, attrs) => {
|
|
11264
11420
|
const node = attrs;
|
|
11265
|
-
if (node.type !==
|
|
11421
|
+
if (node.type !== import_types43.NodeType.RouteNode) return;
|
|
11266
11422
|
const route = attrs;
|
|
11267
11423
|
if (route.service !== serviceName) return;
|
|
11268
11424
|
entries.push({
|
|
@@ -11294,7 +11450,7 @@ function createFirebaseResolveTarget(graph, serviceMap) {
|
|
|
11294
11450
|
return {
|
|
11295
11451
|
targetNodeId: match.routeNodeId,
|
|
11296
11452
|
serviceName,
|
|
11297
|
-
edgeType:
|
|
11453
|
+
edgeType: import_types43.EdgeType.CALLS
|
|
11298
11454
|
};
|
|
11299
11455
|
};
|
|
11300
11456
|
}
|
|
@@ -11321,7 +11477,7 @@ init_cjs_shims();
|
|
|
11321
11477
|
|
|
11322
11478
|
// src/connectors/cloudflare/connector.ts
|
|
11323
11479
|
init_cjs_shims();
|
|
11324
|
-
var
|
|
11480
|
+
var import_types45 = require("@neat.is/types");
|
|
11325
11481
|
|
|
11326
11482
|
// src/connectors/cloudflare/client.ts
|
|
11327
11483
|
init_cjs_shims();
|
|
@@ -11437,7 +11593,7 @@ function mapEventToSignal(event) {
|
|
|
11437
11593
|
if (Number.isNaN(observedAt.getTime())) return null;
|
|
11438
11594
|
const statusCode = metadata?.statusCode;
|
|
11439
11595
|
const isError = typeof statusCode === "number" && statusCode >= ERROR_STATUS_THRESHOLD3;
|
|
11440
|
-
const
|
|
11596
|
+
const path55 = metadata?.trigger ? parsePathFromTrigger(metadata.trigger) : void 0;
|
|
11441
11597
|
return {
|
|
11442
11598
|
targetKind: CLOUDFLARE_TARGET_KIND,
|
|
11443
11599
|
targetName: scriptName,
|
|
@@ -11445,7 +11601,7 @@ function mapEventToSignal(event) {
|
|
|
11445
11601
|
errorCount: isError ? 1 : 0,
|
|
11446
11602
|
lastObservedIso: observedAt.toISOString(),
|
|
11447
11603
|
method,
|
|
11448
|
-
...
|
|
11604
|
+
...path55 ? { path: path55 } : {},
|
|
11449
11605
|
...typeof statusCode === "number" ? { statusCode } : {},
|
|
11450
11606
|
...typeof metadata?.duration === "number" ? { duration: metadata.duration } : {}
|
|
11451
11607
|
};
|
|
@@ -11485,19 +11641,19 @@ function findTaggedWorkerFileNode(graph, workerName) {
|
|
|
11485
11641
|
graph.forEachNode((id, attrs) => {
|
|
11486
11642
|
if (found) return;
|
|
11487
11643
|
const a = attrs;
|
|
11488
|
-
if (a.type ===
|
|
11644
|
+
if (a.type === import_types45.NodeType.FileNode && a.platform === "cloudflare" && a.platformName === workerName) {
|
|
11489
11645
|
found = id;
|
|
11490
11646
|
}
|
|
11491
11647
|
});
|
|
11492
11648
|
return found;
|
|
11493
11649
|
}
|
|
11494
|
-
function findMatchingRouteNode(graph, serviceName, method,
|
|
11495
|
-
const normalizedPath = normalizePathTemplate(
|
|
11650
|
+
function findMatchingRouteNode(graph, serviceName, method, path55) {
|
|
11651
|
+
const normalizedPath = normalizePathTemplate(path55);
|
|
11496
11652
|
let found = null;
|
|
11497
11653
|
graph.forEachNode((id, attrs) => {
|
|
11498
11654
|
if (found) return;
|
|
11499
11655
|
const a = attrs;
|
|
11500
|
-
if (a.type !==
|
|
11656
|
+
if (a.type !== import_types45.NodeType.RouteNode || a.service !== serviceName) return;
|
|
11501
11657
|
if (!a.pathTemplate || normalizePathTemplate(a.pathTemplate) !== normalizedPath) return;
|
|
11502
11658
|
const routeMethod = (a.method ?? "").toUpperCase();
|
|
11503
11659
|
if (routeMethod !== "ALL" && routeMethod !== method) return;
|
|
@@ -11509,18 +11665,18 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
11509
11665
|
return (signal) => {
|
|
11510
11666
|
if (signal.targetKind !== CLOUDFLARE_TARGET_KIND) return null;
|
|
11511
11667
|
const scriptName = signal.targetName;
|
|
11512
|
-
const { method, path:
|
|
11668
|
+
const { method, path: path55 } = signal;
|
|
11513
11669
|
const resolveRouteGrain = (serviceName, wholeFileId) => {
|
|
11514
|
-
if (!method || !
|
|
11515
|
-
return findMatchingRouteNode(graph, serviceName, method,
|
|
11670
|
+
if (!method || !path55) return wholeFileId;
|
|
11671
|
+
return findMatchingRouteNode(graph, serviceName, method, path55) ?? wholeFileId;
|
|
11516
11672
|
};
|
|
11517
11673
|
const mapping = config.workers?.[scriptName];
|
|
11518
11674
|
if (mapping) {
|
|
11519
|
-
const wholeFileId = (0,
|
|
11675
|
+
const wholeFileId = (0, import_types45.fileId)(mapping.service, mapping.entryFile);
|
|
11520
11676
|
return {
|
|
11521
11677
|
targetNodeId: resolveRouteGrain(mapping.service, wholeFileId),
|
|
11522
11678
|
serviceName: mapping.service,
|
|
11523
|
-
edgeType:
|
|
11679
|
+
edgeType: import_types45.EdgeType.CALLS
|
|
11524
11680
|
};
|
|
11525
11681
|
}
|
|
11526
11682
|
const taggedFileId = findTaggedWorkerFileNode(graph, scriptName);
|
|
@@ -11529,13 +11685,13 @@ function createCloudflareResolveTarget(config, graph) {
|
|
|
11529
11685
|
return {
|
|
11530
11686
|
targetNodeId: resolveRouteGrain(fileNode.service, taggedFileId),
|
|
11531
11687
|
serviceName: fileNode.service,
|
|
11532
|
-
edgeType:
|
|
11688
|
+
edgeType: import_types45.EdgeType.CALLS
|
|
11533
11689
|
};
|
|
11534
11690
|
}
|
|
11535
11691
|
return {
|
|
11536
|
-
targetNodeId: (0,
|
|
11692
|
+
targetNodeId: (0, import_types45.infraId)("cloudflare-worker", scriptName),
|
|
11537
11693
|
serviceName: scriptName,
|
|
11538
|
-
edgeType:
|
|
11694
|
+
edgeType: import_types45.EdgeType.CALLS,
|
|
11539
11695
|
ensureInfraNode: { kind: "cloudflare-worker", name: scriptName, provider: "cloudflare" }
|
|
11540
11696
|
};
|
|
11541
11697
|
};
|
|
@@ -11979,7 +12135,7 @@ init_auth();
|
|
|
11979
12135
|
// src/unrouted.ts
|
|
11980
12136
|
init_cjs_shims();
|
|
11981
12137
|
var import_node_fs30 = require("fs");
|
|
11982
|
-
var
|
|
12138
|
+
var import_node_path51 = __toESM(require("path"), 1);
|
|
11983
12139
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
11984
12140
|
return {
|
|
11985
12141
|
timestamp: now.toISOString(),
|
|
@@ -11989,34 +12145,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
11989
12145
|
};
|
|
11990
12146
|
}
|
|
11991
12147
|
async function appendUnroutedSpan(neatHome4, record) {
|
|
11992
|
-
const target =
|
|
12148
|
+
const target = import_node_path51.default.join(neatHome4, "errors.ndjson");
|
|
11993
12149
|
await import_node_fs30.promises.mkdir(neatHome4, { recursive: true });
|
|
11994
12150
|
await import_node_fs30.promises.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
11995
12151
|
}
|
|
11996
12152
|
function unroutedErrorsPath(neatHome4) {
|
|
11997
|
-
return
|
|
12153
|
+
return import_node_path51.default.join(neatHome4, "errors.ndjson");
|
|
11998
12154
|
}
|
|
11999
12155
|
|
|
12000
12156
|
// src/daemon.ts
|
|
12001
|
-
var
|
|
12157
|
+
var import_types48 = require("@neat.is/types");
|
|
12002
12158
|
function daemonJsonPath(scanPath) {
|
|
12003
|
-
return
|
|
12159
|
+
return import_node_path52.default.join(scanPath, "neat-out", "daemon.json");
|
|
12004
12160
|
}
|
|
12005
12161
|
function daemonsDiscoveryDir(home) {
|
|
12006
12162
|
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
12007
|
-
return
|
|
12163
|
+
return import_node_path52.default.join(base, "daemons");
|
|
12008
12164
|
}
|
|
12009
12165
|
function daemonDiscoveryPath(project, home) {
|
|
12010
|
-
return
|
|
12166
|
+
return import_node_path52.default.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
12011
12167
|
}
|
|
12012
12168
|
function sanitizeDiscoveryName(project) {
|
|
12013
12169
|
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
12014
12170
|
}
|
|
12015
12171
|
function neatHomeFromEnv() {
|
|
12016
12172
|
const env = process.env.NEAT_HOME;
|
|
12017
|
-
if (env && env.length > 0) return
|
|
12173
|
+
if (env && env.length > 0) return import_node_path52.default.resolve(env);
|
|
12018
12174
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
12019
|
-
return
|
|
12175
|
+
return import_node_path52.default.join(home, ".neat");
|
|
12020
12176
|
}
|
|
12021
12177
|
function resolveNeatVersion() {
|
|
12022
12178
|
if (process.env.NEAT_LOCAL_VERSION && process.env.NEAT_LOCAL_VERSION.length > 0) {
|
|
@@ -12085,11 +12241,11 @@ function teardownSlot(slot) {
|
|
|
12085
12241
|
}
|
|
12086
12242
|
}
|
|
12087
12243
|
function neatHomeFor(opts) {
|
|
12088
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
12244
|
+
if (opts.neatHome && opts.neatHome.length > 0) return import_node_path52.default.resolve(opts.neatHome);
|
|
12089
12245
|
const env = process.env.NEAT_HOME;
|
|
12090
|
-
if (env && env.length > 0) return
|
|
12246
|
+
if (env && env.length > 0) return import_node_path52.default.resolve(env);
|
|
12091
12247
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
12092
|
-
return
|
|
12248
|
+
return import_node_path52.default.join(home, ".neat");
|
|
12093
12249
|
}
|
|
12094
12250
|
function routeSpanToProject(serviceName, projects) {
|
|
12095
12251
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -12133,11 +12289,11 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
12133
12289
|
if (!serviceName) return true;
|
|
12134
12290
|
if (serviceNameMatchesProject(serviceName, project)) return true;
|
|
12135
12291
|
return graph.someNode(
|
|
12136
|
-
(_id, attrs) => attrs.type ===
|
|
12292
|
+
(_id, attrs) => attrs.type === import_types48.NodeType.ServiceNode && attrs.name === serviceName
|
|
12137
12293
|
);
|
|
12138
12294
|
}
|
|
12139
12295
|
async function bootstrapProject(entry2, connectors = [], neatHome4) {
|
|
12140
|
-
const paths = pathsForProject(entry2.name,
|
|
12296
|
+
const paths = pathsForProject(entry2.name, import_node_path52.default.join(entry2.path, "neat-out"));
|
|
12141
12297
|
try {
|
|
12142
12298
|
const stat = await import_node_fs31.promises.stat(entry2.path);
|
|
12143
12299
|
if (!stat.isDirectory()) {
|
|
@@ -12268,7 +12424,7 @@ async function startDaemon(opts = {}) {
|
|
|
12268
12424
|
const projectArg = typeof opts.project === "string" && opts.project.length > 0 ? opts.project : process.env.NEAT_PROJECT && process.env.NEAT_PROJECT.length > 0 ? process.env.NEAT_PROJECT : null;
|
|
12269
12425
|
const projectPathArg = opts.projectPath && opts.projectPath.length > 0 ? opts.projectPath : process.env.NEAT_PROJECT_PATH && process.env.NEAT_PROJECT_PATH.length > 0 ? process.env.NEAT_PROJECT_PATH : null;
|
|
12270
12426
|
const singleProject = projectArg;
|
|
12271
|
-
const singleProjectPath = singleProject && projectPathArg ?
|
|
12427
|
+
const singleProjectPath = singleProject && projectPathArg ? import_node_path52.default.resolve(projectPathArg) : null;
|
|
12272
12428
|
if (singleProject && !singleProjectPath) {
|
|
12273
12429
|
throw new Error(
|
|
12274
12430
|
`neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
|
|
@@ -12283,7 +12439,7 @@ async function startDaemon(opts = {}) {
|
|
|
12283
12439
|
);
|
|
12284
12440
|
}
|
|
12285
12441
|
}
|
|
12286
|
-
const pidPath =
|
|
12442
|
+
const pidPath = import_node_path52.default.join(home, "neatd.pid");
|
|
12287
12443
|
await writeAtomically(pidPath, `${process.pid}
|
|
12288
12444
|
`);
|
|
12289
12445
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -12690,8 +12846,8 @@ async function startDaemon(opts = {}) {
|
|
|
12690
12846
|
let registryWatcher = null;
|
|
12691
12847
|
let reloadTimer = null;
|
|
12692
12848
|
if (!singleProject) try {
|
|
12693
|
-
const regDir =
|
|
12694
|
-
const regBase =
|
|
12849
|
+
const regDir = import_node_path52.default.dirname(regPath);
|
|
12850
|
+
const regBase = import_node_path52.default.basename(regPath);
|
|
12695
12851
|
registryWatcher = (0, import_node_fs31.watch)(regDir, (_eventType, filename) => {
|
|
12696
12852
|
if (filename !== null && filename !== regBase) return;
|
|
12697
12853
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
@@ -12766,7 +12922,7 @@ init_cjs_shims();
|
|
|
12766
12922
|
var import_node_child_process2 = require("child_process");
|
|
12767
12923
|
var import_node_fs32 = require("fs");
|
|
12768
12924
|
var import_node_net = __toESM(require("net"), 1);
|
|
12769
|
-
var
|
|
12925
|
+
var import_node_path53 = __toESM(require("path"), 1);
|
|
12770
12926
|
var DEFAULT_WEB_PORT = 6328;
|
|
12771
12927
|
var DEFAULT_REST_PORT = 8080;
|
|
12772
12928
|
function asValidPort(value) {
|
|
@@ -12775,11 +12931,11 @@ function asValidPort(value) {
|
|
|
12775
12931
|
}
|
|
12776
12932
|
function projectRoot() {
|
|
12777
12933
|
const fromEnv = process.env.NEAT_SCAN_PATH;
|
|
12778
|
-
return
|
|
12934
|
+
return import_node_path53.default.resolve(fromEnv && fromEnv.length > 0 ? fromEnv : process.cwd());
|
|
12779
12935
|
}
|
|
12780
12936
|
async function readDaemonPorts(root) {
|
|
12781
12937
|
try {
|
|
12782
|
-
const raw = await import_node_fs32.promises.readFile(
|
|
12938
|
+
const raw = await import_node_fs32.promises.readFile(import_node_path53.default.join(root, "neat-out", "daemon.json"), "utf8");
|
|
12783
12939
|
const parsed = JSON.parse(raw);
|
|
12784
12940
|
const ports = parsed?.ports ?? {};
|
|
12785
12941
|
return { web: asValidPort(ports.web), rest: asValidPort(ports.rest) };
|
|
@@ -12824,10 +12980,10 @@ function resolveWebPackageDir() {
|
|
|
12824
12980
|
eval("require")
|
|
12825
12981
|
);
|
|
12826
12982
|
const pkgJsonPath = req.resolve("@neat.is/web/package.json");
|
|
12827
|
-
return
|
|
12983
|
+
return import_node_path53.default.dirname(pkgJsonPath);
|
|
12828
12984
|
}
|
|
12829
12985
|
function resolveStandaloneServerEntry(webDir) {
|
|
12830
|
-
return
|
|
12986
|
+
return import_node_path53.default.join(webDir, ".next/standalone/packages/web/server.js");
|
|
12831
12987
|
}
|
|
12832
12988
|
async function pickInternalPort() {
|
|
12833
12989
|
return new Promise((resolve, reject) => {
|
|
@@ -12880,7 +13036,7 @@ async function spawnWebUI(restPort, opts = {}) {
|
|
|
12880
13036
|
NEAT_API_URL: apiUrl
|
|
12881
13037
|
};
|
|
12882
13038
|
child = (0, import_node_child_process2.spawn)(process.execPath, [serverEntry], {
|
|
12883
|
-
cwd:
|
|
13039
|
+
cwd: import_node_path53.default.dirname(serverEntry),
|
|
12884
13040
|
env,
|
|
12885
13041
|
stdio: ["ignore", "inherit", "inherit"],
|
|
12886
13042
|
detached: false
|
|
@@ -13056,14 +13212,14 @@ function localVersion() {
|
|
|
13056
13212
|
}
|
|
13057
13213
|
function neatHome3() {
|
|
13058
13214
|
if (process.env.NEAT_HOME && process.env.NEAT_HOME.length > 0) {
|
|
13059
|
-
return
|
|
13215
|
+
return import_node_path54.default.resolve(process.env.NEAT_HOME);
|
|
13060
13216
|
}
|
|
13061
13217
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
13062
|
-
return
|
|
13218
|
+
return import_node_path54.default.join(home, ".neat");
|
|
13063
13219
|
}
|
|
13064
13220
|
async function readPid() {
|
|
13065
13221
|
try {
|
|
13066
|
-
const raw = await import_node_fs33.promises.readFile(
|
|
13222
|
+
const raw = await import_node_fs33.promises.readFile(import_node_path54.default.join(neatHome3(), "neatd.pid"), "utf8");
|
|
13067
13223
|
const n = Number.parseInt(raw.trim(), 10);
|
|
13068
13224
|
return Number.isFinite(n) ? n : null;
|
|
13069
13225
|
} catch {
|