Skip to content

Commit ae7f5b4

Browse files
committed
finalize deployment now SSE and won't timeout
1 parent 5104bfe commit ae7f5b4

File tree

9 files changed

+211
-35
lines changed

9 files changed

+211
-35
lines changed

apps/webapp/app/routes/api.v3.deployments.$deploymentId.finalize.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,15 @@ export async function action({ request, params }: ActionFunctionArgs) {
5858
},
5959
});
6060

61+
const pingInterval = setInterval(() => {
62+
writer.write("event: ping\ndata: {}\n\n");
63+
}, 10000); // 10 seconds
64+
6165
service
6266
.call(authenticatedEnv, deploymentId, body.data, writer)
6367
.then(async () => {
68+
clearInterval(pingInterval);
69+
6470
await writer.write(`event: complete\ndata: ${JSON.stringify({ id: deploymentId })}\n\n`);
6571
await writer.close();
6672
})
@@ -77,6 +83,8 @@ export async function action({ request, params }: ActionFunctionArgs) {
7783
errorMessage = { error: "Internal server error" };
7884
}
7985

86+
clearInterval(pingInterval);
87+
8088
await writer.write(`event: error\ndata: ${JSON.stringify(errorMessage)}\n\n`);
8189
await writer.close();
8290
});

apps/webapp/app/v3/services/finalizeDeployment.server.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ export class FinalizeDeploymentService extends BaseService {
4343
throw new ServiceValidationError("Worker deployment does not have a worker");
4444
}
4545

