@neat.is/core 0.4.14 → 0.4.16
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-QZRDQ7WX.js → chunk-VMLWUK7W.js} +41 -29
- package/dist/chunk-VMLWUK7W.js.map +1 -0
- package/dist/cli.cjs +558 -507
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.d.cts +8 -3
- package/dist/cli.d.ts +8 -3
- package/dist/cli.js +215 -169
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +89 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/dist/neatd.cjs +89 -28
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +1 -1
- package/package.json +3 -4
- package/dist/chunk-QZRDQ7WX.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -57,13 +57,47 @@ import {
|
|
|
57
57
|
} from "./chunk-6CO7C4IU.js";
|
|
58
58
|
|
|
59
59
|
// src/cli.ts
|
|
60
|
-
import
|
|
61
|
-
import { promises as fs7
|
|
60
|
+
import path9 from "path";
|
|
61
|
+
import { promises as fs7 } from "fs";
|
|
62
|
+
|
|
63
|
+
// src/banner.ts
|
|
64
|
+
import path from "path";
|
|
65
|
+
import { readFileSync } from "fs";
|
|
62
66
|
import { fileURLToPath } from "url";
|
|
67
|
+
function readPackageVersion() {
|
|
68
|
+
const here = typeof __dirname !== "undefined" ? __dirname : path.dirname(fileURLToPath(import.meta.url));
|
|
69
|
+
const candidates = [
|
|
70
|
+
path.resolve(here, "../package.json"),
|
|
71
|
+
path.resolve(here, "../../package.json")
|
|
72
|
+
];
|
|
73
|
+
for (const candidate of candidates) {
|
|
74
|
+
try {
|
|
75
|
+
const raw = readFileSync(candidate, "utf8");
|
|
76
|
+
const parsed = JSON.parse(raw);
|
|
77
|
+
if (parsed.name === "@neat.is/core" && typeof parsed.version === "string") {
|
|
78
|
+
return parsed.version;
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return "unknown";
|
|
84
|
+
}
|
|
85
|
+
function printBanner() {
|
|
86
|
+
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");
|
|
87
|
+
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");
|
|
88
|
+
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 ");
|
|
89
|
+
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 ");
|
|
90
|
+
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 ");
|
|
91
|
+
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 ");
|
|
92
|
+
console.log("");
|
|
93
|
+
console.log(" Network Expressive Architecting Tool");
|
|
94
|
+
console.log(` neat.is \xB7 v${readPackageVersion()} \xB7 Apache 2.0`);
|
|
95
|
+
console.log("");
|
|
96
|
+
}
|
|
63
97
|
|
|
64
98
|
// src/gitignore.ts
|
|
65
99
|
import { promises as fs } from "fs";
|
|
66
|
-
import
|
|
100
|
+
import path2 from "path";
|
|
67
101
|
var NEAT_OUT_LINE = "neat-out/";
|
|
68
102
|
var NEAT_HEADER = "# NEAT \u2014 machine-local snapshots and events";
|
|
69
103
|
function isNeatOutLine(line) {
|
|
@@ -71,7 +105,7 @@ function isNeatOutLine(line) {
|
|
|
71
105
|
return trimmed === "neat-out/" || trimmed === "neat-out";
|
|
72
106
|
}
|
|
73
107
|
async function ensureNeatOutIgnored(projectDir) {
|
|
74
|
-
const file =
|
|
108
|
+
const file = path2.join(projectDir, ".gitignore");
|
|
75
109
|
let existing = null;
|
|
76
110
|
try {
|
|
77
111
|
existing = await fs.readFile(file, "utf8");
|
|
@@ -193,7 +227,7 @@ function formatIncompat(inc) {
|
|
|
193
227
|
|
|
194
228
|
// src/watch.ts
|
|
195
229
|
import fs2 from "fs";
|
|
196
|
-
import
|
|
230
|
+
import path3 from "path";
|
|
197
231
|
import chokidar from "chokidar";
|
|
198
232
|
var ALL_PHASES = [
|
|
199
233
|
"services",
|
|
@@ -206,8 +240,8 @@ var ALL_PHASES = [
|
|
|
206
240
|
];
|
|
207
241
|
function classifyChange(relPath) {
|
|
208
242
|
const phases = /* @__PURE__ */ new Set();
|
|
209
|
-
const base =
|
|
210
|
-
const segments = relPath.split(
|
|
243
|
+
const base = path3.basename(relPath).toLowerCase();
|
|
244
|
+
const segments = relPath.split(path3.sep).map((s) => s.toLowerCase());
|
|
211
245
|
if (base === "package.json" || base === "requirements.txt" || base === "pyproject.toml" || base === "setup.py") {
|
|
212
246
|
phases.add("services");
|
|
213
247
|
phases.add("aliases");
|
|
@@ -328,9 +362,9 @@ function countWatchableDirs(scanPath, limit) {
|
|
|
328
362
|
for (const e of entries) {
|
|
329
363
|
if (count >= limit) return;
|
|
330
364
|
if (!e.isDirectory()) continue;
|
|
331
|
-
if (IGNORED_WATCH_PATHS.some((re) => re.test(
|
|
365
|
+
if (IGNORED_WATCH_PATHS.some((re) => re.test(path3.join(dir, e.name) + path3.sep))) continue;
|
|
332
366
|
count++;
|
|
333
|
-
if (depth < 2) visit(
|
|
367
|
+
if (depth < 2) visit(path3.join(dir, e.name), depth + 1);
|
|
334
368
|
}
|
|
335
369
|
};
|
|
336
370
|
visit(scanPath, 0);
|
|
@@ -348,8 +382,8 @@ async function startWatch(graph, opts) {
|
|
|
348
382
|
const projectName = opts.project ?? DEFAULT_PROJECT;
|
|
349
383
|
await loadGraphFromDisk(graph, opts.outPath);
|
|
350
384
|
const detachEventBus = attachGraphToEventBus(graph, { project: projectName });
|
|
351
|
-
const policyFilePath =
|
|
352
|
-
const policyViolationsPath =
|
|
385
|
+
const policyFilePath = path3.join(opts.scanPath, "policy.json");
|
|
386
|
+
const policyViolationsPath = path3.join(path3.dirname(opts.outPath), "policy-violations.ndjson");
|
|
353
387
|
let policies = [];
|
|
354
388
|
try {
|
|
355
389
|
policies = await loadPolicyFile(policyFilePath);
|
|
@@ -400,7 +434,7 @@ async function startWatch(graph, opts) {
|
|
|
400
434
|
assertBindAuthority(host, auth.authToken);
|
|
401
435
|
const port = opts.port ?? 8080;
|
|
402
436
|
const otelPort = opts.otelPort ?? 4318;
|
|
403
|
-
const cachePath = opts.embeddingsCachePath ??
|
|
437
|
+
const cachePath = opts.embeddingsCachePath ?? path3.join(path3.dirname(opts.outPath), "embeddings.json");
|
|
404
438
|
let searchIndex;
|
|
405
439
|
try {
|
|
406
440
|
searchIndex = await buildSearchIndex(graph, { cachePath });
|
|
@@ -418,7 +452,7 @@ async function startWatch(graph, opts) {
|
|
|
418
452
|
// Paths are derived from the explicit options the watch caller passes
|
|
419
453
|
// — pathsForProject is only used to fill in the embeddings/snapshot
|
|
420
454
|
// fields so the registry shape is complete.
|
|
421
|
-
...pathsForProject(projectName,
|
|
455
|
+
...pathsForProject(projectName, path3.dirname(opts.outPath)),
|
|
422
456
|
snapshotPath: opts.outPath,
|
|
423
457
|
errorsPath: opts.errorsPath,
|
|
424
458
|
staleEventsPath: opts.staleEventsPath
|
|
@@ -505,9 +539,9 @@ async function startWatch(graph, opts) {
|
|
|
505
539
|
};
|
|
506
540
|
const onPath = (absPath) => {
|
|
507
541
|
if (shouldIgnore(absPath)) return;
|
|
508
|
-
const rel =
|
|
542
|
+
const rel = path3.relative(opts.scanPath, absPath);
|
|
509
543
|
if (!rel || rel.startsWith("..")) return;
|
|
510
|
-
pendingPaths.add(rel.split(
|
|
544
|
+
pendingPaths.add(rel.split(path3.sep).join("/"));
|
|
511
545
|
const phases = classifyChange(rel);
|
|
512
546
|
if (phases.size === 0) {
|
|
513
547
|
for (const p of ALL_PHASES) pending.add(p);
|
|
@@ -561,7 +595,7 @@ async function startWatch(graph, opts) {
|
|
|
561
595
|
|
|
562
596
|
// src/deploy/detect.ts
|
|
563
597
|
import { promises as fs3 } from "fs";
|
|
564
|
-
import
|
|
598
|
+
import path4 from "path";
|
|
565
599
|
import { spawn } from "child_process";
|
|
566
600
|
import { randomBytes } from "crypto";
|
|
567
601
|
function generateToken() {
|
|
@@ -661,7 +695,7 @@ async function runDeploy(opts = {}) {
|
|
|
661
695
|
const token = generateToken();
|
|
662
696
|
switch (substrate) {
|
|
663
697
|
case "docker-compose": {
|
|
664
|
-
const artifactPath =
|
|
698
|
+
const artifactPath = path4.join(cwd, "docker-compose.neat.yml");
|
|
665
699
|
const contents = emitDockerCompose(cwd);
|
|
666
700
|
await fs3.writeFile(artifactPath, contents, "utf8");
|
|
667
701
|
return {
|
|
@@ -669,11 +703,11 @@ async function runDeploy(opts = {}) {
|
|
|
669
703
|
artifactPath,
|
|
670
704
|
token,
|
|
671
705
|
contents,
|
|
672
|
-
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${
|
|
706
|
+
startCommand: `NEAT_AUTH_TOKEN=${token} docker compose -f ${path4.basename(artifactPath)} up -d`
|
|
673
707
|
};
|
|
674
708
|
}
|
|
675
709
|
case "systemd": {
|
|
676
|
-
const artifactPath =
|
|
710
|
+
const artifactPath = path4.join(cwd, "neat.service");
|
|
677
711
|
const contents = emitSystemdUnit(cwd);
|
|
678
712
|
await fs3.writeFile(artifactPath, contents, "utf8");
|
|
679
713
|
return {
|
|
@@ -705,7 +739,7 @@ ${contents}EOF
|
|
|
705
739
|
|
|
706
740
|
// src/installers/javascript.ts
|
|
707
741
|
import { promises as fs4 } from "fs";
|
|
708
|
-
import
|
|
742
|
+
import path5 from "path";
|
|
709
743
|
import semver from "semver";
|
|
710
744
|
|
|
711
745
|
// src/installers/templates.ts
|
|
@@ -1251,11 +1285,11 @@ var OTEL_ENV = {
|
|
|
1251
1285
|
value: "http://localhost:4318/projects/<project>/v1/traces"
|
|
1252
1286
|
};
|
|
1253
1287
|
function serviceNodeName(pkg, serviceDir) {
|
|
1254
|
-
return pkg.name ??
|
|
1288
|
+
return pkg.name ?? path5.basename(serviceDir);
|
|
1255
1289
|
}
|
|
1256
1290
|
function projectToken(pkg, serviceDir, project) {
|
|
1257
1291
|
if (project && project.length > 0) return project;
|
|
1258
|
-
return pkg.name ??
|
|
1292
|
+
return pkg.name ?? path5.basename(serviceDir);
|
|
1259
1293
|
}
|
|
1260
1294
|
async function readJsonFile(p) {
|
|
1261
1295
|
try {
|
|
@@ -1268,16 +1302,16 @@ async function readJsonFile(p) {
|
|
|
1268
1302
|
async function detectRuntimeKind(pkgRoot, pkg) {
|
|
1269
1303
|
const deps = allDeps(pkg);
|
|
1270
1304
|
if ("react-native" in deps || "expo" in deps) return "react-native";
|
|
1271
|
-
const appJson = await readJsonFile(
|
|
1305
|
+
const appJson = await readJsonFile(path5.join(pkgRoot, "app.json"));
|
|
1272
1306
|
if (appJson && typeof appJson === "object" && "expo" in appJson) {
|
|
1273
1307
|
return "react-native";
|
|
1274
1308
|
}
|
|
1275
|
-
if (await exists(
|
|
1309
|
+
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
1310
|
return "browser-bundle";
|
|
1277
1311
|
}
|
|
1278
|
-
if (await exists(
|
|
1279
|
-
if (await exists(
|
|
1280
|
-
if (await exists(
|
|
1312
|
+
if (await exists(path5.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
|
|
1313
|
+
if (await exists(path5.join(pkgRoot, "bun.lockb"))) return "bun";
|
|
1314
|
+
if (await exists(path5.join(pkgRoot, "deno.json")) || await exists(path5.join(pkgRoot, "deno.lock"))) {
|
|
1281
1315
|
return "deno";
|
|
1282
1316
|
}
|
|
1283
1317
|
const engines = pkg.engines ?? {};
|
|
@@ -1286,7 +1320,7 @@ async function detectRuntimeKind(pkgRoot, pkg) {
|
|
|
1286
1320
|
}
|
|
1287
1321
|
async function readPackageJson(serviceDir) {
|
|
1288
1322
|
try {
|
|
1289
|
-
const raw = await fs4.readFile(
|
|
1323
|
+
const raw = await fs4.readFile(path5.join(serviceDir, "package.json"), "utf8");
|
|
1290
1324
|
return JSON.parse(raw);
|
|
1291
1325
|
} catch {
|
|
1292
1326
|
return null;
|
|
@@ -1330,7 +1364,7 @@ function needsVersionUpgrade(installed, expected) {
|
|
|
1330
1364
|
var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
|
|
1331
1365
|
async function findNextConfig(serviceDir) {
|
|
1332
1366
|
for (const name of NEXT_CONFIG_CANDIDATES) {
|
|
1333
|
-
const candidate =
|
|
1367
|
+
const candidate = path5.join(serviceDir, name);
|
|
1334
1368
|
if (await exists(candidate)) return candidate;
|
|
1335
1369
|
}
|
|
1336
1370
|
return null;
|
|
@@ -1357,7 +1391,7 @@ function hasRemixDependency(pkg) {
|
|
|
1357
1391
|
}
|
|
1358
1392
|
async function findRemixEntry(serviceDir) {
|
|
1359
1393
|
for (const rel of REMIX_ENTRY_CANDIDATES) {
|
|
1360
|
-
const candidate =
|
|
1394
|
+
const candidate = path5.join(serviceDir, rel);
|
|
1361
1395
|
if (await exists(candidate)) return candidate;
|
|
1362
1396
|
}
|
|
1363
1397
|
return null;
|
|
@@ -1369,14 +1403,14 @@ function hasSvelteKitDependency(pkg) {
|
|
|
1369
1403
|
}
|
|
1370
1404
|
async function findSvelteKitHooks(serviceDir) {
|
|
1371
1405
|
for (const rel of SVELTEKIT_HOOKS_CANDIDATES) {
|
|
1372
|
-
const candidate =
|
|
1406
|
+
const candidate = path5.join(serviceDir, rel);
|
|
1373
1407
|
if (await exists(candidate)) return candidate;
|
|
1374
1408
|
}
|
|
1375
1409
|
return null;
|
|
1376
1410
|
}
|
|
1377
1411
|
async function findSvelteKitConfig(serviceDir) {
|
|
1378
1412
|
for (const rel of SVELTEKIT_CONFIG_CANDIDATES) {
|
|
1379
|
-
const candidate =
|
|
1413
|
+
const candidate = path5.join(serviceDir, rel);
|
|
1380
1414
|
if (await exists(candidate)) return candidate;
|
|
1381
1415
|
}
|
|
1382
1416
|
return null;
|
|
@@ -1387,7 +1421,7 @@ function hasNuxtDependency(pkg) {
|
|
|
1387
1421
|
}
|
|
1388
1422
|
async function findNuxtConfig(serviceDir) {
|
|
1389
1423
|
for (const name of NUXT_CONFIG_CANDIDATES) {
|
|
1390
|
-
const candidate =
|
|
1424
|
+
const candidate = path5.join(serviceDir, name);
|
|
1391
1425
|
if (await exists(candidate)) return candidate;
|
|
1392
1426
|
}
|
|
1393
1427
|
return null;
|
|
@@ -1398,7 +1432,7 @@ function hasAstroDependency(pkg) {
|
|
|
1398
1432
|
}
|
|
1399
1433
|
async function findAstroConfig(serviceDir) {
|
|
1400
1434
|
for (const name of ASTRO_CONFIG_CANDIDATES) {
|
|
1401
|
-
const candidate =
|
|
1435
|
+
const candidate = path5.join(serviceDir, name);
|
|
1402
1436
|
if (await exists(candidate)) return candidate;
|
|
1403
1437
|
}
|
|
1404
1438
|
return null;
|
|
@@ -1412,7 +1446,7 @@ function parseNextMajor(range) {
|
|
|
1412
1446
|
return Number.isFinite(n) ? n : null;
|
|
1413
1447
|
}
|
|
1414
1448
|
async function isTypeScriptProject(serviceDir) {
|
|
1415
|
-
return exists(
|
|
1449
|
+
return exists(path5.join(serviceDir, "tsconfig.json"));
|
|
1416
1450
|
}
|
|
1417
1451
|
var INDEX_EXTENSIONS = [".ts", ".tsx", ".js", ".mjs", ".cjs"];
|
|
1418
1452
|
var INDEX_CANDIDATES = INDEX_EXTENSIONS.map((ext) => `index${ext}`);
|
|
@@ -1458,7 +1492,7 @@ function entryFromScript(script) {
|
|
|
1458
1492
|
}
|
|
1459
1493
|
async function resolveEntry(serviceDir, pkg) {
|
|
1460
1494
|
if (typeof pkg.main === "string" && pkg.main.length > 0) {
|
|
1461
|
-
const candidate =
|
|
1495
|
+
const candidate = path5.resolve(serviceDir, pkg.main);
|
|
1462
1496
|
if (await exists(candidate)) return candidate;
|
|
1463
1497
|
}
|
|
1464
1498
|
if (pkg.bin) {
|
|
@@ -1472,36 +1506,36 @@ async function resolveEntry(serviceDir, pkg) {
|
|
|
1472
1506
|
if (typeof first === "string") binEntry = first;
|
|
1473
1507
|
}
|
|
1474
1508
|
if (binEntry) {
|
|
1475
|
-
const candidate =
|
|
1509
|
+
const candidate = path5.resolve(serviceDir, binEntry);
|
|
1476
1510
|
if (await exists(candidate)) return candidate;
|
|
1477
1511
|
}
|
|
1478
1512
|
}
|
|
1479
1513
|
const startEntry = entryFromScript(pkg.scripts?.start);
|
|
1480
1514
|
if (startEntry) {
|
|
1481
|
-
const candidate =
|
|
1515
|
+
const candidate = path5.resolve(serviceDir, startEntry);
|
|
1482
1516
|
if (await exists(candidate)) return candidate;
|
|
1483
1517
|
}
|
|
1484
1518
|
const devEntry = entryFromScript(pkg.scripts?.dev);
|
|
1485
1519
|
if (devEntry) {
|
|
1486
|
-
const candidate =
|
|
1520
|
+
const candidate = path5.resolve(serviceDir, devEntry);
|
|
1487
1521
|
if (await exists(candidate)) return candidate;
|
|
1488
1522
|
}
|
|
1489
1523
|
for (const rel of SRC_INDEX_CANDIDATES) {
|
|
1490
|
-
const candidate =
|
|
1524
|
+
const candidate = path5.join(serviceDir, rel);
|
|
1491
1525
|
if (await exists(candidate)) return candidate;
|
|
1492
1526
|
}
|
|
1493
1527
|
for (const rel of SRC_NAMED_CANDIDATES) {
|
|
1494
|
-
const candidate =
|
|
1528
|
+
const candidate = path5.join(serviceDir, rel);
|
|
1495
1529
|
if (await exists(candidate)) return candidate;
|
|
1496
1530
|
}
|
|
1497
1531
|
for (const name of INDEX_CANDIDATES) {
|
|
1498
|
-
const candidate =
|
|
1532
|
+
const candidate = path5.join(serviceDir, name);
|
|
1499
1533
|
if (await exists(candidate)) return candidate;
|
|
1500
1534
|
}
|
|
1501
1535
|
return null;
|
|
1502
1536
|
}
|
|
1503
1537
|
function dispatchEntry(entryFile, pkg) {
|
|
1504
|
-
const ext =
|
|
1538
|
+
const ext = path5.extname(entryFile).toLowerCase();
|
|
1505
1539
|
if (ext === ".ts" || ext === ".tsx") return "ts";
|
|
1506
1540
|
if (ext === ".mjs") return "esm";
|
|
1507
1541
|
if (ext === ".cjs") return "cjs";
|
|
@@ -1518,9 +1552,9 @@ function otelInitContents(flavor) {
|
|
|
1518
1552
|
return OTEL_INIT_CJS;
|
|
1519
1553
|
}
|
|
1520
1554
|
function injectionLine(flavor, entryFile, otelInitFile) {
|
|
1521
|
-
let rel =
|
|
1555
|
+
let rel = path5.relative(path5.dirname(entryFile), otelInitFile);
|
|
1522
1556
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
1523
|
-
rel = rel.split(
|
|
1557
|
+
rel = rel.split(path5.sep).join("/");
|
|
1524
1558
|
if (flavor === "cjs") return `require('${rel}')`;
|
|
1525
1559
|
if (flavor === "esm") return `import '${rel}'`;
|
|
1526
1560
|
const tsRel = rel.replace(/\.ts$/, "");
|
|
@@ -1533,23 +1567,23 @@ function lineIsOtelInjection(line) {
|
|
|
1533
1567
|
}
|
|
1534
1568
|
async function detectsSrcLayout(serviceDir) {
|
|
1535
1569
|
const [hasSrcApp, hasSrcPages, hasRootApp, hasRootPages] = await Promise.all([
|
|
1536
|
-
exists(
|
|
1537
|
-
exists(
|
|
1538
|
-
exists(
|
|
1539
|
-
exists(
|
|
1570
|
+
exists(path5.join(serviceDir, "src", "app")),
|
|
1571
|
+
exists(path5.join(serviceDir, "src", "pages")),
|
|
1572
|
+
exists(path5.join(serviceDir, "app")),
|
|
1573
|
+
exists(path5.join(serviceDir, "pages"))
|
|
1540
1574
|
]);
|
|
1541
1575
|
return (hasSrcApp || hasSrcPages) && !hasRootApp && !hasRootPages;
|
|
1542
1576
|
}
|
|
1543
1577
|
async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project) {
|
|
1544
1578
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
1545
1579
|
const srcLayout = await detectsSrcLayout(serviceDir);
|
|
1546
|
-
const baseDir = srcLayout ?
|
|
1547
|
-
const instrumentationFile =
|
|
1548
|
-
const instrumentationNodeFile =
|
|
1580
|
+
const baseDir = srcLayout ? path5.join(serviceDir, "src") : serviceDir;
|
|
1581
|
+
const instrumentationFile = path5.join(baseDir, useTs ? "instrumentation.ts" : "instrumentation.js");
|
|
1582
|
+
const instrumentationNodeFile = path5.join(
|
|
1549
1583
|
baseDir,
|
|
1550
1584
|
useTs ? "instrumentation.node.ts" : "instrumentation.node.js"
|
|
1551
1585
|
);
|
|
1552
|
-
const envNeatFile =
|
|
1586
|
+
const envNeatFile = path5.join(baseDir, ".env.neat");
|
|
1553
1587
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
1554
1588
|
const dependencyEdits = [];
|
|
1555
1589
|
for (const sdk of SDK_PACKAGES) {
|
|
@@ -1654,7 +1688,7 @@ function buildDependencyEdits(pkg, manifestPath) {
|
|
|
1654
1688
|
return edits;
|
|
1655
1689
|
}
|
|
1656
1690
|
async function queueEnvNeat(serviceDir, pkg, project, generatedFiles) {
|
|
1657
|
-
const envNeatFile =
|
|
1691
|
+
const envNeatFile = path5.join(serviceDir, ".env.neat");
|
|
1658
1692
|
if (!await exists(envNeatFile)) {
|
|
1659
1693
|
generatedFiles.push({
|
|
1660
1694
|
file: envNeatFile,
|
|
@@ -1689,7 +1723,7 @@ function fileImportsOtelHook(raw, specifiers) {
|
|
|
1689
1723
|
}
|
|
1690
1724
|
async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
1691
1725
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
1692
|
-
const otelServerFile =
|
|
1726
|
+
const otelServerFile = path5.join(
|
|
1693
1727
|
serviceDir,
|
|
1694
1728
|
useTs ? "app/otel.server.ts" : "app/otel.server.js"
|
|
1695
1729
|
);
|
|
@@ -1746,11 +1780,11 @@ async function planRemix(serviceDir, pkg, manifestPath, entryFile, project) {
|
|
|
1746
1780
|
}
|
|
1747
1781
|
async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project) {
|
|
1748
1782
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
1749
|
-
const otelInitFile =
|
|
1783
|
+
const otelInitFile = path5.join(
|
|
1750
1784
|
serviceDir,
|
|
1751
1785
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
1752
1786
|
);
|
|
1753
|
-
const resolvedHooksFile = hooksFile ??
|
|
1787
|
+
const resolvedHooksFile = hooksFile ?? path5.join(serviceDir, useTs ? "src/hooks.server.ts" : "src/hooks.server.js");
|
|
1754
1788
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
1755
1789
|
const generatedFiles = [];
|
|
1756
1790
|
const entrypointEdits = [];
|
|
@@ -1812,11 +1846,11 @@ async function planSvelteKit(serviceDir, pkg, manifestPath, hooksFile, project)
|
|
|
1812
1846
|
}
|
|
1813
1847
|
async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
1814
1848
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
1815
|
-
const otelPluginFile =
|
|
1849
|
+
const otelPluginFile = path5.join(
|
|
1816
1850
|
serviceDir,
|
|
1817
1851
|
useTs ? "server/plugins/otel.ts" : "server/plugins/otel.js"
|
|
1818
1852
|
);
|
|
1819
|
-
const otelInitFile =
|
|
1853
|
+
const otelInitFile = path5.join(
|
|
1820
1854
|
serviceDir,
|
|
1821
1855
|
useTs ? "server/plugins/otel-init.ts" : "server/plugins/otel-init.js"
|
|
1822
1856
|
);
|
|
@@ -1867,19 +1901,19 @@ async function planNuxt(serviceDir, pkg, manifestPath, project) {
|
|
|
1867
1901
|
var ASTRO_MIDDLEWARE_CANDIDATES = ["src/middleware.ts", "src/middleware.js"];
|
|
1868
1902
|
async function findAstroMiddleware(serviceDir) {
|
|
1869
1903
|
for (const rel of ASTRO_MIDDLEWARE_CANDIDATES) {
|
|
1870
|
-
const candidate =
|
|
1904
|
+
const candidate = path5.join(serviceDir, rel);
|
|
1871
1905
|
if (await exists(candidate)) return candidate;
|
|
1872
1906
|
}
|
|
1873
1907
|
return null;
|
|
1874
1908
|
}
|
|
1875
1909
|
async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
1876
1910
|
const useTs = await isTypeScriptProject(serviceDir);
|
|
1877
|
-
const otelInitFile =
|
|
1911
|
+
const otelInitFile = path5.join(
|
|
1878
1912
|
serviceDir,
|
|
1879
1913
|
useTs ? "src/otel-init.ts" : "src/otel-init.js"
|
|
1880
1914
|
);
|
|
1881
1915
|
const existingMiddleware = await findAstroMiddleware(serviceDir);
|
|
1882
|
-
const middlewareFile = existingMiddleware ??
|
|
1916
|
+
const middlewareFile = existingMiddleware ?? path5.join(serviceDir, useTs ? "src/middleware.ts" : "src/middleware.js");
|
|
1883
1917
|
const dependencyEdits = buildDependencyEdits(pkg, manifestPath);
|
|
1884
1918
|
const generatedFiles = [];
|
|
1885
1919
|
const entrypointEdits = [];
|
|
@@ -1975,7 +2009,7 @@ async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
|
|
|
1975
2009
|
}
|
|
1976
2010
|
async function plan(serviceDir, opts) {
|
|
1977
2011
|
const pkg = await readPackageJson(serviceDir);
|
|
1978
|
-
const manifestPath =
|
|
2012
|
+
const manifestPath = path5.join(serviceDir, "package.json");
|
|
1979
2013
|
const project = opts?.project;
|
|
1980
2014
|
const empty = {
|
|
1981
2015
|
language: "javascript",
|
|
@@ -2010,8 +2044,8 @@ async function plan(serviceDir, opts) {
|
|
|
2010
2044
|
return { ...empty, libOnly: true };
|
|
2011
2045
|
}
|
|
2012
2046
|
const flavor = dispatchEntry(entryFile, pkg);
|
|
2013
|
-
const otelInitFile =
|
|
2014
|
-
const envNeatFile =
|
|
2047
|
+
const otelInitFile = path5.join(path5.dirname(entryFile), otelInitFilename(flavor));
|
|
2048
|
+
const envNeatFile = path5.join(serviceDir, ".env.neat");
|
|
2015
2049
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
2016
2050
|
const dependencyEdits = [];
|
|
2017
2051
|
for (const sdk of SDK_PACKAGES) {
|
|
@@ -2080,13 +2114,13 @@ async function plan(serviceDir, opts) {
|
|
|
2080
2114
|
};
|
|
2081
2115
|
}
|
|
2082
2116
|
function isAllowedWritePath(serviceDir, target) {
|
|
2083
|
-
const rel =
|
|
2117
|
+
const rel = path5.relative(serviceDir, target);
|
|
2084
2118
|
if (rel.startsWith("..")) return false;
|
|
2085
|
-
const base =
|
|
2119
|
+
const base = path5.basename(target);
|
|
2086
2120
|
if (base === "package.json") return true;
|
|
2087
2121
|
if (base === ".env.neat") return true;
|
|
2088
2122
|
if (/^otel-init\.(?:js|cjs|mjs|ts)$/.test(base)) return true;
|
|
2089
|
-
const relPosix = rel.split(
|
|
2123
|
+
const relPosix = rel.split(path5.sep).join("/");
|
|
2090
2124
|
if (/^instrumentation(?:\.node)?\.(?:js|cjs|mjs|ts)$/.test(base)) {
|
|
2091
2125
|
if (relPosix === base) return true;
|
|
2092
2126
|
if (relPosix === `src/${base}`) return true;
|
|
@@ -2103,7 +2137,7 @@ function isAllowedWritePath(serviceDir, target) {
|
|
|
2103
2137
|
return false;
|
|
2104
2138
|
}
|
|
2105
2139
|
async function writeAtomic(file, contents) {
|
|
2106
|
-
await fs4.mkdir(
|
|
2140
|
+
await fs4.mkdir(path5.dirname(file), { recursive: true });
|
|
2107
2141
|
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
2108
2142
|
await fs4.writeFile(tmp, contents, "utf8");
|
|
2109
2143
|
await fs4.rename(tmp, file);
|
|
@@ -2287,7 +2321,7 @@ async function rollback(installPlan, originals, createdFiles) {
|
|
|
2287
2321
|
...removed.map((f) => `removed: ${f}`),
|
|
2288
2322
|
""
|
|
2289
2323
|
];
|
|
2290
|
-
const rollbackPath =
|
|
2324
|
+
const rollbackPath = path5.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
2291
2325
|
await fs4.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
2292
2326
|
}
|
|
2293
2327
|
function injectInstrumentationHook(raw) {
|
|
@@ -2317,7 +2351,7 @@ var javascriptInstaller = {
|
|
|
2317
2351
|
|
|
2318
2352
|
// src/installers/python.ts
|
|
2319
2353
|
import { promises as fs5 } from "fs";
|
|
2320
|
-
import
|
|
2354
|
+
import path6 from "path";
|
|
2321
2355
|
var SDK_PACKAGES2 = [
|
|
2322
2356
|
{ name: "opentelemetry-distro", version: ">=0.49b0" },
|
|
2323
2357
|
{ name: "opentelemetry-exporter-otlp", version: ">=1.28.0" }
|
|
@@ -2338,7 +2372,7 @@ async function exists2(p) {
|
|
|
2338
2372
|
async function detect2(serviceDir) {
|
|
2339
2373
|
const markers = ["requirements.txt", "pyproject.toml", "setup.py"];
|
|
2340
2374
|
for (const m of markers) {
|
|
2341
|
-
if (await exists2(
|
|
2375
|
+
if (await exists2(path6.join(serviceDir, m))) return true;
|
|
2342
2376
|
}
|
|
2343
2377
|
return false;
|
|
2344
2378
|
}
|
|
@@ -2348,7 +2382,7 @@ function reqPackageName(line) {
|
|
|
2348
2382
|
return head.replace(/[<>=!~].*$/, "").toLowerCase();
|
|
2349
2383
|
}
|
|
2350
2384
|
async function planRequirementsTxtEdits(serviceDir) {
|
|
2351
|
-
const file =
|
|
2385
|
+
const file = path6.join(serviceDir, "requirements.txt");
|
|
2352
2386
|
if (!await exists2(file)) return null;
|
|
2353
2387
|
const raw = await fs5.readFile(file, "utf8");
|
|
2354
2388
|
const presentNames = new Set(
|
|
@@ -2358,7 +2392,7 @@ async function planRequirementsTxtEdits(serviceDir) {
|
|
|
2358
2392
|
return { manifest: file, missing: [...missing] };
|
|
2359
2393
|
}
|
|
2360
2394
|
async function planProcfileEdits(serviceDir) {
|
|
2361
|
-
const procfile =
|
|
2395
|
+
const procfile = path6.join(serviceDir, "Procfile");
|
|
2362
2396
|
if (!await exists2(procfile)) return [];
|
|
2363
2397
|
const raw = await fs5.readFile(procfile, "utf8");
|
|
2364
2398
|
const edits = [];
|
|
@@ -2447,7 +2481,7 @@ async function apply2(installPlan) {
|
|
|
2447
2481
|
if (raw === void 0) {
|
|
2448
2482
|
throw new Error(`python installer: cannot read ${file} during apply`);
|
|
2449
2483
|
}
|
|
2450
|
-
const base =
|
|
2484
|
+
const base = path6.basename(file);
|
|
2451
2485
|
if (base === "requirements.txt") {
|
|
2452
2486
|
const edits = installPlan.dependencyEdits.filter((e) => e.file === file);
|
|
2453
2487
|
if (edits.length > 0) {
|
|
@@ -2486,7 +2520,7 @@ async function rollback2(installPlan, originals) {
|
|
|
2486
2520
|
...restored.map((f) => `restored: ${f}`),
|
|
2487
2521
|
""
|
|
2488
2522
|
];
|
|
2489
|
-
const rollbackPath =
|
|
2523
|
+
const rollbackPath = path6.join(installPlan.serviceDir, "neat-rollback.patch");
|
|
2490
2524
|
await fs5.writeFile(rollbackPath, lines.join("\n"), "utf8");
|
|
2491
2525
|
}
|
|
2492
2526
|
var pythonInstaller = {
|
|
@@ -2611,7 +2645,7 @@ function renderPatch(sections) {
|
|
|
2611
2645
|
import { promises as fs6 } from "fs";
|
|
2612
2646
|
import http from "http";
|
|
2613
2647
|
import net from "net";
|
|
2614
|
-
import
|
|
2648
|
+
import path7 from "path";
|
|
2615
2649
|
import { spawn as spawn2 } from "child_process";
|
|
2616
2650
|
import readline from "readline";
|
|
2617
2651
|
async function extractAndPersist(opts) {
|
|
@@ -2620,7 +2654,7 @@ async function extractAndPersist(opts) {
|
|
|
2620
2654
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
2621
2655
|
resetGraph(graphKey);
|
|
2622
2656
|
const graph = getGraph(graphKey);
|
|
2623
|
-
const projectPaths = pathsForProject(graphKey,
|
|
2657
|
+
const projectPaths = pathsForProject(graphKey, path7.join(opts.scanPath, "neat-out"));
|
|
2624
2658
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
2625
2659
|
errorsPath: projectPaths.errorsPath
|
|
2626
2660
|
});
|
|
@@ -2674,7 +2708,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2674
2708
|
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
2675
2709
|
} else if (outcome.outcome === "react-native") {
|
|
2676
2710
|
reactNative++;
|
|
2677
|
-
const svcName =
|
|
2711
|
+
const svcName = path7.basename(svc.dir);
|
|
2678
2712
|
console.log(
|
|
2679
2713
|
`neat: ${svc.dir} detected as React Native / Expo
|
|
2680
2714
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -2685,7 +2719,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2685
2719
|
);
|
|
2686
2720
|
} else if (outcome.outcome === "bun") {
|
|
2687
2721
|
bun++;
|
|
2688
|
-
const svcName =
|
|
2722
|
+
const svcName = path7.basename(svc.dir);
|
|
2689
2723
|
console.log(
|
|
2690
2724
|
`neat: ${svc.dir} detected as Bun
|
|
2691
2725
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -2696,7 +2730,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2696
2730
|
);
|
|
2697
2731
|
} else if (outcome.outcome === "deno") {
|
|
2698
2732
|
deno++;
|
|
2699
|
-
const svcName =
|
|
2733
|
+
const svcName = path7.basename(svc.dir);
|
|
2700
2734
|
console.log(
|
|
2701
2735
|
`neat: ${svc.dir} detected as Deno
|
|
2702
2736
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -2707,7 +2741,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2707
2741
|
);
|
|
2708
2742
|
} else if (outcome.outcome === "cloudflare-workers") {
|
|
2709
2743
|
cloudflareWorkers++;
|
|
2710
|
-
const svcName =
|
|
2744
|
+
const svcName = path7.basename(svc.dir);
|
|
2711
2745
|
console.log(
|
|
2712
2746
|
`neat: ${svc.dir} detected as Cloudflare Workers
|
|
2713
2747
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -2718,7 +2752,7 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2718
2752
|
);
|
|
2719
2753
|
} else if (outcome.outcome === "electron") {
|
|
2720
2754
|
electron++;
|
|
2721
|
-
const svcName =
|
|
2755
|
+
const svcName = path7.basename(svc.dir);
|
|
2722
2756
|
console.log(
|
|
2723
2757
|
`neat: ${svc.dir} detected as Electron
|
|
2724
2758
|
The installer doesn't cover this runtime deterministically.
|
|
@@ -2892,10 +2926,10 @@ function formatPortCollisionMessage(port) {
|
|
|
2892
2926
|
];
|
|
2893
2927
|
}
|
|
2894
2928
|
function spawnDaemonDetached() {
|
|
2895
|
-
const here =
|
|
2929
|
+
const here = path7.dirname(new URL(import.meta.url).pathname);
|
|
2896
2930
|
const candidates = [
|
|
2897
|
-
|
|
2898
|
-
|
|
2931
|
+
path7.join(here, "neatd.cjs"),
|
|
2932
|
+
path7.join(here, "neatd.js")
|
|
2899
2933
|
];
|
|
2900
2934
|
let entry2 = null;
|
|
2901
2935
|
const fsSync = __require("fs");
|
|
@@ -2959,6 +2993,7 @@ async function runOrchestrator(opts) {
|
|
|
2959
2993
|
result.exitCode = 2;
|
|
2960
2994
|
return result;
|
|
2961
2995
|
}
|
|
2996
|
+
printBanner();
|
|
2962
2997
|
console.log(`neat: ${opts.scanPath}`);
|
|
2963
2998
|
console.log("");
|
|
2964
2999
|
const persisted = await extractAndPersist({
|
|
@@ -2969,6 +3004,13 @@ async function runOrchestrator(opts) {
|
|
|
2969
3004
|
const { graph, services, languages } = persisted;
|
|
2970
3005
|
result.steps.discovery = { services: services.length, languages };
|
|
2971
3006
|
console.log(`discovered ${services.length} service(s) across ${languages.length} language(s)`);
|
|
3007
|
+
if (services.length === 0) {
|
|
3008
|
+
console.error(
|
|
3009
|
+
`neat: no services found in ${opts.scanPath} \u2014 run from inside your project root, or \`npx neat.is <path>\``
|
|
3010
|
+
);
|
|
3011
|
+
result.exitCode = 2;
|
|
3012
|
+
return result;
|
|
3013
|
+
}
|
|
2972
3014
|
let runApply = !opts.noInstrument;
|
|
2973
3015
|
if (runApply && !opts.yes && process.stdout.isTTY && process.stdin.isTTY) {
|
|
2974
3016
|
runApply = await promptYesNo("instrument your services and open the dashboard?");
|
|
@@ -3092,10 +3134,16 @@ function printSummary(result, graph, dashboardUrl) {
|
|
|
3092
3134
|
for (const [t, c] of [...byEdge.entries()].sort()) console.log(` ${t}: ${c}`);
|
|
3093
3135
|
console.log("");
|
|
3094
3136
|
console.log(`dashboard: ${dashboardUrl}`);
|
|
3137
|
+
const token = process.env.NEAT_AUTH_TOKEN;
|
|
3138
|
+
if (typeof token === "string" && token.length > 0) {
|
|
3139
|
+
console.log(`auth token: ${token}`);
|
|
3140
|
+
} else {
|
|
3141
|
+
console.log("running locally \u2014 open the dashboard, no token needed");
|
|
3142
|
+
}
|
|
3095
3143
|
}
|
|
3096
3144
|
|
|
3097
3145
|
// src/cli-verbs.ts
|
|
3098
|
-
import
|
|
3146
|
+
import path8 from "path";
|
|
3099
3147
|
|
|
3100
3148
|
// src/cli-client.ts
|
|
3101
3149
|
import { Provenance as Provenance2 } from "@neat.is/types";
|
|
@@ -3123,10 +3171,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3123
3171
|
const root = baseUrl.replace(/\/$/, "");
|
|
3124
3172
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
3125
3173
|
return {
|
|
3126
|
-
async get(
|
|
3174
|
+
async get(path10) {
|
|
3127
3175
|
let res;
|
|
3128
3176
|
try {
|
|
3129
|
-
res = await fetch(`${root}${
|
|
3177
|
+
res = await fetch(`${root}${path10}`, {
|
|
3130
3178
|
headers: { ...authHeader }
|
|
3131
3179
|
});
|
|
3132
3180
|
} catch (err) {
|
|
@@ -3138,16 +3186,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3138
3186
|
const body = await res.text().catch(() => "");
|
|
3139
3187
|
throw new HttpError(
|
|
3140
3188
|
res.status,
|
|
3141
|
-
`${res.status} ${res.statusText} on GET ${
|
|
3189
|
+
`${res.status} ${res.statusText} on GET ${path10}: ${body}`,
|
|
3142
3190
|
body
|
|
3143
3191
|
);
|
|
3144
3192
|
}
|
|
3145
3193
|
return await res.json();
|
|
3146
3194
|
},
|
|
3147
|
-
async post(
|
|
3195
|
+
async post(path10, body) {
|
|
3148
3196
|
let res;
|
|
3149
3197
|
try {
|
|
3150
|
-
res = await fetch(`${root}${
|
|
3198
|
+
res = await fetch(`${root}${path10}`, {
|
|
3151
3199
|
method: "POST",
|
|
3152
3200
|
headers: { "content-type": "application/json", ...authHeader },
|
|
3153
3201
|
body: JSON.stringify(body)
|
|
@@ -3161,7 +3209,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3161
3209
|
const text = await res.text().catch(() => "");
|
|
3162
3210
|
throw new HttpError(
|
|
3163
3211
|
res.status,
|
|
3164
|
-
`${res.status} ${res.statusText} on POST ${
|
|
3212
|
+
`${res.status} ${res.statusText} on POST ${path10}: ${text}`,
|
|
3165
3213
|
text
|
|
3166
3214
|
);
|
|
3167
3215
|
}
|
|
@@ -3175,12 +3223,12 @@ function projectPath(project, suffix) {
|
|
|
3175
3223
|
}
|
|
3176
3224
|
async function runRootCause(client, input) {
|
|
3177
3225
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
3178
|
-
const
|
|
3226
|
+
const path10 = projectPath(
|
|
3179
3227
|
input.project,
|
|
3180
3228
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
3181
3229
|
);
|
|
3182
3230
|
try {
|
|
3183
|
-
const result = await client.get(
|
|
3231
|
+
const result = await client.get(path10);
|
|
3184
3232
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
3185
3233
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
3186
3234
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -3206,12 +3254,12 @@ async function runRootCause(client, input) {
|
|
|
3206
3254
|
}
|
|
3207
3255
|
async function runBlastRadius(client, input) {
|
|
3208
3256
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
3209
|
-
const
|
|
3257
|
+
const path10 = projectPath(
|
|
3210
3258
|
input.project,
|
|
3211
3259
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
3212
3260
|
);
|
|
3213
3261
|
try {
|
|
3214
|
-
const result = await client.get(
|
|
3262
|
+
const result = await client.get(path10);
|
|
3215
3263
|
if (result.totalAffected === 0) {
|
|
3216
3264
|
return {
|
|
3217
3265
|
summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
|
|
@@ -3245,12 +3293,12 @@ function formatBlastEntry(n) {
|
|
|
3245
3293
|
}
|
|
3246
3294
|
async function runDependencies(client, input) {
|
|
3247
3295
|
const depth = input.depth ?? 3;
|
|
3248
|
-
const
|
|
3296
|
+
const path10 = projectPath(
|
|
3249
3297
|
input.project,
|
|
3250
3298
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
3251
3299
|
);
|
|
3252
3300
|
try {
|
|
3253
|
-
const result = await client.get(
|
|
3301
|
+
const result = await client.get(path10);
|
|
3254
3302
|
if (result.total === 0) {
|
|
3255
3303
|
return {
|
|
3256
3304
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -3331,9 +3379,9 @@ function formatDuration(ms) {
|
|
|
3331
3379
|
return `${Math.round(h / 24)}d`;
|
|
3332
3380
|
}
|
|
3333
3381
|
async function runIncidents(client, input) {
|
|
3334
|
-
const
|
|
3382
|
+
const path10 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
3335
3383
|
try {
|
|
3336
|
-
const body = await client.get(
|
|
3384
|
+
const body = await client.get(path10);
|
|
3337
3385
|
const events = body.events;
|
|
3338
3386
|
if (events.length === 0) {
|
|
3339
3387
|
return {
|
|
@@ -3623,7 +3671,7 @@ async function resolveProjectEntry(opts) {
|
|
|
3623
3671
|
const cwd = opts.cwd ?? process.cwd();
|
|
3624
3672
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
3625
3673
|
for (const entry2 of entries) {
|
|
3626
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
3674
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path8.sep}`)) {
|
|
3627
3675
|
return entry2;
|
|
3628
3676
|
}
|
|
3629
3677
|
}
|
|
@@ -3774,8 +3822,24 @@ async function runSync(opts) {
|
|
|
3774
3822
|
|
|
3775
3823
|
// src/cli.ts
|
|
3776
3824
|
import { DivergenceTypeSchema } from "@neat.is/types";
|
|
3825
|
+
function isNpxInvocation() {
|
|
3826
|
+
if (process.env.npm_command === "exec") return true;
|
|
3827
|
+
const execpath = process.env.npm_execpath ?? "";
|
|
3828
|
+
if (execpath.includes("npx")) return true;
|
|
3829
|
+
const entry2 = process.argv[1] ?? "";
|
|
3830
|
+
if (entry2.includes("/_npx/") || entry2.includes("\\_npx\\")) return true;
|
|
3831
|
+
return false;
|
|
3832
|
+
}
|
|
3833
|
+
function commandPrefix() {
|
|
3834
|
+
return isNpxInvocation() ? "npx neat.is" : "neat";
|
|
3835
|
+
}
|
|
3777
3836
|
function usage() {
|
|
3778
|
-
|
|
3837
|
+
const neat = commandPrefix();
|
|
3838
|
+
console.log("Installed via npx? Prefix commands with `npx neat.is`, or install once: `npm i -g neat.is`.");
|
|
3839
|
+
console.log("");
|
|
3840
|
+
console.log(`usage: ${neat} <command> [args] [--project <name>]`);
|
|
3841
|
+
console.log("");
|
|
3842
|
+
console.log(`Run \`${neat}\` with no command from inside your project to go zero-to-graph in one step.`);
|
|
3779
3843
|
console.log("");
|
|
3780
3844
|
console.log("lifecycle commands:");
|
|
3781
3845
|
console.log(" init <path> One-time install: discover, extract, register, plan SDK install.");
|
|
@@ -3815,30 +3879,30 @@ function usage() {
|
|
|
3815
3879
|
console.log("");
|
|
3816
3880
|
console.log("query commands (mirror the MCP tools, ADR-050):");
|
|
3817
3881
|
console.log(" root-cause <node-id> Walk inbound edges to find what broke first.");
|
|
3818
|
-
console.log(
|
|
3882
|
+
console.log(` example: ${neat} root-cause service:<name>`);
|
|
3819
3883
|
console.log(" blast-radius <node-id> BFS outbound \u2014 what would break if this dies.");
|
|
3820
|
-
console.log(
|
|
3884
|
+
console.log(` example: ${neat} blast-radius database:<host>`);
|
|
3821
3885
|
console.log(" dependencies <node-id> Transitive outbound dependencies.");
|
|
3822
3886
|
console.log(" Flags: --depth N (default 3, max 10)");
|
|
3823
|
-
console.log(
|
|
3887
|
+
console.log(` example: ${neat} dependencies service:<name> --depth 2`);
|
|
3824
3888
|
console.log(" observed-dependencies <node-id> OBSERVED-only outbound edges (runtime traffic).");
|
|
3825
|
-
console.log(
|
|
3889
|
+
console.log(` example: ${neat} observed-dependencies service:<name>`);
|
|
3826
3890
|
console.log(" incidents [<node-id>] Recent error events; per-node when an id is given.");
|
|
3827
3891
|
console.log(" Flags: --limit N (default 20)");
|
|
3828
|
-
console.log(
|
|
3892
|
+
console.log(` example: ${neat} incidents service:<name> --limit 5`);
|
|
3829
3893
|
console.log(" search <query> Semantic (or substring) match on node names/ids.");
|
|
3830
|
-
console.log(
|
|
3894
|
+
console.log(` example: ${neat} search "checkout"`);
|
|
3831
3895
|
console.log(" diff --against <snapshot> Compare the live graph to a saved snapshot.");
|
|
3832
|
-
console.log(
|
|
3896
|
+
console.log(` example: ${neat} diff --against ./snapshots/baseline.json`);
|
|
3833
3897
|
console.log(" stale-edges Recent OBSERVED \u2192 STALE transitions.");
|
|
3834
3898
|
console.log(" Flags: --limit N, --edge-type CALLS|CONNECTS_TO|...");
|
|
3835
|
-
console.log(
|
|
3899
|
+
console.log(` example: ${neat} stale-edges --edge-type CALLS`);
|
|
3836
3900
|
console.log(" policies Current policy violations.");
|
|
3837
3901
|
console.log(" Flags: --node <id>, --hypothetical-action <json>");
|
|
3838
|
-
console.log(
|
|
3902
|
+
console.log(` example: ${neat} policies --node service:<name> --json`);
|
|
3839
3903
|
console.log(" divergences Where code (EXTRACTED) and production (OBSERVED) disagree.");
|
|
3840
3904
|
console.log(" Flags: --type <list>, --min-confidence <0..1>, --node <id>");
|
|
3841
|
-
console.log(
|
|
3905
|
+
console.log(` example: ${neat} divergences --min-confidence 0.7`);
|
|
3842
3906
|
console.log("");
|
|
3843
3907
|
console.log("flags:");
|
|
3844
3908
|
console.log(' --project <name> Name the project this command targets. Default: "default".');
|
|
@@ -3982,40 +4046,10 @@ function assignFlag(out, field, value) {
|
|
|
3982
4046
|
;
|
|
3983
4047
|
out[field] = value;
|
|
3984
4048
|
}
|
|
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
4049
|
function printVersion() {
|
|
4004
4050
|
process.stdout.write(`${readPackageVersion()}
|
|
4005
4051
|
`);
|
|
4006
4052
|
}
|
|
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
4053
|
function printDiscoveryReport(opts, services) {
|
|
4020
4054
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
4021
4055
|
const mode = opts.dryRun ? "dry-run" : opts.apply ? "apply" : "patch-only";
|
|
@@ -4063,12 +4097,12 @@ async function runInit(opts) {
|
|
|
4063
4097
|
printDiscoveryReport(opts, services);
|
|
4064
4098
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
4065
4099
|
const patch = renderPatch(sections);
|
|
4066
|
-
const patchPath =
|
|
4100
|
+
const patchPath = path9.join(opts.scanPath, "neat.patch");
|
|
4067
4101
|
if (opts.dryRun) {
|
|
4068
4102
|
await fs7.writeFile(patchPath, patch, "utf8");
|
|
4069
4103
|
written.push(patchPath);
|
|
4070
4104
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
4071
|
-
const gitignorePath =
|
|
4105
|
+
const gitignorePath = path9.join(opts.scanPath, ".gitignore");
|
|
4072
4106
|
const gitignoreExists = await fs7.stat(gitignorePath).then(() => true).catch(() => false);
|
|
4073
4107
|
const verb = gitignoreExists ? "append" : "create";
|
|
4074
4108
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
@@ -4080,9 +4114,9 @@ async function runInit(opts) {
|
|
|
4080
4114
|
const graph = getGraph(graphKey);
|
|
4081
4115
|
const projectPaths = pathsForProject(
|
|
4082
4116
|
graphKey,
|
|
4083
|
-
|
|
4117
|
+
path9.join(opts.scanPath, "neat-out")
|
|
4084
4118
|
);
|
|
4085
|
-
const errorsPath =
|
|
4119
|
+
const errorsPath = path9.join(path9.dirname(opts.outPath), path9.basename(projectPaths.errorsPath));
|
|
4086
4120
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
4087
4121
|
await saveGraphToDisk(graph, opts.outPath);
|
|
4088
4122
|
written.push(opts.outPath);
|
|
@@ -4201,9 +4235,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
4201
4235
|
};
|
|
4202
4236
|
function claudeConfigPath() {
|
|
4203
4237
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
4204
|
-
if (override && override.length > 0) return
|
|
4238
|
+
if (override && override.length > 0) return path9.resolve(override);
|
|
4205
4239
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4206
|
-
return
|
|
4240
|
+
return path9.join(home, ".claude.json");
|
|
4207
4241
|
}
|
|
4208
4242
|
async function runSkill(opts) {
|
|
4209
4243
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -4227,7 +4261,7 @@ async function runSkill(opts) {
|
|
|
4227
4261
|
...existing,
|
|
4228
4262
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
4229
4263
|
};
|
|
4230
|
-
await fs7.mkdir(
|
|
4264
|
+
await fs7.mkdir(path9.dirname(target), { recursive: true });
|
|
4231
4265
|
await fs7.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
4232
4266
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
4233
4267
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
@@ -4243,16 +4277,24 @@ async function runSkill(opts) {
|
|
|
4243
4277
|
return { exitCode: 0 };
|
|
4244
4278
|
}
|
|
4245
4279
|
async function main() {
|
|
4246
|
-
const
|
|
4247
|
-
|
|
4280
|
+
const argv = process.argv.slice(2);
|
|
4281
|
+
const cmd0 = argv[0];
|
|
4282
|
+
if (cmd0 === "-h" || cmd0 === "--help") {
|
|
4248
4283
|
usage();
|
|
4249
4284
|
process.exit(0);
|
|
4250
4285
|
}
|
|
4251
|
-
if (
|
|
4286
|
+
if (cmd0 === "--version" || cmd0 === "-v" || cmd0 === "version") {
|
|
4252
4287
|
printVersion();
|
|
4253
4288
|
process.exit(0);
|
|
4254
4289
|
}
|
|
4255
|
-
const
|
|
4290
|
+
const argvParsed = parseArgs(argv);
|
|
4291
|
+
if (argvParsed.positional.length === 0) {
|
|
4292
|
+
const orchestratorCode2 = await tryOrchestrator(process.cwd(), argvParsed);
|
|
4293
|
+
if (orchestratorCode2 !== null && orchestratorCode2 !== 0) process.exit(orchestratorCode2);
|
|
4294
|
+
return;
|
|
4295
|
+
}
|
|
4296
|
+
const cmd = argvParsed.positional[0];
|
|
4297
|
+
const parsed = { ...argvParsed, positional: argvParsed.positional.slice(1) };
|
|
4256
4298
|
const { positional, apply: apply3, dryRun, noInstall } = parsed;
|
|
4257
4299
|
const project = parsed.project ?? DEFAULT_PROJECT;
|
|
4258
4300
|
if (cmd === "init") {
|
|
@@ -4266,12 +4308,12 @@ async function main() {
|
|
|
4266
4308
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
4267
4309
|
process.exit(2);
|
|
4268
4310
|
}
|
|
4269
|
-
const scanPath =
|
|
4311
|
+
const scanPath = path9.resolve(target);
|
|
4270
4312
|
const projectExplicit = parsed.project !== null;
|
|
4271
|
-
const projectName = projectExplicit ? project :
|
|
4313
|
+
const projectName = projectExplicit ? project : path9.basename(scanPath);
|
|
4272
4314
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
4273
|
-
const fallback = pathsForProject(projectKey,
|
|
4274
|
-
const outPath =
|
|
4315
|
+
const fallback = pathsForProject(projectKey, path9.join(scanPath, "neat-out")).snapshotPath;
|
|
4316
|
+
const outPath = path9.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
4275
4317
|
const result = await runInit({
|
|
4276
4318
|
scanPath,
|
|
4277
4319
|
outPath,
|
|
@@ -4292,21 +4334,21 @@ async function main() {
|
|
|
4292
4334
|
usage();
|
|
4293
4335
|
process.exit(2);
|
|
4294
4336
|
}
|
|
4295
|
-
const scanPath =
|
|
4337
|
+
const scanPath = path9.resolve(target);
|
|
4296
4338
|
const stat = await fs7.stat(scanPath).catch(() => null);
|
|
4297
4339
|
if (!stat || !stat.isDirectory()) {
|
|
4298
4340
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
4299
4341
|
process.exit(2);
|
|
4300
4342
|
}
|
|
4301
|
-
const projectPaths = pathsForProject(project,
|
|
4302
|
-
const outPath =
|
|
4303
|
-
const errorsPath =
|
|
4304
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
4343
|
+
const projectPaths = pathsForProject(project, path9.join(scanPath, "neat-out"));
|
|
4344
|
+
const outPath = path9.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
4345
|
+
const errorsPath = path9.resolve(
|
|
4346
|
+
process.env.NEAT_ERRORS_PATH ?? path9.join(path9.dirname(outPath), path9.basename(projectPaths.errorsPath))
|
|
4305
4347
|
);
|
|
4306
|
-
const staleEventsPath =
|
|
4307
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
4348
|
+
const staleEventsPath = path9.resolve(
|
|
4349
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? path9.join(path9.dirname(outPath), path9.basename(projectPaths.staleEventsPath))
|
|
4308
4350
|
);
|
|
4309
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
4351
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path9.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
4310
4352
|
const handle = await startWatch(getGraph(project), {
|
|
4311
4353
|
scanPath,
|
|
4312
4354
|
outPath,
|
|
@@ -4452,11 +4494,11 @@ async function main() {
|
|
|
4452
4494
|
process.exit(1);
|
|
4453
4495
|
}
|
|
4454
4496
|
async function tryOrchestrator(cmd, parsed) {
|
|
4455
|
-
const scanPath =
|
|
4497
|
+
const scanPath = path9.resolve(cmd);
|
|
4456
4498
|
const stat = await fs7.stat(scanPath).catch(() => null);
|
|
4457
4499
|
if (!stat || !stat.isDirectory()) return null;
|
|
4458
4500
|
const projectExplicit = parsed.project !== null;
|
|
4459
|
-
const projectName = projectExplicit ? parsed.project :
|
|
4501
|
+
const projectName = projectExplicit ? parsed.project : path9.basename(scanPath);
|
|
4460
4502
|
const result = await runOrchestrator({
|
|
4461
4503
|
scanPath,
|
|
4462
4504
|
project: projectName,
|
|
@@ -4656,11 +4698,15 @@ if (/[\\/]cli\.(?:cjs|js)$/.test(entry) || entry.endsWith("/cli") || entry.endsW
|
|
|
4656
4698
|
export {
|
|
4657
4699
|
CLAUDE_SKILL_CONFIG,
|
|
4658
4700
|
QUERY_VERBS,
|
|
4701
|
+
commandPrefix,
|
|
4702
|
+
isNpxInvocation,
|
|
4703
|
+
main,
|
|
4659
4704
|
parseArgs,
|
|
4660
4705
|
printBanner,
|
|
4661
4706
|
readPackageVersion,
|
|
4662
4707
|
runInit,
|
|
4663
4708
|
runQueryVerb,
|
|
4664
|
-
runSkill
|
|
4709
|
+
runSkill,
|
|
4710
|
+
usage
|
|
4665
4711
|
};
|
|
4666
4712
|
//# sourceMappingURL=cli.js.map
|