GeezerVison is showing github.com — fix(webapp): re-sync webhook endpoints on promote and rollback · triggerdotdev/trigger.dev@aed0848
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboard +6Lines changed: 6 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
| | 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. |
Collapse file Copy file name to clipboardExpand all lines: apps/webapp/app/v3/services/changeCurrentDeployment.server.ts +34-15Lines changed: 34 additions & 15 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -171,16 +171,16 @@ export class ChangeCurrentDeploymentService extends BaseService { | 171 | 171 | ); | 172 | 172 | } | 173 | 173 |
| 174 | | - const [scheduleSyncError] = await tryCatch(this.#syncSchedulesForDeployment(deployment)); | | 174 | + const [declarativeSyncError] = await tryCatch(this.#syncDeclarativeResources(deployment)); | 175 | 175 |
| 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, | 179 | 179 | }); | 180 | 180 | } | 181 | 181 | } | 182 | 182 |
| 183 | | - async #syncSchedulesForDeployment(deployment: WorkerDeployment) { | | 183 | + async #syncDeclarativeResources(deployment: WorkerDeployment) { | 184 | 184 | const worker = await this._prisma.backgroundWorker.findFirst({ | 185 | 185 | where: { id: deployment.workerId! }, | 186 | 186 | }); | @@ -221,16 +221,35 @@ export class ChangeCurrentDeploymentService extends BaseService { | 221 | 221 | return; | 222 | 222 | } | 223 | 223 |
| 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 | + ) | 234 | 246 | ); | | 247 | + | | 248 | + if (webhooksError) { | | 249 | + logger.error("Error syncing declarative webhooks on deployment change", { | | 250 | + deploymentId: deployment.id, | | 251 | + error: webhooksError, | | 252 | + }); | | 253 | + } | 235 | 254 | } | 236 | 255 | } |
Collapse file Copy file name to clipboardExpand all lines: apps/webapp/app/v3/services/stripBackgroundWorkerMetadataForStorage.server.ts +6-1Lines changed: 6 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -7,7 +7,8 @@ import type { Prisma } from "@trigger.dev/database"; | 7 | 7 | * (BackgroundWorker.{contentHash,cliVersion,sdkVersion,runtime,runtimeVersion}, | 8 | 8 | * BackgroundWorkerTask, BackgroundWorkerFile, TaskQueue, Prompt). Today the | 9 | 9 | * 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, | 11 | 12 | * and tasks[].filePath are kept solely to satisfy BackgroundWorkerMetadata's | 12 | 13 | * required fields when the column is parsed back. | 13 | 14 | */ | @@ -24,5 +25,9 @@ export function stripBackgroundWorkerMetadataForStorage( | 24 | 25 | filePath: t.filePath, | 25 | 26 | schedule: t.schedule, | 26 | 27 | })), | | 28 | + webhookEndpoints: metadata.webhookEndpoints as unknown as Prisma.InputJsonValue[] | undefined, | | 29 | + webhookSubscribers: metadata.webhookSubscribers as unknown as | | 30 | + | Prisma.InputJsonValue[] | | 31 | + | undefined, | 27 | 32 | }; | 28 | 33 | } |
Collapse file Copy file name to clipboard +67Lines changed: 67 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 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 | +}); |
|