1+ import pLimit from "p-limit" ;
12import { Vercel } from "@vercel/sdk" ;
23import type {
34 ResponseBodyEnvs ,
@@ -441,7 +442,21 @@ export class VercelIntegrationRepository {
441442 } ) ,
442443 "Failed to fetch Vercel environment variables" ,
443444 { projectId, teamId }
444- ) . map ( ( response ) => extractVercelEnvs ( response ) . map ( toVercelEnvironmentVariable ) ) ;
445+ ) . map ( ( response ) => {
446+ // Warn if response is paginated (more data exists that we're not fetching)
447+ if (
448+ "pagination" in response &&
449+ response . pagination &&
450+ "next" in response . pagination &&
451+ response . pagination . next !== null
452+ ) {
453+ logger . warn (
454+ "Vercel filterProjectEnvs returned paginated response - some env vars may be missing" ,
455+ { projectId, count : response . pagination . count }
456+ ) ;
457+ }
458+ return extractVercelEnvs ( response ) . map ( toVercelEnvironmentVariable ) ;
459+ } ) ;
445460 }
446461
447462 static getVercelEnvironmentVariableValues (
@@ -469,9 +484,12 @@ export class VercelIntegrationRepository {
469484 } ) ;
470485
471486 // Fetch decrypted values for encrypted vars, use list values for others
487+ const concurrencyLimit = pLimit ( 5 ) ;
472488 return ResultAsync . fromPromise (
473489 Promise . all (
474- filteredEnvs . map ( ( env ) => this . #resolveEnvVarValue( client , projectId , teamId , env ) )
490+ filteredEnvs . map ( ( env ) =>
491+ concurrencyLimit ( ( ) => this . #resolveEnvVarValue( client , projectId , teamId , env ) )
492+ )
475493 ) ,
476494 ( error ) => toVercelApiError ( error )
477495 ) . map ( ( results ) => results . filter ( ( v ) : v is VercelEnvironmentVariableValue => v !== null ) ) ;
@@ -588,97 +606,100 @@ export class VercelIntegrationRepository {
588606 return okAsync ( [ ] ) ;
589607 }
590608
609+ const concurrencyLimit = pLimit ( 5 ) ;
591610 return ResultAsync . fromPromise (
592611 Promise . all (
593- envVars . map ( async ( env ) => {
594- if ( ! env . id || ! env . key ) return null ;
595-
596- const envId = env . id ;
597- const envKey = env . key ;
598- const type = env . type || "plain" ;
599- const isSecret = isVercelSecretType ( type ) ;
600-
601- if ( isSecret ) return null ;
602-
603- const listValue = ( env as any ) . value as string | undefined ;
604- const applyToAllCustomEnvs = ( env as any ) . applyToAllCustomEnvironments as boolean | undefined ;
605-
606- if ( listValue ) {
607- return {
608- key : envKey ,
609- value : listValue ,
610- target : normalizeTarget ( env . target ) ,
611- type,
612- isSecret,
613- applyToAllCustomEnvironments : applyToAllCustomEnvs ,
614- } ;
615- }
616-
617- // Try to get the decrypted value for this shared env var
618- const getResult = await ResultAsync . fromPromise (
619- client . environment . getSharedEnvVar ( {
620- id : envId ,
621- teamId,
622- } ) ,
623- ( error ) => error
624- ) ;
612+ envVars . map ( ( env ) =>
613+ concurrencyLimit ( async ( ) => {
614+ if ( ! env . id || ! env . key ) return null ;
615+
616+ const envId = env . id ;
617+ const envKey = env . key ;
618+ const type = env . type || "plain" ;
619+ const isSecret = isVercelSecretType ( type ) ;
620+
621+ if ( isSecret ) return null ;
622+
623+ const listValue = ( env as any ) . value as string | undefined ;
624+ const applyToAllCustomEnvs = ( env as any ) . applyToAllCustomEnvironments as boolean | undefined ;
625+
626+ if ( listValue ) {
627+ return {
628+ key : envKey ,
629+ value : listValue ,
630+ target : normalizeTarget ( env . target ) ,
631+ type,
632+ isSecret,
633+ applyToAllCustomEnvironments : applyToAllCustomEnvs ,
634+ } ;
635+ }
625636
626- if ( getResult . isOk ( ) ) {
627- if ( ! getResult . value . value ) return null ;
628- return {
629- key : envKey ,
630- value : getResult . value . value ,
631- target : normalizeTarget ( env . target ) ,
632- type,
633- isSecret,
634- applyToAllCustomEnvironments : applyToAllCustomEnvs ,
635- } ;
636- }
637+ // Try to get the decrypted value for this shared env var
638+ const getResult = await ResultAsync . fromPromise (
639+ client . environment . getSharedEnvVar ( {
640+ id : envId ,
641+ teamId,
642+ } ) ,
643+ ( error ) => error
644+ ) ;
645+
646+ if ( getResult . isOk ( ) ) {
647+ if ( ! getResult . value . value ) return null ;
648+ return {
649+ key : envKey ,
650+ value : getResult . value . value ,
651+ target : normalizeTarget ( env . target ) ,
652+ type,
653+ isSecret,
654+ applyToAllCustomEnvironments : applyToAllCustomEnvs ,
655+ } ;
656+ }
637657
638- // Workaround: Vercel SDK may throw ResponseValidationError even when the API response
639- // is valid (e.g., deletedAt: null vs expected number). Extract value from rawValue.
640- const error = getResult . error ;
641- let errorValue : string | undefined ;
642- if ( error && typeof error === "object" && "rawValue" in error ) {
643- const rawValue = ( error as any ) . rawValue ;
644- if ( rawValue && typeof rawValue === "object" && "value" in rawValue ) {
645- errorValue = rawValue . value as string | undefined ;
658+ // Workaround: Vercel SDK may throw ResponseValidationError even when the API response
659+ // is valid (e.g., deletedAt: null vs expected number). Extract value from rawValue.
660+ const error = getResult . error ;
661+ let errorValue : string | undefined ;
662+ if ( error && typeof error === "object" && "rawValue" in error ) {
663+ const rawValue = ( error as any ) . rawValue ;
664+ if ( rawValue && typeof rawValue === "object" && "value" in rawValue ) {
665+ errorValue = rawValue . value as string | undefined ;
666+ }
646667 }
647- }
648668
649- const fallbackValue = errorValue || listValue ;
669+ const fallbackValue = errorValue || listValue ;
670+
671+ if ( fallbackValue ) {
672+ logger . warn ( "getSharedEnvVar failed validation, using value from error.rawValue or list response" , {
673+ teamId,
674+ envId,
675+ envKey,
676+ error : error instanceof Error ? error . message : String ( error ) ,
677+ hasErrorRawValue : ! ! errorValue ,
678+ hasListValue : ! ! listValue ,
679+ valueLength : fallbackValue . length ,
680+ } ) ;
681+ return {
682+ key : envKey ,
683+ value : fallbackValue ,
684+ target : normalizeTarget ( env . target ) ,
685+ type,
686+ isSecret,
687+ applyToAllCustomEnvironments : applyToAllCustomEnvs ,
688+ } ;
689+ }
650690
651- if ( fallbackValue ) {
652- logger . warn ( "getSharedEnvVar failed validation, using value from error.rawValue or list response" , {
691+ logger . warn ( "Failed to get decrypted value for shared env var, no fallback available" , {
653692 teamId,
693+ projectId,
654694 envId,
655695 envKey,
656696 error : error instanceof Error ? error . message : String ( error ) ,
657- hasErrorRawValue : ! ! errorValue ,
658- hasListValue : ! ! listValue ,
659- valueLength : fallbackValue . length ,
697+ errorStack : error instanceof Error ? error . stack : undefined ,
698+ hasRawValue : error && typeof error === "object" && "rawValue" in error ,
660699 } ) ;
661- return {
662- key : envKey ,
663- value : fallbackValue ,
664- target : normalizeTarget ( env . target ) ,
665- type,
666- isSecret,
667- applyToAllCustomEnvironments : applyToAllCustomEnvs ,
668- } ;
669- }
670-
671- logger . warn ( "Failed to get decrypted value for shared env var, no fallback available" , {
672- teamId,
673- projectId,
674- envId,
675- envKey,
676- error : error instanceof Error ? error . message : String ( error ) ,
677- errorStack : error instanceof Error ? error . stack : undefined ,
678- hasRawValue : error && typeof error === "object" && "rawValue" in error ,
679- } ) ;
680- return null ;
681- } )
700+ return null ;
701+ } )
702+ )
682703 ) ,
683704 ( error ) => {
684705 logger . error ( "Failed to process shared environment variable values" , {
@@ -696,21 +717,43 @@ export class VercelIntegrationRepository {
696717 client : Vercel ,
697718 teamId ?: string | null
698719 ) : ResultAsync < VercelProject [ ] , VercelApiError > {
699- return wrapVercelCall (
700- client . projects . getProjects ( {
701- ...( teamId && { teamId } ) ,
702- } ) ,
703- "Failed to fetch Vercel projects" ,
704- { teamId }
705- ) . map ( ( response ) => {
706- // GetProjectsResponseBody is a union: objects with `projects` array, or direct array
707- const projects = Array . isArray ( response )
708- ? response
709- : "projects" in response
710- ? response . projects
711- : [ ] ;
712- return projects . map ( ( { id, name } ) : VercelProject => ( { id, name } ) ) ;
713- } ) ;
720+ return ResultAsync . fromPromise (
721+ ( async ( ) => {
722+ const allProjects : VercelProject [ ] = [ ] ;
723+ let from : string | undefined ;
724+
725+ do {
726+ const response = await client . projects . getProjects ( {
727+ ...( teamId && { teamId } ) ,
728+ limit : "100" ,
729+ ...( from && { from } ) ,
730+ } ) ;
731+
732+ const projects = Array . isArray ( response )
733+ ? response
734+ : "projects" in response
735+ ? response . projects
736+ : [ ] ;
737+ allProjects . push ( ...projects . map ( ( { id, name } ) : VercelProject => ( { id, name } ) ) ) ;
738+
739+ // Get pagination token for next page
740+ const pagination =
741+ ! Array . isArray ( response ) && "pagination" in response
742+ ? response . pagination
743+ : undefined ;
744+ from =
745+ pagination && "next" in pagination && pagination . next !== null
746+ ? String ( pagination . next )
747+ : undefined ;
748+ } while ( from ) ;
749+
750+ return allProjects ;
751+ } ) ( ) ,
752+ ( error ) => {
753+ logger . error ( "Failed to fetch Vercel projects" , { teamId, error } ) ;
754+ return toVercelApiError ( error ) ;
755+ }
756+ ) ;
714757 }
715758
716759 static async updateVercelOrgIntegrationToken ( params : {
0 commit comments