Ctrl K
ComunidadGuías…

scripts/dev-worktree.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bun
/**
 * Run the platform from a worktree against an isolated workdir.
 *
 * Use case: smoke-test or QA against a feature branch without colliding
 * with a primary `~/.nimblebrain` dev or another worktree's state. Runs
 * in dev mode (no `instance.json` → no auth gate), so it's suitable for
 * Chrome DevTools-driven E2E against `/v1/*` endpoints with no login
 * dance.
 *
 * Convention (per worktree root):
 *   - Workdir: `<cwd>/.nimblebrain-worktree/`
 *   - Config:  `<cwd>/.nimblebrain-worktree/nimblebrain.json` (auto-seeded on first run)
 *   - Ports:   API 27271, Web 27270 (override via `NB_API_PORT` / `NB_WEB_PORT`)
 *   - Auth:    none (dev mode — no `instance.json`)
 *
 * Set `ANTHROPIC_API_KEY` (or other provider keys) in your shell to
 * unlock real LLM calls. As a convenience, this script also auto-loads
 * `<worktree>/.env` if present, otherwise the main repo's `.env`
 * (discovered via `git rev-parse --git-common-dir`). Shell-exported
 * values always win over the file. Without any keys set, everything
 * but model invocation still works — uploads, MCP resources, tool
 * calls, conversation log.
 *
 * Reset state:  `rm -rf .nimblebrain-worktree && bun run dev:worktree`
 * Share state across worktrees:  `NB_WORK_DIR=/abs/path bun run dev:worktree`
 */

import { spawn } from "node:child_process";
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { loadDotenvIntoProcess } from "./lib/dev-env.ts";
import { installIfMissing } from "./lib/dev-prepare.ts";
import { buildDevWorktreeSeed, WORKDIR_NAME } from "./lib/dev-worktree-config.ts";

// Anchor the worktree root from the script's location, not `process.cwd()`,
// so `bun run scripts/dev-worktree.ts` from a subdirectory still resolves
// the right place. The script lives at `<worktree>/scripts/dev-worktree.ts`,
// so the parent of its containing directory is the worktree root.
const WORKTREE_ROOT = dirname(import.meta.dir);
// `||` (not `??`) so an accidentally-exported empty `NB_WORK_DIR=""` from a
// misconfigured shell or direnv doesn't slip through and produce nonsense
// paths. Same for the port vars below.
const WORKDIR = process.env.NB_WORK_DIR || join(WORKTREE_ROOT, WORKDIR_NAME);
const CONFIG_PATH = join(WORKDIR, "nimblebrain.json");
const API_PORT = process.env.NB_API_PORT || "27271";
const WEB_PORT = process.env.NB_WEB_PORT || "27270";

function seedConfigIfMissing(): void {
  if (existsSync(CONFIG_PATH)) return;
  mkdirSync(WORKDIR, { recursive: true });
  const seed = buildDevWorktreeSeed(WORKTREE_ROOT, WORKDIR);
  writeFileSync(CONFIG_PATH, `${JSON.stringify(seed, null, 2)}\n`);
  console.log(`[dev:worktree] Seeded ${CONFIG_PATH}`);
}

seedConfigIfMissing();

// Auto-load .env BEFORE the spawn so the child process inherits keys.
// Discovery is worktree-local first, then the main repo (shared `.env`
// via `git rev-parse --git-common-dir`). Shell exports always win, so
// `direnv` / `mise` workflows aren't disrupted. Silent when no .env
// found — the prior "set in your shell" contract still works.
const envResult = loadDotenvIntoProcess(WORKTREE_ROOT);

// Root ONLY. `scripts/dev.ts` imports from `src/`, so it cannot install the
// deps it needs to be loaded — that has to happen before it is spawned. Web
// deps and app UI builds live in `scripts/lib/dev-prepare.ts`, which dev.ts
// runs, so every launcher gets them and not just this one.
installIfMissing("root", WORKTREE_ROOT, "[dev:worktree]");

console.log("[dev:worktree] Starting");
console.log(`[dev:worktree]   Worktree: ${WORKTREE_ROOT}`);
console.log(`[dev:worktree]   Workdir:  ${WORKDIR}`);
console.log(`[dev:worktree]   API:      http://localhost:${API_PORT}`);
console.log(`[dev:worktree]   Web:      http://localhost:${WEB_PORT}`);
console.log("[dev:worktree]   Auth:     none (dev mode)");
if (envResult.path) {
  const note =
    envResult.skipped.length > 0
      ? ` (applied ${envResult.applied.length}, ${envResult.skipped.length} skipped — shell already set)`
      : ` (applied ${envResult.applied.length})`;
  console.log(`[dev:worktree]   Loaded .env from ${envResult.path}${note}`);
}

// Delegate to the shared dev orchestrator (serve API + Vite web). The runtime
// binary is serve-only — it no longer has a `dev` subcommand — so worktree dev
// goes through `scripts/dev.ts`, the same launcher `bun run dev` uses, with the
// worktree's ports/workdir threaded via env below.
const child = spawn("bun", ["run", "scripts/dev.ts", "--port", API_PORT, "--config", CONFIG_PATH], {
  stdio: "inherit",
  cwd: WORKTREE_ROOT,
  env: {
    ...process.env,
    NB_API_PORT: API_PORT,
    NB_WEB_PORT: WEB_PORT,
    NB_WORK_DIR: WORKDIR,
  },
});

child.on("error", (err) => {
  console.error(`[dev:worktree] Failed to spawn bun: ${err.message}`);
  process.exit(1);
});

child.on("exit", (code) => process.exit(code ?? 0));