@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
@@ -1,12 +1,17 @@
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
- import { loadConfig, type ResolvedConfig } from "../config.js";
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";
9
+ import {
10
+ getLocalTokens,
11
+ getOrCreateLocalWorkspace,
12
+ getLocalWorkspaceName,
13
+ LocalNotRunningError,
14
+ } from "../../api/local.js";
10
15
 
11
16
  /**
12
17
  * Build command options
@@ -18,8 +23,8 @@ export interface BuildCommandOptions {
18
23
  dryRun?: boolean;
19
24
  /** Override the token from config (used for branch tokens) */
20
25
  tokenOverride?: string;
21
- /** Use /v1/deploy instead of /v1/build (for main branch) */
22
- useDeployEndpoint?: boolean;
26
+ /** Override the devMode from config */
27
+ devModeOverride?: DevMode;
23
28
  }
24
29
 
25
30
  /**
@@ -41,7 +46,8 @@ export interface BuildCommandResult {
41
46
  /**
42
47
  * Run the build command
43
48
  *
44
- * 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.
45
51
  *
46
52
  * @param options - Build options
47
53
  * @returns Build command result
@@ -86,67 +92,123 @@ export async function runBuild(options: BuildCommandOptions = {}): Promise<Build
86
92
  };
87
93
  }
88
94
 
89
- // Deploy to Tinybird
90
- // Determine token and endpoint based on git branch
91
- let effectiveToken = options.tokenOverride ?? config.token;
92
- let useDeployEndpoint = options.useDeployEndpoint ?? config.isMainBranch;
93
-
94
- // For feature branches, get or create the Tinybird branch and use its token
95
+ // Determine devMode
96
+ const devMode = options.devModeOverride ?? config.devMode;
95
97
  const debug = !!process.env.TINYBIRD_DEBUG;
98
+
96
99
  if (debug) {
97
- console.log(`[debug] isMainBranch: ${config.isMainBranch}`);
98
- console.log(`[debug] tinybirdBranch: ${config.tinybirdBranch}`);
99
- console.log(`[debug] tokenOverride: ${!!options.tokenOverride}`);
100
+ console.log(`[debug] devMode: ${devMode}`);
100
101
  }
101
- if (!config.isMainBranch && config.tinybirdBranch && !options.tokenOverride) {
102
- if (debug) {
103
- console.log(`[debug] Getting/creating Tinybird branch: ${config.tinybirdBranch}`);
104
- }
102
+
103
+ let deployResult: BuildApiResult;
104
+
105
+ // Handle local mode
106
+ if (devMode === "local") {
105
107
  try {
106
- const tinybirdBranch = await getOrCreateBranch(
108
+ // Get tokens from local container
109
+ if (debug) {
110
+ console.log(`[debug] Getting local tokens from ${LOCAL_BASE_URL}/tokens`);
111
+ }
112
+
113
+ const localTokens = await getLocalTokens();
114
+
115
+ // Get or create workspace based on branch name
116
+ const workspaceName = getLocalWorkspaceName(config.tinybirdBranch, config.cwd);
117
+ if (debug) {
118
+ console.log(`[debug] Using local workspace: ${workspaceName}`);
119
+ }
120
+
121
+ const { workspace, wasCreated } = await getOrCreateLocalWorkspace(localTokens, workspaceName);
122
+ if (debug) {
123
+ console.log(`[debug] Workspace ${wasCreated ? "created" : "found"}: ${workspace.name}`);
124
+ }
125
+
126
+ // Always use /v1/build for local (no deploy endpoint)
127
+ deployResult = await buildToTinybird(
107
128
  {
108
- baseUrl: config.baseUrl,
109
- token: config.token,
129
+ baseUrl: LOCAL_BASE_URL,
130
+ token: workspace.token,
110
131
  },
111
- config.tinybirdBranch
132
+ buildResult.resources
112
133
  );
113
-
114
- if (!tinybirdBranch.token) {
134
+ } catch (error) {
135
+ if (error instanceof LocalNotRunningError) {
115
136
  return {
116
137
  success: false,
117
138
  build: buildResult,
118
- error: `Branch '${config.tinybirdBranch}' was created but no token was returned.`,
139
+ error: error.message,
119
140
  durationMs: Date.now() - startTime,
120
141
  };
121
142
  }
143
+ return {
144
+ success: false,
145
+ build: buildResult,
146
+ error: `Local build failed: ${(error as Error).message}`,
147
+ durationMs: Date.now() - startTime,
148
+ };
149
+ }
150
+ } else {
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;
122
155
 
123
- effectiveToken = tinybirdBranch.token;
124
- useDeployEndpoint = false; // Always use /v1/build for branches
125
- if (debug) {
126
- console.log(`[debug] Using branch token for branch: ${config.tinybirdBranch}`);
127
- }
128
- } catch (error) {
156
+ if (isMainBranch && !options.tokenOverride) {
129
157
  return {
130
158
  success: false,
131
159
  build: buildResult,
132
- error: `Failed to get/create branch: ${(error as Error).message}`,
160
+ error: `Cannot deploy to main workspace with 'build' command. Use 'tinybird deploy' to deploy to production, or switch to a feature branch.`,
133
161
  durationMs: Date.now() - startTime,
134
162
  };
135
163
  }
136
- }
137
164
 
138
- let deployResult: BuildApiResult;
139
- try {
140
- // Use /v1/deploy for main branch, /v1/build for feature branches
141
- if (useDeployEndpoint) {
142
- deployResult = await deployToMain(
143
- {
144
- baseUrl: config.baseUrl,
145
- token: effectiveToken,
146
- },
147
- buildResult.resources
148
- );
149
- } else {
165
+ if (debug) {
166
+ console.log(`[debug] isMainBranch: ${config.isMainBranch}`);
167
+ console.log(`[debug] tinybirdBranch: ${config.tinybirdBranch}`);
168
+ console.log(`[debug] tokenOverride: ${!!options.tokenOverride}`);
169
+ }
170
+
171
+ let effectiveToken = options.tokenOverride ?? config.token;
172
+
173
+ // Get or create the Tinybird branch and use its token
174
+ if (!options.tokenOverride) {
175
+ if (debug) {
176
+ console.log(`[debug] Getting/creating Tinybird branch: ${config.tinybirdBranch}`);
177
+ }
178
+ try {
179
+ const tinybirdBranch = await getOrCreateBranch(
180
+ {
181
+ baseUrl: config.baseUrl,
182
+ token: config.token,
183
+ },
184
+ config.tinybirdBranch!
185
+ );
186
+
187
+ if (!tinybirdBranch.token) {
188
+ return {
189
+ success: false,
190
+ build: buildResult,
191
+ error: `Branch '${config.tinybirdBranch}' was created but no token was returned.`,
192
+ durationMs: Date.now() - startTime,
193
+ };
194
+ }
195
+
196
+ effectiveToken = tinybirdBranch.token;
197
+ if (debug) {
198
+ console.log(`[debug] Using branch token for branch: ${config.tinybirdBranch}`);
199
+ }
200
+ } catch (error) {
201
+ return {
202
+ success: false,
203
+ build: buildResult,
204
+ error: `Failed to get/create branch: ${(error as Error).message}`,
205
+ durationMs: Date.now() - startTime,
206
+ };
207
+ }
208
+ }
209
+
210
+ try {
211
+ // Always use /v1/build for branches
150
212
  deployResult = await buildToTinybird(
151
213
  {
152
214
  baseUrl: config.baseUrl,
@@ -154,14 +216,14 @@ export async function runBuild(options: BuildCommandOptions = {}): Promise<Build
154
216
  },
155
217
  buildResult.resources
156
218
  );
219
+ } catch (error) {
220
+ return {
221
+ success: false,
222
+ build: buildResult,
223
+ error: `Deploy failed: ${(error as Error).message}`,
224
+ durationMs: Date.now() - startTime,
225
+ };
157
226
  }
158
- } catch (error) {
159
- return {
160
- success: false,
161
- build: buildResult,
162
- error: `Deploy failed: ${(error as Error).message}`,
163
- durationMs: Date.now() - startTime,
164
- };
165
227
  }
166
228
 
167
229
  if (!deployResult.success) {
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Deploy command - deploys resources to main Tinybird workspace
3
+ */
4
+
5
+ import { loadConfig, type ResolvedConfig } from "../config.js";
6
+ import { buildFromInclude, type BuildFromIncludeResult } from "../../generator/index.js";
7
+ import { deployToMain } from "../../api/deploy.js";
8
+ import type { BuildApiResult } from "../../api/build.js";
9
+
10
+ /**
11
+ * Deploy command options
12
+ */
13
+ export interface DeployCommandOptions {
14
+ /** Working directory (defaults to cwd) */
15
+ cwd?: string;
16
+ /** Skip pushing to API (just generate) */
17
+ dryRun?: boolean;
18
+ }
19
+
20
+ /**
21
+ * Deploy command result
22
+ */
23
+ export interface DeployCommandResult {
24
+ /** Whether the deploy was successful */
25
+ success: boolean;
26
+ /** Build result with generated resources */
27
+ build?: BuildFromIncludeResult;
28
+ /** Deploy API result (if not dry run) */
29
+ deploy?: BuildApiResult;
30
+ /** Error message if failed */
31
+ error?: string;
32
+ /** Duration in milliseconds */
33
+ durationMs: number;
34
+ }
35
+
36
+ /**
37
+ * Run the deploy command
38
+ *
39
+ * Builds resources and deploys to main Tinybird workspace (production).
40
+ *
41
+ * @param options - Deploy options
42
+ * @returns Deploy command result
43
+ */
44
+ export async function runDeploy(options: DeployCommandOptions = {}): Promise<DeployCommandResult> {
45
+ const startTime = Date.now();
46
+ const cwd = options.cwd ?? process.cwd();
47
+
48
+ // Load config
49
+ let config: ResolvedConfig;
50
+ try {
51
+ config = loadConfig(cwd);
52
+ } catch (error) {
53
+ return {
54
+ success: false,
55
+ error: (error as Error).message,
56
+ durationMs: Date.now() - startTime,
57
+ };
58
+ }
59
+
60
+ // Build resources from include paths
61
+ let buildResult: BuildFromIncludeResult;
62
+ try {
63
+ buildResult = await buildFromInclude({
64
+ includePaths: config.include,
65
+ cwd: config.cwd,
66
+ });
67
+ } catch (error) {
68
+ return {
69
+ success: false,
70
+ error: `Build failed: ${(error as Error).message}`,
71
+ durationMs: Date.now() - startTime,
72
+ };
73
+ }
74
+
75
+ // If dry run, return without pushing
76
+ if (options.dryRun) {
77
+ return {
78
+ success: true,
79
+ build: buildResult,
80
+ durationMs: Date.now() - startTime,
81
+ };
82
+ }
83
+
84
+ const debug = !!process.env.TINYBIRD_DEBUG;
85
+
86
+ if (debug) {
87
+ console.log(`[debug] Deploying to main workspace`);
88
+ console.log(`[debug] baseUrl: ${config.baseUrl}`);
89
+ }
90
+
91
+ // Deploy to main workspace using /v1/deploy endpoint
92
+ let deployResult: BuildApiResult;
93
+ try {
94
+ deployResult = await deployToMain(
95
+ {
96
+ baseUrl: config.baseUrl,
97
+ token: config.token,
98
+ },
99
+ buildResult.resources
100
+ );
101
+ } catch (error) {
102
+ return {
103
+ success: false,
104
+ build: buildResult,
105
+ error: `Deploy failed: ${(error as Error).message}`,
106
+ durationMs: Date.now() - startTime,
107
+ };
108
+ }
109
+
110
+ if (!deployResult.success) {
111
+ return {
112
+ success: false,
113
+ build: buildResult,
114
+ deploy: deployResult,
115
+ error: deployResult.error,
116
+ durationMs: Date.now() - startTime,
117
+ };
118
+ }
119
+
120
+ return {
121
+ success: true,
122
+ build: buildResult,
123
+ deploy: deployResult,
124
+ durationMs: Date.now() - startTime,
125
+ };
126
+ }
@@ -4,7 +4,7 @@
4
4
 
