1+ import { emptyEnvironmentVariableValuesEnabledForProject } from "~/v3/environmentVariables/emptyValuesFlag.server" ;
2+ import {
3+ normalizeTarget ,
4+ isVercelSecretType ,
5+ toVercelEnvironmentVariableValue ,
6+ resolveVercelSharedValue ,
7+ mergeVercelEnvironmentVariableValues ,
8+ } from "~/v3/vercel/environmentVariableValues" ;
19import pLimit from "p-limit" ;
210import { Vercel } from "@vercel/sdk" ;
311import type {
@@ -38,12 +46,6 @@ import {
3846// Pure helpers
3947// ---------------------------------------------------------------------------
4048
41- function normalizeTarget ( target : string [ ] | string | undefined ) : string [ ] {
42- if ( Array . isArray ( target ) ) return target . filter ( Boolean ) ;
43- if ( typeof target === "string" ) return [ target ] ;
44- return [ ] ;
45- }
46-
4749function readProjectEnvs (
4850 response : unknown ,
4951 logContext : Record < string , unknown >
@@ -86,10 +88,6 @@ function hasVercelEnvVarForTarget(envs: ResponseBodyEnvs[], key: string, target:
8688 } ) ;
8789}
8890
89- function isVercelSecretType ( type : string ) : boolean {
90- return type === "secret" || type === "sensitive" ;
91- }
92-
9391export type CreateEnvVarsIfAbsentResult = {
9492 written : string [ ] ;
9593 skipped : string [ ] ;
@@ -226,19 +224,6 @@ function toVercelCustomEnvironment({
226224 return { id, slug, description, branchMatcher } ;
227225}
228226
229- function toVercelEnvironmentVariableValue (
230- env : ResponseBodyEnvs
231- ) : VercelEnvironmentVariableValue | null {
232- if ( ! env . value ) return null ;
233- return {
234- key : env . key ,
235- value : env . value ,
236- target : normalizeTarget ( env . target ) ,
237- type : env . type ,
238- isSecret : isVercelSecretType ( env . type ) ,
239- } ;
240- }
241-
242227// ---------------------------------------------------------------------------
243228// Repository
244229// ---------------------------------------------------------------------------
@@ -474,7 +459,8 @@ export class VercelIntegrationRepository {
474459 teamId ?: string | null ,
475460 target ?: string ,
476461 /** If provided, only include keys that pass this filter */
477- shouldIncludeKey ?: ( key : string ) => boolean
462+ shouldIncludeKey ?: ( key : string ) => boolean ,
463+ allowEmptyValues = true
478464 ) : ResultAsync < VercelEnvironmentVariableValue [ ] , VercelApiError > {
479465 return wrapVercelCallWithRecovery (
480466 client . projects . filterProjectEnvs ( {
@@ -499,7 +485,9 @@ export class VercelIntegrationRepository {
499485 return ResultAsync . fromPromise (
500486 Promise . all (
501487 filteredEnvs . map ( ( env ) =>
502- concurrencyLimit ( ( ) => this . #resolveEnvVarValue( client , projectId , teamId , env ) )
488+ concurrencyLimit ( ( ) =>
489+ this . #resolveEnvVarValue( client , projectId , teamId , env , allowEmptyValues )
490+ )
503491 )
504492 ) ,
505493 ( error ) => toVercelApiError ( error )
@@ -511,12 +499,13 @@ export class VercelIntegrationRepository {
511499 client : Vercel ,
512500 projectId : string ,
513501 teamId : string | null | undefined ,
514- env : ResponseBodyEnvs
502+ env : ResponseBodyEnvs ,
503+ allowEmptyValues : boolean
515504 ) : Promise < VercelEnvironmentVariableValue | null > {
516505 // Non-encrypted vars: use value from list response if present
517506 if ( env . type !== "encrypted" || ! env . id ) {
518507 if ( env . value === undefined || env . value === null ) return null ;
519- return toVercelEnvironmentVariableValue ( env ) ;
508+ return toVercelEnvironmentVariableValue ( env , allowEmptyValues ) ;
520509 }
521510
522511 // Encrypted vars: fetch decrypted value via individual endpoint
@@ -545,7 +534,8 @@ export class VercelIntegrationRepository {
545534
546535 // API returns union: ResponseBody1 has no value, ResponseBody2/3 have value
547536 const decryptedValue = ( result . value as { value ?: string } ) . value ;
548- if ( typeof decryptedValue !== "string" ) return null ;
537+ if ( typeof decryptedValue !== "string" || ( ! allowEmptyValues && decryptedValue . trim ( ) === "" ) )
538+ return null ;
549539
550540 return {
551541 key : env . key ,
@@ -653,7 +643,8 @@ export class VercelIntegrationRepository {
653643 client : Vercel ,
654644 accessToken : string ,
655645 teamId : string ,
656- projectId ?: string // Optional: filter by project
646+ projectId ?: string ,
647+ allowEmptyValues = true
657648 ) : ResultAsync <
658649 Array < {
659650 key : string ;
@@ -684,53 +675,43 @@ export class VercelIntegrationRepository {
684675
685676 if ( isSecret ) return null ;
686677
687- const listValue = env . value ;
688- const applyToAllCustomEnvs = env . applyToAllCustomEnvironments ;
689-
690- if ( listValue ) {
691- return {
692- key : envKey ,
693- value : listValue ,
694- target : normalizeTarget ( env . target ) ,
695- type,
696- isSecret,
697- applyToAllCustomEnvironments : applyToAllCustomEnvs ,
698- } ;
699- }
700-
701- // Try to get the decrypted value for this shared env var
702- const getResult = await callVercelWithRecovery (
703- client . environment . getSharedEnvVar ( {
704- id : envId ,
705- teamId,
706- } ) ,
707- VercelSchemas . getSharedEnvVar ,
708- { context : "getSharedEnvVar" }
709- ) ;
678+ const value = await resolveVercelSharedValue (
679+ env . value ,
680+ async ( ) => {
681+ const getResult = await callVercelWithRecovery (
682+ client . environment . getSharedEnvVar ( { id : envId , teamId } ) ,
683+ VercelSchemas . getSharedEnvVar ,
684+ { context : "getSharedEnvVar" }
685+ ) ;
710686
711- if ( getResult . isOk ( ) ) {
712- if ( ! getResult . value . value ) return null ;
713- return {
714- key : envKey ,
715- value : getResult . value . value ,
716- target : normalizeTarget ( env . target ) ,
717- type,
718- isSecret,
719- applyToAllCustomEnvironments : applyToAllCustomEnvs ,
720- } ;
721- }
722-
723- logger . warn ( "Failed to get decrypted value for shared env var" , {
724- teamId,
725- projectId,
726- envId,
727- envKey,
728- error : getResult . error . message ,
729- errorType : getResult . error . errorType ,
730- status : getResult . error . status ,
731- authInvalid : getResult . error . authInvalid ,
732- } ) ;
733- return null ;
687+ if ( getResult . isOk ( ) ) {
688+ return getResult . value . value ?? null ;
689+ }
690+
691+ logger . warn ( "Failed to get decrypted value for shared env var" , {
692+ teamId,
693+ projectId,
694+ envId,
695+ envKey,
696+ error : getResult . error . message ,
697+ errorType : getResult . error . errorType ,
698+ status : getResult . error . status ,
699+ authInvalid : getResult . error . authInvalid ,
700+ } ) ;
701+ return null ;
702+ } ,
703+ allowEmptyValues
704+ ) ;
705+ if ( value === null ) return null ;
706+
707+ return {
708+ key : envKey ,
709+ value,
710+ target : normalizeTarget ( env . target ) ,
711+ type,
712+ isSecret,
713+ applyToAllCustomEnvironments : env . applyToAllCustomEnvironments ,
714+ } ;
734715 } )
735716 )
736717 ) ,
@@ -1334,6 +1315,9 @@ export class VercelIntegrationRepository {
13341315 }
13351316
13361317 const envVarRepository = new EnvironmentVariablesRepository ( ) ;
1318+ const allowEmptyValues = await emptyEnvironmentVariableValuesEnabledForProject (
1319+ params . projectId
1320+ ) ;
13371321
13381322 // Fetch shared env vars once (they apply across all targets)
13391323 let sharedEnvVars : Array < {
@@ -1350,7 +1334,8 @@ export class VercelIntegrationRepository {
13501334 client ,
13511335 accessToken ,
13521336 params . teamId ,
1353- params . vercelProjectId
1337+ params . vercelProjectId ,
1338+ allowEmptyValues
13541339 ) ;
13551340 sharedEnvVars = sharedResult . unwrapOr ( [ ] ) ;
13561341 }
@@ -1373,7 +1358,8 @@ export class VercelIntegrationRepository {
13731358 params . vercelProjectId ,
13741359 params . teamId ,
13751360 mapping . vercelTarget ,
1376- shouldIncludeKey
1361+ shouldIncludeKey ,
1362+ allowEmptyValues
13771363 ) ;
13781364
13791365 if ( envVarsResult . isErr ( ) ) {
@@ -1399,11 +1385,10 @@ export class VercelIntegrationRepository {
13991385 return matchesTarget || matchesCustomEnv ;
14001386 } ) ;
14011387
1402- const projectEnvVarKeys = new Set ( projectEnvVars . map ( ( v ) => v . key ) ) ;
1403- const sharedEnvVarsToAdd = filteredSharedEnvVars . filter (
1404- ( v ) => ! projectEnvVarKeys . has ( v . key )
1388+ const mergedEnvVars = mergeVercelEnvironmentVariableValues (
1389+ projectEnvVars ,
1390+ filteredSharedEnvVars
14051391 ) ;
1406- const mergedEnvVars = [ ...projectEnvVars , ...sharedEnvVarsToAdd ] ;
14071392
14081393 if ( mergedEnvVars . length === 0 ) {
14091394 return ;
0 commit comments