@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,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
+ }
@@ -187,9 +187,16 @@ export async function runDev(options: DevCommandOptions = {}): Promise<DevContro
187
187
  };
188
188
  } else {
189
189
  // Branch mode: use Tinybird cloud with branches
190
- // If we're on a feature branch, get or create the Tinybird branch
190
+ // Prevent dev mode on main branch - must use deploy command
191
+ if (config.isMainBranch || !config.tinybirdBranch) {
192
+ throw new Error(
193
+ `Cannot use 'dev' command on main branch. Use 'tinybird deploy' to deploy to production, or switch to a feature branch.`
194
+ );
195
+ }
196
+
197
+ // Get or create the Tinybird branch
191
198
  // Use tinybirdBranch (sanitized name) for Tinybird API, gitBranch for display
192
- if (!config.isMainBranch && config.tinybirdBranch) {
199
+ if (config.tinybirdBranch) {
193
200
  const branchName = config.tinybirdBranch; // Sanitized name for Tinybird
194
201
 
195
202
  // Always fetch fresh from API to avoid stale cache issues
@@ -246,10 +253,10 @@ export async function runDev(options: DevCommandOptions = {}): Promise<DevContro
246
253
  options.onBuildStart?.();
247
254
 
248
255
  try {
256
+ // Always use runBuild - main branch is blocked at startup
249
257
  const result = await runBuild({
250
258
  cwd: config.cwd,
251
259
  tokenOverride: effectiveToken,
252
- useDeployEndpoint: devMode !== "local" && config.isMainBranch,
253
260
  devModeOverride: devMode,
254
261
  });
255
262
  options.onBuildComplete?.(result);
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
4
  import * as os from "os";
5
- import { runInit } from "./init.js";
5
+ import { runInit, findExistingDatafiles } from "./init.js";
6
6
 
7
7
  // Mock the auth module to avoid browser login
8
8
  vi.mock("../auth.js", () => ({
@@ -25,15 +25,15 @@ describe("Init Command", () => {
25
25
  });
26
26
 
27
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 });
28
+ it("creates tinybird folder with datasources.ts, endpoints.ts, client.ts when project has no src folder", async () => {
29
+ const result = await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "tinybird" });
30
30
 
31
31
  expect(result.success).toBe(true);
32
32
  expect(result.created).toContain("tinybird/datasources.ts");
33
- expect(result.created).toContain("tinybird/pipes.ts");
33
+ expect(result.created).toContain("tinybird/endpoints.ts");
34
34
  expect(result.created).toContain("tinybird/client.ts");
35
35
  expect(fs.existsSync(path.join(tempDir, "tinybird", "datasources.ts"))).toBe(true);
36
- expect(fs.existsSync(path.join(tempDir, "tinybird", "pipes.ts"))).toBe(true);
36
+ expect(fs.existsSync(path.join(tempDir, "tinybird", "endpoints.ts"))).toBe(true);
37
37
  expect(fs.existsSync(path.join(tempDir, "tinybird", "client.ts"))).toBe(true);
38
38
  });
39
39
 
@@ -41,17 +41,17 @@ describe("Init Command", () => {
41
41
  // Create src folder to simulate existing project
42
42
  fs.mkdirSync(path.join(tempDir, "src"));
43
43
 
44
- const result = await runInit({ cwd: tempDir, skipLogin: true });
44
+ const result = await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "src/tinybird" });
45
45
 
46
46
  expect(result.success).toBe(true);
47
47
  expect(result.created).toContain("src/tinybird/datasources.ts");
48
- expect(result.created).toContain("src/tinybird/pipes.ts");
48
+ expect(result.created).toContain("src/tinybird/endpoints.ts");
49
49
  expect(result.created).toContain("src/tinybird/client.ts");
50
50
  expect(
51
51
  fs.existsSync(path.join(tempDir, "src", "tinybird", "datasources.ts"))
52
52
  ).toBe(true);
53
53
  expect(
54
- fs.existsSync(path.join(tempDir, "src", "tinybird", "pipes.ts"))
54
+ fs.existsSync(path.join(tempDir, "src", "tinybird", "endpoints.ts"))
55
55
  ).toBe(true);
56
56
  expect(
57
57
  fs.existsSync(path.join(tempDir, "src", "tinybird", "client.ts"))
@@ -59,7 +59,7 @@ describe("Init Command", () => {
59
59
  });
60
60
 
61
61
  it("creates tinybird.json with correct include paths for tinybird folder", async () => {
62
- const result = await runInit({ cwd: tempDir, skipLogin: true });
62
+ const result = await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "tinybird" });
63
63
 
64
64
  expect(result.success).toBe(true);
65
65
  expect(result.created).toContain("tinybird.json");
@@ -69,14 +69,14 @@ describe("Init Command", () => {
69
69
  );
70
70
  expect(config.include).toEqual([
71
71
  "tinybird/datasources.ts",
72
- "tinybird/pipes.ts",
72
+ "tinybird/endpoints.ts",
73
73
  ]);
74
74
  });
75
75
 
76
76
  it("creates tinybird.json with correct include paths for src/tinybird", async () => {
77
77
  fs.mkdirSync(path.join(tempDir, "src"));
78
78
 
79
- const result = await runInit({ cwd: tempDir, skipLogin: true });
79
+ const result = await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "src/tinybird" });
80
80
 
81
81
  expect(result.success).toBe(true);
82
82
 
@@ -85,14 +85,14 @@ describe("Init Command", () => {
85
85
  );
86
86
  expect(config.include).toEqual([
87
87
  "src/tinybird/datasources.ts",
88
- "src/tinybird/pipes.ts",
88
+ "src/tinybird/endpoints.ts",
89
89
  ]);
90
90
  });
91
91
  });
