@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,398 @@
1
+ import { describe, it, expect, beforeEach, vi } from "vitest";
2
+ import { runInfo } from "./info.js";
3
+
4
+ // Mock the config module
5
+ vi.mock("../config.js", () => ({
6
+ loadConfigAsync: vi.fn(),
7
+ LOCAL_BASE_URL: "http://localhost:7181",
8
+ }));
9
+
10
+ // Mock the API modules
11
+ vi.mock("../../api/workspaces.js", () => ({
12
+ getWorkspace: vi.fn(),
13
+ }));
14
+
15
+ vi.mock("../../api/branches.js", () => ({
16
+ listBranches: vi.fn(),
17
+ getBranch: vi.fn(),
18
+ }));
19
+
20
+ vi.mock("../../api/dashboard.js", () => ({
21
+ getDashboardUrl: vi.fn(),
22
+ getBranchDashboardUrl: vi.fn(),
23
+ getLocalDashboardUrl: vi.fn(),
24
+ }));
25
+
26
+ vi.mock("../../api/local.js", () => ({
27
+ isLocalRunning: vi.fn(),
28
+ getLocalTokens: vi.fn(),
29
+ getOrCreateLocalWorkspace: vi.fn(),
30
+ getLocalWorkspaceName: vi.fn(),
31
+ }));
32
+
33
+ // Import mocked functions
34
+ import { loadConfigAsync } from "../config.js";
35
+ import { getWorkspace } from "../../api/workspaces.js";
36
+ import { listBranches, getBranch } from "../../api/branches.js";
37
+ import { getDashboardUrl, getBranchDashboardUrl, getLocalDashboardUrl } from "../../api/dashboard.js";
38
+ import {
39
+ isLocalRunning,
40
+ getLocalTokens,
41
+ getOrCreateLocalWorkspace,
42
+ getLocalWorkspaceName,
43
+ } from "../../api/local.js";
44
+
45
+ const mockedLoadConfigAsync = vi.mocked(loadConfigAsync);
46
+ const mockedGetWorkspace = vi.mocked(getWorkspace);
47
+ const mockedListBranches = vi.mocked(listBranches);
48
+ const mockedGetBranch = vi.mocked(getBranch);
49
+ const mockedGetDashboardUrl = vi.mocked(getDashboardUrl);
50
+ const mockedGetBranchDashboardUrl = vi.mocked(getBranchDashboardUrl);
51
+ const mockedGetLocalDashboardUrl = vi.mocked(getLocalDashboardUrl);
52
+ const mockedIsLocalRunning = vi.mocked(isLocalRunning);
53
+ const mockedGetLocalTokens = vi.mocked(getLocalTokens);
54
+ const mockedGetOrCreateLocalWorkspace = vi.mocked(getOrCreateLocalWorkspace);
55
+ const mockedGetLocalWorkspaceName = vi.mocked(getLocalWorkspaceName);
56
+
57
+ describe("Info Command", () => {
58
+ beforeEach(() => {
59
+ vi.clearAllMocks();
60
+ });
61
+
62
+ describe("config loading", () => {
63
+ it("returns error when config loading fails", async () => {
64
+ mockedLoadConfigAsync.mockRejectedValue(new Error("No tinybird.json found"));
65
+
66
+ const result = await runInfo();
67
+
68
+ expect(result.success).toBe(false);
69
+ expect(result.error).toContain("No tinybird.json found");
70
+ });
71
+ });
72
+
73
+ describe("branch mode", () => {
74
+ const mockConfig = {
75
+ cwd: "/test/project",
76
+ configPath: "/test/project/tinybird.json",
77
+ devMode: "branch" as const,
78
+ gitBranch: "feature/test",
79
+ tinybirdBranch: "feature_test",
80
+ isMainBranch: false,
81
+ baseUrl: "https://api.tinybird.co",
82
+ token: "test-token",
83
+ include: [],
84
+ };
85
+
86
+ const mockWorkspace = {
87
+ id: "ws-123",
88
+ name: "test-workspace",
89
+ user_email: "user@example.com",
90
+ user_id: "user-123",
91
+ scope: "WORKSPACE",
92
+ main: null,
93
+ };
94
+
95
+ beforeEach(() => {
96
+ mockedLoadConfigAsync.mockResolvedValue(mockConfig);
97
+ mockedGetWorkspace.mockResolvedValue(mockWorkspace);
98
+ mockedListBranches.mockResolvedValue([]);
99
+ mockedGetDashboardUrl.mockReturnValue("https://cloud.tinybird.co/gcp/europe-west3/test-workspace");
100
+ });
101
+
102
+ it("returns cloud info in branch mode", async () => {
103
+ const result = await runInfo();
104
+
105
+ expect(result.success).toBe(true);
106
+ expect(result.cloud).toEqual({
107
+ workspaceName: "test-workspace",
108
+ workspaceId: "ws-123",
109
+ userEmail: "user@example.com",
110
+ apiHost: "https://api.tinybird.co",
111
+ dashboardUrl: "https://cloud.tinybird.co/gcp/europe-west3/test-workspace",
112
+ token: "test-token",
113
+ });
114
+ });
115
+
116
+ it("does not return local info in branch mode", async () => {
117
+ const result = await runInfo();
118
+
119
+ expect(result.success).toBe(true);
120
+ expect(result.local).toBeUndefined();
121
+ expect(mockedIsLocalRunning).not.toHaveBeenCalled();
122
+ });
123
+
124
+ it("returns project info", async () => {
125
+ const result = await runInfo();
126
+
127
+ expect(result.success).toBe(true);
128
+ expect(result.project).toEqual({
129
+ cwd: "/test/project",
130
+ configPath: "/test/project/tinybird.json",
131
+ devMode: "branch",
132
+ gitBranch: "feature/test",
133
+ tinybirdBranch: "feature_test",
134
+ isMainBranch: false,
135
+ });
136
+ });
137
+
138
+ it("returns branch info when on a feature branch", async () => {
139
+ const mockBranch = {
140
+ id: "branch-123",
141
+ name: "feature_test",
142
+ token: "branch-token",
143
+ created_at: "2024-01-01",
144
+ };
145
+ mockedGetBranch.mockResolvedValue(mockBranch);
146
+ mockedGetBranchDashboardUrl.mockReturnValue(
147
+ "https://cloud.tinybird.co/gcp/europe-west3/test-workspace~feature_test"
148
+ );
149
+
150
+ const result = await runInfo();
151
+
152
+ expect(result.success).toBe(true);
153
+ expect(result.branch).toEqual({
154
+ name: "feature_test",
155
+ id: "branch-123",
156
+ token: "branch-token",
157
+ dashboardUrl: "https://cloud.tinybird.co/gcp/europe-west3/test-workspace~feature_test",
158
+ });
159
+ });
160
+
161
+ it("does not return branch info when on main branch", async () => {
162
+ mockedLoadConfigAsync.mockResolvedValue({
163
+ ...mockConfig,
164
+ gitBranch: "main",
165
+ tinybirdBranch: null,
166
+ isMainBranch: true,
167
+ });
168
+
169
+ const result = await runInfo();
170
+
171
+ expect(result.success).toBe(true);
172
+ expect(result.branch).toBeUndefined();
173
+ expect(mockedGetBranch).not.toHaveBeenCalled();
174
+ });
175
+
176
+ it("returns branches list in branch mode", async () => {
177
+ const mockBranches = [
178
+ { id: "b1", name: "branch1", created_at: "2024-01-01" },
179
+ { id: "b2", name: "branch2", created_at: "2024-01-02" },
180
+ ];
181
+ mockedListBranches.mockResolvedValue(mockBranches);
182
+
183
+ const result = await runInfo();
184
+
185
+ expect(result.success).toBe(true);
186
+ expect(result.branches).toEqual(mockBranches);
187
+ });
188
+ });
189
+
190
+ describe("local mode", () => {
191
+ const mockConfig = {
192
+ cwd: "/test/project",
193
+ configPath: "/test/project/tinybird.json",
194
+ devMode: "local" as const,
195
+ gitBranch: "feature/test",
196
+ tinybirdBranch: "feature_test",
197
+ isMainBranch: false,
198
+ baseUrl: "https://api.tinybird.co",
199
+ token: "test-token",
200
+ include: [],
201
+ };
202
+
203
+ const mockWorkspace = {
204
+ id: "ws-123",
205
+ name: "test-workspace",
206
+ user_email: "user@example.com",
207
+ user_id: "user-123",
208
+ scope: "WORKSPACE",
209
+ main: null,
210
+ };
211
+
212
+ beforeEach(() => {
213
+ mockedLoadConfigAsync.mockResolvedValue(mockConfig);
214
+ mockedGetWorkspace.mockResolvedValue(mockWorkspace);
215
+ mockedGetDashboardUrl.mockReturnValue("https://cloud.tinybird.co/gcp/europe-west3/test-workspace");
216
+ });
217
+
218
+ it("returns cloud info in local mode", async () => {
219
+ mockedIsLocalRunning.mockResolvedValue(false);
220
+
221
+ const result = await runInfo();
222
+
223
+ expect(result.success).toBe(true);
224
+ expect(result.cloud).toBeDefined();
225
+ expect(result.cloud?.workspaceName).toBe("test-workspace");
226
+ });
227
+
228
+ it("returns local info when local is running", async () => {
229
+ mockedIsLocalRunning.mockResolvedValue(true);
230
+ mockedGetLocalTokens.mockResolvedValue({
231
+ user_token: "local-user-token",
232
+ admin_token: "local-admin-token",
233
+ workspace_admin_token: "local-ws-admin-token",
234
+ });
235
+ mockedGetLocalWorkspaceName.mockReturnValue("local_workspace");
236
+ mockedGetOrCreateLocalWorkspace.mockResolvedValue({
237
+ workspace: {
238
+ id: "local-ws-123",
239
+ name: "local_workspace",
240
+ token: "local-token",
241
+ },
242
+ wasCreated: false,
243
+ });
244
+ mockedGetLocalDashboardUrl.mockReturnValue("https://cloud.tinybird.co/local/7181/local_workspace");
245
+
246
+ const result = await runInfo();
247
+
248
+ expect(result.success).toBe(true);
249
+ expect(result.local).toEqual({
250
+ running: true,
251
+ workspaceName: "local_workspace",
252
+ workspaceId: "local-ws-123",
253
+ apiHost: "http://localhost:7181",
254
+ dashboardUrl: "https://cloud.tinybird.co/local/7181/local_workspace",
255
+ token: "local-token",
256
+ });
257
+ });
258
+
259
+ it("returns local info with running=false when local is not running", async () => {
260
+ mockedIsLocalRunning.mockResolvedValue(false);
261
+
262
+ const result = await runInfo();
263
+
264
+ expect(result.success).toBe(true);
265
+ expect(result.local).toEqual({
266
+ running: false,
267
+ apiHost: "http://localhost:7181",
268
+ });
269
+ });
270
+
271
+ it("does not return branch info in local mode", async () => {
272
+ mockedIsLocalRunning.mockResolvedValue(false);
273
+
274
+ const result = await runInfo();
275
+
276
+ expect(result.success).toBe(true);
277
+ expect(result.branch).toBeUndefined();
278
+ expect(result.branches).toEqual([]);
279
+ expect(mockedGetBranch).not.toHaveBeenCalled();
280
+ expect(mockedListBranches).not.toHaveBeenCalled();
281
+ });
282
+ });
283
+
284
+ describe("error handling", () => {
285
+ it("returns error when workspace fetch fails", async () => {
286
+ mockedLoadConfigAsync.mockResolvedValue({
287
+ cwd: "/test",
288
+ configPath: "/test/tinybird.json",
289
+ devMode: "branch" as const,
290
+ gitBranch: null,
291
+ tinybirdBranch: null,
292
+ isMainBranch: true,
293
+ baseUrl: "https://api.tinybird.co",
294
+ token: "test-token",
295
+ include: [],
296
+ });
297
+ mockedGetWorkspace.mockRejectedValue(new Error("Unauthorized"));
298
+
299
+ const result = await runInfo();
300
+
301
+ expect(result.success).toBe(false);
302
+ expect(result.error).toContain("Failed to get workspace info");
303
+ expect(result.error).toContain("Unauthorized");
304
+ });
305
+
306
+ it("handles branch fetch error gracefully", async () => {
307
+ mockedLoadConfigAsync.mockResolvedValue({
308
+ cwd: "/test",
309
+ configPath: "/test/tinybird.json",
310
+ devMode: "branch" as const,
311
+ gitBranch: "feature/test",
312
+ tinybirdBranch: "feature_test",
313
+ isMainBranch: false,
314
+ baseUrl: "https://api.tinybird.co",
315
+ token: "test-token",
316
+ include: [],
317
+ });
318
+ mockedGetWorkspace.mockResolvedValue({
319
+ id: "ws-123",
320
+ name: "test-workspace",
321
+ user_email: "user@example.com",
322
+ user_id: "user-123",
323
+ scope: "WORKSPACE",
324
+ main: null,
325
+ });
326
+ mockedGetBranch.mockRejectedValue(new Error("Branch not found"));
327
+ mockedListBranches.mockResolvedValue([]);
328
+ mockedGetDashboardUrl.mockReturnValue(null);
329
+
330
+ const result = await runInfo();
331
+
332
+ expect(result.success).toBe(true);
333
+ expect(result.branch).toBeUndefined();
334
+ });
335
+
336
+ it("handles branches list fetch error gracefully", async () => {
337
+ mockedLoadConfigAsync.mockResolvedValue({
338
+ cwd: "/test",
339
+ configPath: "/test/tinybird.json",
340
+ devMode: "branch" as const,
341
+ gitBranch: "main",
342
+ tinybirdBranch: null,
343
+ isMainBranch: true,
344
+ baseUrl: "https://api.tinybird.co",
345
+ token: "test-token",
346
+ include: [],
347
+ });
348
+ mockedGetWorkspace.mockResolvedValue({
349
+ id: "ws-123",
350
+ name: "test-workspace",
351
+ user_email: "user@example.com",
352
+ user_id: "user-123",
353
+ scope: "WORKSPACE",
354
+ main: null,
355
+ });
356
+ mockedListBranches.mockRejectedValue(new Error("Network error"));
357
+ mockedGetDashboardUrl.mockReturnValue(null);
358
+
359
+ const result = await runInfo();
360
+
361
+ expect(result.success).toBe(true);
362
+ expect(result.branches).toEqual([]);
363
+ });
364
+
365
+ it("handles local workspace fetch error gracefully", async () => {
366
+ mockedLoadConfigAsync.mockResolvedValue({
367
+ cwd: "/test",
368
+ configPath: "/test/tinybird.json",
369
+ devMode: "local" as const,
370
+ gitBranch: "feature/test",
371
+ tinybirdBranch: "feature_test",
372
+ isMainBranch: false,
373
+ baseUrl: "https://api.tinybird.co",
374
+ token: "test-token",
375
+ include: [],
376
+ });
377
+ mockedGetWorkspace.mockResolvedValue({
378
+ id: "ws-123",
379
+ name: "test-workspace",
380
+ user_email: "user@example.com",
381
+ user_id: "user-123",
382
+ scope: "WORKSPACE",
383
+ main: null,
384
+ });
385
+ mockedIsLocalRunning.mockResolvedValue(true);
386
+ mockedGetLocalTokens.mockRejectedValue(new Error("Connection refused"));
387
+ mockedGetDashboardUrl.mockReturnValue(null);
388
+
389
+ const result = await runInfo();
390
+
391
+ expect(result.success).toBe(true);
392
+ expect(result.local).toEqual({
393
+ running: true,
394
+ apiHost: "http://localhost:7181",
395
+ });
396
+ });
397
+ });
398
+ });
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Info command - shows information about the current project and workspace
3
+ */
4
+
5
+ import { loadConfigAsync, LOCAL_BASE_URL, type ResolvedConfig } from "../config.js";
6
+ import { getWorkspace, type TinybirdWorkspace } from "../../api/workspaces.js";
7
+ import { listBranches, getBranch, type TinybirdBranch } from "../../api/branches.js";
8
+ import { getDashboardUrl, getBranchDashboardUrl, getLocalDashboardUrl } from "../../api/dashboard.js";
9
+ import {
10
+ isLocalRunning,
11
+ getLocalTokens,
12
+ getOrCreateLocalWorkspace,
13
+ getLocalWorkspaceName,
14
+ } from "../../api/local.js";
15
+
16
+ /**
17
+ * Info command options
18
+ */
19
+ export interface InfoCommandOptions {
20
+ /** Working directory (defaults to cwd) */
21
+ cwd?: string;
22
+ /** Output as JSON */
23
+ json?: boolean;
24
+ }
25
+
26
+ /**
27
+ * Cloud/workspace information
28
+ */
29
+ export interface CloudInfo {
30
+ /** Workspace name */
31
+ workspaceName: string;
32
+ /** Workspace ID */
33
+ workspaceId: string;
34
+ /** User email */
35
+ userEmail: string;
36
+ /** API host URL */
37
+ apiHost: string;
38
+ /** Dashboard URL */
39
+ dashboardUrl?: string;
40
+ /** Token */
41
+ token: string;
42
+ }
43
+
44
+ /**
45
+ * Project configuration information
46
+ */
47
+ export interface ProjectInfo {
48
+ /** Current working directory */
49
+ cwd: string;
50
+ /** Path to tinybird.json */
51
+ configPath: string;
52
+ /** Development mode */
53
+ devMode: string;
54
+ /** Git branch */
55
+ gitBranch: string | null;
56
+ /** Tinybird branch (sanitized) */
57
+ tinybirdBranch: string | null;
58
+ /** Whether on main branch */
59
+ isMainBranch: boolean;
60
+ }
61
+
62
+ /**
63
+ * Current branch information (when working on a branch)
64
+ */
65
+ export interface BranchInfo {
66
+ /** Branch name */
67
+ name: string;
68
+ /** Branch ID */
69
+ id: string;
70
+ /** Branch token */
71
+ token: string;
72
+ /** Dashboard URL */
73
+ dashboardUrl?: string;
74
+ }
75
+
76
+ /**
77
+ * Local Tinybird workspace information
78
+ */
79
+ export interface LocalInfo {
80
+ /** Whether local Tinybird is running */
81
+ running: boolean;
82
+ /** Workspace name */
83
+ workspaceName?: string;
84
+ /** Workspace ID */
85
+ workspaceId?: string;
86
+ /** API host URL */
87
+ apiHost: string;
88
+ /** Dashboard URL */
89
+ dashboardUrl?: string;
90
+ /** Token */
91
+ token?: string;
92
+ }
93
+
94
+ /**
95
+ * Result of the info command
96
+ */
97
+ export interface InfoCommandResult {
98
+ /** Whether the operation was successful */
99
+ success: boolean;
100
+ /** Cloud/workspace info */
101
+ cloud?: CloudInfo;
102
+ /** Local workspace info (when devMode is local) */
103
+ local?: LocalInfo;
104
+ /** Project info */
105
+ project?: ProjectInfo;
106
+ /** Current branch info (if on a branch) */
107
+ branch?: BranchInfo;
108
+ /** List of all branches */
109
+ branches?: TinybirdBranch[];
110
+ /** Error message if failed */
111
+ error?: string;
112
+ }
113
+
114
+ /**
115
+ * Run the info command
116
+ *
117
+ * @param options - Command options
118
+ * @returns Info result
119
+ */
120
+ export async function runInfo(
121
+ options: InfoCommandOptions = {}
122
+ ): Promise<InfoCommandResult> {
123
+ const cwd = options.cwd ?? process.cwd();
124
+
125
+ // Load config
126
+ let config: ResolvedConfig;
127
+ try {
128
+ config = await loadConfigAsync(cwd);
129
+ } catch (error) {
130
+ return {
131
+ success: false,
132
+ error: (error as Error).message,
133
+ };
134
+ }
135
+
136
+ // Build project info first (always available)
137
+ const projectInfo: ProjectInfo = {
138
+ cwd: config.cwd,
139
+ configPath: config.configPath,
140
+ devMode: config.devMode,
141
+ gitBranch: config.gitBranch,
142
+ tinybirdBranch: config.tinybirdBranch,
143
+ isMainBranch: config.isMainBranch,
144
+ };
145
+
146
+ // Always get cloud/workspace info first (needed for local workspace name on main branch)
147
+ let workspace: TinybirdWorkspace;
148
+ try {
149
+ workspace = await getWorkspace({
150
+ baseUrl: config.baseUrl,
151
+ token: config.token,
152
+ });
153
+ } catch (error) {
154
+ return {
155
+ success: false,
156
+ error: `Failed to get workspace info: ${(error as Error).message}`,
157
+ };
158
+ }
159
+
160
+ // Build cloud info
161
+ const cloudInfo: CloudInfo = {
162
+ workspaceName: workspace.name,
163
+ workspaceId: workspace.id,
164
+ userEmail: workspace.user_email,
165
+ apiHost: config.baseUrl,
166
+ dashboardUrl: getDashboardUrl(config.baseUrl, workspace.name) ?? undefined,
167
+ token: config.token,
168
+ };
169
+
170
+ // Get local info if in local mode
171
+ let localInfo: LocalInfo | undefined;
172
+ if (config.devMode === "local") {
173
+ const localRunning = await isLocalRunning();
174
+ localInfo = {
175
+ running: localRunning,
176
+ apiHost: LOCAL_BASE_URL,
177
+ };
178
+
179
+ if (localRunning) {
180
+ try {
181
+ const tokens = await getLocalTokens();
182
+ // Determine workspace name: use authenticated workspace name on main branch,
183
+ // otherwise use branch name (for trunk-based development support)
184
+ let workspaceName: string;
185
+ if (config.isMainBranch || !config.tinybirdBranch) {
186
+ // On main branch: use the authenticated workspace name
187
+ workspaceName = workspace.name;
188
+ } else {
189
+ // On feature branch: use branch name
190
+ workspaceName = getLocalWorkspaceName(config.tinybirdBranch, config.cwd);
191
+ }
192
+ const { workspace: localWorkspace } = await getOrCreateLocalWorkspace(tokens, workspaceName);
193
+ localInfo = {
194
+ running: true,
195
+ workspaceName: localWorkspace.name,
196
+ workspaceId: localWorkspace.id,
197
+ apiHost: LOCAL_BASE_URL,
198
+ dashboardUrl: getLocalDashboardUrl(localWorkspace.name),
199
+ token: localWorkspace.token,
200
+ };
201
+ } catch {
202
+ // Local is running but couldn't get workspace info
203
+ }
204
+ }
205
+ }
206
+
207
+ // Get current branch info only if we're in branch mode (not local mode)
208
+ let branchInfo: BranchInfo | undefined;
209
+ let branches: TinybirdBranch[] = [];
210
+
211
+ if (config.devMode === "branch") {
212
+ // Get current branch info if we're on a branch (not main)
213
+ if (!config.isMainBranch && config.tinybirdBranch) {
214
+ try {
215
+ const branch = await getBranch(
216
+ {
217
+ baseUrl: config.baseUrl,
218
+ token: config.token,
219
+ },
220
+ config.tinybirdBranch
221
+ );
222
+ const dashboardUrl = getBranchDashboardUrl(config.baseUrl, workspace.name, branch.name) ?? undefined;
223
+ branchInfo = {
224
+ name: branch.name,
225
+ id: branch.id,
226
+ token: branch.token ?? "",
227
+ dashboardUrl,
228
+ };
229
+ } catch {
230
+ // Branch might not exist yet, that's ok
231
+ }
232
+ }
233
+
234
+ // Get all branches
235
+ try {
236
+ branches = await listBranches({
237
+ baseUrl: config.baseUrl,
238
+ token: config.token,
239
+ });
240
+ } catch {
241
+ // Branches are optional, don't fail if we can't fetch them
242
+ }
243
+ }
244
+
245
+ return {
246
+ success: true,
247
+ cloud: cloudInfo,
248
+ local: localInfo,
249
+ project: projectInfo,
250
+ branch: branchInfo,
251
+ branches,
252
+ };
253
+ }