@neat.is/core 0.9.21-dev.20260924 → 0.9.21

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.
@@ -7,6 +7,7 @@ import {
7
7
  buildApi,
8
8
  extractFromDirectory,
9
9
  getGraph,
10
+ getPushProviderDispatch,
10
11
  handleSpan,
11
12
  isBoundaryTimeoutSymptom,
12
13
  junctionFetch,
@@ -32,7 +33,7 @@ import {
32
33
  startStalenessLoop,
33
34
  touchLastSeen,
34
35
  writeAtomically
35
- } from "./chunk-NFJ4W5T6.js";
36
+ } from "./chunk-J3VVEB45.js";
36
37
  import {
37
38
  assertBindAuthority,
38
39
  buildOtelReceiver,
@@ -80,6 +81,10 @@ async function reconcileFrontierSurfaces(graph, errorsPath) {
80
81
  stageHangSurfaces(graph, incidents);
81
82
  }
82
83
 
84
+ // src/connectors/hosted.ts
85
+ import { mkdir, readFile, rename, writeFile } from "fs/promises";
86
+ import { dirname, join } from "path";
87
+
83
88
  // src/connectors/railway/target-ref.ts
84
89
  function nonEmptyString(v) {
85
90
  return typeof v === "string" && v.length > 0;
@@ -124,11 +129,14 @@ function credentialRecord(provider, cred) {
124
129
  switch (provider) {
125
130
  case "supabase":
126
131
  return { managementToken: cred.accessToken };
132
+ case "gcp":
133
+ if (!cred.projectRef) throw new Error("gcp credential delivered without a project ref");
134
+ return { projectId: cred.projectRef, accessToken: cred.accessToken };
127
135
  default:
128
136
  return { token: cred.accessToken };
129
137
  }
130
138
  }
131
- function hostedOptions(provider, summary, serviceName) {
139
+ function hostedOptions(provider, summary, serviceName, firebaseServiceMap) {
132
140
  switch (provider) {
133
141
  case "supabase": {
134
142
  const ref = summary.projectRef;
@@ -144,10 +152,125 @@ function hostedOptions(provider, summary, serviceName) {
144
152
  serviceNameById: { [target.serviceId]: serviceName }
145
153
  };
146
154
  }
155
+ case "firebase": {
156
+ return { inferServices: true, excludeCloudRun: true, ...firebaseServiceMap ?? {} };
157
+ }
158
+ case "cloud-run":
159
+ case "gcp-lb":
160
+ return { inferServices: true };
147
161
  default:
148
162
  return null;
149
163
  }
150
164
  }
165
+ function parseFirebaseServiceMap(raw) {
166
+ if (!raw) return void 0;
167
+ try {
168
+ const parsed = JSON.parse(raw);
169
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
170
+ const out = {};
171
+ for (const key of ["functions", "cloudRun", "hosting"]) {
172
+ const group = parsed[key];
173
+ if (group === void 0) continue;
174
+ if (!group || typeof group !== "object" || Array.isArray(group)) return void 0;
175
+ const entries = Object.entries(group);
176
+ if (entries.some(([, v]) => typeof v !== "string" || v.length === 0)) return void 0;
177
+ out[key] = Object.fromEntries(entries);
178
+ }
179
+ return Object.keys(out).length > 0 ? out : void 0;
180
+ } catch {
181
+ return void 0;
182
+ }
183
+ }
184
+ function pushHandlesPath(projectDir) {
185
+ return join(projectDir, "neat-out", "connectors-hosted.json");
186
+ }
187
+ async function readPushHandles(projectDir) {
188
+ try {
189
+ const parsed = JSON.parse(await readFile(pushHandlesPath(projectDir), "utf8"));
190
+ return parsed && typeof parsed === "object" ? parsed : {};
191
+ } catch {
192
+ return {};
193
+ }
194
+ }
195
+ async function writePushHandle(projectDir, provider, handle) {
196
+ const path5 = pushHandlesPath(projectDir);
197
+ const handles = await readPushHandles(projectDir);
198
+ handles[provider] = handle;
199
+ await mkdir(dirname(path5), { recursive: true });
200
+ const tmp = `${path5}.${process.pid}.tmp`;
201
+ await writeFile(tmp, `${JSON.stringify(handles, null, 2)}
202
+ `, "utf8");
203
+ await rename(tmp, path5);
204
+ }
205
+ function pushCredentialRecord(provider, cred, deps) {
206
+ switch (provider) {
207
+ case "vercel":
208
+ return { token: cred.accessToken, otelToken: deps.otelToken ?? deps.daemonToken };
209
+ default:
210
+ return { token: cred.accessToken };
211
+ }
212
+ }
213
+ function hostedPushOptions(provider, summary, deps) {
214
+ switch (provider) {
215
+ case "vercel": {
216
+ if (!deps.publicUrl) {
217
+ return { skip: "no public URL for this daemon (NEAT_PUBLIC_URL) \u2014 a drain has nowhere to deliver" };
218
+ }
219
+ if (!summary.projectRef) return { skip: "no Vercel team selected yet \u2014 drains are team-scoped" };
220
+ const endpoint = `${deps.publicUrl.replace(/\/+$/, "")}/v1/traces`;
221
+ return { options: { teamId: summary.projectRef, endpoint }, endpoint };
222
+ }
223
+ default:
224
+ return { skip: "no hosted option mapping for this push provider" };
225
+ }
226
+ }
227
+ async function provisionHostedPushProvider(summary, dispatch, input) {
228
+ const { deps, projectDir, onSkip, onProvisioned } = input;
229
+ const provider = summary.provider;
230
+ const mapped = hostedPushOptions(provider, summary, deps);
231
+ if ("skip" in mapped) {
232
+ onSkip?.(provider, mapped.skip);
233
+ return;
234
+ }
235
+ let cred;
236
+ try {
237
+ cred = await cpGet(
238
+ `/internal/projects/${deps.projectId}/connections/${provider}/credential`,
239
+ deps
240
+ );
241
+ } catch (err) {
242
+ onSkip?.(provider, `credential delivery failed \u2014 ${err.message}`);
243
+ return;
244
+ }
245
+ const credentials = pushCredentialRecord(provider, cred, deps);
246
+ const existing = (await readPushHandles(projectDir))[provider];
247
+ const alreadyProvisioned = existing !== void 0 && existing.endpoint === mapped.endpoint;
248
+ const options = alreadyProvisioned ? { ...mapped.options, ...existing.options } : mapped.options;
249
+ const seam = deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {};
250
+ const validation = await dispatch.validate({ credentials, options, ...seam });
251
+ if (!validation.ok) {
252
+ onSkip?.(provider, `drain delivery test failed \u2014 ${validation.reason}`);
253
+ return;
254
+ }
255
+ if (alreadyProvisioned) {
256
+ onProvisioned?.(provider, "drain already provisioned \u2014 delivery re-validated");
257
+ return;
258
+ }
259
+ const result = await dispatch.provision({ credentials, options, ...seam });
260
+ if (!result.ok) {
261
+ onSkip?.(provider, `drain provisioning failed \u2014 ${result.reason}`);
262
+ return;
263
+ }
264
+ await writePushHandle(projectDir, provider, {
265
+ endpoint: mapped.endpoint,
266
+ options: result.options ?? {},
267
+ provisionedAt: (/* @__PURE__ */ new Date()).toISOString()
268
+ });
269
+ onProvisioned?.(provider, result.note);
270
+ }
271
+ var HOSTED_CONNECTORS_FOR = {
272
+ gcp: ["firebase", "cloud-run", "gcp-lb"]
273
+ };
151
274
  function createHostedCredentialSource(provider, deps) {
152
275
  let cached = null;
153
276
  return async () => {
@@ -176,40 +299,50 @@ async function startHostedConnectors(input) {
176
299
  if (!Array.isArray(connections)) return () => {
177
300
  };
178
301
  const stops = [];
302
+ const lookupPush = input.pushDispatch ?? getPushProviderDispatch;
179
303
  for (const c of connections) {
180
- const dispatch = PROVIDER_DISPATCH[c.provider];
181
- if (!dispatch) {
182
- onSkip?.(c.provider, "no pull connector for this provider");
304
+ const push = lookupPush(c.provider);
305
+ if (push) {
306
+ await provisionHostedPushProvider(c, push, input);
183
307
  continue;
184
308
  }
185
- if (c.needsProjectSelection || !c.projectRef) {
186
- onSkip?.(c.provider, "no project selected yet \u2014 not pullable");
187
- continue;
188
- }
189
- const options = hostedOptions(c.provider, c, project);
190
- if (!options) {
191
- onSkip?.(c.provider, "no hosted option mapping for this provider");
192
- continue;
193
- }
194
- let built;
195
- try {
196
- built = dispatch.build(graph, options);
197
- } catch (err) {
198
- onSkip?.(c.provider, err.message);
199
- continue;
309
+ const credentialSource = createHostedCredentialSource(c.provider, deps);
310
+ for (const name of HOSTED_CONNECTORS_FOR[c.provider] ?? [c.provider]) {
311
+ const dispatch = PROVIDER_DISPATCH[name];
312
+ if (!dispatch) {
313
+ onSkip?.(name, "no pull connector for this provider");
314
+ continue;
315
+ }
316
+ if (c.needsProjectSelection || !c.projectRef) {
317
+ onSkip?.(name, "no project selected yet \u2014 not pullable");
318
+ continue;
319
+ }
320
+ const options = hostedOptions(name, c, project, input.firebaseServiceMap);
321
+ if (!options) {
322
+ onSkip?.(name, "no hosted option mapping for this provider");
323
+ continue;
324
+ }
325
+ let built;
326
+ try {
327
+ built = dispatch.build(graph, options);
328
+ } catch (err) {
329
+ onSkip?.(name, err.message);
330
+ continue;
331
+ }
332
+ stops.push(
333
+ startLoop(
334
+ built.connector,
335
+ { projectDir, project, credentials: {}, ...errorsPath ? { errorsPath } : {} },
336
+ graph,
337
+ built.resolveTarget,
338
+ {
339
+ connectorId: `hosted:${name}`,
340
+ // The credential is fetched by the CONNECTION's provider (`gcp`), not the connector's name.
341
+ refreshCredentials: credentialSource
342
+ }
343
+ )
344
+ );
200
345
  }
201
- stops.push(
202
- startLoop(
203
- built.connector,
204
- { projectDir, project, credentials: {}, ...errorsPath ? { errorsPath } : {} },
205
- graph,
206
- built.resolveTarget,
207
- {
208
- connectorId: `hosted:${c.provider}`,
209
- refreshCredentials: createHostedCredentialSource(c.provider, deps)
210
- }
211
- )
212
- );
213
346
  }
214
347
  return () => {
215
348
  for (const stop of stops) stop();
@@ -223,10 +356,18 @@ async function maybeStartHostedConnectors(input) {
223
356
  if (!cpUrl || !projectId || !daemonToken) return () => {
224
357
  };
225
358
  return startHostedConnectors({
226
- deps: { cpUrl, projectId, daemonToken, ...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {} },
359
+ deps: {
360
+ cpUrl,
361
+ projectId,
362
+ daemonToken,
363
+ ...env.NEAT_PUBLIC_URL ? { publicUrl: env.NEAT_PUBLIC_URL } : {},
364
+ ...env.NEAT_OTEL_TOKEN ? { otelToken: env.NEAT_OTEL_TOKEN } : {},
365
+ ...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}
366
+ },
227
367
  graph: input.graph,
228
368
  projectDir: input.projectDir,
229
369
  project: input.project,
370
+ ...parseFirebaseServiceMap(env.NEAT_FIREBASE_SERVICE_MAP) ? { firebaseServiceMap: parseFirebaseServiceMap(env.NEAT_FIREBASE_SERVICE_MAP) } : {},
230
371
  ...input.errorsPath ? { errorsPath: input.errorsPath } : {},
231
372
  ...input.onSkip ? { onSkip: input.onSkip } : {}
232
373
  });
@@ -306,6 +447,8 @@ async function syncOneRepo(r, input) {
306
447
  let dir;
307
448
  try {
308
449
  dir = await mkdtemp(path.join(tmpRoot, "neat-repo-"));
450
+ await cpPostStatus(deps, r.owner, r.name, { syncStatus: "syncing", detail: "cloning" }).catch(() => {
451
+ });
309
452
  await cloneRepo(r.cloneUrl, r.defaultBranch, dir);
310
453
  const extracted = await extract(graph, dir);
311
454
  const nodes = extracted?.nodesAdded ?? 0;
@@ -333,23 +476,26 @@ async function runRepoSyncPass(input) {
333
476
  repos = await cpGet2(`/internal/projects/${input.deps.projectId}/repos`, input.deps);
334
477
  } catch (err) {
335
478
  input.onSkip?.("(all)", `control plane repo list unreadable \u2014 ${err.message}`);
336
- return;
479
+ return false;
337
480
  }
338
- if (!Array.isArray(repos)) return;
481
+ if (!Array.isArray(repos)) return false;
339
482
  for (const r of repos) {
340
- if (!needsSync(r)) continue;
483
+ if (!input.forceResync && !needsSync(r)) continue;
341
484
  await syncOneRepo(r, input);
342
485
  }
486
+ return true;
343
487
  }
344
488
  async function startRepoSync(input) {
345
489
  const intervalMs = input.intervalMs ?? DEFAULT_SYNC_INTERVAL_MS;
346
490
  let stopped = false;
347
491
  let running = false;
492
+ let bootResyncDone = false;
348
493
  const tick = async () => {
349
494
  if (stopped || running) return;
350
495
  running = true;
351
496
  try {
352
- await runRepoSyncPass(input);
497
+ const listed = await runRepoSyncPass({ ...input, forceResync: !bootResyncDone });
498
+ if (listed) bootResyncDone = true;
353
499
  } finally {
354
500
  running = false;
355
501
  }
@@ -740,7 +886,7 @@ function createK8sResolveTarget(graph) {
740
886
  }
741
887
 
742
888
  // src/connectors/kubernetes/substrate.ts
743
- import { readFile } from "fs/promises";
889
+ import { readFile as readFile2 } from "fs/promises";
744
890
  import os2 from "os";
745
891
  import path2 from "path";
746
892
  function defaultHome() {
@@ -754,7 +900,7 @@ function k8sSubstrateConfigPath(home = defaultHome()) {
754
900
  async function readK8sSubstrateConfig(home = defaultHome()) {
755
901
  let raw;
756
902
  try {
757
- raw = await readFile(k8sSubstrateConfigPath(home), "utf8");
903
+ raw = await readFile2(k8sSubstrateConfigPath(home), "utf8");
758
904
  } catch {
759
905
  return { version: 1, deployments: [] };
760
906
  }
@@ -1753,4 +1899,4 @@ export {
1753
1899
  resolveHost,
1754
1900
  startDaemon
1755
1901
  };
1756
- //# sourceMappingURL=chunk-SK2FEJKB.js.map
1902
+ //# sourceMappingURL=chunk-KVJQQUUY.js.map