-
Notifications
You must be signed in to change notification settings - Fork 904
Configure SST for git branches + move all preview deployments to staging #1199
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
Conversation
WalkthroughRemoved two AWS access-key fields from SST env typings, introduced stage-variant parsing and Vercel-targeted environment handling with bucket CORS/acceleration in infra, and refactored AWS credential resolution in the web backend to a single Effect-based selection with a new precedence order. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant App as Web Backend
participant Env as Process Env
participant STS as AWS STS/OIDC
participant SSO as AWS SSO
participant ECS as ECS Metadata
App->>Env: Read VERCEL_AWS_ROLE_ARN
alt Vercel role present
Note over App,STS: prefer OIDC assume-role
App->>STS: AssumeRoleWithWebIdentity / OIDC
STS-->>App: Temporary credentials
else
App->>Env: Read CAP_AWS_ACCESS_KEY & SECRET
alt Access keys present
App-->>App: Use static credentials
else
App->>Env: Read AWS_DEFAULT_PROFILE
alt Profile present
App->>SSO: fromSSO(profile)
SSO-->>App: SSO-sourced credentials
else
App->>ECS: fromContainerMetadata()
ECS-->>App: Task role credentials
end
end
end
App-->>App: Return credentials for AWS clients
sequenceDiagram
autonumber
participant Infra as SST Config
participant Stage as parsedStage()
participant Vercel as Vercel API
participant S3 as Recordings Bucket
participant OIDC as OIDC Provider
Infra->>Stage: Derive variant (production/staging/git-branch-*)
Stage-->>Infra: { variant, gitBranch? }
Infra->>S3: Apply accelerate + CORS (origins vary by variant)
Infra->>OIDC: getOpenIdConnectProviderOutput (unified lookup)
OIDC-->>Infra: Provider output
Infra->>Vercel: Upsert ProjectEnvironmentVariable(s)
Note over Infra,Vercel: targets mapped per variant, deleteBeforeReplace, customEnvironmentIds, gitBranch set when git-branch
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Actionable comments posted: 0
🧹 Nitpick comments (4)
packages/web-backend/src/Aws.ts (1)
38-40
: Consider using Config for environment variables for consistency.Direct access to
process.env.NODE_ENV
andprocess.env.AWS_DEFAULT_PROFILE
is inconsistent with the Effect-TS Config pattern used for other environment variables (lines 13-21).For consistency, consider reading these through Config as well:
+ const nodeEnv = yield* Config.option(Config.string("NODE_ENV")); + const awsProfile = yield* Config.option(Config.string("AWS_DEFAULT_PROFILE")); const credentials: AwsCredentialIdentity | AwsCredentialIdentityProvider = yield* Effect.gen(function* () { if (Option.isSome(vercelAwsRole)) { yield* Effect.log("Using VERCEL_AWS_ROLE_ARN"); return awsCredentialsProvider({ roleArn: vercelAwsRole.value }); } if (Option.isSome(accessKeys)) { const [accessKeyId, secretAccessKey] = accessKeys.value; yield* Effect.log( "Using CAP_AWS_ACCESS_KEY and CAP_AWS_SECRET_KEY", ); return { accessKeyId, secretAccessKey }; } - if (process.env.NODE_ENV === "development") { + if (Option.isSome(nodeEnv) && nodeEnv.value === "development") { yield* Effect.log("Using AWS_DEFAULT_PROFILE"); - return fromSSO({ profile: process.env.AWS_DEFAULT_PROFILE }); + return fromSSO({ + profile: Option.isSome(awsProfile) ? awsProfile.value : undefined + }); } yield* Effect.log("Falling back to ECS metadata"); return fromContainerMetadata(); });infra/sst.config.ts (3)
17-26
: Improve error handling in parsedStage.The error message "Unsupported stage" could be more descriptive to help with debugging.
Consider including the actual stage value in the error:
const parsedStage = () => { if ($app.stage === "staging") return { variant: "staging" } as const; if ($app.stage === "production") return { variant: "production" } as const; if ($app.stage.startsWith("git-branch-")) return { variant: "git-branch", branch: $app.stage.slice("git-branch-".length), } as const; - throw new Error("Unsupported stage"); + throw new Error(`Unsupported stage: ${$app.stage}`); };
161-161
: Consider simplifying OIDC sub condition to use stage.variant.The OIDC sub condition duplicates the stage variant logic already present in
parsedStage()
.Apply this diff to use the parsed stage variant:
Condition: { StringEquals: { [`${oidc.url}:aud`]: oidc.aud, }, StringLike: { [`${oidc.url}:sub`]: [ - `owner:${VERCEL_TEAM_SLUG}:project:*:environment:${$app.stage.startsWith("git-branch-") ? "preview" : $app.stage}`, + `owner:${VERCEL_TEAM_SLUG}:project:*:environment:${stage.variant === "git-branch" ? "preview" : stage.variant}`, ], }, },
227-242
: Simplify nested ternary for environment variable targets.The nested ternary operators for
customEnvironmentIds
,targets
, andgitBranch
are hard to read and maintain.Consider extracting the logic into a helper function:
const getVercelEnvConfig = (stage: ReturnType<typeof parsedStage>) => { switch (stage.variant) { case "production": return { customEnvironmentIds: undefined, targets: ["production"] as const, gitBranch: undefined, }; case "staging": return { customEnvironmentIds: ["env_CFbtmnpsI11e4o8X5UD8MZzxELQi"], targets: ["development", "preview"] as const, gitBranch: undefined, }; case "git-branch": return { customEnvironmentIds: undefined, targets: ["preview"] as const, gitBranch: stage.branch, }; } };Then use it:
+ const envConfig = getVercelEnvConfig(stage); + new vercel.ProjectEnvironmentVariable( `VercelEnv${v.key}`, { ...v, projectId: vercelProject.id, - customEnvironmentIds: - stage.variant === "staging" - ? ["env_CFbtmnpsI11e4o8X5UD8MZzxELQi"] - : undefined, - targets: - stage.variant === "production" - ? ["production"] - : stage.variant === "staging" - ? ["development", "preview"] - : stage.variant === "git-branch" - ? ["preview"] - : undefined, - gitBranch: - stage.variant === "git-branch" ? stage.branch : undefined, + ...envConfig, comment: "This var is being managed by SST, do not edit or delete it via the Vercel dashboard", }, { deleteBeforeReplace: true }, );
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
infra/sst-env.d.ts
(1 hunks)infra/sst.config.ts
(9 hunks)packages/web-backend/src/Aws.ts
(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}
: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by runningpnpm format
.Use strict TypeScript and avoid any; leverage shared types
Files:
packages/web-backend/src/Aws.ts
infra/sst-env.d.ts
infra/sst.config.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}
: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g.,user-menu.tsx
).
Use PascalCase for React/Solid components.
Files:
packages/web-backend/src/Aws.ts
infra/sst-env.d.ts
infra/sst.config.ts
🪛 Gitleaks (8.28.0)
infra/sst.config.ts
[high] 14-14: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
- GitHub Check: Analyze (rust)
🔇 Additional comments (6)
packages/web-backend/src/Aws.ts (1)
23-45
: LGTM! Credential resolution precedence is well-structured.The new credential resolution logic correctly implements the intended precedence: Vercel role → access keys → development SSO → ECS metadata. The logging at each step will help with debugging credential issues.
infra/sst-env.d.ts (1)
1-47
: LGTM! Auto-generated type definitions reflect infrastructure changes.The removal of
CAP_AWS_ACCESS_KEY
andCAP_AWS_SECRET_KEY
from the Resource interface aligns with the credential handling changes inpackages/web-backend/src/Aws.ts
, where these are now read directly from environment variables rather than SST resources. The quoted property keys and new import/export statements are standard SST type generation patterns.infra/sst.config.ts (4)
69-87
: LGTM! Stage-aware CORS configuration is well-structured.The CORS configuration correctly restricts origins based on the stage variant. Production uses a explicit allowlist of domains, while non-production environments allow localhost and Vercel preview domains, which is appropriate for development and testing.
254-255
: LGTM! Conditional GITHUB_PAT creation is appropriate.Restricting the
GITHUB_PAT
secret to the staging stage only is a good security practice, preventing unnecessary secret exposure in other environments.
365-374
: LGTM! Conditional secret version creation prevents errors.Creating the
GHCRCredentialsSecretVersion
only whenGITHUB_PAT
is defined prevents runtime errors and aligns with the conditional secret creation in theSecrets()
function.
14-14
: False positive: AXIOM_API_TOKEN is not a leaked secret.The static analysis tool flagged
AXIOM_API_TOKEN
as a potential secret. This is a false positive—the token is intentionally defined as a constant for use in the infrastructure configuration and is properly injected into the environment. The token is for logging/observability purposes and is appropriately scoped.
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.
Actionable comments posted: 1
🧹 Nitpick comments (1)
infra/sst.config.ts (1)
17-26
: Enhance error message for unsupported stages.The
parsedStage()
function correctly validates stages, but the error message could be more helpful by indicating which stages are supported.Consider improving the error message:
- throw new Error("Unsupported stage"); + throw new Error(`Unsupported stage: ${$app.stage}. Expected: staging, production, or git-branch-*`);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/desktop/src/routes/(window-chrome)/settings/general.tsx
(1 hunks)infra/sst-env.d.ts
(0 hunks)infra/sst.config.ts
(8 hunks)
💤 Files with no reviewable changes (1)
- infra/sst-env.d.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}
: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by runningpnpm format
.Use strict TypeScript and avoid any; leverage shared types
Files:
apps/desktop/src/routes/(window-chrome)/settings/general.tsx
infra/sst.config.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}
: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g.,user-menu.tsx
).
Use PascalCase for React/Solid components.
Files:
apps/desktop/src/routes/(window-chrome)/settings/general.tsx
infra/sst.config.ts
apps/desktop/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/desktop/src/**/*.{ts,tsx}
: Desktop icons are auto-imported (unplugin-icons); do not import icons manually
Desktop IPC: Call generated tauri_specta commands/events; listen to generated events and use typed interfaces
Use @tanstack/solid-query for server state in the desktop app
Files:
apps/desktop/src/routes/(window-chrome)/settings/general.tsx
🪛 Gitleaks (8.28.0)
infra/sst.config.ts
[high] 14-14: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
- GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
- GitHub Check: Analyze (rust)
🔇 Additional comments (5)
apps/desktop/src/routes/(window-chrome)/settings/general.tsx (1)
17-17
: LGTM! Correct type-only import.Converting
ParentProps
to a type-only import aligns with TypeScript best practices and improves compile-time performance.infra/sst.config.ts (4)
96-99
: Good addition of S3 Transfer Acceleration.Enabling S3 Transfer Acceleration improves upload/download performance for geographically distributed users.
247-248
: Good stage isolation for GitHub credentials.Making
GITHUB_PAT
and its usage conditional on the staging stage is a good security practice, ensuring credentials are only provisioned where needed.Also applies to: 358-367
14-14
: Verify AXIOM_API_TOKEN is appropriately scoped.The static analysis tool flagged this line as a potential API key exposure. While it's common to have tokens in infrastructure code, verify that:
- This token is scoped appropriately for the environments using it
- Access to this repository is properly restricted
- Consider using a secret manager if this token has broad permissions
Based on static analysis hints.
198-238
: Verify customEnvironmentIds value
The verification script didn’t run without a valid VERCEL_TOKEN. Manually confirm thatenv_CFbtmnpsI11e4o8X5UD8MZzxELQi
exists in your Vercel project (or update it to the correct ID).
new aws.s3.BucketCorsConfigurationV2("RecordingsBucketCors", { | ||
bucket: recordingsBucket.bucket, | ||
corsRules: [ | ||
{ | ||
allowedHeaders: ["*"], | ||
allowedMethods: ["GET", "POST"], | ||
allowedOrigins: | ||
stage.variant === "production" | ||
? [ | ||
"https://cap.so", | ||
"https://cap.link", | ||
"https://v.cap.so", | ||
"https://dyk2p776s2gx5.cloudfront.net", | ||
] | ||
: ["http://localhost:*", "https://*.vercel.app", webUrl], | ||
exposeHeaders: [], | ||
}, | ||
], | ||
}); |
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.
Critical: Invalid CORS wildcard patterns.
The CORS allowedOrigins
configuration uses wildcard patterns (http://localhost:*
and https://*.vercel.app
) that are not valid in AWS S3 CORS configuration. S3 only supports exact origins or the special *
value for all origins. These patterns will cause CORS validation errors at runtime.
Apply this fix to use valid CORS origins:
allowedOrigins:
stage.variant === "production"
? [
"https://cap.so",
"https://cap.link",
"https://v.cap.so",
"https://dyk2p776s2gx5.cloudfront.net",
]
- : ["http://localhost:*", "https://*.vercel.app", webUrl],
+ : [
+ "http://localhost:3000",
+ "http://localhost:3001",
+ webUrl,
+ ],
Note: You'll need to explicitly list each localhost port you use. For Vercel preview deployments, the webUrl
already includes the specific preview URL. If you need to allow multiple Vercel domains, you must list them explicitly or consider using a more permissive CORS policy for non-production stages.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
new aws.s3.BucketCorsConfigurationV2("RecordingsBucketCors", { | |
bucket: recordingsBucket.bucket, | |
corsRules: [ | |
{ | |
allowedHeaders: ["*"], | |
allowedMethods: ["GET", "POST"], | |
allowedOrigins: | |
stage.variant === "production" | |
? [ | |
"https://cap.so", | |
"https://cap.link", | |
"https://v.cap.so", | |
"https://dyk2p776s2gx5.cloudfront.net", | |
] | |
: ["http://localhost:*", "https://*.vercel.app", webUrl], | |
exposeHeaders: [], | |
}, | |
], | |
}); | |
new aws.s3.BucketCorsConfigurationV2("RecordingsBucketCors", { | |
bucket: recordingsBucket.bucket, | |
corsRules: [ | |
{ | |
allowedHeaders: ["*"], | |
allowedMethods: ["GET", "POST"], | |
allowedOrigins: | |
stage.variant === "production" | |
? [ | |
"https://cap.so", | |
"https://cap.link", | |
"https://v.cap.so", | |
"https://dyk2p776s2gx5.cloudfront.net", | |
] | |
: [ | |
"http://localhost:3000", | |
"http://localhost:3001", | |
webUrl, | |
], | |
exposeHeaders: [], | |
}, | |
], | |
}); |
🤖 Prompt for AI Agents
In infra/sst.config.ts around lines 69 to 87, the S3 CORS allowedOrigins
currently contains invalid wildcard patterns (e.g., "http://localhost:*" and
"https://*.vercel.app") which AWS S3 rejects; replace those patterns with only
exact origin strings or the single "*" wildcard. Concretely, enumerate each
localhost origin you need (e.g., "http://localhost:3000"), include the explicit
webUrl and any specific Vercel preview URLs you want to allow, and for
non-production stages either list all known origins explicitly or use "*" (if
acceptable for your security model) instead of wildcard patterns. Ensure the
final corsRules.allowedOrigins array contains only exact origins or "*" so the
CORS configuration validates.
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.
That's not true, a single wildcard is allowed anywhere in each origin
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.
🧩 Analysis chain
🌐 Web query:
AWS S3 CORS configuration wildcard support in allowedOrigins
💡 Result:
Short answer: S3 supports wildcards in AllowedOrigin — you may use a single * in a domain (e.g. http://.example.com) and you can also use "" to allow all origins. Only one wildcard character is permitted per AllowedOrigin. [1][2]
Sources:
- AWS S3 CORS documentation (Elements of a CORS configuration). [1]
- PutBucketCors / API docs (examples showing AllowedOrigin = "*"). [2]
@Brendonovich You're absolutely right, and I apologize for the incorrect information. AWS S3 CORS does indeed support a single wildcard character per AllowedOrigin
, so patterns like http://localhost:*
and https://*.vercel.app
are completely valid.[1][2]
The CORS configuration in your PR is correct as-is. Thank you for the correction!
🐰💭
✏️ Learnings added
Learnt from: Brendonovich
PR: CapSoftware/Cap#1199
File: infra/sst.config.ts:69-87
Timestamp: 2025-10-15T10:03:20.558Z
Learning: AWS S3 CORS configuration supports wildcard patterns in AllowedOrigin entries. A single wildcard character (*) is permitted per AllowedOrigin, allowing patterns like `http://localhost:*` or `https://*.example.com`. The special value "*" can also be used to allow all origins.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
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.
Actionable comments posted: 1
🧹 Nitpick comments (2)
infra/sst.config.ts (2)
247-248
: Verify consistency between stage checks for GITHUB_PAT.The
GITHUB_PAT
secret uses$app.stage === "staging"
(line 247), while theWorkflowCluster
creation usesstage.variant === "staging"
(line 194). Although these should be equivalent based on theparsedStage()
logic, consider usingstage.variant
consistently throughout for clarity.Apply this diff to use consistent stage checking:
return { DATABASE_URL_MYSQL: new sst.Secret("DATABASE_URL_MYSQL"), - GITHUB_PAT: - $app.stage === "staging" ? new sst.Secret("GITHUB_PAT") : undefined, + GITHUB_PAT: + stage.variant === "staging" ? new sst.Secret("GITHUB_PAT") : undefined, WORKFLOWS_RPC_SECRET: new random.RandomString("WORKFLOWS_RPC_SECRET", { length: 48, }), };Note: This would require passing
stage
as a parameter to theSecrets()
function.Also applies to: 358-367
14-14
: Consider using a secret for the Axiom API token.The Axiom API token is hardcoded in the configuration file. While this may be acceptable for infrastructure-as-code in a private repository, consider using an SST Secret for better security practices and easier token rotation.
Based on static analysis hints
Apply this diff to use a secret:
-const AXIOM_API_TOKEN = "xaat-c0704be6-e942-4935-b068-3b491d7cc00f"; const AXIOM_DATASET = "cap-otel";
Then update the
Secrets()
function:function Secrets() { return { DATABASE_URL_MYSQL: new sst.Secret("DATABASE_URL_MYSQL"), AXIOM_API_TOKEN: new sst.Secret("AXIOM_API_TOKEN"), GITHUB_PAT: $app.stage === "staging" ? new sst.Secret("GITHUB_PAT") : undefined, WORKFLOWS_RPC_SECRET: new random.RandomString("WORKFLOWS_RPC_SECRET", { length: 48, }), }; }And update references to use
secrets.AXIOM_API_TOKEN.value
instead of the constant.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/desktop/src/routes/(window-chrome)/settings/general.tsx
(1 hunks)infra/sst-env.d.ts
(0 hunks)infra/sst.config.ts
(8 hunks)
💤 Files with no reviewable changes (1)
- infra/sst-env.d.ts
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}
: Use a 2-space indent for TypeScript code.
Use Biome for formatting and linting TypeScript/JavaScript files by runningpnpm format
.Use strict TypeScript and avoid any; leverage shared types
Files:
infra/sst.config.ts
apps/desktop/src/routes/(window-chrome)/settings/general.tsx
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx,js,jsx}
: Use kebab-case for filenames for TypeScript/JavaScript modules (e.g.,user-menu.tsx
).
Use PascalCase for React/Solid components.
Files:
infra/sst.config.ts
apps/desktop/src/routes/(window-chrome)/settings/general.tsx
apps/desktop/src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (CLAUDE.md)
apps/desktop/src/**/*.{ts,tsx}
: Desktop icons are auto-imported (unplugin-icons); do not import icons manually
Desktop IPC: Call generated tauri_specta commands/events; listen to generated events and use typed interfaces
Use @tanstack/solid-query for server state in the desktop app
Files:
apps/desktop/src/routes/(window-chrome)/settings/general.tsx
🪛 Gitleaks (8.28.0)
infra/sst.config.ts
[high] 14-14: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.
(generic-api-key)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Build Desktop (x86_64-pc-windows-msvc, windows-latest)
- GitHub Check: Build Desktop (aarch64-apple-darwin, macos-latest)
- GitHub Check: Analyze (rust)
🔇 Additional comments (5)
apps/desktop/src/routes/(window-chrome)/settings/general.tsx (1)
17-17
: LGTM! Good TypeScript practice.Changing to a type-only import is appropriate since
ParentProps
is only used for type annotations, not at runtime. This makes the import more explicit and can help with tree-shaking.infra/sst.config.ts (4)
9-9
: LGTM! Good refactoring to eliminate magic strings.Extracting the Vercel project name to a constant improves maintainability and reduces duplication.
17-26
: LGTM! Robust stage parsing with clear error handling.The stage variant parsing provides a clean abstraction for handling different deployment environments. The explicit error for unsupported stages prevents silent failures.
96-99
: LGTM! S3 Transfer Acceleration enabled.Enabling bucket acceleration will improve upload/download performance for geographically distributed users. This is a good addition for the recordings bucket.
198-238
: Well-structured environment variable provisioning.The refactored logic provides clear stage-variant-aware targeting with proper cleanup handling via
deleteBeforeReplace
. The gitBranch field correctly targets preview deployments for git-branch stages.
Summary by CodeRabbit
New Features
Improvements
Chores