From 1237495292654a1317e0a838c59745a037db9202 Mon Sep 17 00:00:00 2001 From: Gabriel Pan Gantes Date: Sun, 17 May 2026 09:51:12 +0200 Subject: [PATCH] Add CI for vercel-node-mongo + bring pnpm setup up to repo standard PR #37 landed the initial pnpm migration for this project with a minimal config (no preinstall audit, no full safety stack). This PR brings it in line with the same hardening every other migrated project ships with, then adds CI. pnpm setup brought up to standard: package.json - engines.node >=22.11.0 -> >=24.0.0 - engines.pnpm >=11.0.0 added - devEngines.runtime with onFail: error - scripts.preinstall: pnpm audit && pnpm audit signatures - scripts.lint:lockfile: pnpm install --frozen-lockfile pnpm-workspace.yaml - full safety stack: - minimumReleaseAge 10080 -> 20160 (7d -> 14d) - minimumReleaseAgeIgnoreMissingTime: true - engineStrict: true - trustPolicy: no-downgrade + trustPolicyIgnoreAfter 90d - blockExoticSubdeps: true - savePrefix: "" - resolutionMode: highest - minimumReleaseAgeExclude: '@pluggyai/*' - allowBuilds: {} - overrides: {} .gitignore - block package-lock.json and yarn.lock .npmrc removed (workspace.yaml is the single source of truth) Dependency bump required to make preinstall audit pass: - mongodb 3.6.6 -> 3.6.10 (same minor, patches GHSA-vxvm-qww3-2fh7: driver may publish events containing auth data) Source fixes surfaced by tsc --noEmit in strict mode: - api/notifications.ts now lazy-constructs PluggyClient inside the handler and guards both env vars (was throwing at module import time when env was missing, and passing string | undefined into PluggyClient which strictly requires string). - lib/db.ts now guards MONGO_URI before passing it to MongoClient. Plumbing for CI: - new tsconfig.json (ES2022, NodeNext, strict, isolatedModules, noEmit; includes api/** and lib/**) - typescript 5.9.3 devDependency - typecheck / lint / test / build scripts (all -> tsc --noEmit) CI workflow (.github/workflows/vercel-mongo-ci.yml): - path-filtered on examples/vercel-node-mongo/** + the workflow - concurrency-cancels older runs on the same ref - Node 24, pnpm 11.1.1, install --frozen-lockfile, then lint / test / build --- .github/workflows/vercel-mongo-ci.yml | 53 +++++++++++++++++ examples/vercel-node-mongo/.gitignore | 6 +- examples/vercel-node-mongo/.npmrc | 7 --- .../vercel-node-mongo/api/notifications.ts | 9 ++- examples/vercel-node-mongo/lib/db.ts | 4 ++ examples/vercel-node-mongo/package.json | 25 ++++++-- examples/vercel-node-mongo/pnpm-lock.yaml | 38 ++++++++----- .../vercel-node-mongo/pnpm-workspace.yaml | 57 ++++++++++++++++++- examples/vercel-node-mongo/tsconfig.json | 14 +++++ 9 files changed, 183 insertions(+), 30 deletions(-) create mode 100644 .github/workflows/vercel-mongo-ci.yml delete mode 100644 examples/vercel-node-mongo/.npmrc create mode 100644 examples/vercel-node-mongo/tsconfig.json diff --git a/.github/workflows/vercel-mongo-ci.yml b/.github/workflows/vercel-mongo-ci.yml new file mode 100644 index 0000000..851db9d --- /dev/null +++ b/.github/workflows/vercel-mongo-ci.yml @@ -0,0 +1,53 @@ +name: vercel-node-mongo CI + +on: + push: + branches: [master] + paths: + - examples/vercel-node-mongo/** + - .github/workflows/vercel-mongo-ci.yml + pull_request: + paths: + - examples/vercel-node-mongo/** + - .github/workflows/vercel-mongo-ci.yml + +concurrency: + group: vercel-mongo-${{ github.ref }} + cancel-in-progress: true + +jobs: + ci: + name: install / lint / test / build + runs-on: ubuntu-latest + defaults: + run: + working-directory: examples/vercel-node-mongo + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up pnpm + uses: pnpm/action-setup@v4 + with: + version: 11.1.1 + run_install: false + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: pnpm + cache-dependency-path: examples/vercel-node-mongo/pnpm-lock.yaml + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Lint + run: pnpm run lint + + - name: Test + run: pnpm run test + + - name: Build + run: pnpm run build diff --git a/examples/vercel-node-mongo/.gitignore b/examples/vercel-node-mongo/.gitignore index 1373f1d..d4e6398 100644 --- a/examples/vercel-node-mongo/.gitignore +++ b/examples/vercel-node-mongo/.gitignore @@ -1,4 +1,8 @@ .vercel .env -node_modules \ No newline at end of file +node_modules + +# Block npm/yarn lockfiles — this project is pnpm-only +package-lock.json +yarn.lock \ No newline at end of file diff --git a/examples/vercel-node-mongo/.npmrc b/examples/vercel-node-mongo/.npmrc deleted file mode 100644 index 26116ac..0000000 --- a/examples/vercel-node-mongo/.npmrc +++ /dev/null @@ -1,7 +0,0 @@ -save-exact=true -registry=https://registry.npmjs.org/ -engine-strict=true -audit-level=moderate -update-notifier=false -fund=false - diff --git a/examples/vercel-node-mongo/api/notifications.ts b/examples/vercel-node-mongo/api/notifications.ts index 25dd1e0..8f5fbd4 100644 --- a/examples/vercel-node-mongo/api/notifications.ts +++ b/examples/vercel-node-mongo/api/notifications.ts @@ -4,9 +4,14 @@ import { saveItem } from '../lib/items' const { PLUGGY_CLIENT_ID, PLUGGY_CLIENT_SECRET } = process.env -const pluggyClient = new PluggyClient({ clientId: PLUGGY_CLIENT_ID, clientSecret: PLUGGY_CLIENT_SECRET }) - export default async (req: VercelRequest, res: VercelResponse) => { + if (!PLUGGY_CLIENT_ID || !PLUGGY_CLIENT_SECRET) { + res.status(500).json({ message: 'Missing PLUGGY_CLIENT_ID or PLUGGY_CLIENT_SECRET' }) + return + } + + const pluggyClient = new PluggyClient({ clientId: PLUGGY_CLIENT_ID, clientSecret: PLUGGY_CLIENT_SECRET }) + const { id, event } = req.body if (!id) { res.status(400).json({ message: 'id parameter missing in body request.' }) diff --git a/examples/vercel-node-mongo/lib/db.ts b/examples/vercel-node-mongo/lib/db.ts index 88443c1..5ac5d44 100644 --- a/examples/vercel-node-mongo/lib/db.ts +++ b/examples/vercel-node-mongo/lib/db.ts @@ -3,6 +3,10 @@ import { MongoClient } from 'mongodb' const { MONGO_URI } = process.env export function getClient() { + if (!MONGO_URI) { + throw new Error('Missing MONGO_URI environment variable') + } + return new MongoClient(MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true diff --git a/examples/vercel-node-mongo/package.json b/examples/vercel-node-mongo/package.json index 3f27a6b..f854393 100644 --- a/examples/vercel-node-mongo/package.json +++ b/examples/vercel-node-mongo/package.json @@ -1,16 +1,33 @@ { "name": "pluggy-vercel-node-mongo-example", + "packageManager": "pnpm@11.1.1", "engines": { - "node": ">=22.11.0" + "node": ">=24.0.0", + "pnpm": ">=11.0.0" + }, + "devEngines": { + "runtime": { + "name": "node", + "version": ">=24.0.0", + "onFail": "error" + } + }, + "scripts": { + "preinstall": "pnpm audit && pnpm audit signatures", + "lint:lockfile": "pnpm install --frozen-lockfile", + "typecheck": "tsc --noEmit", + "lint": "tsc --noEmit", + "test": "tsc --noEmit", + "build": "tsc --noEmit" }, - "packageManager": "pnpm@11.1.1", "dependencies": { - "mongodb": "3.6.6", + "mongodb": "3.6.10", "pg": "8.6.0", "pluggy-sdk": "0.7.1" }, "devDependencies": { "@types/mongodb": "3.6.12", - "@vercel/node": "1.10.0" + "@vercel/node": "1.10.0", + "typescript": "5.9.3" } } diff --git a/examples/vercel-node-mongo/pnpm-lock.yaml b/examples/vercel-node-mongo/pnpm-lock.yaml index 0ebf9f4..2fad086 100644 --- a/examples/vercel-node-mongo/pnpm-lock.yaml +++ b/examples/vercel-node-mongo/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: mongodb: - specifier: 3.6.6 - version: 3.6.6 + specifier: 3.6.10 + version: 3.6.10 pg: specifier: 8.6.0 version: 8.6.0 @@ -24,6 +24,9 @@ importers: '@vercel/node': specifier: 1.10.0 version: 1.10.0 + typescript: + specifier: 5.9.3 + version: 5.9.3 packages: @@ -34,8 +37,8 @@ packages: '@types/mongodb@3.6.12': resolution: {integrity: sha512-49aEzQD5VdHPxyd5dRyQdqEveAg9LanwrH8RQipnMuulwzKmODXIZRp0umtxi1eBUfEusRkoy8AVOMr+kVuFog==} - '@types/node@25.7.0': - resolution: {integrity: sha512-z+pdZyxE+RTQE9AcboAZCb4otwcrvgHD+GlBpPgn0emDVt0ohrTMhAwlr2Wd9nZ+nihhYFxO2pThz3C5qSu2Eg==} + '@types/node@25.6.0': + resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} '@vercel/node@1.10.0': resolution: {integrity: sha512-11FIFoF4v9m/jAOSamwBDMsS2yIDa0fSnVhR70bbcUdBcu3BGGMCpg+jpA6rvkQSOa/WbTRvvu9hFh5bOsZxrw==} @@ -84,8 +87,8 @@ packages: memory-pager@1.5.0: resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} - mongodb@3.6.6: - resolution: {integrity: sha512-WlirMiuV1UPbej5JeCMqE93JRfZ/ZzqE7nJTwP85XzjAF4rRSeq2bGCb1cjfoHLOF06+HxADaPGqT0g3SbVT1w==} + mongodb@3.6.10: + resolution: {integrity: sha512-fvIBQBF7KwCJnDZUnFFy4WqEFP8ibdXeFANnylW19+vOwdjOAvqIzPdsNCEMT6VKTHnYu4K64AWRih0mkFms6Q==} engines: {node: '>=4'} peerDependencies: aws4: '*' @@ -226,8 +229,13 @@ packages: engines: {node: '>=4.2.0'} hasBin: true - undici-types@7.21.0: - resolution: {integrity: sha512-w9IMgQrz4O0YN1LtB7K5P63vhlIOvC7opSmouCJ+ZywlPAlO9gIkJ+otk6LvGpAs2wg4econaCz3TvQ9xPoyuQ==} + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.19.2: + resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -255,15 +263,15 @@ snapshots: '@types/mongodb@3.6.12': dependencies: '@types/bson': 4.2.4 - '@types/node': 25.7.0 + '@types/node': 25.6.0 - '@types/node@25.7.0': + '@types/node@25.6.0': dependencies: - undici-types: 7.21.0 + undici-types: 7.19.2 '@vercel/node@1.10.0': dependencies: - '@types/node': 25.7.0 + '@types/node': 25.6.0 ts-node: 8.9.1(typescript@3.9.3) typescript: 3.9.3 @@ -297,7 +305,7 @@ snapshots: memory-pager@1.5.0: optional: true - mongodb@3.6.6: + mongodb@3.6.10: dependencies: bl: 2.2.1 bson: 1.1.6 @@ -419,7 +427,9 @@ snapshots: typescript@3.9.3: {} - undici-types@7.21.0: {} + typescript@5.9.3: {} + + undici-types@7.19.2: {} util-deprecate@1.0.2: {} diff --git a/examples/vercel-node-mongo/pnpm-workspace.yaml b/examples/vercel-node-mongo/pnpm-workspace.yaml index b417ab8..d663c66 100644 --- a/examples/vercel-node-mongo/pnpm-workspace.yaml +++ b/examples/vercel-node-mongo/pnpm-workspace.yaml @@ -1,3 +1,56 @@ -minimumReleaseAge: 10080 -autoInstallPeers: true +# Supply-chain hardening +# ---------------------- +# Reject any package version published less than 14 days ago when +# resolving. Most supply-chain attacks are detected within hours or +# days; the 14-day window absorbs that while keeping upgrades moving +# at a reasonable pace. For a critical CVE that demands a sub-14d +# upgrade, add a temporary `minimumReleaseAgeExclude` entry with a +# CVE link and remove it once the window closes. +# 14 days = 60 * 24 * 14 minutes +minimumReleaseAge: 20160 + +# When the registry metadata is missing the `time` field for a +# version, fall back to allowing it. +minimumReleaseAgeIgnoreMissingTime: true + +# Whitelist for the 14-day wait. `@pluggyai/*` ships from our own +# release pipeline. +minimumReleaseAgeExclude: + - '@pluggyai/*' + +# Pinned explicitly to survive any future default change. +resolutionMode: highest + +# Refuse to install if Node / pnpm don't satisfy the `engines` field. +engineStrict: true + +# Fail the install if a package version's trust level drops +# compared to earlier versions. +trustPolicy: no-downgrade + +# Auto-skip the trust check for packages older than 90 days. +trustPolicyIgnoreAfter: 129600 + +# Block transitive deps from being resolved from exotic sources. +blockExoticSubdeps: true + + +# Version pinning +# --------------- + +# Save exact resolved versions (no semver ranges) on `pnpm add`. +savePrefix: "" + + +# Per-package decisions +# --------------------- + +# pnpm 11 blocks postinstall / install / preinstall scripts by +# default; every package that ships one must be listed here. +# Default-deny: new entries start as `false` and only flip to `true` +# after a conscious review. +allowBuilds: {} + +# Force a specific version on transitive dependencies. +overrides: {} diff --git a/examples/vercel-node-mongo/tsconfig.json b/examples/vercel-node-mongo/tsconfig.json new file mode 100644 index 0000000..7026c4b --- /dev/null +++ b/examples/vercel-node-mongo/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "noEmit": true + }, + "include": ["api/**/*", "lib/**/*"] +}