@neat.is/core 0.4.15 → 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 +923 -590
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +19 -4
- package/dist/cli.d.ts +19 -4
- package/dist/cli.js +291 -186
- 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 +3 -4
- package/dist/chunk-VMLWUK7W.js.map +0 -1
- package/dist/chunk-XS4CGNRO.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
normalizeProjectPath,
|
|
37
37
|
pathsForProject,
|
|
38
38
|
promoteFrontierNodes,
|
|
39
|
+
pruneRegistry,
|
|
39
40
|
removeProject,
|
|
40
41
|
resetGraph,
|
|
41
42
|
retireEdgesByFile,
|
|
@@ -44,7 +45,7 @@ import {
|
|
|
44
45
|
setStatus,
|
|
45
46
|
startPersistLoop,
|
|
46
47
|
startStalenessLoop
|
|
47
|
-
} from "./chunk-
|
|
48
|
+
} from "./chunk-LUDSPX5N.js";
|
|
48
49
|
import {
|
|
49
50
|
startOtelGrpcReceiver
|
|
50
51
|
} from "./chunk-GHPHVXYM.js";
|
|
@@ -57,13 +58,47 @@ import {
|
|
|
57
58
|
} from "./chunk-6CO7C4IU.js";
|
|
58
59
|
|
|
59
60
|
// src/cli.ts
|
|
60
|
-
import
|
|
61
|
-
import { promises as fs7
|
|
61
|
+
import path9 from "path";
|
|
62
|
+
import { promises as fs7 } from "fs";
|
|
63
|
+
|
|
64
|
+
// src/banner.ts
|
|
65
|
+
import path from "path";
|
|
66
|
+
import { readFileSync } from "fs";
|
|
62
67
|
import { fileURLToPath } from "url";
|
|
68
|
+
function readPackageVersion() {
|
|
69
|
+
const here = typeof __dirname !== "undefined" ? __dirname : path.dirname(fileURLToPath(import.meta.url));
|
|
70
|
+
const candidates = [
|
|
71
|
+
path.resolve(here, "../package.json"),
|
|
72
|
+
path.resolve(here, "../../package.json")
|
|
73
|
+
];
|
|
74
|
+
for (const candidate of candidates) {
|
|
75
|
+
try {
|
|
76
|
+
const raw = readFileSync(candidate, "utf8");
|
|
77
|
+
const parsed = JSON.parse(raw);
|
|
78
|
+
if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
|
|
79
|
+
return parsed.version;
|
|
80
|
+
}
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return "unknown";
|
|
85
|
+
}
|
|
86
|
+
function printBanner() {
|
|
87
|
+
console.log("\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557");
|
|
88
|
+
console.log("\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D");
|
|
89
|
+
console.log("\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ");
|
|
90
|
+
console.log("\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ");
|
|
91
|
+
console.log("\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ");
|
|
92
|
+
console.log("\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ");
|
|
93
|
+
console.log("");
|
|
94
|
+
console.log(" Network Expressive Architecting Tool");
|
|
95
|
+
console.log(` neat.is \xB7 v${readPackageVersion()} \xB7 Apache 2.0`);
|
|
96
|
+
console.log("");
|
|
97
|
+
}
|
|
63
98
|
|
|
64
99
|
// src/gitignore.ts
|
|
65
100
|
import { promises as fs } from "fs";
|
|
66
|
-
import
|
|
101
|
+
import path2 from "path";
|
|
67
102
|
var NEAT_OUT_LINE = "neat-out/";
|
|
68
103
|
var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
|
|
69
104
|
function isNeatOutLine(line) {
|
|
@@ -71,7 +106,7 @@ function isNeatOutLine(line) {
|
|
|
71
106
|
return trimmed === "neat-out/" || trimmed === "neat-out";
|
|
72
107
|
}
|
|
73
108
|
async function ensureNeatOutIgnored(projectDir) {
|
|
74
|
-
const file =
|
|
109
|
+
const file = path2.join(projectDir, ".gitignore");
|
|
75
110
|
let existing = null;
|
|
76
111
|
try {
|
|
77
112
|
existing = await fs.readFile(file, "utf8");
|
|
@@ -193,7 +228,7 @@ function formatIncompat(inc) {
|
|
|
193
228
|
|
|
194
229
|
// src/watch.ts
|
|
195
230
|
import fs2 from "fs";
|
|
196
|
-
import
|
|
231
|
+
import path3 from "path";
|
|
197
232
|
import chokidar from "chokidar";
|
|
198
233
|
var ALL_PHASES = [
|
|
199
234
|
"services",
|
|
@@ -206,8 +241,8 @@ var ALL_PHASES = [
|
|
|
206
241
|
];
|
|
207
242
|
function classifyChange(relPath) {
|
|
208
243
|
const phases = /* @__PURE__ */ new Set();
|
|
209
|
-
const base =
|
|
210
|
-
const segments = relPath.split(
|
|
244
|
+
const base = path3.basename(relPath).toLowerCase();
|
|
245
|
+
const segments = relPath.split(path3.sep).map((s) => s.toLowerCase());
|
|
211
246
|
if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
|
|
212
247
|
phases.add("services");
|
|
213
248
|
phases.add("aliases");
|
|
@@ -328,9 +363,9 @@ function countWatchableDirs(scanPath, limit) {
|
|
|
328
363
|
for (const e of entries) {
|
|
329
364
|
if (count >= limit) return;
|
|
330
365
|
if (!e.isDirectory()) continue;
|
|
331
|
-
if (IGNORED_WATCH_PATHS.some((re) => re.test(
|
|
366
|
+
if (IGNORED_WATCH_PATHS.some((re) => re.test(path3.join(dir, e.name) + path3.sep))) continue;
|
|
332
367
|
count++;
|
|
333
|
-
if (depth < 2) visit(
|
|
368
|
+
if (depth < 2) visit(path3.join(dir, e.name), depth + 1);
|
|
334
369
|
}
|
|
335
370
|
};
|
|
336
371
|
visit(scanPath, 0);
|
|
@@ -348,8 +383,8 @@ async function startWatch(graph, opts) {
|
|
|
348
383
|
const projectName = opts.project ?? DEFAULT_PROJECT;
|
|
349
384
|
await loadGraphFromDisk(graph, opts.outPath);
|
|
350
385
|
const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
|
|
351
|
-
const policyFilePath =
|
|
352
|
-
const policyViolationsPath =
|
|
386
|
+
const policyFilePath = path3.join(opts.scanPath, "policy.json");
|
|
387
|
+
const policyViolationsPath = path3.join(path3.dirname(opts.outPath), "policy-violations.ndjson");
|
|
353
388
|
let policies = [];
|
|
354
389
|
try {
|
|
355
390
|
policies = await loadPolicyFile(policyFilePath);
|
|
@@ -400,7 +435,7 @@ async function startWatch(graph, opts) {
|
|
|
400
435
|
assertBindAuthority(host, auth.authToken);
|
|
401
436
|
const port = opts.port ?? 8080;
|
|
402
437
|
const otelPort = opts.otelPort ?? 4318;
|
|
403
|
-
const cachePath = opts.embeddingsCachePath ??
|
|
438
|
+
const cachePath = opts.embeddingsCachePath ?? path3.join(path3.dirname(opts.outPath), "embeddings.json");
|
|
404
439
|
let searchIndex;
|
|
405
440
|
try {
|
|
406
441
|
searchIndex = await buildSearchIndex(graph, { cachePath });
|
|
@@ -418,7 +453,7 @@ async function startWatch(graph, opts) {
|
|
|
418
453
|
// Paths are derived from the explicit options the watch caller passes
|
|
419
454
|
// — pathsForProject is only used to fill in the embeddings/snapshot
|
|
420
455
|
// fields so the registry shape is complete.
|
|
421
|
-
...pathsForProject(projectName,
|
|
456
|
+
...pathsForProject(projectName, path3.dirname(opts.outPath)),
|
|
422
457
|
snapshotPath: opts.outPath,
|
|
423
458
|
errorsPath: opts.errorsPath,
|
|
424
459
|
staleEventsPath: opts.staleEventsPath
|
|
@@ -505,9 +540,9 @@ async function startWatch(graph, opts) {
|
|
|
505
540
|
};
|
|
506
541
|
const onPath = (absPath) => {
|
|
507
542
|
if (shouldIgnore(absPath)) return;
|
|
508
|
-
const rel =
|
|
543
|
+
const rel = path3.relative(opts.scanPath, absPath);
|
|
509
544
|
if (!rel || rel.startsWith("..")) return;
|
|
510
|
-
pendingPaths.add(rel.split(
|
|
545
|
+
pendingPaths.add(rel.split(path3.sep).join("/"));
|
|
511
546
|
const phases = classifyChange(rel);
|
|
512
547
|
if (phases.size === 0) {
|
|
513
548
|
for (const p of ALL_PHASES) pending.add(p);
|
|
@@ -561,7 +596,7 @@ async function startWatch(graph, opts) {
|
|
|
561
596
|
|
|
562
597
|
// src/deploy/detect.ts
|
|
563
598
|
import { promises as fs3 } from "fs";
|
|
564
|
-
import
|
|
599
|
+
import path4 from "path";
|
|
565
600
|
import { spawn } from "child_process";
|
|
566
601
|
import { randomBytes } from "crypto";
|
|
567
602
|
function generateToken() {
|
|
@@ -661,7 +696,7 @@ async function runDeploy(opts = {}) {
|
|
|
661
696
|
const token = generateToken();
|
|
662
697
|
switch (substrate) {
|
|
663
698
|
case "docker-compose": {
|
|
664
|
-
const artifactPath =
|
|
699
|
+
const artifactPath = path4.join(cwd, "docker-compose.neat.yml");
|
|
665
700
|
const contents = emitDockerCompose(cwd);
|
|
666
701
|
await fs3.writeFile(artifactPath, contents, "utf8");
|
|
667
702
|
return {
|
|
@@ -669,11 +704,11 @@ async function runDeploy(opts = {}) {
|
|
|
669
704
|
artifactPath,
|
|
670
705
|
token,
|
|
671
706
|
contents,
|
|
672
|
-
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${
|
|
707
|
+
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${path4.basename(artifactPath)} up -d`
|
|
673
708
|
};
|
|
674
709
|
}
|
|
675
710
|
case "systemd": {
|
|
676
|
-
const artifactPath =
|
|
711
|
+
const artifactPath = path4.join(cwd, "neat.service");
|
|
677
712
|
const contents = emitSystemdUnit(cwd);
|
|
678
713
|
await fs3.writeFile(artifactPath, contents, "utf8");
|
|
679
714
|
return {
|
|
@@ -705,7 +740,7 @@ ${contents}EOF
|
|
|
705
740
|
|
|
706
741
|
// src/installers/javascript.ts
|
|
707
742
|
import { promises as fs4 } from "fs";
|
|
708
|
-
import
|
|
743
|
+
import path5 from "path";
|
|
709
744
|
import semver from "semver";
|
|
710
745
|
|
|
711
746
|
// src/installers/templates.ts
|
|
@@ -1251,11 +1286,11 @@ var OTEL_ENV = {
|
|
|
1251
1286
|
value: "http://localhost:4318/projects/<project>/v1/traces"
|
|
1252
1287
|
};
|
|
1253
1288
|
function serviceNodeName(pkg, serviceDir) {
|
|
1254
|
-
return pkg.name ??
|
|
1289
|
+
return pkg.name ?? path5.basename(serviceDir);
|
|
1255
1290
|
}
|
|
1256
1291
|
function projectToken(pkg, serviceDir, project) {
|
|
1257
1292
|
if (project && project.length > 0) return project;
|
|
1258
|
-
return pkg.name ??
|
|
1293
|
+
return pkg.name ?? path5.basename(serviceDir);
|
|
1259
1294
|
}
|
|
1260
1295
|
async function readJsonFile(p) {
|
|
1261
1296
|
try {
|
|
@@ -1268,16 +1303,16 @@ async function readJsonFile(p) {
|
|
|
1268
1303
|
async function detectRuntimeKind(pkgRoot, pkg) {
|
|
1269
1304
|
const deps = allDeps(pkg);
|
|
1270
1305
|
if ("react-native" in deps || "expo" in deps) return "react-native";
|
|
1271
|
-
const appJson = await readJsonFile(
|
|
1306
|
+
const appJson = await readJsonFile(path5.join(pkgRoot, "app.json"));
|
|
1272
1307
|
if (appJson && typeof appJson === "object" && "expo" in appJson) {
|
|
1273
1308
|
return "react-native";
|
|
1274
1309
|
}
|
|
1275
|
-
if (await exists(
|
|
1310
|
+
if (await exists(path5.join(pkgRoot, "vite.config.js")) || await exists(path5.join(pkgRoot, "vite.config.ts")) || await exists(path5.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
|
|
1276
1311
|
return "browser-bundle";
|
|
1277
1312
|
}
|
|
1278
|
-
if (await exists(
|
|
1279
|
-
if (await exists(
|
|
1280
|
-
if (await exists(
|
|
1313
|
+
if (await exists(path5.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
|
|
1314
|
+
if (await exists(path5.join(pkgRoot, "bun.lockb"))) return "bun";
|
|
1315
|
+
if (await exists(path5.join(pkgRoot, "deno.json")) || await exists(path5.join(pkgRoot, "deno.lock"))) {
|
|
1281
1316
|
return "deno";
|
|
1282
1317
|
}
|
|
1283
1318
|
const engines = pkg.engines ?? {};
|
|
@@ -1286,7 +1321,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
|
|
|
1286
1321
|
}
|
|
1287
1322
|
async function readPackageJson(serviceDir) {
|
|
1288
1323
|
try {
|
|
1289
|
-
const raw = await fs4.readFile(
|
|
1324
|
+
const raw = await fs4.readFile(path5.join(serviceDir, "package.json"), "utf8");
|
|
1290
1325
|
return JSON.parse(raw);
|
|
1291
1326
|
} catch {
|
|
1292
1327
|
return null;
|
|
@@ -1330,7 +1365,7 @@ function needsVersionUpgrade(installed, expected) {
|
|
|
1330
1365
|
var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
|
|
1331
1366
|
async function findNextConfig(serviceDir) {
|
|
1332
1367
|
for (const name of NEXT_CONFIG_CANDIDATES) {
|
|
1333
|
-
const candidate =
|
|
1368
|
+
const candidate = path5.join(serviceDir, name);
|
|
1334
1369
|
if (await exists(candidate)) return candidate;
|
|
1335
1370
|
}
|
|
1336
1371
|
return null;
|
|
@@ -1357,7 +1392,7 @@ function hasRemixDependency(pkg) {
|
|
|
1357
1392
|
}
|
|
1358
1393
|
async function findRemixEntry(serviceDir) {
|
|
1359
1394
|
for (const rel of REMIX_ENTRY_CANDIDATES) {
|
|
1360
|
-
const candidate =
|
|
1395
|
+
const candidate = path5.join(serviceDir, rel);
|
|
1361
1396
|
if (await exists(candidate)) return candidate;
|
|
1362
1397
|
}
|
|
1363
1398
|
return null;
|
|
@@ -1369,14 +1404,14 @@ function hasSvelteKitDependency(pkg) {
|
|
|
1369
1404
|
}
|
|
1370
1405
|
async function findSvelteKitHooks(serviceDir) {
|
|
1371
1406
|
for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
|
|
1372
|
-
const candidate =
|
|
1407
|
+
const candidate = path5.join(serviceDir, rel);
|
|
1373
1408
|
if (await exists(candidate)) return candidate;
|
|
1374
1409
|
}
|
|
1375
1410
|
return null;
|
|
1376
1411
|
}
|
|
1377
1412
|
async function findSvelteKitConfig(serviceDir) {
|
|
1378
1413
|
for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
|
|
1379
|
-
const candidate =
|
|
1414
|
+
const candidate = path5.join(serviceDir, rel);
|
|
1380
1415
|
if (await exists(candidate)) return candidate;
|
|
1381
1416
|
}
|
|
1382
1417
|
return null;
|
|
@@ -1387,7 +1422,7 @@ function hasNuxtDependency(pkg) {
|
|
|
1387
1422
|
}
|
|
1388
1423
|
async function findNuxtConfig(serviceDir) {
|
|
1389
1424
|
for (const name of NUXT_CONFIG_CANDIDATES) {
|
|
1390
|
-
const candidate =
|
|
1425
|
+
const candidate = path5.join(serviceDir, name);
|
|
1391
1426
|
if (await exists(candidate)) return candidate;
|
|
1392
1427
|
}
|
|
1393
1428
|
return null;
|
|
@@ -1398,7 +1433,7 @@ function hasAstroDependency(pkg) {
|
|
|
1398
1433
|
}
|
|
1399
1434
|
async function findAstroConfig(serviceDir) {
|
|
1400
1435
|
for (const name of ASTRO_CONFIG_CANDIDATES) {
|
|
1401
|
-
const candidate =
|
|
1436
|
+
const candidate = path5.join(serviceDir, name);
|
|
1402
1437
|
if (await exists(candidate)) return candidate;
|
|
1403
1438
|
}
|
|
1404
1439
|
return null;
|
|
@@ -1412,7 +1447,7 @@ function parseNextMajor(range) {
|
|
|
1412
1447
|
return Number.isFinite(n) ? n : null;
|
|
1413
1448
|
}
|
|
1414
1449
|
async function isTypeScriptProject(serviceDir) {
|
|
1415
|
-
return exists(
|
|
1450
|
+
return exists(path5.join(serviceDir, "tsconfig.json"));
|
|
1416
1451
|
}
|
|
1417
1452
|
var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
|
|
1418
1453
|
var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
|
|
@@ -1458,7 +1493,7 @@ function entryFromScript(script) {
|
|
|
1458
1493
|
}
|
|
1459
1494
|
async function resolveEntry(serviceDir, pkg) {
|
|
1460
1495
|
if (typeof pkg.main === "string" && pkg.main.length > 0) {
|
|
1461
|
-
const candidate =
|
|
1496
|
+
const candidate = path5.resolve(serviceDir, pkg.main);
|
|
1462
1497
|
if (await exists(candidate)) return candidate;
|
|
1463
1498
|
}
|
|
1464
1499
|
if (pkg.bin) {
|
|
@@ -1472,36 +1507,36 @@ async function resolveEntry(serviceDir, pkg) {
|
|
|
1472
1507
|
if (typeof first === "string") binEntry = first;
|
|
1473
1508
|
}
|
|
1474
1509
|
if (binEntry) {
|
|
1475
|
-
const candidate =
|
|
1510
|
+
const candidate = path5.resolve(serviceDir, binEntry);
|
|
1476
1511
|
if (await exists(candidate)) return candidate;
|
|
1477
1512
|
}
|
|
1478
1513
|
}
|
|
1479
1514
|
const startEntry = entryFromScript(pkg.scripts?.start);
|
|
1480
1515
|
if (startEntry) {
|
|
1481
|
-
const candidate =
|
|
1516
|
+
const candidate = path5.resolve(serviceDir, startEntry);
|
|
1482
1517
|
if (await exists(candidate)) return candidate;
|
|
1483
1518
|
}
|
|
1484
1519
|
const devEntry = entryFromScript(pkg.scripts?.dev);
|
|
1485
1520
|
if (devEntry) {
|
|
1486
|
-
const candidate =
|
|
1521
|
+
const candidate = path5.resolve(serviceDir, devEntry);
|
|
1487
1522
|
if (await exists(candidate)) return candidate;
|
|
1488
1523
|
}
|
|
1489
1524
|
for (const rel of SRC_INDEX_CANDIDATES) {
|
|
1490
|
-
const candidate =
|
|
1525
|
+
const candidate = path5.join(serviceDir, rel);
|
|
1491
1526
|
if (await exists(candidate)) return candidate;
|
|
1492
1527
|
}
|
|
1493
1528
|
for (const rel of SRC_NAMED_CANDIDATES) {
|
|
1494
|
-
const candidate =
|
|
1529
|
+
const candidate = path5.join(serviceDir, rel);
|
|
1495
1530
|
if (await exists(candidate)) return candidate;
|
|
1496
1531
|
}
|
|
1497
1532
|
for (const name of INDEX_CANDIDATES) {
|
|
1498
|
-
const candidate =
|
|
1533
|
+
const candidate = path5.join(serviceDir, name);
|
|
1499
1534
|
if (await exists(candidate)) return candidate;
|
|
1500
1535
|
}
|
|
1501
1536
|
return null;
|
|
1502
1537
|
}
|
|
1503
1538
|
function dispatchEntry(entryFile, pkg) {
|
|
1504
|
-
const ext =
|
|
1539
|
+
const ext = path5.extname(entryFile).toLowerCase();
|
|
1505
1540
|
if (ext === ".ts" || ext === ".tsx") return "ts";
|
|
1506
1541
|
if (ext === ".mjs") return "esm";
|
|
1507
1542
|
if (ext === ".cjs") return "cjs";
|
|
@@ -1518,9 +1553,9 @@ function otelInitContents(flavor) {
|
|
|
1518
1553
|
return OTEL_INIT_CJS;
|
|
1519
1554
|
}
|
|
1520
1555
|
function injectionLine(flavor, entryFile, otelInitFile) {
|
|
1521
|
-
let rel =
|
|
1556
|
+
let rel = path5.relative(path5.dirname(entryFile), otelInitFile);
|
|
1522
1557
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
1523
|
-
rel = rel.split(
|
|
1558
|
+
rel = rel.split(path5.sep).join("/");
|
|
1524
1559
|
if (flavor === "cjs") return `require('${rel}')`;
|
|
1525
1560
|
if (flavor === "esm") return `import '${rel}'`;
|
|
1526
1561
|
const tsRel = rel.replace(/\.ts$/, "");
|
|
@@ -1533,23 +1568,23 @@ function lineIsOtelInjection(line) {
|
|
|
1533
1568
|
}
|
|
1534
1569
|
async function detectsSrcLayout(serviceDir) {
|
|
1535
1570
|
const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
|
|
1536
|
-
exists(
|
|
1537
|
-
exists(
|
|
1538
|
-
exists(
|
|
1539
|
-
exists(
|
|
1571
|
+
exists(path5.join(serviceDir, "src", "app")),
|
|
1572
|
+
exists(path5.join(serviceDir, "src", "pages")),
|
|
1573
|
+
exists(path5.join(serviceDir, "app")),
|
|
1574
|
+
exists(path5.join(serviceDir, "pages"))
|
|
1540
1575
|
]);
|
|
1541
1576
|
return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
|
|
1542
1577
|
}
|
|
1543
1578
|
async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
|
|
1544
1579
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
1545
1580
|
const srcLayout = await detectsSrcLayout(serviceDir);
|
|
1546
|
-
const baseDir = srcLayout ?
|
|
1547
|
-
const instrumentationFile =
|
|
1548
|
-
const instrumentationNodeFile =
|
|
1581
|
+
const baseDir = srcLayout ? path5.join(serviceDir, "src") : serviceDir;
|
|
1582
|
+
const instrumentationFile = path5.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
|
|
1583
|
+
const instrumentationNodeFile = path5.join(
|
|
1549
1584
|
baseDir,
|
|
1550
1585
|
useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
|
|
1551
1586
|
);
|
|
1552
|
-
const envNeatFile =
|
|
1587
|
+
const envNeatFile = path5.join(baseDir, ".env.neat");
|
|
1553
1588
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
1554
1589
|
const dependencyEdits = [];
|
|
1555
1590
|
for (const sdk of SDK_PACKAGES) {
|
|
@@ -1654,7 +1689,7 @@ function buildDependencyEdits(pkg, manifestPath) {
|
|
|
1654
1689
|
return edits;
|
|
1655
1690
|
}
|
|
1656
1691
|
async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
|
|
1657
|
-
const envNeatFile =
|
|
1692
|
+
const envNeatFile = path5.join(serviceDir, ".env.neat");
|
|
1658
1693
|
if (!await exists(envNeatFile)) {
|
|
1659
1694
|
generatedFiles.push({
|
|
1660
1695
|
file: envNeatFile,
|
|
@@ -1689,7 +1724,7 @@ function fileImportsOtelHook(raw, specifiers) {
|
|
|
1689
1724
|
}
|
|
1690
1725
|
async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
1691
1726
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
1692
|
-
const otelServerFile =
|
|
1727
|
+
const otelServerFile = path5.join(
|
|
1693
1728
|
serviceDir,
|
|
1694
1729
|
useTs ? "app/otel.server.ts" : "app/otel.server.js"
|
|
1695
1730
|
);
|
|
@@ -1746,11 +1781,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
1746
1781
|
}
|
|
1747
1782
|
async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
|
|
1748
1783
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
1749
|
-
const otelInitFile =
|
|
1784
|
+
const otelInitFile = path5.join(
|
|
1750
1785
|
serviceDir,
|
|
1751
1786
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
1752
1787
|
);
|
|
1753
|
-
const resolvedHooksFile = hooksFile ??
|
|
1788
|
+
const resolvedHooksFile = hooksFile ?? path5.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
|
|
1754
1789
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
1755
1790
|
const generatedFiles = [];
|
|
1756
1791
|
const entrypointEdits = [];
|
|
@@ -1812,11 +1847,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
1812
1847
|
}
|
|
1813
1848
|
async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
1814
1849
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
1815
|
-
const otelPluginFile =
|
|
1850
|
+
const otelPluginFile = path5.join(
|
|
1816
1851
|
serviceDir,
|
|
1817
1852
|
useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
|
|
1818
1853
|
);
|
|
1819
|
-
const otelInitFile =
|
|
1854
|
+
const otelInitFile = path5.join(
|
|
1820
1855
|
serviceDir,
|
|
1821
1856
|
useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
|
|
1822
1857
|
);
|
|
@@ -1867,19 +1902,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
|
1867
1902
|
var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
|
|
1868
1903
|
async function findAstroMiddleware(serviceDir) {
|
|
1869
1904
|
for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
|
|
1870
|
-
const candidate =
|
|
1905
|
+
const candidate = path5.join(serviceDir, rel);
|
|
1871
1906
|
if (await exists(candidate)) return candidate;
|
|
1872
1907
|
}
|
|
1873
1908
|
return null;
|
|
1874
1909
|
}
|
|
1875
1910
|
async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
1876
1911
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
1877
|
-
const otelInitFile =
|
|
1912
|
+
const otelInitFile = path5.join(
|
|
1878
1913
|
serviceDir,
|
|
1879
1914
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
1880
1915
|
);
|
|
1881
1916
|
const existingMiddleware = await findAstroMiddleware(serviceDir);
|
|
1882
|
-
const middlewareFile = existingMiddleware ??
|
|
1917
|
+
const middlewareFile = existingMiddleware ?? path5.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
|
|
1883
1918
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
1884
1919
|
const generatedFiles = [];
|
|
1885
1920
|
const entrypointEdits = [];
|
|
@@ -1975,7 +2010,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
|
|
|
1975
2010
|
}
|
|
1976
2011
|
async function plan(serviceDir, opts) {
|
|
1977
2012
|
const pkg = await readPackageJson(serviceDir);
|
|
1978
|
-
const manifestPath =
|
|
2013
|
+
const manifestPath = path5.join(serviceDir, "package.json");
|
|
1979
2014
|
const project = opts?.project;
|
|
1980
2015
|
const empty = {
|
|
1981
2016
|
language: "javascript",
|
|
@@ -2010,8 +2045,8 @@ async function plan(serviceDir, opts) {
|
|
|
2010
2045
|
return { ...empty, libOnly: true };
|
|
2011
2046
|
}
|
|
2012
2047
|
const flavor = dispatchEntry(entryFile, pkg);
|
|
2013
|
-
const otelInitFile =
|
|
2014
|
-
const envNeatFile =
|
|
2048
|
+
const otelInitFile = path5.join(path5.dirname(entryFile), otelInitFilename(flavor));
|
|
2049
|
+
const envNeatFile = path5.join(serviceDir, ".env.neat");
|
|
2015
2050
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
2016
2051
|
const dependencyEdits = [];
|
|
2017
2052
|
for (const sdk of SDK_PACKAGES) {
|
|
@@ -2080,13 +2115,13 @@ async function plan(serviceDir, opts) {
|
|
|
2080
2115
|
};
|
|
2081
2116
|
}
|
|
2082
2117
|
function isAllowedWritePath(serviceDir, target) {
|
|
2083
|
-
const rel =
|
|
2118
|
+
const rel = path5.relative(serviceDir, target);
|
|
2084
2119
|
if (rel.startsWith("..")) return false;
|
|
2085
|
-
const base =
|
|
2120
|
+
const base = path5.basename(target);
|
|
2086
2121
|
if (base === "package.json") return true;
|
|
2087
2122
|
if (base === ".env.neat") return true;
|
|
2088
2123
|
if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
|
|
2089
|
-
const relPosix = rel.split(
|
|
2124
|
+
const relPosix = rel.split(path5.sep).join("/");
|
|
2090
2125
|
if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
|
|
2091
2126
|
if (relPosix === base) return true;
|
|
2092
2127
|
if (relPosix === `src/${base}`) return true;
|
|
@@ -2103,7 +2138,7 @@ function isAllowedWritePath(serviceDir, target) {
|
|
|
2103
2138
|
return false;
|
|
2104
2139
|
}
|
|
2105
2140
|
async function writeAtomic(file, contents) {
|
|
2106
|
-
await fs4.mkdir(
|
|
2141
|
+
await fs4.mkdir(path5.dirname(file), { recursive: true });
|
|
2107
2142
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
2108
2143
|
await fs4.writeFile(tmp, contents, "utf8");
|
|
2109
2144
|
await fs4.rename(tmp, file);
|
|
@@ -2287,7 +2322,7 @@ async function rollback(installPlan, originals, createdFiles) {
|
|
|
2287
2322
|
...removed.map((f) => `removed: ${f}`),
|
|
2288
2323
|
""
|
|
2289
2324
|
];
|
|
2290
|
-
const rollbackPath =
|
|
2325
|
+
const rollbackPath = path5.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
2291
2326
|
await fs4.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
2292
2327
|
}
|
|
2293
2328
|
function injectInstrumentationHook(raw) {
|
|
@@ -2317,7 +2352,7 @@ var javascriptInstaller = {
|
|
|
2317
2352
|
|
|
2318
2353
|
// src/installers/python.ts
|
|
2319
2354
|
import { promises as fs5 } from "fs";
|
|
2320
|
-
import
|
|
2355
|
+
import path6 from "path";
|
|
2321
2356
|
var SDK_PACKAGES2 = [
|
|
2322
2357
|
{ name: "opentelemetry-distro", version: ">=0.49b0" },
|
|
2323
2358
|
{ name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
|
|
@@ -2338,7 +2373,7 @@ async function exists2(p) {
|
|
|
2338
2373
|
async function detect2(serviceDir) {
|
|
2339
2374
|
const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
|
|
2340
2375
|
for (const m of markers) {
|
|
2341
|
-
if (await exists2(
|
|
2376
|
+
if (await exists2(path6.join(serviceDir, m))) return true;
|
|
2342
2377
|
}
|
|
2343
2378
|
return false;
|
|
2344
2379
|
}
|
|
@@ -2348,7 +2383,7 @@ function reqPackageName(line) {
|
|
|
2348
2383
|
return head.replace(/[<>=!~].*$/, "").toLowerCase();
|
|
2349
2384
|
}
|
|
2350
2385
|
async function planRequirementsTxtEdits(serviceDir) {
|
|
2351
|
-
const file =
|
|
2386
|
+
const file = path6.join(serviceDir, "requirements.txt");
|
|
2352
2387
|
if (!await exists2(file)) return null;
|
|
2353
2388
|
const raw = await fs5.readFile(file, "utf8");
|
|
2354
2389
|
const presentNames = new Set(
|
|
@@ -2358,7 +2393,7 @@ async function planRequirementsTxtEdits(serviceDir) {
|
|
|
2358
2393
|
return { manifest: file, missing: [...missing] };
|
|
2359
2394
|
}
|
|
2360
2395
|
async function planProcfileEdits(serviceDir) {
|
|
2361
|
-
const procfile =
|
|
2396
|
+
const procfile = path6.join(serviceDir, "Procfile");
|
|
2362
2397
|
if (!await exists2(procfile)) return [];
|
|
2363
2398
|
const raw = await fs5.readFile(procfile, "utf8");
|
|
2364
2399
|
const edits = [];
|
|
@@ -2447,7 +2482,7 @@ async function apply2(installPlan) {
|
|
|
2447
2482
|
if (raw === void 0) {
|
|
2448
2483
|
throw new Error(`python installer: cannot read ${file} during apply`);
|
|
2449
2484
|
}
|
|
2450
|
-
const base =
|
|
2485
|
+
const base = path6.basename(file);
|
|
2451
2486
|
if (base === "requirements.txt") {
|
|
2452
2487
|
const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
|
|
2453
2488
|
if (edits.length > 0) {
|
|
@@ -2486,7 +2521,7 @@ async function rollback2(installPlan, originals) {
|
|
|
2486
2521
|
...restored.map((f) => `restored: ${f}`),
|
|
2487
2522
|
""
|
|
2488
2523
|
];
|
|
2489
|
-
const rollbackPath =
|
|
2524
|
+
const rollbackPath = path6.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
2490
2525
|
await fs5.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
2491
2526
|
}
|
|
2492
2527
|
var pythonInstaller = {
|
|
@@ -2611,7 +2646,7 @@ function renderPatch(sections) {
|
|
|
2611
2646
|
import { promises as fs6 } from "fs";
|
|
2612
2647
|
import http from "http";
|
|
2613
2648
|
import net from "net";
|
|
2614
|
-
import
|
|
2649
|
+
import path7 from "path";
|
|
2615
2650
|
import { spawn as spawn2 } from "child_process";
|
|
2616
2651
|
import readline from "readline";
|
|
2617
2652
|
async function extractAndPersist(opts) {
|
|
@@ -2620,7 +2655,7 @@ async function extractAndPersist(opts) {
|
|
|
2620
2655
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
2621
2656
|
resetGraph(graphKey);
|
|
2622
2657
|
const graph = getGraph(graphKey);
|
|
2623
|
-
const projectPaths = pathsForProject(graphKey,
|
|
2658
|
+
const projectPaths = pathsForProject(graphKey, path7.join(opts.scanPath, "neat-out"));
|
|
2624
2659
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
2625
2660
|
errorsPath: projectPaths.errorsPath
|
|
2626
2661
|
});
|
|
@@ -2674,7 +2709,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2674
2709
|
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
2675
2710
|
} else if (outcome.outcome === "react-native") {
|
|
2676
2711
|
reactNative++;
|
|
2677
|
-
const svcName =
|
|
2712
|
+
const svcName = path7.basename(svc.dir);
|
|
2678
2713
|
console.log(
|
|
2679
2714
|
`neat: ${svc.dir} detected as React Native / Expo
|
|
2680
2715
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -2685,7 +2720,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2685
2720
|
);
|
|
2686
2721
|
} else if (outcome.outcome === "bun") {
|
|
2687
2722
|
bun++;
|
|
2688
|
-
const svcName =
|
|
2723
|
+
const svcName = path7.basename(svc.dir);
|
|
2689
2724
|
console.log(
|
|
2690
2725
|
`neat: ${svc.dir} detected as Bun
|
|
2691
2726
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -2696,7 +2731,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2696
2731
|
);
|
|
2697
2732
|
} else if (outcome.outcome === "deno") {
|
|
2698
2733
|
deno++;
|
|
2699
|
-
const svcName =
|
|
2734
|
+
const svcName = path7.basename(svc.dir);
|
|
2700
2735
|
console.log(
|
|
2701
2736
|
`neat: ${svc.dir} detected as Deno
|
|
2702
2737
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -2707,7 +2742,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2707
2742
|
);
|
|
2708
2743
|
} else if (outcome.outcome === "cloudflare-workers") {
|
|
2709
2744
|
cloudflareWorkers++;
|
|
2710
|
-
const svcName =
|
|
2745
|
+
const svcName = path7.basename(svc.dir);
|
|
2711
2746
|
console.log(
|
|
2712
2747
|
`neat: ${svc.dir} detected as Cloudflare Workers
|
|
2713
2748
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -2718,7 +2753,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2718
2753
|
);
|
|
2719
2754
|
} else if (outcome.outcome === "electron") {
|
|
2720
2755
|
electron++;
|
|
2721
|
-
const svcName =
|
|
2756
|
+
const svcName = path7.basename(svc.dir);
|
|
2722
2757
|
console.log(
|
|
2723
2758
|
`neat: ${svc.dir} detected as Electron
|
|
2724
2759
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -2892,10 +2927,10 @@ function formatPortCollisionMessage(port) {
|
|
|
2892
2927
|
];
|
|
2893
2928
|
}
|
|
2894
2929
|
function spawnDaemonDetached() {
|
|
2895
|
-
const here =
|
|
2930
|
+
const here = path7.dirname(new URL(import.meta.url).pathname);
|
|
2896
2931
|
const candidates = [
|
|
2897
|
-
|
|
2898
|
-
|
|
2932
|
+
path7.join(here, "neatd.cjs"),
|
|
2933
|
+
path7.join(here, "neatd.js")
|
|
2899
2934
|
];
|
|
2900
2935
|
let entry2 = null;
|
|
2901
2936
|
const fsSync = __require("fs");
|
|
@@ -2959,6 +2994,7 @@ async function runOrchestrator(opts) {
|
|
|
2959
2994
|
result.exitCode = 2;
|
|
2960
2995
|
return result;
|
|
2961
2996
|
}
|
|
2997
|
+
printBanner();
|
|
2962
2998
|
console.log(`neat: ${opts.scanPath}`);
|
|
2963
2999
|
console.log("");
|
|
2964
3000
|
const persisted = await extractAndPersist({
|
|
@@ -2969,6 +3005,13 @@ async function runOrchestrator(opts) {
|
|
|
2969
3005
|
const { graph, services, languages } = persisted;
|
|
2970
3006
|
result.steps.discovery = { services: services.length, languages };
|
|
2971
3007
|
console.log(`discovered ${services.length} service(s) across ${languages.length} language(s)`);
|
|
3008
|
+
if (services.length === 0) {
|
|
3009
|
+
console.error(
|
|
3010
|
+
`neat: no services found in ${opts.scanPath} \u2014 run from inside your project root, or \`npx neat.is <path>\``
|
|
3011
|
+
);
|
|
3012
|
+
result.exitCode = 2;
|
|
3013
|
+
return result;
|
|
3014
|
+
}
|
|
2972
3015
|
let runApply = !opts.noInstrument;
|
|
2973
3016
|
if (runApply && !opts.yes && process.stdout.isTTY && process.stdin.isTTY) {
|
|
2974
3017
|
runApply = await promptYesNo("instrument your services and open the dashboard?");
|
|
@@ -3092,10 +3135,16 @@ function printSummary(result, graph, dashboardUrl) {
|
|
|
3092
3135
|
for (const [t, c] of [...byEdge.entries()].sort()) console.log(` ${t}: ${c}`);
|
|
3093
3136
|
console.log("");
|
|
3094
3137
|
console.log(`dashboard: ${dashboardUrl}`);
|
|
3138
|
+
const token = process.env.NEAT_AUTH_TOKEN;
|
|
3139
|
+
if (typeof token === "string" && token.length > 0) {
|
|
3140
|
+
console.log(`auth token: ${token}`);
|
|
3141
|
+
} else {
|
|
3142
|
+
console.log("running locally \u2014 open the dashboard, no token needed");
|
|
3143
|
+
}
|
|
3095
3144
|
}
|
|
3096
3145
|
|
|
3097
3146
|
// src/cli-verbs.ts
|
|
3098
|
-
import
|
|
3147
|
+
import path8 from "path";
|
|
3099
3148
|
|
|
3100
3149
|
// src/cli-client.ts
|
|
3101
3150
|
import { Provenance as Provenance2 } from "@neat.is/types";
|
|
@@ -3123,10 +3172,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3123
3172
|
const root = baseUrl.replace(/\/$/, "");
|
|
3124
3173
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
3125
3174
|
return {
|
|
3126
|
-
async get(
|
|
3175
|
+
async get(path10) {
|
|
3127
3176
|
let res;
|
|
3128
3177
|
try {
|
|
3129
|
-
res = await fetch(`${root}${
|
|
3178
|
+
res = await fetch(`${root}${path10}`, {
|
|
3130
3179
|
headers: { ...authHeader }
|
|
3131
3180
|
});
|
|
3132
3181
|
} catch (err) {
|
|
@@ -3138,16 +3187,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3138
3187
|
const body = await res.text().catch(() => "");
|
|
3139
3188
|
throw new HttpError(
|
|
3140
3189
|
res.status,
|
|
3141
|
-
`${res.status} ${res.statusText} on GET ${
|
|
3190
|
+
`${res.status} ${res.statusText} on GET ${path10}: ${body}`,
|
|
3142
3191
|
body
|
|
3143
3192
|
);
|
|
3144
3193
|
}
|
|
3145
3194
|
return await res.json();
|
|
3146
3195
|
},
|
|
3147
|
-
async post(
|
|
3196
|
+
async post(path10, body) {
|
|
3148
3197
|
let res;
|
|
3149
3198
|
try {
|
|
3150
|
-
res = await fetch(`${root}${
|
|
3199
|
+
res = await fetch(`${root}${path10}`, {
|
|
3151
3200
|
method: "POST",
|
|
3152
3201
|
headers: { "content-type": "application/json", ...authHeader },
|
|
3153
3202
|
body: JSON.stringify(body)
|
|
@@ -3161,7 +3210,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3161
3210
|
const text = await res.text().catch(() => "");
|
|
3162
3211
|
throw new HttpError(
|
|
3163
3212
|
res.status,
|
|
3164
|
-
`${res.status} ${res.statusText} on POST ${
|
|
3213
|
+
`${res.status} ${res.statusText} on POST ${path10}: ${text}`,
|
|
3165
3214
|
text
|
|
3166
3215
|
);
|
|
3167
3216
|
}
|
|
@@ -3175,12 +3224,12 @@ function projectPath(project, suffix) {
|
|
|
3175
3224
|
}
|
|
3176
3225
|
async function runRootCause(client, input) {
|
|
3177
3226
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
3178
|
-
const
|
|
3227
|
+
const path10 = projectPath(
|
|
3179
3228
|
input.project,
|
|
3180
3229
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
3181
3230
|
);
|
|
3182
3231
|
try {
|
|
3183
|
-
const result = await client.get(
|
|
3232
|
+
const result = await client.get(path10);
|
|
3184
3233
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
3185
3234
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
3186
3235
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -3206,12 +3255,12 @@ async function runRootCause(client, input) {
|
|
|
3206
3255
|
}
|
|
3207
3256
|
async function runBlastRadius(client, input) {
|
|
3208
3257
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
3209
|
-
const
|
|
3258
|
+
const path10 = projectPath(
|
|
3210
3259
|
input.project,
|
|
3211
3260
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
3212
3261
|
);
|
|
3213
3262
|
try {
|
|
3214
|
-
const result = await client.get(
|
|
3263
|
+
const result = await client.get(path10);
|
|
3215
3264
|
if (result.totalAffected === 0) {
|
|
3216
3265
|
return {
|
|
3217
3266
|
summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
|
|
@@ -3245,12 +3294,12 @@ function formatBlastEntry(n) {
|
|
|
3245
3294
|
}
|
|
3246
3295
|
async function runDependencies(client, input) {
|
|
3247
3296
|
const depth = input.depth ?? 3;
|
|
3248
|
-
const
|
|
3297
|
+
const path10 = projectPath(
|
|
3249
3298
|
input.project,
|
|
3250
3299
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
3251
3300
|
);
|
|
3252
3301
|
try {
|
|
3253
|
-
const result = await client.get(
|
|
3302
|
+
const result = await client.get(path10);
|
|
3254
3303
|
if (result.total === 0) {
|
|
3255
3304
|
return {
|
|
3256
3305
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -3331,9 +3380,9 @@ function formatDuration(ms) {
|
|
|
3331
3380
|
return `${Math.round(h / 24)}d`;
|
|
3332
3381
|
}
|
|
3333
3382
|
async function runIncidents(client, input) {
|
|
3334
|
-
const
|
|
3383
|
+
const path10 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
3335
3384
|
try {
|
|
3336
|
-
const body = await client.get(
|
|
3385
|
+
const body = await client.get(path10);
|
|
3337
3386
|
const events = body.events;
|
|
3338
3387
|
if (events.length === 0) {
|
|
3339
3388
|
return {
|
|
@@ -3623,7 +3672,7 @@ async function resolveProjectEntry(opts) {
|
|
|
3623
3672
|
const cwd = opts.cwd ?? process.cwd();
|
|
3624
3673
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
3625
3674
|
for (const entry2 of entries) {
|
|
3626
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
3675
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path8.sep}`)) {
|
|
3627
3676
|
return entry2;
|
|
3628
3677
|
}
|
|
3629
3678
|
}
|
|
@@ -3774,8 +3823,24 @@ async function runSync(opts) {
|
|
|
3774
3823
|
|
|
3775
3824
|
// src/cli.ts
|
|
3776
3825
|
import { DivergenceTypeSchema } from "@neat.is/types";
|
|
3826
|
+
function isNpxInvocation() {
|
|
3827
|
+
if (process.env.npm_command === "exec") return true;
|
|
3828
|
+
const execpath = process.env.npm_execpath ?? "";
|
|
3829
|
+
if (execpath.includes("npx")) return true;
|
|
3830
|
+
const entry2 = process.argv[1] ?? "";
|
|
3831
|
+
if (entry2.includes("/_npx/") || entry2.includes("\\_npx\\")) return true;
|
|
3832
|
+
return false;
|
|
3833
|
+
}
|
|
3834
|
+
function commandPrefix() {
|
|
3835
|
+
return isNpxInvocation() ? "npx neat.is" : "neat";
|
|
3836
|
+
}
|
|
3777
3837
|
function usage() {
|
|
3778
|
-
|
|
3838
|
+
const neat = commandPrefix();
|
|
3839
|
+
console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
|
|
3840
|
+
console.log("");
|
|
3841
|
+
console.log(`usage: ${neat} <command> [args] [--project <name>]`);
|
|
3842
|
+
console.log("");
|
|
3843
|
+
console.log(`Run \`${neat}\` with no command from inside your project to go zero-to-graph in one step.`);
|
|
3779
3844
|
console.log("");
|
|
3780
3845
|
console.log("lifecycle commands:");
|
|
3781
3846
|
console.log(" init <path> One-time install: discover, extract, register, plan SDK install.");
|
|
@@ -3794,6 +3859,8 @@ function usage() {
|
|
|
3794
3859
|
console.log(" uninstall <name>");
|
|
3795
3860
|
console.log(" Remove a project from the registry. Does not touch");
|
|
3796
3861
|
console.log(" neat-out/, policy.json, or any user file.");
|
|
3862
|
+
console.log(" prune Drop registry entries whose path is gone from disk.");
|
|
3863
|
+
console.log(" Flags: --json emit the removed list as JSON");
|
|
3797
3864
|
console.log(" version Print the installed @neat.is/core version and exit.");
|
|
3798
3865
|
console.log(" Aliases: --version, -v.");
|
|
3799
3866
|
console.log(" skill Install or print the Claude Code MCP drop-in.");
|
|
@@ -3815,30 +3882,30 @@ function usage() {
|
|
|
3815
3882
|
console.log("");
|
|
3816
3883
|
console.log("query commands (mirror the MCP tools, ADR-050):");
|
|
3817
3884
|
console.log(" root-cause <node-id> Walk inbound edges to find what broke first.");
|
|
3818
|
-
console.log(
|
|
3885
|
+
console.log(` example: ${neat} root-cause service:<name>`);
|
|
3819
3886
|
console.log(" blast-radius <node-id> BFS outbound \u2014 what would break if this dies.");
|
|
3820
|
-
console.log(
|
|
3887
|
+
console.log(` example: ${neat} blast-radius database:<host>`);
|
|
3821
3888
|
console.log(" dependencies <node-id> Transitive outbound dependencies.");
|
|
3822
3889
|
console.log(" Flags: --depth N (default 3, max 10)");
|
|
3823
|
-
console.log(
|
|
3890
|
+
console.log(` example: ${neat} dependencies service:<name> --depth 2`);
|
|
3824
3891
|
console.log(" observed-dependencies <node-id> OBSERVED-only outbound edges (runtime traffic).");
|
|
3825
|
-
console.log(
|
|
3892
|
+
console.log(` example: ${neat} observed-dependencies service:<name>`);
|
|
3826
3893
|
console.log(" incidents [<node-id>] Recent error events; per-node when an id is given.");
|
|
3827
3894
|
console.log(" Flags: --limit N (default 20)");
|
|
3828
|
-
console.log(
|
|
3895
|
+
console.log(` example: ${neat} incidents service:<name> --limit 5`);
|
|
3829
3896
|
console.log(" search <query> Semantic (or substring) match on node names/ids.");
|
|
3830
|
-
console.log(
|
|
3897
|
+
console.log(` example: ${neat} search "checkout"`);
|
|
3831
3898
|
console.log(" diff --against <snapshot> Compare the live graph to a saved snapshot.");
|
|
3832
|
-
console.log(
|
|
3899
|
+
console.log(` example: ${neat} diff --against ./snapshots/baseline.json`);
|
|
3833
3900
|
console.log(" stale-edges Recent OBSERVED \u2192 STALE transitions.");
|
|
3834
3901
|
console.log(" Flags: --limit N, --edge-type CALLS|CONNECTS_TO|...");
|
|
3835
|
-
console.log(
|
|
3902
|
+
console.log(` example: ${neat} stale-edges --edge-type CALLS`);
|
|
3836
3903
|
console.log(" policies Current policy violations.");
|
|
3837
3904
|
console.log(" Flags: --node <id>, --hypothetical-action <json>");
|
|
3838
|
-
console.log(
|
|
3905
|
+
console.log(` example: ${neat} policies --node service:<name> --json`);
|
|
3839
3906
|
console.log(" divergences Where code (EXTRACTED) and production (OBSERVED) disagree.");
|
|
3840
3907
|
console.log(" Flags: --type <list>, --min-confidence <0..1>, --node <id>");
|
|
3841
|
-
console.log(
|
|
3908
|
+
console.log(` example: ${neat} divergences --min-confidence 0.7`);
|
|
3842
3909
|
console.log("");
|
|
3843
3910
|
console.log("flags:");
|
|
3844
3911
|
console.log(' --project <name> Name the project this command targets. Default: "default".');
|
|
@@ -3853,6 +3920,7 @@ function usage() {
|
|
|
3853
3920
|
console.log("");
|
|
3854
3921
|
console.log("environment:");
|
|
3855
3922
|
console.log(" NEAT_API_URL base URL for the core REST API (default http://localhost:8080)");
|
|
3923
|
+
console.log(" alias: NEAT_CORE_URL (the name the MCP server reads)");
|
|
3856
3924
|
console.log(" NEAT_PROJECT project name when --project isn't passed");
|
|
3857
3925
|
}
|
|
3858
3926
|
var STRING_FLAGS = [
|
|
@@ -3982,40 +4050,10 @@ function assignFlag(out, field, value) {
|
|
|
3982
4050
|
;
|
|
3983
4051
|
out[field] = value;
|
|
3984
4052
|
}
|
|
3985
|
-
function readPackageVersion() {
|
|
3986
|
-
const here = typeof __dirname !== "undefined" ? __dirname : path8.dirname(fileURLToPath(import.meta.url));
|
|
3987
|
-
const candidates = [
|
|
3988
|
-
path8.resolve(here, "../package.json"),
|
|
3989
|
-
path8.resolve(here, "../../package.json")
|
|
3990
|
-
];
|
|
3991
|
-
for (const candidate of candidates) {
|
|
3992
|
-
try {
|
|
3993
|
-
const raw = readFileSync(candidate, "utf8");
|
|
3994
|
-
const parsed = JSON.parse(raw);
|
|
3995
|
-
if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
|
|
3996
|
-
return parsed.version;
|
|
3997
|
-
}
|
|
3998
|
-
} catch {
|
|
3999
|
-
}
|
|
4000
|
-
}
|
|
4001
|
-
return "unknown";
|
|
4002
|
-
}
|
|
4003
4053
|
function printVersion() {
|
|
4004
4054
|
process.stdout.write(`${readPackageVersion()}
|
|
4005
4055
|
`);
|
|
4006
4056
|
}
|
|
4007
|
-
function printBanner() {
|
|
4008
|
-
console.log("\u2588\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557");
|
|
4009
|
-
console.log("\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D");
|
|
4010
|
-
console.log("\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 ");
|
|
4011
|
-
console.log("\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 ");
|
|
4012
|
-
console.log("\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ");
|
|
4013
|
-
console.log("\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D ");
|
|
4014
|
-
console.log("");
|
|
4015
|
-
console.log(" Network Expressive Architecting Tool");
|
|
4016
|
-
console.log(` neat.is \xB7 v${readPackageVersion()} \xB7 Apache 2.0`);
|
|
4017
|
-
console.log("");
|
|
4018
|
-
}
|
|
4019
4057
|
function printDiscoveryReport(opts, services) {
|
|
4020
4058
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
4021
4059
|
const mode = opts.dryRun ? "dry-run" : opts.apply ? "apply" : "patch-only";
|
|
@@ -4063,12 +4101,12 @@ async function runInit(opts) {
|
|
|
4063
4101
|
printDiscoveryReport(opts, services);
|
|
4064
4102
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
4065
4103
|
const patch = renderPatch(sections);
|
|
4066
|
-
const patchPath =
|
|
4104
|
+
const patchPath = path9.join(opts.scanPath, "neat.patch");
|
|
4067
4105
|
if (opts.dryRun) {
|
|
4068
4106
|
await fs7.writeFile(patchPath, patch, "utf8");
|
|
4069
4107
|
written.push(patchPath);
|
|
4070
4108
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
4071
|
-
const gitignorePath =
|
|
4109
|
+
const gitignorePath = path9.join(opts.scanPath, ".gitignore");
|
|
4072
4110
|
const gitignoreExists = await fs7.stat(gitignorePath).then(() => true).catch(() => false);
|
|
4073
4111
|
const verb = gitignoreExists ? "append" : "create";
|
|
4074
4112
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
@@ -4080,9 +4118,9 @@ async function runInit(opts) {
|
|
|
4080
4118
|
const graph = getGraph(graphKey);
|
|
4081
4119
|
const projectPaths = pathsForProject(
|
|
4082
4120
|
graphKey,
|
|
4083
|
-
|
|
4121
|
+
path9.join(opts.scanPath, "neat-out")
|
|
4084
4122
|
);
|
|
4085
|
-
const errorsPath =
|
|
4123
|
+
const errorsPath = path9.join(path9.dirname(opts.outPath), path9.basename(projectPaths.errorsPath));
|
|
4086
4124
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
4087
4125
|
await saveGraphToDisk(graph, opts.outPath);
|
|
4088
4126
|
written.push(opts.outPath);
|
|
@@ -4194,16 +4232,16 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
4194
4232
|
command: "npx",
|
|
4195
4233
|
args: ["-y", "@neat.is/mcp"],
|
|
4196
4234
|
env: {
|
|
4197
|
-
|
|
4235
|
+
NEAT_CORE_URL: "http://localhost:8080"
|
|
4198
4236
|
}
|
|
4199
4237
|
}
|
|
4200
4238
|
}
|
|
4201
4239
|
};
|
|
4202
4240
|
function claudeConfigPath() {
|
|
4203
4241
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
4204
|
-
if (override && override.length > 0) return
|
|
4242
|
+
if (override && override.length > 0) return path9.resolve(override);
|
|
4205
4243
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4206
|
-
return
|
|
4244
|
+
return path9.join(home, ".claude.json");
|
|
4207
4245
|
}
|
|
4208
4246
|
async function runSkill(opts) {
|
|
4209
4247
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -4227,7 +4265,7 @@ async function runSkill(opts) {
|
|
|
4227
4265
|
...existing,
|
|
4228
4266
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
4229
4267
|
};
|
|
4230
|
-
await fs7.mkdir(
|
|
4268
|
+
await fs7.mkdir(path9.dirname(target), { recursive: true });
|
|
4231
4269
|
await fs7.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
4232
4270
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
4233
4271
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
@@ -4240,19 +4278,30 @@ async function runSkill(opts) {
|
|
|
4240
4278
|
console.log("");
|
|
4241
4279
|
console.log("Manual install: copy mcpServers.neat from --print-config into ~/.claude.json,");
|
|
4242
4280
|
console.log("then restart Claude Code. See packages/claude-skill/SKILL.md for the tool list.");
|
|
4281
|
+
console.log("");
|
|
4282
|
+
console.log("The MCP server reads NEAT_CORE_URL for the daemon URL \u2014 point it at a");
|
|
4283
|
+
console.log("non-default daemon by editing that value in the generated config.");
|
|
4243
4284
|
return { exitCode: 0 };
|
|
4244
4285
|
}
|
|
4245
4286
|
async function main() {
|
|
4246
|
-
const
|
|
4247
|
-
|
|
4287
|
+
const argv = process.argv.slice(2);
|
|
4288
|
+
const cmd0 = argv[0];
|
|
4289
|
+
if (cmd0 === "-h" || cmd0 === "--help") {
|
|
4248
4290
|
usage();
|
|
4249
4291
|
process.exit(0);
|
|
4250
4292
|
}
|
|
4251
|
-
if (
|
|
4293
|
+
if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
|
|
4252
4294
|
printVersion();
|
|
4253
4295
|
process.exit(0);
|
|
4254
4296
|
}
|
|
4255
|
-
const
|
|
4297
|
+
const argvParsed = parseArgs(argv);
|
|
4298
|
+
if (argvParsed.positional.length === 0) {
|
|
4299
|
+
const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
|
|
4300
|
+
if (orchestratorCode2 !== null && orchestratorCode2 !== 0) process.exit(orchestratorCode2);
|
|
4301
|
+
return;
|
|
4302
|
+
}
|
|
4303
|
+
const cmd = argvParsed.positional[0];
|
|
4304
|
+
const parsed = { ...argvParsed, positional: argvParsed.positional.slice(1) };
|
|
4256
4305
|
const { positional, apply: apply3, dryRun, noInstall } = parsed;
|
|
4257
4306
|
const project = parsed.project ?? DEFAULT_PROJECT;
|
|
4258
4307
|
if (cmd === "init") {
|
|
@@ -4266,12 +4315,12 @@ async function main() {
|
|
|
4266
4315
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
4267
4316
|
process.exit(2);
|
|
4268
4317
|
}
|
|
4269
|
-
const scanPath =
|
|
4318
|
+
const scanPath = path9.resolve(target);
|
|
4270
4319
|
const projectExplicit = parsed.project !== null;
|
|
4271
|
-
const projectName = projectExplicit ? project :
|
|
4320
|
+
const projectName = projectExplicit ? project : path9.basename(scanPath);
|
|
4272
4321
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
4273
|
-
const fallback = pathsForProject(projectKey,
|
|
4274
|
-
const outPath =
|
|
4322
|
+
const fallback = pathsForProject(projectKey, path9.join(scanPath, "neat-out")).snapshotPath;
|
|
4323
|
+
const outPath = path9.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
4275
4324
|
const result = await runInit({
|
|
4276
4325
|
scanPath,
|
|
4277
4326
|
outPath,
|
|
@@ -4292,21 +4341,21 @@ async function main() {
|
|
|
4292
4341
|
usage();
|
|
4293
4342
|
process.exit(2);
|
|
4294
4343
|
}
|
|
4295
|
-
const scanPath =
|
|
4344
|
+
const scanPath = path9.resolve(target);
|
|
4296
4345
|
const stat = await fs7.stat(scanPath).catch(() => null);
|
|
4297
4346
|
if (!stat || !stat.isDirectory()) {
|
|
4298
4347
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
4299
4348
|
process.exit(2);
|
|
4300
4349
|
}
|
|
4301
|
-
const projectPaths = pathsForProject(project,
|
|
4302
|
-
const outPath =
|
|
4303
|
-
const errorsPath =
|
|
4304
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
4350
|
+
const projectPaths = pathsForProject(project, path9.join(scanPath, "neat-out"));
|
|
4351
|
+
const outPath = path9.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
4352
|
+
const errorsPath = path9.resolve(
|
|
4353
|
+
process.env.NEAT_ERRORS_PATH ?? path9.join(path9.dirname(outPath), path9.basename(projectPaths.errorsPath))
|
|
4305
4354
|
);
|
|
4306
|
-
const staleEventsPath =
|
|
4307
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
4355
|
+
const staleEventsPath = path9.resolve(
|
|
4356
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? path9.join(path9.dirname(outPath), path9.basename(projectPaths.staleEventsPath))
|
|
4308
4357
|
);
|
|
4309
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
4358
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path9.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
4310
4359
|
const handle = await startWatch(getGraph(project), {
|
|
4311
4360
|
scanPath,
|
|
4312
4361
|
outPath,
|
|
@@ -4399,6 +4448,19 @@ async function main() {
|
|
|
4399
4448
|
console.log("note: neat-out/, policy.json, and other files at the project path were left in place.");
|
|
4400
4449
|
return;
|
|
4401
4450
|
}
|
|
4451
|
+
if (cmd === "prune") {
|
|
4452
|
+
const removed = await pruneRegistry({ ttlMs: 0 });
|
|
4453
|
+
if (parsed.json) {
|
|
4454
|
+
console.log(JSON.stringify(removed.map((p) => ({ name: p.name, path: p.path })), null, 2));
|
|
4455
|
+
return;
|
|
4456
|
+
}
|
|
4457
|
+
if (removed.length === 0) {
|
|
4458
|
+
console.log("nothing to prune \u2014 every registered project path still exists.");
|
|
4459
|
+
return;
|
|
4460
|
+
}
|
|
4461
|
+
console.log(`pruned ${removed.length} project${removed.length === 1 ? "" : "s"}: ${removed.map((p) => p.name).join(", ")}`);
|
|
4462
|
+
return;
|
|
4463
|
+
}
|
|
4402
4464
|
if (cmd === "deploy") {
|
|
4403
4465
|
const artifact = await runDeploy();
|
|
4404
4466
|
const block = renderOtelEnvBlock2(artifact.token);
|
|
@@ -4452,11 +4514,11 @@ async function main() {
|
|
|
4452
4514
|
process.exit(1);
|
|
4453
4515
|
}
|
|
4454
4516
|
async function tryOrchestrator(cmd, parsed) {
|
|
4455
|
-
const scanPath =
|
|
4517
|
+
const scanPath = path9.resolve(cmd);
|
|
4456
4518
|
const stat = await fs7.stat(scanPath).catch(() => null);
|
|
4457
4519
|
if (!stat || !stat.isDirectory()) return null;
|
|
4458
4520
|
const projectExplicit = parsed.project !== null;
|
|
4459
|
-
const projectName = projectExplicit ? parsed.project :
|
|
4521
|
+
const projectName = projectExplicit ? parsed.project : path9.basename(scanPath);
|
|
4460
4522
|
const result = await runOrchestrator({
|
|
4461
4523
|
scanPath,
|
|
4462
4524
|
project: projectName,
|
|
@@ -4486,12 +4548,44 @@ function resolveProjectFlag(parsed) {
|
|
|
4486
4548
|
if (env && env.length > 0 && env !== DEFAULT_PROJECT) return env;
|
|
4487
4549
|
return void 0;
|
|
4488
4550
|
}
|
|
4551
|
+
var ProjectResolutionError = class extends Error {
|
|
4552
|
+
constructor(message, exitCode = 2) {
|
|
4553
|
+
super(message);
|
|
4554
|
+
this.exitCode = exitCode;
|
|
4555
|
+
this.name = "ProjectResolutionError";
|
|
4556
|
+
}
|
|
4557
|
+
exitCode;
|
|
4558
|
+
};
|
|
4559
|
+
async function resolveProjectForVerb(client, parsed) {
|
|
4560
|
+
const explicit = resolveProjectFlag(parsed);
|
|
4561
|
+
if (explicit) return explicit;
|
|
4562
|
+
const projects = await client.get("/projects");
|
|
4563
|
+
if (projects.some((p) => p.name === DEFAULT_PROJECT)) {
|
|
4564
|
+
return void 0;
|
|
4565
|
+
}
|
|
4566
|
+
if (projects.length === 1) {
|
|
4567
|
+
return projects[0].name;
|
|
4568
|
+
}
|
|
4569
|
+
if (projects.length === 0) {
|
|
4570
|
+
throw new ProjectResolutionError(
|
|
4571
|
+
"No projects are registered with the daemon yet. Run `npx neat.is` in a repo to build a graph first, then re-run this command."
|
|
4572
|
+
);
|
|
4573
|
+
}
|
|
4574
|
+
const names = projects.map((p) => p.name).sort().map((n) => ` ${n}`).join("\n");
|
|
4575
|
+
throw new ProjectResolutionError(
|
|
4576
|
+
`Several projects are registered and none is named "default", so I can't pick one for you.
|
|
4577
|
+
Pass --project <name> to choose:
|
|
4578
|
+
${names}`
|
|
4579
|
+
);
|
|
4580
|
+
}
|
|
4581
|
+
function resolveDaemonUrl() {
|
|
4582
|
+
return process.env.NEAT_API_URL ?? process.env.NEAT_CORE_URL ?? "http://localhost:8080";
|
|
4583
|
+
}
|
|
4489
4584
|
async function runQueryVerb(cmd, parsed) {
|
|
4490
|
-
const baseUrl =
|
|
4585
|
+
const baseUrl = resolveDaemonUrl();
|
|
4491
4586
|
const client = createHttpClient(baseUrl, resolveAuthToken());
|
|
4492
|
-
const project = resolveProjectFlag(parsed);
|
|
4493
4587
|
const positional = parsed.positional;
|
|
4494
|
-
let
|
|
4588
|
+
let makeWork;
|
|
4495
4589
|
switch (cmd) {
|
|
4496
4590
|
case "root-cause": {
|
|
4497
4591
|
const node = positional[0];
|
|
@@ -4499,7 +4593,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4499
4593
|
console.error("neat root-cause: missing <node-id>");
|
|
4500
4594
|
return 2;
|
|
4501
4595
|
}
|
|
4502
|
-
|
|
4596
|
+
makeWork = (project) => runRootCause(client, {
|
|
4503
4597
|
errorNode: node,
|
|
4504
4598
|
...parsed.errorId ? { errorId: parsed.errorId } : {},
|
|
4505
4599
|
...project ? { project } : {}
|
|
@@ -4512,7 +4606,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4512
4606
|
console.error("neat blast-radius: missing <node-id>");
|
|
4513
4607
|
return 2;
|
|
4514
4608
|
}
|
|
4515
|
-
|
|
4609
|
+
makeWork = (project) => runBlastRadius(client, {
|
|
4516
4610
|
nodeId: node,
|
|
4517
4611
|
...parsed.depth !== null ? { depth: parsed.depth } : {},
|
|
4518
4612
|
...project ? { project } : {}
|
|
@@ -4525,7 +4619,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4525
4619
|
console.error("neat dependencies: missing <node-id>");
|
|
4526
4620
|
return 2;
|
|
4527
4621
|
}
|
|
4528
|
-
|
|
4622
|
+
makeWork = (project) => runDependencies(client, {
|
|
4529
4623
|
nodeId: node,
|
|
4530
4624
|
...parsed.depth !== null ? { depth: parsed.depth } : {},
|
|
4531
4625
|
...project ? { project } : {}
|
|
@@ -4538,14 +4632,14 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4538
4632
|
console.error("neat observed-dependencies: missing <node-id>");
|
|
4539
4633
|
return 2;
|
|
4540
4634
|
}
|
|
4541
|
-
|
|
4635
|
+
makeWork = (project) => runObservedDependencies(client, {
|
|
4542
4636
|
nodeId: node,
|
|
4543
4637
|
...project ? { project } : {}
|
|
4544
4638
|
});
|
|
4545
4639
|
break;
|
|
4546
4640
|
}
|
|
4547
4641
|
case "incidents": {
|
|
4548
|
-
|
|
4642
|
+
makeWork = (project) => runIncidents(client, {
|
|
4549
4643
|
...positional[0] ? { nodeId: positional[0] } : {},
|
|
4550
4644
|
...parsed.limit !== null ? { limit: parsed.limit } : {},
|
|
4551
4645
|
...project ? { project } : {}
|
|
@@ -4558,7 +4652,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4558
4652
|
console.error("neat search: missing <query>");
|
|
4559
4653
|
return 2;
|
|
4560
4654
|
}
|
|
4561
|
-
|
|
4655
|
+
makeWork = (project) => runSearch(client, { query: q, ...project ? { project } : {} });
|
|
4562
4656
|
break;
|
|
4563
4657
|
}
|
|
4564
4658
|
case "diff": {
|
|
@@ -4567,14 +4661,14 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4567
4661
|
console.error("neat diff: --against <snapshot-path> is required");
|
|
4568
4662
|
return 2;
|
|
4569
4663
|
}
|
|
4570
|
-
|
|
4664
|
+
makeWork = (project) => runDiff(client, {
|
|
4571
4665
|
againstSnapshot: against,
|
|
4572
4666
|
...project ? { project } : {}
|
|
4573
4667
|
});
|
|
4574
4668
|
break;
|
|
4575
4669
|
}
|
|
4576
4670
|
case "stale-edges": {
|
|
4577
|
-
|
|
4671
|
+
makeWork = (project) => runStaleEdges(client, {
|
|
4578
4672
|
...parsed.limit !== null ? { limit: parsed.limit } : {},
|
|
4579
4673
|
...parsed.edgeType ? { edgeType: parsed.edgeType } : {},
|
|
4580
4674
|
...project ? { project } : {}
|
|
@@ -4593,7 +4687,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4593
4687
|
return 2;
|
|
4594
4688
|
}
|
|
4595
4689
|
}
|
|
4596
|
-
|
|
4690
|
+
makeWork = (project) => runPolicies(client, {
|
|
4597
4691
|
...parsed.node ? { nodeId: parsed.node } : {},
|
|
4598
4692
|
...hypothetical ? { hypotheticalAction: hypothetical } : {},
|
|
4599
4693
|
...project ? { project } : {}
|
|
@@ -4617,7 +4711,7 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4617
4711
|
}
|
|
4618
4712
|
typeFilter = out;
|
|
4619
4713
|
}
|
|
4620
|
-
|
|
4714
|
+
makeWork = (project) => runDivergences(client, {
|
|
4621
4715
|
...typeFilter ? { type: typeFilter } : {},
|
|
4622
4716
|
...parsed.minConfidence !== null ? { minConfidence: parsed.minConfidence } : {},
|
|
4623
4717
|
...parsed.node ? { node: parsed.node } : {},
|
|
@@ -4630,16 +4724,21 @@ async function runQueryVerb(cmd, parsed) {
|
|
|
4630
4724
|
return 2;
|
|
4631
4725
|
}
|
|
4632
4726
|
try {
|
|
4633
|
-
const
|
|
4727
|
+
const project = await resolveProjectForVerb(client, parsed);
|
|
4728
|
+
const result = await makeWork(project);
|
|
4634
4729
|
if (parsed.json) process.stdout.write(formatJson(result) + "\n");
|
|
4635
4730
|
else process.stdout.write(formatHuman(result) + "\n");
|
|
4636
4731
|
return 0;
|
|
4637
4732
|
} catch (err) {
|
|
4733
|
+
if (err instanceof ProjectResolutionError) {
|
|
4734
|
+
console.error(`neat ${cmd}: ${err.message}`);
|
|
4735
|
+
return err.exitCode;
|
|
4736
|
+
}
|
|
4638
4737
|
if (err instanceof HttpError) {
|
|
4639
4738
|
const detail = err.responseBody.length > 0 ? err.responseBody : err.message;
|
|
4640
4739
|
console.error(`neat ${cmd}: ${detail.trim()}`);
|
|
4641
4740
|
} else if (err instanceof TransportError) {
|
|
4642
|
-
console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (NEAT_API_URL=${
|
|
4741
|
+
console.error(`neat ${cmd}: ${err.message}. Is the daemon running? (NEAT_API_URL=${resolveDaemonUrl()})`);
|
|
4643
4742
|
} else {
|
|
4644
4743
|
console.error(`neat ${cmd}: ${err.message}`);
|
|
4645
4744
|
}
|
|
@@ -4655,12 +4754,18 @@ if (/[\\/]cli\.(?:cjs|js)$/.test(entry) || entry.endsWith("/cli") || entry.endsW
|
|
|
4655
4754
|
}
|
|
4656
4755
|
export {
|
|
4657
4756
|
CLAUDE_SKILL_CONFIG,
|
|
4757
|
+
ProjectResolutionError,
|
|
4658
4758
|
QUERY_VERBS,
|
|
4759
|
+
commandPrefix,
|
|
4760
|
+
isNpxInvocation,
|
|
4761
|
+
main,
|
|
4659
4762
|
parseArgs,
|
|
4660
4763
|
printBanner,
|
|
4661
4764
|
readPackageVersion,
|
|
4765
|
+
resolveProjectForVerb,
|
|
4662
4766
|
runInit,
|
|
4663
4767
|
runQueryVerb,
|
|
4664
|
-
runSkill
|
|
4768
|
+
runSkill,
|
|
4769
|
+
usage
|
|
4665
4770
|
};
|
|
4666
4771
|
//# sourceMappingURL=cli.js.map
|