From 4d0921e5bddd8b0ce57a4589120124aebb604dee Mon Sep 17 00:00:00 2001 From: myftija Date: Mon, 15 Sep 2025 10:48:50 +0200 Subject: [PATCH 1/4] Add external build data and image platform to the get deployment endpoint --- .../app/routes/api.v1.deployments.$deploymentId.ts | 10 +++++++--- packages/core/src/v3/schemas/api.ts | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/apps/webapp/app/routes/api.v1.deployments.$deploymentId.ts b/apps/webapp/app/routes/api.v1.deployments.$deploymentId.ts index 643b1f8a13..ca3417b75b 100644 --- a/apps/webapp/app/routes/api.v1.deployments.$deploymentId.ts +++ b/apps/webapp/app/routes/api.v1.deployments.$deploymentId.ts @@ -1,4 +1,5 @@ -import { LoaderFunctionArgs, json } from "@remix-run/server-runtime"; +import { type LoaderFunctionArgs, json } from "@remix-run/server-runtime"; +import { type GetDeploymentResponseBody } from "@trigger.dev/core/v3"; import { z } from "zod"; import { prisma } from "~/db.server"; import { authenticateApiRequest } from "~/services/apiAuth.server"; @@ -52,7 +53,10 @@ export async function loader({ request, params }: LoaderFunctionArgs) { shortCode: deployment.shortCode, version: deployment.version, imageReference: deployment.imageReference, - errorData: deployment.errorData, + imagePlatform: deployment.imagePlatform, + externalBuildData: + deployment.externalBuildData as GetDeploymentResponseBody["externalBuildData"], + errorData: deployment.errorData as GetDeploymentResponseBody["errorData"], worker: deployment.worker ? { id: deployment.worker.friendlyId, @@ -65,5 +69,5 @@ export async function loader({ request, params }: LoaderFunctionArgs) { })), } : undefined, - }); + } satisfies GetDeploymentResponseBody); } diff --git a/packages/core/src/v3/schemas/api.ts b/packages/core/src/v3/schemas/api.ts index da9f776568..e4c71bb341 100644 --- a/packages/core/src/v3/schemas/api.ts +++ b/packages/core/src/v3/schemas/api.ts @@ -467,6 +467,8 @@ export const GetDeploymentResponseBody = z.object({ shortCode: z.string(), version: z.string(), imageReference: z.string().nullish(), + imagePlatform: z.string(), + externalBuildData: ExternalBuildData.optional().nullable(), errorData: DeploymentErrorData.nullish(), worker: z .object({ From 513dc6684e49ada04fc14b84cb43e0ef0d3426a9 Mon Sep 17 00:00:00 2001 From: myftija Date: Mon, 15 Sep 2025 10:59:22 +0200 Subject: [PATCH 2/4] If provided, attach to an existing deployment in the deploy command --- packages/cli-v3/src/commands/deploy.ts | 76 +++++++++++++++++++++----- 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 75acc421cd..88e3517275 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -1,6 +1,9 @@ import { intro, log, outro } from "@clack/prompts"; import { getBranch, prepareDeploymentError, tryCatch } from "@trigger.dev/core/v3"; -import { InitializeDeploymentResponseBody } from "@trigger.dev/core/v3/schemas"; +import { + InitializeDeploymentRequestBody, + InitializeDeploymentResponseBody, +} from "@trigger.dev/core/v3/schemas"; import { Command, Option as CommandOption } from "commander"; import { resolve } from "node:path"; import { isCI } from "std-env"; @@ -306,19 +309,17 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { return; } - const deploymentResponse = await projectClient.client.initializeDeployment({ - contentHash: buildManifest.contentHash, - userId: authorization.auth.tokenType === "personal" ? authorization.userId : undefined, - gitMeta, - type: features.run_engine_v2 ? "MANAGED" : "V1", - runtime: buildManifest.runtime, - }); - - if (!deploymentResponse.success) { - throw new Error(`Failed to start deployment: ${deploymentResponse.error}`); - } - - const deployment = deploymentResponse.data; + const deployment = await initializeOrAttachDeployment( + projectClient.client, + { + contentHash: buildManifest.contentHash, + userId: authorization.auth.tokenType === "personal" ? authorization.userId : undefined, + gitMeta, + type: features.run_engine_v2 ? "MANAGED" : "V1", + runtime: buildManifest.runtime, + }, + envVars.TRIGGER_EXISTING_DEPLOYMENT_ID + ); const isLocalBuild = !deployment.externalBuildData; // Fail fast if we know local builds will fail @@ -619,7 +620,7 @@ export async function syncEnvVarsWithServer( async function failDeploy( client: CliApiClient, - deployment: Deployment, + deployment: Pick, error: { name: string; message: string }, logs: string, $spinner: ReturnType, @@ -735,6 +736,51 @@ async function failDeploy( } } +async function initializeOrAttachDeployment( + apiClient: CliApiClient, + data: InitializeDeploymentRequestBody, + existingDeploymentId?: string +): Promise { + if (existingDeploymentId) { + // In the build server we initialize the deployment before installing the project dependencies, + // so that the status is correctly reflected in the dashboard. In this case, we need to attach + // to the existing deployment and continue with the remote build process. + // This is a workaround to avoid major changes in the deploy command and workflow. In the future, + // we'll likely make the build server the entry point of the flow for building and deploying and also + // adapt the related deployment API endpoints. + + const existingDeploymentOrError = await apiClient.getDeployment(existingDeploymentId); + + if (!existingDeploymentOrError.success) { + throw new Error( + `Failed to attach to existing deployment: ${existingDeploymentOrError.error}` + ); + } + + const { imageReference } = existingDeploymentOrError.data; + if (!imageReference) { + // this is just an artifact of our current DB schema + // `imageReference` is stored as nullable, but it should always exist + throw new Error("Existing deployment does not have an image reference"); + } + + return { + ...existingDeploymentOrError.data, + imageTag: imageReference, + }; + } + + const newDeploymentOrError = await apiClient.initializeDeployment({ + ...data, + }); + + if (!newDeploymentOrError.success) { + throw new Error(`Failed to start deployment: ${newDeploymentOrError.error}`); + } + + return newDeploymentOrError.data; +} + export function verifyDirectory(dir: string, projectPath: string) { if (dir !== "." && !isDirectory(projectPath)) { if (dir === "staging" || dir === "prod" || dir === "preview") { From 47ba59977ae464087ec51800a5116b1b45ae55bd Mon Sep 17 00:00:00 2001 From: myftija Date: Mon, 15 Sep 2025 11:15:01 +0200 Subject: [PATCH 3/4] Check status for existing deployments --- packages/cli-v3/src/commands/deploy.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 88e3517275..ce55379267 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -757,13 +757,22 @@ async function initializeOrAttachDeployment( ); } - const { imageReference } = existingDeploymentOrError.data; + const { imageReference, status } = existingDeploymentOrError.data; if (!imageReference) { // this is just an artifact of our current DB schema // `imageReference` is stored as nullable, but it should always exist throw new Error("Existing deployment does not have an image reference"); } + if ( + status === "CANCELED" || + status === "FAILED" || + status === "TIMED_OUT" || + status === "DEPLOYED" + ) { + throw new Error(`Existing deployment is in an unexpected state: ${status}`); + } + return { ...existingDeploymentOrError.data, imageTag: imageReference, From 3d564cd10c4ef9e9637f61244b3192b91292a967 Mon Sep 17 00:00:00 2001 From: myftija Date: Mon, 15 Sep 2025 12:47:05 +0200 Subject: [PATCH 4/4] Add changeset --- .changeset/seven-taxis-glow.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/seven-taxis-glow.md diff --git a/.changeset/seven-taxis-glow.md b/.changeset/seven-taxis-glow.md new file mode 100644 index 0000000000..b79f977326 --- /dev/null +++ b/.changeset/seven-taxis-glow.md @@ -0,0 +1,6 @@ +--- +"trigger.dev": patch +"@trigger.dev/core": patch +--- + +Attach to existing deployment for deployments triggered in the build server. If `TRIGGER_EXISTING_DEPLOYMENT_ID` env var is set, the `deploy` command now skips the deployment initialization.