Skip to content

Commit aa84e5f

Browse files
committed
chore(webapp): one reader for Vercel project env vars
The project env endpoint returns every record in one response: no `limit` parameter, no cursor. Only the team-level shared endpoint pages, and that read already walks its cursor. Measured against a project holding 500 records. The call sites had drifted to three different answers about that. One warned that variables might be missing, one refused to act on a list it believed truncated, and five said nothing at all. The warning fired on nothing and read like a known limitation, which is how it came to be believed. Project env reads now go through readProjectEnvs, which extracts the records and, if Vercel ever does add a cursor here, logs that the read is silently partial instead of implying a cap that does not exist.
1 parent 494d1bd commit aa84e5f

2 files changed

Lines changed: 25 additions & 19 deletions

File tree

apps/webapp/app/models/vercelIntegration.server.ts

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,23 @@ function normalizeTarget(target: string[] | string | undefined): string[] {
4343
return [];
4444
}
4545

46+
function readProjectEnvs(
47+
response: unknown,
48+
logContext: Record<string, unknown>
49+
): ResponseBodyEnvs[] {
50+
const cursor = (response as { pagination?: { next?: unknown } } | null | undefined)?.pagination
51+
?.next;
52+
53+
if (typeof cursor === "number" && cursor > 0) {
54+
logger.error(
55+
"Vercel project env list returned a pagination cursor — this endpoint has always returned every record in one response, so this read is incomplete and needs paginating",
56+
logContext
57+
);
58+
}
59+
60+
return extractVercelEnvs(response as FilterProjectEnvsResponseBody);
61+
}
62+
4663
function extractVercelEnvs(response: FilterProjectEnvsResponseBody): ResponseBodyEnvs[] {
4764
if ("envs" in response && Array.isArray(response.envs)) {
4865
return response.envs;
@@ -509,19 +526,7 @@ export class VercelIntegrationRepository {
509526
{ projectId, teamId },
510527
toVercelApiError
511528
).map((response) => {
512-
// Warn if response is paginated (more data exists that we're not fetching)
513-
if (
514-
"pagination" in response &&
515-
response.pagination &&
516-
"next" in response.pagination &&
517-
response.pagination.next !== null
518-
) {
519-
logger.warn(
520-
"Vercel filterProjectEnvs returned paginated response - some env vars may be missing",
521-
{ projectId, count: response.pagination.count }
522-
);
523-
}
524-
return extractVercelEnvs(response).map(toVercelEnvironmentVariable);
529+
return readProjectEnvs(response, { projectId, teamId }).map(toVercelEnvironmentVariable);
525530
});
526531
}
527532

@@ -544,7 +549,7 @@ export class VercelIntegrationRepository {
544549
toVercelApiError
545550
).andThen((response) => {
546551
// Apply all filters BEFORE decryption to avoid unnecessary API calls
547-
const filteredEnvs = extractVercelEnvs(response).filter((env) => {
552+
const filteredEnvs = readProjectEnvs(response, { projectId, teamId }).filter((env) => {
548553
if (target && !normalizeTarget(env.target).includes(target)) return false;
549554
if (shouldIncludeKey && !shouldIncludeKey(env.key)) return false;
550555
if (isVercelSecretType(env.type)) return false;
@@ -1237,7 +1242,7 @@ export class VercelIntegrationRepository {
12371242
}
12381243
);
12391244

1240-
const envs = extractVercelEnvs(existingEnvs);
1245+
const envs = readProjectEnvs(existingEnvs, { vercelProjectId, teamId });
12411246

12421247
const existingEnv = envs.find((env) => {
12431248
if (env.key !== key) return false;
@@ -1296,7 +1301,7 @@ export class VercelIntegrationRepository {
12961301
}
12971302
);
12981303

1299-
const envs = extractVercelEnvs(existingEnvs);
1304+
const envs = readProjectEnvs(existingEnvs, { vercelProjectId, teamId });
13001305

13011306
const existingEnv = envs.find((env) => {
13021307
if (env.key !== key) return false;
@@ -1668,7 +1673,7 @@ export class VercelIntegrationRepository {
16681673
}
16691674
);
16701675

1671-
const existingEnvsList = extractVercelEnvs(existingEnvs);
1676+
const existingEnvsList = readProjectEnvs(existingEnvs, { vercelProjectId, teamId });
16721677

16731678
const toCreate: Array<{
16741679
key: string;
@@ -1951,7 +1956,7 @@ export class VercelIntegrationRepository {
19511956
}
19521957
);
19531958

1954-
const envs = extractVercelEnvs(existingEnvs);
1959+
const envs = readProjectEnvs(existingEnvs, { vercelProjectId, teamId });
19551960
const idsToRemove = envs.filter((env) => env.key === key && env.id).map((env) => env.id!);
19561961

19571962
if (idsToRemove.length === 0) {
@@ -1990,7 +1995,7 @@ export class VercelIntegrationRepository {
19901995
}
19911996
);
19921997

1993-
const envs = extractVercelEnvs(existingEnvs);
1998+
const envs = readProjectEnvs(existingEnvs, { vercelProjectId, teamId });
19941999

19952000
// Vercel can have multiple env vars with the same key but different targets
19962001
const existingEnv = envs.find((existing) => {

apps/webapp/app/models/vercelSdkRecovery.server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ export const VercelSchemas = {
147147
.object({
148148
envs: z.array(z.record(z.unknown())),
149149
pagination: z.unknown().optional(),
150+
hiddenProductionEnvCount: z.number().optional(),
150151
})
151152
.passthrough(),
152153
z.array(z.record(z.unknown())),

0 commit comments

Comments
 (0)