@tinybirdco/sdk 0.0.3 → 0.0.6

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 (122) hide show
  1. package/README.md +87 -14
  2. package/dist/api/deploy.d.ts +41 -3
  3. package/dist/api/deploy.d.ts.map +1 -1
  4. package/dist/api/deploy.js +141 -19
  5. package/dist/api/deploy.js.map +1 -1
  6. package/dist/api/deploy.test.js +77 -29
  7. package/dist/api/deploy.test.js.map +1 -1
  8. package/dist/api/local.d.ts +92 -0
  9. package/dist/api/local.d.ts.map +1 -0
  10. package/dist/api/local.js +176 -0
  11. package/dist/api/local.js.map +1 -0
  12. package/dist/api/local.test.d.ts +2 -0
  13. package/dist/api/local.test.d.ts.map +1 -0
  14. package/dist/api/local.test.js +182 -0
  15. package/dist/api/local.test.js.map +1 -0
  16. package/dist/api/resources.d.ts +178 -0
  17. package/dist/api/resources.d.ts.map +1 -0
  18. package/dist/api/resources.js +244 -0
  19. package/dist/api/resources.js.map +1 -0
  20. package/dist/api/resources.test.d.ts +2 -0
  21. package/dist/api/resources.test.d.ts.map +1 -0
  22. package/dist/api/resources.test.js +255 -0
  23. package/dist/api/resources.test.js.map +1 -0
  24. package/dist/cli/commands/build.d.ts +6 -4
  25. package/dist/cli/commands/build.d.ts.map +1 -1
  26. package/dist/cli/commands/build.js +95 -47
  27. package/dist/cli/commands/build.js.map +1 -1
  28. package/dist/cli/commands/deploy.d.ts +39 -0
  29. package/dist/cli/commands/deploy.d.ts.map +1 -0
  30. package/dist/cli/commands/deploy.js +90 -0
  31. package/dist/cli/commands/deploy.js.map +1 -0
  32. package/dist/cli/commands/dev.d.ts +9 -2
  33. package/dist/cli/commands/dev.d.ts.map +1 -1
  34. package/dist/cli/commands/dev.js +60 -31
  35. package/dist/cli/commands/dev.js.map +1 -1
  36. package/dist/cli/commands/init.d.ts +24 -1
  37. package/dist/cli/commands/init.d.ts.map +1 -1
  38. package/dist/cli/commands/init.js +174 -23
  39. package/dist/cli/commands/init.js.map +1 -1
  40. package/dist/cli/commands/init.test.js +190 -30
  41. package/dist/cli/commands/init.test.js.map +1 -1
  42. package/dist/cli/config.d.ts +14 -0
  43. package/dist/cli/config.d.ts.map +1 -1
  44. package/dist/cli/config.js +7 -0
  45. package/dist/cli/config.js.map +1 -1
  46. package/dist/cli/config.test.js +29 -0
  47. package/dist/cli/config.test.js.map +1 -1
  48. package/dist/cli/index.js +107 -11
  49. package/dist/cli/index.js.map +1 -1
  50. package/dist/cli/utils/package-manager.d.ts +8 -0
  51. package/dist/cli/utils/package-manager.d.ts.map +1 -0
  52. package/dist/cli/utils/package-manager.js +45 -0
  53. package/dist/cli/utils/package-manager.js.map +1 -0
  54. package/dist/cli/utils/package-manager.test.d.ts +2 -0
  55. package/dist/cli/utils/package-manager.test.d.ts.map +1 -0
  56. package/dist/cli/utils/package-manager.test.js +85 -0
  57. package/dist/cli/utils/package-manager.test.js.map +1 -0
  58. package/dist/codegen/index.d.ts +39 -0
  59. package/dist/codegen/index.d.ts.map +1 -0
  60. package/dist/codegen/index.js +300 -0
  61. package/dist/codegen/index.js.map +1 -0
  62. package/dist/codegen/index.test.d.ts +2 -0
  63. package/dist/codegen/index.test.d.ts.map +1 -0
  64. package/dist/codegen/index.test.js +310 -0
  65. package/dist/codegen/index.test.js.map +1 -0
  66. package/dist/codegen/type-mapper.d.ts +20 -0
  67. package/dist/codegen/type-mapper.d.ts.map +1 -0
  68. package/dist/codegen/type-mapper.js +238 -0
  69. package/dist/codegen/type-mapper.js.map +1 -0
  70. package/dist/codegen/type-mapper.test.d.ts +2 -0
  71. package/dist/codegen/type-mapper.test.d.ts.map +1 -0
  72. package/dist/codegen/type-mapper.test.js +167 -0
  73. package/dist/codegen/type-mapper.test.js.map +1 -0
  74. package/dist/codegen/utils.d.ts +46 -0
  75. package/dist/codegen/utils.d.ts.map +1 -0
  76. package/dist/codegen/utils.js +141 -0
  77. package/dist/codegen/utils.js.map +1 -0
  78. package/dist/codegen/utils.test.d.ts +2 -0
  79. package/dist/codegen/utils.test.d.ts.map +1 -0
  80. package/dist/codegen/utils.test.js +178 -0
  81. package/dist/codegen/utils.test.js.map +1 -0
  82. package/dist/generator/index.d.ts +3 -0
  83. package/dist/generator/index.d.ts.map +1 -1
  84. package/dist/generator/index.js +17 -1
  85. package/dist/generator/index.js.map +1 -1
  86. package/dist/generator/index.test.js +104 -1
  87. package/dist/generator/index.test.js.map +1 -1
  88. package/dist/generator/loader.d.ts +15 -0
  89. package/dist/generator/loader.d.ts.map +1 -1
  90. package/dist/generator/loader.js +24 -0
  91. package/dist/generator/loader.js.map +1 -1
  92. package/dist/test/handlers.d.ts +49 -0
  93. package/dist/test/handlers.d.ts.map +1 -1
  94. package/dist/test/handlers.js +45 -0
  95. package/dist/test/handlers.js.map +1 -1
  96. package/package.json +4 -2
  97. package/src/api/deploy.test.ts +135 -34
  98. package/src/api/deploy.ts +203 -23
  99. package/src/api/local.test.ts +250 -0
  100. package/src/api/local.ts +270 -0
  101. package/src/api/resources.test.ts +332 -0
  102. package/src/api/resources.ts +554 -0
  103. package/src/cli/commands/build.ts +115 -53
  104. package/src/cli/commands/deploy.ts +126 -0
  105. package/src/cli/commands/dev.ts +81 -36
  106. package/src/cli/commands/init.test.ts +239 -30
  107. package/src/cli/commands/init.ts +243 -26
  108. package/src/cli/config.test.ts +47 -0
  109. package/src/cli/config.ts +20 -0
  110. package/src/cli/index.ts +120 -11
  111. package/src/cli/utils/package-manager.test.ts +118 -0
  112. package/src/cli/utils/package-manager.ts +44 -0
  113. package/src/codegen/index.test.ts +367 -0
  114. package/src/codegen/index.ts +379 -0
  115. package/src/codegen/type-mapper.test.ts +224 -0
  116. package/src/codegen/type-mapper.ts +265 -0
  117. package/src/codegen/utils.test.ts +221 -0
  118. package/src/codegen/utils.ts +174 -0
  119. package/src/generator/index.test.ts +121 -1
  120. package/src/generator/index.ts +19 -1
  121. package/src/generator/loader.ts +43 -0
  122. package/src/test/handlers.ts +58 -0
@@ -4,12 +4,14 @@
4
4
 
5
5
  import * as fs from "fs";
6
6
  import * as path from "path";
7
+ import * as p from "@clack/prompts";
8
+ import pc from "picocolors";
7
9
  import {
8
10
  hasValidToken,
9
- getTinybirdDir,
10
11
  getRelativeTinybirdDir,
11
12
  getConfigPath,
12
13
  updateConfig,
14
+ type DevMode,
13
15
  } from "../config.js";
14
16
  import { browserLogin } from "../auth.js";
15
17
  import { saveTinybirdToken } from "../env.js";
@@ -40,9 +42,9 @@ export type PageViewsRow = InferRow<typeof pageViews>;
40
42
  `;
41
43
 
42
44
  /**
43
- * Default starter content for pipes.ts
45
+ * Default starter content for endpoints.ts
44
46
  */
45
- const PIPES_CONTENT = `import { defineEndpoint, node, t, p, type InferParams, type InferOutputRow } from "@tinybirdco/sdk";
47
+ const ENDPOINTS_CONTENT = `import { defineEndpoint, node, t, p, type InferParams, type InferOutputRow } from "@tinybirdco/sdk";
46
48
 
47
49
  /**
48
50
  * Top pages endpoint - get the most visited pages
@@ -88,7 +90,7 @@ const CLIENT_CONTENT = `/**
88
90
  * Tinybird Client
89
91
  *
90
92
  * This file defines the typed Tinybird client for your project.
91
- * Add your datasources and pipes here as you create them.
93
+ * Add your datasources and endpoints here as you create them.
92
94
  */
93
95
 
94
96
  import { createTinybirdClient } from "@tinybirdco/sdk";
@@ -97,7 +99,7 @@ import { createTinybirdClient } from "@tinybirdco/sdk";
97
99
  import { pageViews, type PageViewsRow } from "./datasources";
98
100
 
99
101
  // Import endpoints and their types
100
- import { topPages, type TopPagesParams, type TopPagesOutput } from "./pipes";
102
+ import { topPages, type TopPagesParams, type TopPagesOutput } from "./endpoints";
101
103
 
102
104
  // Create the typed Tinybird client
103
105
  export const tinybird = createTinybirdClient({
@@ -115,14 +117,21 @@ export { pageViews, topPages };
115
117
  /**
116
118
  * Default config content generator
117
119
  */
118
- function createDefaultConfig(tinybirdDir: string) {
120
+ function createDefaultConfig(
121
+ tinybirdDir: string,
122
+ devMode: DevMode,
123
+ additionalIncludes: string[] = []
124
+ ) {
125
+ const include = [
126
+ `${tinybirdDir}/datasources.ts`,
127
+ `${tinybirdDir}/endpoints.ts`,
128
+ ...additionalIncludes,
129
+ ];
119
130
  return {
120
- include: [
121
- `${tinybirdDir}/datasources.ts`,
122
- `${tinybirdDir}/pipes.ts`,
123
- ],
131
+ include,
124
132
  token: "${TINYBIRD_TOKEN}",
125
133
  baseUrl: "https://api.tinybird.co",
134
+ devMode,
126
135
  };
127
136
  }
128
137
 
@@ -136,6 +145,14 @@ export interface InitOptions {
136
145
  force?: boolean;
137
146
  /** Skip the login flow */
138
147
  skipLogin?: boolean;
148
+ /** Development mode - if provided, skip interactive prompt */
149
+ devMode?: DevMode;
150
+ /** Client path - if provided, skip interactive prompt */
151
+ clientPath?: string;
152
+ /** Skip prompts for existing datafiles - for testing */
153
+ skipDatafilePrompt?: boolean;
154
+ /** Auto-include existing datafiles without prompting - for testing */
155
+ includeExistingDatafiles?: boolean;
139
156
  }
140
157
 
141
158
  /**
@@ -156,6 +173,66 @@ export interface InitResult {
156
173
  workspaceName?: string;
157
174
  /** User email after login */
158
175
  userEmail?: string;
176
+ /** Selected development mode */
177
+ devMode?: DevMode;
178
+ /** Selected client path */
179
+ clientPath?: string;
180
+ /** Existing datafiles that were added to config */
181
+ existingDatafiles?: string[];
182
+ }
183
+
184
+ /**
185
+ * Find existing .datasource and .pipe files in the repository
186
+ *
187
+ * @param cwd - Working directory to search from
188
+ * @param maxDepth - Maximum directory depth to search (default: 5)
189
+ * @returns Array of relative file paths
190
+ */
191
+ export function findExistingDatafiles(
192
+ cwd: string,
193
+ maxDepth: number = 5
194
+ ): string[] {
195
+ const files: string[] = [];
196
+
197
+ function searchDir(dir: string, depth: number): void {
198
+ if (depth > maxDepth) return;
199
+
200
+ let entries: fs.Dirent[];
201
+ try {
202
+ entries = fs.readdirSync(dir, { withFileTypes: true });
203
+ } catch {
204
+ return; // Skip directories we can't read
205
+ }
206
+
207
+ for (const entry of entries) {
208
+ const fullPath = path.join(dir, entry.name);
209
+
210
+ // Skip node_modules and hidden directories
211
+ if (
212
+ entry.isDirectory() &&
213
+ (entry.name === "node_modules" ||
214
+ entry.name.startsWith(".") ||
215
+ entry.name === "dist" ||
216
+ entry.name === "build")
217
+ ) {
218
+ continue;
219
+ }
220
+
221
+ if (entry.isDirectory()) {
222
+ searchDir(fullPath, depth + 1);
223
+ } else if (
224
+ entry.isFile() &&
225
+ (entry.name.endsWith(".datasource") || entry.name.endsWith(".pipe"))
226
+ ) {
227
+ // Convert to relative path
228
+ const relativePath = path.relative(cwd, fullPath);
229
+ files.push(relativePath);
230
+ }
231
+ }
232
+ }
233
+
234
+ searchDir(cwd, 0);
235
+ return files.sort();
159
236
  }
160
237
 
161
238
  /**
@@ -163,7 +240,7 @@ export interface InitResult {
163
240
  *
164
241
  * Creates:
165
242
  * - tinybird.json in the project root
166
- * - src/tinybird/ folder with datasources.ts, pipes.ts, and client.ts
243
+ * - src/tinybird/ folder with datasources.ts, endpoints.ts, and client.ts
167
244
  *
168
245
  * @param options - Init options
169
246
  * @returns Init result
@@ -175,14 +252,90 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
175
252
 
176
253
  const created: string[] = [];
177
254
  const skipped: string[] = [];
255
+ let existingDatafiles: string[] = [];
256
+
257
+ // Check for existing .datasource and .pipe files
258
+ const foundDatafiles = findExistingDatafiles(cwd);
259
+
260
+ // Determine devMode - prompt if not provided
261
+ let devMode: DevMode = options.devMode ?? "branch";
262
+
263
+ if (!options.devMode) {
264
+ // Show interactive prompt for workflow selection
265
+ p.intro(pc.cyan("tinybird init"));
266
+
267
+ const workflowChoice = await p.select({
268
+ message: "How do you want to develop with Tinybird?",
269
+ options: [
270
+ {
271
+ value: "branch",
272
+ label: "Branches",
273
+ hint: "Use Tinybird Cloud with git-based branching",
274
+ },
275
+ {
276
+ value: "local",
277
+ label: "Tinybird Local",
278
+ hint: "Run your own Tinybird instance locally",
279
+ },
280
+ ],
281
+ });
282
+
283
+ if (p.isCancel(workflowChoice)) {
284
+ p.cancel("Init cancelled.");
285
+ return {
286
+ success: false,
287
+ created: [],
288
+ skipped: [],
289
+ error: "Cancelled by user",
290
+ };
291
+ }
292
+
293
+ devMode = workflowChoice as DevMode;
294
+ }
178
295
 
179
296
  // Determine tinybird folder path based on project structure
180
- const tinybirdDir = getTinybirdDir(cwd);
181
- const relativeTinybirdDir = getRelativeTinybirdDir(cwd);
297
+ const defaultRelativePath = getRelativeTinybirdDir(cwd);
298
+ let relativeTinybirdDir = options.clientPath ?? defaultRelativePath;
299
+
300
+ if (!options.clientPath && !options.devMode) {
301
+ // Ask user to confirm or change the client path
302
+ const clientPathChoice = await p.text({
303
+ message: "Where should we generate the Tinybird client?",
304
+ placeholder: defaultRelativePath,
305
+ defaultValue: defaultRelativePath,
306
+ });
307
+
308
+ if (p.isCancel(clientPathChoice)) {
309
+ p.cancel("Init cancelled.");
310
+ return {
311
+ success: false,
312
+ created: [],
313
+ skipped: [],
314
+ error: "Cancelled by user",
315
+ };
316
+ }
317
+
318
+ relativeTinybirdDir = clientPathChoice || defaultRelativePath;
319
+ }
320
+
321
+ // Ask about existing datafiles if found
322
+ if (foundDatafiles.length > 0 && !options.skipDatafilePrompt) {
323
+ const includeDatafiles =
324
+ options.includeExistingDatafiles ??
325
+ (await promptForExistingDatafiles(foundDatafiles));
326
+
327
+ if (includeDatafiles) {
328
+ existingDatafiles = foundDatafiles;
329
+ }
330
+ } else if (options.includeExistingDatafiles && foundDatafiles.length > 0) {
331
+ existingDatafiles = foundDatafiles;
332
+ }
333
+
334
+ const tinybirdDir = path.join(cwd, relativeTinybirdDir);
182
335
 
183
336
  // File paths
184
337
  const datasourcesPath = path.join(tinybirdDir, "datasources.ts");
185
- const pipesPath = path.join(tinybirdDir, "pipes.ts");
338
+ const endpointsPath = path.join(tinybirdDir, "endpoints.ts");
186
339
  const clientPath = path.join(tinybirdDir, "client.ts");
187
340
 
188
341
  // Create config file (tinybird.json)
@@ -191,7 +344,11 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
191
344
  skipped.push("tinybird.json");
192
345
  } else {
193
346
  try {
194
- const config = createDefaultConfig(relativeTinybirdDir);
347
+ const config = createDefaultConfig(
348
+ relativeTinybirdDir,
349
+ devMode,
350
+ existingDatafiles
351
+ );
195
352
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
196
353
  created.push("tinybird.json");
197
354
  } catch (error) {
@@ -212,7 +369,9 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
212
369
  success: false,
213
370
  created,
214
371
  skipped,
215
- error: `Failed to create ${relativeTinybirdDir} folder: ${(error as Error).message}`,
372
+ error: `Failed to create ${relativeTinybirdDir} folder: ${
373
+ (error as Error).message
374
+ }`,
216
375
  };
217
376
  }
218
377
 
@@ -233,19 +392,19 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
233
392
  }
234
393
  }
235
394
 
236
- // Create pipes.ts
237
- if (fs.existsSync(pipesPath) && !force) {
238
- skipped.push(`${relativeTinybirdDir}/pipes.ts`);
395
+ // Create endpoints.ts
396
+ if (fs.existsSync(endpointsPath) && !force) {
397
+ skipped.push(`${relativeTinybirdDir}/endpoints.ts`);
239
398
  } else {
240
399
  try {
241
- fs.writeFileSync(pipesPath, PIPES_CONTENT);
242
- created.push(`${relativeTinybirdDir}/pipes.ts`);
400
+ fs.writeFileSync(endpointsPath, ENDPOINTS_CONTENT);
401
+ created.push(`${relativeTinybirdDir}/endpoints.ts`);
243
402
  } catch (error) {
244
403
  return {
245
404
  success: false,
246
405
  created,
247
406
  skipped,
248
- error: `Failed to create pipes.ts: ${(error as Error).message}`,
407
+ error: `Failed to create endpoints.ts: ${(error as Error).message}`,
249
408
  };
250
409
  }
251
410
  }
@@ -288,8 +447,16 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
288
447
  modified = true;
289
448
  }
290
449
 
450
+ if (!packageJson.scripts["tinybird:deploy"]) {
451
+ packageJson.scripts["tinybird:deploy"] = "tinybird deploy";
452
+ modified = true;
453
+ }
454
+
291
455
  if (modified) {
292
- fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
456
+ fs.writeFileSync(
457
+ packageJsonPath,
458
+ JSON.stringify(packageJson, null, 2) + "\n"
459
+ );
293
460
  created.push("package.json (added tinybird scripts)");
294
461
  }
295
462
  } catch {
@@ -312,8 +479,9 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
312
479
  }
313
480
 
314
481
  // If custom base URL, update tinybird.json
315
- if (authResult.baseUrl && authResult.baseUrl !== "https://api.tinybird.co") {
316
- updateConfig(configPath, { baseUrl: authResult.baseUrl });
482
+ const baseUrl = authResult.baseUrl ?? "https://api.tinybird.co";
483
+ if (baseUrl !== "https://api.tinybird.co") {
484
+ updateConfig(configPath, { baseUrl });
317
485
  }
318
486
 
319
487
  return {
@@ -323,15 +491,25 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
323
491
  loggedIn: true,
324
492
  workspaceName: authResult.workspaceName,
325
493
  userEmail: authResult.userEmail,
494
+ devMode,
495
+ clientPath: relativeTinybirdDir,
496
+ existingDatafiles:
497
+ existingDatafiles.length > 0 ? existingDatafiles : undefined,
326
498
  };
327
499
  } catch (error) {
328
500
  // Login succeeded but saving credentials failed
329
- console.error(`Warning: Failed to save credentials: ${(error as Error).message}`);
501
+ console.error(
502
+ `Warning: Failed to save credentials: ${(error as Error).message}`
503
+ );
330
504
  return {
331
505
  success: true,
332
506
  created,
333
507
  skipped,
334
508
  loggedIn: false,
509
+ devMode,
510
+ clientPath: relativeTinybirdDir,
511
+ existingDatafiles:
512
+ existingDatafiles.length > 0 ? existingDatafiles : undefined,
335
513
  };
336
514
  }
337
515
  } else {
@@ -341,6 +519,10 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
341
519
  created,
342
520
  skipped,
343
521
  loggedIn: false,
522
+ devMode,
523
+ clientPath: relativeTinybirdDir,
524
+ existingDatafiles:
525
+ existingDatafiles.length > 0 ? existingDatafiles : undefined,
344
526
  };
345
527
  }
346
528
  }
@@ -349,5 +531,40 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
349
531
  success: true,
350
532
  created,
351
533
  skipped,
534
+ devMode,
535
+ clientPath: relativeTinybirdDir,
536
+ existingDatafiles:
537
+ existingDatafiles.length > 0 ? existingDatafiles : undefined,
352
538
  };
353
539
  }
540
+
541
+ /**
542
+ * Prompt user about including existing datafiles
543
+ */
544
+ async function promptForExistingDatafiles(
545
+ datafiles: string[]
546
+ ): Promise<boolean> {
547
+ const datasourceCount = datafiles.filter((f) =>
548
+ f.endsWith(".datasource")
549
+ ).length;
550
+ const pipeCount = datafiles.filter((f) => f.endsWith(".pipe")).length;
551
+
552
+ const parts: string[] = [];
553
+ if (datasourceCount > 0) {
554
+ parts.push(`${datasourceCount} .datasource file${datasourceCount > 1 ? "s" : ""}`);
555
+ }
556
+ if (pipeCount > 0) {
557
+ parts.push(`${pipeCount} .pipe file${pipeCount > 1 ? "s" : ""}`);
558
+ }
559
+
560
+ const confirmInclude = await p.confirm({
561
+ message: `Found ${parts.join(" and ")} in your project. Include them in tinybird.json?`,
562
+ initialValue: true,
563
+ });
564
+
565
+ if (p.isCancel(confirmInclude)) {
566
+ return false;
567
+ }
568
+
569
+ return confirmInclude;
570
+ }
@@ -267,6 +267,53 @@ describe("Config", () => {
267
267
 
268
268
  expect(() => loadConfig(tempDir)).toThrow("Failed to parse");
269
269
  });
270
+
271
+ it("defaults devMode to branch when not specified", () => {
272
+ const config = {
273
+ include: ["lib/datasources.ts"],
274
+ token: "test-token",
275
+ };
276
+ fs.writeFileSync(
277
+ path.join(tempDir, "tinybird.json"),
278
+ JSON.stringify(config)
279
+ );
280
+
281
+ const result = loadConfig(tempDir);
282
+
283
+ expect(result.devMode).toBe("branch");
284
+ });
285
+
286
+ it("loads devMode as branch when explicitly set", () => {
287
+ const config = {
288
+ include: ["lib/datasources.ts"],
289
+ token: "test-token",
290
+ devMode: "branch",
291
+ };
292
+ fs.writeFileSync(
293
+ path.join(tempDir, "tinybird.json"),
294
+ JSON.stringify(config)
295
+ );
296
+
297
+ const result = loadConfig(tempDir);
298
+
299
+ expect(result.devMode).toBe("branch");
300
+ });
301
+
302
+ it("loads devMode as local when set", () => {
303
+ const config = {
304
+ include: ["lib/datasources.ts"],
305
+ token: "test-token",
306
+ devMode: "local",
307
+ };
308
+ fs.writeFileSync(
309
+ path.join(tempDir, "tinybird.json"),
310
+ JSON.stringify(config)
311
+ );
312
+
313
+ const result = loadConfig(tempDir);
314
+
315
+ expect(result.devMode).toBe("local");
316
+ });
270
317
  });
