@neat.is/core 0.4.4 → 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-YJOA7BBF.js → chunk-3QCRUEQD.js} +2 -2
- package/dist/{chunk-ZVNP3ZDH.js → chunk-HVF4S7J3.js} +6 -3
- package/dist/chunk-HVF4S7J3.js.map +1 -0
- package/dist/{chunk-J4YBTD24.js → chunk-NON5AXOR.js} +38 -4
- package/dist/chunk-NON5AXOR.js.map +1 -0
- package/dist/{chunk-G3YGPWJL.js → chunk-RBWL4HRB.js} +13 -2
- package/dist/chunk-RBWL4HRB.js.map +1 -0
- package/dist/cli.cjs +358 -105
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +335 -97
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +49 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +49 -1
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +3 -3
- package/dist/{otel-grpc-FIERFRGD.js → otel-grpc-VSPMP3OS.js} +3 -3
- package/dist/server.cjs +15 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-G3YGPWJL.js.map +0 -1
- package/dist/chunk-J4YBTD24.js.map +0 -1
- package/dist/chunk-ZVNP3ZDH.js.map +0 -1
- /package/dist/{chunk-YJOA7BBF.js.map → chunk-3QCRUEQD.js.map} +0 -0
- /package/dist/{otel-grpc-FIERFRGD.js.map → otel-grpc-VSPMP3OS.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -41,19 +41,19 @@ import {
|
|
|
41
41
|
setStatus,
|
|
42
42
|
startPersistLoop,
|
|
43
43
|
startStalenessLoop
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-RBWL4HRB.js";
|
|
45
45
|
import {
|
|
46
46
|
startOtelGrpcReceiver
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-3QCRUEQD.js";
|
|
48
48
|
import {
|
|
49
49
|
__dirname,
|
|
50
50
|
__require,
|
|
51
51
|
buildOtelReceiver
|
|
52
|
-
} from "./chunk-
|
|
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
|
|
@@ -697,22 +697,40 @@ var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
|
|
|
697
697
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
698
698
|
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
699
699
|
|
|
700
|
-
require('@opentelemetry/
|
|
700
|
+
const { NodeSDK } = require('@opentelemetry/sdk-node')
|
|
701
|
+
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
|
|
702
|
+
|
|
703
|
+
const instrumentations = [getNodeAutoInstrumentations()]
|
|
704
|
+
__INSTRUMENTATION_BLOCK__
|
|
705
|
+
new NodeSDK({ instrumentations }).start()
|
|
701
706
|
`;
|
|
702
707
|
var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
|
|
703
708
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
704
709
|
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
705
710
|
|
|
706
|
-
|
|
711
|
+
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
712
|
+
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
713
|
+
|
|
714
|
+
const instrumentations = [getNodeAutoInstrumentations()]
|
|
715
|
+
__INSTRUMENTATION_BLOCK__
|
|
716
|
+
new NodeSDK({ instrumentations }).start()
|
|
707
717
|
`;
|
|
708
718
|
var OTEL_INIT_TS = `${OTEL_INIT_HEADER}
|
|
709
719
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
710
720
|
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projects/__PROJECT__/v1/traces'
|
|
711
721
|
|
|
712
|
-
|
|
722
|
+
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
723
|
+
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
724
|
+
|
|
725
|
+
const instrumentations = [getNodeAutoInstrumentations()]
|
|
726
|
+
__INSTRUMENTATION_BLOCK__
|
|
727
|
+
new NodeSDK({ instrumentations }).start()
|
|
713
728
|
`;
|
|
714
|
-
function renderNodeOtelInit(template, serviceName, projectName) {
|
|
715
|
-
|
|
729
|
+
function renderNodeOtelInit(template, serviceName, projectName, registrations = []) {
|
|
730
|
+
const block = registrations.length === 0 ? "" : `
|
|
731
|
+
${registrations.join("\n")}
|
|
732
|
+
`;
|
|
733
|
+
return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
|
|
716
734
|
}
|
|
717
735
|
function renderEnvNeat(serviceName, projectName) {
|
|
718
736
|
return [
|
|
@@ -744,7 +762,9 @@ process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projec
|
|
|
744
762
|
import { NodeSDK } from '@opentelemetry/sdk-node'
|
|
745
763
|
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'
|
|
746
764
|
|
|
747
|
-
|
|
765
|
+
const instrumentations = [getNodeAutoInstrumentations()]
|
|
766
|
+
__INSTRUMENTATION_BLOCK__
|
|
767
|
+
new NodeSDK({ instrumentations }).start()
|
|
748
768
|
`;
|
|
749
769
|
var NEXT_INSTRUMENTATION_NODE_JS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
750
770
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
@@ -753,10 +773,15 @@ process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/projec
|
|
|
753
773
|
const { NodeSDK } = require('@opentelemetry/sdk-node')
|
|
754
774
|
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node')
|
|
755
775
|
|
|
756
|
-
|
|
776
|
+
const instrumentations = [getNodeAutoInstrumentations()]
|
|
777
|
+
__INSTRUMENTATION_BLOCK__
|
|
778
|
+
new NodeSDK({ instrumentations }).start()
|
|
757
779
|
`;
|
|
758
|
-
function renderNextInstrumentationNode(template, serviceName, projectName) {
|
|
759
|
-
|
|
780
|
+
function renderNextInstrumentationNode(template, serviceName, projectName, registrations = []) {
|
|
781
|
+
const block = registrations.length === 0 ? "" : `
|
|
782
|
+
${registrations.join("\n")}
|
|
783
|
+
`;
|
|
784
|
+
return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
|
|
760
785
|
}
|
|
761
786
|
var FRAMEWORK_OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-074). OpenTelemetry SDK hook.";
|
|
762
787
|
var FRAMEWORK_OTEL_INIT_TS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
|
|
@@ -852,6 +877,25 @@ var SDK_PACKAGES = [
|
|
|
852
877
|
{ name: "@opentelemetry/sdk-node", version: "^0.57.0" },
|
|
853
878
|
{ name: "@opentelemetry/auto-instrumentations-node", version: "^0.55.0" }
|
|
854
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
|
+
}
|
|
885
|
+
function detectNonBundledInstrumentations(pkg) {
|
|
886
|
+
const deps = allDeps(pkg);
|
|
887
|
+
const out = [];
|
|
888
|
+
if ("@prisma/client" in deps) {
|
|
889
|
+
const prismaMajor = getMajor(deps["@prisma/client"]);
|
|
890
|
+
const prismaInstrVersion = prismaMajor >= 6 ? "^6.0.0" : "^5.0.0";
|
|
891
|
+
out.push({
|
|
892
|
+
pkg: "@prisma/instrumentation",
|
|
893
|
+
version: prismaInstrVersion,
|
|
894
|
+
registration: "instrumentations.push(new (require('@prisma/instrumentation').PrismaInstrumentation)())"
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
return out;
|
|
898
|
+
}
|
|
855
899
|
var OTEL_ENV = {
|
|
856
900
|
// ADR-069 §4 — endpoint moves into the per-package .env.neat (written
|
|
857
901
|
// by the apply phase). The envEdits surface stays for the dry-run
|
|
@@ -1142,8 +1186,19 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
1142
1186
|
version: sdk.version
|
|
1143
1187
|
});
|
|
1144
1188
|
}
|
|
1189
|
+
const nonBundled = detectNonBundledInstrumentations(pkg);
|
|
1190
|
+
for (const inst of nonBundled) {
|
|
1191
|
+
if (inst.pkg in existingDeps) continue;
|
|
1192
|
+
dependencyEdits.push({
|
|
1193
|
+
file: manifestPath,
|
|
1194
|
+
kind: "add",
|
|
1195
|
+
name: inst.pkg,
|
|
1196
|
+
version: inst.version
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1145
1199
|
const svcName = serviceNodeName(pkg, serviceDir);
|
|
1146
1200
|
const projectName = projectToken(pkg, serviceDir, project);
|
|
1201
|
+
const registrations = nonBundled.map((i) => i.registration);
|
|
1147
1202
|
const generatedFiles = [];
|
|
1148
1203
|
if (!await exists(instrumentationFile)) {
|
|
1149
1204
|
generatedFiles.push({
|
|
@@ -1158,7 +1213,8 @@ async function planNext(serviceDir, pkg, manifestPath, nextConfigPath, project)
|
|
|
1158
1213
|
contents: renderNextInstrumentationNode(
|
|
1159
1214
|
useTs ? NEXT_INSTRUMENTATION_NODE_TS : NEXT_INSTRUMENTATION_NODE_JS,
|
|
1160
1215
|
svcName,
|
|
1161
|
-
projectName
|
|
1216
|
+
projectName,
|
|
1217
|
+
registrations
|
|
1162
1218
|
),
|
|
1163
1219
|
skipIfExists: true
|
|
1164
1220
|
});
|
|
@@ -1508,55 +1564,73 @@ async function planAstro(serviceDir, pkg, manifestPath, project) {
|
|
|
1508
1564
|
framework: "astro"
|
|
1509
1565
|
};
|
|
1510
1566
|
}
|
|
1511
|
-
async function
|
|
1512
|
-
const pkg = await readPackageJson(serviceDir);
|
|
1513
|
-
const manifestPath = path4.join(serviceDir, "package.json");
|
|
1514
|
-
const project = opts?.project;
|
|
1515
|
-
const empty = {
|
|
1516
|
-
language: "javascript",
|
|
1517
|
-
serviceDir,
|
|
1518
|
-
dependencyEdits: [],
|
|
1519
|
-
entrypointEdits: [],
|
|
1520
|
-
envEdits: [],
|
|
1521
|
-
generatedFiles: []
|
|
1522
|
-
};
|
|
1523
|
-
if (!pkg) return empty;
|
|
1567
|
+
async function findFrameworkDispatch(serviceDir, pkg, manifestPath, project) {
|
|
1524
1568
|
if (hasNextDependency(pkg)) {
|
|
1525
1569
|
const nextConfig = await findNextConfig(serviceDir);
|
|
1526
1570
|
if (nextConfig) {
|
|
1527
|
-
return planNext(serviceDir, pkg, manifestPath, nextConfig, project);
|
|
1571
|
+
return () => planNext(serviceDir, pkg, manifestPath, nextConfig, project);
|
|
1528
1572
|
}
|
|
1529
1573
|
}
|
|
1530
1574
|
if (hasRemixDependency(pkg)) {
|
|
1531
1575
|
const remixEntry = await findRemixEntry(serviceDir);
|
|
1532
1576
|
if (remixEntry) {
|
|
1533
|
-
return planRemix(serviceDir, pkg, manifestPath, remixEntry, project);
|
|
1577
|
+
return () => planRemix(serviceDir, pkg, manifestPath, remixEntry, project);
|
|
1534
1578
|
}
|
|
1535
1579
|
}
|
|
1536
1580
|
if (hasSvelteKitDependency(pkg)) {
|
|
1537
1581
|
const hooks = await findSvelteKitHooks(serviceDir);
|
|
1538
1582
|
const config = await findSvelteKitConfig(serviceDir);
|
|
1539
1583
|
if (hooks || config) {
|
|
1540
|
-
return planSvelteKit(serviceDir, pkg, manifestPath, hooks, project);
|
|
1584
|
+
return () => planSvelteKit(serviceDir, pkg, manifestPath, hooks, project);
|
|
1541
1585
|
}
|
|
1542
1586
|
}
|
|
1543
1587
|
if (hasNuxtDependency(pkg)) {
|
|
1544
1588
|
const nuxtConfig = await findNuxtConfig(serviceDir);
|
|
1545
1589
|
if (nuxtConfig) {
|
|
1546
|
-
return planNuxt(serviceDir, pkg, manifestPath, project);
|
|
1590
|
+
return () => planNuxt(serviceDir, pkg, manifestPath, project);
|
|
1547
1591
|
}
|
|
1548
1592
|
}
|
|
1549
1593
|
if (hasAstroDependency(pkg)) {
|
|
1550
1594
|
const astroConfig = await findAstroConfig(serviceDir);
|
|
1551
1595
|
if (astroConfig) {
|
|
1552
|
-
return planAstro(serviceDir, pkg, manifestPath, project);
|
|
1596
|
+
return () => planAstro(serviceDir, pkg, manifestPath, project);
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
return null;
|
|
1600
|
+
}
|
|
1601
|
+
async function plan(serviceDir, opts) {
|
|
1602
|
+
const pkg = await readPackageJson(serviceDir);
|
|
1603
|
+
const manifestPath = path4.join(serviceDir, "package.json");
|
|
1604
|
+
const project = opts?.project;
|
|
1605
|
+
const empty = {
|
|
1606
|
+
language: "javascript",
|
|
1607
|
+
serviceDir,
|
|
1608
|
+
dependencyEdits: [],
|
|
1609
|
+
entrypointEdits: [],
|
|
1610
|
+
envEdits: [],
|
|
1611
|
+
generatedFiles: []
|
|
1612
|
+
};
|
|
1613
|
+
if (!pkg) return empty;
|
|
1614
|
+
const frameworkDispatch = await findFrameworkDispatch(
|
|
1615
|
+
serviceDir,
|
|
1616
|
+
pkg,
|
|
1617
|
+
manifestPath,
|
|
1618
|
+
project
|
|
1619
|
+
);
|
|
1620
|
+
let entryFile = null;
|
|
1621
|
+
if (!frameworkDispatch) {
|
|
1622
|
+
entryFile = await resolveEntry(serviceDir, pkg);
|
|
1623
|
+
if (!entryFile) {
|
|
1624
|
+
return { ...empty, libOnly: true };
|
|
1553
1625
|
}
|
|
1554
1626
|
}
|
|
1627
|
+
if (frameworkDispatch) {
|
|
1628
|
+
return frameworkDispatch();
|
|
1629
|
+
}
|
|
1555
1630
|
const runtimeKind = await detectRuntimeKind(serviceDir, pkg);
|
|
1556
1631
|
if (runtimeKind !== "node") {
|
|
1557
1632
|
return { ...empty, runtimeKind };
|
|
1558
1633
|
}
|
|
1559
|
-
const entryFile = await resolveEntry(serviceDir, pkg);
|
|
1560
1634
|
if (!entryFile) {
|
|
1561
1635
|
return { ...empty, libOnly: true };
|
|
1562
1636
|
}
|
|
@@ -1574,6 +1648,16 @@ async function plan(serviceDir, opts) {
|
|
|
1574
1648
|
version: sdk.version
|
|
1575
1649
|
});
|
|
1576
1650
|
}
|
|
1651
|
+
const nonBundled = detectNonBundledInstrumentations(pkg);
|
|
1652
|
+
for (const inst of nonBundled) {
|
|
1653
|
+
if (inst.pkg in existingDeps) continue;
|
|
1654
|
+
dependencyEdits.push({
|
|
1655
|
+
file: manifestPath,
|
|
1656
|
+
kind: "add",
|
|
1657
|
+
name: inst.pkg,
|
|
1658
|
+
version: inst.version
|
|
1659
|
+
});
|
|
1660
|
+
}
|
|
1577
1661
|
const entrypointEdits = [];
|
|
1578
1662
|
try {
|
|
1579
1663
|
const raw = await fs4.readFile(entryFile, "utf8");
|
|
@@ -1592,11 +1676,17 @@ async function plan(serviceDir, opts) {
|
|
|
1592
1676
|
}
|
|
1593
1677
|
const svcName = serviceNodeName(pkg, serviceDir);
|
|
1594
1678
|
const projectName = projectToken(pkg, serviceDir, project);
|
|
1679
|
+
const registrations = nonBundled.map((i) => i.registration);
|
|
1595
1680
|
const generatedFiles = [];
|
|
1596
1681
|
if (!await exists(otelInitFile)) {
|
|
1597
1682
|
generatedFiles.push({
|
|
1598
1683
|
file: otelInitFile,
|
|
1599
|
-
contents: renderNodeOtelInit(
|
|
1684
|
+
contents: renderNodeOtelInit(
|
|
1685
|
+
otelInitContents(flavor),
|
|
1686
|
+
svcName,
|
|
1687
|
+
projectName,
|
|
1688
|
+
registrations
|
|
1689
|
+
),
|
|
1600
1690
|
skipIfExists: true
|
|
1601
1691
|
});
|
|
1602
1692
|
}
|
|
@@ -2126,18 +2216,99 @@ function renderPatch(sections) {
|
|
|
2126
2216
|
}
|
|
2127
2217
|
|
|
2128
2218
|
// src/orchestrator.ts
|
|
2129
|
-
import { promises as
|
|
2219
|
+
import { promises as fs7 } from "fs";
|
|
2130
2220
|
import http from "http";
|
|
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";
|
|
2131
2228
|
import path6 from "path";
|
|
2132
2229
|
import { spawn as spawn2 } from "child_process";
|
|
2133
|
-
|
|
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
|
|
2134
2305
|
async function extractAndPersist(opts) {
|
|
2135
2306
|
const services = await discoverServices(opts.scanPath);
|
|
2136
2307
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
2137
2308
|
const graphKey = opts.projectExplicit ? opts.project : DEFAULT_PROJECT;
|
|
2138
2309
|
resetGraph(graphKey);
|
|
2139
2310
|
const graph = getGraph(graphKey);
|
|
2140
|
-
const projectPaths = pathsForProject(graphKey,
|
|
2311
|
+
const projectPaths = pathsForProject(graphKey, path7.join(opts.scanPath, "neat-out"));
|
|
2141
2312
|
const extraction = await extractFromDirectory(graph, opts.scanPath, {
|
|
2142
2313
|
errorsPath: projectPaths.errorsPath
|
|
2143
2314
|
});
|
|
@@ -2155,12 +2326,15 @@ async function extractAndPersist(opts) {
|
|
|
2155
2326
|
errorsPath: projectPaths.errorsPath
|
|
2156
2327
|
};
|
|
2157
2328
|
}
|
|
2158
|
-
async function applyInstallersOver(services, project) {
|
|
2329
|
+
async function applyInstallersOver(services, project, options = {}) {
|
|
2330
|
+
const resolveManager = options.resolveManager ?? detectPackageManager;
|
|
2331
|
+
const runInstall = options.runInstall ?? runPackageManagerInstall;
|
|
2159
2332
|
let instrumented = 0;
|
|
2160
2333
|
let already = 0;
|
|
2161
2334
|
let libOnly = 0;
|
|
2162
2335
|
let browserBundle = 0;
|
|
2163
2336
|
let reactNative = 0;
|
|
2337
|
+
const installPlans = /* @__PURE__ */ new Map();
|
|
2164
2338
|
for (const svc of services) {
|
|
2165
2339
|
const installer = await pickInstaller(svc.dir);
|
|
2166
2340
|
if (!installer) continue;
|
|
@@ -2170,8 +2344,14 @@ async function applyInstallersOver(services, project) {
|
|
|
2170
2344
|
continue;
|
|
2171
2345
|
}
|
|
2172
2346
|
const outcome = await installer.apply(plan3);
|
|
2173
|
-
if (outcome.outcome === "instrumented")
|
|
2174
|
-
|
|
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++;
|
|
2175
2355
|
else if (outcome.outcome === "lib-only") libOnly++;
|
|
2176
2356
|
else if (outcome.outcome === "browser-bundle") {
|
|
2177
2357
|
browserBundle++;
|
|
@@ -2181,7 +2361,30 @@ async function applyInstallersOver(services, project) {
|
|
|
2181
2361
|
console.log(`skipping ${svc.dir}: React Native target; browser-OTel support lands in a future release.`);
|
|
2182
2362
|
}
|
|
2183
2363
|
}
|
|
2184
|
-
|
|
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
|
+
};
|
|
2185
2388
|
}
|
|
2186
2389
|
async function promptYesNo(question) {
|
|
2187
2390
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -2294,11 +2497,33 @@ async function waitForDaemonReady(restPort, timeoutMs) {
|
|
|
2294
2497
|
stillBootstrapping: projects.filter((p) => p.status === "bootstrapping").map((p) => p.name)
|
|
2295
2498
|
};
|
|
2296
2499
|
}
|
|
2500
|
+
var NEAT_PORTS = [8080, 4318, 6328];
|
|
2501
|
+
async function isPortFree(port) {
|
|
2502
|
+
return new Promise((resolve) => {
|
|
2503
|
+
const server = net.createServer();
|
|
2504
|
+
server.once("error", () => resolve(false));
|
|
2505
|
+
server.once("listening", () => server.close(() => resolve(true)));
|
|
2506
|
+
server.listen(port, "127.0.0.1");
|
|
2507
|
+
});
|
|
2508
|
+
}
|
|
2509
|
+
async function probePortsFree() {
|
|
2510
|
+
for (const port of NEAT_PORTS) {
|
|
2511
|
+
if (!await isPortFree(port)) return { free: false, held: port };
|
|
2512
|
+
}
|
|
2513
|
+
return { free: true };
|
|
2514
|
+
}
|
|
2515
|
+
function formatPortCollisionMessage(port) {
|
|
2516
|
+
return [
|
|
2517
|
+
`neat: port ${port} is in use; the NEAT daemon needs it.`,
|
|
2518
|
+
` run \`neatd stop\` to release the previous daemon, or`,
|
|
2519
|
+
` \`lsof -i :${port}\` to find the holding process.`
|
|
2520
|
+
];
|
|
2521
|
+
}
|
|
2297
2522
|
function spawnDaemonDetached() {
|
|
2298
|
-
const here =
|
|
2523
|
+
const here = path7.dirname(new URL(import.meta.url).pathname);
|
|
2299
2524
|
const candidates = [
|
|
2300
|
-
|
|
2301
|
-
|
|
2525
|
+
path7.join(here, "neatd.cjs"),
|
|
2526
|
+
path7.join(here, "neatd.js")
|
|
2302
2527
|
];
|
|
2303
2528
|
let entry2 = null;
|
|
2304
2529
|
const fsSync = __require("fs");
|
|
@@ -2318,7 +2543,7 @@ function spawnDaemonDetached() {
|
|
|
2318
2543
|
if (!hasToken && (!env.HOST || env.HOST.length === 0)) {
|
|
2319
2544
|
env.HOST = "127.0.0.1";
|
|
2320
2545
|
}
|
|
2321
|
-
const child =
|
|
2546
|
+
const child = spawn3(process.execPath, [entry2, "start"], {
|
|
2322
2547
|
detached: true,
|
|
2323
2548
|
// stderr inherits the orchestrator's fd so the daemon's
|
|
2324
2549
|
// `BindAuthorityError` message lands in front of the operator instead
|
|
@@ -2335,7 +2560,7 @@ function openBrowser(url) {
|
|
|
2335
2560
|
const cmd = platform === "darwin" ? "open" : platform === "win32" ? "cmd" : "xdg-open";
|
|
2336
2561
|
const args = platform === "win32" ? ["/c", "start", "", url] : [url];
|
|
2337
2562
|
try {
|
|
2338
|
-
const child =
|
|
2563
|
+
const child = spawn3(cmd, args, { detached: true, stdio: "ignore" });
|
|
2339
2564
|
child.on("error", () => {
|
|
2340
2565
|
});
|
|
2341
2566
|
child.unref();
|
|
@@ -2356,7 +2581,7 @@ async function runOrchestrator(opts) {
|
|
|
2356
2581
|
browser: "skipped"
|
|
2357
2582
|
}
|
|
2358
2583
|
};
|
|
2359
|
-
const stat = await
|
|
2584
|
+
const stat = await fs7.stat(opts.scanPath).catch(() => null);
|
|
2360
2585
|
if (!stat || !stat.isDirectory()) {
|
|
2361
2586
|
console.error(`neat: ${opts.scanPath} is not a directory`);
|
|
2362
2587
|
result.exitCode = 2;
|
|
@@ -2424,12 +2649,24 @@ async function runOrchestrator(opts) {
|
|
|
2424
2649
|
console.log(
|
|
2425
2650
|
`instrumented ${tally.instrumented}, already ${tally.alreadyInstrumented}, lib-only ${tally.libOnly}`
|
|
2426
2651
|
);
|
|
2652
|
+
const failedInstalls = tally.packageManagerInstalls.filter((i) => i.exitCode !== 0);
|
|
2653
|
+
if (failedInstalls.length > 0) {
|
|
2654
|
+
result.exitCode = 1;
|
|
2655
|
+
}
|
|
2427
2656
|
}
|
|
2428
2657
|
const restPort = Number(process.env.PORT ?? 8080);
|
|
2429
2658
|
const timeoutMs = opts.daemonReadyTimeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
|
|
2430
2659
|
if (await checkDaemonHealth(restPort)) {
|
|
2431
2660
|
result.steps.daemon = "already-running";
|
|
2432
2661
|
} else {
|
|
2662
|
+
const probe = await probePortsFree();
|
|
2663
|
+
if (!probe.free) {
|
|
2664
|
+
for (const line of formatPortCollisionMessage(probe.held)) {
|
|
2665
|
+
console.error(line);
|
|
2666
|
+
}
|
|
2667
|
+
result.exitCode = 3;
|
|
2668
|
+
return result;
|
|
2669
|
+
}
|
|
2433
2670
|
try {
|
|
2434
2671
|
spawnDaemonDetached();
|
|
2435
2672
|
} catch (err) {
|
|
@@ -2486,7 +2723,7 @@ function printSummary(result, graph, dashboardUrl) {
|
|
|
2486
2723
|
}
|
|
2487
2724
|
|
|
2488
2725
|
// src/cli-verbs.ts
|
|
2489
|
-
import
|
|
2726
|
+
import path8 from "path";
|
|
2490
2727
|
|
|
2491
2728
|
// src/cli-client.ts
|
|
2492
2729
|
import { Provenance as Provenance2 } from "@neat.is/types";
|
|
@@ -2510,10 +2747,10 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
2510
2747
|
const root = baseUrl.replace(/\/$/, "");
|
|
2511
2748
|
const authHeader = bearerToken && bearerToken.length > 0 ? { authorization: `Bearer ${bearerToken}` } : {};
|
|
2512
2749
|
return {
|
|
2513
|
-
async get(
|
|
2750
|
+
async get(path10) {
|
|
2514
2751
|
let res;
|
|
2515
2752
|
try {
|
|
2516
|
-
res = await fetch(`${root}${
|
|
2753
|
+
res = await fetch(`${root}${path10}`, {
|
|
2517
2754
|
headers: { ...authHeader }
|
|
2518
2755
|
});
|
|
2519
2756
|
} catch (err) {
|
|
@@ -2525,16 +2762,16 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
2525
2762
|
const body = await res.text().catch(() => "");
|
|
2526
2763
|
throw new HttpError(
|
|
2527
2764
|
res.status,
|
|
2528
|
-
`${res.status} ${res.statusText} on GET ${
|
|
2765
|
+
`${res.status} ${res.statusText} on GET ${path10}: ${body}`,
|
|
2529
2766
|
body
|
|
2530
2767
|
);
|
|
2531
2768
|
}
|
|
2532
2769
|
return await res.json();
|
|
2533
2770
|
},
|
|
2534
|
-
async post(
|
|
2771
|
+
async post(path10, body) {
|
|
2535
2772
|
let res;
|
|
2536
2773
|
try {
|
|
2537
|
-
res = await fetch(`${root}${
|
|
2774
|
+
res = await fetch(`${root}${path10}`, {
|
|
2538
2775
|
method: "POST",
|
|
2539
2776
|
headers: { "content-type": "application/json", ...authHeader },
|
|
2540
2777
|
body: JSON.stringify(body)
|
|
@@ -2548,7 +2785,7 @@ function createHttpClient(baseUrl, bearerToken) {
|
|
|
2548
2785
|
const text = await res.text().catch(() => "");
|
|
2549
2786
|
throw new HttpError(
|
|
2550
2787
|
res.status,
|
|
2551
|
-
`${res.status} ${res.statusText} on POST ${
|
|
2788
|
+
`${res.status} ${res.statusText} on POST ${path10}: ${text}`,
|
|
2552
2789
|
text
|
|
2553
2790
|
);
|
|
2554
2791
|
}
|
|
@@ -2562,12 +2799,12 @@ function projectPath(project, suffix) {
|
|
|
2562
2799
|
}
|
|
2563
2800
|
async function runRootCause(client, input) {
|
|
2564
2801
|
const qs = input.errorId ? `?errorId=${encodeURIComponent(input.errorId)}` : "";
|
|
2565
|
-
const
|
|
2802
|
+
const path10 = projectPath(
|
|
2566
2803
|
input.project,
|
|
2567
2804
|
`/graph/root-cause/${encodeURIComponent(input.errorNode)}${qs}`
|
|
2568
2805
|
);
|
|
2569
2806
|
try {
|
|
2570
|
-
const result = await client.get(
|
|
2807
|
+
const result = await client.get(path10);
|
|
2571
2808
|
const arrowPath = result.traversalPath.join(" \u2190 ");
|
|
2572
2809
|
const provenances = result.edgeProvenances.length ? result.edgeProvenances.join(", ") : "(direct, no edges traversed)";
|
|
2573
2810
|
const summary = `Root cause for ${input.errorNode} is ${result.rootCauseNode}. ` + result.rootCauseReason + (result.fixRecommendation ? ` Recommended fix: ${result.fixRecommendation}.` : "");
|
|
@@ -2593,12 +2830,12 @@ async function runRootCause(client, input) {
|
|
|
2593
2830
|
}
|
|
2594
2831
|
async function runBlastRadius(client, input) {
|
|
2595
2832
|
const qs = input.depth !== void 0 ? `?depth=${input.depth}` : "";
|
|
2596
|
-
const
|
|
2833
|
+
const path10 = projectPath(
|
|
2597
2834
|
input.project,
|
|
2598
2835
|
`/graph/blast-radius/${encodeURIComponent(input.nodeId)}${qs}`
|
|
2599
2836
|
);
|
|
2600
2837
|
try {
|
|
2601
|
-
const result = await client.get(
|
|
2838
|
+
const result = await client.get(path10);
|
|
2602
2839
|
if (result.totalAffected === 0) {
|
|
2603
2840
|
return {
|
|
2604
2841
|
summary: `${result.origin} has no downstream dependencies. Nothing else would break if it failed.`
|
|
@@ -2632,12 +2869,12 @@ function formatBlastEntry(n) {
|
|
|
2632
2869
|
}
|
|
2633
2870
|
async function runDependencies(client, input) {
|
|
2634
2871
|
const depth = input.depth ?? 3;
|
|
2635
|
-
const
|
|
2872
|
+
const path10 = projectPath(
|
|
2636
2873
|
input.project,
|
|
2637
2874
|
`/graph/dependencies/${encodeURIComponent(input.nodeId)}?depth=${depth}`
|
|
2638
2875
|
);
|
|
2639
2876
|
try {
|
|
2640
|
-
const result = await client.get(
|
|
2877
|
+
const result = await client.get(path10);
|
|
2641
2878
|
if (result.total === 0) {
|
|
2642
2879
|
return {
|
|
2643
2880
|
summary: depth === 1 ? `${input.nodeId} has no direct dependencies in the graph.` : `${input.nodeId} has no dependencies (BFS to depth ${depth}).`
|
|
@@ -2718,9 +2955,9 @@ function formatDuration(ms) {
|
|
|
2718
2955
|
return `${Math.round(h / 24)}d`;
|
|
2719
2956
|
}
|
|
2720
2957
|
async function runIncidents(client, input) {
|
|
2721
|
-
const
|
|
2958
|
+
const path10 = input.nodeId ? projectPath(input.project, `/incidents/${encodeURIComponent(input.nodeId)}`) : projectPath(input.project, "/incidents");
|
|
2722
2959
|
try {
|
|
2723
|
-
const body = await client.get(
|
|
2960
|
+
const body = await client.get(path10);
|
|
2724
2961
|
const events = body.events;
|
|
2725
2962
|
if (events.length === 0) {
|
|
2726
2963
|
return {
|
|
@@ -3010,7 +3247,7 @@ async function resolveProjectEntry(opts) {
|
|
|
3010
3247
|
const cwd = opts.cwd ?? process.cwd();
|
|
3011
3248
|
const resolvedCwd = await normalizeProjectPath(cwd);
|
|
3012
3249
|
for (const entry2 of entries) {
|
|
3013
|
-
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${
|
|
3250
|
+
if (resolvedCwd === entry2.path || resolvedCwd.startsWith(`${entry2.path}${path8.sep}`)) {
|
|
3014
3251
|
return entry2;
|
|
3015
3252
|
}
|
|
3016
3253
|
}
|
|
@@ -3235,7 +3472,8 @@ function usage() {
|
|
|
3235
3472
|
console.log(" 0 success");
|
|
3236
3473
|
console.log(" 1 server error (4xx/5xx body printed to stderr)");
|
|
3237
3474
|
console.log(" 2 misuse (missing args, bad flags) \u2014 handled before any network call");
|
|
3238
|
-
console.log(" 3 daemon
|
|
3475
|
+
console.log(" 3 environmental \u2014 daemon unreachable, or one of ports 8080 / 4318 / 6328");
|
|
3476
|
+
console.log(" is held by another process when `neat <path>` tries to spawn the daemon");
|
|
3239
3477
|
console.log("");
|
|
3240
3478
|
console.log("environment:");
|
|
3241
3479
|
console.log(" NEAT_API_URL base URL for the core REST API (default http://localhost:8080)");
|
|
@@ -3369,10 +3607,10 @@ function assignFlag(out, field, value) {
|
|
|
3369
3607
|
out[field] = value;
|
|
3370
3608
|
}
|
|
3371
3609
|
function readPackageVersion() {
|
|
3372
|
-
const here = typeof __dirname !== "undefined" ? __dirname :
|
|
3610
|
+
const here = typeof __dirname !== "undefined" ? __dirname : path9.dirname(fileURLToPath(import.meta.url));
|
|
3373
3611
|
const candidates = [
|
|
3374
|
-
|
|
3375
|
-
|
|
3612
|
+
path9.resolve(here, "../package.json"),
|
|
3613
|
+
path9.resolve(here, "../../package.json")
|
|
3376
3614
|
];
|
|
3377
3615
|
for (const candidate of candidates) {
|
|
3378
3616
|
try {
|
|
@@ -3440,7 +3678,7 @@ async function buildPatchSections(services, project) {
|
|
|
3440
3678
|
}
|
|
3441
3679
|
async function runInit(opts) {
|
|
3442
3680
|
const written = [];
|
|
3443
|
-
const stat = await
|
|
3681
|
+
const stat = await fs8.stat(opts.scanPath).catch(() => null);
|
|
3444
3682
|
if (!stat || !stat.isDirectory()) {
|
|
3445
3683
|
console.error(`neat init: ${opts.scanPath} is not a directory`);
|
|
3446
3684
|
return { exitCode: 2, writtenFiles: written };
|
|
@@ -3449,13 +3687,13 @@ async function runInit(opts) {
|
|
|
3449
3687
|
printDiscoveryReport(opts, services);
|
|
3450
3688
|
const sections = opts.noInstall ? [] : await buildPatchSections(services, opts.project);
|
|
3451
3689
|
const patch = renderPatch(sections);
|
|
3452
|
-
const patchPath =
|
|
3690
|
+
const patchPath = path9.join(opts.scanPath, "neat.patch");
|
|
3453
3691
|
if (opts.dryRun) {
|
|
3454
|
-
await
|
|
3692
|
+
await fs8.writeFile(patchPath, patch, "utf8");
|
|
3455
3693
|
written.push(patchPath);
|
|
3456
3694
|
console.log(`dry-run: patch written to ${patchPath}`);
|
|
3457
|
-
const gitignorePath =
|
|
3458
|
-
const gitignoreExists = await
|
|
3695
|
+
const gitignorePath = path9.join(opts.scanPath, ".gitignore");
|
|
3696
|
+
const gitignoreExists = await fs8.stat(gitignorePath).then(() => true).catch(() => false);
|
|
3459
3697
|
const verb = gitignoreExists ? "append" : "create";
|
|
3460
3698
|
console.log(`dry-run: would ${verb} ${gitignorePath} (add neat-out/)`);
|
|
3461
3699
|
console.log("rerun without --dry-run to register and snapshot.");
|
|
@@ -3466,9 +3704,9 @@ async function runInit(opts) {
|
|
|
3466
3704
|
const graph = getGraph(graphKey);
|
|
3467
3705
|
const projectPaths = pathsForProject(
|
|
3468
3706
|
graphKey,
|
|
3469
|
-
|
|
3707
|
+
path9.join(opts.scanPath, "neat-out")
|
|
3470
3708
|
);
|
|
3471
|
-
const errorsPath =
|
|
3709
|
+
const errorsPath = path9.join(path9.dirname(opts.outPath), path9.basename(projectPaths.errorsPath));
|
|
3472
3710
|
const result = await extractFromDirectory(graph, opts.scanPath, { errorsPath });
|
|
3473
3711
|
await saveGraphToDisk(graph, opts.outPath);
|
|
3474
3712
|
written.push(opts.outPath);
|
|
@@ -3547,7 +3785,7 @@ async function runInit(opts) {
|
|
|
3547
3785
|
console.log("Run `npm install` (or your language equivalent) to refresh lockfiles.");
|
|
3548
3786
|
}
|
|
3549
3787
|
} else {
|
|
3550
|
-
await
|
|
3788
|
+
await fs8.writeFile(patchPath, patch, "utf8");
|
|
3551
3789
|
written.push(patchPath);
|
|
3552
3790
|
}
|
|
3553
3791
|
}
|
|
@@ -3587,9 +3825,9 @@ var CLAUDE_SKILL_CONFIG = {
|
|
|
3587
3825
|
};
|
|
3588
3826
|
function claudeConfigPath() {
|
|
3589
3827
|
const override = process.env.NEAT_CLAUDE_CONFIG;
|
|
3590
|
-
if (override && override.length > 0) return
|
|
3828
|
+
if (override && override.length > 0) return path9.resolve(override);
|
|
3591
3829
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
3592
|
-
return
|
|
3830
|
+
return path9.join(home, ".claude.json");
|
|
3593
3831
|
}
|
|
3594
3832
|
async function runSkill(opts) {
|
|
3595
3833
|
const snippet = JSON.stringify(CLAUDE_SKILL_CONFIG, null, 2) + "\n";
|
|
@@ -3601,7 +3839,7 @@ async function runSkill(opts) {
|
|
|
3601
3839
|
const target = claudeConfigPath();
|
|
3602
3840
|
let existing = {};
|
|
3603
3841
|
try {
|
|
3604
|
-
existing = JSON.parse(await
|
|
3842
|
+
existing = JSON.parse(await fs8.readFile(target, "utf8"));
|
|
3605
3843
|
} catch (err) {
|
|
3606
3844
|
if (err.code !== "ENOENT") {
|
|
3607
3845
|
console.error(`neat skill: failed to read ${target} \u2014 ${err.message}`);
|
|
@@ -3613,8 +3851,8 @@ async function runSkill(opts) {
|
|
|
3613
3851
|
...existing,
|
|
3614
3852
|
mcpServers: { ...mcp, neat: CLAUDE_SKILL_CONFIG.mcpServers.neat }
|
|
3615
3853
|
};
|
|
3616
|
-
await
|
|
3617
|
-
await
|
|
3854
|
+
await fs8.mkdir(path9.dirname(target), { recursive: true });
|
|
3855
|
+
await fs8.writeFile(target, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
3618
3856
|
console.log(`neat skill: wrote mcpServers.neat to ${target}`);
|
|
3619
3857
|
console.log("restart Claude Code to pick up the new MCP server.");
|
|
3620
3858
|
return { exitCode: 0 };
|
|
@@ -3652,12 +3890,12 @@ async function main() {
|
|
|
3652
3890
|
console.error("neat init: --apply and --dry-run are mutually exclusive");
|
|
3653
3891
|
process.exit(2);
|
|
3654
3892
|
}
|
|
3655
|
-
const scanPath =
|
|
3893
|
+
const scanPath = path9.resolve(target);
|
|
3656
3894
|
const projectExplicit = parsed.project !== null;
|
|
3657
|
-
const projectName = projectExplicit ? project :
|
|
3895
|
+
const projectName = projectExplicit ? project : path9.basename(scanPath);
|
|
3658
3896
|
const projectKey = projectExplicit ? project : DEFAULT_PROJECT;
|
|
3659
|
-
const fallback = pathsForProject(projectKey,
|
|
3660
|
-
const outPath =
|
|
3897
|
+
const fallback = pathsForProject(projectKey, path9.join(scanPath, "neat-out")).snapshotPath;
|
|
3898
|
+
const outPath = path9.resolve(process.env.NEAT_OUT_PATH ?? fallback);
|
|
3661
3899
|
const result = await runInit({
|
|
3662
3900
|
scanPath,
|
|
3663
3901
|
outPath,
|
|
@@ -3678,21 +3916,21 @@ async function main() {
|
|
|
3678
3916
|
usage();
|
|
3679
3917
|
process.exit(2);
|
|
3680
3918
|
}
|
|
3681
|
-
const scanPath =
|
|
3682
|
-
const stat = await
|
|
3919
|
+
const scanPath = path9.resolve(target);
|
|
3920
|
+
const stat = await fs8.stat(scanPath).catch(() => null);
|
|
3683
3921
|
if (!stat || !stat.isDirectory()) {
|
|
3684
3922
|
console.error(`neat watch: ${scanPath} is not a directory`);
|
|
3685
3923
|
process.exit(2);
|
|
3686
3924
|
}
|
|
3687
|
-
const projectPaths = pathsForProject(project,
|
|
3688
|
-
const outPath =
|
|
3689
|
-
const errorsPath =
|
|
3690
|
-
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))
|
|
3691
3929
|
);
|
|
3692
|
-
const staleEventsPath =
|
|
3693
|
-
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))
|
|
3694
3932
|
);
|
|
3695
|
-
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;
|
|
3696
3934
|
const handle = await startWatch(getGraph(project), {
|
|
3697
3935
|
scanPath,
|
|
3698
3936
|
outPath,
|
|
@@ -3838,11 +4076,11 @@ async function main() {
|
|
|
3838
4076
|
process.exit(1);
|
|
3839
4077
|
}
|
|
3840
4078
|
async function tryOrchestrator(cmd, parsed) {
|
|
3841
|
-
const scanPath =
|
|
3842
|
-
const stat = await
|
|
4079
|
+
const scanPath = path9.resolve(cmd);
|
|
4080
|
+
const stat = await fs8.stat(scanPath).catch(() => null);
|
|
3843
4081
|
if (!stat || !stat.isDirectory()) return null;
|
|
3844
4082
|
const projectExplicit = parsed.project !== null;
|
|
3845
|
-
const projectName = projectExplicit ? parsed.project :
|
|
4083
|
+
const projectName = projectExplicit ? parsed.project : path9.basename(scanPath);
|
|
3846
4084
|
const result = await runOrchestrator({
|
|
3847
4085
|
scanPath,
|
|
3848
4086
|
project: projectName,
|