@tinybirdco/sdk 0.0.25 → 0.0.26

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 CHANGED
@@ -271,12 +271,19 @@ tinybird deploy --dry-run # Preview without pushing
271
271
 
272
272
  ### `tinybird login`
273
273
 
274
- Authenticate with Tinybird via browser.
274
+ Authenticate with Tinybird via browser. Use this to set up credentials for an existing project without reinitializing.
275
275
 
276
276
  ```bash
277
277
  tinybird login
278
278
  ```
279
279
 
280
+ This is useful when:
281
+ - You cloned an existing project that has `tinybird.json` but no credentials
282
+ - Your token has expired or needs to be refreshed
283
+ - You're switching to a different Tinybird workspace
284
+
285
+ The command saves your token to `.env.local` and updates the `baseUrl` in `tinybird.json` if you select a different region.
286
+
280
287
  ### `tinybird branch`
281
288
 
282
289
  Manage Tinybird branches.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=login.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.test.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/login.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,249 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import * as os from "os";
5
+ import { runLogin } from "./login.js";
6
+ // Mock the auth module to avoid browser login
7
+ vi.mock("../auth.js", () => ({
8
+ browserLogin: vi.fn(),
9
+ }));
10
+ // Import the mocked function
11
+ import { browserLogin } from "../auth.js";
12
+ const mockedBrowserLogin = vi.mocked(browserLogin);
13
+ describe("Login Command", () => {
14
+ let tempDir;
15
+ beforeEach(() => {
16
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-login-test-"));
17
+ vi.clearAllMocks();
18
+ });
19
+ afterEach(() => {
20
+ try {
21
+ fs.rmSync(tempDir, { recursive: true });
22
+ }
23
+ catch {
24
+ // Ignore cleanup errors
25
+ }
26
+ });
27
+ describe("config file requirement", () => {
28
+ it("fails when no tinybird.json exists", async () => {
29
+ const result = await runLogin({ cwd: tempDir });
30
+ expect(result.success).toBe(false);
31
+ expect(result.error).toContain("No tinybird.json found");
32
+ expect(result.error).toContain("npx tinybird init");
33
+ });
34
+ it("succeeds when tinybird.json exists", async () => {
35
+ // Create a tinybird.json config
36
+ const config = { include: ["lib/tinybird.ts"], token: "${TINYBIRD_TOKEN}" };
37
+ fs.writeFileSync(path.join(tempDir, "tinybird.json"), JSON.stringify(config));
38
+ mockedBrowserLogin.mockResolvedValue({
39
+ success: true,
40
+ token: "test-token",
41
+ baseUrl: "https://api.tinybird.co",
42
+ workspaceName: "test-workspace",
43
+ userEmail: "user@example.com",
44
+ });
45
+ const result = await runLogin({ cwd: tempDir });
46
+ expect(result.success).toBe(true);
47
+ expect(result.workspaceName).toBe("test-workspace");
48
+ expect(result.userEmail).toBe("user@example.com");
49
+ expect(result.baseUrl).toBe("https://api.tinybird.co");
50
+ });
51
+ it("finds tinybird.json in parent directory (monorepo support)", async () => {
52
+ // Create nested directory structure
53
+ const nestedDir = path.join(tempDir, "packages", "app");
54
+ fs.mkdirSync(nestedDir, { recursive: true });
55
+ // Create tinybird.json in root
56
+ const config = { include: ["lib/tinybird.ts"], token: "${TINYBIRD_TOKEN}" };
57
+ fs.writeFileSync(path.join(tempDir, "tinybird.json"), JSON.stringify(config));
58
+ mockedBrowserLogin.mockResolvedValue({
59
+ success: true,
60
+ token: "test-token",
61
+ baseUrl: "https://api.tinybird.co",
62
+ workspaceName: "test-workspace",
63
+ userEmail: "user@example.com",
64
+ });
65
+ const result = await runLogin({ cwd: nestedDir });
66
+ expect(result.success).toBe(true);
67
+ });
68
+ });
69
+ describe("authentication flow", () => {
70
+ beforeEach(() => {
71
+ // Create a tinybird.json config
72
+ const config = { include: ["lib/tinybird.ts"], token: "${TINYBIRD_TOKEN}" };
73
+ fs.writeFileSync(path.join(tempDir, "tinybird.json"), JSON.stringify(config));
74
+ });
75
+ it("returns error when browser login fails", async () => {
76
+ mockedBrowserLogin.mockResolvedValue({
77
+ success: false,
78
+ error: "Authentication timed out",
79
+ });
80
+ const result = await runLogin({ cwd: tempDir });
81
+ expect(result.success).toBe(false);
82
+ expect(result.error).toBe("Authentication timed out");
83
+ });
84
+ it("returns error when token is missing from auth result", async () => {
85
+ mockedBrowserLogin.mockResolvedValue({
86
+ success: true,
87
+ // No token provided
88
+ });
89
+ const result = await runLogin({ cwd: tempDir });
90
+ expect(result.success).toBe(false);
91
+ expect(result.error).toBe("Login failed");
92
+ });
93
+ it("passes apiHost option to browserLogin", async () => {
94
+ mockedBrowserLogin.mockResolvedValue({
95
+ success: true,
96
+ token: "test-token",
97
+ baseUrl: "https://api.us-east.tinybird.co",
98
+ workspaceName: "test-workspace",
99
+ });
100
+ await runLogin({ cwd: tempDir, apiHost: "https://api.us-east.tinybird.co" });
101
+ expect(mockedBrowserLogin).toHaveBeenCalledWith({
102
+ apiHost: "https://api.us-east.tinybird.co",
103
+ });
104
+ });
105
+ });
106
+ describe("token storage", () => {
107
+ beforeEach(() => {
108
+ // Create a tinybird.json config
109
+ const config = { include: ["lib/tinybird.ts"], token: "${TINYBIRD_TOKEN}" };
110
+ fs.writeFileSync(path.join(tempDir, "tinybird.json"), JSON.stringify(config));
111
+ });
112
+ it("creates .env.local with token", async () => {
113
+ mockedBrowserLogin.mockResolvedValue({
114
+ success: true,
115
+ token: "new-token-123",
116
+ baseUrl: "https://api.tinybird.co",
117
+ workspaceName: "test-workspace",
118
+ });
119
+ const result = await runLogin({ cwd: tempDir });
120
+ expect(result.success).toBe(true);
121
+ const envContent = fs.readFileSync(path.join(tempDir, ".env.local"), "utf-8");
122
+ expect(envContent).toContain("TINYBIRD_TOKEN=new-token-123");
123
+ });
124
+ it("updates existing token in .env.local", async () => {
125
+ // Create existing .env.local with old token
126
+ fs.writeFileSync(path.join(tempDir, ".env.local"), "TINYBIRD_TOKEN=old-token\nOTHER_VAR=value\n");
127
+ mockedBrowserLogin.mockResolvedValue({
128
+ success: true,
129
+ token: "new-token-456",
130
+ baseUrl: "https://api.tinybird.co",
131
+ workspaceName: "test-workspace",
132
+ });
133
+ const result = await runLogin({ cwd: tempDir });
134
+ expect(result.success).toBe(true);
135
+ const envContent = fs.readFileSync(path.join(tempDir, ".env.local"), "utf-8");
136
+ expect(envContent).toContain("TINYBIRD_TOKEN=new-token-456");
137
+ expect(envContent).toContain("OTHER_VAR=value");
138
+ expect(envContent).not.toContain("old-token");
139
+ });
140
+ it("appends token to existing .env.local without TINYBIRD_TOKEN", async () => {
141
+ // Create existing .env.local without token
142
+ fs.writeFileSync(path.join(tempDir, ".env.local"), "OTHER_VAR=value\n");
143
+ mockedBrowserLogin.mockResolvedValue({
144
+ success: true,
145
+ token: "new-token-789",
146
+ baseUrl: "https://api.tinybird.co",
147
+ workspaceName: "test-workspace",
148
+ });
149
+ const result = await runLogin({ cwd: tempDir });
150
+ expect(result.success).toBe(true);
151
+ const envContent = fs.readFileSync(path.join(tempDir, ".env.local"), "utf-8");
152
+ expect(envContent).toContain("OTHER_VAR=value");
153
+ expect(envContent).toContain("TINYBIRD_TOKEN=new-token-789");
154
+ });
155
+ it("saves .env.local in same directory as tinybird.json (monorepo)", async () => {
156
+ // Create nested directory structure
157
+ const nestedDir = path.join(tempDir, "packages", "app");
158
+ fs.mkdirSync(nestedDir, { recursive: true });
159
+ // Move tinybird.json to root
160
+ fs.unlinkSync(path.join(tempDir, "tinybird.json"));
161
+ const config = { include: ["lib/tinybird.ts"], token: "${TINYBIRD_TOKEN}" };
162
+ fs.writeFileSync(path.join(tempDir, "tinybird.json"), JSON.stringify(config));
163
+ mockedBrowserLogin.mockResolvedValue({
164
+ success: true,
165
+ token: "test-token",
166
+ baseUrl: "https://api.tinybird.co",
167
+ workspaceName: "test-workspace",
168
+ });
169
+ const result = await runLogin({ cwd: nestedDir });
170
+ expect(result.success).toBe(true);
171
+ // .env.local should be in the root (same as tinybird.json), not in nested dir
172
+ expect(fs.existsSync(path.join(tempDir, ".env.local"))).toBe(true);
173
+ expect(fs.existsSync(path.join(nestedDir, ".env.local"))).toBe(false);
174
+ });
175
+ });
176
+ describe("baseUrl update", () => {
177
+ beforeEach(() => {
178
+ // Create a tinybird.json config
179
+ const config = {
180
+ include: ["lib/tinybird.ts"],
181
+ token: "${TINYBIRD_TOKEN}",
182
+ baseUrl: "https://api.tinybird.co",
183
+ };
184
+ fs.writeFileSync(path.join(tempDir, "tinybird.json"), JSON.stringify(config, null, 2));
185
+ });
186
+ it("updates baseUrl in tinybird.json when region changes", async () => {
187
+ mockedBrowserLogin.mockResolvedValue({
188
+ success: true,
189
+ token: "test-token",
190
+ baseUrl: "https://api.us-east.tinybird.co",
191
+ workspaceName: "test-workspace",
192
+ });
193
+ const result = await runLogin({ cwd: tempDir });
194
+ expect(result.success).toBe(true);
195
+ const config = JSON.parse(fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8"));
196
+ expect(config.baseUrl).toBe("https://api.us-east.tinybird.co");
197
+ });
198
+ it("does not modify tinybird.json when baseUrl is not provided", async () => {
199
+ mockedBrowserLogin.mockResolvedValue({
200
+ success: true,
201
+ token: "test-token",
202
+ // No baseUrl
203
+ workspaceName: "test-workspace",
204
+ });
205
+ const result = await runLogin({ cwd: tempDir });
206
+ expect(result.success).toBe(true);
207
+ const config = JSON.parse(fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8"));
208
+ // Original baseUrl preserved
209
+ expect(config.baseUrl).toBe("https://api.tinybird.co");
210
+ });
211
+ });
212
+ describe("return values", () => {
213
+ beforeEach(() => {
214
+ const config = { include: ["lib/tinybird.ts"], token: "${TINYBIRD_TOKEN}" };
215
+ fs.writeFileSync(path.join(tempDir, "tinybird.json"), JSON.stringify(config));
216
+ });
217
+ it("returns workspace name from auth result", async () => {
218
+ mockedBrowserLogin.mockResolvedValue({
219
+ success: true,
220
+ token: "test-token",
221
+ workspaceName: "my-workspace",
222
+ });
223
+ const result = await runLogin({ cwd: tempDir });
224
+ expect(result.success).toBe(true);
225
+ expect(result.workspaceName).toBe("my-workspace");
226
+ });
227
+ it("returns user email from auth result", async () => {
228
+ mockedBrowserLogin.mockResolvedValue({
229
+ success: true,
230
+ token: "test-token",
231
+ userEmail: "developer@example.com",
232
+ });
233
+ const result = await runLogin({ cwd: tempDir });
234
+ expect(result.success).toBe(true);
235
+ expect(result.userEmail).toBe("developer@example.com");
236
+ });
237
+ it("returns baseUrl from auth result", async () => {
238
+ mockedBrowserLogin.mockResolvedValue({
239
+ success: true,
240
+ token: "test-token",
241
+ baseUrl: "https://api.eu-central.tinybird.co",
242
+ });
243
+ const result = await runLogin({ cwd: tempDir });
244
+ expect(result.success).toBe(true);
245
+ expect(result.baseUrl).toBe("https://api.eu-central.tinybird.co");
246
+ });
247
+ });
248
+ });
249
+ //# sourceMappingURL=login.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.test.js","sourceRoot":"","sources":["../../../src/cli/commands/login.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinybirdco/sdk",
3
- "version": "0.0.25",
3
+ "version": "0.0.26",
4
4
  "description": "TypeScript SDK for Tinybird Forward - define datasources and pipes as TypeScript",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,354 @@
1
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import * as os from "os";
5
+ import { runLogin } from "./login.js";
6
+
7
+ // Mock the auth module to avoid browser login
8
+ vi.mock("../auth.js", () => ({
9
+ browserLogin: vi.fn(),
10
+ }));
11
+
12
+ // Import the mocked function
13
+ import { browserLogin } from "../auth.js";
14
+
15
+ const mockedBrowserLogin = vi.mocked(browserLogin);
16
+
17
+ describe("Login Command", () => {
18
+ let tempDir: string;
19
+
20
+ beforeEach(() => {
21
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-login-test-"));
22
+ vi.clearAllMocks();
23
+ });
24
+
25
+ afterEach(() => {
26
+ try {
27
+ fs.rmSync(tempDir, { recursive: true });
28
+ } catch {
29
+ // Ignore cleanup errors
30
+ }
31
+ });
32
+
33
+ describe("config file requirement", () => {
34
+ it("fails when no tinybird.json exists", async () => {
35
+ const result = await runLogin({ cwd: tempDir });
36
+
37
+ expect(result.success).toBe(false);
38
+ expect(result.error).toContain("No tinybird.json found");
39
+ expect(result.error).toContain("npx tinybird init");
40
+ });
41
+
42
+ it("succeeds when tinybird.json exists", async () => {
43
+ // Create a tinybird.json config
44
+ const config = { include: ["lib/tinybird.ts"], token: "${TINYBIRD_TOKEN}" };
45
+ fs.writeFileSync(
46
+ path.join(tempDir, "tinybird.json"),
47
+ JSON.stringify(config)
48
+ );
49
+
50
+ mockedBrowserLogin.mockResolvedValue({
51
+ success: true,
52
+ token: "test-token",
53
+ baseUrl: "https://api.tinybird.co",
54
+ workspaceName: "test-workspace",
55
+ userEmail: "user@example.com",
56
+ });
57
+
58
+ const result = await runLogin({ cwd: tempDir });
59
+
60
+ expect(result.success).toBe(true);
61
+ expect(result.workspaceName).toBe("test-workspace");
62
+ expect(result.userEmail).toBe("user@example.com");
63
+ expect(result.baseUrl).toBe("https://api.tinybird.co");
64
+ });
65
+
66
+ it("finds tinybird.json in parent directory (monorepo support)", async () => {
67
+ // Create nested directory structure
68
+ const nestedDir = path.join(tempDir, "packages", "app");
69
+ fs.mkdirSync(nestedDir, { recursive: true });
70
+
71
+ // Create tinybird.json in root
72
+ const config = { include: ["lib/tinybird.ts"], token: "${TINYBIRD_TOKEN}" };
73
+ fs.writeFileSync(
74
+ path.join(tempDir, "tinybird.json"),
75
+ JSON.stringify(config)
76
+ );
77
+
78
+ mockedBrowserLogin.mockResolvedValue({
79
+ success: true,
80
+ token: "test-token",
81
+ baseUrl: "https://api.tinybird.co",
82
+ workspaceName: "test-workspace",
83
+ userEmail: "user@example.com",
84
+ });
85
+
86
+ const result = await runLogin({ cwd: nestedDir });
87
+
88
+ expect(result.success).toBe(true);
89
+ });
90
+ });
91
+
92
+ describe("authentication flow", () => {
93
+ beforeEach(() => {
94
+ // Create a tinybird.json config
95
+ const config = { include: ["lib/tinybird.ts"], token: "${TINYBIRD_TOKEN}" };
96
+ fs.writeFileSync(
97
+ path.join(tempDir, "tinybird.json"),
98
+ JSON.stringify(config)
99
+ );
100
+ });
101
+
102
+ it("returns error when browser login fails", async () => {
103
+ mockedBrowserLogin.mockResolvedValue({
104
+ success: false,
105
+ error: "Authentication timed out",
106
+ });
107
+
108
+ const result = await runLogin({ cwd: tempDir });
109
+
110
+ expect(result.success).toBe(false);
111
+ expect(result.error).toBe("Authentication timed out");
112
+ });
113
+
114
+ it("returns error when token is missing from auth result", async () => {
115
+ mockedBrowserLogin.mockResolvedValue({
116
+ success: true,
117
+ // No token provided
118
+ });
119
+
120
+ const result = await runLogin({ cwd: tempDir });
121
+
122
+ expect(result.success).toBe(false);
123
+ expect(result.error).toBe("Login failed");
124
+ });
125
+
126
+ it("passes apiHost option to browserLogin", async () => {
127
+ mockedBrowserLogin.mockResolvedValue({
128
+ success: true,
129
+ token: "test-token",
130
+ baseUrl: "https://api.us-east.tinybird.co",
131
+ workspaceName: "test-workspace",
132
+ });
133
+
134
+ await runLogin({ cwd: tempDir, apiHost: "https://api.us-east.tinybird.co" });
135
+
136
+ expect(mockedBrowserLogin).toHaveBeenCalledWith({
137
+ apiHost: "https://api.us-east.tinybird.co",
138
+ });
139
+ });
140
+ });
141
+
142
+ describe("token storage", () => {
143
+ beforeEach(() => {
144
+ // Create a tinybird.json config
145
+ const config = { include: ["lib/tinybird.ts"], token: "${TINYBIRD_TOKEN}" };
146
+ fs.writeFileSync(
147
+ path.join(tempDir, "tinybird.json"),
148
+ JSON.stringify(config)
149
+ );
150
+ });
151
+
152
+ it("creates .env.local with token", async () => {
153
+ mockedBrowserLogin.mockResolvedValue({
154
+ success: true,
155
+ token: "new-token-123",
156
+ baseUrl: "https://api.tinybird.co",
157
+ workspaceName: "test-workspace",
158
+ });
159
+
160
+ const result = await runLogin({ cwd: tempDir });
161
+
162
+ expect(result.success).toBe(true);
163
+
164
+ const envContent = fs.readFileSync(
165
+ path.join(tempDir, ".env.local"),
166
+ "utf-8"
167
+ );
168
+ expect(envContent).toContain("TINYBIRD_TOKEN=new-token-123");
169
+ });
170
+
171
+ it("updates existing token in .env.local", async () => {
172
+ // Create existing .env.local with old token
173
+ fs.writeFileSync(
174
+ path.join(tempDir, ".env.local"),
175
+ "TINYBIRD_TOKEN=old-token\nOTHER_VAR=value\n"
176
+ );
177
+
178
+ mockedBrowserLogin.mockResolvedValue({
179
+ success: true,
180
+ token: "new-token-456",
181
+ baseUrl: "https://api.tinybird.co",
182
+ workspaceName: "test-workspace",
183
+ });
184
+
185
+ const result = await runLogin({ cwd: tempDir });
186
+
187
+ expect(result.success).toBe(true);
188
+
189
+ const envContent = fs.readFileSync(
190
+ path.join(tempDir, ".env.local"),
191
+ "utf-8"
192
+ );
193
+ expect(envContent).toContain("TINYBIRD_TOKEN=new-token-456");
194
+ expect(envContent).toContain("OTHER_VAR=value");
195
+ expect(envContent).not.toContain("old-token");
196
+ });
197
+
198
+ it("appends token to existing .env.local without TINYBIRD_TOKEN", async () => {
199
+ // Create existing .env.local without token
200
+ fs.writeFileSync(
201
+ path.join(tempDir, ".env.local"),
202
+ "OTHER_VAR=value\n"
203
+ );
204
+
205
+ mockedBrowserLogin.mockResolvedValue({
206
+ success: true,
207
+ token: "new-token-789",
208
+ baseUrl: "https://api.tinybird.co",
209
+ workspaceName: "test-workspace",
210
+ });
211
+
212
+ const result = await runLogin({ cwd: tempDir });
213
+
214
+ expect(result.success).toBe(true);
215
+
216
+ const envContent = fs.readFileSync(
217
+ path.join(tempDir, ".env.local"),
218
+ "utf-8"
219
+ );
220
+ expect(envContent).toContain("OTHER_VAR=value");
221
+ expect(envContent).toContain("TINYBIRD_TOKEN=new-token-789");
222
+ });
223
+
224
+ it("saves .env.local in same directory as tinybird.json (monorepo)", async () => {
225
+ // Create nested directory structure
226
+ const nestedDir = path.join(tempDir, "packages", "app");
227
+ fs.mkdirSync(nestedDir, { recursive: true });
228
+
229
+ // Move tinybird.json to root
230
+ fs.unlinkSync(path.join(tempDir, "tinybird.json"));
231
+ const config = { include: ["lib/tinybird.ts"], token: "${TINYBIRD_TOKEN}" };
232
+ fs.writeFileSync(
233
+ path.join(tempDir, "tinybird.json"),
234
+ JSON.stringify(config)
235
+ );
236
+
237
+ mockedBrowserLogin.mockResolvedValue({
238
+ success: true,
239
+ token: "test-token",
240
+ baseUrl: "https://api.tinybird.co",
241
+ workspaceName: "test-workspace",
242
+ });
243
+
244
+ const result = await runLogin({ cwd: nestedDir });
245
+
246
+ expect(result.success).toBe(true);
247
+
248
+ // .env.local should be in the root (same as tinybird.json), not in nested dir
249
+ expect(fs.existsSync(path.join(tempDir, ".env.local"))).toBe(true);
250
+ expect(fs.existsSync(path.join(nestedDir, ".env.local"))).toBe(false);
251
+ });
252
+ });
253
+
254
+ describe("baseUrl update", () => {
255
+ beforeEach(() => {
256
+ // Create a tinybird.json config
257
+ const config = {
258
+ include: ["lib/tinybird.ts"],
259
+ token: "${TINYBIRD_TOKEN}",
260
+ baseUrl: "https://api.tinybird.co",
261
+ };
262
+ fs.writeFileSync(
263
+ path.join(tempDir, "tinybird.json"),
264
+ JSON.stringify(config, null, 2)
265
+ );
266
+ });
267
+
268
+ it("updates baseUrl in tinybird.json when region changes", async () => {
269
+ mockedBrowserLogin.mockResolvedValue({
270
+ success: true,
271
+ token: "test-token",
272
+ baseUrl: "https://api.us-east.tinybird.co",
273
+ workspaceName: "test-workspace",
274
+ });
275
+
276
+ const result = await runLogin({ cwd: tempDir });
277
+
278
+ expect(result.success).toBe(true);
279
+
280
+ const config = JSON.parse(
281
+ fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
282
+ );
283
+ expect(config.baseUrl).toBe("https://api.us-east.tinybird.co");
284
+ });
285
+
286
+ it("does not modify tinybird.json when baseUrl is not provided", async () => {
287
+ mockedBrowserLogin.mockResolvedValue({
288
+ success: true,
289
+ token: "test-token",
290
+ // No baseUrl
291
+ workspaceName: "test-workspace",
292
+ });
293
+
294
+ const result = await runLogin({ cwd: tempDir });
295
+
296
+ expect(result.success).toBe(true);
297
+
298
+ const config = JSON.parse(
299
+ fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
300
+ );
301
+ // Original baseUrl preserved
302
+ expect(config.baseUrl).toBe("https://api.tinybird.co");
303
+ });
304
+ });
305
+
306
+ describe("return values", () => {
307
+ beforeEach(() => {
308
+ const config = { include: ["lib/tinybird.ts"], token: "${TINYBIRD_TOKEN}" };
309
+ fs.writeFileSync(
310
+ path.join(tempDir, "tinybird.json"),
311
+ JSON.stringify(config)
312
+ );
313
+ });
314
+
315
+ it("returns workspace name from auth result", async () => {
316
+ mockedBrowserLogin.mockResolvedValue({
317
+ success: true,
318
+ token: "test-token",
319
+ workspaceName: "my-workspace",
320
+ });
321
+
322
+ const result = await runLogin({ cwd: tempDir });
323
+
324
+ expect(result.success).toBe(true);
325
+ expect(result.workspaceName).toBe("my-workspace");
326
+ });
327
+
328
+ it("returns user email from auth result", async () => {
329
+ mockedBrowserLogin.mockResolvedValue({
330
+ success: true,
331
+ token: "test-token",
332
+ userEmail: "developer@example.com",
333
+ });
334
+
335
+ const result = await runLogin({ cwd: tempDir });
336
+
337
+ expect(result.success).toBe(true);
338
+ expect(result.userEmail).toBe("developer@example.com");
339
+ });
340
+
341
+ it("returns baseUrl from auth result", async () => {
342
+ mockedBrowserLogin.mockResolvedValue({
343
+ success: true,
344
+ token: "test-token",
345
+ baseUrl: "https://api.eu-central.tinybird.co",
346
+ });
347
+
348
+ const result = await runLogin({ cwd: tempDir });
349
+
350
+ expect(result.success).toBe(true);
351
+ expect(result.baseUrl).toBe("https://api.eu-central.tinybird.co");
352
+ });
353
+ });
354
+ });