From fc5eb2068a21244a4fb48e8b9f9938a16bd573f4 Mon Sep 17 00:00:00 2001 From: myftija Date: Tue, 23 Sep 2025 20:28:41 +0200 Subject: [PATCH 1/5] Add installing status to the deployment db schema --- .../migration.sql | 3 +++ internal-packages/database/prisma/schema.prisma | 8 +++++--- 2 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 internal-packages/database/prisma/migrations/20250923182708_add_installing_status_to_deployments/migration.sql diff --git a/internal-packages/database/prisma/migrations/20250923182708_add_installing_status_to_deployments/migration.sql b/internal-packages/database/prisma/migrations/20250923182708_add_installing_status_to_deployments/migration.sql new file mode 100644 index 0000000000..705623a933 --- /dev/null +++ b/internal-packages/database/prisma/migrations/20250923182708_add_installing_status_to_deployments/migration.sql @@ -0,0 +1,3 @@ +ALTER TYPE "public"."WorkerDeploymentStatus" ADD VALUE 'INSTALLING'; + +ALTER TABLE "public"."WorkerDeployment" ADD COLUMN "installedAt" TIMESTAMP(3); \ No newline at end of file diff --git a/internal-packages/database/prisma/schema.prisma b/internal-packages/database/prisma/schema.prisma index 041a7a0cc4..c3c26ba507 100644 --- a/internal-packages/database/prisma/schema.prisma +++ b/internal-packages/database/prisma/schema.prisma @@ -1764,9 +1764,10 @@ model WorkerDeployment { triggeredBy User? @relation(fields: [triggeredById], references: [id], onDelete: SetNull, onUpdate: Cascade) triggeredById String? - startedAt DateTime? - builtAt DateTime? - deployedAt DateTime? + startedAt DateTime? + installedAt DateTime? + builtAt DateTime? + deployedAt DateTime? failedAt DateTime? errorData Json? @@ -1787,6 +1788,7 @@ model WorkerDeployment { enum WorkerDeploymentStatus { PENDING + INSTALLING /// This is the status when the image is being built BUILDING /// This is the status when the image is built and we are waiting for the indexing to finish From 2d277e6ae4d491fc3fc9b045228e27696aa3b927 Mon Sep 17 00:00:00 2001 From: myftija Date: Tue, 23 Sep 2025 20:58:04 +0200 Subject: [PATCH 2/5] Replace the deployments /start endpoint with /progress --- ....v1.deployments.$deploymentId.progress.ts} | 13 ++- .../app/v3/services/deployment.server.ts | 98 +++++++++++++------ packages/core/src/v3/schemas/api.ts | 4 +- 3 files changed, 79 insertions(+), 36 deletions(-) rename apps/webapp/app/routes/{api.v1.deployments.$deploymentId.start.ts => api.v1.deployments.$deploymentId.progress.ts} (84%) diff --git a/apps/webapp/app/routes/api.v1.deployments.$deploymentId.start.ts b/apps/webapp/app/routes/api.v1.deployments.$deploymentId.progress.ts similarity index 84% rename from apps/webapp/app/routes/api.v1.deployments.$deploymentId.start.ts rename to apps/webapp/app/routes/api.v1.deployments.$deploymentId.progress.ts index 1272a8418d..d07d9a2f3c 100644 --- a/apps/webapp/app/routes/api.v1.deployments.$deploymentId.start.ts +++ b/apps/webapp/app/routes/api.v1.deployments.$deploymentId.progress.ts @@ -1,5 +1,5 @@ import { type ActionFunctionArgs, json } from "@remix-run/server-runtime"; -import { StartDeploymentRequestBody } from "@trigger.dev/core/v3"; +import { ProgressDeploymentRequestBody } from "@trigger.dev/core/v3"; import { z } from "zod"; import { authenticateRequest } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; @@ -35,7 +35,7 @@ export async function action({ request, params }: ActionFunctionArgs) { const { deploymentId } = parsedParams.data; const rawBody = await request.json(); - const body = StartDeploymentRequestBody.safeParse(rawBody); + const body = ProgressDeploymentRequestBody.safeParse(rawBody); if (!body.success) { return json({ error: "Invalid request body", issues: body.error.issues }, { status: 400 }); @@ -44,7 +44,7 @@ export async function action({ request, params }: ActionFunctionArgs) { const deploymentService = new DeploymentService(); return await deploymentService - .startDeployment(authenticatedEnv, deploymentId, { + .progressDeployment(authenticatedEnv, deploymentId, { contentHash: body.data.contentHash, git: body.data.gitMeta, runtime: body.data.runtime, @@ -59,8 +59,11 @@ export async function action({ request, params }: ActionFunctionArgs) { return new Response(null, { status: 204 }); // ignore these errors for now case "deployment_not_found": return json({ error: "Deployment not found" }, { status: 404 }); - case "deployment_not_pending": - return json({ error: "Deployment is not pending" }, { status: 409 }); + case "deployment_cannot_be_progressed": + return json( + { error: "Deployment is not in a progressable state (PENDING or INSTALLING)" }, + { status: 409 } + ); case "failed_to_create_remote_build": return json({ error: "Failed to create remote build" }, { status: 500 }); case "other": diff --git a/apps/webapp/app/v3/services/deployment.server.ts b/apps/webapp/app/v3/services/deployment.server.ts index 495ea91496..d3411d9f8f 100644 --- a/apps/webapp/app/v3/services/deployment.server.ts +++ b/apps/webapp/app/v3/services/deployment.server.ts @@ -8,7 +8,20 @@ import { env } from "~/env.server"; import { createRemoteImageBuild } from "../remoteImageBuilder.server"; export class DeploymentService extends BaseService { - public startDeployment( + /** + * Progresses a deployment from PENDING to INSTALLING and then to BUILDING. + * Also extends the deployment timeout. + * + * When progressing to BUILDING, the remote Depot build is also created. + * + * Only acts when the current status allows. Not idempotent. + * + * @param authenticatedEnv The environment which the deployment belongs to. + * @param friendlyId The friendly deployment ID. + * @param updates Optional deployment details to persist. + */ + + public progressDeployment( authenticatedEnv: AuthenticatedEnvironment, friendlyId: string, updates: Partial & { git: GitMeta }> @@ -37,37 +50,26 @@ export class DeploymentService extends BaseService { }); const validateDeployment = (deployment: Pick) => { - if (deployment.status !== "PENDING") { - logger.warn("Attempted starting deployment that is not in PENDING status", { - deployment, - }); - return errAsync({ type: "deployment_not_pending" as const }); + if (deployment.status !== "PENDING" && deployment.status !== "INSTALLING") { + logger.warn( + "Attempted progressing deployment that is not in PENDING or INSTALLING status", + { + deployment, + } + ); + return errAsync({ type: "deployment_cannot_be_progressed" as const }); } return okAsync(deployment); }; - const createRemoteBuild = (deployment: Pick) => - fromPromise(createRemoteImageBuild(authenticatedEnv.project), (error) => ({ - type: "failed_to_create_remote_build" as const, - cause: error, - })).map((build) => ({ - id: deployment.id, - externalBuildData: build, - })); - - const updateDeployment = ( - deployment: Pick & { - externalBuildData: ExternalBuildData | undefined; - } - ) => + const progressToInstalling = (deployment: Pick) => fromPromise( this._prisma.workerDeployment.updateMany({ where: { id: deployment.id, status: "PENDING" }, // status could've changed in the meantime, we're not locking the row data: { ...updates, - externalBuildData: deployment.externalBuildData, - status: "BUILDING", + status: "INSTALLING", startedAt: new Date(), }, }), @@ -77,17 +79,51 @@ export class DeploymentService extends BaseService { }) ).andThen((result) => { if (result.count === 0) { - return errAsync({ type: "deployment_not_pending" as const }); + return errAsync({ type: "deployment_cannot_be_progressed" as const }); } - return okAsync({ id: deployment.id }); + return okAsync({ id: deployment.id, status: "INSTALLING" as const }); }); - const extendTimeout = (deployment: Pick) => + const createRemoteBuild = (deployment: Pick) => + fromPromise(createRemoteImageBuild(authenticatedEnv.project), (error) => ({ + type: "failed_to_create_remote_build" as const, + cause: error, + })); + + const progressToBuilding = (deployment: Pick) => + createRemoteBuild(deployment) + .andThen((externalBuildData) => + fromPromise( + this._prisma.workerDeployment.updateMany({ + where: { id: deployment.id, status: "INSTALLING" }, // status could've changed in the meantime, we're not locking the row + data: { + ...updates, + externalBuildData, + status: "BUILDING", + installedAt: new Date(), + }, + }), + (error) => ({ + type: "other" as const, + cause: error, + }) + ) + ) + .andThen((result) => { + if (result.count === 0) { + return errAsync({ type: "deployment_cannot_be_progressed" as const }); + } + return okAsync({ id: deployment.id, status: "BUILDING" as const }); + }); + + const extendTimeout = (deployment: Pick) => fromPromise( TimeoutDeploymentService.enqueue( deployment.id, - "BUILDING" satisfies WorkerDeploymentStatus, - "Building timed out", + deployment.status, + deployment.status === "INSTALLING" + ? "Installing dependencies timed out" + : "Building timed out", new Date(Date.now() + env.DEPLOY_TIMEOUT_MS) ), (error) => ({ @@ -98,8 +134,12 @@ export class DeploymentService extends BaseService { return getDeployment() .andThen(validateDeployment) - .andThen(createRemoteBuild) - .andThen(updateDeployment) + .andThen((deployment) => { + if (deployment.status === "PENDING") { + return progressToInstalling(deployment); + } + return progressToBuilding(deployment); + }) .andThen(extendTimeout) .map(() => undefined); } diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 7bfd4e23d3..4fdea4a5e8 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -377,13 +377,13 @@ export const FinalizeDeploymentRequestBody = z.object({ export type FinalizeDeploymentRequestBody = z.infer; -export const StartDeploymentRequestBody = z.object({ +export const ProgressDeploymentRequestBody = z.object({ contentHash: z.string().optional(), gitMeta: GitMeta.optional(), runtime: z.string().optional(), }); -export type StartDeploymentRequestBody = z.infer; +export type ProgressDeploymentRequestBody = z.infer; export const ExternalBuildData = z.object({ buildId: z.string(), From 4569f01d320af469df31b92b9b2789d3677001ba Mon Sep 17 00:00:00 2001 From: myftija Date: Tue, 23 Sep 2025 21:06:17 +0200 Subject: [PATCH 3/5] Show the installing status in the dashboard --- apps/webapp/app/components/runs/v3/DeploymentStatus.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apps/webapp/app/components/runs/v3/DeploymentStatus.tsx b/apps/webapp/app/components/runs/v3/DeploymentStatus.tsx index 5f5f3e0177..1eae4c548a 100644 --- a/apps/webapp/app/components/runs/v3/DeploymentStatus.tsx +++ b/apps/webapp/app/components/runs/v3/DeploymentStatus.tsx @@ -53,6 +53,7 @@ export function DeploymentStatusIcon({ return ( ); + case "INSTALLING": case "BUILDING": case "DEPLOYING": return ; @@ -78,6 +79,7 @@ export function deploymentStatusClassNameColor(status: WorkerDeploymentStatus): switch (status) { case "PENDING": return "text-charcoal-500"; + case "INSTALLING": case "BUILDING": case "DEPLOYING": return "text-pending"; @@ -98,6 +100,8 @@ export function deploymentStatusTitle(status: WorkerDeploymentStatus, isBuilt: b switch (status) { case "PENDING": return "Queued…"; + case "INSTALLING": + return "Installing…"; case "BUILDING": return "Building…"; case "DEPLOYING": @@ -127,6 +131,7 @@ export function deploymentStatusTitle(status: WorkerDeploymentStatus, isBuilt: b // PENDING and CANCELED are not used so are ommited from the UI export const deploymentStatuses: WorkerDeploymentStatus[] = [ "PENDING", + "INSTALLING", "BUILDING", "DEPLOYING", "DEPLOYED", @@ -138,6 +143,8 @@ export function deploymentStatusDescription(status: WorkerDeploymentStatus): str switch (status) { case "PENDING": return "The deployment is queued and waiting to be processed."; + case "INSTALLING": + return "The project dependencies are being installed."; case "BUILDING": return "The code is being built and prepared for deployment."; case "DEPLOYING": From c8b327bd09c5577a10c6309dee43d68a3d5a5de2 Mon Sep 17 00:00:00 2001 From: myftija Date: Tue, 23 Sep 2025 21:15:12 +0200 Subject: [PATCH 4/5] Add installing status to the api schema and cli --- packages/cli-v3/src/commands/deploy.ts | 1 + packages/core/src/v3/schemas/api.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index ce55379267..d8c27a7917 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -676,6 +676,7 @@ async function failDeploy( switch (serverDeployment.status) { case "PENDING": + case "INSTALLING": case "DEPLOYING": case "BUILDING": { await doOutputLogs(); diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index 4fdea4a5e8..1a6242b0c1 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -465,6 +465,7 @@ export const GetDeploymentResponseBody = z.object({ id: z.string(), status: z.enum([ "PENDING", + "INSTALLING", "BUILDING", "DEPLOYING", "DEPLOYED", From 303986ecff02791b733d488d5ddf8e80d2a8b7c8 Mon Sep 17 00:00:00 2001 From: myftija Date: Tue, 23 Sep 2025 21:16:44 +0200 Subject: [PATCH 5/5] Add changeset --- .changeset/kind-kids-teach.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/kind-kids-teach.md diff --git a/.changeset/kind-kids-teach.md b/.changeset/kind-kids-teach.md new file mode 100644 index 0000000000..65e19fbe96 --- /dev/null +++ b/.changeset/kind-kids-teach.md @@ -0,0 +1,6 @@ +--- +"trigger.dev": patch +"@trigger.dev/core": patch +--- + +Added INSTALLING status to the deployment status enum.