@tinybirdco/sdk 0.0.52 → 0.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/deploy.d.ts +11 -18
- package/dist/api/deploy.d.ts.map +1 -1
- package/dist/api/deploy.js +69 -77
- package/dist/api/deploy.js.map +1 -1
- package/dist/api/deploy.test.js +71 -90
- package/dist/api/deploy.test.js.map +1 -1
- package/package.json +1 -1
- package/src/api/deploy.test.ts +85 -158
- package/src/api/deploy.ts +91 -106
package/src/api/deploy.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Deploy resources to Tinybird main workspace
|
|
3
|
-
* Uses the /v1/deploy endpoint
|
|
3
|
+
* Uses the /v1/deploy endpoint with auto-promotion enabled
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { GeneratedResources } from "../generator/index.js";
|
|
@@ -14,7 +14,7 @@ const FORWARD_CLASSIC_GUIDANCE =
|
|
|
14
14
|
* Feedback item from deployment response
|
|
15
15
|
*/
|
|
16
16
|
export interface DeploymentFeedback {
|
|
17
|
-
resource: string;
|
|
17
|
+
resource: string | null;
|
|
18
18
|
level: "ERROR" | "WARNING" | "INFO";
|
|
19
19
|
message: string;
|
|
20
20
|
}
|
|
@@ -31,13 +31,6 @@ export interface Deployment {
|
|
|
31
31
|
feedback?: DeploymentFeedback[];
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
/**
|
|
35
|
-
* Response from /v1/deployments list endpoint
|
|
36
|
-
*/
|
|
37
|
-
export interface DeploymentsListResponse {
|
|
38
|
-
deployments: Deployment[];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
34
|
/**
|
|
42
35
|
* Response from /v1/deploy endpoint
|
|
43
36
|
*/
|
|
@@ -48,6 +41,14 @@ export interface DeployResponse {
|
|
|
48
41
|
errors?: Array<{ filename?: string; error: string }>;
|
|
49
42
|
}
|
|
50
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Response from /v1/deployments/{id} endpoint
|
|
46
|
+
*/
|
|
47
|
+
export interface DeploymentStatusResponse {
|
|
48
|
+
result: string;
|
|
49
|
+
deployment: Deployment;
|
|
50
|
+
}
|
|
51
|
+
|
|
51
52
|
/**
|
|
52
53
|
* Detailed deployment information with resource changes
|
|
53
54
|
*/
|
|
@@ -74,21 +75,11 @@ export interface DeploymentDetails extends Deployment {
|
|
|
74
75
|
errors?: Array<{ filename?: string; error: string }>;
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
/**
|
|
78
|
-
* Response from /v1/deployments/{id} endpoint
|
|
79
|
-
*/
|
|
80
|
-
export interface DeploymentStatusResponse {
|
|
81
|
-
result: string;
|
|
82
|
-
deployment: Deployment;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
78
|
/**
|
|
86
79
|
* Deploy generated resources to Tinybird main workspace
|
|
87
80
|
*
|
|
88
81
|
* Uses the /v1/deploy endpoint which accepts all resources in a single
|
|
89
|
-
* multipart form request.
|
|
90
|
-
* 1. Polls until the deployment is ready (status === 'data_ready')
|
|
91
|
-
* 2. Sets the deployment as live via /v1/deployments/{id}/set-live
|
|
82
|
+
* multipart form request.
|
|
92
83
|
*
|
|
93
84
|
* @param config - Build configuration with API URL and token
|
|
94
85
|
* @param resources - Generated resources to deploy
|
|
@@ -161,13 +152,14 @@ export async function deployToMain(
|
|
|
161
152
|
pollIntervalMs?: number;
|
|
162
153
|
maxPollAttempts?: number;
|
|
163
154
|
check?: boolean;
|
|
155
|
+
autoPromote?: boolean;
|
|
164
156
|
allowDestructiveOperations?: boolean;
|
|
165
157
|
callbacks?: DeployCallbacks;
|
|
166
158
|
}
|
|
167
159
|
): Promise<BuildApiResult> {
|
|
168
160
|
const debug = options?.debug ?? !!process.env.TINYBIRD_DEBUG;
|
|
169
161
|
const pollIntervalMs = options?.pollIntervalMs ?? 1000;
|
|
170
|
-
const maxPollAttempts = options?.maxPollAttempts ?? 120;
|
|
162
|
+
const maxPollAttempts = options?.maxPollAttempts ?? 120;
|
|
171
163
|
const baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
172
164
|
|
|
173
165
|
const formData = new FormData();
|
|
@@ -202,46 +194,17 @@ export async function deployToMain(
|
|
|
202
194
|
);
|
|
203
195
|
}
|
|
204
196
|
|
|
205
|
-
//
|
|
206
|
-
|
|
207
|
-
const deploymentsUrl = `${baseUrl}/v1/deployments`;
|
|
208
|
-
const deploymentsResponse = await tinybirdFetch(deploymentsUrl, {
|
|
209
|
-
headers: {
|
|
210
|
-
Authorization: `Bearer ${config.token}`,
|
|
211
|
-
},
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
if (deploymentsResponse.ok) {
|
|
215
|
-
const deploymentsBody = (await deploymentsResponse.json()) as DeploymentsListResponse;
|
|
216
|
-
const staleDeployments = deploymentsBody.deployments.filter(
|
|
217
|
-
(d) => !d.live && d.status !== "live"
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
for (const stale of staleDeployments) {
|
|
221
|
-
if (debug) {
|
|
222
|
-
console.log(`[debug] Cleaning up stale deployment: ${stale.id} (status: ${stale.status})`);
|
|
223
|
-
}
|
|
224
|
-
await tinybirdFetch(`${baseUrl}/v1/deployments/${stale.id}`, {
|
|
225
|
-
method: "DELETE",
|
|
226
|
-
headers: {
|
|
227
|
-
Authorization: `Bearer ${config.token}`,
|
|
228
|
-
},
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
} catch (e) {
|
|
233
|
-
// Ignore errors during cleanup - we'll try to deploy anyway
|
|
234
|
-
if (debug) {
|
|
235
|
-
console.log(`[debug] Failed to clean up stale deployments: ${e}`);
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Step 1: Create deployment via /v1/deploy
|
|
197
|
+
// Create deployment via /v1/deploy.
|
|
198
|
+
// `auto_promote=true` makes the API promote the deployment automatically.
|
|
240
199
|
const deployUrlBase = `${baseUrl}/v1/deploy`;
|
|
241
200
|
const urlParams = new URLSearchParams();
|
|
242
201
|
if (options?.check) {
|
|
243
202
|
urlParams.set("check", "true");
|
|
244
203
|
}
|
|
204
|
+
const autoPromote = options?.autoPromote ?? !options?.check;
|
|
205
|
+
if (autoPromote) {
|
|
206
|
+
urlParams.set("auto_promote", "true");
|
|
207
|
+
}
|
|
245
208
|
if (options?.allowDestructiveOperations) {
|
|
246
209
|
urlParams.set("allow_destructive_operations", "true");
|
|
247
210
|
}
|
|
@@ -282,7 +245,7 @@ export async function deployToMain(
|
|
|
282
245
|
return feedback
|
|
283
246
|
.map((f) => {
|
|
284
247
|
// Extract just the filename from "Datasource events.datasource" format
|
|
285
|
-
const resourceName = f.resource
|
|
248
|
+
const resourceName = f.resource?.split(" ").pop() ?? f.resource ?? "unknown";
|
|
286
249
|
return `${resourceName}: ${normalizeDeployErrorMessage(f.message)}`;
|
|
287
250
|
})
|
|
288
251
|
.join("\n");
|
|
@@ -408,18 +371,22 @@ export async function deployToMain(
|
|
|
408
371
|
});
|
|
409
372
|
}
|
|
410
373
|
|
|
411
|
-
|
|
412
|
-
let
|
|
413
|
-
let attempts = 0;
|
|
374
|
+
let deployment = deploymentDetails;
|
|
375
|
+
let statusAttempts = 0;
|
|
414
376
|
|
|
415
377
|
options?.callbacks?.onWaitingForReady?.();
|
|
416
378
|
|
|
417
|
-
while (
|
|
379
|
+
while (
|
|
380
|
+
deployment.status !== "data_ready" &&
|
|
381
|
+
deployment.status !== "failed" &&
|
|
382
|
+
deployment.status !== "error" &&
|
|
383
|
+
statusAttempts < maxPollAttempts
|
|
384
|
+
) {
|
|
418
385
|
await sleep(pollIntervalMs);
|
|
419
|
-
|
|
386
|
+
statusAttempts++;
|
|
420
387
|
|
|
421
388
|
if (debug) {
|
|
422
|
-
console.log(`[debug] Polling deployment status (attempt ${
|
|
389
|
+
console.log(`[debug] Polling deployment status (attempt ${statusAttempts})...`);
|
|
423
390
|
}
|
|
424
391
|
|
|
425
392
|
const statusUrl = `${baseUrl}/v1/deployments/${deploymentId}`;
|
|
@@ -443,30 +410,13 @@ export async function deployToMain(
|
|
|
443
410
|
|
|
444
411
|
const statusBody = (await statusResponse.json()) as DeploymentStatusResponse;
|
|
445
412
|
deployment = statusBody.deployment;
|
|
446
|
-
|
|
447
|
-
if (debug) {
|
|
448
|
-
console.log(`[debug] Deployment status: ${deployment.status}`);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// Check for failed status
|
|
452
|
-
if (deployment.status === "failed" || deployment.status === "error") {
|
|
453
|
-
return {
|
|
454
|
-
success: false,
|
|
455
|
-
result: "failed",
|
|
456
|
-
error: `Deployment failed with status: ${deployment.status}`,
|
|
457
|
-
datasourceCount: resources.datasources.length,
|
|
458
|
-
pipeCount: resources.pipes.length,
|
|
459
|
-
connectionCount: resources.connections?.length ?? 0,
|
|
460
|
-
buildId: deploymentId,
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
413
|
}
|
|
464
414
|
|
|
465
|
-
if (deployment.status
|
|
415
|
+
if (deployment.status === "failed" || deployment.status === "error") {
|
|
466
416
|
return {
|
|
467
417
|
success: false,
|
|
468
418
|
result: "failed",
|
|
469
|
-
error: `Deployment
|
|
419
|
+
error: `Deployment failed with status: ${deployment.status}`,
|
|
470
420
|
datasourceCount: resources.datasources.length,
|
|
471
421
|
pipeCount: resources.pipes.length,
|
|
472
422
|
connectionCount: resources.connections?.length ?? 0,
|
|
@@ -474,28 +424,11 @@ export async function deployToMain(
|
|
|
474
424
|
};
|
|
475
425
|
}
|
|
476
426
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
// Step 3: Set the deployment as live
|
|
480
|
-
const setLiveUrl = `${baseUrl}/v1/deployments/${deploymentId}/set-live`;
|
|
481
|
-
|
|
482
|
-
if (debug) {
|
|
483
|
-
console.log(`[debug] POST ${setLiveUrl}`);
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
const setLiveResponse = await tinybirdFetch(setLiveUrl, {
|
|
487
|
-
method: "POST",
|
|
488
|
-
headers: {
|
|
489
|
-
Authorization: `Bearer ${config.token}`,
|
|
490
|
-
},
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
if (!setLiveResponse.ok) {
|
|
494
|
-
const setLiveBody = await setLiveResponse.text();
|
|
427
|
+
if (deployment.status !== "data_ready") {
|
|
495
428
|
return {
|
|
496
429
|
success: false,
|
|
497
430
|
result: "failed",
|
|
498
|
-
error: `
|
|
431
|
+
error: `Deployment timed out after ${maxPollAttempts} attempts. Last status: ${deployment.status}`,
|
|
499
432
|
datasourceCount: resources.datasources.length,
|
|
500
433
|
pipeCount: resources.pipes.length,
|
|
501
434
|
connectionCount: resources.connections?.length ?? 0,
|
|
@@ -503,11 +436,66 @@ export async function deployToMain(
|
|
|
503
436
|
};
|
|
504
437
|
}
|
|
505
438
|
|
|
439
|
+
options?.callbacks?.onDeploymentReady?.();
|
|
440
|
+
|
|
441
|
+
if (autoPromote && !deployment.live) {
|
|
442
|
+
let promotionAttempts = 0;
|
|
443
|
+
options?.callbacks?.onWaitingForPromote?.();
|
|
444
|
+
|
|
445
|
+
while (!deployment.live && promotionAttempts < maxPollAttempts) {
|
|
446
|
+
await sleep(pollIntervalMs);
|
|
447
|
+
promotionAttempts++;
|
|
448
|
+
|
|
449
|
+
if (debug) {
|
|
450
|
+
console.log(`[debug] Polling auto-promote status (attempt ${promotionAttempts})...`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const statusUrl = `${baseUrl}/v1/deployments/${deploymentId}`;
|
|
454
|
+
const statusResponse = await tinybirdFetch(statusUrl, {
|
|
455
|
+
headers: {
|
|
456
|
+
Authorization: `Bearer ${config.token}`,
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
if (!statusResponse.ok) {
|
|
461
|
+
return {
|
|
462
|
+
success: false,
|
|
463
|
+
result: "failed",
|
|
464
|
+
error: `Failed to check deployment status: ${statusResponse.status} ${statusResponse.statusText}`,
|
|
465
|
+
datasourceCount: resources.datasources.length,
|
|
466
|
+
pipeCount: resources.pipes.length,
|
|
467
|
+
connectionCount: resources.connections?.length ?? 0,
|
|
468
|
+
buildId: deploymentId,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const statusBody = (await statusResponse.json()) as DeploymentStatusResponse;
|
|
473
|
+
deployment = statusBody.deployment;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (!deployment.live) {
|
|
477
|
+
return {
|
|
478
|
+
success: false,
|
|
479
|
+
result: "failed",
|
|
480
|
+
error: `Deployment reached data_ready but auto-promote did not complete after ${maxPollAttempts} attempts`,
|
|
481
|
+
datasourceCount: resources.datasources.length,
|
|
482
|
+
pipeCount: resources.pipes.length,
|
|
483
|
+
connectionCount: resources.connections?.length ?? 0,
|
|
484
|
+
buildId: deploymentId,
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
options?.callbacks?.onDeploymentPromoted?.();
|
|
489
|
+
}
|
|
490
|
+
|
|
506
491
|
if (debug) {
|
|
507
|
-
|
|
492
|
+
const stateLabel = deployment.live ? "live" : "ready";
|
|
493
|
+
console.log(`[debug] Deployment ${deploymentId} is now ${stateLabel}`);
|
|
508
494
|
}
|
|
509
495
|
|
|
510
|
-
|
|
496
|
+
if (deployment.live) {
|
|
497
|
+
options?.callbacks?.onDeploymentLive?.(deploymentId);
|
|
498
|
+
}
|
|
511
499
|
|
|
512
500
|
return {
|
|
513
501
|
success: true,
|
|
@@ -529,9 +517,6 @@ export async function deployToMain(
|
|
|
529
517
|
};
|
|
530
518
|
}
|
|
531
519
|
|
|
532
|
-
/**
|
|
533
|
-
* Helper function to sleep for a given number of milliseconds
|
|
534
|
-
*/
|
|
535
520
|
function sleep(ms: number): Promise<void> {
|
|
536
521
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
537
522
|
}
|