@neat.is/core 0.10.2 → 0.10.3

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
@@ -19403,14 +19403,12 @@ async function readLockPid(lockPath) {
19403
19403
  async function classifyLockHolder(lockPath, probe = defaultLockHolderProbe) {
19404
19404
  const lockPid = await readLockPid(lockPath);
19405
19405
  const daemonPid = await probe.daemonPidFromFile();
19406
- if (daemonPid !== void 0 && daemonPid !== process.pid && probe.isPidAlive(daemonPid) && // The lock already names the daemon, or the daemon answers on its port.
19407
- // Either confirms a live daemon is in the picture (the second guards
19408
- // against a stale pidfile whose PID got reused).
19409
- (daemonPid === lockPid || await probe.daemonResponds())) {
19410
- return { kind: "daemon", pid: daemonPid };
19411
- }
19412
19406
  if (lockPid !== void 0 && lockPid !== process.pid && probe.isPidAlive(lockPid)) {
19413
- return { kind: "command", pid: lockPid };
19407
+ return { kind: lockPid === daemonPid ? "daemon" : "command", pid: lockPid };
19408
+ }
19409
+ if (daemonPid !== void 0 && daemonPid !== process.pid && probe.isPidAlive(daemonPid) && // Guards against a stale pidfile whose PID got reused.
19410
+ await probe.daemonResponds()) {
19411
+ return { kind: "daemon", pid: daemonPid };
19414
19412
  }
19415
19413
  return { kind: "stale" };
19416
19414
  }
@@ -29364,9 +29362,15 @@ async function snapshotProjectStatus(restPort, project, body) {
29364
29362
  }
29365
29363
  return [{ name: project, status: await probeProjectHealth(restPort, project) }];
29366
29364
  }
