@tinybirdco/sdk 0.0.29 → 0.0.31

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 (95) hide show
  1. package/README.md +44 -4
  2. package/dist/cli/commands/branch.js +5 -5
  3. package/dist/cli/commands/branch.js.map +1 -1
  4. package/dist/cli/commands/build.d.ts.map +1 -1
  5. package/dist/cli/commands/build.js +2 -2
  6. package/dist/cli/commands/build.js.map +1 -1
  7. package/dist/cli/commands/build.test.d.ts +2 -0
  8. package/dist/cli/commands/build.test.d.ts.map +1 -0
  9. package/dist/cli/commands/build.test.js +266 -0
  10. package/dist/cli/commands/build.test.js.map +1 -0
  11. package/dist/cli/commands/clear.d.ts.map +1 -1
  12. package/dist/cli/commands/clear.js +2 -2
  13. package/dist/cli/commands/clear.js.map +1 -1
  14. package/dist/cli/commands/deploy.js +2 -2
  15. package/dist/cli/commands/deploy.js.map +1 -1
  16. package/dist/cli/commands/dev.js +12 -12
  17. package/dist/cli/commands/dev.js.map +1 -1
  18. package/dist/cli/commands/info.d.ts +104 -0
  19. package/dist/cli/commands/info.d.ts.map +1 -0
  20. package/dist/cli/commands/info.js +140 -0
  21. package/dist/cli/commands/info.js.map +1 -0
  22. package/dist/cli/commands/info.test.d.ts +2 -0
  23. package/dist/cli/commands/info.test.d.ts.map +1 -0
  24. package/dist/cli/commands/info.test.js +336 -0
  25. package/dist/cli/commands/info.test.js.map +1 -0
  26. package/dist/cli/commands/init.d.ts.map +1 -1
  27. package/dist/cli/commands/init.js +44 -26
  28. package/dist/cli/commands/init.js.map +1 -1
  29. package/dist/cli/commands/init.test.js +44 -25
  30. package/dist/cli/commands/init.test.js.map +1 -1
  31. package/dist/cli/commands/login.d.ts.map +1 -1
  32. package/dist/cli/commands/login.js +7 -6
  33. package/dist/cli/commands/login.js.map +1 -1
  34. package/dist/cli/commands/login.test.js +1 -1
  35. package/dist/cli/commands/login.test.js.map +1 -1
  36. package/dist/cli/commands/open-dashboard.d.ts +39 -0
  37. package/dist/cli/commands/open-dashboard.d.ts.map +1 -0
  38. package/dist/cli/commands/open-dashboard.js +127 -0
  39. package/dist/cli/commands/open-dashboard.js.map +1 -0
  40. package/dist/cli/commands/open-dashboard.test.d.ts +2 -0
  41. package/dist/cli/commands/open-dashboard.test.d.ts.map +1 -0
  42. package/dist/cli/commands/open-dashboard.test.js +373 -0
  43. package/dist/cli/commands/open-dashboard.test.js.map +1 -0
  44. package/dist/cli/commands/preview.d.ts.map +1 -1
  45. package/dist/cli/commands/preview.js +2 -2
  46. package/dist/cli/commands/preview.js.map +1 -1
  47. package/dist/cli/config-loader.d.ts +18 -0
  48. package/dist/cli/config-loader.d.ts.map +1 -0
  49. package/dist/cli/config-loader.js +57 -0
  50. package/dist/cli/config-loader.js.map +1 -0
  51. package/dist/cli/config-types.d.ts +28 -0
  52. package/dist/cli/config-types.d.ts.map +1 -0
  53. package/dist/cli/config-types.js +8 -0
  54. package/dist/cli/config-types.js.map +1 -0
  55. package/dist/cli/config.d.ts +63 -29
  56. package/dist/cli/config.d.ts.map +1 -1
  57. package/dist/cli/config.js +139 -43
  58. package/dist/cli/config.js.map +1 -1
  59. package/dist/cli/config.test.js +73 -9
  60. package/dist/cli/config.test.js.map +1 -1
  61. package/dist/cli/index.js +64 -0
  62. package/dist/cli/index.js.map +1 -1
  63. package/dist/cli/output.d.ts +40 -0
  64. package/dist/cli/output.d.ts.map +1 -1
  65. package/dist/cli/output.js +74 -0
  66. package/dist/cli/output.js.map +1 -1
  67. package/dist/client/base.d.ts.map +1 -1
  68. package/dist/client/base.js +21 -9
  69. package/dist/client/base.js.map +1 -1
  70. package/dist/index.d.ts +1 -0
  71. package/dist/index.d.ts.map +1 -1
  72. package/package.json +5 -1
  73. package/src/cli/commands/branch.ts +5 -5
  74. package/src/cli/commands/build.test.ts +310 -0
  75. package/src/cli/commands/build.ts +2 -2
  76. package/src/cli/commands/clear.ts +2 -2
  77. package/src/cli/commands/deploy.ts +2 -2
  78. package/src/cli/commands/dev.ts +12 -12
  79. package/src/cli/commands/info.test.ts +398 -0
  80. package/src/cli/commands/info.ts +253 -0
  81. package/src/cli/commands/init.test.ts +53 -37
  82. package/src/cli/commands/init.ts +49 -30
  83. package/src/cli/commands/login.test.ts +1 -1
  84. package/src/cli/commands/login.ts +7 -6
  85. package/src/cli/commands/open-dashboard.test.ts +472 -0
  86. package/src/cli/commands/open-dashboard.ts +177 -0
  87. package/src/cli/commands/preview.ts +2 -2
  88. package/src/cli/config-loader.ts +87 -0
  89. package/src/cli/config-types.ts +29 -0
  90. package/src/cli/config.test.ts +95 -8
  91. package/src/cli/config.ts +179 -70
  92. package/src/cli/index.ts +73 -0
  93. package/src/cli/output.ts +111 -0
  94. package/src/client/base.ts +33 -16
  95. package/src/index.ts +4 -0
