@tinybirdco/sdk 0.0.4 → 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 (103) hide show
  1. package/README.md +52 -13
  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/resources.d.ts +178 -0
  9. package/dist/api/resources.d.ts.map +1 -0
  10. package/dist/api/resources.js +244 -0
  11. package/dist/api/resources.js.map +1 -0
  12. package/dist/api/resources.test.d.ts +2 -0
  13. package/dist/api/resources.test.d.ts.map +1 -0
  14. package/dist/api/resources.test.js +255 -0
  15. package/dist/api/resources.test.js.map +1 -0
  16. package/dist/cli/commands/build.d.ts +3 -4
  17. package/dist/cli/commands/build.d.ts.map +1 -1
  18. package/dist/cli/commands/build.js +23 -25
  19. package/dist/cli/commands/build.js.map +1 -1
  20. package/dist/cli/commands/deploy.d.ts +39 -0
  21. package/dist/cli/commands/deploy.d.ts.map +1 -0
  22. package/dist/cli/commands/deploy.js +90 -0
  23. package/dist/cli/commands/deploy.js.map +1 -0
  24. package/dist/cli/commands/dev.d.ts.map +1 -1
  25. package/dist/cli/commands/dev.js +7 -3
  26. package/dist/cli/commands/dev.js.map +1 -1
  27. package/dist/cli/commands/init.d.ts +24 -1
  28. package/dist/cli/commands/init.d.ts.map +1 -1
  29. package/dist/cli/commands/init.js +174 -23
  30. package/dist/cli/commands/init.js.map +1 -1
  31. package/dist/cli/commands/init.test.js +190 -30
  32. package/dist/cli/commands/init.test.js.map +1 -1
  33. package/dist/cli/index.js +72 -12
  34. package/dist/cli/index.js.map +1 -1
  35. package/dist/cli/utils/package-manager.d.ts +8 -0
  36. package/dist/cli/utils/package-manager.d.ts.map +1 -0
  37. package/dist/cli/utils/package-manager.js +45 -0
  38. package/dist/cli/utils/package-manager.js.map +1 -0
  39. package/dist/cli/utils/package-manager.test.d.ts +2 -0
  40. package/dist/cli/utils/package-manager.test.d.ts.map +1 -0
  41. package/dist/cli/utils/package-manager.test.js +85 -0
  42. package/dist/cli/utils/package-manager.test.js.map +1 -0
  43. package/dist/codegen/index.d.ts +39 -0
  44. package/dist/codegen/index.d.ts.map +1 -0
  45. package/dist/codegen/index.js +300 -0
  46. package/dist/codegen/index.js.map +1 -0
  47. package/dist/codegen/index.test.d.ts +2 -0
  48. package/dist/codegen/index.test.d.ts.map +1 -0
  49. package/dist/codegen/index.test.js +310 -0
  50. package/dist/codegen/index.test.js.map +1 -0
  51. package/dist/codegen/type-mapper.d.ts +20 -0
  52. package/dist/codegen/type-mapper.d.ts.map +1 -0
  53. package/dist/codegen/type-mapper.js +238 -0
  54. package/dist/codegen/type-mapper.js.map +1 -0
  55. package/dist/codegen/type-mapper.test.d.ts +2 -0
  56. package/dist/codegen/type-mapper.test.d.ts.map +1 -0
  57. package/dist/codegen/type-mapper.test.js +167 -0
  58. package/dist/codegen/type-mapper.test.js.map +1 -0
  59. package/dist/codegen/utils.d.ts +46 -0
  60. package/dist/codegen/utils.d.ts.map +1 -0
  61. package/dist/codegen/utils.js +141 -0
  62. package/dist/codegen/utils.js.map +1 -0
  63. package/dist/codegen/utils.test.d.ts +2 -0
  64. package/dist/codegen/utils.test.d.ts.map +1 -0
  65. package/dist/codegen/utils.test.js +178 -0
  66. package/dist/codegen/utils.test.js.map +1 -0
  67. package/dist/generator/index.d.ts +3 -0
  68. package/dist/generator/index.d.ts.map +1 -1
  69. package/dist/generator/index.js +17 -1
  70. package/dist/generator/index.js.map +1 -1
  71. package/dist/generator/index.test.js +104 -1
  72. package/dist/generator/index.test.js.map +1 -1
  73. package/dist/generator/loader.d.ts +15 -0
  74. package/dist/generator/loader.d.ts.map +1 -1
  75. package/dist/generator/loader.js +24 -0
  76. package/dist/generator/loader.js.map +1 -1
  77. package/dist/test/handlers.d.ts +49 -0
  78. package/dist/test/handlers.d.ts.map +1 -1
  79. package/dist/test/handlers.js +45 -0
  80. package/dist/test/handlers.js.map +1 -1
  81. package/package.json +4 -2
  82. package/src/api/deploy.test.ts +135 -34
  83. package/src/api/deploy.ts +203 -23
  84. package/src/api/resources.test.ts +332 -0
  85. package/src/api/resources.ts +554 -0
  86. package/src/cli/commands/build.ts +29 -33
  87. package/src/cli/commands/deploy.ts +126 -0
  88. package/src/cli/commands/dev.ts +10 -3
  89. package/src/cli/commands/init.test.ts +239 -30
  90. package/src/cli/commands/init.ts +243 -26
  91. package/src/cli/index.ts +84 -11
  92. package/src/cli/utils/package-manager.test.ts +118 -0
  93. package/src/cli/utils/package-manager.ts +44 -0
  94. package/src/codegen/index.test.ts +367 -0
  95. package/src/codegen/index.ts +379 -0
  96. package/src/codegen/type-mapper.test.ts +224 -0
  97. package/src/codegen/type-mapper.ts +265 -0
  98. package/src/codegen/utils.test.ts +221 -0
  99. package/src/codegen/utils.ts +174 -0
  100. package/src/generator/index.test.ts +121 -1
  101. package/src/generator/index.ts +19 -1
  102. package/src/generator/loader.ts +43 -0
  103. package/src/test/handlers.ts +58 -0
@@ -0,0 +1,554 @@
1
+ /**
2
+ * Tinybird Resources API client
3
+ * Functions to list and fetch datasources and pipes from a workspace
4
+ */
5
+
6
+ import type { WorkspaceApiConfig } from "./workspaces.js";
7
+
8
+ /**
9
+ * Error thrown by resource API operations
10
+ */
11
+ export class ResourceApiError extends Error {
12
+ constructor(
13
+ message: string,
14
+ public readonly status: number,
15
+ public readonly endpoint: string,
16
+ public readonly body?: unknown
17
+ ) {
18
+ super(message);
19
+ this.name = "ResourceApiError";
20
+ }
21
+ }
22
+
23
+ // ============ Datasource Types ============
24
+
25
+ /**
26
+ * Column information from Tinybird API
27
+ */
28
+ export interface DatasourceColumn {
29
+ /** Column name */
30
+ name: string;
31
+ /** ClickHouse type (e.g., "String", "DateTime", "Nullable(String)") */
32
+ type: string;
33
+ /** JSON path for JSON extraction */
34
+ jsonpath?: string;
35
+ /** Default value expression */
36
+ default?: string;
37
+ /** Codec for compression */
38
+ codec?: string;
39
+ }
40
+
41
+ /**
42
+ * Engine information from Tinybird API
43
+ */
44
+ export interface DatasourceEngine {
45
+ /** Engine type (e.g., "MergeTree", "ReplacingMergeTree") */
46
+ type: string;
47
+ /** Sorting key columns */
48
+ sorting_key?: string;
49
+ /** Partition key expression */
50
+ partition_key?: string;
51
+ /** Primary key columns */
52
+ primary_key?: string;
53
+ /** TTL expression */
54
+ ttl?: string;
55
+ /** Version column (ReplacingMergeTree) */
56
+ ver?: string;
57
+ /** Sign column (CollapsingMergeTree) */
58
+ sign?: string;
59
+ /** Version column (VersionedCollapsingMergeTree) */
60
+ version?: string;
61
+ /** Summing columns (SummingMergeTree) */
62
+ summing_columns?: string;
63
+ }
64
+
65
+ /**
66
+ * Full datasource information from Tinybird API
67
+ */
68
+ export interface DatasourceInfo {
69
+ /** Datasource name */
70
+ name: string;
71
+ /** Human-readable description */
72
+ description?: string;
73
+ /** Column definitions */
74
+ columns: DatasourceColumn[];
75
+ /** Engine configuration */
76
+ engine: DatasourceEngine;
77
+ }
78
+
79
+ /**
80
+ * Datasource list item from /v0/datasources
81
+ */
82
+ interface DatasourceListItem {
83
+ name: string;
84
+ description?: string;
85
+ }
86
+
87
+ /**
88
+ * Column definition from API response
89
+ */
90
+ interface ColumnResponse {
91
+ name: string;
92
+ type: string;
93
+ jsonpath?: string;
94
+ default_value?: string;
95
+ codec?: string;
96
+ nullable?: boolean;
97
+ }
98
+
99
+ /**
100
+ * Engine object from API response
101
+ * Note: The detail endpoint uses different property names than the list endpoint
102
+ */
103
+ interface EngineResponse {
104
+ engine?: string;
105
+ // Detail endpoint uses these names
106
+ sorting_key?: string;
107
+ partition_key?: string;
108
+ primary_key?: string;
109
+ // List endpoint uses these names
110
+ engine_sorting_key?: string;
111
+ engine_partition_key?: string;
112
+ engine_primary_key?: string;
113
+ // Other engine properties
114
+ engine_ver?: string;
115
+ engine_sign?: string;
116
+ engine_version?: string;
117
+ engine_summing_columns?: string;
118
+ }
119
+
120
+ /**
121
+ * Datasource detail response from /v0/datasources/{name}
122
+ */
123
+ interface DatasourceDetailResponse {
124
+ name: string;
125
+ description?: string;
126
+ // Detail endpoint has columns under schema.columns
127
+ schema?: {
128
+ columns?: ColumnResponse[];
129
+ };
130
+ // List endpoint has columns at top level
131
+ columns?: ColumnResponse[];
132
+ engine?: EngineResponse;
133
+ sorting_key?: string;
134
+ partition_key?: string;
135
+ primary_key?: string;
136
+ ttl?: string;
137
+ }
138
+
139
+ // ============ Pipe Types ============
140
+
141
+ /**
142
+ * Node information from a pipe
143
+ */
144
+ export interface PipeNode {
145
+ /** Node name */
146
+ name: string;
147
+ /** SQL query */
148
+ sql: string;
149
+ /** Node description */
150
+ description?: string;
151
+ }
152
+
153
+ /**
154
+ * Parameter information from a pipe
155
+ */
156
+ export interface PipeParam {
157
+ /** Parameter name */
158
+ name: string;
159
+ /** ClickHouse type */
160
+ type: string;
161
+ /** Default value */
162
+ default?: string | number;
163
+ /** Whether the parameter is required */
164
+ required: boolean;
165
+ /** Parameter description */
166
+ description?: string;
167
+ }
168
+
169
+ /**
170
+ * Pipe type classification
171
+ */
172
+ export type PipeType = "endpoint" | "materialized" | "copy" | "pipe";
173
+
174
+ /**
175
+ * Full pipe information from Tinybird API
176
+ */
177
+ export interface PipeInfo {
178
+ /** Pipe name */
179
+ name: string;
180
+ /** Human-readable description */
181
+ description?: string;
182
+ /** Nodes in the pipe */
183
+ nodes: PipeNode[];
184
+ /** Query parameters */
185
+ params: PipeParam[];
186
+ /** Pipe type */
187
+ type: PipeType;
188
+ /** Endpoint configuration (if type is endpoint) */
189
+ endpoint?: {
190
+ enabled: boolean;
191
+ cache?: { enabled: boolean; ttl?: number };
192
+ };
193
+ /** Materialized view configuration (if type is materialized) */
194
+ materialized?: {
195
+ datasource: string;
196
+ };
197
+ /** Copy pipe configuration (if type is copy) */
198
+ copy?: {
199
+ target_datasource: string;
200
+ copy_schedule?: string;
201
+ copy_mode?: "append" | "replace";
202
+ };
203
+ /** Output column schema (for endpoints) */
204
+ output_columns: DatasourceColumn[];
205
+ }
206
+
207
+ /**
208
+ * Pipe list item from /v0/pipes
209
+ */
210
+ interface PipeListItem {
211
+ name: string;
212
+ description?: string;
213
+ type?: string;
214
+ endpoint?: string;
215
+ }
216
+
217
+ /**
218
+ * Pipe detail response from /v0/pipes/{name}
219
+ */
220
+ interface PipeDetailResponse {
221
+ name: string;
222
+ description?: string;
223
+ nodes?: Array<{
224
+ name: string;
225
+ sql: string;
226
+ description?: string;
227
+ params?: Array<{
228
+ name: string;
229
+ type: string;
230
+ default?: string | number;
231
+ required?: boolean;
232
+ description?: string;
233
+ }>;
234
+ columns?: Array<{
235
+ name: string;
236
+ type: string;
237
+ }>;
238
+ }>;
239
+ type?: string;
240
+ endpoint?: string;
241
+ copy_target_datasource?: string;
242
+ copy_schedule?: string;
243
+ copy_mode?: string;
244
+ materialized_datasource?: string;
245
+ }
246
+
247
+ // ============ API Helper ============
248
+
249
+ /**
250
+ * Handle API response and throw appropriate errors
251
+ */
252
+ async function handleApiResponse<T>(
253
+ response: Response,
254
+ endpoint: string
255
+ ): Promise<T> {
256
+ if (response.status === 401) {
257
+ throw new ResourceApiError(
258
+ "Invalid or expired token",
259
+ 401,
260
+ endpoint
261
+ );
262
+ }
263
+ if (response.status === 403) {
264
+ throw new ResourceApiError(
265
+ "Insufficient permissions to access resources",
266
+ 403,
267
+ endpoint
268
+ );
269
+ }
270
+ if (response.status === 404) {
271
+ throw new ResourceApiError(
272
+ "Resource not found",
273
+ 404,
274
+ endpoint
275
+ );
276
+ }
277
+ if (!response.ok) {
278
+ const body = await response.text();
279
+ throw new ResourceApiError(
280
+ `API request failed: ${response.status} ${response.statusText}`,
281
+ response.status,
282
+ endpoint,
283
+ body
284
+ );
285
+ }
286
+ return response.json() as Promise<T>;
287
+ }
288
+
289
+ // ============ Datasource API ============
290
+
291
+ /**
292
+ * List all datasources in the workspace
293
+ *
294
+ * @param config - API configuration
295
+ * @returns Array of datasource names
296
+ */
297
+ export async function listDatasources(
298
+ config: WorkspaceApiConfig
299
+ ): Promise<string[]> {
300
+ const url = new URL("/v0/datasources", config.baseUrl);
301
+
302
+ const response = await fetch(url.toString(), {
303
+ method: "GET",
304
+ headers: {
305
+ Authorization: `Bearer ${config.token}`,
306
+ },
307
+ });
308
+
309
+ const data = await handleApiResponse<{ datasources: DatasourceListItem[] }>(
310
+ response,
311
+ "/v0/datasources"
312
+ );
313
+
314
+ return data.datasources.map((ds) => ds.name);
315
+ }
316
+
317
+ /**
318
+ * Get detailed information about a specific datasource
319
+ *
320
+ * @param config - API configuration
321
+ * @param name - Datasource name
322
+ * @returns Datasource information including schema and engine
323
+ */
324
+ export async function getDatasource(
325
+ config: WorkspaceApiConfig,
326
+ name: string
327
+ ): Promise<DatasourceInfo> {
328
+ const url = new URL(`/v0/datasources/${encodeURIComponent(name)}`, config.baseUrl);
329
+
330
+ const response = await fetch(url.toString(), {
331
+ method: "GET",
332
+ headers: {
333
+ Authorization: `Bearer ${config.token}`,
334
+ },
335
+ });
336
+
337
+ const data = await handleApiResponse<DatasourceDetailResponse>(
338
+ response,
339
+ `/v0/datasources/${name}`
340
+ );
341
+
342
+ // Extract columns from either schema.columns (detail) or columns (list)
343
+ const rawColumns = data.schema?.columns ?? data.columns ?? [];
344
+
345
+ // Extract engine info from the engine object
346
+ const engineObj = data.engine;
347
+ const engineType = parseEngineType(engineObj?.engine);
348
+
349
+ // Engine properties can be in different places depending on the endpoint
350
+ const sortingKey =
351
+ engineObj?.sorting_key ?? engineObj?.engine_sorting_key ?? data.sorting_key;
352
+ const partitionKey =
353
+ engineObj?.partition_key ?? engineObj?.engine_partition_key ?? data.partition_key;
354
+ const primaryKey =
355
+ engineObj?.primary_key ?? engineObj?.engine_primary_key ?? data.primary_key;
356
+
357
+ return {
358
+ name: data.name,
359
+ description: data.description,
360
+ columns: rawColumns.map((col) => ({
361
+ name: col.name,
362
+ type: col.type,
363
+ jsonpath: col.jsonpath,
364
+ default: col.default_value,
365
+ codec: col.codec,
366
+ })),
367
+ engine: {
368
+ type: engineType,
369
+ sorting_key: sortingKey,
370
+ partition_key: partitionKey,
371
+ primary_key: primaryKey,
372
+ ttl: data.ttl,
373
+ ver: engineObj?.engine_ver,
374
+ sign: engineObj?.engine_sign,
375
+ version: engineObj?.engine_version,
376
+ summing_columns: engineObj?.engine_summing_columns,
377
+ },
378
+ };
379
+ }
380
+
381
+ /**
382
+ * Parse engine type from engine string
383
+ */
384
+ function parseEngineType(engineString?: string): string {
385
+ if (!engineString) {
386
+ return "MergeTree";
387
+ }
388
+
389
+ // Engine string might be like "MergeTree" or "ReplacingMergeTree(version_column)"
390
+ const match = engineString.match(/^(\w+)/);
391
+ return match ? match[1] : "MergeTree";
392
+ }
393
+
394
+ // ============ Pipe API ============
395
+
396
+ /**
397
+ * List all pipes in the workspace
398
+ *
399
+ * @param config - API configuration
400
+ * @returns Array of pipe names
401
+ */
402
+ export async function listPipes(
403
+ config: WorkspaceApiConfig
404
+ ): Promise<string[]> {
405
+ const url = new URL("/v0/pipes", config.baseUrl);
406
+
407
+ const response = await fetch(url.toString(), {
408
+ method: "GET",
409
+ headers: {
410
+ Authorization: `Bearer ${config.token}`,
411
+ },
412
+ });
413
+
414
+ const data = await handleApiResponse<{ pipes: PipeListItem[] }>(
415
+ response,
416
+ "/v0/pipes"
417
+ );
418
+
419
+ return data.pipes.map((p) => p.name);
420
+ }
421
+
422
+ /**
423
+ * Get detailed information about a specific pipe
424
+ *
425
+ * @param config - API configuration
426
+ * @param name - Pipe name
427
+ * @returns Pipe information including nodes, params, and output schema
428
+ */
429
+ export async function getPipe(
430
+ config: WorkspaceApiConfig,
431
+ name: string
432
+ ): Promise<PipeInfo> {
433
+ const url = new URL(`/v0/pipes/${encodeURIComponent(name)}`, config.baseUrl);
434
+
435
+ const response = await fetch(url.toString(), {
436
+ method: "GET",
437
+ headers: {
438
+ Authorization: `Bearer ${config.token}`,
439
+ },
440
+ });
441
+
442
+ const data = await handleApiResponse<PipeDetailResponse>(
443
+ response,
444
+ `/v0/pipes/${name}`
445
+ );
446
+
447
+ // Determine pipe type
448
+ let pipeType: PipeType = "pipe";
449
+ if (data.endpoint) {
450
+ pipeType = "endpoint";
451
+ } else if (data.materialized_datasource) {
452
+ pipeType = "materialized";
453
+ } else if (data.copy_target_datasource) {
454
+ pipeType = "copy";
455
+ }
456
+
457
+ // Extract nodes
458
+ const nodes: PipeNode[] = (data.nodes ?? []).map((node) => ({
459
+ name: node.name,
460
+ sql: node.sql,
461
+ description: node.description,
462
+ }));
463
+
464
+ // Extract params from all nodes (they're typically on the first node)
465
+ const params: PipeParam[] = [];
466
+ const seenParams = new Set<string>();
467
+ for (const node of data.nodes ?? []) {
468
+ for (const param of node.params ?? []) {
469
+ if (!seenParams.has(param.name)) {
470
+ seenParams.add(param.name);
471
+ params.push({
472
+ name: param.name,
473
+ type: param.type,
474
+ default: param.default,
475
+ required: param.required ?? true,
476
+ description: param.description,
477
+ });
478
+ }
479
+ }
480
+ }
481
+
482
+ // Extract output columns from the last node
483
+ const lastNode = data.nodes?.[data.nodes.length - 1];
484
+ const outputColumns: DatasourceColumn[] = (lastNode?.columns ?? []).map((col) => ({
485
+ name: col.name,
486
+ type: col.type,
487
+ }));
488
+
489
+ return {
490
+ name: data.name,
491
+ description: data.description,
492
+ nodes,
493
+ params,
494
+ type: pipeType,
495
+ endpoint: pipeType === "endpoint" ? { enabled: true } : undefined,
496
+ materialized: data.materialized_datasource
497
+ ? { datasource: data.materialized_datasource }
498
+ : undefined,
499
+ copy: data.copy_target_datasource
500
+ ? {
501
+ target_datasource: data.copy_target_datasource,
502
+ copy_schedule: data.copy_schedule,
503
+ copy_mode: data.copy_mode as "append" | "replace" | undefined,
504
+ }
505
+ : undefined,
506
+ output_columns: outputColumns,
507
+ };
508
+ }
509
+
510
+ // ============ Convenience Functions ============
511
+
512
+ /**
513
+ * Fetch all resources from a workspace
514
+ *
515
+ * @param config - API configuration
516
+ * @returns All datasources and pipes with full details
517
+ */
518
+ export async function fetchAllResources(
519
+ config: WorkspaceApiConfig
520
+ ): Promise<{
521
+ datasources: DatasourceInfo[];
522
+ pipes: PipeInfo[];
523
+ }> {
524
+ // List all resources first
525
+ const [datasourceNames, pipeNames] = await Promise.all([
526
+ listDatasources(config),
527
+ listPipes(config),
528
+ ]);
529
+
530
+ // Fetch details in parallel
531
+ const [datasources, pipes] = await Promise.all([
532
+ Promise.all(datasourceNames.map((name) => getDatasource(config, name))),
533
+ Promise.all(pipeNames.map((name) => getPipe(config, name))),
534
+ ]);
535
+
536
+ return { datasources, pipes };
537
+ }
538
+
539
+ /**
540
+ * Check if a workspace has any resources
541
+ *
542
+ * @param config - API configuration
543
+ * @returns True if the workspace has at least one datasource or pipe
544
+ */
545
+ export async function hasResources(
546
+ config: WorkspaceApiConfig
547
+ ): Promise<boolean> {
548
+ const [datasourceNames, pipeNames] = await Promise.all([
549
+ listDatasources(config),
550
+ listPipes(config),
551
+ ]);
552
+
553
+ return datasourceNames.length > 0 || pipeNames.length > 0;
554
+ }
@@ -1,11 +1,10 @@
1
1
  /**
2
- * Build command - generates and pushes resources to Tinybird
2
+ * Build command - generates and pushes resources to Tinybird branches
3
3
  */