92
92
 
93
93
  describe("config file creation", () => {
94
94
  it("creates tinybird.json with default values", async () => {
95
- await runInit({ cwd: tempDir, skipLogin: true });
95
+ await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "tinybird" });
96
96
 
97
97
  const config = JSON.parse(
98
98
  fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
@@ -109,7 +109,7 @@ describe("Init Command", () => {
109
109
  JSON.stringify(existingConfig)
110
110
  );
111
111
 
112
- const result = await runInit({ cwd: tempDir, skipLogin: true });
112
+ const result = await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "tinybird" });
113
113
 
114
114
  expect(result.success).toBe(true);
115
115
  expect(result.skipped).toContain("tinybird.json");
@@ -128,7 +128,7 @@ describe("Init Command", () => {
128
128
  JSON.stringify(existingConfig)
129
129
  );
130
130
 
131
- const result = await runInit({ cwd: tempDir, skipLogin: true, force: true });
131
+ const result = await runInit({ cwd: tempDir, skipLogin: true, force: true, devMode: "branch", clientPath: "tinybird" });
132
132
 
133
133
  expect(result.success).toBe(true);
134
134
  expect(result.created).toContain("tinybird.json");
@@ -138,14 +138,14 @@ describe("Init Command", () => {
138
138
  );
139
139
  expect(config.include).toEqual([
140
140
  "tinybird/datasources.ts",
141
- "tinybird/pipes.ts",
141
+ "tinybird/endpoints.ts",
142
142
  ]);
143
143
  });
144
144
  });
145
145
 
