chore: infra tooling and configs

This commit is contained in:
ayo 2026-07-03 21:54:29 +02:00
parent 7685a6dea7
commit 0532fd6d66
10 changed files with 11194 additions and 34 deletions

View file

@ -28,19 +28,48 @@ pnpm -F mnswpr run build:preview # build the app and serve the production previ
### Infra (local CLI only — no web dashboards)
All infra runs through local CLIs. Each app **owns its infra scripts** in its own `package.json` under generic, tech-agnostic names (`deploy:db`, not `deploy:firestore`) — run them by targeting the app with pnpm's `-F` filter (no root wrapper scripts):
**Every infra operation — provision, deploy hosting, deploy DB, manage env — is doable from the CLI, and every configuration/schema is codified in-repo.** Nothing lives only in a web dashboard. There are two distinct layers, both owned by the app:
**1. App infra *config* — declarative, committed, deployed state.** These files ARE the source of truth; deploying just pushes them up. For `mnswpr`, all under `apps/mnswpr/`:
| File | Codifies |
| --- | --- |
| `firebase.json` | Firestore + emulator wiring (rules/indexes paths, emulator ports) |
| `.firebaserc` | Firebase project aliases (`default`/`prod`/`dev`) |
| `firestore.rules` | Firestore security rules (server-side access control) |
| `firestore.indexes.json` | Firestore indexes (none needed — documented inline) |
| `netlify.toml` | Netlify hosting: build command, publish dir, redirects, headers, build env |
| `.env.example` | The full env-var contract; real prod values are set as Netlify env vars via CLI, never committed |
**2. App infra *tools* — the CLIs that act on that config.** They are versioned **devDependencies** of the app (not `npx`-on-demand, not global installs), so `pnpm install` pins them and every machine gets the same version. `mnswpr` depends on `firebase-tools` and `netlify-cli`; its scripts call the `firebase`/`netlify` binaries directly (pnpm puts the app's `node_modules/.bin` on `PATH`). A future app using a different stack (Postgres, a different host) declares *its* CLIs as *its* devDependencies and backs the same generic script names — so `pnpm -F <name> run deploy:db` stays uniform.
Each app **owns its infra scripts** in its own `package.json` under generic, tech-agnostic names (`deploy:db`, not `deploy:firestore`) — run them by targeting the app with pnpm's `-F` filter (no root wrapper scripts):
```bash
pnpm -F mnswpr run db:start # local DB emulator (mnswpr -> Firestore), standalone
pnpm -F mnswpr run db:seed # seed the running local emulator
pnpm -F mnswpr run db:stop # kill a stray/orphaned Firestore emulator holding :8080
pnpm -F mnswpr run deploy:db # deploy DB rules/indexes (-> firebase deploy --only firestore)
pnpm -F mnswpr run deploy:site # build + deploy hosting (-> netlify deploy --prod)
pnpm -F mnswpr run deploy:site # build + deploy hosting (-> netlify deploy --prod --dir=dist)
```
Each app defines the same generic script names backed by whatever stack it uses (e.g. Postgres, a different host), so `pnpm -F <name> run deploy:db` is uniform across apps. `deploy:site` requires a one-time `npx netlify-cli login && npx netlify-cli link` per app.
**One-time per app / per machine (all CLI, no dashboard):**
The Firestore emulator needs **Java** (it's a JVM program). `pnpm install` runs a root `postinstall` (`scripts/ensure-java.mjs`) that installs a user-local Temurin JRE 21 into `~/.local` without `sudo` when `java` is missing — idempotent, non-fatal, and auto-skipped on `CI` / `SKIP_JRE_SETUP` / unsupported platforms.
```bash
pnpm -F mnswpr exec firebase login # auth the Firebase CLI
pnpm -F mnswpr exec netlify login # auth the Netlify CLI
pnpm -F mnswpr exec netlify link # bind the app dir to its Netlify site (writes .netlify/, gitignored)
```
**Managing hosting env vars via CLI** (keeps prod Firebase keys + `VITE_LB_NAMESPACE=mw` out of git while still reproducible):
```bash
pnpm -F mnswpr exec netlify env:set VITE_LB_NAMESPACE mw # set one var
pnpm -F mnswpr exec netlify env:import .env.production # bulk-import from a local (gitignored) env file
pnpm -F mnswpr exec netlify env:list # inspect what's set
```
**Non-npm tools get a setup script instead of a devDependency.** The Firestore emulator needs **Java** (it's a JVM program), which isn't an npm package — so `pnpm install` runs a root `postinstall` (`scripts/ensure-java.mjs`) that installs a user-local Temurin JRE 21 into `~/.local` without `sudo` when `java` is missing — idempotent, non-fatal, and auto-skipped on `CI` / `SKIP_JRE_SETUP` / unsupported platforms. Any future infra tool that isn't on npm follows the same pattern (a checked-in setup script), never a manual install step.
Tests are co-located with the package they exercise (`packages/utils/test/`, `packages/mnswpr/test/`) and run under **Vitest** with a jsdom environment (root config in `vitest.config.js`). They cover the shared utils and drive the engine through real DOM events (mount `#app`, dispatch mouse events, assert on cell/grid attributes). For anything visual or input-timing related, also verify by running `pnpm -F mnswpr run dev` and playing.

View file

@ -45,21 +45,31 @@ pnpm -F mnswpr run preview # preview its production build
## Infra (per-app, via local CLI)
Infra runs through local CLIs, never web dashboards. Each app owns its infra scripts
under generic names (`deploy:db`, `deploy:site`, `db:start`, `db:seed`) — run them by
targeting the app with pnpm's `-F` filter:
Every infra operation runs through local CLIs — never web dashboards — and every
config/schema is codified in-repo. Two layers, both owned by the app:
- **Config** (declarative, committed state): for `mnswpr`, `firebase.json`, `.firebaserc`,
`firestore.rules`, `firestore.indexes.json`, `netlify.toml`, and `.env.example`.
- **Tools** (the CLIs acting on that config): versioned **devDependencies** of the app
(`firebase-tools`, `netlify-cli`) — installed by `pnpm install`, not `npx`/global.
Each app owns its infra scripts under generic names — run them by targeting the app
with pnpm's `-F` filter:
```bash
pnpm -F mnswpr run db:start # start the local DB emulator (Firestore) — needs Java, see app README
pnpm -F mnswpr run db:seed # seed the running emulator with dev data
pnpm -F mnswpr run db:stop # kill a stray emulator left holding :8080
pnpm -F mnswpr run deploy:db # deploy DB rules/indexes (firebase deploy --only firestore)
pnpm -F mnswpr run deploy:site # build + deploy hosting (netlify deploy --prod)
pnpm -F mnswpr run deploy:site # build + deploy hosting (netlify deploy --prod --dir=dist)
```
Apps are named `<name>`, so a future app uses the same command shape
(`pnpm -F <name> run deploy:db`), backed by whatever stack that app uses.
`deploy:site` needs a one-time `npx netlify-cli login && npx netlify-cli link` per app.
(`pnpm -F <name> run deploy:db`), backed by whatever stack (and CLIs) that app declares.
One-time per app: `pnpm -F mnswpr exec firebase login`, and
`pnpm -F mnswpr exec netlify login && pnpm -F mnswpr exec netlify link`. Manage prod
hosting env vars via CLI too (e.g. `netlify env:set` / `netlify env:import`). See
[AGENTS.md](AGENTS.md) for the full infra reference.
See [apps/mnswpr/README.md](apps/mnswpr/README.md) for the game itself, and each package's
README for library usage.

View file

@ -17,8 +17,8 @@ Firebase and Netlify that have to be done by hand.
From this app directory (`apps/mnswpr`):
```bash
npx firebase login
npx firebase deploy --only firestore:rules,firestore:indexes --project prod
pnpm -F mnswpr exec firebase login
pnpm -F mnswpr exec firebase deploy --only firestore:rules,firestore:indexes --project prod
```
- Uses committed [`firestore.rules`](firestore.rules) + [`firestore.indexes.json`](firestore.indexes.json).

View file

@ -68,7 +68,7 @@ pnpm build:lib # build the publishable library
The leader board is backed by [Google Firestore](https://firebase.google.com). For local development the app talks to the **Firestore emulator** by default — fully local, no cloud, no deploy. The flag `VITE_FIRESTORE_EMULATOR=1` is already set in `app/.env.development`.
`dev` is the whole loop in one command — it wraps Vite in `firebase emulators:exec`, so the Firestore emulator (on :8080, + UI) comes up, gets **seeded with sample scores automatically**, and the app dev server starts against it; everything shuts down when you stop it. (Each `dev` starts a fresh in-memory emulator, so the auto-seed writes exactly one clean set every time — no accumulation.) `firebase-tools` itself is fetched on demand via `npx`, so the only prerequisite is **Java** — the Firestore emulator is a Java program (`java -jar cloud-firestore-emulator-*.jar`), and firebase-tools does not bundle a JRE.
`dev` is the whole loop in one command — it wraps Vite in `firebase emulators:exec`, so the Firestore emulator (on :8080, + UI) comes up, gets **seeded with sample scores automatically**, and the app dev server starts against it; everything shuts down when you stop it. (Each `dev` starts a fresh in-memory emulator, so the auto-seed writes exactly one clean set every time — no accumulation.) `firebase-tools` is a pinned **devDependency** of this app (installed by `pnpm install`, invoked as the `firebase` binary), so the only extra prerequisite is **Java** — the Firestore emulator is a Java program (`java -jar cloud-firestore-emulator-*.jar`), and firebase-tools does not bundle a JRE.
**Usually automatic.** `pnpm install` runs a root `postinstall` ([`scripts/ensure-java.mjs`](../../scripts/ensure-java.mjs)) that installs a user-local Temurin JRE 21 into `~/.local` (no `sudo`) when `java` isn't already on your PATH. It's idempotent and never fails the install, and it skips when `CI` or `SKIP_JRE_SETUP=1` is set, or on unsupported platforms.

View file

@ -98,23 +98,26 @@ Dev config lives in the committed [`app/.env.development`](../app/.env.developme
### One-time CLI setup
The Firebase project already exists — no need to create it.
`firebase-tools` is a pinned devDependency of this app (installed by `pnpm install`),
so run it as the `firebase` binary via pnpm — no global install, no `npx`. The
Firebase project already exists — no need to create it.
```bash
npx firebase login
pnpm -F mnswpr exec firebase login
```
### Deploy rules + indexes
Deploys go to **production** by default. Target a project explicitly with
`--project`:
The `deploy:db` script (`pnpm -F mnswpr run deploy:db`) deploys everything under
`firestore` to the **default** project. To target a specific project or a subset,
call the CLI directly:
```bash
# production (default alias 'prod')
npx firebase deploy --only firestore:rules,firestore:indexes --project prod
pnpm -F mnswpr exec firebase deploy --only firestore:rules,firestore:indexes --project prod
# development database
npx firebase deploy --only firestore:rules,firestore:indexes --project dev
pnpm -F mnswpr exec firebase deploy --only firestore:rules,firestore:indexes --project dev
```
> ⚠️ Deploying **replaces** whatever rules currently live in the Console. The
@ -133,7 +136,8 @@ locally (so you validate them before deploying). The flag
`VITE_FIRESTORE_EMULATOR=1` is set in [`app/.env.development`](../app/.env.development).
> Prerequisite: the Firestore emulator is a Java process, so you need a JDK
> (11+) installed. `firebase-tools` is fetched on demand via `npx`.
> (11+) installed. `firebase-tools` is a pinned devDependency of this app
> (installed by `pnpm install`, run as the `firebase` binary).
Everyday dev loop:

View file

@ -139,8 +139,8 @@ Both aliases target the one project we actually have:
### 7. Deploy & verify
1. `npx firebase deploy --only firestore:rules,firestore:indexes --project prod`
2. Set the Netlify env vars (`VITE_LB_NAMESPACE=mw` + `VITE_FIREBASE_*`).
1. `pnpm -F mnswpr exec firebase deploy --only firestore:rules,firestore:indexes --project prod`
2. Set the Netlify env vars via CLI: `pnpm -F mnswpr exec netlify env:import .env.production` (or `netlify env:set VITE_LB_NAMESPACE mw` + each `VITE_FIREBASE_*`).
3. `pnpm dev`, win a game → confirm the write lands in **`mw-test-scores`**, and
the prod `mw-scores` is untouched (Firestore console).
4. Confirm reads on both `mw-scores` and `mw-test-scores` succeed under the new

36
apps/mnswpr/netlify.toml Normal file
View file

@ -0,0 +1,36 @@
# Netlify hosting config for mnswpr.com — the app-level, declarative hosting
# state, codified here so hosting is reproducible from the repo and deployed via
# CLI (`pnpm -F mnswpr run deploy:site`), never the web dashboard. This file is
# app infra *config*; the CLI that reads it (`netlify-cli`) is app infra *tooling*
# and lives in this app's devDependencies. See AGENTS.md "Infra".
# The `deploy:site` script pre-builds locally and uploads `dist` via the CLI, so
# this [build] block is only exercised when Netlify builds the site from git
# (dashboard "base directory" = apps/mnswpr). It is a pnpm workspace, so
# `pnpm install` from here installs the whole monorepo before building.
[build]
command = "pnpm install --frozen-lockfile && pnpm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "20"
# Production leaderboard namespace (dev/test uses `mw-test`). Applies to
# git-triggered builds; local CLI builds read app/.env.production instead.
# Firebase VITE_* keys are public but are set as Netlify env vars (via
# `netlify env:set` / `netlify env:import`, not the dashboard) to keep prod
# values out of git — see apps/mnswpr/.env.example.
VITE_LB_NAMESPACE = "mw"
# Pretty URL for the frozen Legends page (built as /legends.html).
[[redirects]]
from = "/legends"
to = "/legends.html"
status = 200
# Baseline security headers applied to every response.
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "SAMEORIGIN"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"

View file

@ -6,14 +6,14 @@
"type": "module",
"main": "main.js",
"scripts": {
"dev": "npx -y firebase-tools emulators:exec --only firestore --ui \"node scripts/seed-dev-scores.js; vite\"",
"dev": "firebase emulators:exec --only firestore --ui \"node scripts/seed-dev-scores.js; vite\"",
"dev:no-db": "vite",
"build": "vite build",
"preview": "vite preview",
"build:preview": "npm run build && npm run preview",
"deploy:db": "npx -y firebase-tools deploy --only firestore",
"deploy:site": "npm run build && npx -y netlify-cli deploy --prod --dir=dist",
"db:start": "npx -y firebase-tools emulators:start --only firestore",
"deploy:db": "firebase deploy --only firestore",
"deploy:site": "npm run build && netlify deploy --prod --dir=dist",
"db:start": "firebase emulators:start --only firestore",
"db:seed": "FIRESTORE_EMULATOR_HOST=127.0.0.1:8080 node scripts/seed-dev-scores.js",
"db:stop": "pkill -f '[c]loud-firestore-emulator'; pkill -f '[f]irebase.* emulators:'; true"
},
@ -22,6 +22,8 @@
"@cozy-games/leaderboard": "workspace:*",
"@cozy-games/utils": "workspace:*",
"firebase": "^12.11.0",
"firebase-tools": "^15.22.4",
"netlify-cli": "^26.1.0",
"web-component-base": "^4.1.2"
},
"author": "Ayo Ayco"

File diff suppressed because it is too large Load diff

View file

@ -4,5 +4,11 @@ packages:
- "sites/*"
allowBuilds:
'@firebase/util': false
'@parcel/watcher': false
esbuild: false
netlify-cli: false
protobufjs: false
re2: false
sharp: false
unix-dgram: false
web-component-base: false