@neat.is/core 0.6.3 → 0.6.4
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-PWIT35Z4.js → chunk-22X2YM5H.js} +2 -2
- package/dist/{chunk-L64CATKE.js → chunk-UCDXHLCJ.js} +82 -9
- package/dist/chunk-UCDXHLCJ.js.map +1 -0
- package/dist/cli.cjs +81 -8
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.cjs +142 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +142 -8
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +150 -8
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-L64CATKE.js.map +0 -1
- /package/dist/{chunk-PWIT35Z4.js.map → chunk-22X2YM5H.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
routeSpanToProject,
|
|
3
3
|
startDaemon
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-22X2YM5H.js";
|
|
5
5
|
import {
|
|
6
6
|
ProjectNameCollisionError,
|
|
7
7
|
addProject,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
thresholdForEdgeType,
|
|
38
38
|
touchLastSeen,
|
|
39
39
|
writeAtomically
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-UCDXHLCJ.js";
|
|
41
41
|
import {
|
|
42
42
|
startOtelGrpcReceiver
|
|
43
43
|
} from "./chunk-7QXN726V.js";
|
package/dist/neatd.cjs
CHANGED
|
@@ -2285,6 +2285,41 @@ async function writeExtractionErrors(errors, errorsPath) {
|
|
|
2285
2285
|
const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2286
2286
|
await import_node_fs4.promises.appendFile(errorsPath, lines, "utf8");
|
|
2287
2287
|
}
|
|
2288
|
+
function extractionHealthPathFor(errorsPath) {
|
|
2289
|
+
const dir = import_node_path4.default.dirname(errorsPath);
|
|
2290
|
+
const suffix = import_node_path4.default.basename(errorsPath).replace(/^errors/, "").replace(/\.ndjson$/, "");
|
|
2291
|
+
return import_node_path4.default.join(dir, `extraction-health${suffix}.json`);
|
|
2292
|
+
}
|
|
2293
|
+
var HEALTH_FILE_SAMPLE = 100;
|
|
2294
|
+
async function writeExtractionHealth(errors, healthPath, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2295
|
+
const byProducer = {};
|
|
2296
|
+
for (const e of errors) byProducer[e.producer] = (byProducer[e.producer] ?? 0) + 1;
|
|
2297
|
+
const coverage = {
|
|
2298
|
+
skippedFiles: errors.length,
|
|
2299
|
+
byProducer,
|
|
2300
|
+
files: errors.slice(0, HEALTH_FILE_SAMPLE).map((e) => e.file),
|
|
2301
|
+
updatedAt: now
|
|
2302
|
+
};
|
|
2303
|
+
await import_node_fs4.promises.mkdir(import_node_path4.default.dirname(healthPath), { recursive: true });
|
|
2304
|
+
await import_node_fs4.promises.writeFile(healthPath, JSON.stringify(coverage), "utf8");
|
|
2305
|
+
}
|
|
2306
|
+
async function readExtractionHealth(healthPath) {
|
|
2307
|
+
let raw;
|
|
2308
|
+
try {
|
|
2309
|
+
raw = await import_node_fs4.promises.readFile(healthPath, "utf8");
|
|
2310
|
+
} catch {
|
|
2311
|
+
return void 0;
|
|
2312
|
+
}
|
|
2313
|
+
try {
|
|
2314
|
+
const obj = JSON.parse(raw);
|
|
2315
|
+
if (typeof obj?.skippedFiles !== "number" || !obj.byProducer || !Array.isArray(obj.files)) {
|
|
2316
|
+
return void 0;
|
|
2317
|
+
}
|
|
2318
|
+
return obj;
|
|
2319
|
+
} catch {
|
|
2320
|
+
return void 0;
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2288
2323
|
var droppedSink = [];
|
|
2289
2324
|
function noteExtractedDropped(edge) {
|
|
2290
2325
|
droppedSink.push(edge);
|
|
@@ -3236,14 +3271,16 @@ function reconcileObservedRelPath(graph, serviceName, relPath) {
|
|
|
3236
3271
|
return best ?? relPath;
|
|
3237
3272
|
}
|
|
3238
3273
|
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
3239
|
-
const
|
|
3240
|
-
const
|
|
3274
|
+
const svcAttrs = graph.hasNode(serviceNodeId) ? graph.getNodeAttributes(serviceNodeId) : void 0;
|
|
3275
|
+
const canonicalService = svcAttrs && svcAttrs.type === import_types6.NodeType.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
|
|
3276
|
+
const relPath = reconcileObservedRelPath(graph, canonicalService, callSite.relPath);
|
|
3277
|
+
const fileNodeId = (0, import_types6.fileId)(canonicalService, relPath);
|
|
3241
3278
|
if (!graph.hasNode(fileNodeId)) {
|
|
3242
3279
|
const language = languageForExt(relPath);
|
|
3243
3280
|
const node = {
|
|
3244
3281
|
id: fileNodeId,
|
|
3245
3282
|
type: import_types6.NodeType.FileNode,
|
|
3246
|
-
service:
|
|
3283
|
+
service: canonicalService,
|
|
3247
3284
|
path: relPath,
|
|
3248
3285
|
...language ? { language } : {},
|
|
3249
3286
|
...callSite.originalRelPath ? { originalPath: callSite.originalRelPath } : {},
|
|
@@ -8352,6 +8389,15 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
8352
8389
|
);
|
|
8353
8390
|
}
|
|
8354
8391
|
}
|
|
8392
|
+
if (opts.errorsPath) {
|
|
8393
|
+
try {
|
|
8394
|
+
await writeExtractionHealth(errorEntries, extractionHealthPathFor(opts.errorsPath));
|
|
8395
|
+
} catch (err) {
|
|
8396
|
+
console.warn(
|
|
8397
|
+
`[neat] failed to write extraction health sidecar: ${err.message}`
|
|
8398
|
+
);
|
|
8399
|
+
}
|
|
8400
|
+
}
|
|
8355
8401
|
const droppedEntries = drainDroppedExtracted();
|
|
8356
8402
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
8357
8403
|
const rejectedPath = import_node_path43.default.join(import_node_path43.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
@@ -9326,6 +9372,67 @@ function registryLockPath() {
|
|
|
9326
9372
|
function daemonPidPath() {
|
|
9327
9373
|
return import_node_path48.default.join(neatHome(), "neatd.pid");
|
|
9328
9374
|
}
|
|
9375
|
+
function daemonsDir() {
|
|
9376
|
+
return import_node_path48.default.join(neatHome(), "daemons");
|
|
9377
|
+
}
|
|
9378
|
+
function isFiniteInt(v) {
|
|
9379
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
9380
|
+
}
|
|
9381
|
+
function parseDaemonRecord(raw) {
|
|
9382
|
+
let obj;
|
|
9383
|
+
try {
|
|
9384
|
+
obj = JSON.parse(raw);
|
|
9385
|
+
} catch {
|
|
9386
|
+
return void 0;
|
|
9387
|
+
}
|
|
9388
|
+
if (typeof obj !== "object" || obj === null) return void 0;
|
|
9389
|
+
const r = obj;
|
|
9390
|
+
const ports = r.ports;
|
|
9391
|
+
if (typeof r.project !== "string" || typeof r.projectPath !== "string" || !isFiniteInt(r.pid) || r.status !== "running" && r.status !== "stopped" || typeof r.startedAt !== "string" || typeof r.neatVersion !== "string" || !ports || !isFiniteInt(ports.rest) || !isFiniteInt(ports.otlp) || !isFiniteInt(ports.web)) {
|
|
9392
|
+
return void 0;
|
|
9393
|
+
}
|
|
9394
|
+
return {
|
|
9395
|
+
project: r.project,
|
|
9396
|
+
projectPath: r.projectPath,
|
|
9397
|
+
pid: r.pid,
|
|
9398
|
+
status: r.status,
|
|
9399
|
+
ports: { rest: ports.rest, otlp: ports.otlp, web: ports.web },
|
|
9400
|
+
startedAt: r.startedAt,
|
|
9401
|
+
neatVersion: r.neatVersion
|
|
9402
|
+
};
|
|
9403
|
+
}
|
|
9404
|
+
var defaultDiscoveryProbe = { isPidAlive: isPidAliveDefault };
|
|
9405
|
+
async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
9406
|
+
const dir = daemonsDir();
|
|
9407
|
+
let names;
|
|
9408
|
+
try {
|
|
9409
|
+
names = await import_node_fs28.promises.readdir(dir);
|
|
9410
|
+
} catch (err) {
|
|
9411
|
+
if (err.code === "ENOENT") return [];
|
|
9412
|
+
throw err;
|
|
9413
|
+
}
|
|
9414
|
+
const out = [];
|
|
9415
|
+
for (const name of names) {
|
|
9416
|
+
if (!name.endsWith(".json")) continue;
|
|
9417
|
+
const file = import_node_path48.default.join(dir, name);
|
|
9418
|
+
let raw;
|
|
9419
|
+
try {
|
|
9420
|
+
raw = await import_node_fs28.promises.readFile(file, "utf8");
|
|
9421
|
+
} catch {
|
|
9422
|
+
continue;
|
|
9423
|
+
}
|
|
9424
|
+
const record = parseDaemonRecord(raw);
|
|
9425
|
+
if (!record) continue;
|
|
9426
|
+
const live = record.status === "running" && probe.isPidAlive(record.pid);
|
|
9427
|
+
out.push({ record, live, source: file });
|
|
9428
|
+
}
|
|
9429
|
+
out.sort((a, b) => a.record.project.localeCompare(b.record.project));
|
|
9430
|
+
return out;
|
|
9431
|
+
}
|
|
9432
|
+
async function findDaemonByProject(name, probe = defaultDiscoveryProbe) {
|
|
9433
|
+
const discovered = await discoverDaemons(probe);
|
|
9434
|
+
return discovered.find((d) => d.record.project === name);
|
|
9435
|
+
}
|
|
9329
9436
|
function isPidAliveDefault(pid) {
|
|
9330
9437
|
try {
|
|
9331
9438
|
process.kill(pid, 0);
|
|
@@ -9835,7 +9942,11 @@ function resolveProject(registry, req2, reply, bootstrap, singleProject) {
|
|
|
9835
9942
|
void reply.code(503).send({ ready: false, project: name, status: "broken" });
|
|
9836
9943
|
return null;
|
|
9837
9944
|
}
|
|
9838
|
-
void reply.code(404).send({
|
|
9945
|
+
void reply.code(404).send({
|
|
9946
|
+
error: "project not found",
|
|
9947
|
+
project: name,
|
|
9948
|
+
hint: "This daemon does not host that project. GET /projects lists what it serves (see hostedHere)."
|
|
9949
|
+
});
|
|
9839
9950
|
return null;
|
|
9840
9951
|
}
|
|
9841
9952
|
return ctx;
|
|
@@ -9873,6 +9984,9 @@ function registerRoutes(scope, ctx) {
|
|
|
9873
9984
|
const proj = resolveProject(registry, req2, reply, ctx.bootstrap, ctx.singleProject);
|
|
9874
9985
|
if (!proj) return;
|
|
9875
9986
|
const uptimeMs = Date.now() - startedAt;
|
|
9987
|
+
const coverage = await readExtractionHealth(
|
|
9988
|
+
extractionHealthPathFor(proj.paths.errorsPath)
|
|
9989
|
+
);
|
|
9876
9990
|
return {
|
|
9877
9991
|
ok: true,
|
|
9878
9992
|
project: proj.name,
|
|
@@ -9883,7 +9997,8 @@ function registerRoutes(scope, ctx) {
|
|
|
9883
9997
|
uptime: Math.floor(uptimeMs / 1e3),
|
|
9884
9998
|
nodeCount: proj.graph.order,
|
|
9885
9999
|
edgeCount: proj.graph.size,
|
|
9886
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
10000
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10001
|
+
...coverage ? { coverage } : {}
|
|
9887
10002
|
};
|
|
9888
10003
|
});
|
|
9889
10004
|
}
|
|
@@ -10525,12 +10640,17 @@ async function buildApi(opts) {
|
|
|
10525
10640
|
// The daemon is serving this project, so it's active unless its
|
|
10526
10641
|
// bootstrap broke. ('bootstrapping' still reads as active — the project
|
|
10527
10642
|
// is the one to land on; its graph route answers 503 until ready.)
|
|
10528
|
-
status: phase === "broken" ? "broken" : "active"
|
|
10643
|
+
status: phase === "broken" ? "broken" : "active",
|
|
10644
|
+
hostedHere: true
|
|
10529
10645
|
};
|
|
10530
10646
|
return [entry2];
|
|
10531
10647
|
}
|
|
10532
10648
|
try {
|
|
10533
|
-
|
|
10649
|
+
const entries = await listProjects();
|
|
10650
|
+
return entries.map((entry2) => ({
|
|
10651
|
+
...entry2,
|
|
10652
|
+
hostedHere: registry.has(entry2.name)
|
|
10653
|
+
}));
|
|
10534
10654
|
} catch (err) {
|
|
10535
10655
|
return reply.code(500).send({
|
|
10536
10656
|
error: "failed to read project registry",
|
|
@@ -10544,7 +10664,21 @@ async function buildApi(opts) {
|
|
|
10544
10664
|
if (!entry2) {
|
|
10545
10665
|
return reply.code(404).send({ error: "project not found", project: req2.params.project });
|
|
10546
10666
|
}
|
|
10547
|
-
|
|
10667
|
+
const hostedHere = registry.has(entry2.name);
|
|
10668
|
+
if (!hostedHere) {
|
|
10669
|
+
const owner = await findDaemonByProject(entry2.name).catch(() => void 0);
|
|
10670
|
+
if (owner) {
|
|
10671
|
+
return {
|
|
10672
|
+
project: { ...entry2, hostedHere },
|
|
10673
|
+
servedBy: {
|
|
10674
|
+
path: owner.record.projectPath,
|
|
10675
|
+
restPort: owner.record.ports.rest,
|
|
10676
|
+
live: owner.live
|
|
10677
|
+
}
|
|
10678
|
+
};
|
|
10679
|
+
}
|
|
10680
|
+
}
|
|
10681
|
+
return { project: { ...entry2, hostedHere } };
|
|
10548
10682
|
} catch (err) {
|
|
10549
10683
|
return reply.code(500).send({
|
|
10550
10684
|
error: "failed to read project registry",
|