docs: add ADR-0007 for pre-commit hooks and align documentation references
- decisions/0007-pre-commit-hooks-and-conventional-commits.md formalizes Husky + lint-staged + commitlint with Conventional Commits as the local quality-gate baseline. - decisions/README.md index updated. - docs/setup/03 section 8 rewritten to reference the ADR and document the full hook setup (pre-commit, commit-msg, commitlint config). - docs/setup/03 future-work table 'ADR(s)' column removed; future ADR numbers are now assigned at the moment each ADR is written, not pre-reserved. - CLAUDE.md aligned: pre-allocated phase-2 ADR numbers replaced by phase references; a pointer to ADR-0007 added under 'Local quality gates'.
This commit is contained in:
@@ -1,76 +1,270 @@
|
||||
# 🏗️ Angular monorepo with Nx + pnpm
|
||||
# 🏗️ Bootstrap the Nx monorepo for adastra-portal
|
||||
|
||||
> ⚠️ This guide is **pending revision**: the placeholder workspace name (`my-workspace`) and app name (`web`) must be replaced with project-specific values, and the procedure must be extended to cover the Node API / BFF side. Treat the steps below as a baseline only.
|
||||
> **Status.** Aligned with phase-1 ADRs ([0002](../../decisions/0002-adopt-nx-monorepo-apps-preset.md), [0003](../../decisions/0003-workspace-and-app-naming-convention.md), [0004](../../decisions/0004-frontend-stack-angular-csr-zoneless-signals.md), [0005](../../decisions/0005-backend-stack-nestjs.md), [0006](../../decisions/0006-persistence-postgresql-prisma.md)). Replaces the original placeholder-based draft.
|
||||
|
||||
## Goal
|
||||
|
||||
Create a scalable and maintainable architecture.
|
||||
Scaffold the `adastra-portal` Nx monorepo containing:
|
||||
|
||||
- `apps/portal-shell` — Angular SPA frontend (CSR, standalone, zoneless, Signals, Vitest, SCSS, strict TS).
|
||||
- `apps/portal-bff` — NestJS BFF backend (Express adapter, global `ValidationPipe`, Jest test runner).
|
||||
- `libs/feature/<name>` and `libs/shared/<scope>` — feature and shared libraries.
|
||||
|
||||
Out of scope here (covered by later phases): authentication (Entra ID), sessions (Redis), observability (Pino + OpenTelemetry), audit trail, accessibility / performance / security baselines in CI, downstream API integration, on-prem infrastructure stack.
|
||||
|
||||
---
|
||||
|
||||
## 1) Workspace creation
|
||||
## Prerequisites
|
||||
|
||||
- WSL + Zsh — see [01-wsl-terminal-setup.md](01-wsl-terminal-setup.md).
|
||||
- Node.js latest LTS + pnpm via corepack — see [02-dev-web-stack.md](02-dev-web-stack.md).
|
||||
- Working tree under the WSL filesystem (`~/dev/`, `~/Works/`), never `/mnt/c/`.
|
||||
- The repository `adastra-portal` already exists locally with the ADRs and docs committed (you are reading one of them).
|
||||
|
||||
---
|
||||
|
||||
## 1) Initialize the Nx workspace inside the existing repository
|
||||
|
||||
The repository already carries documentation, ADRs, `.vscode/`, and `.gitignore`. `create-nx-workspace` always generates into a *new* directory, so we generate next to the repo and copy the workspace files in.
|
||||
|
||||
```bash
|
||||
cd ~/dev
|
||||
|
||||
pnpm dlx create-nx-workspace@latest my-workspace \
|
||||
--preset=angular-monorepo \
|
||||
# 1. Generate a clean Nx workspace next to the repo
|
||||
cd ~/dev # or any scratch location outside the repo
|
||||
pnpm dlx create-nx-workspace@latest adastra-portal-bootstrap \
|
||||
--preset=apps \
|
||||
--pm=pnpm \
|
||||
--appName=web \
|
||||
--style=scss \
|
||||
--ci=skip \
|
||||
--useGitHub=false
|
||||
|
||||
# 2. Copy the workspace files into the existing repo
|
||||
cd adastra-portal-bootstrap
|
||||
cp -v package.json pnpm-workspace.yaml nx.json tsconfig.base.json \
|
||||
.prettierrc .prettierignore .editorconfig \
|
||||
eslint.config.mjs \
|
||||
~/Works/adastra_portal/
|
||||
|
||||
# 3. Drop the scratch workspace
|
||||
cd ~ && rm -rf ~/dev/adastra-portal-bootstrap
|
||||
```
|
||||
|
||||
The `apps` preset is generic — it does not privilege a runtime ([ADR-0002](../../decisions/0002-adopt-nx-monorepo-apps-preset.md)). Plugins are added in step 2.
|
||||
|
||||
Verify:
|
||||
|
||||
```bash
|
||||
cd ~/Works/adastra_portal
|
||||
pnpm install
|
||||
ls nx.json package.json tsconfig.base.json
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2) Install Angular and Node plugins
|
||||
|
||||
```bash
|
||||
pnpm add -D @nx/angular @nx/nest @nx/vite @nx/eslint @nx/js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3) Generate the frontend app — `portal-shell`
|
||||
|
||||
```bash
|
||||
pnpm nx g @nx/angular:application portal-shell \
|
||||
--directory=apps/portal-shell \
|
||||
--routing=true \
|
||||
--unitTestRunner=vitest
|
||||
--standalone=true \
|
||||
--style=scss \
|
||||
--bundler=esbuild \
|
||||
--unitTestRunner=vitest \
|
||||
--e2eTestRunner=playwright \
|
||||
--strict=true \
|
||||
--tags="scope:portal-shell,type:app"
|
||||
```
|
||||
|
||||
---
|
||||
### Enable zoneless change detection (ADR-0004)
|
||||
|
||||
## 2) Recommended layout
|
||||
Edit `apps/portal-shell/src/app/app.config.ts`:
|
||||
|
||||
```text
|
||||
apps/
|
||||
web/
|
||||
```typescript
|
||||
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { routes } from './app.routes';
|
||||
|
||||
libs/
|
||||
shared/ui/
|
||||
shared/data-access/
|
||||
shared/util/
|
||||
feature/auth/
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideZonelessChangeDetection(),
|
||||
provideRouter(routes),
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
Remove every reference to `zone.js`:
|
||||
|
||||
## 3) Generate libraries
|
||||
- delete any `import 'zone.js'` from `apps/portal-shell/src/main.ts` and `apps/portal-shell/src/test-setup.ts`;
|
||||
- drop `zone.js` from any `polyfills` array in `apps/portal-shell/project.json`;
|
||||
- check the lockfile no longer pulls `zone.js`.
|
||||
|
||||
Verify:
|
||||
|
||||
```bash
|
||||
pnpm nx g @nx/angular:library shared-ui
|
||||
pnpm nx g @nx/angular:library feature-auth
|
||||
grep -RIn "zone.js" apps/portal-shell || echo "no zone.js references — OK"
|
||||
```
|
||||
|
||||
### Strict TypeScript (workspace-wide)
|
||||
|
||||
In `tsconfig.base.json`:
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"exactOptionalPropertyTypes": true,
|
||||
"noImplicitOverride": true,
|
||||
"noPropertyAccessFromIndexSignature": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4) Main commands
|
||||
## 4) Generate the backend app — `portal-bff`
|
||||
|
||||
```bash
|
||||
pnpm nx serve web
|
||||
pnpm nx build web
|
||||
pnpm nx test web
|
||||
pnpm nx lint web
|
||||
pnpm nx g @nx/nest:application portal-bff \
|
||||
--directory=apps/portal-bff \
|
||||
--unitTestRunner=jest \
|
||||
--tags="scope:portal-bff,type:app"
|
||||
```
|
||||
|
||||
> **Test runner note.** NestJS ships with Jest. Vitest unification across the monorepo is possible (via `@nx/vite:configuration`) and remains a future option — no ADR yet.
|
||||
|
||||
### Wire the global `ValidationPipe` (ADR-0005)
|
||||
|
||||
Edit `apps/portal-bff/src/main.ts`:
|
||||
|
||||
```typescript
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { ValidationPipe } from '@nestjs/common';
|
||||
import { AppModule } from './app/app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
app.useGlobalPipes(
|
||||
new ValidationPipe({
|
||||
whitelist: true,
|
||||
forbidNonWhitelisted: true,
|
||||
transform: true,
|
||||
}),
|
||||
);
|
||||
|
||||
// Phase-2 security ADRs will add: helmet, CORS allowlist, cookie-session,
|
||||
// CSRF protection, rate limiting, auth guards, structured error filter.
|
||||
|
||||
await app.listen(process.env.PORT ?? 3000);
|
||||
}
|
||||
bootstrap();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5) Workspace-wide commands
|
||||
## 5) Add PostgreSQL + Prisma (ADR-0006)
|
||||
|
||||
```bash
|
||||
pnpm nx run-many -t lint test build
|
||||
pnpm nx affected -t lint test build
|
||||
pnpm add -D prisma
|
||||
pnpm add @prisma/client nestjs-prisma
|
||||
|
||||
mkdir -p apps/portal-bff/prisma
|
||||
pnpm exec prisma init \
|
||||
--datasource-provider postgresql \
|
||||
--schema apps/portal-bff/prisma/schema.prisma
|
||||
```
|
||||
|
||||
Wire `PrismaModule` in `apps/portal-bff/src/app/app.module.ts`:
|
||||
|
||||
```typescript
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from 'nestjs-prisma';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PrismaModule.forRoot({ isGlobal: true }),
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
```
|
||||
|
||||
Commit a `.env.example` (the actual `.env` is git-ignored):
|
||||
|
||||
```dotenv
|
||||
DATABASE_URL="postgresql://portal:portal@localhost:5432/portal_dev?schema=public"
|
||||
```
|
||||
|
||||
> **Out of scope.** PostgreSQL provisioning (HA, backups, RLS policies for the dual-audience model) and Prisma migration deployment will be handled by infrastructure ADRs (phase 3).
|
||||
|
||||
---
|
||||
|
||||
## 6) Generate the first libraries
|
||||
|
||||
Convention ([ADR-0003](../../decisions/0003-workspace-and-app-naming-convention.md)):
|
||||
- `libs/feature/<name>` for vertical features;
|
||||
- `libs/shared/<scope>` for cross-cutting concerns.
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Frontend feature lib
|
||||
pnpm nx g @nx/angular:library feature-auth \
|
||||
--directory=libs/feature/auth \
|
||||
--standalone=true \
|
||||
--tags="scope:portal-shell,type:feature"
|
||||
|
||||
# Shared UI lib (frontend-only)
|
||||
pnpm nx g @nx/angular:library shared-ui \
|
||||
--directory=libs/shared/ui \
|
||||
--standalone=true \
|
||||
--tags="scope:portal-shell,type:shared"
|
||||
|
||||
# Shared utility lib (consumed by both apps)
|
||||
pnpm nx g @nx/js:library shared-util \
|
||||
--directory=libs/shared/util \
|
||||
--tags="scope:shared,type:shared"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6) Prettier
|
||||
## 7) Enforce module boundaries
|
||||
|
||||
`.prettierrc`:
|
||||
In `eslint.config.mjs`, configure `@nx/enforce-module-boundaries` with the project tags above:
|
||||
|
||||
```js
|
||||
{
|
||||
'@nx/enforce-module-boundaries': [
|
||||
'error',
|
||||
{
|
||||
depConstraints: [
|
||||
{ sourceTag: 'scope:portal-shell', onlyDependOnLibsWithTags: ['scope:portal-shell', 'scope:shared'] },
|
||||
{ sourceTag: 'scope:portal-bff', onlyDependOnLibsWithTags: ['scope:portal-bff', 'scope:shared'] },
|
||||
{ sourceTag: 'type:app', onlyDependOnLibsWithTags: ['type:feature', 'type:shared'] },
|
||||
{ sourceTag: 'type:feature', onlyDependOnLibsWithTags: ['type:feature', 'type:shared'] },
|
||||
{ sourceTag: 'type:shared', onlyDependOnLibsWithTags: ['type:shared'] },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
```
|
||||
|
||||
This prevents `portal-shell` from importing `portal-bff` code (and vice versa), and keeps `shared-*` libraries free of feature-level dependencies.
|
||||
|
||||
---
|
||||
|
||||
## 8) Prettier, git hooks, and Conventional Commits
|
||||
|
||||
See [ADR-0007](../../decisions/0007-pre-commit-hooks-and-conventional-commits.md) for the rationale (Husky + lint-staged + commitlint with Conventional Commits).
|
||||
|
||||
`.prettierrc` (project-wide):
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -80,37 +274,87 @@ pnpm nx affected -t lint test build
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7) Git hooks
|
||||
Install Husky, lint-staged, commitlint:
|
||||
|
||||
```bash
|
||||
pnpm add -D husky lint-staged
|
||||
pnpm add -D husky lint-staged @commitlint/cli @commitlint/config-conventional
|
||||
pnpm exec husky init
|
||||
```
|
||||
|
||||
`.husky/pre-commit`:
|
||||
|
||||
```bash
|
||||
pnpm exec lint-staged
|
||||
```
|
||||
|
||||
`.husky/commit-msg`:
|
||||
|
||||
```bash
|
||||
pnpm exec commitlint --edit "$1"
|
||||
```
|
||||
|
||||
`commitlint.config.cjs` (workspace root):
|
||||
|
||||
```js
|
||||
module.exports = { extends: ['@commitlint/config-conventional'] };
|
||||
```
|
||||
|
||||
`package.json` excerpt:
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": { "prepare": "husky" },
|
||||
"lint-staged": {
|
||||
"*.{ts,html,scss,md,json}": ["pnpm nx format:write --uncommitted"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8) CI/CD
|
||||
## 9) CI/CD
|
||||
|
||||
> ⚠️ The original procedure targeted GitLab CI. The project repository now lives on Gitea, so this section needs to be rewritten for Gitea Actions (or the chosen CI runner). Tracked under the next round of CI ADRs.
|
||||
|
||||
---
|
||||
|
||||
## 9) Daily workflow
|
||||
The pipeline targets the Gitea repository (`gitea@git.unespace.com:julien/adastra_portal.git`). Pipeline shape (Gitea Actions vs. third-party runner) and branch protection are deferred to phase-3 ADRs (planned: ADR-0017). Locally, the equivalent of the future CI check is:
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm start
|
||||
pnpm nx g component login
|
||||
pnpm nx affected -t lint test build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Result
|
||||
## 10) Daily workflow
|
||||
|
||||
* Scalable monorepo
|
||||
* Optimized builds
|
||||
* Fast CI
|
||||
* Clear architecture
|
||||
```bash
|
||||
# Dev servers
|
||||
pnpm nx serve portal-shell # http://localhost:4200
|
||||
pnpm nx serve portal-bff # http://localhost:3000
|
||||
|
||||
# Tests
|
||||
pnpm nx test portal-shell # Vitest
|
||||
pnpm nx test portal-bff # Jest
|
||||
|
||||
# Quality sweeps
|
||||
pnpm nx run-many -t lint test # full
|
||||
pnpm nx affected -t lint test build # only what changed since main
|
||||
|
||||
# Generators
|
||||
pnpm nx g @nx/angular:component <name> --project=portal-shell --standalone
|
||||
pnpm nx g @nx/nest:resource <name> --project=portal-bff
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What this guide does *not* cover (yet)
|
||||
|
||||
| Concern | Phase |
|
||||
| --- | --- |
|
||||
| Authentication (Entra ID workforce + External ID) | 2 |
|
||||
| Sessions / cache (Redis self-hosted) | 2 |
|
||||
| Downstream API access (existing apps) | 2 |
|
||||
| Observability (Pino + OpenTelemetry + W3C) | 2 |
|
||||
| Audit trail | 2 |
|
||||
| Accessibility baseline (WCAG 2.2 AA + axe-core in CI) | 3 |
|
||||
| Performance budgets (Lighthouse CI) | 3 |
|
||||
| Security baseline (CSP, dep / secret scanning, OWASP ASVS) | 3 |
|
||||
| CI/CD on Gitea | 3 |
|
||||
| On-prem infrastructure stack (orchestration, Postgres HA, Redis HA, observability backend) | 3 |
|
||||
|
||||
Reference in New Issue
Block a user