@firebreak/vitals 1.2.2 → 1.3.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.
@@ -61,7 +61,7 @@ function statusToLabel(status) {
61
61
  return labels[status];
62
62
  }
63
63
  function toJson(response) {
64
- return {
64
+ const json = {
65
65
  status: statusToLabel(response.status),
66
66
  timestamp: response.timestamp,
67
67
  checks: Object.fromEntries(
@@ -71,38 +71,62 @@ function toJson(response) {
71
71
  ])
72
72
  )
73
73
  };
74
+ if (response.cachedAt !== void 0) {
75
+ json.cachedAt = response.cachedAt;
76
+ }
77
+ return json;
74
78
  }
75
79
  function httpStatusCode(status) {
76
80
  return status === Status.HEALTHY ? 200 : 503;
77
81
  }
78
82
 
79
83
  // src/handler.ts
80
- var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks"];
84
+ var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
81
85
  function createHealthcheckHandler(options) {
82
- const { registry, token = null, queryParamName = "token", metadata = {} } = options;
86
+ const { registry, token: rawToken, deep = false, queryParamName = "token", metadata = {} } = options;
87
+ const isEmptyToken = typeof rawToken === "string" && rawToken.trim() === "";
88
+ if (deep && isEmptyToken) {
89
+ throw new Error(
90
+ "Cannot use `deep: true` with an empty string token. This would expose deep healthcheck data without authentication. Either set a non-empty token or explicitly set `token: null`."
91
+ );
92
+ }
93
+ const token = rawToken == null || isEmptyToken ? null : rawToken;
83
94
  for (const key of Object.keys(metadata)) {
84
95
  if (RESERVED_METADATA_KEYS.includes(key)) {
85
96
  throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
86
97
  }
87
98
  }
88
99
  return async (req) => {
89
- if (token !== null) {
90
- const provided = extractToken({
91
- queryParams: req.queryParams,
92
- authorizationHeader: req.authorizationHeader,
93
- queryParamName
94
- });
95
- if (provided === null) {
96
- const body = {
97
- status: "ok",
98
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
99
- ...metadata
100
+ if (token === null) {
101
+ if (deep) {
102
+ const response2 = await registry.run();
103
+ return {
104
+ status: httpStatusCode(response2.status),
105
+ body: { ...metadata, ...toJson(response2) }
100
106
  };
101
- return { status: 200, body };
102
- }
103
- if (!verifyToken(provided, token)) {
104
- return { status: 403, body: { error: "Forbidden" } };
105
107
  }
108
+ const body = {
109
+ status: "ok",
110
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
111
+ ...metadata
112
+ };
113
+ return { status: 200, body };
114
+ }
115
+ const provided = extractToken({
116
+ queryParams: req.queryParams,
117
+ authorizationHeader: req.authorizationHeader,
118
+ queryParamName
119
+ });
120
+ if (provided === null) {
121
+ const body = {
122
+ status: "ok",
123
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
124
+ ...metadata
125
+ };
126
+ return { status: 200, body };
127
+ }
128
+ if (!verifyToken(provided, token)) {
129
+ return { status: 403, body: { error: "Forbidden" } };
106
130
  }
107
131
  const response = await registry.run();
108
132
  return {
@@ -112,6 +136,240 @@ function createHealthcheckHandler(options) {
112
136
  };
113
137
  }
114
138
 
139
+ // src/html.ts
140
+ function escapeHtml(str) {
141
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
142
+ }
143
+ function statusColor(status) {
144
+ switch (status) {
145
+ case "healthy":
146
+ case "ok":
147
+ return "#22C55E";
148
+ case "degraded":
149
+ return "#F59E0B";
150
+ case "outage":
151
+ return "#ED1C24";
152
+ default:
153
+ return "#ED1C24";
154
+ }
155
+ }
156
+ var DEEP_KNOWN_KEYS = /* @__PURE__ */ new Set(["status", "timestamp", "checks", "cachedAt"]);
157
+ function isDeepResponse(body) {
158
+ return "checks" in body && "status" in body && body.status !== "ok";
159
+ }
160
+ function isShallowResponse(body) {
161
+ return "status" in body && body.status === "ok";
162
+ }
163
+ function isErrorResponse(body) {
164
+ return "error" in body && !("status" in body);
165
+ }
166
+ function renderStatusBadge(label, color) {
167
+ return `<span style="
168
+ display: inline-flex;
169
+ align-items: center;
170
+ gap: 8px;
171
+ padding: 4px 12px;
172
+ border-radius: 9999px;
173
+ background: ${color}1A;
174
+ font-family: 'Inter', system-ui, sans-serif;
175
+ font-size: 12px;
176
+ font-weight: 600;
177
+ letter-spacing: 0.05em;
178
+ text-transform: uppercase;
179
+ color: ${color};
180
+ "><span style="
181
+ width: 8px;
182
+ height: 8px;
183
+ border-radius: 50%;
184
+ background: ${color};
185
+ box-shadow: 0 0 6px ${color}80;
186
+ "></span>${escapeHtml(label)}</span>`;
187
+ }
188
+ function renderMetadataItems(items) {
189
+ if (items.length === 0) return "";
190
+ return `<div style="
191
+ display: flex;
192
+ flex-wrap: wrap;
193
+ gap: 24px;
194
+ margin-top: 16px;
195
+ ">${items.map(
196
+ ([label, value]) => `<div>
197
+ <div style="
198
+ font-family: 'Inter', system-ui, sans-serif;
199
+ font-size: 10px;
200
+ font-weight: 600;
201
+ letter-spacing: 0.05em;
202
+ text-transform: uppercase;
203
+ color: #A1A1AA;
204
+ margin-bottom: 4px;
205
+ ">${escapeHtml(label)}</div>
206
+ <div style="
207
+ font-family: 'JetBrains Mono', monospace;
208
+ font-size: 13px;
209
+ color: #E4E4E7;
210
+ ">${escapeHtml(value)}</div>
211
+ </div>`
212
+ ).join("")}</div>`;
213
+ }
214
+ function renderCheckRow(name, check) {
215
+ const color = statusColor(check.status);
216
+ return `<div style="
217
+ display: flex;
218
+ align-items: center;
219
+ gap: 12px;
220
+ padding: 12px 0;
221
+ border-top: 1px solid #27272A;
222
+ ">
223
+ <span style="
224
+ width: 8px;
225
+ height: 8px;
226
+ border-radius: 50%;
227
+ background: ${color};
228
+ box-shadow: 0 0 6px ${color}80;
229
+ flex-shrink: 0;
230
+ "></span>
231
+ <span style="
232
+ font-family: 'Inter', system-ui, sans-serif;
233
+ font-size: 12px;
234
+ text-transform: uppercase;
235
+ letter-spacing: 0.05em;
236
+ color: ${color};
237
+ font-weight: 600;
238
+ min-width: 72px;
239
+ ">${escapeHtml(check.status)}</span>
240
+ <span style="
241
+ font-family: 'Inter', system-ui, sans-serif;
242
+ font-size: 14px;
243
+ font-weight: 500;
244
+ color: #E4E4E7;
245
+ flex: 1;
246
+ ">${escapeHtml(name)}</span>
247
+ <span style="
248
+ font-family: 'JetBrains Mono', monospace;
249
+ font-size: 12px;
250
+ color: #A1A1AA;
251
+ min-width: 60px;
252
+ text-align: right;
253
+ ">${escapeHtml(String(check.latencyMs))}ms</span>
254
+ <span style="
255
+ font-family: 'JetBrains Mono', monospace;
256
+ font-size: 12px;
257
+ color: #71717A;
258
+ max-width: 200px;
259
+ overflow: hidden;
260
+ text-overflow: ellipsis;
261
+ white-space: nowrap;
262
+ ">${escapeHtml(check.message)}</span>
263
+ </div>`;
264
+ }
265
+ function renderPage(borderColor, content) {
266
+ return `<!DOCTYPE html>
267
+ <html lang="en">
268
+ <head>
269
+ <meta charset="utf-8">
270
+ <meta name="viewport" content="width=device-width, initial-scale=1">
271
+ <title>Healthcheck</title>
272
+ <style></style>
273
+ </head>
274
+ <body style="
275
+ margin: 0;
276
+ padding: 40px 20px;
277
+ background: #08080A;
278
+ min-height: 100vh;
279
+ display: flex;
280
+ justify-content: center;
281
+ align-items: flex-start;
282
+ box-sizing: border-box;
283
+ ">
284
+ <div style="
285
+ width: 100%;
286
+ max-width: 560px;
287
+ background: #18181B;
288
+ border: 1px solid #27272A;
289
+ border-left: 3px solid ${borderColor};
290
+ border-radius: 8px;
291
+ padding: 24px;
292
+ ">
293
+ ${content}
294
+ </div>
295
+ </body>
296
+ </html>`;
297
+ }
298
+ function renderHealthcheckHtml(body) {
299
+ if (isErrorResponse(body)) {
300
+ const color = "#ED1C24";
301
+ return renderPage(
302
+ color,
303
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
304
+ <span style="
305
+ font-family: 'Inter', system-ui, sans-serif;
306
+ font-size: 18px;
307
+ font-weight: 700;
308
+ color: #E4E4E7;
309
+ ">Healthcheck</span>
310
+ ${renderStatusBadge("ERROR", color)}
311
+ </div>
312
+ <div style="
313
+ margin-top: 16px;
314
+ font-family: 'JetBrains Mono', monospace;
315
+ font-size: 13px;
316
+ color: #EF4444;
317
+ ">${escapeHtml(body.error)}</div>`
318
+ );
319
+ }
320
+ if (isShallowResponse(body)) {
321
+ const color = statusColor("ok");
322
+ const { status: _, timestamp, ...rest } = body;
323
+ const metadataItems = Object.entries(rest).map(
324
+ ([k, v]) => [k, String(v)]
325
+ );
326
+ const allItems = [
327
+ ["timestamp", timestamp],
328
+ ...metadataItems
329
+ ];
330
+ return renderPage(
331
+ color,
332
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
333
+ <span style="
334
+ font-family: 'Inter', system-ui, sans-serif;
335
+ font-size: 18px;
336
+ font-weight: 700;
337
+ color: #E4E4E7;
338
+ ">Healthcheck</span>
339
+ ${renderStatusBadge("OK", color)}
340
+ </div>
341
+ ${renderMetadataItems(allItems)}`
342
+ );
343
+ }
344
+ if (isDeepResponse(body)) {
345
+ const color = statusColor(body.status);
346
+ const { timestamp, checks } = body;
347
+ const metadataItems = Object.entries(body).filter(([k]) => !DEEP_KNOWN_KEYS.has(k)).map(([k, v]) => [k, String(v)]);
348
+ const allItems = [
349
+ ["timestamp", timestamp],
350
+ ...metadataItems
351
+ ];
352
+ const checkRows = Object.entries(checks).sort(([a], [b]) => a.localeCompare(b)).map(([name, check]) => renderCheckRow(name, check)).join("");
353
+ return renderPage(
354
+ color,
355
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
356
+ <span style="
357
+ font-family: 'Inter', system-ui, sans-serif;
358
+ font-size: 18px;
359
+ font-weight: 700;
360
+ color: #E4E4E7;
361
+ ">Healthcheck</span>
362
+ ${renderStatusBadge(body.status, color)}
363
+ </div>
364
+ ${renderMetadataItems(allItems)}
365
+ <div style="margin-top: 20px;">
366
+ ${checkRows}
367
+ </div>`
368
+ );
369
+ }
370
+ return renderPage("#ED1C24", `<pre style="color: #E4E4E7; font-family: 'JetBrains Mono', monospace;">${escapeHtml(JSON.stringify(body, null, 2))}</pre>`);
371
+ }
372
+
115
373
  // src/integrations/express.ts
116
374
  function createHealthcheckMiddleware(options) {
117
375
  const handle = createHealthcheckHandler(options);
@@ -121,7 +379,12 @@ function createHealthcheckMiddleware(options) {
121
379
  queryParams: req.query,
122
380
  authorizationHeader: req.headers.authorization ?? null
123
381
  });
124
- res.status(result.status).json(result.body);
382
+ const preferred = req.accepts(["json", "html"]);
383
+ if (preferred === "html") {
384
+ res.status(result.status).type("html").send(renderHealthcheckHtml(result.body));
385
+ } else {
386
+ res.status(result.status).json(result.body);
387
+ }
125
388
  } catch {
126
389
  res.status(500).json({ error: "Internal Server Error" });
127
390
  }
@@ -1,6 +1,6 @@
1
1
  import { RequestHandler } from 'express';
2
- import { H as HealthcheckHandlerOptions } from '../handler-Bvf66wzh.cjs';
3
- import '../core-BJ2Z0rRi.cjs';
2
+ import { H as HealthcheckHandlerOptions } from '../handler-COH7lot9.cjs';
3
+ import '../core-Bee03bJm.cjs';
4
4
 
5
5
  type HealthcheckMiddlewareOptions = HealthcheckHandlerOptions;
6
6
  declare function createHealthcheckMiddleware(options: HealthcheckMiddlewareOptions): RequestHandler;
@@ -1,6 +1,6 @@
1
1
  import { RequestHandler } from 'express';
2
- import { H as HealthcheckHandlerOptions } from '../handler-R2U3ygLo.js';
3
- import '../core-BJ2Z0rRi.js';
2
+ import { H as HealthcheckHandlerOptions } from '../handler-CVYUad84.js';
3
+ import '../core-Bee03bJm.js';
4
4
 
5
5
  type HealthcheckMiddlewareOptions = HealthcheckHandlerOptions;
6
6
  declare function createHealthcheckMiddleware(options: HealthcheckMiddlewareOptions): RequestHandler;
@@ -35,7 +35,7 @@ function statusToLabel(status) {
35
35
  return labels[status];
36
36
  }
37
37
  function toJson(response) {
38
- return {
38
+ const json = {
39
39
  status: statusToLabel(response.status),
40
40
  timestamp: response.timestamp,
41
41
  checks: Object.fromEntries(
@@ -45,38 +45,62 @@ function toJson(response) {
45
45
  ])
46
46
  )
47
47
  };
48
+ if (response.cachedAt !== void 0) {
49
+ json.cachedAt = response.cachedAt;
50
+ }
51
+ return json;
48
52
  }
49
53
  function httpStatusCode(status) {
50
54
  return status === Status.HEALTHY ? 200 : 503;
51
55
  }
52
56
 
53
57
  // src/handler.ts
54
- var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks"];
58
+ var RESERVED_METADATA_KEYS = ["status", "timestamp", "checks", "cachedAt"];
55
59
  function createHealthcheckHandler(options) {
56
- const { registry, token = null, queryParamName = "token", metadata = {} } = options;
60
+ const { registry, token: rawToken, deep = false, queryParamName = "token", metadata = {} } = options;
61
+ const isEmptyToken = typeof rawToken === "string" && rawToken.trim() === "";
62
+ if (deep && isEmptyToken) {
63
+ throw new Error(
64
+ "Cannot use `deep: true` with an empty string token. This would expose deep healthcheck data without authentication. Either set a non-empty token or explicitly set `token: null`."
65
+ );
66
+ }
67
+ const token = rawToken == null || isEmptyToken ? null : rawToken;
57
68
  for (const key of Object.keys(metadata)) {
58
69
  if (RESERVED_METADATA_KEYS.includes(key)) {
59
70
  throw new Error(`Metadata key '${key}' is reserved. Use a different key name.`);
60
71
  }
61
72
  }
62
73
  return async (req) => {
63
- if (token !== null) {
64
- const provided = extractToken({
65
- queryParams: req.queryParams,
66
- authorizationHeader: req.authorizationHeader,
67
- queryParamName
68
- });
69
- if (provided === null) {
70
- const body = {
71
- status: "ok",
72
- timestamp: (/* @__PURE__ */ new Date()).toISOString(),
73
- ...metadata
74
+ if (token === null) {
75
+ if (deep) {
76
+ const response2 = await registry.run();
77
+ return {
78
+ status: httpStatusCode(response2.status),
79
+ body: { ...metadata, ...toJson(response2) }
74
80
  };
75
- return { status: 200, body };
76
- }
77
- if (!verifyToken(provided, token)) {
78
- return { status: 403, body: { error: "Forbidden" } };
79
81
  }
82
+ const body = {
83
+ status: "ok",
84
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
85
+ ...metadata
86
+ };
87
+ return { status: 200, body };
88
+ }
89
+ const provided = extractToken({
90
+ queryParams: req.queryParams,
91
+ authorizationHeader: req.authorizationHeader,
92
+ queryParamName
93
+ });
94
+ if (provided === null) {
95
+ const body = {
96
+ status: "ok",
97
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
98
+ ...metadata
99
+ };
100
+ return { status: 200, body };
101
+ }
102
+ if (!verifyToken(provided, token)) {
103
+ return { status: 403, body: { error: "Forbidden" } };
80
104
  }
81
105
  const response = await registry.run();
82
106
  return {
@@ -86,6 +110,240 @@ function createHealthcheckHandler(options) {
86
110
  };
87
111
  }
88
112
 
113
+ // src/html.ts
114
+ function escapeHtml(str) {
115
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
116
+ }
117
+ function statusColor(status) {
118
+ switch (status) {
119
+ case "healthy":
120
+ case "ok":
121
+ return "#22C55E";
122
+ case "degraded":
123
+ return "#F59E0B";
124
+ case "outage":
125
+ return "#ED1C24";
126
+ default:
127
+ return "#ED1C24";
128
+ }
129
+ }
130
+ var DEEP_KNOWN_KEYS = /* @__PURE__ */ new Set(["status", "timestamp", "checks", "cachedAt"]);
131
+ function isDeepResponse(body) {
132
+ return "checks" in body && "status" in body && body.status !== "ok";
133
+ }
134
+ function isShallowResponse(body) {
135
+ return "status" in body && body.status === "ok";
136
+ }
137
+ function isErrorResponse(body) {
138
+ return "error" in body && !("status" in body);
139
+ }
140
+ function renderStatusBadge(label, color) {
141
+ return `<span style="
142
+ display: inline-flex;
143
+ align-items: center;
144
+ gap: 8px;
145
+ padding: 4px 12px;
146
+ border-radius: 9999px;
147
+ background: ${color}1A;
148
+ font-family: 'Inter', system-ui, sans-serif;
149
+ font-size: 12px;
150
+ font-weight: 600;
151
+ letter-spacing: 0.05em;
152
+ text-transform: uppercase;
153
+ color: ${color};
154
+ "><span style="
155
+ width: 8px;
156
+ height: 8px;
157
+ border-radius: 50%;
158
+ background: ${color};
159
+ box-shadow: 0 0 6px ${color}80;
160
+ "></span>${escapeHtml(label)}</span>`;
161
+ }
162
+ function renderMetadataItems(items) {
163
+ if (items.length === 0) return "";
164
+ return `<div style="
165
+ display: flex;
166
+ flex-wrap: wrap;
167
+ gap: 24px;
168
+ margin-top: 16px;
169
+ ">${items.map(
170
+ ([label, value]) => `<div>
171
+ <div style="
172
+ font-family: 'Inter', system-ui, sans-serif;
173
+ font-size: 10px;
174
+ font-weight: 600;
175
+ letter-spacing: 0.05em;
176
+ text-transform: uppercase;
177
+ color: #A1A1AA;
178
+ margin-bottom: 4px;
179
+ ">${escapeHtml(label)}</div>
180
+ <div style="
181
+ font-family: 'JetBrains Mono', monospace;
182
+ font-size: 13px;
183
+ color: #E4E4E7;
184
+ ">${escapeHtml(value)}</div>
185
+ </div>`
186
+ ).join("")}</div>`;
187
+ }
188
+ function renderCheckRow(name, check) {
189
+ const color = statusColor(check.status);
190
+ return `<div style="
191
+ display: flex;
192
+ align-items: center;
193
+ gap: 12px;
194
+ padding: 12px 0;
195
+ border-top: 1px solid #27272A;
196
+ ">
197
+ <span style="
198
+ width: 8px;
199
+ height: 8px;
200
+ border-radius: 50%;
201
+ background: ${color};
202
+ box-shadow: 0 0 6px ${color}80;
203
+ flex-shrink: 0;
204
+ "></span>
205
+ <span style="
206
+ font-family: 'Inter', system-ui, sans-serif;
207
+ font-size: 12px;
208
+ text-transform: uppercase;
209
+ letter-spacing: 0.05em;
210
+ color: ${color};
211
+ font-weight: 600;
212
+ min-width: 72px;
213
+ ">${escapeHtml(check.status)}</span>
214
+ <span style="
215
+ font-family: 'Inter', system-ui, sans-serif;
216
+ font-size: 14px;
217
+ font-weight: 500;
218
+ color: #E4E4E7;
219
+ flex: 1;
220
+ ">${escapeHtml(name)}</span>
221
+ <span style="
222
+ font-family: 'JetBrains Mono', monospace;
223
+ font-size: 12px;
224
+ color: #A1A1AA;
225
+ min-width: 60px;
226
+ text-align: right;
227
+ ">${escapeHtml(String(check.latencyMs))}ms</span>
228
+ <span style="
229
+ font-family: 'JetBrains Mono', monospace;
230
+ font-size: 12px;
231
+ color: #71717A;
232
+ max-width: 200px;
233
+ overflow: hidden;
234
+ text-overflow: ellipsis;
235
+ white-space: nowrap;
236
+ ">${escapeHtml(check.message)}</span>
237
+ </div>`;
238
+ }
239
+ function renderPage(borderColor, content) {
240
+ return `<!DOCTYPE html>
241
+ <html lang="en">
242
+ <head>
243
+ <meta charset="utf-8">
244
+ <meta name="viewport" content="width=device-width, initial-scale=1">
245
+ <title>Healthcheck</title>
246
+ <style></style>
247
+ </head>
248
+ <body style="
249
+ margin: 0;
250
+ padding: 40px 20px;
251
+ background: #08080A;
252
+ min-height: 100vh;
253
+ display: flex;
254
+ justify-content: center;
255
+ align-items: flex-start;
256
+ box-sizing: border-box;
257
+ ">
258
+ <div style="
259
+ width: 100%;
260
+ max-width: 560px;
261
+ background: #18181B;
262
+ border: 1px solid #27272A;
263
+ border-left: 3px solid ${borderColor};
264
+ border-radius: 8px;
265
+ padding: 24px;
266
+ ">
267
+ ${content}
268
+ </div>
269
+ </body>
270
+ </html>`;
271
+ }
272
+ function renderHealthcheckHtml(body) {
273
+ if (isErrorResponse(body)) {
274
+ const color = "#ED1C24";
275
+ return renderPage(
276
+ color,
277
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
278
+ <span style="
279
+ font-family: 'Inter', system-ui, sans-serif;
280
+ font-size: 18px;
281
+ font-weight: 700;
282
+ color: #E4E4E7;
283
+ ">Healthcheck</span>
284
+ ${renderStatusBadge("ERROR", color)}
285
+ </div>
286
+ <div style="
287
+ margin-top: 16px;
288
+ font-family: 'JetBrains Mono', monospace;
289
+ font-size: 13px;
290
+ color: #EF4444;
291
+ ">${escapeHtml(body.error)}</div>`
292
+ );
293
+ }
294
+ if (isShallowResponse(body)) {
295
+ const color = statusColor("ok");
296
+ const { status: _, timestamp, ...rest } = body;
297
+ const metadataItems = Object.entries(rest).map(
298
+ ([k, v]) => [k, String(v)]
299
+ );
300
+ const allItems = [
301
+ ["timestamp", timestamp],
302
+ ...metadataItems
303
+ ];
304
+ return renderPage(
305
+ color,
306
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
307
+ <span style="
308
+ font-family: 'Inter', system-ui, sans-serif;
309
+ font-size: 18px;
310
+ font-weight: 700;
311
+ color: #E4E4E7;
312
+ ">Healthcheck</span>
313
+ ${renderStatusBadge("OK", color)}
314
+ </div>
315
+ ${renderMetadataItems(allItems)}`
316
+ );
317
+ }
318
+ if (isDeepResponse(body)) {
319
+ const color = statusColor(body.status);
320
+ const { timestamp, checks } = body;
321
+ const metadataItems = Object.entries(body).filter(([k]) => !DEEP_KNOWN_KEYS.has(k)).map(([k, v]) => [k, String(v)]);
322
+ const allItems = [
323
+ ["timestamp", timestamp],
324
+ ...metadataItems
325
+ ];
326
+ const checkRows = Object.entries(checks).sort(([a], [b]) => a.localeCompare(b)).map(([name, check]) => renderCheckRow(name, check)).join("");
327
+ return renderPage(
328
+ color,
329
+ `<div style="display: flex; align-items: center; justify-content: space-between;">
330
+ <span style="
331
+ font-family: 'Inter', system-ui, sans-serif;
332
+ font-size: 18px;
333
+ font-weight: 700;
334
+ color: #E4E4E7;
335
+ ">Healthcheck</span>
336
+ ${renderStatusBadge(body.status, color)}
337
+ </div>
338
+ ${renderMetadataItems(allItems)}
339
+ <div style="margin-top: 20px;">
340
+ ${checkRows}
341
+ </div>`
342
+ );
343
+ }
344
+ return renderPage("#ED1C24", `<pre style="color: #E4E4E7; font-family: 'JetBrains Mono', monospace;">${escapeHtml(JSON.stringify(body, null, 2))}</pre>`);
345
+ }
346
+
89
347
  // src/integrations/express.ts
90
348
  function createHealthcheckMiddleware(options) {
91
349
  const handle = createHealthcheckHandler(options);
@@ -95,7 +353,12 @@ function createHealthcheckMiddleware(options) {
95
353
  queryParams: req.query,
96
354
  authorizationHeader: req.headers.authorization ?? null
97
355
  });
98
- res.status(result.status).json(result.body);
356
+ const preferred = req.accepts(["json", "html"]);
357
+ if (preferred === "html") {
358
+ res.status(result.status).type("html").send(renderHealthcheckHtml(result.body));
359
+ } else {
360
+ res.status(result.status).json(result.body);
361
+ }
99
362
  } catch {
100
363
  res.status(500).json({ error: "Internal Server Error" });
101
364
  }