@@ -6,7 +6,7 @@ import type {
66import type {
77 GetV9ProjectsIdOrNameCustomEnvironmentsEnvironments ,
88} from "@vercel/sdk/models/getv9projectsidornamecustomenvironmentsop" ;
9- import type { GetProjectsProjects } from "@vercel/sdk/models/getprojectsop" ;
9+ import type { ResponseBodyProjects } from "@vercel/sdk/models/getprojectsop" ;
1010import {
1111 Organization ,
1212 OrganizationIntegration ,
@@ -192,7 +192,7 @@ export type VercelEnvironmentVariableValue = {
192192} ;
193193
194194/** Narrowed Vercel project type – only id and name. */
195- export type VercelProject = Pick < GetProjectsProjects , "id" | "name" > ;
195+ export type VercelProject = Pick < ResponseBodyProjects , "id" | "name" > ;
196196
197197// ---------------------------------------------------------------------------
198198// Mapper functions – narrow wide SDK responses into our domain types.
@@ -429,29 +429,81 @@ export class VercelIntegrationRepository {
429429 client : Vercel ,
430430 projectId : string ,
431431 teamId ?: string | null ,
432- target ?: string
432+ target ?: string ,
433+ /** If provided, only include keys that pass this filter */
434+ shouldIncludeKey ?: ( key : string ) => boolean
433435 ) : ResultAsync < VercelEnvironmentVariableValue [ ] , VercelApiError > {
434436 return wrapVercelCall (
435437 client . projects . filterProjectEnvs ( {
436438 idOrName : projectId ,
437439 ...( teamId && { teamId } ) ,
438- decrypt : "true" ,
439440 } ) ,
440441 "Failed to fetch Vercel environment variable values" ,
441442 { projectId, teamId, target }
442- ) . map ( ( response ) => {
443- const envs = extractVercelEnvs ( response ) ;
444- return envs
445- . filter ( ( env ) => {
446- if ( ! env . value ) return false ;
447- if ( target ) return normalizeTarget ( env . target ) . includes ( target ) ;
448- return true ;
449- } )
450- . map ( toVercelEnvironmentVariableValue )
451- . filter ( ( v ) : v is VercelEnvironmentVariableValue => v !== null ) ;
443+ ) . andThen ( ( response ) => {
444+ // Apply all filters BEFORE decryption to avoid unnecessary API calls
445+ const filteredEnvs = extractVercelEnvs ( response ) . filter ( ( env ) => {
446+ if ( target && ! normalizeTarget ( env . target ) . includes ( target ) ) return false ;
447+ if ( shouldIncludeKey && ! shouldIncludeKey ( env . key ) ) return false ;
448+ if ( isVercelSecretType ( env . type ) ) return false ;
449+ return true ;
450+ } ) ;
451+
452+ // Fetch decrypted values for encrypted vars, use list values for others
453+ return ResultAsync . fromPromise (
454+ Promise . all (
455+ filteredEnvs . map ( ( env ) => this . #resolveEnvVarValue( client , projectId , teamId , env ) )
456+ ) ,
457+ ( error ) => toVercelApiError ( error )
458+ ) . map ( ( results ) => results . filter ( ( v ) : v is VercelEnvironmentVariableValue => v !== null ) ) ;
452459 } ) ;
453460 }
454461
462+ static async #resolveEnvVarValue(
463+ client : Vercel ,
464+ projectId : string ,
465+ teamId : string | null | undefined ,
466+ env : ResponseBodyEnvs
467+ ) : Promise < VercelEnvironmentVariableValue | null > {
468+ // Non-encrypted vars: use value from list response if present
469+ if ( env . type !== "encrypted" || ! env . id ) {
470+ if ( env . value === undefined || env . value === null ) return null ;
471+ return toVercelEnvironmentVariableValue ( env ) ;
472+ }
473+
474+ // Encrypted vars: fetch decrypted value via individual endpoint
475+ // (list endpoint's decrypt param is deprecated)
476+ const result = await ResultAsync . fromPromise (
477+ client . projects . getProjectEnv ( {
478+ idOrName : projectId ,
479+ id : env . id ,
480+ ...( teamId && { teamId } ) ,
481+ } ) ,
482+ ( error ) => error
483+ ) ;
484+
485+ if ( result . isErr ( ) ) {
486+ logger . warn ( "Failed to decrypt Vercel env var" , {
487+ projectId,
488+ envVarKey : env . key ,
489+ error : result . error instanceof Error ? result . error . message : String ( result . error ) ,
490+ } ) ;
491+ return null ;
492+ }
493+
494+ // API returns union: ResponseBody1 has no value, ResponseBody2/3 have value
495+ const decryptedValue = ( result . value as { value ?: string } ) . value ;
496+ if ( typeof decryptedValue !== "string" ) return null ;
497+
498+ return {
499+ key : env . key ,
500+ value : decryptedValue ,
501+ target : normalizeTarget ( env . target ) ,
502+ type : env . type ,
503+ isSecret : false ,
504+ } ;
505+ }
506+
455507 static getVercelSharedEnvironmentVariables (
456508 client : Vercel ,
457509 teamId : string ,
@@ -632,7 +684,12 @@ export class VercelIntegrationRepository {
632684 "Failed to fetch Vercel projects" ,
633685 { teamId }
634686 ) . map ( ( response ) => {
635- const projects = response . projects || [ ] ;
687+ // GetProjectsResponseBody is a union: objects with `projects` array, or direct array
688+ const projects = Array . isArray ( response )
689+ ? response
690+ : "projects" in response
691+ ? response . projects
692+ : [ ] ;
636693 return projects . map ( ( { id, name } ) : VercelProject => ( { id, name } ) ) ;
637694 } ) ;
638695 }
@@ -970,6 +1027,14 @@ export class VercelIntegrationRepository {
9701027 syncEnvVarsMapping : SyncEnvVarsMapping ;
9711028 orgIntegration : OrganizationIntegration & { tokenReference : SecretReference } ;
9721029 } ) : ResultAsync < { syncedCount : number ; errors : string [ ] } , VercelApiError > {
1030+ logger . info ( "pullEnvVarsFromVercel: Starting" , {
1031+ projectId : params . projectId ,
1032+ vercelProjectId : params . vercelProjectId ,
1033+ teamId : params . teamId ,
1034+ vercelStagingEnvironment : params . vercelStagingEnvironment ,
1035+ syncEnvVarsMappingKeys : Object . keys ( params . syncEnvVarsMapping ) ,
1036+ } ) ;
1037+
9731038 return this . getVercelClient ( params . orgIntegration ) . andThen ( ( client ) =>
9741039 ResultAsync . fromPromise (
9751040 ( async ( ) => {
@@ -1046,20 +1111,31 @@ export class VercelIntegrationRepository {
10461111 for ( const mapping of envMapping ) {
10471112 const iterResult = await ResultAsync . fromPromise (
10481113 ( async ( ) => {
1114+ // Build filter to avoid decrypting vars that will be filtered out anyway
1115+ const excludeKeys = new Set ( [ "TRIGGER_SECRET_KEY" , "TRIGGER_VERSION" ] ) ;
1116+ const shouldIncludeKey = ( key : string ) =>
1117+ ! excludeKeys . has ( key ) &&
1118+ shouldSyncEnvVar ( params . syncEnvVarsMapping , key , mapping . triggerEnvType as TriggerEnvironmentType ) ;
1119+
10491120 const envVarsResult = await this . getVercelEnvironmentVariableValues (
10501121 client ,
10511122 params . vercelProjectId ,
10521123 params . teamId ,
1053- mapping . vercelTarget
1124+ mapping . vercelTarget ,
1125+ shouldIncludeKey
10541126 ) ;
10551127
10561128 if ( envVarsResult . isErr ( ) ) {
1129+ logger . error ( "pullEnvVarsFromVercel: Failed to get env vars" , {
1130+ triggerEnvType : mapping . triggerEnvType ,
1131+ vercelTarget : mapping . vercelTarget ,
1132+ error : envVarsResult . error . message ,
1133+ } ) ;
10571134 errors . push ( `Failed to get env vars for ${ mapping . triggerEnvType } : ${ envVarsResult . error . message } ` ) ;
10581135 return ;
10591136 }
10601137
10611138 const projectEnvVars = envVarsResult . value ;
1062-
10631139 const standardTargets = [ "production" , "preview" , "development" ] ;
10641140 const isCustomEnvironment = ! standardTargets . includes ( mapping . vercelTarget ) ;
10651141
@@ -1084,7 +1160,7 @@ export class VercelIntegrationRepository {
10841160 if ( envVar . isSecret ) {
10851161 return false ;
10861162 }
1087- if ( envVar . key === "TRIGGER_SECRET_KEY" ) {
1163+ if ( envVar . key === "TRIGGER_SECRET_KEY" || envVar . key === "TRIGGER_VERSION" ) {
10881164 return false ;
10891165 }
10901166 return shouldSyncEnvVar (
0 commit comments