@neat.is/core 0.10.3-dev.20260930 → 0.10.4

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/cli.cjs CHANGED
@@ -51,6 +51,10 @@ function assertBindAuthority(host, token) {
51
51
  if (isLoopbackHost2(host)) return;
52
52
  throw new BindAuthorityError(host);
53
53
  }
54
+ function isBearerDelegated(req) {
55
+ const config = req.routeOptions?.config;
56
+ return config?.bearerDelegated === true;
57
+ }
54
58
  function mountBearerAuth(app, opts) {
55
59
  if (!opts.token || opts.token.length === 0) return;
56
60
  if (opts.trustProxy) return;
@@ -66,6 +70,10 @@ function mountBearerAuth(app, opts) {
66
70
  done();
67
71
  return;
68
72
  }
73
+ if (isBearerDelegated(req)) {
74
+ done();
75
+ return;
76
+ }
69
77
  if (publicRead && PUBLIC_READ_METHODS.has(req.method)) {
70
78
  done();
71
79
  return;
@@ -99,7 +107,7 @@ function readAuthEnv(env = process.env) {
99
107
  publicRead: parseBoolEnv(env.NEAT_PUBLIC_READ)
100
108
  };
101
109
  }
102
- var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_PATHS, PROJECT_SCOPED_UNAUTH_PATTERN;
110
+ var import_node_crypto, LOOPBACK_HOSTS, BindAuthorityError, PUBLIC_READ_METHODS, DEFAULT_UNAUTH_PATHS, PROJECT_SCOPED_UNAUTH_PATTERN, BEARER_DELEGATED;
103
111
  var init_auth = __esm({
104
112
  "src/auth.ts"() {
105
113
  "use strict";
@@ -129,6 +137,7 @@ var init_auth = __esm({
129
137
  PROJECT_SCOPED_UNAUTH_PATTERN = new RegExp(
130
138
  `^/projects/[^/]+/(?:${DEFAULT_UNAUTH_PATHS.map((p) => p.slice(1)).join("|")})$`
131
139
  );
140
+ BEARER_DELEGATED = { bearerDelegated: true };
132
141
  }
133
142
  });
134
143
 
@@ -24226,6 +24235,19 @@ function registerRoutes(scope, ctx) {
24226
24235
  });
24227
24236
  }
24228
24237
  });
24238
+ scope.post("/repo-sync", async (req, reply) => {
24239
+ const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
24240
+ if (!proj) return;
24241
+ const syncNow = ctx.repoSync?.(proj.name);
24242
+ if (!syncNow) {
24243
+ return reply.code(404).send({
24244
+ error: "repo-sync is not running for this project",
24245
+ project: proj.name,
24246
+ hint: "Only a hosted daemon syncs bound repos. On a local daemon, POST /graph/scan re-extracts the project path."
24247
+ });
24248
+ }
24249
+ return reply.code(202).send({ project: proj.name, ...syncNow() });
24250
+ });
24229
24251
  scope.post("/graph/scan", async (req, reply) => {
24230
24252
  const proj = resolveProject(registry, req, reply, ctx.bootstrap, ctx.singleProject);
24231
24253
  if (!proj) return;
@@ -24473,6 +24495,39 @@ function registerRoutes(scope, ctx) {
24473
24495
  }
24474
24496
  });
24475
24497
  }
