mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
vinext@0.2.1
1 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9277b101b2 |
feat(dev): add dev server lock file (#1193)
* feat(dev): add dev server lock file Write the running dev server's PID, port, and URL into `node_modules/.cache/vinext/dev-lock.json`. When a second `vinext dev` process starts in the same project directory, vinext reads the lock file and prints an actionable error: Another vinext dev server is already running. - Local: http://localhost:3000 - PID: 12345 - Dir: /path/to/project You can access the existing server at http://localhost:3000, or run `kill 12345` to stop it and start a new one. This is especially useful for AI coding agents, which frequently attempt to start `vinext dev` without knowing a server is already running. The structured error gives the agent the PID to kill the existing process or the URL to connect to it — no manual intervention required. If the recorded PID is dead (stale lock from a crashed run), the new process takes over the lock automatically. Set `VINEXT_NO_DEV_LOCK=1` to opt out entirely. Behavior modeled on Next.js' dev server lock file: https://github.com/vercel/next.js/blob/canary/packages/next/src/build/lockfile.ts vinext uses a JSON file + PID liveness check (`process.kill(pid, 0)`) rather than a native `flock()`. The race window on acquisition is small and benign: at worst, two dev servers race and one fails to bind its port. * chore(dev-lockfile): unexport types only used internally knip flagged FormatErrorOptions, AcquireOptions, AcquireSuccess, AcquireFailure, and AcquireResult as unused exports. These are only referenced inside dev-lockfile.ts itself, so drop the `export` keyword to keep the public surface minimal. * review(dev-lockfile): address self-review and bot feedback - Capture ownerPid at acquire time and use it in release() instead of reading info.pid. Decouples release from update() so a future caller passing a different PID through update() can't trick release() into deleting another process's lock. Added a regression test. - Document the TOCTOU window between readLockfile() and writeLockfile() inline so future contributors don't try to 'fix' the intentionally accepted race. - Document the unreachable existing: undefined branch in formatAlreadyRunningError as a defensive fallback. - Document the process.on('exit', ...) signal semantics — exit fires on graceful shutdown and after default SIGINT/SIGTERM handlers, but not on hard crashes. The next vinext dev will take over the stale lock. - Set lock file mode to 0o600. Defense-in-depth: the PID is also discoverable via ps, so this isn't load-bearing. - Use Vite's server.resolvedUrls.local[0] in cli.ts when building the post-listen appUrl. Vite substitutes 'localhost' for wildcard binds (0.0.0.0) so the URL is actually clickable. Also substitute 'localhost' in the pre-listen initial info for the same reason. - Add tests: * exit listener is registered/deregistered correctly * update() with a different PID doesn't affect release() ownership * lock file is created with 0o600 on POSIX * review(dev-lockfile): release on listen failure, preserve startedAt Two issues caught in the bot re-review: 1. If `server.listen()` throws (e.g. strictPort and the port is taken), the dev() function previously threw without releasing the lock. The process.on('exit') handler still cleaned up afterwards, but in the brief window between listen failure and process exit, the lock file claimed a server was running at a port nobody was listening on. A concurrent `vinext dev` in that window would have shown a misleading 'already running' error. Wrap createServer + listen in try/catch and call lockfile.release() on failure. The exit listener is still registered as a safety net for unexpected exit paths. 2. update() after server.listen() was setting `startedAt: Date.now()`, which overwrote the original acquisition timestamp. `startedAt` is meant to reflect when the process started (for 'how long has this server been running?' debugging), not when the URL was resolved. Capture `startedAt` once at acquisition time and pass the same value into update(). Added a regression test. * feat(dev-lockfile): move lock file to .vinext/dev/lock.json Previously the lock file lived at node_modules/.cache/vinext/dev-lock.json. .vinext/ is already the established convention for project-local vinext state — the fonts plugin uses .vinext/fonts/ to cache self-hosted Google Fonts, and the monorepo's own .gitignore already excludes .vinext. Benefits: - Doesn't pollute node_modules (which package managers may aggressively clear during install / pnpm prune). - Co-located with other vinext state, so users find it where they expect. - Mirrors Next.js' .next/dev/lock layout more closely. Follow-up: vinext init does not currently add .vinext to user gitignores. That's a separate hygiene improvement also relevant to the fonts cache. |