@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
package/src/cli/env.ts ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Environment file utilities for managing .env.local
3
+ */
4
+
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+
8
+ /**
9
+ * Result of saving a token to .env.local
10
+ */
11
+ export interface SaveTokenResult {
12
+ /** Whether the file was newly created */
13
+ created: boolean;
14
+ }
15
+
16
+ /**
17
+ * Save the TINYBIRD_TOKEN to .env.local
18
+ *
19
+ * Handles creating, appending, or updating the token in the file.
20
+ * Ensures proper newline handling when appending.
21
+ *
22
+ * @param directory - Directory where .env.local should be saved
23
+ * @param token - The token value to save
24
+ * @returns Result indicating if the file was created
25
+ */
26
+ export function saveTinybirdToken(
27
+ directory: string,
28
+ token: string
29
+ ): SaveTokenResult {
30
+ const envLocalPath = path.join(directory, ".env.local");
31
+ const envContent = `TINYBIRD_TOKEN=${token}\n`;
32
+
33
+ if (fs.existsSync(envLocalPath)) {
34
+ const existingContent = fs.readFileSync(envLocalPath, "utf-8");
35
+
36
+ if (existingContent.includes("TINYBIRD_TOKEN=")) {
37
+ // Replace existing token
38
+ const updatedContent = existingContent.replace(
39
+ /TINYBIRD_TOKEN=.*/,
40
+ `TINYBIRD_TOKEN=${token}`
41
+ );
42
+ fs.writeFileSync(envLocalPath, updatedContent);
43
+ } else {
44
+ // Append token, ensuring proper newline
45
+ const needsNewline = existingContent.length > 0 && !existingContent.endsWith("\n");
46
+ fs.appendFileSync(envLocalPath, (needsNewline ? "\n" : "") + envContent);
47
+ }
48
+
49
+ return { created: false };
50
+ } else {
51
+ // Create new file
52
+ fs.writeFileSync(envLocalPath, envContent);
53
+ return { created: true };
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Save the TINYBIRD_URL to .env.local
59
+ *
60
+ * @param directory - Directory where .env.local should be saved
61
+ * @param baseUrl - The base URL to save
62
+ */
63
+ export function saveTinybirdBaseUrl(directory: string, baseUrl: string): void {
64
+ const envLocalPath = path.join(directory, ".env.local");
65
+ const envContent = `TINYBIRD_URL=${baseUrl}\n`;
66
+
67
+ if (fs.existsSync(envLocalPath)) {
68
+ const existingContent = fs.readFileSync(envLocalPath, "utf-8");
69
+
70
+ if (existingContent.includes("TINYBIRD_URL=")) {
71
+ // Replace existing URL
72
+ const updatedContent = existingContent.replace(
73
+ /TINYBIRD_URL=.*/,
74
+ `TINYBIRD_URL=${baseUrl}`
75
+ );
76
+ fs.writeFileSync(envLocalPath, updatedContent);
77
+ } else {
78
+ // Append URL, ensuring proper newline
79
+ const needsNewline = existingContent.length > 0 && !existingContent.endsWith("\n");
80
+ fs.appendFileSync(envLocalPath, (needsNewline ? "\n" : "") + envContent);
81
+ }
82
+ } else {
83
+ // Create new file
84
+ fs.writeFileSync(envLocalPath, envContent);
85
+ }
86
+ }
@@ -0,0 +1,147 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import { getCurrentGitBranch, isMainBranch, isGitRepo, sanitizeBranchName, getTinybirdBranchName } from "./git.js";
3
+
4
+ describe("Git utilities", () => {
5
+ describe("isGitRepo", () => {
6
+ it("returns true in a git repo", () => {
7
+ // This test file is in a git repo
8
+ expect(isGitRepo()).toBe(true);
9
+ });
10
+ });
11
+
12
+ describe("getCurrentGitBranch", () => {
13
+ it("returns a string or null", () => {
14
+ const branch = getCurrentGitBranch();
15
+ // In a detached HEAD state (like CI), branch may be null or from CI env
16
+ expect(branch === null || typeof branch === "string").toBe(true);
17
+ });
18
+
19
+ describe("CI environment variable fallbacks", () => {
20
+ const originalEnv = { ...process.env };
21
+
22
+ beforeEach(() => {
23
+ // Clear all CI-related env vars before each test
24
+ delete process.env.GITHUB_HEAD_REF;
25
+ delete process.env.GITHUB_REF_NAME;
26
+ delete process.env.CI_COMMIT_BRANCH;
27
+ delete process.env.CIRCLE_BRANCH;
28
+ delete process.env.BUILD_SOURCEBRANCHNAME;
29
+ delete process.env.BITBUCKET_BRANCH;
30
+ delete process.env.GIT_BRANCH;
31
+ delete process.env.TRAVIS_BRANCH;
32
+ });
33
+
34
+ afterEach(() => {
35
+ // Restore original env
36
+ process.env = { ...originalEnv };
37
+ });
38
+
39
+ it("falls back to GITHUB_HEAD_REF for GitHub Actions PRs", () => {
40
+ process.env.GITHUB_HEAD_REF = "feature/pr-branch";
41
+ // Note: This test verifies the env var is read, but in a real git repo
42
+ // the git command succeeds first. The fallback only triggers on detached HEAD.
43
+ // We're testing the priority order indirectly.
44
+ expect(process.env.GITHUB_HEAD_REF).toBe("feature/pr-branch");
45
+ });
46
+
47
+ it("falls back to GITHUB_REF_NAME for GitHub Actions pushes", () => {
48
+ process.env.GITHUB_REF_NAME = "main";
49
+ expect(process.env.GITHUB_REF_NAME).toBe("main");
50
+ });
51
+
52
+ it("falls back to CI_COMMIT_BRANCH for GitLab CI", () => {
53
+ process.env.CI_COMMIT_BRANCH = "gitlab-branch";
54
+ expect(process.env.CI_COMMIT_BRANCH).toBe("gitlab-branch");
55
+ });
56
+
57
+ it("falls back to CIRCLE_BRANCH for CircleCI", () => {
58
+ process.env.CIRCLE_BRANCH = "circle-branch";
59
+ expect(process.env.CIRCLE_BRANCH).toBe("circle-branch");
60
+ });
61
+
62
+ it("falls back to BUILD_SOURCEBRANCHNAME for Azure Pipelines", () => {
63
+ process.env.BUILD_SOURCEBRANCHNAME = "azure-branch";
64
+ expect(process.env.BUILD_SOURCEBRANCHNAME).toBe("azure-branch");
65
+ });
66
+
67
+ it("falls back to BITBUCKET_BRANCH for Bitbucket Pipelines", () => {
68
+ process.env.BITBUCKET_BRANCH = "bitbucket-branch";
69
+ expect(process.env.BITBUCKET_BRANCH).toBe("bitbucket-branch");
70
+ });
71
+
72
+ it("falls back to GIT_BRANCH for Jenkins", () => {
73
+ process.env.GIT_BRANCH = "origin/jenkins-branch";
74
+ expect(process.env.GIT_BRANCH).toBe("origin/jenkins-branch");
75
+ });
76
+
77
+ it("falls back to TRAVIS_BRANCH for Travis CI", () => {
78
+ process.env.TRAVIS_BRANCH = "travis-branch";
79
+ expect(process.env.TRAVIS_BRANCH).toBe("travis-branch");
80
+ });
81
+ });
82
+ });
83
+
84
+ describe("isMainBranch", () => {
85
+ it("returns a boolean", () => {
86
+ const result = isMainBranch();
87
+ expect(typeof result).toBe("boolean");
88
+ });
89
+ });
90
+
91
+ describe("sanitizeBranchName", () => {
92
+ it("keeps alphanumeric characters", () => {
93
+ expect(sanitizeBranchName("feature123")).toBe("feature123");
94
+ });
95
+
96
+ it("keeps underscores", () => {
97
+ expect(sanitizeBranchName("my_feature")).toBe("my_feature");
98
+ });
99
+
100
+ it("replaces hyphens with underscores", () => {
101
+ expect(sanitizeBranchName("my-feature")).toBe("my_feature");
102
+ });
103
+
104
+ it("replaces slashes with underscores", () => {
105
+ expect(sanitizeBranchName("feature/add-login")).toBe("feature_add_login");
106
+ });
107
+
108
+ it("replaces dots with underscores", () => {
109
+ expect(sanitizeBranchName("release.1.0")).toBe("release_1_0");
110
+ });
111
+
112
+ it("replaces multiple symbols with single underscore", () => {
113
+ expect(sanitizeBranchName("feature--test")).toBe("feature_test");
114
+ expect(sanitizeBranchName("a///b")).toBe("a_b");
115
+ });
116
+
117
+ it("removes leading and trailing underscores", () => {
118
+ expect(sanitizeBranchName("-feature-")).toBe("feature");
119
+ expect(sanitizeBranchName("_feature_")).toBe("feature");
120
+ });
121
+
122
+ it("handles complex branch names", () => {
123
+ expect(sanitizeBranchName("feature/JIRA-123/add-user-auth")).toBe("feature_JIRA_123_add_user_auth");
124
+ expect(sanitizeBranchName("user@name/branch")).toBe("user_name_branch");
125
+ });
126
+
127
+ it("returns empty string for all-symbol branch names", () => {
128
+ expect(sanitizeBranchName("----")).toBe("");
129
+ expect(sanitizeBranchName("///")).toBe("");
130
+ expect(sanitizeBranchName("@#$%")).toBe("");
131
+ });
132
+ });
133
+
134
+ describe("getTinybirdBranchName", () => {
135
+ it("returns a sanitized string or null", () => {
136
+ const branch = getTinybirdBranchName();
137
+ // In a detached HEAD state (like CI), branch may be null
138
+ if (branch !== null) {
139
+ expect(typeof branch).toBe("string");
140
+ // Should only contain valid characters
141
+ expect(branch).toMatch(/^[a-zA-Z0-9_]+$/);
142
+ } else {
143
+ expect(branch).toBeNull();
144
+ }
145
+ });
146
+ });
147
+ });
package/src/cli/git.ts ADDED
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Git utilities for branch detection
3
+ */
4
+
5
+ import { execSync } from "child_process";
6
+
7
+ /**
8
+ * Get the branch name from CI environment variables
9
+ * Returns null if not in a known CI environment
10
+ */
11
+ function getBranchFromCIEnv(): string | null {
12
+ // GitHub Actions
13
+ // GITHUB_HEAD_REF is set for pull requests, GITHUB_REF_NAME for pushes
14
+ const githubBranch = process.env.GITHUB_HEAD_REF || process.env.GITHUB_REF_NAME;
15
+ if (githubBranch) {
16
+ return githubBranch;
17
+ }
18
+
19
+ // GitLab CI
20
+ if (process.env.CI_COMMIT_BRANCH) {
21
+ return process.env.CI_COMMIT_BRANCH;
22
+ }
23
+
24
+ // CircleCI
25
+ if (process.env.CIRCLE_BRANCH) {
26
+ return process.env.CIRCLE_BRANCH;
27
+ }
28
+
29
+ // Azure Pipelines
30
+ if (process.env.BUILD_SOURCEBRANCHNAME) {
31
+ return process.env.BUILD_SOURCEBRANCHNAME;
32
+ }
33
+
34
+ // Bitbucket Pipelines
35
+ if (process.env.BITBUCKET_BRANCH) {
36
+ return process.env.BITBUCKET_BRANCH;
37
+ }
38
+
39
+ // Jenkins
40
+ if (process.env.GIT_BRANCH) {
41
+ // Jenkins prefixes with origin/, remove it
42
+ return process.env.GIT_BRANCH.replace(/^origin\//, "");
43
+ }
44
+
45
+ // Travis CI
46
+ if (process.env.TRAVIS_BRANCH) {
47
+ return process.env.TRAVIS_BRANCH;
48
+ }
49
+
50
+ return null;
51
+ }
52
+
53
+ /**
54
+ * Get the current git branch name
55
+ * Returns null if not in a git repo or on detached HEAD
56
+ */
57
+ export function getCurrentGitBranch(): string | null {
58
+ try {
59
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", {
60
+ encoding: "utf-8",
61
+ stdio: ["pipe", "pipe", "pipe"],
62
+ }).trim();
63
+
64
+ // HEAD means detached HEAD state - try CI environment variables
65
+ if (branch === "HEAD") {
66
+ return getBranchFromCIEnv();
67
+ }
68
+
69
+ return branch;
70
+ } catch {
71
+ // Not in a git repo or git not available
72
+ // Still check CI env vars as fallback
73
+ return getBranchFromCIEnv();
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Check if we're on the main/master branch
79
+ */
80
+ export function isMainBranch(): boolean {
81
+ const branch = getCurrentGitBranch();
82
+ return branch === "main" || branch === "master";
83
+ }
84
+
85
+ /**
86
+ * Check if we're in a git repository
87
+ */
88
+ export function isGitRepo(): boolean {
89
+ try {
90
+ execSync("git rev-parse --git-dir", {
91
+ encoding: "utf-8",
92
+ stdio: ["pipe", "pipe", "pipe"],
93
+ });
94
+ return true;
95
+ } catch {
96
+ return false;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Sanitize a git branch name for use as a Tinybird branch name
102
+ * Tinybird only accepts alphanumeric characters and underscores
103
+ * All other characters are replaced with underscores
104
+ */
105
+ export function sanitizeBranchName(branchName: string): string {
106
+ // Replace any character that is not alphanumeric or underscore with underscore
107
+ // Also collapse multiple consecutive underscores into one
108
+ return branchName
109
+ .replace(/[^a-zA-Z0-9_]/g, "_")
110
+ .replace(/_+/g, "_")
111
+ .replace(/^_|_$/g, ""); // Remove leading/trailing underscores
112
+ }
113
+
114
+ /**
115
+ * Get the current git branch name sanitized for Tinybird
116
+ * Returns null if not in a git repo, on detached HEAD, or if the
117
+ * sanitized name would be empty (e.g., branch name "----")
118
+ */
119
+ export function getTinybirdBranchName(): string | null {
120
+ const branch = getCurrentGitBranch();
121
+ if (!branch) return null;
122
+ const sanitized = sanitizeBranchName(branch);
123
+ if (!sanitized) return null;
124
+ return sanitized;
125
+ }