@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.
- package/README.md +518 -0
- package/bin/tinybird.js +7 -0
- package/dist/api/branches.d.ts +98 -0
- package/dist/api/branches.d.ts.map +1 -0
- package/dist/api/branches.js +203 -0
- package/dist/api/branches.js.map +1 -0
- package/dist/api/branches.test.d.ts +2 -0
- package/dist/api/branches.test.d.ts.map +1 -0
- package/dist/api/branches.test.js +286 -0
- package/dist/api/branches.test.js.map +1 -0
- package/dist/api/build.d.ts +130 -0
- package/dist/api/build.d.ts.map +1 -0
- package/dist/api/build.js +143 -0
- package/dist/api/build.js.map +1 -0
- package/dist/api/build.test.d.ts +2 -0
- package/dist/api/build.test.d.ts.map +1 -0
- package/dist/api/build.test.js +138 -0
- package/dist/api/build.test.js.map +1 -0
- package/dist/api/deploy.d.ts +39 -0
- package/dist/api/deploy.d.ts.map +1 -0
- package/dist/api/deploy.js +135 -0
- package/dist/api/deploy.js.map +1 -0
- package/dist/api/deploy.test.d.ts +2 -0
- package/dist/api/deploy.test.d.ts.map +1 -0
- package/dist/api/deploy.test.js +118 -0
- package/dist/api/deploy.test.js.map +1 -0
- package/dist/api/workspaces.d.ts +46 -0
- package/dist/api/workspaces.d.ts.map +1 -0
- package/dist/api/workspaces.js +39 -0
- package/dist/api/workspaces.js.map +1 -0
- package/dist/api/workspaces.test.d.ts +2 -0
- package/dist/api/workspaces.test.d.ts.map +1 -0
- package/dist/api/workspaces.test.js +65 -0
- package/dist/api/workspaces.test.js.map +1 -0
- package/dist/cli/auth.d.ts +86 -0
- package/dist/cli/auth.d.ts.map +1 -0
- package/dist/cli/auth.js +284 -0
- package/dist/cli/auth.js.map +1 -0
- package/dist/cli/branch-store.d.ts +53 -0
- package/dist/cli/branch-store.d.ts.map +1 -0
- package/dist/cli/branch-store.js +91 -0
- package/dist/cli/branch-store.js.map +1 -0
- package/dist/cli/branch-store.test.d.ts +2 -0
- package/dist/cli/branch-store.test.d.ts.map +1 -0
- package/dist/cli/branch-store.test.js +115 -0
- package/dist/cli/branch-store.test.js.map +1 -0
- package/dist/cli/commands/branch.d.ts +82 -0
- package/dist/cli/commands/branch.d.ts.map +1 -0
- package/dist/cli/commands/branch.js +215 -0
- package/dist/cli/commands/branch.js.map +1 -0
- package/dist/cli/commands/build.d.ts +43 -0
- package/dist/cli/commands/build.d.ts.map +1 -0
- package/dist/cli/commands/build.js +138 -0
- package/dist/cli/commands/build.js.map +1 -0
- package/dist/cli/commands/dev.d.ts +78 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/dev.js +226 -0
- package/dist/cli/commands/dev.js.map +1 -0
- package/dist/cli/commands/init.d.ts +45 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +277 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/init.test.d.ts +2 -0
- package/dist/cli/commands/init.test.d.ts.map +1 -0
- package/dist/cli/commands/init.test.js +158 -0
- package/dist/cli/commands/init.test.js.map +1 -0
- package/dist/cli/commands/login.d.ts +37 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +64 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/config.d.ts +114 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +258 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/config.test.d.ts +2 -0
- package/dist/cli/config.test.d.ts.map +1 -0
- package/dist/cli/config.test.js +243 -0
- package/dist/cli/config.test.js.map +1 -0
- package/dist/cli/env.d.ts +29 -0
- package/dist/cli/env.d.ts.map +1 -0
- package/dist/cli/env.js +66 -0
- package/dist/cli/env.js.map +1 -0
- package/dist/cli/git.d.ts +29 -0
- package/dist/cli/git.d.ts.map +1 -0
- package/dist/cli/git.js +114 -0
- package/dist/cli/git.js.map +1 -0
- package/dist/cli/git.test.d.ts +2 -0
- package/dist/cli/git.test.d.ts.map +1 -0
- package/dist/cli/git.test.js +125 -0
- package/dist/cli/git.test.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +337 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/utils/schema-validation.d.ts +95 -0
- package/dist/cli/utils/schema-validation.d.ts.map +1 -0
- package/dist/cli/utils/schema-validation.js +175 -0
- package/dist/cli/utils/schema-validation.js.map +1 -0
- package/dist/cli/utils/schema-validation.test.d.ts +5 -0
- package/dist/cli/utils/schema-validation.test.d.ts.map +1 -0
- package/dist/cli/utils/schema-validation.test.js +173 -0
- package/dist/cli/utils/schema-validation.test.js.map +1 -0
- package/dist/client/base.d.ts +116 -0
- package/dist/client/base.d.ts.map +1 -0
- package/dist/client/base.js +328 -0
- package/dist/client/base.js.map +1 -0
- package/dist/client/types.d.ts +137 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +43 -0
- package/dist/client/types.js.map +1 -0
- package/dist/generator/client.d.ts +44 -0
- package/dist/generator/client.d.ts.map +1 -0
- package/dist/generator/client.js +144 -0
- package/dist/generator/client.js.map +1 -0
- package/dist/generator/datasource.d.ts +57 -0
- package/dist/generator/datasource.d.ts.map +1 -0
- package/dist/generator/datasource.js +169 -0
- package/dist/generator/datasource.js.map +1 -0
- package/dist/generator/datasource.test.d.ts +2 -0
- package/dist/generator/datasource.test.d.ts.map +1 -0
- package/dist/generator/datasource.test.js +254 -0
- package/dist/generator/datasource.test.js.map +1 -0
- package/dist/generator/index.d.ts +131 -0
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +121 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/index.test.d.ts +2 -0
- package/dist/generator/index.test.d.ts.map +1 -0
- package/dist/generator/index.test.js +175 -0
- package/dist/generator/index.test.js.map +1 -0
- package/dist/generator/loader.d.ts +156 -0
- package/dist/generator/loader.d.ts.map +1 -0
- package/dist/generator/loader.js +295 -0
- package/dist/generator/loader.js.map +1 -0
- package/dist/generator/pipe.d.ts +72 -0
- package/dist/generator/pipe.d.ts.map +1 -0
- package/dist/generator/pipe.js +174 -0
- package/dist/generator/pipe.js.map +1 -0
- package/dist/generator/pipe.test.d.ts +2 -0
- package/dist/generator/pipe.test.d.ts.map +1 -0
- package/dist/generator/pipe.test.js +393 -0
- package/dist/generator/pipe.test.js.map +1 -0
- package/dist/index.d.ts +74 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/infer/index.d.ts +202 -0
- package/dist/infer/index.d.ts.map +1 -0
- package/dist/infer/index.js +5 -0
- package/dist/infer/index.js.map +1 -0
- package/dist/schema/datasource.d.ts +135 -0
- package/dist/schema/datasource.d.ts.map +1 -0
- package/dist/schema/datasource.js +105 -0
- package/dist/schema/datasource.js.map +1 -0
- package/dist/schema/datasource.test.d.ts +2 -0
- package/dist/schema/datasource.test.d.ts.map +1 -0
- package/dist/schema/datasource.test.js +142 -0
- package/dist/schema/datasource.test.js.map +1 -0
- package/dist/schema/engines.d.ts +157 -0
- package/dist/schema/engines.d.ts.map +1 -0
- package/dist/schema/engines.js +155 -0
- package/dist/schema/engines.js.map +1 -0
- package/dist/schema/engines.test.d.ts +2 -0
- package/dist/schema/engines.test.d.ts.map +1 -0
- package/dist/schema/engines.test.js +221 -0
- package/dist/schema/engines.test.js.map +1 -0
- package/dist/schema/params.d.ts +106 -0
- package/dist/schema/params.d.ts.map +1 -0
- package/dist/schema/params.js +138 -0
- package/dist/schema/params.js.map +1 -0
- package/dist/schema/params.test.d.ts +2 -0
- package/dist/schema/params.test.d.ts.map +1 -0
- package/dist/schema/params.test.js +175 -0
- package/dist/schema/params.test.js.map +1 -0
- package/dist/schema/pipe.d.ts +436 -0
- package/dist/schema/pipe.d.ts.map +1 -0
- package/dist/schema/pipe.js +484 -0
- package/dist/schema/pipe.js.map +1 -0
- package/dist/schema/pipe.test.d.ts +2 -0
- package/dist/schema/pipe.test.d.ts.map +1 -0
- package/dist/schema/pipe.test.js +488 -0
- package/dist/schema/pipe.test.js.map +1 -0
- package/dist/schema/project.d.ts +202 -0
- package/dist/schema/project.d.ts.map +1 -0
- package/dist/schema/project.js +188 -0
- package/dist/schema/project.js.map +1 -0
- package/dist/schema/project.test.d.ts +2 -0
- package/dist/schema/project.test.d.ts.map +1 -0
- package/dist/schema/project.test.js +180 -0
- package/dist/schema/project.test.js.map +1 -0
- package/dist/schema/types.d.ts +140 -0
- package/dist/schema/types.d.ts.map +1 -0
- package/dist/schema/types.js +174 -0
- package/dist/schema/types.js.map +1 -0
- package/dist/schema/types.test.d.ts +2 -0
- package/dist/schema/types.test.d.ts.map +1 -0
- package/dist/schema/types.test.js +176 -0
- package/dist/schema/types.test.js.map +1 -0
- package/dist/test/handlers.d.ts +58 -0
- package/dist/test/handlers.d.ts.map +1 -0
- package/dist/test/handlers.js +62 -0
- package/dist/test/handlers.js.map +1 -0
- package/dist/test/setup.d.ts +5 -0
- package/dist/test/setup.d.ts.map +1 -0
- package/dist/test/setup.js +11 -0
- package/dist/test/setup.js.map +1 -0
- package/package.json +57 -0
- package/src/api/branches.test.ts +377 -0
- package/src/api/branches.ts +334 -0
- package/src/api/build.test.ts +216 -0
- package/src/api/build.ts +266 -0
- package/src/api/deploy.test.ts +193 -0
- package/src/api/deploy.ts +163 -0
- package/src/api/workspaces.test.ts +81 -0
- package/src/api/workspaces.ts +77 -0
- package/src/cli/auth.ts +358 -0
- package/src/cli/branch-store.test.ts +139 -0
- package/src/cli/branch-store.ts +137 -0
- package/src/cli/commands/branch.ts +306 -0
- package/src/cli/commands/build.ts +183 -0
- package/src/cli/commands/dev.ts +334 -0
- package/src/cli/commands/init.test.ts +249 -0
- package/src/cli/commands/init.ts +323 -0
- package/src/cli/commands/login.ts +98 -0
- package/src/cli/config.test.ts +359 -0
- package/src/cli/config.ts +335 -0
- package/src/cli/env.ts +86 -0
- package/src/cli/git.test.ts +147 -0
- package/src/cli/git.ts +125 -0
- package/src/cli/index.ts +382 -0
- package/src/cli/utils/schema-validation.test.ts +222 -0
- package/src/cli/utils/schema-validation.ts +272 -0
- package/src/client/base.ts +414 -0
- package/src/client/types.ts +165 -0
- package/src/generator/client.ts +194 -0
- package/src/generator/datasource.test.ts +297 -0
- package/src/generator/datasource.ts +217 -0
- package/src/generator/index.test.ts +209 -0
- package/src/generator/index.ts +203 -0
- package/src/generator/loader.ts +406 -0
- package/src/generator/pipe.test.ts +441 -0
- package/src/generator/pipe.ts +220 -0
- package/src/index.ts +191 -0
- package/src/infer/index.ts +247 -0
- package/src/schema/datasource.test.ts +187 -0
- package/src/schema/datasource.ts +195 -0
- package/src/schema/engines.test.ts +247 -0
- package/src/schema/engines.ts +271 -0
- package/src/schema/params.test.ts +208 -0
- package/src/schema/params.ts +249 -0
- package/src/schema/pipe.test.ts +588 -0
- package/src/schema/pipe.ts +832 -0
- package/src/schema/project.test.ts +236 -0
- package/src/schema/project.ts +394 -0
- package/src/schema/types.test.ts +212 -0
- package/src/schema/types.ts +366 -0
- package/src/test/handlers.ts +79 -0
- package/src/test/setup.ts +13 -0
|
@@ -0,0 +1,359 @@
|
|
|
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 {
|
|
6
|
+
hasSrcFolder,
|
|
7
|
+
getLibDir,
|
|
8
|
+
getRelativeLibDir,
|
|
9
|
+
getTinybirdSchemaPath,
|
|
10
|
+
getRelativeSchemaPath,
|
|
11
|
+
findConfigFile,
|
|
12
|
+
loadConfig,
|
|
13
|
+
getConfigPath,
|
|
14
|
+
configExists,
|
|
15
|
+
updateConfig,
|
|
16
|
+
hasValidToken,
|
|
17
|
+
} from "./config.js";
|
|
18
|
+
|
|
19
|
+
describe("Config", () => {
|
|
20
|
+
let tempDir: string;
|
|
21
|
+
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-config-test-"));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
afterEach(() => {
|
|
27
|
+
try {
|
|
28
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
29
|
+
} catch {
|
|
30
|
+
// Ignore cleanup errors
|
|
31
|
+
}
|
|
32
|
+
vi.restoreAllMocks();
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("hasSrcFolder", () => {
|
|
36
|
+
it("returns true when src folder exists", () => {
|
|
37
|
+
fs.mkdirSync(path.join(tempDir, "src"));
|
|
38
|
+
|
|
39
|
+
expect(hasSrcFolder(tempDir)).toBe(true);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("returns false when src folder does not exist", () => {
|
|
43
|
+
expect(hasSrcFolder(tempDir)).toBe(false);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("returns false when src is a file not a folder", () => {
|
|
47
|
+
fs.writeFileSync(path.join(tempDir, "src"), "not a folder");
|
|
48
|
+
|
|
49
|
+
expect(hasSrcFolder(tempDir)).toBe(false);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("getLibDir", () => {
|
|
54
|
+
it("returns src/tinybird when project has src folder", () => {
|
|
55
|
+
fs.mkdirSync(path.join(tempDir, "src"));
|
|
56
|
+
|
|
57
|
+
expect(getLibDir(tempDir)).toBe(path.join(tempDir, "src", "tinybird"));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("returns tinybird when project does not have src folder", () => {
|
|
61
|
+
expect(getLibDir(tempDir)).toBe(path.join(tempDir, "tinybird"));
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe("getRelativeLibDir", () => {
|
|
66
|
+
it("returns src/tinybird when project has src folder", () => {
|
|
67
|
+
fs.mkdirSync(path.join(tempDir, "src"));
|
|
68
|
+
|
|
69
|
+
expect(getRelativeLibDir(tempDir)).toBe("src/tinybird");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("returns tinybird when project does not have src folder", () => {
|
|
73
|
+
expect(getRelativeLibDir(tempDir)).toBe("tinybird");
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("getTinybirdSchemaPath", () => {
|
|
78
|
+
it("returns src/tinybird/datasources.ts when project has src folder", () => {
|
|
79
|
+
fs.mkdirSync(path.join(tempDir, "src"));
|
|
80
|
+
|
|
81
|
+
expect(getTinybirdSchemaPath(tempDir)).toBe(
|
|
82
|
+
path.join(tempDir, "src", "tinybird", "datasources.ts")
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("returns tinybird/datasources.ts when project does not have src folder", () => {
|
|
87
|
+
expect(getTinybirdSchemaPath(tempDir)).toBe(
|
|
88
|
+
path.join(tempDir, "tinybird", "datasources.ts")
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("getRelativeSchemaPath", () => {
|
|
94
|
+
it("returns src/tinybird/datasources.ts when project has src folder", () => {
|
|
95
|
+
fs.mkdirSync(path.join(tempDir, "src"));
|
|
96
|
+
|
|
97
|
+
expect(getRelativeSchemaPath(tempDir)).toBe("src/tinybird/datasources.ts");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("returns tinybird/datasources.ts when project does not have src folder", () => {
|
|
101
|
+
expect(getRelativeSchemaPath(tempDir)).toBe("tinybird/datasources.ts");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("findConfigFile", () => {
|
|
106
|
+
it("finds tinybird.json in current directory", () => {
|
|
107
|
+
const configPath = path.join(tempDir, "tinybird.json");
|
|
108
|
+
fs.writeFileSync(configPath, "{}");
|
|
109
|
+
|
|
110
|
+
expect(findConfigFile(tempDir)).toBe(configPath);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("finds tinybird.json in parent directory", () => {
|
|
114
|
+
const nestedDir = path.join(tempDir, "src", "app");
|
|
115
|
+
fs.mkdirSync(nestedDir, { recursive: true });
|
|
116
|
+
const configPath = path.join(tempDir, "tinybird.json");
|
|
117
|
+
fs.writeFileSync(configPath, "{}");
|
|
118
|
+
|
|
119
|
+
expect(findConfigFile(nestedDir)).toBe(configPath);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("returns null when no config file exists", () => {
|
|
123
|
+
expect(findConfigFile(tempDir)).toBe(null);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("getConfigPath", () => {
|
|
128
|
+
it("returns path to tinybird.json in directory", () => {
|
|
129
|
+
expect(getConfigPath(tempDir)).toBe(path.join(tempDir, "tinybird.json"));
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe("configExists", () => {
|
|
134
|
+
it("returns true when config exists", () => {
|
|
135
|
+
fs.writeFileSync(path.join(tempDir, "tinybird.json"), "{}");
|
|
136
|
+
|
|
137
|
+
expect(configExists(tempDir)).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("returns false when config does not exist", () => {
|
|
141
|
+
expect(configExists(tempDir)).toBe(false);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe("loadConfig", () => {
|
|
146
|
+
beforeEach(() => {
|
|
147
|
+
// Mock git functions to avoid git dependency in tests
|
|
148
|
+
vi.mock("./git.js", () => ({
|
|
149
|
+
getCurrentGitBranch: () => "main",
|
|
150
|
+
isMainBranch: () => true,
|
|
151
|
+
getTinybirdBranchName: () => null,
|
|
152
|
+
}));
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("throws error when no config file exists", () => {
|
|
156
|
+
expect(() => loadConfig(tempDir)).toThrow("Could not find tinybird.json");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("loads config with include array", () => {
|
|
160
|
+
const config = {
|
|
161
|
+
include: ["lib/datasources.ts", "lib/pipes.ts"],
|
|
162
|
+
token: "test-token",
|
|
163
|
+
};
|
|
164
|
+
fs.writeFileSync(
|
|
165
|
+
path.join(tempDir, "tinybird.json"),
|
|
166
|
+
JSON.stringify(config)
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const result = loadConfig(tempDir);
|
|
170
|
+
|
|
171
|
+
expect(result.include).toEqual(["lib/datasources.ts", "lib/pipes.ts"]);
|
|
172
|
+
expect(result.token).toBe("test-token");
|
|
173
|
+
expect(result.baseUrl).toBe("https://api.tinybird.co");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("loads legacy config with schema (backward compat)", () => {
|
|
177
|
+
const config = {
|
|
178
|
+
schema: "src/lib/tinybird.ts",
|
|
179
|
+
token: "test-token",
|
|
180
|
+
};
|
|
181
|
+
fs.writeFileSync(
|
|
182
|
+
path.join(tempDir, "tinybird.json"),
|
|
183
|
+
JSON.stringify(config)
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
const result = loadConfig(tempDir);
|
|
187
|
+
|
|
188
|
+
// Legacy schema is converted to include array
|
|
189
|
+
expect(result.include).toEqual(["src/lib/tinybird.ts"]);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("loads config with custom baseUrl", () => {
|
|
193
|
+
const config = {
|
|
194
|
+
schema: "lib/tinybird.ts",
|
|
195
|
+
token: "test-token",
|
|
196
|
+
baseUrl: "https://api.us-east.tinybird.co",
|
|
197
|
+
};
|
|
198
|
+
fs.writeFileSync(
|
|
199
|
+
path.join(tempDir, "tinybird.json"),
|
|
200
|
+
JSON.stringify(config)
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
const result = loadConfig(tempDir);
|
|
204
|
+
|
|
205
|
+
expect(result.baseUrl).toBe("https://api.us-east.tinybird.co");
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("interpolates environment variables in token", () => {
|
|
209
|
+
process.env.TEST_TINYBIRD_TOKEN = "secret-token-from-env";
|
|
210
|
+
const config = {
|
|
211
|
+
schema: "lib/tinybird.ts",
|
|
212
|
+
token: "${TEST_TINYBIRD_TOKEN}",
|
|
213
|
+
};
|
|
214
|
+
fs.writeFileSync(
|
|
215
|
+
path.join(tempDir, "tinybird.json"),
|
|
216
|
+
JSON.stringify(config)
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
const result = loadConfig(tempDir);
|
|
220
|
+
|
|
221
|
+
expect(result.token).toBe("secret-token-from-env");
|
|
222
|
+
delete process.env.TEST_TINYBIRD_TOKEN;
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("throws error when env var is not set", () => {
|
|
226
|
+
delete process.env.NONEXISTENT_VAR;
|
|
227
|
+
const config = {
|
|
228
|
+
schema: "lib/tinybird.ts",
|
|
229
|
+
token: "${NONEXISTENT_VAR}",
|
|
230
|
+
};
|
|
231
|
+
fs.writeFileSync(
|
|
232
|
+
path.join(tempDir, "tinybird.json"),
|
|
233
|
+
JSON.stringify(config)
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
expect(() => loadConfig(tempDir)).toThrow(
|
|
237
|
+
"Environment variable NONEXISTENT_VAR is not set"
|
|
238
|
+
);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
it("throws error when include field is missing", () => {
|
|
242
|
+
const config = {
|
|
243
|
+
token: "test-token",
|
|
244
|
+
};
|
|
245
|
+
fs.writeFileSync(
|
|
246
|
+
path.join(tempDir, "tinybird.json"),
|
|
247
|
+
JSON.stringify(config)
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
expect(() => loadConfig(tempDir)).toThrow("Missing 'include' field");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it("throws error when token field is missing", () => {
|
|
254
|
+
const config = {
|
|
255
|
+
schema: "lib/tinybird.ts",
|
|
256
|
+
};
|
|
257
|
+
fs.writeFileSync(
|
|
258
|
+
path.join(tempDir, "tinybird.json"),
|
|
259
|
+
JSON.stringify(config)
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
expect(() => loadConfig(tempDir)).toThrow("Missing 'token' field");
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("throws error for invalid JSON", () => {
|
|
266
|
+
fs.writeFileSync(path.join(tempDir, "tinybird.json"), "not valid json");
|
|
267
|
+
|
|
268
|
+
expect(() => loadConfig(tempDir)).toThrow("Failed to parse");
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe("updateConfig", () => {
|
|
273
|
+
it("updates existing config file", () => {
|
|
274
|
+
const configPath = path.join(tempDir, "tinybird.json");
|
|
275
|
+
const initialConfig = {
|
|
276
|
+
schema: "lib/tinybird.ts",
|
|
277
|
+
token: "${TINYBIRD_TOKEN}",
|
|
278
|
+
baseUrl: "https://api.tinybird.co",
|
|
279
|
+
};
|
|
280
|
+
fs.writeFileSync(configPath, JSON.stringify(initialConfig));
|
|
281
|
+
|
|
282
|
+
updateConfig(configPath, { baseUrl: "https://api.us-east.tinybird.co" });
|
|
283
|
+
|
|
284
|
+
const updatedConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
285
|
+
expect(updatedConfig.baseUrl).toBe("https://api.us-east.tinybird.co");
|
|
286
|
+
expect(updatedConfig.schema).toBe("lib/tinybird.ts");
|
|
287
|
+
expect(updatedConfig.token).toBe("${TINYBIRD_TOKEN}");
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("throws error when config file does not exist", () => {
|
|
291
|
+
const configPath = path.join(tempDir, "nonexistent.json");
|
|
292
|
+
|
|
293
|
+
expect(() => updateConfig(configPath, { baseUrl: "test" })).toThrow(
|
|
294
|
+
"Config not found"
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
describe("hasValidToken", () => {
|
|
300
|
+
it("returns false when no config exists", () => {
|
|
301
|
+
expect(hasValidToken(tempDir)).toBe(false);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("returns true when token is literal value", () => {
|
|
305
|
+
const config = {
|
|
306
|
+
schema: "lib/tinybird.ts",
|
|
307
|
+
token: "p.some-token",
|
|
308
|
+
};
|
|
309
|
+
fs.writeFileSync(
|
|
310
|
+
path.join(tempDir, "tinybird.json"),
|
|
311
|
+
JSON.stringify(config)
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
expect(hasValidToken(tempDir)).toBe(true);
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("returns true when token env var is set", () => {
|
|
318
|
+
process.env.HAS_VALID_TOKEN_TEST = "some-value";
|
|
319
|
+
const config = {
|
|
320
|
+
schema: "lib/tinybird.ts",
|
|
321
|
+
token: "${HAS_VALID_TOKEN_TEST}",
|
|
322
|
+
};
|
|
323
|
+
fs.writeFileSync(
|
|
324
|
+
path.join(tempDir, "tinybird.json"),
|
|
325
|
+
JSON.stringify(config)
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
expect(hasValidToken(tempDir)).toBe(true);
|
|
329
|
+
delete process.env.HAS_VALID_TOKEN_TEST;
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("returns false when token env var is not set", () => {
|
|
333
|
+
delete process.env.MISSING_TOKEN_VAR;
|
|
334
|
+
const config = {
|
|
335
|
+
schema: "lib/tinybird.ts",
|
|
336
|
+
token: "${MISSING_TOKEN_VAR}",
|
|
337
|
+
};
|
|
338
|
+
fs.writeFileSync(
|
|
339
|
+
path.join(tempDir, "tinybird.json"),
|
|
340
|
+
JSON.stringify(config)
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
expect(hasValidToken(tempDir)).toBe(false);
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
it("returns false when token field is empty", () => {
|
|
347
|
+
const config = {
|
|
348
|
+
schema: "lib/tinybird.ts",
|
|
349
|
+
token: "",
|
|
350
|
+
};
|
|
351
|
+
fs.writeFileSync(
|
|
352
|
+
path.join(tempDir, "tinybird.json"),
|
|
353
|
+
JSON.stringify(config)
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
expect(hasValidToken(tempDir)).toBe(false);
|
|
357
|
+
});
|
|
358
|
+
});
|
|
359
|
+
});
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration loader for tinybird.json
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from "fs";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import { getCurrentGitBranch, isMainBranch, getTinybirdBranchName } from "./git.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Tinybird configuration file structure
|
|
11
|
+
*/
|
|
12
|
+
export interface TinybirdConfig {
|
|
13
|
+
/** Array of TypeScript files to scan for datasources and pipes */
|
|
14
|
+
include?: string[];
|
|
15
|
+
/** @deprecated Use `include` instead. Path to the TypeScript schema entry point */
|
|
16
|
+
schema?: string;
|
|
17
|
+
/** API token (supports ${ENV_VAR} interpolation) */
|
|
18
|
+
token: string;
|
|
19
|
+
/** Tinybird API base URL (optional, defaults to EU region) */
|
|
20
|
+
baseUrl?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Resolved configuration with all values expanded
|
|
25
|
+
*/
|
|
26
|
+
export interface ResolvedConfig {
|
|
27
|
+
/** Array of TypeScript files to scan for datasources and pipes */
|
|
28
|
+
include: string[];
|
|
29
|
+
/** Resolved API token (workspace main token) */
|
|
30
|
+
token: string;
|
|
31
|
+
/** Tinybird API base URL */
|
|
32
|
+
baseUrl: string;
|
|
33
|
+
/** Path to the config file */
|
|
34
|
+
configPath: string;
|
|
35
|
+
/** Working directory */
|
|
36
|
+
cwd: string;
|
|
37
|
+
/** Current git branch (null if not in git repo or detached HEAD) */
|
|
38
|
+
gitBranch: string | null;
|
|
39
|
+
/** Sanitized branch name for Tinybird (symbols replaced with underscores) */
|
|
40
|
+
tinybirdBranch: string | null;
|
|
41
|
+
/** Whether we're on the main/master branch */
|
|
42
|
+
isMainBranch: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Default base URL (EU region)
|
|
47
|
+
*/
|
|
48
|
+
const DEFAULT_BASE_URL = "https://api.tinybird.co";
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Config file name
|
|
52
|
+
*/
|
|
53
|
+
const CONFIG_FILE = "tinybird.json";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Tinybird folder name
|
|
57
|
+
*/
|
|
58
|
+
const TINYBIRD_FOLDER = "tinybird";
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Detect if project has a src folder
|
|
62
|
+
*/
|
|
63
|
+
export function hasSrcFolder(cwd: string): boolean {
|
|
64
|
+
const srcPath = path.join(cwd, "src");
|
|
65
|
+
return fs.existsSync(srcPath) && fs.statSync(srcPath).isDirectory();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the tinybird directory path based on project structure
|
|
70
|
+
* Returns 'src/tinybird' if project has src folder, otherwise 'tinybird'
|
|
71
|
+
*/
|
|
72
|
+
export function getTinybirdDir(cwd: string): string {
|
|
73
|
+
return hasSrcFolder(cwd) ? path.join(cwd, "src", TINYBIRD_FOLDER) : path.join(cwd, TINYBIRD_FOLDER);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get the relative tinybird directory path based on project structure
|
|
78
|
+
*/
|
|
79
|
+
export function getRelativeTinybirdDir(cwd: string): string {
|
|
80
|
+
return hasSrcFolder(cwd) ? `src/${TINYBIRD_FOLDER}` : TINYBIRD_FOLDER;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get the datasources.ts path based on project structure
|
|
85
|
+
*/
|
|
86
|
+
export function getDatasourcesPath(cwd: string): string {
|
|
87
|
+
return path.join(getTinybirdDir(cwd), "datasources.ts");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get the pipes.ts path based on project structure
|
|
92
|
+
*/
|
|
93
|
+
export function getPipesPath(cwd: string): string {
|
|
94
|
+
return path.join(getTinybirdDir(cwd), "pipes.ts");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get the client.ts path based on project structure
|
|
99
|
+
*/
|
|
100
|
+
export function getClientPath(cwd: string): string {
|
|
101
|
+
return path.join(getTinybirdDir(cwd), "client.ts");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Legacy exports for backwards compatibility
|
|
105
|
+
export function getLibDir(cwd: string): string {
|
|
106
|
+
return getTinybirdDir(cwd);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function getRelativeLibDir(cwd: string): string {
|
|
110
|
+
return getRelativeTinybirdDir(cwd);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function getTinybirdSchemaPath(cwd: string): string {
|
|
114
|
+
return getDatasourcesPath(cwd);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function getRelativeSchemaPath(cwd: string): string {
|
|
118
|
+
return `${getRelativeTinybirdDir(cwd)}/datasources.ts`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Interpolate environment variables in a string
|
|
123
|
+
*
|
|
124
|
+
* Supports ${VAR_NAME} syntax
|
|
125
|
+
*/
|
|
126
|
+
function interpolateEnvVars(value: string): string {
|
|
127
|
+
return value.replace(/\$\{([^}]+)\}/g, (_, envVar) => {
|
|
128
|
+
const envValue = process.env[envVar];
|
|
129
|
+
if (envValue === undefined) {
|
|
130
|
+
throw new Error(`Environment variable ${envVar} is not set`);
|
|
131
|
+
}
|
|
132
|
+
return envValue;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Find the config file by walking up the directory tree
|
|
138
|
+
*
|
|
139
|
+
* @param startDir - Directory to start searching from
|
|
140
|
+
* @returns Path to the config file, or null if not found
|
|
141
|
+
*/
|
|
142
|
+
export function findConfigFile(startDir: string): string | null {
|
|
143
|
+
let currentDir = startDir;
|
|
144
|
+
|
|
145
|
+
while (true) {
|
|
146
|
+
const configPath = path.join(currentDir, CONFIG_FILE);
|
|
147
|
+
if (fs.existsSync(configPath)) {
|
|
148
|
+
return configPath;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const parentDir = path.dirname(currentDir);
|
|
152
|
+
if (parentDir === currentDir) {
|
|
153
|
+
// Reached root
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
currentDir = parentDir;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Load and resolve the tinybird.json configuration
|
|
162
|
+
*
|
|
163
|
+
* @param cwd - Working directory to start searching from (defaults to process.cwd())
|
|
164
|
+
* @returns Resolved configuration
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* const config = loadConfig();
|
|
169
|
+
* console.log(config.schema); // 'lib/tinybird.ts' or 'src/lib/tinybird.ts'
|
|
170
|
+
* console.log(config.token); // 'p.xxx' (resolved from ${TINYBIRD_TOKEN})
|
|
171
|
+
* ```
|
|
172
|
+
*/
|
|
173
|
+
export function loadConfig(cwd: string = process.cwd()): ResolvedConfig {
|
|
174
|
+
const configPath = findConfigFile(cwd);
|
|
175
|
+
|
|
176
|
+
if (!configPath) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
`Could not find ${CONFIG_FILE}. Run 'npx tinybird init' to create one.`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
let rawContent: string;
|
|
183
|
+
try {
|
|
184
|
+
rawContent = fs.readFileSync(configPath, "utf-8");
|
|
185
|
+
} catch (error) {
|
|
186
|
+
throw new Error(`Failed to read ${configPath}: ${(error as Error).message}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let config: TinybirdConfig;
|
|
190
|
+
try {
|
|
191
|
+
config = JSON.parse(rawContent) as TinybirdConfig;
|
|
192
|
+
} catch (error) {
|
|
193
|
+
throw new Error(`Failed to parse ${configPath}: ${(error as Error).message}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Validate required fields - need either include or schema
|
|
197
|
+
if (!config.include && !config.schema) {
|
|
198
|
+
throw new Error(`Missing 'include' field in ${configPath}. Add an array of files to scan for datasources and pipes.`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!config.token) {
|
|
202
|
+
throw new Error(`Missing 'token' field in ${configPath}`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Resolve include paths (support legacy schema field)
|
|
206
|
+
let include: string[];
|
|
207
|
+
if (config.include) {
|
|
208
|
+
include = config.include;
|
|
209
|
+
} else if (config.schema) {
|
|
210
|
+
// Legacy mode: treat schema as a single include path
|
|
211
|
+
include = [config.schema];
|
|
212
|
+
} else {
|
|
213
|
+
// Should never reach here due to validation above
|
|
214
|
+
include = [];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Get the directory containing the config file
|
|
218
|
+
const configDir = path.dirname(configPath);
|
|
219
|
+
|
|
220
|
+
// Resolve token (may contain env vars)
|
|
221
|
+
let resolvedToken: string;
|
|
222
|
+
try {
|
|
223
|
+
resolvedToken = interpolateEnvVars(config.token);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`Failed to resolve token in ${configPath}: ${(error as Error).message}`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Resolve base URL
|
|
231
|
+
let resolvedBaseUrl = DEFAULT_BASE_URL;
|
|
232
|
+
if (config.baseUrl) {
|
|
233
|
+
try {
|
|
234
|
+
resolvedBaseUrl = interpolateEnvVars(config.baseUrl);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`Failed to resolve baseUrl in ${configPath}: ${(error as Error).message}`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Detect git branch
|
|
243
|
+
const gitBranch = getCurrentGitBranch();
|
|
244
|
+
const tinybirdBranch = getTinybirdBranchName();
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
include,
|
|
248
|
+
token: resolvedToken,
|
|
249
|
+
baseUrl: resolvedBaseUrl,
|
|
250
|
+
configPath,
|
|
251
|
+
cwd: configDir,
|
|
252
|
+
gitBranch,
|
|
253
|
+
tinybirdBranch,
|
|
254
|
+
isMainBranch: isMainBranch(),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check if a config file exists in the given directory
|
|
260
|
+
*/
|
|
261
|
+
export function configExists(cwd: string = process.cwd()): boolean {
|
|
262
|
+
return findConfigFile(cwd) !== null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get the expected config file path for a directory
|
|
267
|
+
*/
|
|
268
|
+
export function getConfigPath(cwd: string = process.cwd()): string {
|
|
269
|
+
return path.join(cwd, CONFIG_FILE);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Update specific fields in tinybird.json
|
|
274
|
+
*
|
|
275
|
+
* Throws an error if the config file doesn't exist to prevent creating
|
|
276
|
+
* partial config files that would break loadConfig.
|
|
277
|
+
*
|
|
278
|
+
* @param configPath - Path to the config file
|
|
279
|
+
* @param updates - Fields to update
|
|
280
|
+
* @throws Error if config file doesn't exist
|
|
281
|
+
*/
|
|
282
|
+
export function updateConfig(
|
|
283
|
+
configPath: string,
|
|
284
|
+
updates: Partial<TinybirdConfig>
|
|
285
|
+
): void {
|
|
286
|
+
if (!fs.existsSync(configPath)) {
|
|
287
|
+
throw new Error(`Config not found at ${configPath}`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
291
|
+
const config = JSON.parse(content) as TinybirdConfig;
|
|
292
|
+
|
|
293
|
+
// Merge updates
|
|
294
|
+
const updated = { ...config, ...updates };
|
|
295
|
+
|
|
296
|
+
fs.writeFileSync(configPath, JSON.stringify(updated, null, 2) + "\n");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Check if a valid token is configured (either in file or via env var)
|
|
301
|
+
*
|
|
302
|
+
* @param cwd - Working directory to search from
|
|
303
|
+
* @returns true if a valid token exists
|
|
304
|
+
*/
|
|
305
|
+
export function hasValidToken(cwd: string = process.cwd()): boolean {
|
|
306
|
+
try {
|
|
307
|
+
const configPath = findConfigFile(cwd);
|
|
308
|
+
if (!configPath) {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const content = fs.readFileSync(configPath, "utf-8");
|
|
313
|
+
const config = JSON.parse(content) as TinybirdConfig;
|
|
314
|
+
|
|
315
|
+
if (!config.token) {
|
|
316
|
+
return false;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Check if token is a placeholder or env var reference
|
|
320
|
+
if (config.token.includes("${")) {
|
|
321
|
+
// Try to resolve the env var
|
|
322
|
+
try {
|
|
323
|
+
const resolved = interpolateEnvVars(config.token);
|
|
324
|
+
return Boolean(resolved);
|
|
325
|
+
} catch {
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Token is a literal value
|
|
331
|
+
return Boolean(config.token);
|
|
332
|
+
} catch {
|
|
333
|
+
return false;
|
|
334
|
+
}
|
|
335
|
+
}
|