24498
+ var OTLP_BODY_LIMIT = 16 * 1024 * 1024;
24499
+ function otlpOnRest(getReceiver) {
24500
+ return async function plugin(scope) {
24501
+ scope.removeAllContentTypeParsers();
24502
+ scope.addContentTypeParser(
24503
+ "*",
24504
+ { parseAs: "buffer", bodyLimit: OTLP_BODY_LIMIT },
24505
+ (_req, body, done) => done(null, body)
24506
+ );
24507
+ const relay = async (req, reply) => {
24508
+ const receiver = getReceiver();
24509
+ if (!receiver) {
24510
+ return reply.code(503).header("retry-after", "1").send({ error: "OTLP receiver is starting" });
24511
+ }
24512
+ const headers = { ...req.headers };
24513
+ delete headers["content-length"];
24514
+ delete headers["transfer-encoding"];
24515
+ delete headers.connection;
24516
+ const res = await receiver.inject({
24517
+ method: "POST",
24518
+ url: req.url,
24519
+ headers,
24520
+ payload: req.body
24521
+ });
24522
+ const contentType = res.headers["content-type"];
24523
+ if (contentType !== void 0) void reply.header("content-type", contentType);
24524
+ return reply.code(res.statusCode).send(res.rawPayload);
24525
+ };
24526
+ const route = { config: BEARER_DELEGATED, bodyLimit: OTLP_BODY_LIMIT };
24527
+ scope.post("/v1/traces", route, relay);
24528
+ scope.post("/projects/:project/v1/traces", route, relay);
24529
+ };
24530
+ }
24476
24531
  async function buildApi(opts) {
24477
24532
  const app = (0, import_fastify2.default)({ logger: false, routerOptions: { maxParamLength: 1024 } });
24478
24533
  await app.register(import_cors.default, { origin: true });
@@ -24491,6 +24546,7 @@ async function buildApi(opts) {
24491
24546
  authProxy: trustProxy === true,
24492
24547
  requiresAuth
24493
24548
  }));
24549
+ if (opts.otlpReceiver) await app.register(otlpOnRest(opts.otlpReceiver));
24494
24550
  const startedAt = opts.startedAt ?? Date.now();
24495
24551
  const registry = buildLegacyRegistry(opts);
24496
24552
  const legacyErrorsExplicit = !opts.projects && opts.errorsPath !== void 0;
@@ -24521,6 +24577,7 @@ async function buildApi(opts) {
24521
24577
  bootstrap: opts.bootstrap,
24522
24578
  singleProject: opts.singleProject?.name,
24523
24579
  connectorsHome: opts.connectorsHome,
24580
+ repoSync: opts.repoSync,
24524
24581
  runPoll: opts.runPoll ?? runConnectorPoll
24525
24582
  };
24526
24583
  app.get("/health", async () => {
@@ -30607,6 +30664,15 @@ function bindUrl(webUrl, projectId) {
30607
30664
  function onboardingUrl(webUrl) {
30608
30665
  return `${webUrl}/onboarding`;
30609
30666
  }
30667
+ async function offerLocalExtraction(input, d, out, readLine) {
30668
+ const run = d.orchestrator;
30669
+ if (!run || !readLine) return false;
30670
+ const answer = (await readLine("Build it now? [Y/n] "))?.trim().toLowerCase();
30671
+ if (answer === "n" || answer === "no") return false;
30672
+ out("");
30673
+ const code = await run(d.cwd ?? process.cwd(), {});
30674
+ return code === 0;
30675
+ }
30610
30676
  async function runPostLoginConnect(input) {
30611
30677
  const d = input.deps;
30612
30678
  const out = d.out ?? ((line) => console.log(line));
@@ -30616,9 +30682,27 @@ async function runPostLoginConnect(input) {
30616
30682
  const readLine = d.readLine;
30617
30683
  const repo = await detect6(cwd);
30618
30684
  out("");
30685
+ const cmd = commandPrefix();
30686
+ const syncLine = `${cmd} sync --to ${input.project.name}`;
30619
30687
  if (!repo) {
30620
30688
  out("This directory has no GitHub remote, so it can't be bound to the project.");
30621
- out(` Push its graph instead: neat sync --to ${input.project.name}`);
30689
+ const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
30690
+ if (!hasGraph) {
30691
+ out(`A graph of this directory has to be built first, then pushed to ${input.project.name}.`);
30692
+ const built = await offerLocalExtraction(input, d, out, readLine);
30693
+ out("");
30694
+ if (built) {
30695
+ out(`Now push it to ${input.project.name}:`);
30696
+ out(` ${syncLine}`);
30697
+ } else {
30698
+ out(`When you're ready, build it and push it to ${input.project.name}:`);
30699
+ out(` ${cmd}`);
30700
+ out(` ${syncLine}`);
30701
+ }
30702
+ out(` Manage projects and repos: ${input.webUrl}`);
30703
+ return;
30704
+ }
30705
+ out(` Push its graph instead: ${syncLine}`);
30622
30706
  out(` Manage projects and repos: ${input.webUrl}`);
30623
30707
  return;
30624
30708
  }
@@ -30652,18 +30736,18 @@ async function runPostLoginConnect(input) {
30652
30736
  out("");
30653
30737
  if (!hasGraph) {
30654
30738
  out("There is no local graph to push yet. Build one, then push it:");
30655
- out(" neat");
30656
- out(` neat sync --to ${input.project.name}`);
30739
+ out(` ${cmd}`);
30740
+ out(` ${syncLine}`);
30657
30741
  return;
30658
30742
  }
30659
30743
  out("Push the local graph with:");
30660
- out(` neat sync --to ${input.project.name}`);
30744
+ out(` ${syncLine}`);
30661
30745
  return;
30662
30746
  }
30663
30747
  out("");
30664
30748
  out("No problem \u2014 when you want this repo in the graph:");
30665
30749
  out(` Bind it: ${bindUrl(input.webUrl, input.project.id)}`);
30666
- out(` Or push it: neat sync --to ${input.project.name}`);
30750
+ out(` Or push it: ${syncLine}`);
30667
30751
  }
30668
30752
 
30669
30753
  // src/login-sso.ts
@@ -30842,7 +30926,7 @@ async function runSsoLogin(opts, deps) {
30842
30926
  ...deps.signal ? { signal: deps.signal } : {}
30843
30927
  });
30844
30928
  if (waited.kind === "aborted") {
30845
- out("Stopped. Run `neat login` again once your project is up.");
30929
+ out(`Stopped. Run \`${commandPrefix()} login\` again once your project is up.`);
30846
30930
  return 130;
30847
30931
  }
30848
30932
  if (waited.kind === "timeout") {
@@ -30910,7 +30994,7 @@ async function runSsoLogin(opts, deps) {
30910
30994
  }
30911
30995
  });
30912
30996
  out("");
30913
- out("Run `neat logout` to switch back to your local daemon.");
30997
+ out(`Run \`${commandPrefix()} logout\` to switch back to your local daemon.`);
30914
30998
  }
30915
30999
  return 0;
30916
31000
  }