5
5
  import * as path from "path";
6
6
  import { watch } from "chokidar";
7
- import { loadConfig, configExists, findConfigFile, hasValidToken, updateConfig, type ResolvedConfig } from "../config.js";
7
+ import { loadConfig, configExists, findConfigFile, hasValidToken, updateConfig, LOCAL_BASE_URL, type ResolvedConfig, type DevMode } from "../config.js";
8
8
  import { runBuild, type BuildCommandResult } from "./build.js";
9
9
  import { getOrCreateBranch, type TinybirdBranch } from "../../api/branches.js";
10
10
  import { browserLogin } from "../auth.js";
@@ -13,6 +13,12 @@ import {
13
13
  validatePipeSchemas,
14
14
  type SchemaValidationResult,
15
15
  } from "../utils/schema-validation.js";
16
+ import {
17
+ getLocalTokens,
18
+ getOrCreateLocalWorkspace,
19
+ getLocalWorkspaceName,
20
+ type LocalWorkspace,
21
+ } from "../../api/local.js";
16
22
 
17
23
  /**
18
24
  * Login result info
@@ -44,6 +50,8 @@ export interface DevCommandOptions {
44
50
  onLoginComplete?: (info: LoginInfo) => void;
45
51
  /** Callback when schema validation completes */
46
52
  onSchemaValidation?: (result: SchemaValidationResult) => void;
