@tinybirdco/sdk 0.0.80 → 0.0.82
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/README.md +1 -0
- package/dist/api/branches.test.js +28 -0
- package/dist/api/branches.test.js.map +1 -1
- package/dist/api/deploy.d.ts +14 -4
- package/dist/api/deploy.d.ts.map +1 -1
- package/dist/api/deploy.js +104 -100
- package/dist/api/deploy.js.map +1 -1
- package/dist/api/deploy.test.js +165 -76
- package/dist/api/deploy.test.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +2 -2
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/clear.js +2 -2
- package/dist/cli/commands/clear.js.map +1 -1
- package/dist/cli/commands/deploy.d.ts +8 -0
- package/dist/cli/commands/deploy.d.ts.map +1 -1
- package/dist/cli/commands/deploy.js +2 -0
- package/dist/cli/commands/deploy.js.map +1 -1
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +2 -2
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/preview.js +2 -2
- package/dist/cli/commands/preview.js.map +1 -1
- package/dist/cli/commands/preview.test.js +39 -0
- package/dist/cli/commands/preview.test.js.map +1 -1
- package/dist/cli/config-types.d.ts +4 -1
- package/dist/cli/config-types.d.ts.map +1 -1
- package/dist/cli/config.d.ts +1 -1
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +2 -3
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/config.test.js +13 -4
- package/dist/cli/config.test.js.map +1 -1
- package/dist/cli/index.js +11 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/output.d.ts +15 -0
- package/dist/cli/output.d.ts.map +1 -1
- package/dist/cli/output.js +24 -0
- package/dist/cli/output.js.map +1 -1
- package/dist/client/base.js +2 -2
- package/dist/client/base.js.map +1 -1
- package/dist/test/handlers.d.ts +2 -6
- package/dist/test/handlers.d.ts.map +1 -1
- package/dist/test/handlers.js +5 -9
- package/dist/test/handlers.js.map +1 -1
- package/package.json +1 -1
- package/src/api/branches.test.ts +32 -0
- package/src/api/deploy.test.ts +193 -100
- package/src/api/deploy.ts +129 -116
- package/src/cli/commands/build.ts +3 -4
- package/src/cli/commands/clear.ts +2 -2
- package/src/cli/commands/deploy.ts +10 -0
- package/src/cli/commands/dev.ts +3 -4
- package/src/cli/commands/preview.test.ts +46 -0
- package/src/cli/commands/preview.ts +2 -2
- package/src/cli/config-types.ts +4 -1
- package/src/cli/config.test.ts +20 -4
- package/src/cli/config.ts +3 -4
- package/src/cli/index.ts +18 -0
- package/src/cli/output.ts +27 -0
- package/src/client/base.ts +2 -2
- package/src/test/handlers.ts +6 -10
package/src/api/deploy.ts
CHANGED
|
@@ -10,6 +10,19 @@ import { tinybirdFetch } from "./fetcher.js";
|
|
|
10
10
|
const FORWARD_CLASSIC_GUIDANCE =
|
|
11
11
|
"Use the Tinybird Classic CLI (`tb`) from a Tinybird Classic workspace for this operation.";
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Poll interval used while waiting for a deployment to reach `data_ready`
|
|
15
|
+
* (and, when auto-promoting, `live`). Matches the Tinybird CLI (`tb deploy`).
|
|
16
|
+
*/
|
|
17
|
+
const POLL_INTERVAL_MS = 5_000;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* How many consecutive `failed` status polls to tolerate before giving up.
|
|
21
|
+
* With a 5s poll interval this is ~5 minutes, matching the CLI safety valve
|
|
22
|
+
* for deployments that fail but never auto-delete.
|
|
23
|
+
*/
|
|
24
|
+
export const MAX_CONSECUTIVE_FAILED_POLLS = 60;
|
|
25
|
+
|
|
13
26
|
/**
|
|
14
27
|
* Feedback item from deployment response
|
|
15
28
|
*/
|
|
@@ -87,8 +100,10 @@ export interface DeploymentStatusResponse {
|
|
|
87
100
|
*
|
|
88
101
|
* Uses the /v1/deploy endpoint which accepts all resources in a single
|
|
89
102
|
* multipart form request. After creating the deployment, this function:
|
|
90
|
-
* 1.
|
|
91
|
-
*
|
|
103
|
+
* 1. When `auto` is true (default), sends `auto_promote=true` so the
|
|
104
|
+
* server promotes the deployment as soon as it's ready.
|
|
105
|
+
* 2. When `wait` is true (default), polls until the deployment reaches
|
|
106
|
+
* `data_ready` (and, if `auto` is also true, until it becomes live).
|
|
92
107
|
*
|
|
93
108
|
* @param config - Build configuration with API URL and token
|
|
94
109
|
* @param resources - Generated resources to deploy
|
|
@@ -139,6 +154,8 @@ export interface DeploymentChanges {
|
|
|
139
154
|
export interface DeployCallbacks {
|
|
140
155
|
/** Called when deployment is created and changes are available */
|
|
141
156
|
onChanges?: (changes: DeploymentChanges) => void;
|
|
157
|
+
/** Called when deployment was submitted but wait was disabled */
|
|
158
|
+
onDeploymentSubmitted?: (deploymentId: string) => void;
|
|
142
159
|
/** Called when waiting for deployment to be ready */
|
|
143
160
|
onWaitingForReady?: () => void;
|
|
144
161
|
/** Called when deployment is ready */
|
|
@@ -158,18 +175,17 @@ export async function deployToMain(
|
|
|
158
175
|
resources: GeneratedResources,
|
|
159
176
|
options?: {
|
|
160
177
|
debug?: boolean;
|
|
161
|
-
pollIntervalMs?: number;
|
|
162
|
-
maxPollAttempts?: number;
|
|
163
178
|
check?: boolean;
|
|
164
179
|
allowDestructiveOperations?: boolean;
|
|
180
|
+
wait?: boolean;
|
|
181
|
+
auto?: boolean;
|
|
165
182
|
callbacks?: DeployCallbacks;
|
|
166
183
|
}
|
|
167
184
|
): Promise<BuildApiResult> {
|
|
168
185
|
const debug = options?.debug ?? !!process.env.TINYBIRD_DEBUG;
|
|
169
|
-
const
|
|
170
|
-
const
|
|
186
|
+
const wait = options?.wait ?? true;
|
|
187
|
+
const auto = options?.auto ?? true;
|
|
171
188
|
const baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
172
|
-
let previousLiveDeploymentId: string | undefined;
|
|
173
189
|
|
|
174
190
|
const formData = new FormData();
|
|
175
191
|
|
|
@@ -231,10 +247,6 @@ export async function deployToMain(
|
|
|
231
247
|
|
|
232
248
|
if (deploymentsResponse.ok) {
|
|
233
249
|
const deploymentsBody = (await deploymentsResponse.json()) as DeploymentsListResponse;
|
|
234
|
-
const previousLiveDeployment = deploymentsBody.deployments.find(
|
|
235
|
-
(d) => d.live || d.status === "live"
|
|
236
|
-
);
|
|
237
|
-
previousLiveDeploymentId = previousLiveDeployment?.id;
|
|
238
250
|
const staleDeployments = deploymentsBody.deployments.filter(
|
|
239
251
|
(d) => !d.live && d.status !== "live"
|
|
240
252
|
);
|
|
@@ -264,6 +276,9 @@ export async function deployToMain(
|
|
|
264
276
|
const urlParams = new URLSearchParams();
|
|
265
277
|
if (options?.check) {
|
|
266
278
|
urlParams.set("check", "true");
|
|
279
|
+
} else if (auto) {
|
|
280
|
+
// Server will auto-promote the deployment when it's ready
|
|
281
|
+
urlParams.set("auto_promote", "true");
|
|
267
282
|
}
|
|
268
283
|
if (options?.allowDestructiveOperations) {
|
|
269
284
|
urlParams.set("allow_destructive_operations", "true");
|
|
@@ -431,18 +446,67 @@ export async function deployToMain(
|
|
|
431
446
|
});
|
|
432
447
|
}
|
|
433
448
|
|
|
434
|
-
|
|
449
|
+
const deploymentChanges = {
|
|
450
|
+
pipes: {
|
|
451
|
+
changed: deploymentDetails.changed_pipe_names ?? [],
|
|
452
|
+
created: deploymentDetails.new_pipe_names ?? [],
|
|
453
|
+
deleted: deploymentDetails.deleted_pipe_names ?? [],
|
|
454
|
+
},
|
|
455
|
+
datasources: {
|
|
456
|
+
changed: deploymentDetails.changed_datasource_names ?? [],
|
|
457
|
+
created: deploymentDetails.new_datasource_names ?? [],
|
|
458
|
+
deleted: deploymentDetails.deleted_datasource_names ?? [],
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
// If we're not waiting, return as soon as the server accepted the deployment.
|
|
463
|
+
if (!wait) {
|
|
464
|
+
options?.callbacks?.onDeploymentSubmitted?.(deploymentId);
|
|
465
|
+
return {
|
|
466
|
+
success: true,
|
|
467
|
+
result: "success",
|
|
468
|
+
datasourceCount: resources.datasources.length,
|
|
469
|
+
pipeCount: resources.pipes.length,
|
|
470
|
+
connectionCount: resources.connections?.length ?? 0,
|
|
471
|
+
buildId: deploymentId,
|
|
472
|
+
...deploymentChanges,
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Step 2: Poll until the deployment reaches a terminal state.
|
|
435
477
|
let deployment = body.deployment;
|
|
436
|
-
let
|
|
478
|
+
let timesSeenFailed = 0;
|
|
479
|
+
let notifiedReady = false;
|
|
480
|
+
let notifiedWaitingForPromote = false;
|
|
437
481
|
|
|
438
482
|
options?.callbacks?.onWaitingForReady?.();
|
|
439
483
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
484
|
+
const isDone = (): boolean => {
|
|
485
|
+
if (deployment.status !== "data_ready") {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
if (auto) {
|
|
489
|
+
// When auto-promoting we must also wait for the server to flip it live.
|
|
490
|
+
return deployment.live === true;
|
|
491
|
+
}
|
|
492
|
+
return true;
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
const buildError = (message: string): BuildApiResult => ({
|
|
496
|
+
success: false,
|
|
497
|
+
result: "failed",
|
|
498
|
+
error: message,
|
|
499
|
+
datasourceCount: resources.datasources.length,
|
|
500
|
+
pipeCount: resources.pipes.length,
|
|
501
|
+
connectionCount: resources.connections?.length ?? 0,
|
|
502
|
+
buildId: deploymentId,
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
while (!isDone()) {
|
|
506
|
+
await sleep(POLL_INTERVAL_MS);
|
|
443
507
|
|
|
444
508
|
if (debug) {
|
|
445
|
-
console.log(`[debug] Polling deployment status
|
|
509
|
+
console.log(`[debug] Polling deployment status...`);
|
|
446
510
|
}
|
|
447
511
|
|
|
448
512
|
const statusUrl = `${baseUrl}/v1/deployments/${deploymentId}`;
|
|
@@ -453,114 +517,72 @@ export async function deployToMain(
|
|
|
453
517
|
});
|
|
454
518
|
|
|
455
519
|
if (!statusResponse.ok) {
|
|
456
|
-
return
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
error: `Failed to check deployment status: ${statusResponse.status} ${statusResponse.statusText}`,
|
|
460
|
-
datasourceCount: resources.datasources.length,
|
|
461
|
-
pipeCount: resources.pipes.length,
|
|
462
|
-
connectionCount: resources.connections?.length ?? 0,
|
|
463
|
-
buildId: deploymentId,
|
|
464
|
-
};
|
|
520
|
+
return buildError(
|
|
521
|
+
`Failed to check deployment status: ${statusResponse.status} ${statusResponse.statusText}`
|
|
522
|
+
);
|
|
465
523
|
}
|
|
466
524
|
|
|
467
525
|
const statusBody = (await statusResponse.json()) as DeploymentStatusResponse;
|
|
468
526
|
deployment = statusBody.deployment;
|
|
469
527
|
|
|
470
528
|
if (debug) {
|
|
471
|
-
console.log(
|
|
529
|
+
console.log(
|
|
530
|
+
`[debug] Deployment status: ${deployment.status} (live=${deployment.live ?? false})`
|
|
531
|
+
);
|
|
472
532
|
}
|
|
473
533
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
buildId: deploymentId,
|
|
484
|
-
};
|
|
534
|
+
if (deployment.status === "failed") {
|
|
535
|
+
timesSeenFailed++;
|
|
536
|
+
if (timesSeenFailed > MAX_CONSECUTIVE_FAILED_POLLS) {
|
|
537
|
+
return buildError(
|
|
538
|
+
"Deployment failed to create and didn't start deleting automatically after 5 minutes. " +
|
|
539
|
+
"You might need to delete it manually in the UI."
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
continue;
|
|
485
543
|
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
if (deployment.status !== "data_ready") {
|
|
489
|
-
return {
|
|
490
|
-
success: false,
|
|
491
|
-
result: "failed",
|
|
492
|
-
error: `Deployment timed out after ${maxPollAttempts} attempts. Last status: ${deployment.status}`,
|
|
493
|
-
datasourceCount: resources.datasources.length,
|
|
494
|
-
pipeCount: resources.pipes.length,
|
|
495
|
-
connectionCount: resources.connections?.length ?? 0,
|
|
496
|
-
buildId: deploymentId,
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
544
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
Authorization: `Bearer ${config.token}`,
|
|
513
|
-
},
|
|
514
|
-
});
|
|
545
|
+
if (deployment.status === "deleting" || deployment.status === "deleted") {
|
|
546
|
+
const errors = deployment.feedback
|
|
547
|
+
?.filter((f) => f.level === "ERROR")
|
|
548
|
+
.map((f) => f.message)
|
|
549
|
+
.join("\n");
|
|
550
|
+
const errorSuffix = errors ? `\n${errors}` : "";
|
|
551
|
+
return buildError(
|
|
552
|
+
`Deployment failed and ${
|
|
553
|
+
deployment.status === "deleting" ? "is being" : "was"
|
|
554
|
+
} deleted automatically.${errorSuffix}`
|
|
555
|
+
);
|
|
556
|
+
}
|
|
515
557
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
558
|
+
if (
|
|
559
|
+
auto &&
|
|
560
|
+
deployment.status === "data_ready" &&
|
|
561
|
+
!deployment.live &&
|
|
562
|
+
!notifiedWaitingForPromote
|
|
563
|
+
) {
|
|
564
|
+
if (!notifiedReady) {
|
|
565
|
+
options?.callbacks?.onDeploymentReady?.();
|
|
566
|
+
notifiedReady = true;
|
|
567
|
+
}
|
|
568
|
+
options?.callbacks?.onWaitingForPromote?.();
|
|
569
|
+
notifiedWaitingForPromote = true;
|
|
570
|
+
}
|
|
527
571
|
}
|
|
528
572
|
|
|
529
|
-
if (
|
|
530
|
-
|
|
573
|
+
if (!notifiedReady) {
|
|
574
|
+
options?.callbacks?.onDeploymentReady?.();
|
|
575
|
+
notifiedReady = true;
|
|
531
576
|
}
|
|
532
577
|
|
|
533
|
-
if (
|
|
578
|
+
if (auto) {
|
|
534
579
|
if (debug) {
|
|
535
|
-
console.log(`[debug]
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
const deletePreviousResponse = await tinybirdFetch(
|
|
539
|
-
`${baseUrl}/v1/deployments/${previousLiveDeploymentId}`,
|
|
540
|
-
{
|
|
541
|
-
method: "DELETE",
|
|
542
|
-
headers: {
|
|
543
|
-
Authorization: `Bearer ${config.token}`,
|
|
544
|
-
},
|
|
545
|
-
}
|
|
546
|
-
);
|
|
547
|
-
|
|
548
|
-
if (!deletePreviousResponse.ok) {
|
|
549
|
-
const deletePreviousBody = await deletePreviousResponse.text();
|
|
550
|
-
return {
|
|
551
|
-
success: false,
|
|
552
|
-
result: "failed",
|
|
553
|
-
error: `Failed to remove previous deployment: ${deletePreviousResponse.status} ${deletePreviousResponse.statusText}\n${deletePreviousBody}`,
|
|
554
|
-
datasourceCount: resources.datasources.length,
|
|
555
|
-
pipeCount: resources.pipes.length,
|
|
556
|
-
connectionCount: resources.connections?.length ?? 0,
|
|
557
|
-
buildId: deploymentId,
|
|
558
|
-
};
|
|
580
|
+
console.log(`[debug] Deployment ${deploymentId} is now live`);
|
|
559
581
|
}
|
|
582
|
+
options?.callbacks?.onDeploymentPromoted?.();
|
|
583
|
+
options?.callbacks?.onDeploymentLive?.(deploymentId);
|
|
560
584
|
}
|
|
561
585
|
|
|
562
|
-
options?.callbacks?.onDeploymentLive?.(deploymentId);
|
|
563
|
-
|
|
564
586
|
return {
|
|
565
587
|
success: true,
|
|
566
588
|
result: "success",
|
|
@@ -568,16 +590,7 @@ export async function deployToMain(
|
|
|
568
590
|
pipeCount: resources.pipes.length,
|
|
569
591
|
connectionCount: resources.connections?.length ?? 0,
|
|
570
592
|
buildId: deploymentId,
|
|
571
|
-
|
|
572
|
-
changed: deploymentDetails.changed_pipe_names ?? [],
|
|
573
|
-
created: deploymentDetails.new_pipe_names ?? [],
|
|
574
|
-
deleted: deploymentDetails.deleted_pipe_names ?? [],
|
|
575
|
-
},
|
|
576
|
-
datasources: {
|
|
577
|
-
changed: deploymentDetails.changed_datasource_names ?? [],
|
|
578
|
-
created: deploymentDetails.new_datasource_names ?? [],
|
|
579
|
-
deleted: deploymentDetails.deleted_datasource_names ?? [],
|
|
580
|
-
},
|
|
593
|
+
...deploymentChanges,
|
|
581
594
|
};
|
|
582
595
|
}
|
|
583
596
|
|
|
@@ -225,10 +225,9 @@ export async function runBuild(options: BuildCommandOptions = {}): Promise<Build
|
|
|
225
225
|
console.log(`[debug] Getting/creating Tinybird branch: ${config.tinybirdBranch}`);
|
|
226
226
|
}
|
|
227
227
|
try {
|
|
228
|
-
const branchDataMode: BranchDataMode | undefined =
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
: undefined;
|
|
228
|
+
const branchDataMode: BranchDataMode | undefined = options.lastPartition
|
|
229
|
+
? "last_partition"
|
|
230
|
+
: config.branchDataMode ?? undefined;
|
|
232
231
|
const branchOptions = branchDataMode
|
|
233
232
|
? { branch_data_mode: branchDataMode }
|
|
234
233
|
: undefined;
|
|
@@ -149,8 +149,8 @@ async function clearCloudBranch(config: ResolvedConfig): Promise<ClearResult> {
|
|
|
149
149
|
|
|
150
150
|
// Clear the branch (delete and recreate)
|
|
151
151
|
const branchOptions: CreateBranchOptions | undefined =
|
|
152
|
-
config.devMode !== "local" && config.branchDataMode
|
|
153
|
-
? { branch_data_mode:
|
|
152
|
+
config.devMode !== "local" && config.branchDataMode
|
|
153
|
+
? { branch_data_mode: config.branchDataMode }
|
|
154
154
|
: undefined;
|
|
155
155
|
|
|
156
156
|
const newBranch = await clearBranch(
|
|
@@ -19,6 +19,14 @@ export interface DeployCommandOptions {
|
|
|
19
19
|
check?: boolean;
|
|
20
20
|
/** Allow deleting existing resources in main workspace deploys */
|
|
21
21
|
allowDestructiveOperations?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Wait for the deployment to finish. Defaults to true.
|
|
24
|
+
*/
|
|
25
|
+
wait?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Auto-promote the deployment when it's ready. Defaults to true.
|
|
28
|
+
*/
|
|
29
|
+
auto?: boolean;
|
|
22
30
|
/** Callbacks for deploy progress */
|
|
23
31
|
callbacks?: DeployCallbacks;
|
|
24
32
|
}
|
|
@@ -106,6 +114,8 @@ export async function runDeploy(options: DeployCommandOptions = {}): Promise<Dep
|
|
|
106
114
|
{
|
|
107
115
|
check: options.check,
|
|
108
116
|
allowDestructiveOperations: options.allowDestructiveOperations,
|
|
117
|
+
wait: options.wait,
|
|
118
|
+
auto: options.auto,
|
|
109
119
|
callbacks: options.callbacks,
|
|
110
120
|
}
|
|
111
121
|
);
|
package/src/cli/commands/dev.ts
CHANGED
|
@@ -240,10 +240,9 @@ export async function runDev(
|
|
|
240
240
|
// Use tinybirdBranch (sanitized name) for Tinybird API, gitBranch for display
|
|
241
241
|
if (config.tinybirdBranch) {
|
|
242
242
|
const branchName = config.tinybirdBranch; // Sanitized name for Tinybird
|
|
243
|
-
const branchDataMode: BranchDataMode | undefined =
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
: undefined;
|
|
243
|
+
const branchDataMode: BranchDataMode | undefined = options.lastPartition
|
|
244
|
+
? "last_partition"
|
|
245
|
+
: config.branchDataMode ?? undefined;
|
|
247
246
|
const branchOptions = branchDataMode
|
|
248
247
|
? { branch_data_mode: branchDataMode }
|
|
249
248
|
: undefined;
|
|
@@ -120,6 +120,52 @@ describe("Preview command", () => {
|
|
|
120
120
|
);
|
|
121
121
|
});
|
|
122
122
|
|
|
123
|
+
it("creates cloud preview branch without data when branch_data_mode is not set", async () => {
|
|
124
|
+
const { loadConfigAsync } = await import("../config.js");
|
|
125
|
+
const { buildFromInclude } = await import("../../generator/index.js");
|
|
126
|
+
const { getBranch, createBranch } = await import("../../api/branches.js");
|
|
127
|
+
const { deployToMain } = await import("../../api/deploy.js");
|
|
128
|
+
|
|
129
|
+
vi.mocked(loadConfigAsync).mockResolvedValue({
|
|
130
|
+
include: ["test.ts"],
|
|
131
|
+
token: "p.test-token",
|
|
132
|
+
baseUrl: "https://api.tinybird.co",
|
|
133
|
+
configPath: "/test/tinybird.config.json",
|
|
134
|
+
devMode: "branch",
|
|
135
|
+
cwd: "/test",
|
|
136
|
+
gitBranch: "feature-test",
|
|
137
|
+
tinybirdBranch: "feature_test",
|
|
138
|
+
isMainBranch: false,
|
|
139
|
+
branchDataMode: null,
|
|
140
|
+
});
|
|
141
|
+
vi.mocked(buildFromInclude).mockResolvedValue({
|
|
142
|
+
resources: { datasources: [], pipes: [], connections: [] },
|
|
143
|
+
entities: { datasources: {}, pipes: {}, connections: {}, rawDatasources: [], rawPipes: [], sourceFiles: [] },
|
|
144
|
+
stats: { datasourceCount: 0, pipeCount: 0, connectionCount: 0 },
|
|
145
|
+
});
|
|
146
|
+
vi.mocked(getBranch).mockRejectedValue(new Error("not found"));
|
|
147
|
+
vi.mocked(createBranch).mockResolvedValue({
|
|
148
|
+
id: "b1",
|
|
149
|
+
name: "tmp_ci_feature_test",
|
|
150
|
+
token: "p.branch",
|
|
151
|
+
created_at: "2024-01-01T00:00:00Z",
|
|
152
|
+
});
|
|
153
|
+
vi.mocked(deployToMain).mockResolvedValue({
|
|
154
|
+
success: true,
|
|
155
|
+
result: "success",
|
|
156
|
+
datasourceCount: 0,
|
|
157
|
+
pipeCount: 0,
|
|
158
|
+
connectionCount: 0,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await runPreview();
|
|
162
|
+
expect(createBranch).toHaveBeenCalledWith(
|
|
163
|
+
expect.any(Object),
|
|
164
|
+
"tmp_ci_feature_test",
|
|
165
|
+
undefined
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
123
169
|
it("ignores config branch_data_mode in local mode", async () => {
|
|
124
170
|
const { loadConfigAsync } = await import("../config.js");
|
|
125
171
|
const { buildFromInclude } = await import("../../generator/index.js");
|
|
@@ -227,8 +227,8 @@ export async function runPreview(options: PreviewCommandOptions = {}): Promise<P
|
|
|
227
227
|
try {
|
|
228
228
|
const apiConfig = { baseUrl: config.baseUrl, token: config.token };
|
|
229
229
|
const branchOptions: CreateBranchOptions | undefined =
|
|
230
|
-
config.branchDataMode
|
|
231
|
-
? { branch_data_mode:
|
|
230
|
+
config.branchDataMode
|
|
231
|
+
? { branch_data_mode: config.branchDataMode }
|
|
232
232
|
: undefined;
|
|
233
233
|
|
|
234
234
|
// Check if branch already exists and delete it for a fresh start
|
package/src/cli/config-types.ts
CHANGED
|
@@ -28,6 +28,9 @@ export interface TinybirdConfig {
|
|
|
28
28
|
baseUrl?: string;
|
|
29
29
|
/** Development mode: "branch" (default) or "local" */
|
|
30
30
|
devMode?: DevMode;
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* Branch data mode applied on cloud branch creation (shared snake_case key,
|
|
33
|
+
* also read by the tb CLI). Omit to create branches without data (default).
|
|
34
|
+
*/
|
|
32
35
|
branch_data_mode?: BranchDataMode;
|
|
33
36
|
}
|
package/src/cli/config.test.ts
CHANGED
|
@@ -343,7 +343,23 @@ describe("Config", () => {
|
|
|
343
343
|
expect(result.branchDataMode).toBe("last_partition");
|
|
344
344
|
});
|
|
345
345
|
|
|
346
|
-
it("
|
|
346
|
+
it("throws when branch_data_mode is none", () => {
|
|
347
|
+
const config = {
|
|
348
|
+
include: ["lib/datasources.ts"],
|
|
349
|
+
token: "test-token",
|
|
350
|
+
branch_data_mode: "none",
|
|
351
|
+
};
|
|
352
|
+
fs.writeFileSync(
|
|
353
|
+
path.join(tempDir, "tinybird.json"),
|
|
354
|
+
JSON.stringify(config)
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
expect(() => loadConfig(tempDir)).toThrow(
|
|
358
|
+
"Invalid branch_data_mode 'none'. Allowed values are: last_partition."
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it("defaults branch_data_mode to no data when missing", () => {
|
|
347
363
|
const config = {
|
|
348
364
|
include: ["lib/datasources.ts"],
|
|
349
365
|
token: "test-token",
|
|
@@ -354,10 +370,10 @@ describe("Config", () => {
|
|
|
354
370
|
);
|
|
355
371
|
|
|
356
372
|
const result = loadConfig(tempDir);
|
|
357
|
-
expect(result.branchDataMode).
|
|
373
|
+
expect(result.branchDataMode).toBeNull();
|
|
358
374
|
});
|
|
359
375
|
|
|
360
|
-
it("defaults empty branch_data_mode to
|
|
376
|
+
it("defaults empty branch_data_mode to no data", () => {
|
|
361
377
|
const config = {
|
|
362
378
|
include: ["lib/datasources.ts"],
|
|
363
379
|
token: "test-token",
|
|
@@ -369,7 +385,7 @@ describe("Config", () => {
|
|
|
369
385
|
);
|
|
370
386
|
|
|
371
387
|
const result = loadConfig(tempDir);
|
|
372
|
-
expect(result.branchDataMode).
|
|
388
|
+
expect(result.branchDataMode).toBeNull();
|
|
373
389
|
});
|
|
374
390
|
|
|
375
391
|
it("throws when branch_data_mode is all_partitions", () => {
|
package/src/cli/config.ts
CHANGED
|
@@ -17,7 +17,6 @@ export {
|
|
|
17
17
|
import { BRANCH_DATA_MODE_VALUES } from "./config-types.js";
|
|
18
18
|
import type { BranchDataMode, DevMode, TinybirdConfig } from "./config-types.js";
|
|
19
19
|
|
|
20
|
-
const DEFAULT_BRANCH_DATA_MODE: BranchDataMode = "last_partition";
|
|
21
20
|
|
|
22
21
|
/**
|
|
23
22
|
* Resolved configuration with all values expanded
|
|
@@ -41,7 +40,7 @@ export interface ResolvedConfig {
|
|
|
41
40
|
isMainBranch: boolean;
|
|
42
41
|
/** Development mode: "branch" or "local" */
|
|
43
42
|
devMode: DevMode;
|
|
44
|
-
/** Branch data mode configured in tinybird.config.json */
|
|
43
|
+
/** Branch data mode configured in tinybird.config.json (null = create branches without data, the default) */
|
|
45
44
|
branchDataMode?: BranchDataMode | null;
|
|
46
45
|
}
|
|
47
46
|
|
|
@@ -212,10 +211,10 @@ function resolveBranchDataMode(raw: Record<string, unknown>): { mode: BranchData
|
|
|
212
211
|
}
|
|
213
212
|
|
|
214
213
|
const value = raw["branch_data_mode"];
|
|
215
|
-
if (value === undefined || value === null) return { mode:
|
|
214
|
+
if (value === undefined || value === null) return { mode: null, explicit: false };
|
|
216
215
|
if (typeof value !== "string") throw new Error("branch_data_mode must be a string.");
|
|
217
216
|
const mode = value.trim().toLowerCase();
|
|
218
|
-
if (!mode) return { mode:
|
|
217
|
+
if (!mode) return { mode: null, explicit: false };
|
|
219
218
|
if (!BRANCH_DATA_MODE_VALUES.includes(mode as BranchDataMode)) {
|
|
220
219
|
throw new Error(
|
|
221
220
|
`Invalid branch_data_mode '${value}'. Allowed values are: ${BRANCH_DATA_MODE_VALUES.join(", ")}.`
|
package/src/cli/index.ts
CHANGED
|
@@ -482,18 +482,33 @@ function createCli(): Command {
|
|
|
482
482
|
"--allow-destructive-operations",
|
|
483
483
|
"Allow deploys that delete existing datasources, pipes, or connections"
|
|
484
484
|
)
|
|
485
|
+
.option("--wait", "Wait for the deployment to finish (default)")
|
|
486
|
+
.option("--no-wait", "Return as soon as the deployment is submitted")
|
|
487
|
+
.option(
|
|
488
|
+
"--auto",
|
|
489
|
+
"Auto-promote the deployment when it's ready (default)"
|
|
490
|
+
)
|
|
491
|
+
.option(
|
|
492
|
+
"--no-auto",
|
|
493
|
+
"Do not auto-promote the deployment; leave it staged for a manual promote"
|
|
494
|
+
)
|
|
485
495
|
.option("--debug", "Show debug output including API requests/responses")
|
|
486
496
|
.action(async (options) => {
|
|
487
497
|
if (options.debug) {
|
|
488
498
|
process.env.TINYBIRD_DEBUG = "1";
|
|
489
499
|
}
|
|
490
500
|
|
|
501
|
+
const wait = options.wait !== false;
|
|
502
|
+
const auto = options.auto !== false;
|
|
503
|
+
|
|
491
504
|
output.highlight("Deploying to main workspace...");
|
|
492
505
|
|
|
493
506
|
const result = await runDeploy({
|
|
494
507
|
dryRun: options.dryRun,
|
|
495
508
|
check: options.check,
|
|
496
509
|
allowDestructiveOperations: options.allowDestructiveOperations,
|
|
510
|
+
wait,
|
|
511
|
+
auto,
|
|
497
512
|
callbacks: {
|
|
498
513
|
onChanges: (deployChanges) => {
|
|
499
514
|
// Show changes table immediately after deployment is created
|
|
@@ -531,8 +546,11 @@ function createCli(): Command {
|
|
|
531
546
|
|
|
532
547
|
output.showChangesTable(changes);
|
|
533
548
|
},
|
|
549
|
+
onDeploymentSubmitted: (id) => output.showDeploymentSubmitted(id, auto),
|
|
534
550
|
onWaitingForReady: () => output.showWaitingForDeployment(),
|
|
535
551
|
onDeploymentReady: () => output.showDeploymentReady(),
|
|
552
|
+
onWaitingForPromote: () => output.showWaitingForPromote(),
|
|
553
|
+
onDeploymentPromoted: () => output.showDeploymentPromoted(),
|
|
536
554
|
onDeploymentLive: (id) => output.showDeploymentLive(id),
|
|
537
555
|
onValidating: () => output.showValidatingDeployment(),
|
|
538
556
|
},
|
package/src/cli/output.ts
CHANGED
|
@@ -218,6 +218,30 @@ export function showDeploymentLive(deploymentId: string): void {
|
|
|
218
218
|
success(`✓ Deployment #${deploymentId} is live!`);
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
+
/**
|
|
222
|
+
* Show waiting for deployment promotion message
|
|
223
|
+
*/
|
|
224
|
+
export function showWaitingForPromote(): void {
|
|
225
|
+
info("» Waiting for deployment to be promoted...");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Show deployment submitted message (used when --no-wait is set)
|
|
230
|
+
*/
|
|
231
|
+
export function showDeploymentSubmitted(deploymentId: string, autoPromote: boolean): void {
|
|
232
|
+
const autoFrag = autoPromote
|
|
233
|
+
? " It will be auto-promoted when ready."
|
|
234
|
+
: " It won't be auto-promoted when ready.";
|
|
235
|
+
success(`✓ Deployment #${deploymentId} submitted.${autoFrag}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Show deployment promoted message
|
|
240
|
+
*/
|
|
241
|
+
export function showDeploymentPromoted(): void {
|
|
242
|
+
success("✓ Deployment promoted");
|
|
243
|
+
}
|
|
244
|
+
|
|
221
245
|
/**
|
|
222
246
|
* Show validating deployment message
|
|
223
247
|
*/
|
|
@@ -424,6 +448,9 @@ export const output = {
|
|
|
424
448
|
showNoChanges,
|
|
425
449
|
showWaitingForDeployment,
|
|
426
450
|
showDeploymentReady,
|
|
451
|
+
showWaitingForPromote,
|
|
452
|
+
showDeploymentPromoted,
|
|
453
|
+
showDeploymentSubmitted,
|
|
427
454
|
showDeploymentLive,
|
|
428
455
|
showValidatingDeployment,
|
|
429
456
|
showDeploySuccess,
|
package/src/client/base.ts
CHANGED
|
@@ -342,8 +342,8 @@ export class TinybirdClient {
|
|
|
342
342
|
|
|
343
343
|
const branchName = config.tinybirdBranch;
|
|
344
344
|
const branchOptions: CreateBranchOptions | undefined =
|
|
345
|
-
config.devMode !== "local" && config.branchDataMode
|
|
346
|
-
? { branch_data_mode:
|
|
345
|
+
config.devMode !== "local" && config.branchDataMode
|
|
346
|
+
? { branch_data_mode: config.branchDataMode }
|
|
347
347
|
: undefined;
|
|
348
348
|
|
|
349
349
|
// Get or create branch (always fetch fresh to avoid stale cache issues)
|