# 🏗️ Bootstrap the Nx monorepo for apf-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 `apf-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 `apf-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 apf-portal-bootstrap \
  --preset=apps \
  --pm=pnpm \
  --ci=skip \
  --useGitHub=false

# 2. Copy the workspace files into the existing repo
cd apf-portal-bootstrap
cp -v package.json pnpm-workspace.yaml nx.json tsconfig.base.json \
       .prettierrc .prettierignore .editorconfig \
       eslint.config.mjs \
       ~/Works/apf_portal/

# 3. Drop the scratch workspace
cd ~ && rm -rf ~/dev/apf-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/apf_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/apf_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     |
