@neat.is/core 0.4.11 → 0.4.12
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-W4RNPPB5.js → chunk-7JY6F7BY.js} +2 -2
- package/dist/{chunk-OJHI33LD.js → chunk-WDG4QEFO.js} +616 -173
- package/dist/chunk-WDG4QEFO.js.map +1 -0
- package/dist/cli.cjs +994 -534
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +189 -169
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +669 -226
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +667 -224
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +802 -359
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +4 -2
- package/dist/chunk-OJHI33LD.js.map +0 -1
- /package/dist/{chunk-W4RNPPB5.js.map → chunk-7JY6F7BY.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
attachGraphToEventBus,
|
|
18
18
|
buildApi,
|
|
19
19
|
computeDivergences,
|
|
20
|
+
detectPackageManager,
|
|
20
21
|
discoverServices,
|
|
21
22
|
emitNeatEvent,
|
|
22
23
|
ensureCompatLoaded,
|
|
@@ -37,11 +38,12 @@ import {
|
|
|
37
38
|
removeProject,
|
|
38
39
|
resetGraph,
|
|
39
40
|
retireEdgesByFile,
|
|
41
|
+
runPackageManagerInstall,
|
|
40
42
|
saveGraphToDisk,
|
|
41
43
|
setStatus,
|
|
42
44
|
startPersistLoop,
|
|
43
45
|
startStalenessLoop
|
|
44
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-WDG4QEFO.js";
|
|
45
47
|
import {
|
|
46
48
|
startOtelGrpcReceiver
|
|
47
49
|
} from "./chunk-3QCRUEQD.js";
|
|
@@ -54,8 +56,8 @@ import {
|
|
|
54
56
|
} from "./chunk-HVF4S7J3.js";
|
|
55
57
|
|
|
56
58
|
// src/cli.ts
|
|
57
|
-
import
|
|
58
|
-
import { promises as
|
|
59
|
+
import path8 from "path";
|
|
60
|
+
import { promises as fs7, readFileSync } from "fs";
|
|
59
61
|
import { fileURLToPath } from "url";
|
|
60
62
|
|
|
61
63
|
// src/gitignore.ts
|
|
@@ -696,6 +698,7 @@ ${contents}EOF
|
|
|
696
698
|
// src/installers/javascript.ts
|
|
697
699
|
import { promises as fs4 } from "fs";
|
|
698
700
|
import path4 from "path";
|
|
701
|
+
import semver from "semver";
|
|
699
702
|
|
|
700
703
|
// src/installers/templates.ts
|
|
701
704
|
var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
|
|
@@ -1255,6 +1258,13 @@ async function detectRuntimeKind(pkgRoot, pkg) {
|
|
|
1255
1258
|
if (await exists(path4.join(pkgRoot, "vite.config.js")) || await exists(path4.join(pkgRoot, "vite.config.ts")) || await exists(path4.join(pkgRoot, "vite.config.mjs")) || "vite" in deps) {
|
|
1256
1259
|
return "browser-bundle";
|
|
1257
1260
|
}
|
|
1261
|
+
if (await exists(path4.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
|
|
1262
|
+
if (await exists(path4.join(pkgRoot, "bun.lockb"))) return "bun";
|
|
1263
|
+
if (await exists(path4.join(pkgRoot, "deno.json")) || await exists(path4.join(pkgRoot, "deno.lock"))) {
|
|
1264
|
+
return "deno";
|
|
1265
|
+
}
|
|
1266
|
+
const engines = pkg.engines ?? {};
|
|
1267
|
+
if ("electron" in engines) return "electron";
|
|
1258
1268
|
return "node";
|
|
1259
1269
|
}
|
|
1260
1270
|
async function readPackageJson(serviceDir) {
|
|
@@ -1294,6 +1304,12 @@ async function detect(serviceDir) {
|
|
|
1294
1304
|
const pkg = await readPackageJson(serviceDir);
|
|
1295
1305
|
return pkg !== null && typeof pkg.name === "string";
|
|
1296
1306
|
}
|
|
1307
|
+
function needsVersionUpgrade(installed, expected) {
|
|
1308
|
+
return !semver.satisfies(
|
|
1309
|
+
semver.minVersion(installed)?.version ?? installed,
|
|
1310
|
+
expected
|
|
1311
|
+
) && !semver.intersects(installed, expected);
|
|
1312
|
+
}
|
|
1297
1313
|
var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
|
|
1298
1314
|
async function findNextConfig(serviceDir) {
|
|
1299
1315
|
for (const name of NEXT_CONFIG_CANDIDATES) {
|
|
@@ -1520,23 +1536,23 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
1520
1536
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
1521
1537
|
const dependencyEdits = [];
|
|
1522
1538
|
for (const sdk of SDK_PACKAGES) {
|
|
1523
|
-
if (sdk.name in existingDeps)
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
});
|
|
1539
|
+
if (sdk.name in existingDeps) {
|
|
1540
|
+
if (needsVersionUpgrade(existingDeps[sdk.name], sdk.version)) {
|
|
1541
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: sdk.name, version: sdk.version, fromVersion: existingDeps[sdk.name] });
|
|
1542
|
+
}
|
|
1543
|
+
continue;
|
|
1544
|
+
}
|
|
1545
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
|
|
1530
1546
|
}
|
|
1531
1547
|
const nonBundled = detectNonBundledInstrumentations(pkg);
|
|
1532
1548
|
for (const inst of nonBundled) {
|
|
1533
|
-
if (inst.pkg in existingDeps)
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
});
|
|
1549
|
+
if (inst.pkg in existingDeps) {
|
|
1550
|
+
if (needsVersionUpgrade(existingDeps[inst.pkg], inst.version)) {
|
|
1551
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: inst.pkg, version: inst.version, fromVersion: existingDeps[inst.pkg] });
|
|
1552
|
+
}
|
|
1553
|
+
continue;
|
|
1554
|
+
}
|
|
1555
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: inst.pkg, version: inst.version });
|
|
1540
1556
|
}
|
|
1541
1557
|
const svcName = serviceNodeName(pkg, serviceDir);
|
|
1542
1558
|
const projectName = projectToken(pkg, serviceDir, project);
|
|
@@ -1982,23 +1998,23 @@ async function plan(serviceDir, opts) {
|
|
|
1982
1998
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
1983
1999
|
const dependencyEdits = [];
|
|
1984
2000
|
for (const sdk of SDK_PACKAGES) {
|
|
1985
|
-
if (sdk.name in existingDeps)
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
});
|
|
2001
|
+
if (sdk.name in existingDeps) {
|
|
2002
|
+
if (needsVersionUpgrade(existingDeps[sdk.name], sdk.version)) {
|
|
2003
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: sdk.name, version: sdk.version, fromVersion: existingDeps[sdk.name] });
|
|
2004
|
+
}
|
|
2005
|
+
continue;
|
|
2006
|
+
}
|
|
2007
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
|
|
1992
2008
|
}
|
|
1993
2009
|
const nonBundled = detectNonBundledInstrumentations(pkg);
|
|
1994
2010
|
for (const inst of nonBundled) {
|
|
1995
|
-
if (inst.pkg in existingDeps)
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
});
|
|
2011
|
+
if (inst.pkg in existingDeps) {
|
|
2012
|
+
if (needsVersionUpgrade(existingDeps[inst.pkg], inst.version)) {
|
|
2013
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: inst.pkg, version: inst.version, fromVersion: existingDeps[inst.pkg] });
|
|
2014
|
+
}
|
|
2015
|
+
continue;
|
|
2016
|
+
}
|
|
2017
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: inst.pkg, version: inst.version });
|
|
2002
2018
|
}
|
|
2003
2019
|
const entrypointEdits = [];
|
|
2004
2020
|
try {
|
|
@@ -2101,6 +2117,28 @@ async function apply(installPlan) {
|
|
|
2101
2117
|
writtenFiles: []
|
|
2102
2118
|
};
|
|
2103
2119
|
}
|
|
2120
|
+
if (installPlan.runtimeKind === "bun") {
|
|
2121
|
+
return { serviceDir, outcome: "bun", reason: "Bun runtime; use BYO-OTel", writtenFiles: [] };
|
|
2122
|
+
}
|
|
2123
|
+
if (installPlan.runtimeKind === "deno") {
|
|
2124
|
+
return { serviceDir, outcome: "deno", reason: "Deno runtime; use BYO-OTel", writtenFiles: [] };
|
|
2125
|
+
}
|
|
2126
|
+
if (installPlan.runtimeKind === "cloudflare-workers") {
|
|
2127
|
+
return {
|
|
2128
|
+
serviceDir,
|
|
2129
|
+
outcome: "cloudflare-workers",
|
|
2130
|
+
reason: "Cloudflare Workers runtime; use BYO-OTel",
|
|
2131
|
+
writtenFiles: []
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
if (installPlan.runtimeKind === "electron") {
|
|
2135
|
+
return {
|
|
2136
|
+
serviceDir,
|
|
2137
|
+
outcome: "electron",
|
|
2138
|
+
reason: "Electron; use BYO-OTel",
|
|
2139
|
+
writtenFiles: []
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2104
2142
|
if (installPlan.dependencyEdits.length === 0 && installPlan.entrypointEdits.length === 0 && (installPlan.generatedFiles?.length ?? 0) === 0 && installPlan.nextConfigEdit === void 0) {
|
|
2105
2143
|
return {
|
|
2106
2144
|
serviceDir,
|
|
@@ -2151,6 +2189,8 @@ async function apply(installPlan) {
|
|
|
2151
2189
|
if (!(dep.name in (pkg.dependencies ?? {}))) {
|
|
2152
2190
|
pkg.dependencies[dep.name] = dep.version;
|
|
2153
2191
|
}
|
|
2192
|
+
} else if (dep.kind === "upgrade") {
|
|
2193
|
+
pkg.dependencies[dep.name] = dep.version;
|
|
2154
2194
|
} else {
|
|
2155
2195
|
delete pkg.dependencies[dep.name];
|
|
2156
2196
|
}
|
|
@@ -2551,99 +2591,19 @@ function renderPatch(sections) {
|
|
|
2551
2591
|
}
|
|
2552
2592
|
|
|
2553
2593
|
// src/orchestrator.ts
|
|
2554
|
-
import { promises as
|
|
2594
|
+
import { promises as fs6 } from "fs";
|
|
2555
2595
|
import http from "http";
|
|
2556
2596
|
import net from "net";
|
|
2557
|
-
import path7 from "path";
|
|
2558
|
-
import { spawn as spawn3 } from "child_process";
|
|
2559
|
-
import readline from "readline";
|
|
2560
|
-
|
|
2561
|
-
// src/installers/package-manager.ts
|
|
2562
|
-
import { promises as fs6 } from "fs";
|
|
2563
2597
|
import path6 from "path";
|
|
2564
2598
|
import { spawn as spawn2 } from "child_process";
|
|
2565
|
-
|
|
2566
|
-
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
2567
|
-
{ lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
|
|
2568
|
-
{ lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
|
|
2569
|
-
{
|
|
2570
|
-
lockfile: "package-lock.json",
|
|
2571
|
-
pm: "npm",
|
|
2572
|
-
args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
|
|
2573
|
-
}
|
|
2574
|
-
];
|
|
2575
|
-
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
2576
|
-
async function exists3(p) {
|
|
2577
|
-
try {
|
|
2578
|
-
await fs6.access(p);
|
|
2579
|
-
return true;
|
|
2580
|
-
} catch {
|
|
2581
|
-
return false;
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
|
-
async function detectPackageManager(serviceDir) {
|
|
2585
|
-
let dir = path6.resolve(serviceDir);
|
|
2586
|
-
const stops = /* @__PURE__ */ new Set();
|
|
2587
|
-
for (let i = 0; i < 64; i++) {
|
|
2588
|
-
if (stops.has(dir)) break;
|
|
2589
|
-
stops.add(dir);
|
|
2590
|
-
for (const candidate of LOCKFILE_PRIORITY) {
|
|
2591
|
-
const lockPath = path6.join(dir, candidate.lockfile);
|
|
2592
|
-
if (await exists3(lockPath)) {
|
|
2593
|
-
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
2594
|
-
}
|
|
2595
|
-
}
|
|
2596
|
-
const parent = path6.dirname(dir);
|
|
2597
|
-
if (parent === dir) break;
|
|
2598
|
-
dir = parent;
|
|
2599
|
-
}
|
|
2600
|
-
return { pm: "npm", cwd: path6.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
2601
|
-
}
|
|
2602
|
-
async function runPackageManagerInstall(cmd) {
|
|
2603
|
-
return new Promise((resolve) => {
|
|
2604
|
-
const child = spawn2(cmd.pm, cmd.args, {
|
|
2605
|
-
cwd: cmd.cwd,
|
|
2606
|
-
// Inherit PATH + HOME so the user's installed managers resolve.
|
|
2607
|
-
env: process.env,
|
|
2608
|
-
// `false` keeps the parent in control of cleanup if the orchestrator
|
|
2609
|
-
// exits before install finishes. Cross-platform-safe.
|
|
2610
|
-
shell: false,
|
|
2611
|
-
stdio: ["ignore", "ignore", "pipe"]
|
|
2612
|
-
});
|
|
2613
|
-
let stderr = "";
|
|
2614
|
-
child.stderr?.on("data", (chunk) => {
|
|
2615
|
-
stderr += chunk.toString("utf8");
|
|
2616
|
-
});
|
|
2617
|
-
child.on("error", (err) => {
|
|
2618
|
-
resolve({
|
|
2619
|
-
pm: cmd.pm,
|
|
2620
|
-
cwd: cmd.cwd,
|
|
2621
|
-
args: cmd.args,
|
|
2622
|
-
exitCode: 127,
|
|
2623
|
-
stderr: stderr + `
|
|
2624
|
-
${err.message}`
|
|
2625
|
-
});
|
|
2626
|
-
});
|
|
2627
|
-
child.on("close", (code) => {
|
|
2628
|
-
resolve({
|
|
2629
|
-
pm: cmd.pm,
|
|
2630
|
-
cwd: cmd.cwd,
|
|
2631
|
-
args: cmd.args,
|
|
2632
|
-
exitCode: code ?? 1,
|
|
2633
|
-
stderr: stderr.trim()
|
|
2634
|
-
});
|
|
2635
|
-
});
|
|
2636
|
-
});
|
|
2637
|
-
}
|
|
2638
|
-
|
|
2639
|
-
// src/orchestrator.ts
|
|
2599
|
+
import readline from "readline";
|
|
2640
2600
|
async function extractAndPersist(opts) {
|
|
2641
2601
|
const services = await discoverServices(opts.scanPath);
|
|
2642
2602
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
2643
2603
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
2644
2604
|
resetGraph(graphKey);
|
|
2645
2605
|
const graph = getGraph(graphKey);
|
|
2646
|
-
const projectPaths = pathsForProject(graphKey,
|
|
2606
|
+
const projectPaths = pathsForProject(graphKey, path6.join(opts.scanPath, "neat-out"));
|
|
2647
2607
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
2648
2608
|
errorsPath: projectPaths.errorsPath
|
|
2649
2609
|
});
|
|
@@ -2669,6 +2629,10 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2669
2629
|
let libOnly = 0;
|
|
2670
2630
|
let browserBundle = 0;
|
|
2671
2631
|
let reactNative = 0;
|
|
2632
|
+
let bun = 0;
|
|
2633
|
+
let deno = 0;
|
|
2634
|
+
let cloudflareWorkers = 0;
|
|
2635
|
+
let electron = 0;
|
|
2672
2636
|
const installPlans = /* @__PURE__ */ new Map();
|
|
2673
2637
|
for (const svc of services) {
|
|
2674
2638
|
const installer = await pickInstaller(svc.dir);
|
|
@@ -2693,7 +2657,59 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2693
2657
|
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
2694
2658
|
} else if (outcome.outcome === "react-native") {
|
|
2695
2659
|
reactNative++;
|
|
2696
|
-
|
|
2660
|
+
const svcName = path6.basename(svc.dir);
|
|
2661
|
+
console.log(
|
|
2662
|
+
`neat: ${svc.dir} detected as React Native / Expo
|
|
2663
|
+
The installer doesn't cover this runtime deterministically.
|
|
2664
|
+
Configure your OTel binding to send spans to:
|
|
2665
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
2666
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
2667
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
2668
|
+
);
|
|
2669
|
+
} else if (outcome.outcome === "bun") {
|
|
2670
|
+
bun++;
|
|
2671
|
+
const svcName = path6.basename(svc.dir);
|
|
2672
|
+
console.log(
|
|
2673
|
+
`neat: ${svc.dir} detected as Bun
|
|
2674
|
+
The installer doesn't cover this runtime deterministically.
|
|
2675
|
+
Configure your OTel binding to send spans to:
|
|
2676
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
2677
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
2678
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
2679
|
+
);
|
|
2680
|
+
} else if (outcome.outcome === "deno") {
|
|
2681
|
+
deno++;
|
|
2682
|
+
const svcName = path6.basename(svc.dir);
|
|
2683
|
+
console.log(
|
|
2684
|
+
`neat: ${svc.dir} detected as Deno
|
|
2685
|
+
The installer doesn't cover this runtime deterministically.
|
|
2686
|
+
Configure your OTel binding to send spans to:
|
|
2687
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
2688
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
2689
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
2690
|
+
);
|
|
2691
|
+
} else if (outcome.outcome === "cloudflare-workers") {
|
|
2692
|
+
cloudflareWorkers++;
|
|
2693
|
+
const svcName = path6.basename(svc.dir);
|
|
2694
|
+
console.log(
|
|
2695
|
+
`neat: ${svc.dir} detected as Cloudflare Workers
|
|
2696
|
+
The installer doesn't cover this runtime deterministically.
|
|
2697
|
+
Configure your OTel binding to send spans to:
|
|
2698
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
2699
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
2700
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
2701
|
+
);
|
|
2702
|
+
} else if (outcome.outcome === "electron") {
|
|
2703
|
+
electron++;
|
|
2704
|
+
const svcName = path6.basename(svc.dir);
|
|
2705
|
+
console.log(
|
|
2706
|
+
`neat: ${svc.dir} detected as Electron
|
|
2707
|
+
The installer doesn't cover this runtime deterministically.
|
|
2708
|
+
Configure your OTel binding to send spans to:
|
|
2709
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
2710
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
2711
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
2712
|
+
);
|
|
2697
2713
|
}
|
|
2698
2714
|
}
|
|
2699
2715
|
const packageManagerInstalls = [];
|
|
@@ -2718,6 +2734,10 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2718
2734
|
libOnly,
|
|
2719
2735
|
browserBundle,
|
|
2720
2736
|
reactNative,
|
|
2737
|
+
bun,
|
|
2738
|
+
deno,
|
|
2739
|
+
cloudflareWorkers,
|
|
2740
|
+
electron,
|
|
2721
2741
|
packageManagerInstalls
|
|
2722
2742
|
};
|
|
2723
2743
|
}
|
|
@@ -2855,10 +2875,10 @@ function formatPortCollisionMessage(port) {
|
|
|
2855
2875
|
];
|
|
2856
2876
|
}
|
|
2857
2877
|
function spawnDaemonDetached() {
|
|
2858
|
-
const here =
|
|
2878
|
+
const here = path6.dirname(new URL(import.meta.url).pathname);
|
|
2859
2879
|
const candidates = [
|
|
2860
|
-
|
|
2861
|
-
|
|
2880
|
+
path6.join(here, "neatd.cjs"),
|
|
2881
|
+
path6.join(here, "neatd.js")
|
|
2862
2882
|
];
|
|
2863
2883
|
let entry2 = null;
|
|
2864
2884
|
const fsSync = __require("fs");
|
|
@@ -2878,7 +2898,7 @@ function spawnDaemonDetached() {
|
|
|
2878
2898
|
if (!hasToken && (!env.HOST || env.HOST.length === 0)) {
|
|
2879
2899
|
env.HOST = "127.0.0.1";
|
|
2880
2900
|
}
|
|
2881
|
-
const child =
|
|
2901
|
+
const child = spawn2(process.execPath, [entry2, "start"], {
|
|
2882
2902
|
detached: true,
|
|
2883
2903
|
// stderr inherits the orchestrator's fd so the daemon's
|
|
2884
2904
|
// `BindAuthorityError` message lands in front of the operator instead
|
|
@@ -2895,7 +2915,7 @@ function openBrowser(url) {
|
|
|
2895
2915
|
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
2896
2916
|
const args = platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
2897
2917
|
try {
|
|
2898
|
-
const child =
|
|
2918
|
+
const child = spawn2(cmd, args, { detached: true, stdio: "ignore" });
|
|
2899
2919
|
child.on("error", () => {
|
|
2900
2920
|
});
|
|
2901
2921
|
child.unref();
|
|
@@ -2916,7 +2936,7 @@ async function runOrchestrator(opts) {
|
|
|
2916
2936
|
browser: "skipped"
|
|
2917
2937
|
}
|
|
2918
2938
|
};
|
|
2919
|
-
const stat = await
|
|
2939
|
+
const stat = await fs6.stat(opts.scanPath).catch(() => null);
|
|
2920
2940
|
if (!stat || !stat.isDirectory()) {
|
|
2921
2941
|
console.error(`neat: ${opts.scanPath} is not a directory`);
|
|
2922
2942
|
result.exitCode = 2;
|
|
@@ -3058,7 +3078,7 @@ function printSummary(result, graph, dashboardUrl) {
|
|
|
3058
3078
|
}
|
|
3059
3079
|
|
|
3060
3080
|
// src/cli-verbs.ts
|
|
3061
|
-
import
|
|
3081
|
+
import path7 from "path";
|
|
3062
3082
|
|
|
3063
3083
|
// src/cli-client.ts
|
|
3064
3084
|
import { Provenance as Provenance2 } from "@neat.is/types";
|
|
@@ -3086,10 +3106,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3086
3106
|
const root = baseUrl.replace(/\/$/, "");
|
|
3087
3107
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
3088
3108
|
return {
|
|
3089
|
-
async get(
|
|
3109
|
+
async get(path9) {
|
|
3090
3110
|
let res;
|
|
3091
3111
|
try {
|
|
3092
|
-
res = await fetch(`${root}${
|
|
3112
|
+
res = await fetch(`${root}${path9}`, {
|
|
3093
3113
|
headers: { ...authHeader }
|
|
3094
3114
|
});
|
|
3095
3115
|
} catch (err) {
|
|
@@ -3101,16 +3121,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3101
3121
|
const body = await res.text().catch(() => "");
|
|
3102
3122
|
throw new HttpError(
|
|
3103
3123
|
res.status,
|
|
3104
|
-
`${res.status} ${res.statusText} on GET ${
|
|
3124
|
+
`${res.status} ${res.statusText} on GET ${path9}: ${body}`,
|
|
3105
3125
|
body
|
|
3106
3126
|
);
|
|
3107
3127
|
}
|
|
3108
3128
|
return await res.json();
|
|
3109
3129
|
},
|
|
3110
|
-
async post(
|
|
3130
|
+
async post(path9, body) {
|
|
3111
3131
|
let res;
|
|
3112
3132
|
try {
|
|
3113
|
-
res = await fetch(`${root}${
|
|
3133
|
+
res = await fetch(`${root}${path9}`, {
|
|
3114
3134
|
method: "POST",
|
|
3115
3135
|
headers: { "content-type": "application/json", ...authHeader },
|
|
3116
3136
|
body: JSON.stringify(body)
|
|
@@ -3124,7 +3144,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3124
3144
|
const text = await res.text().catch(() => "");
|
|
3125
3145
|
throw new HttpError(
|
|
3126
3146
|
res.status,
|
|
3127
|
-
`${res.status} ${res.statusText} on POST ${
|
|
3147
|
+
`${res.status} ${res.statusText} on POST ${path9}: ${text}`,
|
|
3128
3148
|
text
|
|
3129
3149
|
);
|
|
3130
3150
|
}
|
|
@@ -3138,12 +3158,12 @@ function projectPath(project, suffix) {
|
|
|
3138
3158
|
}
|
|
3139
3159
|
async function runRootCause(client, input) {
|
|
3140
3160
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
3141
|
-
const
|
|
3161
|
+
const path9 = projectPath(
|
|
3142
3162
|
input.project,
|
|
3143
3163
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
3144
3164
|
);
|
|
3145
3165
|
try {
|
|
3146
|
-
const result = await client.get(
|
|
3166
|
+
const result = await client.get(path9);
|
|
3147
3167
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
3148
3168
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
3149
3169
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -3169,12 +3189,12 @@ async function runRootCause(client, input) {
|
|
|
3169
3189
|
}
|
|
3170
3190
|
async function runBlastRadius(client, input) {
|
|
3171
3191
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
3172
|
-
const
|
|
3192
|
+
const path9 = projectPath(
|
|
3173
3193
|
input.project,
|
|
3174
3194
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
3175
3195
|
);
|
|
3176
3196
|
try {
|
|
3177
|
-
const result = await client.get(
|
|
3197
|
+
const result = await client.get(path9);
|
|
3178
3198
|
if (result.totalAffected === 0) {
|
|
3179
3199
|
return {
|
|
3180
3200
|
summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
|
|
@@ -3208,12 +3228,12 @@ function formatBlastEntry(n) {
|
|
|
3208
3228
|
}
|
|
3209
3229
|
async function runDependencies(client, input) {
|
|
3210
3230
|
const depth = input.depth ?? 3;
|
|
3211
|
-
const
|
|
3231
|
+
const path9 = projectPath(
|
|
3212
3232
|
input.project,
|
|
3213
3233
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
3214
3234
|
);
|
|
3215
3235
|
try {
|
|
3216
|
-
const result = await client.get(
|
|
3236
|
+
const result = await client.get(path9);
|
|
3217
3237
|
if (result.total === 0) {
|
|
3218
3238
|
return {
|
|
3219
3239
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -3294,9 +3314,9 @@ function formatDuration(ms) {
|
|
|
3294
3314
|
return `${Math.round(h / 24)}d`;
|
|
3295
3315
|
}
|
|
3296
3316
|
async function runIncidents(client, input) {
|
|
3297
|
-
const
|
|
3317
|
+
const path9 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
3298
3318
|
try {
|
|
3299
|
-
const body = await client.get(
|
|
3319
|
+
const body = await client.get(path9);
|
|
3300
3320
|
const events = body.events;
|
|
3301
3321
|
if (events.length === 0) {
|
|
3302
3322
|
return {
|
|
@@ -3586,7 +3606,7 @@ async function resolveProjectEntry(opts) {
|
|
|
3586
3606
|
const cwd = opts.cwd ?? process.cwd();
|
|
3587
3607
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
3588
3608
|
for (const entry2 of entries) {
|
|
3589
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
3609
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path7.sep}`)) {
|
|
3590
3610
|
return entry2;
|
|
3591
3611
|
}
|
|
3592
3612
|
}
|
|
@@ -3946,10 +3966,10 @@ function assignFlag(out, field, value) {
|
|
|
3946
3966
|
out[field] = value;
|
|
3947
3967
|
}
|
|
3948
3968
|
function readPackageVersion() {
|
|
3949
|
-
const here = typeof __dirname !== "undefined" ? __dirname :
|
|
3969
|
+
const here = typeof __dirname !== "undefined" ? __dirname : path8.dirname(fileURLToPath(import.meta.url));
|
|
3950
3970
|
const candidates = [
|
|
3951
|
-
|
|
3952
|
-
|
|
3971
|
+
path8.resolve(here, "../package.json"),
|
|
3972
|
+
path8.resolve(here, "../../package.json")
|
|
3953
3973
|
];
|
|
3954
3974
|
for (const candidate of candidates) {
|
|
3955
3975
|
try {
|
|
@@ -4017,7 +4037,7 @@ async function buildPatchSections(services, project) {
|
|
|
4017
4037
|
}
|
|
4018
4038
|
async function runInit(opts) {
|
|
4019
4039
|
const written = [];
|
|
4020
|
-
const stat = await
|
|
4040
|
+
const stat = await fs7.stat(opts.scanPath).catch(() => null);
|
|
4021
4041
|
if (!stat || !stat.isDirectory()) {
|
|
4022
4042
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
4023
4043
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -4026,13 +4046,13 @@ async function runInit(opts) {
|
|
|
4026
4046
|
printDiscoveryReport(opts, services);
|
|
4027
4047
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
4028
4048
|
const patch = renderPatch(sections);
|
|
4029
|
-
const patchPath =
|
|
4049
|
+
const patchPath = path8.join(opts.scanPath, "neat.patch");
|
|
4030
4050
|
if (opts.dryRun) {
|
|
4031
|
-
await
|
|
4051
|
+
await fs7.writeFile(patchPath, patch, "utf8");
|
|
4032
4052
|
written.push(patchPath);
|
|
4033
4053
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
4034
|
-
const gitignorePath =
|
|
4035
|
-
const gitignoreExists = await
|
|
4054
|
+
const gitignorePath = path8.join(opts.scanPath, ".gitignore");
|
|
4055
|
+
const gitignoreExists = await fs7.stat(gitignorePath).then(() => true).catch(() => false);
|
|
4036
4056
|
const verb = gitignoreExists ? "append" : "create";
|
|
4037
4057
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
4038
4058
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -4043,9 +4063,9 @@ async function runInit(opts) {
|
|
|
4043
4063
|
const graph = getGraph(graphKey);
|
|
4044
4064
|
const projectPaths = pathsForProject(
|
|
4045
4065
|
graphKey,
|
|
4046
|
-
|
|
4066
|
+
path8.join(opts.scanPath, "neat-out")
|
|
4047
4067
|
);
|
|
4048
|
-
const errorsPath =
|
|
4068
|
+
const errorsPath = path8.join(path8.dirname(opts.outPath), path8.basename(projectPaths.errorsPath));
|
|
4049
4069
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
4050
4070
|
await saveGraphToDisk(graph, opts.outPath);
|
|
4051
4071
|
written.push(opts.outPath);
|
|
@@ -4124,7 +4144,7 @@ async function runInit(opts) {
|
|
|
4124
4144
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
4125
4145
|
}
|
|
4126
4146
|
} else {
|
|
4127
|
-
await
|
|
4147
|
+
await fs7.writeFile(patchPath, patch, "utf8");
|
|
4128
4148
|
written.push(patchPath);
|
|
4129
4149
|
}
|
|
4130
4150
|
}
|
|
@@ -4164,9 +4184,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
4164
4184
|
};
|
|
4165
4185
|
function claudeConfigPath() {
|
|
4166
4186
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
4167
|
-
if (override && override.length > 0) return
|
|
4187
|
+
if (override && override.length > 0) return path8.resolve(override);
|
|
4168
4188
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4169
|
-
return
|
|
4189
|
+
return path8.join(home, ".claude.json");
|
|
4170
4190
|
}
|
|
4171
4191
|
async function runSkill(opts) {
|
|
4172
4192
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -4178,7 +4198,7 @@ async function runSkill(opts) {
|
|
|
4178
4198
|
const target = claudeConfigPath();
|
|
4179
4199
|
let existing = {};
|
|
4180
4200
|
try {
|
|
4181
|
-
existing = JSON.parse(await
|
|
4201
|
+
existing = JSON.parse(await fs7.readFile(target, "utf8"));
|
|
4182
4202
|
} catch (err) {
|
|
4183
4203
|
if (err.code !== "ENOENT") {
|
|
4184
4204
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -4190,8 +4210,8 @@ async function runSkill(opts) {
|
|
|
4190
4210
|
...existing,
|
|
4191
4211
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
4192
4212
|
};
|
|
4193
|
-
await
|
|
4194
|
-
await
|
|
4213
|
+
await fs7.mkdir(path8.dirname(target), { recursive: true });
|
|
4214
|
+
await fs7.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
4195
4215
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
4196
4216
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
4197
4217
|
return { exitCode: 0 };
|
|
@@ -4229,12 +4249,12 @@ async function main() {
|
|
|
4229
4249
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
4230
4250
|
process.exit(2);
|
|
4231
4251
|
}
|
|
4232
|
-
const scanPath =
|
|
4252
|
+
const scanPath = path8.resolve(target);
|
|
4233
4253
|
const projectExplicit = parsed.project !== null;
|
|
4234
|
-
const projectName = projectExplicit ? project :
|
|
4254
|
+
const projectName = projectExplicit ? project : path8.basename(scanPath);
|
|
4235
4255
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
4236
|
-
const fallback = pathsForProject(projectKey,
|
|
4237
|
-
const outPath =
|
|
4256
|
+
const fallback = pathsForProject(projectKey, path8.join(scanPath, "neat-out")).snapshotPath;
|
|
4257
|
+
const outPath = path8.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
4238
4258
|
const result = await runInit({
|
|
4239
4259
|
scanPath,
|
|
4240
4260
|
outPath,
|
|
@@ -4255,21 +4275,21 @@ async function main() {
|
|
|
4255
4275
|
usage();
|
|
4256
4276
|
process.exit(2);
|
|
4257
4277
|
}
|
|
4258
|
-
const scanPath =
|
|
4259
|
-
const stat = await
|
|
4278
|
+
const scanPath = path8.resolve(target);
|
|
4279
|
+
const stat = await fs7.stat(scanPath).catch(() => null);
|
|
4260
4280
|
if (!stat || !stat.isDirectory()) {
|
|
4261
4281
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
4262
4282
|
process.exit(2);
|
|
4263
4283
|
}
|
|
4264
|
-
const projectPaths = pathsForProject(project,
|
|
4265
|
-
const outPath =
|
|
4266
|
-
const errorsPath =
|
|
4267
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
4284
|
+
const projectPaths = pathsForProject(project, path8.join(scanPath, "neat-out"));
|
|
4285
|
+
const outPath = path8.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
4286
|
+
const errorsPath = path8.resolve(
|
|
4287
|
+
process.env.NEAT_ERRORS_PATH ?? path8.join(path8.dirname(outPath), path8.basename(projectPaths.errorsPath))
|
|
4268
4288
|
);
|
|
4269
|
-
const staleEventsPath =
|
|
4270
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
4289
|
+
const staleEventsPath = path8.resolve(
|
|
4290
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? path8.join(path8.dirname(outPath), path8.basename(projectPaths.staleEventsPath))
|
|
4271
4291
|
);
|
|
4272
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
4292
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path8.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
4273
4293
|
const handle = await startWatch(getGraph(project), {
|
|
4274
4294
|
scanPath,
|
|
4275
4295
|
outPath,
|
|
@@ -4415,11 +4435,11 @@ async function main() {
|
|
|
4415
4435
|
process.exit(1);
|
|
4416
4436
|
}
|
|
4417
4437
|
async function tryOrchestrator(cmd, parsed) {
|
|
4418
|
-
const scanPath =
|
|
4419
|
-
const stat = await
|
|
4438
|
+
const scanPath = path8.resolve(cmd);
|
|
4439
|
+
const stat = await fs7.stat(scanPath).catch(() => null);
|
|
4420
4440
|
if (!stat || !stat.isDirectory()) return null;
|
|
4421
4441
|
const projectExplicit = parsed.project !== null;
|
|
4422
|
-
const projectName = projectExplicit ? parsed.project :
|
|
4442
|
+
const projectName = projectExplicit ? parsed.project : path8.basename(scanPath);
|
|
4423
4443
|
const result = await runOrchestrator({
|
|
4424
4444
|
scanPath,
|
|
4425
4445
|
project: projectName,
|