271
318
 
272
319
  describe("updateConfig", () => {
package/src/cli/config.ts CHANGED
@@ -6,6 +6,13 @@ import * as fs from "fs";
6
6
  import * as path from "path";
7
7
  import { getCurrentGitBranch, isMainBranch, getTinybirdBranchName } from "./git.js";
8
8
 
9
+ /**
10
+ * Development mode options
11
+ * - "branch": Use Tinybird cloud with branches (default)
12
+ * - "local": Use local Tinybird container at localhost:7181
13
+ */
14
+ export type DevMode = "branch" | "local";
15
+
9
16
  /**
10
17
  * Tinybird configuration file structure
11
18
  */
@@ -18,6 +25,8 @@ export interface TinybirdConfig {
18
25
  token: string;
19
26
  /** Tinybird API base URL (optional, defaults to EU region) */
20
27
  baseUrl?: string;
28
+ /** Development mode: "branch" (default) or "local" */
29
+ devMode?: DevMode;
21
30
  }
22
31
 
23
32
  /**
@@ -40,6 +49,8 @@ export interface ResolvedConfig {
40
49
  tinybirdBranch: string | null;
41
50
  /** Whether we're on the main/master branch */
42
51
  isMainBranch: boolean;
52
+ /** Development mode: "branch" or "local" */
53
+ devMode: DevMode;
43
54
  }
44
55
 
45
56
  /**
@@ -47,6 +58,11 @@ export interface ResolvedConfig {
47
58
  */
48
59
  const DEFAULT_BASE_URL = "https://api.tinybird.co";
49
60
 
61
+ /**
62
+ * Local Tinybird base URL
63
+ */
64
+ export const LOCAL_BASE_URL = "http://localhost:7181";
65
+
50
66
  /**
51
67
  * Config file name
52
68
  */
@@ -243,6 +259,9 @@ export function loadConfig(cwd: string = process.cwd()): ResolvedConfig {
243
259
  const gitBranch = getCurrentGitBranch();
244
260
  const tinybirdBranch = getTinybirdBranchName();
245
261
 
262
+ // Resolve devMode (default to "branch")
263
+ const devMode: DevMode = config.devMode ?? "branch";
264
+
246
265
  return {
247
266
  include,
248
267
  token: resolvedToken,
@@ -252,6 +271,7 @@ export function loadConfig(cwd: string = process.cwd()): ResolvedConfig {
252
271
  gitBranch,
253
272
  tinybirdBranch,
254
273
  isMainBranch: isMainBranch(),
274
+ devMode,
255
275
  };
256
276
  }
257
277