Skip to content

Sync env vars (parent env vars) #2120

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export class ScheduleListPresenter extends BasePresenter {
type: true,
slug: true,
branchName: true,
archivedAt: true,
orgMember: {
select: {
user: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,32 @@ export async function action({ params, request }: ActionFunctionArgs) {
})),
});

if (environment.parentEnvironmentId && body.parentVariables) {
const parentResult = await repository.create(environment.project.id, {
override: typeof body.override === "boolean" ? body.override : false,
environmentIds: [environment.parentEnvironmentId],
variables: Object.entries(body.parentVariables).map(([key, value]) => ({
key,
value,
})),
});

let childFailure = !result.success ? result : undefined;
let parentFailure = !parentResult.success ? parentResult : undefined;

if (result.success || parentResult.success) {
return json({ success: true });
} else {
return json(
{
error: childFailure?.error || parentFailure?.error || "Unknown error",
variableErrors: childFailure?.variableErrors || parentFailure?.variableErrors,
},
{ status: 400 }
);
}
}

if (result.success) {
return json({ success: true });
} else {
Expand Down
16 changes: 11 additions & 5 deletions apps/webapp/app/services/upsertBranch.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ export class UpsertBranchService {
const limits = await checkBranchLimit(
this.#prismaClient,
parentEnvironment.organization.id,
parentEnvironment.project.id
parentEnvironment.project.id,
sanitizedBranchName
);

if (limits.isAtLimit) {
Expand Down Expand Up @@ -148,9 +149,10 @@ export class UpsertBranchService {
export async function checkBranchLimit(
prisma: PrismaClientOrTransaction,
organizationId: string,
projectId: string
projectId: string,
newBranchName?: string
) {
const used = await prisma.runtimeEnvironment.count({
const usedEnvs = await prisma.runtimeEnvironment.findMany({
where: {
projectId,
branchName: {
Expand All @@ -159,11 +161,15 @@ export async function checkBranchLimit(
archivedAt: null,
},
});

const count = newBranchName
? usedEnvs.filter((env) => env.branchName !== newBranchName).length
: usedEnvs.length;
const limit = await getLimit(organizationId, "branches", 50);

return {
used,
used: count,
limit,
isAtLimit: used >= limit,
isAtLimit: count >= limit,
};
}
6 changes: 4 additions & 2 deletions apps/webapp/app/v3/services/checkSchedule.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,11 @@ export class CheckScheduleService extends BaseService {
environments,
}: {
prisma: PrismaClientOrTransaction;
environments: { id: string; type: RuntimeEnvironmentType }[];
environments: { id: string; type: RuntimeEnvironmentType; archivedAt: Date | null }[];
}) {
const deployedEnvironments = environments.filter((env) => env.type !== "DEVELOPMENT");
const deployedEnvironments = environments.filter(
(env) => env.type !== "DEVELOPMENT" && !env.archivedAt
);
const schedulesCount = await prisma.taskScheduleInstance.count({
where: {
environmentId: {
Expand Down
5 changes: 5 additions & 0 deletions apps/webapp/app/v3/services/createBackgroundWorker.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ export class CreateBackgroundWorkerService extends BaseService {
backgroundWorker,
environment,
});

if (schedulesError instanceof ServiceValidationError) {
throw schedulesError;
}

throw new ServiceValidationError("Error syncing declarative schedules");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,10 @@ export class CreateDeploymentBackgroundWorkerService extends BaseService {
error: schedulesError,
});

const serviceError = new ServiceValidationError("Error syncing declarative schedules");
const serviceError =
schedulesError instanceof ServiceValidationError
? schedulesError
: new ServiceValidationError("Error syncing declarative schedules");

await this.#failBackgroundWorkerDeployment(deployment, serviceError);

Expand Down
62 changes: 40 additions & 22 deletions packages/build/src/extensions/core/syncEnvVars.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { BuildContext, BuildExtension } from "@trigger.dev/core/v3/build";

export type SyncEnvVarsBody = Record<string, string> | Array<{ name: string; value: string }>;
export type SyncEnvVarsBody =
| Record<string, string>
| Array<{ name: string; value: string; isParentEnv?: boolean }>;

export type SyncEnvVarsResult =
| SyncEnvVarsBody
Expand Down Expand Up @@ -96,24 +98,11 @@ export function syncEnvVars(fn: SyncEnvVarsFunction, options?: SyncEnvVarsOption
return;
}

const env = Object.entries(result).reduce(
(acc, [key, value]) => {
if (UNSYNCABLE_ENV_VARS.includes(key)) {
return acc;
}
const env = stripUnsyncableEnvVars(result.env);
const parentEnv = result.parentEnv ? stripUnsyncableEnvVars(result.parentEnv) : undefined;

// Strip out any TRIGGER_ prefix env vars
if (UNSYNCABLE_ENV_VARS_PREFIXES.some((prefix) => key.startsWith(prefix))) {
return acc;
}

acc[key] = value;
return acc;
},
{} as Record<string, string>
);

const numberOfEnvVars = Object.keys(env).length;
const numberOfEnvVars =
Object.keys(env).length + (parentEnv ? Object.keys(parentEnv).length : 0);

if (numberOfEnvVars === 0) {
$spinner.stop("No env vars detected");
Expand All @@ -129,22 +118,44 @@ export function syncEnvVars(fn: SyncEnvVarsFunction, options?: SyncEnvVarsOption
id: "sync-env-vars",
deploy: {
env,
parentEnv,
override: options?.override ?? true,
},
});
},
};
}

function stripUnsyncableEnvVars(env: Record<string, string>): Record<string, string> {
return Object.entries(env).reduce(
(acc, [key, value]) => {
if (UNSYNCABLE_ENV_VARS.includes(key)) {
return acc;
}

// Strip out any TRIGGER_ prefix env vars
if (UNSYNCABLE_ENV_VARS_PREFIXES.some((prefix) => key.startsWith(prefix))) {
return acc;
}

acc[key] = value;
return acc;
},
{} as Record<string, string>
);
}

async function callSyncEnvVarsFn(
syncEnvVarsFn: SyncEnvVarsFunction | undefined,
env: Record<string, string>,
environment: string,
branch: string | undefined,
context: BuildContext
): Promise<Record<string, string> | undefined> {
): Promise<{ env: Record<string, string>; parentEnv?: Record<string, string> } | undefined> {
if (syncEnvVarsFn && typeof syncEnvVarsFn === "function") {
let resolvedEnvVars: Record<string, string> = {};
let resolvedEnvVars: { env: Record<string, string>; parentEnv?: Record<string, string> } = {
env: {},
};
let result;

try {
Expand Down Expand Up @@ -172,11 +183,18 @@ async function callSyncEnvVarsFn(
typeof item.name === "string" &&
typeof item.value === "string"
) {
resolvedEnvVars[item.name] = item.value;
if (item.isParentEnv) {
if (!resolvedEnvVars.parentEnv) {
resolvedEnvVars.parentEnv = {};
}
resolvedEnvVars.parentEnv[item.name] = item.value;
} else {
resolvedEnvVars.env[item.name] = item.value;
}
}
}
} else if (result) {
resolvedEnvVars = result;
resolvedEnvVars.env = result;
}

return resolvedEnvVars;
Expand Down
19 changes: 13 additions & 6 deletions packages/build/src/extensions/core/vercelSyncEnvVars.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { BuildExtension } from "@trigger.dev/core/v3/build";
import { syncEnvVars } from "../core.js";

type EnvVar = { name: string; value: string; isParentEnv?: boolean };

export function syncVercelEnvVars(options?: {
projectId?: string;
vercelAccessToken?: string;
Expand Down Expand Up @@ -57,7 +59,7 @@ export function syncVercelEnvVars(options?: {
}
const params = new URLSearchParams({ decrypt: "true" });
if (vercelTeamId) params.set("teamId", vercelTeamId);
if (branch) params.set("gitBranch", branch);
params.set("target", vercelEnvironment);
const vercelApiUrl = `https://api.vercel.com/v8/projects/${projectId}/env?${params}`;

try {
Expand All @@ -73,15 +75,20 @@ export function syncVercelEnvVars(options?: {

const data = await response.json();

const filteredEnvs = data.envs
const isBranchable = ctx.environment === "preview";

const filteredEnvs: EnvVar[] = data.envs
.filter(
(env: { type: string; value: string; target: string[] }) =>
env.value && env.target.includes(vercelEnvironment)
)
.map((env: { key: string; value: string }) => ({
name: env.key,
value: env.value,
}));
.map((env: { key: string; value: string; gitBranch?: string }) => {
return {
name: env.key,
value: env.value,
isParentEnv: isBranchable && !env.gitBranch,
};
});

return filteredEnvs;
} catch (error) {
Expand Down
21 changes: 21 additions & 0 deletions packages/cli-v3/src/build/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ function applyLayerToManifest(layer: BuildLayer, manifest: BuildManifest): Build
$manifest.deploy.env ??= {};
$manifest.deploy.sync ??= {};
$manifest.deploy.sync.env ??= {};
$manifest.deploy.sync.parentEnv ??= {};

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

if (layer.deploy?.parentEnv) {
$manifest.deploy.env ??= {};
$manifest.deploy.sync ??= {};
$manifest.deploy.sync.parentEnv ??= {};

for (const [key, value] of Object.entries(layer.deploy.parentEnv)) {
if (!value) {
continue;
}

if (layer.deploy.override || $manifest.deploy.env[key] === undefined) {
const existingValue = $manifest.deploy.env[key];

if (existingValue !== value) {
$manifest.deploy.sync.parentEnv[key] = value;
}
}
}
}

if (layer.dependencies) {
const externals = $manifest.externals ?? [];

Expand Down
Loading