4
4
 
5
5
  import { loadConfig, LOCAL_BASE_URL, type ResolvedConfig, type DevMode } from "../config.js";
6
6
  import { buildFromInclude, type BuildFromIncludeResult } from "../../generator/index.js";
7
7
  import { buildToTinybird, type BuildApiResult } from "../../api/build.js";
8
- import { deployToMain } from "../../api/deploy.js";
9
8
  import { getOrCreateBranch } from "../../api/branches.js";
10
9
  import {
11
10
  getLocalTokens,
@@ -24,8 +23,6 @@ export interface BuildCommandOptions {
24
23
  dryRun?: boolean;
25
24
  /** Override the token from config (used for branch tokens) */
26
25
  tokenOverride?: string;
27
- /** Use /v1/deploy instead of /v1/build (for main branch) */
28
- useDeployEndpoint?: boolean;
29
26
  /** Override the devMode from config */
30
27
  devModeOverride?: DevMode;
31
28
  }
@@ -49,7 +46,8 @@ export interface BuildCommandResult {
49
46
  /**
50
47
  * Run the build command
51
48
  *
52
- * Loads the schema, generates resources, and pushes to Tinybird API.
49
+ * Builds resources and pushes to Tinybird branches (not main).
50
+ * Use runDeploy for deploying to production.
53
51
  *
54
52
  * @param options - Build options
55
53
  * @returns Build command result
@@ -150,12 +148,19 @@ export async function runBuild(options: BuildCommandOptions = {}): Promise<Build
150
148
  };
151
149
  }
152
150
  } else {
153
- // Branch mode (default) - existing logic
154
- // Deploy to Tinybird
155
- // Determine token and endpoint based on git branch
156
- let effectiveToken = options.tokenOverride ?? config.token;
157
- // Use deploy endpoint if on main branch OR if no branch can be detected
158
- let useDeployEndpoint = options.useDeployEndpoint ?? (config.isMainBranch || !config.tinybirdBranch);
151
+ // Branch mode (default)
152
+ // Prevent building to main - must use deploy command
153
+ // Skip this check if tokenOverride is provided (dev command passes branch token)
154
+ const isMainBranch = config.isMainBranch || !config.tinybirdBranch;
155
+
156
+ if (isMainBranch && !options.tokenOverride) {
157
+ return {
158
+ success: false,
159
+ build: buildResult,
160
+ error: `Cannot deploy to main workspace with 'build' command. Use 'tinybird deploy' to deploy to production, or switch to a feature branch.`,
161
+ durationMs: Date.now() - startTime,
162
+ };
163
+ }
159
164
 
160
165
  if (debug) {
161
166
  console.log(`[debug] isMainBranch: ${config.isMainBranch}`);
@@ -163,8 +168,10 @@ export async function runBuild(options: BuildCommandOptions = {}): Promise<Build
163
168
  console.log(`[debug] tokenOverride: ${!!options.tokenOverride}`);
164
169
  }
165
170
 
166
- // For feature branches, get or create the Tinybird branch and use its token
167
- if (!config.isMainBranch && config.tinybirdBranch && !options.tokenOverride) {
171
+ let effectiveToken = options.tokenOverride ?? config.token;
172
+
173
+ // Get or create the Tinybird branch and use its token
174
+ if (!options.tokenOverride) {
168
175
  if (debug) {
169
176
  console.log(`[debug] Getting/creating Tinybird branch: ${config.tinybirdBranch}`);
170
177
  }
@@ -174,7 +181,7 @@ export async function runBuild(options: BuildCommandOptions = {}): Promise<Build
174
181
  baseUrl: config.baseUrl,
175
182
  token: config.token,
176
183
  },
177
- config.tinybirdBranch
184
+ config.tinybirdBranch!
178
185
  );
179
186
 
180
187
  if (!tinybirdBranch.token) {
@@ -187,7 +194,6 @@ export async function runBuild(options: BuildCommandOptions = {}): Promise<Build
187
194
  }
188
195
 
189
196
  effectiveToken = tinybirdBranch.token;
190
- useDeployEndpoint = false; // Always use /v1/build for branches
191
197
  if (debug) {
192
198
  console.log(`[debug] Using branch token for branch: ${config.tinybirdBranch}`);
193
199
  }
@@ -202,24 +208,14 @@ export async function runBuild(options: BuildCommandOptions = {}): Promise<Build
202
208
  }
203
209
 
204
210
  try {
205
- // Use /v1/deploy for main branch, /v1/build for feature branches
206
- if (useDeployEndpoint) {
207
- deployResult = await deployToMain(
208
- {
209
- baseUrl: config.baseUrl,
210
- token: effectiveToken,
211
- },
212
- buildResult.resources
213
- );
214
- } else {
215
- deployResult = await buildToTinybird(
216
- {
217
- baseUrl: config.baseUrl,
218
- token: effectiveToken,
219
- },
220
- buildResult.resources
221
- );
222
- }
211
+ // Always use /v1/build for branches
212
+ deployResult = await buildToTinybird(
213
+ {
214
+ baseUrl: config.baseUrl,
215
+ token: effectiveToken,
216
+ },
217
+ buildResult.resources
218
+ );
223
219
  } catch (error) {
224
220
  return {
225
221
  success: false,