chore: infra tooling and configs
This commit is contained in:
parent
7685a6dea7
commit
0532fd6d66
10 changed files with 11194 additions and 34 deletions
39
AGENTS.md
39
AGENTS.md
|
|
@ -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)
|
### 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
|
```bash
|
||||||
pnpm -F mnswpr run db:start # local DB emulator (mnswpr -> Firestore), standalone
|
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:seed # seed the running local emulator
|
||||||
pnpm -F mnswpr run db:stop # kill a stray/orphaned Firestore emulator holding :8080
|
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: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.
|
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.
|
||||||
|
|
||||||
|
|
|
||||||
22
README.md
22
README.md
|
|
@ -45,21 +45,31 @@ pnpm -F mnswpr run preview # preview its production build
|
||||||
|
|
||||||
## Infra (per-app, via local CLI)
|
## Infra (per-app, via local CLI)
|
||||||
|
|
||||||
Infra runs through local CLIs, never web dashboards. Each app owns its infra scripts
|
Every infra operation runs through local CLIs — never web dashboards — and every
|
||||||
under generic names (`deploy:db`, `deploy:site`, `db:start`, `db:seed`) — run them by
|
config/schema is codified in-repo. Two layers, both owned by the app:
|
||||||
targeting the app with pnpm's `-F` filter:
|
|
||||||
|
- **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
|
```bash
|
||||||
pnpm -F mnswpr run db:start # start the local DB emulator (Firestore) — needs Java, see app README
|
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: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 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: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
|
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.
|
(`pnpm -F <name> run deploy:db`), backed by whatever stack (and CLIs) that app declares.
|
||||||
`deploy:site` needs a one-time `npx netlify-cli login && npx netlify-cli link` per app.
|
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
|
See [apps/mnswpr/README.md](apps/mnswpr/README.md) for the game itself, and each package's
|
||||||
README for library usage.
|
README for library usage.
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,8 @@ Firebase and Netlify that have to be done by hand.
|
||||||
From this app directory (`apps/mnswpr`):
|
From this app directory (`apps/mnswpr`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx firebase login
|
pnpm -F mnswpr exec firebase login
|
||||||
npx firebase deploy --only firestore:rules,firestore:indexes --project prod
|
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).
|
- Uses committed [`firestore.rules`](firestore.rules) + [`firestore.indexes.json`](firestore.indexes.json).
|
||||||
|
|
|
||||||
|
|
@ -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`.
|
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.
|
**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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,23 +98,26 @@ Dev config lives in the committed [`app/.env.development`](../app/.env.developme
|
||||||
|
|
||||||
### One-time CLI setup
|
### 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
|
```bash
|
||||||
npx firebase login
|
pnpm -F mnswpr exec firebase login
|
||||||
```
|
```
|
||||||
|
|
||||||
### Deploy rules + indexes
|
### Deploy rules + indexes
|
||||||
|
|
||||||
Deploys go to **production** by default. Target a project explicitly with
|
The `deploy:db` script (`pnpm -F mnswpr run deploy:db`) deploys everything under
|
||||||
`--project`:
|
`firestore` to the **default** project. To target a specific project or a subset,
|
||||||
|
call the CLI directly:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# production (default alias 'prod')
|
# 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
|
# 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
|
> ⚠️ 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).
|
`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
|
> 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:
|
Everyday dev loop:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -139,8 +139,8 @@ Both aliases target the one project we actually have:
|
||||||
|
|
||||||
### 7. Deploy & verify
|
### 7. Deploy & verify
|
||||||
|
|
||||||
1. `npx firebase deploy --only firestore:rules,firestore:indexes --project prod`
|
1. `pnpm -F mnswpr exec firebase deploy --only firestore:rules,firestore:indexes --project prod`
|
||||||
2. Set the Netlify env vars (`VITE_LB_NAMESPACE=mw` + `VITE_FIREBASE_*`).
|
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
|
3. `pnpm dev`, win a game → confirm the write lands in **`mw-test-scores`**, and
|
||||||
the prod `mw-scores` is untouched (Firestore console).
|
the prod `mw-scores` is untouched (Firestore console).
|
||||||
4. Confirm reads on both `mw-scores` and `mw-test-scores` succeed under the new
|
4. Confirm reads on both `mw-scores` and `mw-test-scores` succeed under the new
|
||||||
|
|
|
||||||
36
apps/mnswpr/netlify.toml
Normal file
36
apps/mnswpr/netlify.toml
Normal 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"
|
||||||
|
|
@ -6,14 +6,14 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"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",
|
"dev:no-db": "vite",
|
||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"build:preview": "npm run build && npm run preview",
|
"build:preview": "npm run build && npm run preview",
|
||||||
"deploy:db": "npx -y firebase-tools deploy --only firestore",
|
"deploy:db": "firebase deploy --only firestore",
|
||||||
"deploy:site": "npm run build && npx -y netlify-cli deploy --prod --dir=dist",
|
"deploy:site": "npm run build && netlify deploy --prod --dir=dist",
|
||||||
"db:start": "npx -y firebase-tools emulators:start --only firestore",
|
"db:start": "firebase emulators:start --only firestore",
|
||||||
"db:seed": "FIRESTORE_EMULATOR_HOST=127.0.0.1:8080 node scripts/seed-dev-scores.js",
|
"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"
|
"db:stop": "pkill -f '[c]loud-firestore-emulator'; pkill -f '[f]irebase.* emulators:'; true"
|
||||||
},
|
},
|
||||||
|
|
@ -22,6 +22,8 @@
|
||||||
"@cozy-games/leaderboard": "workspace:*",
|
"@cozy-games/leaderboard": "workspace:*",
|
||||||
"@cozy-games/utils": "workspace:*",
|
"@cozy-games/utils": "workspace:*",
|
||||||
"firebase": "^12.11.0",
|
"firebase": "^12.11.0",
|
||||||
|
"firebase-tools": "^15.22.4",
|
||||||
|
"netlify-cli": "^26.1.0",
|
||||||
"web-component-base": "^4.1.2"
|
"web-component-base": "^4.1.2"
|
||||||
},
|
},
|
||||||
"author": "Ayo Ayco"
|
"author": "Ayo Ayco"
|
||||||
|
|
|
||||||
11087
pnpm-lock.yaml
11087
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -4,5 +4,11 @@ packages:
|
||||||
- "sites/*"
|
- "sites/*"
|
||||||
allowBuilds:
|
allowBuilds:
|
||||||
'@firebase/util': false
|
'@firebase/util': false
|
||||||
|
'@parcel/watcher': false
|
||||||
|
esbuild: false
|
||||||
|
netlify-cli: false
|
||||||
protobufjs: false
|
protobufjs: false
|
||||||
|
re2: false
|
||||||
|
sharp: false
|
||||||
|
unix-dgram: false
|
||||||
web-component-base: false
|
web-component-base: false
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue