Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

post-deploy-smoke.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

post-deploy-smoke.tsBlame129 lines · 1 contributor
b1be050CC LABS App1#!/usr/bin/env bun
2/**
3 * Post-deploy smoke CLI.
4 *
5 * Runs the 15-endpoint smoke suite (`src/lib/post-deploy-smoke.ts`)
6 * against $GLUECRON_HOST (default http://localhost:3010) AFTER systemctl
7 * restart but BEFORE the workflow marks the deploy successful.
8 *
9 * Also verifies that the latest *.sql in drizzle/ is present in the
10 * running app's /api/version migrations list. This is the second line
11 * of defence against the silent-migration-failure bug that broke
12 * gluecron.com for hours.
13 *
14 * Exit codes:
15 * 0 — every check passed AND the latest migration is reported by the
16 * live process.
17 * 1 — at least one endpoint failed.
18 * 2 — endpoints all passed but the latest migration is missing from
19 * the live process's reported list (a fresh deploy is running
20 * but the DB schema is behind).
21 *
22 * Usage:
23 * bun scripts/post-deploy-smoke.ts
24 * GLUECRON_HOST=https://gluecron.com bun scripts/post-deploy-smoke.ts
25 */
26
27import { readdir } from "fs/promises";
28import { join } from "path";
29import {
30 runChecks,
31 formatTable,
32 latestMigration,
33 type FetchLike,
34} from "../src/lib/post-deploy-smoke";
35
36const HOST = (process.env.GLUECRON_HOST || "http://localhost:3010").replace(
37 /\/$/,
38 ""
39);
40
41const fetchImpl: FetchLike = async (url, init) => {
42 const res = await fetch(url, init);
43 return {
44 status: res.status,
45 text: () => res.text(),
46 };
47};
48
49async function main() {
50 console.log(`[smoke] target: ${HOST}`);
51 const summary = await runChecks({
52 baseUrl: HOST,
53 fetchImpl,
54 log: (line) => console.log(line),
55 });
56
57 console.log("");
58 console.log(formatTable(summary.results));
59 console.log("");
60 console.log(
61 `[smoke] ${summary.passed}/${summary.results.length} checks passed`
62 );
63
64 if (!summary.ok) {
65 console.error("[smoke] FAIL — at least one endpoint check failed");
66 process.exit(1);
67 }
68
69 // ─── Migration-applied verification ─────────────────────────────────
70 // Curl /api/version and confirm the latest drizzle/*.sql file is in
71 // the migrations[] array the live process reports.
72 let drizzleFiles: string[] = [];
73 try {
74 drizzleFiles = (await readdir(join(process.cwd(), "drizzle"))).filter((f) =>
75 f.endsWith(".sql")
76 );
77 } catch (err) {
78 console.warn(
79 `[smoke] WARN: couldn't read drizzle/ directory (${(err as Error).message}) — skipping migration check`
80 );
81 process.exit(0);
82 }
83 const latest = latestMigration(drizzleFiles);
84 if (!latest) {
85 console.warn("[smoke] WARN: no migration files found — skipping check");
86 process.exit(0);
87 }
88
89 let liveMigrations: string[] | null = null;
90 try {
91 const res = await fetch(`${HOST}/api/version`);
92 if (res.status === 200) {
93 const body = (await res.json()) as { migrations?: unknown };
94 if (Array.isArray(body.migrations)) {
95 liveMigrations = body.migrations.filter(
96 (m): m is string => typeof m === "string"
97 );
98 }
99 }
100 } catch (err) {
101 console.warn(
102 `[smoke] WARN: /api/version migrations fetch failed: ${(err as Error).message}`
103 );
104 }
105
106 if (liveMigrations === null) {
107 console.warn(
108 "[smoke] WARN: /api/version did not report migrations[] — server hasn't been redeployed with the S3 patch yet. Skipping migration check."
109 );
110 process.exit(0);
111 }
112
113 if (!liveMigrations.includes(latest)) {
114 console.error(
115 `[smoke] FAIL: latest migration ${latest} is NOT in the live process's applied list (reported: ${liveMigrations.slice(-5).join(", ")})`
116 );
117 process.exit(2);
118 }
119
120 console.log(
121 `[smoke] OK: latest migration ${latest} is applied (live process confirms)`
122 );
123 process.exit(0);
124}
125
126main().catch((err) => {
127 console.error("[smoke] crashed:", err);
128 process.exit(1);
129});