@neat.is/core 0.4.17 → 0.4.18
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-6CO7C4IU.js → chunk-BGPWBRLU.js} +4 -2
- package/dist/{chunk-6CO7C4IU.js.map → chunk-BGPWBRLU.js.map} +1 -1
- package/dist/{chunk-CEDXXMGO.js → chunk-GXWBMJDJ.js} +207 -22
- package/dist/chunk-GXWBMJDJ.js.map +1 -0
- package/dist/{chunk-GHPHVXYM.js → chunk-MTXF77TN.js} +2 -2
- package/dist/{chunk-LUDSPX5N.js → chunk-T3X6XJPU.js} +111 -2
- package/dist/{chunk-LUDSPX5N.js.map → chunk-T3X6XJPU.js.map} +1 -1
- package/dist/cli.cjs +453 -135
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +256 -70
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +193 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -13
- package/dist/index.d.ts +43 -13
- package/dist/index.js +4 -4
- package/dist/neatd.cjs +355 -57
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +161 -37
- package/dist/neatd.js.map +1 -1
- package/dist/{otel-grpc-QAISVAY5.js → otel-grpc-I67ONCRM.js} +3 -3
- package/dist/server.cjs +2 -0
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +3 -3
- package/package.json +2 -2
- package/dist/chunk-CEDXXMGO.js.map +0 -1
- /package/dist/{chunk-GHPHVXYM.js.map → chunk-MTXF77TN.js.map} +0 -0
- /package/dist/{otel-grpc-QAISVAY5.js.map → otel-grpc-I67ONCRM.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
readDaemonRecord
|
|
4
|
+
} from "./chunk-GXWBMJDJ.js";
|
|
2
5
|
import {
|
|
3
6
|
buildSearchIndex
|
|
4
7
|
} from "./chunk-XOOCA5T7.js";
|
|
@@ -24,10 +27,12 @@ import {
|
|
|
24
27
|
ensureCompatLoaded,
|
|
25
28
|
evaluateAllPolicies,
|
|
26
29
|
extractFromDirectory,
|
|
30
|
+
findDaemonByProject,
|
|
27
31
|
formatExtractionBanner,
|
|
28
32
|
formatPrecisionFloorBanner,
|
|
29
33
|
getGraph,
|
|
30
34
|
isStrictExtractionEnabled,
|
|
35
|
+
listMachineProjects,
|
|
31
36
|
listProjects,
|
|
32
37
|
loadGraphFromDisk,
|
|
33
38
|
loadPolicyFile,
|
|
@@ -37,25 +42,27 @@ import {
|
|
|
37
42
|
pathsForProject,
|
|
38
43
|
promoteFrontierNodes,
|
|
39
44
|
pruneRegistry,
|
|
45
|
+
removeDaemonRecord,
|
|
40
46
|
removeProject,
|
|
41
47
|
resetGraph,
|
|
42
48
|
retireEdgesByFile,
|
|
43
49
|
runPackageManagerInstall,
|
|
44
50
|
saveGraphToDisk,
|
|
45
51
|
setStatus,
|
|
52
|
+
signalDaemonStop,
|
|
46
53
|
startPersistLoop,
|
|
47
54
|
startStalenessLoop
|
|
48
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-T3X6XJPU.js";
|
|
49
56
|
import {
|
|
50
57
|
startOtelGrpcReceiver
|
|
51
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-MTXF77TN.js";
|
|
52
59
|
import {
|
|
53
60
|
__dirname,
|
|
54
61
|
__require,
|
|
55
62
|
assertBindAuthority,
|
|
56
63
|
buildOtelReceiver,
|
|
57
64
|
readAuthEnv
|
|
58
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-BGPWBRLU.js";
|
|
59
66
|
|
|
60
67
|
// src/cli.ts
|
|
61
68
|
import path9 from "path";
|
|
@@ -745,9 +752,50 @@ import semver from "semver";
|
|
|
745
752
|
|
|
746
753
|
// src/installers/templates.ts
|
|
747
754
|
var OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-069). OpenTelemetry auto-instrumentation hook.";
|
|
748
|
-
var OTEL_INIT_STAMP = "// neat-template-version:
|
|
755
|
+
var OTEL_INIT_STAMP = "// neat-template-version: 6 \u2014 daemon.json endpoint resolution (ADR-096) + layered file-first capture (ADR-090).";
|
|
749
756
|
var OTEL_OTLP_HEADERS_JS = "if (process.env.NEAT_OTEL_TOKEN) process.env.OTEL_EXPORTER_OTLP_HEADERS ||= 'Authorization=Bearer ' + process.env.NEAT_OTEL_TOKEN";
|
|
750
757
|
var OTEL_OTLP_PROTOCOL_JS = "process.env.OTEL_EXPORTER_OTLP_PROTOCOL ||= 'http/json'";
|
|
758
|
+
var OTEL_ENDPOINT_RESOLVER_CJS = `;(function () {
|
|
759
|
+
try {
|
|
760
|
+
if (process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return
|
|
761
|
+
const __neatFs = require('node:fs')
|
|
762
|
+
const __neatPath = require('node:path')
|
|
763
|
+
let __neatDir = process.cwd()
|
|
764
|
+
for (let __i = 0; __i < 8; __i++) {
|
|
765
|
+
try {
|
|
766
|
+
const __rec = JSON.parse(__neatFs.readFileSync(__neatPath.join(__neatDir, 'neat-out', 'daemon.json'), 'utf8'))
|
|
767
|
+
if (__rec && __rec.ports && typeof __rec.ports.otlp === 'number') {
|
|
768
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://localhost:' + __rec.ports.otlp + '/v1/traces'
|
|
769
|
+
break
|
|
770
|
+
}
|
|
771
|
+
} catch (_e) {}
|
|
772
|
+
const __parent = __neatPath.dirname(__neatDir)
|
|
773
|
+
if (__parent === __neatDir) break
|
|
774
|
+
__neatDir = __parent
|
|
775
|
+
}
|
|
776
|
+
} catch (_e) {}
|
|
777
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/v1/traces'
|
|
778
|
+
})()`;
|
|
779
|
+
var OTEL_ESM_NODE_IMPORTS = "import { readFileSync as __neatReadFileSync } from 'node:fs'\nimport { join as __neatJoin, dirname as __neatDirname } from 'node:path'";
|
|
780
|
+
var OTEL_ENDPOINT_RESOLVER_ESM = `;(function () {
|
|
781
|
+
try {
|
|
782
|
+
if (process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || process.env.OTEL_EXPORTER_OTLP_ENDPOINT) return
|
|
783
|
+
let __neatDir = process.cwd()
|
|
784
|
+
for (let __i = 0; __i < 8; __i++) {
|
|
785
|
+
try {
|
|
786
|
+
const __rec = JSON.parse(__neatReadFileSync(__neatJoin(__neatDir, 'neat-out', 'daemon.json'), 'utf8'))
|
|
787
|
+
if (__rec && __rec.ports && typeof __rec.ports.otlp === 'number') {
|
|
788
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = 'http://localhost:' + __rec.ports.otlp + '/v1/traces'
|
|
789
|
+
break
|
|
790
|
+
}
|
|
791
|
+
} catch (_e) {}
|
|
792
|
+
const __parent = __neatDirname(__neatDir)
|
|
793
|
+
if (__parent === __neatDir) break
|
|
794
|
+
__neatDir = __parent
|
|
795
|
+
}
|
|
796
|
+
} catch (_e) {}
|
|
797
|
+
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||= 'http://localhost:4318/v1/traces'
|
|
798
|
+
})()`;
|
|
751
799
|
function neatCaptureSource(ts) {
|
|
752
800
|
const spanT = ts ? ": any" : "";
|
|
753
801
|
const strOpt = ts ? ": string | undefined" : "";
|
|
@@ -1036,7 +1084,7 @@ try {
|
|
|
1036
1084
|
var OTEL_INIT_CJS = `${OTEL_INIT_HEADER}
|
|
1037
1085
|
${OTEL_INIT_STAMP}
|
|
1038
1086
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
1039
|
-
|
|
1087
|
+
${OTEL_ENDPOINT_RESOLVER_CJS}
|
|
1040
1088
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1041
1089
|
${OTEL_OTLP_HEADERS_JS}
|
|
1042
1090
|
|
|
@@ -1054,8 +1102,9 @@ ${neatWireCaptureSource(false)}
|
|
|
1054
1102
|
`;
|
|
1055
1103
|
var OTEL_INIT_ESM = `${OTEL_INIT_HEADER}
|
|
1056
1104
|
${OTEL_INIT_STAMP}
|
|
1105
|
+
${OTEL_ESM_NODE_IMPORTS}
|
|
1057
1106
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
1058
|
-
|
|
1107
|
+
${OTEL_ENDPOINT_RESOLVER_ESM}
|
|
1059
1108
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1060
1109
|
${OTEL_OTLP_HEADERS_JS}
|
|
1061
1110
|
|
|
@@ -1078,8 +1127,9 @@ ${OTEL_INIT_STAMP}
|
|
|
1078
1127
|
// strict user tsconfig would reject; suppressing type-checking here keeps the
|
|
1079
1128
|
// generated file from breaking the host project's \`tsc\` gate (#427) without
|
|
1080
1129
|
// constraining the runtime logic. The file is regenerated, never hand-edited.
|
|
1130
|
+
${OTEL_ESM_NODE_IMPORTS}
|
|
1081
1131
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
1082
|
-
|
|
1132
|
+
${OTEL_ENDPOINT_RESOLVER_ESM}
|
|
1083
1133
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1084
1134
|
${OTEL_OTLP_HEADERS_JS}
|
|
1085
1135
|
|
|
@@ -1101,11 +1151,14 @@ ${registrations.join("\n")}
|
|
|
1101
1151
|
`;
|
|
1102
1152
|
return template.replace(/__SERVICE_NAME__/g, serviceName).replace(/__PROJECT__/g, projectName).replace(/__INSTRUMENTATION_BLOCK__\n?/g, block);
|
|
1103
1153
|
}
|
|
1104
|
-
function renderEnvNeat(serviceName,
|
|
1154
|
+
function renderEnvNeat(serviceName, _projectName) {
|
|
1105
1155
|
return [
|
|
1106
1156
|
"# Generated by `neat init --apply` (ADR-069).",
|
|
1107
1157
|
`OTEL_SERVICE_NAME=${serviceName}`,
|
|
1108
|
-
|
|
1158
|
+
"# Advisory only \u2014 the generated otel-init resolves the live endpoint from",
|
|
1159
|
+
"# <project>/neat-out/daemon.json (ports.otlp) at boot (ADR-096). This is the",
|
|
1160
|
+
"# canonical default the daemon takes when its first-choice OTLP port is free.",
|
|
1161
|
+
"OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces",
|
|
1109
1162
|
"OTEL_EXPORTER_OTLP_PROTOCOL=http/json",
|
|
1110
1163
|
"# Set NEAT_OTEL_TOKEN to the daemon's OTLP secret to authenticate exported spans (#410).",
|
|
1111
1164
|
"# NEAT_OTEL_TOKEN=",
|
|
@@ -1128,8 +1181,9 @@ export async function register() {
|
|
|
1128
1181
|
}
|
|
1129
1182
|
`;
|
|
1130
1183
|
var NEXT_INSTRUMENTATION_NODE_TS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
1184
|
+
${OTEL_ESM_NODE_IMPORTS}
|
|
1131
1185
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
1132
|
-
|
|
1186
|
+
${OTEL_ENDPOINT_RESOLVER_ESM}
|
|
1133
1187
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1134
1188
|
${OTEL_OTLP_HEADERS_JS}
|
|
1135
1189
|
|
|
@@ -1142,7 +1196,7 @@ new NodeSDK({ instrumentations }).start()
|
|
|
1142
1196
|
`;
|
|
1143
1197
|
var NEXT_INSTRUMENTATION_NODE_JS = `${NEXT_INSTRUMENTATION_HEADER}
|
|
1144
1198
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
1145
|
-
|
|
1199
|
+
${OTEL_ENDPOINT_RESOLVER_CJS}
|
|
1146
1200
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1147
1201
|
${OTEL_OTLP_HEADERS_JS}
|
|
1148
1202
|
|
|
@@ -1161,8 +1215,9 @@ ${registrations.join("\n")}
|
|
|
1161
1215
|
}
|
|
1162
1216
|
var FRAMEWORK_OTEL_INIT_HEADER = "// Generated by `neat init --apply` (ADR-074). OpenTelemetry SDK hook.";
|
|
1163
1217
|
var FRAMEWORK_OTEL_INIT_TS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
|
|
1218
|
+
${OTEL_ESM_NODE_IMPORTS}
|
|
1164
1219
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
1165
|
-
|
|
1220
|
+
${OTEL_ENDPOINT_RESOLVER_ESM}
|
|
1166
1221
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1167
1222
|
${OTEL_OTLP_HEADERS_JS}
|
|
1168
1223
|
|
|
@@ -1177,7 +1232,7 @@ sdk.start()
|
|
|
1177
1232
|
`;
|
|
1178
1233
|
var FRAMEWORK_OTEL_INIT_JS_BODY = `${FRAMEWORK_OTEL_INIT_HEADER}
|
|
1179
1234
|
process.env.OTEL_SERVICE_NAME ||= '__SERVICE_NAME__'
|
|
1180
|
-
|
|
1235
|
+
${OTEL_ENDPOINT_RESOLVER_CJS}
|
|
1181
1236
|
${OTEL_OTLP_PROTOCOL_JS}
|
|
1182
1237
|
${OTEL_OTLP_HEADERS_JS}
|
|
1183
1238
|
|
|
@@ -2834,10 +2889,6 @@ async function fetchDaemonHealth(restPort) {
|
|
|
2834
2889
|
});
|
|
2835
2890
|
});
|
|
2836
2891
|
}
|
|
2837
|
-
async function checkDaemonHealth(restPort) {
|
|
2838
|
-
const body = await fetchDaemonHealth(restPort);
|
|
2839
|
-
return body !== null;
|
|
2840
|
-
}
|
|
2841
2892
|
async function probeProjectHealth(restPort, name) {
|
|
2842
2893
|
return new Promise((resolve) => {
|
|
2843
2894
|
const req = http.get(
|
|
@@ -2913,12 +2964,6 @@ async function isPortFree(port) {
|
|
|
2913
2964
|
server.listen(port, "127.0.0.1");
|
|
2914
2965
|
});
|
|
2915
2966
|
}
|
|
2916
|
-
async function probePortsFree() {
|
|
2917
|
-
for (const port of NEAT_PORTS) {
|
|
2918
|
-
if (!await isPortFree(port)) return { free: false, held: port };
|
|
2919
|
-
}
|
|
2920
|
-
return { free: true };
|
|
2921
|
-
}
|
|
2922
2967
|
function formatPortCollisionMessage(port) {
|
|
2923
2968
|
return [
|
|
2924
2969
|
`neat: port ${port} is in use; the NEAT daemon needs it.`,
|
|
@@ -2926,7 +2971,78 @@ function formatPortCollisionMessage(port) {
|
|
|
2926
2971
|
` \`lsof -i :${port}\` to find the holding process.`
|
|
2927
2972
|
];
|
|
2928
2973
|
}
|
|
2929
|
-
|
|
2974
|
+
var PORT_ALLOCATION_ATTEMPTS = 8;
|
|
2975
|
+
var PORT_STRIDE = 1;
|
|
2976
|
+
async function tripleFree(ports) {
|
|
2977
|
+
for (const p of [ports.rest, ports.otlp, ports.web]) {
|
|
2978
|
+
if (!await isPortFree(p)) return false;
|
|
2979
|
+
}
|
|
2980
|
+
return true;
|
|
2981
|
+
}
|
|
2982
|
+
async function allocatePorts() {
|
|
2983
|
+
const [baseRest, baseOtlp, baseWeb] = NEAT_PORTS;
|
|
2984
|
+
for (let i = 0; i < PORT_ALLOCATION_ATTEMPTS; i++) {
|
|
2985
|
+
const candidate = {
|
|
2986
|
+
rest: baseRest + i * PORT_STRIDE,
|
|
2987
|
+
otlp: baseOtlp + i * PORT_STRIDE,
|
|
2988
|
+
web: baseWeb + i * PORT_STRIDE
|
|
2989
|
+
};
|
|
2990
|
+
if (await tripleFree(candidate)) return candidate;
|
|
2991
|
+
}
|
|
2992
|
+
return null;
|
|
2993
|
+
}
|
|
2994
|
+
async function persistedPortsFor(scanPath) {
|
|
2995
|
+
const record = await readDaemonRecord(scanPath);
|
|
2996
|
+
if (!record) return null;
|
|
2997
|
+
return { rest: record.ports.rest, otlp: record.ports.otlp, web: record.ports.web };
|
|
2998
|
+
}
|
|
2999
|
+
async function acquireSpawnLock(scanPath) {
|
|
3000
|
+
const lockPath = path7.join(scanPath, "neat-out", "daemon.spawn.lock");
|
|
3001
|
+
await fs6.mkdir(path7.dirname(lockPath), { recursive: true });
|
|
3002
|
+
const STALE_LOCK_MS = 6e4;
|
|
3003
|
+
try {
|
|
3004
|
+
const fd = await fs6.open(lockPath, "wx");
|
|
3005
|
+
await fd.writeFile(`${process.pid}
|
|
3006
|
+
`, "utf8");
|
|
3007
|
+
await fd.close();
|
|
3008
|
+
return async () => {
|
|
3009
|
+
await fs6.unlink(lockPath).catch(() => {
|
|
3010
|
+
});
|
|
3011
|
+
};
|
|
3012
|
+
} catch (err) {
|
|
3013
|
+
if (err.code !== "EEXIST") return null;
|
|
3014
|
+
try {
|
|
3015
|
+
const stat = await fs6.stat(lockPath);
|
|
3016
|
+
if (Date.now() - stat.mtimeMs > STALE_LOCK_MS) {
|
|
3017
|
+
await fs6.unlink(lockPath).catch(() => {
|
|
3018
|
+
});
|
|
3019
|
+
return acquireSpawnLock(scanPath);
|
|
3020
|
+
}
|
|
3021
|
+
} catch {
|
|
3022
|
+
return acquireSpawnLock(scanPath);
|
|
3023
|
+
}
|
|
3024
|
+
return null;
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
async function waitForPeerDaemon(restPort, project, timeoutMs) {
|
|
3028
|
+
const deadline = Date.now() + timeoutMs;
|
|
3029
|
+
while (Date.now() < deadline) {
|
|
3030
|
+
if (await healthIsForProject(restPort, project)) return true;
|
|
3031
|
+
await new Promise((r) => setTimeout(r, PROBE_INTERVAL_MS));
|
|
3032
|
+
}
|
|
3033
|
+
return healthIsForProject(restPort, project);
|
|
3034
|
+
}
|
|
3035
|
+
async function healthIsForProject(restPort, project) {
|
|
3036
|
+
const body = await fetchDaemonHealth(restPort);
|
|
3037
|
+
if (body === null) return false;
|
|
3038
|
+
const named = body.project;
|
|
3039
|
+
if (typeof named === "string") return named === project;
|
|
3040
|
+
if (Array.isArray(body.projects)) {
|
|
3041
|
+
return body.projects.some((p) => p.name === project);
|
|
3042
|
+
}
|
|
3043
|
+
return false;
|
|
3044
|
+
}
|
|
3045
|
+
function spawnDaemonDetached(spec) {
|
|
2930
3046
|
const here = path7.dirname(new URL(import.meta.url).pathname);
|
|
2931
3047
|
const candidates = [
|
|
2932
3048
|
path7.join(here, "neatd.cjs"),
|
|
@@ -2950,6 +3066,13 @@ function spawnDaemonDetached() {
|
|
|
2950
3066
|
if (!hasToken && (!env.HOST || env.HOST.length === 0)) {
|
|
2951
3067
|
env.HOST = "127.0.0.1";
|
|
2952
3068
|
}
|
|
3069
|
+
if (spec) {
|
|
3070
|
+
env.NEAT_PROJECT = spec.project;
|
|
3071
|
+
env.NEAT_PROJECT_PATH = spec.projectPath;
|
|
3072
|
+
env.PORT = String(spec.ports.rest);
|
|
3073
|
+
env.OTEL_PORT = String(spec.ports.otlp);
|
|
3074
|
+
env.NEAT_WEB_PORT = String(spec.ports.web);
|
|
3075
|
+
}
|
|
2953
3076
|
const child = spawn2(process.execPath, [entry2, "start"], {
|
|
2954
3077
|
detached: true,
|
|
2955
3078
|
// stderr inherits the orchestrator's fd so the daemon's
|
|
@@ -3069,48 +3192,75 @@ async function runOrchestrator(opts) {
|
|
|
3069
3192
|
result.exitCode = 1;
|
|
3070
3193
|
}
|
|
3071
3194
|
}
|
|
3072
|
-
const restPort = Number(process.env.PORT ?? 8080);
|
|
3073
3195
|
const timeoutMs = opts.daemonReadyTimeoutMs ?? DEFAULT_DAEMON_READY_TIMEOUT_MS;
|
|
3074
|
-
|
|
3196
|
+
const persistedPorts = await persistedPortsFor(opts.scanPath);
|
|
3197
|
+
let allocated = null;
|
|
3198
|
+
if (persistedPorts && await healthIsForProject(persistedPorts.rest, currentProjectName)) {
|
|
3075
3199
|
result.steps.daemon = "already-running";
|
|
3200
|
+
allocated = persistedPorts;
|
|
3076
3201
|
} else {
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3202
|
+
if (persistedPorts && await isPortFree(persistedPorts.rest) && await tripleFree(persistedPorts)) {
|
|
3203
|
+
allocated = persistedPorts;
|
|
3204
|
+
} else {
|
|
3205
|
+
allocated = await allocatePorts();
|
|
3206
|
+
}
|
|
3207
|
+
if (!allocated) {
|
|
3208
|
+
for (const line of formatPortCollisionMessage(NEAT_PORTS[0])) {
|
|
3080
3209
|
console.error(line);
|
|
3081
3210
|
}
|
|
3082
3211
|
result.exitCode = 3;
|
|
3083
3212
|
return result;
|
|
3084
3213
|
}
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
if (!ready.ready) {
|
|
3095
|
-
console.error(`neat: daemon did not become ready within ${timeoutMs}ms`);
|
|
3096
|
-
if (ready.stillBootstrapping.length > 0) {
|
|
3097
|
-
console.error(
|
|
3098
|
-
`neat: still bootstrapping: ${ready.stillBootstrapping.join(", ")}`
|
|
3099
|
-
);
|
|
3214
|
+
const release = await acquireSpawnLock(opts.scanPath);
|
|
3215
|
+
if (!release) {
|
|
3216
|
+
const reused = await waitForPeerDaemon(allocated.rest, currentProjectName, timeoutMs);
|
|
3217
|
+
if (reused) {
|
|
3218
|
+
result.steps.daemon = "already-running";
|
|
3219
|
+
} else {
|
|
3220
|
+
console.error("neat: another `neat` is spawning this project but its daemon did not come up in time");
|
|
3221
|
+
result.exitCode = 1;
|
|
3222
|
+
return result;
|
|
3100
3223
|
}
|
|
3101
|
-
|
|
3102
|
-
|
|
3224
|
+
} else {
|
|
3225
|
+
try {
|
|
3226
|
+
if (await healthIsForProject(allocated.rest, currentProjectName)) {
|
|
3227
|
+
result.steps.daemon = "already-running";
|
|
3228
|
+
} else {
|
|
3229
|
+
spawnDaemonDetached({
|
|
3230
|
+
project: currentProjectName,
|
|
3231
|
+
projectPath: opts.scanPath,
|
|
3232
|
+
ports: allocated
|
|
3233
|
+
});
|
|
3234
|
+
const ready = await waitForDaemonReady(allocated.rest, timeoutMs);
|
|
3235
|
+
result.steps.daemon = ready.ready ? "spawned" : "timed-out";
|
|
3236
|
+
if (!ready.ready) {
|
|
3237
|
+
console.error(`neat: daemon did not become ready within ${timeoutMs}ms`);
|
|
3238
|
+
if (ready.stillBootstrapping.length > 0) {
|
|
3239
|
+
console.error(`neat: still bootstrapping: ${ready.stillBootstrapping.join(", ")}`);
|
|
3240
|
+
}
|
|
3241
|
+
if (ready.brokenProjects.length > 0) {
|
|
3242
|
+
console.error(`neat: broken projects: ${ready.brokenProjects.join(", ")}`);
|
|
3243
|
+
}
|
|
3244
|
+
result.exitCode = 1;
|
|
3245
|
+
return result;
|
|
3246
|
+
}
|
|
3247
|
+
if (ready.brokenProjects.length > 0) {
|
|
3248
|
+
console.warn(
|
|
3249
|
+
`neat: ${ready.brokenProjects.length} project(s) reported broken: ${ready.brokenProjects.join(", ")}`
|
|
3250
|
+
);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
} catch (err) {
|
|
3254
|
+
console.error(`neat: daemon spawn failed \u2014 ${err.message}`);
|
|
3255
|
+
result.exitCode = 1;
|
|
3256
|
+
return result;
|
|
3257
|
+
} finally {
|
|
3258
|
+
await release();
|
|
3103
3259
|
}
|
|
3104
|
-
result.exitCode = 1;
|
|
3105
|
-
return result;
|
|
3106
|
-
}
|
|
3107
|
-
if (ready.brokenProjects.length > 0) {
|
|
3108
|
-
console.warn(
|
|
3109
|
-
`neat: ${ready.brokenProjects.length} project(s) reported broken: ${ready.brokenProjects.join(", ")}`
|
|
3110
|
-
);
|
|
3111
3260
|
}
|
|
3112
3261
|
}
|
|
3113
|
-
const
|
|
3262
|
+
const webPort = allocated?.web ?? NEAT_PORTS[2];
|
|
3263
|
+
const dashboardUrl = opts.dashboardUrl ?? `http://localhost:${webPort}`;
|
|
3114
3264
|
if (opts.noOpen || !process.stdout.isTTY) {
|
|
3115
3265
|
result.steps.browser = "skipped";
|
|
3116
3266
|
} else {
|
|
@@ -3678,7 +3828,7 @@ async function resolveProjectEntry(opts) {
|
|
|
3678
3828
|
}
|
|
3679
3829
|
return null;
|
|
3680
3830
|
}
|
|
3681
|
-
async function
|
|
3831
|
+
async function checkDaemonHealth(baseUrl) {
|
|
3682
3832
|
try {
|
|
3683
3833
|
const res = await fetch(`${baseUrl.replace(/\/$/, "")}/health`, {
|
|
3684
3834
|
signal: AbortSignal.timeout(1500)
|
|
@@ -3779,7 +3929,7 @@ async function runSync(opts) {
|
|
|
3779
3929
|
}
|
|
3780
3930
|
} else {
|
|
3781
3931
|
const daemonUrl = opts.daemonUrl ?? process.env.NEAT_API_URL ?? "http://localhost:8080";
|
|
3782
|
-
const healthy = await
|
|
3932
|
+
const healthy = await checkDaemonHealth(daemonUrl);
|
|
3783
3933
|
if (healthy) {
|
|
3784
3934
|
try {
|
|
3785
3935
|
await pushSnapshotToRemote({
|
|
@@ -3853,11 +4003,15 @@ function usage() {
|
|
|
3853
4003
|
console.log(" watch <path> Start neat-core, watch <path>, re-extract on changes.");
|
|
3854
4004
|
console.log(" PORT (default 8080), OTEL_PORT (4318), HOST (0.0.0.0)");
|
|
3855
4005
|
console.log(" control listeners. NEAT_OTLP_GRPC=true also opens 4317.");
|
|
3856
|
-
console.log(" list
|
|
3857
|
-
console.log("
|
|
3858
|
-
console.log("
|
|
4006
|
+
console.log(" list Report the daemons running on this machine (alias: ps).");
|
|
4007
|
+
console.log(" Reads ~/.neat/daemons/ and folds in any registered");
|
|
4008
|
+
console.log(" project no daemon has self-described yet.");
|
|
4009
|
+
console.log(" ps Alias of list.");
|
|
4010
|
+
console.log(" pause <name> Stop a project's daemon until it is started again.");
|
|
4011
|
+
console.log(" resume <name> Bring a paused project back; for a stopped daemon, re-run");
|
|
4012
|
+
console.log(" `neat <path>` to start it.");
|
|
3859
4013
|
console.log(" uninstall <name>");
|
|
3860
|
-
console.log("
|
|
4014
|
+
console.log(" Stop a project's daemon and retire it. Does not touch");
|
|
3861
4015
|
console.log(" neat-out/, policy.json, or any user file.");
|
|
3862
4016
|
console.log(" prune Drop registry entries whose path is gone from disk.");
|
|
3863
4017
|
console.log(" Flags: --json emit the removed list as JSON");
|
|
@@ -4054,6 +4208,14 @@ function printVersion() {
|
|
|
4054
4208
|
process.stdout.write(`${readPackageVersion()}
|
|
4055
4209
|
`);
|
|
4056
4210
|
}
|
|
4211
|
+
function formatMachineProjectRow(r) {
|
|
4212
|
+
if (r.ports) {
|
|
4213
|
+
const where = r.pid !== void 0 ? ` pid=${r.pid}` : "";
|
|
4214
|
+
return `${r.project} ${r.state} rest=${r.ports.rest} otlp=${r.ports.otlp} web=${r.ports.web} ${r.projectPath}${where}`;
|
|
4215
|
+
}
|
|
4216
|
+
const status = r.registryStatus ? ` (${r.registryStatus})` : "";
|
|
4217
|
+
return `${r.project} ${r.state}${status} ${r.projectPath}`;
|
|
4218
|
+
}
|
|
4057
4219
|
function printDiscoveryReport(opts, services) {
|
|
4058
4220
|
const languages = [...new Set(services.map((s) => s.node.language))].sort();
|
|
4059
4221
|
const mode = opts.dryRun ? "dry-run" : opts.apply ? "apply" : "patch-only";
|
|
@@ -4382,16 +4544,14 @@ async function main() {
|
|
|
4382
4544
|
process.on("SIGINT", shutdown);
|
|
4383
4545
|
return;
|
|
4384
4546
|
}
|
|
4385
|
-
if (cmd === "list") {
|
|
4386
|
-
const
|
|
4387
|
-
if (
|
|
4388
|
-
console.log("no projects registered. run `neat init <path>` to register one.");
|
|
4547
|
+
if (cmd === "list" || cmd === "ps") {
|
|
4548
|
+
const rows = await listMachineProjects();
|
|
4549
|
+
if (rows.length === 0) {
|
|
4550
|
+
console.log("no daemons running and no projects registered. run `neat init <path>` to register one.");
|
|
4389
4551
|
return;
|
|
4390
4552
|
}
|
|
4391
|
-
for (const
|
|
4392
|
-
|
|
4393
|
-
const langs = p.languages.length > 0 ? p.languages.join(",") : "-";
|
|
4394
|
-
console.log(`${p.name} ${p.status} ${langs} ${p.path} last-seen=${seen}`);
|
|
4553
|
+
for (const r of rows) {
|
|
4554
|
+
console.log(formatMachineProjectRow(r));
|
|
4395
4555
|
}
|
|
4396
4556
|
return;
|
|
4397
4557
|
}
|
|
@@ -4402,6 +4562,15 @@ async function main() {
|
|
|
4402
4562
|
usage();
|
|
4403
4563
|
process.exit(2);
|
|
4404
4564
|
}
|
|
4565
|
+
const daemon = await findDaemonByProject(name);
|
|
4566
|
+
if (daemon) {
|
|
4567
|
+
if (daemon.live && signalDaemonStop(daemon.record.pid)) {
|
|
4568
|
+
console.log(`paused: ${name} (${daemon.record.projectPath}) \u2014 stopped daemon pid ${daemon.record.pid}`);
|
|
4569
|
+
} else {
|
|
4570
|
+
console.log(`paused: ${name} (${daemon.record.projectPath}) \u2014 daemon was not running`);
|
|
4571
|
+
}
|
|
4572
|
+
return;
|
|
4573
|
+
}
|
|
4405
4574
|
try {
|
|
4406
4575
|
const entry2 = await setStatus(name, "paused");
|
|
4407
4576
|
console.log(`paused: ${entry2.name} (${entry2.path})`);
|
|
@@ -4418,6 +4587,15 @@ async function main() {
|
|
|
4418
4587
|
usage();
|
|
4419
4588
|
process.exit(2);
|
|
4420
4589
|
}
|
|
4590
|
+
const daemon = await findDaemonByProject(name);
|
|
4591
|
+
if (daemon) {
|
|
4592
|
+
if (daemon.live) {
|
|
4593
|
+
console.log(`resume: ${name} (${daemon.record.projectPath}) \u2014 daemon already running`);
|
|
4594
|
+
} else {
|
|
4595
|
+
console.log(`resume: ${name} (${daemon.record.projectPath}) \u2014 run \`neat ${daemon.record.projectPath}\` to start its daemon again`);
|
|
4596
|
+
}
|
|
4597
|
+
return;
|
|
4598
|
+
}
|
|
4421
4599
|
try {
|
|
4422
4600
|
const entry2 = await setStatus(name, "active");
|
|
4423
4601
|
console.log(`resumed: ${entry2.name} (${entry2.path})`);
|
|
@@ -4439,12 +4617,20 @@ async function main() {
|
|
|
4439
4617
|
usage();
|
|
4440
4618
|
process.exit(2);
|
|
4441
4619
|
}
|
|
4620
|
+
const daemon = await findDaemonByProject(name);
|
|
4442
4621
|
const removed = await removeProject(name);
|
|
4443
|
-
if (!removed) {
|
|
4622
|
+
if (!daemon && !removed) {
|
|
4444
4623
|
console.error(`neat uninstall: no project named "${name}"`);
|
|
4445
4624
|
process.exit(1);
|
|
4446
4625
|
}
|
|
4447
|
-
|
|
4626
|
+
const projectPath2 = daemon?.record.projectPath ?? removed?.path ?? "(unknown path)";
|
|
4627
|
+
if (daemon) {
|
|
4628
|
+
if (daemon.live && signalDaemonStop(daemon.record.pid)) {
|
|
4629
|
+
console.log(`uninstall: ${name} \u2014 stopped daemon pid ${daemon.record.pid}`);
|
|
4630
|
+
}
|
|
4631
|
+
await removeDaemonRecord(daemon.source);
|
|
4632
|
+
}
|
|
4633
|
+
console.log(`unregistered: ${name} (${projectPath2})`);
|
|
4448
4634
|
console.log("note: neat-out/, policy.json, and other files at the project path were left in place.");
|
|
4449
4635
|
return;
|
|
4450
4636
|
}
|