diff --git a/AGENTS.md b/AGENTS.md index 8370661976..51812e86a6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,7 +6,8 @@ pnpm monorepo. Node >= 26, pnpm v11.8 - `apps/client` : Vue 3 + Vite + vue-dsfr (French gov design system), Pinia, UnoCSS - `apps/server` : Fastify 4 + Prisma 6 (PostgreSQL), contract-first API via @ts-rest -- `apps/server-nestjs` : NestJS rewrite (in progress) +- `apps/server-nestjs` : NestJS rewrite (in progress) — the only modifiable + backend target; `apps/server` is frozen (read-only reference) - `plugins/*` : argocd, gitlab, harbor, keycloak, kubernetes, nexus, sonarqube, vault - `packages/shared` : API contracts (@ts-rest), types, permissions (BigInt bitmasks) - `packages/hooks` : plugin hook system (core of plugin architecture) @@ -19,6 +20,7 @@ pnpm monorepo. Node >= 26, pnpm v11.8 ## Server architecture (Fastify) Resource-based organization in `apps/server/src/resources/`. Each resource follows a 3-file pattern: + - `router.ts` : route handlers (auth, permissions, delegates to business) - `business.ts` : business logic, orchestrates queries + hook calls - `queries.ts` : Prisma database queries @@ -26,6 +28,10 @@ Resource-based organization in `apps/server/src/resources/`. Each resource follo API contracts defined in `@cpn-console/shared`, shared with client via @ts-rest. Auth: Keycloak + Fastify session. Permissions: BigInt bitmasks (`ProjectAuthorized`, `AdminAuthorized`). +server-nestjs services receive configuration via injection +(`@Inject(xxxConfigFactory.KEY)` + `ConfigType`), +never `process.env`. + ## Plugin / Hook system Hook lifecycle: `pre` -> `main` -> `post` (sequential steps, parallel plugin execution). On failure: `revert`. @@ -33,23 +39,35 @@ Plugins are statically imported in `apps/server/src/plugins.ts`, then external p Each plugin: `index.ts` (Plugin interface), `infos.ts` (metadata/config), `functions.ts` (hook handlers). Plugins use TS module augmentation to extend `ProjectStore` and `Config` interfaces. +server-nestjs parity: every `eventEmitter.emitAsync('.')` must +have a matching `@OnEvent` consumer bridging to `capturePluginResult`, or +Keycloak/GitLab group syncs silently stop at cutover. + ## Database (Prisma) Multi-file schema in `apps/server/src/prisma/schema/*.prisma` (project, user, token, admin, topography). Migrations: standard Prisma Migrate. Major version data migrations in `migrations/v9/`. +`deleteMany`/`updateMany` with an `undefined` filter value matches ALL rows — +always filter by concrete id. `ProjectRole`/`Repository` foreign keys do not cascade. + ## Environment config - Files: `.env`, `.env.docker`, `.env.integ` in `apps/client/`, `apps/server/`, `apps/server-nestjs/` -- Templates: `*-example` suffix (git-tracked), active files gitignored +- Templates: `*-example` suffix (not `.example`; git-tracked), active files gitignored - Override chain (weakest to strongest): `.env` < `.env.docker` (if DOCKER=true) < `.env.integ` (if INTEGRATION=true) < explicit env vars - Server loading: `apps/server/src/utils/env.ts` | Client: `apps/client/vite.config.ts` +- `ci/scripts/init-env.sh` copies `*-example` to active equivalents (non-destructive) ## Testing - **Vitest**: unit tests everywhere (server, client, packages, plugins) — colocated `*.spec.ts` files - **Playwright**: E2E in `playwright/` (Chromium + Firefox, parallel) - Commands: `pnpm test` (all unit), `pnpm playwright:test` +- Deterministic tests: a faker draw must never be able to cross a branch + threshold (pin the draw window), otherwise CI flakes. +- Always prefer `mockDeep` for mocks (type safety over plain + `vi.fn()`/hand-rolled mocks); no describe-scope calls. ## Code quality @@ -57,6 +75,10 @@ Migrations: standard Prisma Migrate. Major version data migrations in `migration - Stylelint for CSS/Vue in client - Husky hooks: pre-commit (lint-staged), commit-msg (commitlint), pre-push (unit tests) - Conventional commits enforced: `feat`, `fix`, `chore`, `docs`, `refactor`, `revert`, `build` +- Fix at the shared source all callers route through, not a guard duplicated in + every caller. +- Before reporting done, format: `pnpm format`, then run the gates — `pnpm lint` + plus the targeted vitest specs. ## TypeScript @@ -64,6 +86,14 @@ Migrations: standard Prisma Migrate. Major version data migrations in `migration - Server: extends shared base, uses `ts-patch`/`tspc` for path transform in emitted JS - Client: does NOT extend shared base, uses `Bundler` module resolution - server-nestjs: standalone config with `emitDecoratorMetadata` + `experimentalDecorators` +- Strict type safety: no `as` casts (including `as any`), no `any`, and no + unchecked escapes of `unknown` — anything that breaks the type-checking + chain. Narrow with `if` type guards (include the offending identifier in the + error message). Parse, don't validate: at the input boundary, turn external + data into the most precise type it allows — the returned type is the proof — + so nothing downstream re-checks; a check-and-throw that returns nothing + preserves nothing. +- Helpers return new objects; do not mutate inputs. ## Main commands @@ -78,8 +108,3 @@ Migrations: standard Prisma Migrate. Major version data migrations in `migration - Branches: `main` (protected) + `hotfix/*` - Release Please for automated versioning, changelogs, npm publish, Docker images, Helm chart updates - PR template: `.github/PULL_REQUEST_TEMPLATE.md` - -## Conventions - -- Template env files use `-example` suffix (not `.example`) -- `ci/scripts/init-env.sh` copies `*-example` to active equivalents (non-destructive)