Appearance
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 tooling › dev.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 devReach 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.
| Flag | Value | What it does | Default |
|---|---|---|---|
--fresh | no value | Wipes the worktree's persisted Miniflare state (.wrangler/state-dev-<wranglerPort>/) before starting, instead of reusing it. | off — state persists between runs |
--config | app/vite.dev.config.mjs | Fixed in the vite spawn — not read from dev.mjs's command line. | fixed |
--host | 127.0.0.1 | Fixed in the vite spawn, so the dev server answers only on localhost. Not read from dev.mjs's command line. | fixed |
--ip | 127.0.0.1 | Fixed 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 |
--port | the worktree's vite and wrangler ports | Fixed 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 |
--var | NAME:VALUE pairs | Repeated 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(defaultdev-admin@gw.local),GW_DEV_WRANGLER_PORT,GW_DEV_VITE_PORT,GW_DEV_GATE_TIMEOUT_MSSEED_TARGET,GW_DENSE_SEED,GW_OPPONENT_SEED,BOT_RESTRAINTGW_SPEED_ECONOMY,GW_SPEED_FLEET,GW_SPEED_RESEARCH,GW_CLOCK_RATEGW_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:
| Code | Meaning |
|---|---|
0 | start tore down cleanly on a signal; stop ran (with or without anything to stop); status found the loop live and both readiness probes passing. |
1 | status 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. |
2 | start 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. |
3 | status found no live lock record — the loop is not running. |
Examples
Start the loop for local development:
bash
npm run devCheck whether a loop is already up, before starting a second one:
bash
npm run dev -- statusReset persisted Miniflare state after a schema or seed change, then start clean:
bash
npm run dev -- --freshStop a loop that is running in another terminal:
bash
npm run dev -- stopNotes
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