@neat.is/core 0.9.18-dev.20260918 → 0.9.19
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-MGQSR6QZ.js → chunk-NTVHVZYC.js} +191 -29
- package/dist/chunk-NTVHVZYC.js.map +1 -0
- package/dist/cli.cjs +1133 -1013
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +158 -47
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +259 -96
- 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 +1 -1
- package/dist/neatd.cjs +271 -108
- package/dist/neatd.cjs.map +1 -1
- package/dist/neatd.js +1 -1
- package/package.json +3 -2
- package/dist/chunk-MGQSR6QZ.js.map +0 -1
|
@@ -48,7 +48,7 @@ import {
|
|
|
48
48
|
unlinkSync,
|
|
49
49
|
writeFileSync
|
|
50
50
|
} from "fs";
|
|
51
|
-
import
|
|
51
|
+
import path4 from "path";
|
|
52
52
|
import { createRequire } from "module";
|
|
53
53
|
|
|
54
54
|
// src/hang-sensor.ts
|
|
@@ -83,13 +83,13 @@ async function reconcileFrontierSurfaces(graph, errorsPath) {
|
|
|
83
83
|
// src/connectors/hosted.ts
|
|
84
84
|
var CREDENTIAL_REFRESH_SKEW_MS = 6e4;
|
|
85
85
|
var CP_REQUEST_TIMEOUT_MS = 1e4;
|
|
86
|
-
async function cpGet(
|
|
86
|
+
async function cpGet(path5, deps) {
|
|
87
87
|
const f = deps.fetchImpl ?? fetch;
|
|
88
|
-
const res = await f(`${deps.cpUrl.replace(/\/+$/, "")}${
|
|
88
|
+
const res = await f(`${deps.cpUrl.replace(/\/+$/, "")}${path5}`, {
|
|
89
89
|
headers: { authorization: `Bearer ${deps.daemonToken}`, accept: "application/json" },
|
|
90
90
|
signal: AbortSignal.timeout(CP_REQUEST_TIMEOUT_MS)
|
|
91
91
|
});
|
|
92
|
-
if (!res.ok) throw new Error(`control plane ${
|
|
92
|
+
if (!res.ok) throw new Error(`control plane ${path5} \u2192 HTTP ${res.status}`);
|
|
93
93
|
return await res.json();
|
|
94
94
|
}
|
|
95
95
|
function credentialRecord(provider, cred) {
|
|
@@ -195,6 +195,155 @@ async function maybeStartHostedConnectors(input) {
|
|
|
195
195
|
});
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
// src/connectors/hosted-repos.ts
|
|
199
|
+
import os from "os";
|
|
200
|
+
import path from "path";
|
|
201
|
+
import { mkdtemp, rm } from "fs/promises";
|
|
202
|
+
var CP_REQUEST_TIMEOUT_MS2 = 1e4;
|
|
203
|
+
var DEFAULT_SYNC_INTERVAL_MS = 5 * 6e4;
|
|
204
|
+
var CLONE_TIMEOUT_MS = 5 * 6e4;
|
|
205
|
+
function scrubToken(s) {
|
|
206
|
+
return s.replace(/x-access-token:[^@\s]+@/g, "x-access-token:***@");
|
|
207
|
+
}
|
|
208
|
+
async function cpGet2(pathname, deps) {
|
|
209
|
+
const f = deps.fetchImpl ?? fetch;
|
|
210
|
+
const res = await f(`${deps.cpUrl.replace(/\/+$/, "")}${pathname}`, {
|
|
211
|
+
headers: { authorization: `Bearer ${deps.daemonToken}`, accept: "application/json" },
|
|
212
|
+
signal: AbortSignal.timeout(CP_REQUEST_TIMEOUT_MS2)
|
|
213
|
+
});
|
|
214
|
+
if (!res.ok) throw new Error(`control plane ${pathname} \u2192 HTTP ${res.status}`);
|
|
215
|
+
return await res.json();
|
|
216
|
+
}
|
|
217
|
+
async function cpPostStatus(deps, owner, name, body) {
|
|
218
|
+
const f = deps.fetchImpl ?? fetch;
|
|
219
|
+
const url = `${deps.cpUrl.replace(/\/+$/, "")}/internal/projects/${deps.projectId}/repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}/status`;
|
|
220
|
+
const res = await f(url, {
|
|
221
|
+
method: "POST",
|
|
222
|
+
headers: { authorization: `Bearer ${deps.daemonToken}`, "content-type": "application/json" },
|
|
223
|
+
body: JSON.stringify(body),
|
|
224
|
+
signal: AbortSignal.timeout(CP_REQUEST_TIMEOUT_MS2)
|
|
225
|
+
});
|
|
226
|
+
if (!res.ok && res.status !== 404) throw new Error(`control plane status POST \u2192 HTTP ${res.status}`);
|
|
227
|
+
}
|
|
228
|
+
var defaultCloneRepo = async (cloneUrl, ref, destDir) => {
|
|
229
|
+
const [{ default: git }, httpMod, fs3] = await Promise.all([
|
|
230
|
+
import("isomorphic-git"),
|
|
231
|
+
import("isomorphic-git/http/node"),
|
|
232
|
+
import("fs")
|
|
233
|
+
]);
|
|
234
|
+
const http = httpMod.default ?? httpMod;
|
|
235
|
+
const parsed = new URL(cloneUrl);
|
|
236
|
+
const password = parsed.password || parsed.username;
|
|
237
|
+
const username = parsed.password ? parsed.username : "x-access-token";
|
|
238
|
+
const cleanUrl = `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
|
|
239
|
+
const clone = git.clone({
|
|
240
|
+
fs: fs3,
|
|
241
|
+
http,
|
|
242
|
+
dir: destDir,
|
|
243
|
+
url: cleanUrl,
|
|
244
|
+
// Absent ref → isomorphic-git clones the remote's default branch.
|
|
245
|
+
...ref ? { ref } : {},
|
|
246
|
+
singleBranch: true,
|
|
247
|
+
depth: 1,
|
|
248
|
+
onAuth: () => ({ username, password })
|
|
249
|
+
});
|
|
250
|
+
let timer;
|
|
251
|
+
const timeout = new Promise((_, reject) => {
|
|
252
|
+
timer = setTimeout(() => reject(new Error(`clone timed out after ${CLONE_TIMEOUT_MS}ms`)), CLONE_TIMEOUT_MS);
|
|
253
|
+
});
|
|
254
|
+
try {
|
|
255
|
+
await Promise.race([clone, timeout]);
|
|
256
|
+
} finally {
|
|
257
|
+
if (timer) clearTimeout(timer);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
function needsSync(r) {
|
|
261
|
+
return r.syncStatus === void 0 || r.syncStatus === "syncing";
|
|
262
|
+
}
|
|
263
|
+
async function syncOneRepo(r, input) {
|
|
264
|
+
const { deps, graph } = input;
|
|
265
|
+
const cloneRepo = input.cloneRepo ?? defaultCloneRepo;
|
|
266
|
+
const extract = input.extract ?? extractFromDirectory;
|
|
267
|
+
const label = `${r.owner}/${r.name}`;
|
|
268
|
+
const tmpRoot = input.tmpRoot ?? os.tmpdir();
|
|
269
|
+
let dir;
|
|
270
|
+
try {
|
|
271
|
+
dir = await mkdtemp(path.join(tmpRoot, "neat-repo-"));
|
|
272
|
+
await cloneRepo(r.cloneUrl, r.defaultBranch, dir);
|
|
273
|
+
await extract(graph, dir);
|
|
274
|
+
await cpPostStatus(deps, r.owner, r.name, {
|
|
275
|
+
syncStatus: "synced",
|
|
276
|
+
lastSyncAt: new Date(input.now?.() ?? Date.now()).toISOString()
|
|
277
|
+
});
|
|
278
|
+
} catch (err) {
|
|
279
|
+
input.onError?.(label, err);
|
|
280
|
+
await cpPostStatus(deps, r.owner, r.name, {
|
|
281
|
+
syncStatus: "failed",
|
|
282
|
+
detail: scrubToken(err.message).slice(0, 300)
|
|
283
|
+
}).catch(() => {
|
|
284
|
+
});
|
|
285
|
+
} finally {
|
|
286
|
+
if (dir) await rm(dir, { recursive: true, force: true }).catch(() => {
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
async function runRepoSyncPass(input) {
|
|
291
|
+
let repos;
|
|
292
|
+
try {
|
|
293
|
+
repos = await cpGet2(`/internal/projects/${input.deps.projectId}/repos`, input.deps);
|
|
294
|
+
} catch (err) {
|
|
295
|
+
input.onSkip?.("(all)", `control plane repo list unreadable \u2014 ${err.message}`);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (!Array.isArray(repos)) return;
|
|
299
|
+
for (const r of repos) {
|
|
300
|
+
if (!needsSync(r)) continue;
|
|
301
|
+
await syncOneRepo(r, input);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
async function startRepoSync(input) {
|
|
305
|
+
const intervalMs = input.intervalMs ?? DEFAULT_SYNC_INTERVAL_MS;
|
|
306
|
+
let stopped = false;
|
|
307
|
+
let running = false;
|
|
308
|
+
const tick = async () => {
|
|
309
|
+
if (stopped || running) return;
|
|
310
|
+
running = true;
|
|
311
|
+
try {
|
|
312
|
+
await runRepoSyncPass(input);
|
|
313
|
+
} finally {
|
|
314
|
+
running = false;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
void tick();
|
|
318
|
+
const timer = setInterval(() => {
|
|
319
|
+
void tick();
|
|
320
|
+
}, intervalMs);
|
|
321
|
+
if (typeof timer.unref === "function") timer.unref();
|
|
322
|
+
return () => {
|
|
323
|
+
stopped = true;
|
|
324
|
+
clearInterval(timer);
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
async function maybeStartRepoSync(input) {
|
|
328
|
+
const env = input.env ?? process.env;
|
|
329
|
+
const cpUrl = env.NEAT_CP_URL;
|
|
330
|
+
const projectId = env.NEAT_CP_PROJECT_ID;
|
|
331
|
+
const daemonToken = env.NEAT_AUTH_TOKEN;
|
|
332
|
+
if (!cpUrl || !projectId || !daemonToken) return () => {
|
|
333
|
+
};
|
|
334
|
+
return startRepoSync({
|
|
335
|
+
deps: { cpUrl, projectId, daemonToken, ...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {} },
|
|
336
|
+
graph: input.graph,
|
|
337
|
+
project: input.project,
|
|
338
|
+
...input.cloneRepo ? { cloneRepo: input.cloneRepo } : {},
|
|
339
|
+
...input.extract ? { extract: input.extract } : {},
|
|
340
|
+
...input.intervalMs !== void 0 ? { intervalMs: input.intervalMs } : {},
|
|
341
|
+
...input.tmpRoot ? { tmpRoot: input.tmpRoot } : {},
|
|
342
|
+
...input.onSkip ? { onSkip: input.onSkip } : {},
|
|
343
|
+
...input.onError ? { onError: input.onError } : {}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
|
|
198
347
|
// src/connectors/kubernetes/client.ts
|
|
199
348
|
import { Agent, request as httpsRequest } from "https";
|
|
200
349
|
function deploymentsPath(namespace) {
|
|
@@ -244,9 +393,9 @@ function makeK8sFetchImpl(transport) {
|
|
|
244
393
|
req.end();
|
|
245
394
|
}));
|
|
246
395
|
}
|
|
247
|
-
async function listResource(transport, namespace,
|
|
396
|
+
async function listResource(transport, namespace, path5, opts = {}) {
|
|
248
397
|
const base = opts.apiUrl ?? transport.server;
|
|
249
|
-
const url = `${base.replace(/\/$/, "")}${
|
|
398
|
+
const url = `${base.replace(/\/$/, "")}${path5}`;
|
|
250
399
|
const fetchImpl = opts.fetchImpl ?? makeK8sFetchImpl(transport);
|
|
251
400
|
const res = await junctionFetch(
|
|
252
401
|
url,
|
|
@@ -256,7 +405,7 @@ async function listResource(transport, namespace, path4, opts = {}) {
|
|
|
256
405
|
{ provider: "kubernetes", accountKey: `${safeHost(transport.server)}/${namespace}`, fetchImpl }
|
|
257
406
|
);
|
|
258
407
|
if (!res.ok) {
|
|
259
|
-
throw new Error(`kubernetes ${
|
|
408
|
+
throw new Error(`kubernetes ${path5} failed: ${res.status} ${res.statusText}`);
|
|
260
409
|
}
|
|
261
410
|
const json = await res.json();
|
|
262
411
|
return Array.isArray(json.items) ? json.items : [];
|
|
@@ -552,15 +701,15 @@ function createK8sResolveTarget(graph) {
|
|
|
552
701
|
|
|
553
702
|
// src/connectors/kubernetes/substrate.ts
|
|
554
703
|
import { readFile } from "fs/promises";
|
|
555
|
-
import
|
|
556
|
-
import
|
|
704
|
+
import os2 from "os";
|
|
705
|
+
import path2 from "path";
|
|
557
706
|
function defaultHome() {
|
|
558
707
|
const override = process.env.NEAT_HOME;
|
|
559
|
-
if (override && override.length > 0) return
|
|
560
|
-
return
|
|
708
|
+
if (override && override.length > 0) return path2.resolve(override);
|
|
709
|
+
return path2.join(os2.homedir(), ".neat");
|
|
561
710
|
}
|
|
562
711
|
function k8sSubstrateConfigPath(home = defaultHome()) {
|
|
563
|
-
return
|
|
712
|
+
return path2.join(home, "k8s.json");
|
|
564
713
|
}
|
|
565
714
|
async function readK8sSubstrateConfig(home = defaultHome()) {
|
|
566
715
|
let raw;
|
|
@@ -656,7 +805,7 @@ function createKubernetesConnector(graph, config, fetchImpl) {
|
|
|
656
805
|
|
|
657
806
|
// src/unrouted.ts
|
|
658
807
|
import { promises as fs } from "fs";
|
|
659
|
-
import
|
|
808
|
+
import path3 from "path";
|
|
660
809
|
function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new Date()) {
|
|
661
810
|
return {
|
|
662
811
|
timestamp: now.toISOString(),
|
|
@@ -666,34 +815,34 @@ function buildUnroutedSpanRecord(serviceName, traceId, now = /* @__PURE__ */ new
|
|
|
666
815
|
};
|
|
667
816
|
}
|
|
668
817
|
async function appendUnroutedSpan(neatHome, record) {
|
|
669
|
-
const target =
|
|
818
|
+
const target = path3.join(neatHome, "errors.ndjson");
|
|
670
819
|
await fs.mkdir(neatHome, { recursive: true });
|
|
671
820
|
await fs.appendFile(target, JSON.stringify(record) + "\n", "utf8");
|
|
672
821
|
}
|
|
673
822
|
function unroutedErrorsPath(neatHome) {
|
|
674
|
-
return
|
|
823
|
+
return path3.join(neatHome, "errors.ndjson");
|
|
675
824
|
}
|
|
676
825
|
|
|
677
826
|
// src/daemon.ts
|
|
678
827
|
import { NodeType } from "@neat.is/types";
|
|
679
828
|
function daemonJsonPath(scanPath) {
|
|
680
|
-
return
|
|
829
|
+
return path4.join(scanPath, "neat-out", "daemon.json");
|
|
681
830
|
}
|
|
682
831
|
function daemonsDiscoveryDir(home) {
|
|
683
832
|
const base = home && home.length > 0 ? home : neatHomeFromEnv();
|
|
684
|
-
return
|
|
833
|
+
return path4.join(base, "daemons");
|
|
685
834
|
}
|
|
686
835
|
function daemonDiscoveryPath(project, home) {
|
|
687
|
-
return
|
|
836
|
+
return path4.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
|
|
688
837
|
}
|
|
689
838
|
function sanitizeDiscoveryName(project) {
|
|
690
839
|
return project.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
691
840
|
}
|
|
692
841
|
function neatHomeFromEnv() {
|
|
693
842
|
const env = process.env.NEAT_HOME;
|
|
694
|
-
if (env && env.length > 0) return
|
|
843
|
+
if (env && env.length > 0) return path4.resolve(env);
|
|
695
844
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
696
|
-
return
|
|
845
|
+
return path4.join(home, ".neat");
|
|
697
846
|
}
|
|
698
847
|
async function readDaemonRecord(scanPath) {
|
|
699
848
|
try {
|
|
@@ -776,17 +925,21 @@ function teardownSlot(slot) {
|
|
|
776
925
|
slot.stopK8sSubstrate();
|
|
777
926
|
} catch {
|
|
778
927
|
}
|
|
928
|
+
try {
|
|
929
|
+
slot.stopRepoSync();
|
|
930
|
+
} catch {
|
|
931
|
+
}
|
|
779
932
|
try {
|
|
780
933
|
slot.detachEvents();
|
|
781
934
|
} catch {
|
|
782
935
|
}
|
|
783
936
|
}
|
|
784
937
|
function neatHomeFor(opts) {
|
|
785
|
-
if (opts.neatHome && opts.neatHome.length > 0) return
|
|
938
|
+
if (opts.neatHome && opts.neatHome.length > 0) return path4.resolve(opts.neatHome);
|
|
786
939
|
const env = process.env.NEAT_HOME;
|
|
787
|
-
if (env && env.length > 0) return
|
|
940
|
+
if (env && env.length > 0) return path4.resolve(env);
|
|
788
941
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
789
|
-
return
|
|
942
|
+
return path4.join(home, ".neat");
|
|
790
943
|
}
|
|
791
944
|
function routeSpanToProject(serviceName, projects) {
|
|
792
945
|
if (!serviceName) return DEFAULT_PROJECT;
|
|
@@ -838,7 +991,7 @@ function spanBelongsToSingleProject(graph, project, serviceName) {
|
|
|
838
991
|
);
|
|
839
992
|
}
|
|
840
993
|
async function bootstrapProject(entry, connectors = [], neatHome) {
|
|
841
|
-
const paths = pathsForProject(entry.name,
|
|
994
|
+
const paths = pathsForProject(entry.name, path4.join(entry.path, "neat-out"));
|
|
842
995
|
try {
|
|
843
996
|
const stat = await fs2.stat(entry.path);
|
|
844
997
|
if (!stat.isDirectory()) {
|
|
@@ -864,6 +1017,8 @@ async function bootstrapProject(entry, connectors = [], neatHome) {
|
|
|
864
1017
|
},
|
|
865
1018
|
stopK8sSubstrate: () => {
|
|
866
1019
|
},
|
|
1020
|
+
stopRepoSync: () => {
|
|
1021
|
+
},
|
|
867
1022
|
detachEvents: () => {
|
|
868
1023
|
},
|
|
869
1024
|
status: "broken",
|
|
@@ -915,6 +1070,12 @@ async function bootstrapProject(entry, connectors = [], neatHome) {
|
|
|
915
1070
|
...neatHome ? { home: neatHome } : {},
|
|
916
1071
|
onSkip: (skipped, reason) => console.warn(`neatd: k8s substrate "${skipped.id}" skipped for project "${entry.name}" \u2014 ${reason}`)
|
|
917
1072
|
});
|
|
1073
|
+
const stopRepoSync = await maybeStartRepoSync({
|
|
1074
|
+
project: entry.name,
|
|
1075
|
+
graph,
|
|
1076
|
+
onSkip: (repo, reason) => console.warn(`neatd: repo-sync "${repo}" skipped for project "${entry.name}" \u2014 ${reason}`),
|
|
1077
|
+
onError: (repo, err) => console.warn(`neatd: repo-sync "${repo}" failed for project "${entry.name}" \u2014 ${err.message}`)
|
|
1078
|
+
});
|
|
918
1079
|
await touchLastSeen(entry.name).catch(() => {
|
|
919
1080
|
});
|
|
920
1081
|
return {
|
|
@@ -927,6 +1088,7 @@ async function bootstrapProject(entry, connectors = [], neatHome) {
|
|
|
927
1088
|
stopConnectors,
|
|
928
1089
|
stopHostedConnectors,
|
|
929
1090
|
stopK8sSubstrate,
|
|
1091
|
+
stopRepoSync,
|
|
930
1092
|
detachEvents,
|
|
931
1093
|
status: "active"
|
|
932
1094
|
};
|
|
@@ -983,7 +1145,7 @@ async function startDaemon(opts = {}) {
|
|
|
983
1145
|
const projectArg = typeof opts.project === "string" && opts.project.length > 0 ? opts.project : process.env.NEAT_PROJECT && process.env.NEAT_PROJECT.length > 0 ? process.env.NEAT_PROJECT : null;
|
|
984
1146
|
const projectPathArg = opts.projectPath && opts.projectPath.length > 0 ? opts.projectPath : process.env.NEAT_PROJECT_PATH && process.env.NEAT_PROJECT_PATH.length > 0 ? process.env.NEAT_PROJECT_PATH : null;
|
|
985
1147
|
const singleProject = projectArg;
|
|
986
|
-
const singleProjectPath = singleProject && projectPathArg ?
|
|
1148
|
+
const singleProjectPath = singleProject && projectPathArg ? path4.resolve(projectPathArg) : null;
|
|
987
1149
|
if (singleProject && !singleProjectPath) {
|
|
988
1150
|
throw new Error(
|
|
989
1151
|
`neatd: project "${singleProject}" given without a projectPath; pass NEAT_PROJECT_PATH alongside NEAT_PROJECT.`
|
|
@@ -998,7 +1160,7 @@ async function startDaemon(opts = {}) {
|
|
|
998
1160
|
);
|
|
999
1161
|
}
|
|
1000
1162
|
}
|
|
1001
|
-
const pidPath =
|
|
1163
|
+
const pidPath = path4.join(home, "neatd.pid");
|
|
1002
1164
|
await writeAtomically(pidPath, `${process.pid}
|
|
1003
1165
|
`);
|
|
1004
1166
|
const slots = /* @__PURE__ */ new Map();
|
|
@@ -1470,8 +1632,8 @@ async function startDaemon(opts = {}) {
|
|
|
1470
1632
|
let registryWatcher = null;
|
|
1471
1633
|
let reloadTimer = null;
|
|
1472
1634
|
if (!singleProject) try {
|
|
1473
|
-
const regDir =
|
|
1474
|
-
const regBase =
|
|
1635
|
+
const regDir = path4.dirname(regPath);
|
|
1636
|
+
const regBase = path4.basename(regPath);
|
|
1475
1637
|
registryWatcher = watch(regDir, (_eventType, filename) => {
|
|
1476
1638
|
if (filename !== null && filename !== regBase) return;
|
|
1477
1639
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
@@ -1551,4 +1713,4 @@ export {
|
|
|
1551
1713
|
resolveHost,
|
|
1552
1714
|
startDaemon
|
|
1553
1715
|
};
|
|
1554
|
-
//# sourceMappingURL=chunk-
|
|
1716
|
+
//# sourceMappingURL=chunk-NTVHVZYC.js.map
|