diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2c8c3f8..c19dd54 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,6 +47,19 @@ the database on first boot), and starts a watcher that reloads on change. No `.e needed: the compose file supplies development defaults. If you do create one, its values win, so you can point at a real messaging transport or OAuth provider without editing the compose file. +The container installs its own `node_modules` into a volume rather than using the one on your +machine, which is built for your platform rather than the container's. Every boot checks that +volume against `package-lock.json` and reinstalls when the two have diverged, so pulling a branch +that adds or bumps a dependency needs nothing beyond a restart. To throw the volume away and +start from a clean install: + +```bash +docker compose -f docker-compose.dev.yml down +docker volume rm seamless-auth-dev_node-modules-dev +``` + +`docker compose -f docker-compose.dev.yml down -v` removes it too, along with the database. + To run the project rather than work on it, use `docker compose up` instead. That uses the published image and also serves the admin console at `/console`, which the dev stack does not bundle. See the Docker Quickstart in [README.md](./README.md). diff --git a/Dockerfile.dev b/Dockerfile.dev index 81b894f..d7eedd1 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -4,8 +4,8 @@ WORKDIR /app RUN apk add --no-cache postgresql-client netcat-openbsd -COPY package.json package-lock.json* ./ -RUN npm ci +COPY package.json package-lock.json* syncDevDeps.sh ./ +RUN sh ./syncDevDeps.sh COPY . . RUN mkdir -p ./keys diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index aaa840d..6a5401c 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -19,13 +19,18 @@ services: - '${API_PORT:-5312}:5312' volumes: - .:/app - - /app/node_modules + # node_modules stays out of the bind mount: the host tree is built for the host's + # platform. syncDevDeps.sh reinstalls into this volume whenever package-lock.json + # has moved on, since the volume itself outlives `up --build`. + - node-modules-dev:/app/node_modules # Dockerfile.dev has no build step, so the compiled entrypoint cannot run from a - # fresh clone. Generate dev signing keys, migrate (creating the database on first - # boot), then run the watcher against the TypeScript sources. + # fresh clone. Install any dependencies added since this container last booted, + # generate dev signing keys, migrate (creating the database on first boot), then + # run the watcher against the TypeScript sources. entrypoint: ['/bin/sh', '-c'] command: - > + ./syncDevDeps.sh && npx tsx src/scripts/initKeys.ts && (npm run migrate:up || (npm run db:create && npm run migrate:up)) && npm run dev:container @@ -88,3 +93,4 @@ services: volumes: pgdata-dev: + node-modules-dev: diff --git a/syncDevDeps.sh b/syncDevDeps.sh new file mode 100755 index 0000000..7460e77 --- /dev/null +++ b/syncDevDeps.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# Installs node_modules when it does not match package-lock.json. +# +# The dev stack keeps node_modules in its own volume so the host tree, built for the +# host's platform, never reaches the container. That volume outlives `docker compose +# up --build`, so without this the container keeps running whatever was installed the +# first time it booted, however far package.json has moved on since. +# +# Also run at image build time, so a fresh volume inherits a stamp that matches what +# the image already installed and the first boot does not reinstall it. +set -eu + +STAMP=node_modules/.package-lock-stamp +LOCK_HASH="$(md5sum package-lock.json | cut -d ' ' -f 1)" + +if [ -f "$STAMP" ] && [ "$(cat "$STAMP")" = "$LOCK_HASH" ]; then + echo "Dependencies match package-lock.json, skipping install." + exit 0 +fi + +echo "Installing dependencies from package-lock.json..." +npm ci --no-audit --no-fund +printf '%s' "$LOCK_HASH" > "$STAMP"