@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/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
startConnectorPolling,
|
|
14
14
|
validateConnectorEntry,
|
|
15
15
|
writeDaemonRecord
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-22X2YM5H.js";
|
|
17
17
|
import {
|
|
18
18
|
buildSearchIndex
|
|
19
19
|
} from "./chunk-BIY46Q6U.js";
|
|
@@ -72,7 +72,7 @@ import {
|
|
|
72
72
|
startPersistLoop,
|
|
73
73
|
startStalenessLoop,
|
|
74
74
|
upsertConnectorEntry
|
|
75
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-UCDXHLCJ.js";
|
|
76
76
|
import {
|
|
77
77
|
startOtelGrpcReceiver
|
|
78
78
|
} from "./chunk-7QXN726V.js";
|
package/dist/index.cjs
CHANGED
|
@@ -2322,6 +2322,41 @@ async function writeExtractionErrors(errors, errorsPath) {
|
|
|
2322
2322
|
const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2323
2323
|
await import_node_fs4.promises.appendFile(errorsPath, lines, "utf8");
|
|
2324
2324
|
}
|
|
2325
|
+
function extractionHealthPathFor(errorsPath) {
|
|
2326
|
+
const dir = import_node_path4.default.dirname(errorsPath);
|
|
2327
|
+
const suffix = import_node_path4.default.basename(errorsPath).replace(/^errors/, "").replace(/\.ndjson$/, "");
|
|
2328
|
+
return import_node_path4.default.join(dir, `extraction-health${suffix}.json`);
|
|
2329
|
+
}
|
|
2330
|
+
var HEALTH_FILE_SAMPLE = 100;
|
|
2331
|
+
async function writeExtractionHealth(errors, healthPath, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2332
|
+
const byProducer = {};
|
|
2333
|
+
for (const e of errors) byProducer[e.producer] = (byProducer[e.producer] ?? 0) + 1;
|
|
2334
|
+
const coverage = {
|
|
2335
|
+
skippedFiles: errors.length,
|
|
2336
|
+
byProducer,
|
|
2337
|
+
files: errors.slice(0, HEALTH_FILE_SAMPLE).map((e) => e.file),
|
|
2338
|
+
updatedAt: now
|
|
2339
|
+
};
|
|
2340
|
+
await import_node_fs4.promises.mkdir(import_node_path4.default.dirname(healthPath), { recursive: true });
|
|
2341
|
+
await import_node_fs4.promises.writeFile(healthPath, JSON.stringify(coverage), "utf8");
|
|
2342
|
+
}
|
|
2343
|
+
async function readExtractionHealth(healthPath) {
|
|
2344
|
+
let raw;
|
|
2345
|
+
try {
|
|
2346
|
+
raw = await import_node_fs4.promises.readFile(healthPath, "utf8");
|
|
2347
|
+
} catch {
|
|
2348
|
+
return void 0;
|
|
2349
|
+
}
|
|
2350
|
+
try {
|
|
2351
|
+
const obj = JSON.parse(raw);
|
|
2352
|
+
if (typeof obj?.skippedFiles !== "number" || !obj.byProducer || !Array.isArray(obj.files)) {
|
|
2353
|
+
return void 0;
|
|
2354
|
+
}
|
|
2355
|
+
return obj;
|
|
2356
|
+
} catch {
|
|
2357
|
+
return void 0;
|
|
2358
|
+
}
|
|
2359
|
+
}
|
|
2325
2360
|
var droppedSink = [];
|
|
2326
2361
|
function noteExtractedDropped(edge) {
|
|
2327
2362
|
droppedSink.push(edge);
|
|
@@ -3273,14 +3308,16 @@ function reconcileObservedRelPath(graph, serviceName, relPath) {
|
|
|
3273
3308
|
return best ?? relPath;
|
|
3274
3309
|
}
|
|
3275
3310
|
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
3276
|
-
const
|
|
3277
|
-
const
|
|
3311
|
+
const svcAttrs = graph.hasNode(serviceNodeId) ? graph.getNodeAttributes(serviceNodeId) : void 0;
|
|
3312
|
+
const canonicalService = svcAttrs && svcAttrs.type === import_types6.NodeType.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
|
|
3313
|
+
const relPath = reconcileObservedRelPath(graph, canonicalService, callSite.relPath);
|
|
3314
|
+
const fileNodeId = (0, import_types6.fileId)(canonicalService, relPath);
|
|
3278
3315
|
if (!graph.hasNode(fileNodeId)) {
|
|
3279
3316
|
const language = languageForExt(relPath);
|
|
3280
3317
|
const node = {
|
|
3281
3318
|
id: fileNodeId,
|
|
3282
3319
|
type: import_types6.NodeType.FileNode,
|
|
3283
|
-
service:
|
|
3320
|
+
service: canonicalService,
|
|
3284
3321
|
path: relPath,
|
|
3285
3322
|
...language ? { language } : {},
|
|
3286
3323
|
...callSite.originalRelPath ? { originalPath: callSite.originalRelPath } : {},
|
|
@@ -8392,6 +8429,15 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
8392
8429
|
);
|
|
8393
8430
|
}
|
|
8394
8431
|
}
|
|
8432
|
+
if (opts.errorsPath) {
|
|
8433
|
+
try {
|
|
8434
|
+
await writeExtractionHealth(errorEntries, extractionHealthPathFor(opts.errorsPath));
|
|
8435
|
+
} catch (err) {
|
|
8436
|
+
console.warn(
|
|
8437
|
+
`[neat] failed to write extraction health sidecar: ${err.message}`
|
|
8438
|
+
);
|
|
8439
|
+
}
|
|
8440
|
+
}
|
|
8395
8441
|
const droppedEntries = drainDroppedExtracted();
|
|
8396
8442
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
8397
8443
|
const rejectedPath = import_node_path43.default.join(import_node_path43.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
@@ -9366,6 +9412,67 @@ function registryLockPath() {
|
|
|
9366
9412
|
function daemonPidPath() {
|
|
9367
9413
|
return import_node_path48.default.join(neatHome(), "neatd.pid");
|
|
9368
9414
|
}
|
|
9415
|
+
function daemonsDir() {
|
|
9416
|
+
return import_node_path48.default.join(neatHome(), "daemons");
|
|
9417
|
+
}
|
|
9418
|
+
function isFiniteInt(v) {
|
|
9419
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
9420
|
+
}
|
|
9421
|
+
function parseDaemonRecord(raw) {
|
|
9422
|
+
let obj;
|
|
9423
|
+
try {
|
|
9424
|
+
obj = JSON.parse(raw);
|
|
9425
|
+
} catch {
|
|
9426
|
+
return void 0;
|
|
9427
|
+
}
|
|
9428
|
+
if (typeof obj !== "object" || obj === null) return void 0;
|
|
9429
|
+
const r = obj;
|
|
9430
|
+
const ports = r.ports;
|
|
9431
|
+
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)) {
|
|
9432
|
+
return void 0;
|
|
9433
|
+
}
|
|
9434
|
+
return {
|
|
9435
|
+
project: r.project,
|
|
9436
|
+
projectPath: r.projectPath,
|
|
9437
|
+
pid: r.pid,
|
|
9438
|
+
status: r.status,
|
|
9439
|
+
ports: { rest: ports.rest, otlp: ports.otlp, web: ports.web },
|
|
9440
|
+
startedAt: r.startedAt,
|
|
9441
|
+
neatVersion: r.neatVersion
|
|
9442
|
+
};
|
|
9443
|
+
}
|
|
9444
|
+
var defaultDiscoveryProbe = { isPidAlive: isPidAliveDefault };
|
|
9445
|
+
async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
9446
|
+
const dir = daemonsDir();
|
|
9447
|
+
let names;
|
|
9448
|
+
try {
|
|
9449
|
+
names = await import_node_fs28.promises.readdir(dir);
|
|
9450
|
+
} catch (err) {
|
|
9451
|
+
if (err.code === "ENOENT") return [];
|
|
9452
|
+
throw err;
|
|
9453
|
+
}
|
|
9454
|
+
const out = [];
|
|
9455
|
+
for (const name of names) {
|
|
9456
|
+
if (!name.endsWith(".json")) continue;
|
|
9457
|
+
const file = import_node_path48.default.join(dir, name);
|
|
9458
|
+
let raw;
|
|
9459
|
+
try {
|
|
9460
|
+
raw = await import_node_fs28.promises.readFile(file, "utf8");
|
|
9461
|
+
} catch {
|
|
9462
|
+
continue;
|
|
9463
|
+
}
|
|
9464
|
+
const record = parseDaemonRecord(raw);
|
|
9465
|
+
if (!record) continue;
|
|
9466
|
+
const live = record.status === "running" && probe.isPidAlive(record.pid);
|
|
9467
|
+
out.push({ record, live, source: file });
|
|
9468
|
+
}
|
|
9469
|
+
out.sort((a, b) => a.record.project.localeCompare(b.record.project));
|
|
9470
|
+
return out;
|
|
9471
|
+
}
|
|
9472
|
+
async function findDaemonByProject(name, probe = defaultDiscoveryProbe) {
|
|
9473
|
+
const discovered = await discoverDaemons(probe);
|
|
9474
|
+
return discovered.find((d) => d.record.project === name);
|
|
9475
|
+
}
|
|
9369
9476
|
function isPidAliveDefault(pid) {
|
|
9370
9477
|
try {
|
|
9371
9478
|
process.kill(pid, 0);
|
|
@@ -9933,7 +10040,11 @@ function resolveProject(registry, req, reply, bootstrap, singleProject) {
|
|
|
9933
10040
|
void reply.code(503).send({ ready: false, project: name, status: "broken" });
|
|
9934
10041
|
return null;
|
|
9935
10042
|
}
|
|
9936
|
-
void reply.code(404).send({
|
|
10043
|
+
void reply.code(404).send({
|
|
10044
|
+
error: "project not found",
|
|
10045
|
+
project: name,
|
|
10046
|
+
hint: "This daemon does not host that project. GET /projects lists what it serves (see hostedHere)."
|
|
10047
|
+
});
|
|
9937
10048
|
return null;
|
|
9938
10049
|
}
|
|
9939
10050
|
return ctx;
|
|
@@ -9971,6 +10082,9 @@ function registerRoutes(scope, ctx) {
|
|
|
9971
10082
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
9972
10083
|
if (!proj) return;
|
|
9973
10084
|
const uptimeMs = Date.now() - startedAt;
|
|
10085
|
+
const coverage = await readExtractionHealth(
|
|
10086
|
+
extractionHealthPathFor(proj.paths.errorsPath)
|
|
10087
|
+
);
|
|
9974
10088
|
return {
|
|
9975
10089
|
ok: true,
|
|
9976
10090
|
project: proj.name,
|
|
@@ -9981,7 +10095,8 @@ function registerRoutes(scope, ctx) {
|
|
|
9981
10095
|
uptime: Math.floor(uptimeMs / 1e3),
|
|
9982
10096
|
nodeCount: proj.graph.order,
|
|
9983
10097
|
edgeCount: proj.graph.size,
|
|
9984
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
10098
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10099
|
+
...coverage ? { coverage } : {}
|
|
9985
10100
|
};
|
|
9986
10101
|
});
|
|
9987
10102
|
}
|
|
@@ -10623,12 +10738,17 @@ async function buildApi(opts) {
|
|
|
10623
10738
|
// The daemon is serving this project, so it's active unless its
|
|
10624
10739
|
// bootstrap broke. ('bootstrapping' still reads as active — the project
|
|
10625
10740
|
// is the one to land on; its graph route answers 503 until ready.)
|
|
10626
|
-
status: phase === "broken" ? "broken" : "active"
|
|
10741
|
+
status: phase === "broken" ? "broken" : "active",
|
|
10742
|
+
hostedHere: true
|
|
10627
10743
|
};
|
|
10628
10744
|
return [entry];
|
|
10629
10745
|
}
|
|
10630
10746
|
try {
|
|
10631
|
-
|
|
10747
|
+
const entries = await listProjects();
|
|
10748
|
+
return entries.map((entry) => ({
|
|
10749
|
+
...entry,
|
|
10750
|
+
hostedHere: registry.has(entry.name)
|
|
10751
|
+
}));
|
|
10632
10752
|
} catch (err) {
|
|
10633
10753
|
return reply.code(500).send({
|
|
10634
10754
|
error: "failed to read project registry",
|
|
@@ -10642,7 +10762,21 @@ async function buildApi(opts) {
|
|
|
10642
10762
|
if (!entry) {
|
|
10643
10763
|
return reply.code(404).send({ error: "project not found", project: req.params.project });
|
|
10644
10764
|
}
|
|
10645
|
-
|
|
10765
|
+
const hostedHere = registry.has(entry.name);
|
|
10766
|
+
if (!hostedHere) {
|
|
10767
|
+
const owner = await findDaemonByProject(entry.name).catch(() => void 0);
|
|
10768
|
+
if (owner) {
|
|
10769
|
+
return {
|
|
10770
|
+
project: { ...entry, hostedHere },
|
|
10771
|
+
servedBy: {
|
|
10772
|
+
path: owner.record.projectPath,
|
|
10773
|
+
restPort: owner.record.ports.rest,
|
|
10774
|
+
live: owner.live
|
|
10775
|
+
}
|
|
10776
|
+
};
|
|
10777
|
+
}
|
|
10778
|
+
}
|
|
10779
|
+
return { project: { ...entry, hostedHere } };
|
|
10646
10780
|
} catch (err) {
|
|
10647
10781
|
return reply.code(500).send({
|
|
10648
10782
|
error: "failed to read project registry",
|