@neat.is/core 0.4.5 → 0.4.6
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-6GXBAR3M.js → chunk-NON5AXOR.js} +36 -2
- package/dist/chunk-NON5AXOR.js.map +1 -0
- package/dist/cli.cjs +198 -74
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +187 -64
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +34 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/neatd.cjs +34 -0
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-6GXBAR3M.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -52,8 +52,8 @@ import {
|
|
|
52
52
|
} from "./chunk-HVF4S7J3.js";
|
|
53
53
|
|
|
54
54
|
// src/cli.ts
|
|
55
|
-
import
|
|
56
|
-
import { promises as
|
|
55
|
+
import path9 from "path";
|
|
56
|
+
import { promises as fs8, readFileSync } from "fs";
|
|
57
57
|
import { fileURLToPath } from "url";
|
|
58
58
|
|
|
59
59
|
// src/gitignore.ts
|
|
@@ -877,13 +877,20 @@ var SDK_PACKAGES = [
|
|
|
877
877
|
{ name: "@opentelemetry/sdk-node", version: "^0.57.0" },
|
|
878
878
|
{ name: "@opentelemetry/auto-instrumentations-node", version: "^0.55.0" }
|
|
879
879
|
];
|
|
880
|
+
function getMajor(versionRange) {
|
|
881
|
+
if (!versionRange) return 0;
|
|
882
|
+
const match = versionRange.match(/(\d+)/);
|
|
883
|
+
return match ? parseInt(match[1], 10) : 0;
|
|
884
|
+
}
|
|
880
885
|
function detectNonBundledInstrumentations(pkg) {
|
|
881
886
|
const deps = allDeps(pkg);
|
|
882
887
|
const out = [];
|
|
883
888
|
if ("@prisma/client" in deps) {
|
|
889
|
+
const prismaMajor = getMajor(deps["@prisma/client"]);
|
|
890
|
+
const prismaInstrVersion = prismaMajor >= 6 ? "^6.0.0" : "^5.0.0";
|
|
884
891
|
out.push({
|
|
885
892
|
pkg: "@prisma/instrumentation",
|
|
886
|
-
version:
|
|
893
|
+
version: prismaInstrVersion,
|
|
887
894
|
registration: "instrumentations.push(new (require('@prisma/instrumentation').PrismaInstrumentation)())"
|
|
888
895
|
});
|
|
889
896
|
}
|
|
@@ -2209,19 +2216,99 @@ function renderPatch(sections) {
|
|
|
2209
2216
|
}
|
|
2210
2217
|
|
|
2211
2218
|
// src/orchestrator.ts
|
|
2212
|
-
import { promises as
|
|
2219
|
+
import { promises as fs7 } from "fs";
|
|
2213
2220
|
import http from "http";
|
|
2214
2221
|
import net from "net";
|
|
2222
|
+
import path7 from "path";
|
|
2223
|
+
import { spawn as spawn3 } from "child_process";
|
|
2224
|
+
import readline from "readline";
|
|
2225
|
+
|
|
2226
|
+
// src/installers/package-manager.ts
|
|
2227
|
+
import { promises as fs6 } from "fs";
|
|
2215
2228
|
import path6 from "path";
|
|
2216
2229
|
import { spawn as spawn2 } from "child_process";
|
|
2217
|
-
|
|
2230
|
+
var LOCKFILE_PRIORITY = [
|
|
2231
|
+
{ lockfile: "bun.lockb", pm: "bun", args: ["install", "--no-summary"] },
|
|
2232
|
+
{ lockfile: "pnpm-lock.yaml", pm: "pnpm", args: ["install", "--no-summary"] },
|
|
2233
|
+
{ lockfile: "yarn.lock", pm: "yarn", args: ["install", "--silent"] },
|
|
2234
|
+
{
|
|
2235
|
+
lockfile: "package-lock.json",
|
|
2236
|
+
pm: "npm",
|
|
2237
|
+
args: ["install", "--no-audit", "--no-fund", "--prefer-offline"]
|
|
2238
|
+
}
|
|
2239
|
+
];
|
|
2240
|
+
var NPM_FALLBACK_ARGS = ["install", "--no-audit", "--no-fund", "--prefer-offline"];
|
|
2241
|
+
async function exists3(p) {
|
|
2242
|
+
try {
|
|
2243
|
+
await fs6.access(p);
|
|
2244
|
+
return true;
|
|
2245
|
+
} catch {
|
|
2246
|
+
return false;
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
async function detectPackageManager(serviceDir) {
|
|
2250
|
+
let dir = path6.resolve(serviceDir);
|
|
2251
|
+
const stops = /* @__PURE__ */ new Set();
|
|
2252
|
+
for (let i = 0; i < 64; i++) {
|
|
2253
|
+
if (stops.has(dir)) break;
|
|
2254
|
+
stops.add(dir);
|
|
2255
|
+
for (const candidate of LOCKFILE_PRIORITY) {
|
|
2256
|
+
const lockPath = path6.join(dir, candidate.lockfile);
|
|
2257
|
+
if (await exists3(lockPath)) {
|
|
2258
|
+
return { pm: candidate.pm, cwd: dir, args: [...candidate.args] };
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
const parent = path6.dirname(dir);
|
|
2262
|
+
if (parent === dir) break;
|
|
2263
|
+
dir = parent;
|
|
2264
|
+
}
|
|
2265
|
+
return { pm: "npm", cwd: path6.resolve(serviceDir), args: [...NPM_FALLBACK_ARGS] };
|
|
2266
|
+
}
|
|
2267
|
+
async function runPackageManagerInstall(cmd) {
|
|
2268
|
+
return new Promise((resolve) => {
|
|
2269
|
+
const child = spawn2(cmd.pm, cmd.args, {
|
|
2270
|
+
cwd: cmd.cwd,
|
|
2271
|
+
// Inherit PATH + HOME so the user's installed managers resolve.
|
|
2272
|
+
env: process.env,
|
|
2273
|
+
// `false` keeps the parent in control of cleanup if the orchestrator
|
|
2274
|
+
// exits before install finishes. Cross-platform-safe.
|
|
2275
|
+
shell: false,
|
|
2276
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
2277
|
+
});
|
|
2278
|
+
let stderr = "";
|
|
2279
|
+
child.stderr?.on("data", (chunk) => {
|
|
2280
|
+
stderr += chunk.toString("utf8");
|
|
2281
|
+
});
|
|
2282
|
+
child.on("error", (err) => {
|
|
2283
|
+
resolve({
|
|
2284
|
+
pm: cmd.pm,
|
|
2285
|
+
cwd: cmd.cwd,
|
|
2286
|
+
args: cmd.args,
|
|
2287
|
+
exitCode: 127,
|
|
2288
|
+
stderr: stderr + `
|
|
2289
|
+
${err.message}`
|
|
2290
|
+
});
|
|
2291
|
+
});
|
|
2292
|
+
child.on("close", (code) => {
|
|
2293
|
+
resolve({
|
|
2294
|
+
pm: cmd.pm,
|
|
2295
|
+
cwd: cmd.cwd,
|
|
2296
|
+
args: cmd.args,
|
|
2297
|
+
exitCode: code ?? 1,
|
|
2298
|
+
stderr: stderr.trim()
|
|
2299
|
+
});
|
|
2300
|
+
});
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
// src/orchestrator.ts
|
|
2218
2305
|
async function extractAndPersist(opts) {
|
|
2219
2306
|
const services = await discoverServices(opts.scanPath);
|
|
2220
2307
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
2221
2308
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
2222
2309
|
resetGraph(graphKey);
|
|
2223
2310
|
const graph = getGraph(graphKey);
|
|
2224
|
-
const projectPaths = pathsForProject(graphKey,
|
|
2311
|
+
const projectPaths = pathsForProject(graphKey, path7.join(opts.scanPath, "neat-out"));
|
|
2225
2312
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
2226
2313
|
errorsPath: projectPaths.errorsPath
|
|
2227
2314
|
});
|
|
@@ -2239,12 +2326,15 @@ async function extractAndPersist(opts) {
|
|
|
2239
2326
|
errorsPath: projectPaths.errorsPath
|
|
2240
2327
|
};
|
|
2241
2328
|
}
|
|
2242
|
-
async function applyInstallersOver(services, project) {
|
|
2329
|
+
async function applyInstallersOver(services, project, options = {}) {
|
|
2330
|
+
const resolveManager = options.resolveManager ?? detectPackageManager;
|
|
2331
|
+
const runInstall = options.runInstall ?? runPackageManagerInstall;
|
|
2243
2332
|
let instrumented = 0;
|
|
2244
2333
|
let already = 0;
|
|
2245
2334
|
let libOnly = 0;
|
|
2246
2335
|
let browserBundle = 0;
|
|
2247
2336
|
let reactNative = 0;
|
|
2337
|
+
const installPlans = /* @__PURE__ */ new Map();
|
|
2248
2338
|
for (const svc of services) {
|
|
2249
2339
|
const installer = await pickInstaller(svc.dir);
|
|
2250
2340
|
if (!installer) continue;
|
|
@@ -2254,8 +2344,14 @@ async function applyInstallersOver(services, project) {
|
|
|
2254
2344
|
continue;
|
|
2255
2345
|
}
|
|
2256
2346
|
const outcome = await installer.apply(plan3);
|
|
2257
|
-
if (outcome.outcome === "instrumented")
|
|
2258
|
-
|
|
2347
|
+
if (outcome.outcome === "instrumented") {
|
|
2348
|
+
instrumented++;
|
|
2349
|
+
if (plan3.dependencyEdits.length > 0) {
|
|
2350
|
+
const cmd = await resolveManager(svc.dir);
|
|
2351
|
+
const key = `${cmd.pm}:${cmd.cwd}`;
|
|
2352
|
+
if (!installPlans.has(key)) installPlans.set(key, cmd);
|
|
2353
|
+
}
|
|
2354
|
+
} else if (outcome.outcome === "already-instrumented") already++;
|
|
2259
2355
|
else if (outcome.outcome === "lib-only") libOnly++;
|
|
2260
2356
|
else if (outcome.outcome === "browser-bundle") {
|
|
2261
2357
|
browserBundle++;
|
|
@@ -2265,7 +2361,30 @@ async function applyInstallersOver(services, project) {
|
|
|
2265
2361
|
console.log(`skipping ${svc.dir}: React Native target; browser-OTel support lands in a future release.`);
|
|
2266
2362
|
}
|
|
2267
2363
|
}
|
|
2268
|
-
|
|
2364
|
+
const packageManagerInstalls = [];
|
|
2365
|
+
for (const cmd of installPlans.values()) {
|
|
2366
|
+
console.log(`running \`${cmd.pm} ${cmd.args.join(" ")}\` in ${cmd.cwd}`);
|
|
2367
|
+
const result = await runInstall(cmd);
|
|
2368
|
+
packageManagerInstalls.push(result);
|
|
2369
|
+
if (result.exitCode !== 0) {
|
|
2370
|
+
console.error(
|
|
2371
|
+
`neat: ${cmd.pm} install failed in ${cmd.cwd} (exit ${result.exitCode}); run it manually to surface the error.`
|
|
2372
|
+
);
|
|
2373
|
+
if (result.stderr.length > 0) {
|
|
2374
|
+
for (const line of result.stderr.split(/\r?\n/).slice(0, 20)) {
|
|
2375
|
+
console.error(` ${line}`);
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
return {
|
|
2381
|
+
instrumented,
|
|
2382
|
+
alreadyInstrumented: already,
|
|
2383
|
+
libOnly,
|
|
2384
|
+
browserBundle,
|
|
2385
|
+
reactNative,
|
|
2386
|
+
packageManagerInstalls
|
|
2387
|
+
};
|
|
2269
2388
|
}
|
|
2270
2389
|
async function promptYesNo(question) {
|
|
2271
2390
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -2401,10 +2520,10 @@ function formatPortCollisionMessage(port) {
|
|
|
2401
2520
|
];
|
|
2402
2521
|
}
|
|
2403
2522
|
function spawnDaemonDetached() {
|
|
2404
|
-
const here =
|
|
2523
|
+
const here = path7.dirname(new URL(import.meta.url).pathname);
|
|
2405
2524
|
const candidates = [
|
|
2406
|
-
|
|
2407
|
-
|
|
2525
|
+
path7.join(here, "neatd.cjs"),
|
|
2526
|
+
path7.join(here, "neatd.js")
|
|
2408
2527
|
];
|
|
2409
2528
|
let entry2 = null;
|
|
2410
2529
|
const fsSync = __require("fs");
|
|
@@ -2424,7 +2543,7 @@ function spawnDaemonDetached() {
|
|
|
2424
2543
|
if (!hasToken && (!env.HOST || env.HOST.length === 0)) {
|
|
2425
2544
|
env.HOST = "127.0.0.1";
|
|
2426
2545
|
}
|
|
2427
|
-
const child =
|
|
2546
|
+
const child = spawn3(process.execPath, [entry2, "start"], {
|
|
2428
2547
|
detached: true,
|
|
2429
2548
|
// stderr inherits the orchestrator's fd so the daemon's
|
|
2430
2549
|
// `BindAuthorityError` message lands in front of the operator instead
|
|
@@ -2441,7 +2560,7 @@ function openBrowser(url) {
|
|
|
2441
2560
|
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
2442
2561
|
const args = platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
2443
2562
|
try {
|
|
2444
|
-
const child =
|
|
2563
|
+
const child = spawn3(cmd, args, { detached: true, stdio: "ignore" });
|
|
2445
2564
|
child.on("error", () => {
|
|
2446
2565
|
});
|
|
2447
2566
|
child.unref();
|
|
@@ -2462,7 +2581,7 @@ async function runOrchestrator(opts) {
|
|
|
2462
2581
|
browser: "skipped"
|
|
2463
2582
|
}
|
|
2464
2583
|
};
|
|
2465
|
-
const stat = await
|
|
2584
|
+
const stat = await fs7.stat(opts.scanPath).catch(() => null);
|
|
2466
2585
|
if (!stat || !stat.isDirectory()) {
|
|
2467
2586
|
console.error(`neat: ${opts.scanPath} is not a directory`);
|
|
2468
2587
|
result.exitCode = 2;
|
|
@@ -2530,6 +2649,10 @@ async function runOrchestrator(opts) {
|
|
|
2530
2649
|
console.log(
|
|
2531
2650
|
`instrumented ${tally.instrumented}, already ${tally.alreadyInstrumented}, lib-only ${tally.libOnly}`
|
|
2532
2651
|
);
|
|
2652
|
+
const failedInstalls = tally.packageManagerInstalls.filter((i) => i.exitCode !== 0);
|
|
2653
|
+
if (failedInstalls.length > 0) {
|
|
2654
|
+
result.exitCode = 1;
|
|
2655
|
+
}
|
|
2533
2656
|
}
|
|
2534
2657
|
const restPort = Number(process.env.PORT ?? 8080);
|
|
2535
2658
|
const timeoutMs = opts.daemonReadyTimeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
|
|
@@ -2600,7 +2723,7 @@ function printSummary(result, graph, dashboardUrl) {
|
|
|
2600
2723
|
}
|
|
2601
2724
|
|
|
2602
2725
|
// src/cli-verbs.ts
|
|
2603
|
-
import
|
|
2726
|
+
import path8 from "path";
|
|
2604
2727
|
|
|
2605
2728
|
// src/cli-client.ts
|
|
2606
2729
|
import { Provenance as Provenance2 } from "@neat.is/types";
|
|
@@ -2624,10 +2747,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
2624
2747
|
const root = baseUrl.replace(/\/$/, "");
|
|
2625
2748
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
2626
2749
|
return {
|
|
2627
|
-
async get(
|
|
2750
|
+
async get(path10) {
|
|
2628
2751
|
let res;
|
|
2629
2752
|
try {
|
|
2630
|
-
res = await fetch(`${root}${
|
|
2753
|
+
res = await fetch(`${root}${path10}`, {
|
|
2631
2754
|
headers: { ...authHeader }
|
|
2632
2755
|
});
|
|
2633
2756
|
} catch (err) {
|
|
@@ -2639,16 +2762,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
2639
2762
|
const body = await res.text().catch(() => "");
|
|
2640
2763
|
throw new HttpError(
|
|
2641
2764
|
res.status,
|
|
2642
|
-
`${res.status} ${res.statusText} on GET ${
|
|
2765
|
+
`${res.status} ${res.statusText} on GET ${path10}: ${body}`,
|
|
2643
2766
|
body
|
|
2644
2767
|
);
|
|
2645
2768
|
}
|
|
2646
2769
|
return await res.json();
|
|
2647
2770
|
},
|
|
2648
|
-
async post(
|
|
2771
|
+
async post(path10, body) {
|
|
2649
2772
|
let res;
|
|
2650
2773
|
try {
|
|
2651
|
-
res = await fetch(`${root}${
|
|
2774
|
+
res = await fetch(`${root}${path10}`, {
|
|
2652
2775
|
method: "POST",
|
|
2653
2776
|
headers: { "content-type": "application/json", ...authHeader },
|
|
2654
2777
|
body: JSON.stringify(body)
|
|
@@ -2662,7 +2785,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
2662
2785
|
const text = await res.text().catch(() => "");
|
|
2663
2786
|
throw new HttpError(
|
|
2664
2787
|
res.status,
|
|
2665
|
-
`${res.status} ${res.statusText} on POST ${
|
|
2788
|
+
`${res.status} ${res.statusText} on POST ${path10}: ${text}`,
|
|
2666
2789
|
text
|
|
2667
2790
|
);
|
|
2668
2791
|
}
|
|
@@ -2676,12 +2799,12 @@ function projectPath(project, suffix) {
|
|
|
2676
2799
|
}
|
|
2677
2800
|
async function runRootCause(client, input) {
|
|
2678
2801
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
2679
|
-
const
|
|
2802
|
+
const path10 = projectPath(
|
|
2680
2803
|
input.project,
|
|
2681
2804
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
2682
2805
|
);
|
|
2683
2806
|
try {
|
|
2684
|
-
const result = await client.get(
|
|
2807
|
+
const result = await client.get(path10);
|
|
2685
2808
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
2686
2809
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
2687
2810
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -2707,12 +2830,12 @@ async function runRootCause(client, input) {
|
|
|
2707
2830
|
}
|
|
2708
2831
|
async function runBlastRadius(client, input) {
|
|
2709
2832
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
2710
|
-
const
|
|
2833
|
+
const path10 = projectPath(
|
|
2711
2834
|
input.project,
|
|
2712
2835
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
2713
2836
|
);
|
|
2714
2837
|
try {
|
|
2715
|
-
const result = await client.get(
|
|
2838
|
+
const result = await client.get(path10);
|
|
2716
2839
|
if (result.totalAffected === 0) {
|
|
2717
2840
|
return {
|
|
2718
2841
|
summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
|
|
@@ -2746,12 +2869,12 @@ function formatBlastEntry(n) {
|
|
|
2746
2869
|
}
|
|
2747
2870
|
async function runDependencies(client, input) {
|
|
2748
2871
|
const depth = input.depth ?? 3;
|
|
2749
|
-
const
|
|
2872
|
+
const path10 = projectPath(
|
|
2750
2873
|
input.project,
|
|
2751
2874
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
2752
2875
|
);
|
|
2753
2876
|
try {
|
|
2754
|
-
const result = await client.get(
|
|
2877
|
+
const result = await client.get(path10);
|
|
2755
2878
|
if (result.total === 0) {
|
|
2756
2879
|
return {
|
|
2757
2880
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -2832,9 +2955,9 @@ function formatDuration(ms) {
|
|
|
2832
2955
|
return `${Math.round(h / 24)}d`;
|
|
2833
2956
|
}
|
|
2834
2957
|
async function runIncidents(client, input) {
|
|
2835
|
-
const
|
|
2958
|
+
const path10 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
2836
2959
|
try {
|
|
2837
|
-
const body = await client.get(
|
|
2960
|
+
const body = await client.get(path10);
|
|
2838
2961
|
const events = body.events;
|
|
2839
2962
|
if (events.length === 0) {
|
|
2840
2963
|
return {
|
|
@@ -3124,7 +3247,7 @@ async function resolveProjectEntry(opts) {
|
|
|
3124
3247
|
const cwd = opts.cwd ?? process.cwd();
|
|
3125
3248
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
3126
3249
|
for (const entry2 of entries) {
|
|
3127
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
3250
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path8.sep}`)) {
|
|
3128
3251
|
return entry2;
|
|
3129
3252
|
}
|
|
3130
3253
|
}
|
|
@@ -3484,10 +3607,10 @@ function assignFlag(out, field, value) {
|
|
|
3484
3607
|
out[field] = value;
|
|
3485
3608
|
}
|
|
3486
3609
|
function readPackageVersion() {
|
|
3487
|
-
const here = typeof __dirname !== "undefined" ? __dirname :
|
|
3610
|
+
const here = typeof __dirname !== "undefined" ? __dirname : path9.dirname(fileURLToPath(import.meta.url));
|
|
3488
3611
|
const candidates = [
|
|
3489
|
-
|
|
3490
|
-
|
|
3612
|
+
path9.resolve(here, "../package.json"),
|
|
3613
|
+
path9.resolve(here, "../../package.json")
|
|
3491
3614
|
];
|
|
3492
3615
|
for (const candidate of candidates) {
|
|
3493
3616
|
try {
|
|
@@ -3555,7 +3678,7 @@ async function buildPatchSections(services, project) {
|
|
|
3555
3678
|
}
|
|
3556
3679
|
async function runInit(opts) {
|
|
3557
3680
|
const written = [];
|
|
3558
|
-
const stat = await
|
|
3681
|
+
const stat = await fs8.stat(opts.scanPath).catch(() => null);
|
|
3559
3682
|
if (!stat || !stat.isDirectory()) {
|
|
3560
3683
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
3561
3684
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -3564,13 +3687,13 @@ async function runInit(opts) {
|
|
|
3564
3687
|
printDiscoveryReport(opts, services);
|
|
3565
3688
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
3566
3689
|
const patch = renderPatch(sections);
|
|
3567
|
-
const patchPath =
|
|
3690
|
+
const patchPath = path9.join(opts.scanPath, "neat.patch");
|
|
3568
3691
|
if (opts.dryRun) {
|
|
3569
|
-
await
|
|
3692
|
+
await fs8.writeFile(patchPath, patch, "utf8");
|
|
3570
3693
|
written.push(patchPath);
|
|
3571
3694
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
3572
|
-
const gitignorePath =
|
|
3573
|
-
const gitignoreExists = await
|
|
3695
|
+
const gitignorePath = path9.join(opts.scanPath, ".gitignore");
|
|
3696
|
+
const gitignoreExists = await fs8.stat(gitignorePath).then(() => true).catch(() => false);
|
|
3574
3697
|
const verb = gitignoreExists ? "append" : "create";
|
|
3575
3698
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
3576
3699
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -3581,9 +3704,9 @@ async function runInit(opts) {
|
|
|
3581
3704
|
const graph = getGraph(graphKey);
|
|
3582
3705
|
const projectPaths = pathsForProject(
|
|
3583
3706
|
graphKey,
|
|
3584
|
-
|
|
3707
|
+
path9.join(opts.scanPath, "neat-out")
|
|
3585
3708
|
);
|
|
3586
|
-
const errorsPath =
|
|
3709
|
+
const errorsPath = path9.join(path9.dirname(opts.outPath), path9.basename(projectPaths.errorsPath));
|
|
3587
3710
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
3588
3711
|
await saveGraphToDisk(graph, opts.outPath);
|
|
3589
3712
|
written.push(opts.outPath);
|
|
@@ -3662,7 +3785,7 @@ async function runInit(opts) {
|
|
|
3662
3785
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
3663
3786
|
}
|
|
3664
3787
|
} else {
|
|
3665
|
-
await
|
|
3788
|
+
await fs8.writeFile(patchPath, patch, "utf8");
|
|
3666
3789
|
written.push(patchPath);
|
|
3667
3790
|
}
|
|
3668
3791
|
}
|
|
@@ -3702,9 +3825,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
3702
3825
|
};
|
|
3703
3826
|
function claudeConfigPath() {
|
|
3704
3827
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
3705
|
-
if (override && override.length > 0) return
|
|
3828
|
+
if (override && override.length > 0) return path9.resolve(override);
|
|
3706
3829
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
3707
|
-
return
|
|
3830
|
+
return path9.join(home, ".claude.json");
|
|
3708
3831
|
}
|
|
3709
3832
|
async function runSkill(opts) {
|
|
3710
3833
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -3716,7 +3839,7 @@ async function runSkill(opts) {
|
|
|
3716
3839
|
const target = claudeConfigPath();
|
|
3717
3840
|
let existing = {};
|
|
3718
3841
|
try {
|
|
3719
|
-
existing = JSON.parse(await
|
|
3842
|
+
existing = JSON.parse(await fs8.readFile(target, "utf8"));
|
|
3720
3843
|
} catch (err) {
|
|
3721
3844
|
if (err.code !== "ENOENT") {
|
|
3722
3845
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -3728,8 +3851,8 @@ async function runSkill(opts) {
|
|
|
3728
3851
|
...existing,
|
|
3729
3852
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
3730
3853
|
};
|
|
3731
|
-
await
|
|
3732
|
-
await
|
|
3854
|
+
await fs8.mkdir(path9.dirname(target), { recursive: true });
|
|
3855
|
+
await fs8.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
3733
3856
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
3734
3857
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
3735
3858
|
return { exitCode: 0 };
|
|
@@ -3767,12 +3890,12 @@ async function main() {
|
|
|
3767
3890
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
3768
3891
|
process.exit(2);
|
|
3769
3892
|
}
|
|
3770
|
-
const scanPath =
|
|
3893
|
+
const scanPath = path9.resolve(target);
|
|
3771
3894
|
const projectExplicit = parsed.project !== null;
|
|
3772
|
-
const projectName = projectExplicit ? project :
|
|
3895
|
+
const projectName = projectExplicit ? project : path9.basename(scanPath);
|
|
3773
3896
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
3774
|
-
const fallback = pathsForProject(projectKey,
|
|
3775
|
-
const outPath =
|
|
3897
|
+
const fallback = pathsForProject(projectKey, path9.join(scanPath, "neat-out")).snapshotPath;
|
|
3898
|
+
const outPath = path9.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
3776
3899
|
const result = await runInit({
|
|
3777
3900
|
scanPath,
|
|
3778
3901
|
outPath,
|
|
@@ -3793,21 +3916,21 @@ async function main() {
|
|
|
3793
3916
|
usage();
|
|
3794
3917
|
process.exit(2);
|
|
3795
3918
|
}
|
|
3796
|
-
const scanPath =
|
|
3797
|
-
const stat = await
|
|
3919
|
+
const scanPath = path9.resolve(target);
|
|
3920
|
+
const stat = await fs8.stat(scanPath).catch(() => null);
|
|
3798
3921
|
if (!stat || !stat.isDirectory()) {
|
|
3799
3922
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
3800
3923
|
process.exit(2);
|
|
3801
3924
|
}
|
|
3802
|
-
const projectPaths = pathsForProject(project,
|
|
3803
|
-
const outPath =
|
|
3804
|
-
const errorsPath =
|
|
3805
|
-
process.env.NEAT_ERRORS_PATH ??
|
|
3925
|
+
const projectPaths = pathsForProject(project, path9.join(scanPath, "neat-out"));
|
|
3926
|
+
const outPath = path9.resolve(process.env.NEAT_OUT_PATH ?? projectPaths.snapshotPath);
|
|
3927
|
+
const errorsPath = path9.resolve(
|
|
3928
|
+
process.env.NEAT_ERRORS_PATH ?? path9.join(path9.dirname(outPath), path9.basename(projectPaths.errorsPath))
|
|
3806
3929
|
);
|
|
3807
|
-
const staleEventsPath =
|
|
3808
|
-
process.env.NEAT_STALE_EVENTS_PATH ??
|
|
3930
|
+
const staleEventsPath = path9.resolve(
|
|
3931
|
+
process.env.NEAT_STALE_EVENTS_PATH ?? path9.join(path9.dirname(outPath), path9.basename(projectPaths.staleEventsPath))
|
|
3809
3932
|
);
|
|
3810
|
-
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ?
|
|
3933
|
+
const embeddingsCachePath = process.env.NEAT_EMBEDDINGS_CACHE_PATH ? path9.resolve(process.env.NEAT_EMBEDDINGS_CACHE_PATH) : void 0;
|
|
3811
3934
|
const handle = await startWatch(getGraph(project), {
|
|
3812
3935
|
scanPath,
|
|
3813
3936
|
outPath,
|
|
@@ -3953,11 +4076,11 @@ async function main() {
|
|
|
3953
4076
|
process.exit(1);
|
|
3954
4077
|
}
|
|
3955
4078
|
async function tryOrchestrator(cmd, parsed) {
|
|
3956
|
-
const scanPath =
|
|
3957
|
-
const stat = await
|
|
4079
|
+
const scanPath = path9.resolve(cmd);
|
|
4080
|
+
const stat = await fs8.stat(scanPath).catch(() => null);
|
|
3958
4081
|
if (!stat || !stat.isDirectory()) return null;
|
|
3959
4082
|
const projectExplicit = parsed.project !== null;
|
|
3960
|
-
const projectName = projectExplicit ? parsed.project :
|
|
4083
|
+
const projectName = projectExplicit ? parsed.project : path9.basename(scanPath);
|
|
3961
4084
|
const result = await runOrchestrator({
|
|
3962
4085
|
scanPath,
|
|
3963
4086
|
project: projectName,
|