Skip to content

Commit e49ccc1

Browse files
authored
feat(buildExtensions): syncSupabaseEnvVars build extension (#3152)
with docs
1 parent c013322 commit e49ccc1

File tree

11 files changed

+466
-60
lines changed

11 files changed

+466
-60
lines changed

.changeset/warm-falcons-joke.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@trigger.dev/build": patch
3+
---
4+
5+
Add syncSupabaseEnvVars to pull database connection strings and save them as trigger.dev environment variables

apps/webapp/app/components/integrations/VercelBuildSettings.tsx

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Switch } from "~/components/primitives/Switch";
22
import { Label } from "~/components/primitives/Label";
33
import { Hint } from "~/components/primitives/Hint";
44
import { TextLink } from "~/components/primitives/TextLink";
5+
import { SimpleTooltip } from "~/components/primitives/Tooltip";
56
import {
67
EnvironmentIcon,
78
environmentFullTitle,
@@ -18,6 +19,8 @@ type BuildSettingsFieldsProps = {
1819
atomicBuilds: EnvSlug[];
1920
onAtomicBuildsChange: (slugs: EnvSlug[]) => void;
2021
envVarsConfigLink?: string;
22+
/** Slugs that should be forced off and disabled, with tooltip reason. */
23+
disabledEnvSlugs?: Partial<Record<EnvSlug, string>>;
2124
};
2225

2326
export function BuildSettingsFields({
@@ -29,7 +32,11 @@ export function BuildSettingsFields({
2932
atomicBuilds,
3033
onAtomicBuildsChange,
3134
envVarsConfigLink,
35+
disabledEnvSlugs,
3236
}: BuildSettingsFieldsProps) {
37+
const isSlugDisabled = (slug: EnvSlug) => !!disabledEnvSlugs?.[slug];
38+
const enabledSlugs = availableEnvSlugs.filter((s) => !isSlugDisabled(s));
39+
3340
return (
3441
<>
3542
{/* Pull env vars before build */}
@@ -41,11 +48,11 @@ export function BuildSettingsFields({
4148
<Switch
4249
variant="small"
4350
checked={
44-
availableEnvSlugs.length > 0 &&
45-
availableEnvSlugs.every((s) => pullEnvVarsBeforeBuild.includes(s))
51+
enabledSlugs.length > 0 &&
52+
enabledSlugs.every((s) => pullEnvVarsBeforeBuild.includes(s))
4653
}
4754
onCheckedChange={(checked) => {
48-
onPullEnvVarsChange(checked ? [...availableEnvSlugs] : []);
55+
onPullEnvVarsChange(checked ? [...enabledSlugs] : []);
4956
}}
5057
/>
5158
)}
@@ -63,8 +70,13 @@ export function BuildSettingsFields({
6370
<div className="flex flex-col gap-2 rounded border bg-charcoal-800 p-3">
6471
{availableEnvSlugs.map((slug) => {
6572
const envType = envSlugToType(slug);
66-
return (
67-
<div key={slug} className="flex items-center justify-between">
73+
const disabled = isSlugDisabled(slug);
74+
const disabledReason = disabledEnvSlugs?.[slug];
75+
const row = (
76+
<div
77+
key={slug}
78+
className={`flex items-center justify-between ${disabled ? "opacity-50" : ""}`}
79+
>
6880
<div className="flex items-center gap-1.5">
6981
<EnvironmentIcon environment={{ type: envType }} className="size-4" />
7082
<span className={`text-sm ${environmentTextClassName({ type: envType })}`}>
@@ -73,7 +85,8 @@ export function BuildSettingsFields({
7385
</div>
7486
<Switch
7587
variant="small"
76-
checked={pullEnvVarsBeforeBuild.includes(slug)}
88+
checked={disabled ? false : pullEnvVarsBeforeBuild.includes(slug)}
89+
disabled={disabled}
7790
onCheckedChange={(checked) => {
7891
onPullEnvVarsChange(
7992
checked
@@ -84,6 +97,12 @@ export function BuildSettingsFields({
8497
/>
8598
</div>
8699
);
100+
if (disabled && disabledReason) {
101+
return (
102+
<SimpleTooltip key={slug} button={row} content={disabledReason} side="left" />
103+
);
104+
}
105+
return row;
87106
})}
88107
</div>
89108
</div>
@@ -97,17 +116,17 @@ export function BuildSettingsFields({
97116
<Switch
98117
variant="small"
99118
checked={
100-
availableEnvSlugs.length > 0 &&
101-
availableEnvSlugs.every(
119+
enabledSlugs.length > 0 &&
120+
enabledSlugs.every(
102121
(s) => discoverEnvVars.includes(s) || !pullEnvVarsBeforeBuild.includes(s)
103122
) &&
104-
availableEnvSlugs.some((s) => discoverEnvVars.includes(s))
123+
enabledSlugs.some((s) => discoverEnvVars.includes(s))
105124
}
106-
disabled={!availableEnvSlugs.some((s) => pullEnvVarsBeforeBuild.includes(s))}
125+
disabled={!enabledSlugs.some((s) => pullEnvVarsBeforeBuild.includes(s))}
107126
onCheckedChange={(checked) => {
108127
onDiscoverEnvVarsChange(
109128
checked
110-
? availableEnvSlugs.filter((s) => pullEnvVarsBeforeBuild.includes(s))
129+
? enabledSlugs.filter((s) => pullEnvVarsBeforeBuild.includes(s))
111130
: []
112131
);
113132
}}
@@ -122,11 +141,13 @@ export function BuildSettingsFields({
122141
<div className="flex flex-col gap-2 rounded border bg-charcoal-800 p-3">
123142
{availableEnvSlugs.map((slug) => {
124143
const envType = envSlugToType(slug);
144+
const disabled = isSlugDisabled(slug);
145+
const disabledReason = disabledEnvSlugs?.[slug];
125146
const isPullDisabled = !pullEnvVarsBeforeBuild.includes(slug);
126-
return (
147+
const row = (
127148
<div
128149
key={slug}
129-
className={`flex items-center justify-between ${isPullDisabled ? "opacity-50" : ""}`}
150+
className={`flex items-center justify-between ${disabled || isPullDisabled ? "opacity-50" : ""}`}
130151
>
131152
<div className="flex items-center gap-1.5">
132153
<EnvironmentIcon environment={{ type: envType }} className="size-4" />
@@ -136,8 +157,8 @@ export function BuildSettingsFields({
136157
</div>
137158
<Switch
138159
variant="small"
139-
checked={discoverEnvVars.includes(slug)}
140-
disabled={isPullDisabled}
160+
checked={disabled ? false : discoverEnvVars.includes(slug)}
161+
disabled={disabled || isPullDisabled}
141162
onCheckedChange={(checked) => {
142163
onDiscoverEnvVarsChange(
143164
checked
@@ -148,6 +169,12 @@ export function BuildSettingsFields({
148169
/>
149170
</div>
150171
);
172+
if (disabled && disabledReason) {
173+
return (
174+
<SimpleTooltip key={slug} button={row} content={disabledReason} side="left" />
175+
);
176+
}
177+
return row;
151178
})}
152179
</div>
153180
</div>

apps/webapp/app/components/integrations/VercelOnboardingModal.tsx

Lines changed: 30 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { vercelAppInstallPath, v3ProjectSettingsIntegrationsPath, githubAppInsta
4848
import type { loader } from "~/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel";
4949
import { useEffect, useState, useCallback, useRef } from "react";
5050
import { usePostHogTracking } from "~/hooks/usePostHog";
51+
import { TextLink } from "../primitives/TextLink";
5152

5253
function safeRedirectUrl(url: string): string | null {
5354
try {
@@ -224,9 +225,10 @@ export function VercelOnboardingModal({
224225
() => availableEnvSlugsForOnboardingBuildSettings
225226
);
226227

227-
// Sync pullEnvVarsBeforeBuild and discoverEnvVars when hasStagingEnvironment becomes true (once)
228+
// Sync pullEnvVarsBeforeBuild and discoverEnvVars when hasStagingEnvironment becomes true
229+
// AND a custom Vercel environment is mapped (once)
228230
useEffect(() => {
229-
if (hasStagingEnvironment && !hasSyncedStagingRef.current) {
231+
if (hasStagingEnvironment && vercelStagingEnvironment && !hasSyncedStagingRef.current) {
230232
hasSyncedStagingRef.current = true;
231233
setPullEnvVarsBeforeBuild((prev) => {
232234
if (!prev.includes("stg")) {
@@ -241,7 +243,15 @@ export function VercelOnboardingModal({
241243
return prev;
242244
});
243245
}
244-
}, [hasStagingEnvironment]);
246+
}, [hasStagingEnvironment, vercelStagingEnvironment]);
247+
248+
// Strip "stg" from build settings when the staging environment mapping is cleared
249+
useEffect(() => {
250+
if (!vercelStagingEnvironment) {
251+
setPullEnvVarsBeforeBuild((prev) => prev.filter((s) => s !== "stg"));
252+
setDiscoverEnvVars((prev) => prev.filter((s) => s !== "stg"));
253+
}
254+
}, [vercelStagingEnvironment]);
245255

246256
// Sync pullEnvVarsBeforeBuild and discoverEnvVars when hasPreviewEnvironment becomes true (once)
247257
useEffect(() => {
@@ -670,6 +680,11 @@ export function VercelOnboardingModal({
670680
const showBuildSettings = state === "build-settings";
671681
const showGitHubConnection = state === "github-connection";
672682

683+
const disabledEnvSlugsForBuildSettings =
684+
hasStagingEnvironment && !vercelStagingEnvironment
685+
? ({ stg: "Map a custom Vercel environment to Staging to enable this" } as Partial<Record<EnvSlug, string>>)
686+
: undefined;
687+
673688
return (
674689
<Dialog open={isOpen} onOpenChange={(open) => {
675690
if (!open && !fromMarketplaceContext) {
@@ -731,7 +746,7 @@ export function VercelOnboardingModal({
731746
)}
732747

733748
<Hint>
734-
Once connected, your <code className="text-xs">TRIGGER_SECRET_KEY</code> will be
749+
Once connected, your <code className="text-xs rounded bg-charcoal-700 px-1 py-0.5 text-text-bright">TRIGGER_SECRET_KEY</code> will be
735750
automatically synced to Vercel for each environment.
736751
</Hint>
737752

@@ -775,6 +790,10 @@ export function VercelOnboardingModal({
775790
<Paragraph className="text-sm">
776791
Select which custom Vercel environment should map to Trigger.dev's Staging
777792
environment. Production and Preview environments are mapped automatically.
793+
If you skip this step, the{" "}
794+
<code className="rounded bg-charcoal-700 px-1 py-0.5 text-text-bright">TRIGGER_SECRET_KEY</code>{" "}
795+
will not be installed for the staging environment in Vercel. You can configure this later in
796+
project settings.
778797
</Paragraph>
779798

780799
<Select
@@ -800,15 +819,6 @@ export function VercelOnboardingModal({
800819
))}
801820
</Select>
802821

803-
<Callout variant="info">
804-
<p className="text-xs">
805-
If you skip this step, the{" "}
806-
<code className="rounded bg-charcoal-700 px-1 py-0.5 text-text-bright">TRIGGER_SECRET_KEY</code>{" "}
807-
will not be installed for the staging environment in Vercel. You can configure this later in
808-
project settings.
809-
</p>
810-
</Callout>
811-
812822
<Paragraph className="text-xs text-text-dimmed">
813823
Make sure the staging branch in your Vercel project's Git settings matches the staging branch
814824
configured in your GitHub integration.
@@ -845,25 +855,13 @@ export function VercelOnboardingModal({
845855

846856
{showEnvVarSync && (
847857
<div className="flex flex-col gap-4">
848-
<Header3>Pull Environment Variables</Header3>
849-
<Paragraph className="text-sm">
850-
Select which environment variables to pull from Vercel now. This is a one-time pull.
851-
Later on environment variables can be pulled before each build.
852-
</Paragraph>
853-
854-
<div className="flex gap-4 text-sm">
855-
<div className="rounded border bg-charcoal-750 px-3 py-2">
856-
<span className="font-medium text-text-bright">{syncableEnvVars.length}</span>
857-
<span className="text-text-dimmed"> can be pulled</span>
858-
</div>
859-
{secretEnvVars.length > 0 && (
860-
<div className="rounded border bg-charcoal-750 px-3 py-2">
861-
<span className="font-medium text-amber-400">{secretEnvVars.length}</span>
862-
<span className="text-text-dimmed"> secret (cannot pull)</span>
863-
</div>
864-
)}
858+
<div className="flex flex-col gap-1">
859+
<Header3>Pull Environment Variables</Header3>
860+
<Paragraph className="text-sm">
861+
Choose which environment variables to import from Vercel. This runs as a one time pull to prefill your project with the variables it needs. You’ll be able to pull again later, or enable automatic syncing before each build if you prefer.
862+
If you are using Supabase or Neon branching, <TextLink href="https://trigger.dev/docs/vercel-integration#supabase-and-neon-database-branching" target="_blank" rel="noopener noreferrer">read the docs</TextLink> for the recommended setup.
863+
</Paragraph>
865864
</div>
866-
867865
<div className="flex items-center justify-between rounded border bg-charcoal-800 p-3">
868866
<div>
869867
<Label>Pull all environment variables now</Label>
@@ -1052,6 +1050,7 @@ export function VercelOnboardingModal({
10521050
onDiscoverEnvVarsChange={setDiscoverEnvVars}
10531051
atomicBuilds={atomicBuilds}
10541052
onAtomicBuildsChange={setAtomicBuilds}
1053+
disabledEnvSlugs={disabledEnvSlugsForBuildSettings}
10551054
/>
10561055

10571056
<FormButtons

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,16 +1759,18 @@ export class VercelIntegrationRepository {
17591759
vercelProjectId: string,
17601760
teamId?: string | null
17611761
): ResultAsync<void, VercelApiError> {
1762-
return wrapVercelCall(
1762+
return wrapVercelCallWithRecovery(
17631763
client.projects.updateProject({
17641764
idOrName: vercelProjectId,
17651765
...(teamId && { teamId }),
17661766
requestBody: {
17671767
autoAssignCustomDomains: false,
17681768
},
17691769
}),
1770+
VercelSchemas.updateProject,
17701771
"Failed to disable autoAssignCustomDomains",
1771-
{ vercelProjectId, teamId }
1772+
{ vercelProjectId, teamId },
1773+
toVercelApiError
17721774
).map(() => undefined);
17731775
}
17741776

apps/webapp/app/routes/resources.orgs.$organizationSlug.projects.$projectParam.env.$envParam.vercel.tsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,11 @@ function ConnectedVercelProjectForm({
632632
const availableEnvSlugs = getAvailableEnvSlugs(hasStagingEnvironment, hasPreviewEnvironment);
633633
const availableEnvSlugsForBuildSettings = getAvailableEnvSlugsForBuildSettings(hasStagingEnvironment, hasPreviewEnvironment);
634634

635+
const disabledEnvSlugsForBuildSettings: Partial<Record<EnvSlug, string>> | undefined =
636+
hasStagingEnvironment && !configValues.vercelStagingEnvironment
637+
? { stg: "Map a custom Vercel environment to Staging to enable this" }
638+
: undefined;
639+
635640
const formatSelectedEnvs = (selected: EnvSlug[], availableSlugs: EnvSlug[] = availableEnvSlugs): string => {
636641
if (selected.length === 0) return "None selected";
637642
if (selected.length === availableSlugs.length) return "All environments";
@@ -727,12 +732,24 @@ function ConnectedVercelProjectForm({
727732
setValue={(value) => {
728733
if (!Array.isArray(value)) {
729734
const env = customEnvironments?.find((e) => e.id === value);
730-
setConfigValues((prev) => ({
731-
...prev,
732-
vercelStagingEnvironment: env
733-
? { environmentId: env.id, displayName: env.slug }
734-
: null,
735-
}));
735+
setConfigValues((prev) => {
736+
const next = {
737+
...prev,
738+
vercelStagingEnvironment: env
739+
? { environmentId: env.id, displayName: env.slug }
740+
: null,
741+
};
742+
// When clearing the staging mapping, strip "stg" from build settings
743+
if (!env) {
744+
next.pullEnvVarsBeforeBuild = prev.pullEnvVarsBeforeBuild.filter(
745+
(s) => s !== "stg"
746+
);
747+
next.discoverEnvVars = prev.discoverEnvVars.filter(
748+
(s) => s !== "stg"
749+
);
750+
}
751+
return next;
752+
});
736753
}
737754
}}
738755
items={[{ id: "", slug: "None" }, ...customEnvironments]}
@@ -770,6 +787,7 @@ function ConnectedVercelProjectForm({
770787
setConfigValues((prev) => ({ ...prev, atomicBuilds: slugs }))
771788
}
772789
envVarsConfigLink={`/orgs/${organizationSlug}/projects/${projectSlug}/env/${environmentSlug}/environment-variables`}
790+
disabledEnvSlugs={disabledEnvSlugsForBuildSettings}
773791
/>
774792

775793
{/* Warning: autoAssignCustomDomains must be disabled for atomic deployments */}

apps/webapp/app/v3/vercel/vercelProjectIntegrationSchema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,8 @@ export function createDefaultVercelIntegrationData(
8585
return {
8686
config: {
8787
atomicBuilds: ["prod"],
88-
pullEnvVarsBeforeBuild: ["prod", "stg", "preview"],
89-
discoverEnvVars: ["prod", "stg", "preview"],
88+
pullEnvVarsBeforeBuild: ["prod", "preview"],
89+
discoverEnvVars: ["prod", "preview"],
9090
vercelStagingEnvironment: null,
9191
},
9292
syncEnvVarsMapping: {},

docs/config/extensions/overview.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ Trigger.dev provides a set of built-in extensions that you can use to customize
5757
| [additionalPackages](/config/extensions/additionalPackages) | Install additional npm packages in your build image |
5858
| [syncEnvVars](/config/extensions/syncEnvVars) | Automatically sync environment variables from external services to Trigger.dev |
5959
| [syncVercelEnvVars](/config/extensions/syncEnvVars#syncVercelEnvVars) | Automatically sync environment variables from Vercel to Trigger.dev |
60+
| [syncSupabaseEnvVars](/config/extensions/syncEnvVars#syncSupabaseEnvVars) | Automatically sync environment variables from Supabase to Trigger.dev |
6061
| [esbuildPlugin](/config/extensions/esbuildPlugin) | Add existing or custom esbuild extensions to customize your build process |
6162
| [emitDecoratorMetadata](/config/extensions/emitDecoratorMetadata) | Enable `emitDecoratorMetadata` in your TypeScript build |
6263
| [audioWaveform](/config/extensions/audioWaveform) | Add Audio Waveform to your build image |

0 commit comments

Comments
 (0)