Skip to content

Commit aed0848

Browse files
ericallamTrigger.dev RepoOps
authored andcommitted
fix(webapp): re-sync webhook endpoints on promote and rollback
Promoting or rolling back a deployment now updates webhook endpoints to match the deployment that becomes current. Mono-RevId: dff3d7c34e49b2a8f7e4e5a4b4cc79b145e98888
1 parent 97ef639 commit aed0848

4 files changed

Lines changed: 113 additions & 16 deletions

File tree

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
area: webapp
3+
type: fix
4+
---
5+
6+
Promoting or rolling back a deployment now updates your webhook endpoints to match the deployment that's now current.

‎apps/webapp/app/v3/services/changeCurrentDeployment.server.ts‎

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -171,16 +171,16 @@ export class ChangeCurrentDeploymentService extends BaseService {
171171
);
172172
}
173173

174-
const [scheduleSyncError] = await tryCatch(this.#syncSchedulesForDeployment(deployment));
174+
const [declarativeSyncError] = await tryCatch(this.#syncDeclarativeResources(deployment));
175175

176-
if (scheduleSyncError) {
177-
logger.error("Error syncing declarative schedules on deployment change", {
178-
error: scheduleSyncError,
176+
if (declarativeSyncError) {
177+
logger.error("Error syncing declarative resources on deployment change", {
178+
error: declarativeSyncError,
179179
});
180180
}
181181
}
182182

183-
async #syncSchedulesForDeployment(deployment: WorkerDeployment) {
183+
async #syncDeclarativeResources(deployment: WorkerDeployment) {
184184
const worker = await this._prisma.backgroundWorker.findFirst({
185185
where: { id: deployment.workerId! },
186186
});
@@ -221,16 +221,35 @@ export class ChangeCurrentDeploymentService extends BaseService {
221221
return;
222222
}
223223

224-
await syncDeclarativeSchedules(parsed.data.tasks, worker, environment, this._prisma);
225-
await syncDeclarativeWebhooks(
226-
{
227-
endpoints: parsed.data.webhookEndpoints,
228-
subscribers: parsed.data.webhookSubscribers,
229-
},
230-
worker,
231-
environment,
232-
this._prisma,
233-
webhookPrisma
224+
const [schedulesError] = await tryCatch(
225+
syncDeclarativeSchedules(parsed.data.tasks, worker, environment, this._prisma)
226+
);
227+
228+
if (schedulesError) {
229+
logger.error("Error syncing declarative schedules on deployment change", {
230+
deploymentId: deployment.id,
231+
error: schedulesError,
232+
});
233+
}
234+
235+
const [webhooksError] = await tryCatch(
236+
syncDeclarativeWebhooks(
237+
{
238+
endpoints: parsed.data.webhookEndpoints,
239+
subscribers: parsed.data.webhookSubscribers,
240+
},
241+
worker,
242+
environment,
243+
this._prisma,
244+
webhookPrisma
245+
)
234246
);
247+
248+
if (webhooksError) {
249+
logger.error("Error syncing declarative webhooks on deployment change", {
250+
deploymentId: deployment.id,
251+
error: webhooksError,
252+
});
253+
}
235254
}
236255
}

‎apps/webapp/app/v3/services/stripBackgroundWorkerMetadataForStorage.server.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import type { Prisma } from "@trigger.dev/database";
77
* (BackgroundWorker.{contentHash,cliVersion,sdkVersion,runtime,runtimeVersion},
88
* BackgroundWorkerTask, BackgroundWorkerFile, TaskQueue, Prompt). Today the
99
* only post-write reader is changeCurrentDeployment.server.ts, which feeds
10-
* tasks[].schedule into syncDeclarativeSchedules. packageVersion, contentHash,
10+
* tasks[].schedule into syncDeclarativeSchedules and webhookEndpoints +
11+
* webhookSubscribers into syncDeclarativeWebhooks. packageVersion, contentHash,
1112
* and tasks[].filePath are kept solely to satisfy BackgroundWorkerMetadata's
1213
* required fields when the column is parsed back.
1314
*/
@@ -24,5 +25,9 @@ export function stripBackgroundWorkerMetadataForStorage(
2425
filePath: t.filePath,
2526
schedule: t.schedule,
2627
})),
28+
webhookEndpoints: metadata.webhookEndpoints as unknown as Prisma.InputJsonValue[] | undefined,
29+
webhookSubscribers: metadata.webhookSubscribers as unknown as
30+
| Prisma.InputJsonValue[]
31+
| undefined,
2732
};
2833
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { BackgroundWorkerMetadata } from "@trigger.dev/core/v3";
2+
import { describe, expect, it } from "vitest";
3+
import { stripBackgroundWorkerMetadataForStorage } from "~/v3/services/stripBackgroundWorkerMetadataForStorage.server";
4+
5+
const baseMetadata: BackgroundWorkerMetadata = {
6+
packageVersion: "4.8.1",
7+
contentHash: "hash",
8+
tasks: [
9+
{ id: "plain", filePath: "src/trigger/plain.ts", exportName: "plain" },
10+
{
11+
id: "nightly",
12+
filePath: "src/trigger/nightly.ts",
13+
exportName: "nightly",
14+
schedule: { cron: "0 0 * * *", timezone: "UTC" },
15+
},
16+
],
17+
};
18+
19+
function roundTrip(metadata: BackgroundWorkerMetadata) {
20+
const stored = JSON.parse(JSON.stringify(stripBackgroundWorkerMetadataForStorage(metadata)));
21+
return BackgroundWorkerMetadata.parse(stored);
22+
}
23+
24+
describe("stripBackgroundWorkerMetadataForStorage", () => {
25+
it("keeps only scheduled tasks", () => {
26+
const parsed = roundTrip(baseMetadata);
27+
28+
expect(parsed.tasks.map((t) => t.id)).toEqual(["nightly"]);
29+
expect(parsed.tasks[0].schedule).toEqual({ cron: "0 0 * * *", timezone: "UTC" });
30+
});
31+
32+
it("keeps webhook endpoints and subscribers so promotion can re-sync them", () => {
33+
const webhookEndpoints: BackgroundWorkerMetadata["webhookEndpoints"] = [
34+
{
35+
id: "stripe-events",
36+
filePath: "src/trigger/stripe.ts",
37+
source: "stripe",
38+
verifierArtifact: { kind: "bundle", bundleUrl: "https://example.test/v.js", hash: "h" },
39+
},
40+
];
41+
const webhookSubscribers: BackgroundWorkerMetadata["webhookSubscribers"] = [
42+
{
43+
endpointId: "stripe-events",
44+
target: { type: "task", id: "plain", taskId: "plain" },
45+
},
46+
];
47+
48+
const parsed = roundTrip({ ...baseMetadata, webhookEndpoints, webhookSubscribers });
49+
50+
expect(parsed.webhookEndpoints).toEqual(webhookEndpoints);
51+
expect(parsed.webhookSubscribers).toEqual(webhookSubscribers);
52+
});
53+
54+
it("keeps an empty endpoint list so promotion removes endpoints the worker no longer declares", () => {
55+
const parsed = roundTrip({ ...baseMetadata, webhookEndpoints: [], webhookSubscribers: [] });
56+
57+
expect(parsed.webhookEndpoints).toEqual([]);
58+
expect(parsed.webhookSubscribers).toEqual([]);
59+
});
60+
61+
it("leaves webhook fields absent when the worker didn't declare them", () => {
62+
const parsed = roundTrip(baseMetadata);
63+
64+
expect(parsed.webhookEndpoints).toBeUndefined();
65+
expect(parsed.webhookSubscribers).toBeUndefined();
66+
});
67+
});

0 commit comments

Comments
 (0)