Skip to content

Commit 3104e7e

Browse files
committed
feat(vercel): integrate app slug, update callbacks, and clean UI imports
Add support for VERCEL_INTEGRATION_APP_SLUG and require it for Vercel integration availability checks. Update the Vercel install URL and redirect callback to use the configured app slug and the /vercel/callback path so install flows from Vercel Marketplace route correctly back to the app. Improve route and component code: - Add loader comment to vercel.configure route. - Remove unused conform import and unused OrgIntegrationRepository. - Add Tooltip primitives import and utility shortEnvironmentLabel for environment display. - Consolidate and reuse v3ProjectSettingsPath earlier in the handler to avoid duplication. - Remove unused cn and message redirect import cleanup. These changes fix Marketplace installation URL construction, ensure the integration is only advertised when fully configured, and tidy up imports and handler flow for clearer UI logic. Move code around, so it’s easier to understand. Make the data flow more transparent. For dashboard initialized flow: vercel/install -> vercel -> vercel/callback -> vercel/connect For marketplace initialized flow: Vercel -> vercel/callback -> vercel/onboarding -> vercel/connect
1 parent f4a6d4f commit 3104e7e

14 files changed

+490
-831
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
"packages/cli-v3/e2e": true
88
},
99
"vitest.disableWorkspaceWarning": true,
10-
"typescript.experimental.useTsgo": false,
10+
"typescript.experimental.useTsgo": true,
1111
"chat.agent.maxRequests": 10000
1212
}

apps/webapp/app/env.server.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -428,6 +428,7 @@ const EnvironmentSchema = z
428428
/** Vercel integration OAuth credentials */
429429
VERCEL_INTEGRATION_CLIENT_ID: z.string().optional(),
430430
VERCEL_INTEGRATION_CLIENT_SECRET: z.string().optional(),
431+
VERCEL_INTEGRATION_APP_SLUG: z.string().optional(),
431432