146
146
  describe("file content creation", () => {
147
147
  it("creates datasources.ts with example datasource and InferRow type", async () => {
148
- await runInit({ cwd: tempDir, skipLogin: true });
148
+ await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "tinybird" });
149
149
 
150
150
  const content = fs.readFileSync(
151
151
  path.join(tempDir, "tinybird", "datasources.ts"),
@@ -158,11 +158,11 @@ describe("Init Command", () => {
158
158
  expect(content).toContain("PageViewsRow");
159
159
  });
160
160
 
161
- it("creates pipes.ts with example endpoint and types", async () => {
162
- await runInit({ cwd: tempDir, skipLogin: true });
161
+ it("creates endpoints.ts with example endpoint and types", async () => {
162
+ await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "tinybird" });
163
163
 
164
164
  const content = fs.readFileSync(
165
- path.join(tempDir, "tinybird", "pipes.ts"),
165
+ path.join(tempDir, "tinybird", "endpoints.ts"),
166
166
  "utf-8"
167
167
  );
168
168
 
@@ -175,7 +175,7 @@ describe("Init Command", () => {
175
175
  });
176
176
 
177
177
  it("creates client.ts with createTinybirdClient", async () => {
178
- await runInit({ cwd: tempDir, skipLogin: true });
178
+ await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "tinybird" });
179
179
 
180
180
  const content = fs.readFileSync(
181
181
  path.join(tempDir, "tinybird", "client.ts"),
@@ -195,7 +195,7 @@ describe("Init Command", () => {
195
195
  "// existing content"
196
196
  );
197
197
 
198
- const result = await runInit({ cwd: tempDir, skipLogin: true });
198
+ const result = await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "tinybird" });
199
199
 
200
200
  expect(result.success).toBe(true);
201
201
  expect(result.skipped).toContain("tinybird/datasources.ts");
@@ -215,7 +215,7 @@ describe("Init Command", () => {
215
215
  "// existing content"
216
216
  );
217
217
 
218
- const result = await runInit({ cwd: tempDir, skipLogin: true, force: true });
218
+ const result = await runInit({ cwd: tempDir, skipLogin: true, force: true, devMode: "branch", clientPath: "tinybird" });
219
219
 
220
220
  expect(result.success).toBe(true);
221
221
  expect(result.created).toContain("tinybird/datasources.ts");
@@ -229,14 +229,14 @@ describe("Init Command", () => {
229
229
  });
230
230
 
231
231
  describe("package.json scripts", () => {
232
- it("adds tinybird:dev and tinybird:build scripts to existing package.json", async () => {
232
+ it("adds tinybird:dev, tinybird:build, and tinybird:deploy scripts to existing package.json", async () => {
233
233
  const packageJson = { name: "test-project", scripts: { dev: "next dev" } };
234
234
  fs.writeFileSync(
235
235
  path.join(tempDir, "package.json"),
236
236
  JSON.stringify(packageJson, null, 2)
237
237
  );
238
238
 
239
- const result = await runInit({ cwd: tempDir, skipLogin: true });
239
+ const result = await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "tinybird" });
240
240
 
241
241
  expect(result.success).toBe(true);
242
242
  expect(result.created).toContain("package.json (added tinybird scripts)");
@@ -246,6 +246,7 @@ describe("Init Command", () => {
246
246
  );
247
247
  expect(updatedPackageJson.scripts["tinybird:dev"]).toBe("tinybird dev");
248
248
  expect(updatedPackageJson.scripts["tinybird:build"]).toBe("tinybird build");
249
+ expect(updatedPackageJson.scripts["tinybird:deploy"]).toBe("tinybird deploy");
249
250
  expect(updatedPackageJson.scripts.dev).toBe("next dev"); // preserved
250
251
  });
251
252
 
@@ -255,6 +256,7 @@ describe("Init Command", () => {
255
256
  scripts: {
256
257
  "tinybird:dev": "custom dev command",
257
258
  "tinybird:build": "custom build command",
259
+ "tinybird:deploy": "custom deploy command",
258
260
  },
259
261
  };
260
262
  fs.writeFileSync(
@@ -262,7 +264,7 @@ describe("Init Command", () => {
262
264
  JSON.stringify(packageJson, null, 2)
263
265
  );
264
266
 
265
- const result = await runInit({ cwd: tempDir, skipLogin: true });
267
+ const result = await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "tinybird" });
266
268
 
267
269
  expect(result.success).toBe(true);
268
270
  expect(result.created).not.toContain("package.json (added tinybird scripts)");
@@ -272,6 +274,7 @@ describe("Init Command", () => {
272
274
  );
273
275
  expect(updatedPackageJson.scripts["tinybird:dev"]).toBe("custom dev command");
274
276
  expect(updatedPackageJson.scripts["tinybird:build"]).toBe("custom build command");
277
+ expect(updatedPackageJson.scripts["tinybird:deploy"]).toBe("custom deploy command");
275
278
  });
276
279
 
277
280
  it("creates scripts object if package.json has no scripts", async () => {
@@ -281,7 +284,7 @@ describe("Init Command", () => {
281
284
  JSON.stringify(packageJson, null, 2)
282
285
  );
283
286
 
284
- const result = await runInit({ cwd: tempDir, skipLogin: true });
287
+ const result = await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "tinybird" });
285
288
 
286
289
  expect(result.success).toBe(true);
287
290
 
@@ -293,7 +296,7 @@ describe("Init Command", () => {
293
296
  });
294
297
 
295
298
  it("does not fail if no package.json exists", async () => {
296
- const result = await runInit({ cwd: tempDir, skipLogin: true });
299
+ const result = await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "tinybird" });
297
300
 
298
301
  expect(result.success).toBe(true);
299
302
  expect(result.created).not.toContain("package.json (added tinybird scripts)");
@@ -304,7 +307,7 @@ describe("Init Command", () => {
304
307
  it("creates tinybird directory if it does not exist", async () => {
305
308
  expect(fs.existsSync(path.join(tempDir, "tinybird"))).toBe(false);
306
309
 
307
- await runInit({ cwd: tempDir, skipLogin: true });
310
+ await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "tinybird" });
308
311
 
309
312
  expect(fs.existsSync(path.join(tempDir, "tinybird"))).toBe(true);
310
313
  });
@@ -313,9 +316,215 @@ describe("Init Command", () => {
313
316
  fs.mkdirSync(path.join(tempDir, "src"));
314
317
  expect(fs.existsSync(path.join(tempDir, "src", "tinybird"))).toBe(false);
315
318
 
316
- await runInit({ cwd: tempDir, skipLogin: true });
319
+ await runInit({ cwd: tempDir, skipLogin: true, devMode: "branch", clientPath: "src/tinybird" });
317
320
 
318
321
  expect(fs.existsSync(path.join(tempDir, "src", "tinybird"))).toBe(true);
319
322
  });
320
323
  });
324
+
325
+ describe("findExistingDatafiles", () => {
326
+ it("finds .datasource files in the project", () => {
327
+ fs.mkdirSync(path.join(tempDir, "datasources"), { recursive: true });
328
+ fs.writeFileSync(path.join(tempDir, "datasources", "events.datasource"), "");
329
+ fs.writeFileSync(path.join(tempDir, "datasources", "users.datasource"), "");
330
+
331
+ const files = findExistingDatafiles(tempDir);
332
+
333
+ expect(files).toContain("datasources/events.datasource");
334
+ expect(files).toContain("datasources/users.datasource");
335
+ });
336
+
337
+ it("finds .pipe files in the project", () => {
338
+ fs.mkdirSync(path.join(tempDir, "pipes"), { recursive: true });
339
+ fs.writeFileSync(path.join(tempDir, "pipes", "top_events.pipe"), "");
340
+ fs.writeFileSync(path.join(tempDir, "pipes", "analytics.pipe"), "");
341
+
342
+ const files = findExistingDatafiles(tempDir);
343
+
344
+ expect(files).toContain("pipes/analytics.pipe");
345
+ expect(files).toContain("pipes/top_events.pipe");
346
+ });
347
+
348
+ it("finds both .datasource and .pipe files", () => {
349
+ fs.mkdirSync(path.join(tempDir, "tinybird"), { recursive: true });
350
+ fs.writeFileSync(path.join(tempDir, "tinybird", "events.datasource"), "");
351
+ fs.writeFileSync(path.join(tempDir, "tinybird", "top_events.pipe"), "");
352
+
353
+ const files = findExistingDatafiles(tempDir);
354
+
355
+ expect(files).toHaveLength(2);
356
+ expect(files).toContain("tinybird/events.datasource");
357
+ expect(files).toContain("tinybird/top_events.pipe");
358
+ });
359
+
360
+ it("skips node_modules directory", () => {
361
+ fs.mkdirSync(path.join(tempDir, "node_modules", "some-package"), { recursive: true });
362
+ fs.writeFileSync(path.join(tempDir, "node_modules", "some-package", "test.datasource"), "");
363
+
364
+ const files = findExistingDatafiles(tempDir);
365
+
366
+ expect(files).not.toContain("node_modules/some-package/test.datasource");
367
+ expect(files).toHaveLength(0);
368
+ });
369
+
370
+ it("skips hidden directories", () => {
371
+ fs.mkdirSync(path.join(tempDir, ".git", "hooks"), { recursive: true });
372
+ fs.writeFileSync(path.join(tempDir, ".git", "hooks", "test.datasource"), "");
373
+
374
+ const files = findExistingDatafiles(tempDir);
375
+
376
+ expect(files).not.toContain(".git/hooks/test.datasource");
377
+ expect(files).toHaveLength(0);
378
+ });
379
+
380
+ it("skips dist and build directories", () => {
381
+ fs.mkdirSync(path.join(tempDir, "dist"), { recursive: true });
382
+ fs.mkdirSync(path.join(tempDir, "build"), { recursive: true });
383
+ fs.writeFileSync(path.join(tempDir, "dist", "test.datasource"), "");
384
+ fs.writeFileSync(path.join(tempDir, "build", "test.pipe"), "");
385
+
386
+ const files = findExistingDatafiles(tempDir);
387
+
388
+ expect(files).toHaveLength(0);
389
+ });
390
+
391
+ it("returns empty array when no datafiles exist", () => {
392
+ const files = findExistingDatafiles(tempDir);
393
+
394
+ expect(files).toEqual([]);
395
+ });
396
+
397
+ it("respects maxDepth parameter", () => {
398
+ // Create a deeply nested file
399
+ fs.mkdirSync(path.join(tempDir, "a", "b", "c", "d", "e", "f"), { recursive: true });
400
+ fs.writeFileSync(path.join(tempDir, "a", "b", "c", "d", "e", "f", "deep.datasource"), "");
401
+ // Create a shallow file
402
+ fs.writeFileSync(path.join(tempDir, "a", "shallow.datasource"), "");
403
+
404
+ const filesDefault = findExistingDatafiles(tempDir, 5);
405
+ expect(filesDefault).toContain("a/shallow.datasource");
406
+ expect(filesDefault).not.toContain("a/b/c/d/e/f/deep.datasource");
407
+
408
+ const filesDeep = findExistingDatafiles(tempDir, 10);
409
+ expect(filesDeep).toContain("a/b/c/d/e/f/deep.datasource");
410
+ });
411
+
412
+ it("returns files in sorted order", () => {
413
+ fs.mkdirSync(path.join(tempDir, "datasources"), { recursive: true });
414
+ fs.writeFileSync(path.join(tempDir, "datasources", "zebra.datasource"), "");
415
+ fs.writeFileSync(path.join(tempDir, "datasources", "alpha.datasource"), "");
416
+ fs.writeFileSync(path.join(tempDir, "datasources", "beta.datasource"), "");
417
+
418
+ const files = findExistingDatafiles(tempDir);
419
+
420
+ expect(files).toEqual([
421
+ "datasources/alpha.datasource",
422
+ "datasources/beta.datasource",
423
+ "datasources/zebra.datasource",
424
+ ]);
425
+ });
426
+ });
427
+
428
+ describe("existing datafiles detection", () => {
429
+ it("includes existing datafiles in config when user opts in", async () => {
430
+ // Create existing datafiles
431
+ fs.mkdirSync(path.join(tempDir, "datasources"), { recursive: true });
432
+ fs.writeFileSync(path.join(tempDir, "datasources", "events.datasource"), "");
433
+ fs.mkdirSync(path.join(tempDir, "pipes"), { recursive: true });
434
+ fs.writeFileSync(path.join(tempDir, "pipes", "top_events.pipe"), "");
435
+
436
+ const result = await runInit({
437
+ cwd: tempDir,
438
+ skipLogin: true,
439
+ devMode: "branch",
440
+ clientPath: "tinybird",
441
+ skipDatafilePrompt: true,
442
+ includeExistingDatafiles: true,
443
+ });
444
+
445
+ expect(result.success).toBe(true);
446
+ expect(result.existingDatafiles).toContain("datasources/events.datasource");
447
+ expect(result.existingDatafiles).toContain("pipes/top_events.pipe");
448
+
449
+ const config = JSON.parse(
450
+ fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
451
+ );
452
+ expect(config.include).toContain("datasources/events.datasource");
453
+ expect(config.include).toContain("pipes/top_events.pipe");
454
+ });
455
+
456
+ it("does not include existing datafiles when user opts out", async () => {
457
+ // Create existing datafiles
458
+ fs.mkdirSync(path.join(tempDir, "datasources"), { recursive: true });
459
+ fs.writeFileSync(path.join(tempDir, "datasources", "events.datasource"), "");
460
+
461
+ const result = await runInit({
462
+ cwd: tempDir,
463
+ skipLogin: true,
464
+ devMode: "branch",
465
+ clientPath: "tinybird",
466
+ skipDatafilePrompt: true,
467
+ includeExistingDatafiles: false,
468
+ });
469
+
470
+ expect(result.success).toBe(true);
471
+ expect(result.existingDatafiles).toBeUndefined();
472
+
473
+ const config = JSON.parse(
474
+ fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
475
+ );
476
+ expect(config.include).not.toContain("datasources/events.datasource");
477
+ expect(config.include).toEqual([
478
+ "tinybird/datasources.ts",
479
+ "tinybird/endpoints.ts",
480
+ ]);
481
+ });
482
+
483
+ it("preserves TypeScript include paths alongside datafiles", async () => {
484
+ // Create existing datafiles
485
+ fs.mkdirSync(path.join(tempDir, "datasources"), { recursive: true });
486
+ fs.writeFileSync(path.join(tempDir, "datasources", "events.datasource"), "");
487
+
488
+ const result = await runInit({
489
+ cwd: tempDir,
490
+ skipLogin: true,
491
+ devMode: "branch",
492
+ clientPath: "tinybird",
493
+ skipDatafilePrompt: true,
494
+ includeExistingDatafiles: true,
495
+ });
496
+
497
+ expect(result.success).toBe(true);
498
+
499
+ const config = JSON.parse(
500
+ fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
501
+ );
502
+ // Should have both TypeScript files AND datafiles
503
+ expect(config.include).toContain("tinybird/datasources.ts");
504
+ expect(config.include).toContain("tinybird/endpoints.ts");
505
+ expect(config.include).toContain("datasources/events.datasource");
506
+ });
507
+
508
+ it("handles projects with no existing datafiles", async () => {
509
+ const result = await runInit({
510
+ cwd: tempDir,
511
+ skipLogin: true,
512
+ devMode: "branch",
513
+ clientPath: "tinybird",
514
+ skipDatafilePrompt: true,
515
+ includeExistingDatafiles: true,
516
+ });
517
+
518
+ expect(result.success).toBe(true);
519
+ expect(result.existingDatafiles).toBeUndefined();
520
+
521
+ const config = JSON.parse(
522
+ fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
523
+ );
524
+ expect(config.include).toEqual([
525
+ "tinybird/datasources.ts",
526
+ "tinybird/endpoints.ts",
527
+ ]);
528
+ });
529
+ });
321
530
  });