@tinybirdco/sdk 0.0.1

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 (258) hide show
  1. package/README.md +518 -0
  2. package/bin/tinybird.js +7 -0
  3. package/dist/api/branches.d.ts +98 -0
  4. package/dist/api/branches.d.ts.map +1 -0
  5. package/dist/api/branches.js +203 -0
  6. package/dist/api/branches.js.map +1 -0
  7. package/dist/api/branches.test.d.ts +2 -0
  8. package/dist/api/branches.test.d.ts.map +1 -0
  9. package/dist/api/branches.test.js +286 -0
  10. package/dist/api/branches.test.js.map +1 -0
  11. package/dist/api/build.d.ts +130 -0
  12. package/dist/api/build.d.ts.map +1 -0
  13. package/dist/api/build.js +143 -0
  14. package/dist/api/build.js.map +1 -0
  15. package/dist/api/build.test.d.ts +2 -0
  16. package/dist/api/build.test.d.ts.map +1 -0
  17. package/dist/api/build.test.js +138 -0
  18. package/dist/api/build.test.js.map +1 -0
  19. package/dist/api/deploy.d.ts +39 -0
  20. package/dist/api/deploy.d.ts.map +1 -0
  21. package/dist/api/deploy.js +135 -0
  22. package/dist/api/deploy.js.map +1 -0
  23. package/dist/api/deploy.test.d.ts +2 -0
  24. package/dist/api/deploy.test.d.ts.map +1 -0
  25. package/dist/api/deploy.test.js +118 -0
  26. package/dist/api/deploy.test.js.map +1 -0
  27. package/dist/api/workspaces.d.ts +46 -0
  28. package/dist/api/workspaces.d.ts.map +1 -0
  29. package/dist/api/workspaces.js +39 -0
  30. package/dist/api/workspaces.js.map +1 -0
  31. package/dist/api/workspaces.test.d.ts +2 -0
  32. package/dist/api/workspaces.test.d.ts.map +1 -0
  33. package/dist/api/workspaces.test.js +65 -0
  34. package/dist/api/workspaces.test.js.map +1 -0
  35. package/dist/cli/auth.d.ts +86 -0
  36. package/dist/cli/auth.d.ts.map +1 -0
  37. package/dist/cli/auth.js +284 -0
  38. package/dist/cli/auth.js.map +1 -0
  39. package/dist/cli/branch-store.d.ts +53 -0
  40. package/dist/cli/branch-store.d.ts.map +1 -0
  41. package/dist/cli/branch-store.js +91 -0
  42. package/dist/cli/branch-store.js.map +1 -0
  43. package/dist/cli/branch-store.test.d.ts +2 -0
  44. package/dist/cli/branch-store.test.d.ts.map +1 -0
  45. package/dist/cli/branch-store.test.js +115 -0
  46. package/dist/cli/branch-store.test.js.map +1 -0
  47. package/dist/cli/commands/branch.d.ts +82 -0
  48. package/dist/cli/commands/branch.d.ts.map +1 -0
  49. package/dist/cli/commands/branch.js +215 -0
  50. package/dist/cli/commands/branch.js.map +1 -0
  51. package/dist/cli/commands/build.d.ts +43 -0
  52. package/dist/cli/commands/build.d.ts.map +1 -0
  53. package/dist/cli/commands/build.js +138 -0
  54. package/dist/cli/commands/build.js.map +1 -0
  55. package/dist/cli/commands/dev.d.ts +78 -0
  56. package/dist/cli/commands/dev.d.ts.map +1 -0
  57. package/dist/cli/commands/dev.js +226 -0
  58. package/dist/cli/commands/dev.js.map +1 -0
  59. package/dist/cli/commands/init.d.ts +45 -0
  60. package/dist/cli/commands/init.d.ts.map +1 -0
  61. package/dist/cli/commands/init.js +277 -0
  62. package/dist/cli/commands/init.js.map +1 -0
  63. package/dist/cli/commands/init.test.d.ts +2 -0
  64. package/dist/cli/commands/init.test.d.ts.map +1 -0
  65. package/dist/cli/commands/init.test.js +158 -0
  66. package/dist/cli/commands/init.test.js.map +1 -0
  67. package/dist/cli/commands/login.d.ts +37 -0
  68. package/dist/cli/commands/login.d.ts.map +1 -0
  69. package/dist/cli/commands/login.js +64 -0
  70. package/dist/cli/commands/login.js.map +1 -0
  71. package/dist/cli/config.d.ts +114 -0
  72. package/dist/cli/config.d.ts.map +1 -0
  73. package/dist/cli/config.js +258 -0
  74. package/dist/cli/config.js.map +1 -0
  75. package/dist/cli/config.test.d.ts +2 -0
  76. package/dist/cli/config.test.d.ts.map +1 -0
  77. package/dist/cli/config.test.js +243 -0
  78. package/dist/cli/config.test.js.map +1 -0
  79. package/dist/cli/env.d.ts +29 -0
  80. package/dist/cli/env.d.ts.map +1 -0
  81. package/dist/cli/env.js +66 -0
  82. package/dist/cli/env.js.map +1 -0
  83. package/dist/cli/git.d.ts +29 -0
  84. package/dist/cli/git.d.ts.map +1 -0
  85. package/dist/cli/git.js +114 -0
  86. package/dist/cli/git.js.map +1 -0
  87. package/dist/cli/git.test.d.ts +2 -0
  88. package/dist/cli/git.test.d.ts.map +1 -0
  89. package/dist/cli/git.test.js +125 -0
  90. package/dist/cli/git.test.js.map +1 -0
  91. package/dist/cli/index.d.ts +7 -0
  92. package/dist/cli/index.d.ts.map +1 -0
  93. package/dist/cli/index.js +337 -0
  94. package/dist/cli/index.js.map +1 -0
  95. package/dist/cli/utils/schema-validation.d.ts +95 -0
  96. package/dist/cli/utils/schema-validation.d.ts.map +1 -0
  97. package/dist/cli/utils/schema-validation.js +175 -0
  98. package/dist/cli/utils/schema-validation.js.map +1 -0
  99. package/dist/cli/utils/schema-validation.test.d.ts +5 -0
  100. package/dist/cli/utils/schema-validation.test.d.ts.map +1 -0
  101. package/dist/cli/utils/schema-validation.test.js +173 -0
  102. package/dist/cli/utils/schema-validation.test.js.map +1 -0
  103. package/dist/client/base.d.ts +116 -0
  104. package/dist/client/base.d.ts.map +1 -0
  105. package/dist/client/base.js +328 -0
  106. package/dist/client/base.js.map +1 -0
  107. package/dist/client/types.d.ts +137 -0
  108. package/dist/client/types.d.ts.map +1 -0
  109. package/dist/client/types.js +43 -0
  110. package/dist/client/types.js.map +1 -0
  111. package/dist/generator/client.d.ts +44 -0
  112. package/dist/generator/client.d.ts.map +1 -0
  113. package/dist/generator/client.js +144 -0
  114. package/dist/generator/client.js.map +1 -0
  115. package/dist/generator/datasource.d.ts +57 -0
  116. package/dist/generator/datasource.d.ts.map +1 -0
  117. package/dist/generator/datasource.js +169 -0
  118. package/dist/generator/datasource.js.map +1 -0
  119. package/dist/generator/datasource.test.d.ts +2 -0
  120. package/dist/generator/datasource.test.d.ts.map +1 -0
  121. package/dist/generator/datasource.test.js +254 -0
  122. package/dist/generator/datasource.test.js.map +1 -0
  123. package/dist/generator/index.d.ts +131 -0
  124. package/dist/generator/index.d.ts.map +1 -0
  125. package/dist/generator/index.js +121 -0
  126. package/dist/generator/index.js.map +1 -0
  127. package/dist/generator/index.test.d.ts +2 -0
  128. package/dist/generator/index.test.d.ts.map +1 -0
  129. package/dist/generator/index.test.js +175 -0
  130. package/dist/generator/index.test.js.map +1 -0
  131. package/dist/generator/loader.d.ts +156 -0
  132. package/dist/generator/loader.d.ts.map +1 -0
  133. package/dist/generator/loader.js +295 -0
  134. package/dist/generator/loader.js.map +1 -0
  135. package/dist/generator/pipe.d.ts +72 -0
  136. package/dist/generator/pipe.d.ts.map +1 -0
  137. package/dist/generator/pipe.js +174 -0
  138. package/dist/generator/pipe.js.map +1 -0
  139. package/dist/generator/pipe.test.d.ts +2 -0
  140. package/dist/generator/pipe.test.d.ts.map +1 -0
  141. package/dist/generator/pipe.test.js +393 -0
  142. package/dist/generator/pipe.test.js.map +1 -0
  143. package/dist/index.d.ts +74 -0
  144. package/dist/index.d.ts.map +1 -0
  145. package/dist/index.js +73 -0
  146. package/dist/index.js.map +1 -0
  147. package/dist/infer/index.d.ts +202 -0
  148. package/dist/infer/index.d.ts.map +1 -0
  149. package/dist/infer/index.js +5 -0
  150. package/dist/infer/index.js.map +1 -0
  151. package/dist/schema/datasource.d.ts +135 -0
  152. package/dist/schema/datasource.d.ts.map +1 -0
  153. package/dist/schema/datasource.js +105 -0
  154. package/dist/schema/datasource.js.map +1 -0
  155. package/dist/schema/datasource.test.d.ts +2 -0
  156. package/dist/schema/datasource.test.d.ts.map +1 -0
  157. package/dist/schema/datasource.test.js +142 -0
  158. package/dist/schema/datasource.test.js.map +1 -0
  159. package/dist/schema/engines.d.ts +157 -0
  160. package/dist/schema/engines.d.ts.map +1 -0
  161. package/dist/schema/engines.js +155 -0
  162. package/dist/schema/engines.js.map +1 -0
  163. package/dist/schema/engines.test.d.ts +2 -0
  164. package/dist/schema/engines.test.d.ts.map +1 -0
  165. package/dist/schema/engines.test.js +221 -0
  166. package/dist/schema/engines.test.js.map +1 -0
  167. package/dist/schema/params.d.ts +106 -0
  168. package/dist/schema/params.d.ts.map +1 -0
  169. package/dist/schema/params.js +138 -0
  170. package/dist/schema/params.js.map +1 -0
  171. package/dist/schema/params.test.d.ts +2 -0
  172. package/dist/schema/params.test.d.ts.map +1 -0
  173. package/dist/schema/params.test.js +175 -0
  174. package/dist/schema/params.test.js.map +1 -0
  175. package/dist/schema/pipe.d.ts +436 -0
  176. package/dist/schema/pipe.d.ts.map +1 -0
  177. package/dist/schema/pipe.js +484 -0
  178. package/dist/schema/pipe.js.map +1 -0
  179. package/dist/schema/pipe.test.d.ts +2 -0
  180. package/dist/schema/pipe.test.d.ts.map +1 -0
  181. package/dist/schema/pipe.test.js +488 -0
  182. package/dist/schema/pipe.test.js.map +1 -0
  183. package/dist/schema/project.d.ts +202 -0
  184. package/dist/schema/project.d.ts.map +1 -0
  185. package/dist/schema/project.js +188 -0
  186. package/dist/schema/project.js.map +1 -0
  187. package/dist/schema/project.test.d.ts +2 -0
  188. package/dist/schema/project.test.d.ts.map +1 -0
  189. package/dist/schema/project.test.js +180 -0
  190. package/dist/schema/project.test.js.map +1 -0
  191. package/dist/schema/types.d.ts +140 -0
  192. package/dist/schema/types.d.ts.map +1 -0
  193. package/dist/schema/types.js +174 -0
  194. package/dist/schema/types.js.map +1 -0
  195. package/dist/schema/types.test.d.ts +2 -0
  196. package/dist/schema/types.test.d.ts.map +1 -0
  197. package/dist/schema/types.test.js +176 -0
  198. package/dist/schema/types.test.js.map +1 -0
  199. package/dist/test/handlers.d.ts +58 -0
  200. package/dist/test/handlers.d.ts.map +1 -0
  201. package/dist/test/handlers.js +62 -0
  202. package/dist/test/handlers.js.map +1 -0
  203. package/dist/test/setup.d.ts +5 -0
  204. package/dist/test/setup.d.ts.map +1 -0
  205. package/dist/test/setup.js +11 -0
  206. package/dist/test/setup.js.map +1 -0
  207. package/package.json +57 -0
  208. package/src/api/branches.test.ts +377 -0
  209. package/src/api/branches.ts +334 -0
  210. package/src/api/build.test.ts +216 -0
  211. package/src/api/build.ts +266 -0
  212. package/src/api/deploy.test.ts +193 -0
  213. package/src/api/deploy.ts +163 -0
  214. package/src/api/workspaces.test.ts +81 -0
  215. package/src/api/workspaces.ts +77 -0
  216. package/src/cli/auth.ts +358 -0
  217. package/src/cli/branch-store.test.ts +139 -0
  218. package/src/cli/branch-store.ts +137 -0
  219. package/src/cli/commands/branch.ts +306 -0
  220. package/src/cli/commands/build.ts +183 -0
  221. package/src/cli/commands/dev.ts +334 -0
  222. package/src/cli/commands/init.test.ts +249 -0
  223. package/src/cli/commands/init.ts +323 -0
  224. package/src/cli/commands/login.ts +98 -0
  225. package/src/cli/config.test.ts +359 -0
  226. package/src/cli/config.ts +335 -0
  227. package/src/cli/env.ts +86 -0
  228. package/src/cli/git.test.ts +147 -0
  229. package/src/cli/git.ts +125 -0
  230. package/src/cli/index.ts +382 -0
  231. package/src/cli/utils/schema-validation.test.ts +222 -0
  232. package/src/cli/utils/schema-validation.ts +272 -0
  233. package/src/client/base.ts +414 -0
  234. package/src/client/types.ts +165 -0
  235. package/src/generator/client.ts +194 -0
  236. package/src/generator/datasource.test.ts +297 -0
  237. package/src/generator/datasource.ts +217 -0
  238. package/src/generator/index.test.ts +209 -0
  239. package/src/generator/index.ts +203 -0
  240. package/src/generator/loader.ts +406 -0
  241. package/src/generator/pipe.test.ts +441 -0
  242. package/src/generator/pipe.ts +220 -0
  243. package/src/index.ts +191 -0
  244. package/src/infer/index.ts +247 -0
  245. package/src/schema/datasource.test.ts +187 -0
  246. package/src/schema/datasource.ts +195 -0
  247. package/src/schema/engines.test.ts +247 -0
  248. package/src/schema/engines.ts +271 -0
  249. package/src/schema/params.test.ts +208 -0
  250. package/src/schema/params.ts +249 -0
  251. package/src/schema/pipe.test.ts +588 -0
  252. package/src/schema/pipe.ts +832 -0
  253. package/src/schema/project.test.ts +236 -0
  254. package/src/schema/project.ts +394 -0
  255. package/src/schema/types.test.ts +212 -0
  256. package/src/schema/types.ts +366 -0
  257. package/src/test/handlers.ts +79 -0
  258. package/src/test/setup.ts +13 -0
