@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,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dev command - watch mode with automatic sync
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import { watch } from "chokidar";
|
|
7
|
+
import { loadConfig, configExists, findConfigFile, hasValidToken, updateConfig, type ResolvedConfig } from "../config.js";
|
|
8
|
+
import { runBuild, type BuildCommandResult } from "./build.js";
|
|
9
|
+
import { getOrCreateBranch, type TinybirdBranch } from "../../api/branches.js";
|
|
10
|
+
import { browserLogin } from "../auth.js";
|
|
11
|
+
import { saveTinybirdToken } from "../env.js";
|
|
12
|
+
import {
|
|
13
|
+
validatePipeSchemas,
|
|
14
|
+
type SchemaValidationResult,
|
|
15
|
+
} from "../utils/schema-validation.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Login result info
|
|
19
|
+
*/
|
|
20
|
+
export interface LoginInfo {
|
|
21
|
+
/** Workspace name */
|
|
22
|
+
workspaceName?: string;
|
|
23
|
+
/** User email */
|
|
24
|
+
userEmail?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Dev command options
|
|
29
|
+
*/
|
|
30
|
+
export interface DevCommandOptions {
|
|
31
|
+
/** Working directory (defaults to cwd) */
|
|
32
|
+
cwd?: string;
|
|
33
|
+
/** Debounce delay in milliseconds (default: 100) */
|
|
34
|
+
debounce?: number;
|
|
35
|
+
/** Callback when build starts */
|
|
36
|
+
onBuildStart?: () => void;
|
|
37
|
+
/** Callback when build completes */
|
|
38
|
+
onBuildComplete?: (result: BuildCommandResult) => void;
|
|
39
|
+
/** Callback when an error occurs */
|
|
40
|
+
onError?: (error: Error) => void;
|
|
41
|
+
/** Callback when branch is created/detected */
|
|
42
|
+
onBranchReady?: (info: BranchReadyInfo) => void;
|
|
43
|
+
/** Callback when login is needed and completed */
|
|
44
|
+
onLoginComplete?: (info: LoginInfo) => void;
|
|
45
|
+
/** Callback when schema validation completes */
|
|
46
|
+
onSchemaValidation?: (result: SchemaValidationResult) => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Information about the branch being used
|
|
51
|
+
*/
|
|
52
|
+
export interface BranchReadyInfo {
|
|
53
|
+
/** Git branch name */
|
|
54
|
+
gitBranch: string | null;
|
|
55
|
+
/** Whether we're on the main branch */
|
|
56
|
+
isMainBranch: boolean;
|
|
57
|
+
/** Tinybird branch info (null if on main) */
|
|
58
|
+
tinybirdBranch?: TinybirdBranch;
|
|
59
|
+
/** Whether the branch was newly created */
|
|
60
|
+
wasCreated?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Dev command controller
|
|
65
|
+
*/
|
|
66
|
+
export interface DevController {
|
|
67
|
+
/** Stop watching and clean up */
|
|
68
|
+
stop: () => Promise<void>;
|
|
69
|
+
/** Trigger a manual rebuild */
|
|
70
|
+
rebuild: () => Promise<BuildCommandResult>;
|
|
71
|
+
/** The configuration being used */
|
|
72
|
+
config: ResolvedConfig;
|
|
73
|
+
/** The effective token (branch token or main token) */
|
|
74
|
+
effectiveToken: string;
|
|
75
|
+
/** Branch info */
|
|
76
|
+
branchInfo: BranchReadyInfo;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Run the dev command
|
|
81
|
+
*
|
|
82
|
+
* Watches for file changes and automatically rebuilds and pushes to Tinybird.
|
|
83
|
+
* Automatically manages Tinybird branches based on git branch:
|
|
84
|
+
* - Main branch: uses workspace token and /v1/deploy
|
|
85
|
+
* - Feature branches: creates/reuses Tinybird branch and uses /v1/build
|
|
86
|
+
*
|
|
87
|
+
* @param options - Dev options
|
|
88
|
+
* @returns Dev controller
|
|
89
|
+
*/
|
|
90
|
+
export async function runDev(options: DevCommandOptions = {}): Promise<DevController> {
|
|
91
|
+
const cwd = options.cwd ?? process.cwd();
|
|
92
|
+
const debounceMs = options.debounce ?? 100;
|
|
93
|
+
|
|
94
|
+
// Check if project is initialized
|
|
95
|
+
if (!configExists(cwd)) {
|
|
96
|
+
throw new Error(
|
|
97
|
+
"No tinybird.json found. Run 'npx tinybird init' to initialize a project."
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check if authentication is set up, if not trigger login
|
|
102
|
+
if (!hasValidToken(cwd)) {
|
|
103
|
+
console.log("No authentication found. Starting login flow...\n");
|
|
104
|
+
|
|
105
|
+
const authResult = await browserLogin();
|
|
106
|
+
|
|
107
|
+
if (!authResult.success || !authResult.token) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
authResult.error ?? "Login failed. Run 'npx tinybird login' to authenticate."
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Find the config file (may be in parent directory)
|
|
114
|
+
const configPath = findConfigFile(cwd);
|
|
115
|
+
if (!configPath) {
|
|
116
|
+
throw new Error("No tinybird.json found. Run 'npx tinybird init' first.");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Save token to .env.local (in same directory as tinybird.json)
|
|
120
|
+
const configDir = path.dirname(configPath);
|
|
121
|
+
saveTinybirdToken(configDir, authResult.token);
|
|
122
|
+
|
|
123
|
+
// Update baseUrl in tinybird.json if it changed
|
|
124
|
+
if (authResult.baseUrl) {
|
|
125
|
+
updateConfig(configPath, {
|
|
126
|
+
baseUrl: authResult.baseUrl,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Set the token in the environment for this session
|
|
131
|
+
process.env.TINYBIRD_TOKEN = authResult.token;
|
|
132
|
+
|
|
133
|
+
options.onLoginComplete?.({
|
|
134
|
+
workspaceName: authResult.workspaceName,
|
|
135
|
+
userEmail: authResult.userEmail,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Load config (now should have valid token)
|
|
140
|
+
let config: ResolvedConfig;
|
|
141
|
+
try {
|
|
142
|
+
config = loadConfig(cwd);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Determine effective token based on git branch
|
|
148
|
+
let effectiveToken = config.token;
|
|
149
|
+
let branchInfo: BranchReadyInfo = {
|
|
150
|
+
gitBranch: config.gitBranch,
|
|
151
|
+
isMainBranch: config.isMainBranch,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// If we're on a feature branch, get or create the Tinybird branch
|
|
155
|
+
// Use tinybirdBranch (sanitized name) for Tinybird API, gitBranch for display
|
|
156
|
+
if (!config.isMainBranch && config.tinybirdBranch) {
|
|
157
|
+
const branchName = config.tinybirdBranch; // Sanitized name for Tinybird
|
|
158
|
+
|
|
159
|
+
// Always fetch fresh from API to avoid stale cache issues
|
|
160
|
+
const tinybirdBranch = await getOrCreateBranch(
|
|
161
|
+
{
|
|
162
|
+
baseUrl: config.baseUrl,
|
|
163
|
+
token: config.token,
|
|
164
|
+
},
|
|
165
|
+
branchName
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (!tinybirdBranch.token) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Branch '${branchName}' was created but no token was returned. ` +
|
|
171
|
+
`This may be an API issue.`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
effectiveToken = tinybirdBranch.token;
|
|
176
|
+
branchInfo = {
|
|
177
|
+
gitBranch: config.gitBranch, // Original git branch name for display
|
|
178
|
+
isMainBranch: false,
|
|
179
|
+
tinybirdBranch,
|
|
180
|
+
wasCreated: tinybirdBranch.wasCreated ?? false,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Notify about branch readiness
|
|
185
|
+
options.onBranchReady?.(branchInfo);
|
|
186
|
+
|
|
187
|
+
// Get directories to watch from include paths
|
|
188
|
+
const watchDirs = new Set<string>();
|
|
189
|
+
for (const includePath of config.include) {
|
|
190
|
+
const absolutePath = path.isAbsolute(includePath)
|
|
191
|
+
? includePath
|
|
192
|
+
: path.resolve(config.cwd, includePath);
|
|
193
|
+
watchDirs.add(path.dirname(absolutePath));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Debounce state
|
|
197
|
+
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
198
|
+
let isBuilding = false;
|
|
199
|
+
let pendingBuild = false;
|
|
200
|
+
|
|
201
|
+
// Build function
|
|
202
|
+
async function doBuild(): Promise<BuildCommandResult> {
|
|
203
|
+
if (isBuilding) {
|
|
204
|
+
pendingBuild = true;
|
|
205
|
+
return { success: false, error: "Build already in progress", durationMs: 0 };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
isBuilding = true;
|
|
209
|
+
options.onBuildStart?.();
|
|
210
|
+
|
|
211
|
+
try {
|
|
212
|
+
const result = await runBuild({
|
|
213
|
+
cwd: config.cwd,
|
|
214
|
+
tokenOverride: effectiveToken,
|
|
215
|
+
useDeployEndpoint: config.isMainBranch,
|
|
216
|
+
});
|
|
217
|
+
options.onBuildComplete?.(result);
|
|
218
|
+
|
|
219
|
+
// Validate pipe schemas after successful deploy
|
|
220
|
+
if (
|
|
221
|
+
result.success &&
|
|
222
|
+
result.build?.entities &&
|
|
223
|
+
result.deploy?.pipes &&
|
|
224
|
+
options.onSchemaValidation
|
|
225
|
+
) {
|
|
226
|
+
// Get changed pipes from deploy result
|
|
227
|
+
const changedPipes = [
|
|
228
|
+
...result.deploy.pipes.created,
|
|
229
|
+
...result.deploy.pipes.changed,
|
|
230
|
+
];
|
|
231
|
+
|
|
232
|
+
if (changedPipes.length > 0) {
|
|
233
|
+
try {
|
|
234
|
+
const validation = await validatePipeSchemas({
|
|
235
|
+
entities: result.build.entities,
|
|
236
|
+
pipeNames: changedPipes,
|
|
237
|
+
baseUrl: config.baseUrl,
|
|
238
|
+
token: effectiveToken,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
options.onSchemaValidation(validation);
|
|
242
|
+
} catch (validationError) {
|
|
243
|
+
// Don't fail the build due to validation errors
|
|
244
|
+
options.onError?.(validationError as Error);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return result;
|
|
250
|
+
} catch (error) {
|
|
251
|
+
const result: BuildCommandResult = {
|
|
252
|
+
success: false,
|
|
253
|
+
error: (error as Error).message,
|
|
254
|
+
durationMs: 0,
|
|
255
|
+
};
|
|
256
|
+
options.onBuildComplete?.(result);
|
|
257
|
+
return result;
|
|
258
|
+
} finally {
|
|
259
|
+
isBuilding = false;
|
|
260
|
+
|
|
261
|
+
// If there was a pending build, trigger it
|
|
262
|
+
if (pendingBuild) {
|
|
263
|
+
pendingBuild = false;
|
|
264
|
+
scheduleBuild();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Schedule a debounced build
|
|
270
|
+
function scheduleBuild(): void {
|
|
271
|
+
if (debounceTimer) {
|
|
272
|
+
clearTimeout(debounceTimer);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
debounceTimer = setTimeout(() => {
|
|
276
|
+
debounceTimer = null;
|
|
277
|
+
doBuild().catch((error) => {
|
|
278
|
+
options.onError?.(error as Error);
|
|
279
|
+
});
|
|
280
|
+
}, debounceMs);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Set up file watcher for all include directories
|
|
284
|
+
const watcher = watch(Array.from(watchDirs), {
|
|
285
|
+
ignored: [
|
|
286
|
+
/(^|[\/\\])\../, // Ignore dotfiles
|
|
287
|
+
/node_modules/,
|
|
288
|
+
/\.tinybird-schema-.*\.mjs$/, // Ignore temporary bundle files
|
|
289
|
+
/\.tinybird-entities-.*\.mjs$/, // Ignore temporary entity files
|
|
290
|
+
],
|
|
291
|
+
persistent: true,
|
|
292
|
+
ignoreInitial: true,
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// Watch for changes
|
|
296
|
+
watcher.on("change", (filePath) => {
|
|
297
|
+
if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
|
|
298
|
+
scheduleBuild();
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
watcher.on("add", (filePath) => {
|
|
303
|
+
if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
|
|
304
|
+
scheduleBuild();
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
watcher.on("unlink", (filePath) => {
|
|
309
|
+
if (filePath.endsWith(".ts") || filePath.endsWith(".js")) {
|
|
310
|
+
scheduleBuild();
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
watcher.on("error", (error: unknown) => {
|
|
315
|
+
options.onError?.(error instanceof Error ? error : new Error(String(error)));
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Do initial build
|
|
319
|
+
await doBuild();
|
|
320
|
+
|
|
321
|
+
// Return controller
|
|
322
|
+
return {
|
|
323
|
+
stop: async () => {
|
|
324
|
+
if (debounceTimer) {
|
|
325
|
+
clearTimeout(debounceTimer);
|
|
326
|
+
}
|
|
327
|
+
await watcher.close();
|
|
328
|
+
},
|
|
329
|
+
rebuild: doBuild,
|
|
330
|
+
config,
|
|
331
|
+
effectiveToken,
|
|
332
|
+
branchInfo,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
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 { runInit } from "./init.js";
|
|
6
|
+
|
|
7
|
+
// Mock the auth module to avoid browser login
|
|
8
|
+
vi.mock("../auth.js", () => ({
|
|
9
|
+
browserLogin: vi.fn().mockResolvedValue({ success: false }),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe("Init Command", () => {
|
|
13
|
+
let tempDir: string;
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "tinybird-init-test-"));
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
try {
|
|
21
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
22
|
+
} catch {
|
|
23
|
+
// Ignore cleanup errors
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("folder structure creation", () => {
|
|
28
|
+
it("creates tinybird folder with datasources.ts, pipes.ts, client.ts when project has no src folder", async () => {
|
|
29
|
+
const result = await runInit({ cwd: tempDir, skipLogin: true });
|
|
30
|
+
|
|
31
|
+
expect(result.success).toBe(true);
|
|
32
|
+
expect(result.created).toContain("tinybird/datasources.ts");
|
|
33
|
+
expect(result.created).toContain("tinybird/pipes.ts");
|
|
34
|
+
expect(result.created).toContain("tinybird/client.ts");
|
|
35
|
+
expect(fs.existsSync(path.join(tempDir, "tinybird", "datasources.ts"))).toBe(true);
|
|
36
|
+
expect(fs.existsSync(path.join(tempDir, "tinybird", "pipes.ts"))).toBe(true);
|
|
37
|
+
expect(fs.existsSync(path.join(tempDir, "tinybird", "client.ts"))).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("creates src/tinybird folder with files when project has src folder", async () => {
|
|
41
|
+
// Create src folder to simulate existing project
|
|
42
|
+
fs.mkdirSync(path.join(tempDir, "src"));
|
|
43
|
+
|
|
44
|
+
const result = await runInit({ cwd: tempDir, skipLogin: true });
|
|
45
|
+
|
|
46
|
+
expect(result.success).toBe(true);
|
|
47
|
+
expect(result.created).toContain("src/tinybird/datasources.ts");
|
|
48
|
+
expect(result.created).toContain("src/tinybird/pipes.ts");
|
|
49
|
+
expect(result.created).toContain("src/tinybird/client.ts");
|
|
50
|
+
expect(
|
|
51
|
+
fs.existsSync(path.join(tempDir, "src", "tinybird", "datasources.ts"))
|
|
52
|
+
).toBe(true);
|
|
53
|
+
expect(
|
|
54
|
+
fs.existsSync(path.join(tempDir, "src", "tinybird", "pipes.ts"))
|
|
55
|
+
).toBe(true);
|
|
56
|
+
expect(
|
|
57
|
+
fs.existsSync(path.join(tempDir, "src", "tinybird", "client.ts"))
|
|
58
|
+
).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it("creates tinybird.json with correct include paths for tinybird folder", async () => {
|
|
62
|
+
const result = await runInit({ cwd: tempDir, skipLogin: true });
|
|
63
|
+
|
|
64
|
+
expect(result.success).toBe(true);
|
|
65
|
+
expect(result.created).toContain("tinybird.json");
|
|
66
|
+
|
|
67
|
+
const config = JSON.parse(
|
|
68
|
+
fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
|
|
69
|
+
);
|
|
70
|
+
expect(config.include).toEqual([
|
|
71
|
+
"tinybird/datasources.ts",
|
|
72
|
+
"tinybird/pipes.ts",
|
|
73
|
+
]);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("creates tinybird.json with correct include paths for src/tinybird", async () => {
|
|
77
|
+
fs.mkdirSync(path.join(tempDir, "src"));
|
|
78
|
+
|
|
79
|
+
const result = await runInit({ cwd: tempDir, skipLogin: true });
|
|
80
|
+
|
|
81
|
+
expect(result.success).toBe(true);
|
|
82
|
+
|
|
83
|
+
const config = JSON.parse(
|
|
84
|
+
fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
|
|
85
|
+
);
|
|
86
|
+
expect(config.include).toEqual([
|
|
87
|
+
"src/tinybird/datasources.ts",
|
|
88
|
+
"src/tinybird/pipes.ts",
|
|
89
|
+
]);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe("config file creation", () => {
|
|
94
|
+
it("creates tinybird.json with default values", async () => {
|
|
95
|
+
await runInit({ cwd: tempDir, skipLogin: true });
|
|
96
|
+
|
|
97
|
+
const config = JSON.parse(
|
|
98
|
+
fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
expect(config.token).toBe("${TINYBIRD_TOKEN}");
|
|
102
|
+
expect(config.baseUrl).toBe("https://api.tinybird.co");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("skips tinybird.json if it already exists", async () => {
|
|
106
|
+
const existingConfig = { schema: "custom.ts", token: "existing" };
|
|
107
|
+
fs.writeFileSync(
|
|
108
|
+
path.join(tempDir, "tinybird.json"),
|
|
109
|
+
JSON.stringify(existingConfig)
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const result = await runInit({ cwd: tempDir, skipLogin: true });
|
|
113
|
+
|
|
114
|
+
expect(result.success).toBe(true);
|
|
115
|
+
expect(result.skipped).toContain("tinybird.json");
|
|
116
|
+
|
|
117
|
+
// Verify it wasn't overwritten
|
|
118
|
+
const config = JSON.parse(
|
|
119
|
+
fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
|
|
120
|
+
);
|
|
121
|
+
expect(config.schema).toBe("custom.ts");
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it("overwrites tinybird.json with force option", async () => {
|
|
125
|
+
const existingConfig = { include: ["custom.ts"], token: "existing" };
|
|
126
|
+
fs.writeFileSync(
|
|
127
|
+
path.join(tempDir, "tinybird.json"),
|
|
128
|
+
JSON.stringify(existingConfig)
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const result = await runInit({ cwd: tempDir, skipLogin: true, force: true });
|
|
132
|
+
|
|
133
|
+
expect(result.success).toBe(true);
|
|
134
|
+
expect(result.created).toContain("tinybird.json");
|
|
135
|
+
|
|
136
|
+
const config = JSON.parse(
|
|
137
|
+
fs.readFileSync(path.join(tempDir, "tinybird.json"), "utf-8")
|
|
138
|
+
);
|
|
139
|
+
expect(config.include).toEqual([
|
|
140
|
+
"tinybird/datasources.ts",
|
|
141
|
+
"tinybird/pipes.ts",
|
|
142
|
+
]);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("file content creation", () => {
|
|
147
|
+
it("creates datasources.ts with example datasource and InferRow type", async () => {
|
|
148
|
+
await runInit({ cwd: tempDir, skipLogin: true });
|
|
149
|
+
|
|
150
|
+
const content = fs.readFileSync(
|
|
151
|
+
path.join(tempDir, "tinybird", "datasources.ts"),
|
|
152
|
+
"utf-8"
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
expect(content).toContain("defineDatasource");
|
|
156
|
+
expect(content).toContain("export const pageViews");
|
|
157
|
+
expect(content).toContain("InferRow");
|
|
158
|
+
expect(content).toContain("PageViewsRow");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("creates pipes.ts with example endpoint and types", async () => {
|
|
162
|
+
await runInit({ cwd: tempDir, skipLogin: true });
|
|
163
|
+
|
|
164
|
+
const content = fs.readFileSync(
|
|
165
|
+
path.join(tempDir, "tinybird", "pipes.ts"),
|
|
166
|
+
"utf-8"
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
expect(content).toContain("defineEndpoint");
|
|
170
|
+
expect(content).toContain("export const topPages");
|
|
171
|
+
expect(content).toContain("InferParams");
|
|
172
|
+
expect(content).toContain("InferOutputRow");
|
|
173
|
+
expect(content).toContain("TopPagesParams");
|
|
174
|
+
expect(content).toContain("TopPagesOutput");
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("creates client.ts with createTinybirdClient", async () => {
|
|
178
|
+
await runInit({ cwd: tempDir, skipLogin: true });
|
|
179
|
+
|
|
180
|
+
const content = fs.readFileSync(
|
|
181
|
+
path.join(tempDir, "tinybird", "client.ts"),
|
|
182
|
+
"utf-8"
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
expect(content).toContain("createTinybirdClient");
|
|
186
|
+
expect(content).toContain("export const tinybird");
|
|
187
|
+
expect(content).toContain("pageViews");
|
|
188
|
+
expect(content).toContain("topPages");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("skips files that already exist", async () => {
|
|
192
|
+
fs.mkdirSync(path.join(tempDir, "tinybird"), { recursive: true });
|
|
193
|
+
fs.writeFileSync(
|
|
194
|
+
path.join(tempDir, "tinybird", "datasources.ts"),
|
|
195
|
+
"// existing content"
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const result = await runInit({ cwd: tempDir, skipLogin: true });
|
|
199
|
+
|
|
200
|
+
expect(result.success).toBe(true);
|
|
201
|
+
expect(result.skipped).toContain("tinybird/datasources.ts");
|
|
202
|
+
|
|
203
|
+
// Verify it wasn't overwritten
|
|
204
|
+
const content = fs.readFileSync(
|
|
205
|
+
path.join(tempDir, "tinybird", "datasources.ts"),
|
|
206
|
+
"utf-8"
|
|
207
|
+
);
|
|
208
|
+
expect(content).toBe("// existing content");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("overwrites files with force option", async () => {
|
|
212
|
+
fs.mkdirSync(path.join(tempDir, "tinybird"), { recursive: true });
|
|
213
|
+
fs.writeFileSync(
|
|
214
|
+
path.join(tempDir, "tinybird", "datasources.ts"),
|
|
215
|
+
"// existing content"
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const result = await runInit({ cwd: tempDir, skipLogin: true, force: true });
|
|
219
|
+
|
|
220
|
+
expect(result.success).toBe(true);
|
|
221
|
+
expect(result.created).toContain("tinybird/datasources.ts");
|
|
222
|
+
|
|
223
|
+
const content = fs.readFileSync(
|
|
224
|
+
path.join(tempDir, "tinybird", "datasources.ts"),
|
|
225
|
+
"utf-8"
|
|
226
|
+
);
|
|
227
|
+
expect(content).toContain("defineDatasource");
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("directory creation", () => {
|
|
232
|
+
it("creates tinybird directory if it does not exist", async () => {
|
|
233
|
+
expect(fs.existsSync(path.join(tempDir, "tinybird"))).toBe(false);
|
|
234
|
+
|
|
235
|
+
await runInit({ cwd: tempDir, skipLogin: true });
|
|
236
|
+
|
|
237
|
+
expect(fs.existsSync(path.join(tempDir, "tinybird"))).toBe(true);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("creates src/tinybird directory if project has src folder", async () => {
|
|
241
|
+
fs.mkdirSync(path.join(tempDir, "src"));
|
|
242
|
+
expect(fs.existsSync(path.join(tempDir, "src", "tinybird"))).toBe(false);
|
|
243
|
+
|
|
244
|
+
await runInit({ cwd: tempDir, skipLogin: true });
|
|
245
|
+
|
|
246
|
+
expect(fs.existsSync(path.join(tempDir, "src", "tinybird"))).toBe(true);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|