@erquhart/convex-worktrees
v0.0.1
Published
Per-worktree isolated Convex dev deployments, wired into your worktree tool (Zed, Conductor, Cursor).
Downloads
265
Maintainers
Readme
@erquhart/convex-worktrees
Give every git worktree its own isolated Convex dev deployment — seeded from your main deployment, with stable ports and per-worktree env vars — so you can run several worktrees side by side without them stepping on each other.
You wire one command, convex-worktrees init, into whatever tool spawns your
worktrees. It runs after the worktree is created and is idempotent (safe to
re-run).
Quick start
Add a config (optional — sensible defaults are used without one):
// convex-worktrees.config.js import { defineConfig } from "@erquhart/convex-worktrees"; export default defineConfig({ portEnvVars: "PORT", env: { GITHUB_REDIRECT_URI: "http://127.0.0.1:${port}/" }, });Wire
initinto your worktree tool (below).Run your dev server in the main checkout once first, so it has the
.env.localthat worktrees are seeded from.
Wiring it into your tool
Zed
.zed/tasks.json — runs on the create_worktree hook:
[
{
"label": "Set up Convex worktree",
"command": "npx",
"args": ["@erquhart/convex-worktrees", "init"],
"hooks": ["create_worktree"],
"reveal": "always",
"hide": "on_success"
}
]Conductor
.conductor/settings.toml:
[scripts]
setup = "npx @erquhart/convex-worktrees init"Conductor hands each workspace a 10-port range via CONDUCTOR_PORT; the tool
uses it as the base port automatically.
Cursor
.cursor/worktrees.json:
{
"setup-worktree": ["npx @erquhart/convex-worktrees init"]
}Anything else / manual
Run it from inside the worktree:
cd path/to/worktree
npx @erquhart/convex-worktrees initIt finds the main worktree via git worktree list (or ROOT_WORKTREE_PATH).
What init does
- Copies
.env.localfrom the main worktree. - Installs dependencies (frozen) with your package manager — detected from the
command that invoked it (
npx→npm,pnpm dlx→pnpm,bunx→bun, …). Toggle off or replace with a custom command viainstall. - Creates/selects a per-worktree dev deployment.
- Seeds it from main (
convex exportfrom main →importinto the new deployment). Toggle off, point at an existing export, or replace with your own command viaseed. - Reserves stable ports and writes them into the env file.
- Applies your
enventries withconvex env set.
It writes a CONVEX_WORKTREES_READY marker last, so re-runs are a no-op. Pass
--force to re-run anyway (re-seeding overwrites the deployment).
Commands
| Command | Description |
| --- | --- |
| init | Initialize the current worktree (run from your tool's hook). |
| init --check | Report what would happen, without making changes. |
| init --force | Re-run setup even if the worktree is already marked ready. |
| list | Show recorded worktree → port assignments. |
Configuration
convex-worktrees.config.js (or .mjs) in the repo root. Every field is
optional. The default export may be the object below, or a function of the
worktree context ({ name, worktreeRoot, mainRoot }) returning it.
| Field | Default | Notes |
| --- | --- | --- |
| envFile | ".env.local" | File copied from main and written into. |
| install | true | true detects the package manager and runs a frozen install; false skips it; a string runs that command instead. |
| seed | true | true seeds from main via export/import; false skips it; a string runs that command instead (after the deployment is created); an object tunes the built-in seed (below). |
| seed.existingExportPath | — | Import this export instead of creating one. Relative paths resolve against the main checkout. |
| seed.exportFileStorage | false | Include file storage in the export (convex export --include-file-storage). |
| portEnvVars | "PORT" | Env var name(s) the reserved port(s) are written into. A list reserves consecutive ports: name i gets basePort + i. |
| portRange | [5174, 5973] | Pool to allocate from; used only when the tool provides no port (Zed, Cursor, manual). |
| env | {} | convex env set pairs. Values support ${name}, ${port}, ${port+N}. |
Ports
When the tool doesn't hand over a port, each worktree gets a stable base port
derived from its name, recorded in .convex-worktrees/ports.json in the main
worktree. Other worktrees' ports and any port with a live listener are probed
past, and assignments for deleted worktrees are pruned automatically. A
worktree's port is best-effort stable — if another process claims it while
you're away, the next run moves it to a free one.
License
MIT
