@tinybirdco/sdk 0.0.4 → 0.0.7

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 (165) hide show
  1. package/README.md +52 -13
  2. package/dist/api/branches.d.ts.map +1 -1
  3. package/dist/api/branches.js +6 -5
  4. package/dist/api/branches.js.map +1 -1
  5. package/dist/api/branches.test.js +32 -6
  6. package/dist/api/branches.test.js.map +1 -1
  7. package/dist/api/build.d.ts.map +1 -1
  8. package/dist/api/build.js +2 -1
  9. package/dist/api/build.js.map +1 -1
  10. package/dist/api/deploy.d.ts +42 -3
  11. package/dist/api/deploy.d.ts.map +1 -1
  12. package/dist/api/deploy.js +162 -19
  13. package/dist/api/deploy.js.map +1 -1
  14. package/dist/api/deploy.test.js +83 -31
  15. package/dist/api/deploy.test.js.map +1 -1
  16. package/dist/api/fetcher.d.ts +6 -0
  17. package/dist/api/fetcher.d.ts.map +1 -0
  18. package/dist/api/fetcher.js +13 -0
  19. package/dist/api/fetcher.js.map +1 -0
  20. package/dist/api/local.d.ts.map +1 -1
  21. package/dist/api/local.js +5 -4
  22. package/dist/api/local.js.map +1 -1
  23. package/dist/api/local.test.js.map +1 -1
  24. package/dist/api/resources.d.ts +178 -0
  25. package/dist/api/resources.d.ts.map +1 -0
  26. package/dist/api/resources.js +245 -0
  27. package/dist/api/resources.js.map +1 -0
  28. package/dist/api/resources.test.d.ts +2 -0
  29. package/dist/api/resources.test.d.ts.map +1 -0
  30. package/dist/api/resources.test.js +255 -0
  31. package/dist/api/resources.test.js.map +1 -0
  32. package/dist/api/workspaces.d.ts.map +1 -1
  33. package/dist/api/workspaces.js +2 -1
  34. package/dist/api/workspaces.js.map +1 -1
  35. package/dist/api/workspaces.test.js +9 -1
  36. package/dist/api/workspaces.test.js.map +1 -1
  37. package/dist/cli/auth.d.ts.map +1 -1
  38. package/dist/cli/auth.js +2 -1
  39. package/dist/cli/auth.js.map +1 -1
  40. package/dist/cli/commands/build.d.ts +3 -4
  41. package/dist/cli/commands/build.d.ts.map +1 -1
  42. package/dist/cli/commands/build.js +23 -25
  43. package/dist/cli/commands/build.js.map +1 -1
  44. package/dist/cli/commands/deploy.d.ts +41 -0
  45. package/dist/cli/commands/deploy.d.ts.map +1 -0
  46. package/dist/cli/commands/deploy.js +92 -0
  47. package/dist/cli/commands/deploy.js.map +1 -0
  48. package/dist/cli/commands/dev.d.ts.map +1 -1
  49. package/dist/cli/commands/dev.js +7 -3
  50. package/dist/cli/commands/dev.js.map +1 -1
  51. package/dist/cli/commands/init.d.ts +38 -1
  52. package/dist/cli/commands/init.d.ts.map +1 -1
  53. package/dist/cli/commands/init.js +434 -23
  54. package/dist/cli/commands/init.js.map +1 -1
  55. package/dist/cli/commands/init.test.js +190 -30
  56. package/dist/cli/commands/init.test.js.map +1 -1
  57. package/dist/cli/index.js +80 -15
  58. package/dist/cli/index.js.map +1 -1
  59. package/dist/cli/utils/package-manager.d.ts +8 -0
  60. package/dist/cli/utils/package-manager.d.ts.map +1 -0
  61. package/dist/cli/utils/package-manager.js +45 -0
  62. package/dist/cli/utils/package-manager.js.map +1 -0
  63. package/dist/cli/utils/package-manager.test.d.ts +2 -0
  64. package/dist/cli/utils/package-manager.test.d.ts.map +1 -0
  65. package/dist/cli/utils/package-manager.test.js +85 -0
  66. package/dist/cli/utils/package-manager.test.js.map +1 -0
  67. package/dist/client/base.d.ts.map +1 -1
  68. package/dist/client/base.js +2 -1
  69. package/dist/client/base.js.map +1 -1
  70. package/dist/codegen/index.d.ts +39 -0
  71. package/dist/codegen/index.d.ts.map +1 -0
  72. package/dist/codegen/index.js +300 -0
  73. package/dist/codegen/index.js.map +1 -0
  74. package/dist/codegen/index.test.d.ts +2 -0
  75. package/dist/codegen/index.test.d.ts.map +1 -0
  76. package/dist/codegen/index.test.js +310 -0
  77. package/dist/codegen/index.test.js.map +1 -0
  78. package/dist/codegen/type-mapper.d.ts +20 -0
  79. package/dist/codegen/type-mapper.d.ts.map +1 -0
  80. package/dist/codegen/type-mapper.js +238 -0
  81. package/dist/codegen/type-mapper.js.map +1 -0
  82. package/dist/codegen/type-mapper.test.d.ts +2 -0
  83. package/dist/codegen/type-mapper.test.d.ts.map +1 -0
  84. package/dist/codegen/type-mapper.test.js +167 -0
  85. package/dist/codegen/type-mapper.test.js.map +1 -0
  86. package/dist/codegen/utils.d.ts +46 -0
  87. package/dist/codegen/utils.d.ts.map +1 -0
  88. package/dist/codegen/utils.js +141 -0
  89. package/dist/codegen/utils.js.map +1 -0
  90. package/dist/codegen/utils.test.d.ts +2 -0
  91. package/dist/codegen/utils.test.d.ts.map +1 -0
  92. package/dist/codegen/utils.test.js +178 -0
  93. package/dist/codegen/utils.test.js.map +1 -0
  94. package/dist/generator/index.d.ts +3 -0
  95. package/dist/generator/index.d.ts.map +1 -1
  96. package/dist/generator/index.js +17 -1
  97. package/dist/generator/index.js.map +1 -1
  98. package/dist/generator/index.test.js +104 -1
  99. package/dist/generator/index.test.js.map +1 -1
  100. package/dist/generator/loader.d.ts +15 -0
  101. package/dist/generator/loader.d.ts.map +1 -1
  102. package/dist/generator/loader.js +24 -0
  103. package/dist/generator/loader.js.map +1 -1
  104. package/dist/schema/connection.d.ts.map +1 -1
  105. package/dist/schema/connection.js +3 -2
  106. package/dist/schema/connection.js.map +1 -1
  107. package/dist/schema/datasource.d.ts.map +1 -1
  108. package/dist/schema/datasource.js +3 -2
  109. package/dist/schema/datasource.js.map +1 -1
  110. package/dist/schema/params.d.ts.map +1 -1
  111. package/dist/schema/params.js +3 -2
  112. package/dist/schema/params.js.map +1 -1
  113. package/dist/schema/pipe.d.ts +2 -2
  114. package/dist/schema/pipe.d.ts.map +1 -1
  115. package/dist/schema/pipe.js +4 -4
  116. package/dist/schema/pipe.js.map +1 -1
  117. package/dist/schema/project.d.ts.map +1 -1
  118. package/dist/schema/project.js +3 -2
  119. package/dist/schema/project.js.map +1 -1
  120. package/dist/schema/types.d.ts.map +1 -1
  121. package/dist/schema/types.js +3 -2
  122. package/dist/schema/types.js.map +1 -1
  123. package/dist/test/handlers.d.ts +49 -0
  124. package/dist/test/handlers.d.ts.map +1 -1
  125. package/dist/test/handlers.js +45 -0
  126. package/dist/test/handlers.js.map +1 -1
  127. package/package.json +4 -2
  128. package/src/api/branches.test.ts +65 -57
  129. package/src/api/branches.ts +7 -5
  130. package/src/api/build.ts +2 -1
  131. package/src/api/deploy.test.ts +141 -36
  132. package/src/api/deploy.ts +231 -23
  133. package/src/api/fetcher.ts +17 -0
  134. package/src/api/local.test.ts +43 -31
  135. package/src/api/local.ts +5 -4
  136. package/src/api/resources.test.ts +332 -0
  137. package/src/api/resources.ts +555 -0
  138. package/src/api/workspaces.test.ts +15 -9
  139. package/src/api/workspaces.ts +3 -1
  140. package/src/cli/auth.ts +2 -1
  141. package/src/cli/commands/build.ts +29 -33
  142. package/src/cli/commands/deploy.ts +131 -0
  143. package/src/cli/commands/dev.ts +10 -3
  144. package/src/cli/commands/init.test.ts +239 -30
  145. package/src/cli/commands/init.ts +548 -26
  146. package/src/cli/index.ts +117 -20
  147. package/src/cli/utils/package-manager.test.ts +118 -0
  148. package/src/cli/utils/package-manager.ts +44 -0
  149. package/src/client/base.ts +3 -2
  150. package/src/codegen/index.test.ts +367 -0
  151. package/src/codegen/index.ts +379 -0
  152. package/src/codegen/type-mapper.test.ts +224 -0
  153. package/src/codegen/type-mapper.ts +265 -0
  154. package/src/codegen/utils.test.ts +221 -0
  155. package/src/codegen/utils.ts +174 -0
  156. package/src/generator/index.test.ts +121 -1
  157. package/src/generator/index.ts +19 -1
  158. package/src/generator/loader.ts +43 -0
  159. package/src/schema/connection.ts +3 -2
  160. package/src/schema/datasource.ts +3 -2
  161. package/src/schema/params.ts +3 -2
  162. package/src/schema/pipe.ts +4 -4
  163. package/src/schema/project.ts +3 -2
  164. package/src/schema/types.ts +3 -2
  165. package/src/test/handlers.ts +58 -0
