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 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/api/src/users/users.service.spec.ts b/apps/api/src/users/users.service.spec.ts index 0a2767b..4b6ef61 100644 --- a/apps/api/src/users/users.service.spec.ts +++ b/apps/api/src/users/users.service.spec.ts @@ -398,11 +398,17 @@ 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); + // 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 () => { 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)