Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions FRAMEWORK-API.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# @lenne.tech/nest-server — Framework API Reference

> Auto-generated from source code on 2026-06-03 (v11.26.3)
> Auto-generated from source code on 2026-06-14 (v11.27.0)
> File: `FRAMEWORK-API.md` — compact, machine-readable API surface for Claude Code

## CoreModule.forRoot()
Expand All @@ -26,11 +26,12 @@
- `debugProcessInput?`: `boolean` (default: `false`) — When true, logs a debug message when prepareInput() changes the input type during process().
- `email?`: `{ defaultSender?: { email?: string; name?: string; }; mailjet?: MailjetOption...` — SMTP and template configuration for sending emails
- `env?`: `string` — Environment
- `version?`: `string` — Semantic version of the running build (e.g. from package.json / meta.json).
- `errorCode?`: `IErrorCode` — Configuration for the error code module
- `execAfterInit?`: `string` — Exec a command after server is initialized
- `filter?`: `{ maxLimit?: number; }` — Filter configuration and defaults
- `graphQl?`: `false | { driver?: ApolloDriverConfig; enableSubscriptionAuth?: boolean; maxC...` — Configuration of the GraphQL module
- `healthCheck?`: `{ configs?: { database?: { enabled?: boolean; key?: string; options?: Mongoos...` — Whether to activate health check endpoints
- `healthCheck?`: `{ configs?: { build?: { enabled?: boolean; }; database?: { enabled?: boolean;...` — Whether to activate health check endpoints
- `hostname?`: `string` — Hostname of the server
- `ignoreSelectionsForPopulate?`: `boolean` — Ignore selections in fieldSelection
- `jwt?`: `IJwt & JwtModuleOptions & { refresh?: IJwt & { renewal?: boolean; }; sameToke...` — Configuration of JavaScript Web Token (JWT) module
Expand Down
249 changes: 249 additions & 0 deletions migration-guides/11.26.3-to-11.27.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
# Migration Guide: 11.26.3 → 11.27.0

## Overview

| Category | Details |
|----------|---------|
| **Breaking Changes** | None |
| **New Features** | Build-identity helpers (`getCommit()` / `getBuildInfo()`); the running build's commit / version / environment is now surfaced in the `/health-check` response; new `IServerOptions.version` and `healthCheck.configs.build` options |
| **Bugfixes** | None |
| **Migration Effort** | 0 minutes (automatic) — the health check exposes the build identity on update. ~5 minutes optional to bake the real commit SHA into your image so it stops reporting `unknown` |

This release makes the **running build identifiable at runtime**. App and API are
typically deployed together but versioned independently, so a partial / stale
rollout (one container older than the other) is otherwise hard to spot. The build
**commit SHA** is the drift detector: bake the same CI commit into both images and
compare them.

---

## Quick Migration