@@ -0,0 +1,334 @@
1
+ /**
2
+ * Dev command - watch mode with automatic sync
3
+ */
4
+
5
+ import * as path from "path";
6
+ import { watch } from "chokidar";
7
+ import { loadConfig, configExists, findConfigFile, hasValidToken, updateConfig, type ResolvedConfig } from "../config.js";
8
+ import { runBuild, type BuildCommandResult } from "./build.js";
9
+ import { getOrCreateBranch, type TinybirdBranch } from "../../api/branches.js";
10
+ import { browserLogin } from "../auth.js";
11
+ import { saveTinybirdToken } from "../env.js";
12
+ import {
13
+ validatePipeSchemas,
14
+ type SchemaValidationResult,
15
+ } from "../utils/schema-validation.js";
16
+
17
+ /**
18
+ * Login result info
19
+ */
20
+ export interface LoginInfo {
21
+ /** Workspace name */
22
+ workspaceName?: string;
23
+ /** User email */
24
+ userEmail?: string;
25
+ }
26
+
27
+ /**
28
+ * Dev command options
29
+ */
30
+ export interface DevCommandOptions {
31
+ /** Working directory (defaults to cwd) */
32
+ cwd?: string;
33
+ /** Debounce delay in milliseconds (default: 100) */
34
+ debounce?: number;
35
+ /** Callback when build starts */
36
+ onBuildStart?: () => void;
37
+ /** Callback when build completes */
38
+ onBuildComplete?: (result: BuildCommandResult) => void;
39
+ /** Callback when an error occurs */
40
+ onError?: (error: Error) => void;
41
+ /** Callback when branch is created/detected */
42
+ onBranchReady?: (info: BranchReadyInfo) => void;
43
+ /** Callback when login is needed and completed */
44
+ onLoginComplete?: (info: LoginInfo) => void;
45
+ /** Callback when schema validation completes */
46
+ onSchemaValidation?: (result: SchemaValidationResult) => void;
47
+ }
48
+
49
+ /**
50
+ * Information about the branch being used
51
+ */
52
+ export interface BranchReadyInfo {
53
+ /** Git branch name */
54
+ gitBranch: string | null;
55
+ /** Whether we're on the main branch */
56
+ isMainBranch: boolean;
57
+ /** Tinybird branch info (null if on main) */
58
+ tinybirdBranch?: TinybirdBranch;
59
+ /** Whether the branch was newly created */
60
+ wasCreated?: boolean;
61
+ }
62
+
63
+ /**
64
+ * Dev command controller
65
+ */
66
+ export interface DevController {
67
+ /** Stop watching and clean up */
68
+ stop: () => Promise<void>;
69
+ /** Trigger a manual rebuild */
70
+ rebuild: () => Promise<BuildCommandResult>;
71
+ /** The configuration being used */
72
+ config: ResolvedConfig;
73
+ /** The effective token (branch token or main token) */
74
+ effectiveToken: string;
75
+ /** Branch info */
76
+ branchInfo: BranchReadyInfo;
77
+ }
78
+
79
+ /**
80
+ * Run the dev command
81
+ *
82
+ * Watches for file changes and automatically rebuilds and pushes to Tinybird.
83
+ * Automatically manages Tinybird branches based on git branch:
84
+ * - Main branch: uses workspace token and /v1/deploy
85
+ * - Feature branches: creates/reuses Tinybird branch and uses /v1/build
86
+ *
87
+ * @param options - Dev options
88
+ * @returns Dev controller
89
+ */
90
+ export async function runDev(options: DevCommandOptions = {}): Promise<DevController> {
91
+ const cwd = options.cwd ?? process.cwd();
92
+ const debounceMs = options.debounce ?? 100;
93
+
94
+ // Check if project is initialized
95
+ if (!configExists(cwd)) {
96
+ throw new Error(
97
+ "No tinybird.json found. Run 'npx tinybird init' to initialize a project."
98
+ );
99
+ }
100
+
101
+ // Check if authentication is set up, if not trigger login
102
+ if (!hasValidToken(cwd)) {
103
+ console.log("No authentication found. Starting login flow...\n");
104
+
105
+ const authResult = await browserLogin();
106
+
107
+ if (!authResult.success || !authResult.token) {
108
+ throw new Error(
109
+ authResult.error ?? "Login failed. Run 'npx tinybird login' to authenticate."
110
+ );
111
+ }
112
+
113
+ // Find the config file (may be in parent directory)
114
+ const configPath = findConfigFile(cwd);
115
+ if (!configPath) {
116
+ throw new Error("No tinybird.json found. Run 'npx tinybird init' first.");
117
+ }
118
+
119
+ // Save token to .env.local (in same directory as tinybird.json)
120
+ const configDir = path.dirname(configPath);
121
+ saveTinybirdToken(configDir, authResult.token);
122
+
123
+ // Update baseUrl in tinybird.json if it changed
124
+ if (authResult.baseUrl) {
125
+ updateConfig(configPath, {
126
+ baseUrl: authResult.baseUrl,
127
+ });
128
+ }
129
+
130
+ // Set the token in the environment for this session
131
+ process.env.TINYBIRD_TOKEN = authResult.token;
132
+
133
+ options.onLoginComplete?.({
134
+ workspaceName: authResult.workspaceName,
135
+ userEmail: authResult.userEmail,
136
+ });
137
+ }
138
+
139
+ // Load config (now should have valid token)
140
+ let config: ResolvedConfig;
141
+ try {
142
+ config = loadConfig(cwd);
143
+ } catch (error) {
144
+ throw error;
145
+ }
146
+
147
+ // Determine effective token based on git branch
148
+ let effectiveToken = config.token;
149
+ let branchInfo: BranchReadyInfo = {
150
+ gitBranch: config.gitBranch,
151
+ isMainBranch: config.isMainBranch,
152
+ };
153
+
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
+ );
167
+
168
+ if (!tinybirdBranch.token) {
169
+ throw new Error(
170
+ `Branch '${branchName}' was created but no token was returned. ` +
171
+ `This may be an API issue.`
172
+ );
173
+ }
174
+
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
+ };
182
+ }
183
+
184
+ // Notify about branch readiness
185
+ options.onBranchReady?.(branchInfo);
186
+
187
+ // Get directories to watch from include paths
188
+ const watchDirs = new Set<string>();
189
+ for (const includePath of config.include) {
190
+ const absolutePath = path.isAbsolute(includePath)
191
+ ? includePath
192
+ : path.resolve(config.cwd, includePath);
193
+ watchDirs.add(path.dirname(absolutePath));
194
+ }
195
+
196
+ // Debounce state
197
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null;
198
+ let isBuilding = false;
199
+ let pendingBuild = false;
200
+
201
+ // Build function
202
+ async function doBuild(): Promise<BuildCommandResult> {
203
+ if (isBuilding) {
204
+ pendingBuild = true;
205
+ return { success: false, error: "Build already in progress", durationMs: 0 };
206
+ }
207
+
208
+ isBuilding = true;
209
+ options.onBuildStart?.();
210
+
211
+ try {
212
+ const result = await runBuild({
213
+ cwd: config.cwd,
214
+ tokenOverride: effectiveToken,
215
+ useDeployEndpoint: config.isMainBranch,
216
+ });
217
+ options.onBuildComplete?.(result);
218
+
219
+ // Validate pipe schemas after successful deploy
220
+ if (
221
+ result.success &&
222
+ result.build?.entities &&
223
+ result.deploy?.pipes &&
224
+ options.onSchemaValidation
225
+ ) {
226
+ // Get changed pipes from deploy result
227
+ const changedPipes = [
228
+ ...result.deploy.pipes.created,
229
+ ...result.deploy.pipes.changed,
230
+ ];
231
+
232
+ if (changedPipes.length > 0) {
233
+ try {
234
+ const validation = await validatePipeSchemas({
235
+ entities: result.build.entities,
236
+ pipeNames: changedPipes,
237
+ baseUrl: config.baseUrl,
238
+ token: effectiveToken,
239
+ });
240
+
241
+ options.onSchemaValidation(validation);
242
+ } catch (validationError) {
243
+ // Don't fail the build due to validation errors
244
+ options.onError?.(validationError as Error);
245
+ }
246
+ }
247
+ }
248
+
249
+ return result;
250
+ } catch (error) {
251
+ const result: BuildCommandResult = {
252
+ success: false,
253
+ error: (error as Error).message,
254
+ durationMs: 0,
255
+ };
256
+ options.onBuildComplete?.(result);
257
+ return result;
258
+ } finally {
259
+ isBuilding = false;
260
+
261
+ // If there was a pending build, trigger it
262
+ if (pendingBuild) {
263
+ pendingBuild = false;
264
+ scheduleBuild();
265
+ }
266
+ }
267
+ }
268
+
269
+ // Schedule a debounced build
270
+ function scheduleBuild(): void {
271
+ if (debounceTimer) {
272
+ clearTimeout(debounceTimer);
273
+ }
274
+
275
+ debounceTimer = setTimeout(() => {
276
+ debounceTimer = null;
277
+ doBuild().catch((error) => {
278
+ options.onError?.(error as Error);
279
+ });
280
+ }, debounceMs);
281
+ }
282
+
283
+ // Set up file watcher for all include directories
284
+ const watcher = watch(Array.from(watchDirs), {
285
+ ignored: [
286
+ /(^|[\/\\])\../, // Ignore dotfiles
287
+ /node_modules/,
288
+ /\.tinybird-schema-.*\.mjs$/, // Ignore temporary bundle files
289
+ /\.tinybird-entities-.*\.mjs$/, // Ignore temporary entity files
290
+ ],
291
+ persistent: true,
292
+ ignoreInitial: true,
293
+ });
294
+
295
+ // Watch for changes
296
+ watcher.on("change", (filePath) => {
297
+ if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
298
+ scheduleBuild();
299
+ }
300
+ });
301
+
302
+ watcher.on("add", (filePath) => {
303
+ if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
304
+ scheduleBuild();
305
+ }
306
+ });
307
+
308
+ watcher.on("unlink", (filePath) => {
309
+ if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
310
+ scheduleBuild();
311
+ }
312
+ });
313
+
314
+ watcher.on("error", (error: unknown) => {
315
+ options.onError?.(error instanceof Error ? error : new Error(String(error)));
316
+ });
317
+
318
+ // Do initial build
319
+ await doBuild();
320
+
321
+ // Return controller
322
+ return {
323
+ stop: async () => {
324
+ if (debounceTimer) {
325
+ clearTimeout(debounceTimer);
326
+ }
327
+ await watcher.close();
328
+ },
329
+ rebuild: doBuild,
330
+ config,
331
+ effectiveToken,
332
+ branchInfo,
333
+ };
334
+ }
@@ -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 { runInit } from "./init.js";
6
+
7
+ // Mock the auth module to avoid browser login
8
+ vi.mock("../auth.js", () => ({
9
+ browserLogin: vi.fn().mockResolvedValue({ success: false }),
10
+ }));
11
+
12
+ describe("Init Command", () => {
13
+ let tempDir: string;
14
+
15
+ beforeEach(() => {
16
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-init-test-"));
17
+ });
18
+
19
+ afterEach(() => {
20
+ try {
21
+ fs.rmSync(tempDir, { recursive: true });
22
+ } catch {
23
+ // Ignore cleanup errors
24
+ }
25
+ });
26
+
27
+ describe("folder structure creation", () => {
28
+ it("creates tinybird folder with datasources.ts, pipes.ts, client.ts when project has no src folder", async () => {
29
+ const result = await runInit({ cwd: tempDir, skipLogin: true });
30
+
31
+ expect(result.success).toBe(true);
32
+ expect(result.created).toContain("tinybird/datasources.ts");
33
+ expect(result.created).toContain("tinybird/pipes.ts");
34
+ expect(result.created).toContain("tinybird/client.ts");
35
+ expect(fs.existsSync(path.join(tempDir, "tinybird", "datasources.ts"))).toBe(true);
36
+ expect(fs.existsSync(path.join(tempDir, "tinybird", "pipes.ts"))).toBe(true);
37
+ expect(fs.existsSync(path.join(tempDir, "tinybird", "client.ts"))).toBe(true);
38
+ });
39
+
40
+ it("creates src/tinybird folder with files when project has src folder", async () => {
41
+ // Create src folder to simulate existing project
42
+ fs.mkdirSync(path.join(tempDir, "src"));
43
+
44
+ const result = await runInit({ cwd: tempDir, skipLogin: true });
45
+
46
+ expect(result.success).toBe(true);
47
+ expect(result.created).toContain("src/tinybird/datasources.ts");
48
+ expect(result.created).toContain("src/tinybird/pipes.ts");
49
+ expect(result.created).toContain("src/tinybird/client.ts");
50
+ expect(
51
+ fs.existsSync(path.join(tempDir, "src", "tinybird", "datasources.ts"))
52
+ ).toBe(true);
53
+ expect(
54
+ fs.existsSync(path.join(tempDir, "src", "tinybird", "pipes.ts"))
55
+ ).toBe(true);
56
+ expect(
57
+ fs.existsSync(path.join(tempDir, "src", "tinybird", "client.ts"))
58
+ ).toBe(true);
59
+ });
60
+
61
+ it("creates tinybird.json with correct include paths for tinybird folder", async () => {
62
+ const result = await runInit({ cwd: tempDir, skipLogin: true });
63
+
64
+ expect(result.success).toBe(true);
65
+ expect(result.created).toContain("tinybird.json");
66
+
67
+ const config = JSON.parse(
68
+ fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
69
+ );
70
+ expect(config.include).toEqual([
71
+ "tinybird/datasources.ts",
72
+ "tinybird/pipes.ts",
73
+ ]);
74
+ });
75
+
76
+ it("creates tinybird.json with correct include paths for src/tinybird", async () => {
77
+ fs.mkdirSync(path.join(tempDir, "src"));
78
+
79
+ const result = await runInit({ cwd: tempDir, skipLogin: true });
80
+
81
+ expect(result.success).toBe(true);
82
+
83
+ const config = JSON.parse(
84
+ fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
85
+ );
86
+ expect(config.include).toEqual([
87
+ "src/tinybird/datasources.ts",
88
+ "src/tinybird/pipes.ts",
89
+ ]);
90
+ });
91
+ });
92
+
93
+ describe("config file creation", () => {
94
+ it("creates tinybird.json with default values", async () => {
95
+ await runInit({ cwd: tempDir, skipLogin: true });
96
+
97
+ const config = JSON.parse(
98
+ fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
99
+ );
100
+
101
+ expect(config.token).toBe("${TINYBIRD_TOKEN}");
102
+ expect(config.baseUrl).toBe("https://api.tinybird.co");
103
+ });
104
+
105
+ it("skips tinybird.json if it already exists", async () => {
106
+ const existingConfig = { schema: "custom.ts", token: "existing" };
107
+ fs.writeFileSync(
108
+ path.join(tempDir, "tinybird.json"),
109
+ JSON.stringify(existingConfig)
110
+ );
111
+
112
+ const result = await runInit({ cwd: tempDir, skipLogin: true });
113
+
114
+ expect(result.success).toBe(true);
115
+ expect(result.skipped).toContain("tinybird.json");
116
+
117
+ // Verify it wasn't overwritten
118
+ const config = JSON.parse(
119
+ fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
120
+ );
121
+ expect(config.schema).toBe("custom.ts");
122
+ });
123
+
124
+ it("overwrites tinybird.json with force option", async () => {
125
+ const existingConfig = { include: ["custom.ts"], token: "existing" };
126
+ fs.writeFileSync(
127
+ path.join(tempDir, "tinybird.json"),
128
+ JSON.stringify(existingConfig)
129
+ );
130
+
131
+ const result = await runInit({ cwd: tempDir, skipLogin: true, force: true });
132
+
133
+ expect(result.success).toBe(true);
134
+ expect(result.created).toContain("tinybird.json");
135
+
136
+ const config = JSON.parse(
137
+ fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
138
+ );
139
+ expect(config.include).toEqual([
140
+ "tinybird/datasources.ts",
141
+ "tinybird/pipes.ts",
142
+ ]);
143
+ });
144
+ });
145
+
146
+ describe("file content creation", () => {
147
+ it("creates datasources.ts with example datasource and InferRow type", async () => {
148
+ await runInit({ cwd: tempDir, skipLogin: true });
149
+
150
+ const content = fs.readFileSync(
151
+ path.join(tempDir, "tinybird", "datasources.ts"),
152
+ "utf-8"
153
+ );
154
+
155
+ expect(content).toContain("defineDatasource");
156
+ expect(content).toContain("export const pageViews");
157
+ expect(content).toContain("InferRow");
158
+ expect(content).toContain("PageViewsRow");
159
+ });
160
+
161
+ it("creates pipes.ts with example endpoint and types", async () => {
162
+ await runInit({ cwd: tempDir, skipLogin: true });
163
+
164
+ const content = fs.readFileSync(
165
+ path.join(tempDir, "tinybird", "pipes.ts"),
166
+ "utf-8"
167
+ );
168
+
169
+ expect(content).toContain("defineEndpoint");
170
+ expect(content).toContain("export const topPages");
171
+ expect(content).toContain("InferParams");
172
+ expect(content).toContain("InferOutputRow");
173
+ expect(content).toContain("TopPagesParams");
174
+ expect(content).toContain("TopPagesOutput");
175
+ });
176
+
177
+ it("creates client.ts with createTinybirdClient", async () => {
178
+ await runInit({ cwd: tempDir, skipLogin: true });
179
+
180
+ const content = fs.readFileSync(
181
+ path.join(tempDir, "tinybird", "client.ts"),
182
+ "utf-8"
183
+ );
184
+
185
+ expect(content).toContain("createTinybirdClient");
186
+ expect(content).toContain("export const tinybird");
187
+ expect(content).toContain("pageViews");
188
+ expect(content).toContain("topPages");
189
+ });
190
+
191
+ it("skips files that already exist", async () => {
192
+ fs.mkdirSync(path.join(tempDir, "tinybird"), { recursive: true });
193
+ fs.writeFileSync(
194
+ path.join(tempDir, "tinybird", "datasources.ts"),
195
+ "// existing content"
196
+ );
197
+
198
+ const result = await runInit({ cwd: tempDir, skipLogin: true });
199
+
200
+ expect(result.success).toBe(true);
201
+ expect(result.skipped).toContain("tinybird/datasources.ts");
202
+
203
+ // Verify it wasn't overwritten
204
+ const content = fs.readFileSync(
205
+ path.join(tempDir, "tinybird", "datasources.ts"),
206
+ "utf-8"
207
+ );
208
+ expect(content).toBe("// existing content");
209
+ });
210
+
211
+ it("overwrites files with force option", async () => {
212
+ fs.mkdirSync(path.join(tempDir, "tinybird"), { recursive: true });
213
+ fs.writeFileSync(
214
+ path.join(tempDir, "tinybird", "datasources.ts"),
215
+ "// existing content"
216
+ );
217
+
218
+ const result = await runInit({ cwd: tempDir, skipLogin: true, force: true });
219
+
220
+ expect(result.success).toBe(true);
221
+ expect(result.created).toContain("tinybird/datasources.ts");
222
+
223
+ const content = fs.readFileSync(
224
+ path.join(tempDir, "tinybird", "datasources.ts"),
225
+ "utf-8"
226
+ );
227
+ expect(content).toContain("defineDatasource");
228
+ });
229
+ });
230
+
231
+ describe("directory creation", () => {
232
+ it("creates tinybird directory if it does not exist", async () => {
233
+ expect(fs.existsSync(path.join(tempDir, "tinybird"))).toBe(false);
234
+
235
+ await runInit({ cwd: tempDir, skipLogin: true });
236
+
237
+ expect(fs.existsSync(path.join(tempDir, "tinybird"))).toBe(true);
238
+ });
239
+
240
+ it("creates src/tinybird directory if project has src folder", async () => {
241
+ fs.mkdirSync(path.join(tempDir, "src"));
242
+ expect(fs.existsSync(path.join(tempDir, "src", "tinybird"))).toBe(false);
243
+
244
+ await runInit({ cwd: tempDir, skipLogin: true });
245
+
246
+ expect(fs.existsSync(path.join(tempDir, "src", "tinybird"))).toBe(true);
247
+ });
248
+ });
249
+ });