@neat.is/core 0.4.11 → 0.4.13
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-OJHI33LD.js → chunk-5W7H35JJ.js} +1111 -343
- package/dist/chunk-5W7H35JJ.js.map +1 -0
- package/dist/{chunk-W4RNPPB5.js → chunk-TWNJX26R.js} +2 -2
- package/dist/cli.cjs +1471 -682
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +197 -169
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1145 -380
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/neatd.cjs +1133 -368
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +2 -2
- package/dist/server.cjs +1267 -502
- 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-TWNJX26R.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
addCallEdges,
|
|
11
11
|
addConfigNodes,
|
|
12
12
|
addDatabasesAndCompat,
|
|
13
|
+
addImports,
|
|
13
14
|
addInfra,
|
|
14
15
|
addProject,
|
|
15
16
|
addServiceAliases,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
attachGraphToEventBus,
|
|
18
19
|
buildApi,
|
|
19
20
|
computeDivergences,
|
|
21
|
+
detectPackageManager,
|
|
20
22
|
discoverServices,
|
|
21
23
|
emitNeatEvent,
|
|
22
24
|
ensureCompatLoaded,
|
|
@@ -37,11 +39,12 @@ import {
|
|
|
37
39
|
removeProject,
|
|
38
40
|
resetGraph,
|
|
39
41
|
retireEdgesByFile,
|
|
42
|
+
runPackageManagerInstall,
|
|
40
43
|
saveGraphToDisk,
|
|
41
44
|
setStatus,
|
|
42
45
|
startPersistLoop,
|
|
43
46
|
startStalenessLoop
|
|
44
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-5W7H35JJ.js";
|
|
45
48
|
import {
|
|
46
49
|
startOtelGrpcReceiver
|
|
47
50
|
} from "./chunk-3QCRUEQD.js";
|
|
@@ -54,8 +57,8 @@ import {
|
|
|
54
57
|
} from "./chunk-HVF4S7J3.js";
|
|
55
58
|
|
|
56
59
|
// src/cli.ts
|
|
57
|
-
import
|
|
58
|
-
import { promises as
|
|
60
|
+
import path8 from "path";
|
|
61
|
+
import { promises as fs7, readFileSync } from "fs";
|
|
59
62
|
import { fileURLToPath } from "url";
|
|
60
63
|
|
|
61
64
|
// src/gitignore.ts
|
|
@@ -195,6 +198,7 @@ import chokidar from "chokidar";
|
|
|
195
198
|
var ALL_PHASES = [
|
|
196
199
|
"services",
|
|
197
200
|
"aliases",
|
|
201
|
+
"imports",
|
|
198
202
|
"databases",
|
|
199
203
|
"configs",
|
|
200
204
|
"calls",
|
|
@@ -218,6 +222,7 @@ function classifyChange(relPath) {
|
|
|
218
222
|
phases.add("aliases");
|
|
219
223
|
}
|
|
220
224
|
if (/\.(?:js|jsx|mjs|cjs|ts|tsx|py)$/.test(base)) {
|
|
225
|
+
phases.add("imports");
|
|
221
226
|
phases.add("calls");
|
|
222
227
|
}
|
|
223
228
|
if (/\.ya?ml$/.test(base) && !/^docker-compose.*\.ya?ml$/.test(base)) {
|
|
@@ -239,6 +244,11 @@ async function runExtractPhases(graph, scanPath, phases, project = DEFAULT_PROJE
|
|
|
239
244
|
if (phases.has("aliases")) {
|
|
240
245
|
await addServiceAliases(graph, scanPath, services);
|
|
241
246
|
}
|
|
247
|
+
if (phases.has("imports")) {
|
|
248
|
+
const r = await addImports(graph, services);
|
|
249
|
+
nodesAdded += r.nodesAdded;
|
|
250
|
+
edgesAdded += r.edgesAdded;
|
|
251
|
+
}
|
|
242
252
|
if (phases.has("databases")) {
|
|
243
253
|
const r = await addDatabasesAndCompat(graph, services, scanPath);
|
|
244
254
|
nodesAdded += r.nodesAdded;
|
|
@@ -696,6 +706,7 @@ ${contents}EOF
|
|
|
696
706
|
// src/installers/javascript.ts
|
|
697
707
|
import { promises as fs4 } from "fs";
|
|
698
708
|
import path4 from "path";
|
|
709
|
+
import semver from "semver";
|
|
699
710
|
|
|
700
711
|
// src/installers/templates.ts
|
|
701
712
|
var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
|
|
@@ -1255,6 +1266,13 @@ async function detectRuntimeKind(pkgRoot, pkg) {
|
|
|
1255
1266
|
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
1267
|
return "browser-bundle";
|
|
1257
1268
|
}
|
|
1269
|
+
if (await exists(path4.join(pkgRoot, "wrangler.toml"))) return "cloudflare-workers";
|
|
1270
|
+
if (await exists(path4.join(pkgRoot, "bun.lockb"))) return "bun";
|
|
1271
|
+
if (await exists(path4.join(pkgRoot, "deno.json")) || await exists(path4.join(pkgRoot, "deno.lock"))) {
|
|
1272
|
+
return "deno";
|
|
1273
|
+
}
|
|
1274
|
+
const engines = pkg.engines ?? {};
|
|
1275
|
+
if ("electron" in engines) return "electron";
|
|
1258
1276
|
return "node";
|
|
1259
1277
|
}
|
|
1260
1278
|
async function readPackageJson(serviceDir) {
|
|
@@ -1294,6 +1312,12 @@ async function detect(serviceDir) {
|
|
|
1294
1312
|
const pkg = await readPackageJson(serviceDir);
|
|
1295
1313
|
return pkg !== null && typeof pkg.name === "string";
|
|
1296
1314
|
}
|
|
1315
|
+
function needsVersionUpgrade(installed, expected) {
|
|
1316
|
+
return !semver.satisfies(
|
|
1317
|
+
semver.minVersion(installed)?.version ?? installed,
|
|
1318
|
+
expected
|
|
1319
|
+
) && !semver.intersects(installed, expected);
|
|
1320
|
+
}
|
|
1297
1321
|
var NEXT_CONFIG_CANDIDATES = ["next.config.js", "next.config.ts", "next.config.mjs"];
|
|
1298
1322
|
async function findNextConfig(serviceDir) {
|
|
1299
1323
|
for (const name of NEXT_CONFIG_CANDIDATES) {
|
|
@@ -1520,23 +1544,23 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
1520
1544
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
1521
1545
|
const dependencyEdits = [];
|
|
1522
1546
|
for (const sdk of SDK_PACKAGES) {
|
|
1523
|
-
if (sdk.name in existingDeps)
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
});
|
|
1547
|
+
if (sdk.name in existingDeps) {
|
|
1548
|
+
if (needsVersionUpgrade(existingDeps[sdk.name], sdk.version)) {
|
|
1549
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: sdk.name, version: sdk.version, fromVersion: existingDeps[sdk.name] });
|
|
1550
|
+
}
|
|
1551
|
+
continue;
|
|
1552
|
+
}
|
|
1553
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
|
|
1530
1554
|
}
|
|
1531
1555
|
const nonBundled = detectNonBundledInstrumentations(pkg);
|
|
1532
1556
|
for (const inst of nonBundled) {
|
|
1533
|
-
if (inst.pkg in existingDeps)
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
});
|
|
1557
|
+
if (inst.pkg in existingDeps) {
|
|
1558
|
+
if (needsVersionUpgrade(existingDeps[inst.pkg], inst.version)) {
|
|
1559
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: inst.pkg, version: inst.version, fromVersion: existingDeps[inst.pkg] });
|
|
1560
|
+
}
|
|
1561
|
+
continue;
|
|
1562
|
+
}
|
|
1563
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: inst.pkg, version: inst.version });
|
|
1540
1564
|
}
|
|
1541
1565
|
const svcName = serviceNodeName(pkg, serviceDir);
|
|
1542
1566
|
const projectName = projectToken(pkg, serviceDir, project);
|
|
@@ -1982,23 +2006,23 @@ async function plan(serviceDir, opts) {
|
|
|
1982
2006
|
const existingDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
1983
2007
|
const dependencyEdits = [];
|
|
1984
2008
|
for (const sdk of SDK_PACKAGES) {
|
|
1985
|
-
if (sdk.name in existingDeps)
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
});
|
|
2009
|
+
if (sdk.name in existingDeps) {
|
|
2010
|
+
if (needsVersionUpgrade(existingDeps[sdk.name], sdk.version)) {
|
|
2011
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: sdk.name, version: sdk.version, fromVersion: existingDeps[sdk.name] });
|
|
2012
|
+
}
|
|
2013
|
+
continue;
|
|
2014
|
+
}
|
|
2015
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: sdk.name, version: sdk.version });
|
|
1992
2016
|
}
|
|
1993
2017
|
const nonBundled = detectNonBundledInstrumentations(pkg);
|
|
1994
2018
|
for (const inst of nonBundled) {
|
|
1995
|
-
if (inst.pkg in existingDeps)
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
});
|
|
2019
|
+
if (inst.pkg in existingDeps) {
|
|
2020
|
+
if (needsVersionUpgrade(existingDeps[inst.pkg], inst.version)) {
|
|
2021
|
+
dependencyEdits.push({ file: manifestPath, kind: "upgrade", name: inst.pkg, version: inst.version, fromVersion: existingDeps[inst.pkg] });
|
|
2022
|
+
}
|
|
2023
|
+
continue;
|
|
2024
|
+
}
|
|
2025
|
+
dependencyEdits.push({ file: manifestPath, kind: "add", name: inst.pkg, version: inst.version });
|
|
2002
2026
|
}
|
|
2003
2027
|
const entrypointEdits = [];
|
|
2004
2028
|
try {
|
|
@@ -2101,6 +2125,28 @@ async function apply(installPlan) {
|
|
|
2101
2125
|
writtenFiles: []
|
|
2102
2126
|
};
|
|
2103
2127
|
}
|
|
2128
|
+
if (installPlan.runtimeKind === "bun") {
|
|
2129
|
+
return { serviceDir, outcome: "bun", reason: "Bun runtime; use BYO-OTel", writtenFiles: [] };
|
|
2130
|
+
}
|
|
2131
|
+
if (installPlan.runtimeKind === "deno") {
|
|
2132
|
+
return { serviceDir, outcome: "deno", reason: "Deno runtime; use BYO-OTel", writtenFiles: [] };
|
|
2133
|
+
}
|
|
2134
|
+
if (installPlan.runtimeKind === "cloudflare-workers") {
|
|
2135
|
+
return {
|
|
2136
|
+
serviceDir,
|
|
2137
|
+
outcome: "cloudflare-workers",
|
|
2138
|
+
reason: "Cloudflare Workers runtime; use BYO-OTel",
|
|
2139
|
+
writtenFiles: []
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
if (installPlan.runtimeKind === "electron") {
|
|
2143
|
+
return {
|
|
2144
|
+
serviceDir,
|
|
2145
|
+
outcome: "electron",
|
|
2146
|
+
reason: "Electron; use BYO-OTel",
|
|
2147
|
+
writtenFiles: []
|
|
2148
|
+
};
|
|
2149
|
+
}
|
|
2104
2150
|
if (installPlan.dependencyEdits.length === 0 && installPlan.entrypointEdits.length === 0 && (installPlan.generatedFiles?.length ?? 0) === 0 && installPlan.nextConfigEdit === void 0) {
|
|
2105
2151
|
return {
|
|
2106
2152
|
serviceDir,
|
|
@@ -2151,6 +2197,8 @@ async function apply(installPlan) {
|
|
|
2151
2197
|
if (!(dep.name in (pkg.dependencies ?? {}))) {
|
|
2152
2198
|
pkg.dependencies[dep.name] = dep.version;
|
|
2153
2199
|
}
|
|
2200
|
+
} else if (dep.kind === "upgrade") {
|
|
2201
|
+
pkg.dependencies[dep.name] = dep.version;
|
|
2154
2202
|
} else {
|
|
2155
2203
|
delete pkg.dependencies[dep.name];
|
|
2156
2204
|
}
|
|
@@ -2551,99 +2599,19 @@ function renderPatch(sections) {
|
|
|
2551
2599
|
}
|
|
2552
2600
|
|
|
2553
2601
|
// src/orchestrator.ts
|
|
2554
|
-
import { promises as
|
|
2602
|
+
import { promises as fs6 } from "fs";
|
|
2555
2603
|
import http from "http";
|
|
2556
2604
|
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
2605
|
import path6 from "path";
|
|
2564
2606
|
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
|
|
2607
|
+
import readline from "readline";
|
|
2640
2608
|
async function extractAndPersist(opts) {
|
|
2641
2609
|
const services = await discoverServices(opts.scanPath);
|
|
2642
2610
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
2643
2611
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
2644
2612
|
resetGraph(graphKey);
|
|
2645
2613
|
const graph = getGraph(graphKey);
|
|
2646
|
-
const projectPaths = pathsForProject(graphKey,
|
|
2614
|
+
const projectPaths = pathsForProject(graphKey, path6.join(opts.scanPath, "neat-out"));
|
|
2647
2615
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
2648
2616
|
errorsPath: projectPaths.errorsPath
|
|
2649
2617
|
});
|
|
@@ -2669,6 +2637,10 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2669
2637
|
let libOnly = 0;
|
|
2670
2638
|
let browserBundle = 0;
|
|
2671
2639
|
let reactNative = 0;
|
|
2640
|
+
let bun = 0;
|
|
2641
|
+
let deno = 0;
|
|
2642
|
+
let cloudflareWorkers = 0;
|
|
2643
|
+
let electron = 0;
|
|
2672
2644
|
const installPlans = /* @__PURE__ */ new Map();
|
|
2673
2645
|
for (const svc of services) {
|
|
2674
2646
|
const installer = await pickInstaller(svc.dir);
|
|
@@ -2693,7 +2665,59 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2693
2665
|
console.log(`skipping ${svc.dir}: browser bundle; browser-OTel support lands in a future release.`);
|
|
2694
2666
|
} else if (outcome.outcome === "react-native") {
|
|
2695
2667
|
reactNative++;
|
|
2696
|
-
|
|
2668
|
+
const svcName = path6.basename(svc.dir);
|
|
2669
|
+
console.log(
|
|
2670
|
+
`neat: ${svc.dir} detected as React Native / Expo
|
|
2671
|
+
The installer doesn't cover this runtime deterministically.
|
|
2672
|
+
Configure your OTel binding to send spans to:
|
|
2673
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
2674
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
2675
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
2676
|
+
);
|
|
2677
|
+
} else if (outcome.outcome === "bun") {
|
|
2678
|
+
bun++;
|
|
2679
|
+
const svcName = path6.basename(svc.dir);
|
|
2680
|
+
console.log(
|
|
2681
|
+
`neat: ${svc.dir} detected as Bun
|
|
2682
|
+
The installer doesn't cover this runtime deterministically.
|
|
2683
|
+
Configure your OTel binding to send spans to:
|
|
2684
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
2685
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
2686
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
2687
|
+
);
|
|
2688
|
+
} else if (outcome.outcome === "deno") {
|
|
2689
|
+
deno++;
|
|
2690
|
+
const svcName = path6.basename(svc.dir);
|
|
2691
|
+
console.log(
|
|
2692
|
+
`neat: ${svc.dir} detected as Deno
|
|
2693
|
+
The installer doesn't cover this runtime deterministically.
|
|
2694
|
+
Configure your OTel binding to send spans to:
|
|
2695
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
2696
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
2697
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
2698
|
+
);
|
|
2699
|
+
} else if (outcome.outcome === "cloudflare-workers") {
|
|
2700
|
+
cloudflareWorkers++;
|
|
2701
|
+
const svcName = path6.basename(svc.dir);
|
|
2702
|
+
console.log(
|
|
2703
|
+
`neat: ${svc.dir} detected as Cloudflare Workers
|
|
2704
|
+
The installer doesn't cover this runtime deterministically.
|
|
2705
|
+
Configure your OTel binding to send spans to:
|
|
2706
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
2707
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
2708
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
2709
|
+
);
|
|
2710
|
+
} else if (outcome.outcome === "electron") {
|
|
2711
|
+
electron++;
|
|
2712
|
+
const svcName = path6.basename(svc.dir);
|
|
2713
|
+
console.log(
|
|
2714
|
+
`neat: ${svc.dir} detected as Electron
|
|
2715
|
+
The installer doesn't cover this runtime deterministically.
|
|
2716
|
+
Configure your OTel binding to send spans to:
|
|
2717
|
+
http://localhost:4318/projects/${project}/v1/traces
|
|
2718
|
+
Set OTEL_SERVICE_NAME=${svcName}
|
|
2719
|
+
See docs/installer-scope.md \u2192 "Manual setup for out-of-scope runtimes"`
|
|
2720
|
+
);
|
|
2697
2721
|
}
|
|
2698
2722
|
}
|
|
2699
2723
|
const packageManagerInstalls = [];
|
|
@@ -2718,6 +2742,10 @@ async function applyInstallersOver(services, project, options = {}) {
|
|
|
2718
2742
|
libOnly,
|
|
2719
2743
|
browserBundle,
|
|
2720
2744
|
reactNative,
|
|
2745
|
+
bun,
|
|
2746
|
+
deno,
|
|
2747
|
+
cloudflareWorkers,
|
|
2748
|
+
electron,
|
|
2721
2749
|
packageManagerInstalls
|
|
2722
2750
|
};
|
|
2723
2751
|
}
|
|
@@ -2855,10 +2883,10 @@ function formatPortCollisionMessage(port) {
|
|
|
2855
2883
|
];
|
|
2856
2884
|
}
|
|
2857
2885
|
function spawnDaemonDetached() {
|
|
2858
|
-
const here =
|
|
2886
|
+
const here = path6.dirname(new URL(import.meta.url).pathname);
|
|
2859
2887
|
const candidates = [
|
|
2860
|
-
|
|
2861
|
-
|
|
2888
|
+
path6.join(here, "neatd.cjs"),
|
|
2889
|
+
path6.join(here, "neatd.js")
|
|
2862
2890
|
];
|
|
2863
2891
|
let entry2 = null;
|
|
2864
2892
|
const fsSync = __require("fs");
|
|
@@ -2878,7 +2906,7 @@ function spawnDaemonDetached() {
|
|
|
2878
2906
|
if (!hasToken && (!env.HOST || env.HOST.length === 0)) {
|
|
2879
2907
|
env.HOST = "127.0.0.1";
|
|
2880
2908
|
}
|
|
2881
|
-
const child =
|
|
2909
|
+
const child = spawn2(process.execPath, [entry2, "start"], {
|
|
2882
2910
|
detached: true,
|
|
2883
2911
|
// stderr inherits the orchestrator's fd so the daemon's
|
|
2884
2912
|
// `BindAuthorityError` message lands in front of the operator instead
|
|
@@ -2895,7 +2923,7 @@ function openBrowser(url) {
|
|
|
2895
2923
|
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
2896
2924
|
const args = platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
2897
2925
|
try {
|
|
2898
|
-
const child =
|
|
2926
|
+
const child = spawn2(cmd, args, { detached: true, stdio: "ignore" });
|
|
2899
2927
|
child.on("error", () => {
|
|
2900
2928
|
});
|
|
2901
2929
|
child.unref();
|
|
@@ -2916,7 +2944,7 @@ async function runOrchestrator(opts) {
|
|
|
2916
2944
|
browser: "skipped"
|
|
2917
2945
|
}
|
|
2918
2946
|
};
|
|
2919
|
-
const stat = await
|
|
2947
|
+
const stat = await fs6.stat(opts.scanPath).catch(() => null);
|
|
2920
2948
|
if (!stat || !stat.isDirectory()) {
|
|
2921
2949
|
console.error(`neat: ${opts.scanPath} is not a directory`);
|
|
2922
2950
|
result.exitCode = 2;
|
|
@@ -3058,7 +3086,7 @@ function printSummary(result, graph, dashboardUrl) {
|
|
|
3058
3086
|
}
|
|
3059
3087
|
|
|
3060
3088
|
// src/cli-verbs.ts
|
|
3061
|
-
import
|
|
3089
|
+
import path7 from "path";
|
|
3062
3090
|
|
|
3063
3091
|
// src/cli-client.ts
|
|
3064
3092
|
import { Provenance as Provenance2 } from "@neat.is/types";
|
|
@@ -3086,10 +3114,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3086
3114
|
const root = baseUrl.replace(/\/$/, "");
|
|
3087
3115
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
3088
3116
|
return {
|
|
3089
|
-
async get(
|
|
3117
|
+
async get(path9) {
|
|
3090
3118
|
let res;
|
|
3091
3119
|
try {
|
|
3092
|
-
res = await fetch(`${root}${
|
|
3120
|
+
res = await fetch(`${root}${path9}`, {
|
|
3093
3121
|
headers: { ...authHeader }
|
|
3094
3122
|
});
|
|
3095
3123
|
} catch (err) {
|
|
@@ -3101,16 +3129,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3101
3129
|
const body = await res.text().catch(() => "");
|
|
3102
3130
|
throw new HttpError(
|
|
3103
3131
|
res.status,
|
|
3104
|
-
`${res.status} ${res.statusText} on GET ${
|
|
3132
|
+
`${res.status} ${res.statusText} on GET ${path9}: ${body}`,
|
|
3105
3133
|
body
|
|
3106
3134
|
);
|
|
3107
3135
|
}
|
|
3108
3136
|
return await res.json();
|
|
3109
3137
|
},
|
|
3110
|
-
async post(
|
|
3138
|
+
async post(path9, body) {
|
|
3111
3139
|
let res;
|
|
3112
3140
|
try {
|
|
3113
|
-
res = await fetch(`${root}${
|
|
3141
|
+
res = await fetch(`${root}${path9}`, {
|
|
3114
3142
|
method: "POST",
|
|
3115
3143
|
headers: { "content-type": "application/json", ...authHeader },
|
|
3116
3144
|
body: JSON.stringify(body)
|
|
@@ -3124,7 +3152,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
3124
3152
|
const text = await res.text().catch(() => "");
|
|
3125
3153
|
throw new HttpError(
|
|
3126
3154
|
res.status,
|
|
3127
|
-
`${res.status} ${res.statusText} on POST ${
|
|
3155
|
+
`${res.status} ${res.statusText} on POST ${path9}: ${text}`,
|
|
3128
3156
|
text
|
|
3129
3157
|
);
|
|
3130
3158
|
}
|
|
@@ -3138,12 +3166,12 @@ function projectPath(project, suffix) {
|
|
|
3138
3166
|
}
|
|
3139
3167
|
async function runRootCause(client, input) {
|
|
3140
3168
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
3141
|
-
const
|
|
3169
|
+
const path9 = projectPath(
|
|
3142
3170
|
input.project,
|
|
3143
3171
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
3144
3172
|
);
|
|
3145
3173
|
try {
|
|
3146
|
-
const result = await client.get(
|
|
3174
|
+
const result = await client.get(path9);
|
|
3147
3175
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
3148
3176
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
3149
3177
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -3169,12 +3197,12 @@ async function runRootCause(client, input) {
|
|
|
3169
3197
|
}
|
|
3170
3198
|
async function runBlastRadius(client, input) {
|
|
3171
3199
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
3172
|
-
const
|
|
3200
|
+
const path9 = projectPath(
|
|
3173
3201
|
input.project,
|
|
3174
3202
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
3175
3203
|
);
|
|
3176
3204
|
try {
|
|
3177
|
-
const result = await client.get(
|
|
3205
|
+
const result = await client.get(path9);
|
|
3178
3206
|
if (result.totalAffected === 0) {
|
|
3179
3207
|
return {
|
|
3180
3208
|
summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
|
|
@@ -3208,12 +3236,12 @@ function formatBlastEntry(n) {
|
|
|
3208
3236
|
}
|
|
3209
3237
|
async function runDependencies(client, input) {
|
|
3210
3238
|
const depth = input.depth ?? 3;
|
|
3211
|
-
const
|
|
3239
|
+
const path9 = projectPath(
|
|
3212
3240
|
input.project,
|
|
3213
3241
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
3214
3242
|
);
|
|
3215
3243
|
try {
|
|
3216
|
-
const result = await client.get(
|
|
3244
|
+
const result = await client.get(path9);
|
|
3217
3245
|
if (result.total === 0) {
|
|
3218
3246
|
return {
|
|
3219
3247
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -3294,9 +3322,9 @@ function formatDuration(ms) {
|
|
|
3294
3322
|
return `${Math.round(h / 24)}d`;
|
|
3295
3323
|
}
|
|
3296
3324
|
async function runIncidents(client, input) {
|
|
3297
|
-
const
|
|
3325
|
+
const path9 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
3298
3326
|
try {
|
|
3299
|
-
const body = await client.get(
|
|
3327
|
+
const body = await client.get(path9);
|
|
3300
3328
|
const events = body.events;
|
|
3301
3329
|
if (events.length === 0) {
|
|
3302
3330
|
return {
|
|
@@ -3586,7 +3614,7 @@ async function resolveProjectEntry(opts) {
|
|
|
3586
3614
|
const cwd = opts.cwd ?? process.cwd();
|
|
3587
3615
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
3588
3616
|
for (const entry2 of entries) {
|
|
3589
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
3617
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path7.sep}`)) {
|
|
3590
3618
|
return entry2;
|
|
3591
3619
|
}
|
|
3592
3620
|
}
|
|
@@ -3946,10 +3974,10 @@ function assignFlag(out, field, value) {
|
|
|
3946
3974
|
out[field] = value;
|
|
3947
3975
|
}
|
|
3948
3976
|
function readPackageVersion() {
|
|
3949
|
-
const here = typeof __dirname !== "undefined" ? __dirname :
|
|
3977
|
+
const here = typeof __dirname !== "undefined" ? __dirname : path8.dirname(fileURLToPath(import.meta.url));
|
|
3950
3978
|
const candidates = [
|
|
3951
|
-
|
|
3952
|
-
|
|
3979
|
+
path8.resolve(here, "../package.json"),
|
|
3980
|
+
path8.resolve(here, "../../package.json")
|
|
3953
3981
|
];
|
|
3954
3982
|
for (const candidate of candidates) {
|
|
3955
3983
|
try {
|
|
@@ -4017,7 +4045,7 @@ async function buildPatchSections(services, project) {
|
|
|
4017
4045
|
}
|
|
4018
4046
|
async function runInit(opts) {
|
|
4019
4047
|
const written = [];
|
|
4020
|
-
const stat = await
|
|
4048
|
+
const stat = await fs7.stat(opts.scanPath).catch(() => null);
|
|
4021
4049
|
if (!stat || !stat.isDirectory()) {
|
|
4022
4050
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
4023
4051
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -4026,13 +4054,13 @@ async function runInit(opts) {
|
|
|
4026
4054
|
printDiscoveryReport(opts, services);
|
|
4027
4055
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
4028
4056
|
const patch = renderPatch(sections);
|
|
4029
|
-
const patchPath =
|
|
4057
|
+
const patchPath = path8.join(opts.scanPath, "neat.patch");
|
|
4030
4058
|
if (opts.dryRun) {
|
|
4031
|
-
await
|
|
4059
|
+
await fs7.writeFile(patchPath, patch, "utf8");
|
|
4032
4060
|
written.push(patchPath);
|
|
4033
4061
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
4034
|
-
const gitignorePath =
|
|
4035
|
-
const gitignoreExists = await
|
|
4062
|
+
const gitignorePath = path8.join(opts.scanPath, ".gitignore");
|
|
4063
|
+
const gitignoreExists = await fs7.stat(gitignorePath).then(() => true).catch(() => false);
|
|
4036
4064
|
const verb = gitignoreExists ? "append" : "create";
|
|
4037
4065
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
4038
4066
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -4043,9 +4071,9 @@ async function runInit(opts) {
|
|
|
4043
4071
|
const graph = getGraph(graphKey);
|
|
4044
4072
|
const projectPaths = pathsForProject(
|
|
4045
4073
|
graphKey,
|
|
4046
|
-
|
|
4074
|
+
path8.join(opts.scanPath, "neat-out")
|
|
4047
4075
|
);
|
|
4048
|
-
const errorsPath =
|
|
4076
|
+
const errorsPath = path8.join(path8.dirname(opts.outPath), path8.basename(projectPaths.errorsPath));
|
|
4049
4077
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
4050
4078
|
await saveGraphToDisk(graph, opts.outPath);
|
|
4051
4079
|
written.push(opts.outPath);
|
|
@@ -4124,7 +4152,7 @@ async function runInit(opts) {
|
|
|
4124
4152
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
4125
4153
|
}
|
|
4126
4154
|
} else {
|
|
4127
|
-
await
|
|
4155
|
+
await fs7.writeFile(patchPath, patch, "utf8");
|
|
4128
4156
|
written.push(patchPath);
|
|
4129
4157
|
}
|
|
4130
4158
|
}
|
|
@@ -4164,9 +4192,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
4164
4192
|
};
|
|
4165
4193
|
function claudeConfigPath() {
|
|
4166
4194
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
4167
|
-
if (override && override.length > 0) return
|
|
4195
|
+
if (override && override.length > 0) return path8.resolve(override);
|
|
4168
4196
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
4169
|
-
return
|
|
4197
|
+
return path8.join(home, ".claude.json");
|
|
4170
4198
|
}
|
|
4171
4199
|
async function runSkill(opts) {
|
|
4172
4200
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -4178,7 +4206,7 @@ async function runSkill(opts) {
|
|
|
4178
4206
|
const target = claudeConfigPath();
|
|
4179
4207
|
let existing = {};
|
|
4180
4208
|
try {
|
|
4181
|
-
existing = JSON.parse(await
|
|
4209
|
+
existing = JSON.parse(await fs7.readFile(target, "utf8"));
|
|
4182
4210
|
} catch (err) {
|
|
4183
4211
|
if (err.code !== "ENOENT") {
|
|
4184
4212
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -4190,8 +4218,8 @@ async function runSkill(opts) {
|
|
|
4190
4218
|
...existing,
|
|
4191
4219
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
4192
4220
|
};
|
|
4193
|
-
await
|
|
4194
|
-
await
|
|
4221
|
+
await fs7.mkdir(path8.dirname(target), { recursive: true });
|
|
4222
|
+
await fs7.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
4195
4223
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
4196
4224
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
4197
4225
|
return { exitCode: 0 };
|
|
@@ -4229,12 +4257,12 @@ async function main() {
|
|
|
4229
4257
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
4230
4258
|
process.exit(2);
|
|
4231
4259
|
}
|
|
4232
|
-
const scanPath =
|
|
4260
|
+
const scanPath = path8.resolve(target);
|
|
4233
4261
|
const projectExplicit = parsed.project !== null;
|
|
4234
|
-
const projectName = projectExplicit ? project :
|
|
4262
|
+
const projectName = projectExplicit ? project : path8.basename(scanPath);
|
|
4235
4263
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
4236
|
-
const fallback = pathsForProject(projectKey,
|
|
4237
|
-
const outPath =
|
|
4264
|
+
const fallback = pathsForProject(projectKey, path8.join(scanPath, "neat-out")).snapshotPath;
|
|
4265
|
+
const outPath = path8.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
4238
4266
|
const result = await runInit({
|
|
4239
4267
|
scanPath,
|
|
4240
4268
|
outPath,
|
|
@@ -4255,21 +4283,21 @@ async function main() {
|
|
|
4255
4283
|
usage();
|
|
4256
4284
|
process.exit(2);
|
|
4257
4285
|
}
|
|
4258
|
-
const scanPath =
|
|
4259
|
-
const stat = await
|
|
4286
|
+
const scanPath = path8.resolve(target);
|
|
4287
|
+
const stat = await fs7.stat(scanPath).catch(() => null);
|
|
4260
4288
|
if (!stat || !stat.isDirectory()) {
|
|
4261
4289
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
4262
4290
|
process.exit(2);
|
|
4263
4291
|
}
|
|
4264
|
-
const projectPaths = pathsForProject(project,
|
|
4265
|
-
const outPath =
|
|
4266
|
-
const errorsPath =
|
|
4267
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
4292
|
+
const projectPaths = pathsForProject(project, path8.join(scanPath, "neat-out"));
|
|
4293
|
+
const outPath = path8.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
4294
|
+
const errorsPath = path8.resolve(
|
|
4295
|
+
process.env.NEAT_ERRORS_PATH ?? path8.join(path8.dirname(outPath), path8.basename(projectPaths.errorsPath))
|
|
4268
4296
|
);
|
|
4269
|
-
const staleEventsPath =
|
|
4270
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
4297
|
+
const staleEventsPath = path8.resolve(
|
|
4298
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? path8.join(path8.dirname(outPath), path8.basename(projectPaths.staleEventsPath))
|
|
4271
4299
|
);
|
|
4272
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
4300
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path8.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
4273
4301
|
const handle = await startWatch(getGraph(project), {
|
|
4274
4302
|
scanPath,
|
|
4275
4303
|
outPath,
|
|
@@ -4415,11 +4443,11 @@ async function main() {
|
|
|
4415
4443
|
process.exit(1);
|
|
4416
4444
|
}
|
|
4417
4445
|
async function tryOrchestrator(cmd, parsed) {
|
|
4418
|
-
const scanPath =
|
|
4419
|
-
const stat = await
|
|
4446
|
+
const scanPath = path8.resolve(cmd);
|
|
4447
|
+
const stat = await fs7.stat(scanPath).catch(() => null);
|
|
4420
4448
|
if (!stat || !stat.isDirectory()) return null;
|
|
4421
4449
|
const projectExplicit = parsed.project !== null;
|
|
4422
|
-
const projectName = projectExplicit ? parsed.project :
|
|
4450
|
+
const projectName = projectExplicit ? parsed.project : path8.basename(scanPath);
|
|
4423
4451
|
const result = await runOrchestrator({
|
|
4424
4452
|
scanPath,
|
|
4425
4453
|
project: projectName,
|