@tinybirdco/sdk 0.0.30 → 0.0.32
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 +29 -4
- package/dist/cli/auth.d.ts.map +1 -1
- package/dist/cli/auth.js +1 -0
- package/dist/cli/auth.js.map +1 -1
- package/dist/cli/commands/branch.js +5 -5
- package/dist/cli/commands/branch.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +2 -2
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/build.test.d.ts +2 -0
- package/dist/cli/commands/build.test.d.ts.map +1 -0
- package/dist/cli/commands/build.test.js +266 -0
- package/dist/cli/commands/build.test.js.map +1 -0
- package/dist/cli/commands/clear.d.ts.map +1 -1
- package/dist/cli/commands/clear.js +2 -2
- package/dist/cli/commands/clear.js.map +1 -1
- package/dist/cli/commands/deploy.js +2 -2
- package/dist/cli/commands/deploy.js.map +1 -1
- package/dist/cli/commands/dev.js +12 -12
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/info.js +2 -2
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/info.test.js +11 -13
- package/dist/cli/commands/info.test.js.map +1 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +44 -26
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/init.test.js +44 -25
- package/dist/cli/commands/init.test.js.map +1 -1
- package/dist/cli/commands/login.d.ts.map +1 -1
- package/dist/cli/commands/login.js +7 -6
- package/dist/cli/commands/login.js.map +1 -1
- package/dist/cli/commands/login.test.js +1 -1
- package/dist/cli/commands/login.test.js.map +1 -1
- package/dist/cli/commands/preview.d.ts.map +1 -1
- package/dist/cli/commands/preview.js +2 -2
- package/dist/cli/commands/preview.js.map +1 -1
- package/dist/cli/config-loader.d.ts +18 -0
- package/dist/cli/config-loader.d.ts.map +1 -0
- package/dist/cli/config-loader.js +57 -0
- package/dist/cli/config-loader.js.map +1 -0
- package/dist/cli/config-types.d.ts +28 -0
- package/dist/cli/config-types.d.ts.map +1 -0
- package/dist/cli/config-types.js +8 -0
- package/dist/cli/config-types.js.map +1 -0
- package/dist/cli/config.d.ts +63 -29
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +139 -43
- package/dist/cli/config.js.map +1 -1
- package/dist/cli/config.test.js +73 -9
- package/dist/cli/config.test.js.map +1 -1
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/client/base.d.ts.map +1 -1
- package/dist/client/base.js +21 -9
- package/dist/client/base.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/cli/auth.ts +1 -0
- package/src/cli/commands/branch.ts +5 -5
- package/src/cli/commands/build.test.ts +310 -0
- package/src/cli/commands/build.ts +2 -2
- package/src/cli/commands/clear.ts +2 -2
- package/src/cli/commands/deploy.ts +2 -2
- package/src/cli/commands/dev.ts +12 -12
- package/src/cli/commands/info.test.ts +11 -13
- package/src/cli/commands/info.ts +2 -2
- package/src/cli/commands/init.test.ts +53 -37
- package/src/cli/commands/init.ts +49 -30
- package/src/cli/commands/login.test.ts +1 -1
- package/src/cli/commands/login.ts +7 -6
- package/src/cli/commands/preview.ts +2 -2
- package/src/cli/config-loader.ts +87 -0
- package/src/cli/config-types.ts +29 -0
- package/src/cli/config.test.ts +95 -8
- package/src/cli/config.ts +179 -70
- package/src/cli/index.ts +3 -0
- package/src/client/base.ts +33 -16
- package/src/index.ts +4 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal config file loader
|
|
3
|
+
* Supports .json, .cjs, and .mjs files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as fs from "node:fs/promises";
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
import { pathToFileURL } from "node:url";
|
|
9
|
+
|
|
10
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
11
|
+
|
|
12
|
+
export type LoadedConfig<T> = {
|
|
13
|
+
config: T;
|
|
14
|
+
filepath: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type LoadConfigOptions = {
|
|
18
|
+
cwd?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
22
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function readJsonFile<T>(filepath: string): Promise<T> {
|
|
26
|
+
const raw = await fs.readFile(filepath, "utf8");
|
|
27
|
+
return JSON.parse(raw) as T;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolve the config export from a module
|
|
32
|
+
* Supports default export, module.exports, and function configs
|
|
33
|
+
*/
|
|
34
|
+
async function resolveConfigExport(mod: unknown): Promise<unknown> {
|
|
35
|
+
const moduleObj = mod as Record<string, unknown>;
|
|
36
|
+
const exported = moduleObj?.default ?? mod;
|
|
37
|
+
|
|
38
|
+
// Allow config as function (sync/async)
|
|
39
|
+
if (typeof exported === "function") {
|
|
40
|
+
return await (exported as () => MaybePromise<unknown>)();
|
|
41
|
+
}
|
|
42
|
+
return exported;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Load a config file from disk
|
|
47
|
+
* Supports .json, .cjs, and .mjs files
|
|
48
|
+
*/
|
|
49
|
+
export async function loadConfigFile<T = unknown>(
|
|
50
|
+
configPath: string,
|
|
51
|
+
opts: LoadConfigOptions = {}
|
|
52
|
+
): Promise<LoadedConfig<T>> {
|
|
53
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
54
|
+
const filepath = path.isAbsolute(configPath)
|
|
55
|
+
? configPath
|
|
56
|
+
: path.resolve(cwd, configPath);
|
|
57
|
+
|
|
58
|
+
const ext = path.extname(filepath).toLowerCase();
|
|
59
|
+
|
|
60
|
+
if (ext === ".json") {
|
|
61
|
+
const config = await readJsonFile<T>(filepath);
|
|
62
|
+
return { config, filepath };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (ext === ".mjs" || ext === ".cjs") {
|
|
66
|
+
// Load JS modules via runtime import for bundler compatibility
|
|
67
|
+
const url = pathToFileURL(filepath).href;
|
|
68
|
+
const mod = await import(
|
|
69
|
+
/* webpackIgnore: true */
|
|
70
|
+
/* @vite-ignore */
|
|
71
|
+
url
|
|
72
|
+
);
|
|
73
|
+
const config = await resolveConfigExport(mod);
|
|
74
|
+
|
|
75
|
+
if (!isObject(config)) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Config in ${filepath} must export an object (or a function returning an object).`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { config: config as T, filepath };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Unsupported config extension "${ext}". Use .json, .mjs, or .cjs`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration types for tinybird.config.{ts,js,json}
|
|
3
|
+
*
|
|
4
|
+
* This file is separate from config.ts to avoid pulling in esbuild
|
|
5
|
+
* when these types are imported by client code.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Development mode options
|
|
10
|
+
* - "branch": Use Tinybird cloud with branches (default)
|
|
11
|
+
* - "local": Use local Tinybird container at localhost:7181
|
|
12
|
+
*/
|
|
13
|
+
export type DevMode = "branch" | "local";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Tinybird configuration file structure
|
|
17
|
+
*/
|
|
18
|
+
export interface TinybirdConfig {
|
|
19
|
+
/** Array of TypeScript files to scan for datasources and pipes */
|
|
20
|
+
include?: string[];
|
|
21
|
+
/** @deprecated Use `include` instead. Path to the TypeScript schema entry point */
|
|
22
|
+
schema?: string;
|
|
23
|
+
/** API token (supports ${ENV_VAR} interpolation) */
|
|
24
|
+
token: string;
|
|
25
|
+
/** Tinybird API base URL (optional, defaults to EU region) */
|
|
26
|
+
baseUrl?: string;
|
|
27
|
+
/** Development mode: "branch" (default) or "local" */
|
|
28
|
+
devMode?: DevMode;
|
|
29
|
+
}
|
package/src/cli/config.test.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getRelativeTinybirdDir,
|
|
9
9
|
findConfigFile,
|
|
10
10
|
loadConfig,
|
|
11
|
+
loadConfigAsync,
|
|
11
12
|
getConfigPath,
|
|
12
13
|
configExists,
|
|
13
14
|
updateConfig,
|
|
@@ -73,20 +74,60 @@ describe("Config", () => {
|
|
|
73
74
|
});
|
|
74
75
|
|
|
75
76
|
describe("findConfigFile", () => {
|
|
76
|
-
it("finds tinybird.json in current directory", () => {
|
|
77
|
+
it("finds tinybird.config.json in current directory", () => {
|
|
78
|
+
const configPath = path.join(tempDir, "tinybird.config.json");
|
|
79
|
+
fs.writeFileSync(configPath, "{}");
|
|
80
|
+
|
|
81
|
+
const result = findConfigFile(tempDir);
|
|
82
|
+
expect(result).not.toBe(null);
|
|
83
|
+
expect(result?.path).toBe(configPath);
|
|
84
|
+
expect(result?.type).toBe("tinybird.config.json");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("finds tinybird.json in current directory (legacy)", () => {
|
|
77
88
|
const configPath = path.join(tempDir, "tinybird.json");
|
|
78
89
|
fs.writeFileSync(configPath, "{}");
|
|
79
90
|
|
|
80
|
-
|
|
91
|
+
const result = findConfigFile(tempDir);
|
|
92
|
+
expect(result).not.toBe(null);
|
|
93
|
+
expect(result?.path).toBe(configPath);
|
|
94
|
+
expect(result?.type).toBe("tinybird.json");
|
|
81
95
|
});
|
|
82
96
|
|
|
83
|
-
it("finds tinybird.json in parent directory", () => {
|
|
97
|
+
it("finds tinybird.config.json in parent directory", () => {
|
|
84
98
|
const nestedDir = path.join(tempDir, "src", "app");
|
|
85
99
|
fs.mkdirSync(nestedDir, { recursive: true });
|
|
86
|
-
const configPath = path.join(tempDir, "tinybird.json");
|
|
100
|
+
const configPath = path.join(tempDir, "tinybird.config.json");
|
|
87
101
|
fs.writeFileSync(configPath, "{}");
|
|
88
102
|
|
|
89
|
-
|
|
103
|
+
const result = findConfigFile(nestedDir);
|
|
104
|
+
expect(result).not.toBe(null);
|
|
105
|
+
expect(result?.path).toBe(configPath);
|
|
106
|
+
expect(result?.type).toBe("tinybird.config.json");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("prioritizes tinybird.config.json over tinybird.json", () => {
|
|
110
|
+
const newConfig = path.join(tempDir, "tinybird.config.json");
|
|
111
|
+
const legacyConfig = path.join(tempDir, "tinybird.json");
|
|
112
|
+
fs.writeFileSync(newConfig, "{}");
|
|
113
|
+
fs.writeFileSync(legacyConfig, "{}");
|
|
114
|
+
|
|
115
|
+
const result = findConfigFile(tempDir);
|
|
116
|
+
expect(result).not.toBe(null);
|
|
117
|
+
expect(result?.path).toBe(newConfig);
|
|
118
|
+
expect(result?.type).toBe("tinybird.config.json");
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("prioritizes tinybird.config.mjs over tinybird.config.cjs", () => {
|
|
122
|
+
const mjsConfig = path.join(tempDir, "tinybird.config.mjs");
|
|
123
|
+
const cjsConfig = path.join(tempDir, "tinybird.config.cjs");
|
|
124
|
+
fs.writeFileSync(mjsConfig, "export default {};");
|
|
125
|
+
fs.writeFileSync(cjsConfig, "module.exports = {};");
|
|
126
|
+
|
|
127
|
+
const result = findConfigFile(tempDir);
|
|
128
|
+
expect(result).not.toBe(null);
|
|
129
|
+
expect(result?.path).toBe(mjsConfig);
|
|
130
|
+
expect(result?.type).toBe("tinybird.config.mjs");
|
|
90
131
|
});
|
|
91
132
|
|
|
92
133
|
it("returns null when no config file exists", () => {
|
|
@@ -95,8 +136,10 @@ describe("Config", () => {
|
|
|
95
136
|
});
|
|
96
137
|
|
|
97
138
|
describe("getConfigPath", () => {
|
|
98
|
-
it("returns path to tinybird.json in directory", () => {
|
|
99
|
-
expect(getConfigPath(tempDir)).toBe(
|
|
139
|
+
it("returns path to tinybird.config.json (new default) in directory", () => {
|
|
140
|
+
expect(getConfigPath(tempDir)).toBe(
|
|
141
|
+
path.join(tempDir, "tinybird.config.json")
|
|
142
|
+
);
|
|
100
143
|
});
|
|
101
144
|
});
|
|
102
145
|
|
|
@@ -123,7 +166,7 @@ describe("Config", () => {
|
|
|
123
166
|
});
|
|
124
167
|
|
|
125
168
|
it("throws error when no config file exists", () => {
|
|
126
|
-
expect(() => loadConfig(tempDir)).toThrow("Could not find
|
|
169
|
+
expect(() => loadConfig(tempDir)).toThrow("Could not find config file");
|
|
127
170
|
});
|
|
128
171
|
|
|
129
172
|
it("loads config with include array", () => {
|
|
@@ -286,6 +329,50 @@ describe("Config", () => {
|
|
|
286
329
|
});
|
|
287
330
|
});
|
|
288
331
|
|
|
332
|
+
describe("loadConfigAsync", () => {
|
|
333
|
+
beforeEach(() => {
|
|
334
|
+
// Mock git functions to avoid git dependency in tests
|
|
335
|
+
vi.mock("./git.js", () => ({
|
|
336
|
+
getCurrentGitBranch: () => "main",
|
|
337
|
+
isMainBranch: () => true,
|
|
338
|
+
getTinybirdBranchName: () => null,
|
|
339
|
+
}));
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("loads tinybird.config.mjs", async () => {
|
|
343
|
+
fs.writeFileSync(
|
|
344
|
+
path.join(tempDir, "tinybird.config.mjs"),
|
|
345
|
+
`export default {
|
|
346
|
+
include: ["lib/datasources.ts"],
|
|
347
|
+
token: "test-token"
|
|
348
|
+
};`
|
|
349
|
+
);
|
|
350
|
+
|
|
351
|
+
const result = await loadConfigAsync(tempDir);
|
|
352
|
+
|
|
353
|
+
expect(result.include).toEqual(["lib/datasources.ts"]);
|
|
354
|
+
expect(result.token).toBe("test-token");
|
|
355
|
+
expect(result.baseUrl).toBe("https://api.tinybird.co");
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("loads tinybird.config.cjs", async () => {
|
|
359
|
+
fs.writeFileSync(
|
|
360
|
+
path.join(tempDir, "tinybird.config.cjs"),
|
|
361
|
+
`module.exports = {
|
|
362
|
+
include: ["lib/datasources.ts"],
|
|
363
|
+
token: "test-token"
|
|
364
|
+
};`
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
const result = await loadConfigAsync(tempDir);
|
|
368
|
+
|
|
369
|
+
expect(result.include).toEqual(["lib/datasources.ts"]);
|
|
370
|
+
expect(result.token).toBe("test-token");
|
|
371
|
+
expect(result.baseUrl).toBe("https://api.tinybird.co");
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
});
|
|
375
|
+
|
|
289
376
|
describe("updateConfig", () => {
|
|
290
377
|
it("updates existing config file", () => {
|
|
291
378
|
const configPath = path.join(tempDir, "tinybird.json");
|
package/src/cli/config.ts
CHANGED
|
@@ -6,28 +6,9 @@ import * as fs from "fs";
|
|
|
6
6
|
import * as path from "path";
|
|
7
7
|
import { getCurrentGitBranch, isMainBranch, getTinybirdBranchName } from "./git.js";
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* - "local": Use local Tinybird container at localhost:7181
|
|
13
|
-
*/
|
|
14
|
-
export type DevMode = "branch" | "local";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Tinybird configuration file structure
|
|
18
|
-
*/
|
|
19
|
-
export interface TinybirdConfig {
|
|
20
|
-
/** Array of TypeScript files to scan for datasources and pipes */
|
|
21
|
-
include?: string[];
|
|
22
|
-
/** @deprecated Use `include` instead. Path to the TypeScript schema entry point */
|
|
23
|
-
schema?: string;
|
|
24
|
-
/** API token (supports ${ENV_VAR} interpolation) */
|
|
25
|
-
token: string;
|
|
26
|
-
/** Tinybird API base URL (optional, defaults to EU region) */
|
|
27
|
-
baseUrl?: string;
|
|
28
|
-
/** Development mode: "branch" (default) or "local" */
|
|
29
|
-
devMode?: DevMode;
|
|
30
|
-
}
|
|
9
|
+
// Re-export types from config-types.ts (separate file to avoid bundling esbuild)
|
|
10
|
+
export type { DevMode, TinybirdConfig } from "./config-types.js";
|
|
11
|
+
import type { DevMode, TinybirdConfig } from "./config-types.js";
|
|
31
12
|
|
|
32
13
|
/**
|
|
33
14
|
* Resolved configuration with all values expanded
|
|
@@ -64,9 +45,25 @@ const DEFAULT_BASE_URL = "https://api.tinybird.co";
|
|
|
64
45
|
export const LOCAL_BASE_URL = "http://localhost:7181";
|
|
65
46
|
|
|
66
47
|
/**
|
|
67
|
-
* Config file
|
|
48
|
+
* Config file names in priority order
|
|
49
|
+
* - tinybird.config.mjs: ESM config with dynamic logic
|
|
50
|
+
* - tinybird.config.cjs: CommonJS config with dynamic logic
|
|
51
|
+
* - tinybird.config.json: Standard JSON config (default for new projects)
|
|
52
|
+
* - tinybird.json: Legacy JSON config (backward compatible)
|
|
68
53
|
*/
|
|
69
|
-
const
|
|
54
|
+
const CONFIG_FILES = [
|
|
55
|
+
"tinybird.config.mjs",
|
|
56
|
+
"tinybird.config.cjs",
|
|
57
|
+
"tinybird.config.json",
|
|
58
|
+
"tinybird.json",
|
|
59
|
+
] as const;
|
|
60
|
+
|
|
61
|
+
type ConfigFileType = (typeof CONFIG_FILES)[number];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Default config file name for new projects
|
|
65
|
+
*/
|
|
66
|
+
const DEFAULT_CONFIG_FILE = "tinybird.config.json";
|
|
70
67
|
|
|
71
68
|
/**
|
|
72
69
|
* Tinybird file path within lib folder
|
|
@@ -135,19 +132,33 @@ function interpolateEnvVars(value: string): string {
|
|
|
135
132
|
});
|
|
136
133
|
}
|
|
137
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Result of finding a config file
|
|
137
|
+
*/
|
|
138
|
+
export interface ConfigFileResult {
|
|
139
|
+
/** Full path to the config file */
|
|
140
|
+
path: string;
|
|
141
|
+
/** Type of config file found */
|
|
142
|
+
type: ConfigFileType;
|
|
143
|
+
}
|
|
144
|
+
|
|
138
145
|
/**
|
|
139
146
|
* Find the config file by walking up the directory tree
|
|
147
|
+
* Checks for all supported config file names in priority order
|
|
140
148
|
*
|
|
141
149
|
* @param startDir - Directory to start searching from
|
|
142
|
-
* @returns Path
|
|
150
|
+
* @returns Path and type of the config file, or null if not found
|
|
143
151
|
*/
|
|
144
|
-
export function findConfigFile(startDir: string):
|
|
152
|
+
export function findConfigFile(startDir: string): ConfigFileResult | null {
|
|
145
153
|
let currentDir = startDir;
|
|
146
154
|
|
|
147
155
|
while (true) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
156
|
+
// Check each config file type in priority order
|
|
157
|
+
for (const configFile of CONFIG_FILES) {
|
|
158
|
+
const configPath = path.join(currentDir, configFile);
|
|
159
|
+
if (fs.existsSync(configPath)) {
|
|
160
|
+
return { path: configPath, type: configFile };
|
|
161
|
+
}
|
|
151
162
|
}
|
|
152
163
|
|
|
153
164
|
const parentDir = path.dirname(currentDir);
|
|
@@ -159,42 +170,13 @@ export function findConfigFile(startDir: string): string | null {
|
|
|
159
170
|
}
|
|
160
171
|
}
|
|
161
172
|
|
|
173
|
+
// Import the universal config loader
|
|
174
|
+
import { loadConfigFile } from "./config-loader.js";
|
|
175
|
+
|
|
162
176
|
/**
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
* @param cwd - Working directory to start searching from (defaults to process.cwd())
|
|
166
|
-
* @returns Resolved configuration
|
|
167
|
-
*
|
|
168
|
-
* @example
|
|
169
|
-
* ```ts
|
|
170
|
-
* const config = loadConfig();
|
|
171
|
-
* console.log(config.schema); // 'lib/tinybird.ts' or 'src/lib/tinybird.ts'
|
|
172
|
-
* console.log(config.token); // 'p.xxx' (resolved from ${TINYBIRD_TOKEN})
|
|
173
|
-
* ```
|
|
177
|
+
* Resolve a TinybirdConfig to a ResolvedConfig
|
|
174
178
|
*/
|
|
175
|
-
|
|
176
|
-
const configPath = findConfigFile(cwd);
|
|
177
|
-
|
|
178
|
-
if (!configPath) {
|
|
179
|
-
throw new Error(
|
|
180
|
-
`Could not find ${CONFIG_FILE}. Run 'npx tinybird init' to create one.`
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
let rawContent: string;
|
|
185
|
-
try {
|
|
186
|
-
rawContent = fs.readFileSync(configPath, "utf-8");
|
|
187
|
-
} catch (error) {
|
|
188
|
-
throw new Error(`Failed to read ${configPath}: ${(error as Error).message}`);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
let config: TinybirdConfig;
|
|
192
|
-
try {
|
|
193
|
-
config = JSON.parse(rawContent) as TinybirdConfig;
|
|
194
|
-
} catch (error) {
|
|
195
|
-
throw new Error(`Failed to parse ${configPath}: ${(error as Error).message}`);
|
|
196
|
-
}
|
|
197
|
-
|
|
179
|
+
function resolveConfig(config: TinybirdConfig, configPath: string): ResolvedConfig {
|
|
198
180
|
// Validate required fields - need either include or schema
|
|
199
181
|
if (!config.include && !config.schema) {
|
|
200
182
|
throw new Error(`Missing 'include' field in ${configPath}. Add an array of files to scan for datasources and pipes.`);
|
|
@@ -262,28 +244,140 @@ export function loadConfig(cwd: string = process.cwd()): ResolvedConfig {
|
|
|
262
244
|
}
|
|
263
245
|
|
|
264
246
|
/**
|
|
265
|
-
*
|
|
247
|
+
* Load and resolve the Tinybird configuration
|
|
248
|
+
*
|
|
249
|
+
* Supports the following config file formats (in priority order):
|
|
250
|
+
* - tinybird.config.mjs: ESM config with dynamic logic
|
|
251
|
+
* - tinybird.config.cjs: CommonJS config with dynamic logic
|
|
252
|
+
* - tinybird.config.json: Standard JSON config
|
|
253
|
+
* - tinybird.json: Legacy JSON config (backward compatible)
|
|
254
|
+
*
|
|
255
|
+
* @param cwd - Working directory to start searching from (defaults to process.cwd())
|
|
256
|
+
* @returns Resolved configuration
|
|
257
|
+
*
|
|
258
|
+
* @example
|
|
259
|
+
* ```ts
|
|
260
|
+
* const config = loadConfig();
|
|
261
|
+
* console.log(config.include); // ['lib/tinybird.ts']
|
|
262
|
+
* console.log(config.token); // 'p.xxx' (resolved from ${TINYBIRD_TOKEN})
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
export function loadConfig(cwd: string = process.cwd()): ResolvedConfig {
|
|
266
|
+
const configResult = findConfigFile(cwd);
|
|
267
|
+
|
|
268
|
+
if (!configResult) {
|
|
269
|
+
throw new Error(
|
|
270
|
+
`Could not find config file. Run 'npx tinybird init' to create one.\n` +
|
|
271
|
+
`Searched for: ${CONFIG_FILES.join(", ")}`
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const { path: configPath, type: configType } = configResult;
|
|
276
|
+
|
|
277
|
+
// JSON files can be loaded synchronously
|
|
278
|
+
if (configType === "tinybird.config.json" || configType === "tinybird.json") {
|
|
279
|
+
let rawContent: string;
|
|
280
|
+
try {
|
|
281
|
+
rawContent = fs.readFileSync(configPath, "utf-8");
|
|
282
|
+
} catch (error) {
|
|
283
|
+
throw new Error(`Failed to read ${configPath}: ${(error as Error).message}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
let config: TinybirdConfig;
|
|
287
|
+
try {
|
|
288
|
+
config = JSON.parse(rawContent) as TinybirdConfig;
|
|
289
|
+
} catch (error) {
|
|
290
|
+
throw new Error(`Failed to parse ${configPath}: ${(error as Error).message}`);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return resolveConfig(config, configPath);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// For JS files, we need to throw an error asking to use the async version
|
|
297
|
+
throw new Error(
|
|
298
|
+
`Config file ${configPath} is a JavaScript file. ` +
|
|
299
|
+
`Use loadConfigAsync() instead of loadConfig() to load .mjs/.cjs config files.`
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Load and resolve the Tinybird configuration (async version)
|
|
305
|
+
*
|
|
306
|
+
* This async version supports all config file formats including JS files
|
|
307
|
+
* that may contain dynamic logic.
|
|
308
|
+
*
|
|
309
|
+
* @param cwd - Working directory to start searching from (defaults to process.cwd())
|
|
310
|
+
* @returns Promise resolving to the configuration
|
|
311
|
+
*/
|
|
312
|
+
export async function loadConfigAsync(cwd: string = process.cwd()): Promise<ResolvedConfig> {
|
|
313
|
+
const configResult = findConfigFile(cwd);
|
|
314
|
+
|
|
315
|
+
if (!configResult) {
|
|
316
|
+
throw new Error(
|
|
317
|
+
`Could not find config file. Run 'npx tinybird init' to create one.\n` +
|
|
318
|
+
`Searched for: ${CONFIG_FILES.join(", ")}`
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const { path: configPath } = configResult;
|
|
323
|
+
|
|
324
|
+
// Use the universal config loader for all file types
|
|
325
|
+
const { config } = await loadConfigFile<TinybirdConfig>(configPath);
|
|
326
|
+
|
|
327
|
+
return resolveConfig(config, configPath);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Check if a config file exists in the given directory or its parents
|
|
266
332
|
*/
|
|
267
333
|
export function configExists(cwd: string = process.cwd()): boolean {
|
|
268
334
|
return findConfigFile(cwd) !== null;
|
|
269
335
|
}
|
|
270
336
|
|
|
337
|
+
/**
|
|
338
|
+
* Get the path to an existing config file, or the default path for a new config
|
|
339
|
+
* This is useful for the init command which needs to either update an existing config
|
|
340
|
+
* or create a new one with the new default name
|
|
341
|
+
*/
|
|
342
|
+
export function getExistingOrNewConfigPath(cwd: string = process.cwd()): string {
|
|
343
|
+
const existing = findExistingConfigPath(cwd);
|
|
344
|
+
return existing ?? path.join(cwd, DEFAULT_CONFIG_FILE);
|
|
345
|
+
}
|
|
346
|
+
|
|
271
347
|
/**
|
|
272
348
|
* Get the expected config file path for a directory
|
|
349
|
+
* Returns the path for the default config file name (tinybird.config.json)
|
|
273
350
|
*/
|
|
274
351
|
export function getConfigPath(cwd: string = process.cwd()): string {
|
|
275
|
-
return path.join(cwd,
|
|
352
|
+
return path.join(cwd, DEFAULT_CONFIG_FILE);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Find an existing config file in a directory
|
|
357
|
+
* Returns the path to the first matching config file, or null if none found
|
|
358
|
+
*/
|
|
359
|
+
export function findExistingConfigPath(cwd: string = process.cwd()): string | null {
|
|
360
|
+
for (const configFile of CONFIG_FILES) {
|
|
361
|
+
const configPath = path.join(cwd, configFile);
|
|
362
|
+
if (fs.existsSync(configPath)) {
|
|
363
|
+
return configPath;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return null;
|
|
276
367
|
}
|
|
277
368
|
|
|
278
369
|
/**
|
|
279
|
-
* Update specific fields in
|
|
370
|
+
* Update specific fields in a JSON config file
|
|
371
|
+
*
|
|
372
|
+
* Note: Only works with JSON config files (.json). For JS config files,
|
|
373
|
+
* the user needs to update them manually.
|
|
280
374
|
*
|
|
281
375
|
* Throws an error if the config file doesn't exist to prevent creating
|
|
282
376
|
* partial config files that would break loadConfig.
|
|
283
377
|
*
|
|
284
378
|
* @param configPath - Path to the config file
|
|
285
379
|
* @param updates - Fields to update
|
|
286
|
-
* @throws Error if config file doesn't exist
|
|
380
|
+
* @throws Error if config file doesn't exist or is not a JSON file
|
|
287
381
|
*/
|
|
288
382
|
export function updateConfig(
|
|
289
383
|
configPath: string,
|
|
@@ -293,6 +387,12 @@ export function updateConfig(
|
|
|
293
387
|
throw new Error(`Config not found at ${configPath}`);
|
|
294
388
|
}
|
|
295
389
|
|
|
390
|
+
if (!configPath.endsWith(".json")) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
`Cannot update ${configPath}. Only JSON config files can be updated programmatically.`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
|
|
296
396
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
297
397
|
const config = JSON.parse(content) as TinybirdConfig;
|
|
298
398
|
|
|
@@ -305,17 +405,26 @@ export function updateConfig(
|
|
|
305
405
|
/**
|
|
306
406
|
* Check if a valid token is configured (either in file or via env var)
|
|
307
407
|
*
|
|
408
|
+
* Note: For JS config files, this only works if the token is a static value
|
|
409
|
+
* or environment variable reference in the file.
|
|
410
|
+
*
|
|
308
411
|
* @param cwd - Working directory to search from
|
|
309
412
|
* @returns true if a valid token exists
|
|
310
413
|
*/
|
|
311
414
|
export function hasValidToken(cwd: string = process.cwd()): boolean {
|
|
312
415
|
try {
|
|
313
|
-
const
|
|
314
|
-
if (!
|
|
416
|
+
const configResult = findConfigFile(cwd);
|
|
417
|
+
if (!configResult) {
|
|
315
418
|
return false;
|
|
316
419
|
}
|
|
317
420
|
|
|
318
|
-
|
|
421
|
+
// For JS files, we can't easily check without loading them
|
|
422
|
+
// Return true and let loadConfig handle validation
|
|
423
|
+
if (!configResult.path.endsWith(".json")) {
|
|
424
|
+
return true;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const content = fs.readFileSync(configResult.path, "utf-8");
|
|
319
428
|
const config = JSON.parse(content) as TinybirdConfig;
|
|
320
429
|
|
|
321
430
|
if (!config.token) {
|
package/src/cli/index.ts
CHANGED
|
@@ -247,6 +247,7 @@ function createCli(): Command {
|
|
|
247
247
|
.option("--dry-run", "Generate without pushing to API")
|
|
248
248
|
.option("--debug", "Show debug output including API requests/responses")
|
|
249
249
|
.option("--local", "Use local Tinybird container")
|
|
250
|
+
.option("--branch", "Use Tinybird cloud with branches")
|
|
250
251
|
.action(async (options) => {
|
|
251
252
|
if (options.debug) {
|
|
252
253
|
process.env.TINYBIRD_DEBUG = "1";
|
|
@@ -256,6 +257,8 @@ function createCli(): Command {
|
|
|
256
257
|
let devModeOverride: DevMode | undefined;
|
|
257
258
|
if (options.local) {
|
|
258
259
|
devModeOverride = "local";
|
|
260
|
+
} else if (options.branch) {
|
|
261
|
+
devModeOverride = "branch";
|
|
259
262
|
}
|
|
260
263
|
|
|
261
264
|
const result = await runBuild({
|