@neat.is/core 0.4.16 → 0.4.17
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-VMLWUK7W.js → chunk-CEDXXMGO.js} +16 -2
- package/dist/chunk-CEDXXMGO.js.map +1 -0
- package/dist/{chunk-XS4CGNRO.js → chunk-LUDSPX5N.js} +293 -71
- package/dist/chunk-LUDSPX5N.js.map +1 -0
- package/dist/cli.cjs +568 -286
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +12 -2
- package/dist/cli.d.ts +12 -2
- package/dist/cli.js +76 -17
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +377 -142
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +384 -149
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +269 -97
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-VMLWUK7W.js.map +0 -1
- package/dist/chunk-XS4CGNRO.js.map +0 -1
|
@@ -454,19 +454,19 @@ function confidenceFromMix(edges, now = Date.now()) {
|
|
|
454
454
|
function longestIncomingWalk(graph, start, maxDepth) {
|
|
455
455
|
let best = { path: [start], edges: [] };
|
|
456
456
|
const visited = /* @__PURE__ */ new Set([start]);
|
|
457
|
-
function step(node,
|
|
458
|
-
if (
|
|
459
|
-
best = { path: [...
|
|
457
|
+
function step(node, path39, edges) {
|
|
458
|
+
if (path39.length > best.path.length) {
|
|
459
|
+
best = { path: [...path39], edges: [...edges] };
|
|
460
460
|
}
|
|
461
|
-
if (
|
|
461
|
+
if (path39.length - 1 >= maxDepth) return;
|
|
462
462
|
const incoming = bestEdgeBySource(graph, graph.inboundEdges(node));
|
|
463
463
|
for (const [srcId, edge] of incoming) {
|
|
464
464
|
if (visited.has(srcId)) continue;
|
|
465
465
|
visited.add(srcId);
|
|
466
|
-
|
|
466
|
+
path39.push(srcId);
|
|
467
467
|
edges.push(edge);
|
|
468
|
-
step(srcId,
|
|
469
|
-
|
|
468
|
+
step(srcId, path39, edges);
|
|
469
|
+
path39.pop();
|
|
470
470
|
edges.pop();
|
|
471
471
|
visited.delete(srcId);
|
|
472
472
|
}
|
|
@@ -1093,6 +1093,41 @@ function thresholdForEdgeType(edgeType, overrides) {
|
|
|
1093
1093
|
const map = overrides ?? loadStaleThresholdsFromEnv();
|
|
1094
1094
|
return map[edgeType] ?? FALLBACK_STALE_THRESHOLD_MS;
|
|
1095
1095
|
}
|
|
1096
|
+
var DEFAULT_INCIDENT_THRESHOLDS = {
|
|
1097
|
+
threshold: 5,
|
|
1098
|
+
windowMs: 6e4
|
|
1099
|
+
};
|
|
1100
|
+
function loadIncidentThresholdsFromEnv() {
|
|
1101
|
+
const raw = process.env.NEAT_INCIDENT_THRESHOLDS;
|
|
1102
|
+
if (!raw) return DEFAULT_INCIDENT_THRESHOLDS;
|
|
1103
|
+
try {
|
|
1104
|
+
const overrides = JSON.parse(raw);
|
|
1105
|
+
const merged = { ...DEFAULT_INCIDENT_THRESHOLDS };
|
|
1106
|
+
if (typeof overrides.threshold === "number" && Number.isFinite(overrides.threshold) && overrides.threshold >= 1) {
|
|
1107
|
+
merged.threshold = Math.floor(overrides.threshold);
|
|
1108
|
+
}
|
|
1109
|
+
if (typeof overrides.windowMs === "number" && Number.isFinite(overrides.windowMs) && overrides.windowMs >= 0) {
|
|
1110
|
+
merged.windowMs = overrides.windowMs;
|
|
1111
|
+
}
|
|
1112
|
+
return merged;
|
|
1113
|
+
} catch (err) {
|
|
1114
|
+
console.warn(
|
|
1115
|
+
`[neat] NEAT_INCIDENT_THRESHOLDS could not be parsed (${err.message}); using defaults`
|
|
1116
|
+
);
|
|
1117
|
+
return DEFAULT_INCIDENT_THRESHOLDS;
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
function httpResponseStatus(span) {
|
|
1121
|
+
for (const key of ["http.response.status_code", "http.status_code"]) {
|
|
1122
|
+
const v = span.attributes[key];
|
|
1123
|
+
if (typeof v === "number" && Number.isFinite(v)) return v;
|
|
1124
|
+
if (typeof v === "string") {
|
|
1125
|
+
const n = Number(v);
|
|
1126
|
+
if (Number.isFinite(n)) return n;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return void 0;
|
|
1130
|
+
}
|
|
1096
1131
|
function nowIso(ctx) {
|
|
1097
1132
|
return new Date(ctx.now ? ctx.now() : Date.now()).toISOString();
|
|
1098
1133
|
}
|
|
@@ -1506,6 +1541,71 @@ function makeErrorSpanWriter(errorsPath) {
|
|
|
1506
1541
|
await fs3.appendFile(errorsPath, JSON.stringify(ev) + "\n", "utf8");
|
|
1507
1542
|
};
|
|
1508
1543
|
}
|
|
1544
|
+
async function recordFailingResponseIncident(ctx, span, affectedNode, timestamp, statusCode, count, firstTimestamp) {
|
|
1545
|
+
const attrs = sanitizeAttributes(span.attributes);
|
|
1546
|
+
const first = firstTimestamp ?? timestamp;
|
|
1547
|
+
const peer = pickAddress(span);
|
|
1548
|
+
const message = count > 1 ? `${count} consecutive HTTP ${statusCode} responses` + (peer ? ` to ${peer}` : "") : `HTTP ${statusCode} response` + (peer ? ` from ${peer}` : "");
|
|
1549
|
+
const ev = {
|
|
1550
|
+
id: `${span.traceId}:${span.spanId}`,
|
|
1551
|
+
timestamp,
|
|
1552
|
+
service: span.service,
|
|
1553
|
+
traceId: span.traceId,
|
|
1554
|
+
spanId: span.spanId,
|
|
1555
|
+
errorType: "http-failure",
|
|
1556
|
+
errorMessage: message,
|
|
1557
|
+
...Object.keys(attrs).length > 0 ? { attributes: attrs } : {},
|
|
1558
|
+
affectedNode,
|
|
1559
|
+
httpStatusCode: statusCode,
|
|
1560
|
+
incidentCount: count,
|
|
1561
|
+
firstTimestamp: first,
|
|
1562
|
+
lastTimestamp: timestamp
|
|
1563
|
+
};
|
|
1564
|
+
await appendErrorEvent(ctx, ev);
|
|
1565
|
+
}
|
|
1566
|
+
async function advance4xxBurst(ctx, span, affectedNode, ts, nowMs, status) {
|
|
1567
|
+
const { threshold, windowMs } = loadIncidentThresholdsFromEnv();
|
|
1568
|
+
if (!ctx.burstState) ctx.burstState = /* @__PURE__ */ new Map();
|
|
1569
|
+
const peer = pickAddress(span) ?? span.spanId;
|
|
1570
|
+
const key = `${span.service}->${peer}`;
|
|
1571
|
+
const existing = ctx.burstState.get(key);
|
|
1572
|
+
let state;
|
|
1573
|
+
if (existing && nowMs - existing.lastMs <= windowMs) {
|
|
1574
|
+
existing.count += 1;
|
|
1575
|
+
existing.lastTs = ts;
|
|
1576
|
+
existing.lastMs = nowMs;
|
|
1577
|
+
existing.codes.set(status, (existing.codes.get(status) ?? 0) + 1);
|
|
1578
|
+
state = existing;
|
|
1579
|
+
} else {
|
|
1580
|
+
state = {
|
|
1581
|
+
count: 1,
|
|
1582
|
+
firstTs: ts,
|
|
1583
|
+
lastTs: ts,
|
|
1584
|
+
lastMs: nowMs,
|
|
1585
|
+
codes: /* @__PURE__ */ new Map([[status, 1]])
|
|
1586
|
+
};
|
|
1587
|
+
ctx.burstState.set(key, state);
|
|
1588
|
+
}
|
|
1589
|
+
if (state.count < threshold) return;
|
|
1590
|
+
let dominant = status;
|
|
1591
|
+
let max = 0;
|
|
1592
|
+
for (const [code, n] of state.codes) {
|
|
1593
|
+
if (n > max) {
|
|
1594
|
+
max = n;
|
|
1595
|
+
dominant = code;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
await recordFailingResponseIncident(
|
|
1599
|
+
ctx,
|
|
1600
|
+
span,
|
|
1601
|
+
affectedNode,
|
|
1602
|
+
state.lastTs,
|
|
1603
|
+
dominant,
|
|
1604
|
+
state.count,
|
|
1605
|
+
state.firstTs
|
|
1606
|
+
);
|
|
1607
|
+
ctx.burstState.delete(key);
|
|
1608
|
+
}
|
|
1509
1609
|
async function handleSpan(ctx, span) {
|
|
1510
1610
|
const ts = span.startTimeIso ?? nowIso(ctx);
|
|
1511
1611
|
const nowMs = ctx.now ? ctx.now() : Date.now();
|
|
@@ -1604,6 +1704,14 @@ async function handleSpan(ctx, span) {
|
|
|
1604
1704
|
await appendErrorEvent(ctx, ev);
|
|
1605
1705
|
}
|
|
1606
1706
|
}
|
|
1707
|
+
if (span.statusCode !== 2) {
|
|
1708
|
+
const status = httpResponseStatus(span);
|
|
1709
|
+
if (status !== void 0 && status >= 500) {
|
|
1710
|
+
await recordFailingResponseIncident(ctx, span, sourceId, ts, status, 1);
|
|
1711
|
+
} else if (status !== void 0 && status >= 400 && spanMintsObservedEdge(span.kind)) {
|
|
1712
|
+
await advance4xxBurst(ctx, span, sourceId, ts, nowMs, status);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1607
1715
|
void affectedNode;
|
|
1608
1716
|
if (ctx.onPolicyTrigger) await ctx.onPolicyTrigger(ctx.graph);
|
|
1609
1717
|
}
|
|
@@ -3995,6 +4103,65 @@ function grpcEndpointsFromFile(file, serviceDir) {
|
|
|
3995
4103
|
return out;
|
|
3996
4104
|
}
|
|
3997
4105
|
|
|
4106
|
+
// src/extract/calls/supabase.ts
|
|
4107
|
+
import path27 from "path";
|
|
4108
|
+
import { infraId as infraId5 } from "@neat.is/types";
|
|
4109
|
+
var SUPABASE_JS_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/supabase-js['"`]/;
|
|
4110
|
+
var SUPABASE_SSR_IMPORT_RE = /(?:from\s+['"`]|require\(\s*['"`])@supabase\/ssr['"`]/;
|
|
4111
|
+
var SUPABASE_CLIENT_RE = /\b(createClient|createServerClient|createBrowserClient)\s*\(\s*(?:['"`]([^'"`]*)['"`])?/g;
|
|
4112
|
+
function hostFromLiteral(literal) {
|
|
4113
|
+
if (!literal) return null;
|
|
4114
|
+
const m = /^https?:\/\/([^/:'"`\s]+)/.exec(literal.trim());
|
|
4115
|
+
if (!m) return null;
|
|
4116
|
+
const host = m[1];
|
|
4117
|
+
if (!/\.supabase\.(co|in)$/i.test(host)) return null;
|
|
4118
|
+
return host;
|
|
4119
|
+
}
|
|
4120
|
+
function readImports2(content) {
|
|
4121
|
+
return {
|
|
4122
|
+
hasSupabaseJs: SUPABASE_JS_IMPORT_RE.test(content),
|
|
4123
|
+
hasSupabaseSsr: SUPABASE_SSR_IMPORT_RE.test(content)
|
|
4124
|
+
};
|
|
4125
|
+
}
|
|
4126
|
+
function constructorMatchesImport(name, ctx) {
|
|
4127
|
+
if (name === "createClient") return ctx.hasSupabaseJs;
|
|
4128
|
+
return ctx.hasSupabaseSsr;
|
|
4129
|
+
}
|
|
4130
|
+
function supabaseEndpointsFromFile(file, serviceDir) {
|
|
4131
|
+
const ctx = readImports2(file.content);
|
|
4132
|
+
if (!ctx.hasSupabaseJs && !ctx.hasSupabaseSsr) return [];
|
|
4133
|
+
const out = [];
|
|
4134
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4135
|
+
SUPABASE_CLIENT_RE.lastIndex = 0;
|
|
4136
|
+
let m;
|
|
4137
|
+
while ((m = SUPABASE_CLIENT_RE.exec(file.content)) !== null) {
|
|
4138
|
+
const ctor = m[1];
|
|
4139
|
+
if (!constructorMatchesImport(ctor, ctx)) continue;
|
|
4140
|
+
const host = hostFromLiteral(m[2]);
|
|
4141
|
+
const name = host ?? "env";
|
|
4142
|
+
if (seen.has(name)) continue;
|
|
4143
|
+
seen.add(name);
|
|
4144
|
+
const line = lineOf(file.content, m[0]);
|
|
4145
|
+
out.push({
|
|
4146
|
+
infraId: infraId5("supabase", name),
|
|
4147
|
+
name,
|
|
4148
|
+
kind: "supabase",
|
|
4149
|
+
edgeType: "CALLS",
|
|
4150
|
+
// `createClient(...)` from @supabase/supabase-js (or createServerClient /
|
|
4151
|
+
// createBrowserClient from @supabase/ssr) with the import in scope — a
|
|
4152
|
+
// framework-aware recognizer matched the SDK shape. Verified-call-site
|
|
4153
|
+
// tier (ADR-066), the same grade aws.ts / grpc.ts emit at.
|
|
4154
|
+
confidenceKind: "verified-call-site",
|
|
4155
|
+
evidence: {
|
|
4156
|
+
file: path27.relative(serviceDir, file.path),
|
|
4157
|
+
line,
|
|
4158
|
+
snippet: snippet(file.content, line)
|
|
4159
|
+
}
|
|
4160
|
+
});
|
|
4161
|
+
}
|
|
4162
|
+
return out;
|
|
4163
|
+
}
|
|
4164
|
+
|
|
3998
4165
|
// src/extract/calls/index.ts
|
|
3999
4166
|
function edgeTypeFromEndpoint(ep) {
|
|
4000
4167
|
switch (ep.edgeType) {
|
|
@@ -4023,6 +4190,7 @@ async function addExternalEndpointEdges(graph, services) {
|
|
|
4023
4190
|
endpoints.push(...redisEndpointsFromFile(maskedFile, service.dir));
|
|
4024
4191
|
endpoints.push(...awsEndpointsFromFile(maskedFile, service.dir));
|
|
4025
4192
|
endpoints.push(...grpcEndpointsFromFile(maskedFile, service.dir));
|
|
4193
|
+
endpoints.push(...supabaseEndpointsFromFile(maskedFile, service.dir));
|
|
4026
4194
|
}
|
|
4027
4195
|
if (endpoints.length === 0) continue;
|
|
4028
4196
|
const seenEdges = /* @__PURE__ */ new Set();
|
|
@@ -4093,14 +4261,14 @@ async function addCallEdges(graph, services) {
|
|
|
4093
4261
|
}
|
|
4094
4262
|
|
|
4095
4263
|
// src/extract/infra/docker-compose.ts
|
|
4096
|
-
import
|
|
4264
|
+
import path28 from "path";
|
|
4097
4265
|
import { EdgeType as EdgeType10, Provenance as Provenance8, confidenceForExtracted as confidenceForExtracted7 } from "@neat.is/types";
|
|
4098
4266
|
|
|
4099
4267
|
// src/extract/infra/shared.ts
|
|
4100
|
-
import { NodeType as NodeType10, infraId as
|
|
4268
|
+
import { NodeType as NodeType10, infraId as infraId6 } from "@neat.is/types";
|
|
4101
4269
|
function makeInfraNode(kind, name, provider = "self", extras) {
|
|
4102
4270
|
return {
|
|
4103
|
-
id:
|
|
4271
|
+
id: infraId6(kind, name),
|
|
4104
4272
|
type: NodeType10.InfraNode,
|
|
4105
4273
|
name,
|
|
4106
4274
|
provider,
|
|
@@ -4130,7 +4298,7 @@ function dependsOnList(value) {
|
|
|
4130
4298
|
}
|
|
4131
4299
|
function serviceNameToServiceNode(name, services) {
|
|
4132
4300
|
for (const s of services) {
|
|
4133
|
-
if (s.node.name === name ||
|
|
4301
|
+
if (s.node.name === name || path28.basename(s.dir) === name) return s.node.id;
|
|
4134
4302
|
}
|
|
4135
4303
|
return null;
|
|
4136
4304
|
}
|
|
@@ -4139,7 +4307,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4139
4307
|
let edgesAdded = 0;
|
|
4140
4308
|
let composePath = null;
|
|
4141
4309
|
for (const name of ["docker-compose.yml", "docker-compose.yaml"]) {
|
|
4142
|
-
const abs =
|
|
4310
|
+
const abs = path28.join(scanPath, name);
|
|
4143
4311
|
if (await exists(abs)) {
|
|
4144
4312
|
composePath = abs;
|
|
4145
4313
|
break;
|
|
@@ -4152,13 +4320,13 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4152
4320
|
} catch (err) {
|
|
4153
4321
|
recordExtractionError(
|
|
4154
4322
|
"infra docker-compose",
|
|
4155
|
-
|
|
4323
|
+
path28.relative(scanPath, composePath),
|
|
4156
4324
|
err
|
|
4157
4325
|
);
|
|
4158
4326
|
return { nodesAdded, edgesAdded };
|
|
4159
4327
|
}
|
|
4160
4328
|
if (!compose?.services) return { nodesAdded, edgesAdded };
|
|
4161
|
-
const evidenceFile =
|
|
4329
|
+
const evidenceFile = path28.relative(scanPath, composePath).split(path28.sep).join("/");
|
|
4162
4330
|
const composeNameToNodeId = /* @__PURE__ */ new Map();
|
|
4163
4331
|
for (const [composeName, svc] of Object.entries(compose.services)) {
|
|
4164
4332
|
const matchedServiceId = serviceNameToServiceNode(composeName, services);
|
|
@@ -4199,7 +4367,7 @@ async function addComposeInfra(graph, scanPath, services) {
|
|
|
4199
4367
|
}
|
|
4200
4368
|
|
|
4201
4369
|
// src/extract/infra/dockerfile.ts
|
|
4202
|
-
import
|
|
4370
|
+
import path29 from "path";
|
|
4203
4371
|
import { promises as fs15 } from "fs";
|
|
4204
4372
|
import { EdgeType as EdgeType11, Provenance as Provenance9, confidenceForExtracted as confidenceForExtracted8 } from "@neat.is/types";
|
|
4205
4373
|
function runtimeImage(content) {
|
|
@@ -4220,7 +4388,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4220
4388
|
let nodesAdded = 0;
|
|
4221
4389
|
let edgesAdded = 0;
|
|
4222
4390
|
for (const service of services) {
|
|
4223
|
-
const dockerfilePath =
|
|
4391
|
+
const dockerfilePath = path29.join(service.dir, "Dockerfile");
|
|
4224
4392
|
if (!await exists(dockerfilePath)) continue;
|
|
4225
4393
|
let content;
|
|
4226
4394
|
try {
|
|
@@ -4228,7 +4396,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4228
4396
|
} catch (err) {
|
|
4229
4397
|
recordExtractionError(
|
|
4230
4398
|
"infra dockerfile",
|
|
4231
|
-
|
|
4399
|
+
path29.relative(scanPath, dockerfilePath),
|
|
4232
4400
|
err
|
|
4233
4401
|
);
|
|
4234
4402
|
continue;
|
|
@@ -4240,7 +4408,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4240
4408
|
graph.addNode(node.id, node);
|
|
4241
4409
|
nodesAdded++;
|
|
4242
4410
|
}
|
|
4243
|
-
const relDockerfile = toPosix2(
|
|
4411
|
+
const relDockerfile = toPosix2(path29.relative(service.dir, dockerfilePath));
|
|
4244
4412
|
const { fileNodeId, nodesAdded: fn, edgesAdded: fe } = ensureFileNode(
|
|
4245
4413
|
graph,
|
|
4246
4414
|
service.pkg.name,
|
|
@@ -4259,7 +4427,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4259
4427
|
provenance: Provenance9.EXTRACTED,
|
|
4260
4428
|
confidence: confidenceForExtracted8("structural"),
|
|
4261
4429
|
evidence: {
|
|
4262
|
-
file: toPosix2(
|
|
4430
|
+
file: toPosix2(path29.relative(scanPath, dockerfilePath))
|
|
4263
4431
|
}
|
|
4264
4432
|
};
|
|
4265
4433
|
graph.addEdgeWithKey(edgeId, edge.source, edge.target, edge);
|
|
@@ -4271,7 +4439,7 @@ async function addDockerfileRuntimes(graph, services, scanPath) {
|
|
|
4271
4439
|
|
|
4272
4440
|
// src/extract/infra/terraform.ts
|
|
4273
4441
|
import { promises as fs16 } from "fs";
|
|
4274
|
-
import
|
|
4442
|
+
import path30 from "path";
|
|
4275
4443
|
var RESOURCE_RE = /resource\s+"(aws_[A-Za-z0-9_]+)"\s+"([A-Za-z0-9_-]+)"/g;
|
|
4276
4444
|
async function walkTfFiles(start, depth = 0, max = 5) {
|
|
4277
4445
|
if (depth > max) return [];
|
|
@@ -4280,11 +4448,11 @@ async function walkTfFiles(start, depth = 0, max = 5) {
|
|
|
4280
4448
|
for (const entry of entries) {
|
|
4281
4449
|
if (entry.isDirectory()) {
|
|
4282
4450
|
if (IGNORED_DIRS.has(entry.name) || entry.name === ".terraform") continue;
|
|
4283
|
-
const child =
|
|
4451
|
+
const child = path30.join(start, entry.name);
|
|
4284
4452
|
if (await isPythonVenvDir(child)) continue;
|
|
4285
4453
|
out.push(...await walkTfFiles(child, depth + 1, max));
|
|
4286
4454
|
} else if (entry.isFile() && entry.name.endsWith(".tf")) {
|
|
4287
|
-
out.push(
|
|
4455
|
+
out.push(path30.join(start, entry.name));
|
|
4288
4456
|
}
|
|
4289
4457
|
}
|
|
4290
4458
|
return out;
|
|
@@ -4311,7 +4479,7 @@ async function addTerraformResources(graph, scanPath) {
|
|
|
4311
4479
|
|
|
4312
4480
|
// src/extract/infra/k8s.ts
|
|
4313
4481
|
import { promises as fs17 } from "fs";
|
|
4314
|
-
import
|
|
4482
|
+
import path31 from "path";
|
|
4315
4483
|
import { parseAllDocuments as parseAllDocuments2 } from "yaml";
|
|
4316
4484
|
var K8S_KIND_TO_INFRA_KIND = {
|
|
4317
4485
|
Service: "k8s-service",
|
|
@@ -4329,11 +4497,11 @@ async function walkYamlFiles2(start, depth = 0, max = 5) {
|
|
|
4329
4497
|
for (const entry of entries) {
|
|
4330
4498
|
if (entry.isDirectory()) {
|
|
4331
4499
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
4332
|
-
const child =
|
|
4500
|
+
const child = path31.join(start, entry.name);
|
|
4333
4501
|
if (await isPythonVenvDir(child)) continue;
|
|
4334
4502
|
out.push(...await walkYamlFiles2(child, depth + 1, max));
|
|
4335
|
-
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(
|
|
4336
|
-
out.push(
|
|
4503
|
+
} else if (entry.isFile() && CONFIG_FILE_EXTENSIONS.has(path31.extname(entry.name))) {
|
|
4504
|
+
out.push(path31.join(start, entry.name));
|
|
4337
4505
|
}
|
|
4338
4506
|
}
|
|
4339
4507
|
return out;
|
|
@@ -4377,11 +4545,11 @@ async function addInfra(graph, scanPath, services) {
|
|
|
4377
4545
|
}
|
|
4378
4546
|
|
|
4379
4547
|
// src/extract/index.ts
|
|
4380
|
-
import
|
|
4548
|
+
import path33 from "path";
|
|
4381
4549
|
|
|
4382
4550
|
// src/extract/retire.ts
|
|
4383
4551
|
import { existsSync as existsSync2 } from "fs";
|
|
4384
|
-
import
|
|
4552
|
+
import path32 from "path";
|
|
4385
4553
|
import { NodeType as NodeType11, Provenance as Provenance10 } from "@neat.is/types";
|
|
4386
4554
|
function dropOrphanedFileNodes(graph) {
|
|
4387
4555
|
const orphans = [];
|
|
@@ -4415,11 +4583,11 @@ function retireExtractedEdgesByMissingFile(graph, scanPath, serviceDirs = []) {
|
|
|
4415
4583
|
if (edge.provenance !== Provenance10.EXTRACTED) return;
|
|
4416
4584
|
const evidenceFile = edge.evidence?.file;
|
|
4417
4585
|
if (!evidenceFile) return;
|
|
4418
|
-
if (
|
|
4586
|
+
if (path32.isAbsolute(evidenceFile)) {
|
|
4419
4587
|
if (!existsSync2(evidenceFile)) toDrop.push(id);
|
|
4420
4588
|
return;
|
|
4421
4589
|
}
|
|
4422
|
-
const found = bases.some((base) => existsSync2(
|
|
4590
|
+
const found = bases.some((base) => existsSync2(path32.join(base, evidenceFile)));
|
|
4423
4591
|
if (!found) toDrop.push(id);
|
|
4424
4592
|
});
|
|
4425
4593
|
for (const id of toDrop) graph.dropEdge(id);
|
|
@@ -4459,7 +4627,7 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
4459
4627
|
}
|
|
4460
4628
|
const droppedEntries = drainDroppedExtracted();
|
|
4461
4629
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
4462
|
-
const rejectedPath =
|
|
4630
|
+
const rejectedPath = path33.join(path33.dirname(opts.errorsPath), "rejected.ndjson");
|
|
4463
4631
|
try {
|
|
4464
4632
|
await writeRejectedExtracted(droppedEntries, rejectedPath);
|
|
4465
4633
|
} catch (err) {
|
|
@@ -4736,7 +4904,7 @@ function computeDivergences(graph, opts = {}) {
|
|
|
4736
4904
|
|
|
4737
4905
|
// src/persist.ts
|
|
4738
4906
|
import { promises as fs18 } from "fs";
|
|
4739
|
-
import
|
|
4907
|
+
import path34 from "path";
|
|
4740
4908
|
import { Provenance as Provenance12, observedEdgeId as observedEdgeId2 } from "@neat.is/types";
|
|
4741
4909
|
var SCHEMA_VERSION = 4;
|
|
4742
4910
|
function migrateV1ToV2(payload) {
|
|
@@ -4773,7 +4941,7 @@ function migrateV2ToV3(payload) {
|
|
|
4773
4941
|
return { ...payload, schemaVersion: 3 };
|
|
4774
4942
|
}
|
|
4775
4943
|
async function ensureDir(filePath) {
|
|
4776
|
-
await fs18.mkdir(
|
|
4944
|
+
await fs18.mkdir(path34.dirname(filePath), { recursive: true });
|
|
4777
4945
|
}
|
|
4778
4946
|
async function saveGraphToDisk(graph, outPath) {
|
|
4779
4947
|
await ensureDir(outPath);
|
|
@@ -4923,23 +5091,23 @@ function canonicalJson(value) {
|
|
|
4923
5091
|
}
|
|
4924
5092
|
|
|
4925
5093
|
// src/projects.ts
|
|
4926
|
-
import
|
|
5094
|
+
import path35 from "path";
|
|
4927
5095
|
function pathsForProject(project, baseDir) {
|
|
4928
5096
|
if (project === DEFAULT_PROJECT) {
|
|
4929
5097
|
return {
|
|
4930
|
-
snapshotPath:
|
|
4931
|
-
errorsPath:
|
|
4932
|
-
staleEventsPath:
|
|
4933
|
-
embeddingsCachePath:
|
|
4934
|
-
policyViolationsPath:
|
|
5098
|
+
snapshotPath: path35.join(baseDir, "graph.json"),
|
|
5099
|
+
errorsPath: path35.join(baseDir, "errors.ndjson"),
|
|
5100
|
+
staleEventsPath: path35.join(baseDir, "stale-events.ndjson"),
|
|
5101
|
+
embeddingsCachePath: path35.join(baseDir, "embeddings.json"),
|
|
5102
|
+
policyViolationsPath: path35.join(baseDir, "policy-violations.ndjson")
|
|
4935
5103
|
};
|
|
4936
5104
|
}
|
|
4937
5105
|
return {
|
|
4938
|
-
snapshotPath:
|
|
4939
|
-
errorsPath:
|
|
4940
|
-
staleEventsPath:
|
|
4941
|
-
embeddingsCachePath:
|
|
4942
|
-
policyViolationsPath:
|
|
5106
|
+
snapshotPath: path35.join(baseDir, `${project}.json`),
|
|
5107
|
+
errorsPath: path35.join(baseDir, `errors.${project}.ndjson`),
|
|
5108
|
+
staleEventsPath: path35.join(baseDir, `stale-events.${project}.ndjson`),
|
|
5109
|
+
embeddingsCachePath: path35.join(baseDir, `embeddings.${project}.json`),
|
|
5110
|
+
policyViolationsPath: path35.join(baseDir, `policy-violations.${project}.ndjson`)
|
|
4943
5111
|
};
|
|
4944
5112
|
}
|
|
4945
5113
|
var Projects = class {
|
|
@@ -4980,7 +5148,7 @@ function parseExtraProjects(raw) {
|
|
|
4980
5148
|
// src/registry.ts
|
|
4981
5149
|
import { promises as fs20 } from "fs";
|
|
4982
5150
|
import os2 from "os";
|
|
4983
|
-
import
|
|
5151
|
+
import path36 from "path";
|
|
4984
5152
|
import {
|
|
4985
5153
|
RegistryFileSchema
|
|
4986
5154
|
} from "@neat.is/types";
|
|
@@ -4988,17 +5156,17 @@ var LOCK_TIMEOUT_MS = 5e3;
|
|
|
4988
5156
|
var LOCK_RETRY_MS = 50;
|
|
4989
5157
|
function neatHome() {
|
|
4990
5158
|
const override = process.env.NEAT_HOME;
|
|
4991
|
-
if (override && override.length > 0) return
|
|
4992
|
-
return
|
|
5159
|
+
if (override && override.length > 0) return path36.resolve(override);
|
|
5160
|
+
return path36.join(os2.homedir(), ".neat");
|
|
4993
5161
|
}
|
|
4994
5162
|
function registryPath() {
|
|
4995
|
-
return
|
|
5163
|
+
return path36.join(neatHome(), "projects.json");
|
|
4996
5164
|
}
|
|
4997
5165
|
function registryLockPath() {
|
|
4998
|
-
return
|
|
5166
|
+
return path36.join(neatHome(), "projects.json.lock");
|
|
4999
5167
|
}
|
|
5000
5168
|
function daemonPidPath() {
|
|
5001
|
-
return
|
|
5169
|
+
return path36.join(neatHome(), "neatd.pid");
|
|
5002
5170
|
}
|
|
5003
5171
|
function isPidAliveDefault(pid) {
|
|
5004
5172
|
try {
|
|
@@ -5058,7 +5226,7 @@ function lockHolderMessage(holder, lockPath, timeoutMs) {
|
|
|
5058
5226
|
}
|
|
5059
5227
|
}
|
|
5060
5228
|
async function normalizeProjectPath(input) {
|
|
5061
|
-
const resolved =
|
|
5229
|
+
const resolved = path36.resolve(input);
|
|
5062
5230
|
try {
|
|
5063
5231
|
return await fs20.realpath(resolved);
|
|
5064
5232
|
} catch {
|
|
@@ -5066,7 +5234,7 @@ async function normalizeProjectPath(input) {
|
|
|
5066
5234
|
}
|
|
5067
5235
|
}
|
|
5068
5236
|
async function writeAtomically(target, contents) {
|
|
5069
|
-
await fs20.mkdir(
|
|
5237
|
+
await fs20.mkdir(path36.dirname(target), { recursive: true });
|
|
5070
5238
|
const tmp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
|
|
5071
5239
|
const fd = await fs20.open(tmp, "w");
|
|
5072
5240
|
try {
|
|
@@ -5079,7 +5247,7 @@ async function writeAtomically(target, contents) {
|
|
|
5079
5247
|
}
|
|
5080
5248
|
async function acquireLock(lockPath, timeoutMs = LOCK_TIMEOUT_MS, probe = defaultLockHolderProbe) {
|
|
5081
5249
|
const deadline = Date.now() + timeoutMs;
|
|
5082
|
-
await fs20.mkdir(
|
|
5250
|
+
await fs20.mkdir(path36.dirname(lockPath), { recursive: true });
|
|
5083
5251
|
let probedHolder = false;
|
|
5084
5252
|
while (true) {
|
|
5085
5253
|
try {
|
|
@@ -5215,6 +5383,58 @@ async function removeProject(name) {
|
|
|
5215
5383
|
return removed;
|
|
5216
5384
|
});
|
|
5217
5385
|
}
|
|
5386
|
+
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
5387
|
+
var DEFAULT_PRUNE_TTL_MS = 7 * DAY_MS2;
|
|
5388
|
+
function pruneTtlMs() {
|
|
5389
|
+
const raw = process.env.NEAT_REGISTRY_PRUNE_TTL_MS;
|
|
5390
|
+
if (!raw) return DEFAULT_PRUNE_TTL_MS;
|
|
5391
|
+
const n = Number.parseInt(raw, 10);
|
|
5392
|
+
if (Number.isFinite(n) && n >= 0) return n;
|
|
5393
|
+
console.warn(
|
|
5394
|
+
`[neat] NEAT_REGISTRY_PRUNE_TTL_MS could not be parsed (${raw}); using default ${DEFAULT_PRUNE_TTL_MS}ms`
|
|
5395
|
+
);
|
|
5396
|
+
return DEFAULT_PRUNE_TTL_MS;
|
|
5397
|
+
}
|
|
5398
|
+
async function statPathStatus(p) {
|
|
5399
|
+
try {
|
|
5400
|
+
const stat = await fs20.stat(p);
|
|
5401
|
+
return stat.isDirectory() ? "present" : "unknown";
|
|
5402
|
+
} catch (err) {
|
|
5403
|
+
return err.code === "ENOENT" ? "gone" : "unknown";
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
async function pruneRegistry(opts = {}) {
|
|
5407
|
+
const ttlMs = opts.ttlMs ?? pruneTtlMs();
|
|
5408
|
+
const statPath = opts.statPath ?? statPathStatus;
|
|
5409
|
+
const now = opts.now ?? Date.now;
|
|
5410
|
+
return withLock(async () => {
|
|
5411
|
+
const reg = await readRegistry();
|
|
5412
|
+
const removed = [];
|
|
5413
|
+
const kept = [];
|
|
5414
|
+
for (const entry of reg.projects) {
|
|
5415
|
+
const status = await statPath(entry.path);
|
|
5416
|
+
if (status !== "gone") {
|
|
5417
|
+
kept.push(entry);
|
|
5418
|
+
continue;
|
|
5419
|
+
}
|
|
5420
|
+
if (ttlMs <= 0) {
|
|
5421
|
+
removed.push(entry);
|
|
5422
|
+
continue;
|
|
5423
|
+
}
|
|
5424
|
+
const lastSeen = Date.parse(entry.lastSeenAt ?? entry.registeredAt);
|
|
5425
|
+
const age = now() - (Number.isFinite(lastSeen) ? lastSeen : 0);
|
|
5426
|
+
if (age > ttlMs) {
|
|
5427
|
+
removed.push(entry);
|
|
5428
|
+
} else {
|
|
5429
|
+
kept.push(entry);
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
if (removed.length === 0) return [];
|
|
5433
|
+
reg.projects = kept;
|
|
5434
|
+
await writeRegistry(reg);
|
|
5435
|
+
return removed;
|
|
5436
|
+
});
|
|
5437
|
+
}
|
|
5218
5438
|
|
|
5219
5439
|
// src/api.ts
|
|
5220
5440
|
import Fastify from "fastify";
|
|
@@ -5223,13 +5443,13 @@ import { DivergenceTypeSchema, PoliciesCheckBodySchema, PolicySeveritySchema } f
|
|
|
5223
5443
|
|
|
5224
5444
|
// src/extend/index.ts
|
|
5225
5445
|
import { promises as fs22 } from "fs";
|
|
5226
|
-
import
|
|
5446
|
+
import path38 from "path";
|
|
5227
5447
|
import os3 from "os";
|
|
5228
5448
|
import { resolve as registryResolve, list as registryList } from "@neat.is/instrumentation-registry";
|
|
5229
5449
|
|
|
5230
5450
|
// src/installers/package-manager.ts
|
|
5231
5451
|
import { promises as fs21 } from "fs";
|
|
5232
|
-
import
|
|
5452
|
+
import path37 from "path";
|
|
5233
5453
|
import { spawn } from "child_process";
|
|
5234
5454
|
var LOCKFILE_PRIORITY = [
|
|
5235
5455
|
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
@@ -5251,22 +5471,22 @@ async function exists2(p) {
|
|
|
5251
5471
|
}
|
|
5252
5472
|
}
|
|
5253
5473
|
async function detectPackageManager(serviceDir) {
|
|
5254
|
-
let dir =
|
|
5474
|
+
let dir = path37.resolve(serviceDir);
|
|
5255
5475
|
const stops = /* @__PURE__ */ new Set();
|
|
5256
5476
|
for (let i = 0; i < 64; i++) {
|
|
5257
5477
|
if (stops.has(dir)) break;
|
|
5258
5478
|
stops.add(dir);
|
|
5259
5479
|
for (const candidate of LOCKFILE_PRIORITY) {
|
|
5260
|
-
const lockPath =
|
|
5480
|
+
const lockPath = path37.join(dir, candidate.lockfile);
|
|
5261
5481
|
if (await exists2(lockPath)) {
|
|
5262
5482
|
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
5263
5483
|
}
|
|
5264
5484
|
}
|
|
5265
|
-
const parent =
|
|
5485
|
+
const parent = path37.dirname(dir);
|
|
5266
5486
|
if (parent === dir) break;
|
|
5267
5487
|
dir = parent;
|
|
5268
5488
|
}
|
|
5269
|
-
return { pm: "npm", cwd:
|
|
5489
|
+
return { pm: "npm", cwd: path37.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
5270
5490
|
}
|
|
5271
5491
|
async function runPackageManagerInstall(cmd) {
|
|
5272
5492
|
return new Promise((resolve) => {
|
|
@@ -5315,7 +5535,7 @@ async function fileExists2(p) {
|
|
|
5315
5535
|
}
|
|
5316
5536
|
}
|
|
5317
5537
|
async function readPackageJson(scanPath) {
|
|
5318
|
-
const pkgPath =
|
|
5538
|
+
const pkgPath = path38.join(scanPath, "package.json");
|
|
5319
5539
|
const raw = await fs22.readFile(pkgPath, "utf8");
|
|
5320
5540
|
return JSON.parse(raw);
|
|
5321
5541
|
}
|
|
@@ -5326,11 +5546,11 @@ async function findHookFiles(scanPath) {
|
|
|
5326
5546
|
).sort();
|
|
5327
5547
|
}
|
|
5328
5548
|
function extendLogPath() {
|
|
5329
|
-
return process.env.NEAT_EXTEND_LOG ??
|
|
5549
|
+
return process.env.NEAT_EXTEND_LOG ?? path38.join(os3.homedir(), ".neat", "extend-log.ndjson");
|
|
5330
5550
|
}
|
|
5331
5551
|
async function appendExtendLog(entry) {
|
|
5332
5552
|
const logPath = extendLogPath();
|
|
5333
|
-
await fs22.mkdir(
|
|
5553
|
+
await fs22.mkdir(path38.dirname(logPath), { recursive: true });
|
|
5334
5554
|
await fs22.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8");
|
|
5335
5555
|
}
|
|
5336
5556
|
function splicedContent(fileContent, snippet2) {
|
|
@@ -5389,7 +5609,7 @@ function lookupInstrumentation(library, installedVersion) {
|
|
|
5389
5609
|
}
|
|
5390
5610
|
async function describeProjectInstrumentation(ctx) {
|
|
5391
5611
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5392
|
-
const envNeat = await fileExists2(
|
|
5612
|
+
const envNeat = await fileExists2(path38.join(ctx.scanPath, ".env.neat"));
|
|
5393
5613
|
const registryInstrPackages = new Set(
|
|
5394
5614
|
registryList().map((e) => e.instrumentation_package).filter((p) => !!p)
|
|
5395
5615
|
);
|
|
@@ -5411,16 +5631,16 @@ async function applyExtension(ctx, args, options) {
|
|
|
5411
5631
|
);
|
|
5412
5632
|
}
|
|
5413
5633
|
for (const file of hookFiles) {
|
|
5414
|
-
const content = await fs22.readFile(
|
|
5634
|
+
const content = await fs22.readFile(path38.join(ctx.scanPath, file), "utf8");
|
|
5415
5635
|
if (content.includes(args.registration_snippet)) {
|
|
5416
5636
|
return { library: args.library, filesTouched: [], depsAdded: [], installOutput: "", alreadyApplied: true };
|
|
5417
5637
|
}
|
|
5418
5638
|
}
|
|
5419
5639
|
const primaryFile = hookFiles[0];
|
|
5420
|
-
const primaryPath =
|
|
5640
|
+
const primaryPath = path38.join(ctx.scanPath, primaryFile);
|
|
5421
5641
|
const filesTouched = [];
|
|
5422
5642
|
const depsAdded = [];
|
|
5423
|
-
const pkgPath =
|
|
5643
|
+
const pkgPath = path38.join(ctx.scanPath, "package.json");
|
|
5424
5644
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
5425
5645
|
if (!(pkg.dependencies ?? {})[args.instrumentation_package]) {
|
|
5426
5646
|
pkg.dependencies = { ...pkg.dependencies ?? {}, [args.instrumentation_package]: args.version };
|
|
@@ -5466,7 +5686,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
5466
5686
|
};
|
|
5467
5687
|
}
|
|
5468
5688
|
for (const file of hookFiles) {
|
|
5469
|
-
const content = await fs22.readFile(
|
|
5689
|
+
const content = await fs22.readFile(path38.join(ctx.scanPath, file), "utf8");
|
|
5470
5690
|
if (content.includes(args.registration_snippet)) {
|
|
5471
5691
|
return {
|
|
5472
5692
|
library: args.library,
|
|
@@ -5488,7 +5708,7 @@ async function dryRunExtension(ctx, args) {
|
|
|
5488
5708
|
depsToAdd.push(`${args.instrumentation_package}@${args.version}`);
|
|
5489
5709
|
filesTouched.push("package.json");
|
|
5490
5710
|
}
|
|
5491
|
-
const hookContent = await fs22.readFile(
|
|
5711
|
+
const hookContent = await fs22.readFile(path38.join(ctx.scanPath, primaryFile), "utf8");
|
|
5492
5712
|
const patched = splicedContent(hookContent, args.registration_snippet);
|
|
5493
5713
|
if (patched) {
|
|
5494
5714
|
filesTouched.push(primaryFile);
|
|
@@ -5509,7 +5729,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
5509
5729
|
if (!match) {
|
|
5510
5730
|
return { undone: false, message: "no apply found for library" };
|
|
5511
5731
|
}
|
|
5512
|
-
const pkgPath =
|
|
5732
|
+
const pkgPath = path38.join(ctx.scanPath, "package.json");
|
|
5513
5733
|
if (await fileExists2(pkgPath)) {
|
|
5514
5734
|
const pkg = await readPackageJson(ctx.scanPath);
|
|
5515
5735
|
if (pkg.dependencies?.[match.instrumentation_package]) {
|
|
@@ -5520,7 +5740,7 @@ async function rollbackExtension(ctx, args) {
|
|
|
5520
5740
|
}
|
|
5521
5741
|
const hookFiles = await findHookFiles(ctx.scanPath);
|
|
5522
5742
|
for (const file of hookFiles) {
|
|
5523
|
-
const filePath =
|
|
5743
|
+
const filePath = path38.join(ctx.scanPath, file);
|
|
5524
5744
|
const content = await fs22.readFile(filePath, "utf8");
|
|
5525
5745
|
if (content.includes(match.registration_snippet)) {
|
|
5526
5746
|
const filtered = content.split("\n").filter((line) => !line.includes(match.registration_snippet)).join("\n");
|
|
@@ -5545,6 +5765,7 @@ function handleSse(req, reply, opts) {
|
|
|
5545
5765
|
reply.raw.setHeader("Connection", "keep-alive");
|
|
5546
5766
|
reply.raw.setHeader("X-Accel-Buffering", "no");
|
|
5547
5767
|
reply.raw.flushHeaders?.();
|
|
5768
|
+
reply.raw.write(":open\n\n");
|
|
5548
5769
|
let pending = 0;
|
|
5549
5770
|
let dropped = false;
|
|
5550
5771
|
const closeConnection = () => {
|
|
@@ -6254,6 +6475,7 @@ export {
|
|
|
6254
6475
|
setStatus,
|
|
6255
6476
|
touchLastSeen,
|
|
6256
6477
|
removeProject,
|
|
6478
|
+
pruneRegistry,
|
|
6257
6479
|
buildApi
|
|
6258
6480
|
};
|
|
6259
|
-
//# sourceMappingURL=chunk-
|
|
6481
|
+
//# sourceMappingURL=chunk-LUDSPX5N.js.map
|