#!/usr/bin/env tsx import https from 'https'; // Only run in preview environment if (process.env.VERCEL_ENV !== 'preview') { console.log('Not a preview deployment, skipping Railway update'); process.exit(0); } // Check required environment variables const required = { RAILWAY_API_TOKEN: process.env.RAILWAY_API_TOKEN, RAILWAY_PROJECT_ID: process.env.RAILWAY_PROJECT_ID, RAILWAY_SERVICE_ID: process.env.RAILWAY_SERVICE_ID, RAILWAY_ENVIRONMENT_ID: process.env.RAILWAY_ENVIRONMENT_ID, NEXT_PUBLIC_CONVEX_URL: process.env.NEXT_PUBLIC_CONVEX_URL, }; const missing = Object.entries(required) .filter(([_, value]) => !value) .map(([key]) => key); if (missing.length > 0) { console.log(`Missing environment variables: ${missing.join(', ')}`); console.log('Skipping Railway update'); process.exit(0); } // Main execution function async function main() { console.log('🚀 Starting Railway automation...'); console.log(`New CONVEX_URL: ${required.NEXT_PUBLIC_CONVEX_URL}`); // Step 1: Update CONVEX_URL environment variable await updateEnvironmentVariable(); // Step 2: Get current deployments and abort any in progress await abortOngoingDeployments(); // Step 3: Trigger new deployment with updated environment variable await triggerNewDeployment(); console.log('✅ Railway automation completed successfully!'); console.log('â„šī¸ Railway will automatically redeploy when environment variables change.'); } // Run the main function main().catch((error) => { console.log('❌ Railway automation failed:', error.message); process.exit(1); }); // Helper function to make Railway API requests function makeRailwayRequest(query: string, variables?: any): Promise { return new Promise((resolve, reject) => { const requestBody = JSON.stringify({ query, variables }); const req = https.request( { hostname: 'backboard.railway.app', port: 443, path: '/graphql/v2', method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${required.RAILWAY_API_TOKEN}`, 'Content-Length': Buffer.byteLength(requestBody), }, }, (res) => { let data = ''; res.on('data', (chunk) => (data += chunk)); res.on('end', () => { if (res.statusCode === 200) { try { const result = JSON.parse(data); if (result.errors) { reject(new Error(`GraphQL errors: ${JSON.stringify(result.errors)}`)); } else { resolve(result.data); } } catch (error) { reject(new Error(`Failed to parse response: ${error}`)); } } else { reject(new Error(`Railway API returned ${res.statusCode}: ${data}`)); } }); } ); req.on('error', reject); req.write(requestBody); req.end(); }); } // Step 1: Update environment variable async function updateEnvironmentVariable() { console.log('📝 Updating CONVEX_URL environment variable...'); const query = ` mutation variableUpsert($input: VariableUpsertInput!) { variableUpsert(input: $input) } `; const variables = { input: { projectId: required.RAILWAY_PROJECT_ID, environmentId: required.RAILWAY_ENVIRONMENT_ID, serviceId: required.RAILWAY_SERVICE_ID, name: 'CONVEX_URL', value: required.NEXT_PUBLIC_CONVEX_URL, }, }; try { await makeRailwayRequest(query, variables); console.log('✅ CONVEX_URL updated successfully'); } catch (error) { console.log('❌ Failed to update CONVEX_URL:', (error as Error).message); process.exit(1); } } // Step 2: Abort ongoing deployments async function abortOngoingDeployments() { console.log('🔍 Checking for ongoing deployments...'); // Get current deployments const deploymentsQuery = ` query deployments($projectId: String!, $environmentId: String!, $serviceId: String!) { deployments( first: 5 input: { projectId: $projectId environmentId: $environmentId serviceId: $serviceId } ) { edges { node { id status createdAt } } } } `; try { const result = await makeRailwayRequest(deploymentsQuery, { projectId: required.RAILWAY_PROJECT_ID, environmentId: required.RAILWAY_ENVIRONMENT_ID, serviceId: required.RAILWAY_SERVICE_ID, }); const ongoingDeployments = result.deployments.edges .map((edge: any) => edge.node) .filter((deployment: any) => deployment.status === 'BUILDING' || deployment.status === 'DEPLOYING' || deployment.status === 'WAITING' ); if (ongoingDeployments.length > 0) { console.log(`🛑 Found ${ongoingDeployments.length} ongoing deployment(s), aborting...`); for (const deployment of ongoingDeployments) { const abortQuery = ` mutation deploymentAbort($id: String!) { deploymentAbort(id: $id) } `; try { await makeRailwayRequest(abortQuery, { id: deployment.id }); console.log(`✅ Aborted deployment: ${deployment.id}`); } catch (error) { console.log(`âš ī¸ Failed to abort deployment ${deployment.id}:`, (error as Error).message); // Continue with other deployments } } } else { console.log('✅ No ongoing deployments to abort'); } } catch (error) { console.log('âš ī¸ Failed to check deployments:', (error as Error).message); // Continue anyway - not critical } } // Step 3: Trigger new deployment async function triggerNewDeployment() { console.log('🚀 Triggering new deployment with updated CONVEX_URL...'); // Create a new deployment const deploymentCreateQuery = ` mutation deploymentCreate($input: DeploymentCreateInput!) { deploymentCreate(input: $input) { id } } `; try { const result = await makeRailwayRequest(deploymentCreateQuery, { input: { projectId: required.RAILWAY_PROJECT_ID, environmentId: required.RAILWAY_ENVIRONMENT_ID, serviceId: required.RAILWAY_SERVICE_ID, }, }); if (result.deploymentCreate?.id) { console.log(`✅ New deployment created: ${result.deploymentCreate.id}`); } else { console.log('âš ī¸ Deployment creation response was empty, but no errors occurred'); } } catch (error) { console.log('❌ Failed to create new deployment:', (error as Error).message); // Fallback: Try to restart the latest deployment console.log('🔄 Attempting fallback: restart latest deployment...'); try { await restartLatestDeployment(); } catch (fallbackError) { console.log('❌ Fallback also failed:', (fallbackError as Error).message); console.log('â„šī¸ Environment variable was updated successfully.'); console.log('â„šī¸ Railway will automatically redeploy when environment variables change.'); // Don't fail the build - the env var update succeeded, which is the critical part } } } // Fallback function to restart latest deployment async function restartLatestDeployment() { const latestDeploymentQuery = ` query deployments($projectId: String!, $environmentId: String!, $serviceId: String!) { deployments( first: 1 input: { projectId: $projectId environmentId: $environmentId serviceId: $serviceId } ) { edges { node { id status } } } } `; const result = await makeRailwayRequest(latestDeploymentQuery, { projectId: required.RAILWAY_PROJECT_ID, environmentId: required.RAILWAY_ENVIRONMENT_ID, serviceId: required.RAILWAY_SERVICE_ID, }); const latestDeployment = result.deployments.edges[0]?.node; if (latestDeployment) { const restartQuery = ` mutation deploymentRestart($id: String!) { deploymentRestart(id: $id) } `; await makeRailwayRequest(restartQuery, { id: latestDeployment.id }); console.log('✅ Latest deployment restarted successfully'); } else { throw new Error('No deployments found to restart'); } }