Skip to content

Commit d50aa32

Browse files
committed
WIP with vercel preview env var syncing
1 parent 16ddc3f commit d50aa32

File tree

15 files changed

+170
-71
lines changed

15 files changed

+170
-71
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ export class ScheduleListPresenter extends BasePresenter {
6969
type: true,
7070
slug: true,
7171
branchName: true,
72+
archivedAt: true,
7273
orgMember: {
7374
select: {
7475
user: {

apps/webapp/app/routes/api.v1.projects.$projectRef.envvars.$slug.import.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,32 @@ export async function action({ params, request }: ActionFunctionArgs) {
4747
})),
4848
});
4949

50+
if (environment.parentEnvironmentId && body.parentVariables) {
51+
const parentResult = await repository.create(environment.project.id, {
52+
override: typeof body.override === "boolean" ? body.override : false,
53+
environmentIds: [environment.parentEnvironmentId],
54+
variables: Object.entries(body.parentVariables).map(([key, value]) => ({
55+
key,
56+
value,
57+
})),
58+
});
59+
60+
let childFailure = !result.success ? result : undefined;
61+
let parentFailure = !parentResult.success ? parentResult : undefined;
62+
63+
if (result.success && parentResult.success) {
64+
return json({ success: true });
65+
} else {
66+
return json(
67+
{
68+
error: childFailure?.error || parentFailure?.error || "Unknown error",
69+
variableErrors: childFailure?.variableErrors || parentFailure?.variableErrors,
70+
},
71+
{ status: 400 }
72+
);
73+
}
74+
}
75+
5076
if (result.success) {
5177
return json({ success: true });
5278
} else {

apps/webapp/app/services/upsertBranch.server.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ export class UpsertBranchService {
7676
const limits = await checkBranchLimit(
7777
this.#prismaClient,
7878
parentEnvironment.organization.id,
79-
parentEnvironment.project.id
79+
parentEnvironment.project.id,
80+
sanitizedBranchName
8081
);
8182

8283
if (limits.isAtLimit) {
@@ -148,9 +149,10 @@ export class UpsertBranchService {
148149
export async function checkBranchLimit(
149150
prisma: PrismaClientOrTransaction,
150151
organizationId: string,
151-
projectId: string
152+
projectId: string,
153+
newBranchName?: string
152154
) {
153-
const used = await prisma.runtimeEnvironment.count({
155+
const usedEnvs = await prisma.runtimeEnvironment.findMany({
154156
where: {
155157
projectId,
156158
branchName: {
@@ -159,11 +161,15 @@ export async function checkBranchLimit(
159161
archivedAt: null,
160162
},
161163
});
164+
165+
const count = newBranchName
166+
? usedEnvs.filter((env) => env.branchName !== newBranchName).length
167+
: usedEnvs.length;
162168
const limit = await getLimit(organizationId, "branches", 50);
163169

164170
return {
165-
used,
171+
used: count,
166172
limit,
167-
isAtLimit: used >= limit,
173+
isAtLimit: count >= limit,
168174
};
169175
}

apps/webapp/app/v3/services/checkSchedule.server.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,11 @@ export class CheckScheduleService extends BaseService {
108108
environments,
109109
}: {
110110
prisma: PrismaClientOrTransaction;
111-
environments: { id: string; type: RuntimeEnvironmentType }[];
111+
environments: { id: string; type: RuntimeEnvironmentType; archivedAt: Date | null }[];
112112
}) {
113-
const deployedEnvironments = environments.filter((env) => env.type !== "DEVELOPMENT");
113+
const deployedEnvironments = environments.filter(
114+
(env) => env.type !== "DEVELOPMENT" && !env.archivedAt
115+
);
114116
const schedulesCount = await prisma.taskScheduleInstance.count({
115117
where: {
116118
environmentId: {

apps/webapp/app/v3/services/createBackgroundWorker.server.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,11 @@ export class CreateBackgroundWorkerService extends BaseService {
146146
backgroundWorker,
147147
environment,
148148
});
149+
150+
if (schedulesError instanceof ServiceValidationError) {
151+
throw schedulesError;
152+
}
153+
149154
throw new ServiceValidationError("Error syncing declarative schedules");
150155
}
151156

apps/webapp/app/v3/services/createDeploymentBackgroundWorker.server.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,10 @@ export class CreateDeploymentBackgroundWorkerService extends BaseService {
114114
error: schedulesError,
115115
});
116116

117-
const serviceError = new ServiceValidationError("Error syncing declarative schedules");
117+
const serviceError =
118+
schedulesError instanceof ServiceValidationError
119+
? schedulesError
120+
: new ServiceValidationError("Error syncing declarative schedules");
118121

119122
await this.#failBackgroundWorkerDeployment(deployment, serviceError);
120123

packages/build/src/extensions/core/syncEnvVars.ts

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { BuildContext, BuildExtension } from "@trigger.dev/core/v3/build";
22

3-
export type SyncEnvVarsBody = Record<string, string> | Array<{ name: string; value: string }>;
3+
export type SyncEnvVarsBody =
4+
| Record<string, string>
5+
| Array<{ name: string; value: string; isParentEnv?: boolean }>;
46

57
export type SyncEnvVarsResult =
68
| SyncEnvVarsBody
@@ -96,24 +98,11 @@ export function syncEnvVars(fn: SyncEnvVarsFunction, options?: SyncEnvVarsOption
9698
return;
9799
}
98100

99-
const env = Object.entries(result).reduce(
100-
(acc, [key, value]) => {
101-
if (UNSYNCABLE_ENV_VARS.includes(key)) {
102-
return acc;
103-
}
104-
105-
// Strip out any TRIGGER_ prefix env vars
106-
if (UNSYNCABLE_ENV_VARS_PREFIXES.some((prefix) => key.startsWith(prefix))) {
107-
return acc;
108-
}
101+
const env = stripUnsyncableEnvVars(result.env);
102+
const parentEnv = result.parentEnv ? stripUnsyncableEnvVars(result.parentEnv) : undefined;
109103

110-
acc[key] = value;
111-
return acc;
112-
},
113-
{} as Record<string, string>
114-
);
115-
116-
const numberOfEnvVars = Object.keys(env).length;
104+
const numberOfEnvVars =
105+
Object.keys(env).length + (parentEnv ? Object.keys(parentEnv).length : 0);
117106

118107
if (numberOfEnvVars === 0) {
119108
$spinner.stop("No env vars detected");
@@ -125,26 +114,54 @@ export function syncEnvVars(fn: SyncEnvVarsFunction, options?: SyncEnvVarsOption
125114
$spinner.stop(`Found ${numberOfEnvVars} env vars to sync`);
126115
}
127116

117+
context.logger.debug("syncEnvVars", {
118+
env,
119+
parentEnv,
120+
numberOfEnvVars,
121+
});
122+
128123
context.addLayer({
129124
id: "sync-env-vars",
130125
deploy: {
131126
env,
127+
parentEnv,
132128
override: options?.override ?? true,
133129
},
134130
});
135131
},
136132
};
137133
}
138134

135+
function stripUnsyncableEnvVars(env: Record<string, string>): Record<string, string> {
136+
return Object.entries(env).reduce(
137+
(acc, [key, value]) => {
138+
if (UNSYNCABLE_ENV_VARS.includes(key)) {
139+
return acc;
140+
}
141+
142+
// Strip out any TRIGGER_ prefix env vars
143+
if (UNSYNCABLE_ENV_VARS_PREFIXES.some((prefix) => key.startsWith(prefix))) {
144+
return acc;
145+
}
146+
147+
acc[key] = value;
148+
return acc;
149+
},
150+
{} as Record<string, string>
151+
);
152+
}
153+
139154
async function callSyncEnvVarsFn(
140155
syncEnvVarsFn: SyncEnvVarsFunction | undefined,
141156
env: Record<string, string>,
142157
environment: string,
143158
branch: string | undefined,
144159
context: BuildContext
145-
): Promise<Record<string, string> | undefined> {
160+
): Promise<{ env: Record<string, string>; parentEnv?: Record<string, string> } | undefined> {
146161
if (syncEnvVarsFn && typeof syncEnvVarsFn === "function") {
147-
let resolvedEnvVars: Record<string, string> = {};
162+
let resolvedEnvVars: { env: Record<string, string>; parentEnv?: Record<string, string> } = {
163+
env: {},
164+
};
148165
let result;
149166

150167
try {
@@ -172,11 +189,18 @@ async function callSyncEnvVarsFn(
172189
typeof item.name === "string" &&
173190
typeof item.value === "string"
174191
) {
175-
resolvedEnvVars[item.name] = item.value;
192+
if (item.isParentEnv) {
193+
if (!resolvedEnvVars.parentEnv) {
194+
resolvedEnvVars.parentEnv = {};
195+
}
196+
resolvedEnvVars.parentEnv[item.name] = item.value;
197+
} else {
198+
resolvedEnvVars.env[item.name] = item.value;
199+
}
176200
}
177201
}
178202
} else if (result) {
179-
resolvedEnvVars = result;
203+
resolvedEnvVars.env = result;
180204
}
181205

182206
return resolvedEnvVars;

packages/build/src/extensions/core/vercelSyncEnvVars.ts

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { BuildExtension } from "@trigger.dev/core/v3/build";
22
import { syncEnvVars } from "../core.js";
33

4-
type EnvVar = { name: string; value: string; gitBranch?: string };
4+
type EnvVar = { name: string; value: string; isParentEnv?: boolean };
55

66
export function syncVercelEnvVars(options?: {
77
projectId?: string;
@@ -75,37 +75,22 @@ export function syncVercelEnvVars(options?: {
7575

7676
const data = await response.json();
7777

78+
const isBranchable = ctx.environment === "preview";
79+
7880
const filteredEnvs: EnvVar[] = data.envs
7981
.filter(
8082
(env: { type: string; value: string; target: string[] }) =>
8183
env.value && env.target.includes(vercelEnvironment)
8284
)
83-
.map((env: { key: string; value: string; gitBranch?: string }) => ({
84-
name: env.key,
85-
value: env.value,
86-
gitBranch: env.gitBranch,
87-
}));
88-
89-
let envMap: Map<string, EnvVar> = new Map();
90-
91-
// if there's a branch we want to prefer branch values over base values
92-
if (branch) {
93-
for (const env of filteredEnvs) {
94-
if (!envMap.has(env.name)) {
95-
envMap.set(env.name, env);
96-
continue;
97-
}
98-
99-
//if there's a gitBranch we want to override any previous value
100-
if (env.gitBranch === branch) {
101-
envMap.set(env.name, env);
102-
}
103-
}
104-
} else {
105-
envMap = new Map(filteredEnvs.map((env) => [env.name, env]));
106-
}
107-
108-
return Array.from(envMap.values());
85+
.map((env: { key: string; value: string; gitBranch?: string }) => {
86+
return {
87+
name: env.key,
88+
value: env.value,
89+
isParentEnv: isBranchable && !env.gitBranch,
90+
};
91+
});
92+
93+
return filteredEnvs;
10994
} catch (error) {
11095
console.error("Error fetching or processing Vercel environment variables:", error);
11196
throw error; // Re-throw the error to be handled by the caller

packages/cli-v3/src/build/extensions.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ function applyLayerToManifest(layer: BuildLayer, manifest: BuildManifest): Build
143143
$manifest.deploy.env ??= {};
144144
$manifest.deploy.sync ??= {};
145145
$manifest.deploy.sync.env ??= {};
146+
$manifest.deploy.sync.parentEnv ??= {};
146147

147148
for (const [key, value] of Object.entries(layer.deploy.env)) {
148149
if (!value) {
@@ -159,6 +160,26 @@ function applyLayerToManifest(layer: BuildLayer, manifest: BuildManifest): Build
159160
}
160161
}
161162

163+
if (layer.deploy?.parentEnv) {
164+
$manifest.deploy.env ??= {};
165+
$manifest.deploy.sync ??= {};
166+
$manifest.deploy.sync.parentEnv ??= {};
167+
168+
for (const [key, value] of Object.entries(layer.deploy.parentEnv)) {
169+
if (!value) {
170+
continue;
171+
}
172+
173+
if (layer.deploy.override || $manifest.deploy.env[key] === undefined) {
174+
const existingValue = $manifest.deploy.env[key];
175+
176+
if (existingValue !== value) {
177+
$manifest.deploy.sync.parentEnv[key] = value;
178+
}
179+
}
180+
}
181+
}
182+
162183
if (layer.dependencies) {
163184
const externals = $manifest.externals ?? [];
164185

0 commit comments

Comments
 (0)