@lenne.tech/nest-server 11.26.3 → 11.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,249 @@
1
+ # Migration Guide: 11.26.3 → 11.27.0
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ |----------|---------|
7
+ | **Breaking Changes** | None |
8
+ | **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 |
9
+ | **Bugfixes** | None |
10
+ | **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` |
11
+
12
+ This release makes the **running build identifiable at runtime**. App and API are
13
+ typically deployed together but versioned independently, so a partial / stale
14
+ rollout (one container older than the other) is otherwise hard to spot. The build
15
+ **commit SHA** is the drift detector: bake the same CI commit into both images and
16
+ compare them.
17
+
18
+ ---
19
+
20
+ ## Quick Migration
21
+
22
+ No code changes required. The build indicator is added to `/health-check`
23
+ automatically — the commit just reports `'unknown'` until you bake it into the
24
+ image (see [Adopt the full drift detection](#adopt-the-full-drift-detection-optional)).
25
+
26
+ ```bash
27
+ # Update package
28
+ pnpm add @lenne.tech/nest-server@11.27.0
29
+
30
+ # Verify build
31
+ pnpm run build
32
+
33
+ # Run tests
34
+ pnpm test
35
+ ```
36
+
37
+ ---
38
+
39
+ ## What's New in 11.27.0
40
+
41
+ ### 1. Build-identity helpers: `getCommit()` / `getBuildInfo()`
42
+
43
+ Two pure helpers are now exported from `@lenne.tech/nest-server`. They give every
44
+ project ONE canonical way to resolve which build is running, instead of re-reading
45
+ `process.env` ad hoc.
46
+
47
+ ```typescript
48
+ import { getBuildInfo, getCommit } from '@lenne.tech/nest-server';
49
+
50
+ // Commit SHA the build was produced from. Reads process.env.APP_VERSION_COMMIT
51
+ // (override the var name if you like), falling back to 'unknown' for local builds.
52
+ getCommit(); // → 'a1b2c3d…' or 'unknown'
53
+ getCommit('MY_COMMIT_ENV'); // → reads a custom env var
54
+
55
+ // Full build identity, ready to surface via a meta / info endpoint.
56
+ getBuildInfo({ env: 'production', version: '1.4.0' });
57
+ // → { commit: 'a1b2c3d…', env: 'production', version: '1.4.0' }
58
+ ```
59
+
60
+ Also exported: `BuildInfo` (type), `DEFAULT_COMMIT_ENV` (`'APP_VERSION_COMMIT'`),
61
+ `UNKNOWN_COMMIT` (`'unknown'`).
62
+
63
+ ### 2. Build identity in the `/health-check` response
64
+
65
+ The core health check now includes a `build` indicator. It is **always** reported
66
+ with status `up`, so it surfaces under `info`/`details` **without ever affecting
67
+ the overall health status**. Ops and monitoring can detect a drifted / stale
68
+ container, not just the admin UI.
69
+
70
+ ```jsonc
71
+ // GET /health-check (and the GraphQL `healthCheck` query)
72
+ {
73
+ "status": "ok",
74
+ "info": {
75
+ "database": { "status": "up" },
76
+ "build": {
77
+ "status": "up",
78
+ "commit": "a1b2c3d4e5f6…", // process.env.APP_VERSION_COMMIT, or "unknown"
79
+ "env": "production", // from IServerOptions.env
80
+ "version": "1.4.0" // from IServerOptions.version (see #3), or "unknown"
81
+ }
82
+ },
83
+ "details": { "database": { "status": "up" }, "build": { /* same */ } }
84
+ }
85
+ ```
86
+
87
+ Opt out (e.g. if you prefer to keep build info out of an unauthenticated probe):
88
+
89
+ ```typescript
90
+ // config.env.ts
91
+ healthCheck: {
92
+ configs: {
93
+ build: { enabled: false },
94
+ },
95
+ }
96
+ ```
97
+
98
+ ### 3. New `IServerOptions.version` config field
99
+
100
+ So the health-check build indicator can report your app version, set it from your
101
+ `package.json` / `meta.json`:
102
+
103
+ ```typescript
104
+ // config.env.ts
105
+ import metaData = require('./meta.json');
106
+
107
+ const base: Partial<IServerOptions> = {
108
+ env: envName,
109
+ version: metaData.version, // ← surfaced in /health-check build identity
110
+ // …
111
+ };
112
+ ```
113
+
114
+ Without it, the indicator reports `version: 'unknown'` — harmless, but less useful.
115
+
116
+ ### Adopt the full drift detection (optional)
117
+
118
+ For the end-to-end "is App on the same build as API?" check, the commit must be
119
+ baked into the image at build time and exposed publicly. The
120
+ [nest-server-starter](https://github.com/lenneTech/nest-server-starter) reference
121
+ implementation wires all three layers:
122
+
123
+ 1. **Image** — bake the commit at build time:
124
+ ```dockerfile
125
+ # In the runtime stage (the API reads it at runtime):
126
+ ARG APP_VERSION_COMMIT=unknown
127
+ ENV APP_VERSION_COMMIT=$APP_VERSION_COMMIT
128
+ ```
129
+ 2. **Build arg** — feed the CI commit SHA:
130
+ ```yaml
131
+ # docker-compose.yml
132
+ build:
133
+ args:
134
+ APP_VERSION_COMMIT: ${IMAGE_TAG:-unknown} # IMAGE_TAG = CI_COMMIT_SHA
135
+ ```
136
+ 3. **Public endpoint** — the starter's `meta` module exposes `GET /meta`
137
+ (`S_EVERYONE`) returning `{ version, commit, environment, package, title }`,
138
+ so the frontend can read the API commit and compare it against its own.
139
+
140
+ The contract end to end:
141
+
142
+ ```
143
+ CI commit SHA → IMAGE_TAG (CI) → APP_VERSION_COMMIT build arg (compose)
144
+ → ENV in the image (Dockerfile) → getCommit() → /health-check + /meta
145
+ ```
146
+
147
+ Versions (semver) are per-component and may legitimately differ — only the
148
+ **commit** is compared. Local builds without CI report `unknown`, which clients
149
+ use to suppress the drift warning.
150
+
151
+ ---
152
+
153
+ ## Breaking Changes
154
+
155
+ None. All additions are backward compatible.
156
+
157
+ ---
158
+
159
+ ## Compatibility Notes
160
+
161
+ - **Existing `/health-check` consumers:** The response gains one extra key
162
+ (`build`) under `info` and `details`. `status` is unchanged and the `build`
163
+ indicator can never flip the overall status to `error` (it always reports `up`).
164
+ Only consumers that assert the *exact* set of keys need updating; disable it via
165
+ `healthCheck.configs.build.enabled: false` if you must keep the old shape.
166
+ - **GraphQL `healthCheck` query:** `info`/`details` are `JSON` scalars, so the new
167
+ `build` key flows through without any schema change.
168
+ - **`IServerOptions`:** `version` and `healthCheck.configs.build` are new optional
169
+ fields. Existing configs compile unchanged.
170
+ - **Commit resolution:** If you previously read `process.env.APP_VERSION_COMMIT`
171
+ yourself, you can switch to `getCommit()` for the identical result (with the
172
+ `'unknown'` fallback) — optional, not required.
173
+ - **Vendor-mode consumers:** The same additions land in
174
+ `src/core/common/helpers/meta.helper.ts` and
175
+ `src/core/modules/health-check/core-health-check.service.ts`. Sync via the
176
+ vendored-core updater. No flatten-fix change required.
177
+
178
+ ---
179
+
180
+ ## Verifying
181
+
182
+ ```bash
183
+ # Boot the API and read the health check
184
+ pnpm start &
185
+ curl -s http://localhost:3000/health-check | jq '.info.build'
186
+ # → { "status": "up", "commit": "unknown", "env": "local", "version": "1.4.0" }
187
+
188
+ # After a CI build that passes APP_VERSION_COMMIT, "commit" is the real SHA:
189
+ APP_VERSION_COMMIT=$(git rev-parse HEAD) pnpm start &
190
+ curl -s http://localhost:3000/health-check | jq '.info.build.commit'
191
+ ```
192
+
193
+ A unit test for the helpers ships in `tests/unit/meta-helper.spec.ts`.
194
+
195
+ ---
196
+
197
+ ## Troubleshooting
198
+
199
+ ### `commit` is always `"unknown"`
200
+
201
+ The build arg never reached the process. Check, in order:
202
+
203
+ 1. CI passes the commit SHA into the build (`IMAGE_TAG` / `APP_VERSION_COMMIT`).
204
+ 2. `docker-compose.yml` forwards it as a `build.args.APP_VERSION_COMMIT`.
205
+ 3. The **API** Dockerfile declares `ARG`/`ENV APP_VERSION_COMMIT` in the **runtime
206
+ stage** (the API reads it at runtime, not build time).
207
+ 4. For a frontend, the commit must be set **before** the bundler runs (e.g. Nuxt
208
+ freezes `runtimeConfig.public` at build time) — there the `ARG`/`ENV` belongs in
209
+ the **build stage**, not the runtime stage.
210
+
211
+ ### `version` is `"unknown"`
212
+
213
+ Set `IServerOptions.version` (see [#3](#3-new-iserveroptionsversion-config-field)).
214
+ The health check reads `config.version`; nothing else populates it.
215
+
216
+ ### I don't want build info on an unauthenticated endpoint
217
+
218
+ `/health-check` is `S_EVERYONE` by design (probes need it). Disable just the build
219
+ indicator with `healthCheck.configs.build.enabled: false`, or restrict the route
220
+ in your project.
221
+
222
+ ---
223
+
224
+ ## Module Documentation
225
+
226
+ ### Core Common — build-identity helpers
227
+
228
+ - **Helpers:** `src/core/common/helpers/meta.helper.ts` (`getCommit`, `getBuildInfo`, `BuildInfo`, `DEFAULT_COMMIT_ENV`, `UNKNOWN_COMMIT`)
229
+ - **Unit tests:** `tests/unit/meta-helper.spec.ts`
230
+
231
+ ### Core Health Check
232
+
233
+ - **Service:** `src/core/modules/health-check/core-health-check.service.ts` (build indicator)
234
+ - **Controller / Resolver:** `src/core/modules/health-check/core-health-check.controller.ts`, `core-health-check.resolver.ts`
235
+ - **Config:** `IServerOptions.healthCheck.configs.build` + `IServerOptions.version` in `src/core/common/interfaces/server-options.interface.ts`
236
+
237
+ ### Reference implementation (full drift detection)
238
+
239
+ - **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`
240
+ - **Frontend system page:** [nuxt-base-starter](https://github.com/lenneTech/nuxt-base-starter) — `/app/admin/system` compares App vs. API builds
241
+
242
+ ---
243
+
244
+ ## References
245
+
246
+ - [Migration Guide 11.26.2 → 11.26.3](./11.26.2-to-11.26.3.md) — Previous release (OpenAPI enum `$ref` fix)
247
+ - [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)
248
+ - [nuxt-base-starter](https://github.com/lenneTech/nuxt-base-starter) (frontend drift detection)
249
+ - [lt-monorepo](https://github.com/lenneTech/lt-monorepo) — `docker-compose.yml` + CI wiring for the `APP_VERSION_COMMIT` contract
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/nest-server",
3
- "version": "11.26.3",
3
+ "version": "11.27.0",
4
4
  "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).",
5
5
  "keywords": [
6
6
  "node",
@@ -232,7 +232,8 @@
232
232
  "defu@<=6.1.6": "6.1.7",
233
233
  "follow-redirects@<=1.15.11": "1.16.0",
234
234
  "uuid@<14.0.0": "14.0.0",
235
- "postcss@<8.5.10": "8.5.12"
235
+ "postcss@<8.5.10": "8.5.12",
236
+ "esbuild@>=0.17.0 <0.28.1": "0.28.1"
236
237
  },
237
238
  "//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).",
238
239
  "peerDependencyRules": {
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Build identity helpers.
3
+ *
4
+ * A deployed image's exact build is identified by its git commit SHA, baked in
5
+ * at build time via an environment variable (default `APP_VERSION_COMMIT`, fed
6
+ * from the CI commit SHA — the same value typically used as the image tag).
7
+ *
8
+ * Unlike the semantic `version` (a rarely-bumped semver that may legitimately
9
+ * differ between an API and its frontend, since each is versioned independently)
10
+ * the commit SHA uniquely pins the exact running build. A frontend and backend
11
+ * deployed together bake the SAME commit, but each reads its own at runtime — so
12
+ * comparing them detects a drifted / stale container after a partial rollout.
13
+ *
14
+ * @see getCommit
15
+ * @see getBuildInfo
16
+ */
17
+
18
+ /** Default environment variable the build commit SHA is baked into. */
19
+ export const DEFAULT_COMMIT_ENV = 'APP_VERSION_COMMIT';
20
+
21
+ /** Defined value returned when no commit could be resolved. */
22
+ export const UNKNOWN_COMMIT = 'unknown';
23
+
24
+ /**
25
+ * Build identity of the running process.
26
+ */
27
+ export interface BuildInfo {
28
+ /** Git commit SHA the build was produced from, or `'unknown'`. */
29
+ commit: string;
30
+
31
+ /** Environment the process runs in (e.g. `'production'`), if provided. */
32
+ env?: string;
33
+
34
+ /** Semantic version of the build, or `'unknown'`. */
35
+ version?: string;
36
+ }
37
+
38
+ /**
39
+ * Resolve the git commit SHA the running build was produced from.
40
+ *
41
+ * Reads `process.env[envName]` (default `APP_VERSION_COMMIT`). Falls back to
42
+ * `'unknown'` so local / un-tagged builds still return a defined value — clients
43
+ * use `'unknown'` to suppress the "builds drifted" warning instead of comparing
44
+ * against an empty string.
45
+ *
46
+ * @param envName Name of the environment variable holding the commit SHA.
47
+ */
48
+ export function getCommit(envName: string = DEFAULT_COMMIT_ENV): string {
49
+ return process.env[envName] || UNKNOWN_COMMIT;
50
+ }
51
+
52
+ /**
53
+ * Assemble the build identity of the running process.
54
+ *
55
+ * Combines the commit SHA (from the environment) with an optionally supplied
56
+ * `version` and `env`. Designed to be surfaced via a public meta / info endpoint
57
+ * and the health check so deployments can be compared at a glance.
58
+ *
59
+ * @param options.commitEnvName Override the env var the commit SHA is read from.
60
+ * @param options.env Environment label to include (e.g. from the config).
61
+ * @param options.version Semantic version to include (e.g. from package.json).
62
+ */
63
+ export function getBuildInfo(options: { commitEnvName?: string; env?: string; version?: string } = {}): BuildInfo {
64
+ return {
65
+ commit: getCommit(options.commitEnvName),
66
+ env: options.env,
67
+ version: options.version || UNKNOWN_COMMIT,
68
+ };
69
+ }
@@ -1591,6 +1591,17 @@ export interface IServerOptions {
1591
1591
  */
1592
1592
  env?: string;
1593
1593
 
1594
+ /**
1595
+ * Semantic version of the running build (e.g. from package.json / meta.json).
1596
+ *
1597
+ * Surfaced via the build-identity health indicator alongside the commit SHA.
1598
+ * Unlike the commit (which uniquely pins the exact build), the version is a
1599
+ * rarely-bumped semver and may legitimately differ between API and frontend.
1600
+ *
1601
+ * @since 11.27.0
1602
+ */
1603
+ version?: string;
1604
+
1594
1605
  /**
1595
1606
  * Configuration for the error code module
1596
1607
  *
@@ -1671,6 +1682,24 @@ export interface IServerOptions {
1671
1682
  * Configuration of single health checks
1672
1683
  */
1673
1684
  configs?: {
1685
+ /**
1686
+ * Configuration for the build-identity health indicator.
1687
+ *
1688
+ * Always reports status "up" and surfaces the running build's commit SHA,
1689
+ * version and environment under the health check's `info`/`details`, so a
1690
+ * drifted / stale container can be detected after a partial rollout. The
1691
+ * commit is read from `process.env.APP_VERSION_COMMIT` (baked at build
1692
+ * time from the CI commit SHA); `version` comes from {@link IServerOptions.version}.
1693
+ *
1694
+ * @since 11.27.0
1695
+ */
1696
+ build?: {
1697
+ /**
1698
+ * Whether to include build identity in the health check (default: true)
1699
+ */
1700
+ enabled?: boolean;
1701
+ };
1702
+
1674
1703
  /**
1675
1704
  * Configuration for database health check
1676
1705
  */
@@ -9,6 +9,7 @@ import {
9
9
  import type { MongoosePingCheckSettings } from '@nestjs/terminus/dist/health-indicator/database/mongoose.health.js';
10
10
  import type { DiskHealthIndicatorOptions } from '@nestjs/terminus/dist/health-indicator/disk/disk-health-options.type.js';
11
11
 
12
+ import { getBuildInfo } from '../../common/helpers/meta.helper';
12
13
  import { ConfigService } from '../../common/services/config.service';
13
14
 
14
15
  /**
@@ -64,6 +65,18 @@ export class CoreHealthCheckService {
64
65
  ),
65
66
  );
66
67
  }
68
+ // Build identity (commit / version / env) — always reported as "up", so it
69
+ // surfaces under `info`/`details` without ever affecting the overall health
70
+ // status. Lets ops/monitoring detect a drifted or stale container after a
71
+ // partial rollout (the same commit-SHA signal the admin UI compares). The
72
+ // commit is baked into the image at build time (APP_VERSION_COMMIT, fed from
73
+ // the CI commit SHA); `version`/`env` come from the config. Opt out with
74
+ // `healthCheck.configs.build.enabled: false`.
75
+ if (this.config.get<boolean>('healthCheck.configs.build.enabled') !== false) {
76
+ const build = getBuildInfo({ env: this.config.get<string>('env'), version: this.config.get<string>('version') });
77
+ healthIndicatorFunctions.push(async () => ({ build: { ...build, status: 'up' as const } }));
78
+ }
79
+
67
80
  return this.health.check(healthIndicatorFunctions);
68
81
  }
69
82
  }
package/src/index.ts CHANGED
@@ -40,6 +40,7 @@ export * from './core/common/helpers/interceptor.helper';
40
40
  export * from './core/common/helpers/gridfs.helper';
41
41
  export * from './core/common/helpers/input.helper';
42
42
  export * from './core/common/helpers/logging.helper';
43
+ export * from './core/common/helpers/meta.helper';
43
44
  export * from './core/common/helpers/model.helper';
44
45
  export * from './core/common/helpers/register-enum.helper';
45
46
  export * from './core/common/helpers/scim.helper';