@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/neatd.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
import {
|
|
3
3
|
reconcileDaemonRecordSync,
|
|
4
4
|
startDaemon
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-22X2YM5H.js";
|
|
6
6
|
import {
|
|
7
7
|
listProjects,
|
|
8
8
|
registryPath
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-UCDXHLCJ.js";
|
|
10
10
|
import {
|
|
11
11
|
BindAuthorityError,
|
|
12
12
|
__require
|
package/dist/server.cjs
CHANGED
|
@@ -2038,6 +2038,41 @@ async function writeExtractionErrors(errors, errorsPath) {
|
|
|
2038
2038
|
const lines = errors.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
2039
2039
|
await import_node_fs6.promises.appendFile(errorsPath, lines, "utf8");
|
|
2040
2040
|
}
|
|
2041
|
+
function extractionHealthPathFor(errorsPath) {
|
|
2042
|
+
const dir = import_node_path6.default.dirname(errorsPath);
|
|
2043
|
+
const suffix = import_node_path6.default.basename(errorsPath).replace(/^errors/, "").replace(/\.ndjson$/, "");
|
|
2044
|
+
return import_node_path6.default.join(dir, `extraction-health${suffix}.json`);
|
|
2045
|
+
}
|
|
2046
|
+
var HEALTH_FILE_SAMPLE = 100;
|
|
2047
|
+
async function writeExtractionHealth(errors, healthPath, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
2048
|
+
const byProducer = {};
|
|
2049
|
+
for (const e of errors) byProducer[e.producer] = (byProducer[e.producer] ?? 0) + 1;
|
|
2050
|
+
const coverage = {
|
|
2051
|
+
skippedFiles: errors.length,
|
|
2052
|
+
byProducer,
|
|
2053
|
+
files: errors.slice(0, HEALTH_FILE_SAMPLE).map((e) => e.file),
|
|
2054
|
+
updatedAt: now
|
|
2055
|
+
};
|
|
2056
|
+
await import_node_fs6.promises.mkdir(import_node_path6.default.dirname(healthPath), { recursive: true });
|
|
2057
|
+
await import_node_fs6.promises.writeFile(healthPath, JSON.stringify(coverage), "utf8");
|
|
2058
|
+
}
|
|
2059
|
+
async function readExtractionHealth(healthPath) {
|
|
2060
|
+
let raw;
|
|
2061
|
+
try {
|
|
2062
|
+
raw = await import_node_fs6.promises.readFile(healthPath, "utf8");
|
|
2063
|
+
} catch {
|
|
2064
|
+
return void 0;
|
|
2065
|
+
}
|
|
2066
|
+
try {
|
|
2067
|
+
const obj = JSON.parse(raw);
|
|
2068
|
+
if (typeof obj?.skippedFiles !== "number" || !obj.byProducer || !Array.isArray(obj.files)) {
|
|
2069
|
+
return void 0;
|
|
2070
|
+
}
|
|
2071
|
+
return obj;
|
|
2072
|
+
} catch {
|
|
2073
|
+
return void 0;
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2041
2076
|
var droppedSink = [];
|
|
2042
2077
|
function noteExtractedDropped(edge) {
|
|
2043
2078
|
droppedSink.push(edge);
|
|
@@ -2989,14 +3024,16 @@ function reconcileObservedRelPath(graph, serviceName, relPath) {
|
|
|
2989
3024
|
return best ?? relPath;
|
|
2990
3025
|
}
|
|
2991
3026
|
function ensureObservedFileNode(graph, serviceName, serviceNodeId, callSite) {
|
|
2992
|
-
const
|
|
2993
|
-
const
|
|
3027
|
+
const svcAttrs = graph.hasNode(serviceNodeId) ? graph.getNodeAttributes(serviceNodeId) : void 0;
|
|
3028
|
+
const canonicalService = svcAttrs && svcAttrs.type === import_types5.NodeType.ServiceNode && typeof svcAttrs.name === "string" ? svcAttrs.name : serviceName;
|
|
3029
|
+
const relPath = reconcileObservedRelPath(graph, canonicalService, callSite.relPath);
|
|
3030
|
+
const fileNodeId = (0, import_types5.fileId)(canonicalService, relPath);
|
|
2994
3031
|
if (!graph.hasNode(fileNodeId)) {
|
|
2995
3032
|
const language = languageForExt(relPath);
|
|
2996
3033
|
const node = {
|
|
2997
3034
|
id: fileNodeId,
|
|
2998
3035
|
type: import_types5.NodeType.FileNode,
|
|
2999
|
-
service:
|
|
3036
|
+
service: canonicalService,
|
|
3000
3037
|
path: relPath,
|
|
3001
3038
|
...language ? { language } : {},
|
|
3002
3039
|
...callSite.originalRelPath ? { originalPath: callSite.originalRelPath } : {},
|
|
@@ -8914,6 +8951,15 @@ async function extractFromDirectory(graph, scanPath, opts = {}) {
|
|
|
8914
8951
|
);
|
|
8915
8952
|
}
|
|
8916
8953
|
}
|
|
8954
|
+
if (opts.errorsPath) {
|
|
8955
|
+
try {
|
|
8956
|
+
await writeExtractionHealth(errorEntries, extractionHealthPathFor(opts.errorsPath));
|
|
8957
|
+
} catch (err) {
|
|
8958
|
+
console.warn(
|
|
8959
|
+
`[neat] failed to write extraction health sidecar: ${err.message}`
|
|
8960
|
+
);
|
|
8961
|
+
}
|
|
8962
|
+
}
|
|
8917
8963
|
const droppedEntries = drainDroppedExtracted();
|
|
8918
8964
|
if (isRejectedLogEnabled() && opts.errorsPath && droppedEntries.length > 0) {
|
|
8919
8965
|
const rejectedPath = import_node_path45.default.join(import_node_path45.default.dirname(opts.errorsPath), "rejected.ndjson");
|
|
@@ -9214,6 +9260,75 @@ function neatHome() {
|
|
|
9214
9260
|
function registryPath() {
|
|
9215
9261
|
return import_node_path48.default.join(neatHome(), "projects.json");
|
|
9216
9262
|
}
|
|
9263
|
+
function daemonsDir() {
|
|
9264
|
+
return import_node_path48.default.join(neatHome(), "daemons");
|
|
9265
|
+
}
|
|
9266
|
+
function isFiniteInt(v) {
|
|
9267
|
+
return typeof v === "number" && Number.isFinite(v);
|
|
9268
|
+
}
|
|
9269
|
+
function parseDaemonRecord(raw) {
|
|
9270
|
+
let obj;
|
|
9271
|
+
try {
|
|
9272
|
+
obj = JSON.parse(raw);
|
|
9273
|
+
} catch {
|
|
9274
|
+
return void 0;
|
|
9275
|
+
}
|
|
9276
|
+
if (typeof obj !== "object" || obj === null) return void 0;
|
|
9277
|
+
const r = obj;
|
|
9278
|
+
const ports = r.ports;
|
|
9279
|
+
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)) {
|
|
9280
|
+
return void 0;
|
|
9281
|
+
}
|
|
9282
|
+
return {
|
|
9283
|
+
project: r.project,
|
|
9284
|
+
projectPath: r.projectPath,
|
|
9285
|
+
pid: r.pid,
|
|
9286
|
+
status: r.status,
|
|
9287
|
+
ports: { rest: ports.rest, otlp: ports.otlp, web: ports.web },
|
|
9288
|
+
startedAt: r.startedAt,
|
|
9289
|
+
neatVersion: r.neatVersion
|
|
9290
|
+
};
|
|
9291
|
+
}
|
|
9292
|
+
var defaultDiscoveryProbe = { isPidAlive: isPidAliveDefault };
|
|
9293
|
+
async function discoverDaemons(probe = defaultDiscoveryProbe) {
|
|
9294
|
+
const dir = daemonsDir();
|
|
9295
|
+
let names;
|
|
9296
|
+
try {
|
|
9297
|
+
names = await import_node_fs28.promises.readdir(dir);
|
|
9298
|
+
} catch (err) {
|
|
9299
|
+
if (err.code === "ENOENT") return [];
|
|
9300
|
+
throw err;
|
|
9301
|
+
}
|
|
9302
|
+
const out = [];
|
|
9303
|
+
for (const name of names) {
|
|
9304
|
+
if (!name.endsWith(".json")) continue;
|
|
9305
|
+
const file = import_node_path48.default.join(dir, name);
|
|
9306
|
+
let raw;
|
|
9307
|
+
try {
|
|
9308
|
+
raw = await import_node_fs28.promises.readFile(file, "utf8");
|
|
9309
|
+
} catch {
|
|
9310
|
+
continue;
|
|
9311
|
+
}
|
|
9312
|
+
const record = parseDaemonRecord(raw);
|
|
9313
|
+
if (!record) continue;
|
|
9314
|
+
const live = record.status === "running" && probe.isPidAlive(record.pid);
|
|
9315
|
+
out.push({ record, live, source: file });
|
|
9316
|
+
}
|
|
9317
|
+
out.sort((a, b) => a.record.project.localeCompare(b.record.project));
|
|
9318
|
+
return out;
|
|
9319
|
+
}
|
|
9320
|
+
async function findDaemonByProject(name, probe = defaultDiscoveryProbe) {
|
|
9321
|
+
const discovered = await discoverDaemons(probe);
|
|
9322
|
+
return discovered.find((d) => d.record.project === name);
|
|
9323
|
+
}
|
|
9324
|
+
function isPidAliveDefault(pid) {
|
|
9325
|
+
try {
|
|
9326
|
+
process.kill(pid, 0);
|
|
9327
|
+
return true;
|
|
9328
|
+
} catch (err) {
|
|
9329
|
+
return err.code === "EPERM";
|
|
9330
|
+
}
|
|
9331
|
+
}
|
|
9217
9332
|
async function readRegistry() {
|
|
9218
9333
|
const file = registryPath();
|
|
9219
9334
|
let raw;
|
|
@@ -9491,7 +9606,11 @@ function resolveProject(registry, req, reply, bootstrap, singleProject) {
|
|
|
9491
9606
|
void reply.code(503).send({ ready: false, project: name, status: "broken" });
|
|
9492
9607
|
return null;
|
|
9493
9608
|
}
|
|
9494
|
-
void reply.code(404).send({
|
|
9609
|
+
void reply.code(404).send({
|
|
9610
|
+
error: "project not found",
|
|
9611
|
+
project: name,
|
|
9612
|
+
hint: "This daemon does not host that project. GET /projects lists what it serves (see hostedHere)."
|
|
9613
|
+
});
|
|
9495
9614
|
return null;
|
|
9496
9615
|
}
|
|
9497
9616
|
return ctx;
|
|
@@ -9529,6 +9648,9 @@ function registerRoutes(scope, ctx) {
|
|
|
9529
9648
|
const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
|
|
9530
9649
|
if (!proj) return;
|
|
9531
9650
|
const uptimeMs = Date.now() - startedAt;
|
|
9651
|
+
const coverage = await readExtractionHealth(
|
|
9652
|
+
extractionHealthPathFor(proj.paths.errorsPath)
|
|
9653
|
+
);
|
|
9532
9654
|
return {
|
|
9533
9655
|
ok: true,
|
|
9534
9656
|
project: proj.name,
|
|
@@ -9539,7 +9661,8 @@ function registerRoutes(scope, ctx) {
|
|
|
9539
9661
|
uptime: Math.floor(uptimeMs / 1e3),
|
|
9540
9662
|
nodeCount: proj.graph.order,
|
|
9541
9663
|
edgeCount: proj.graph.size,
|
|
9542
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
9664
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9665
|
+
...coverage ? { coverage } : {}
|
|
9543
9666
|
};
|
|
9544
9667
|
});
|
|
9545
9668
|
}
|
|
@@ -10181,12 +10304,17 @@ async function buildApi(opts) {
|
|
|
10181
10304
|
// The daemon is serving this project, so it's active unless its
|
|
10182
10305
|
// bootstrap broke. ('bootstrapping' still reads as active — the project
|
|
10183
10306
|
// is the one to land on; its graph route answers 503 until ready.)
|
|
10184
|
-
status: phase === "broken" ? "broken" : "active"
|
|
10307
|
+
status: phase === "broken" ? "broken" : "active",
|
|
10308
|
+
hostedHere: true
|
|
10185
10309
|
};
|
|
10186
10310
|
return [entry];
|
|
10187
10311
|
}
|
|
10188
10312
|
try {
|
|
10189
|
-
|
|
10313
|
+
const entries = await listProjects();
|
|
10314
|
+
return entries.map((entry) => ({
|
|
10315
|
+
...entry,
|
|
10316
|
+
hostedHere: registry.has(entry.name)
|
|
10317
|
+
}));
|
|
10190
10318
|
} catch (err) {
|
|
10191
10319
|
return reply.code(500).send({
|
|
10192
10320
|
error: "failed to read project registry",
|
|
@@ -10200,7 +10328,21 @@ async function buildApi(opts) {
|
|
|
10200
10328
|
if (!entry) {
|
|
10201
10329
|
return reply.code(404).send({ error: "project not found", project: req.params.project });
|
|
10202
10330
|
}
|
|
10203
|
-
|
|
10331
|
+
const hostedHere = registry.has(entry.name);
|
|
10332
|
+
if (!hostedHere) {
|
|
10333
|
+
const owner = await findDaemonByProject(entry.name).catch(() => void 0);
|
|
10334
|
+
if (owner) {
|
|
10335
|
+
return {
|
|
10336
|
+
project: { ...entry, hostedHere },
|
|
10337
|
+
servedBy: {
|
|
10338
|
+
path: owner.record.projectPath,
|
|
10339
|
+
restPort: owner.record.ports.rest,
|
|
10340
|
+
live: owner.live
|
|
10341
|
+
}
|
|
10342
|
+
};
|
|
10343
|
+
}
|
|
10344
|
+
}
|
|
10345
|
+
return { project: { ...entry, hostedHere } };
|
|
10204
10346
|
} catch (err) {
|
|
10205
10347
|
return reply.code(500).send({
|
|
10206
10348
|
error: "failed to read project registry",
|