@lenne.tech/nest-server 11.26.2 → 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,186 @@
1
+ # Migration Guide: 11.26.2 → 11.26.3
2
+
3
+ ## Overview
4
+
5
+ | Category | Details |
6
+ |----------|---------|
7
+ | **Breaking Changes** | None |
8
+ | **Bugfixes** | `@UnifiedField({ enum: … })` no longer emits a broken, unnamed `$ref` in the generated OpenAPI document under `@nestjs/swagger >= 11.4` — enum fields now produce proper named component schemas (or clean inline enums when `enumName: null` / auto-detection fails) instead of `allOf: [{ $ref: '#/components/schemas/' }]`, which crashed OpenAPI client generators like `@hey-api/openapi-ts` |
9
+ | **New Features** | None |
10
+ | **Migration Effort** | 0 minutes (automatic) — drop-in patch release |
11
+
12
+ ---
13
+
14
+ ## Quick Migration
15
+
16
+ No code changes required. The fix applies automatically as soon as the package is updated.
17
+
18
+ ```bash
19
+ # Update package
20
+ pnpm add @lenne.tech/nest-server@11.26.3
21
+
22
+ # Verify build
23
+ pnpm run build
24
+
25
+ # Run tests
26
+ pnpm test
27
+
28
+ # (Optional) regenerate REST client SDK against the new OpenAPI document
29
+ pnpm --filter @your-app/api-sdk openapi-ts
30
+ ```
31
+
32
+ ---
33
+
34
+ ## What's Fixed in 11.26.3
35
+
36
+ ### OpenAPI: broken empty `$ref` on enum-typed `@UnifiedField` properties
37
+
38
+ **Affects:** Any project that
39
+
40
+ - consumes `@lenne.tech/nest-server` together with `@nestjs/swagger >= 11.4` (this repo pinned to `11.4.2`), AND
41
+ - exposes REST endpoints whose DTOs declare enum fields via `@UnifiedField({ enum: … })`, AND
42
+ - runs an OpenAPI client generator (e.g. `@hey-api/openapi-ts`, `openapi-typescript`, `openapi-generator-cli`) against the bundled OpenAPI document.
43
+
44
+ **Symptom (before 11.26.3):**
45
+
46
+ The decorator passed `type: () => String` to `@nestjs/swagger` ALONGSIDE `enum` + `enumName`. `@nestjs/swagger <= 11.2` silently tolerated the combination, but `@nestjs/swagger >= 11.4` emits a broken, **unnamed** enum reference and never registers the enum under `components.schemas`:
47
+
48
+ ```jsonc
49
+ // Generated OpenAPI document — BROKEN
50
+ {
51
+ "components": {
52
+ "schemas": {
53
+ "SomeInput": {
54
+ "properties": {
55
+ "status": {
56
+ "allOf": [
57
+ { "$ref": "#/components/schemas/" } // ← empty target!
58
+ ]
59
+ }
60
+ }
61
+ }
62
+ // ← StatusEnum is missing entirely from components.schemas
63
+ }
64
+ }
65
+ }
66
+ ```
67
+
68
+ Downstream tools crash:
69
+
70
+ ```
71
+ @hey-api/openapi-ts: Missing $ref pointer "#/components/schemas/". Token "" does not exist.
72
+ ```
73
+
74
+ **Fix (in 11.26.3):**
75
+
76
+ `@UnifiedField` no longer sets `swaggerOpts.type` when the field is an enum. `@nestjs/swagger` derives the schema from `enum` + `enumName` correctly:
77
+
78
+ ```jsonc
79
+ // Generated OpenAPI document — CORRECT
80
+ {
81
+ "components": {
82
+ "schemas": {
83
+ "StatusEnum": { "type": "string", "enum": ["draft", "published", "review"] },
84
+ "SomeInput": {
85
+ "properties": {
86
+ "status": {
87
+ "allOf": [
88
+ { "$ref": "#/components/schemas/StatusEnum" } // ← named, resolvable
89
+ ]
90
+ }
91
+ }
92
+ }
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ **Behaviour matrix:**
99
+
100
+ | `@UnifiedField` form | OpenAPI output before 11.26.3 | OpenAPI output in 11.26.3 |
101
+ |---|---|---|
102
+ | `{ enum: MyEnum, enumName: 'MyEnum' }` | Empty `$ref`, `MyEnum` missing from `components.schemas` | Named `MyEnum` schema, property uses `$ref` |
103
+ | `{ enum: MyEnum }` + `registerEnum(MyEnum, { name: 'MyEnum' })` | Empty `$ref` | Named `MyEnum` schema, property uses `$ref` |
104
+ | `{ enum: MyEnum, enumName: null }` (opt out) | Empty `$ref` | Inline `enum: [...]`, no `$ref`, no named schema |
105
+ | `{ enum: MyEnum }` without registration | Empty `$ref` | Inline `enum: [...]`, no `$ref`, no named schema |
106
+ | Long-form `{ enum: { enum: MyEnum, enumName: 'MyEnum' } }` (deprecated) | Empty `$ref` | Named `MyEnum` schema, property uses `$ref` (deprecation warning unchanged) |
107
+ | Non-enum fields (`String`, `Number`, `Date`, custom classes, …) | Unchanged | Unchanged |
108
+
109
+ GraphQL schema, class-validator runtime validation (`IsEnum`), Mongoose `@Prop`, and field-level `@Restricted` / `@Roles` behaviour are all bit-for-bit identical to 11.26.2.
110
+
111
+ ---
112
+
113
+ ## Compatibility Notes
114
+
115
+ - **`@nestjs/swagger <= 11.2` consumers:** The previously emitted `type: () => String` was redundant; removing it produces the same enum schema as before. No observable change.
116
+ - **`@nestjs/swagger >= 11.4` consumers:** The OpenAPI document for enum fields changes from a **broken** empty `$ref` to a **correct** named schema (or clean inline enum). This is strictly a defect fix — any client generator that was previously crashing now succeeds.
117
+ - **OpenAPI client generators / SDK consumers:** After updating, regenerate the SDK once. Enum properties that were previously typed `string` (when the generator silently dropped the broken `$ref`) will now be typed as the proper enum union — review the generated SDK once and adjust call-sites if you relied on the loose `string` type.
118
+ - **GraphQL consumers:** No change. The `Field(...)` factory and enum resolution are untouched.
119
+ - **`@UnifiedField` public API:** Unchanged. All option shapes (`enum: MyEnum`, `enumName`, deprecated long-form `{ enum: { … } }`, `enumName: null`) keep their documented semantics.
120
+ - **Mongoose / class-validator:** Unchanged. `@Prop({ type: baseType })` still applied for enum fields; `IsEnum(...)` is still the authoritative validator.
121
+ - **Vendor-mode consumers:** Same fix lands in `src/core/common/decorators/unified-field.decorator.ts`. Sync via `/lt-dev:backend:update-nest-server-core`. No flatten-fix change required.
122
+ - **Hidden / excluded enum fields (`@UnifiedField({ exclude: true })`):** Unaffected — those still hide from the OpenAPI document via `ApiHideProperty()`.
123
+
124
+ ---
125
+
126
+ ## Verifying the Fix
127
+
128
+ If you previously hit the empty-`$ref` defect, confirm the regenerated OpenAPI document:
129
+
130
+ ```bash
131
+ # 1. Boot the API and dump the OpenAPI document
132
+ pnpm start &
133
+ curl -s http://localhost:3000/api-json > /tmp/openapi.json
134
+
135
+ # 2. There must be no empty/unnamed component refs
136
+ grep -F '"$ref": "#/components/schemas/"' /tmp/openapi.json && echo 'BROKEN' || echo 'OK'
137
+
138
+ # 3. Every enum used in a DTO must appear in components.schemas
139
+ jq '.components.schemas | keys' /tmp/openapi.json
140
+ ```
141
+
142
+ A complete regression test ships in `tests/unified-field-enum-swagger.e2e-spec.ts` and inspects the real document built by `SwaggerModule.createDocument()` for:
143
+
144
+ - no empty `$ref` anywhere in the document,
145
+ - a named component schema per enum (string / numeric / array / auto-detected / deprecated long-form),
146
+ - correct enum values and property references,
147
+ - inline-enum fallback for `enumName: null` and for unregistered enums (no empty `$ref`).
148
+
149
+ ---
150
+
151
+ ## Troubleshooting
152
+
153
+ ### After updating, my generated SDK still has an empty `$ref` error
154
+
155
+ Make sure the SDK is regenerated against a **freshly rebuilt** API. Stale `openapi.json` artefacts checked into the consumer repo continue to be broken. Rebuild the API (`pnpm run build`) and re-export the document (`/api-json` or `SwaggerModule.createDocument` snapshot) before running your codegen.
156
+
157
+ ### My enum properties used to be typed `string` in the generated client and now they're a strict union
158
+
159
+ That is the corrected behaviour — the previous client was generated against a broken document and silently widened the type. Update call-sites to use the enum union (or import the enum from your shared package). If you need the loose `string` type during the rollout, your codegen typically offers an `--enum-style` flag (e.g. `@hey-api/openapi-ts` → `enums: 'javascript'`) to keep the old shape.
160
+
161
+ ### I rely on the deprecated long-form `enum: { enum: MyEnum, enumName: 'MyEnum' }`
162
+
163
+ It still works and now produces the same named schema as the shortcut form. The deprecation warning emitted at decoration time is unchanged. Plan to migrate to the shortcut form (`enum: MyEnum, enumName: 'MyEnum'`) before a future MINOR removes the long form.
164
+
165
+ ---
166
+
167
+ ## Module Documentation
168
+
169
+ ### Core Common — `@UnifiedField`
170
+
171
+ - **Decorator:** `src/core/common/decorators/unified-field.decorator.ts`
172
+ - **Architecture notes:** [.claude/rules/architecture.md](../.claude/rules/architecture.md) (Input Validation section)
173
+ - **Reference tests:**
174
+ - `tests/unified-field-enum-swagger.e2e-spec.ts` — OpenAPI schema regression guard (the contract this release restores)
175
+ - `tests/unified-field-enum.e2e-spec.ts` — metadata-level enum behaviour
176
+ - `tests/unified-field-enum-api.e2e-spec.ts` — runtime REST/GraphQL enum behaviour
177
+
178
+ ---
179
+
180
+ ## References
181
+
182
+ - [Migration Guide 11.26.1 → 11.26.2](./11.26.1-to-11.26.2.md) — Previous release (`COOKIE_PREFIX` env, cross-layer cookie-prefix lockstep)
183
+ - [Architecture rules — Input Validation](../.claude/rules/architecture.md)
184
+ - [@nestjs/swagger 11.4 release notes](https://github.com/nestjs/swagger/releases) — context for the schema-emission change that surfaced the latent defect
185
+ - [@hey-api/openapi-ts](https://heyapi.dev/) — one of the OpenAPI client generators that was crashing on the broken document
186
+ - [nest-server-starter](https://github.com/lenneTech/nest-server-starter) (reference implementation)
@@ -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.2",
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": {
@@ -408,13 +408,19 @@ export function UnifiedField(opts: UnifiedFieldOptions = {}): PropertyDecorator
408
408
  swaggerOpts.required = true;
409
409
  }
410
410
 
411
- // Set type for swagger
412
- if (baseType) {
413
- if (normalizedEnum) {
414
- swaggerOpts.type = () => String;
415
- } else {
416
- swaggerOpts.type = baseType;
417
- }
411
+ // Set type for swagger.
412
+ //
413
+ // For enum fields we deliberately do NOT set `type`: @nestjs/swagger derives
414
+ // the schema from `enum` + `enumName` (set further below). Passing
415
+ // `type: () => String` ALONGSIDE `enum`/`enumName` makes @nestjs/swagger
416
+ // >= 11.4 emit a broken, UNNAMED enum reference
417
+ // (`allOf: [{ $ref: '#/components/schemas/' }]`) and never adds the enum to
418
+ // `components.schemas`. That crashes OpenAPI client generators — e.g.
419
+ // @hey-api/openapi-ts fails with «Missing $ref pointer "#/components/schemas/"».
420
+ // (On @nestjs/swagger <= 11.2 the extra `type` was tolerated, which is why
421
+ // this only surfaced after a swagger bump.)
422
+ if (baseType && !normalizedEnum) {
423
+ swaggerOpts.type = baseType;
418
424
  }
419
425
 
420
426
  // Set description
@@ -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';