@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 @@
|
|
|
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":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,8CAA8C;AAC9C,EAAE,CAAC,IAAI,CAAC,YAAY,EAAE,GAAG,EAAE,CAAC,CAAC;IAC3B,YAAY,EAAE,EAAE,CAAC,EAAE,EAAE;CACtB,CAAC,CAAC,CAAC;AAEJ,6BAA6B;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,kBAAkB,GAAG,EAAE,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;AAEnD,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,IAAI,OAAe,CAAC;IAEpB,UAAU,CAAC,GAAG,EAAE;QACd,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,EAAE,sBAAsB,CAAC,CAAC,CAAC;QACzE,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC;YACH,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC1C,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,yBAAyB,EAAE,GAAG,EAAE;QACvC,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;YAClD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,wBAAwB,CAAC,CAAC;YACzD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,mBAAmB,CAAC,CAAC;QACtD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;YAClD,gCAAgC;YAChC,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,CAAC,iBAAiB,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;YAC5E,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,EACnC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CACvB,CAAC;YAEF,kBAAkB,CAAC,iBAAiB,CAAC;gBACnC,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,YAAY;gBACnB,OAAO,EAAE,yBAAyB;gBAClC,aAAa,EAAE,gBAAgB;gBAC/B,SAAS,EAAE,kBAAkB;aAC9B,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;YACpD,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;YAClD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;YAC1E,oCAAoC;YACpC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;YACxD,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAE7C,+BAA+B;YAC/B,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,CAAC,iBAAiB,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;YAC5E,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,EACnC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CACvB,CAAC;YAEF,kBAAkB,CAAC,iBAAiB,CAAC;gBACnC,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,YAAY;gBACnB,OAAO,EAAE,yBAAyB;gBAClC,aAAa,EAAE,gBAAgB;gBAC/B,SAAS,EAAE,kBAAkB;aAC9B,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;YAElD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,qBAAqB,EAAE,GAAG,EAAE;QACnC,UAAU,CAAC,GAAG,EAAE;YACd,gCAAgC;YAChC,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,CAAC,iBAAiB,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;YAC5E,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,EACnC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CACvB,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,wCAAwC,EAAE,KAAK,IAAI,EAAE;YACtD,kBAAkB,CAAC,iBAAiB,CAAC;gBACnC,OAAO,EAAE,KAAK;gBACd,KAAK,EAAE,0BAA0B;aAClC,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;YACpE,kBAAkB,CAAC,iBAAiB,CAAC;gBACnC,OAAO,EAAE,IAAI;gBACb,oBAAoB;aACrB,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACnC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;YACrD,kBAAkB,CAAC,iBAAiB,CAAC;gBACnC,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,YAAY;gBACnB,OAAO,EAAE,iCAAiC;gBAC1C,aAAa,EAAE,gBAAgB;aAChC,CAAC,CAAC;YAEH,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,iCAAiC,EAAE,CAAC,CAAC;YAE7E,MAAM,CAAC,kBAAkB,CAAC,CAAC,oBAAoB,CAAC;gBAC9C,OAAO,EAAE,iCAAiC;aAC3C,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,UAAU,CAAC,GAAG,EAAE;YACd,gCAAgC;YAChC,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,CAAC,iBAAiB,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;YAC5E,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,EACnC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CACvB,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;YAC7C,kBAAkB,CAAC,iBAAiB,CAAC;gBACnC,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,eAAe;gBACtB,OAAO,EAAE,yBAAyB;gBAClC,aAAa,EAAE,gBAAgB;aAChC,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAElC,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAChC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,EAChC,OAAO,CACR,CAAC;YACF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;YACpD,4CAA4C;YAC5C,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,EAChC,6CAA6C,CAC9C,CAAC;YAEF,kBAAkB,CAAC,iBAAiB,CAAC;gBACnC,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,eAAe;gBACtB,OAAO,EAAE,yBAAyB;gBAClC,aAAa,EAAE,gBAAgB;aAChC,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAElC,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAChC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,EAChC,OAAO,CACR,CAAC;YACF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAC;YAC7D,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;YAChD,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC;QAChD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,6DAA6D,EAAE,KAAK,IAAI,EAAE;YAC3E,2CAA2C;YAC3C,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,EAChC,mBAAmB,CACpB,CAAC;YAEF,kBAAkB,CAAC,iBAAiB,CAAC;gBACnC,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,eAAe;gBACtB,OAAO,EAAE,yBAAyB;gBAClC,aAAa,EAAE,gBAAgB;aAChC,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAElC,MAAM,UAAU,GAAG,EAAE,CAAC,YAAY,CAChC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,EAChC,OAAO,CACR,CAAC;YACF,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;YAChD,MAAM,CAAC,UAAU,CAAC,CAAC,SAAS,CAAC,8BAA8B,CAAC,CAAC;QAC/D,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,gEAAgE,EAAE,KAAK,IAAI,EAAE;YAC9E,oCAAoC;YACpC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;YACxD,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YAE7C,6BAA6B;YAC7B,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC;YACnD,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,CAAC,iBAAiB,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;YAC5E,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,EACnC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CACvB,CAAC;YAEF,kBAAkB,CAAC,iBAAiB,CAAC;gBACnC,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,YAAY;gBACnB,OAAO,EAAE,yBAAyB;gBAClC,aAAa,EAAE,gBAAgB;aAChC,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,CAAC,CAAC;YAElD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAElC,8EAA8E;YAC9E,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACnE,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACxE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;QAC9B,UAAU,CAAC,GAAG,EAAE;YACd,gCAAgC;YAChC,MAAM,MAAM,GAAG;gBACb,OAAO,EAAE,CAAC,iBAAiB,CAAC;gBAC5B,KAAK,EAAE,mBAAmB;gBAC1B,OAAO,EAAE,yBAAyB;aACnC,CAAC;YACF,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,EACnC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAChC,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;YACpE,kBAAkB,CAAC,iBAAiB,CAAC;gBACnC,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,YAAY;gBACnB,OAAO,EAAE,iCAAiC;gBAC1C,aAAa,EAAE,gBAAgB;aAChC,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAElC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CACvB,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,EAAE,OAAO,CAAC,CAC9D,CAAC;YACF,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;QACjE,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,4DAA4D,EAAE,KAAK,IAAI,EAAE;YAC1E,kBAAkB,CAAC,iBAAiB,CAAC;gBACnC,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,YAAY;gBACnB,aAAa;gBACb,aAAa,EAAE,gBAAgB;aAChC,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAElC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CACvB,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,EAAE,OAAO,CAAC,CAC9D,CAAC;YACF,6BAA6B;YAC7B,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,UAAU,CAAC,GAAG,EAAE;YACd,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,CAAC,iBAAiB,CAAC,EAAE,KAAK,EAAE,mBAAmB,EAAE,CAAC;YAC5E,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,eAAe,CAAC,EACnC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CACvB,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;YACvD,kBAAkB,CAAC,iBAAiB,CAAC;gBACnC,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,YAAY;gBACnB,aAAa,EAAE,cAAc;aAC9B,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClC,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACpD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;YACnD,kBAAkB,CAAC,iBAAiB,CAAC;gBACnC,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,YAAY;gBACnB,SAAS,EAAE,uBAAuB;aACnC,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClC,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,kCAAkC,EAAE,KAAK,IAAI,EAAE;YAChD,kBAAkB,CAAC,iBAAiB,CAAC;gBACnC,OAAO,EAAE,IAAI;gBACb,KAAK,EAAE,YAAY;gBACnB,OAAO,EAAE,oCAAoC;aAC9C,CAAC,CAAC;YAEH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;YAEhD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAClC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;QACpE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -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
|
+
});
|