|
| 1 | +/** |
| 2 | + * Remotion Lambda deployment service — one-time CLI setup only. |
| 3 | + * |
| 4 | + * This file imports deploySite, deployFunction, and getOrCreateBucket from |
| 5 | + * @remotion/lambda, which pull in @rspack/core → @rspack/binding (a native |
| 6 | + * binary). These MUST NOT be imported from Vercel serverless routes. |
| 7 | + * |
| 8 | + * Run this locally or in CI to set up the Lambda infrastructure, then store |
| 9 | + * the resulting REMOTION_SERVE_URL and REMOTION_FUNCTION_NAME as env vars. |
| 10 | + * |
| 11 | + * @module lib/services/remotion-deploy |
| 12 | + */ |
| 13 | + |
| 14 | +import { |
| 15 | + deploySite, |
| 16 | + deployFunction, |
| 17 | + getOrCreateBucket, |
| 18 | + type AwsRegion, |
| 19 | +} from "@remotion/lambda"; |
| 20 | + |
| 21 | +// --------------------------------------------------------------------------- |
| 22 | +// Helpers |
| 23 | +// --------------------------------------------------------------------------- |
| 24 | + |
| 25 | +function log(message: string, data?: Record<string, unknown>): void { |
| 26 | + const ts = new Date().toISOString(); |
| 27 | + if (data) { |
| 28 | + console.log(`[REMOTION] [${ts}] ${message}`, data); |
| 29 | + } else { |
| 30 | + console.log(`[REMOTION] [${ts}] ${message}`); |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +// --------------------------------------------------------------------------- |
| 35 | +// Types |
| 36 | +// --------------------------------------------------------------------------- |
| 37 | + |
| 38 | +export interface DeployResult { |
| 39 | + functionName: string; |
| 40 | + serveUrl: string; |
| 41 | + siteName: string; |
| 42 | + bucketName: string; |
| 43 | + region: string; |
| 44 | +} |
| 45 | + |
| 46 | +// --------------------------------------------------------------------------- |
| 47 | +// Deploy helper (one-time setup) |
| 48 | +// --------------------------------------------------------------------------- |
| 49 | + |
| 50 | +/** |
| 51 | + * Deploy the Remotion Lambda infrastructure (one-time setup). |
| 52 | + * |
| 53 | + * This will: |
| 54 | + * 1. Create or reuse an S3 bucket for Remotion |
| 55 | + * 2. Deploy the Remotion bundle (site) to S3 |
| 56 | + * 3. Deploy the Lambda function |
| 57 | + * |
| 58 | + * After running this, set the returned `serveUrl` as REMOTION_SERVE_URL |
| 59 | + * and `functionName` as REMOTION_FUNCTION_NAME in your environment. |
| 60 | + * |
| 61 | + * @param entryPoint - Path to the Remotion entry file (e.g., "remotion/index.ts") |
| 62 | + * @returns Deploy result with function name, serve URL, etc. |
| 63 | + */ |
| 64 | +export async function deployRemotionLambda( |
| 65 | + entryPoint: string = "remotion/index.ts" |
| 66 | +): Promise<DeployResult> { |
| 67 | + const region = (process.env.REMOTION_AWS_REGION || "us-east-1") as AwsRegion; |
| 68 | + |
| 69 | + if (!process.env.AWS_ACCESS_KEY_ID || !process.env.AWS_SECRET_ACCESS_KEY) { |
| 70 | + throw new Error( |
| 71 | + "[REMOTION] Cannot deploy: AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY " + |
| 72 | + "must be set in environment variables." |
| 73 | + ); |
| 74 | + } |
| 75 | + |
| 76 | + log("Starting Remotion Lambda deployment", { region, entryPoint }); |
| 77 | + |
| 78 | + // Step 1: Get or create the S3 bucket |
| 79 | + log("Step 1/3: Getting or creating S3 bucket..."); |
| 80 | + let bucketName: string; |
| 81 | + try { |
| 82 | + const bucketResult = await getOrCreateBucket({ region }); |
| 83 | + bucketName = bucketResult.bucketName; |
| 84 | + log(`Bucket ready: ${bucketName}`); |
| 85 | + } catch (err: unknown) { |
| 86 | + const message = err instanceof Error ? err.message : String(err); |
| 87 | + throw new Error( |
| 88 | + `[REMOTION] Failed to get or create S3 bucket in ${region}: ${message}. ` + |
| 89 | + `Ensure your AWS credentials have S3 permissions.` |
| 90 | + ); |
| 91 | + } |
| 92 | + |
| 93 | + // Step 2: Deploy the site (bundle) to S3 |
| 94 | + log("Step 2/3: Deploying Remotion bundle to S3..."); |
| 95 | + let serveUrl: string; |
| 96 | + let siteName: string; |
| 97 | + try { |
| 98 | + const siteResult = await deploySite({ |
| 99 | + entryPoint, |
| 100 | + bucketName, |
| 101 | + region, |
| 102 | + siteName: "codingcat-video-pipeline", |
| 103 | + options: { |
| 104 | + onBundleProgress: (progress: number) => { |
| 105 | + if (progress % 25 === 0 || progress === 100) { |
| 106 | + log(`Bundle progress: ${progress}%`); |
| 107 | + } |
| 108 | + }, |
| 109 | + onUploadProgress: (upload) => { |
| 110 | + if (upload.totalFiles > 0) { |
| 111 | + const pct = Math.round( |
| 112 | + (upload.filesUploaded / upload.totalFiles) * 100 |
| 113 | + ); |
| 114 | + if (pct % 25 === 0) { |
| 115 | + log( |
| 116 | + `Upload progress: ${upload.filesUploaded}/${upload.totalFiles} files (${pct}%)` |
| 117 | + ); |
| 118 | + } |
| 119 | + } |
| 120 | + }, |
| 121 | + }, |
| 122 | + }); |
| 123 | + serveUrl = siteResult.serveUrl; |
| 124 | + siteName = siteResult.siteName; |
| 125 | + log(`Site deployed: ${serveUrl}`, { siteName }); |
| 126 | + } catch (err: unknown) { |
| 127 | + const message = err instanceof Error ? err.message : String(err); |
| 128 | + throw new Error( |
| 129 | + `[REMOTION] Failed to deploy site to S3: ${message}. ` + |
| 130 | + `Ensure the entry point "${entryPoint}" exists and is a valid Remotion project.` |
| 131 | + ); |
| 132 | + } |
| 133 | + |
| 134 | + // Step 3: Deploy the Lambda function |
| 135 | + log("Step 3/3: Deploying Lambda function..."); |
| 136 | + let functionName: string; |
| 137 | + try { |
| 138 | + const fnResult = await deployFunction({ |
| 139 | + region, |
| 140 | + timeoutInSeconds: 240, |
| 141 | + memorySizeInMb: 2048, |
| 142 | + createCloudWatchLogGroup: true, |
| 143 | + cloudWatchLogRetentionPeriodInDays: 14, |
| 144 | + diskSizeInMb: 2048, |
| 145 | + }); |
| 146 | + functionName = fnResult.functionName; |
| 147 | + log( |
| 148 | + `Lambda function deployed: ${functionName}` + |
| 149 | + (fnResult.alreadyExisted ? " (already existed)" : " (newly created)") |
| 150 | + ); |
| 151 | + } catch (err: unknown) { |
| 152 | + const message = err instanceof Error ? err.message : String(err); |
| 153 | + throw new Error( |
| 154 | + `[REMOTION] Failed to deploy Lambda function: ${message}. ` + |
| 155 | + `Ensure your AWS credentials have Lambda and IAM permissions. ` + |
| 156 | + `See: https://www.remotion.dev/docs/lambda/permissions` |
| 157 | + ); |
| 158 | + } |
| 159 | + |
| 160 | + log("Deployment complete! Set these environment variables:", { |
| 161 | + REMOTION_SERVE_URL: serveUrl, |
| 162 | + REMOTION_FUNCTION_NAME: functionName, |
| 163 | + REMOTION_AWS_REGION: region, |
| 164 | + }); |
| 165 | + |
| 166 | + return { |
| 167 | + functionName, |
| 168 | + serveUrl, |
| 169 | + siteName, |
| 170 | + bucketName, |
| 171 | + region, |
| 172 | + }; |
| 173 | +} |
0 commit comments