@tinybirdco/sdk 0.0.12 → 0.0.14

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.
Files changed (62) hide show
  1. package/dist/api/build.d.ts +3 -1
  2. package/dist/api/build.d.ts.map +1 -1
  3. package/dist/api/build.js +2 -0
  4. package/dist/api/build.js.map +1 -1
  5. package/dist/api/deploy.d.ts +80 -2
  6. package/dist/api/deploy.d.ts.map +1 -1
  7. package/dist/api/deploy.js +82 -38
  8. package/dist/api/deploy.js.map +1 -1
  9. package/dist/cli/commands/deploy.d.ts +3 -0
  10. package/dist/cli/commands/deploy.d.ts.map +1 -1
  11. package/dist/cli/commands/deploy.js +1 -0
  12. package/dist/cli/commands/deploy.js.map +1 -1
  13. package/dist/cli/commands/init.d.ts.map +1 -1
  14. package/dist/cli/commands/init.js +24 -2
  15. package/dist/cli/commands/init.js.map +1 -1
  16. package/dist/cli/commands/preview.d.ts +65 -0
  17. package/dist/cli/commands/preview.d.ts.map +1 -0
  18. package/dist/cli/commands/preview.js +234 -0
  19. package/dist/cli/commands/preview.js.map +1 -0
  20. package/dist/cli/commands/preview.test.d.ts +2 -0
  21. package/dist/cli/commands/preview.test.d.ts.map +1 -0
  22. package/dist/cli/commands/preview.test.js +36 -0
  23. package/dist/cli/commands/preview.test.js.map +1 -0
  24. package/dist/cli/index.js +198 -41
  25. package/dist/cli/index.js.map +1 -1
  26. package/dist/cli/output.d.ts +133 -0
  27. package/dist/cli/output.d.ts.map +1 -0
  28. package/dist/cli/output.js +222 -0
  29. package/dist/cli/output.js.map +1 -0
  30. package/dist/cli/output.test.d.ts +5 -0
  31. package/dist/cli/output.test.d.ts.map +1 -0
  32. package/dist/cli/output.test.js +215 -0
  33. package/dist/cli/output.test.js.map +1 -0
  34. package/dist/client/preview.d.ts +36 -0
  35. package/dist/client/preview.d.ts.map +1 -0
  36. package/dist/client/preview.js +161 -0
  37. package/dist/client/preview.js.map +1 -0
  38. package/dist/client/preview.test.d.ts +2 -0
  39. package/dist/client/preview.test.d.ts.map +1 -0
  40. package/dist/client/preview.test.js +137 -0
  41. package/dist/client/preview.test.js.map +1 -0
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +2 -0
  45. package/dist/index.js.map +1 -1
  46. package/dist/schema/project.d.ts.map +1 -1
  47. package/dist/schema/project.js +7 -3
  48. package/dist/schema/project.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/api/build.ts +5 -1
  51. package/src/api/deploy.ts +170 -10
  52. package/src/cli/commands/deploy.ts +4 -1
  53. package/src/cli/commands/init.ts +24 -2
  54. package/src/cli/commands/preview.test.ts +42 -0
  55. package/src/cli/commands/preview.ts +313 -0
  56. package/src/cli/index.ts +212 -57
  57. package/src/cli/output.test.ts +259 -0
  58. package/src/cli/output.ts +268 -0
  59. package/src/client/preview.test.ts +168 -0
  60. package/src/client/preview.ts +210 -0
  61. package/src/index.ts +8 -0
  62. package/src/schema/project.ts +9 -3
@@ -127,6 +127,11 @@ on:
127
127
  jobs:
128
128
  tinybird:
129
129
  runs-on: ubuntu-latest
130
+ outputs:
131
+ branch_name: \${{ steps.preview.outputs.branch_name }}
132
+ branch_id: \${{ steps.preview.outputs.branch_id }}
133
+ branch_token: \${{ steps.preview.outputs.branch_token }}
134
+ branch_url: \${{ steps.preview.outputs.branch_url }}
130
135
  steps:
131
136
  - uses: actions/checkout@v4
132
137
  - uses: pnpm/action-setup@v4
@@ -136,7 +141,16 @@ jobs:
136
141
  cache: "pnpm"
137
142
  - run: pnpm install --frozen-lockfile
138
143
  - run: pnpm run tinybird:build
