Replace raw db.execute tuple casts with queryRows/rowsFrom/execResult/ affectedRows helpers from lib/db, drop redundant mysql2 casts on typed query builders, and centralize per-test fakeForm into test/fake-form. Update db mocks in tests so helpers resolve against mocked execute.
109 lines
3.0 KiB
TypeScript
109 lines
3.0 KiB
TypeScript
import "server-only";
|
|
import { constants } from "node:fs";
|
|
import { access, readdir } from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { sql } from "drizzle-orm";
|
|
import {
|
|
type DiagnosticState,
|
|
migrationState,
|
|
workerState,
|
|
} from "@/lib/admin/installation-state";
|
|
import { fetchOpsHealth } from "@/lib/admin/ops-health";
|
|
import { queryRows } from "@/lib/db";
|
|
import { redis } from "@/lib/redis";
|
|
import { siteSettings } from "@/lib/services/site-settings";
|
|
|
|
export async function inspectInstallation() {
|
|
const started = performance.now();
|
|
const [health, storage, expected, applied, heartbeat, renderer] =
|
|
await Promise.all([
|
|
fetchOpsHealth(),
|
|
Promise.all(
|
|
["storage", "public/nitro-assets", "public/swf"].map(async (name) => ({
|
|
name,
|
|
state: await access(
|
|
path.join(process.cwd(), name),
|
|
constants.R_OK | constants.W_OK,
|
|
)
|
|
.then(() => "ok" as const)
|
|
.catch(() => "failed" as const),
|
|
})),
|
|
),
|
|
readdir(path.join(process.cwd(), "drizzle/migrations"))
|
|
.then((files) =>
|
|
files
|
|
.filter((name) => name.endsWith(".sql"))
|
|
.map((name) => name.slice(0, -4)),
|
|
)
|
|
.catch(() => []),
|
|
queryRows<{ migration: string }>(
|
|
sql`SELECT migration FROM cms_migrations`,
|
|
)
|
|
.then((rows) => rows.map((row) => row.migration))
|
|
.catch(() => null),
|
|
redis
|
|
? redis.get("cms:jobs-worker:heartbeat").catch(() => null)
|
|
: Promise.resolve(null),
|
|
siteSettings.get("nitro_client_url", "").catch(() => ""),
|
|
]);
|
|
const migration = migrationState(expected, applied);
|
|
const release = process.env.NEXT_PUBLIC_CMS_RELEASE ?? "unknown";
|
|
const rows: Array<{ key: string; state: DiagnosticState; detail?: string }> =
|
|
[
|
|
{
|
|
key: "release",
|
|
state: /^[a-f0-9]{40}$/i.test(release) ? "ok" : "unknown",
|
|
detail: /^[a-f0-9]{40}$/i.test(release) ? release : undefined,
|
|
},
|
|
{
|
|
key: "database",
|
|
state: health.dbOk ? "ok" : "failed",
|
|
detail: health.dbOk ? `${health.dbLatencyMs} ms` : undefined,
|
|
},
|
|
{
|
|
key: "redis",
|
|
state:
|
|
health.redisOk === null
|
|
? "missing"
|
|
: health.redisOk
|
|
? "ok"
|
|
: "failed",
|
|
},
|
|
{ key: "emulator", state: health.emulatorOk ? "ok" : "failed" },
|
|
{
|
|
key: "storage",
|
|
state: storage.every((item) => item.state === "ok") ? "ok" : "failed",
|
|
detail:
|
|
storage
|
|
.filter((item) => item.state !== "ok")
|
|
.map((item) => item.name)
|
|
.join(", ") || undefined,
|
|
},
|
|
{
|
|
key: "migrations",
|
|
state: migration.state,
|
|
detail:
|
|
migration.pending === null ? undefined : String(migration.pending),
|
|
},
|
|
{
|
|
key: "worker",
|
|
state: workerState(heartbeat),
|
|
detail:
|
|
heartbeat && Number.isFinite(Date.parse(heartbeat))
|
|
? new Date(heartbeat).toISOString()
|
|
: undefined,
|
|
},
|
|
{
|
|
key: "renderer",
|
|
state: health.dbOk ? "ok" : "unknown",
|
|
detail: health.dbOk && !renderer ? "/nitro-client/" : undefined,
|
|
},
|
|
{ key: "registry", state: "unknown" },
|
|
];
|
|
return {
|
|
rows,
|
|
checkedAt: new Date().toISOString(),
|
|
durationMs: Math.round(performance.now() - started),
|
|
};
|
|
}
|