Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
翻译准备中
尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证制作者的介绍仅有上面的一句话。 更多详情请阅读仓库 README。 仓库 README ↗
正在加载完整 README。
查看完整 README ↗Game nights for groups of friends. The MVP covers date coordination only; see issue #1 for the spec, GLOSSARY.md for the terms and docs/adr/ for the decisions.
Stack: Nuxt 4, Tailwind v4, Drizzle on Neon Postgres, Vitest. Details in docs/research/.
Requirements: Node 24 (.nvmrc) and pnpm (the version is pinned in package.json; corepack enable picks it up).
Install: pnpm install
Create your own Neon dev branch, so you never touch staging or production data. In the Neon console open the COMPIES project, go to Branches → New branch, branch from main and name it dev-<your name>. Copy its connection string.
Copy .env.example to .env and set the env vars:
| Variable | What |
|---|---|
DATABASE_URL | Connection string of your Neon dev branch |
BETTER_AUTH_URL | Origin the app runs on: http://localhost:3000 locally |
BETTER_AUTH_SECRET | Random string, at least 32 characters (openssl rand -base64 32) |
DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET | Discord OAuth app; its redirect URIs include <origin>/api/auth/callback/discord |
TWITCH_CLIENT_ID, TWITCH_CLIENT_SECRET | Twitch OAuth app; its redirect URIs include <origin>/api/auth/callback/twitch |
HOST_PROVIDER_IDS | Who has the Host permission: discord:<user id>,twitch:<user id> (ADR 0002) |
ADMIN_PROVIDER_IDS | The Platform admin, same format; also gets the Host permission |
R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY | Cloudflare account id and R2 API token (object read and write) for game images |
R2_BUCKET | Bucket of this environment: compies-staging or compies-production |
R2_JURISDICTION | Optional: eu when the buckets use the EU jurisdiction, else empty |
REALTIME_URL | The realtime Worker the Event page connects to: http://localhost:8787 locally, the *.workers.dev URL of the staging or production Worker in Vercel (read at build time). Empty: no live connection, the page polls every 15 s |
ROOM_TOKEN_SECRET | Random string that signs the 60-second room token; the Worker holds the same value |
ROOM_INTERNAL_SECRET | Random string that signs the calls from the app to the Worker, and the deploy workflow's game-day check; the Worker holds the same value |
ROOM_TOKEN_SECRET_PREVIOUS, ROOM_INTERNAL_SECRET_PREVIOUS | Optional, only while a key rotates: the old value, accepted next to the new one (the app's deploy check and the Worker both read them) |
The allowlists apply at each sign-in, so a change takes effect at that User's next sign-in.
The R2 variables are set in Vercel, not in your .env, by scripts/wizards/r2-setup.sh (#139); on Windows, run it from Git Bash, since WSL's bash can't run the Windows Vercel CLI. Image upload needs them; tests use an in-memory store, so leave them empty locally.
Apply the migrations to your branch: pnpm db:migrate
Start the dev server: pnpm dev, then open http://localhost:3000. GET /api/health answers {"status":"ok"} when the database is reachable and migrated. To sign in without Discord or Twitch, open http://localhost:3000/api/dev/sign-in?as=host (or member, admin; &to=/bibliothek picks the page). The route exists only in the dev server.
realtime/)The Event page updates live through a Cloudflare Worker with one PartyServer room per Event (ADR 0012, ADR 0013). It is its own install with its own lockfile, so Vercel never installs wrangler. To try it locally:
pnpm --dir realtime install --frozen-lockfilerealtime/.dev.vars.example to realtime/.dev.vars and give ROOM_TOKEN_SECRET and ROOM_INTERNAL_SECRET the same values as in your .env. Set REALTIME_URL=http://localhost:8787 in .env.pnpm dev:realtime (wrangler dev on port 8787) next to pnpm dev. Open an Event in two browsers: a Vote shows in the other at once..github/workflows/realtime-deploy.yml deploys it: a push to staging deploys compies-rt-staging, a push to main deploys compies-rt, each only when the push touches realtime/ or a shared/ file the Worker imports. A production deploy drops every open socket, so it first asks the app's deploy check (GET /api/internal/deploy-check, signed with the room's internal key) and fails on a day with a Session Date (Europe/Berlin) or while a Session runs (for 16 h after its start). To deploy anyway, or to deploy by hand, run the workflow from the Actions tab: target picks the Worker, override skips the check. Releases to main wait for a day without a Session (docs/agents/release.md).
One-off setup, once per Cloudflare account: run scripts/wizards/realtime-setup.sh (--dry-run walks it without access; on Windows use Git Bash). It deploys both Workers, gives each its two secrets, sets the Vercel variables and these GitHub Actions secrets:
| Secret | What |
|---|---|
CLOUDFLARE_API_TOKEN | Token from the template „Edit Cloudflare Workers“, scoped to the account. Without it staging skips the deploy with a warning and production fails |
CLOUDFLARE_ACCOUNT_ID | The Cloudflare account that owns the Workers |
ROOM_INTERNAL_SECRET_PRODUCTION | The production ROOM_INTERNAL_SECRET (the one Vercel's Production environment and the production Worker have); signs the game-day check |
Each Worker's own ROOM_TOKEN_SECRET and ROOM_INTERNAL_SECRET are Worker secrets (wrangler secret put … --env staging|production, from realtime/), and the same pair, with REALTIME_URL, is set in Vercel per environment.
Without the Worker the page polls every 15 s, as before. The Worker's checks: pnpm --dir realtime typecheck and pnpm --dir realtime test (workerd). To rotate a secret, set the new value in Vercel and as the Worker's secret, and the old one as the Worker's ROOM_TOKEN_SECRET_PREVIOUS or ROOM_INTERNAL_SECRET_PREVIOUS until open tokens and calls have expired.
Edit server/db/schema.ts, then run pnpm db:generate and commit the generated SQL in drizzle/. Apply it with pnpm db:migrate. Never use drizzle-kit push against a shared branch.
| Command | What |
|---|---|
pnpm typecheck | nuxt typecheck (vue-tsc) over app, server, tests and configs |
pnpm lint | ESLint (@nuxt/eslint, stylistic rules) |
pnpm knip | Unused files, exports and dependencies (knip.jsonc); an export only a test uses counts as unused |
pnpm test | All Vitest projects; pnpm vitest --project unit (or integration, nuxt) runs one |
The test projects:
unit (test/unit/): pure domain rules from server/domain/.integration (test/integration/): server handlers called over HTTP against an in-memory PGlite with the committed migrations applied, via test/integration/harness.ts. No Neon needed. Server code imports h3 and its own modules explicitly instead of relying on Nitro auto-imports, so these tests can load it.nuxt (test/nuxt/): pages and components in a Nuxt runtime.CI runs all three checks on pull requests into and pushes to staging and main, and the Worker's typecheck and tests (realtime/) after them.
Only staging and main build on Vercel (vercel.json); feature branches don't deploy (ADR 0007).
| Branch | URL | Database |
|---|---|---|
staging | https://staging.compies.christried.me (behind Vercel login) | Neon staging branch |
main | https://compies.christried.me | Neon main branch |
Flow: feature branch cut from staging → PR into staging → check on staging → release PR staging → main. Both branches are protected: changes land only through a PR with green CI, and force pushes are blocked.
The Vercel build command is pnpm db:migrate && pnpm build, so each deploy first applies the committed migrations to that environment's DATABASE_URL. drizzle-kit runs all pending migrations in one transaction. If one fails, nothing is applied, the build fails, and the previous deployment keeps serving. Fix forward with a new migration; never edit a migration that has already run on staging.
ESLint rejects @vercel/* imports, because the app must not depend on Vercel-only runtime APIs (ADR 0005).