139
- - run: pnpm run tinybird:deploy -- --check
144
+ - name: Create preview branch
145
+ id: preview
146
+ run: |
147
+ result=$(pnpm run tinybird:preview --json 2>&1)
148
+ echo "$result" | jq -r '"branch_name=" + .branch.name' >> $GITHUB_OUTPUT
149
+ echo "$result" | jq -r '"branch_id=" + .branch.id' >> $GITHUB_OUTPUT
150
+ token=$(echo "$result" | jq -r '.branch.token')
151
+ echo "::add-mask::$token"
152
+ echo "branch_token=$token" >> $GITHUB_OUTPUT
153
+ echo "$result" | jq -r '"branch_url=" + .branch.url' >> $GITHUB_OUTPUT
140
154
  env:
141
155
  TINYBIRD_TOKEN: \${{ secrets.TINYBIRD_TOKEN }}
142
156
  `;
@@ -188,7 +202,15 @@ tinybird_ci:
188
202
  - corepack enable
189
203
  - pnpm install --frozen-lockfile
190
204
  - pnpm run tinybird:build
191
- - pnpm run tinybird:deploy -- --check
205
+ - |
206
+ result=$(pnpm run tinybird:preview --json 2>&1)
207
+ echo "TINYBIRD_BRANCH_NAME=$(echo "$result" | jq -r '.branch.name')" >> tinybird.env
208
+ echo "TINYBIRD_BRANCH_ID=$(echo "$result" | jq -r '.branch.id')" >> tinybird.env
209
+ echo "TINYBIRD_BRANCH_TOKEN=$(echo "$result" | jq -r '.branch.token')" >> tinybird.env
210
+ echo "TINYBIRD_BRANCH_URL=$(echo "$result" | jq -r '.branch.url')" >> tinybird.env
211
+ artifacts:
212
+ reports:
213
+ dotenv: tinybird.env
192
214
  variables:
193
215
  TINYBIRD_TOKEN: \${TINYBIRD_TOKEN}
194
216
  `;
