DockerfileExtra copied multiplayer/package.json alone and ran `npm install`,
so the image shipped whatever npm resolved at build time and the committed
multiplayer/package-lock.json described nothing that ran in production:
Dependabot alerts on it were meaningless either way. Copy the lockfile and
install with `npm ci --omit=dev` so the image is reproducible and the lock is
the source of truth for what ships.
The lock is refreshed with `npm update <dep>` per direct dependency, which
moves each to the newest version inside its existing caret range without
touching package.json. That is exactly what a lockfile-less `npm install`
resolves today (verified: a fresh `npm install --package-lock-only` from the
same package.json produces identical versions), so the image does not regress:
ws 8.19.0 -> 8.21.3 (covers the open alerts on ws < 8.21.0)
y-websocket 3.0.0 -> 3.1.0
yjs 13.6.29 -> 13.6.32
lib0 0.2.117, y-protocols 1.0.7, isomorphic.js 0.2.5 unchanged
package.json has no devDependencies and the lock has no `dev: true` entries,
so `--omit=dev` changes nothing today and only guards against future ones.
Validation:
- `npm ci` on the pre-update lock and `npm ci --omit=dev` on the updated lock
both succeed; `node --check server.mjs gateway.mjs` passes.
- Smoke start: server.mjs listens on PORT=3999 and logs "Multiplayer server
running"; gateway.mjs listens on PORT=3998 and logs its route table.
- The same COPY/RUN lines built on node:22-slim with the repo root as build
context (the context build-extra-image.yml uses) install the six locked
packages and pass `node --check`. A full DockerfileExtra build was skipped:
it is a single stage on an uncached multi-GB base image.
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>