@neat.is/core 0.9.18 → 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.
@@ -48,7 +48,7 @@ import {
48
48
  unlinkSync,
49
49
  writeFileSync
50
50
  } from "fs";
51
- import path3 from "path";
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(path4, deps) {
86
+ async function cpGet(path5, deps) {
87
87
  const f = deps.fetchImpl ?? fetch;
88
- const res = await f(`${deps.cpUrl.replace(/\/+$/, "")}${path4}`, {
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 ${path4} \u2192 HTTP ${res.status}`);
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, path4, opts = {}) {
396
+ async function listResource(transport, namespace, path5, opts = {}) {
248
397
  const base = opts.apiUrl ?? transport.server;
249
- const url = `${base.replace(/\/$/, "")}${path4}`;
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 ${path4} failed: ${res.status} ${res.statusText}`);
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 os from "os";
556
- import path from "path";
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 path.resolve(override);
560
- return path.join(os.homedir(), ".neat");
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 path.join(home, "k8s.json");
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 path2 from "path";
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 = path2.join(neatHome, "errors.ndjson");
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 path2.join(neatHome, "errors.ndjson");
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 path3.join(scanPath, "neat-out", "daemon.json");
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 path3.join(base, "daemons");
833
+ return path4.join(base, "daemons");
685
834
  }
686
835
  function daemonDiscoveryPath(project, home) {
687
- return path3.join(daemonsDiscoveryDir(home), `${sanitizeDiscoveryName(project)}.json`);
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 path3.resolve(env);
843
+ if (env && env.length > 0) return path4.resolve(env);
695
844
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
696
- return path3.join(home, ".neat");
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 path3.resolve(opts.neatHome);
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 path3.resolve(env);
940
+ if (env && env.length > 0) return path4.resolve(env);
788
941
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
789
- return path3.join(home, ".neat");
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, path3.join(entry.path, "neat-out"));
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 ? path3.resolve(projectPathArg) : null;
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 = path3.join(home, "neatd.pid");
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 = path3.dirname(regPath);
1474
- const regBase = path3.basename(regPath);
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-MGQSR6QZ.js.map
1716
+ //# sourceMappingURL=chunk-NTVHVZYC.js.map