mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
feat(api): public /api/version endpoint for the v1.4 About surface (#128)
Adds a public GET endpoint that exposes the running build's version, optional Git SHA, optional ISO build timestamp, license, and canonical links (repository, changelog, docs site). Three consumers already lined up against this: - The Settings → About section (v1.4 plan A3) renders version + build metadata + a "Check for updates" button. - The footer of every page can show the version dezent without another roundtrip. - The "Check for updates" button compares the returned version against the GHCR API. Build-time env vars (`NEXT_PUBLIC_APP_BUILD_SHA`, `NEXT_PUBLIC_APP_BUILT_AT`) are read straight off `process.env` — the existing `docker-publish.yml` workflow can wire them once it needs to. Local `pnpm dev` returns `null` for both fields and the UI falls back to "development" wording. Static export (`force-static`) so the endpoint adds zero load on the DB and survives in the static-cache page chunks. Tests: 395 → 400 (+3 for the version envelope shape and env-var plumbing). The remaining About UI lands in a follow-up so this PR stays scoped to the API contract. Co-authored-by: Marc-André Bombeck <noreply@anthropic.com>
This commit is contained in:
1 parent
760b2c9ba0
commit
f131d6be2c
2 files changed
+91
No files matched your search
@@ -0,0 +1,52 @@
|
||||
import { describe, expect, it, vi, beforeEach } from "vitest";
|
||||
|
||||
vi.mock("@/lib/api-handler", () => ({
|
||||
apiHandler: <T extends (...args: unknown[]) => unknown>(fn: T) => fn,
|
||||
}));
|
||||
|
||||
import { GET } from "../route";
|
||||
|
||||
beforeEach(() => {
|
||||
delete process.env.NEXT_PUBLIC_APP_BUILD_SHA;
|
||||
delete process.env.NEXT_PUBLIC_APP_BUILT_AT;
|
||||
});
|
||||
|
||||
interface VersionEnvelope {
|
||||
data: {
|
||||
version: string;
|
||||
buildSha: string | null;
|
||||
builtAt: string | null;
|
||||
license: string;
|
||||
repository: string;
|
||||
changelog: string;
|
||||
docs: string;
|
||||
};
|
||||
}
|
||||
|
||||
describe("GET /api/version", () => {
|
||||
it("returns the package.json version", async () => {
|
||||
const response = await (GET as unknown as () => Promise<Response>)();
|
||||
const body = (await response.json()) as VersionEnvelope;
|
||||
expect(body.data.version).toMatch(/^\d+\.\d+\.\d+(-[a-z0-9.-]+)?$/i);
|
||||
expect(body.data.license).toBe("AGPL-3.0");
|
||||
expect(body.data.repository).toContain("github.com");
|
||||
expect(body.data.changelog).toContain("CHANGELOG");
|
||||
expect(body.data.docs).toContain("docs.healthlog.dev");
|
||||
});
|
||||
|
||||
it("returns null buildSha / builtAt for a `pnpm dev` build", async () => {
|
||||
const response = await (GET as unknown as () => Promise<Response>)();
|
||||
const body = (await response.json()) as VersionEnvelope;
|
||||
expect(body.data.buildSha).toBeNull();
|
||||
expect(body.data.builtAt).toBeNull();
|
||||
});
|
||||
|
||||
it("surfaces the build SHA and timestamp when the env vars are set", async () => {
|
||||
process.env.NEXT_PUBLIC_APP_BUILD_SHA = "abc1234";
|
||||
process.env.NEXT_PUBLIC_APP_BUILT_AT = "2026-05-08T12:00:00.000Z";
|
||||
const response = await (GET as unknown as () => Promise<Response>)();
|
||||
const body = (await response.json()) as VersionEnvelope;
|
||||
expect(body.data.buildSha).toBe("abc1234");
|
||||
expect(body.data.builtAt).toBe("2026-05-08T12:00:00.000Z");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { apiHandler } from "@/lib/api-handler";
|
||||
import { apiSuccess } from "@/lib/api-response";
|
||||
import packageJson from "../../../../package.json";
|
||||
|
||||
export const dynamic = "force-static";
|
||||
export const revalidate = false;
|
||||
|
||||
/**
|
||||
* GET /api/version
|
||||
*
|
||||
* Public endpoint exposing the running build's version. Used by the
|
||||
* Settings → About surface, the footer of every page, and the
|
||||
* "Check for updates" button (which compares against the GHCR API).
|
||||
*
|
||||
* The build SHA and built-at timestamp come from environment variables
|
||||
* baked at image build time:
|
||||
* - `NEXT_PUBLIC_APP_BUILD_SHA` — short Git SHA, set by the
|
||||
* `docker-publish` workflow.
|
||||
* - `NEXT_PUBLIC_APP_BUILT_AT` — ISO-8601 build timestamp, same
|
||||
* workflow.
|
||||
*
|
||||
* For local `pnpm dev` neither is set; the route returns `null` and
|
||||
* the UI falls back to "development" wording.
|
||||
*/
|
||||
export const GET = apiHandler(async () => {
|
||||
const version = packageJson.version;
|
||||
const buildSha = process.env.NEXT_PUBLIC_APP_BUILD_SHA?.trim() || null;
|
||||
const builtAt = process.env.NEXT_PUBLIC_APP_BUILT_AT?.trim() || null;
|
||||
|
||||
return apiSuccess({
|
||||
version,
|
||||
buildSha,
|
||||
builtAt,
|
||||
license: "AGPL-3.0",
|
||||
repository: "https://github.com/MBombeck/HealthLog",
|
||||
changelog: "https://github.com/MBombeck/HealthLog/blob/main/CHANGELOG.md",
|
||||
docs: "https://docs.healthlog.dev",
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user