Skip to content

Commit 5104bfe

Browse files
committed
deploy v2 streaming WIP
1 parent 7fa45ad commit 5104bfe

File tree

3 files changed

+111
-8
lines changed

3 files changed

+111
-8
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { z } from "zod";
44
import { authenticateApiRequest } from "~/services/apiAuth.server";
55
import { logger } from "~/services/logger.server";
66
import { ServiceValidationError } from "~/v3/services/baseService.server";
7-
import { FinalizeDeploymentV2Service } from "~/v3/services/finalizeDeploymentV2";
7+
import { FinalizeDeploymentV2Service } from "~/v3/services/finalizeDeploymentV2.server";
88

99
const ParamsSchema = z.object({
1010
deploymentId: z.string(),
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import { ActionFunctionArgs, json } from "@remix-run/server-runtime";
2+
import { FinalizeDeploymentRequestBody } from "@trigger.dev/core/v3";
3+
import { z } from "zod";
4+
import { authenticateApiRequest } from "~/services/apiAuth.server";
5+
import { logger } from "~/services/logger.server";
6+
import { ServiceValidationError } from "~/v3/services/baseService.server";
7+
import { FinalizeDeploymentV2Service } from "~/v3/services/finalizeDeploymentV2.server";
8+
9+
const ParamsSchema = z.object({
10+
deploymentId: z.string(),
11+
});
12+
13+
export async function action({ request, params }: ActionFunctionArgs) {
14+
// Ensure this is a POST request
15+
if (request.method.toUpperCase() !== "POST") {
16+
return { status: 405, body: "Method Not Allowed" };
17+
}
18+
19+
const parsedParams = ParamsSchema.safeParse(params);
20+
21+
if (!parsedParams.success) {
22+
return json({ error: "Invalid params" }, { status: 400 });
23+
}
24+
25+
// Next authenticate the request
26+
const authenticationResult = await authenticateApiRequest(request);
27+
28+
if (!authenticationResult) {
29+
logger.info("Invalid or missing api key", { url: request.url });
30+
return json({ error: "Invalid or Missing API key" }, { status: 401 });
31+
}
32+
33+
const authenticatedEnv = authenticationResult.environment;
34+
35+
const { deploymentId } = parsedParams.data;
36+
37+
const rawBody = await request.json();
38+
const body = FinalizeDeploymentRequestBody.safeParse(rawBody);
39+
40+
if (!body.success) {
41+
return json({ error: "Invalid body", issues: body.error.issues }, { status: 400 });
42+
}
43+
44+
try {
45+
// Create a text stream chain
46+
const stream = new TransformStream();
47+
const encoder = new TextEncoderStream();
48+
const writer = stream.writable.getWriter();
49+
50+
const service = new FinalizeDeploymentV2Service();
51+
52+
// Chain the streams: stream -> encoder -> response
53+
const response = new Response(stream.readable.pipeThrough(encoder), {
54+
headers: {
55+
"Content-Type": "text/event-stream",
56+
"Cache-Control": "no-cache",
57+
Connection: "keep-alive",
58+
},
59+
});
60+
61+
service
62+
.call(authenticatedEnv, deploymentId, body.data, writer)
63+
.then(async () => {
64+
await writer.write(`event: complete\ndata: ${JSON.stringify({ id: deploymentId })}\n\n`);
65+
await writer.close();
66+
})
67+
.catch(async (error) => {
68+
let errorMessage;
69+
70+
if (error instanceof ServiceValidationError) {
71+
errorMessage = { error: error.message };
72+
} else if (error instanceof Error) {
73+
logger.error("Error finalizing deployment", { error: error.message });
74+
errorMessage = { error: `Internal server error: ${error.message}` };
75+
} else {
76+
logger.error("Error finalizing deployment", { error: String(error) });
77+
errorMessage = { error: "Internal server error" };
78+
}
79+
80+
await writer.write(`event: error\ndata: ${JSON.stringify(errorMessage)}\n\n`);
81+
await writer.close();
82+
});
83+
84+
return response;
85+
} catch (error) {
86+
if (error instanceof ServiceValidationError) {
87+
return json({ error: error.message }, { status: 400 });
88+
} else if (error instanceof Error) {
89+
logger.error("Error finalizing deployment", { error: error.message });
90+
return json({ error: `Internal server error: ${error.message}` }, { status: 500 });
91+
} else {
92+
logger.error("Error finalizing deployment", { error: String(error) });
93+
return json({ error: "Internal server error" }, { status: 500 });
94+
}
95+
}
96+
}

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

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ export class FinalizeDeploymentV2Service extends BaseService {
1313
public async call(
1414
authenticatedEnv: AuthenticatedEnvironment,
1515
id: string,
16-
body: FinalizeDeploymentRequestBody
16+
body: FinalizeDeploymentRequestBody,
17+
writer?: WritableStreamDefaultWriter
1718
) {
1819
// if it's self hosted, lets just use the v1 finalize deployment service
1920
if (body.selfHosted) {
@@ -148,11 +149,10 @@ type ExecutePushResult =
148149
logs: string;
149150
};
150151

151-
async function executePushToRegistry({
152-
depot,
153-
registry,
154-
deployment,
155-
}: ExecutePushToRegistryOptions): Promise<ExecutePushResult> {
152+
async function executePushToRegistry(
153+
{ depot, registry, deployment }: ExecutePushToRegistryOptions,
154+
writer?: WritableStreamDefaultWriter
155+
): Promise<ExecutePushResult> {
156156
// Step 1: We need to "login" to the digital ocean registry
157157
const configDir = await ensureLoggedIntoDockerRegistry(registry.host, {
158158
username: registry.username,
@@ -180,7 +180,7 @@ async function executePushToRegistry({
180180
try {
181181
const processCode = await new Promise<number | null>((res, rej) => {
182182
// For some reason everything is output on stderr, not stdout
183-
childProcess.stderr?.on("data", (data: Buffer) => {
183+
childProcess.stderr?.on("data", async (data: Buffer) => {
184184
const text = data.toString();
185185

186186
// Emitted data chunks can contain multiple lines. Remove empty lines.
@@ -191,6 +191,13 @@ async function executePushToRegistry({
191191
imageTag,
192192
deployment,
193193
});
194+
195+
// Now we can write strings directly
196+
if (writer) {
197+
for (const line of lines) {
198+
await writer.write(`event: log\ndata: ${JSON.stringify({ message: line })}\n\n`);
199+
}
200+
}
194201
});
195202

196203
childProcess.on("error", (e) => rej(e));

0 commit comments

Comments
 (0)