29367
- async function waitForDaemonReady(restPort, project, timeoutMs) {
29368
- const deadline = Date.now() + timeoutMs;
29369
- let lastBootstrapping = [];
29365
+ var STILL_WAITING_INTERVAL_MS = 1e4;
29366
+ function daemonProgressLine(project, event) {
29367
+ if (event.kind === "starting") return `starting the daemon for ${project}\u2026`;
29368
+ return `still waiting for ${project}'s daemon (${Math.round(event.elapsedMs / 1e3)}s)`;
29369
+ }
29370
+ async function waitForDaemonReady(restPort, project, timeoutMs, onProgress, reminderIntervalMs = STILL_WAITING_INTERVAL_MS) {
29371
+ const startedAt = Date.now();
29372
+ const deadline = startedAt + timeoutMs;
29373
+ let nextReminderAt = startedAt + reminderIntervalMs;
29370
29374
  while (Date.now() < deadline) {
29371
29375
  const body = await fetchDaemonHealth(restPort);
29372
29376
  if (body !== null) {
@@ -29376,15 +29380,10 @@ async function waitForDaemonReady(restPort, project, timeoutMs) {
29376
29380
  if (bootstrapping.length === 0) {
29377
29381
  return { ready: true, brokenProjects: broken, stillBootstrapping: [] };
29378
29382
  }
29379
- const key = bootstrapping.slice().sort().join(",");
29380
- const prevKey = lastBootstrapping.slice().sort().join(",");
29381
- if (key !== prevKey) {
29382
- const plural = bootstrapping.length === 1 ? "" : "s";
29383
- console.log(
29384
- `neat: waiting on ${bootstrapping.length} project${plural}: ${bootstrapping.join(", ")}`
29385
- );
29386
- lastBootstrapping = bootstrapping;
29387
- }
29383
+ }
29384
+ if (Date.now() >= nextReminderAt) {
29385
+ onProgress?.({ kind: "waiting", elapsedMs: Date.now() - startedAt });
29386
+ nextReminderAt += reminderIntervalMs;
29388
29387
  }
29389
29388
  await new Promise((r) => setTimeout(r, PROBE_INTERVAL_MS));
29390
29389
  }
@@ -29596,7 +29595,8 @@ async function ensureProjectDaemon(opts) {
29596
29595
  projectPath: opts.projectPath,
29597
29596
  ports: allocated
29598
29597
  });
29599
- const ready = await waitForDaemonReady(allocated.rest, opts.project, timeoutMs);
29598
+ opts.onProgress?.({ kind: "starting" });
29599
+ const ready = await waitForDaemonReady(allocated.rest, opts.project, timeoutMs, opts.onProgress);
29600
29600
  if (!ready.ready) {
29601
29601
  return {
29602
29602
  status: "timed-out",
@@ -29742,7 +29742,8 @@ async function runOrchestrator(opts) {
29742
29742
  const outcome = await ensureProjectDaemon({
29743
29743
  project: currentProjectName,
29744
29744
  projectPath: opts.scanPath,
29745
- timeoutMs
29745
+ timeoutMs,
29746
+ onProgress: (event) => console.log(`neat: ${daemonProgressLine(currentProjectName, event)}`)
29746
29747
  });
29747
29748
  const allocated = "ports" in outcome ? outcome.ports : null;
29748
29749
  switch (outcome.status) {
@@ -30606,6 +30607,15 @@ function bindUrl(webUrl, projectId) {
30606
30607
  function onboardingUrl(webUrl) {
30607
30608
  return `${webUrl}/onboarding`;
30608
30609
  }
30610
+ async function offerLocalExtraction(input, d, out, readLine) {
30611
+ const run = d.orchestrator;
30612
+ if (!run || !readLine) return false;
30613
+ const answer = (await readLine("Build it now? [Y/n] "))?.trim().toLowerCase();
30614
+ if (answer === "n" || answer === "no") return false;
30615
+ out("");
30616
+ const code = await run(d.cwd ?? process.cwd(), {});
30617
+ return code === 0;
30618
+ }
30609
30619
  async function runPostLoginConnect(input) {
30610
30620
  const d = input.deps;
30611
30621
  const out = d.out ?? ((line) => console.log(line));
@@ -30615,9 +30625,27 @@ async function runPostLoginConnect(input) {
30615
30625
  const readLine = d.readLine;
30616
30626
  const repo = await detect6(cwd);
30617
30627
  out("");
30628
+ const cmd = commandPrefix();
30629
+ const syncLine = `${cmd} sync --to ${input.project.name}`;
30618
30630
  if (!repo) {
30619
30631
  out("This directory has no GitHub remote, so it can't be bound to the project.");
30620
- out(` Push its graph instead: neat sync --to ${input.project.name}`);
30632
+ const hasGraph = await (d.hasLocalGraph ?? hasLocalSnapshot)(cwd);
30633
+ if (!hasGraph) {
30634
+ out(`A graph of this directory has to be built first, then pushed to ${input.project.name}.`);
30635
+ const built = await offerLocalExtraction(input, d, out, readLine);
30636
+ out("");
30637
+ if (built) {
30638
+ out(`Now push it to ${input.project.name}:`);
30639
+ out(` ${syncLine}`);
30640
+ } else {
30641
+ out(`When you're ready, build it and push it to ${input.project.name}:`);
30642
+ out(` ${cmd}`);
30643
+ out(` ${syncLine}`);
30644
+ }
30645
+ out(` Manage projects and repos: ${input.webUrl}`);
30646
+ return;
30647
+ }
30648
+ out(` Push its graph instead: ${syncLine}`);
30621
30649
  out(` Manage projects and repos: ${input.webUrl}`);
30622
30650
  return;
30623
30651
  }
@@ -30651,18 +30679,18 @@ async function runPostLoginConnect(input) {
30651
30679
  out("");
30652
30680
  if (!hasGraph) {
30653
30681
  out("There is no local graph to push yet. Build one, then push it:");
30654
- out(" neat");
30655
- out(` neat sync --to ${input.project.name}`);
30682
+ out(` ${cmd}`);
30683
+ out(` ${syncLine}`);
30656
30684
  return;
30657
30685
  }
30658
30686
  out("Push the local graph with:");
30659
- out(` neat sync --to ${input.project.name}`);
30687
+ out(` ${syncLine}`);
30660
30688
  return;
30661
30689
  }
30662
30690
  out("");
30663
30691
  out("No problem \u2014 when you want this repo in the graph:");
30664
30692
  out(` Bind it: ${bindUrl(input.webUrl, input.project.id)}`);
30665
- out(` Or push it: neat sync --to ${input.project.name}`);
30693
+ out(` Or push it: ${syncLine}`);
30666
30694
  }
30667
30695
 
30668
30696
  // src/login-sso.ts
@@ -30841,7 +30869,7 @@ async function runSsoLogin(opts, deps) {
30841
30869
  ...deps.signal ? { signal: deps.signal } : {}
30842
30870
  });
30843
30871
  if (waited.kind === "aborted") {
30844
- out("Stopped. Run `neat login` again once your project is up.");
30872
+ out(`Stopped. Run \`${commandPrefix()} login\` again once your project is up.`);
30845
30873
  return 130;
30846
30874
  }
30847
30875
  if (waited.kind === "timeout") {
@@ -30909,7 +30937,7 @@ async function runSsoLogin(opts, deps) {
30909
30937
  }
30910
30938
  });
30911
30939
  out("");
30912
- out("Run `neat logout` to switch back to your local daemon.");
30940
+ out(`Run \`${commandPrefix()} logout\` to switch back to your local daemon.`);
30913
30941
  }
30914
30942
  return 0;
30915
30943
  }
@@ -31811,7 +31839,13 @@ async function runUpCommand(argv, deps) {
31811
31839
  err("neat up: that daemon doesn't run on this machine, so it isn't yours to start.");
31812
31840
  return 2;
31813
31841
  }
31814
- const outcome = await ensure({ project: entry2.name, projectPath: entry2.path });
31842
+ const project = entry2.name;
31843
+ const outcome = await ensure({
31844
+ project,
31845
+ projectPath: entry2.path,
31846
+ // `--json` prints one object and nothing else, so progress stays quiet there.
31847
+ ...args.json ? {} : { onProgress: (event) => out(`neat up: ${daemonProgressLine(project, event)}`) }
31848
+ });
31815
31849
  const log = daemonLogPath(entry2.path);
31816
31850
  if (args.json) {
31817
31851
  const ports = "ports" in outcome ? outcome.ports : void 0;
@@ -34931,23 +34965,24 @@ async function tryOrchestrator(cmd, parsed, overrides = {}) {
34931
34965
  return result.exitCode;
34932
34966
  }
34933
34967
  async function runWelcomeFlow(parsed) {
34968
+ const runLocal = async (cwd, opts) => await tryOrchestrator(
34969
+ cwd,
34970
+ {
34971
+ ...parsed,
34972
+ ...opts?.project !== void 0 ? { project: opts.project } : {},
34973
+ ...opts?.noInstrument !== void 0 ? { noInstrument: opts.noInstrument } : {},
34974
+ ...opts?.yes !== void 0 ? { yes: opts.yes } : {}
34975
+ },
34976
+ { skipBanner: opts?.headerShown === true }
34977
+ ) ?? 0;
34934
34978
  return runWelcome({
34935
- login: (loginArgv) => runLoginCommand(loginArgv),
34979
+ login: (loginArgv) => runLoginCommand(loginArgv, { connect: { orchestrator: runLocal } }),
34936
34980
  // What the person chose at the door reaches the orchestrator as the flags they
34937
34981
  // stand for: the name as `--project`, the instrument answer as `--no-instrument`
34938
34982
  // or as `yes` (which keeps the orchestrator's own prompt quiet, since the door
34939
34983
  // already asked). `headerShown` isn't a flag anyone can type — it says the
34940
34984
  // door's wordmark already opened this run, so the orchestrator's banner is skipped.
34941
- orchestrator: async (cwd, opts) => await tryOrchestrator(
34942
- cwd,
34943
- {
34944
- ...parsed,
34945
- ...opts?.project !== void 0 ? { project: opts.project } : {},
34946
- ...opts?.noInstrument !== void 0 ? { noInstrument: opts.noInstrument } : {},
34947
- ...opts?.yes !== void 0 ? { yes: opts.yes } : {}
34948
- },
34949
- { skipBanner: opts?.headerShown === true }
34950
- ) ?? 0,
34985
+ orchestrator: runLocal,
34951
34986
  // An explicit flag is an answer; don't ask again.
34952
34987
  instrumentFlagGiven: parsed.noInstrument || parsed.dryRun
34953
34988
  });