432433
/** These enable the alerts feature in v3 */
433434
ALERT_EMAIL_TRANSPORT: z.enum(["resend", "smtp", "aws-ses"]).optional(),

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export class OrgIntegrationRepository {
9090
!!env.ORG_SLACK_INTEGRATION_CLIENT_ID && !!env.ORG_SLACK_INTEGRATION_CLIENT_SECRET;
9191

9292
static isVercelSupported =
93-
!!env.VERCEL_INTEGRATION_CLIENT_ID && !!env.VERCEL_INTEGRATION_CLIENT_SECRET;
93+
!!env.VERCEL_INTEGRATION_CLIENT_ID && !!env.VERCEL_INTEGRATION_CLIENT_SECRET && !!env.VERCEL_INTEGRATION_APP_SLUG;
9494

9595
/**
9696
* Generate the URL to install the Vercel integration.
@@ -101,8 +101,8 @@ export class OrgIntegrationRepository {
101101
static vercelInstallUrl(state: string): string {
102102
// The user goes to Vercel's marketplace to install the integration
103103
// After installation, Vercel redirects to our callback with the authorization code
104-
const redirectUri = encodeURIComponent(`${env.APP_ORIGIN}/callback/vercel`);
105-
return `https://vercel.com/integrations/trigger/new?state=${state}&redirect_uri=${redirectUri}`;
104+
const redirectUri = encodeURIComponent(`${env.APP_ORIGIN}/vercel/callback`);
105+
return `https://vercel.com/integrations/${env.VERCEL_INTEGRATION_APP_SLUG}/new?state=${state}&redirect_uri=${redirectUri}`;
106106
}
107107

108108
static slackAuthorizationUrl(

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,6 +676,7 @@ export class VercelIntegrationRepository {
676676
installationId?: string;
677677
organization: Pick<Organization, "id">;
678678
raw?: Record<string, any>;
679+
origin: 'marketplace' | 'dashboard';
679680
}): Promise<OrganizationIntegration> {
680681
const result = await $transaction(prisma, async (tx) => {
681682
const secretStore = getSecretStore("DATABASE", {
@@ -718,6 +719,7 @@ export class VercelIntegrationRepository {
718719
teamId: params.teamId,
719720
userId: params.userId,
720721
installationId: params.installationId,
722+
origin: params.origin,
721723
} as any,
722724
},
723725
});

apps/webapp/app/presenters/v3/VercelSettingsPresenter.server.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type PrismaClient } from "@trigger.dev/database";
1+
import { type PrismaClient, type RuntimeEnvironmentType } from "@trigger.dev/database";
22
import { fromPromise, ok, ResultAsync } from "neverthrow";
33
import { env } from "~/env.server";
44
import { OrgIntegrationRepository } from "~/models/orgIntegration.server";
@@ -7,6 +7,7 @@ import {
77
VercelCustomEnvironment,
88
VercelEnvironmentVariable,
99
} from "~/models/vercelIntegration.server";
10+
import { EnvironmentVariablesRepository } from "~/v3/environmentVariables/environmentVariablesRepository.server";
1011
import {
1112
VercelProjectIntegrationDataSchema,
1213
VercelProjectIntegrationData,
@@ -44,7 +45,9 @@ export type VercelOnboardingData = {
4445
environmentVariables: VercelEnvironmentVariable[];
4546
availableProjects: VercelAvailableProject[];
4647
hasProjectSelected: boolean;
48+
hasProjectSelected: boolean;
4749
authInvalid?: boolean;
50+
existingVariables: Record<string, { environments: RuntimeEnvironmentType[] }>;
4851
};
4952

5053
export class VercelSettingsPresenter extends BasePresenter {
@@ -255,7 +258,9 @@ export class VercelSettingsPresenter extends BasePresenter {
255258
environmentVariables: [],
256259
availableProjects: [],
257260
hasProjectSelected: false,
261+
hasProjectSelected: false,
258262
authInvalid: true,
263+
existingVariables: {},
259264
};
260265
}
261266

@@ -283,7 +288,9 @@ export class VercelSettingsPresenter extends BasePresenter {
283288
environmentVariables: [],
284289
availableProjects: [],
285290
hasProjectSelected: false,
291+
hasProjectSelected: false,
286292
authInvalid: availableProjectsResult.authInvalid,
293+
existingVariables: {},
287294
};
288295
}
289296

@@ -293,7 +300,9 @@ export class VercelSettingsPresenter extends BasePresenter {
293300
customEnvironments: [],
294301
environmentVariables: [],
295302
availableProjects: availableProjectsResult.data,
303+
availableProjects: availableProjectsResult.data,
296304
hasProjectSelected: false,
305+
existingVariables: {},
297306
};
298307
}
299308

@@ -330,7 +339,10 @@ export class VercelSettingsPresenter extends BasePresenter {
330339
environmentVariables: [],
331340
availableProjects: availableProjectsResult.data,
332341
hasProjectSelected: true,
342+
availableProjects: availableProjectsResult.data,
343+
hasProjectSelected: true,
333344
authInvalid: true,
345+
existingVariables: {},
334346
};
335347
}
336348

@@ -361,11 +373,22 @@ export class VercelSettingsPresenter extends BasePresenter {
361373
a.key.localeCompare(b.key)
362374
);
363375

376+
// Get existing environment variables in Trigger.dev
377+
const envVarRepository = new EnvironmentVariablesRepository(this._replica as PrismaClient);
378+
const existingVariables = await envVarRepository.getProject(projectId);
379+
const existingVariablesRecord: Record<string, { environments: RuntimeEnvironmentType[] }> = {};
380+
for (const v of existingVariables) {
381+
existingVariablesRecord[v.key] = {
382+
environments: v.values.map((val) => val.environment.type),
383+
};
384+
}
385+
364386
return {
365387
customEnvironments,
366388
environmentVariables: sortedEnvVars,
367389
availableProjects: availableProjectsResult.data,
368390
hasProjectSelected: true,
391+
existingVariables: existingVariablesRecord,
369392
};
370393
} catch (error) {
371394
// Log the error and return null to indicate failure

apps/webapp/app/routes/_app.orgs.$organizationSlug_.projects.new/route.tsx

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
v3ProjectPath,
3434
v3ProjectSettingsPath,
3535
} from "~/utils/pathBuilder";
36+
import { generateVercelOAuthState } from "~/v3/vercel/vercelOAuthState.server";
3637

3738
export async function loader({ params, request }: LoaderFunctionArgs) {
3839
const userId = await requireUserId(request);
@@ -118,19 +119,42 @@ export const action: ActionFunction = async ({ request, params }) => {
118119
version: submission.value.projectVersion,
119120
});
120121

121-
// If this is a Vercel integration flow, redirect back to callback
122+
// If this is a Vercel integration flow, generate state and redirect to connect
122123
if (code && configurationId) {
124+
const environment = await prisma.runtimeEnvironment.findFirst({
125+
where: {
126+
projectId: project.id,
127+
slug: "prod",
128+
archivedAt: null,
129+
},
130+
});
131+
132+
if (!environment) {
133+
return redirectWithErrorMessage(
134+
newProjectPath({ slug: organizationSlug }),
135+
request,
136+
"Failed to find project environment."
137+
);
138+
}
139+
140+
const state = await generateVercelOAuthState({
141+
organizationId: project.organization.id,
142+
projectId: project.id,
143+
environmentSlug: environment.slug,
144+
organizationSlug: project.organization.slug,
145+
projectSlug: project.slug,
146+
});
147+
123148
const params = new URLSearchParams({
149+
state,
124150
code,
125151
configurationId,
126-
organizationId: project.organization.id,
127-
projectId: project.id,
152+
origin: "marketplace",
128153
});
129154
if (next) {
130155
params.set("next", next);
131156
}
132-
const callbackUrl = `/callback/vercel?${params.toString()}`;
133-
return redirect(callbackUrl);
157+
return redirect(`/vercel/connect?${params.toString()}`);
134158
}
135159

136160
return redirectWithSuccessMessage(

0 commit comments

Comments
 (0)