mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-20 14:03:47 +00:00
feat(admin): display running version in footer and /health
Inline package.json version at bundle time via src/config/version.ts (resolveJsonModule), surface it in the shared admin/status footer and add it to the /health JSON so self-hosters can tell which build runs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -192,7 +192,7 @@ Breakdown of the _"Per-feed favicon from the last sender's domain"_ item above (
|
||||
|
||||
Self-host operational quality-of-life: knowing which version you run, when to update, and how many people run KTN.
|
||||
|
||||
- [ ] `P3·S` **Display the running version** **[table-stakes, easy]** — surface the deployed app version (from `package.json` `version`, currently `0.2.1`) somewhere visible: the admin UI footer and/or the public status page (`src/routes/home.tsx`), and ideally the `/health` JSON. Bundle the version at build time (inline the `package.json` version into the Worker, since there's no filesystem at runtime) and render it. Foundation for the update-notification item below. — _origin: internal_
|
||||
- [x] `P3·S` **Display the running version** **[table-stakes, easy]** — surface the deployed app version (from `package.json` `version`, currently `0.2.1`) somewhere visible: the admin UI footer and/or the public status page (`src/routes/home.tsx`), and ideally the `/health` JSON. Bundle the version at build time (inline the `package.json` version into the Worker, since there's no filesystem at runtime) and render it. Foundation for the update-notification item below. — **Shipped:** `package.json` version is inlined at bundle time via `src/config/version.ts` (`import pkg from "../../package.json"`, `resolveJsonModule`), exposed as `APP_VERSION`; rendered in the shared admin/status footer (`src/routes/admin/ui.tsx` Layout, so both the status page and admin show it) and added to the `/health` JSON. — _origin: internal_
|
||||
|
||||
- [ ] `P3·M` **Notify when an update is available** **[differentiating for self-hosters]** — compare the running version against the latest GitHub Release tag and show a discreet "update available → vX.Y.Z" banner in the admin UI when behind. Fetch `https://api.github.com/repos/<owner>/<repo>/releases/latest` (cache aggressively — Cache API / KV with a long TTL — to respect GitHub rate limits and avoid a call per page load), compare semver against the bundled version. Depends on the "display version" item. Keep it opt-out-able (it makes one outbound call). — _origin: internal_
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import pkg from "../../package.json";
|
||||
|
||||
/**
|
||||
* The running app version, inlined from package.json at bundle time (the Worker
|
||||
* has no filesystem at runtime). Surfaced in the admin/status footer and the
|
||||
* /health JSON so a self-hoster can tell which build is deployed.
|
||||
*/
|
||||
export const APP_VERSION: string = pkg.version;
|
||||
@@ -1,5 +1,6 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import worker from "./index";
|
||||
import { APP_VERSION } from "./config/version";
|
||||
import { createMockEnv } from "./test/setup";
|
||||
import { createFeedRecord } from "./application/feed-service";
|
||||
import { FeedRepository } from "./infrastructure/feed-repository";
|
||||
@@ -97,6 +98,17 @@ describe("scheduled (cron) TTL cleanup", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /health", () => {
|
||||
it("reports status ok and the bundled app version", async () => {
|
||||
const res = await worker.fetch(req("/health"), env as unknown as Env);
|
||||
expect(res.status).toBe(200);
|
||||
const body = (await res.json()) as { status: string; version: string };
|
||||
expect(body.status).toBe("ok");
|
||||
expect(body.version).toBe(APP_VERSION);
|
||||
expect(body.version).toMatch(/^\d+\.\d+\.\d+/);
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET /robots.txt", () => {
|
||||
it("returns 200 and disallows the private feed/entry paths", async () => {
|
||||
const res = await worker.fetch(req("/robots.txt"), env as unknown as Env);
|
||||
|
||||
+4
-1
@@ -13,6 +13,7 @@ import { hubRouter } from "./routes/hub";
|
||||
import { apiApp } from "./routes/api";
|
||||
import { handleCloudflareEmail } from "./infrastructure/cloudflare-email";
|
||||
import { Env } from "./types";
|
||||
import { APP_VERSION } from "./config/version";
|
||||
import { logger } from "./infrastructure/logger";
|
||||
import { FeedRepository } from "./infrastructure/feed-repository";
|
||||
import { purgeExpiredFeeds } from "./application/feed-cleanup";
|
||||
@@ -185,7 +186,9 @@ app.get("/favicon.ico", handleFavicon); // readers/browsers that hardcode .ico
|
||||
app.get("/favicon/:feedId", handleFeedFavicon);
|
||||
|
||||
// Health check endpoint for monitoring
|
||||
app.get("/health", (c) => c.json({ status: "ok", timestamp: Date.now() }));
|
||||
app.get("/health", (c) =>
|
||||
c.json({ status: "ok", version: APP_VERSION, timestamp: Date.now() }),
|
||||
);
|
||||
|
||||
// Public status page (counters + link to admin)
|
||||
app.get("/", handleHome);
|
||||
|
||||
@@ -3,6 +3,7 @@ import layoutCss from "../../styles/layout.css";
|
||||
import componentsCss from "../../styles/components.css";
|
||||
import utilitiesCss from "../../styles/utilities.css";
|
||||
import { interactiveScripts } from "../../scripts/index";
|
||||
import { APP_VERSION } from "../../config/version";
|
||||
import { FAVICON_PATH } from "../favicon";
|
||||
import { Env } from "../../types";
|
||||
import {
|
||||
@@ -77,6 +78,10 @@ export const Layout = ({ title, label = "admin", children }: LayoutProps) => {
|
||||
>
|
||||
♥ Sponsor
|
||||
</a>
|
||||
<span class="site-footer-sep" aria-hidden="true">
|
||||
·
|
||||
</span>
|
||||
<span class="site-footer-version">v{APP_VERSION}</span>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -247,3 +247,8 @@
|
||||
.site-footer-sponsor:hover {
|
||||
color: #db61a2 !important;
|
||||
}
|
||||
|
||||
.site-footer-version {
|
||||
font-variant-numeric: tabular-nums;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"strict": true,
|
||||
"lib": ["ES2021"],
|
||||
"types": ["@cloudflare/workers-types"],
|
||||
|
||||
Reference in New Issue
Block a user