46+
if (deployment.status === "DEPLOYED") {
47+
logger.debug("Worker deployment is already deployed", { id });
48+
49+
return deployment;
50+
}
51+
4652
if (deployment.status !== "DEPLOYING") {
4753
logger.error("Worker deployment is not in DEPLOYING status", { id });
4854
throw new ServiceValidationError("Worker deployment is not in DEPLOYING status");

apps/webapp/app/v3/services/finalizeDeploymentV2.server.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -84,24 +84,27 @@ export class FinalizeDeploymentV2Service extends BaseService {
8484
throw new ServiceValidationError("Missing depot token");
8585
}
8686

87-
const pushResult = await executePushToRegistry({
88-
depot: {
89-
buildId: externalBuildData.data.buildId,
90-
orgToken: env.DEPOT_TOKEN,
91-
projectId: externalBuildData.data.projectId,
92-
},
93-
registry: {
94-
host: env.DEPLOY_REGISTRY_HOST,
95-
namespace: env.DEPLOY_REGISTRY_NAMESPACE,
96-
username: env.DEPLOY_REGISTRY_USERNAME,
97-
password: env.DEPLOY_REGISTRY_PASSWORD,
98-
},
99-
deployment: {
100-
version: deployment.version,
101-
environmentSlug: deployment.environment.slug,
102-
projectExternalRef: deployment.worker.project.externalRef,
87+
const pushResult = await executePushToRegistry(
88+
{
89+
depot: {
90+
buildId: externalBuildData.data.buildId,
91+
orgToken: env.DEPOT_TOKEN,
92+
projectId: externalBuildData.data.projectId,
93+
},
94+
registry: {
95+
host: env.DEPLOY_REGISTRY_HOST,
96+
namespace: env.DEPLOY_REGISTRY_NAMESPACE,
97+
username: env.DEPLOY_REGISTRY_USERNAME,
98+
password: env.DEPLOY_REGISTRY_PASSWORD,
99+
},
100+
deployment: {
101+
version: deployment.version,
102+
environmentSlug: deployment.environment.slug,
103+
projectExternalRef: deployment.worker.project.externalRef,
104+
},
103105
},
104-
});
106+
writer
107+
);
105108

106109
if (!pushResult.ok) {
107110
throw new ServiceValidationError(pushResult.error);

packages/cli-v3/src/apiClient.ts

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
FailDeploymentResponseBody,
2222
FinalizeDeploymentRequestBody,
2323
} from "@trigger.dev/core/v3";
24-
import { zodfetch, ApiError } from "@trigger.dev/core/v3/zodfetch";
24+
import { zodfetch, ApiError, zodfetchSSE } from "@trigger.dev/core/v3/zodfetch";
2525

2626
export class CliApiClient {
2727
constructor(
@@ -247,23 +247,65 @@ export class CliApiClient {
247247
);
248248
}
249249

250-
async finalizeDeployment(id: string, body: FinalizeDeploymentRequestBody) {
250+
async finalizeDeployment(
251+
id: string,
252+
body: FinalizeDeploymentRequestBody,
253+
onLog?: (message: string) => void
254+
): Promise<ApiResult<FailDeploymentResponseBody>> {
251255
if (!this.accessToken) {
252256
throw new Error("finalizeDeployment: No access token");
253257
}
254258

255-
return wrapZodFetch(
256-
FailDeploymentResponseBody,
257-
`${this.apiURL}/api/v2/deployments/${id}/finalize`,
258-
{
259+
let resolvePromise: (value: ApiResult<FailDeploymentResponseBody>) => void;
260+
let rejectPromise: (reason: any) => void;
261+
262+
const promise = new Promise<ApiResult<FailDeploymentResponseBody>>((resolve, reject) => {
263+
resolvePromise = resolve;
264+
rejectPromise = reject;
265+
});
266+
267+
const source = zodfetchSSE({
268+
url: `${this.apiURL}/api/v3/deployments/${id}/finalize`,
269+
request: {
259270
method: "POST",
260271
headers: {
261272
Authorization: `Bearer ${this.accessToken}`,
262273
"Content-Type": "application/json",
263274
},
264275
body: JSON.stringify(body),
265-
}
266-
);
276+
},
277+
messages: {
278+
error: z.object({ error: z.string() }),
279+
log: z.object({ message: z.string() }),
280+
complete: FailDeploymentResponseBody,
281+
},
282+
});
283+
284+
source.onMessage("complete", (message) => {
285+
resolvePromise({
286+
success: true,
287+
data: message,
288+
});
289+
});
290+
291+
source.onMessage("error", ({ error }) => {
292+
rejectPromise({
293+
success: false,
294+
error,
295+
});
296+
});
297+
298+
if (onLog) {
299+
source.onMessage("log", ({ message }) => {
300+
onLog(message);
301+
});
302+
}
303+
304+
const result = await promise;
305+
306+
source.stop();
307+
308+
return result;
267309
}
268310

269311
async startDeploymentIndexing(deploymentId: string, body: StartDeploymentIndexingRequestBody) {

packages/cli-v3/src/commands/deploy.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -222,10 +222,10 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
222222
forcedExternals,
223223
listener: {
224224
onBundleStart() {
225-
$buildSpinner.start("Building project");
225+
$buildSpinner.start("Building trigger code");
226226
},
227227
onBundleComplete(result) {
228-
$buildSpinner.stop("Successfully built project");
228+
$buildSpinner.stop("Successfully built code");
229229

230230
logger.debug("Bundle result", result);
231231
},
@@ -328,9 +328,9 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
328328
const $spinner = spinner();
329329

330330
if (isLinksSupported) {
331-
$spinner.start(`Deploying version ${version} ${deploymentLink}`);
331+
$spinner.start(`Building version ${version} ${deploymentLink}`);
332332
} else {
333-
$spinner.start(`Deploying version ${version}`);
333+
$spinner.start(`Building version ${version}`);
334334
}
335335

336336
const selfHostedRegistryHost = deployment.registryHost ?? options.registry;
@@ -359,6 +359,13 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
359359
compilationPath: destination.path,
360360
buildEnvVars: buildManifest.build.env,
361361
network: options.network,
362+
onLog: (logMessage) => {
363+
if (isLinksSupported) {
364+
$spinner.message(`Building version ${version} ${deploymentLink}: ${logMessage}`);
365+
} else {
366+
$spinner.message(`Building version ${version}: ${logMessage}`);
367+
}
368+
},
362369
});
363370

364371
logger.debug("Build result", buildResult);
@@ -426,10 +433,26 @@ async function _deployCommand(dir: string, options: DeployCommandOptions) {
426433
}`
427434
: `${buildResult.image}${buildResult.digest ? `@${buildResult.digest}` : ""}`;
428435

429-
const finalizeResponse = await projectClient.client.finalizeDeployment(deployment.id, {
430-
imageReference,
431-
selfHosted: options.selfHosted,
432-
});
436+
if (isLinksSupported) {
437+
$spinner.message(`Deploying version ${version} ${deploymentLink}`);
438+
} else {
439+
$spinner.message(`Deploying version ${version}`);
440+
}
441+
442+
const finalizeResponse = await projectClient.client.finalizeDeployment(
443+
deployment.id,
444+
{
445+
imageReference,
446+
selfHosted: options.selfHosted,
447+
},
448+
(logMessage) => {
449+
if (isLinksSupported) {
450+
$spinner.message(`Deploying version ${version} ${deploymentLink}: ${logMessage}`);
451+
} else {
452+
$spinner.message(`Deploying version ${version}: ${logMessage}`);
453+
}
454+
}
455+
);
433456

434457
if (!finalizeResponse.success) {
435458
await failDeploy(

packages/cli-v3/src/deploy/buildImage.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface BuildImageOptions {
3636
apiUrl: string;
3737
apiKey: string;
3838
buildEnvVars?: Record<string, string | undefined>;
39+
onLog?: (log: string) => void;
3940

4041
// Optional deployment spinner
4142
deploymentSpinner?: any; // Replace 'any' with the actual type if known
@@ -65,6 +66,7 @@ export async function buildImage(options: BuildImageOptions) {
6566
apiUrl,
6667
apiKey,
6768
buildEnvVars,
69+
onLog,
6870
} = options;
6971

7072
if (selfHosted) {
@@ -86,6 +88,7 @@ export async function buildImage(options: BuildImageOptions) {
8688
apiKey,
8789
buildEnvVars,
8890
network: options.network,
91+
onLog,
8992
});
9093
}
9194

@@ -115,6 +118,7 @@ export async function buildImage(options: BuildImageOptions) {
115118
apiUrl,
116119
apiKey,
117120
buildEnvVars,
121+
onLog,
118122
});
119123
}
120124

@@ -138,6 +142,7 @@ export interface DepotBuildImageOptions {
138142
noCache?: boolean;
139143
extraCACerts?: string;
140144
buildEnvVars?: Record<string, string | undefined>;
145+
onLog?: (log: string) => void;
141146
}
142147

143148
type BuildImageSuccess = {
@@ -225,6 +230,10 @@ async function depotBuildImage(options: DepotBuildImageOptions): Promise<BuildIm
225230
// Emitted data chunks can contain multiple lines. Remove empty lines.
226231
const lines = text.split("\n").filter(Boolean);
227232

233+
for (const line of lines) {
234+
options.onLog?.(line);
235+
}
236+
228237
errors.push(...lines);
229238
logger.debug(text);
230239
});
@@ -278,6 +287,7 @@ interface SelfHostedBuildImageOptions {
278287
extraCACerts?: string;
279288
buildEnvVars?: Record<string, string | undefined>;
280289
network?: string;
290+
onLog?: (log: string) => void;
281291
}
282292

283293
async function selfHostedBuildImage(
@@ -336,6 +346,7 @@ async function selfHostedBuildImage(
336346
// line will be from stderr/stdout in the order you'd see it in a term
337347
errors.push(line);
338348
logger.debug(line);
349+
options.onLog?.(line);
339350
}
340351

341352
if (buildProcess.exitCode !== 0) {

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@
197197
"@opentelemetry/sdk-trace-node": "1.25.1",
198198
"@opentelemetry/semantic-conventions": "1.25.1",
199199
"dequal": "^2.0.3",
200+
"eventsource": "^3.0.5",
200201
"eventsource-parser": "^3.0.0",
201202
"execa": "^8.0.1",
202203
"humanize-duration": "^3.27.3",

0 commit comments

Comments
 (0)