Conversation
…ckages - Replace git tag --list strategy with package.json-driven tag validation in all 16 publish workflows; use git rev-parse to verify the exact tag exists rather than guessing the latest repo-wide tag - Update error guidance to reflect feat/** → develop → master flow - Standardize dependabot to npm-only, grouped, monthly cadence across all 16 packages; remove github-actions ecosystem updates - Add missing dependabot.yml to AuthKit-UI, ChartKit-UI, HealthKit, HooksKit, paymentkit, StorageKit
* chore(config): add @interfaces/* and @indicators/* path aliases * chore(config): rename eslint.config.js to .mjs to fix ESM loading * chore(git): fix husky pre-commit hook for v10 compatibility * feat(interfaces): add IHealthIndicator, HealthStatus, HealthIndicatorResult * feat(indicators): add PostgresHealthIndicator (SELECT 1 + timeout) * test(indicators): add PostgresHealthIndicator unit tests (success/error/timeout) * feat(indicators): add RedisHealthIndicator (PING + timeout) * test(indicators): add RedisHealthIndicator unit tests (success/error/timeout) * feat(indicators): add HttpHealthIndicator (GET + 2xx check + timeout) * test(indicators): add HttpHealthIndicator unit tests (2xx/non-2xx/network/timeout) * chore(deps): update package-lock after npm install --------- Co-authored-by: saad moumou <saad.moumou.coder@gmail.com>
…iceUnavailableException)
|
There was a problem hiding this comment.
Pull request overview
Adds a reusable @ciscode/health-kit NestJS module providing liveness/readiness HTTP endpoints backed by a pluggable indicator system, plus project/tooling updates to support publishing and CI.
Changes:
- Introduces
HealthKitModule,HealthService, and/live+/readyendpoints (controller factory). - Adds built-in health indicators (Postgres, Redis, Mongo, HTTP) with Jest unit tests.
- Updates TS path aliases, ESLint/lint-staged/Husky, and GitHub workflows for CI + publishing.
Reviewed changes
Copilot reviewed 26 out of 29 changed files in this pull request and generated 14 comments.
Show a summary per file
| File | Description |
|---|---|
| tsconfig.json | Adds path aliases for @interfaces/* and @indicators/*. |
| tsconfig.build.json | Forces build output to CommonJS/Node resolution for dist/. |
| src/services/health.service.ts | Implements concurrent liveness/readiness checks via Promise.allSettled. |
| src/services/health.service.spec.ts | Unit tests for HealthService success/failure/concurrency behavior. |
| src/interfaces/health-indicator.interface.ts | Defines indicator contract and result types. |
| src/indicators/redis.indicator.ts | Redis PING-based indicator with timeout handling. |
| src/indicators/redis.indicator.spec.ts | Tests Redis indicator up/down/timeout paths. |
| src/indicators/postgres.indicator.ts | Postgres SELECT 1 indicator with timeout handling. |
| src/indicators/postgres.indicator.spec.ts | Tests Postgres indicator up/down/timeout paths. |
| src/indicators/mongo.indicator.ts | Mongo { ping: 1 } indicator with timeout handling. |
| src/indicators/mongo.indicator.spec.ts | Tests Mongo indicator up/down/timeout paths. |
| src/indicators/http.indicator.ts | HTTP GET-based indicator using fetch + AbortController timeout. |
| src/indicators/http.indicator.spec.ts | Tests HTTP indicator for 2xx, non-2xx, errors, and timeout. |
| src/index.ts | Switches public exports from template to health-kit API exports. |
| src/health-kit.module.ts | Adds module registration API wiring indicators into HealthService + controller. |
| src/controllers/health.controller.ts | Adds controller factory for /live and /ready endpoints. |
| src/controllers/health.controller.spec.ts | Tests controller behavior (200 vs 503 via exception). |
| package.json | Renames package, updates description/repo, adjusts peer deps, and scripts. |
| package-lock.json | Updates lockfile metadata and dev dependency set. |
| lint-staged.config.mjs | Adds lint-staged config for Prettier + ESLint autofix. |
| jest.config.ts | Adds Jest mappers for new TS path aliases. |
| eslint.config.mjs | Reworks ESLint flat config (TS + import ordering + ignores). |
| eslint.config.js | Removes old ESLint config file. |
| .husky/pre-commit | Changes pre-commit hook to run lint-staged. |
| .github/workflows/release-check.yml | Makes Sonar scan run in PR CI and adds commit status reporting. |
| .github/workflows/publish.yml | Publishes on master push with version-tag validation and provenance. |
| .github/workflows/pr-validation.yml | Updates CI Node version to 22. |
| .github/dependabot.yml | Adds monthly Dependabot updates for npm deps. |
| .github/CODEOWNERS | Adds CODEOWNERS ownership rules. |
| * import { HealthModule } from '@ciscode/health-kit'; | ||
| * import { MongoHealthIndicator } from '@ciscode/health-kit'; | ||
| * | ||
| * @Module({ | ||
| * imports: [ | ||
| * HealthModule.register({ | ||
| * path: 'health', | ||
| * liveness: [], | ||
| * readiness: [new MongoHealthIndicator(dataSource)], |
There was a problem hiding this comment.
The example uses HealthModule.register(...), but the module exported by this package is HealthKitModule. Also, the example passes dataSource into MongoHealthIndicator, but that indicator expects a Mongo Db-like handle (e.g. mongoose.connection.db). Update the example to match the actual public API and expected constructor argument.
| * import { HealthModule } from '@ciscode/health-kit'; | |
| * import { MongoHealthIndicator } from '@ciscode/health-kit'; | |
| * | |
| * @Module({ | |
| * imports: [ | |
| * HealthModule.register({ | |
| * path: 'health', | |
| * liveness: [], | |
| * readiness: [new MongoHealthIndicator(dataSource)], | |
| * import { HealthKitModule, MongoHealthIndicator } from '@ciscode/health-kit'; | |
| * import mongoose from 'mongoose'; | |
| * | |
| * @Module({ | |
| * imports: [ | |
| * HealthKitModule.register({ | |
| * path: 'health', | |
| * liveness: [], | |
| * readiness: [new MongoHealthIndicator(mongoose.connection.db)], |
| async live(): Promise<HealthCheckResult> { | ||
| const result = await this.healthService.checkLiveness(); | ||
| if (result.status === "error") throw new ServiceUnavailableException(result); | ||
| return result; |
There was a problem hiding this comment.
Throwing ServiceUnavailableException(result) will serialize and return the full indicator payload (including internal dependency names and error messages) to callers. Consider returning a minimal 503 response (or making detailed output configurable) to avoid leaking operational details outside trusted networks.
| - name: Build | ||
| run: npm run build --if-present | ||
|
|
||
| - name: Run tests (if present) | ||
| run: npm test --if-present | ||
| continue-on-error: false | ||
| - name: Lint | ||
| run: npm run lint --if-present 2>/dev/null || true | ||
|
|
||
| - name: Build package | ||
| run: npm run build | ||
| - name: Test | ||
| run: npm test --if-present 2>/dev/null || true |
There was a problem hiding this comment.
These lint/test steps swallow failures (|| true), so the workflow can publish even when linting or tests fail. Remove the error suppression so publishing is blocked on failing checks (or make it explicitly conditional with a separate opt-in).
|
|
||
| echo "✅ package.json version: $PKG_VERSION" | ||
| echo "✅ Tag $TAG exists in repo" |
There was a problem hiding this comment.
The tag validation only checks that the version tag exists somewhere in the repo; it does not verify that the current commit being published is actually tagged with that version. Consider asserting the tag points at HEAD (or at least that package.json version matches the commit tag) to prevent publishing an unintended revision.
| echo "✅ package.json version: $PKG_VERSION" | |
| echo "✅ Tag $TAG exists in repo" | |
| HEAD_SHA=$(git rev-parse HEAD) | |
| TAG_SHA=$(git rev-parse "$TAG") | |
| if [[ "$HEAD_SHA" != "$TAG_SHA" ]]; then | |
| echo "❌ ERROR: Tag $TAG does not point at HEAD." | |
| echo "" | |
| echo "HEAD commit: $HEAD_SHA" | |
| echo "Tag commit: $TAG_SHA" | |
| echo "" | |
| echo "Ensure that the commit being pushed to master is the one tagged with $TAG." | |
| echo "Typically you should:" | |
| echo " 1. Create the version with 'npm version patch|minor|major' on the release commit" | |
| echo " 2. Push branch + tag so that $TAG points at the commit being published" | |
| exit 1 | |
| fi | |
| echo "✅ package.json version: $PKG_VERSION" | |
| echo "✅ Tag $TAG exists in repo" | |
| echo "✅ Tag $TAG points at HEAD" |
| async check(): Promise<HealthIndicatorResult> { | ||
| try { | ||
| await Promise.race([this.client.ping(), this._timeout()]); | ||
| return { name: "redis", status: "up" }; | ||
| } catch (error) { | ||
| return { | ||
| name: "redis", | ||
| status: "down", | ||
| message: error instanceof Error ? error.message : "Unknown error", | ||
| }; | ||
| } | ||
| } | ||
|
|
||
| private _timeout(): Promise<never> { | ||
| return new Promise((_, reject) => | ||
| setTimeout(() => reject(new Error("Timeout")), this.timeoutMs), | ||
| ); | ||
| } |
There was a problem hiding this comment.
The timeout implementation starts a setTimeout but never clears it on success, so each health check schedules a timer that will still fire later. Consider storing the timer handle and clearTimeout it in a finally, or use an AbortSignal/timeout helper that can be cancelled.
| * @example | ||
| * ```typescript | ||
| * import Redis from 'ioredis'; | ||
| * | ||
| * const redis = new Redis({ host: 'localhost', port: 6379 }); | ||
| * | ||
| * HealthModule.register({ | ||
| * path: 'health', | ||
| * liveness: [], | ||
| * readiness: [new RedisHealthIndicator(redis)], | ||
| * }); | ||
| * ``` |
There was a problem hiding this comment.
The example references HealthModule.register(...), but the exported module is HealthKitModule (per src/index.ts). Update the docs so consumers can copy/paste a working example.
| * @example | ||
| * ```typescript | ||
| * // With a pg.Pool | ||
| * const pool = new Pool({ connectionString: process.env.DATABASE_URL }); | ||
| * | ||
| * HealthModule.register({ | ||
| * path: 'health', | ||
| * liveness: [], | ||
| * readiness: [new PostgresHealthIndicator(pool)], | ||
| * }); | ||
| * ``` |
There was a problem hiding this comment.
The example references HealthModule.register(...), but the exported module is HealthKitModule (per src/index.ts). Update the docs so consumers can copy/paste a working example.
| * @example | ||
| * ```typescript | ||
| * import mongoose from 'mongoose'; | ||
| * | ||
| * HealthModule.register({ | ||
| * path: 'health', | ||
| * liveness: [], | ||
| * readiness: [new MongoHealthIndicator(mongoose.connection.db)], | ||
| * }); | ||
| * ``` |
There was a problem hiding this comment.
The example references HealthModule.register(...), but the exported module is HealthKitModule (per src/index.ts). Update the docs so consumers can copy/paste a working example.
| * @example | ||
| * ```typescript | ||
| * HealthModule.register({ | ||
| * path: 'health', | ||
| * liveness: [], | ||
| * readiness: [ | ||
| * new HttpHealthIndicator('https://api.example.com/health'), | ||
| * ], | ||
| * }); | ||
| * ``` |
There was a problem hiding this comment.
The example references HealthModule.register(...), but the exported module is HealthKitModule (per src/index.ts). Update the docs so consumers can copy/paste a working example.
| -Dsonar.organization=${{ env.SONAR_ORGANIZATION }} | ||
| -Dsonar.projectKey=${{ env.SONAR_PROJECT_KEY }} | ||
| -Dsonar.sources=src | ||
| -Dsonar.tests=test |
There was a problem hiding this comment.
Sonar is configured with -Dsonar.tests=test, but this repo’s Jest config also places many tests under src/**/*.spec.ts. As-is, Sonar may treat src/**/*.spec.ts as source code (or miss tests/coverage attribution). Consider setting sonar.tests to include src (or adding explicit sonar.test.inclusions / sonar.exclusions for **/*.spec.ts).
| -Dsonar.tests=test | |
| -Dsonar.tests=src,test | |
| -Dsonar.test.inclusions=**/*.spec.ts |



Summary
Why
Checklist
npm run lintpassesnpm run typecheckpassesnpm testpassesnpm run buildpassesnpx changeset) if this affects consumersNotes