@neat.is/core 0.10.3 → 0.10.5-dev.20261001

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.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  resolveNeatVersion,
9
9
  startK8sSubstratePolling,
10
10
  writeDaemonRecord
11
- } from "./chunk-DLQNMU3R.js";
11
+ } from "./chunk-TONL5O2E.js";
12
12
  import {
13
13
  buildSearchIndex
14
14
  } from "./chunk-BC53SCT7.js";
@@ -82,10 +82,10 @@ import {
82
82
  startStalenessLoop,
83
83
  upsertConnectorEntry,
84
84
  validateConnectorEntry
85
- } from "./chunk-LNIVRE4L.js";
85
+ } from "./chunk-HHAPD6JD.js";
86
86
  import {
87
87
  startOtelGrpcReceiver
88
- } from "./chunk-5SJHA5ZH.js";
88
+ } from "./chunk-LXQCL4MP.js";
89
89
  import {
90
90
  __dirname,
91
91
  __require,
@@ -94,7 +94,7 @@ import {
94
94
  isLoopbackHost,
95
95
  listenSteppingOtlp,
96
96
  readAuthEnv
97
- } from "./chunk-M7CGATVC.js";
97
+ } from "./chunk-EFBWQIKG.js";
98
98
 
99
99
  // src/cli.ts
100
100
  import path21 from "path";
@@ -5441,6 +5441,52 @@ async function waitForFirstProject(fetchImpl, cpUrl, accessToken, deps) {
5441
5441
  if (deps.signal?.aborted) return { kind: "aborted" };
5442
5442
  }
5443
5443
  }