No code changes required. The build indicator is added to `/health-check`
automatically — the commit just reports `'unknown'` until you bake it into the
image (see [Adopt the full drift detection](#adopt-the-full-drift-detection-optional)).

```bash
# Update package
pnpm add @lenne.tech/nest-server@11.27.0

# Verify build
pnpm run build

# Run tests
pnpm test
```

---

## What's New in 11.27.0

### 1. Build-identity helpers: `getCommit()` / `getBuildInfo()`

Two pure helpers are now exported from `@lenne.tech/nest-server`. They give every
project ONE canonical way to resolve which build is running, instead of re-reading
`process.env` ad hoc.

```typescript
import { getBuildInfo, getCommit } from '@lenne.tech/nest-server';

// Commit SHA the build was produced from. Reads process.env.APP_VERSION_COMMIT
// (override the var name if you like), falling back to 'unknown' for local builds.
getCommit(); // → 'a1b2c3d…' or 'unknown'
getCommit('MY_COMMIT_ENV'); // → reads a custom env var

// Full build identity, ready to surface via a meta / info endpoint.
getBuildInfo({ env: 'production', version: '1.4.0' });
// → { commit: 'a1b2c3d…', env: 'production', version: '1.4.0' }
```

Also exported: `BuildInfo` (type), `DEFAULT_COMMIT_ENV` (`'APP_VERSION_COMMIT'`),
`UNKNOWN_COMMIT` (`'unknown'`).

### 2. Build identity in the `/health-check` response

The core health check now includes a `build` indicator. It is **always** reported
with status `up`, so it surfaces under `info`/`details` **without ever affecting
the overall health status**. Ops and monitoring can detect a drifted / stale
container, not just the admin UI.

```jsonc
// GET /health-check (and the GraphQL `healthCheck` query)
{
"status": "ok",
"info": {
"database": { "status": "up" },
"build": {
"status": "up",
"commit": "a1b2c3d4e5f6…", // process.env.APP_VERSION_COMMIT, or "unknown"
"env": "production", // from IServerOptions.env
"version": "1.4.0" // from IServerOptions.version (see #3), or "unknown"
}
},
"details": { "database": { "status": "up" }, "build": { /* same */ } }
}
```

Opt out (e.g. if you prefer to keep build info out of an unauthenticated probe):

```typescript
// config.env.ts
healthCheck: {
configs: {
build: { enabled: false },
},
}
```

### 3. New `IServerOptions.version` config field

So the health-check build indicator can report your app version, set it from your
`package.json` / `meta.json`:

```typescript
// config.env.ts
import metaData = require('./meta.json');

const base: Partial<IServerOptions> = {
env: envName,
version: metaData.version, // ← surfaced in /health-check build identity
// …
};
```

Without it, the indicator reports `version: 'unknown'` — harmless, but less useful.

### Adopt the full drift detection (optional)

For the end-to-end "is App on the same build as API?" check, the commit must be
baked into the image at build time and exposed publicly. The
[nest-server-starter](https://github.com/lenneTech/nest-server-starter) reference
implementation wires all three layers:

1. **Image** — bake the commit at build time:
```dockerfile
# In the runtime stage (the API reads it at runtime):
ARG APP_VERSION_COMMIT=unknown
ENV APP_VERSION_COMMIT=$APP_VERSION_COMMIT
```
2. **Build arg** — feed the CI commit SHA:
```yaml
# docker-compose.yml
build:
args:
APP_VERSION_COMMIT: ${IMAGE_TAG:-unknown} # IMAGE_TAG = CI_COMMIT_SHA
```
3. **Public endpoint** — the starter's `meta` module exposes `GET /meta`
(`S_EVERYONE`) returning `{ version, commit, environment, package, title }`,
so the frontend can read the API commit and compare it against its own.

The contract end to end:

```
CI commit SHA → IMAGE_TAG (CI) → APP_VERSION_COMMIT build arg (compose)
→ ENV in the image (Dockerfile) → getCommit() → /health-check + /meta
```

Versions (semver) are per-component and may legitimately differ — only the
**commit** is compared. Local builds without CI report `unknown`, which clients
use to suppress the drift warning.

---

## Breaking Changes

None. All additions are backward compatible.

---

## Compatibility Notes

- **Existing `/health-check` consumers:** The response gains one extra key
(`build`) under `info` and `details`. `status` is unchanged and the `build`
indicator can never flip the overall status to `error` (it always reports `up`).
Only consumers that assert the *exact* set of keys need updating; disable it via
`healthCheck.configs.build.enabled: false` if you must keep the old shape.
- **GraphQL `healthCheck` query:** `info`/`details` are `JSON` scalars, so the new
`build` key flows through without any schema change.
- **`IServerOptions`:** `version` and `healthCheck.configs.build` are new optional
fields. Existing configs compile unchanged.
- **Commit resolution:** If you previously read `process.env.APP_VERSION_COMMIT`
yourself, you can switch to `getCommit()` for the identical result (with the
`'unknown'` fallback) — optional, not required.
- **Vendor-mode consumers:** The same additions land in
`src/core/common/helpers/meta.helper.ts` and
`src/core/modules/health-check/core-health-check.service.ts`. Sync via the
vendored-core updater. No flatten-fix change required.

---

## Verifying

```bash
# Boot the API and read the health check
pnpm start &
curl -s http://localhost:3000/health-check | jq '.info.build'
# → { "status": "up", "commit": "unknown", "env": "local", "version": "1.4.0" }

# After a CI build that passes APP_VERSION_COMMIT, "commit" is the real SHA:
APP_VERSION_COMMIT=$(git rev-parse HEAD) pnpm start &
curl -s http://localhost:3000/health-check | jq '.info.build.commit'
```

A unit test for the helpers ships in `tests/unit/meta-helper.spec.ts`.

---

## Troubleshooting

### `commit` is always `"unknown"`

The build arg never reached the process. Check, in order:

1. CI passes the commit SHA into the build (`IMAGE_TAG` / `APP_VERSION_COMMIT`).
2. `docker-compose.yml` forwards it as a `build.args.APP_VERSION_COMMIT`.
3. The **API** Dockerfile declares `ARG`/`ENV APP_VERSION_COMMIT` in the **runtime
stage** (the API reads it at runtime, not build time).
4. For a frontend, the commit must be set **before** the bundler runs (e.g. Nuxt
freezes `runtimeConfig.public` at build time) — there the `ARG`/`ENV` belongs in
the **build stage**, not the runtime stage.

### `version` is `"unknown"`

Set `IServerOptions.version` (see [#3](#3-new-iserveroptionsversion-config-field)).
The health check reads `config.version`; nothing else populates it.

### I don't want build info on an unauthenticated endpoint

`/health-check` is `S_EVERYONE` by design (probes need it). Disable just the build
indicator with `healthCheck.configs.build.enabled: false`, or restrict the route
in your project.

---

## Module Documentation

### Core Common — build-identity helpers

- **Helpers:** `src/core/common/helpers/meta.helper.ts` (`getCommit`, `getBuildInfo`, `BuildInfo`, `DEFAULT_COMMIT_ENV`, `UNKNOWN_COMMIT`)
- **Unit tests:** `tests/unit/meta-helper.spec.ts`

### Core Health Check

- **Service:** `src/core/modules/health-check/core-health-check.service.ts` (build indicator)
- **Controller / Resolver:** `src/core/modules/health-check/core-health-check.controller.ts`, `core-health-check.resolver.ts`
- **Config:** `IServerOptions.healthCheck.configs.build` + `IServerOptions.version` in `src/core/common/interfaces/server-options.interface.ts`

### Reference implementation (full drift detection)

- **API meta module + Dockerfile build arg:** [nest-server-starter](https://github.com/lenneTech/nest-server-starter) — `src/server/modules/meta/` exposes `GET /meta` with `commit`
- **Frontend system page:** [nuxt-base-starter](https://github.com/lenneTech/nuxt-base-starter) — `/app/admin/system` compares App vs. API builds

---

## References

- [Migration Guide 11.26.2 → 11.26.3](./11.26.2-to-11.26.3.md) — Previous release (OpenAPI enum `$ref` fix)
- [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)
- [nuxt-base-starter](https://github.com/lenneTech/nuxt-base-starter) (frontend drift detection)
- [lt-monorepo](https://github.com/lenneTech/lt-monorepo) — `docker-compose.yml` + CI wiring for the `APP_VERSION_COMMIT` contract
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@lenne.tech/nest-server",
"version": "11.26.3",
"version": "11.27.0",
"description": "Modern, fast, powerful Node.js web framework in TypeScript based on Nest with a GraphQL API and a connection to MongoDB (or other databases).",
"keywords": [
"node",
Expand Down Expand Up @@ -232,7 +232,8 @@
"defu@<=6.1.6": "6.1.7",
"follow-redirects@<=1.15.11": "1.16.0",
"uuid@<14.0.0": "14.0.0",
"postcss@<8.5.10": "8.5.12"
"postcss@<8.5.10": "8.5.12",
"esbuild@>=0.17.0 <0.28.1": "0.28.1"
},
"//peerDependencyRules": "allowedVersions: deps lag behind our newer majors (graphql-upload wants @types/express@^4, the deprecated apollo playground plugin wants @apollo/server@^4) — both work with our v5. ignoreMissing: browser-only vis-network peers pulled in transitively via yuml-diagram (server-side UML generation never renders, so these are not needed).",
"peerDependencyRules": {
Expand Down
Loading
Loading