@@ -0,0 +1,42 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+ import { generatePreviewBranchName } from "./preview.js";
3
+
4
+ describe("Preview command", () => {
5
+ describe("generatePreviewBranchName", () => {
6
+ beforeEach(() => {
7
+ vi.useFakeTimers();
8
+ vi.setSystemTime(new Date("2024-02-06T12:00:00Z"));
9
+ });
10
+
11
+ it("generates name with branch and timestamp", () => {
12
+ const result = generatePreviewBranchName("feature-branch");
13
+ expect(result).toBe("tmp_ci_feature_branch_1707220800");
14
+ });
15
+
16
+ it("sanitizes branch name with slashes", () => {
17
+ const result = generatePreviewBranchName("feature/add-login");
18
+ expect(result).toBe("tmp_ci_feature_add_login_1707220800");
19
+ });
20
+
21
+ it("sanitizes branch name with dots", () => {
22
+ const result = generatePreviewBranchName("release.1.0");
23
+ expect(result).toBe("tmp_ci_release_1_0_1707220800");
24
+ });
25
+
26
+ it("handles complex branch names", () => {
27
+ const result = generatePreviewBranchName("feature/JIRA-123/add-user-auth");
28
+ expect(result).toBe("tmp_ci_feature_JIRA_123_add_user_auth_1707220800");
29
+ });
30
+
31
+ it("uses 'unknown' when branch is null", () => {
32
+ const result = generatePreviewBranchName(null);
33
+ expect(result).toBe("tmp_ci_unknown_1707220800");
34
+ });
35
+
36
+ it("uses unix timestamp in seconds", () => {
37
+ // 1707220800 is 2024-02-06T12:00:00Z in seconds
38
+ const result = generatePreviewBranchName("test");
39
+ expect(result).toMatch(/_1707220800$/);
40
+ });
41
+ });
42
+ });
@@ -0,0 +1,313 @@
1
+ /**
2
+ * Preview command - creates ephemeral preview branch and deploys resources
3
+ */
4
+
5
+ import { loadConfig, LOCAL_BASE_URL, type ResolvedConfig, type DevMode } from "../config.js";
6
+ import { buildFromInclude, type BuildFromIncludeResult } from "../../generator/index.js";
7
+ import { createBranch, type TinybirdBranch } from "../../api/branches.js";
8
+ import { deployToMain } from "../../api/deploy.js";
9
+ import { buildToTinybird } from "../../api/build.js";
10
+ import {
11
+ getLocalTokens,
12
+ getOrCreateLocalWorkspace,
13
+ LocalNotRunningError,
14
+ } from "../../api/local.js";
15
+ import { sanitizeBranchName, getCurrentGitBranch } from "../git.js";
16
+ import type { BuildApiResult } from "../../api/build.js";
17
+
18
+ /**
19
+ * Preview command options
20
+ */
21
+ export interface PreviewCommandOptions {
22
+ /** Working directory (defaults to cwd) */
23
+ cwd?: string;
24
+ /** Skip pushing to API (just generate) */
25
+ dryRun?: boolean;
26
+ /** Validate deploy with Tinybird API without applying */
27
+ check?: boolean;
28
+ /** Override preview branch name */
29
+ name?: string;
30
+ /** Override the devMode from config */
31
+ devModeOverride?: DevMode;
32
+ }
33
+
34
+ /**
35
+ * Preview command result
36
+ */
37
+ export interface PreviewCommandResult {
38
+ /** Whether the preview was successful */
39
+ success: boolean;
40
+ /** Branch information */
41
+ branch?: {
42
+ name: string;
43
+ id: string;
44
+ token: string;
45
+ url: string;
46
+ created_at: string;
47
+ };
48
+ /** Build statistics */
49
+ build?: {
50
+ datasourceCount: number;
51
+ pipeCount: number;
52
+ };
53
+ /** Deploy result */
54
+ deploy?: {
55
+ result: string;
56
+ };
57
+ /** Error message if failed */
58
+ error?: string;
59
+ /** Duration in milliseconds */
60
+ durationMs: number;
61
+ }
62
+
63
+ /**
64
+ * Generate preview branch name with format: tmp_ci_${branch}_${timestamp}
65
+ *
66
+ * @param gitBranch - Current git branch name (or null)
67
+ * @returns Preview branch name
68
+ */
69
+ export function generatePreviewBranchName(gitBranch: string | null): string {
70
+ const timestamp = Math.floor(Date.now() / 1000);
71
+ const branchPart = gitBranch ? sanitizeBranchName(gitBranch) : "unknown";
72
+ return `tmp_ci_${branchPart}_${timestamp}`;
73
+ }
74
+
75
+ /**
76
+ * Run the preview command
77
+ *
78
+ * Creates an ephemeral preview branch and deploys resources to it.
79
+ * Preview branches are not cached and are meant for CI/testing.
80
+ *
81
+ * @param options - Preview options
82
+ * @returns Preview command result
83
+ */
84
+ export async function runPreview(options: PreviewCommandOptions = {}): Promise<PreviewCommandResult> {
85
+ const startTime = Date.now();
86
+ const cwd = options.cwd ?? process.cwd();
87
+
88
+ // Load config
89
+ let config: ResolvedConfig;
90
+ try {
91
+ config = loadConfig(cwd);
92
+ } catch (error) {
93
+ return {
94
+ success: false,
95
+ error: (error as Error).message,
96
+ durationMs: Date.now() - startTime,
97
+ };
98
+ }
99
+
100
+ // Get current git branch and generate preview branch name
101
+ const gitBranch = getCurrentGitBranch();
102
+ const previewBranchName = options.name ?? generatePreviewBranchName(gitBranch);
103
+
104
+ // Build resources from include paths
105
+ let buildResult: BuildFromIncludeResult;
106
+ try {
107
+ buildResult = await buildFromInclude({
108
+ includePaths: config.include,
109
+ cwd: config.cwd,
110
+ });
111
+ } catch (error) {
112
+ return {
113
+ success: false,
114
+ error: `Build failed: ${(error as Error).message}`,
115
+ durationMs: Date.now() - startTime,
116
+ };
117
+ }
118
+
119
+ const buildStats = {
120
+ datasourceCount: buildResult.stats.datasourceCount,
121
+ pipeCount: buildResult.stats.pipeCount,
122
+ };
123
+
124
+ // If dry run, return without creating branch or deploying
125
+ if (options.dryRun) {
126
+ return {
127
+ success: true,
128
+ branch: {
129
+ name: previewBranchName,
130
+ id: "(dry-run)",
131
+ token: "(dry-run)",
132
+ url: config.baseUrl,
133
+ created_at: new Date().toISOString(),
134
+ },
135
+ build: buildStats,
136
+ durationMs: Date.now() - startTime,
137
+ };
138
+ }
139
+
140
+ const debug = !!process.env.TINYBIRD_DEBUG;
141
+ const devMode = options.devModeOverride ?? config.devMode;
142
+
143
+ if (debug) {
144
+ console.log(`[debug] devMode: ${devMode}`);
145
+ console.log(`[debug] previewBranchName: ${previewBranchName}`);
146
+ }
147
+
148
+ // Handle local mode
149
+ if (devMode === "local") {
150
+ try {
151
+ if (debug) {
152
+ console.log(`[debug] Getting local tokens from ${LOCAL_BASE_URL}/tokens`);
153
+ }
154
+
155
+ const localTokens = await getLocalTokens();
156
+
157
+ // Create workspace with preview branch name
158
+ if (debug) {
159
+ console.log(`[debug] Creating local workspace: ${previewBranchName}`);
160
+ }
161
+
162
+ const { workspace, wasCreated } = await getOrCreateLocalWorkspace(localTokens, previewBranchName);
163
+ if (debug) {
164
+ console.log(`[debug] Workspace ${wasCreated ? "created" : "found"}: ${workspace.name}`);
165
+ }
166
+
167
+ // Use /v1/build for local (no deploy endpoint in local)
168
+ const deployResult = await buildToTinybird(
169
+ {
170
+ baseUrl: LOCAL_BASE_URL,
171
+ token: workspace.token,
172
+ },
173
+ buildResult.resources
174
+ );
175
+
176
+ if (!deployResult.success) {
177
+ return {
178
+ success: false,
179
+ branch: {
180
+ name: previewBranchName,
181
+ id: workspace.id,
182
+ token: workspace.token,
183
+ url: LOCAL_BASE_URL,
184
+ created_at: new Date().toISOString(),
185
+ },
186
+ build: buildStats,
187
+ error: deployResult.error,
188
+ durationMs: Date.now() - startTime,
189
+ };
190
+ }
191
+
192
+ return {
193
+ success: true,
194
+ branch: {
195
+ name: previewBranchName,
196
+ id: workspace.id,
197
+ token: workspace.token,
198
+ url: LOCAL_BASE_URL,
199
+ created_at: new Date().toISOString(),
200
+ },
201
+ build: buildStats,
202
+ deploy: {
203
+ result: deployResult.result,
204
+ },
205
+ durationMs: Date.now() - startTime,
206
+ };
207
+ } catch (error) {
208
+ if (error instanceof LocalNotRunningError) {
209
+ return {
210
+ success: false,
211
+ error: error.message,
212
+ durationMs: Date.now() - startTime,
213
+ };
214
+ }
215
+ return {
216
+ success: false,
217
+ error: `Local preview failed: ${(error as Error).message}`,
218
+ durationMs: Date.now() - startTime,
219
+ };
220
+ }
221
+ }
222
+
223
+ // Cloud mode - create branch and deploy using /v1/deploy
224
+ let branch: TinybirdBranch;
225
+ try {
226
+ if (debug) {
227
+ console.log(`[debug] Creating preview branch: ${previewBranchName}`);
228
+ }
229
+
230
+ branch = await createBranch(
231
+ { baseUrl: config.baseUrl, token: config.token },
232
+ previewBranchName
233
+ );
234
+
235
+ if (debug) {
236
+ console.log(`[debug] Branch created: ${branch.name} (${branch.id})`);
237
+ }
238
+ } catch (error) {
239
+ return {
240
+ success: false,
241
+ error: `Failed to create preview branch: ${(error as Error).message}`,
242
+ durationMs: Date.now() - startTime,
243
+ };
244
+ }
245
+
246
+ if (!branch.token) {
247
+ return {
248
+ success: false,
249
+ error: `Preview branch created but no token returned`,
250
+ durationMs: Date.now() - startTime,
251
+ };
252
+ }
253
+
254
+ // Deploy to branch using /v1/deploy (production-like experience)
255
+ let deployResult: BuildApiResult;
256
+ try {
257
+ if (debug) {
258
+ console.log(`[debug] Deploying to preview branch using branch token`);
259
+ }
260
+
261
+ deployResult = await deployToMain(
262
+ { baseUrl: config.baseUrl, token: branch.token },
263
+ buildResult.resources,
264
+ { check: options.check, allowDestructiveOperations: true }
265
+ );
266
+ } catch (error) {
267
+ return {
268
+ success: false,
269
+ branch: {
270
+ name: branch.name,
271
+ id: branch.id,
272
+ token: branch.token,
273
+ url: config.baseUrl,
274
+ created_at: branch.created_at,
275
+ },
276
+ build: buildStats,
277
+ error: `Deploy failed: ${(error as Error).message}`,
278
+ durationMs: Date.now() - startTime,
279
+ };
280
+ }
281
+
282
+ if (!deployResult.success) {
283
+ return {
284
+ success: false,
285
+ branch: {
286
+ name: branch.name,
287
+ id: branch.id,
288
+ token: branch.token,
289
+ url: config.baseUrl,
290
+ created_at: branch.created_at,
291
+ },
292
+ build: buildStats,
293
+ error: deployResult.error,
294
+ durationMs: Date.now() - startTime,
295
+ };
296
+ }
297
+
298
+ return {
299
+ success: true,
300
+ branch: {
301
+ name: branch.name,
302
+ id: branch.id,
303
+ token: branch.token,
304
+ url: config.baseUrl,
305
+ created_at: branch.created_at,
306
+ },
307
+ build: buildStats,
308
+ deploy: {
309
+ result: deployResult.result,
310
+ },
311
+ durationMs: Date.now() - startTime,
312
+ };
313
+ }