5444
+ function projectNameForRepo(repo) {
5445
+ const slug = repo.name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
5446
+ return slug || "repo";
5447
+ }
5448
+ async function detailOf(res) {
5449
+ const body = await res.text().catch(() => "");
5450
+ try {
5451
+ const parsed = JSON.parse(body);
5452
+ if (parsed.error) return parsed.error;
5453
+ } catch {
5454
+ }
5455
+ return body.trim() || `HTTP ${res.status}`;
5456
+ }
5457
+ async function createProjectForRepo(fetchImpl, cpUrl, accessToken, name) {
5458
+ const headers = { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" };
5459
+ let project;
5460
+ try {
5461
+ const res = await fetchImpl(`${cpUrl}/me/projects`, {
5462
+ method: "POST",
5463
+ headers,
5464
+ body: JSON.stringify({ name }),
5465
+ signal: AbortSignal.timeout(CP_TIMEOUT_MS)
5466
+ });
5467
+ if (!res.ok) return { ok: false, reason: "not-created", detail: await detailOf(res) };
5468
+ project = await res.json();
5469
+ } catch (err) {
5470
+ return { ok: false, reason: "not-created", detail: err instanceof Error ? err.message : String(err) };
5471
+ }
5472
+ try {
5473
+ const res = await fetchImpl(`${cpUrl}/me/projects/${encodeURIComponent(project.id)}/provision`, {
5474
+ method: "POST",
5475
+ headers,
5476
+ signal: AbortSignal.timeout(CP_TIMEOUT_MS)
5477
+ });
5478
+ if (res.status === 402) return { ok: false, reason: "needs-plan", project, detail: await detailOf(res) };
5479
+ if (!res.ok) return { ok: false, reason: "not-provisioned", project, detail: await detailOf(res) };
5480
+ } catch (err) {
5481
+ return {
5482
+ ok: false,
5483
+ reason: "not-provisioned",
5484
+ project,
5485
+ detail: err instanceof Error ? err.message : String(err)
5486
+ };
5487
+ }
5488
+ return { ok: true, project };
5489
+ }
5444
5490
  function bindUrl(webUrl, projectId) {
5445
5491
  return `${webUrl}/config/repos?project=${encodeURIComponent(projectId)}`;
5446
5492
  }
@@ -5466,7 +5512,8 @@ async function runPostLoginConnect(input) {
5466
5512
  const repo = await detect6(cwd);
5467
5513
  out("");
5468
5514
  const cmd = commandPrefix();
5469
- const syncLine = `${cmd} sync --to ${input.project.name}`;
5515
+ const pushCmd = input.endpoint ? `${cmd} sync --to ${input.endpoint}${input.pushToken ? ` --token ${input.pushToken}` : ""}` : null;
5516
+ const pushInto = (label) => pushCmd ? [` ${pushCmd}`] : [` Its daemon URL and token are on ${label} in the console: ${input.webUrl}`];
5470
5517
  if (!repo) {
5471
5518
  out("This directory has no GitHub remote, so it can't be bound to the project.");
5472
5519
  const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
@@ -5476,16 +5523,17 @@ async function runPostLoginConnect(input) {
5476
5523
  out("");
5477
5524
  if (built) {
5478
5525
  out(`Now push it to ${input.project.name}:`);
5479
- out(` ${syncLine}`);
5526
+ for (const line of pushInto(input.project.name)) out(line);
5480
5527
  } else {
5481
5528
  out(`When you're ready, build it and push it to ${input.project.name}:`);
5482
5529
  out(` ${cmd}`);
5483
- out(` ${syncLine}`);
5530
+ for (const line of pushInto(input.project.name)) out(line);
5484
5531
  }
5485
5532
  out(` Manage projects and repos: ${input.webUrl}`);
5486
5533
  return;
5487
5534
  }
5488
- out(` Push its graph instead: ${syncLine}`);
5535
+ out(` Push its graph to ${input.project.name}:`);
5536
+ for (const line of pushInto(input.project.name)) out(line);
5489
5537
  out(` Manage projects and repos: ${input.webUrl}`);
5490
5538
  return;
5491
5539
  }
@@ -5498,39 +5546,87 @@ async function runPostLoginConnect(input) {
5498
5546
  out(` See the graph: ${input.webUrl}`);
5499
5547
  return;
5500
5548
  }
5501
- out(`${slug} isn't part of ${input.project.name} yet \u2014 logging in connected your account, not this repo.`);
5549
+ out(`${slug} isn't in ${input.project.name} \u2014 logging in connected your account, not this repo.`);
5502
5550
  out("");
5503
- const installed = input.me?.github?.installed;
5504
- const bindLine = installed === false ? "1) Install the GitHub App and bind this repo \u2014 hosted NEAT keeps it in sync" : installed === true ? "1) Bind this repo \u2014 hosted NEAT clones and extracts it, and keeps it in sync" : "1) Bind this repo through the console \u2014 hosted NEAT keeps it in sync";
5505
- out(bindLine);
5506
- out(`2) Push the graph on this machine now \u2014 a snapshot, not a subscription`);
5551
+ const suggested = projectNameForRepo(repo);
5552
+ out("Where should this repo go?");
5553
+ out("");
5554
+ out(`1) A project of its own, called ${suggested}`);
5555
+ out(`2) ${input.project.name} \u2014 the project this login connected to`);
5556
+ for (const line of sharedProjectCaveat(bound)) out(` ${line}`);
5507
5557
  out("3) Neither, for now");
5558
+ out("");
5508
5559
  const answer = readLine ? (await readLine("Choose 1, 2 or 3 (Enter skips): "))?.trim() : void 0;
5509
5560
  if (answer === "1") {
5510
- const url = bindUrl(input.webUrl, input.project.id);
5511
- const opened = (d.openBrowser ?? (() => false))(url);
5512
5561
  out("");
5513
- out(opened ? `Opened ${url}` : `Open this to bind the repo:
5514
- ${url}`);
5562
+ const outcome = await createProjectForRepo(fetchImpl, input.cpUrl, input.accessToken, suggested);
5563
+ reportCreate(outcome, input.webUrl, cmd, input.project.name, out);
5515
5564
  return;
5516
5565
  }
5517
5566
  if (answer === "2") {
5518
- const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
5519
5567
  out("");
5520
- if (!hasGraph) {
5521
- out("There is no local graph to push yet. Build one, then push it:");
5522
- out(` ${cmd}`);
5523
- out(` ${syncLine}`);
5524
- return;
5568
+ out(`Two ways into ${input.project.name} \u2014 either is fine, and you can do both:`);
5569
+ out("");
5570
+ const installed = input.me?.github?.installed;
5571
+ out(
5572
+ installed === false ? " Bind it (install the GitHub App first) \u2014 hosted NEAT keeps it in sync:" : installed === true ? " Bind it \u2014 hosted NEAT clones and extracts it, and keeps it in sync:" : " Bind it through the console \u2014 hosted NEAT keeps it in sync:"
5573
+ );
5574
+ out(` ${bindUrl(input.webUrl, input.project.id)}`);
5575
+ out("");
5576
+ const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
5577
+ if (hasGraph) {
5578
+ out(" Or push the graph on this machine now \u2014 a snapshot, not a subscription:");
5579
+ for (const line of pushInto(input.project.name)) out(` ${line}`);
5580
+ } else {
5581
+ out(" Or build a graph here and push it \u2014 a snapshot, not a subscription:");
5582
+ out(` ${cmd}`);
5583
+ for (const line of pushInto(input.project.name)) out(` ${line}`);
5525
5584
  }
5526
- out("Push the local graph with:");
5527
- out(` ${syncLine}`);
5528
5585
  return;
5529
5586
  }
5530
5587
  out("");
5531
5588
  out("No problem \u2014 when you want this repo in the graph:");
5532
- out(` Bind it: ${bindUrl(input.webUrl, input.project.id)}`);
5533
- out(` Or push it: ${syncLine}`);
5589
+ out(` Its own project: ${input.webUrl}`);
5590
+ out(` Or into ${input.project.name}: ${bindUrl(input.webUrl, input.project.id)}`);
5591
+ }
5592
+ function sharedProjectCaveat(bound) {
5593
+ const hazard = "Two repos in one project can currently retire each other's files (#1294),";
5594
+ if (!bound) return [hazard, "so a graph there may end up short of code until that is fixed."];
5595
+ if (bound.length === 0) return ["Nothing else is bound there yet.", `Adding a second repo later would hit #1294 \u2014 ${hazard.toLowerCase()}`];
5596
+ const names = bound.map((r) => `${r.owner}/${r.name}`).join(", ");
5597
+ return [
5598
+ `${bound.length} already there: ${names}.`,
5599
+ hazard,
5600
+ "so both graphs may end up short of code until that is fixed."
5601
+ ];
5602
+ }
5603
+ function reportCreate(outcome, webUrl, cmd, connectedTo, out) {
5604
+ if (outcome.ok) {
5605
+ out(`Created ${outcome.project.name} and started its daemon.`);
5606
+ out("");
5607
+ out(`This CLI is still pointed at ${connectedTo}. Switch it to the new project with:`);
5608
+ out(` ${cmd} login --project ${outcome.project.name}`);
5609
+ out("");
5610
+ out("Then bind this repo to it:");
5611
+ out(` ${bindUrl(webUrl, outcome.project.id)}`);
5612
+ return;
5613
+ }
5614
+ if (outcome.reason === "needs-plan") {
5615
+ out(`Created ${outcome.project.name}, but starting a daemon for it needs a plan.`);
5616
+ out(` Choose one: ${webUrl}/checkout`);
5617
+ out("");
5618
+ out("The project is made and keeps its name, so there's nothing to redo here afterwards.");
5619
+ return;
5620
+ }
5621
+ if (outcome.reason === "not-provisioned") {
5622
+ out(`Created ${outcome.project.name}, but its daemon didn't start: ${outcome.detail}`);
5623
+ out("");
5624
+ out("The project exists \u2014 start it from the console rather than making a second one:");
5625
+ out(` ${webUrl}`);
5626
+ return;
5627
+ }
5628
+ out(`Couldn't create the project: ${outcome.detail}`);
5629
+ out(`Nothing was made, so this is safe to retry, or do it in the console: ${webUrl}`);
5534
5630
  }
5535
5631
 
5536
5632
  // src/login-sso.ts
@@ -5767,6 +5863,10 @@ async function runSsoLogin(opts, deps) {
5767
5863
  webUrl: opts.webUrl,
5768
5864
  accessToken,
5769
5865
  project: { id: project.id, name: project.name },
5866
+ // What a push actually needs: `sync --to` takes the daemon's URL, and its
5867
+ // token comes from the flag rather than the profile (#1302).
5868
+ endpoint: cred.endpoint,
5869
+ pushToken: cred.authToken,
5770
5870
  me,
5771
5871
  deps: {
5772
5872
  fetchImpl,