Merge pull request #8 from emdash-cms/fix/fresh-deploy-setup-redirect

fix: redirect to setup wizard on fresh deployments
This commit is contained in:
Matt Kane
2026-04-01 14:24:11 +01:00
committed by GitHub
3 changed files with 45 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
---
"@emdash-cms/admin": patch
---
Update branding

View File

@@ -0,0 +1,5 @@
---
"emdash": patch
---
Fix crash on fresh deployments when the first request hits a public page before setup has run. The middleware now detects an empty database and redirects to the setup wizard instead of letting template helpers query missing tables.

View File

@@ -54,6 +54,16 @@ let runtimeInitializing = false;
/** Whether i18n config has been initialized from the virtual module */ /** Whether i18n config has been initialized from the virtual module */
let i18nInitialized = false; let i18nInitialized = false;
/**
* Whether we've verified the database has been set up.
* On a fresh deployment the first request may hit a public page, bypassing
* runtime init. Without this check, template helpers like getSiteSettings()
* would query an empty database and crash. Once verified (or once the runtime
* has initialized via an admin/API request), this stays true for the worker's
* lifetime.
*/
let setupVerified = false;
/** /**
* Get EmDash configuration from virtual module * Get EmDash configuration from virtual module
*/ */
@@ -190,6 +200,28 @@ export const onRequest = defineMiddleware(async (context, next) => {
if (!isEmDashRoute && !isPublicRuntimeRoute && !hasEditCookie && !hasPreviewToken) { if (!isEmDashRoute && !isPublicRuntimeRoute && !hasEditCookie && !hasPreviewToken) {
const sessionUser = await context.session?.get("user"); const sessionUser = await context.session?.get("user");
if (!sessionUser && !playgroundDb) { if (!sessionUser && !playgroundDb) {
// On a fresh deployment the database may be completely empty.
// Public pages call getSiteSettings() / getMenu() via getDb(), which
// bypasses runtime init and would crash with "no such table: options".
// Do a one-time lightweight probe using the same getDb() instance the
// page will use: if the migrations table doesn't exist, no migrations
// have ever run -- redirect to the setup wizard.
if (!setupVerified) {
try {
const { getDb } = await import("../loader.js");
const db = await getDb();
await db
.selectFrom("_emdash_migrations" as keyof Database)
.selectAll()
.limit(1)
.execute();
setupVerified = true;
} catch {
// Table doesn't exist -> fresh database, redirect to setup
return context.redirect("/_emdash/admin/setup");
}
}
const response = await next(); const response = await next();
setBaselineSecurityHeaders(response); setBaselineSecurityHeaders(response);
return response; return response;
@@ -210,6 +242,9 @@ export const onRequest = defineMiddleware(async (context, next) => {
// Get or create runtime // Get or create runtime
const runtime = await getRuntime(config); const runtime = await getRuntime(config);
// Runtime init runs migrations, so the DB is guaranteed set up
setupVerified = true;
// Get manifest (cached after first call) // Get manifest (cached after first call)
const manifest = await runtime.getManifest(); const manifest = await runtime.getManifest();