From 698826c29bb9a31e664ddcfbe22db62edca009cb Mon Sep 17 00:00:00 2001 From: kOaDT Date: Mon, 5 Oct 2026 16:36:59 +0200 Subject: [PATCH 1/5] deps: ship tsx and dotenv as runtime dependencies --- package-lock.json | 36 ++---------------------------------- package.json | 4 ++-- 2 files changed, 4 insertions(+), 36 deletions(-) diff --git a/package-lock.json b/package-lock.json index 18bdd04..6bb05a9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@tailwindcss/typography": "^0.5.19", "better-sqlite3": "^12.6.2", "canvas-confetti": "^1.9.4", + "dotenv": "^17.2.3", "libxmljs2": "^0.37.0", "next": "15.2.2", "pdf-lib": "^1.17.1", @@ -23,6 +24,7 @@ "react-markdown": "^10.1.0", "react-pdf": "^10.3.0", "remark-gfm": "^4.0.1", + "tsx": "^4.21.0", "zod": "^4.4.1" }, "devDependencies": { @@ -36,7 +38,6 @@ "@types/react-dom": "^19", "cross-env": "^7.0.3", "cypress": "^15.10.0", - "dotenv": "^17.2.3", "eslint": "^9", "eslint-config-next": "16.0.6", "eslint-config-prettier": "^10.1.8", @@ -48,7 +49,6 @@ "tailwindcss": "^4", "ts-jest": "^29.4.6", "ts-node": "^10.9.2", - "tsx": "^4.21.0", "typescript": "^5", "wait-on": "^9.0.4" }, @@ -710,7 +710,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -727,7 +726,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -744,7 +742,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -761,7 +758,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -778,7 +774,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -795,7 +790,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -812,7 +806,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -829,7 +822,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -846,7 +838,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -863,7 +854,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -880,7 +870,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -897,7 +886,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -914,7 +902,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -931,7 +918,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -948,7 +934,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -965,7 +950,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -982,7 +966,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -999,7 +982,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1016,7 +998,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1033,7 +1014,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1050,7 +1030,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1067,7 +1046,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1084,7 +1062,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1101,7 +1078,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1118,7 +1094,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1135,7 +1110,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -6167,7 +6141,6 @@ "version": "17.4.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -6519,7 +6492,6 @@ "version": "0.27.7", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -7570,7 +7542,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -7752,7 +7723,6 @@ "version": "4.13.7", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz", "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==", - "dev": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -13608,7 +13578,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -15125,7 +15094,6 @@ "version": "4.21.0", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "~0.27.0", diff --git a/package.json b/package.json index 336c668..883aa24 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ "@tailwindcss/typography": "^0.5.19", "better-sqlite3": "^12.6.2", "canvas-confetti": "^1.9.4", + "dotenv": "^17.2.3", "libxmljs2": "^0.37.0", "next": "15.2.2", "pdf-lib": "^1.17.1", @@ -80,6 +81,7 @@ "react-markdown": "^10.1.0", "react-pdf": "^10.3.0", "remark-gfm": "^4.0.1", + "tsx": "^4.21.0", "zod": "^4.4.1" }, "devDependencies": { @@ -93,7 +95,6 @@ "@types/react-dom": "^19", "cross-env": "^7.0.3", "cypress": "^15.10.0", - "dotenv": "^17.2.3", "eslint": "^9", "eslint-config-next": "16.0.6", "eslint-config-prettier": "^10.1.8", @@ -105,7 +106,6 @@ "tailwindcss": "^4", "ts-jest": "^29.4.6", "ts-node": "^10.9.2", - "tsx": "^4.21.0", "typescript": "^5", "wait-on": "^9.0.4" } From 439a557b4d25461035c8464d3009e75893bd6d71 Mon Sep 17 00:00:00 2001 From: kOaDT Date: Mon, 5 Oct 2026 16:37:20 +0200 Subject: [PATCH 2/5] build: slim the Docker image with a multi-stage build --- .dockerignore | 5 +++++ Dockerfile | 23 +++++++++++++++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/.dockerignore b/.dockerignore index 6ff1d6b..0bbe8c9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -29,3 +29,8 @@ cypress/ .cursor/ .husky/ prisma/*.pem +# Untracked local tooling, absent from any CI build: keeps a locally built +# image identical to the published one. +.claude/ +.plan/ +.venv/ diff --git a/Dockerfile b/Dockerfile index bd4eaae..bfb315b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,17 @@ # Keep this default in sync with .nvmrc; tests/unit/node-version-parity.test.ts # fails the build if they drift. ARG NODE_VERSION=22 -FROM node:${NODE_VERSION}-alpine -RUN apk add --no-cache python3 make g++ gcc libxml2-dev libxslt-dev +FROM node:${NODE_VERSION}-alpine AS builder + +RUN apk add --no-cache python3 make g++ WORKDIR /app COPY package*.json ./ -RUN npm ci +# Cypress runs on the host against the container, never inside it: skip the +# binary its postinstall would otherwise download. +RUN CYPRESS_INSTALL_BINARY=0 npm ci && npm cache clean --force COPY . . @@ -22,7 +25,17 @@ RUN DATABASE_URL=file:/tmp/build.db npx prisma db push --skip-generate ENV NEXT_TELEMETRY_DISABLED=1 RUN DATABASE_URL=file:/tmp/build.db npm run build -RUN rm -f /tmp/build.db +# The entrypoint seeds through tsx, and both the seed and the Prisma config +# load dotenv: they are runtime dependencies, so the prune must keep them. +RUN npm prune --omit=dev \ + && rm -rf .next/cache /tmp/build.db \ + && chmod +x docker-entrypoint.sh + +FROM node:${NODE_VERSION}-alpine + +WORKDIR /app + +COPY --from=builder /app /app RUN mkdir -p /app/data /app/uploads /app/documents/invoices /app/logs @@ -32,7 +45,5 @@ ENV DATABASE_URL=file:/app/data/dev.db ENV PARTNER_SIGNING_KEY_PATH=/app/data/partner-signing-key.pem ENV NEXT_PUBLIC_BASE_URL=http://localhost:3000 -RUN chmod +x ./docker-entrypoint.sh - EXPOSE 3000 ENTRYPOINT ["./docker-entrypoint.sh"] From 69fd08e97a51a3f175ea2cf1371b238457a313b0 Mon Sep 17 00:00:00 2001 From: kOaDT Date: Mon, 5 Oct 2026 16:37:32 +0200 Subject: [PATCH 3/5] build: make the entrypoint fail on a missing package --- docker-entrypoint.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index a35fcfb..b506779 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -5,8 +5,9 @@ DB_FILE="${DATABASE_URL#file:}" if [ ! -f "$DB_FILE" ]; then echo "First run: initializing database..." - npx prisma db push --skip-generate - npx tsx prisma/seed.ts + # Without a TTY, npx would download a missing package instead of failing. + npx --no prisma db push --skip-generate + npx --no tsx prisma/seed.ts echo "Database initialized successfully." else # A pulled image brings models and challenges the mounted volume never saw. From cd2256d698663c6cb03e10356bd364d7fb1e42b5 Mon Sep 17 00:00:00 2001 From: kOaDT Date: Mon, 5 Oct 2026 16:37:43 +0200 Subject: [PATCH 4/5] ci: smoke test the Docker image offline --- .github/workflows/docker-build.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index ec309b9..3efbc58 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -5,6 +5,7 @@ on: branches: [main] paths: - "Dockerfile" + - ".dockerignore" - ".nvmrc" - "docker-compose.yml" - "docker-entrypoint.sh" @@ -33,3 +34,22 @@ jobs: - name: Build Docker image run: docker build -t oss-oopssec-store:test . + + # Offline, so a package missing from the image fails here instead of + # being fetched from the registry at startup. + - name: Smoke test the image offline + timeout-minutes: 5 + run: | + docker run -d --name smoke --network none oss-oopssec-store:test + until docker logs smoke 2>&1 | grep -F "Database initialized successfully." >/dev/null; do + [ "$(docker inspect -f '{{.State.Running}}' smoke)" = true ] || exit 1 + sleep 2 + done + until docker exec smoke wget -qO- http://127.0.0.1:3000/api/flags/count; do + [ "$(docker inspect -f '{{.State.Running}}' smoke)" = true ] || exit 1 + sleep 2 + done + + - name: Show container logs + if: failure() + run: docker logs smoke From be673ca9ccf038f0469d4ec1b1bdcee7a9e83647 Mon Sep 17 00:00:00 2001 From: kOaDT Date: Wed, 7 Oct 2026 22:16:42 +0200 Subject: [PATCH 5/5] 2.22.0 --- package-lock.json | 4 ++-- package.json | 2 +- packages/create-oss-store/package-lock.json | 4 ++-- packages/create-oss-store/package.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6bb05a9..12ab2a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "oss-oopssec-store", - "version": "2.21.1", + "version": "2.22.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "oss-oopssec-store", - "version": "2.21.1", + "version": "2.22.0", "license": "MIT", "dependencies": { "@prisma/client": "^6.19.1", diff --git a/package.json b/package.json index 883aa24..1c5555f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "oss-oopssec-store", "author": "kOaDT", - "version": "2.21.1", + "version": "2.22.0", "private": false, "license": "MIT", "repository": { diff --git a/packages/create-oss-store/package-lock.json b/packages/create-oss-store/package-lock.json index fbaf891..fbd5a91 100644 --- a/packages/create-oss-store/package-lock.json +++ b/packages/create-oss-store/package-lock.json @@ -1,12 +1,12 @@ { "name": "create-oss-store", - "version": "2.21.1", + "version": "2.22.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "create-oss-store", - "version": "2.21.1", + "version": "2.22.0", "license": "MIT", "dependencies": { "chalk": "5.6.2", diff --git a/packages/create-oss-store/package.json b/packages/create-oss-store/package.json index 99ccf19..1d118f2 100644 --- a/packages/create-oss-store/package.json +++ b/packages/create-oss-store/package.json @@ -1,6 +1,6 @@ { "name": "create-oss-store", - "version": "2.21.1", + "version": "2.22.0", "type": "module", "description": "Create a new OSS – OopsSec Store instance for web security CTF training", "author": "kOaDT",