-
Notifications
You must be signed in to change notification settings - Fork 2
Fix deployment issues #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -30,18 +30,24 @@ if (!process.env.DATABASE_URL) { | |||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Log migration info for debugging | ||||||||||||||||||||||||||||||||||||
| const sanitizedDbUrl = (() => { | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| const url = new URL(process.env.DATABASE_URL!); | ||||||||||||||||||||||||||||||||||||
| url.password = "****"; | ||||||||||||||||||||||||||||||||||||
| return url.toString(); | ||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||
| return process.env.DATABASE_URL!.replace(/:[^:@]+@/, ":****@"); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| })(); // Hide password | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| console.log("[Migrations] Starting migration process"); | ||||||||||||||||||||||||||||||||||||
| console.log("[Migrations] Migrations folder:", migrationsFolder); | ||||||||||||||||||||||||||||||||||||
| console.log("[Migrations] Database URL:", sanitizedDbUrl); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| logger.info( | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| migrationsFolder, | ||||||||||||||||||||||||||||||||||||
| databaseUrl: (() => { | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| const url = new URL(process.env.DATABASE_URL!); | ||||||||||||||||||||||||||||||||||||
| url.password = "****"; | ||||||||||||||||||||||||||||||||||||
| return url.toString(); | ||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||
| return process.env.DATABASE_URL!.replace(/:[^:@]+@/, ":****@"); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| })(), // Hide password | ||||||||||||||||||||||||||||||||||||
| databaseUrl: sanitizedDbUrl, | ||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||
| "[Migrations] Starting migration process", | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
|
|
@@ -55,11 +61,102 @@ const db = drizzle(migrationClient); | |||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Run migrations | ||||||||||||||||||||||||||||||||||||
| async function main() { | ||||||||||||||||||||||||||||||||||||
| // Verify migrations folder exists and has files | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| const fs = await import("node:fs/promises"); | ||||||||||||||||||||||||||||||||||||
| const migrationFiles = await fs.readdir(migrationsFolder); | ||||||||||||||||||||||||||||||||||||
| const sqlFiles = migrationFiles.filter((f) => f.endsWith(".sql")); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| logger.info( | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| migrationsFolder, | ||||||||||||||||||||||||||||||||||||
| totalFiles: migrationFiles.length, | ||||||||||||||||||||||||||||||||||||
| sqlFiles: sqlFiles.length, | ||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||
| "[Migrations] Found migration files", | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| if (sqlFiles.length === 0) { | ||||||||||||||||||||||||||||||||||||
| const errorMsg = `No SQL migration files found in ${migrationsFolder}`; | ||||||||||||||||||||||||||||||||||||
| console.error(`[Migrations] ERROR: ${errorMsg}`); | ||||||||||||||||||||||||||||||||||||
| logger.error({}, errorMsg); | ||||||||||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } catch (error: any) { | ||||||||||||||||||||||||||||||||||||
| const errorMsg = `Failed to read migrations folder: ${error.message}`; | ||||||||||||||||||||||||||||||||||||
| console.error(`[Migrations] ERROR: ${errorMsg}`); | ||||||||||||||||||||||||||||||||||||
| console.error(`[Migrations] Stack: ${error.stack}`); | ||||||||||||||||||||||||||||||||||||
| logger.error({ err: error }, errorMsg); | ||||||||||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+83
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Resource Management on Early Exit: When aborting the process due to missing migration files or a failed folder read, the code calls Recommended Solution: await migrationClient.end();
process.exit(1); |
||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+85
to
+91
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a TypeScript best practice to type
Suggested change
|
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Test database connection before running migrations | ||||||||||||||||||||||||||||||||||||
| console.log("[Migrations] Testing database connection..."); | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| const result = await migrationClient`SELECT 1 as test`; | ||||||||||||||||||||||||||||||||||||
| console.log("[Migrations] ✓ Database connection successful"); | ||||||||||||||||||||||||||||||||||||
| logger.info({}, "[Migrations] Database connection test passed"); | ||||||||||||||||||||||||||||||||||||
| } catch (error: any) { | ||||||||||||||||||||||||||||||||||||
| const errorMsg = `Database connection failed: ${error.message}`; | ||||||||||||||||||||||||||||||||||||
| console.error("[Migrations] ✗ DATABASE CONNECTION FAILED"); | ||||||||||||||||||||||||||||||||||||
| console.error("Error Code:", error?.code); | ||||||||||||||||||||||||||||||||||||
| console.error("Error Message:", error?.message); | ||||||||||||||||||||||||||||||||||||
| console.error("Database URL:", sanitizedDbUrl); | ||||||||||||||||||||||||||||||||||||
| console.error("Full Error:", JSON.stringify(error, null, 2)); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| logger.error( | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| err: error, | ||||||||||||||||||||||||||||||||||||
| code: error?.code, | ||||||||||||||||||||||||||||||||||||
| databaseUrl: sanitizedDbUrl, | ||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||
| errorMsg, | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| await migrationClient.end(); | ||||||||||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+99
to
+117
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a recurring pattern of logging detailed error information to both This function could take the error, a message, and any additional context, handle the logging to both console and the structured logger, and then exit the process. For example: function exitWithError(message: string, error: unknown, context: Record<string, any> = {}) {
const errorDetails = {
message: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : undefined,
...context,
};
console.error(`[Migrations] ✗ ERROR: ${message}`);
console.error("Details:", JSON.stringify(errorDetails, null, 2));
logger.error({ err: error, ...context }, `[Migrations] ✗ ${message}`);
// Consider ending the client connection here if it's always needed before exit
// await migrationClient.end();
process.exit(1);
}Using this would simplify the catch blocks significantly. |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| // Check for existing migrations (helps diagnose state) | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| const existingMigrations = await migrationClient` | ||||||||||||||||||||||||||||||||||||
| SELECT table_name | ||||||||||||||||||||||||||||||||||||
| FROM information_schema.tables | ||||||||||||||||||||||||||||||||||||
| WHERE table_schema = 'drizzle' | ||||||||||||||||||||||||||||||||||||
| AND table_name = '__drizzle_migrations' | ||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| if (existingMigrations.length > 0) { | ||||||||||||||||||||||||||||||||||||
| const appliedMigrations = await migrationClient` | ||||||||||||||||||||||||||||||||||||
| SELECT id, hash, created_at | ||||||||||||||||||||||||||||||||||||
| FROM drizzle.__drizzle_migrations | ||||||||||||||||||||||||||||||||||||
| ORDER BY created_at DESC | ||||||||||||||||||||||||||||||||||||
| LIMIT 5 | ||||||||||||||||||||||||||||||||||||
| `; | ||||||||||||||||||||||||||||||||||||
| console.log( | ||||||||||||||||||||||||||||||||||||
| `[Migrations] Found ${appliedMigrations.length} previously applied migrations`, | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| logger.info( | ||||||||||||||||||||||||||||||||||||
| { count: appliedMigrations.length }, | ||||||||||||||||||||||||||||||||||||
| "[Migrations] Previous migrations detected", | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||
| console.log("[Migrations] No previous migrations found (fresh database)"); | ||||||||||||||||||||||||||||||||||||
| logger.info({}, "[Migrations] Fresh database detected"); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } catch (error: any) { | ||||||||||||||||||||||||||||||||||||
| // Ignore errors here - schema might not exist yet | ||||||||||||||||||||||||||||||||||||
| console.log( | ||||||||||||||||||||||||||||||||||||
| "[Migrations] Could not check migration history (expected on first run)", | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+146
to
+151
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Error Handling for Migration History Check: The catch block for the migration history check ignores all errors, assuming they are expected on first run. This could mask other, more serious issues (e.g., permission errors, network issues) that should be logged for visibility and troubleshooting. Recommended Solution: } catch (error: any) {
logger.warn({ err: error }, "Could not check migration history (expected on first run)");
console.log("[Migrations] Could not check migration history (expected on first run)");
} |
||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| console.log("[Migrations] Running migrations..."); | ||||||||||||||||||||||||||||||||||||
| logger.info({}, "[Migrations] Running migrations..."); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| await migrate(db, { migrationsFolder }); | ||||||||||||||||||||||||||||||||||||
| logger.info({}, "[Migrations] ✓ Migrations completed successfully"); | ||||||||||||||||||||||||||||||||||||
| console.log("[Migrations] ✓ Migrations completed successfully"); // Ensure visibility in Railway | ||||||||||||||||||||||||||||||||||||
| } catch (error: any) { | ||||||||||||||||||||||||||||||||||||
| // Check if it's a "relation already exists" error (PostgreSQL code 42P07) | ||||||||||||||||||||||||||||||||||||
| // Drizzle wraps PostgreSQL errors, so check the cause as well | ||||||||||||||||||||||||||||||||||||
|
|
@@ -72,16 +169,29 @@ async function main() { | |||||||||||||||||||||||||||||||||||
| if (isAlreadyExistsError) { | ||||||||||||||||||||||||||||||||||||
| logger.warn({}, "[Migrations] ⚠️ Some tables already exist - skipping"); | ||||||||||||||||||||||||||||||||||||
| logger.info({}, "[Migrations] ✓ Database schema is up to date"); | ||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||
| logger.error( | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| err: error, | ||||||||||||||||||||||||||||||||||||
| code: errorCode, | ||||||||||||||||||||||||||||||||||||
| message: errorMessage, | ||||||||||||||||||||||||||||||||||||
| migrationsFolder, | ||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||
| "[Migrations] ✗ Migration failed", | ||||||||||||||||||||||||||||||||||||
| console.log( | ||||||||||||||||||||||||||||||||||||
| "[Migrations] ✓ Database schema is up to date (some tables already exist)", | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||
| // Log error details to both logger and console for maximum visibility | ||||||||||||||||||||||||||||||||||||
| const errorDetails = { | ||||||||||||||||||||||||||||||||||||
| code: errorCode, | ||||||||||||||||||||||||||||||||||||
| message: errorMessage, | ||||||||||||||||||||||||||||||||||||
| stack: error?.stack, | ||||||||||||||||||||||||||||||||||||
| cause: error?.cause, | ||||||||||||||||||||||||||||||||||||
| migrationsFolder, | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| console.error("[Migrations] ✗ MIGRATION FAILED - ERROR DETAILS:"); | ||||||||||||||||||||||||||||||||||||
| console.error("Error Code:", errorCode); | ||||||||||||||||||||||||||||||||||||
| console.error("Error Message:", errorMessage); | ||||||||||||||||||||||||||||||||||||
| console.error("Full Error:", JSON.stringify(error, null, 2)); | ||||||||||||||||||||||||||||||||||||
| console.error("Error Stack:", error?.stack); | ||||||||||||||||||||||||||||||||||||
| if (error?.cause) { | ||||||||||||||||||||||||||||||||||||
| console.error("Error Cause:", JSON.stringify(error.cause, null, 2)); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| logger.error(errorDetails, "[Migrations] ✗ Migration failed"); | ||||||||||||||||||||||||||||||||||||
| process.exit(1); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The script uses both
console.logand the structuredlogger.infofor startup messages. While usingconsole.logcan be useful for immediate visibility in some deployment environments, this mix creates inconsistency. For better log management, it would be ideal to rely solely on the structured logger (pino). Pino can be configured with a synchronous transport for development or debugging scenarios if immediate output is a concern. Ifconsole.logis a hard requirement for your deployment platform, consider consolidating this logic so you don't have to call bothconsole.logandlogger.infofor the same event.