Skip to content

dev.mjs

Starts and stops the local dev loop — vite serving the game and wrangler serving its Worker API, running together behind a per-worktree lock.

Contributor toolingdev.mjs

What it does

Starts, stops, or reports on the local dev loop from a start, stop, or status verb; start is the default when none is given. Assigns vite and wrangler the port pair tools/worktree-registry.mjs reserves for this worktree, and refuses a second start while a live lock or an already-answering port shows one is running. Waits for both servers to answer before printing its banner. Tears down the whole process tree — lock included — on Ctrl-C, on either half exiting, or on a failed readiness gate.

Usage

The minimal invocation starts the loop with its default port pair:

bash
npm run dev

Reach the other two verbs, or reset persisted state, with the direct form:

bash
node scripts/dev.mjs [start|stop|status] [--fresh]

The start verb is the default. Forward an argument through npm (npm run dev -- stop) or call the script directly (node scripts/dev.mjs stop) to reach stop or status.

Options

The --fresh flag is the only one this script reads from its own command line. The other six are flags dev.mjs hardcodes when spawning vite and wrangler. The source contains them literally, so the docs audit finds them — but a caller cannot set them on dev.mjs itself.

FlagValueWhat it doesDefault
--freshno valueWipes the worktree's persisted Miniflare state (.wrangler/state-dev-<wranglerPort>/) before starting, instead of reusing it.off — state persists between runs
--configapp/vite.dev.config.mjsFixed in the vite spawn — not read from dev.mjs's command line.fixed
--host127.0.0.1Fixed in the vite spawn, so the dev server answers only on localhost. Not read from dev.mjs's command line.fixed
--ip127.0.0.1Fixed in the wrangler spawn, for the same reason. Not read from dev.mjs's command line.fixed
--persist-to.wrangler/state-dev-<wranglerPort> (relative to the repo root)Fixed in the wrangler spawn — wrangler's Miniflare state directory, keyed by this worktree's wrangler port. Not read from dev.mjs's command line.fixed
--portthe worktree's vite and wrangler portsFixed in both spawns — each server gets its own port. Set the ports with GW_DEV_VITE_PORT / GW_DEV_WRANGLER_PORT (see Inputs and outputs) instead of this flag.the worktree's registered port pair
--varNAME:VALUE pairsRepeated in the wrangler spawn, once per environment value injected into the Worker (seed, retention, speed, admin email — see Inputs and outputs). Set the values with the environment variables listed there instead of this flag.see Inputs and outputs

Inputs and outputs

Binds: vite at http://127.0.0.1:<vitePort>/ and wrangler's API at http://127.0.0.1:<wranglerPort>/. Both ports come from tools/worktree-registry.mjs, keyed by the checkout's directory name. The grafted-wars worktree, for example, gets 7343/8343. Override them with GW_DEV_VITE_PORT / GW_DEV_WRANGLER_PORT; a worktree missing from the registry falls back to the shared 7399/8399 pair.

Readiness: the banner ("Ctrl-C to stop both.") prints only once GET / answers 200 and GET /api/auth/session answers 200 or 401, polled every 500ms up to GW_DEV_GATE_TIMEOUT_MS (default 120000ms).

Reads:

  • ADMIN_EMAIL (default dev-admin@gw.local), GW_DEV_WRANGLER_PORT, GW_DEV_VITE_PORT, GW_DEV_GATE_TIMEOUT_MS
  • SEED_TARGET, GW_DENSE_SEED, GW_OPPONENT_SEED, BOT_RESTRAINT
  • GW_SPEED_ECONOMY, GW_SPEED_FLEET, GW_SPEED_RESEARCH, GW_CLOCK_RATE
  • GW_RETAIN_EVENTS, GW_RETAIN_RECEIPTS, GW_RETAIN_RETURNED_MS, GW_MAX_CATCHUP_MS, X_LOCAL_OBSERVABILITY
  • the lock file .gwtmp/dev-lock.json

Writes:

  • .gwtmp/dev-lock.json — pid, both ports, both child pids
  • .wrangler/state-dev-<wranglerPort>/ — Miniflare state; its observability/trace subtree is pruned on every launch, and the whole directory is wiped with --fresh
  • an empty dist/, created if none exists, so wrangler has somewhere to point

Stops: Ctrl-C or SIGTERM tears down both process trees and clears the lock. From another terminal, node scripts/dev.mjs stop (or npm run dev -- stop) kills whatever the lock still names as alive and clears it.

Exit codes:

CodeMeaning
0start tore down cleanly on a signal; stop ran (with or without anything to stop); status found the loop live and both readiness probes passing.
1status found the loop live but not ready; vite failed to spawn at all; wrangler or vite exited unexpectedly during start; the readiness gate timed out.
2start refused — a live lock or an already-answering port blocks it, or the state directory is over 256MB and --fresh was not given; or the verb given is none of start, stop, status.
3status found no live lock record — the loop is not running.

Examples

Start the loop for local development:

bash
npm run dev

Check whether a loop is already up, before starting a second one:

bash
npm run dev -- status

Reset persisted Miniflare state after a schema or seed change, then start clean:

bash
npm run dev -- --fresh

Stop a loop that is running in another terminal:

bash
npm run dev -- stop

Notes

One loop runs per worktree at a time. A live lock record or an already-answering home port blocks a second start, exiting 2. Two different, registered worktrees never collide — each owns its own port pair. The unregistered-worktree fallback (7399/8399) is shared, though, so two unregistered checkouts running npm run dev at once do collide.

Miniflare state persists across restarts by default, so seeded data survives a restart. Once that state tree (after its observability/trace subtree is pruned on every launch) exceeds 256MB, start refuses until --fresh clears it.

The stop verb always exits 0, even when a kill attempt fails partway through a process tree — the failure is only logged, never surfaced as a nonzero exit.

Source

scripts/dev.mjs — part of Contributor tooling