-
Notifications
You must be signed in to change notification settings - Fork 3.6k
feat(health): expose running app version metadata #4757
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,49 @@ | ||
| /** | ||
| * @vitest-environment node | ||
| */ | ||
| import { describe, expect, it } from 'vitest' | ||
| import { NextRequest } from 'next/server' | ||
| import { afterEach, describe, expect, it } from 'vitest' | ||
| import { GET } from '@/app/api/health/route' | ||
|
|
||
| afterEach(() => { | ||
| process.env.APP_VERSION = '' | ||
| process.env.NEXT_PUBLIC_APP_VERSION = '' | ||
| process.env.GIT_SHA = '' | ||
| process.env.VERCEL_GIT_COMMIT_SHA = '' | ||
| process.env.COMMIT_SHA = '' | ||
| }) | ||
|
|
||
| describe('GET /api/health', () => { | ||
| it('returns an ok status payload', async () => { | ||
| const response = await GET() | ||
| it('returns status with runtime version metadata', async () => { | ||
| process.env.APP_VERSION = 'v1.2.3' | ||
| process.env.GIT_SHA = 'abc123' | ||
|
|
||
| const response = await GET(new NextRequest('http://localhost/api/health')) | ||
|
|
||
| expect(response.status).toBe(200) | ||
| await expect(response.json()).resolves.toEqual({ | ||
| status: 'ok', | ||
| timestamp: expect.any(String), | ||
| version: 'v1.2.3', | ||
| commit: 'abc123', | ||
| }) | ||
| }) | ||
|
|
||
| it('falls back to the package version when runtime metadata is not provided', async () => { | ||
| process.env.APP_VERSION = '' | ||
| process.env.NEXT_PUBLIC_APP_VERSION = '' | ||
| process.env.GIT_SHA = '' | ||
| process.env.VERCEL_GIT_COMMIT_SHA = '' | ||
| process.env.COMMIT_SHA = '' | ||
|
|
||
| const response = await GET(new NextRequest('http://localhost/api/health')) | ||
|
|
||
| expect(response.status).toBe(200) | ||
| await expect(response.json()).resolves.toEqual({ | ||
| status: 'ok', | ||
| timestamp: expect.any(String), | ||
| version: '0.1.0', | ||
| commit: null, | ||
| }) | ||
| }) | ||
| }) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,32 @@ | ||
| import type { NextRequest } from 'next/server' | ||
| import { NextResponse } from 'next/server' | ||
| import { healthContract } from '@/lib/api/contracts/health' | ||
| import { parseRequest } from '@/lib/api/server' | ||
| import appPackage from '@/package.json' | ||
|
|
||
| const DEFAULT_VERSION = appPackage.version | ||
|
|
||
| function getAppVersion(): string { | ||
| return process.env.APP_VERSION || process.env.NEXT_PUBLIC_APP_VERSION || DEFAULT_VERSION | ||
| } | ||
|
|
||
| function getAppCommit(): string | null { | ||
| return process.env.GIT_SHA || process.env.VERCEL_GIT_COMMIT_SHA || process.env.COMMIT_SHA || null | ||
| } | ||
|
|
||
| /** | ||
| * Health check endpoint for deployment platforms and container probes. | ||
| */ | ||
| export async function GET(): Promise<Response> { | ||
| return Response.json( | ||
| export async function GET(request: NextRequest): Promise<Response> { | ||
| const parsed = await parseRequest(healthContract, request, {}) | ||
| if (!parsed.success) return parsed.response | ||
|
Comment on lines
+21
to
+22
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Note also that |
||
|
|
||
| return NextResponse.json( | ||
| { | ||
| status: 'ok', | ||
| timestamp: new Date().toISOString(), | ||
| version: getAppVersion(), | ||
| commit: getAppCommit(), | ||
| }, | ||
| { status: 200 } | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { z } from 'zod' | ||
| import { noInputSchema } from '@/lib/api/contracts/primitives' | ||
| import { defineRouteContract } from '@/lib/api/contracts/types' | ||
|
|
||
| export const healthResponseSchema = z.object({ | ||
| status: z.literal('ok'), | ||
| timestamp: z.string(), | ||
| version: z.string(), | ||
| commit: z.string().nullable(), | ||
| }) | ||
|
|
||
| export type HealthResponse = z.output<typeof healthResponseSchema> | ||
|
|
||
| export const healthContract = defineRouteContract({ | ||
| method: 'GET', | ||
| path: '/api/health', | ||
| query: noInputSchema, | ||
| response: { | ||
| mode: 'json', | ||
| schema: healthResponseSchema, | ||
| }, | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'0.1.0'in the test, but it is derived at runtime fromappPackage.versionin the route. If thepackage.jsonversion is ever bumped, this assertion will fail without an obvious cause. Importing the package version directly makes the assertion resilient to version changes.