@@ -0,0 +1,472 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+ import { runOpenDashboard } from "./open-dashboard.js";
3
+
4
+ // Mock the config module
5
+ vi.mock("../config.js", () => ({
6
+ loadConfig: vi.fn(),
7
+ }));
8
+
9
+ // Mock the API modules
10
+ vi.mock("../../api/workspaces.js", () => ({
11
+ getWorkspace: vi.fn(),
12
+ }));
13
+
14
+ vi.mock("../../api/branches.js", () => ({
15
+ getBranch: vi.fn(),
16
+ }));
17
+
18
+ vi.mock("../../api/dashboard.js", () => ({
19
+ getDashboardUrl: vi.fn(),
20
+ getBranchDashboardUrl: vi.fn(),
21
+ getLocalDashboardUrl: vi.fn(),
22
+ }));
23
+
24
+ vi.mock("../../api/local.js", () => ({
25
+ isLocalRunning: vi.fn(),
26
+ getLocalTokens: vi.fn(),
27
+ getOrCreateLocalWorkspace: vi.fn(),
28
+ getLocalWorkspaceName: vi.fn(),
29
+ }));
30
+
31
+ vi.mock("../auth.js", () => ({
32
+ openBrowser: vi.fn(),
33
+ }));
34
+
35
+ // Import mocked functions
36
+ import { loadConfig } from "../config.js";
37
+ import { getWorkspace } from "../../api/workspaces.js";
38
+ import { getBranch } from "../../api/branches.js";
39
+ import {
40
+ getDashboardUrl,
41
+ getBranchDashboardUrl,
42
+ getLocalDashboardUrl,
43
+ } from "../../api/dashboard.js";
44
+ import {
45
+ isLocalRunning,
46
+ getLocalTokens,
47
+ getOrCreateLocalWorkspace,
48
+ getLocalWorkspaceName,
49
+ } from "../../api/local.js";
50
+ import { openBrowser } from "../auth.js";
51
+
52
+ const mockedLoadConfig = vi.mocked(loadConfig);
53
+ const mockedGetWorkspace = vi.mocked(getWorkspace);
54
+ const mockedGetBranch = vi.mocked(getBranch);
55
+ const mockedGetDashboardUrl = vi.mocked(getDashboardUrl);
56
+ const mockedGetBranchDashboardUrl = vi.mocked(getBranchDashboardUrl);
57
+ const mockedGetLocalDashboardUrl = vi.mocked(getLocalDashboardUrl);
58
+ const mockedIsLocalRunning = vi.mocked(isLocalRunning);
59
+ const mockedGetLocalTokens = vi.mocked(getLocalTokens);
60
+ const mockedGetOrCreateLocalWorkspace = vi.mocked(getOrCreateLocalWorkspace);
61
+ const mockedGetLocalWorkspaceName = vi.mocked(getLocalWorkspaceName);
62
+ const mockedOpenBrowser = vi.mocked(openBrowser);
63
+
64
+ describe("Open Dashboard Command", () => {
65
+ beforeEach(() => {
66
+ vi.clearAllMocks();
67
+ mockedOpenBrowser.mockResolvedValue(true);
68
+ });
69
+
70
+ describe("config loading", () => {
71
+ it("returns error when config loading fails", async () => {
72
+ mockedLoadConfig.mockImplementation(() => {
73
+ throw new Error("No tinybird.json found");
74
+ });
75
+
76
+ const result = await runOpenDashboard();
77
+
78
+ expect(result.success).toBe(false);
79
+ expect(result.error).toContain("No tinybird.json found");
80
+ });
81
+ });
82
+
83
+ describe("cloud environment", () => {
84
+ const mockConfig = {
85
+ cwd: "/test/project",
86
+ configPath: "/test/project/tinybird.json",
87
+ devMode: "branch" as const,
88
+ gitBranch: "main",
89
+ tinybirdBranch: null,
90
+ isMainBranch: true,
91
+ baseUrl: "https://api.tinybird.co",
92
+ token: "test-token",
93
+ include: [],
94
+ };
95
+
96
+ const mockWorkspace = {
97
+ id: "ws-123",
98
+ name: "test-workspace",
99
+ user_email: "user@example.com",
100
+ user_id: "user-123",
101
+ scope: "WORKSPACE",
102
+ main: null,
103
+ };
104
+
105
+ beforeEach(() => {
106
+ mockedLoadConfig.mockReturnValue(mockConfig);
107
+ mockedGetWorkspace.mockResolvedValue(mockWorkspace);
108
+ mockedGetDashboardUrl.mockReturnValue(
109
+ "https://cloud.tinybird.co/gcp/europe-west3/test-workspace"
110
+ );
111
+ });
112
+
113
+ it("opens cloud dashboard when environment is cloud", async () => {
114
+ const result = await runOpenDashboard({ environment: "cloud" });
115
+
116
+ expect(result.success).toBe(true);
117
+ expect(result.environment).toBe("cloud");
118
+ expect(result.url).toBe(
119
+ "https://cloud.tinybird.co/gcp/europe-west3/test-workspace"
120
+ );
121
+ expect(result.browserOpened).toBe(true);
122
+ expect(mockedOpenBrowser).toHaveBeenCalledWith(
123
+ "https://cloud.tinybird.co/gcp/europe-west3/test-workspace"
124
+ );
125
+ });
126
+
127
+ it("defaults to cloud when devMode is branch and on main branch", async () => {
128
+ const result = await runOpenDashboard();
129
+
130
+ expect(result.success).toBe(true);
131
+ expect(result.environment).toBe("cloud");
132
+ });
133
+ });
134
+
135
+ describe("branch environment", () => {
136
+ const mockConfig = {
137
+ cwd: "/test/project",
138
+ configPath: "/test/project/tinybird.json",
139
+ devMode: "branch" as const,
140
+ gitBranch: "feature/test",
141
+ tinybirdBranch: "feature_test",
142
+ isMainBranch: false,
143
+ baseUrl: "https://api.tinybird.co",
144
+ token: "test-token",
145
+ include: [],
146
+ };
147
+
148
+ const mockWorkspace = {
149
+ id: "ws-123",
150
+ name: "test-workspace",
151
+ user_email: "user@example.com",
152
+ user_id: "user-123",
153
+ scope: "WORKSPACE",
154
+ main: null,
155
+ };
156
+
157
+ beforeEach(() => {
158
+ mockedLoadConfig.mockReturnValue(mockConfig);
159
+ mockedGetWorkspace.mockResolvedValue(mockWorkspace);
160
+ });
161
+
162
+ it("opens branch dashboard when environment is branch", async () => {
163
+ const mockBranch = {
164
+ id: "branch-123",
165
+ name: "feature_test",
166
+ token: "branch-token",
167
+ created_at: "2024-01-01",
168
+ };
169
+ mockedGetBranch.mockResolvedValue(mockBranch);
170
+ mockedGetBranchDashboardUrl.mockReturnValue(
171
+ "https://cloud.tinybird.co/gcp/europe-west3/test-workspace~feature_test"
172
+ );
173
+
174
+ const result = await runOpenDashboard({ environment: "branch" });
175
+
176
+ expect(result.success).toBe(true);
177
+ expect(result.environment).toBe("branch");
178
+ expect(result.url).toBe(
179
+ "https://cloud.tinybird.co/gcp/europe-west3/test-workspace~feature_test"
180
+ );
181
+ expect(mockedGetBranch).toHaveBeenCalledWith(
182
+ { baseUrl: "https://api.tinybird.co", token: "test-token" },
183
+ "feature_test"
184
+ );
185
+ });
186
+
187
+ it("defaults to branch when devMode is branch and on feature branch", async () => {
188
+ const mockBranch = {
189
+ id: "branch-123",
190
+ name: "feature_test",
191
+ token: "branch-token",
192
+ created_at: "2024-01-01",
193
+ };
194
+ mockedGetBranch.mockResolvedValue(mockBranch);
195
+ mockedGetBranchDashboardUrl.mockReturnValue(
196
+ "https://cloud.tinybird.co/gcp/europe-west3/test-workspace~feature_test"
197
+ );
198
+
199
+ const result = await runOpenDashboard();
200
+
201
+ expect(result.success).toBe(true);
202
+ expect(result.environment).toBe("branch");
203
+ });
204
+
205
+ it("returns error when trying to open branch on main", async () => {
206
+ mockedLoadConfig.mockReturnValue({
207
+ ...mockConfig,
208
+ gitBranch: "main",
209
+ tinybirdBranch: null,
210
+ isMainBranch: true,
211
+ });
212
+
213
+ const result = await runOpenDashboard({ environment: "branch" });
214
+
215
+ expect(result.success).toBe(false);
216
+ expect(result.error).toContain("not on a feature branch");
217
+ });
218
+
219
+ it("returns error when branch does not exist", async () => {
220
+ mockedGetBranch.mockRejectedValue(new Error("Branch not found"));
221
+
222
+ const result = await runOpenDashboard({ environment: "branch" });
223
+
224
+ expect(result.success).toBe(false);
225
+ expect(result.error).toContain("does not exist");
226
+ expect(result.error).toContain("tinybird build");
227
+ });
228
+ });
229
+
230
+ describe("local environment", () => {
231
+ const mockConfig = {
232
+ cwd: "/test/project",
233
+ configPath: "/test/project/tinybird.json",
234
+ devMode: "local" as const,
235
+ gitBranch: "feature/test",
236
+ tinybirdBranch: "feature_test",
237
+ isMainBranch: false,
238
+ baseUrl: "https://api.tinybird.co",
239
+ token: "test-token",
240
+ include: [],
241
+ };
242
+
243
+ const mockWorkspace = {
244
+ id: "ws-123",
245
+ name: "test-workspace",
246
+ user_email: "user@example.com",
247
+ user_id: "user-123",
248
+ scope: "WORKSPACE",
249
+ main: null,
250
+ };
251
+
252
+ beforeEach(() => {
253
+ mockedLoadConfig.mockReturnValue(mockConfig);
254
+ mockedGetWorkspace.mockResolvedValue(mockWorkspace);
255
+ });
256
+
257
+ it("opens local dashboard when environment is local and local is running", async () => {
258
+ mockedIsLocalRunning.mockResolvedValue(true);
259
+ mockedGetLocalTokens.mockResolvedValue({
260
+ user_token: "local-user-token",
261
+ admin_token: "local-admin-token",
262
+ workspace_admin_token: "local-ws-admin-token",
263
+ });
264
+ mockedGetLocalWorkspaceName.mockReturnValue("feature_test");
265
+ mockedGetOrCreateLocalWorkspace.mockResolvedValue({
266
+ workspace: {
267
+ id: "local-ws-123",
268
+ name: "feature_test",
269
+ token: "local-token",
270
+ },
271
+ wasCreated: false,
272
+ });
273
+ mockedGetLocalDashboardUrl.mockReturnValue(
274
+ "https://cloud.tinybird.co/local/7181/feature_test"
275
+ );
276
+
277
+ const result = await runOpenDashboard({ environment: "local" });
278
+
279
+ expect(result.success).toBe(true);
280
+ expect(result.environment).toBe("local");
281
+ expect(result.url).toBe(
282
+ "https://cloud.tinybird.co/local/7181/feature_test"
283
+ );
284
+ });
285
+
286
+ it("defaults to local when devMode is local", async () => {
287
+ mockedIsLocalRunning.mockResolvedValue(true);
288
+ mockedGetLocalTokens.mockResolvedValue({
289
+ user_token: "local-user-token",
290
+ admin_token: "local-admin-token",
291
+ workspace_admin_token: "local-ws-admin-token",
292
+ });
293
+ mockedGetLocalWorkspaceName.mockReturnValue("feature_test");
294
+ mockedGetOrCreateLocalWorkspace.mockResolvedValue({
295
+ workspace: {
296
+ id: "local-ws-123",
297
+ name: "feature_test",
298
+ token: "local-token",
299
+ },
300
+ wasCreated: false,
301
+ });
302
+ mockedGetLocalDashboardUrl.mockReturnValue(
303
+ "https://cloud.tinybird.co/local/7181/feature_test"
304
+ );
305
+
306
+ const result = await runOpenDashboard();
307
+
308
+ expect(result.success).toBe(true);
309
+ expect(result.environment).toBe("local");
310
+ });
311
+
312
+ it("returns error when local is not running", async () => {
313
+ mockedIsLocalRunning.mockResolvedValue(false);
314
+
315
+ const result = await runOpenDashboard({ environment: "local" });
316
+
317
+ expect(result.success).toBe(false);
318
+ expect(result.error).toContain("not running");
319
+ expect(result.error).toContain("docker run");
320
+ });
321
+
322
+ it("uses workspace name on main branch for local", async () => {
323
+ mockedLoadConfig.mockReturnValue({
324
+ ...mockConfig,
325
+ gitBranch: "main",
326
+ tinybirdBranch: null,
327
+ isMainBranch: true,
328
+ });
329
+ mockedIsLocalRunning.mockResolvedValue(true);
330
+ mockedGetLocalTokens.mockResolvedValue({
331
+ user_token: "local-user-token",
332
+ admin_token: "local-admin-token",
333
+ workspace_admin_token: "local-ws-admin-token",
334
+ });
335
+ mockedGetOrCreateLocalWorkspace.mockResolvedValue({
336
+ workspace: {
337
+ id: "local-ws-123",
338
+ name: "test-workspace",
339
+ token: "local-token",
340
+ },
341
+ wasCreated: false,
342
+ });
343
+ mockedGetLocalDashboardUrl.mockReturnValue(
344
+ "https://cloud.tinybird.co/local/7181/test-workspace"
345
+ );
346
+
347
+ const result = await runOpenDashboard({ environment: "local" });
348
+
349
+ expect(result.success).toBe(true);
350
+ expect(mockedGetOrCreateLocalWorkspace).toHaveBeenCalledWith(
351
+ expect.anything(),
352
+ "test-workspace"
353
+ );
354
+ });
355
+ });
356
+
357
+ describe("error handling", () => {
358
+ it("returns error when workspace fetch fails", async () => {
359
+ mockedLoadConfig.mockReturnValue({
360
+ cwd: "/test",
361
+ configPath: "/test/tinybird.json",
362
+ devMode: "branch" as const,
363
+ gitBranch: null,
364
+ tinybirdBranch: null,
365
+ isMainBranch: true,
366
+ baseUrl: "https://api.tinybird.co",
367
+ token: "test-token",
368
+ include: [],
369
+ });
370
+ mockedGetWorkspace.mockRejectedValue(new Error("Unauthorized"));
371
+
372
+ const result = await runOpenDashboard({ environment: "cloud" });
373
+
374
+ expect(result.success).toBe(false);
375
+ expect(result.error).toContain("Failed to get workspace info");
376
+ expect(result.error).toContain("Unauthorized");
377
+ });
378
+
379
+ it("returns error when dashboard URL cannot be generated", async () => {
380
+ mockedLoadConfig.mockReturnValue({
381
+ cwd: "/test",
382
+ configPath: "/test/tinybird.json",
383
+ devMode: "branch" as const,
384
+ gitBranch: null,
385
+ tinybirdBranch: null,
386
+ isMainBranch: true,
387
+ baseUrl: "https://api.tinybird.co",
388
+ token: "test-token",
389
+ include: [],
390
+ });
391
+ mockedGetWorkspace.mockResolvedValue({
392
+ id: "ws-123",
393
+ name: "test-workspace",
394
+ user_email: "user@example.com",
395
+ user_id: "user-123",
396
+ scope: "WORKSPACE",
397
+ main: null,
398
+ });
399
+ mockedGetDashboardUrl.mockReturnValue(null);
400
+
401
+ const result = await runOpenDashboard({ environment: "cloud" });
402
+
403
+ expect(result.success).toBe(false);
404
+ expect(result.error).toContain("Could not generate dashboard URL");
405
+ });
406
+
407
+ it("handles local workspace fetch error", async () => {
408
+ mockedLoadConfig.mockReturnValue({
409
+ cwd: "/test",
410
+ configPath: "/test/tinybird.json",
411
+ devMode: "local" as const,
412
+ gitBranch: "feature/test",
413
+ tinybirdBranch: "feature_test",
414
+ isMainBranch: false,
415
+ baseUrl: "https://api.tinybird.co",
416
+ token: "test-token",
417
+ include: [],
418
+ });
419
+ mockedGetWorkspace.mockResolvedValue({
420
+ id: "ws-123",
421
+ name: "test-workspace",
422
+ user_email: "user@example.com",
423
+ user_id: "user-123",
424
+ scope: "WORKSPACE",
425
+ main: null,
426
+ });
427
+ mockedIsLocalRunning.mockResolvedValue(true);
428
+ mockedGetLocalTokens.mockRejectedValue(new Error("Connection refused"));
429
+
430
+ const result = await runOpenDashboard({ environment: "local" });
431
+
432
+ expect(result.success).toBe(false);
433
+ expect(result.error).toContain("Failed to get local workspace");
434
+ });
435
+ });
436
+
437
+ describe("browser opening", () => {
438
+ it("reports when browser failed to open", async () => {
439
+ mockedLoadConfig.mockReturnValue({
440
+ cwd: "/test",
441
+ configPath: "/test/tinybird.json",
442
+ devMode: "branch" as const,
443
+ gitBranch: null,
444
+ tinybirdBranch: null,
445
+ isMainBranch: true,
446
+ baseUrl: "https://api.tinybird.co",
447
+ token: "test-token",
448
+ include: [],
449
+ });
450
+ mockedGetWorkspace.mockResolvedValue({
451
+ id: "ws-123",
452
+ name: "test-workspace",
453
+ user_email: "user@example.com",
454
+ user_id: "user-123",
455
+ scope: "WORKSPACE",
456
+ main: null,
457
+ });
458
+ mockedGetDashboardUrl.mockReturnValue(
459
+ "https://cloud.tinybird.co/gcp/europe-west3/test-workspace"
460
+ );
461
+ mockedOpenBrowser.mockResolvedValue(false);
462
+
463
+ const result = await runOpenDashboard({ environment: "cloud" });
464
+
465
+ expect(result.success).toBe(true);
466
+ expect(result.browserOpened).toBe(false);
467
+ expect(result.url).toBe(
468
+ "https://cloud.tinybird.co/gcp/europe-west3/test-workspace"
469
+ );
470
+ });
471
+ });
472
+ });
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Open Dashboard command - opens the Tinybird dashboard in the default browser
3
+ */
4
+
5
+ import { loadConfig, type ResolvedConfig } from "../config.js";
6
+ import { getWorkspace } from "../../api/workspaces.js";
7
+ import { getBranch } from "../../api/branches.js";
8
+ import {
9
+ getDashboardUrl,
10
+ getBranchDashboardUrl,
11
+ getLocalDashboardUrl,
12
+ } from "../../api/dashboard.js";
13
+ import {
14
+ isLocalRunning,
15
+ getLocalTokens,
16
+ getOrCreateLocalWorkspace,
17
+ getLocalWorkspaceName,
18
+ } from "../../api/local.js";
19
+ import { openBrowser } from "../auth.js";
20
+
21
+ /**
22
+ * Environment options for opening dashboard
23
+ */
24
+ export type Environment = "cloud" | "local" | "branch";
25
+
26
+ /**
27
+ * Open dashboard command options
28
+ */
29
+ export interface OpenDashboardCommandOptions {
30
+ /** Working directory (defaults to cwd) */
31
+ cwd?: string;
32
+ /** Which environment to open: "cloud", "local", or "branch" */
33
+ environment?: Environment;
34
+ }
35
+
36
+ /**
37
+ * Result of the open dashboard command
38
+ */
39
+ export interface OpenDashboardCommandResult {
40
+ /** Whether the operation was successful */
41
+ success: boolean;
42
+ /** The URL that was opened */
43
+ url?: string;
44
+ /** Which environment was opened */
45
+ environment?: Environment;
46
+ /** Whether the browser was opened */
47
+ browserOpened?: boolean;
48
+ /** Error message if failed */
49
+ error?: string;
50
+ }
51
+
52
+ /**
53
+ * Run the open dashboard command
54
+ *
55
+ * @param options - Command options
56
+ * @returns Result with URL and status
57
+ */
58
+ export async function runOpenDashboard(
59
+ options: OpenDashboardCommandOptions = {}
60
+ ): Promise<OpenDashboardCommandResult> {
61
+ const cwd = options.cwd ?? process.cwd();
62
+
63
+ // Load config
64
+ let config: ResolvedConfig;
65
+ try {
66
+ config = loadConfig(cwd);
67
+ } catch (error) {
68
+ return {
69
+ success: false,
70
+ error: (error as Error).message,
71
+ };
72
+ }
73
+
74
+ // Determine environment: use option if provided, otherwise use devMode from config
75
+ // When devMode is "branch" but on main branch, default to "cloud"
76
+ let environment: Environment;
77
+ if (options.environment) {
78
+ environment = options.environment;
79
+ } else if (config.devMode === "local") {
80
+ environment = "local";
81
+ } else if (config.devMode === "branch" && !config.isMainBranch && config.tinybirdBranch) {
82
+ environment = "branch";
83
+ } else {
84
+ environment = "cloud";
85
+ }
86
+
87
+ // Get workspace info (needed for all dashboard URLs)
88
+ let workspace;
89
+ try {
90
+ workspace = await getWorkspace({
91
+ baseUrl: config.baseUrl,
92
+ token: config.token,
93
+ });
94
+ } catch (error) {
95
+ return {
96
+ success: false,
97
+ error: `Failed to get workspace info: ${(error as Error).message}`,
98
+ };
99
+ }
100
+
101
+ let url: string | null = null;
102
+
103
+ if (environment === "local") {
104
+ // Open local dashboard
105
+ const localRunning = await isLocalRunning();
106
+ if (!localRunning) {
107
+ return {
108
+ success: false,
109
+ error:
110
+ "Tinybird local is not running. Start with: docker run -d -p 7181:7181 tinybirdco/tinybird-local",
111
+ };
112
+ }
113
+
114
+ try {
115
+ const tokens = await getLocalTokens();
116
+ // Determine workspace name: use authenticated workspace name on main branch,
117
+ // otherwise use branch name (for trunk-based development support)
118
+ let workspaceName: string;
119
+ if (config.isMainBranch || !config.tinybirdBranch) {
120
+ workspaceName = workspace.name;
121
+ } else {
122
+ workspaceName = getLocalWorkspaceName(config.tinybirdBranch, config.cwd);
123
+ }
124
+ const { workspace: localWorkspace } = await getOrCreateLocalWorkspace(
125
+ tokens,
126
+ workspaceName
127
+ );
128
+ url = getLocalDashboardUrl(localWorkspace.name);
129
+ } catch (error) {
130
+ return {
131
+ success: false,
132
+ error: `Failed to get local workspace: ${(error as Error).message}`,
133
+ };
134
+ }
135
+ } else if (environment === "branch") {
136
+ // Open branch dashboard
137
+ if (config.isMainBranch || !config.tinybirdBranch) {
138
+ return {
139
+ success: false,
140
+ error: "Cannot open branch dashboard: not on a feature branch.",
141
+ };
142
+ }
143
+
144
+ try {
145
+ const branch = await getBranch(
146
+ { baseUrl: config.baseUrl, token: config.token },
147
+ config.tinybirdBranch
148
+ );
149
+ url = getBranchDashboardUrl(config.baseUrl, workspace.name, branch.name);
150
+ } catch (error) {
151
+ return {
152
+ success: false,
153
+ error: `Branch '${config.tinybirdBranch}' does not exist. Run 'tinybird build' to create it.`,
154
+ };
155
+ }
156
+ } else {
157
+ // Open cloud (main workspace) dashboard
158
+ url = getDashboardUrl(config.baseUrl, workspace.name);
159
+ }
160
+
161
+ if (!url) {
162
+ return {
163
+ success: false,
164
+ error: "Could not generate dashboard URL for this configuration.",
165
+ };
166
+ }
167
+
168
+ // Open the browser
169
+ const browserOpened = await openBrowser(url);
170
+
171
+ return {
172
+ success: true,
173
+ url,
174
+ environment,
175
+ browserOpened,
176
+ };
177
+ }
@@ -2,7 +2,7 @@
2
2
  * Preview command - creates ephemeral preview branch and deploys resources
3
3
  */
4
4
 
5
- import { loadConfig, LOCAL_BASE_URL, type ResolvedConfig, type DevMode } from "../config.js";
5
+ import { loadConfigAsync, LOCAL_BASE_URL, type ResolvedConfig, type DevMode } from "../config.js";
6
6
  import { buildFromInclude, type BuildFromIncludeResult } from "../../generator/index.js";
7
7
  import { createBranch, deleteBranch, getBranch, type TinybirdBranch } from "../../api/branches.js";
8
8
  import { deployToMain } from "../../api/deploy.js";
@@ -90,7 +90,7 @@ export async function runPreview(options: PreviewCommandOptions = {}): Promise<P
90
90
  // Load config
91
91
  let config: ResolvedConfig;
92
92
  try {
93
- config = loadConfig(cwd);
93
+ config = await loadConfigAsync(cwd);
94
94
  } catch (error) {
95
95
  return {
96
96
  success: false,