@@ -34938,23 +35022,24 @@ async function tryOrchestrator(cmd, parsed, overrides = {}) {
34938
35022
  return result.exitCode;
34939
35023
  }
34940
35024
  async function runWelcomeFlow(parsed) {
35025
+ const runLocal = async (cwd, opts) => await tryOrchestrator(
35026
+ cwd,
35027
+ {
35028
+ ...parsed,
35029
+ ...opts?.project !== void 0 ? { project: opts.project } : {},
35030
+ ...opts?.noInstrument !== void 0 ? { noInstrument: opts.noInstrument } : {},
35031
+ ...opts?.yes !== void 0 ? { yes: opts.yes } : {}
35032
+ },
35033
+ { skipBanner: opts?.headerShown === true }
35034
+ ) ?? 0;
34941
35035
  return runWelcome({
34942
- login: (loginArgv) => runLoginCommand(loginArgv),
35036
+ login: (loginArgv) => runLoginCommand(loginArgv, { connect: { orchestrator: runLocal } }),
34943
35037
  // What the person chose at the door reaches the orchestrator as the flags they
34944
35038
  // stand for: the name as `--project`, the instrument answer as `--no-instrument`
34945
35039
  // or as `yes` (which keeps the orchestrator's own prompt quiet, since the door
34946
35040
  // already asked). `headerShown` isn't a flag anyone can type — it says the
34947
35041
  // door's wordmark already opened this run, so the orchestrator's banner is skipped.
34948
- orchestrator: async (cwd, opts) => await tryOrchestrator(
34949
- cwd,
34950
- {
34951
- ...parsed,
34952
- ...opts?.project !== void 0 ? { project: opts.project } : {},
34953
- ...opts?.noInstrument !== void 0 ? { noInstrument: opts.noInstrument } : {},
34954
- ...opts?.yes !== void 0 ? { yes: opts.yes } : {}
34955
- },
34956
- { skipBanner: opts?.headerShown === true }
34957
- ) ?? 0,
35042
+ orchestrator: runLocal,
34958
35043
  // An explicit flag is an answer; don't ask again.
34959
35044
  instrumentFlagGiven: parsed.noInstrument || parsed.dryRun
34960
35045
  });