MigrationRunner applies pending migrations through the migration ledger exactly once; when a migration fails it throws a MigrationFailedError whose diagnostic is a structured object identifying the failing migration (version), the failure phase (apply/record), the underlying cause, and the applied/pending ledger state, serializable via toJSON. Re-exported from the driver boundary so no other package needs the pg driver to run migrations. Advisory lock (T04) and ready gate (T06) remain out of scope.
147 lines
6.6 KiB
Markdown
147 lines
6.6 KiB
Markdown
# Local non-container development
|
|
|
|
This guide documents the **local, non-container developer path** for the EPPP
|
|
workspace: how to install, build and run the project on your machine **without
|
|
Docker**. The containerised path (Docker Compose baseline, container smoke
|
|
tests) is tracked separately as story [E00-S02] Docker Compose baseline and is
|
|
**not** covered here.
|
|
|
|
> Status: this document tracks the E00-S01 workspace bootstrap state. Commands
|
|
> and expected outputs below were verified from a clean clone.
|
|
|
|
## What you get at bootstrap
|
|
|
|
The workspace is a pnpm monorepo with three package groups:
|
|
|
|
| Group | Path | Purpose |
|
|
| --- | --- | --- |
|
|
| `apps/` | `apps/server` (`@personal-blog/server`) | Public server application. Serves the application health endpoint (E00-S02-T03); the Fastify 5 application shell lands in a later story. |
|
|
| `packages/` | `packages/core` (`@personal-blog/core`) | Application core (site identity, content primitives). Bootstrap placeholder. |
|
|
| `packages/` | `packages/database-postgres` (`@personal-blog/database-postgres`) | PostgreSQL database adapter package. Single owner of the `pg`/Kysely driver imports (E00-S03-T02); the migration ledger (`schema_migrations`, E00-S03-T03), the migration advisory lock (E00-S03-T04) and the migration runner with its failure diagnostic (E00-S03-T05) are implemented here. |
|
|
| `extensions/` | `extensions/example` (`@personal-blog/example-extension`) | Example extension exercising the `extensions/` group. Bootstrap placeholder. |
|
|
|
|
A dependency-boundary rule (`dependency-boundaries.json`, enforced by
|
|
`tests/architecture-import.test.mjs`) keeps the groups from collapsing into an
|
|
accidental dependency graph: no `packages/*` package may import a concrete
|
|
`extensions/*` package.
|
|
|
|
## Prerequisites
|
|
|
|
- **Git** — to clone the repository.
|
|
- **Node.js 24.x** — required. The root `package.json` restricts the Node
|
|
engine to 24.x (`engines.node`), and the committed `pnpm-workspace.yaml` sets
|
|
`engineStrict: true`, so `pnpm install` on any other Node version is
|
|
**rejected** (`ERR_PNPM_UNSUPPORTED_ENGINE`) instead of merely warned.
|
|
Install Node 24.x via `nvm`, `fnm` or another version manager to match CI.
|
|
- **pnpm 11.23.0** — pinned via the `packageManager` field in the root
|
|
`package.json`. The easiest way to get exactly this version is Corepack,
|
|
which ships with Node.js (`corepack enable`).
|
|
|
|
No database, no runtime services and no Docker daemon are required at this
|
|
stage.
|
|
|
|
## Install
|
|
|
|
```sh
|
|
# 1. Clone the repository
|
|
git clone https://git.stevanovic.co.uk/Fabrika/PersonalBlog.git
|
|
cd PersonalBlog
|
|
|
|
# 2. Activate the pinned pnpm (11.23.0) via Corepack
|
|
corepack enable
|
|
|
|
# 3. Install dependencies against the committed lockfile (reproducible)
|
|
pnpm install --frozen-lockfile
|
|
```
|
|
|
|
- The install is **frozen**: `--frozen-lockfile` refuses to run if
|
|
`pnpm-lock.yaml` is out of date with the manifests, so every clone gets the
|
|
exact same dependency tree. CI uses the same flag.
|
|
- If you are adding or changing dependencies, run `pnpm install` (without the
|
|
flag) and **commit the resulting `pnpm-lock.yaml`** so the frozen install
|
|
keeps working for everyone.
|
|
- `node_modules/` and the pnpm content-addressable store are git-ignored and
|
|
never committed.
|
|
|
|
## Build
|
|
|
|
```sh
|
|
# Compile every workspace package (apps, packages, extensions) into dist/
|
|
pnpm build
|
|
|
|
# Typecheck every workspace package (no emit)
|
|
pnpm typecheck
|
|
```
|
|
|
|
`pnpm build` runs `tsc -p tsconfig.json` in each package in topological order
|
|
and emits `dist/` (JavaScript + type declarations + source maps) per package.
|
|
Expected result: `apps/server/dist/`, `packages/core/dist/`,
|
|
`packages/database-postgres/dist/` and `extensions/example/dist/` are
|
|
produced, all packages report `Done`, exit 0.
|
|
`dist/` is git-ignored; rebuild whenever you change `src/`.
|
|
|
|
## Run
|
|
|
|
```sh
|
|
# Run the compiled public server entrypoint
|
|
pnpm --filter @personal-blog/server start
|
|
```
|
|
|
|
This runs the `start` script of `apps/server` (`node dist/index.js`), i.e. the
|
|
compiled application entrypoint. Two things to know:
|
|
|
|
1. The command **must follow `pnpm build`** — the `start` script executes the
|
|
compiled artifact in `dist/`, it does not compile first.
|
|
2. Since [E00-S02-T03], `apps/server` serves the **application health
|
|
endpoint**: starting it opens an HTTP server on port 3000 answering
|
|
`GET /health` with HTTP 200 and `{"status":"ok"}`, so the process stays up.
|
|
The real Fastify 5 application shell — which turns this into the full
|
|
serving API — lands in a later story; the `start` command shape stays the
|
|
same once it does.
|
|
|
|
To run the compiled output of any other workspace package directly:
|
|
|
|
```sh
|
|
node <package-dir>/dist/index.js # e.g. node packages/core/dist/index.js
|
|
```
|
|
|
|
## Test
|
|
|
|
```sh
|
|
pnpm test
|
|
```
|
|
|
|
`pnpm test` runs the `node:test` suites under `tests/` (currently
|
|
`tests/architecture-import.test.mjs`, 10 tests) with zero extra dependencies.
|
|
This is also the suite that enforces the dependency-boundary rule.
|
|
|
|
## Smoke check from a clean clone
|
|
|
|
```sh
|
|
git clone https://git.stevanovic.co.uk/Fabrika/PersonalBlog.git && cd PersonalBlog
|
|
corepack enable
|
|
pnpm install --frozen-lockfile # exit 0, lockfile untouched
|
|
pnpm build # 4/4 packages emit dist/, exit 0
|
|
pnpm typecheck # 4/4 packages pass --noEmit, exit 0
|
|
pnpm test # 10/10 pass, exit 0
|
|
pnpm --filter @personal-blog/server start # serves GET /health on port 3000, stays up
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
| Symptom | Cause / fix |
|
|
| --- | --- |
|
|
| `pnpm: command not found` | Corepack shims not activated — run `corepack enable`, or prefix commands with `corepack pnpm ...`. |
|
|
| `ERR_PNPM_OUTDATED_LOCKFILE` | `pnpm-lock.yaml` is out of date with the manifests. Run `pnpm install` (unfrozen) and commit the lockfile update. |
|
|
| `ERR_PNPM_UNSUPPORTED_ENGINE` on install | Your Node version is outside the supported 24.x engine line (`engines.node` in the root `package.json`, enforced by `engineStrict: true` in `pnpm-workspace.yaml`). Install Node 24.x (e.g. via `nvm`, `fnm` or another version manager). |
|
|
| `start` exits immediately with no output | The server crashed or exited at startup — check the process output. Since [E00-S02-T03] the entrypoint serves `GET /health` on port 3000 and stays up; a missing `pnpm build` (stale/absent `dist/`) is the usual cause (see [Run](#run)). |
|
|
| `.env` files | `.env`/`.env.*` are git-ignored; a committed `.env.example` template lands with the environment story (E00-S04). |
|
|
|
|
## Out of scope
|
|
|
|
- Root build/test/typecheck **script wiring** — covered by E00-S01-T05; only
|
|
usage is documented here.
|
|
- **Docker Compose baseline** and container smoke tests — E00-S02.
|
|
- PostgreSQL adapter, migration runner and other runtime services — later E00
|
|
stories.
|