@precisa-saude/worktree-cli 1.8.0 → 1.9.0
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/README.md +14 -1
- package/dist/bin.js +66 -1
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +60 -1
- package/dist/index.js +233 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -68,11 +68,24 @@ Add a `worktree` field to the repo's root `package.json`:
|
|
|
68
68
|
"logPrefix": "precisa-web",
|
|
69
69
|
"env": { "VITE_API_URL": "http://localhost:{api_port}/api" }
|
|
70
70
|
}
|
|
71
|
-
]
|
|
71
|
+
],
|
|
72
|
+
"writeFiles": [
|
|
73
|
+
{ "path": "apps/api/.env.local", "contents": "PORT={api_port}\n" },
|
|
74
|
+
{
|
|
75
|
+
"path": "apps/web/.env.local",
|
|
76
|
+
"contents": "VITE_DEV_PORT={web_port}\nVITE_API_URL=http://localhost:{api_port}/api\n"
|
|
77
|
+
}
|
|
78
|
+
],
|
|
79
|
+
"inheritEnvFromMain": ["apps/api/.env.local", "apps/web/.env.local"],
|
|
80
|
+
"buildCommand": "pnpm turbo run build --filter='./packages/**'"
|
|
72
81
|
}
|
|
73
82
|
}
|
|
74
83
|
```
|
|
75
84
|
|
|
85
|
+
`inheritEnvFromMain` appends KEY=VALUE lines from the main worktree's `.env.local` files into the new worktree's corresponding files at the end of `setup`. Port keys written by `writeFiles` are NOT overwritten — only missing keys are appended. Useful when the main worktree holds long-lived dev credentials (Cognito, Stripe, S3 buckets, DynamoDB tables) that every worktree needs but that aren't worktree-scoped.
|
|
86
|
+
|
|
87
|
+
`buildCommand` runs after `pnpm install` to pre-build workspace packages. Required when downstream apps load workspace deps via Node ESM (e.g. `apps/api` reading `packages/*/dist/index.js`) — without it, the API will crash with `ERR_MODULE_NOT_FOUND` on first start because `dist/` isn't populated.
|
|
88
|
+
|
|
76
89
|
## Usage
|
|
77
90
|
|
|
78
91
|
```bash
|
package/dist/bin.js
CHANGED
|
@@ -449,9 +449,48 @@ async function logs({ branch, cfg, service }) {
|
|
|
449
449
|
|
|
450
450
|
// src/commands/setup.ts
|
|
451
451
|
import { execFileSync, spawnSync as spawnSync2 } from "child_process";
|
|
452
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync } from "fs";
|
|
452
|
+
import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
453
453
|
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
454
454
|
import chalk3 from "chalk";
|
|
455
|
+
|
|
456
|
+
// src/env-merge.ts
|
|
457
|
+
function parseEnvKeys(contents) {
|
|
458
|
+
const keys = /* @__PURE__ */ new Set();
|
|
459
|
+
for (const rawLine of contents.split("\n")) {
|
|
460
|
+
const line = rawLine.trimStart();
|
|
461
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
462
|
+
const eq = line.indexOf("=");
|
|
463
|
+
if (eq <= 0) continue;
|
|
464
|
+
const key = line.slice(0, eq).trim();
|
|
465
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
|
|
466
|
+
keys.add(key);
|
|
467
|
+
}
|
|
468
|
+
return keys;
|
|
469
|
+
}
|
|
470
|
+
function buildInheritedAppend(mainContents, worktreeContents, options = {}) {
|
|
471
|
+
const existingKeys = parseEnvKeys(worktreeContents);
|
|
472
|
+
const linesToAppend = [];
|
|
473
|
+
for (const rawLine of mainContents.split("\n")) {
|
|
474
|
+
const line = rawLine.trimStart();
|
|
475
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
476
|
+
const eq = line.indexOf("=");
|
|
477
|
+
if (eq <= 0) continue;
|
|
478
|
+
const key = line.slice(0, eq).trim();
|
|
479
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
|
|
480
|
+
if (existingKeys.has(key)) continue;
|
|
481
|
+
linesToAppend.push(rawLine);
|
|
482
|
+
existingKeys.add(key);
|
|
483
|
+
}
|
|
484
|
+
if (linesToAppend.length === 0) return "";
|
|
485
|
+
const header = options.header ?? "# Inherited from main worktree by precisa-worktree setup";
|
|
486
|
+
const prefix = worktreeContents.length > 0 && !worktreeContents.endsWith("\n") ? "\n" : "";
|
|
487
|
+
return `${prefix}
|
|
488
|
+
${header}
|
|
489
|
+
${linesToAppend.join("\n")}
|
|
490
|
+
`;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// src/commands/setup.ts
|
|
455
494
|
async function setup({ branch, cfg, repoRoot }) {
|
|
456
495
|
const wtPath = worktreePath(cfg, repoRoot, branch);
|
|
457
496
|
if (existsSync5(wtPath)) {
|
|
@@ -486,6 +525,32 @@ async function setup({ branch, cfg, repoRoot }) {
|
|
|
486
525
|
console.log(` wrote ${path}`);
|
|
487
526
|
}
|
|
488
527
|
}
|
|
528
|
+
if (cfg.inheritEnvFromMain?.length) {
|
|
529
|
+
console.log(chalk3.bold("==> Inheriting non-port env vars from main worktree"));
|
|
530
|
+
for (const relPath of cfg.inheritEnvFromMain) {
|
|
531
|
+
const mainFile = resolve3(repoRoot, relPath);
|
|
532
|
+
const wtFile = resolve3(wtPath, relPath);
|
|
533
|
+
if (!existsSync5(mainFile)) {
|
|
534
|
+
console.log(chalk3.dim(` skip ${relPath} (not present in main worktree)`));
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
const mainContents = readFileSync(mainFile, "utf-8");
|
|
538
|
+
const wtContents = existsSync5(wtFile) ? readFileSync(wtFile, "utf-8") : "";
|
|
539
|
+
const appended = buildInheritedAppend(mainContents, wtContents);
|
|
540
|
+
if (appended.length === 0) {
|
|
541
|
+
console.log(chalk3.dim(` skip ${relPath} (no missing keys to inherit)`));
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
mkdirSync2(dirname2(wtFile), { recursive: true });
|
|
545
|
+
if (existsSync5(wtFile)) {
|
|
546
|
+
appendFileSync(wtFile, appended);
|
|
547
|
+
} else {
|
|
548
|
+
writeFileSync(wtFile, appended.replace(/^\n/, ""));
|
|
549
|
+
}
|
|
550
|
+
const inheritedCount = appended.split("\n").filter((l) => /^[A-Za-z_]/.test(l)).length;
|
|
551
|
+
console.log(` inherited ${inheritedCount} key(s) into ${relPath}`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
489
554
|
console.log("");
|
|
490
555
|
console.log(chalk3.green(`Worktree ready: ${wtPath}`));
|
|
491
556
|
console.log(` Branch: ${branch}`);
|
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bin.ts","../src/commands/dev.ts","../src/config.ts","../src/paths.ts","../src/ports.ts","../src/registry.ts","../src/commands/list.ts","../src/commands/logs.ts","../src/commands/setup.ts","../src/commands/stop.ts","../src/commands/teardown.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { rmdirSync } from 'node:fs';\n\nimport chalk from 'chalk';\nimport { Command } from 'commander';\n\nimport { dev } from './commands/dev.js';\nimport { list } from './commands/list.js';\nimport { logs } from './commands/logs.js';\nimport { setup } from './commands/setup.js';\nimport { stop } from './commands/stop.js';\nimport { teardown } from './commands/teardown.js';\nimport { loadConfig, lockDir, type WorktreeConfig } from './config.js';\nimport { detectBranch, findRepoRoot, mainWorktreeRoot } from './paths.js';\n\nasync function main(): Promise<void> {\n const program = new Command();\n program\n .name('precisa-worktree')\n .description('Git-worktree lifecycle CLI for Precisa Saúde repositories')\n .version('1.0.4');\n\n program\n .command('setup <branch>')\n .description('Create a worktree, install deps, allocate ports')\n .action(async (branch: string) => {\n const { cfg, mainRoot } = await resolveContext({ requireMain: true });\n await setup({ branch, cfg, repoRoot: mainRoot });\n });\n\n program\n .command('dev [branch]')\n .description('Start dev server(s) on the allocated ports')\n .option('-d, --detach', 'Run detached (nohup-style); write to log files', false)\n .option('-f, --force', 'Kill any process already on the port', false)\n .action(async (branchArg: string | undefined, opts: { detach: boolean; force: boolean }) => {\n const { branch, cfg, wtRoot } = await resolveContext({ allowDetect: true, branchArg });\n await dev({ branch, cfg, detach: opts.detach, force: opts.force, repoRoot: wtRoot });\n });\n\n program\n .command('stop [branch]')\n .description('Kill dev server(s) for a worktree')\n .action(async (branchArg?: string) => {\n const { branch, cfg } = await resolveContext({ allowDetect: true, branchArg });\n await stop({ branch, cfg });\n });\n\n program\n .command('teardown <branch>')\n .description('Stop dev servers, remove worktree, delete branch, free ports')\n .option('--keep-branch', 'Preserve the local branch', false)\n .action(async (branch: string, opts: { keepBranch: boolean }) => {\n const { cfg, mainRoot } = await resolveContext({ requireMain: true });\n await teardown({ branch, cfg, keepBranch: opts.keepBranch, repoRoot: mainRoot });\n });\n\n program\n .command('list')\n .description('List all worktrees with ports and status')\n .action(async () => {\n const { cfg, mainRoot } = await resolveContext({ requireMain: true });\n await list({ cfg, repoRoot: mainRoot });\n });\n\n program\n .command('logs [branch]')\n .description('Tail the log for a worktree dev server')\n .option('-s, --service <name>', 'Service name (defaults to the first configured)')\n .action(async (branchArg: string | undefined, opts: { service?: string }) => {\n const { branch, cfg } = await resolveContext({ allowDetect: true, branchArg });\n await logs({ branch, cfg, service: opts.service });\n });\n\n // Global EXIT handler — release the lock if we died mid-operation.\n // Safe because `rmdir` only succeeds on an empty directory we created.\n process.on('exit', () => {\n try {\n // Best-effort; we don't know the config if we failed before loading it.\n // This handler is installed after config load via resolveContext.\n } catch {\n // ignore\n }\n });\n\n try {\n await program.parseAsync(process.argv);\n } catch (err) {\n console.error(chalk.red((err as Error).message));\n process.exit(1);\n }\n}\n\ninterface ResolveOpts {\n /** Allow `detectBranch()` when inside a linked worktree. */\n allowDetect?: boolean;\n branchArg?: string | undefined;\n /** Require the repo root to be the main worktree (setup/teardown/list). */\n requireMain?: boolean;\n}\n\nasync function resolveContext(opts: ResolveOpts): Promise<{\n cfg: WorktreeConfig;\n wtRoot: string;\n mainRoot: string;\n branch: string;\n}> {\n const wtRoot = findRepoRoot();\n const mainRoot = mainWorktreeRoot();\n const cfg = await loadConfig(mainRoot);\n\n // Install an exit handler now that we know the lock path.\n const lockPath = lockDir(cfg);\n process.on('exit', () => {\n try {\n rmdirSync(lockPath);\n } catch {\n // already released or never held\n }\n });\n\n let branch = opts.branchArg ?? '';\n if (!branch && opts.allowDetect) {\n branch = detectBranch(cfg) ?? '';\n }\n\n if (!branch && !opts.requireMain) {\n console.error(\n chalk.red(\n \"Couldn't detect branch. Run from inside a linked worktree or pass the branch name.\",\n ),\n );\n process.exit(1);\n }\n\n return { branch, cfg, mainRoot, wtRoot };\n}\n\nvoid main();\n","import { spawn } from 'node:child_process';\nimport { createWriteStream, existsSync } from 'node:fs';\n\nimport chalk from 'chalk';\n\nimport { devArgs, type ServiceConfig, type WorktreeConfig } from '../config.js';\nimport { logFile, worktreePath } from '../paths.js';\nimport { isPortInUse, killPort } from '../ports.js';\nimport { getBranchEntry, portFor } from '../registry.js';\n\nexport interface DevOptions {\n branch: string;\n cfg: WorktreeConfig;\n /** Run detached from the terminal; write stdout/stderr to log files. */\n detach?: boolean;\n /** If a port is already in use, kill the process instead of aborting. */\n force?: boolean;\n repoRoot: string;\n}\n\nexport async function dev({\n branch,\n cfg,\n detach = false,\n force = false,\n repoRoot,\n}: DevOptions): Promise<void> {\n const entry = await getBranchEntry(cfg, branch);\n if (!entry) {\n console.error(chalk.red(`No ports allocated for '${branch}'. Run setup first.`));\n process.exit(1);\n }\n\n const wtPath = worktreePath(cfg, repoRoot, branch);\n if (!existsSync(wtPath)) {\n console.error(chalk.red(`Worktree not found at ${wtPath}`));\n process.exit(1);\n }\n\n for (const svc of cfg.services) {\n const port = portFor(entry, svc);\n if (isPortInUse(port)) {\n if (force) {\n console.log(chalk.yellow(`Port ${port} in use — killing (--force)`));\n await killPort(port);\n } else {\n console.error(\n chalk.red(`Port ${port} (${svc.name}) already in use. Pass --force to kill.`),\n );\n process.exit(1);\n }\n }\n }\n\n console.log(chalk.bold(`Starting dev servers for '${branch}'...`));\n for (const svc of cfg.services) {\n const port = portFor(entry, svc);\n console.log(` ${svc.name}: http://localhost:${port} (logs: ${logFile(svc, branch)})`);\n }\n console.log('');\n\n const procs = cfg.services.map((svc) => spawnService(svc, entry, wtPath, branch, detach));\n\n if (detach) {\n for (const p of procs) p.unref();\n console.log(chalk.green('Started detached. Use `precisa-worktree stop` to kill.'));\n return;\n }\n\n // Foreground: forward signals, wait for first exit.\n const cleanup = (signal: NodeJS.Signals): void => {\n for (const p of procs) {\n try {\n if (!p.killed) p.kill(signal);\n } catch {\n // already dead\n }\n }\n };\n process.on('SIGINT', () => cleanup('SIGINT'));\n process.on('SIGTERM', () => cleanup('SIGTERM'));\n\n const exitCode = await new Promise<number>((resolvePromise) => {\n let resolved = false;\n for (const p of procs) {\n p.on('exit', (code) => {\n if (resolved) return;\n resolved = true;\n cleanup('SIGTERM');\n resolvePromise(code ?? 1);\n });\n }\n });\n process.exit(exitCode);\n}\n\nfunction spawnService(\n svc: ServiceConfig,\n entry: { ports: Record<string, number> },\n cwd: string,\n branch: string,\n detach: boolean,\n): ReturnType<typeof spawn> {\n const port = portFor(entry, svc);\n const interpolate = (s: string): string => {\n let out = s.replace('{port}', String(port));\n // Allow cross-service refs like `{api_port}` so one service's env\n // can embed another service's allocated port.\n for (const [otherName, otherPort] of Object.entries(entry.ports)) {\n out = out.replaceAll(`{${otherName}_port}`, String(otherPort));\n }\n return out;\n };\n\n const rawArgs = devArgs(svc);\n // No `--` separator: with pnpm 9 + `\"dev\": \"vite\"`, inserting `--`\n // causes vite to receive it as a literal argv entry (`vite -- --port NNN`)\n // and silently ignore the port flag, falling back to its default.\n const args = ['--filter', svc.pnpmFilter, 'dev', ...rawArgs.map(interpolate)];\n\n const env = { ...process.env };\n for (const [k, v] of Object.entries(svc.env ?? {})) {\n env[k] = interpolate(v);\n }\n\n if (detach) {\n const logPath = logFile(svc, branch);\n const out = createWriteStream(logPath, { flags: 'a' });\n const proc = spawn('pnpm', args, {\n cwd,\n detached: true,\n env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n proc.stdout?.pipe(out);\n proc.stderr?.pipe(out);\n return proc;\n }\n\n return spawn('pnpm', args, { cwd, env, stdio: 'inherit' });\n}\n","import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\n\nexport interface ServiceConfig {\n /**\n * Extra args passed to the dev script. `{port}` is replaced with the\n * allocated port. Defaults to `['--port', '{port}']` (works with vite).\n */\n devArgs?: string[];\n /**\n * Optional: additional env vars to set for the dev process. The literal\n * `{port}` is substituted. Example: `{ VITE_API_URL: 'http://localhost:{port}/api' }`.\n */\n env?: Record<string, string>;\n /**\n * First port for feature worktrees. Slot 1 uses this port; slot 2 uses\n * `featureBase + increment`, slot 3 uses `featureBase + 2*increment`, etc.\n */\n featureBase: number;\n /** Increment between slots. Defaults to 10. */\n increment?: number;\n /**\n * Prefix for the log file path: `/tmp/<logPrefix>-<branch>.log`. Required\n * so that sibling services in a repo don't overwrite each other's logs.\n */\n logPrefix: string;\n /** Port used by the default (main) worktree. */\n mainPort: number;\n /** Service name used in CLI output, column headers, and log file naming. */\n name: string;\n /** pnpm filter pattern for `worktree dev`. Example: `@medbench-brasil/site`. */\n pnpmFilter: string;\n}\n\nexport interface WorktreeConfig {\n /**\n * Optional extra build command run during `setup`, after `pnpm install`.\n * Example: `pnpm turbo run build --filter=./packages/*`.\n */\n buildCommand?: string;\n /**\n * Prefix used for worktree directory names: `<prefix>-<branch>`.\n * Example: `medbench-brasil` → worktrees land at\n * `../medbench-brasil-feat-foo`.\n */\n directoryPrefix: string;\n /**\n * Whether to run `pnpm install` during setup. Defaults to true. Set to\n * false for repos where install should be manual or where the script\n * is invoked inside a container.\n */\n install?: boolean;\n /** Absolute path to the lock directory. Defaults to `<portRegistry>.lock.d`. */\n portLockDir?: string;\n /** Absolute path to the port registry JSON file. */\n portRegistry: string;\n /** Services managed by this repo. At least one required. */\n services: ServiceConfig[];\n /**\n * Extra per-service file writes performed during `setup`, after\n * install + build. Intended for repos that need `.env.local`-style\n * files seeded with the allocated ports so that running `dev` without\n * explicit env vars picks up the right values.\n *\n * Each entry is `{ path, contents }` where `contents` supports the\n * same `{port}` and `{<service>_port}` substitutions available in\n * `service.env` / `service.devArgs`.\n *\n * Example (platform):\n * writeFiles: [\n * { path: 'apps/web/.env.local',\n * contents: 'VITE_DEV_PORT={web_port}\\nVITE_API_URL=http://localhost:{api_port}/api\\n' },\n * { path: 'apps/api/.env.local',\n * contents: 'PORT={api_port}\\n' },\n * ]\n */\n writeFiles?: Array<{ path: string; contents: string }>;\n}\n\ninterface PackageJson {\n name?: string;\n worktree?: WorktreeConfig;\n}\n\nexport async function loadConfig(repoRoot: string): Promise<WorktreeConfig> {\n const pkgPath = resolve(repoRoot, 'package.json');\n let pkg: PackageJson;\n try {\n pkg = JSON.parse(await readFile(pkgPath, 'utf-8')) as PackageJson;\n } catch (err) {\n throw new Error(`Failed to read ${pkgPath}: ${(err as Error).message}. Is this the repo root?`);\n }\n\n const cfg = pkg.worktree;\n if (!cfg) {\n throw new Error(\n `No \"worktree\" field in ${pkgPath}. Add a worktree config — see ` +\n `https://github.com/Precisa-Saude/tooling/tree/main/packages/worktree-cli#configuration`,\n );\n }\n\n if (!cfg.directoryPrefix) {\n throw new Error('worktree.directoryPrefix is required');\n }\n if (!cfg.portRegistry) {\n throw new Error('worktree.portRegistry is required');\n }\n if (!cfg.services?.length) {\n throw new Error('worktree.services must contain at least one entry');\n }\n for (const svc of cfg.services) {\n if (!svc.name) throw new Error('service.name is required');\n if (!svc.pnpmFilter) throw new Error(`service.${svc.name}.pnpmFilter is required`);\n if (!svc.logPrefix) throw new Error(`service.${svc.name}.logPrefix is required`);\n if (typeof svc.mainPort !== 'number') {\n throw new Error(`service.${svc.name}.mainPort must be a number`);\n }\n if (typeof svc.featureBase !== 'number') {\n throw new Error(`service.${svc.name}.featureBase must be a number`);\n }\n }\n\n return cfg;\n}\n\nexport function lockDir(cfg: WorktreeConfig): string {\n return cfg.portLockDir ?? `${cfg.portRegistry}.lock.d`;\n}\n\nexport function increment(svc: ServiceConfig): number {\n return svc.increment ?? 10;\n}\n\nexport function devArgs(svc: ServiceConfig): string[] {\n return svc.devArgs ?? ['--port', '{port}'];\n}\n","import { execSync } from 'node:child_process';\nimport { basename, dirname, resolve } from 'node:path';\n\nimport type { ServiceConfig, WorktreeConfig } from './config.js';\n\n/** Convert a branch name to a safe directory-suffix: `feat/foo` → `feat-foo`. */\nexport function branchToSuffix(branch: string): string {\n return branch.replace(/\\//g, '-');\n}\n\nexport function worktreeDirectoryName(cfg: WorktreeConfig, branch: string): string {\n return `${cfg.directoryPrefix}-${branchToSuffix(branch)}`;\n}\n\nexport function worktreePath(cfg: WorktreeConfig, repoRoot: string, branch: string): string {\n const parentDir = dirname(repoRoot);\n return resolve(parentDir, worktreeDirectoryName(cfg, branch));\n}\n\nexport function logFile(svc: ServiceConfig, branch: string): string {\n return `/tmp/${svc.logPrefix}-${branchToSuffix(branch)}.log`;\n}\n\n/**\n * Detect the current branch when invoked from inside a linked worktree.\n * Returns null from the main worktree — refusing auto-detect there avoids\n * accidentally acting on `main` when the user forgot to pass a branch.\n */\nexport function detectBranch(cfg: WorktreeConfig): string | null {\n const cwdBase = basename(process.cwd());\n if (!cwdBase.startsWith(`${cfg.directoryPrefix}-`)) return null;\n try {\n const out = execSync('git branch --show-current', { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .trim();\n return out || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve the repo root by walking up from cwd looking for a `package.json`\n * with a `worktree` field. If invoked from inside a linked worktree, falls\n * back to the main worktree (identified by the directory NOT having the\n * `<prefix>-<branch>` suffix).\n */\nexport function findRepoRoot(): string {\n // `git rev-parse --show-toplevel` gives the worktree root (linked or main).\n try {\n return execSync('git rev-parse --show-toplevel', { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .trim();\n } catch {\n return process.cwd();\n }\n}\n\n/**\n * From a linked worktree, walk back to the main worktree using\n * `git worktree list --porcelain`. The first entry is always the main.\n */\nexport function mainWorktreeRoot(): string {\n try {\n const out = execSync('git worktree list --porcelain', {\n stdio: ['ignore', 'pipe', 'ignore'],\n }).toString();\n const firstLine = out.split('\\n')[0] ?? '';\n const match = firstLine.match(/^worktree (.+)$/);\n if (match) return match[1]!;\n } catch {\n // fall through\n }\n return findRepoRoot();\n}\n","import { execSync } from 'node:child_process';\nimport { setTimeout as sleep } from 'node:timers/promises';\n\n/**\n * Return PIDs listening on `port` using the first available tool.\n * Prefers lsof (macOS default), falls back to ss then fuser (Linux).\n * Returns empty array if none can inspect ports or nothing is listening.\n */\nfunction listenersOnPort(port: number): number[] {\n for (const [cmd, parse] of probers) {\n if (!hasCommand(cmd)) continue;\n try {\n const out = execSync(probeCommand(cmd, port), { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .trim();\n return parse(out);\n } catch {\n // non-zero exit — no listeners found\n return [];\n }\n }\n console.error(`Warning: lsof/ss/fuser not available — can't inspect port ${port}`);\n return [];\n}\n\ntype Prober = [cmd: string, parse: (output: string) => number[]];\n\nconst probers: Prober[] = [\n [\n 'lsof',\n (out) =>\n out\n .split('\\n')\n .map((s) => parseInt(s.trim(), 10))\n .filter((n) => !isNaN(n)),\n ],\n [\n 'ss',\n (out) => {\n const pids: number[] = [];\n for (const line of out.split('\\n')) {\n const matches = line.match(/pid=(\\d+)/g) ?? [];\n for (const m of matches) pids.push(parseInt(m.slice(4), 10));\n }\n return pids;\n },\n ],\n [\n 'fuser',\n (out) =>\n out\n .split(/\\s+/)\n .map((s) => parseInt(s, 10))\n .filter((n) => !isNaN(n)),\n ],\n];\n\nfunction probeCommand(cmd: string, port: number): string {\n switch (cmd) {\n case 'lsof':\n return `lsof -iTCP:${port} -sTCP:LISTEN -t`;\n case 'ss':\n return `ss -tlnp | awk '$4 ~ /:${port}$/ {print}'`;\n case 'fuser':\n return `fuser -n tcp ${port} 2>/dev/null`;\n default:\n throw new Error(`Unsupported prober: ${cmd}`);\n }\n}\n\nfunction hasCommand(cmd: string): boolean {\n try {\n execSync(`command -v ${cmd}`, { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\nexport function isPortInUse(port: number): boolean {\n return listenersOnPort(port).length > 0;\n}\n\n/** Kill processes listening on `port`. SIGTERM first, then SIGKILL if any survive. */\nexport async function killPort(port: number): Promise<void> {\n const pids = listenersOnPort(port);\n if (pids.length === 0) return;\n\n for (const pid of pids) {\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n // process may have exited between listing and kill — ignore\n }\n }\n\n await sleep(1000);\n\n const stillAlive = listenersOnPort(port);\n for (const pid of stillAlive) {\n try {\n process.kill(pid, 'SIGKILL');\n } catch {\n // ignore\n }\n }\n}\n","import { existsSync, mkdirSync, rmdirSync } from 'node:fs';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { setTimeout as sleep } from 'node:timers/promises';\n\nimport { increment, lockDir, type ServiceConfig, type WorktreeConfig } from './config.js';\n\nexport type Registry = Record<string, { slot: number; ports: Record<string, number> }>;\n\nasync function ensureRegistry(cfg: WorktreeConfig): Promise<void> {\n if (!existsSync(cfg.portRegistry)) {\n await writeFile(cfg.portRegistry, '{}');\n }\n}\n\n/**\n * Acquire an exclusive lock via `mkdir` — POSIX-atomic, portable across\n * macOS/Linux, no dependency on `flock` (which isn't on macOS by default).\n * Used to serialize registry read-modify-write across concurrent sessions.\n */\nasync function acquireLock(cfg: WorktreeConfig): Promise<void> {\n const dir = lockDir(cfg);\n const maxTries = 100;\n for (let i = 0; i < maxTries; i++) {\n try {\n mkdirSync(dir);\n return;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;\n }\n await sleep(100);\n }\n throw new Error(\n `Timeout (~10s) waiting for lock at ${dir}. If no other session is ` +\n `running, remove it manually: rmdir ${dir}`,\n );\n}\n\nfunction releaseLock(cfg: WorktreeConfig): void {\n try {\n rmdirSync(lockDir(cfg));\n } catch {\n // ignore — already released or never held\n }\n}\n\n/**\n * Run `fn` while holding the registry lock. Always releases, even on throw.\n * The release is also safe against signal interruption because the lock\n * directory will be removed at process exit via the handler in bin.ts.\n */\nexport async function withLock<T>(cfg: WorktreeConfig, fn: () => Promise<T> | T): Promise<T> {\n await ensureRegistry(cfg);\n await acquireLock(cfg);\n try {\n return await fn();\n } finally {\n releaseLock(cfg);\n }\n}\n\nexport async function readRegistry(cfg: WorktreeConfig): Promise<Registry> {\n await ensureRegistry(cfg);\n const text = await readFile(cfg.portRegistry, 'utf-8');\n try {\n return JSON.parse(text) as Registry;\n } catch {\n return {};\n }\n}\n\nasync function writeRegistry(cfg: WorktreeConfig, reg: Registry): Promise<void> {\n await writeFile(cfg.portRegistry, `${JSON.stringify(reg, null, 2)}\\n`);\n}\n\n/**\n * Allocate a slot+ports for `branch`. Idempotent: returns the existing\n * entry if the branch already has one. Reuses freed slots so slot numbers\n * don't grow unbounded over many create/teardown cycles.\n */\nexport async function allocate(\n cfg: WorktreeConfig,\n branch: string,\n): Promise<{ slot: number; ports: Record<string, number> }> {\n return withLock(cfg, async () => {\n const reg = await readRegistry(cfg);\n if (reg[branch]) return reg[branch];\n\n const usedSlots = new Set(Object.values(reg).map((e) => e.slot));\n let slot = 1;\n while (usedSlots.has(slot)) slot++;\n\n const ports: Record<string, number> = {};\n for (const svc of cfg.services) {\n ports[svc.name] = svc.featureBase + (slot - 1) * increment(svc);\n }\n\n reg[branch] = { ports, slot };\n await writeRegistry(cfg, reg);\n return reg[branch];\n });\n}\n\nexport async function free(cfg: WorktreeConfig, branch: string): Promise<void> {\n return withLock(cfg, async () => {\n const reg = await readRegistry(cfg);\n if (reg[branch]) {\n delete reg[branch];\n await writeRegistry(cfg, reg);\n }\n });\n}\n\nexport async function getBranchEntry(\n cfg: WorktreeConfig,\n branch: string,\n): Promise<{ slot: number; ports: Record<string, number> } | null> {\n const reg = await readRegistry(cfg);\n return reg[branch] ?? null;\n}\n\nexport function portFor(entry: { ports: Record<string, number> }, svc: ServiceConfig): number {\n const port = entry.ports[svc.name];\n if (typeof port !== 'number') {\n throw new Error(\n `No port allocated for service \"${svc.name}\" on this branch. ` +\n `Registry entry may be from an older config version — run teardown + setup again.`,\n );\n }\n return port;\n}\n","import { existsSync } from 'node:fs';\n\nimport { type ServiceConfig, type WorktreeConfig } from '../config.js';\nimport { worktreePath } from '../paths.js';\nimport { isPortInUse } from '../ports.js';\nimport { readRegistry } from '../registry.js';\n\nexport interface ListOptions {\n cfg: WorktreeConfig;\n repoRoot: string;\n}\n\nexport async function list({ cfg, repoRoot }: ListOptions): Promise<void> {\n const reg = await readRegistry(cfg);\n\n console.log(`Worktrees for ${cfg.directoryPrefix}:`);\n console.log('');\n\n const headers = ['BRANCH', ...cfg.services.map((s) => s.name.toUpperCase()), 'STATUS'];\n const widths = [40, ...cfg.services.map(() => 10), 10];\n printRow(headers, widths);\n printRow(\n headers.map((h) => '-'.repeat(Math.min(h.length, 6))),\n widths,\n );\n\n // Main worktree row\n printRow(\n [\n 'main (default)',\n ...cfg.services.map((s) => String(s.mainPort)),\n statusOf(cfg.services, (s) => s.mainPort, /* dirExists */ true),\n ],\n widths,\n );\n\n // Feature worktree rows\n for (const [branch, entry] of Object.entries(reg)) {\n const wtPath = worktreePath(cfg, repoRoot, branch);\n const dirExists = existsSync(wtPath);\n const ports = cfg.services.map((s) => entry.ports[s.name]!);\n printRow(\n [\n branch,\n ...ports.map((p) => String(p)),\n dirExists ? statusOf(cfg.services, (s) => entry.ports[s.name]!, true) : 'missing',\n ],\n widths,\n );\n }\n\n console.log('');\n console.log(`Registry: ${cfg.portRegistry}`);\n}\n\nfunction statusOf(\n services: ServiceConfig[],\n portOf: (s: ServiceConfig) => number,\n dirExists: boolean,\n): string {\n if (!dirExists) return 'missing';\n const anyRunning = services.some((s) => isPortInUse(portOf(s)));\n return anyRunning ? 'running' : 'stopped';\n}\n\nfunction printRow(cells: string[], widths: number[]): void {\n const line = cells.map((c, i) => c.padEnd(widths[i] ?? 10)).join(' ');\n console.log(` ${line}`);\n}\n","import { spawnSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\n\nimport chalk from 'chalk';\n\nimport { type WorktreeConfig } from '../config.js';\nimport { logFile } from '../paths.js';\n\nexport interface LogsOptions {\n branch: string;\n cfg: WorktreeConfig;\n service?: string;\n}\n\nexport async function logs({ branch, cfg, service }: LogsOptions): Promise<void> {\n const svc = service ? cfg.services.find((s) => s.name === service) : cfg.services[0];\n\n if (!svc) {\n const names = cfg.services.map((s) => s.name).join(', ');\n console.error(chalk.red(`Unknown service \"${service}\". Valid services: ${names}`));\n process.exit(1);\n }\n\n const path = logFile(svc, branch);\n if (!existsSync(path)) {\n console.error(chalk.red(`Log file not found: ${path}`));\n console.error(`Start the dev server first: precisa-worktree dev ${branch}`);\n process.exit(1);\n }\n\n // Delegate to tail -f so Ctrl-C works and rotation is handled.\n const result = spawnSync('tail', ['-f', path], { stdio: 'inherit' });\n process.exit(result.status ?? 0);\n}\n","import { execFileSync, spawnSync } from 'node:child_process';\nimport { existsSync, mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\n\nimport chalk from 'chalk';\n\nimport { type WorktreeConfig } from '../config.js';\nimport { logFile, worktreePath } from '../paths.js';\nimport { allocate } from '../registry.js';\n\nexport interface SetupOptions {\n branch: string;\n cfg: WorktreeConfig;\n repoRoot: string;\n}\n\nexport async function setup({ branch, cfg, repoRoot }: SetupOptions): Promise<void> {\n const wtPath = worktreePath(cfg, repoRoot, branch);\n\n if (existsSync(wtPath)) {\n console.error(chalk.red(`Worktree already exists: ${wtPath}`));\n console.error(`To start dev servers: precisa-worktree dev ${branch}`);\n process.exit(1);\n }\n\n console.log(chalk.bold(`==> Creating worktree for '${branch}' at ${wtPath}...`));\n\n // Fetch origin/main from the main worktree; a fresh worktree branches from it.\n execFileSync('git', ['-C', repoRoot, 'fetch', 'origin', 'main'], { stdio: 'inherit' });\n\n // If the branch already exists (local or remote-tracking), check it out\n // into the new worktree instead of creating a fresh branch — otherwise\n // `git worktree add -b` errors out on the duplicate.\n const reuseExisting = branchExists(repoRoot, branch);\n const addArgs = reuseExisting\n ? ['-C', repoRoot, 'worktree', 'add', wtPath, branch]\n : ['-C', repoRoot, 'worktree', 'add', '-b', branch, wtPath, 'origin/main'];\n execFileSync('git', addArgs, { stdio: 'inherit' });\n\n const entry = await allocate(cfg, branch);\n\n if (cfg.install !== false) {\n console.log(chalk.bold('==> Installing dependencies (pnpm install)...'));\n execFileSync('pnpm', ['install'], { cwd: wtPath, stdio: 'inherit' });\n }\n\n if (cfg.buildCommand) {\n console.log(chalk.bold(`==> Running build: ${cfg.buildCommand}`));\n execFileSync('sh', ['-c', cfg.buildCommand], { cwd: wtPath, stdio: 'inherit' });\n }\n\n // Write per-repo files with the allocated ports substituted in. Used\n // by platform to seed apps/*/.env.local so the running dev server\n // picks up the right ports without extra env-var plumbing.\n if (cfg.writeFiles?.length) {\n console.log(chalk.bold('==> Writing worktree-scoped files'));\n for (const { contents, path } of cfg.writeFiles) {\n let interpolated = contents;\n for (const [svcName, port] of Object.entries(entry.ports)) {\n interpolated = interpolated\n .replaceAll(`{${svcName}_port}`, String(port))\n .replaceAll('{port}', String(port));\n }\n const fullPath = resolve(wtPath, path);\n mkdirSync(dirname(fullPath), { recursive: true });\n writeFileSync(fullPath, interpolated);\n console.log(` wrote ${path}`);\n }\n }\n\n console.log('');\n console.log(chalk.green(`Worktree ready: ${wtPath}`));\n console.log(` Branch: ${branch}`);\n for (const svc of cfg.services) {\n const port = entry.ports[svc.name]!;\n console.log(\n ` ${svc.name.padEnd(8)}: http://localhost:${port} (logs: ${logFile(svc, branch)})`,\n );\n }\n console.log('');\n console.log('To start the dev server(s):');\n console.log(` cd ${wtPath} && precisa-worktree dev ${branch}`);\n}\n\nfunction branchExists(repoRoot: string, branch: string): boolean {\n const local = spawnSync(\n 'git',\n ['-C', repoRoot, 'show-ref', '--verify', '--quiet', `refs/heads/${branch}`],\n { stdio: 'ignore' },\n );\n if (local.status === 0) return true;\n const remote = spawnSync(\n 'git',\n ['-C', repoRoot, 'show-ref', '--verify', '--quiet', `refs/remotes/origin/${branch}`],\n { stdio: 'ignore' },\n );\n return remote.status === 0;\n}\n","import chalk from 'chalk';\n\nimport { type WorktreeConfig } from '../config.js';\nimport { isPortInUse, killPort } from '../ports.js';\nimport { getBranchEntry, portFor } from '../registry.js';\n\nexport interface StopOptions {\n branch: string;\n cfg: WorktreeConfig;\n}\n\nexport async function stop({ branch, cfg }: StopOptions): Promise<void> {\n const entry = await getBranchEntry(cfg, branch);\n if (!entry) {\n console.error(`No ports registered for '${branch}'.`);\n process.exit(1);\n }\n\n let anyRunning = false;\n for (const svc of cfg.services) {\n const port = portFor(entry, svc);\n if (isPortInUse(port)) {\n console.log(chalk.bold(`Killing ${svc.name} dev server on port ${port}...`));\n await killPort(port);\n anyRunning = true;\n }\n }\n\n if (!anyRunning) {\n console.log(`No dev servers running for '${branch}'.`);\n }\n}\n","import { execFileSync } from 'node:child_process';\nimport { existsSync, rmSync } from 'node:fs';\n\nimport chalk from 'chalk';\n\nimport { type WorktreeConfig } from '../config.js';\nimport { logFile, worktreePath } from '../paths.js';\nimport { isPortInUse, killPort } from '../ports.js';\nimport { free, getBranchEntry, portFor } from '../registry.js';\n\nexport interface TeardownOptions {\n branch: string;\n cfg: WorktreeConfig;\n keepBranch?: boolean;\n repoRoot: string;\n}\n\nexport async function teardown({\n branch,\n cfg,\n keepBranch = false,\n repoRoot,\n}: TeardownOptions): Promise<void> {\n const entry = await getBranchEntry(cfg, branch);\n const wtPath = worktreePath(cfg, repoRoot, branch);\n\n if (entry) {\n for (const svc of cfg.services) {\n const port = portFor(entry, svc);\n if (isPortInUse(port)) {\n console.log(chalk.bold(`Stopping ${svc.name} dev server on port ${port}...`));\n await killPort(port);\n }\n }\n }\n\n if (existsSync(wtPath)) {\n console.log(chalk.bold(`Removing worktree: ${wtPath}`));\n execFileSync('git', ['-C', repoRoot, 'worktree', 'remove', wtPath, '--force'], {\n stdio: 'inherit',\n });\n }\n\n if (keepBranch) {\n console.log(`Preserving local branch: ${branch}`);\n } else {\n const branchExists = branchRefExists(repoRoot, branch);\n if (branchExists) {\n console.log(chalk.bold(`Deleting local branch: ${branch}`));\n try {\n execFileSync('git', ['-C', repoRoot, 'branch', '-d', branch], {\n stdio: 'ignore',\n });\n } catch {\n // Not fully merged — force-delete since worktree was already removed.\n execFileSync('git', ['-C', repoRoot, 'branch', '-D', branch], {\n stdio: 'inherit',\n });\n }\n }\n }\n\n await free(cfg, branch);\n\n for (const svc of cfg.services) {\n const path = logFile(svc, branch);\n if (existsSync(path)) rmSync(path);\n }\n\n console.log(chalk.green(`Teardown complete for '${branch}'.`));\n}\n\nfunction branchRefExists(repoRoot: string, branch: string): boolean {\n try {\n execFileSync(\n 'git',\n ['-C', repoRoot, 'show-ref', '--verify', '--quiet', `refs/heads/${branch}`],\n {\n stdio: 'ignore',\n },\n );\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["rmdirSync","chalk","existsSync","resolve","execSync","readFile","sleep","sleep","readFile","existsSync","existsSync","existsSync","existsSync","chalk","chalk","existsSync","spawnSync","existsSync","mkdirSync","dirname","resolve","chalk","existsSync","chalk","resolve","mkdirSync","dirname","spawnSync","chalk","chalk","execFileSync","existsSync","chalk","chalk","existsSync","execFileSync","branchExists","chalk","rmdirSync"]}
|
|
1
|
+
{"version":3,"sources":["../src/bin.ts","../src/commands/dev.ts","../src/config.ts","../src/paths.ts","../src/ports.ts","../src/registry.ts","../src/commands/list.ts","../src/commands/logs.ts","../src/commands/setup.ts","../src/env-merge.ts","../src/commands/stop.ts","../src/commands/teardown.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { rmdirSync } from 'node:fs';\n\nimport chalk from 'chalk';\nimport { Command } from 'commander';\n\nimport { dev } from './commands/dev.js';\nimport { list } from './commands/list.js';\nimport { logs } from './commands/logs.js';\nimport { setup } from './commands/setup.js';\nimport { stop } from './commands/stop.js';\nimport { teardown } from './commands/teardown.js';\nimport { loadConfig, lockDir, type WorktreeConfig } from './config.js';\nimport { detectBranch, findRepoRoot, mainWorktreeRoot } from './paths.js';\n\nasync function main(): Promise<void> {\n const program = new Command();\n program\n .name('precisa-worktree')\n .description('Git-worktree lifecycle CLI for Precisa Saúde repositories')\n .version('1.0.4');\n\n program\n .command('setup <branch>')\n .description('Create a worktree, install deps, allocate ports')\n .action(async (branch: string) => {\n const { cfg, mainRoot } = await resolveContext({ requireMain: true });\n await setup({ branch, cfg, repoRoot: mainRoot });\n });\n\n program\n .command('dev [branch]')\n .description('Start dev server(s) on the allocated ports')\n .option('-d, --detach', 'Run detached (nohup-style); write to log files', false)\n .option('-f, --force', 'Kill any process already on the port', false)\n .action(async (branchArg: string | undefined, opts: { detach: boolean; force: boolean }) => {\n const { branch, cfg, wtRoot } = await resolveContext({ allowDetect: true, branchArg });\n await dev({ branch, cfg, detach: opts.detach, force: opts.force, repoRoot: wtRoot });\n });\n\n program\n .command('stop [branch]')\n .description('Kill dev server(s) for a worktree')\n .action(async (branchArg?: string) => {\n const { branch, cfg } = await resolveContext({ allowDetect: true, branchArg });\n await stop({ branch, cfg });\n });\n\n program\n .command('teardown <branch>')\n .description('Stop dev servers, remove worktree, delete branch, free ports')\n .option('--keep-branch', 'Preserve the local branch', false)\n .action(async (branch: string, opts: { keepBranch: boolean }) => {\n const { cfg, mainRoot } = await resolveContext({ requireMain: true });\n await teardown({ branch, cfg, keepBranch: opts.keepBranch, repoRoot: mainRoot });\n });\n\n program\n .command('list')\n .description('List all worktrees with ports and status')\n .action(async () => {\n const { cfg, mainRoot } = await resolveContext({ requireMain: true });\n await list({ cfg, repoRoot: mainRoot });\n });\n\n program\n .command('logs [branch]')\n .description('Tail the log for a worktree dev server')\n .option('-s, --service <name>', 'Service name (defaults to the first configured)')\n .action(async (branchArg: string | undefined, opts: { service?: string }) => {\n const { branch, cfg } = await resolveContext({ allowDetect: true, branchArg });\n await logs({ branch, cfg, service: opts.service });\n });\n\n // Global EXIT handler — release the lock if we died mid-operation.\n // Safe because `rmdir` only succeeds on an empty directory we created.\n process.on('exit', () => {\n try {\n // Best-effort; we don't know the config if we failed before loading it.\n // This handler is installed after config load via resolveContext.\n } catch {\n // ignore\n }\n });\n\n try {\n await program.parseAsync(process.argv);\n } catch (err) {\n console.error(chalk.red((err as Error).message));\n process.exit(1);\n }\n}\n\ninterface ResolveOpts {\n /** Allow `detectBranch()` when inside a linked worktree. */\n allowDetect?: boolean;\n branchArg?: string | undefined;\n /** Require the repo root to be the main worktree (setup/teardown/list). */\n requireMain?: boolean;\n}\n\nasync function resolveContext(opts: ResolveOpts): Promise<{\n cfg: WorktreeConfig;\n wtRoot: string;\n mainRoot: string;\n branch: string;\n}> {\n const wtRoot = findRepoRoot();\n const mainRoot = mainWorktreeRoot();\n const cfg = await loadConfig(mainRoot);\n\n // Install an exit handler now that we know the lock path.\n const lockPath = lockDir(cfg);\n process.on('exit', () => {\n try {\n rmdirSync(lockPath);\n } catch {\n // already released or never held\n }\n });\n\n let branch = opts.branchArg ?? '';\n if (!branch && opts.allowDetect) {\n branch = detectBranch(cfg) ?? '';\n }\n\n if (!branch && !opts.requireMain) {\n console.error(\n chalk.red(\n \"Couldn't detect branch. Run from inside a linked worktree or pass the branch name.\",\n ),\n );\n process.exit(1);\n }\n\n return { branch, cfg, mainRoot, wtRoot };\n}\n\nvoid main();\n","import { spawn } from 'node:child_process';\nimport { createWriteStream, existsSync } from 'node:fs';\n\nimport chalk from 'chalk';\n\nimport { devArgs, type ServiceConfig, type WorktreeConfig } from '../config.js';\nimport { logFile, worktreePath } from '../paths.js';\nimport { isPortInUse, killPort } from '../ports.js';\nimport { getBranchEntry, portFor } from '../registry.js';\n\nexport interface DevOptions {\n branch: string;\n cfg: WorktreeConfig;\n /** Run detached from the terminal; write stdout/stderr to log files. */\n detach?: boolean;\n /** If a port is already in use, kill the process instead of aborting. */\n force?: boolean;\n repoRoot: string;\n}\n\nexport async function dev({\n branch,\n cfg,\n detach = false,\n force = false,\n repoRoot,\n}: DevOptions): Promise<void> {\n const entry = await getBranchEntry(cfg, branch);\n if (!entry) {\n console.error(chalk.red(`No ports allocated for '${branch}'. Run setup first.`));\n process.exit(1);\n }\n\n const wtPath = worktreePath(cfg, repoRoot, branch);\n if (!existsSync(wtPath)) {\n console.error(chalk.red(`Worktree not found at ${wtPath}`));\n process.exit(1);\n }\n\n for (const svc of cfg.services) {\n const port = portFor(entry, svc);\n if (isPortInUse(port)) {\n if (force) {\n console.log(chalk.yellow(`Port ${port} in use — killing (--force)`));\n await killPort(port);\n } else {\n console.error(\n chalk.red(`Port ${port} (${svc.name}) already in use. Pass --force to kill.`),\n );\n process.exit(1);\n }\n }\n }\n\n console.log(chalk.bold(`Starting dev servers for '${branch}'...`));\n for (const svc of cfg.services) {\n const port = portFor(entry, svc);\n console.log(` ${svc.name}: http://localhost:${port} (logs: ${logFile(svc, branch)})`);\n }\n console.log('');\n\n const procs = cfg.services.map((svc) => spawnService(svc, entry, wtPath, branch, detach));\n\n if (detach) {\n for (const p of procs) p.unref();\n console.log(chalk.green('Started detached. Use `precisa-worktree stop` to kill.'));\n return;\n }\n\n // Foreground: forward signals, wait for first exit.\n const cleanup = (signal: NodeJS.Signals): void => {\n for (const p of procs) {\n try {\n if (!p.killed) p.kill(signal);\n } catch {\n // already dead\n }\n }\n };\n process.on('SIGINT', () => cleanup('SIGINT'));\n process.on('SIGTERM', () => cleanup('SIGTERM'));\n\n const exitCode = await new Promise<number>((resolvePromise) => {\n let resolved = false;\n for (const p of procs) {\n p.on('exit', (code) => {\n if (resolved) return;\n resolved = true;\n cleanup('SIGTERM');\n resolvePromise(code ?? 1);\n });\n }\n });\n process.exit(exitCode);\n}\n\nfunction spawnService(\n svc: ServiceConfig,\n entry: { ports: Record<string, number> },\n cwd: string,\n branch: string,\n detach: boolean,\n): ReturnType<typeof spawn> {\n const port = portFor(entry, svc);\n const interpolate = (s: string): string => {\n let out = s.replace('{port}', String(port));\n // Allow cross-service refs like `{api_port}` so one service's env\n // can embed another service's allocated port.\n for (const [otherName, otherPort] of Object.entries(entry.ports)) {\n out = out.replaceAll(`{${otherName}_port}`, String(otherPort));\n }\n return out;\n };\n\n const rawArgs = devArgs(svc);\n // No `--` separator: with pnpm 9 + `\"dev\": \"vite\"`, inserting `--`\n // causes vite to receive it as a literal argv entry (`vite -- --port NNN`)\n // and silently ignore the port flag, falling back to its default.\n const args = ['--filter', svc.pnpmFilter, 'dev', ...rawArgs.map(interpolate)];\n\n const env = { ...process.env };\n for (const [k, v] of Object.entries(svc.env ?? {})) {\n env[k] = interpolate(v);\n }\n\n if (detach) {\n const logPath = logFile(svc, branch);\n const out = createWriteStream(logPath, { flags: 'a' });\n const proc = spawn('pnpm', args, {\n cwd,\n detached: true,\n env,\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n proc.stdout?.pipe(out);\n proc.stderr?.pipe(out);\n return proc;\n }\n\n return spawn('pnpm', args, { cwd, env, stdio: 'inherit' });\n}\n","import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\n\nexport interface ServiceConfig {\n /**\n * Extra args passed to the dev script. `{port}` is replaced with the\n * allocated port. Defaults to `['--port', '{port}']` (works with vite).\n */\n devArgs?: string[];\n /**\n * Optional: additional env vars to set for the dev process. The literal\n * `{port}` is substituted. Example: `{ VITE_API_URL: 'http://localhost:{port}/api' }`.\n */\n env?: Record<string, string>;\n /**\n * First port for feature worktrees. Slot 1 uses this port; slot 2 uses\n * `featureBase + increment`, slot 3 uses `featureBase + 2*increment`, etc.\n */\n featureBase: number;\n /** Increment between slots. Defaults to 10. */\n increment?: number;\n /**\n * Prefix for the log file path: `/tmp/<logPrefix>-<branch>.log`. Required\n * so that sibling services in a repo don't overwrite each other's logs.\n */\n logPrefix: string;\n /** Port used by the default (main) worktree. */\n mainPort: number;\n /** Service name used in CLI output, column headers, and log file naming. */\n name: string;\n /** pnpm filter pattern for `worktree dev`. Example: `@medbench-brasil/site`. */\n pnpmFilter: string;\n}\n\nexport interface WorktreeConfig {\n /**\n * Optional extra build command run during `setup`, after `pnpm install`.\n * Example: `pnpm turbo run build --filter=./packages/*`.\n */\n buildCommand?: string;\n /**\n * Prefix used for worktree directory names: `<prefix>-<branch>`.\n * Example: `medbench-brasil` → worktrees land at\n * `../medbench-brasil-feat-foo`.\n */\n directoryPrefix: string;\n /**\n * Files in the main worktree whose KEY=VALUE lines should be appended\n * to the new worktree's corresponding file at the end of `setup`.\n * Keys already present in the worktree's file (typically from\n * `writeFiles`, e.g. `PORT`, `VITE_API_URL`) are NOT overwritten —\n * only missing keys are appended.\n *\n * Useful when the repo's `.env.local` files in main hold long-lived\n * dev credentials that every worktree needs (Cognito, Stripe, S3\n * buckets, DynamoDB tables, etc.) and that aren't worktree-scoped.\n * Without this, each worktree starts with only the port keys from\n * `writeFiles` and any service that reads those creds crashes on\n * first request.\n *\n * Paths are relative to the repo root and resolve against the main\n * worktree (where the CLI is invoked). If a file is missing in main,\n * the inherit step is skipped for that entry (no error). Comments and\n * blank lines in the source file are not copied.\n *\n * Example (platform):\n * inheritEnvFromMain: ['apps/api/.env.local', 'apps/web/.env.local']\n */\n inheritEnvFromMain?: string[];\n /**\n * Whether to run `pnpm install` during setup. Defaults to true. Set to\n * false for repos where install should be manual or where the script\n * is invoked inside a container.\n */\n install?: boolean;\n /** Absolute path to the lock directory. Defaults to `<portRegistry>.lock.d`. */\n portLockDir?: string;\n /** Absolute path to the port registry JSON file. */\n portRegistry: string;\n /** Services managed by this repo. At least one required. */\n services: ServiceConfig[];\n /**\n * Extra per-service file writes performed during `setup`, after\n * install + build. Intended for repos that need `.env.local`-style\n * files seeded with the allocated ports so that running `dev` without\n * explicit env vars picks up the right values.\n *\n * Each entry is `{ path, contents }` where `contents` supports the\n * same `{port}` and `{<service>_port}` substitutions available in\n * `service.env` / `service.devArgs`.\n *\n * Example (platform):\n * writeFiles: [\n * { path: 'apps/web/.env.local',\n * contents: 'VITE_DEV_PORT={web_port}\\nVITE_API_URL=http://localhost:{api_port}/api\\n' },\n * { path: 'apps/api/.env.local',\n * contents: 'PORT={api_port}\\n' },\n * ]\n */\n writeFiles?: Array<{ path: string; contents: string }>;\n}\n\ninterface PackageJson {\n name?: string;\n worktree?: WorktreeConfig;\n}\n\nexport async function loadConfig(repoRoot: string): Promise<WorktreeConfig> {\n const pkgPath = resolve(repoRoot, 'package.json');\n let pkg: PackageJson;\n try {\n pkg = JSON.parse(await readFile(pkgPath, 'utf-8')) as PackageJson;\n } catch (err) {\n throw new Error(`Failed to read ${pkgPath}: ${(err as Error).message}. Is this the repo root?`);\n }\n\n const cfg = pkg.worktree;\n if (!cfg) {\n throw new Error(\n `No \"worktree\" field in ${pkgPath}. Add a worktree config — see ` +\n `https://github.com/Precisa-Saude/tooling/tree/main/packages/worktree-cli#configuration`,\n );\n }\n\n if (!cfg.directoryPrefix) {\n throw new Error('worktree.directoryPrefix is required');\n }\n if (!cfg.portRegistry) {\n throw new Error('worktree.portRegistry is required');\n }\n if (!cfg.services?.length) {\n throw new Error('worktree.services must contain at least one entry');\n }\n for (const svc of cfg.services) {\n if (!svc.name) throw new Error('service.name is required');\n if (!svc.pnpmFilter) throw new Error(`service.${svc.name}.pnpmFilter is required`);\n if (!svc.logPrefix) throw new Error(`service.${svc.name}.logPrefix is required`);\n if (typeof svc.mainPort !== 'number') {\n throw new Error(`service.${svc.name}.mainPort must be a number`);\n }\n if (typeof svc.featureBase !== 'number') {\n throw new Error(`service.${svc.name}.featureBase must be a number`);\n }\n }\n\n return cfg;\n}\n\nexport function lockDir(cfg: WorktreeConfig): string {\n return cfg.portLockDir ?? `${cfg.portRegistry}.lock.d`;\n}\n\nexport function increment(svc: ServiceConfig): number {\n return svc.increment ?? 10;\n}\n\nexport function devArgs(svc: ServiceConfig): string[] {\n return svc.devArgs ?? ['--port', '{port}'];\n}\n","import { execSync } from 'node:child_process';\nimport { basename, dirname, resolve } from 'node:path';\n\nimport type { ServiceConfig, WorktreeConfig } from './config.js';\n\n/** Convert a branch name to a safe directory-suffix: `feat/foo` → `feat-foo`. */\nexport function branchToSuffix(branch: string): string {\n return branch.replace(/\\//g, '-');\n}\n\nexport function worktreeDirectoryName(cfg: WorktreeConfig, branch: string): string {\n return `${cfg.directoryPrefix}-${branchToSuffix(branch)}`;\n}\n\nexport function worktreePath(cfg: WorktreeConfig, repoRoot: string, branch: string): string {\n const parentDir = dirname(repoRoot);\n return resolve(parentDir, worktreeDirectoryName(cfg, branch));\n}\n\nexport function logFile(svc: ServiceConfig, branch: string): string {\n return `/tmp/${svc.logPrefix}-${branchToSuffix(branch)}.log`;\n}\n\n/**\n * Detect the current branch when invoked from inside a linked worktree.\n * Returns null from the main worktree — refusing auto-detect there avoids\n * accidentally acting on `main` when the user forgot to pass a branch.\n */\nexport function detectBranch(cfg: WorktreeConfig): string | null {\n const cwdBase = basename(process.cwd());\n if (!cwdBase.startsWith(`${cfg.directoryPrefix}-`)) return null;\n try {\n const out = execSync('git branch --show-current', { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .trim();\n return out || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve the repo root by walking up from cwd looking for a `package.json`\n * with a `worktree` field. If invoked from inside a linked worktree, falls\n * back to the main worktree (identified by the directory NOT having the\n * `<prefix>-<branch>` suffix).\n */\nexport function findRepoRoot(): string {\n // `git rev-parse --show-toplevel` gives the worktree root (linked or main).\n try {\n return execSync('git rev-parse --show-toplevel', { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .trim();\n } catch {\n return process.cwd();\n }\n}\n\n/**\n * From a linked worktree, walk back to the main worktree using\n * `git worktree list --porcelain`. The first entry is always the main.\n */\nexport function mainWorktreeRoot(): string {\n try {\n const out = execSync('git worktree list --porcelain', {\n stdio: ['ignore', 'pipe', 'ignore'],\n }).toString();\n const firstLine = out.split('\\n')[0] ?? '';\n const match = firstLine.match(/^worktree (.+)$/);\n if (match) return match[1]!;\n } catch {\n // fall through\n }\n return findRepoRoot();\n}\n","import { execSync } from 'node:child_process';\nimport { setTimeout as sleep } from 'node:timers/promises';\n\n/**\n * Return PIDs listening on `port` using the first available tool.\n * Prefers lsof (macOS default), falls back to ss then fuser (Linux).\n * Returns empty array if none can inspect ports or nothing is listening.\n */\nfunction listenersOnPort(port: number): number[] {\n for (const [cmd, parse] of probers) {\n if (!hasCommand(cmd)) continue;\n try {\n const out = execSync(probeCommand(cmd, port), { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .trim();\n return parse(out);\n } catch {\n // non-zero exit — no listeners found\n return [];\n }\n }\n console.error(`Warning: lsof/ss/fuser not available — can't inspect port ${port}`);\n return [];\n}\n\ntype Prober = [cmd: string, parse: (output: string) => number[]];\n\nconst probers: Prober[] = [\n [\n 'lsof',\n (out) =>\n out\n .split('\\n')\n .map((s) => parseInt(s.trim(), 10))\n .filter((n) => !isNaN(n)),\n ],\n [\n 'ss',\n (out) => {\n const pids: number[] = [];\n for (const line of out.split('\\n')) {\n const matches = line.match(/pid=(\\d+)/g) ?? [];\n for (const m of matches) pids.push(parseInt(m.slice(4), 10));\n }\n return pids;\n },\n ],\n [\n 'fuser',\n (out) =>\n out\n .split(/\\s+/)\n .map((s) => parseInt(s, 10))\n .filter((n) => !isNaN(n)),\n ],\n];\n\nfunction probeCommand(cmd: string, port: number): string {\n switch (cmd) {\n case 'lsof':\n return `lsof -iTCP:${port} -sTCP:LISTEN -t`;\n case 'ss':\n return `ss -tlnp | awk '$4 ~ /:${port}$/ {print}'`;\n case 'fuser':\n return `fuser -n tcp ${port} 2>/dev/null`;\n default:\n throw new Error(`Unsupported prober: ${cmd}`);\n }\n}\n\nfunction hasCommand(cmd: string): boolean {\n try {\n execSync(`command -v ${cmd}`, { stdio: 'ignore' });\n return true;\n } catch {\n return false;\n }\n}\n\nexport function isPortInUse(port: number): boolean {\n return listenersOnPort(port).length > 0;\n}\n\n/** Kill processes listening on `port`. SIGTERM first, then SIGKILL if any survive. */\nexport async function killPort(port: number): Promise<void> {\n const pids = listenersOnPort(port);\n if (pids.length === 0) return;\n\n for (const pid of pids) {\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n // process may have exited between listing and kill — ignore\n }\n }\n\n await sleep(1000);\n\n const stillAlive = listenersOnPort(port);\n for (const pid of stillAlive) {\n try {\n process.kill(pid, 'SIGKILL');\n } catch {\n // ignore\n }\n }\n}\n","import { existsSync, mkdirSync, rmdirSync } from 'node:fs';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { setTimeout as sleep } from 'node:timers/promises';\n\nimport { increment, lockDir, type ServiceConfig, type WorktreeConfig } from './config.js';\n\nexport type Registry = Record<string, { slot: number; ports: Record<string, number> }>;\n\nasync function ensureRegistry(cfg: WorktreeConfig): Promise<void> {\n if (!existsSync(cfg.portRegistry)) {\n await writeFile(cfg.portRegistry, '{}');\n }\n}\n\n/**\n * Acquire an exclusive lock via `mkdir` — POSIX-atomic, portable across\n * macOS/Linux, no dependency on `flock` (which isn't on macOS by default).\n * Used to serialize registry read-modify-write across concurrent sessions.\n */\nasync function acquireLock(cfg: WorktreeConfig): Promise<void> {\n const dir = lockDir(cfg);\n const maxTries = 100;\n for (let i = 0; i < maxTries; i++) {\n try {\n mkdirSync(dir);\n return;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;\n }\n await sleep(100);\n }\n throw new Error(\n `Timeout (~10s) waiting for lock at ${dir}. If no other session is ` +\n `running, remove it manually: rmdir ${dir}`,\n );\n}\n\nfunction releaseLock(cfg: WorktreeConfig): void {\n try {\n rmdirSync(lockDir(cfg));\n } catch {\n // ignore — already released or never held\n }\n}\n\n/**\n * Run `fn` while holding the registry lock. Always releases, even on throw.\n * The release is also safe against signal interruption because the lock\n * directory will be removed at process exit via the handler in bin.ts.\n */\nexport async function withLock<T>(cfg: WorktreeConfig, fn: () => Promise<T> | T): Promise<T> {\n await ensureRegistry(cfg);\n await acquireLock(cfg);\n try {\n return await fn();\n } finally {\n releaseLock(cfg);\n }\n}\n\nexport async function readRegistry(cfg: WorktreeConfig): Promise<Registry> {\n await ensureRegistry(cfg);\n const text = await readFile(cfg.portRegistry, 'utf-8');\n try {\n return JSON.parse(text) as Registry;\n } catch {\n return {};\n }\n}\n\nasync function writeRegistry(cfg: WorktreeConfig, reg: Registry): Promise<void> {\n await writeFile(cfg.portRegistry, `${JSON.stringify(reg, null, 2)}\\n`);\n}\n\n/**\n * Allocate a slot+ports for `branch`. Idempotent: returns the existing\n * entry if the branch already has one. Reuses freed slots so slot numbers\n * don't grow unbounded over many create/teardown cycles.\n */\nexport async function allocate(\n cfg: WorktreeConfig,\n branch: string,\n): Promise<{ slot: number; ports: Record<string, number> }> {\n return withLock(cfg, async () => {\n const reg = await readRegistry(cfg);\n if (reg[branch]) return reg[branch];\n\n const usedSlots = new Set(Object.values(reg).map((e) => e.slot));\n let slot = 1;\n while (usedSlots.has(slot)) slot++;\n\n const ports: Record<string, number> = {};\n for (const svc of cfg.services) {\n ports[svc.name] = svc.featureBase + (slot - 1) * increment(svc);\n }\n\n reg[branch] = { ports, slot };\n await writeRegistry(cfg, reg);\n return reg[branch];\n });\n}\n\nexport async function free(cfg: WorktreeConfig, branch: string): Promise<void> {\n return withLock(cfg, async () => {\n const reg = await readRegistry(cfg);\n if (reg[branch]) {\n delete reg[branch];\n await writeRegistry(cfg, reg);\n }\n });\n}\n\nexport async function getBranchEntry(\n cfg: WorktreeConfig,\n branch: string,\n): Promise<{ slot: number; ports: Record<string, number> } | null> {\n const reg = await readRegistry(cfg);\n return reg[branch] ?? null;\n}\n\nexport function portFor(entry: { ports: Record<string, number> }, svc: ServiceConfig): number {\n const port = entry.ports[svc.name];\n if (typeof port !== 'number') {\n throw new Error(\n `No port allocated for service \"${svc.name}\" on this branch. ` +\n `Registry entry may be from an older config version — run teardown + setup again.`,\n );\n }\n return port;\n}\n","import { existsSync } from 'node:fs';\n\nimport { type ServiceConfig, type WorktreeConfig } from '../config.js';\nimport { worktreePath } from '../paths.js';\nimport { isPortInUse } from '../ports.js';\nimport { readRegistry } from '../registry.js';\n\nexport interface ListOptions {\n cfg: WorktreeConfig;\n repoRoot: string;\n}\n\nexport async function list({ cfg, repoRoot }: ListOptions): Promise<void> {\n const reg = await readRegistry(cfg);\n\n console.log(`Worktrees for ${cfg.directoryPrefix}:`);\n console.log('');\n\n const headers = ['BRANCH', ...cfg.services.map((s) => s.name.toUpperCase()), 'STATUS'];\n const widths = [40, ...cfg.services.map(() => 10), 10];\n printRow(headers, widths);\n printRow(\n headers.map((h) => '-'.repeat(Math.min(h.length, 6))),\n widths,\n );\n\n // Main worktree row\n printRow(\n [\n 'main (default)',\n ...cfg.services.map((s) => String(s.mainPort)),\n statusOf(cfg.services, (s) => s.mainPort, /* dirExists */ true),\n ],\n widths,\n );\n\n // Feature worktree rows\n for (const [branch, entry] of Object.entries(reg)) {\n const wtPath = worktreePath(cfg, repoRoot, branch);\n const dirExists = existsSync(wtPath);\n const ports = cfg.services.map((s) => entry.ports[s.name]!);\n printRow(\n [\n branch,\n ...ports.map((p) => String(p)),\n dirExists ? statusOf(cfg.services, (s) => entry.ports[s.name]!, true) : 'missing',\n ],\n widths,\n );\n }\n\n console.log('');\n console.log(`Registry: ${cfg.portRegistry}`);\n}\n\nfunction statusOf(\n services: ServiceConfig[],\n portOf: (s: ServiceConfig) => number,\n dirExists: boolean,\n): string {\n if (!dirExists) return 'missing';\n const anyRunning = services.some((s) => isPortInUse(portOf(s)));\n return anyRunning ? 'running' : 'stopped';\n}\n\nfunction printRow(cells: string[], widths: number[]): void {\n const line = cells.map((c, i) => c.padEnd(widths[i] ?? 10)).join(' ');\n console.log(` ${line}`);\n}\n","import { spawnSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\n\nimport chalk from 'chalk';\n\nimport { type WorktreeConfig } from '../config.js';\nimport { logFile } from '../paths.js';\n\nexport interface LogsOptions {\n branch: string;\n cfg: WorktreeConfig;\n service?: string;\n}\n\nexport async function logs({ branch, cfg, service }: LogsOptions): Promise<void> {\n const svc = service ? cfg.services.find((s) => s.name === service) : cfg.services[0];\n\n if (!svc) {\n const names = cfg.services.map((s) => s.name).join(', ');\n console.error(chalk.red(`Unknown service \"${service}\". Valid services: ${names}`));\n process.exit(1);\n }\n\n const path = logFile(svc, branch);\n if (!existsSync(path)) {\n console.error(chalk.red(`Log file not found: ${path}`));\n console.error(`Start the dev server first: precisa-worktree dev ${branch}`);\n process.exit(1);\n }\n\n // Delegate to tail -f so Ctrl-C works and rotation is handled.\n const result = spawnSync('tail', ['-f', path], { stdio: 'inherit' });\n process.exit(result.status ?? 0);\n}\n","import { execFileSync, spawnSync } from 'node:child_process';\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\n\nimport chalk from 'chalk';\n\nimport { type WorktreeConfig } from '../config.js';\nimport { buildInheritedAppend } from '../env-merge.js';\nimport { logFile, worktreePath } from '../paths.js';\nimport { allocate } from '../registry.js';\n\nexport interface SetupOptions {\n branch: string;\n cfg: WorktreeConfig;\n repoRoot: string;\n}\n\nexport async function setup({ branch, cfg, repoRoot }: SetupOptions): Promise<void> {\n const wtPath = worktreePath(cfg, repoRoot, branch);\n\n if (existsSync(wtPath)) {\n console.error(chalk.red(`Worktree already exists: ${wtPath}`));\n console.error(`To start dev servers: precisa-worktree dev ${branch}`);\n process.exit(1);\n }\n\n console.log(chalk.bold(`==> Creating worktree for '${branch}' at ${wtPath}...`));\n\n // Fetch origin/main from the main worktree; a fresh worktree branches from it.\n execFileSync('git', ['-C', repoRoot, 'fetch', 'origin', 'main'], { stdio: 'inherit' });\n\n // If the branch already exists (local or remote-tracking), check it out\n // into the new worktree instead of creating a fresh branch — otherwise\n // `git worktree add -b` errors out on the duplicate.\n const reuseExisting = branchExists(repoRoot, branch);\n const addArgs = reuseExisting\n ? ['-C', repoRoot, 'worktree', 'add', wtPath, branch]\n : ['-C', repoRoot, 'worktree', 'add', '-b', branch, wtPath, 'origin/main'];\n execFileSync('git', addArgs, { stdio: 'inherit' });\n\n const entry = await allocate(cfg, branch);\n\n if (cfg.install !== false) {\n console.log(chalk.bold('==> Installing dependencies (pnpm install)...'));\n execFileSync('pnpm', ['install'], { cwd: wtPath, stdio: 'inherit' });\n }\n\n if (cfg.buildCommand) {\n console.log(chalk.bold(`==> Running build: ${cfg.buildCommand}`));\n execFileSync('sh', ['-c', cfg.buildCommand], { cwd: wtPath, stdio: 'inherit' });\n }\n\n // Write per-repo files with the allocated ports substituted in. Used\n // by platform to seed apps/*/.env.local so the running dev server\n // picks up the right ports without extra env-var plumbing.\n if (cfg.writeFiles?.length) {\n console.log(chalk.bold('==> Writing worktree-scoped files'));\n for (const { contents, path } of cfg.writeFiles) {\n let interpolated = contents;\n for (const [svcName, port] of Object.entries(entry.ports)) {\n interpolated = interpolated\n .replaceAll(`{${svcName}_port}`, String(port))\n .replaceAll('{port}', String(port));\n }\n const fullPath = resolve(wtPath, path);\n mkdirSync(dirname(fullPath), { recursive: true });\n writeFileSync(fullPath, interpolated);\n console.log(` wrote ${path}`);\n }\n }\n\n // Inherit non-port env vars from the main worktree's files. Runs after\n // writeFiles so port keys written by the CLI stay authoritative — only\n // missing keys are appended.\n if (cfg.inheritEnvFromMain?.length) {\n console.log(chalk.bold('==> Inheriting non-port env vars from main worktree'));\n for (const relPath of cfg.inheritEnvFromMain) {\n const mainFile = resolve(repoRoot, relPath);\n const wtFile = resolve(wtPath, relPath);\n\n if (!existsSync(mainFile)) {\n console.log(chalk.dim(` skip ${relPath} (not present in main worktree)`));\n continue;\n }\n\n const mainContents = readFileSync(mainFile, 'utf-8');\n const wtContents = existsSync(wtFile) ? readFileSync(wtFile, 'utf-8') : '';\n const appended = buildInheritedAppend(mainContents, wtContents);\n\n if (appended.length === 0) {\n console.log(chalk.dim(` skip ${relPath} (no missing keys to inherit)`));\n continue;\n }\n\n mkdirSync(dirname(wtFile), { recursive: true });\n if (existsSync(wtFile)) {\n appendFileSync(wtFile, appended);\n } else {\n writeFileSync(wtFile, appended.replace(/^\\n/, ''));\n }\n const inheritedCount = appended.split('\\n').filter((l) => /^[A-Za-z_]/.test(l)).length;\n console.log(` inherited ${inheritedCount} key(s) into ${relPath}`);\n }\n }\n\n console.log('');\n console.log(chalk.green(`Worktree ready: ${wtPath}`));\n console.log(` Branch: ${branch}`);\n for (const svc of cfg.services) {\n const port = entry.ports[svc.name]!;\n console.log(\n ` ${svc.name.padEnd(8)}: http://localhost:${port} (logs: ${logFile(svc, branch)})`,\n );\n }\n console.log('');\n console.log('To start the dev server(s):');\n console.log(` cd ${wtPath} && precisa-worktree dev ${branch}`);\n}\n\nfunction branchExists(repoRoot: string, branch: string): boolean {\n const local = spawnSync(\n 'git',\n ['-C', repoRoot, 'show-ref', '--verify', '--quiet', `refs/heads/${branch}`],\n { stdio: 'ignore' },\n );\n if (local.status === 0) return true;\n const remote = spawnSync(\n 'git',\n ['-C', repoRoot, 'show-ref', '--verify', '--quiet', `refs/remotes/origin/${branch}`],\n { stdio: 'ignore' },\n );\n return remote.status === 0;\n}\n","/**\n * Helpers for inheriting non-port env vars from the main worktree's\n * `.env.local` files into a new worktree. See `WorktreeConfig.inheritEnvFromMain`.\n *\n * Intentionally pure: I/O happens in the caller (setup command). Keeping\n * the parsing/merge logic free of fs calls makes it trivial to unit-test\n * and reusable from `dev` if we ever want to re-sync mid-session.\n */\n\n/**\n * Extract just the keys defined in a KEY=VALUE-style env file. Lines that\n * start with `#` (after optional whitespace) and blank lines are skipped.\n * Quoted values and inline `#` comments are not stripped — we only care\n * about the key position. Repeated keys collapse to a single entry (set).\n */\nexport function parseEnvKeys(contents: string): Set<string> {\n const keys = new Set<string>();\n for (const rawLine of contents.split('\\n')) {\n const line = rawLine.trimStart();\n if (line.length === 0 || line.startsWith('#')) continue;\n const eq = line.indexOf('=');\n if (eq <= 0) continue;\n const key = line.slice(0, eq).trim();\n // KEY format must look like an env var name. Skip noise (typos,\n // markdown table rows, anything with whitespace or non-identifier\n // chars on the left of `=`).\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;\n keys.add(key);\n }\n return keys;\n}\n\n/**\n * Given the contents of the main worktree's env file and the worktree's\n * own env file (possibly empty), return the lines to append. Returns\n * `''` (no trailing newline, no header) when nothing needs to be appended\n * so the caller can decide whether to touch the file at all.\n *\n * Key-by-key inheritance, not file replacement — the worktree-CLI's\n * port-specific lines (e.g. `PORT=3010`, `VITE_API_URL=http://localhost:3010/api`)\n * stay authoritative.\n */\nexport function buildInheritedAppend(\n mainContents: string,\n worktreeContents: string,\n options: { header?: string } = {},\n): string {\n const existingKeys = parseEnvKeys(worktreeContents);\n\n const linesToAppend: string[] = [];\n for (const rawLine of mainContents.split('\\n')) {\n const line = rawLine.trimStart();\n if (line.length === 0 || line.startsWith('#')) continue;\n const eq = line.indexOf('=');\n if (eq <= 0) continue;\n const key = line.slice(0, eq).trim();\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;\n if (existingKeys.has(key)) continue;\n linesToAppend.push(rawLine);\n existingKeys.add(key); // dedupe within the source file too\n }\n\n if (linesToAppend.length === 0) return '';\n\n const header = options.header ?? '# Inherited from main worktree by precisa-worktree setup';\n // Ensure separation from whatever the worktree already had — a leading\n // blank line, then the header, then the inherited lines, then trailing\n // newline.\n const prefix = worktreeContents.length > 0 && !worktreeContents.endsWith('\\n') ? '\\n' : '';\n return `${prefix}\\n${header}\\n${linesToAppend.join('\\n')}\\n`;\n}\n","import chalk from 'chalk';\n\nimport { type WorktreeConfig } from '../config.js';\nimport { isPortInUse, killPort } from '../ports.js';\nimport { getBranchEntry, portFor } from '../registry.js';\n\nexport interface StopOptions {\n branch: string;\n cfg: WorktreeConfig;\n}\n\nexport async function stop({ branch, cfg }: StopOptions): Promise<void> {\n const entry = await getBranchEntry(cfg, branch);\n if (!entry) {\n console.error(`No ports registered for '${branch}'.`);\n process.exit(1);\n }\n\n let anyRunning = false;\n for (const svc of cfg.services) {\n const port = portFor(entry, svc);\n if (isPortInUse(port)) {\n console.log(chalk.bold(`Killing ${svc.name} dev server on port ${port}...`));\n await killPort(port);\n anyRunning = true;\n }\n }\n\n if (!anyRunning) {\n console.log(`No dev servers running for '${branch}'.`);\n }\n}\n","import { execFileSync } from 'node:child_process';\nimport { existsSync, rmSync } from 'node:fs';\n\nimport chalk from 'chalk';\n\nimport { type WorktreeConfig } from '../config.js';\nimport { logFile, worktreePath } from '../paths.js';\nimport { isPortInUse, killPort } from '../ports.js';\nimport { free, getBranchEntry, portFor } from '../registry.js';\n\nexport interface TeardownOptions {\n branch: string;\n cfg: WorktreeConfig;\n keepBranch?: boolean;\n repoRoot: string;\n}\n\nexport async function teardown({\n branch,\n cfg,\n keepBranch = false,\n repoRoot,\n}: TeardownOptions): Promise<void> {\n const entry = await getBranchEntry(cfg, branch);\n const wtPath = worktreePath(cfg, repoRoot, branch);\n\n if (entry) {\n for (const svc of cfg.services) {\n const port = portFor(entry, svc);\n if (isPortInUse(port)) {\n console.log(chalk.bold(`Stopping ${svc.name} dev server on port ${port}...`));\n await killPort(port);\n }\n }\n }\n\n if (existsSync(wtPath)) {\n console.log(chalk.bold(`Removing worktree: ${wtPath}`));\n execFileSync('git', ['-C', repoRoot, 'worktree', 'remove', wtPath, '--force'], {\n stdio: 'inherit',\n });\n }\n\n if (keepBranch) {\n console.log(`Preserving local branch: ${branch}`);\n } else {\n const branchExists = branchRefExists(repoRoot, branch);\n if (branchExists) {\n console.log(chalk.bold(`Deleting local branch: ${branch}`));\n try {\n execFileSync('git', ['-C', repoRoot, 'branch', '-d', branch], {\n stdio: 'ignore',\n });\n } catch {\n // Not fully merged — force-delete since worktree was already removed.\n execFileSync('git', ['-C', repoRoot, 'branch', '-D', branch], {\n stdio: 'inherit',\n });\n }\n }\n }\n\n await free(cfg, branch);\n\n for (const svc of cfg.services) {\n const path = logFile(svc, branch);\n if (existsSync(path)) rmSync(path);\n }\n\n console.log(chalk.green(`Teardown complete for '${branch}'.`));\n}\n\nfunction branchRefExists(repoRoot: string, branch: string): boolean {\n try {\n execFileSync(\n 'git',\n ['-C', repoRoot, 'show-ref', '--verify', '--quiet', `refs/heads/${branch}`],\n {\n stdio: 'ignore',\n },\n );\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"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","names":["rmdirSync","chalk","existsSync","resolve","execSync","readFile","sleep","sleep","readFile","existsSync","existsSync","existsSync","existsSync","chalk","chalk","existsSync","spawnSync","existsSync","mkdirSync","dirname","resolve","chalk","existsSync","chalk","resolve","mkdirSync","dirname","spawnSync","chalk","chalk","execFileSync","existsSync","chalk","chalk","existsSync","execFileSync","branchExists","chalk","rmdirSync"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -40,6 +40,29 @@ interface WorktreeConfig {
|
|
|
40
40
|
* `../medbench-brasil-feat-foo`.
|
|
41
41
|
*/
|
|
42
42
|
directoryPrefix: string;
|
|
43
|
+
/**
|
|
44
|
+
* Files in the main worktree whose KEY=VALUE lines should be appended
|
|
45
|
+
* to the new worktree's corresponding file at the end of `setup`.
|
|
46
|
+
* Keys already present in the worktree's file (typically from
|
|
47
|
+
* `writeFiles`, e.g. `PORT`, `VITE_API_URL`) are NOT overwritten —
|
|
48
|
+
* only missing keys are appended.
|
|
49
|
+
*
|
|
50
|
+
* Useful when the repo's `.env.local` files in main hold long-lived
|
|
51
|
+
* dev credentials that every worktree needs (Cognito, Stripe, S3
|
|
52
|
+
* buckets, DynamoDB tables, etc.) and that aren't worktree-scoped.
|
|
53
|
+
* Without this, each worktree starts with only the port keys from
|
|
54
|
+
* `writeFiles` and any service that reads those creds crashes on
|
|
55
|
+
* first request.
|
|
56
|
+
*
|
|
57
|
+
* Paths are relative to the repo root and resolve against the main
|
|
58
|
+
* worktree (where the CLI is invoked). If a file is missing in main,
|
|
59
|
+
* the inherit step is skipped for that entry (no error). Comments and
|
|
60
|
+
* blank lines in the source file are not copied.
|
|
61
|
+
*
|
|
62
|
+
* Example (platform):
|
|
63
|
+
* inheritEnvFromMain: ['apps/api/.env.local', 'apps/web/.env.local']
|
|
64
|
+
*/
|
|
65
|
+
inheritEnvFromMain?: string[];
|
|
43
66
|
/**
|
|
44
67
|
* Whether to run `pnpm install` during setup. Defaults to true. Set to
|
|
45
68
|
* false for repos where install should be manual or where the script
|
|
@@ -77,4 +100,40 @@ interface WorktreeConfig {
|
|
|
77
100
|
}
|
|
78
101
|
declare function loadConfig(repoRoot: string): Promise<WorktreeConfig>;
|
|
79
102
|
|
|
80
|
-
|
|
103
|
+
interface SetupOptions {
|
|
104
|
+
branch: string;
|
|
105
|
+
cfg: WorktreeConfig;
|
|
106
|
+
repoRoot: string;
|
|
107
|
+
}
|
|
108
|
+
declare function setup({ branch, cfg, repoRoot }: SetupOptions): Promise<void>;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Helpers for inheriting non-port env vars from the main worktree's
|
|
112
|
+
* `.env.local` files into a new worktree. See `WorktreeConfig.inheritEnvFromMain`.
|
|
113
|
+
*
|
|
114
|
+
* Intentionally pure: I/O happens in the caller (setup command). Keeping
|
|
115
|
+
* the parsing/merge logic free of fs calls makes it trivial to unit-test
|
|
116
|
+
* and reusable from `dev` if we ever want to re-sync mid-session.
|
|
117
|
+
*/
|
|
118
|
+
/**
|
|
119
|
+
* Extract just the keys defined in a KEY=VALUE-style env file. Lines that
|
|
120
|
+
* start with `#` (after optional whitespace) and blank lines are skipped.
|
|
121
|
+
* Quoted values and inline `#` comments are not stripped — we only care
|
|
122
|
+
* about the key position. Repeated keys collapse to a single entry (set).
|
|
123
|
+
*/
|
|
124
|
+
declare function parseEnvKeys(contents: string): Set<string>;
|
|
125
|
+
/**
|
|
126
|
+
* Given the contents of the main worktree's env file and the worktree's
|
|
127
|
+
* own env file (possibly empty), return the lines to append. Returns
|
|
128
|
+
* `''` (no trailing newline, no header) when nothing needs to be appended
|
|
129
|
+
* so the caller can decide whether to touch the file at all.
|
|
130
|
+
*
|
|
131
|
+
* Key-by-key inheritance, not file replacement — the worktree-CLI's
|
|
132
|
+
* port-specific lines (e.g. `PORT=3010`, `VITE_API_URL=http://localhost:3010/api`)
|
|
133
|
+
* stay authoritative.
|
|
134
|
+
*/
|
|
135
|
+
declare function buildInheritedAppend(mainContents: string, worktreeContents: string, options?: {
|
|
136
|
+
header?: string;
|
|
137
|
+
}): string;
|
|
138
|
+
|
|
139
|
+
export { type ServiceConfig, type WorktreeConfig, buildInheritedAppend, loadConfig, parseEnvKeys, setup };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,73 @@
|
|
|
1
|
+
// src/commands/setup.ts
|
|
2
|
+
import { execFileSync, spawnSync } from "child_process";
|
|
3
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
4
|
+
import { dirname as dirname2, resolve as resolve3 } from "path";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
|
|
7
|
+
// src/env-merge.ts
|
|
8
|
+
function parseEnvKeys(contents) {
|
|
9
|
+
const keys = /* @__PURE__ */ new Set();
|
|
10
|
+
for (const rawLine of contents.split("\n")) {
|
|
11
|
+
const line = rawLine.trimStart();
|
|
12
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
13
|
+
const eq = line.indexOf("=");
|
|
14
|
+
if (eq <= 0) continue;
|
|
15
|
+
const key = line.slice(0, eq).trim();
|
|
16
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
|
|
17
|
+
keys.add(key);
|
|
18
|
+
}
|
|
19
|
+
return keys;
|
|
20
|
+
}
|
|
21
|
+
function buildInheritedAppend(mainContents, worktreeContents, options = {}) {
|
|
22
|
+
const existingKeys = parseEnvKeys(worktreeContents);
|
|
23
|
+
const linesToAppend = [];
|
|
24
|
+
for (const rawLine of mainContents.split("\n")) {
|
|
25
|
+
const line = rawLine.trimStart();
|
|
26
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
27
|
+
const eq = line.indexOf("=");
|
|
28
|
+
if (eq <= 0) continue;
|
|
29
|
+
const key = line.slice(0, eq).trim();
|
|
30
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
|
|
31
|
+
if (existingKeys.has(key)) continue;
|
|
32
|
+
linesToAppend.push(rawLine);
|
|
33
|
+
existingKeys.add(key);
|
|
34
|
+
}
|
|
35
|
+
if (linesToAppend.length === 0) return "";
|
|
36
|
+
const header = options.header ?? "# Inherited from main worktree by precisa-worktree setup";
|
|
37
|
+
const prefix = worktreeContents.length > 0 && !worktreeContents.endsWith("\n") ? "\n" : "";
|
|
38
|
+
return `${prefix}
|
|
39
|
+
${header}
|
|
40
|
+
${linesToAppend.join("\n")}
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// src/paths.ts
|
|
45
|
+
import { execSync } from "child_process";
|
|
46
|
+
import { basename, dirname, resolve } from "path";
|
|
47
|
+
function branchToSuffix(branch) {
|
|
48
|
+
return branch.replace(/\//g, "-");
|
|
49
|
+
}
|
|
50
|
+
function worktreeDirectoryName(cfg, branch) {
|
|
51
|
+
return `${cfg.directoryPrefix}-${branchToSuffix(branch)}`;
|
|
52
|
+
}
|
|
53
|
+
function worktreePath(cfg, repoRoot, branch) {
|
|
54
|
+
const parentDir = dirname(repoRoot);
|
|
55
|
+
return resolve(parentDir, worktreeDirectoryName(cfg, branch));
|
|
56
|
+
}
|
|
57
|
+
function logFile(svc, branch) {
|
|
58
|
+
return `/tmp/${svc.logPrefix}-${branchToSuffix(branch)}.log`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/registry.ts
|
|
62
|
+
import { existsSync, mkdirSync, rmdirSync } from "fs";
|
|
63
|
+
import { readFile as readFile2, writeFile } from "fs/promises";
|
|
64
|
+
import { setTimeout as sleep } from "timers/promises";
|
|
65
|
+
|
|
1
66
|
// src/config.ts
|
|
2
67
|
import { readFile } from "fs/promises";
|
|
3
|
-
import { resolve } from "path";
|
|
68
|
+
import { resolve as resolve2 } from "path";
|
|
4
69
|
async function loadConfig(repoRoot) {
|
|
5
|
-
const pkgPath =
|
|
70
|
+
const pkgPath = resolve2(repoRoot, "package.json");
|
|
6
71
|
let pkg;
|
|
7
72
|
try {
|
|
8
73
|
pkg = JSON.parse(await readFile(pkgPath, "utf-8"));
|
|
@@ -37,7 +102,172 @@ async function loadConfig(repoRoot) {
|
|
|
37
102
|
}
|
|
38
103
|
return cfg;
|
|
39
104
|
}
|
|
105
|
+
function lockDir(cfg) {
|
|
106
|
+
return cfg.portLockDir ?? `${cfg.portRegistry}.lock.d`;
|
|
107
|
+
}
|
|
108
|
+
function increment(svc) {
|
|
109
|
+
return svc.increment ?? 10;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/registry.ts
|
|
113
|
+
async function ensureRegistry(cfg) {
|
|
114
|
+
if (!existsSync(cfg.portRegistry)) {
|
|
115
|
+
await writeFile(cfg.portRegistry, "{}");
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function acquireLock(cfg) {
|
|
119
|
+
const dir = lockDir(cfg);
|
|
120
|
+
const maxTries = 100;
|
|
121
|
+
for (let i = 0; i < maxTries; i++) {
|
|
122
|
+
try {
|
|
123
|
+
mkdirSync(dir);
|
|
124
|
+
return;
|
|
125
|
+
} catch (err) {
|
|
126
|
+
if (err.code !== "EEXIST") throw err;
|
|
127
|
+
}
|
|
128
|
+
await sleep(100);
|
|
129
|
+
}
|
|
130
|
+
throw new Error(
|
|
131
|
+
`Timeout (~10s) waiting for lock at ${dir}. If no other session is running, remove it manually: rmdir ${dir}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
function releaseLock(cfg) {
|
|
135
|
+
try {
|
|
136
|
+
rmdirSync(lockDir(cfg));
|
|
137
|
+
} catch {
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function withLock(cfg, fn) {
|
|
141
|
+
await ensureRegistry(cfg);
|
|
142
|
+
await acquireLock(cfg);
|
|
143
|
+
try {
|
|
144
|
+
return await fn();
|
|
145
|
+
} finally {
|
|
146
|
+
releaseLock(cfg);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function readRegistry(cfg) {
|
|
150
|
+
await ensureRegistry(cfg);
|
|
151
|
+
const text = await readFile2(cfg.portRegistry, "utf-8");
|
|
152
|
+
try {
|
|
153
|
+
return JSON.parse(text);
|
|
154
|
+
} catch {
|
|
155
|
+
return {};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async function writeRegistry(cfg, reg) {
|
|
159
|
+
await writeFile(cfg.portRegistry, `${JSON.stringify(reg, null, 2)}
|
|
160
|
+
`);
|
|
161
|
+
}
|
|
162
|
+
async function allocate(cfg, branch) {
|
|
163
|
+
return withLock(cfg, async () => {
|
|
164
|
+
const reg = await readRegistry(cfg);
|
|
165
|
+
if (reg[branch]) return reg[branch];
|
|
166
|
+
const usedSlots = new Set(Object.values(reg).map((e) => e.slot));
|
|
167
|
+
let slot = 1;
|
|
168
|
+
while (usedSlots.has(slot)) slot++;
|
|
169
|
+
const ports = {};
|
|
170
|
+
for (const svc of cfg.services) {
|
|
171
|
+
ports[svc.name] = svc.featureBase + (slot - 1) * increment(svc);
|
|
172
|
+
}
|
|
173
|
+
reg[branch] = { ports, slot };
|
|
174
|
+
await writeRegistry(cfg, reg);
|
|
175
|
+
return reg[branch];
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/commands/setup.ts
|
|
180
|
+
async function setup({ branch, cfg, repoRoot }) {
|
|
181
|
+
const wtPath = worktreePath(cfg, repoRoot, branch);
|
|
182
|
+
if (existsSync2(wtPath)) {
|
|
183
|
+
console.error(chalk.red(`Worktree already exists: ${wtPath}`));
|
|
184
|
+
console.error(`To start dev servers: precisa-worktree dev ${branch}`);
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
console.log(chalk.bold(`==> Creating worktree for '${branch}' at ${wtPath}...`));
|
|
188
|
+
execFileSync("git", ["-C", repoRoot, "fetch", "origin", "main"], { stdio: "inherit" });
|
|
189
|
+
const reuseExisting = branchExists(repoRoot, branch);
|
|
190
|
+
const addArgs = reuseExisting ? ["-C", repoRoot, "worktree", "add", wtPath, branch] : ["-C", repoRoot, "worktree", "add", "-b", branch, wtPath, "origin/main"];
|
|
191
|
+
execFileSync("git", addArgs, { stdio: "inherit" });
|
|
192
|
+
const entry = await allocate(cfg, branch);
|
|
193
|
+
if (cfg.install !== false) {
|
|
194
|
+
console.log(chalk.bold("==> Installing dependencies (pnpm install)..."));
|
|
195
|
+
execFileSync("pnpm", ["install"], { cwd: wtPath, stdio: "inherit" });
|
|
196
|
+
}
|
|
197
|
+
if (cfg.buildCommand) {
|
|
198
|
+
console.log(chalk.bold(`==> Running build: ${cfg.buildCommand}`));
|
|
199
|
+
execFileSync("sh", ["-c", cfg.buildCommand], { cwd: wtPath, stdio: "inherit" });
|
|
200
|
+
}
|
|
201
|
+
if (cfg.writeFiles?.length) {
|
|
202
|
+
console.log(chalk.bold("==> Writing worktree-scoped files"));
|
|
203
|
+
for (const { contents, path } of cfg.writeFiles) {
|
|
204
|
+
let interpolated = contents;
|
|
205
|
+
for (const [svcName, port] of Object.entries(entry.ports)) {
|
|
206
|
+
interpolated = interpolated.replaceAll(`{${svcName}_port}`, String(port)).replaceAll("{port}", String(port));
|
|
207
|
+
}
|
|
208
|
+
const fullPath = resolve3(wtPath, path);
|
|
209
|
+
mkdirSync2(dirname2(fullPath), { recursive: true });
|
|
210
|
+
writeFileSync(fullPath, interpolated);
|
|
211
|
+
console.log(` wrote ${path}`);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (cfg.inheritEnvFromMain?.length) {
|
|
215
|
+
console.log(chalk.bold("==> Inheriting non-port env vars from main worktree"));
|
|
216
|
+
for (const relPath of cfg.inheritEnvFromMain) {
|
|
217
|
+
const mainFile = resolve3(repoRoot, relPath);
|
|
218
|
+
const wtFile = resolve3(wtPath, relPath);
|
|
219
|
+
if (!existsSync2(mainFile)) {
|
|
220
|
+
console.log(chalk.dim(` skip ${relPath} (not present in main worktree)`));
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const mainContents = readFileSync(mainFile, "utf-8");
|
|
224
|
+
const wtContents = existsSync2(wtFile) ? readFileSync(wtFile, "utf-8") : "";
|
|
225
|
+
const appended = buildInheritedAppend(mainContents, wtContents);
|
|
226
|
+
if (appended.length === 0) {
|
|
227
|
+
console.log(chalk.dim(` skip ${relPath} (no missing keys to inherit)`));
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
mkdirSync2(dirname2(wtFile), { recursive: true });
|
|
231
|
+
if (existsSync2(wtFile)) {
|
|
232
|
+
appendFileSync(wtFile, appended);
|
|
233
|
+
} else {
|
|
234
|
+
writeFileSync(wtFile, appended.replace(/^\n/, ""));
|
|
235
|
+
}
|
|
236
|
+
const inheritedCount = appended.split("\n").filter((l) => /^[A-Za-z_]/.test(l)).length;
|
|
237
|
+
console.log(` inherited ${inheritedCount} key(s) into ${relPath}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
console.log("");
|
|
241
|
+
console.log(chalk.green(`Worktree ready: ${wtPath}`));
|
|
242
|
+
console.log(` Branch: ${branch}`);
|
|
243
|
+
for (const svc of cfg.services) {
|
|
244
|
+
const port = entry.ports[svc.name];
|
|
245
|
+
console.log(
|
|
246
|
+
` ${svc.name.padEnd(8)}: http://localhost:${port} (logs: ${logFile(svc, branch)})`
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
console.log("");
|
|
250
|
+
console.log("To start the dev server(s):");
|
|
251
|
+
console.log(` cd ${wtPath} && precisa-worktree dev ${branch}`);
|
|
252
|
+
}
|
|
253
|
+
function branchExists(repoRoot, branch) {
|
|
254
|
+
const local = spawnSync(
|
|
255
|
+
"git",
|
|
256
|
+
["-C", repoRoot, "show-ref", "--verify", "--quiet", `refs/heads/${branch}`],
|
|
257
|
+
{ stdio: "ignore" }
|
|
258
|
+
);
|
|
259
|
+
if (local.status === 0) return true;
|
|
260
|
+
const remote = spawnSync(
|
|
261
|
+
"git",
|
|
262
|
+
["-C", repoRoot, "show-ref", "--verify", "--quiet", `refs/remotes/origin/${branch}`],
|
|
263
|
+
{ stdio: "ignore" }
|
|
264
|
+
);
|
|
265
|
+
return remote.status === 0;
|
|
266
|
+
}
|
|
40
267
|
export {
|
|
41
|
-
|
|
268
|
+
buildInheritedAppend,
|
|
269
|
+
loadConfig,
|
|
270
|
+
parseEnvKeys,
|
|
271
|
+
setup
|
|
42
272
|
};
|
|
43
273
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\n\nexport interface ServiceConfig {\n /**\n * Extra args passed to the dev script. `{port}` is replaced with the\n * allocated port. Defaults to `['--port', '{port}']` (works with vite).\n */\n devArgs?: string[];\n /**\n * Optional: additional env vars to set for the dev process. The literal\n * `{port}` is substituted. Example: `{ VITE_API_URL: 'http://localhost:{port}/api' }`.\n */\n env?: Record<string, string>;\n /**\n * First port for feature worktrees. Slot 1 uses this port; slot 2 uses\n * `featureBase + increment`, slot 3 uses `featureBase + 2*increment`, etc.\n */\n featureBase: number;\n /** Increment between slots. Defaults to 10. */\n increment?: number;\n /**\n * Prefix for the log file path: `/tmp/<logPrefix>-<branch>.log`. Required\n * so that sibling services in a repo don't overwrite each other's logs.\n */\n logPrefix: string;\n /** Port used by the default (main) worktree. */\n mainPort: number;\n /** Service name used in CLI output, column headers, and log file naming. */\n name: string;\n /** pnpm filter pattern for `worktree dev`. Example: `@medbench-brasil/site`. */\n pnpmFilter: string;\n}\n\nexport interface WorktreeConfig {\n /**\n * Optional extra build command run during `setup`, after `pnpm install`.\n * Example: `pnpm turbo run build --filter=./packages/*`.\n */\n buildCommand?: string;\n /**\n * Prefix used for worktree directory names: `<prefix>-<branch>`.\n * Example: `medbench-brasil` → worktrees land at\n * `../medbench-brasil-feat-foo`.\n */\n directoryPrefix: string;\n /**\n * Whether to run `pnpm install` during setup. Defaults to true. Set to\n * false for repos where install should be manual or where the script\n * is invoked inside a container.\n */\n install?: boolean;\n /** Absolute path to the lock directory. Defaults to `<portRegistry>.lock.d`. */\n portLockDir?: string;\n /** Absolute path to the port registry JSON file. */\n portRegistry: string;\n /** Services managed by this repo. At least one required. */\n services: ServiceConfig[];\n /**\n * Extra per-service file writes performed during `setup`, after\n * install + build. Intended for repos that need `.env.local`-style\n * files seeded with the allocated ports so that running `dev` without\n * explicit env vars picks up the right values.\n *\n * Each entry is `{ path, contents }` where `contents` supports the\n * same `{port}` and `{<service>_port}` substitutions available in\n * `service.env` / `service.devArgs`.\n *\n * Example (platform):\n * writeFiles: [\n * { path: 'apps/web/.env.local',\n * contents: 'VITE_DEV_PORT={web_port}\\nVITE_API_URL=http://localhost:{api_port}/api\\n' },\n * { path: 'apps/api/.env.local',\n * contents: 'PORT={api_port}\\n' },\n * ]\n */\n writeFiles?: Array<{ path: string; contents: string }>;\n}\n\ninterface PackageJson {\n name?: string;\n worktree?: WorktreeConfig;\n}\n\nexport async function loadConfig(repoRoot: string): Promise<WorktreeConfig> {\n const pkgPath = resolve(repoRoot, 'package.json');\n let pkg: PackageJson;\n try {\n pkg = JSON.parse(await readFile(pkgPath, 'utf-8')) as PackageJson;\n } catch (err) {\n throw new Error(`Failed to read ${pkgPath}: ${(err as Error).message}. Is this the repo root?`);\n }\n\n const cfg = pkg.worktree;\n if (!cfg) {\n throw new Error(\n `No \"worktree\" field in ${pkgPath}. Add a worktree config — see ` +\n `https://github.com/Precisa-Saude/tooling/tree/main/packages/worktree-cli#configuration`,\n );\n }\n\n if (!cfg.directoryPrefix) {\n throw new Error('worktree.directoryPrefix is required');\n }\n if (!cfg.portRegistry) {\n throw new Error('worktree.portRegistry is required');\n }\n if (!cfg.services?.length) {\n throw new Error('worktree.services must contain at least one entry');\n }\n for (const svc of cfg.services) {\n if (!svc.name) throw new Error('service.name is required');\n if (!svc.pnpmFilter) throw new Error(`service.${svc.name}.pnpmFilter is required`);\n if (!svc.logPrefix) throw new Error(`service.${svc.name}.logPrefix is required`);\n if (typeof svc.mainPort !== 'number') {\n throw new Error(`service.${svc.name}.mainPort must be a number`);\n }\n if (typeof svc.featureBase !== 'number') {\n throw new Error(`service.${svc.name}.featureBase must be a number`);\n }\n }\n\n return cfg;\n}\n\nexport function lockDir(cfg: WorktreeConfig): string {\n return cfg.portLockDir ?? `${cfg.portRegistry}.lock.d`;\n}\n\nexport function increment(svc: ServiceConfig): number {\n return svc.increment ?? 10;\n}\n\nexport function devArgs(svc: ServiceConfig): string[] {\n return svc.devArgs ?? ['--port', '{port}'];\n}\n"],"mappings":";AAAA,SAAS,gBAAgB;AACzB,SAAS,eAAe;AAmFxB,eAAsB,WAAW,UAA2C;AAC1E,QAAM,UAAU,QAAQ,UAAU,cAAc;AAChD,MAAI;AACJ,MAAI;AACF,UAAM,KAAK,MAAM,MAAM,SAAS,SAAS,OAAO,CAAC;AAAA,EACnD,SAAS,KAAK;AACZ,UAAM,IAAI,MAAM,kBAAkB,OAAO,KAAM,IAAc,OAAO,0BAA0B;AAAA,EAChG;AAEA,QAAM,MAAM,IAAI;AAChB,MAAI,CAAC,KAAK;AACR,UAAM,IAAI;AAAA,MACR,0BAA0B,OAAO;AAAA,IAEnC;AAAA,EACF;AAEA,MAAI,CAAC,IAAI,iBAAiB;AACxB,UAAM,IAAI,MAAM,sCAAsC;AAAA,EACxD;AACA,MAAI,CAAC,IAAI,cAAc;AACrB,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AACA,MAAI,CAAC,IAAI,UAAU,QAAQ;AACzB,UAAM,IAAI,MAAM,mDAAmD;AAAA,EACrE;AACA,aAAW,OAAO,IAAI,UAAU;AAC9B,QAAI,CAAC,IAAI,KAAM,OAAM,IAAI,MAAM,0BAA0B;AACzD,QAAI,CAAC,IAAI,WAAY,OAAM,IAAI,MAAM,WAAW,IAAI,IAAI,yBAAyB;AACjF,QAAI,CAAC,IAAI,UAAW,OAAM,IAAI,MAAM,WAAW,IAAI,IAAI,wBAAwB;AAC/E,QAAI,OAAO,IAAI,aAAa,UAAU;AACpC,YAAM,IAAI,MAAM,WAAW,IAAI,IAAI,4BAA4B;AAAA,IACjE;AACA,QAAI,OAAO,IAAI,gBAAgB,UAAU;AACvC,YAAM,IAAI,MAAM,WAAW,IAAI,IAAI,+BAA+B;AAAA,IACpE;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/commands/setup.ts","../src/env-merge.ts","../src/paths.ts","../src/registry.ts","../src/config.ts"],"sourcesContent":["import { execFileSync, spawnSync } from 'node:child_process';\nimport { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, resolve } from 'node:path';\n\nimport chalk from 'chalk';\n\nimport { type WorktreeConfig } from '../config.js';\nimport { buildInheritedAppend } from '../env-merge.js';\nimport { logFile, worktreePath } from '../paths.js';\nimport { allocate } from '../registry.js';\n\nexport interface SetupOptions {\n branch: string;\n cfg: WorktreeConfig;\n repoRoot: string;\n}\n\nexport async function setup({ branch, cfg, repoRoot }: SetupOptions): Promise<void> {\n const wtPath = worktreePath(cfg, repoRoot, branch);\n\n if (existsSync(wtPath)) {\n console.error(chalk.red(`Worktree already exists: ${wtPath}`));\n console.error(`To start dev servers: precisa-worktree dev ${branch}`);\n process.exit(1);\n }\n\n console.log(chalk.bold(`==> Creating worktree for '${branch}' at ${wtPath}...`));\n\n // Fetch origin/main from the main worktree; a fresh worktree branches from it.\n execFileSync('git', ['-C', repoRoot, 'fetch', 'origin', 'main'], { stdio: 'inherit' });\n\n // If the branch already exists (local or remote-tracking), check it out\n // into the new worktree instead of creating a fresh branch — otherwise\n // `git worktree add -b` errors out on the duplicate.\n const reuseExisting = branchExists(repoRoot, branch);\n const addArgs = reuseExisting\n ? ['-C', repoRoot, 'worktree', 'add', wtPath, branch]\n : ['-C', repoRoot, 'worktree', 'add', '-b', branch, wtPath, 'origin/main'];\n execFileSync('git', addArgs, { stdio: 'inherit' });\n\n const entry = await allocate(cfg, branch);\n\n if (cfg.install !== false) {\n console.log(chalk.bold('==> Installing dependencies (pnpm install)...'));\n execFileSync('pnpm', ['install'], { cwd: wtPath, stdio: 'inherit' });\n }\n\n if (cfg.buildCommand) {\n console.log(chalk.bold(`==> Running build: ${cfg.buildCommand}`));\n execFileSync('sh', ['-c', cfg.buildCommand], { cwd: wtPath, stdio: 'inherit' });\n }\n\n // Write per-repo files with the allocated ports substituted in. Used\n // by platform to seed apps/*/.env.local so the running dev server\n // picks up the right ports without extra env-var plumbing.\n if (cfg.writeFiles?.length) {\n console.log(chalk.bold('==> Writing worktree-scoped files'));\n for (const { contents, path } of cfg.writeFiles) {\n let interpolated = contents;\n for (const [svcName, port] of Object.entries(entry.ports)) {\n interpolated = interpolated\n .replaceAll(`{${svcName}_port}`, String(port))\n .replaceAll('{port}', String(port));\n }\n const fullPath = resolve(wtPath, path);\n mkdirSync(dirname(fullPath), { recursive: true });\n writeFileSync(fullPath, interpolated);\n console.log(` wrote ${path}`);\n }\n }\n\n // Inherit non-port env vars from the main worktree's files. Runs after\n // writeFiles so port keys written by the CLI stay authoritative — only\n // missing keys are appended.\n if (cfg.inheritEnvFromMain?.length) {\n console.log(chalk.bold('==> Inheriting non-port env vars from main worktree'));\n for (const relPath of cfg.inheritEnvFromMain) {\n const mainFile = resolve(repoRoot, relPath);\n const wtFile = resolve(wtPath, relPath);\n\n if (!existsSync(mainFile)) {\n console.log(chalk.dim(` skip ${relPath} (not present in main worktree)`));\n continue;\n }\n\n const mainContents = readFileSync(mainFile, 'utf-8');\n const wtContents = existsSync(wtFile) ? readFileSync(wtFile, 'utf-8') : '';\n const appended = buildInheritedAppend(mainContents, wtContents);\n\n if (appended.length === 0) {\n console.log(chalk.dim(` skip ${relPath} (no missing keys to inherit)`));\n continue;\n }\n\n mkdirSync(dirname(wtFile), { recursive: true });\n if (existsSync(wtFile)) {\n appendFileSync(wtFile, appended);\n } else {\n writeFileSync(wtFile, appended.replace(/^\\n/, ''));\n }\n const inheritedCount = appended.split('\\n').filter((l) => /^[A-Za-z_]/.test(l)).length;\n console.log(` inherited ${inheritedCount} key(s) into ${relPath}`);\n }\n }\n\n console.log('');\n console.log(chalk.green(`Worktree ready: ${wtPath}`));\n console.log(` Branch: ${branch}`);\n for (const svc of cfg.services) {\n const port = entry.ports[svc.name]!;\n console.log(\n ` ${svc.name.padEnd(8)}: http://localhost:${port} (logs: ${logFile(svc, branch)})`,\n );\n }\n console.log('');\n console.log('To start the dev server(s):');\n console.log(` cd ${wtPath} && precisa-worktree dev ${branch}`);\n}\n\nfunction branchExists(repoRoot: string, branch: string): boolean {\n const local = spawnSync(\n 'git',\n ['-C', repoRoot, 'show-ref', '--verify', '--quiet', `refs/heads/${branch}`],\n { stdio: 'ignore' },\n );\n if (local.status === 0) return true;\n const remote = spawnSync(\n 'git',\n ['-C', repoRoot, 'show-ref', '--verify', '--quiet', `refs/remotes/origin/${branch}`],\n { stdio: 'ignore' },\n );\n return remote.status === 0;\n}\n","/**\n * Helpers for inheriting non-port env vars from the main worktree's\n * `.env.local` files into a new worktree. See `WorktreeConfig.inheritEnvFromMain`.\n *\n * Intentionally pure: I/O happens in the caller (setup command). Keeping\n * the parsing/merge logic free of fs calls makes it trivial to unit-test\n * and reusable from `dev` if we ever want to re-sync mid-session.\n */\n\n/**\n * Extract just the keys defined in a KEY=VALUE-style env file. Lines that\n * start with `#` (after optional whitespace) and blank lines are skipped.\n * Quoted values and inline `#` comments are not stripped — we only care\n * about the key position. Repeated keys collapse to a single entry (set).\n */\nexport function parseEnvKeys(contents: string): Set<string> {\n const keys = new Set<string>();\n for (const rawLine of contents.split('\\n')) {\n const line = rawLine.trimStart();\n if (line.length === 0 || line.startsWith('#')) continue;\n const eq = line.indexOf('=');\n if (eq <= 0) continue;\n const key = line.slice(0, eq).trim();\n // KEY format must look like an env var name. Skip noise (typos,\n // markdown table rows, anything with whitespace or non-identifier\n // chars on the left of `=`).\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;\n keys.add(key);\n }\n return keys;\n}\n\n/**\n * Given the contents of the main worktree's env file and the worktree's\n * own env file (possibly empty), return the lines to append. Returns\n * `''` (no trailing newline, no header) when nothing needs to be appended\n * so the caller can decide whether to touch the file at all.\n *\n * Key-by-key inheritance, not file replacement — the worktree-CLI's\n * port-specific lines (e.g. `PORT=3010`, `VITE_API_URL=http://localhost:3010/api`)\n * stay authoritative.\n */\nexport function buildInheritedAppend(\n mainContents: string,\n worktreeContents: string,\n options: { header?: string } = {},\n): string {\n const existingKeys = parseEnvKeys(worktreeContents);\n\n const linesToAppend: string[] = [];\n for (const rawLine of mainContents.split('\\n')) {\n const line = rawLine.trimStart();\n if (line.length === 0 || line.startsWith('#')) continue;\n const eq = line.indexOf('=');\n if (eq <= 0) continue;\n const key = line.slice(0, eq).trim();\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;\n if (existingKeys.has(key)) continue;\n linesToAppend.push(rawLine);\n existingKeys.add(key); // dedupe within the source file too\n }\n\n if (linesToAppend.length === 0) return '';\n\n const header = options.header ?? '# Inherited from main worktree by precisa-worktree setup';\n // Ensure separation from whatever the worktree already had — a leading\n // blank line, then the header, then the inherited lines, then trailing\n // newline.\n const prefix = worktreeContents.length > 0 && !worktreeContents.endsWith('\\n') ? '\\n' : '';\n return `${prefix}\\n${header}\\n${linesToAppend.join('\\n')}\\n`;\n}\n","import { execSync } from 'node:child_process';\nimport { basename, dirname, resolve } from 'node:path';\n\nimport type { ServiceConfig, WorktreeConfig } from './config.js';\n\n/** Convert a branch name to a safe directory-suffix: `feat/foo` → `feat-foo`. */\nexport function branchToSuffix(branch: string): string {\n return branch.replace(/\\//g, '-');\n}\n\nexport function worktreeDirectoryName(cfg: WorktreeConfig, branch: string): string {\n return `${cfg.directoryPrefix}-${branchToSuffix(branch)}`;\n}\n\nexport function worktreePath(cfg: WorktreeConfig, repoRoot: string, branch: string): string {\n const parentDir = dirname(repoRoot);\n return resolve(parentDir, worktreeDirectoryName(cfg, branch));\n}\n\nexport function logFile(svc: ServiceConfig, branch: string): string {\n return `/tmp/${svc.logPrefix}-${branchToSuffix(branch)}.log`;\n}\n\n/**\n * Detect the current branch when invoked from inside a linked worktree.\n * Returns null from the main worktree — refusing auto-detect there avoids\n * accidentally acting on `main` when the user forgot to pass a branch.\n */\nexport function detectBranch(cfg: WorktreeConfig): string | null {\n const cwdBase = basename(process.cwd());\n if (!cwdBase.startsWith(`${cfg.directoryPrefix}-`)) return null;\n try {\n const out = execSync('git branch --show-current', { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .trim();\n return out || null;\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve the repo root by walking up from cwd looking for a `package.json`\n * with a `worktree` field. If invoked from inside a linked worktree, falls\n * back to the main worktree (identified by the directory NOT having the\n * `<prefix>-<branch>` suffix).\n */\nexport function findRepoRoot(): string {\n // `git rev-parse --show-toplevel` gives the worktree root (linked or main).\n try {\n return execSync('git rev-parse --show-toplevel', { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .trim();\n } catch {\n return process.cwd();\n }\n}\n\n/**\n * From a linked worktree, walk back to the main worktree using\n * `git worktree list --porcelain`. The first entry is always the main.\n */\nexport function mainWorktreeRoot(): string {\n try {\n const out = execSync('git worktree list --porcelain', {\n stdio: ['ignore', 'pipe', 'ignore'],\n }).toString();\n const firstLine = out.split('\\n')[0] ?? '';\n const match = firstLine.match(/^worktree (.+)$/);\n if (match) return match[1]!;\n } catch {\n // fall through\n }\n return findRepoRoot();\n}\n","import { existsSync, mkdirSync, rmdirSync } from 'node:fs';\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { setTimeout as sleep } from 'node:timers/promises';\n\nimport { increment, lockDir, type ServiceConfig, type WorktreeConfig } from './config.js';\n\nexport type Registry = Record<string, { slot: number; ports: Record<string, number> }>;\n\nasync function ensureRegistry(cfg: WorktreeConfig): Promise<void> {\n if (!existsSync(cfg.portRegistry)) {\n await writeFile(cfg.portRegistry, '{}');\n }\n}\n\n/**\n * Acquire an exclusive lock via `mkdir` — POSIX-atomic, portable across\n * macOS/Linux, no dependency on `flock` (which isn't on macOS by default).\n * Used to serialize registry read-modify-write across concurrent sessions.\n */\nasync function acquireLock(cfg: WorktreeConfig): Promise<void> {\n const dir = lockDir(cfg);\n const maxTries = 100;\n for (let i = 0; i < maxTries; i++) {\n try {\n mkdirSync(dir);\n return;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err;\n }\n await sleep(100);\n }\n throw new Error(\n `Timeout (~10s) waiting for lock at ${dir}. If no other session is ` +\n `running, remove it manually: rmdir ${dir}`,\n );\n}\n\nfunction releaseLock(cfg: WorktreeConfig): void {\n try {\n rmdirSync(lockDir(cfg));\n } catch {\n // ignore — already released or never held\n }\n}\n\n/**\n * Run `fn` while holding the registry lock. Always releases, even on throw.\n * The release is also safe against signal interruption because the lock\n * directory will be removed at process exit via the handler in bin.ts.\n */\nexport async function withLock<T>(cfg: WorktreeConfig, fn: () => Promise<T> | T): Promise<T> {\n await ensureRegistry(cfg);\n await acquireLock(cfg);\n try {\n return await fn();\n } finally {\n releaseLock(cfg);\n }\n}\n\nexport async function readRegistry(cfg: WorktreeConfig): Promise<Registry> {\n await ensureRegistry(cfg);\n const text = await readFile(cfg.portRegistry, 'utf-8');\n try {\n return JSON.parse(text) as Registry;\n } catch {\n return {};\n }\n}\n\nasync function writeRegistry(cfg: WorktreeConfig, reg: Registry): Promise<void> {\n await writeFile(cfg.portRegistry, `${JSON.stringify(reg, null, 2)}\\n`);\n}\n\n/**\n * Allocate a slot+ports for `branch`. Idempotent: returns the existing\n * entry if the branch already has one. Reuses freed slots so slot numbers\n * don't grow unbounded over many create/teardown cycles.\n */\nexport async function allocate(\n cfg: WorktreeConfig,\n branch: string,\n): Promise<{ slot: number; ports: Record<string, number> }> {\n return withLock(cfg, async () => {\n const reg = await readRegistry(cfg);\n if (reg[branch]) return reg[branch];\n\n const usedSlots = new Set(Object.values(reg).map((e) => e.slot));\n let slot = 1;\n while (usedSlots.has(slot)) slot++;\n\n const ports: Record<string, number> = {};\n for (const svc of cfg.services) {\n ports[svc.name] = svc.featureBase + (slot - 1) * increment(svc);\n }\n\n reg[branch] = { ports, slot };\n await writeRegistry(cfg, reg);\n return reg[branch];\n });\n}\n\nexport async function free(cfg: WorktreeConfig, branch: string): Promise<void> {\n return withLock(cfg, async () => {\n const reg = await readRegistry(cfg);\n if (reg[branch]) {\n delete reg[branch];\n await writeRegistry(cfg, reg);\n }\n });\n}\n\nexport async function getBranchEntry(\n cfg: WorktreeConfig,\n branch: string,\n): Promise<{ slot: number; ports: Record<string, number> } | null> {\n const reg = await readRegistry(cfg);\n return reg[branch] ?? null;\n}\n\nexport function portFor(entry: { ports: Record<string, number> }, svc: ServiceConfig): number {\n const port = entry.ports[svc.name];\n if (typeof port !== 'number') {\n throw new Error(\n `No port allocated for service \"${svc.name}\" on this branch. ` +\n `Registry entry may be from an older config version — run teardown + setup again.`,\n );\n }\n return port;\n}\n","import { readFile } from 'node:fs/promises';\nimport { resolve } from 'node:path';\n\nexport interface ServiceConfig {\n /**\n * Extra args passed to the dev script. `{port}` is replaced with the\n * allocated port. Defaults to `['--port', '{port}']` (works with vite).\n */\n devArgs?: string[];\n /**\n * Optional: additional env vars to set for the dev process. The literal\n * `{port}` is substituted. Example: `{ VITE_API_URL: 'http://localhost:{port}/api' }`.\n */\n env?: Record<string, string>;\n /**\n * First port for feature worktrees. Slot 1 uses this port; slot 2 uses\n * `featureBase + increment`, slot 3 uses `featureBase + 2*increment`, etc.\n */\n featureBase: number;\n /** Increment between slots. Defaults to 10. */\n increment?: number;\n /**\n * Prefix for the log file path: `/tmp/<logPrefix>-<branch>.log`. Required\n * so that sibling services in a repo don't overwrite each other's logs.\n */\n logPrefix: string;\n /** Port used by the default (main) worktree. */\n mainPort: number;\n /** Service name used in CLI output, column headers, and log file naming. */\n name: string;\n /** pnpm filter pattern for `worktree dev`. Example: `@medbench-brasil/site`. */\n pnpmFilter: string;\n}\n\nexport interface WorktreeConfig {\n /**\n * Optional extra build command run during `setup`, after `pnpm install`.\n * Example: `pnpm turbo run build --filter=./packages/*`.\n */\n buildCommand?: string;\n /**\n * Prefix used for worktree directory names: `<prefix>-<branch>`.\n * Example: `medbench-brasil` → worktrees land at\n * `../medbench-brasil-feat-foo`.\n */\n directoryPrefix: string;\n /**\n * Files in the main worktree whose KEY=VALUE lines should be appended\n * to the new worktree's corresponding file at the end of `setup`.\n * Keys already present in the worktree's file (typically from\n * `writeFiles`, e.g. `PORT`, `VITE_API_URL`) are NOT overwritten —\n * only missing keys are appended.\n *\n * Useful when the repo's `.env.local` files in main hold long-lived\n * dev credentials that every worktree needs (Cognito, Stripe, S3\n * buckets, DynamoDB tables, etc.) and that aren't worktree-scoped.\n * Without this, each worktree starts with only the port keys from\n * `writeFiles` and any service that reads those creds crashes on\n * first request.\n *\n * Paths are relative to the repo root and resolve against the main\n * worktree (where the CLI is invoked). If a file is missing in main,\n * the inherit step is skipped for that entry (no error). Comments and\n * blank lines in the source file are not copied.\n *\n * Example (platform):\n * inheritEnvFromMain: ['apps/api/.env.local', 'apps/web/.env.local']\n */\n inheritEnvFromMain?: string[];\n /**\n * Whether to run `pnpm install` during setup. Defaults to true. Set to\n * false for repos where install should be manual or where the script\n * is invoked inside a container.\n */\n install?: boolean;\n /** Absolute path to the lock directory. Defaults to `<portRegistry>.lock.d`. */\n portLockDir?: string;\n /** Absolute path to the port registry JSON file. */\n portRegistry: string;\n /** Services managed by this repo. At least one required. */\n services: ServiceConfig[];\n /**\n * Extra per-service file writes performed during `setup`, after\n * install + build. Intended for repos that need `.env.local`-style\n * files seeded with the allocated ports so that running `dev` without\n * explicit env vars picks up the right values.\n *\n * Each entry is `{ path, contents }` where `contents` supports the\n * same `{port}` and `{<service>_port}` substitutions available in\n * `service.env` / `service.devArgs`.\n *\n * Example (platform):\n * writeFiles: [\n * { path: 'apps/web/.env.local',\n * contents: 'VITE_DEV_PORT={web_port}\\nVITE_API_URL=http://localhost:{api_port}/api\\n' },\n * { path: 'apps/api/.env.local',\n * contents: 'PORT={api_port}\\n' },\n * ]\n */\n writeFiles?: Array<{ path: string; contents: string }>;\n}\n\ninterface PackageJson {\n name?: string;\n worktree?: WorktreeConfig;\n}\n\nexport async function loadConfig(repoRoot: string): Promise<WorktreeConfig> {\n const pkgPath = resolve(repoRoot, 'package.json');\n let pkg: PackageJson;\n try {\n pkg = JSON.parse(await readFile(pkgPath, 'utf-8')) as PackageJson;\n } catch (err) {\n throw new Error(`Failed to read ${pkgPath}: ${(err as Error).message}. Is this the repo root?`);\n }\n\n const cfg = pkg.worktree;\n if (!cfg) {\n throw new Error(\n `No \"worktree\" field in ${pkgPath}. Add a worktree config — see ` +\n `https://github.com/Precisa-Saude/tooling/tree/main/packages/worktree-cli#configuration`,\n );\n }\n\n if (!cfg.directoryPrefix) {\n throw new Error('worktree.directoryPrefix is required');\n }\n if (!cfg.portRegistry) {\n throw new Error('worktree.portRegistry is required');\n }\n if (!cfg.services?.length) {\n throw new Error('worktree.services must contain at least one entry');\n }\n for (const svc of cfg.services) {\n if (!svc.name) throw new Error('service.name is required');\n if (!svc.pnpmFilter) throw new Error(`service.${svc.name}.pnpmFilter is required`);\n if (!svc.logPrefix) throw new Error(`service.${svc.name}.logPrefix is required`);\n if (typeof svc.mainPort !== 'number') {\n throw new Error(`service.${svc.name}.mainPort must be a number`);\n }\n if (typeof svc.featureBase !== 'number') {\n throw new Error(`service.${svc.name}.featureBase must be a number`);\n }\n }\n\n return cfg;\n}\n\nexport function lockDir(cfg: WorktreeConfig): string {\n return cfg.portLockDir ?? `${cfg.portRegistry}.lock.d`;\n}\n\nexport function increment(svc: ServiceConfig): number {\n return svc.increment ?? 10;\n}\n\nexport function devArgs(svc: ServiceConfig): string[] {\n return svc.devArgs ?? ['--port', '{port}'];\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","dirname","resolve","readFile","resolve","readFile","existsSync","resolve","mkdirSync","dirname"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@precisa-saude/worktree-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Shared git-worktree lifecycle CLI for Precisa Saúde repositories — creates/dev-runs/tears-down feature worktrees with per-repo port allocation. Drop-in replacement for each repo's `scripts/worktree.sh`.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"git-worktree",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@types/node": "^22.10.0",
|
|
40
40
|
"tsup": "^8.3.5",
|
|
41
41
|
"typescript": "~5.7.3",
|
|
42
|
-
"@precisa-saude/tsconfig": "1.
|
|
42
|
+
"@precisa-saude/tsconfig": "1.9.0"
|
|
43
43
|
},
|
|
44
44
|
"engines": {
|
|
45
45
|
"node": ">=22.0.0"
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"build": "tsup",
|
|
52
52
|
"clean": "rm -rf dist .turbo",
|
|
53
53
|
"dev": "tsup --watch",
|
|
54
|
+
"test": "pnpm build && node --test test/*.test.mjs",
|
|
54
55
|
"typecheck": "tsc --noEmit"
|
|
55
56
|
}
|
|
56
57
|
}
|