From 5104bfe2357d0ac76dd6226a8001ebb0bd9114bf Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Mon, 27 Jan 2025 13:56:10 +0000 Subject: [PATCH 1/4] deploy v2 streaming WIP --- ...i.v2.deployments.$deploymentId.finalize.ts | 2 +- ...i.v3.deployments.$deploymentId.finalize.ts | 96 +++++++++++++++++++ ...ntV2.ts => finalizeDeploymentV2.server.ts} | 21 ++-- 3 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 apps/webapp/app/routes/api.v3.deployments.$deploymentId.finalize.ts rename apps/webapp/app/v3/services/{finalizeDeploymentV2.ts => finalizeDeploymentV2.server.ts} (93%) diff --git a/apps/webapp/app/routes/api.v2.deployments.$deploymentId.finalize.ts b/apps/webapp/app/routes/api.v2.deployments.$deploymentId.finalize.ts index b4c0f8ec37..768212ee8d 100644 --- a/apps/webapp/app/routes/api.v2.deployments.$deploymentId.finalize.ts +++ b/apps/webapp/app/routes/api.v2.deployments.$deploymentId.finalize.ts @@ -4,7 +4,7 @@ import { z } from "zod"; import { authenticateApiRequest } from "~/services/apiAuth.server"; import { logger } from "~/services/logger.server"; import { ServiceValidationError } from "~/v3/services/baseService.server"; -import { FinalizeDeploymentV2Service } from "~/v3/services/finalizeDeploymentV2"; +import { FinalizeDeploymentV2Service } from "~/v3/services/finalizeDeploymentV2.server"; const ParamsSchema = z.object({ deploymentId: z.string(), diff --git a/apps/webapp/app/routes/api.v3.deployments.$deploymentId.finalize.ts b/apps/webapp/app/routes/api.v3.deployments.$deploymentId.finalize.ts new file mode 100644 index 0000000000..05e421ad99 --- /dev/null +++ b/apps/webapp/app/routes/api.v3.deployments.$deploymentId.finalize.ts @@ -0,0 +1,96 @@ +import { ActionFunctionArgs, json } from "@remix-run/server-runtime"; +import { FinalizeDeploymentRequestBody } from "@trigger.dev/core/v3"; +import { z } from "zod"; +import { authenticateApiRequest } from "~/services/apiAuth.server"; +import { logger } from "~/services/logger.server"; +import { ServiceValidationError } from "~/v3/services/baseService.server"; +import { FinalizeDeploymentV2Service } from "~/v3/services/finalizeDeploymentV2.server"; + +const ParamsSchema = z.object({ + deploymentId: z.string(), +}); + +export async function action({ request, params }: ActionFunctionArgs) { + // Ensure this is a POST request + if (request.method.toUpperCase() !== "POST") { + return { status: 405, body: "Method Not Allowed" }; + } + + const parsedParams = ParamsSchema.safeParse(params); + + if (!parsedParams.success) { + return json({ error: "Invalid params" }, { status: 400 }); + } + + // Next authenticate the request + const authenticationResult = await authenticateApiRequest(request); + + if (!authenticationResult) { + logger.info("Invalid or missing api key", { url: request.url }); + return json({ error: "Invalid or Missing API key" }, { status: 401 }); + } + + const authenticatedEnv = authenticationResult.environment; + + const { deploymentId } = parsedParams.data; + + const rawBody = await request.json(); + const body = FinalizeDeploymentRequestBody.safeParse(rawBody); + + if (!body.success) { + return json({ error: "Invalid body", issues: body.error.issues }, { status: 400 }); + } + + try { + // Create a text stream chain + const stream = new TransformStream(); + const encoder = new TextEncoderStream(); + const writer = stream.writable.getWriter(); + + const service = new FinalizeDeploymentV2Service(); + + // Chain the streams: stream -> encoder -> response + const response = new Response(stream.readable.pipeThrough(encoder), { + headers: { + "Content-Type": "text/event-stream", + "Cache-Control": "no-cache", + Connection: "keep-alive", + }, + }); + + service + .call(authenticatedEnv, deploymentId, body.data, writer) + .then(async () => { + await writer.write(`event: complete\ndata: ${JSON.stringify({ id: deploymentId })}\n\n`); + await writer.close(); + }) + .catch(async (error) => { + let errorMessage; + + if (error instanceof ServiceValidationError) { + errorMessage = { error: error.message }; + } else if (error instanceof Error) { + logger.error("Error finalizing deployment", { error: error.message }); + errorMessage = { error: `Internal server error: ${error.message}` }; + } else { + logger.error("Error finalizing deployment", { error: String(error) }); + errorMessage = { error: "Internal server error" }; + } + + await writer.write(`event: error\ndata: ${JSON.stringify(errorMessage)}\n\n`); + await writer.close(); + }); + + return response; + } catch (error) { + if (error instanceof ServiceValidationError) { + return json({ error: error.message }, { status: 400 }); + } else if (error instanceof Error) { + logger.error("Error finalizing deployment", { error: error.message }); + return json({ error: `Internal server error: ${error.message}` }, { status: 500 }); + } else { + logger.error("Error finalizing deployment", { error: String(error) }); + return json({ error: "Internal server error" }, { status: 500 }); + } + } +} diff --git a/apps/webapp/app/v3/services/finalizeDeploymentV2.ts b/apps/webapp/app/v3/services/finalizeDeploymentV2.server.ts similarity index 93% rename from apps/webapp/app/v3/services/finalizeDeploymentV2.ts rename to apps/webapp/app/v3/services/finalizeDeploymentV2.server.ts index 0af8ef0f8f..41e98876de 100644 --- a/apps/webapp/app/v3/services/finalizeDeploymentV2.ts +++ b/apps/webapp/app/v3/services/finalizeDeploymentV2.server.ts @@ -13,7 +13,8 @@ export class FinalizeDeploymentV2Service extends BaseService { public async call( authenticatedEnv: AuthenticatedEnvironment, id: string, - body: FinalizeDeploymentRequestBody + body: FinalizeDeploymentRequestBody, + writer?: WritableStreamDefaultWriter ) { // if it's self hosted, lets just use the v1 finalize deployment service if (body.selfHosted) { @@ -148,11 +149,10 @@ type ExecutePushResult = logs: string; }; -async function executePushToRegistry({ - depot, - registry, - deployment, -}: ExecutePushToRegistryOptions): Promise { +async function executePushToRegistry( + { depot, registry, deployment }: ExecutePushToRegistryOptions, + writer?: WritableStreamDefaultWriter +): Promise { // Step 1: We need to "login" to the digital ocean registry const configDir = await ensureLoggedIntoDockerRegistry(registry.host, { username: registry.username, @@ -180,7 +180,7 @@ async function executePushToRegistry({ try { const processCode = await new Promise((res, rej) => { // For some reason everything is output on stderr, not stdout - childProcess.stderr?.on("data", (data: Buffer) => { + childProcess.stderr?.on("data", async (data: Buffer) => { const text = data.toString(); // Emitted data chunks can contain multiple lines. Remove empty lines. @@ -191,6 +191,13 @@ async function executePushToRegistry({ imageTag, deployment, }); + + // Now we can write strings directly + if (writer) { + for (const line of lines) { + await writer.write(`event: log\ndata: ${JSON.stringify({ message: line })}\n\n`); + } + } }); childProcess.on("error", (e) => rej(e)); From ae7f5b40b8e3a94d7c0150a6f83cb2698818c006 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Tue, 4 Feb 2025 11:49:53 +0000 Subject: [PATCH 2/4] finalize deployment now SSE and won't timeout --- ...i.v3.deployments.$deploymentId.finalize.ts | 8 ++ .../v3/services/finalizeDeployment.server.ts | 6 ++ .../services/finalizeDeploymentV2.server.ts | 37 ++++----- packages/cli-v3/src/apiClient.ts | 58 ++++++++++++-- packages/cli-v3/src/commands/deploy.ts | 39 ++++++++-- packages/cli-v3/src/deploy/buildImage.ts | 11 +++ packages/core/package.json | 1 + packages/core/src/v3/apiClient/core.ts | 76 ++++++++++++++++++- pnpm-lock.yaml | 10 +++ 9 files changed, 211 insertions(+), 35 deletions(-) diff --git a/apps/webapp/app/routes/api.v3.deployments.$deploymentId.finalize.ts b/apps/webapp/app/routes/api.v3.deployments.$deploymentId.finalize.ts index 05e421ad99..d6594c2520 100644 --- a/apps/webapp/app/routes/api.v3.deployments.$deploymentId.finalize.ts +++ b/apps/webapp/app/routes/api.v3.deployments.$deploymentId.finalize.ts @@ -58,9 +58,15 @@ export async function action({ request, params }: ActionFunctionArgs) { }, }); + const pingInterval = setInterval(() => { + writer.write("event: ping\ndata: {}\n\n"); + }, 10000); // 10 seconds + service .call(authenticatedEnv, deploymentId, body.data, writer) .then(async () => { + clearInterval(pingInterval); + await writer.write(`event: complete\ndata: ${JSON.stringify({ id: deploymentId })}\n\n`); await writer.close(); }) @@ -77,6 +83,8 @@ export async function action({ request, params }: ActionFunctionArgs) { errorMessage = { error: "Internal server error" }; } + clearInterval(pingInterval); + await writer.write(`event: error\ndata: ${JSON.stringify(errorMessage)}\n\n`); await writer.close(); }); diff --git a/apps/webapp/app/v3/services/finalizeDeployment.server.ts b/apps/webapp/app/v3/services/finalizeDeployment.server.ts index e976d41fb7..58ad8b03dd 100644 --- a/apps/webapp/app/v3/services/finalizeDeployment.server.ts +++ b/apps/webapp/app/v3/services/finalizeDeployment.server.ts @@ -43,6 +43,12 @@ export class FinalizeDeploymentService extends BaseService { throw new ServiceValidationError("Worker deployment does not have a worker"); } + if (deployment.status === "DEPLOYED") { + logger.debug("Worker deployment is already deployed", { id }); + + return deployment; + } + if (deployment.status !== "DEPLOYING") { logger.error("Worker deployment is not in DEPLOYING status", { id }); throw new ServiceValidationError("Worker deployment is not in DEPLOYING status"); diff --git a/apps/webapp/app/v3/services/finalizeDeploymentV2.server.ts b/apps/webapp/app/v3/services/finalizeDeploymentV2.server.ts index 41e98876de..cd235fa844 100644 --- a/apps/webapp/app/v3/services/finalizeDeploymentV2.server.ts +++ b/apps/webapp/app/v3/services/finalizeDeploymentV2.server.ts @@ -84,24 +84,27 @@ export class FinalizeDeploymentV2Service extends BaseService { throw new ServiceValidationError("Missing depot token"); } - const pushResult = await executePushToRegistry({ - depot: { - buildId: externalBuildData.data.buildId, - orgToken: env.DEPOT_TOKEN, - projectId: externalBuildData.data.projectId, - }, - registry: { - host: env.DEPLOY_REGISTRY_HOST, - namespace: env.DEPLOY_REGISTRY_NAMESPACE, - username: env.DEPLOY_REGISTRY_USERNAME, - password: env.DEPLOY_REGISTRY_PASSWORD, - }, - deployment: { - version: deployment.version, - environmentSlug: deployment.environment.slug, - projectExternalRef: deployment.worker.project.externalRef, + const pushResult = await executePushToRegistry( + { + depot: { + buildId: externalBuildData.data.buildId, + orgToken: env.DEPOT_TOKEN, + projectId: externalBuildData.data.projectId, + }, + registry: { + host: env.DEPLOY_REGISTRY_HOST, + namespace: env.DEPLOY_REGISTRY_NAMESPACE, + username: env.DEPLOY_REGISTRY_USERNAME, + password: env.DEPLOY_REGISTRY_PASSWORD, + }, + deployment: { + version: deployment.version, + environmentSlug: deployment.environment.slug, + projectExternalRef: deployment.worker.project.externalRef, + }, }, - }); + writer + ); if (!pushResult.ok) { throw new ServiceValidationError(pushResult.error); diff --git a/packages/cli-v3/src/apiClient.ts b/packages/cli-v3/src/apiClient.ts index a8635d89ea..dba286d21c 100644 --- a/packages/cli-v3/src/apiClient.ts +++ b/packages/cli-v3/src/apiClient.ts @@ -21,7 +21,7 @@ import { FailDeploymentResponseBody, FinalizeDeploymentRequestBody, } from "@trigger.dev/core/v3"; -import { zodfetch, ApiError } from "@trigger.dev/core/v3/zodfetch"; +import { zodfetch, ApiError, zodfetchSSE } from "@trigger.dev/core/v3/zodfetch"; export class CliApiClient { constructor( @@ -247,23 +247,65 @@ export class CliApiClient { ); } - async finalizeDeployment(id: string, body: FinalizeDeploymentRequestBody) { + async finalizeDeployment( + id: string, + body: FinalizeDeploymentRequestBody, + onLog?: (message: string) => void + ): Promise> { if (!this.accessToken) { throw new Error("finalizeDeployment: No access token"); } - return wrapZodFetch( - FailDeploymentResponseBody, - `${this.apiURL}/api/v2/deployments/${id}/finalize`, - { + let resolvePromise: (value: ApiResult) => void; + let rejectPromise: (reason: any) => void; + + const promise = new Promise>((resolve, reject) => { + resolvePromise = resolve; + rejectPromise = reject; + }); + + const source = zodfetchSSE({ + url: `${this.apiURL}/api/v3/deployments/${id}/finalize`, + request: { method: "POST", headers: { Authorization: `Bearer ${this.accessToken}`, "Content-Type": "application/json", }, body: JSON.stringify(body), - } - ); + }, + messages: { + error: z.object({ error: z.string() }), + log: z.object({ message: z.string() }), + complete: FailDeploymentResponseBody, + }, + }); + + source.onMessage("complete", (message) => { + resolvePromise({ + success: true, + data: message, + }); + }); + + source.onMessage("error", ({ error }) => { + rejectPromise({ + success: false, + error, + }); + }); + + if (onLog) { + source.onMessage("log", ({ message }) => { + onLog(message); + }); + } + + const result = await promise; + + source.stop(); + + return result; } async startDeploymentIndexing(deploymentId: string, body: StartDeploymentIndexingRequestBody) { diff --git a/packages/cli-v3/src/commands/deploy.ts b/packages/cli-v3/src/commands/deploy.ts index 6d08a8d555..224e0996d1 100644 --- a/packages/cli-v3/src/commands/deploy.ts +++ b/packages/cli-v3/src/commands/deploy.ts @@ -222,10 +222,10 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { forcedExternals, listener: { onBundleStart() { - $buildSpinner.start("Building project"); + $buildSpinner.start("Building trigger code"); }, onBundleComplete(result) { - $buildSpinner.stop("Successfully built project"); + $buildSpinner.stop("Successfully built code"); logger.debug("Bundle result", result); }, @@ -328,9 +328,9 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { const $spinner = spinner(); if (isLinksSupported) { - $spinner.start(`Deploying version ${version} ${deploymentLink}`); + $spinner.start(`Building version ${version} ${deploymentLink}`); } else { - $spinner.start(`Deploying version ${version}`); + $spinner.start(`Building version ${version}`); } const selfHostedRegistryHost = deployment.registryHost ?? options.registry; @@ -359,6 +359,13 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { compilationPath: destination.path, buildEnvVars: buildManifest.build.env, network: options.network, + onLog: (logMessage) => { + if (isLinksSupported) { + $spinner.message(`Building version ${version} ${deploymentLink}: ${logMessage}`); + } else { + $spinner.message(`Building version ${version}: ${logMessage}`); + } + }, }); logger.debug("Build result", buildResult); @@ -426,10 +433,26 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) { }` : `${buildResult.image}${buildResult.digest ? `@${buildResult.digest}` : ""}`; - const finalizeResponse = await projectClient.client.finalizeDeployment(deployment.id, { - imageReference, - selfHosted: options.selfHosted, - }); + if (isLinksSupported) { + $spinner.message(`Deploying version ${version} ${deploymentLink}`); + } else { + $spinner.message(`Deploying version ${version}`); + } + + const finalizeResponse = await projectClient.client.finalizeDeployment( + deployment.id, + { + imageReference, + selfHosted: options.selfHosted, + }, + (logMessage) => { + if (isLinksSupported) { + $spinner.message(`Deploying version ${version} ${deploymentLink}: ${logMessage}`); + } else { + $spinner.message(`Deploying version ${version}: ${logMessage}`); + } + } + ); if (!finalizeResponse.success) { await failDeploy( diff --git a/packages/cli-v3/src/deploy/buildImage.ts b/packages/cli-v3/src/deploy/buildImage.ts index 412cfcf3fb..d2e8dffd16 100644 --- a/packages/cli-v3/src/deploy/buildImage.ts +++ b/packages/cli-v3/src/deploy/buildImage.ts @@ -36,6 +36,7 @@ export interface BuildImageOptions { apiUrl: string; apiKey: string; buildEnvVars?: Record; + onLog?: (log: string) => void; // Optional deployment spinner deploymentSpinner?: any; // Replace 'any' with the actual type if known @@ -65,6 +66,7 @@ export async function buildImage(options: BuildImageOptions) { apiUrl, apiKey, buildEnvVars, + onLog, } = options; if (selfHosted) { @@ -86,6 +88,7 @@ export async function buildImage(options: BuildImageOptions) { apiKey, buildEnvVars, network: options.network, + onLog, }); } @@ -115,6 +118,7 @@ export async function buildImage(options: BuildImageOptions) { apiUrl, apiKey, buildEnvVars, + onLog, }); } @@ -138,6 +142,7 @@ export interface DepotBuildImageOptions { noCache?: boolean; extraCACerts?: string; buildEnvVars?: Record; + onLog?: (log: string) => void; } type BuildImageSuccess = { @@ -225,6 +230,10 @@ async function depotBuildImage(options: DepotBuildImageOptions): Promise; network?: string; + onLog?: (log: string) => void; } async function selfHostedBuildImage( @@ -336,6 +346,7 @@ async function selfHostedBuildImage( // line will be from stderr/stdout in the order you'd see it in a term errors.push(line); logger.debug(line); + options.onLog?.(line); } if (buildProcess.exitCode !== 0) { diff --git a/packages/core/package.json b/packages/core/package.json index a75262fad0..2b51cb8427 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -197,6 +197,7 @@ "@opentelemetry/sdk-trace-node": "1.25.1", "@opentelemetry/semantic-conventions": "1.25.1", "dequal": "^2.0.3", + "eventsource": "^3.0.5", "eventsource-parser": "^3.0.0", "execa": "^8.0.1", "humanize-duration": "^3.27.3", diff --git a/packages/core/src/v3/apiClient/core.ts b/packages/core/src/v3/apiClient/core.ts index 2660d5b601..98e7a9f907 100644 --- a/packages/core/src/v3/apiClient/core.ts +++ b/packages/core/src/v3/apiClient/core.ts @@ -4,7 +4,7 @@ import { RetryOptions } from "../schemas/index.js"; import { calculateNextRetryDelay } from "../utils/retries.js"; import { ApiConnectionError, ApiError, ApiSchemaValidationError } from "./errors.js"; -import { Attributes, Span, context, propagation } from "@opentelemetry/api"; +import { Attributes, context, propagation, Span } from "@opentelemetry/api"; import { SemanticInternalAttributes } from "../semanticInternalAttributes.js"; import type { TriggerTracer } from "../tracer.js"; import { accessoryAttributes } from "../utils/styleAttributes.js"; @@ -16,7 +16,7 @@ import { OffsetLimitPageParams, OffsetLimitPageResponse, } from "./pagination.js"; -import { TriggerJwtOptions } from "../types/tasks.js"; +import { EventSource } from "eventsource"; export const defaultRetryOptions = { maxAttempts: 3, @@ -616,3 +616,75 @@ function injectPropagationHeadersIfInWorker(requestInit?: RequestInit): RequestI headers: new Headers(headersObject), }; } + +export type ZodFetchSSEMessageValueSchema< + TDiscriminatedUnion extends z.ZodDiscriminatedUnion, +> = z.ZodFirstPartySchemaTypes | TDiscriminatedUnion; + +export interface ZodFetchSSEMessageCatalogSchema { + [key: string]: ZodFetchSSEMessageValueSchema; +} + +export type ZodFetchSSEMessageHandlers = + Partial<{ + [K in keyof TCatalogSchema]: (payload: z.infer) => Promise | void; + }>; + +export type ZodFetchSSEOptions = { + url: string; + request?: RequestInit; + messages: TMessageCatalog; + retry?: RetryOptions; +}; + +export class ZodFetchSSEResult { + private _eventSource: EventSource; + + constructor(private options: ZodFetchSSEOptions) { + this._eventSource = new EventSource(options.url, { + fetch: (input, init) => { + return fetch(input, { + ...init, + ...options.request, + headers: { + ...options.request?.headers, + Accept: "text/event-stream", + }, + }); + }, + }); + } + + public onMessage( + type: T, + handler: ZodFetchSSEMessageHandlers[T] + ) { + this._eventSource.addEventListener(type as string, (event) => { + const payload = safeJsonParse(event.data); + + if (!payload) { + return; + } + + const schema = this.options.messages[type]; + + const result = schema.safeParse(payload); + + if (result.success) { + handler?.(result.data); + } else { + console.error(result.error); + } + }); + } + + public stop() { + this._eventSource.close(); + } +} + +export function zodfetchSSE( + options: ZodFetchSSEOptions +): ZodFetchSSEResult { + return new ZodFetchSSEResult(options); +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0d166c96f4..a56f97b3d2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1331,6 +1331,9 @@ importers: dequal: specifier: ^2.0.3 version: 2.0.3 + eventsource: + specifier: ^3.0.5 + version: 3.0.5 eventsource-parser: specifier: ^3.0.0 version: 3.0.0 @@ -21961,6 +21964,13 @@ packages: resolution: {integrity: sha512-T1C0XCUimhxVQzW4zFipdx0SficT651NnkR0ZSH3yQwh+mFMdLfgjABVi4YtMTtaL4s168593DaoaRLMqryavA==} engines: {node: '>=18.0.0'} + /eventsource@3.0.5: + resolution: {integrity: sha512-LT/5J605bx5SNyE+ITBDiM3FxffBiq9un7Vx0EwMDM3vg8sWKx/tO2zC+LMqZ+smAM0F2hblaDZUVZF0te2pSw==} + engines: {node: '>=18.0.0'} + dependencies: + eventsource-parser: 3.0.0 + dev: false + /evt@2.4.13: resolution: {integrity: sha512-haTVOsmjzk+28zpzvVwan9Zw2rLQF2izgi7BKjAPRzZAfcv+8scL0TpM8MzvGNKFYHiy+Bq3r6FYIIUPl9kt3A==} dependencies: From ea8187d7aef58e4064fd9fb83fa3960228c6350e Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Tue, 4 Feb 2025 12:04:45 +0000 Subject: [PATCH 3/4] handle zodfetchSSE connection errors --- packages/cli-v3/src/apiClient.ts | 7 +++++++ packages/core/src/v3/apiClient/core.ts | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/packages/cli-v3/src/apiClient.ts b/packages/cli-v3/src/apiClient.ts index dba286d21c..e9064c641d 100644 --- a/packages/cli-v3/src/apiClient.ts +++ b/packages/cli-v3/src/apiClient.ts @@ -281,6 +281,13 @@ export class CliApiClient { }, }); + source.onConnectionError((error) => { + rejectPromise({ + success: false, + error, + }); + }); + source.onMessage("complete", (message) => { resolvePromise({ success: true, diff --git a/packages/core/src/v3/apiClient/core.ts b/packages/core/src/v3/apiClient/core.ts index 98e7a9f907..d49c3bbb14 100644 --- a/packages/core/src/v3/apiClient/core.ts +++ b/packages/core/src/v3/apiClient/core.ts @@ -655,6 +655,10 @@ export class ZodFetchSSEResult void) { + this._eventSource.onerror = handler; + } + public onMessage( type: T, handler: ZodFetchSSEMessageHandlers[T] From a412b35e8e446f14318f140530542d3e9c36bd58 Mon Sep 17 00:00:00 2001 From: Eric Allam Date: Tue, 4 Feb 2025 12:11:49 +0000 Subject: [PATCH 4/4] Create tender-cycles-melt.md --- .changeset/tender-cycles-melt.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/tender-cycles-melt.md diff --git a/.changeset/tender-cycles-melt.md b/.changeset/tender-cycles-melt.md new file mode 100644 index 0000000000..21caacbb6b --- /dev/null +++ b/.changeset/tender-cycles-melt.md @@ -0,0 +1,6 @@ +--- +"trigger.dev": patch +"@trigger.dev/core": patch +--- + +Fixed deploy timeout issues and improve the output of logs when deploying