package/src/cli/index.ts CHANGED
@@ -16,6 +16,7 @@ import { dirname, resolve } from "node:path";
16
16
  import { Command } from "commander";
17
17
  import { runInit } from "./commands/init.js";
18
18
  import { runBuild } from "./commands/build.js";
19
+ import { runDeploy } from "./commands/deploy.js";
19
20
  import { runDev } from "./commands/dev.js";
20
21
  import { runLogin } from "./commands/login.js";
21
22
  import {
@@ -23,6 +24,7 @@ import {
23
24
  runBranchStatus,
24
25
  runBranchDelete,
25
26
  } from "./commands/branch.js";
27
+ import { detectPackageManagerRunCmd } from "./utils/package-manager.js";
26
28
  import type { DevMode } from "./config.js";
27
29
 
28
30
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -55,12 +57,22 @@ function createCli(): Command {
55
57
  .description("Initialize a new Tinybird TypeScript project")
56
58
  .option("-f, --force", "Overwrite existing files")
57
59
  .option("--skip-login", "Skip browser login flow")
60
+ .option("-m, --mode <mode>", "Development mode: 'branch' or 'local'")
61
+ .option("-p, --path <path>", "Path for Tinybird client files")
58
62
  .action(async (options) => {
59
- console.log("Initializing Tinybird project...\n");
63
+ // Validate mode if provided
64
+ if (options.mode && !["branch", "local"].includes(options.mode)) {
65
+ console.error(
66
+ `Error: Invalid mode '${options.mode}'. Use 'branch' or 'local'.`
67
+ );
68
+ process.exit(1);
69
+ }
60
70
 
61
71
  const result = await runInit({
62
72
  force: options.force,
63
73
  skipLogin: options.skipLogin,
74
+ devMode: options.mode,
75
+ clientPath: options.path,
64
76
  });
65
77
 
66
78
  if (!result.success) {
@@ -82,6 +94,10 @@ function createCli(): Command {
82
94
  });
83
95
  }
84
96
 
97
+ // Detect package manager for run command
98
+ const runCmd = detectPackageManagerRunCmd();
99
+ const clientPath = result.clientPath ?? "tinybird";
100
+
85
101
  if (result.loggedIn) {
86
102
  console.log(`\nLogged in successfully!`);
87
103
  if (result.workspaceName) {
@@ -90,19 +106,25 @@ function createCli(): Command {
90
106
  if (result.userEmail) {
91
107
  console.log(` User: ${result.userEmail}`);
92
108
  }
109
+
110
+ if (result.existingDatafiles && result.existingDatafiles.length > 0) {
111
+ console.log(
112
+ `\nAdded ${result.existingDatafiles.length} existing datafile(s) to tinybird.json.`
113
+ );
114
+ }
93
115
  console.log("\nDone! Next steps:");
94
- console.log(" 1. Edit src/tinybird/schema.ts with your schema");
95
- console.log(" 2. Run 'npx tinybird dev' to start development");
116
+ console.log(` 1. Edit your schema in ${clientPath}/`);
117
+ console.log(` 2. Run '${runCmd} tinybird:dev' to start development`);
96
118
  } else if (result.loggedIn === false) {
97
119
  console.log("\nLogin was skipped or failed.");
98
120
  console.log("\nDone! Next steps:");
99
121
  console.log(" 1. Run 'npx tinybird login' to authenticate");
100
- console.log(" 2. Edit src/tinybird/schema.ts with your schema");
101
- console.log(" 3. Run 'npx tinybird dev' to start development");
122
+ console.log(` 2. Edit your schema in ${clientPath}/`);
123
+ console.log(` 3. Run '${runCmd} tinybird:dev' to start development`);
102
124
  } else {
103
125
  console.log("\nDone! Next steps:");
104
- console.log(" 1. Edit src/tinybird/schema.ts with your schema");
105
- console.log(" 2. Run 'npx tinybird dev' to start development");
126
+ console.log(` 1. Edit your schema in ${clientPath}/`);
127
+ console.log(` 2. Run '${runCmd} tinybird:dev' to start development`);
106
128
  }
107
129
  });
108
130
 
@@ -135,11 +157,10 @@ function createCli(): Command {
135
157
  // Build command
136
158
  program
137
159
  .command("build")
138
- .description("Build and push resources to Tinybird")
160
+ .description("Build and push resources to a Tinybird branch (not main)")
139
161
  .option("--dry-run", "Generate without pushing to API")
140
162
  .option("--debug", "Show debug output including API requests/responses")
141
163
  .option("--local", "Use local Tinybird container")
142
- .option("--branch", "Use Tinybird cloud with branches")
143
164
  .action(async (options) => {
144
165
  if (options.debug) {
145
166
  process.env.TINYBIRD_DEBUG = "1";
@@ -149,8 +170,6 @@ function createCli(): Command {
149
170
  let devModeOverride: DevMode | undefined;
150
171
  if (options.local) {
151
172
  devModeOverride = "local";
152
- } else if (options.branch) {
153
- devModeOverride = "branch";
154
173
  }
155
174
 
156
175
  const modeLabel = devModeOverride === "local" ? " (local)" : "";
@@ -169,7 +188,9 @@ function createCli(): Command {
169
188
  const { build, deploy } = result;
170
189
 
171
190
  if (build) {
172
- console.log(`Generated ${build.stats.datasourceCount} datasource(s), ${build.stats.pipeCount} pipe(s)`);
191
+ console.log(
192
+ `Generated ${build.stats.datasourceCount} datasource(s), ${build.stats.pipeCount} pipe(s)`
193
+ );
173
194
  }
174
195
 
175
196
  if (options.dryRun) {
@@ -200,6 +221,68 @@ function createCli(): Command {
200
221
  console.log(`\n[${formatTime()}] Done in ${result.durationMs}ms`);
201
222
  });
202
223
 
224
+ // Deploy command
225
+ program
226
+ .command("deploy")
227
+ .description("Deploy resources to main Tinybird workspace (production)")
228
+ .option("--dry-run", "Generate without pushing to API")
229
+ .option("--check", "Validate deploy with Tinybird API without applying")
230
+ .option("--debug", "Show debug output including API requests/responses")
231
+ .action(async (options) => {
232
+ if (options.debug) {
233
+ process.env.TINYBIRD_DEBUG = "1";
234
+ }
235
+
236
+ console.log(`[${formatTime()}] Deploying to main workspace...\n`);
237
+
238
+ const result = await runDeploy({
239
+ dryRun: options.dryRun,
240
+ check: options.check,
241
+ });
242
+
243
+ if (!result.success) {
244
+ console.error(`Error: ${result.error}`);
245
+ process.exit(1);
246
+ }
247
+
248
+ const { build, deploy } = result;
249
+
250
+ if (build) {
251
+ console.log(
252
+ `Generated ${build.stats.datasourceCount} datasource(s), ${build.stats.pipeCount} pipe(s)`
253
+ );
254
+ }
255
+
256
+ if (options.dryRun) {
257
+ console.log("\n[Dry run] Resources not deployed to API");
258
+
259
+ // Show generated content
260
+ if (build) {
261
+ console.log("\n--- Generated Datasources ---");
262
+ build.resources.datasources.forEach((ds) => {
263
+ console.log(`\n${ds.name}.datasource:`);
264
+ console.log(ds.content);
265
+ });
266
+
267
+ console.log("\n--- Generated Pipes ---");
268
+ build.resources.pipes.forEach((pipe) => {
269
+ console.log(`\n${pipe.name}.pipe:`);
270
+ console.log(pipe.content);
271
+ });
272
+ }
273
+ } else if (options.check) {
274
+ console.log("\n[Check] Resources validated with Tinybird API");
275
+ } else if (deploy) {
276
+ if (deploy.result === "no_changes") {
277
+ console.log("No changes detected - already up to date");
278
+ } else {
279
+ console.log(`Deployed to main workspace successfully`);
280
+ }
281
+ }
282
+
283
+ console.log(`\n[${formatTime()}] Done in ${result.durationMs}ms`);
284
+ });
285
+
203
286
  // Dev command
204
287
  program
205
288
  .command("dev")
@@ -241,7 +324,9 @@ function createCli(): Command {
241
324
  console.log("Workspace created.\n");
242
325
  } else {
243
326
  console.log(`Using local Tinybird container`);
244
- console.log(`Using existing local workspace '${workspaceName}'\n`);
327
+ console.log(
328
+ `Using existing local workspace '${workspaceName}'\n`
329
+ );
245
330
  }
246
331
  } else if (info.isMainBranch) {
247
332
  console.log("On main branch - deploying to workspace\n");
@@ -253,7 +338,9 @@ function createCli(): Command {
253
338
  console.log("Branch created and token cached.\n");
254
339
  } else {
255
340
  console.log(`Detected git branch: ${info.gitBranch}`);
256
- console.log(`Using existing Tinybird branch '${tinybirdName}'\n`);
341
+ console.log(
342
+ `Using existing Tinybird branch '${tinybirdName}'\n`
343
+ );
257
344
  }
258
345
  } else {
259
346
  console.log("Not in a git repository - deploying to workspace\n");
@@ -274,7 +361,9 @@ function createCli(): Command {
274
361
  if (deploy.result === "no_changes") {
275
362
  console.log(`[${formatTime()}] No changes detected`);
276
363
  } else {
277
- console.log(`[${formatTime()}] Built in ${result.durationMs}ms`);
364
+ console.log(
365
+ `[${formatTime()}] Built in ${result.durationMs}ms`
366
+ );
278
367
 
279
368
  // Show datasource changes
280
369
  if (deploy.datasources) {
@@ -309,7 +398,9 @@ function createCli(): Command {
309
398
  console.log(`[${formatTime()}] Schema validation:`);
310
399
  for (const issue of validation.issues) {
311
400
  if (issue.type === "error") {
312
- console.error(` ERROR [${issue.pipeName}]: ${issue.message}`);
401
+ console.error(
402
+ ` ERROR [${issue.pipeName}]: ${issue.message}`
403
+ );
313
404
  } else {
314
405
  console.warn(` WARN [${issue.pipeName}]: ${issue.message}`);
315
406
  }
@@ -342,8 +433,9 @@ function createCli(): Command {
342
433
  });
343
434
 
344
435
  // Branch command
345
- const branchCommand = new Command("branch")
346
- .description("Manage Tinybird branches");
436
+ const branchCommand = new Command("branch").description(
437
+ "Manage Tinybird branches"
438
+ );
347
439
 
348
440
  branchCommand
349
441
  .command("list")
@@ -380,8 +472,13 @@ function createCli(): Command {
380
472
 
381
473
  console.log("Branch Status:");
382
474
  console.log(` Git branch: ${result.gitBranch ?? "(not in git repo)"}`);
383
- if (result.tinybirdBranchName && result.tinybirdBranchName !== result.gitBranch) {
384
- console.log(` Tinybird branch name: ${result.tinybirdBranchName} (sanitized)`);
475
+ if (
476
+ result.tinybirdBranchName &&
477
+ result.tinybirdBranchName !== result.gitBranch
478
+ ) {
479
+ console.log(
480
+ ` Tinybird branch name: ${result.tinybirdBranchName} (sanitized)`
481
+ );
385
482
  }
386
483
  console.log(` Main branch: ${result.isMainBranch ? "yes" : "no"}`);
387
484
 
@@ -0,0 +1,118 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import * as os from "os";
5
+ import { detectPackageManagerRunCmd } from "./package-manager.js";
6
+
7
+ describe("detectPackageManagerRunCmd", () => {
8
+ let tempDir: string;
9
+
10
+ beforeEach(() => {
11
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pkg-manager-test-"));
12
+ });
13
+
14
+ afterEach(() => {
15
+ try {
16
+ fs.rmSync(tempDir, { recursive: true });
17
+ } catch {
18
+ // Ignore cleanup errors
19
+ }
20
+ });
21
+
22
+ describe("lockfile detection", () => {
23
+ it("detects pnpm from pnpm-lock.yaml", () => {
24
+ fs.writeFileSync(path.join(tempDir, "pnpm-lock.yaml"), "");
25
+ expect(detectPackageManagerRunCmd(tempDir)).toBe("pnpm");
26
+ });
27
+
28
+ it("detects yarn from yarn.lock", () => {
29
+ fs.writeFileSync(path.join(tempDir, "yarn.lock"), "");
30
+ expect(detectPackageManagerRunCmd(tempDir)).toBe("yarn");
31
+ });
32
+
33
+ it("detects bun from bun.lockb", () => {
34
+ fs.writeFileSync(path.join(tempDir, "bun.lockb"), "");
35
+ expect(detectPackageManagerRunCmd(tempDir)).toBe("bun run");
36
+ });
37
+
38
+ it("detects npm from package-lock.json", () => {
39
+ fs.writeFileSync(path.join(tempDir, "package-lock.json"), "{}");
40
+ expect(detectPackageManagerRunCmd(tempDir)).toBe("npm run");
41
+ });
42
+
43
+ it("prioritizes pnpm lockfile over others", () => {
44
+ fs.writeFileSync(path.join(tempDir, "pnpm-lock.yaml"), "");
45
+ fs.writeFileSync(path.join(tempDir, "yarn.lock"), "");
46
+ fs.writeFileSync(path.join(tempDir, "package-lock.json"), "{}");
47
+ expect(detectPackageManagerRunCmd(tempDir)).toBe("pnpm");
48
+ });
49
+
50
+ it("prioritizes yarn lockfile over npm", () => {
51
+ fs.writeFileSync(path.join(tempDir, "yarn.lock"), "");
52
+ fs.writeFileSync(path.join(tempDir, "package-lock.json"), "{}");
53
+ expect(detectPackageManagerRunCmd(tempDir)).toBe("yarn");
54
+ });
55
+ });
56
+
57
+ describe("packageManager field detection", () => {
58
+ it("detects pnpm from packageManager field", () => {
59
+ fs.writeFileSync(
60
+ path.join(tempDir, "package.json"),
61
+ JSON.stringify({ packageManager: "pnpm@9.0.0" })
62
+ );
63
+ expect(detectPackageManagerRunCmd(tempDir)).toBe("pnpm");
64
+ });
65
+
66
+ it("detects yarn from packageManager field", () => {
67
+ fs.writeFileSync(
68
+ path.join(tempDir, "package.json"),
69
+ JSON.stringify({ packageManager: "yarn@4.0.0" })
70
+ );
71
+ expect(detectPackageManagerRunCmd(tempDir)).toBe("yarn");
72
+ });
73
+
74
+ it("detects bun from packageManager field", () => {
75
+ fs.writeFileSync(
76
+ path.join(tempDir, "package.json"),
77
+ JSON.stringify({ packageManager: "bun@1.0.0" })
78
+ );
79
+ expect(detectPackageManagerRunCmd(tempDir)).toBe("bun run");
80
+ });
81
+
82
+ it("prioritizes lockfile over packageManager field", () => {
83
+ fs.writeFileSync(path.join(tempDir, "yarn.lock"), "");
84
+ fs.writeFileSync(
85
+ path.join(tempDir, "package.json"),
86
+ JSON.stringify({ packageManager: "pnpm@9.0.0" })
87
+ );
88
+ expect(detectPackageManagerRunCmd(tempDir)).toBe("yarn");
89
+ });
90
+ });
91
+
92
+ describe("default behavior", () => {
93
+ it("defaults to npm run when no indicators found", () => {
94
+ expect(detectPackageManagerRunCmd(tempDir)).toBe("npm run");
95
+ });
96
+
97
+ it("defaults to npm run when package.json has no packageManager field", () => {
98
+ fs.writeFileSync(
99
+ path.join(tempDir, "package.json"),
100
+ JSON.stringify({ name: "test-project" })
101
+ );
102
+ expect(detectPackageManagerRunCmd(tempDir)).toBe("npm run");
103
+ });
104
+
105
+ it("defaults to npm run when package.json is invalid JSON", () => {
106
+ fs.writeFileSync(path.join(tempDir, "package.json"), "not json");
107
+ expect(detectPackageManagerRunCmd(tempDir)).toBe("npm run");
108
+ });
109
+
110
+ it("defaults to npm run when packageManager is not a string", () => {
111
+ fs.writeFileSync(
112
+ path.join(tempDir, "package.json"),
113
+ JSON.stringify({ packageManager: 123 })
114
+ );
115
+ expect(detectPackageManagerRunCmd(tempDir)).toBe("npm run");
116
+ });
117
+ });
118
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Package manager detection utilities
3
+ */
4
+
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { join } from "node:path";
7
+
8
+ /**
9
+ * Detect package manager and return the appropriate run command
10
+ */
11
+ export function detectPackageManagerRunCmd(cwd: string = process.cwd()): string {
12
+ // Check lockfiles first (most reliable)
13
+ if (existsSync(join(cwd, "pnpm-lock.yaml"))) {
14
+ return "pnpm";
15
+ }
16
+ if (existsSync(join(cwd, "yarn.lock"))) {
17
+ return "yarn";
18
+ }
19
+ if (existsSync(join(cwd, "bun.lockb"))) {
20
+ return "bun run";
21
+ }
22
+ if (existsSync(join(cwd, "package-lock.json"))) {
23
+ return "npm run";
24
+ }
25
+
26
+ // Check packageManager field in package.json
27
+ const packageJsonPath = join(cwd, "package.json");
28
+ if (existsSync(packageJsonPath)) {
29
+ try {
30
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
31
+ const pm = packageJson.packageManager;
32
+ if (typeof pm === "string") {
33
+ if (pm.startsWith("pnpm")) return "pnpm";
34
+ if (pm.startsWith("yarn")) return "yarn";
35
+ if (pm.startsWith("bun")) return "bun run";
36
+ }
37
+ } catch {
38
+ // Ignore parse errors
39
+ }
40
+ }
41
+
42
+ // Default to npm
43
+ return "npm run";
44
+ }
@@ -11,6 +11,7 @@ import type {
11
11
  TinybirdErrorResponse,
12
12
  } from "./types.js";
13
13
  import { TinybirdError } from "./types.js";
14
+ import { createTinybirdFetcher, type TinybirdFetch } from "../api/fetcher.js";
14
15
 
15
16
  /**
16
17
  * Default timeout for requests (30 seconds)
@@ -56,7 +57,7 @@ interface ResolvedTokenInfo {
56
57
  */
57
58
  export class TinybirdClient {
58
59
  private readonly config: ClientConfig;
59
- private readonly fetchFn: typeof fetch;
60
+ private readonly fetchFn: TinybirdFetch;
60
61
  private tokenPromise: Promise<ResolvedTokenInfo> | null = null;
61
62
  private resolvedToken: string | null = null;
62
63
 
@@ -75,7 +76,7 @@ export class TinybirdClient {
75
76
  baseUrl: config.baseUrl.replace(/\/$/, ""),
76
77
  };
77
78
 
78
- this.fetchFn = config.fetch ?? globalThis.fetch;
79
+ this.fetchFn = createTinybirdFetcher(config.fetch ?? globalThis.fetch);
79
80
  }
80
81
 
81
82
  /**