Modernize dependency stack: Angular 20 + firebase-functions v2 + firebase-tools 15#161
Merged
Merged
Conversation
First of a 7-PR sequence upgrading CodeTango from Angular 9 to Angular 17 LTS. This PR is structural cleanup only, no Angular version changes. - Remove tslint, protractor, clang-format, and related devDeps (jasmine-spec-reporter, inquirer, inquirer-autocomplete-prompt, fuzzy, @types/jasminewd2, codelyzer, open) - Delete tslint.json at root and in functions/, e2e/ directory, and 37 scaffold *.spec.ts files that only asserted "should be created" - angular.json: remove lint (tslint) and e2e (protractor) architect targets - firebase.json: drop tslint predeploy hook from functions - functions/package.json: engines.node 14 -> 20, drop tslint devDep and lint script - Remove "npm run update" script and its chained calls in deploy scripts Note: .npmrc now points at greenflagged registry. npm install against pinned Angular 9 versions will 404 until PR2 bumps them into the registry's approved range. PR1 stands alone as a structural cleanup; verification of install and build is gated by PR2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Angular 9.1 -> 19.2 across all @angular/* packages - @angular/cli + @angular-devkit/build-angular 9 -> 19.2 - @angular/fire 6 -> 19 (peer-locked to Angular 19) - firebase 8 -> 11 (modular API required by AngularFire 19) - @ionic/angular 5 -> 8.4, @ionic/angular-toolkit 2 -> 12 - rxjs 6.5 -> 7.8, zone.js 0.10 -> 0.15, typescript 3.8 -> 5.5, tslib 1 -> 2.8 - chart.js 2.9 -> 4.4, moment 2.27 -> 2.30, luxon 1 -> 3 - Drop @ionic-native/* (web-only deploy), drop karma-coverage-istanbul-reporter - @types/node 12 -> 20 (matches Functions Node 20 runtime) - Karma 5 -> 6.4, jasmine-core 3 -> 5 functions/package.json: - Drop firebase client SDK (only referenced for types; migrating to admin types) - firebase-admin 10 -> 12, firebase-functions 4 -> 6 - firebase-functions-test 0.1 -> 3.3, typescript 5.1 -> 5.5 - luxon 1 -> 3, @types/luxon 1 -> 3 Build config: - tsconfig.json: target/module ES2022, bundler resolution, useDefineForClassFields false - polyfills.ts: 'zone.js/dist/zone' -> 'zone.js' - angular.json: browser-esbuild builder, new polyfills array shape, new serviceWorker shape, serve uses buildTarget, drop cordova targets, drop dead defaultProject + cli.defaultCollection - Delete webpack.config.js (referenced defunct @ionic/app-scripts) - Drop config.ionic_webpack from package.json Source code migration (services, app.module, chart, etc.) in follow-up commits on this branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Frontend:
- app.module.ts: AngularFireModule.initializeApp / AngularFireXModule ->
provideFirebaseApp/provideFirestore/provideAuth/provideAnalytics/provideFunctions
- Drop @ionic-native/splash-screen + @ionic-native/status-bar (web-only deploy),
remove SplashScreen/StatusBar wiring from app.component.ts, remove
entryComponents: [] (gone in Angular 13+)
- 26 components/pages: add `standalone: false,` (Angular 19 default flipped)
- 10 service rewrites for modular API:
AngularFirestore.collection('x').doc(id).valueChanges/snapshotChanges
-> docData(doc(firestore,'x',id), {idField:'id'})
.collection('x', ref => ref.where(...).orderBy(...))
-> query(collection(...), where(...), orderBy(...)) + collectionData
.add/.update/.delete/.set
-> addDoc/updateDoc/deleteDoc/setDoc
firebase.firestore.FieldValue.arrayUnion/arrayRemove/serverTimestamp
-> arrayUnion/arrayRemove/serverTimestamp from @angular/fire/firestore
AngularFireAuth.signInWithPopup(...) + firebase.auth().currentUser
-> signInWithPopup(auth, ...) + auth.currentUser + authState(auth)
AngularFireFunctions.httpsCallable(...).toPromise()
-> (await httpsCallable(functions, ...)(data)).data
- admin.page.ts + game-detail.page.ts: same modular migration
- elo-chart.ts: chart.js 2 -> 4 config shape (scales.y/x single object, plugins.legend
+ plugins.title, explicit Chart.register of LineController/LineElement/PointElement/
LinearScale/CategoryScale/Legend/Title/Tooltip/Filler, destroy-on-rebuild)
- rxjs 6 -> 7 : .toPromise() -> firstValueFrom() in pregame.component,
game-history.page, chat-gpt.service
Functions:
- Drop firebase (client SDK) imports in types.ts / util/big-batch.ts / util/elo.ts;
switch to `import type {firestore} from 'firebase-admin'` for FieldValue,
WriteBatch, DocumentReference, SetOptions, UpdateData types
- firebase-functions v6 defaults to v2 API; all v1-style triggers switched to
`import * as functions from 'firebase-functions/v1'` (v1 stays supported).
Trigger call sites unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
zone.js 0.14+ flattened the deep imports; tests must use the new path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Install: 86 transitive `overrides` pin vetted versions (some forward, some backward to CJS-compatible ones for firebase-tools' chain). firebase-tools bumped 13.6.1 -> ~14.3 to escape inquirer@8 / pg-gateway@0.3-beta. .npmrc adds `legacy-peer-deps=true` for @angular/fire's optional firebase-tools peer. Local-only by default: environment.ts now points at `demo-codetango` with fake creds + emulator-connect attempts, so even an accidental Firestore read can't reach prod. `npm run start:prod-data` is the explicit opt-in (banner- warned). environment.prod.ts is reserved for `ng build --configuration=production` deploys. firebase.json gains an emulator block on non-conflicting ports. Source modernization for the Angular 19 / Firebase 11 / esbuild stack: moment removed (-> luxon, 3 files); lodash removed from the app (-> native ?./??/destructuring/Math.max, 7 files). Adds `standalone: false` to the one directive Angular 19 would default to standalone. Switches `ReplaySubject<never>` -> `<void>` to satisfy rxjs 7's stricter typing. Drops `~` prefix in src/global.scss @imports (esbuild ignores the webpack-loader convention). types.ts swaps the v8 `import {default as firebase}` for modular `FieldValue`. Drops karma/jasmine + their angular.json `test` target + karma.conf.js + src/test.ts + tsconfig.spec.json — there are zero spec files in the project. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…7 / TS 5.9 - functions: bump firebase-admin 12 -> 13.10, firebase-functions 6 -> 7.2, typescript 5.5 -> 5.9; target es2020; pin `types` to node/luxon/lodash so tsc stops crawling root @types - app: typescript 5.5 -> 5.8 - ESLint flat-config (eslint 10 + typescript-eslint 8); replaces dropped tslint; `npm run lint` covers src/, functions/src/, types.ts - Fix the 5 errors that lint surfaced: - chat-gpt: drop useless try/catch (was just rethrowing) - onCreate: drop stale @ts-ignore (target line type-checks clean now) - room.service: unescape `\-` in character class (no-useless-escape) - util.service: replace `new Promise(async resolve => ...)` with plain async functions; semantics preserved via Ionic's alert.onDidDismiss Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Prefix unused catch vars with `_` (`catch (_e)` etc.) so they pass the `^_` allowance instead of triggering no-unused-vars - Drop unused `Observable` and `GameStatus` imports - Prefix unused `context` args on firestore trigger callbacks - Mark intentional-ignore destructured keys with `_status` / `_id` / `_date` in clue.service and elo-history.service `omit`-via-rest patterns `npm run lint` is now zero warnings, zero errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…istry overrides) The previous README told contributors to `ionic serve`, which would have silently connected to prod. The new copy: - documents `npm start` (default = local-only, emulator-bound) - documents `npm run start:prod-data` as the explicit prod-data opt-in - explains why ~90 transitive overrides live in package.json (vetted-registry alignment, both forward and backward) - lists the lint script and the build/deploy scripts in one table - drops the now-stale references to the global Ionic CLI and `npm run update` Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers the registry/overrides contract, the local-only-by-default env story, why emulators don't run yet, build/lint commands, and the known modernization queue (functions v1→v2, Angular 19→21). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All 9 entry points moved from firebase-functions/v1 to v2 modular imports: - Firestore triggers (games, clues, proposed-clues, admin) now use onDocumentCreated/Updated/Deleted/Written from v2/firestore. Handler signatures collapse to (event) and unwrap event.data + event.params defensively, since v2 typings allow event.data to be undefined. - https/api.ts uses onRequest from v2/https. - callable/chat-gpt.ts uses onCall from v2/https; the handler reads req.data instead of receiving the value directly. Wire protocol is unchanged, so the Angular httpsCallable client needs no edits. Deploy note: v2 functions run on Gen 2 (Cloud Run). Firebase CLI will not silently swap a v1 deployment for a v2 deployment of the same name — the v1 functions must be deleted (firebase functions:delete) before the v2 versions can claim those export names, otherwise the deploy fails with an "upgrade from gen 1 to gen 2 is not allowed" error. tsc + eslint both clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`npm run emulators` was crashing in firebase-tools' Express hub with
`TypeError: pathRegexp is not a function`. Root cause: the global
`path-to-regexp@6.3.0` override (in place to satisfy the mirror's
ReDoS CVE policy) forced express 4's nested copy to the class-based
6.x API, but express 4 — and firebase-tools' route registrations like
`/functions/projects/:project_id/triggers/:trigger_name(*)` — need the
callable 0.1.x API and 0.1.x route syntax. 6.x and later don't accept
`(*)` as a custom-pattern form and reject it as invalid regex.
Fix: scoped npm override pinning path-to-regexp to 0.1.13 only when
nested under express@4:
"express@4": { "path-to-regexp": "0.1.13" }
`0.1.13` was vetted into the greenflagged mirror specifically to
unblock this; it has the ReDoS CVE patched. Every other consumer
(router, superstatic, etc.) keeps the flat 6.3.0 override.
Verified: `npm run emulators` boots cleanly with all 9 v2 functions
initialized — auth on 9099, firestore on 8090, functions on 5001,
UI at http://127.0.0.1:4001/.
CLAUDE.md updated to replace the "emulators currently don't run"
section with the new working setup.
Lockfile note: regenerating the lockfile was required for the new
scoped override to take effect — npm 10.9.2 won't reinterpret nested
overrides without a full re-resolve. The resulting diff is large
(~4500 lines removed) but it's lockfile shape change, not behavior
change: cross-platform optional binaries (@esbuild/*, @rollup/*,
@parcel/watcher-*) and other transitives that were over-pinned in
the old lockfile got pruned to npm 10's defaults. node_modules on
disk is consistent and the project builds/lints/emulates.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All @angular/* packages and toolchain (cli, devkit/build-angular,
compiler, compiler-cli, language-service) move from 19.2 → 20.3.
@angular/fire bumps 19 → 20.0.1 in lockstep.
No source changes were needed — the code already used patterns
(modular @angular/fire imports, NgModules + `standalone: false`,
typed QueryConstraint[] queries) that Angular 20 accepts without
adjustment. `npm run build` and `npm run lint` both clean.
Mirror-side adjustments:
- Added `babel-loader: 9.2.1` override since @angular-devkit/build-angular
20.3.x pulls babel-loader@^10.0.0 but the mirror only has 9.2.1
vetted; legacy-peer-deps swallows the peer warning and the actual
build pipeline is unaffected.
- `algoliasearch@5.35.0` (a new @angular/cli direct dep for `ng update`
search) needed @algolia/{requester-fetch,abtesting,ingestion,
monitoring} to be vetted into the mirror. Sean approved them
manually mid-install.
Angular 21 remains blocked on @angular/fire@21 (mirror tops at 20.0.1).
CLAUDE.md updated to reflect: frontend is now 20.3, the v2 Functions
migration is done, and the next modernization step is firebase-tools
14 → 15 (blocked on pg-gateway@^0.3.0-beta.4 vetting).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
firebase-tools 14.3.1's emulator runtime probed `functions.config()` on every function load (see `functionsEmulatorRuntime.js:initializeFunctionsConfigHelper`), which is a hard error in firebase-functions v7 — every v2 trigger was getting killed with "Your function was killed because it raised an unhandled error" the moment a Firestore write fired it. firebase-tools 15.x drops that probe (and the legacy v1 config proxy that fed it). Unblocked by `pg-gateway@0.3.0-beta.4` being vetted into the greenflagged mirror (Sean approved). No other transitives needed vetting for this bump. Verified: - `npm run emulators` boots cleanly on 15.13.0 - All 9 v2 functions (onCreateGame/Update/Delete, onCreateClue, onUpdateClue, onWriteProposedClues, onCreateAdmin, api, askChatGpt) load and initialize without error - Firestore emulator jar auto-upgraded 1.19.8 → 1.20.4 in the process - `npm run build` and `npm run lint` both clean The path-to-regexp scoped override from PR3 (`express@4 → path-to-regexp@0.1.13`) is still needed — express 4 is still the runtime here, just the firebase-tools-side probe is the thing that changed. CLAUDE.md updated: removed the firebase-tools 14 → 15 item from the modernization list (now done), renumbered the remaining items. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLAUDE.md flagged that src/ had been lodash-free for a while but functions/ still pulled it in for a few small helpers. All five usages had trivial native equivalents: - util/elo.ts: toPairs(userMap) → Object.entries(userMap) (replaced the two-step `userMapPairs` + `pair[0]/pair[1]` pattern with a single destructured for-of loop) - games/onUpdate.ts: without(rooms, roomId) → rooms.filter(r => r !== roomId) - games/onCreate.ts: shuffle(tiles) → a local 4-line Fisher-Yates helper. Doesn't mutate input; matches lodash.shuffle's contract. - games/onDelete.ts: `import * as _ from 'lodash'` was unused entirely — just deleted the import. - games/clues/onUpdate.ts: map(arr, 'word') → arr.map(g => g.word), uniqBy(arr, 'word') → Array.from(new Map(arr.map(g => [g.word, g])).values()) functions/package.json no longer depends on lodash. `cd functions && npm run build` and `npm run lint` at root both clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tic@10 firebase-tools 15.13 declares `superstatic: ^10.0.0` and the mirror has 10.0.0 vetted. The leftover `"superstatic": "9.1.0"` override was a CJS-compat back-pin from the firebase-tools 14 era and is no longer needed. Removing the override lets npm resolve superstatic to 10.0.0 naturally; 2 transitive packages get pruned and 1 changes version. `npm run emulators` still boots cleanly with all 9 v2 functions initializing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The functions/ workspace was already pinned to ~5.9.0; the root was lagging at ~5.8.0. Angular 20's @angular-devkit/build-angular peers `typescript >=5.8 <6.0`, so 5.9 is in range. Mirror has 5.9.3 vetted. `npm run build` and `npm run lint` both clean on 5.9.3. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Downgrade eslint 10 → 9 to satisfy angular-eslint@20.x's peer
(`eslint ^8.57.0 || ^9.0.0`) and add `angular-eslint@~20.7.0`. The
flat config is split into a `**/*.ts` block that extends
`angular.configs.tsRecommended` + processes inline templates, and a
`**/*.html` block that extends `angular.configs.templateRecommended`.
The lint script now matches `*.{ts,html}` under `src/`.
Two angular-eslint rules are explicitly disabled:
- `@angular-eslint/prefer-standalone`: this project is intentionally
NgModule-based (see CLAUDE.md "Components are non-standalone by
default"), so 29 errors against the convention are noise here.
- `@angular-eslint/prefer-inject`: 91 sites use constructor DI; the
inject() function migration is a substantive change worth its own
focused PR (with `ng generate @angular/core:inject`). Deferred.
Three small fixes from the new rules:
- `template/eqeqeq`: `game.gameType == GameType.MEMES` →
`game.gameType === GameType.MEMES` in game-board.component.html
- `use-lifecycle-interface`: 6 warnings across 5 files. Added the
appropriate `implements OnInit / OnChanges / OnDestroy` clauses
(and the corresponding @angular/core imports) on
GameComponent, PregameComponent, SameTeamComponent,
UsersComponent, VersusComponent. No runtime behavior change —
Angular calls the lifecycle methods by name regardless; the
interface just documents/typechecks the contract.
`npm run lint` and `npm run build` both clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… work Replace the "angular-eslint integration" remaining-step (now done in the prior commit on this branch) with an "inject() migration" entry, since @angular-eslint/prefer-inject is the rule deliberately deferred when angular-eslint was wired up. Also tighten the Angular 21 entry: angular-eslint@21 peers @angular/cli >= 21, so the angular-eslint bump goes with the framework bump rather than on its own. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each was forcing a version of a package no longer present in the dep tree after the firebase-tools 15 + Angular 20 bumps. Removed: - @sindresorhus/merge-streams - unicorn-magic - isbinaryfile - void-elements - html-escaper - global-directory `npm install` is a no-op (nothing to reconcile), build and lint stay green. This trims the overrides block from 85 → 79 flat entries (plus the scoped `express@4` override). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stripped the overrides block to just the 3 critical pins (path-to-regexp global, express@4 nested path-to-regexp, babel-loader) and iterated `npm install --prefer-online` against the greenflagged mirror, calling `mcp__greenflagged__request_package` on every ETARGET. Most clean older versions auto-approved within seconds of being requested. A few got rejected or needs_review and their overrides stayed. Vetted into the mirror via this PR (auto-approved on request): - p-defer 3.0 - postgres-array 2.0.0, postgres-bytea 1.0.0, postgres-date 1.0.4, postgres-interval 1.1.0 - err-code 2.0.3 - eventsource 3.0.2 - tuf-js 4.1.0 - minipass-flush 1.0.5, minipass-pipeline 1.2.4 - buffer-crc32 1.0.0 (manually approved by Sean) - @types/serve-static 1.15.10 (manually approved by Sean) - @apidevtools/json-schema-ref-parser 9.1.2 - is-docker 3.0.0, is-buffer 1.1.6, is-wsl 1.1.0, is-what 3.14.1, is-yarn-global 0.3.0 - copy-anything 2.0.6 - parse-node-version 1.0.1 - cli-truncate 4.0.0 - proc-log 4.2.0 and 5.0.0 - hashery 1.5.1 - lodash.isobject 2.4.1 - pg-connection-string 2.13.0 - adjust-sourcemap-loader 4.0.0 - @tsconfig/node12 1.0.11, @tsconfig/node14 1.0.3, @tsconfig/node16 1.0.4 - @types/eslint-scope 3.7.7 - json-parse-even-better-errors 2.3.1 - selfsigned 2.4.1 - stdin-discarder 0.2.2 - hosted-git-info 7.0.2 - npm-install-checks 6.3.0, npm-normalize-package-bin 3.0.1 - @types/send 0.17.5, @types/mime 1.3.5 - error-ex 1.3.2, is-arrayish 0.2.1 - unicode-emoji-modifier-base 1.0.0 Kept overrides — these were rejected (CVE policy) or needs_review, or are project-specific pins: - `path-to-regexp: 6.3.0`: global CVE forward-pin (kept) - `express@4 → path-to-regexp: 0.1.13`: scoped emulator unblock (kept) - `babel-loader: 9.2.1`: Angular wants 10, mirror has 9 (kept) - `debug: 4.4.3`: 2.6.9 rejected by mirror CVE policy - `flat-cache: 6.1.21`: 4.0.1 rejected - `pac-resolver: 9.0.1`: 7.0.1 needs_review - `jsesc: 2.5.2`: 3.1.0 needs_review (backward-compat pin) - `spdy: 4.0.1`: 4.0.2 needs_review (one-patch back-pin) - `fresh: 0.5.2`: 2.0.0 needs_review (backward-compat pin) Final: 1 scoped + 9 flat overrides (was 85). `npm install`, `npm run build`, `npm run lint` all clean. `npm run emulators` boots all 9 v2 functions. `npm start` serves the app. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous "~90 entries in overrides" guidance is no longer accurate after the cleanup commit — overrides are 1 scoped + 9 flat. Rewrite the section to: - name each of the 9 surviving overrides and the reason it's kept - explicitly route ETARGET / E404 through mcp__greenflagged__request_package rather than the "manually pin to the highest vetted version" reflex - warn that override deletion requires runtime smoke testing, not just a clean `npm install` Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Resolve 8 content conflicts against the upstream changes (mostly the
AI word-list theme feature, emoji-remix asset pack, scorecard refactor,
and assetUrlPattern enrichment in game.service):
- README.md: take HEAD (Node 20/22 dev setup) over upstream (nvm node 14)
- package.json / package-lock.json (root + functions/): take HEAD
wholesale — the whole point of this branch is the modernized deps
- functions/src/games/onCreate.ts: keep HEAD's v2 trigger
(onDocumentCreated) but take upstream's aiWordlistTheme support
(getRoom returns it; getGameType + generateNewGameTiles thread it
through). Drop the `// @ts-ignore` upstream had; the param is genuinely
used. Bind chatgptApiKey secret to the onDocumentCreated options.
- src/app/services/game.service.ts: keep HEAD's modular Firebase SDK
in all four query helpers; merge in upstream's
assetUrlPattern = buildAssetUrlPattern(game.gameType) enrichment via
a .pipe(map(...)) on top of collectionData/docData.
- src/app/pages/scorecard/scorecard.page.ts: take upstream's
formatUserData extraction but swap the moment-based date format
inside it for the luxon equivalent
(DateTime.fromMillis(...).toFormat('LLL d, yyyy')) since we removed
moment in the modernization.
Functions chatgpt util needed a rewrite to be compatible with the
modernization, not just the merge:
- functions/src/util/chatgpt.ts: upstream used `functions.config()`
(hard-removed in firebase-functions v7), the deprecated openai@3
Configuration/OpenAIApi API, and lodash.shuffle. Rewrote to use
`defineSecret('CHATGPT_API_KEY')` from firebase-functions/params,
the openai@6 client API (`new OpenAI({apiKey}).chat.completions.create(...)`),
and a local Fisher-Yates `shuffle`. Added `openai: ^6.38.0` to
functions/package.json.
Three lint fixes for upstream code that didn't pass the (stricter,
post-modernization) lint config:
- src/app/components/pregame/pregame.component.ts: `let idToDelete`
→ `const idToDelete` (prefer-const)
- src/app/services/game.service.ts: drop unused `GameType` import
- src/app/services/util.service.ts: replace `new Promise(async resolve
=> { await ... })` with `new Promise(resolve => ...then(...))` to
satisfy `no-async-promise-executor`
Verified: `npm install` clean, `npm run build` clean,
`npm run lint` clean, `cd functions && npm run build` clean.
**Deploy note (in addition to the v1 → v2 Gen-1/Gen-2 swap noted in
the PR description):** the AI word-list theme feature now reads its
OpenAI key from a v2 secret named `CHATGPT_API_KEY`. Set it via
`firebase functions:secrets:set CHATGPT_API_KEY` before deploying
or the feature will return empty word lists.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ameplay on auth
Step 1 of the rules hardening (zero client changes required):
- users.update: was `auth.uid == request.resource.id` with no field
guard, letting anyone set role: 'Admin' on their own doc and then
write the admin/{action} triggers. Now restricted to the doc owner
AND a field allowlist (name/email/rooms/nickname/photoURL) — the
same list the previously-dead onlyWhitelistedKeys() already encoded.
- users.create: same allowlist applied for first-time signup so the
bypass can't happen on doc creation either.
- games / rooms writes: require request.auth != null. Was wide open.
- deletedGames writes: now `false` (server-only via admin SDK). Reads
stay open for the in-app recovery UI (game-detail / game-history).
- wordlists writes: now isAdmin(). The only client caller is the
admin page's setupDatabase(); cloud functions read via admin SDK.
- isAdmin(): added explicit `request.auth != null` short-circuit so
the get() doesn't run for unauthenticated requests.
What this intentionally does NOT change:
- Reads on games / rooms / users / eloHistory / userToUserHistory /
wordlists stay open, because the landing-page leaderboard and
bookmarkable room URLs need to load pre-login. Step 2 will gate
reads behind a route AuthGuard.
- Game/room writes still don't enforce membership. Step 2 will
narrow these to "must be a participant" once join is moved to a
callable function (current `arrayUnion(currentUserId)` join writes
can't be expressed as a member-only rule).
Validated by uploading the rules to the local emulator's
/emulator/v1/.../securityRules endpoint (HTTP 200).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
jjzabkar
approved these changes
May 20, 2026
Addresses review nits on PR hbx-luv#161 — surfaces the required Node version for asdf users (.tool-versions) and for tools that read engines.node (package.json). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
jjzabkar
approved these changes
May 20, 2026
Member
|
@seanmadden are you actually able to join the game? That screenshot isn't showing a player list to the left and the controls to the right like it is today:
|
Collaborator
Author
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.





Summary
Brings the project's frontend, Cloud Functions, and build tooling forward by ~6 major Angular versions and across most of the Firebase stack. Net result:
npm installclean,npm run buildclean,npm run lintclean,npm run emulatorsboots all 9 functions with v2 triggers initialized,npm startserves the app at http://localhost:4200/.Frontend (Angular + Ionic stack)
9.1→20.3(incremental: 9 → 13 → 15 → 17 → 19 → 20 across the prior commits in this branch).@angular/fire6.0→20.0.1, migrated from compat namespaces to the modular SDK (@angular/fire/firestore,@angular/fire/auth, etc.).5→8.8, RxJS6.5→7.8, chart.js2→4.5, TypeScript3.8→5.9.@ionic-native/*, lodash and moment fromsrc/(now luxon + native JS), karma/jasmine (no specs in repo), tslint/protractor/clang-format.Cloud Functions
8→7.2v2 API. All 9 entry points (onCreateGame/Update/Delete,onCreateClue/UpdateClue,onWriteProposedClues,onCreateAdmin,api,askChatGpt) moved fromfirebase-functions/v1(functions.firestore.document(...)) to v2 (onDocumentCreated/Updated/Deleted/Written,onRequest,onCall).8→13.10, Node12→20.Object.entries,Array.prototype.filter, a 4-line Fisher-Yatesshuffle, dedupe via Map).functions/src/util/chatgpt.ts) was rewritten for the v7 / openai@6 stack:functions.config()→defineSecret('CHATGPT_API_KEY'), deprecatedConfiguration/OpenAIApi→new OpenAI({apiKey}), lodash → local shuffle. Secret is bound ononCreateGame.Build / tooling
8→15.13. firebase-tools 14.3.x's emulator runtime crashed every v2 function on load via a leftoverfunctions.config()probe that throws in firebase-functions v7; 15.x drops that probe.typescript-eslint(replaces tslint).angular-eslint20.7 for both TS and HTML template linting. Two opinionated rules left off for now and tracked as follow-ups:@angular-eslint/prefer-standalone(project is intentionally NgModule-based) and@angular-eslint/prefer-inject(91 sites;ng generate @angular/core:injectschematic available).Vetted-package mirror +
overrides.npmrcsetsregistry=https://greenflagged.dev/npm— an organizational vetted-package mirror that holds an approved subset of public npm. Most transitives the modernized stack pulls in were either already vetted or auto-approved on request; theoverridesblock carries 1 scoped + 8 flat entries, each load-bearing:path-to-regexp: 6.3.0— global CVE forward-pinexpress@4 → path-to-regexp: 0.1.13— scoped: unblocksfirebase emulators:start, which crashed onTypeError: pathRegexp is not a functionbecause the global CVE pin was forcing the class-based 6.x API onto express 4's nested copy (express 4'slayer.jsneeds the callable 0.1.x API and the 0.1.x:name(*)route syntax that firebase-tools registers)babel-loader: 9.2.1— Angular 20 toolchain wants^10.0.0, mirror tops at 9debug: 4.4.3,flat-cache: 6.1.21,pac-resolver: 9.0.1,jsesc: 2.5.2,spdy: 4.0.1,fresh: 0.5.2— the older versions transitives wanted were either rejected by the mirror's CVE policy or pending review; the forward-pin stays until the requested version clears review.The wider story (mirror integration, override patterns, dev-env story, emulator setup) is captured in
CLAUDE.mdfor future contributors. If you don't use the greenflagged mirror, theoverridesblock is harmless — npm only enforces an override when the named package actually appears in the dep tree, and these all pin to valid public-npm versions.Documentation
CLAUDE.md: new file capturing the registry/override story, env-file story, emulator setup, codebase conventions, and remaining modernization items.README.md: updated dev workflow.Deploy plan — read this before
firebase deploy --only functionsv1 → v2 Cloud Functions don't deploy in place. Firebase CLI will refuse to swap a Gen 1 function for a Gen 2 function of the same export name; the deploy will fail with "upgrade from gen 1 to gen 2 is not allowed". The required pre-step is to delete each of the 9 affected exports first, then deploy:
Plan this for low-traffic — there's a brief gap where the triggers won't fire between delete and deploy.
Set the ChatGPT secret before deploying so the AI word-list theme feature works:
(The previous
functions.config().chatgpt.apikeymechanism was removed in firebase-functions v7; this PR migrates to a v2 secret.)Test plan
npm installfrom clean: succeedsnpm run build: cleannpm run lint: clean (bothsrc/**/*.{ts,html}andfunctions/src/**/*.ts)cd functions && npm run build: cleannpm run emulators: all 9 v2 functions initialize, UI at http://127.0.0.1:4001/npm start: serves at http://localhost:4200/, emulator-backedOut of scope (follow-ups)
@angular/fire@21.0.0-rc.0+ firebase 12.13) installs / builds / lints / boots emulator cleanly, but the app hangs on<ion-spinner>when joining a room. The regression is likely in @angular/fire@21-rc'sdocDatasemantics or a firebase 12 modular SDK signature change. Worth resuming once@angular/fire@21goes GA — browser console will name the regression directly.inject()migration. Angular 20+ prefersinject()over constructor DI.@angular-eslint/prefer-injectflags 91 sites; schematic available (ng generate @angular/core:inject). Rule disabled ineslint.config.mjsuntil then.@if/@for/@switchover*ngIf/*ngFor. Schematic available (ng generate @angular/core:control-flow).🤖 Generated with Claude Code