From 04e085456c45889b0ec402b90589be035e04b3ad Mon Sep 17 00:00:00 2001 From: jordiori Date: Mon, 1 Jun 2026 11:47:03 +0200 Subject: [PATCH 1/2] do not delete deployment on check --- src/api/deploy.test.ts | 66 ++++++++++++++++++++++++++++++++++++++++++ src/api/deploy.ts | 61 +++++++++++++++++++------------------- 2 files changed, 98 insertions(+), 29 deletions(-) diff --git a/src/api/deploy.test.ts b/src/api/deploy.test.ts index 14c53ce..816d157 100644 --- a/src/api/deploy.test.ts +++ b/src/api/deploy.test.ts @@ -255,6 +255,72 @@ describe("Deploy API", () => { expect(parsed.searchParams.get("allow_destructive_operations")).toBe("true"); }); + it("skips stale deployment cleanup in check mode", async () => { + let listed = false; + const deletedIds: string[] = []; + let capturedUrl: string | null = null; + + server.use( + http.get(`${BASE_URL}/v1/deployments`, () => { + listed = true; + return HttpResponse.json( + createDeploymentsListResponse({ + deployments: [ + { id: "in-flight", status: "pending", live: false }, + ], + }) + ); + }), + http.delete(`${BASE_URL}/v1/deployments/:id`, ({ params }) => { + deletedIds.push(params.id as string); + return HttpResponse.json({ result: "success" }); + }), + http.post(`${BASE_URL}/v1/deploy`, ({ request }) => { + capturedUrl = request.url; + return HttpResponse.json( + createDeploySuccessResponse({ deploymentId: "deploy-check" }) + ); + }) + ); + + await deployToMain(config, resources, { + pollIntervalMs: 1, + check: true, + }); + + expect(listed).toBe(false); + expect(deletedIds).toEqual([]); + const parsed = new URL(capturedUrl ?? ""); + expect(parsed.searchParams.get("check")).toBe("true"); + }); + + it("deletes stale non-live deployments on a normal deploy", async () => { + const deletedIds: string[] = []; + + server.use( + http.get(`${BASE_URL}/v1/deployments`, () => { + return HttpResponse.json( + createDeploymentsListResponse({ + deployments: [ + { id: "stale-1", status: "pending", live: false }, + { id: "live-1", status: "live", live: true }, + { id: "stale-2", status: "failed", live: false }, + ], + }) + ); + }), + http.delete(`${BASE_URL}/v1/deployments/:id`, ({ params }) => { + deletedIds.push(params.id as string); + return HttpResponse.json({ result: "success" }); + }) + ); + setupSuccessfulDeployFlow("deploy-cleanup"); + + await deployToMain(config, resources, { pollIntervalMs: 1 }); + + expect(deletedIds).toEqual(["stale-1", "stale-2"]); + }); + it("adds actionable guidance to Forward/Classic workspace errors", async () => { server.use( http.post(`${BASE_URL}/v1/deploy`, () => { diff --git a/src/api/deploy.ts b/src/api/deploy.ts index c969f79..2b7fcbb 100644 --- a/src/api/deploy.ts +++ b/src/api/deploy.ts @@ -217,37 +217,40 @@ export async function deployToMain( ); } - // Step 0: Clean up any stale non-live deployments that might block the new deployment - try { - const deploymentsUrl = `${baseUrl}/v1/deployments`; - const deploymentsResponse = await tinybirdFetch(deploymentsUrl, { - headers: { - Authorization: `Bearer ${config.token}`, - }, - }); - - if (deploymentsResponse.ok) { - const deploymentsBody = (await deploymentsResponse.json()) as DeploymentsListResponse; - const staleDeployments = deploymentsBody.deployments.filter( - (d) => !d.live && d.status !== "live" - ); - - for (const stale of staleDeployments) { - if (debug) { - console.log(`[debug] Cleaning up stale deployment: ${stale.id} (status: ${stale.status})`); + // Step 0: Clean up any stale non-live deployments that might block the new deployment. + // Skipped in check mode so validation runs don't tear down in-flight deployments. + if (!options?.check) { + try { + const deploymentsUrl = `${baseUrl}/v1/deployments`; + const deploymentsResponse = await tinybirdFetch(deploymentsUrl, { + headers: { + Authorization: `Bearer ${config.token}`, + }, + }); + + if (deploymentsResponse.ok) { + const deploymentsBody = (await deploymentsResponse.json()) as DeploymentsListResponse; + const staleDeployments = deploymentsBody.deployments.filter( + (d) => !d.live && d.status !== "live" + ); + + for (const stale of staleDeployments) { + if (debug) { + console.log(`[debug] Cleaning up stale deployment: ${stale.id} (status: ${stale.status})`); + } + await tinybirdFetch(`${baseUrl}/v1/deployments/${stale.id}`, { + method: "DELETE", + headers: { + Authorization: `Bearer ${config.token}`, + }, + }); } - await tinybirdFetch(`${baseUrl}/v1/deployments/${stale.id}`, { - method: "DELETE", - headers: { - Authorization: `Bearer ${config.token}`, - }, - }); } - } - } catch (e) { - // Ignore errors during cleanup - we'll try to deploy anyway - if (debug) { - console.log(`[debug] Failed to clean up stale deployments: ${e}`); + } catch (e) { + // Ignore errors during cleanup - we'll try to deploy anyway + if (debug) { + console.log(`[debug] Failed to clean up stale deployments: ${e}`); + } } } From 2a02656b043eae2f038528d6fcd97b123b0c08c4 Mon Sep 17 00:00:00 2001 From: jordiori Date: Mon, 1 Jun 2026 11:48:43 +0200 Subject: [PATCH 2/2] bump to 0.0.75 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7e0a611..9dd3b22 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@tinybirdco/sdk", - "version": "0.0.74", + "version": "0.0.75", "description": "TypeScript SDK for Tinybird Forward - define datasources and pipes as TypeScript", "type": "module", "main": "./dist/index.js",