From 5737707bca41a9f95efdf9033a73f48e8e6afd34 Mon Sep 17 00:00:00 2001 From: Maximiliano Salvatti Date: Sat, 30 May 2026 15:53:24 -0300 Subject: [PATCH 1/4] chore(deps): bump @bymax-one/nest-auth to ^1.0.11 Dev-only security patch (forces tmp >=0.2.6 via overrides); no API changes from 1.0.10. Updates root, apps/api, apps/web and the lockfile. Includes staged .vscode editor settings. Verified: typecheck, lint, format:check and full test suite (API 540 tests, web 701 tests) all green. Co-Authored-By: Claude Opus 4.8 (1M context) --- .vscode/extensions.json | 8 ++++++++ .vscode/settings.json | 41 ++++++++++++++++++++++++++++++++++++++++- apps/api/package.json | 2 +- apps/web/package.json | 2 +- package.json | 2 +- pnpm-lock.yaml | 18 +++++++++--------- 6 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..4d65e9f --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "bradlc.vscode-tailwindcss", + "editorconfig.editorconfig" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json index fae8e3d..6bd07e3 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,43 @@ { + // Formatting model (conflict-free) for this pnpm monorepo + // (apps/api = NestJS 11, apps/web = Next.js 16 + React 19 + Tailwind): + // - Prettier (esbenp.prettier-vscode, reads .prettierrc.mjs) owns formatting. + // - ESLint 10 flat config (eslint.config.mjs) auto-fixes lint + import order on + // save; eslint-config-prettier disables stylistic ESLint rules so the two + // never fight. + // - Indentation / EOL / final-newline / trailing-whitespace come from + // .editorconfig (install EditorConfig.EditorConfig) — not duplicated here. + // - Per-language formatters stop VS Code's built-in formatters from clashing, + // covering React/TSX and CSS (Tailwind). + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "editor.rulers": [100], + + "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[javascriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[jsonc]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[css]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + "[markdown]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, + + // ESLint 10 flat config; "auto" working directories resolve each workspace package. + "eslint.useFlatConfig": true, + "eslint.workingDirectories": [{ "mode": "auto" }], + "typescript.tsdk": "node_modules/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true + "typescript.enablePromptUseWorkspaceTsdk": true, + + "search.exclude": { + "**/dist": true, + "**/coverage": true, + "**/.stryker-tmp": true, + "**/.next": true, + "**/pnpm-lock.yaml": true + } } diff --git a/apps/api/package.json b/apps/api/package.json index 76b39ed..3312695 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -42,7 +42,7 @@ "prisma:studio": "prisma studio" }, "dependencies": { - "@bymax-one/nest-auth": "^1.0.10", + "@bymax-one/nest-auth": "^1.0.11", "@nestjs/common": "^11.1.19", "@nestjs/config": "^4.0.4", "@nestjs/core": "^11.1.19", diff --git a/apps/web/package.json b/apps/web/package.json index 3d94703..f3c1c68 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -36,7 +36,7 @@ "mutation:dry-run": "stryker run --dryRunOnly" }, "dependencies": { - "@bymax-one/nest-auth": "^1.0.10", + "@bymax-one/nest-auth": "^1.0.11", "@hookform/resolvers": "^5.2.2", "@radix-ui/react-avatar": "^1.1.11", "@radix-ui/react-dialog": "^1.1.15", diff --git a/package.json b/package.json index b7a2991..1f1053e 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,7 @@ "onlyBuiltDependencies": [] }, "devDependencies": { - "@bymax-one/nest-auth": "^1.0.10", + "@bymax-one/nest-auth": "^1.0.11", "@commitlint/cli": "^20.5.0", "@commitlint/config-conventional": "^20.5.0", "@eslint/js": "^10.0.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9fd327a..9730b68 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: .: devDependencies: '@bymax-one/nest-auth': - specifier: ^1.0.10 - version: 1.0.10(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/jwt@11.0.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)))(@nestjs/throttler@6.5.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2))(@nestjs/websockets@11.1.19)(@types/express@5.0.6)(class-transformer@0.5.1)(class-validator@0.15.1)(express@5.2.1)(ioredis@5.10.1)(next@16.2.4(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(reflect-metadata@0.2.2) + specifier: ^1.0.11 + version: 1.0.11(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/jwt@11.0.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)))(@nestjs/throttler@6.5.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2))(@nestjs/websockets@11.1.19)(@types/express@5.0.6)(class-transformer@0.5.1)(class-validator@0.15.1)(express@5.2.1)(ioredis@5.10.1)(next@16.2.4(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(reflect-metadata@0.2.2) '@commitlint/cli': specifier: ^20.5.0 version: 20.5.0(@types/node@25.6.0)(conventional-commits-parser@6.4.0)(typescript@6.0.3) @@ -60,8 +60,8 @@ importers: apps/api: dependencies: '@bymax-one/nest-auth': - specifier: ^1.0.10 - version: 1.0.10(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/jwt@11.0.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)))(@nestjs/throttler@6.5.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2))(@nestjs/websockets@11.1.19)(@types/express@5.0.6)(class-transformer@0.5.1)(class-validator@0.15.1)(express@5.2.1)(ioredis@5.10.1)(next@16.2.4(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(reflect-metadata@0.2.2) + specifier: ^1.0.11 + version: 1.0.11(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/jwt@11.0.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)))(@nestjs/throttler@6.5.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2))(@nestjs/websockets@11.1.19)(@types/express@5.0.6)(class-transformer@0.5.1)(class-validator@0.15.1)(express@5.2.1)(ioredis@5.10.1)(next@16.2.4(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(reflect-metadata@0.2.2) '@nestjs/common': specifier: ^11.1.19 version: 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) @@ -226,8 +226,8 @@ importers: apps/web: dependencies: '@bymax-one/nest-auth': - specifier: ^1.0.10 - version: 1.0.10(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/jwt@11.0.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)))(@nestjs/throttler@6.5.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2))(@nestjs/websockets@11.1.19)(@types/express@5.0.6)(class-transformer@0.5.1)(class-validator@0.15.1)(express@5.2.1)(ioredis@5.10.1)(next@16.2.4(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(reflect-metadata@0.2.2) + specifier: ^1.0.11 + version: 1.0.11(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/jwt@11.0.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)))(@nestjs/throttler@6.5.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2))(@nestjs/websockets@11.1.19)(@types/express@5.0.6)(class-transformer@0.5.1)(class-validator@0.15.1)(express@5.2.1)(ioredis@5.10.1)(next@16.2.4(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(reflect-metadata@0.2.2) '@hookform/resolvers': specifier: ^5.2.2 version: 5.2.2(react-hook-form@7.72.1(react@19.2.5)) @@ -696,8 +696,8 @@ packages: resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true - '@bymax-one/nest-auth@1.0.10': - resolution: {integrity: sha512-VFG/9F0aUXvlsQIaDNPZzDVYjLEq/Fn6YJ7s3IptsYl+RY5QLkjoAgBtagCnEqISS6ivBaxgtok5RTW/VRQgtQ==} + '@bymax-one/nest-auth@1.0.11': + resolution: {integrity: sha512-DwQGhjOhj25Rvg+sYKwn98bvI1jDT5Ey2VauFe7m9a2gyCpqjlj3VJcCE9Ez9TkDuMtebTUG+hzZZCW3k5KYog==} engines: {node: '>=24.0.0'} peerDependencies: '@nestjs/common': ^11.0.0 @@ -7474,7 +7474,7 @@ snapshots: dependencies: css-tree: 3.2.1 - '@bymax-one/nest-auth@1.0.10(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/jwt@11.0.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)))(@nestjs/throttler@6.5.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2))(@nestjs/websockets@11.1.19)(@types/express@5.0.6)(class-transformer@0.5.1)(class-validator@0.15.1)(express@5.2.1)(ioredis@5.10.1)(next@16.2.4(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(reflect-metadata@0.2.2)': + '@bymax-one/nest-auth@1.0.11(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(@nestjs/jwt@11.0.2(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2)))(@nestjs/throttler@6.5.0(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.19)(reflect-metadata@0.2.2))(@nestjs/websockets@11.1.19)(@types/express@5.0.6)(class-transformer@0.5.1)(class-validator@0.15.1)(express@5.2.1)(ioredis@5.10.1)(next@16.2.4(@playwright/test@1.59.1)(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(react@19.2.5)(reflect-metadata@0.2.2)': optionalDependencies: '@nestjs/common': 11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.19(@nestjs/common@11.1.19(class-transformer@0.5.1)(class-validator@0.15.1)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.19)(@nestjs/websockets@11.1.19)(reflect-metadata@0.2.2)(rxjs@7.8.2) From 0677f1a949a5b189108407a6b2e8bfd6118699b9 Mon Sep 17 00:00:00 2001 From: Maximiliano Salvatti Date: Sat, 30 May 2026 16:04:41 -0300 Subject: [PATCH 2/4] fix(ci): pass DATABASE_URL placeholder to prisma generate in mutation workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Generate Prisma client" step in mutation.yml and mutation-nightly.yml did not set DATABASE_URL. Prisma 7 resolves env('DATABASE_URL') in prisma.config.ts at CLI load time, so `prisma generate` throws PrismaConfigEnvError before mutation testing can even start. ci.yml already passes a placeholder on the same step; these two workflows were missing it. Surfaced on PR #4 — the first run of mutation.yml (triggered by pnpm-lock.yaml / package.json paths). Pre-existing latent bug, not a regression. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/mutation-nightly.yml | 6 ++++++ .github/workflows/mutation.yml | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/.github/workflows/mutation-nightly.yml b/.github/workflows/mutation-nightly.yml index a9f0e9a..64c5c65 100644 --- a/.github/workflows/mutation-nightly.yml +++ b/.github/workflows/mutation-nightly.yml @@ -44,6 +44,12 @@ jobs: - name: Generate Prisma client working-directory: apps/api run: pnpm prisma:generate + # Prisma 7 resolves `env('DATABASE_URL')` in prisma.config.ts at CLI + # load time, so `prisma generate` fails without it even though it + # never connects to the database. A placeholder is enough — this + # mirrors the same step in ci.yml. + env: + DATABASE_URL: postgresql://postgres:postgres@localhost:5432/ci_placeholder # `--incremental false` overrides the config so we get a true cold # baseline — no cache, no incremental file is consulted. diff --git a/.github/workflows/mutation.yml b/.github/workflows/mutation.yml index d780691..57069fc 100644 --- a/.github/workflows/mutation.yml +++ b/.github/workflows/mutation.yml @@ -107,6 +107,12 @@ jobs: - name: Generate Prisma client working-directory: apps/api run: pnpm prisma:generate + # Prisma 7 resolves `env('DATABASE_URL')` in prisma.config.ts at CLI + # load time, so `prisma generate` fails without it even though it + # never connects to the database. A placeholder is enough — this + # mirrors the same step in ci.yml. + env: + DATABASE_URL: postgresql://postgres:postgres@localhost:5432/ci_placeholder # Restore the incremental cache. Try the PR branch's own cache first, # then fall back to main's so the first run on a new PR still benefits From fa01c943a28b15a40d15f84bf19d19ba2ed4744f Mon Sep 17 00:00:00 2001 From: Maximiliano Salvatti Date: Sat, 30 May 2026 16:21:59 -0300 Subject: [PATCH 3/4] test(api): assert verbatim 404 message in UsersService.findById miss case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The not-found test only asserted the error type (NotFoundException), not the message, so an empty-message variant of the 404 went uncaught — surfacing as a surviving StringLiteral mutant (score 99.87 < break threshold 100) the first time mutation.yml ran on a PR. The incremental cache from main had masked it; the nightly cold run would have caught it. Adds an assertion that the 404 names the requested id verbatim, matching the existing convention in the updateStatus tests. Targeted Stryker run on users.service.ts is back to 100.00% (0 survivors). Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/api/src/users/users.service.spec.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/api/src/users/users.service.spec.ts b/apps/api/src/users/users.service.spec.ts index 0a2767b..12c16fd 100644 --- a/apps/api/src/users/users.service.spec.ts +++ b/apps/api/src/users/users.service.spec.ts @@ -398,11 +398,16 @@ describe('UsersService', () => { * Scenario: the requested user does not exist in the caller's tenant (either * a wrong id or a cross-tenant probe). The service must return 404 rather * than leaking cross-tenant existence via a 403. - * Rule: findById returns NotFoundException (not ForbiddenException) on miss. + * Rule: findById returns NotFoundException (not ForbiddenException) on miss, + * and the 404 message names the requested id verbatim so the client can tell + * which lookup failed. */ findById.mockResolvedValue(null); await expect(service.findById('missing-user', 'acme')).rejects.toThrow(NotFoundException); + await expect(service.findById('missing-user', 'acme')).rejects.toThrow( + "User 'missing-user' not found", + ); }); it('calls the repository with the correct id and tenantId', async () => { From 2b3461396c47e879c55abd11e05907dd51964dc6 Mon Sep 17 00:00:00 2001 From: Maximiliano Salvatti Date: Sat, 30 May 2026 16:28:30 -0300 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20address=20Copilot=20review=20?= =?UTF-8?q?=E2=80=94=20assert=20against=20a=20single=20findById=20promise?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Asserts the type and the verbatim 404 message against one rejected promise instead of calling service.findById twice, so the lookup runs exactly once. Still kills the StringLiteral mutant — targeted Stryker run stays at 100.00%. Co-Authored-By: Claude Opus 4.8 (1M context) --- apps/api/src/users/users.service.spec.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/api/src/users/users.service.spec.ts b/apps/api/src/users/users.service.spec.ts index 12c16fd..4b6ef61 100644 --- a/apps/api/src/users/users.service.spec.ts +++ b/apps/api/src/users/users.service.spec.ts @@ -404,10 +404,11 @@ describe('UsersService', () => { */ findById.mockResolvedValue(null); - await expect(service.findById('missing-user', 'acme')).rejects.toThrow(NotFoundException); - await expect(service.findById('missing-user', 'acme')).rejects.toThrow( - "User 'missing-user' not found", - ); + // Assert the type and the verbatim message against a single rejected + // promise so the lookup runs exactly once. + const lookup = service.findById('missing-user', 'acme'); + await expect(lookup).rejects.toThrow(NotFoundException); + await expect(lookup).rejects.toThrow("User 'missing-user' not found"); }); it('calls the repository with the correct id and tenantId', async () => {