53
+ /** Override the devMode from config */
54
+ devModeOverride?: DevMode;
47
55
  }
48
56
 
49
57
  /**
@@ -54,10 +62,14 @@ export interface BranchReadyInfo {
54
62
  gitBranch: string | null;
55
63
  /** Whether we're on the main branch */
56
64
  isMainBranch: boolean;
57
- /** Tinybird branch info (null if on main) */
65
+ /** Tinybird branch info (null if on main or local mode) */
58
66
  tinybirdBranch?: TinybirdBranch;
59
67
  /** Whether the branch was newly created */
60
68
  wasCreated?: boolean;
69
+ /** Whether using local mode */
70
+ isLocal?: boolean;
71
+ /** Local workspace info (only in local mode) */
72
+ localWorkspace?: LocalWorkspace;
61
73
  }
62
74
 
63
75
  /**
@@ -98,8 +110,19 @@ export async function runDev(options: DevCommandOptions = {}): Promise<DevContro
98
110
  );
99
111
  }
100
112
 
101
- // Check if authentication is set up, if not trigger login
102
- if (!hasValidToken(cwd)) {
113
+ // Load config first to determine devMode
114
+ let config: ResolvedConfig;
115
+ try {
116
+ config = loadConfig(cwd);
117
+ } catch (error) {
118
+ throw error;
119
+ }
120
+
121
+ // Determine devMode
122
+ const devMode = options.devModeOverride ?? config.devMode;
123
+
124
+ // Check if authentication is set up, if not trigger login (skip for local mode)
125
+ if (devMode !== "local" && !hasValidToken(cwd)) {
103
126
  console.log("No authentication found. Starting login flow...\n");
104
127
 
105
128
  const authResult = await browserLogin();
@@ -134,51 +157,72 @@ export async function runDev(options: DevCommandOptions = {}): Promise<DevContro
134
157
  workspaceName: authResult.workspaceName,
135
158
  userEmail: authResult.userEmail,
136
159
  });
137
- }
138
160
 
139
- // Load config (now should have valid token)
140
- let config: ResolvedConfig;
141
- try {
161
+ // Reload config after login
142
162
  config = loadConfig(cwd);
143
- } catch (error) {
144
- throw error;
145
163
  }
146
164
 
147
- // Determine effective token based on git branch
165
+ // Determine effective token and branch info based on devMode
148
166
  let effectiveToken = config.token;
167
+ let effectiveBaseUrl = config.baseUrl;
149
168
  let branchInfo: BranchReadyInfo = {
150
169
  gitBranch: config.gitBranch,
151
170
  isMainBranch: config.isMainBranch,
152
171
  };
153
172
 
154
- // If we're on a feature branch, get or create the Tinybird branch
155
- // Use tinybirdBranch (sanitized name) for Tinybird API, gitBranch for display
156
- if (!config.isMainBranch && config.tinybirdBranch) {
157
- const branchName = config.tinybirdBranch; // Sanitized name for Tinybird
158
-
159
- // Always fetch fresh from API to avoid stale cache issues
160
- const tinybirdBranch = await getOrCreateBranch(
161
- {
162
- baseUrl: config.baseUrl,
163
- token: config.token,
164
- },
165
- branchName
166
- );
173
+ if (devMode === "local") {
174
+ // Local mode: get tokens from local container and set up workspace
175
+ const localTokens = await getLocalTokens();
176
+ const workspaceName = getLocalWorkspaceName(config.tinybirdBranch, config.cwd);
177
+ const { workspace, wasCreated } = await getOrCreateLocalWorkspace(localTokens, workspaceName);
167
178
 
168
- if (!tinybirdBranch.token) {
179
+ effectiveToken = workspace.token;
180
+ effectiveBaseUrl = LOCAL_BASE_URL;
181
+ branchInfo = {
182
+ gitBranch: config.gitBranch,
183
+ isMainBranch: false, // Local mode always uses build, not deploy
184
+ isLocal: true,
185
+ localWorkspace: workspace,
186
+ wasCreated,
187
+ };
188
+ } else {
189
+ // Branch mode: use Tinybird cloud with branches
190
+ // Prevent dev mode on main branch - must use deploy command
191
+ if (config.isMainBranch || !config.tinybirdBranch) {
169
192
  throw new Error(
170
- `Branch '${branchName}' was created but no token was returned. ` +
171
- `This may be an API issue.`
193
+ `Cannot use 'dev' command on main branch. Use 'tinybird deploy' to deploy to production, or switch to a feature branch.`
172
194
  );
173
195
  }
174
196
 
175
- effectiveToken = tinybirdBranch.token;
176
- branchInfo = {
177
- gitBranch: config.gitBranch, // Original git branch name for display
178
- isMainBranch: false,
179
- tinybirdBranch,
180
- wasCreated: tinybirdBranch.wasCreated ?? false,
181
- };
197
+ // Get or create the Tinybird branch
198
+ // Use tinybirdBranch (sanitized name) for Tinybird API, gitBranch for display
199
+ if (config.tinybirdBranch) {
200
+ const branchName = config.tinybirdBranch; // Sanitized name for Tinybird
201
+
202
+ // Always fetch fresh from API to avoid stale cache issues
203
+ const tinybirdBranch = await getOrCreateBranch(
204
+ {
205
+ baseUrl: config.baseUrl,
206
+ token: config.token,
207
+ },
208
+ branchName
209
+ );
210
+
211
+ if (!tinybirdBranch.token) {
212
+ throw new Error(
213
+ `Branch '${branchName}' was created but no token was returned. ` +
214
+ `This may be an API issue.`
215
+ );
216
+ }
217
+
218
+ effectiveToken = tinybirdBranch.token;
219
+ branchInfo = {
220
+ gitBranch: config.gitBranch, // Original git branch name for display
221
+ isMainBranch: false,
222
+ tinybirdBranch,
223
+ wasCreated: tinybirdBranch.wasCreated ?? false,
224
+ };
225
+ }
182
226
  }
183
227
 
184
228
  // Notify about branch readiness
@@ -209,10 +253,11 @@ export async function runDev(options: DevCommandOptions = {}): Promise<DevContro
209
253
  options.onBuildStart?.();
210
254
 
211
255
  try {
256
+ // Always use runBuild - main branch is blocked at startup
212
257
  const result = await runBuild({
213
258
  cwd: config.cwd,
214
259
  tokenOverride: effectiveToken,
215
- useDeployEndpoint: config.isMainBranch,
260
+ devModeOverride: devMode,
216
261
  });
217
262
  options.onBuildComplete?.(result);
218
263
 
@@ -234,7 +279,7 @@ export async function runDev(options: DevCommandOptions = {}): Promise<DevContro
234
279
  const validation = await validatePipeSchemas({
235
280
  entities: result.build.entities,
236
281
  pipeNames: changedPipes,
237
- baseUrl: config.baseUrl,
282
+ baseUrl: effectiveBaseUrl,
238
283
  token: effectiveToken,
239
284
  });
240
285