@tinybirdco/sdk 0.0.4 → 0.0.6
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 +52 -13
- package/dist/api/deploy.d.ts +41 -3
- package/dist/api/deploy.d.ts.map +1 -1
- package/dist/api/deploy.js +141 -19
- package/dist/api/deploy.js.map +1 -1
- package/dist/api/deploy.test.js +77 -29
- package/dist/api/deploy.test.js.map +1 -1
- package/dist/api/resources.d.ts +178 -0
- package/dist/api/resources.d.ts.map +1 -0
- package/dist/api/resources.js +244 -0
- package/dist/api/resources.js.map +1 -0
- package/dist/api/resources.test.d.ts +2 -0
- package/dist/api/resources.test.d.ts.map +1 -0
- package/dist/api/resources.test.js +255 -0
- package/dist/api/resources.test.js.map +1 -0
- package/dist/cli/commands/build.d.ts +3 -4
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +23 -25
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/deploy.d.ts +39 -0
- package/dist/cli/commands/deploy.d.ts.map +1 -0
- package/dist/cli/commands/deploy.js +90 -0
- package/dist/cli/commands/deploy.js.map +1 -0
- package/dist/cli/commands/dev.d.ts.map +1 -1
- package/dist/cli/commands/dev.js +7 -3
- package/dist/cli/commands/dev.js.map +1 -1
- package/dist/cli/commands/init.d.ts +24 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +174 -23
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/init.test.js +190 -30
- package/dist/cli/commands/init.test.js.map +1 -1
- package/dist/cli/index.js +72 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/utils/package-manager.d.ts +8 -0
- package/dist/cli/utils/package-manager.d.ts.map +1 -0
- package/dist/cli/utils/package-manager.js +45 -0
- package/dist/cli/utils/package-manager.js.map +1 -0
- package/dist/cli/utils/package-manager.test.d.ts +2 -0
- package/dist/cli/utils/package-manager.test.d.ts.map +1 -0
- package/dist/cli/utils/package-manager.test.js +85 -0
- package/dist/cli/utils/package-manager.test.js.map +1 -0
- package/dist/codegen/index.d.ts +39 -0
- package/dist/codegen/index.d.ts.map +1 -0
- package/dist/codegen/index.js +300 -0
- package/dist/codegen/index.js.map +1 -0
- package/dist/codegen/index.test.d.ts +2 -0
- package/dist/codegen/index.test.d.ts.map +1 -0
- package/dist/codegen/index.test.js +310 -0
- package/dist/codegen/index.test.js.map +1 -0
- package/dist/codegen/type-mapper.d.ts +20 -0
- package/dist/codegen/type-mapper.d.ts.map +1 -0
- package/dist/codegen/type-mapper.js +238 -0
- package/dist/codegen/type-mapper.js.map +1 -0
- package/dist/codegen/type-mapper.test.d.ts +2 -0
- package/dist/codegen/type-mapper.test.d.ts.map +1 -0
- package/dist/codegen/type-mapper.test.js +167 -0
- package/dist/codegen/type-mapper.test.js.map +1 -0
- package/dist/codegen/utils.d.ts +46 -0
- package/dist/codegen/utils.d.ts.map +1 -0
- package/dist/codegen/utils.js +141 -0
- package/dist/codegen/utils.js.map +1 -0
- package/dist/codegen/utils.test.d.ts +2 -0
- package/dist/codegen/utils.test.d.ts.map +1 -0
- package/dist/codegen/utils.test.js +178 -0
- package/dist/codegen/utils.test.js.map +1 -0
- package/dist/generator/index.d.ts +3 -0
- package/dist/generator/index.d.ts.map +1 -1
- package/dist/generator/index.js +17 -1
- package/dist/generator/index.js.map +1 -1
- package/dist/generator/index.test.js +104 -1
- package/dist/generator/index.test.js.map +1 -1
- package/dist/generator/loader.d.ts +15 -0
- package/dist/generator/loader.d.ts.map +1 -1
- package/dist/generator/loader.js +24 -0
- package/dist/generator/loader.js.map +1 -1
- package/dist/test/handlers.d.ts +49 -0
- package/dist/test/handlers.d.ts.map +1 -1
- package/dist/test/handlers.js +45 -0
- package/dist/test/handlers.js.map +1 -1
- package/package.json +4 -2
- package/src/api/deploy.test.ts +135 -34
- package/src/api/deploy.ts +203 -23
- package/src/api/resources.test.ts +332 -0
- package/src/api/resources.ts +554 -0
- package/src/cli/commands/build.ts +29 -33
- package/src/cli/commands/deploy.ts +126 -0
- package/src/cli/commands/dev.ts +10 -3
- package/src/cli/commands/init.test.ts +239 -30
- package/src/cli/commands/init.ts +243 -26
- package/src/cli/index.ts +84 -11
- package/src/cli/utils/package-manager.test.ts +118 -0
- package/src/cli/utils/package-manager.ts +44 -0
- package/src/codegen/index.test.ts +367 -0
- package/src/codegen/index.ts +379 -0
- package/src/codegen/type-mapper.test.ts +224 -0
- package/src/codegen/type-mapper.ts +265 -0
- package/src/codegen/utils.test.ts +221 -0
- package/src/codegen/utils.ts +174 -0
- package/src/generator/index.test.ts +121 -1
- package/src/generator/index.ts +19 -1
- package/src/generator/loader.ts +43 -0
- package/src/test/handlers.ts +58 -0
package/src/cli/commands/init.ts
CHANGED
|
@@ -4,12 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
import * as fs from "fs";
|
|
6
6
|
import * as path from "path";
|
|
7
|
+
import * as p from "@clack/prompts";
|
|
8
|
+
import pc from "picocolors";
|
|
7
9
|
import {
|
|
8
10
|
hasValidToken,
|
|
9
|
-
getTinybirdDir,
|
|
10
11
|
getRelativeTinybirdDir,
|
|
11
12
|
getConfigPath,
|
|
12
13
|
updateConfig,
|
|
14
|
+
type DevMode,
|
|
13
15
|
} from "../config.js";
|
|
14
16
|
import { browserLogin } from "../auth.js";
|
|
15
17
|
import { saveTinybirdToken } from "../env.js";
|
|
@@ -40,9 +42,9 @@ export type PageViewsRow = InferRow<typeof pageViews>;
|
|
|
40
42
|
`;
|
|
41
43
|
|
|
42
44
|
/**
|
|
43
|
-
* Default starter content for
|
|
45
|
+
* Default starter content for endpoints.ts
|
|
44
46
|
*/
|
|
45
|
-
const
|
|
47
|
+
const ENDPOINTS_CONTENT = `import { defineEndpoint, node, t, p, type InferParams, type InferOutputRow } from "@tinybirdco/sdk";
|
|
46
48
|
|
|
47
49
|
/**
|
|
48
50
|
* Top pages endpoint - get the most visited pages
|
|
@@ -88,7 +90,7 @@ const CLIENT_CONTENT = `/**
|
|
|
88
90
|
* Tinybird Client
|
|
89
91
|
*
|
|
90
92
|
* This file defines the typed Tinybird client for your project.
|
|
91
|
-
* Add your datasources and
|
|
93
|
+
* Add your datasources and endpoints here as you create them.
|
|
92
94
|
*/
|
|
93
95
|
|
|
94
96
|
import { createTinybirdClient } from "@tinybirdco/sdk";
|
|
@@ -97,7 +99,7 @@ import { createTinybirdClient } from "@tinybirdco/sdk";
|
|
|
97
99
|
import { pageViews, type PageViewsRow } from "./datasources";
|
|
98
100
|
|
|
99
101
|
// Import endpoints and their types
|
|
100
|
-
import { topPages, type TopPagesParams, type TopPagesOutput } from "./
|
|
102
|
+
import { topPages, type TopPagesParams, type TopPagesOutput } from "./endpoints";
|
|
101
103
|
|
|
102
104
|
// Create the typed Tinybird client
|
|
103
105
|
export const tinybird = createTinybirdClient({
|
|
@@ -115,14 +117,21 @@ export { pageViews, topPages };
|
|
|
115
117
|
/**
|
|
116
118
|
* Default config content generator
|
|
117
119
|
*/
|
|
118
|
-
function createDefaultConfig(
|
|
120
|
+
function createDefaultConfig(
|
|
121
|
+
tinybirdDir: string,
|
|
122
|
+
devMode: DevMode,
|
|
123
|
+
additionalIncludes: string[] = []
|
|
124
|
+
) {
|
|
125
|
+
const include = [
|
|
126
|
+
`${tinybirdDir}/datasources.ts`,
|
|
127
|
+
`${tinybirdDir}/endpoints.ts`,
|
|
128
|
+
...additionalIncludes,
|
|
129
|
+
];
|
|
119
130
|
return {
|
|
120
|
-
include
|
|
121
|
-
`${tinybirdDir}/datasources.ts`,
|
|
122
|
-
`${tinybirdDir}/pipes.ts`,
|
|
123
|
-
],
|
|
131
|
+
include,
|
|
124
132
|
token: "${TINYBIRD_TOKEN}",
|
|
125
133
|
baseUrl: "https://api.tinybird.co",
|
|
134
|
+
devMode,
|
|
126
135
|
};
|
|
127
136
|
}
|
|
128
137
|
|
|
@@ -136,6 +145,14 @@ export interface InitOptions {
|
|
|
136
145
|
force?: boolean;
|
|
137
146
|
/** Skip the login flow */
|
|
138
147
|
skipLogin?: boolean;
|
|
148
|
+
/** Development mode - if provided, skip interactive prompt */
|
|
149
|
+
devMode?: DevMode;
|
|
150
|
+
/** Client path - if provided, skip interactive prompt */
|
|
151
|
+
clientPath?: string;
|
|
152
|
+
/** Skip prompts for existing datafiles - for testing */
|
|
153
|
+
skipDatafilePrompt?: boolean;
|
|
154
|
+
/** Auto-include existing datafiles without prompting - for testing */
|
|
155
|
+
includeExistingDatafiles?: boolean;
|
|
139
156
|
}
|
|
140
157
|
|
|
141
158
|
/**
|
|
@@ -156,6 +173,66 @@ export interface InitResult {
|
|
|
156
173
|
workspaceName?: string;
|
|
157
174
|
/** User email after login */
|
|
158
175
|
userEmail?: string;
|
|
176
|
+
/** Selected development mode */
|
|
177
|
+
devMode?: DevMode;
|
|
178
|
+
/** Selected client path */
|
|
179
|
+
clientPath?: string;
|
|
180
|
+
/** Existing datafiles that were added to config */
|
|
181
|
+
existingDatafiles?: string[];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Find existing .datasource and .pipe files in the repository
|
|
186
|
+
*
|
|
187
|
+
* @param cwd - Working directory to search from
|
|
188
|
+
* @param maxDepth - Maximum directory depth to search (default: 5)
|
|
189
|
+
* @returns Array of relative file paths
|
|
190
|
+
*/
|
|
191
|
+
export function findExistingDatafiles(
|
|
192
|
+
cwd: string,
|
|
193
|
+
maxDepth: number = 5
|
|
194
|
+
): string[] {
|
|
195
|
+
const files: string[] = [];
|
|
196
|
+
|
|
197
|
+
function searchDir(dir: string, depth: number): void {
|
|
198
|
+
if (depth > maxDepth) return;
|
|
199
|
+
|
|
200
|
+
let entries: fs.Dirent[];
|
|
201
|
+
try {
|
|
202
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
203
|
+
} catch {
|
|
204
|
+
return; // Skip directories we can't read
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
for (const entry of entries) {
|
|
208
|
+
const fullPath = path.join(dir, entry.name);
|
|
209
|
+
|
|
210
|
+
// Skip node_modules and hidden directories
|
|
211
|
+
if (
|
|
212
|
+
entry.isDirectory() &&
|
|
213
|
+
(entry.name === "node_modules" ||
|
|
214
|
+
entry.name.startsWith(".") ||
|
|
215
|
+
entry.name === "dist" ||
|
|
216
|
+
entry.name === "build")
|
|
217
|
+
) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (entry.isDirectory()) {
|
|
222
|
+
searchDir(fullPath, depth + 1);
|
|
223
|
+
} else if (
|
|
224
|
+
entry.isFile() &&
|
|
225
|
+
(entry.name.endsWith(".datasource") || entry.name.endsWith(".pipe"))
|
|
226
|
+
) {
|
|
227
|
+
// Convert to relative path
|
|
228
|
+
const relativePath = path.relative(cwd, fullPath);
|
|
229
|
+
files.push(relativePath);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
searchDir(cwd, 0);
|
|
235
|
+
return files.sort();
|
|
159
236
|
}
|
|
160
237
|
|
|
161
238
|
/**
|
|
@@ -163,7 +240,7 @@ export interface InitResult {
|
|
|
163
240
|
*
|
|
164
241
|
* Creates:
|
|
165
242
|
* - tinybird.json in the project root
|
|
166
|
-
* - src/tinybird/ folder with datasources.ts,
|
|
243
|
+
* - src/tinybird/ folder with datasources.ts, endpoints.ts, and client.ts
|
|
167
244
|
*
|
|
168
245
|
* @param options - Init options
|
|
169
246
|
* @returns Init result
|
|
@@ -175,14 +252,90 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
|
|
|
175
252
|
|
|
176
253
|
const created: string[] = [];
|
|
177
254
|
const skipped: string[] = [];
|
|
255
|
+
let existingDatafiles: string[] = [];
|
|
256
|
+
|
|
257
|
+
// Check for existing .datasource and .pipe files
|
|
258
|
+
const foundDatafiles = findExistingDatafiles(cwd);
|
|
259
|
+
|
|
260
|
+
// Determine devMode - prompt if not provided
|
|
261
|
+
let devMode: DevMode = options.devMode ?? "branch";
|
|
262
|
+
|
|
263
|
+
if (!options.devMode) {
|
|
264
|
+
// Show interactive prompt for workflow selection
|
|
265
|
+
p.intro(pc.cyan("tinybird init"));
|
|
266
|
+
|
|
267
|
+
const workflowChoice = await p.select({
|
|
268
|
+
message: "How do you want to develop with Tinybird?",
|
|
269
|
+
options: [
|
|
270
|
+
{
|
|
271
|
+
value: "branch",
|
|
272
|
+
label: "Branches",
|
|
273
|
+
hint: "Use Tinybird Cloud with git-based branching",
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
value: "local",
|
|
277
|
+
label: "Tinybird Local",
|
|
278
|
+
hint: "Run your own Tinybird instance locally",
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (p.isCancel(workflowChoice)) {
|
|
284
|
+
p.cancel("Init cancelled.");
|
|
285
|
+
return {
|
|
286
|
+
success: false,
|
|
287
|
+
created: [],
|
|
288
|
+
skipped: [],
|
|
289
|
+
error: "Cancelled by user",
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
devMode = workflowChoice as DevMode;
|
|
294
|
+
}
|
|
178
295
|
|
|
179
296
|
// Determine tinybird folder path based on project structure
|
|
180
|
-
const
|
|
181
|
-
|
|
297
|
+
const defaultRelativePath = getRelativeTinybirdDir(cwd);
|
|
298
|
+
let relativeTinybirdDir = options.clientPath ?? defaultRelativePath;
|
|
299
|
+
|
|
300
|
+
if (!options.clientPath && !options.devMode) {
|
|
301
|
+
// Ask user to confirm or change the client path
|
|
302
|
+
const clientPathChoice = await p.text({
|
|
303
|
+
message: "Where should we generate the Tinybird client?",
|
|
304
|
+
placeholder: defaultRelativePath,
|
|
305
|
+
defaultValue: defaultRelativePath,
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
if (p.isCancel(clientPathChoice)) {
|
|
309
|
+
p.cancel("Init cancelled.");
|
|
310
|
+
return {
|
|
311
|
+
success: false,
|
|
312
|
+
created: [],
|
|
313
|
+
skipped: [],
|
|
314
|
+
error: "Cancelled by user",
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
relativeTinybirdDir = clientPathChoice || defaultRelativePath;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Ask about existing datafiles if found
|
|
322
|
+
if (foundDatafiles.length > 0 && !options.skipDatafilePrompt) {
|
|
323
|
+
const includeDatafiles =
|
|
324
|
+
options.includeExistingDatafiles ??
|
|
325
|
+
(await promptForExistingDatafiles(foundDatafiles));
|
|
326
|
+
|
|
327
|
+
if (includeDatafiles) {
|
|
328
|
+
existingDatafiles = foundDatafiles;
|
|
329
|
+
}
|
|
330
|
+
} else if (options.includeExistingDatafiles && foundDatafiles.length > 0) {
|
|
331
|
+
existingDatafiles = foundDatafiles;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const tinybirdDir = path.join(cwd, relativeTinybirdDir);
|
|
182
335
|
|
|
183
336
|
// File paths
|
|
184
337
|
const datasourcesPath = path.join(tinybirdDir, "datasources.ts");
|
|
185
|
-
const
|
|
338
|
+
const endpointsPath = path.join(tinybirdDir, "endpoints.ts");
|
|
186
339
|
const clientPath = path.join(tinybirdDir, "client.ts");
|
|
187
340
|
|
|
188
341
|
// Create config file (tinybird.json)
|
|
@@ -191,7 +344,11 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
|
|
|
191
344
|
skipped.push("tinybird.json");
|
|
192
345
|
} else {
|
|
193
346
|
try {
|
|
194
|
-
const config = createDefaultConfig(
|
|
347
|
+
const config = createDefaultConfig(
|
|
348
|
+
relativeTinybirdDir,
|
|
349
|
+
devMode,
|
|
350
|
+
existingDatafiles
|
|
351
|
+
);
|
|
195
352
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
196
353
|
created.push("tinybird.json");
|
|
197
354
|
} catch (error) {
|
|
@@ -212,7 +369,9 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
|
|
|
212
369
|
success: false,
|
|
213
370
|
created,
|
|
214
371
|
skipped,
|
|
215
|
-
error: `Failed to create ${relativeTinybirdDir} folder: ${
|
|
372
|
+
error: `Failed to create ${relativeTinybirdDir} folder: ${
|
|
373
|
+
(error as Error).message
|
|
374
|
+
}`,
|
|
216
375
|
};
|
|
217
376
|
}
|
|
218
377
|
|
|
@@ -233,19 +392,19 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
|
|
|
233
392
|
}
|
|
234
393
|
}
|
|
235
394
|
|
|
236
|
-
// Create
|
|
237
|
-
if (fs.existsSync(
|
|
238
|
-
skipped.push(`${relativeTinybirdDir}/
|
|
395
|
+
// Create endpoints.ts
|
|
396
|
+
if (fs.existsSync(endpointsPath) && !force) {
|
|
397
|
+
skipped.push(`${relativeTinybirdDir}/endpoints.ts`);
|
|
239
398
|
} else {
|
|
240
399
|
try {
|
|
241
|
-
fs.writeFileSync(
|
|
242
|
-
created.push(`${relativeTinybirdDir}/
|
|
400
|
+
fs.writeFileSync(endpointsPath, ENDPOINTS_CONTENT);
|
|
401
|
+
created.push(`${relativeTinybirdDir}/endpoints.ts`);
|
|
243
402
|
} catch (error) {
|
|
244
403
|
return {
|
|
245
404
|
success: false,
|
|
246
405
|
created,
|
|
247
406
|
skipped,
|
|
248
|
-
error: `Failed to create
|
|
407
|
+
error: `Failed to create endpoints.ts: ${(error as Error).message}`,
|
|
249
408
|
};
|
|
250
409
|
}
|
|
251
410
|
}
|
|
@@ -288,8 +447,16 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
|
|
|
288
447
|
modified = true;
|
|
289
448
|
}
|
|
290
449
|
|
|
450
|
+
if (!packageJson.scripts["tinybird:deploy"]) {
|
|
451
|
+
packageJson.scripts["tinybird:deploy"] = "tinybird deploy";
|
|
452
|
+
modified = true;
|
|
453
|
+
}
|
|
454
|
+
|
|
291
455
|
if (modified) {
|
|
292
|
-
fs.writeFileSync(
|
|
456
|
+
fs.writeFileSync(
|
|
457
|
+
packageJsonPath,
|
|
458
|
+
JSON.stringify(packageJson, null, 2) + "\n"
|
|
459
|
+
);
|
|
293
460
|
created.push("package.json (added tinybird scripts)");
|
|
294
461
|
}
|
|
295
462
|
} catch {
|
|
@@ -312,8 +479,9 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
|
|
|
312
479
|
}
|
|
313
480
|
|
|
314
481
|
// If custom base URL, update tinybird.json
|
|
315
|
-
|
|
316
|
-
|
|
482
|
+
const baseUrl = authResult.baseUrl ?? "https://api.tinybird.co";
|
|
483
|
+
if (baseUrl !== "https://api.tinybird.co") {
|
|
484
|
+
updateConfig(configPath, { baseUrl });
|
|
317
485
|
}
|
|
318
486
|
|
|
319
487
|
return {
|
|
@@ -323,15 +491,25 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
|
|
|
323
491
|
loggedIn: true,
|
|
324
492
|
workspaceName: authResult.workspaceName,
|
|
325
493
|
userEmail: authResult.userEmail,
|
|
494
|
+
devMode,
|
|
495
|
+
clientPath: relativeTinybirdDir,
|
|
496
|
+
existingDatafiles:
|
|
497
|
+
existingDatafiles.length > 0 ? existingDatafiles : undefined,
|
|
326
498
|
};
|
|
327
499
|
} catch (error) {
|
|
328
500
|
// Login succeeded but saving credentials failed
|
|
329
|
-
console.error(
|
|
501
|
+
console.error(
|
|
502
|
+
`Warning: Failed to save credentials: ${(error as Error).message}`
|
|
503
|
+
);
|
|
330
504
|
return {
|
|
331
505
|
success: true,
|
|
332
506
|
created,
|
|
333
507
|
skipped,
|
|
334
508
|
loggedIn: false,
|
|
509
|
+
devMode,
|
|
510
|
+
clientPath: relativeTinybirdDir,
|
|
511
|
+
existingDatafiles:
|
|
512
|
+
existingDatafiles.length > 0 ? existingDatafiles : undefined,
|
|
335
513
|
};
|
|
336
514
|
}
|
|
337
515
|
} else {
|
|
@@ -341,6 +519,10 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
|
|
|
341
519
|
created,
|
|
342
520
|
skipped,
|
|
343
521
|
loggedIn: false,
|
|
522
|
+
devMode,
|
|
523
|
+
clientPath: relativeTinybirdDir,
|
|
524
|
+
existingDatafiles:
|
|
525
|
+
existingDatafiles.length > 0 ? existingDatafiles : undefined,
|
|
344
526
|
};
|
|
345
527
|
}
|
|
346
528
|
}
|
|
@@ -349,5 +531,40 @@ export async function runInit(options: InitOptions = {}): Promise<InitResult> {
|
|
|
349
531
|
success: true,
|
|
350
532
|
created,
|
|
351
533
|
skipped,
|
|
534
|
+
devMode,
|
|
535
|
+
clientPath: relativeTinybirdDir,
|
|
536
|
+
existingDatafiles:
|
|
537
|
+
existingDatafiles.length > 0 ? existingDatafiles : undefined,
|
|
352
538
|
};
|
|
353
539
|
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Prompt user about including existing datafiles
|
|
543
|
+
*/
|
|
544
|
+
async function promptForExistingDatafiles(
|
|
545
|
+
datafiles: string[]
|
|
546
|
+
): Promise<boolean> {
|
|
547
|
+
const datasourceCount = datafiles.filter((f) =>
|
|
548
|
+
f.endsWith(".datasource")
|
|
549
|
+
).length;
|
|
550
|
+
const pipeCount = datafiles.filter((f) => f.endsWith(".pipe")).length;
|
|
551
|
+
|
|
552
|
+
const parts: string[] = [];
|
|
553
|
+
if (datasourceCount > 0) {
|
|
554
|
+
parts.push(`${datasourceCount} .datasource file${datasourceCount > 1 ? "s" : ""}`);
|
|
555
|
+
}
|
|
556
|
+
if (pipeCount > 0) {
|
|
557
|
+
parts.push(`${pipeCount} .pipe file${pipeCount > 1 ? "s" : ""}`);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const confirmInclude = await p.confirm({
|
|
561
|
+
message: `Found ${parts.join(" and ")} in your project. Include them in tinybird.json?`,
|
|
562
|
+
initialValue: true,
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
if (p.isCancel(confirmInclude)) {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return confirmInclude;
|
|
570
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { dirname, resolve } from "node:path";
|
|
|
16
16
|
import { Command } from "commander";
|
|
17
17
|
import { runInit } from "./commands/init.js";
|
|
18
18
|
import { runBuild } from "./commands/build.js";
|
|
19
|
+
import { runDeploy } from "./commands/deploy.js";
|
|
19
20
|
import { runDev } from "./commands/dev.js";
|
|
20
21
|
import { runLogin } from "./commands/login.js";
|
|
21
22
|
import {
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
runBranchStatus,
|
|
24
25
|
runBranchDelete,
|
|
25
26
|
} from "./commands/branch.js";
|
|
27
|
+
import { detectPackageManagerRunCmd } from "./utils/package-manager.js";
|
|
26
28
|
import type { DevMode } from "./config.js";
|
|
27
29
|
|
|
28
30
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
@@ -55,12 +57,20 @@ function createCli(): Command {
|
|
|
55
57
|
.description("Initialize a new Tinybird TypeScript project")
|
|
56
58
|
.option("-f, --force", "Overwrite existing files")
|
|
57
59
|
.option("--skip-login", "Skip browser login flow")
|
|
60
|
+
.option("-m, --mode <mode>", "Development mode: 'branch' or 'local'")
|
|
61
|
+
.option("-p, --path <path>", "Path for Tinybird client files")
|
|
58
62
|
.action(async (options) => {
|
|
59
|
-
|
|
63
|
+
// Validate mode if provided
|
|
64
|
+
if (options.mode && !["branch", "local"].includes(options.mode)) {
|
|
65
|
+
console.error(`Error: Invalid mode '${options.mode}'. Use 'branch' or 'local'.`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
60
68
|
|
|
61
69
|
const result = await runInit({
|
|
62
70
|
force: options.force,
|
|
63
71
|
skipLogin: options.skipLogin,
|
|
72
|
+
devMode: options.mode,
|
|
73
|
+
clientPath: options.path,
|
|
64
74
|
});
|
|
65
75
|
|
|
66
76
|
if (!result.success) {
|
|
@@ -82,6 +92,10 @@ function createCli(): Command {
|
|
|
82
92
|
});
|
|
83
93
|
}
|
|
84
94
|
|
|
95
|
+
// Detect package manager for run command
|
|
96
|
+
const runCmd = detectPackageManagerRunCmd();
|
|
97
|
+
const clientPath = result.clientPath ?? "tinybird";
|
|
98
|
+
|
|
85
99
|
if (result.loggedIn) {
|
|
86
100
|
console.log(`\nLogged in successfully!`);
|
|
87
101
|
if (result.workspaceName) {
|
|
@@ -90,19 +104,25 @@ function createCli(): Command {
|
|
|
90
104
|
if (result.userEmail) {
|
|
91
105
|
console.log(` User: ${result.userEmail}`);
|
|
92
106
|
}
|
|
107
|
+
|
|
108
|
+
if (result.existingDatafiles && result.existingDatafiles.length > 0) {
|
|
109
|
+
console.log(
|
|
110
|
+
`\nAdded ${result.existingDatafiles.length} existing datafile(s) to tinybird.json.`
|
|
111
|
+
);
|
|
112
|
+
}
|
|
93
113
|
console.log("\nDone! Next steps:");
|
|
94
|
-
console.log(
|
|
95
|
-
console.log(
|
|
114
|
+
console.log(` 1. Edit your schema in ${clientPath}/`);
|
|
115
|
+
console.log(` 2. Run '${runCmd} tinybird:dev' to start development`);
|
|
96
116
|
} else if (result.loggedIn === false) {
|
|
97
117
|
console.log("\nLogin was skipped or failed.");
|
|
98
118
|
console.log("\nDone! Next steps:");
|
|
99
119
|
console.log(" 1. Run 'npx tinybird login' to authenticate");
|
|
100
|
-
console.log(
|
|
101
|
-
console.log(
|
|
120
|
+
console.log(` 2. Edit your schema in ${clientPath}/`);
|
|
121
|
+
console.log(` 3. Run '${runCmd} tinybird:dev' to start development`);
|
|
102
122
|
} else {
|
|
103
123
|
console.log("\nDone! Next steps:");
|
|
104
|
-
console.log(
|
|
105
|
-
console.log(
|
|
124
|
+
console.log(` 1. Edit your schema in ${clientPath}/`);
|
|
125
|
+
console.log(` 2. Run '${runCmd} tinybird:dev' to start development`);
|
|
106
126
|
}
|
|
107
127
|
});
|
|
108
128
|
|
|
@@ -135,11 +155,10 @@ function createCli(): Command {
|
|
|
135
155
|
// Build command
|
|
136
156
|
program
|
|
137
157
|
.command("build")
|
|
138
|
-
.description("Build and push resources to Tinybird")
|
|
158
|
+
.description("Build and push resources to a Tinybird branch (not main)")
|
|
139
159
|
.option("--dry-run", "Generate without pushing to API")
|
|
140
160
|
.option("--debug", "Show debug output including API requests/responses")
|
|
141
161
|
.option("--local", "Use local Tinybird container")
|
|
142
|
-
.option("--branch", "Use Tinybird cloud with branches")
|
|
143
162
|
.action(async (options) => {
|
|
144
163
|
if (options.debug) {
|
|
145
164
|
process.env.TINYBIRD_DEBUG = "1";
|
|
@@ -149,8 +168,6 @@ function createCli(): Command {
|
|
|
149
168
|
let devModeOverride: DevMode | undefined;
|
|
150
169
|
if (options.local) {
|
|
151
170
|
devModeOverride = "local";
|
|
152
|
-
} else if (options.branch) {
|
|
153
|
-
devModeOverride = "branch";
|
|
154
171
|
}
|
|
155
172
|
|
|
156
173
|
const modeLabel = devModeOverride === "local" ? " (local)" : "";
|
|
@@ -200,6 +217,62 @@ function createCli(): Command {
|
|
|
200
217
|
console.log(`\n[${formatTime()}] Done in ${result.durationMs}ms`);
|
|
201
218
|
});
|
|
202
219
|
|
|
220
|
+
// Deploy command
|
|
221
|
+
program
|
|
222
|
+
.command("deploy")
|
|
223
|
+
.description("Deploy resources to main Tinybird workspace (production)")
|
|
224
|
+
.option("--dry-run", "Generate without pushing to API")
|
|
225
|
+
.option("--debug", "Show debug output including API requests/responses")
|
|
226
|
+
.action(async (options) => {
|
|
227
|
+
if (options.debug) {
|
|
228
|
+
process.env.TINYBIRD_DEBUG = "1";
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
console.log(`[${formatTime()}] Deploying to main workspace...\n`);
|
|
232
|
+
|
|
233
|
+
const result = await runDeploy({
|
|
234
|
+
dryRun: options.dryRun,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (!result.success) {
|
|
238
|
+
console.error(`Error: ${result.error}`);
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const { build, deploy } = result;
|
|
243
|
+
|
|
244
|
+
if (build) {
|
|
245
|
+
console.log(`Generated ${build.stats.datasourceCount} datasource(s), ${build.stats.pipeCount} pipe(s)`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (options.dryRun) {
|
|
249
|
+
console.log("\n[Dry run] Resources not deployed to API");
|
|
250
|
+
|
|
251
|
+
// Show generated content
|
|
252
|
+
if (build) {
|
|
253
|
+
console.log("\n--- Generated Datasources ---");
|
|
254
|
+
build.resources.datasources.forEach((ds) => {
|
|
255
|
+
console.log(`\n${ds.name}.datasource:`);
|
|
256
|
+
console.log(ds.content);
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
console.log("\n--- Generated Pipes ---");
|
|
260
|
+
build.resources.pipes.forEach((pipe) => {
|
|
261
|
+
console.log(`\n${pipe.name}.pipe:`);
|
|
262
|
+
console.log(pipe.content);
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
} else if (deploy) {
|
|
266
|
+
if (deploy.result === "no_changes") {
|
|
267
|
+
console.log("No changes detected - already up to date");
|
|
268
|
+
} else {
|
|
269
|
+
console.log(`Deployed to main workspace successfully`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
console.log(`\n[${formatTime()}] Done in ${result.durationMs}ms`);
|
|
274
|
+
});
|
|
275
|
+
|
|
203
276
|
// Dev command
|
|
204
277
|
program
|
|
205
278
|
.command("dev")
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
import * as os from "os";
|
|
5
|
+
import { detectPackageManagerRunCmd } from "./package-manager.js";
|
|
6
|
+
|
|
7
|
+
describe("detectPackageManagerRunCmd", () => {
|
|
8
|
+
let tempDir: string;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "pkg-manager-test-"));
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
try {
|
|
16
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
17
|
+
} catch {
|
|
18
|
+
// Ignore cleanup errors
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("lockfile detection", () => {
|
|
23
|
+
it("detects pnpm from pnpm-lock.yaml", () => {
|
|
24
|
+
fs.writeFileSync(path.join(tempDir, "pnpm-lock.yaml"), "");
|
|
25
|
+
expect(detectPackageManagerRunCmd(tempDir)).toBe("pnpm");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("detects yarn from yarn.lock", () => {
|
|
29
|
+
fs.writeFileSync(path.join(tempDir, "yarn.lock"), "");
|
|
30
|
+
expect(detectPackageManagerRunCmd(tempDir)).toBe("yarn");
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("detects bun from bun.lockb", () => {
|
|
34
|
+
fs.writeFileSync(path.join(tempDir, "bun.lockb"), "");
|
|
35
|
+
expect(detectPackageManagerRunCmd(tempDir)).toBe("bun run");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("detects npm from package-lock.json", () => {
|
|
39
|
+
fs.writeFileSync(path.join(tempDir, "package-lock.json"), "{}");
|
|
40
|
+
expect(detectPackageManagerRunCmd(tempDir)).toBe("npm run");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("prioritizes pnpm lockfile over others", () => {
|
|
44
|
+
fs.writeFileSync(path.join(tempDir, "pnpm-lock.yaml"), "");
|
|
45
|
+
fs.writeFileSync(path.join(tempDir, "yarn.lock"), "");
|
|
46
|
+
fs.writeFileSync(path.join(tempDir, "package-lock.json"), "{}");
|
|
47
|
+
expect(detectPackageManagerRunCmd(tempDir)).toBe("pnpm");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("prioritizes yarn lockfile over npm", () => {
|
|
51
|
+
fs.writeFileSync(path.join(tempDir, "yarn.lock"), "");
|
|
52
|
+
fs.writeFileSync(path.join(tempDir, "package-lock.json"), "{}");
|
|
53
|
+
expect(detectPackageManagerRunCmd(tempDir)).toBe("yarn");
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("packageManager field detection", () => {
|
|
58
|
+
it("detects pnpm from packageManager field", () => {
|
|
59
|
+
fs.writeFileSync(
|
|
60
|
+
path.join(tempDir, "package.json"),
|
|
61
|
+
JSON.stringify({ packageManager: "pnpm@9.0.0" })
|
|
62
|
+
);
|
|
63
|
+
expect(detectPackageManagerRunCmd(tempDir)).toBe("pnpm");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("detects yarn from packageManager field", () => {
|
|
67
|
+
fs.writeFileSync(
|
|
68
|
+
path.join(tempDir, "package.json"),
|
|
69
|
+
JSON.stringify({ packageManager: "yarn@4.0.0" })
|
|
70
|
+
);
|
|
71
|
+
expect(detectPackageManagerRunCmd(tempDir)).toBe("yarn");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("detects bun from packageManager field", () => {
|
|
75
|
+
fs.writeFileSync(
|
|
76
|
+
path.join(tempDir, "package.json"),
|
|
77
|
+
JSON.stringify({ packageManager: "bun@1.0.0" })
|
|
78
|
+
);
|
|
79
|
+
expect(detectPackageManagerRunCmd(tempDir)).toBe("bun run");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("prioritizes lockfile over packageManager field", () => {
|
|
83
|
+
fs.writeFileSync(path.join(tempDir, "yarn.lock"), "");
|
|
84
|
+
fs.writeFileSync(
|
|
85
|
+
path.join(tempDir, "package.json"),
|
|
86
|
+
JSON.stringify({ packageManager: "pnpm@9.0.0" })
|
|
87
|
+
);
|
|
88
|
+
expect(detectPackageManagerRunCmd(tempDir)).toBe("yarn");
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe("default behavior", () => {
|
|
93
|
+
it("defaults to npm run when no indicators found", () => {
|
|
94
|
+
expect(detectPackageManagerRunCmd(tempDir)).toBe("npm run");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("defaults to npm run when package.json has no packageManager field", () => {
|
|
98
|
+
fs.writeFileSync(
|
|
99
|
+
path.join(tempDir, "package.json"),
|
|
100
|
+
JSON.stringify({ name: "test-project" })
|
|
101
|
+
);
|
|
102
|
+
expect(detectPackageManagerRunCmd(tempDir)).toBe("npm run");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("defaults to npm run when package.json is invalid JSON", () => {
|
|
106
|
+
fs.writeFileSync(path.join(tempDir, "package.json"), "not json");
|
|
107
|
+
expect(detectPackageManagerRunCmd(tempDir)).toBe("npm run");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("defaults to npm run when packageManager is not a string", () => {
|
|
111
|
+
fs.writeFileSync(
|
|
112
|
+
path.join(tempDir, "package.json"),
|
|
113
|
+
JSON.stringify({ packageManager: 123 })
|
|
114
|
+
);
|
|
115
|
+
expect(detectPackageManagerRunCmd(tempDir)).toBe("npm run");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|