@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.
- package/FRAMEWORK-API.md +3 -2
- package/dist/core/common/decorators/unified-field.decorator.js +2 -7
- package/dist/core/common/decorators/unified-field.decorator.js.map +1 -1
- package/dist/core/common/helpers/meta.helper.d.ts +13 -0
- package/dist/core/common/helpers/meta.helper.js +18 -0
- package/dist/core/common/helpers/meta.helper.js.map +1 -0
- package/dist/core/common/interfaces/server-options.interface.d.ts +4 -0
- package/dist/core/modules/health-check/core-health-check.service.js +5 -0
- package/dist/core/modules/health-check/core-health-check.service.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/migration-guides/11.26.2-to-11.26.3.md +186 -0
- package/migration-guides/11.26.3-to-11.27.0.md +249 -0
- package/package.json +3 -2
- package/src/core/common/decorators/unified-field.decorator.ts +13 -7
- package/src/core/common/helpers/meta.helper.ts +69 -0
- package/src/core/common/interfaces/server-options.interface.ts +29 -0
- package/src/core/modules/health-check/core-health-check.service.ts +13 -0
- package/src/index.ts +1 -0
|
@@ -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.
|
|
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
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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';
|