Skip to content

feat(cli): generate nexus.yaml in-process, eliminate nexus init shell-out#247

Merged
windoliver merged 3 commits intomainfrom
worktree-curried-tinkering-bentley
Apr 12, 2026
Merged

feat(cli): generate nexus.yaml in-process, eliminate nexus init shell-out#247
windoliver merged 3 commits intomainfrom
worktree-curried-tinkering-bentley

Conversation

@windoliver
Copy link
Copy Markdown
Owner

Closes #196.

Summary

  • generateNexusYaml() — generates nexus.yaml directly in TypeScript using FNV-1a port derivation; no nexus init shell-out needed for initialization
  • derivePort(cwd) — stable per-worktree port [10000, 59999] from path hash; each worktree gets an isolated Nexus instance that survives restarts
  • ensureNexusComposeFile() — provisions nexus-stack.yml + 001-enable-pgvector.sql before nexus up runs (fills the gap left by removing nexus init)
  • Fixed OrbStack cross-worktree leakage — removed DEFAULT_NEXUS_URL (localhost:2026) from candidate URL list; OrbStack was forwarding another project's Nexus container to that port, causing grove to adopt a foreign Nexus instance
  • Fixed discoverRunningNexus — dropped brittle --filter ancestor=:edge that missed :latest/:stable images; now detects by port 2026 binding with worktree ownership check
  • persistNexusUrlToConfig() — extracted from startServices() as explicit call-site responsibility; makes the grove.json side effect visible and testable
  • waitForServiceHealth() — replaces 5s blind sleep with exponential-backoff health polling
  • Fixed silent error swallowing — headless mode now uses report() instead of options.onProgress?.() so errors are visible without a TUI

Tests

36 new unit tests in src/cli/nexus-lifecycle.test.ts covering:

  • derivePort: stability, range, snapshots, collision resistance, edge cases
  • inferNexusPreset: all mode/preset combinations
  • readNexusState: missing/malformed/valid JSON
  • generateNexusYaml: schema correctness, port derivation, idempotency, api_key generation
  • readNexusUrl: YAML parsing, missing ports, malformed input
  • readNexusApiKey: priority order (env > state.json > nexus.yaml)

E2E

Two parallel worktrees validated simultaneously:

  • Grove-A: port 45592, Grove-B: port 19411 — both healthy concurrently
  • discoverRunningNexus returns each worktree's own port only
  • Grove-A's API key rejected by Grove-B's Nexus ("Invalid or missing API key")
  • Grove-B's API key rejected by Grove-A's Nexus
  • Separate postgres DBs, separate data dirs, separate compose project names

…-out

Resolves #196. Grove now generates nexus.yaml directly without shelling
out to the nexus CLI, giving each worktree a stable isolated Nexus
instance derived from its path.

Key changes:
- derivePort(cwd): FNV-1a hash → deterministic port [10000, 59999]
- generateNexusYaml(): replaces `nexus init` for YAML generation
- ensureNexusComposeFile(): provisions nexus-stack.yml + pgvector SQL
  before `nexus up` (fills the gap left by removing `nexus init`)
- readNexusState(): unified state.json reader, removes 3 duplicates
- buildNexusUpArgs(): extracted to prevent flag divergence on fallback
- discoverRunningNexus(): drops brittle :edge ancestor filter, detects
  by port 2026 binding — works across :latest/:stable/:edge image tags
- Remove DEFAULT_NEXUS_URL from candidateUrls — fixes OrbStack
  cross-worktree session leakage (port 2026 was forwarded by OrbStack)
- persistNexusUrlToConfig(): extracted from startServices(), callers
  call it explicitly — makes the side effect visible and testable
- waitForServiceHealth(): replaces 5s blind sleep with backoff polling
- Fix silent error swallowing in headless mode (report vs onProgress)
- 36 unit tests for derivePort, inferNexusPreset, readNexusState,
  generateNexusYaml, readNexusUrl, readNexusApiKey

E2E validated: two parallel worktrees (ports 45592, 19411) boot
independently, auth keys are rejected cross-instance, contributions
cannot cross over.
main re-added DEFAULT_NEXUS_URL to candidateUrls; keeping it out to
prevent OrbStack cross-worktree session leakage (another project's
Nexus container forwarded to localhost:2026). Adopt main's ordering
(GROVE_NEXUS_URL first as highest-priority override).
Format nexus-lifecycle.ts and init.ts per biome rules.
Remove unused GenerateNexusYamlOptions import and prefix
unused result variable with _ in nexus-lifecycle.test.ts.
@windoliver windoliver merged commit 786b8f9 into main Apr 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: grove up should generate nexus.yaml directly without shelling out to nexus CLI

1 participant