084ff5c3bf
- 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'.
361 lines
9.9 KiB
Markdown
361 lines
9.9 KiB
Markdown
# 🏗️ Bootstrap the Nx monorepo for adastra-portal
|
|
|
|
> **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
|
|
|
|
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.
|
|
|
|
---
|
|
|
|
## 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
|
|
# 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 \
|
|
--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 \
|
|
--standalone=true \
|
|
--style=scss \
|
|
--bundler=esbuild \
|
|
--unitTestRunner=vitest \
|
|
--e2eTestRunner=playwright \
|
|
--strict=true \
|
|
--tags="scope:portal-shell,type:app"
|
|
```
|
|
|
|
### Enable zoneless change detection (ADR-0004)
|
|
|
|
Edit `apps/portal-shell/src/app/app.config.ts`:
|
|
|
|
```typescript
|
|
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
|
|
import { provideRouter } from '@angular/router';
|
|
import { routes } from './app.routes';
|
|
|
|
export const appConfig: ApplicationConfig = {
|
|
providers: [
|
|
provideZonelessChangeDetection(),
|
|
provideRouter(routes),
|
|
],
|
|
};
|
|
```
|
|
|
|
Remove every reference to `zone.js`:
|
|
|
|
- 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
|
|
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) Generate the backend app — `portal-bff`
|
|
|
|
```bash
|
|
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) Add PostgreSQL + Prisma (ADR-0006)
|
|
|
|
```bash
|
|
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"
|
|
```
|
|
|
|
---
|
|
|
|
## 7) Enforce module boundaries
|
|
|
|
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
|
|
{
|
|
"singleQuote": true,
|
|
"semi": true,
|
|
"printWidth": 100
|
|
}
|
|
```
|
|
|
|
Install Husky, lint-staged, commitlint:
|
|
|
|
```bash
|
|
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"]
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 9) CI/CD
|
|
|
|
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 nx affected -t lint test build
|
|
```
|
|
|
|
---
|
|
|
|
## 10) Daily workflow
|
|
|
|
```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 |
|