tthr 0.0.33 → 0.0.34
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/dist/chunk-ZWLVHKNL.js +414 -0
- package/dist/generate-26HERX3T.js +8 -0
- package/dist/index.js +434 -554
- package/package.json +6 -4
package/dist/index.js
CHANGED
|
@@ -1,145 +1,57 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
clearCredentials,
|
|
4
|
+
detectFramework,
|
|
5
|
+
generateCommand,
|
|
6
|
+
getCredentials,
|
|
7
|
+
getFrameworkDevCommand,
|
|
8
|
+
loadConfig,
|
|
9
|
+
requireAuth,
|
|
10
|
+
resolvePath,
|
|
11
|
+
saveCredentials
|
|
12
|
+
} from "./chunk-ZWLVHKNL.js";
|
|
2
13
|
|
|
3
14
|
// src/index.ts
|
|
4
15
|
import { Command } from "commander";
|
|
5
16
|
|
|
6
17
|
// src/commands/init.ts
|
|
7
|
-
import
|
|
18
|
+
import chalk2 from "chalk";
|
|
8
19
|
import ora2 from "ora";
|
|
9
20
|
import prompts from "prompts";
|
|
10
|
-
import
|
|
11
|
-
import
|
|
21
|
+
import fs2 from "fs-extra";
|
|
22
|
+
import path2 from "path";
|
|
12
23
|
import { execSync } from "child_process";
|
|
13
24
|
|
|
14
|
-
// src/
|
|
25
|
+
// src/commands/deploy.ts
|
|
15
26
|
import chalk from "chalk";
|
|
27
|
+
import ora from "ora";
|
|
16
28
|
import fs from "fs-extra";
|
|
17
29
|
import path from "path";
|
|
18
|
-
var CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || "", ".tether");
|
|
19
|
-
var CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
|
|
20
|
-
async function getCredentials() {
|
|
21
|
-
try {
|
|
22
|
-
if (await fs.pathExists(CREDENTIALS_FILE)) {
|
|
23
|
-
return await fs.readJSON(CREDENTIALS_FILE);
|
|
24
|
-
}
|
|
25
|
-
} catch {
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
async function requireAuth() {
|
|
30
|
-
const credentials = await getCredentials();
|
|
31
|
-
if (!credentials) {
|
|
32
|
-
console.error(chalk.red("\n\u2717 Not logged in\n"));
|
|
33
|
-
console.log(chalk.dim("Run `tthr login` to authenticate\n"));
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
if (credentials.expiresAt) {
|
|
37
|
-
const expiresAt = new Date(credentials.expiresAt);
|
|
38
|
-
if (/* @__PURE__ */ new Date() > expiresAt) {
|
|
39
|
-
console.error(chalk.red("\n\u2717 Session expired\n"));
|
|
40
|
-
console.log(chalk.dim("Run `tthr login` to authenticate\n"));
|
|
41
|
-
process.exit(1);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return credentials;
|
|
45
|
-
}
|
|
46
|
-
async function saveCredentials(credentials) {
|
|
47
|
-
await fs.ensureDir(CONFIG_DIR);
|
|
48
|
-
await fs.writeJSON(CREDENTIALS_FILE, credentials, { spaces: 2, mode: 384 });
|
|
49
|
-
}
|
|
50
|
-
async function clearCredentials() {
|
|
51
|
-
try {
|
|
52
|
-
await fs.remove(CREDENTIALS_FILE);
|
|
53
|
-
} catch {
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// src/commands/deploy.ts
|
|
58
|
-
import chalk2 from "chalk";
|
|
59
|
-
import ora from "ora";
|
|
60
|
-
import fs3 from "fs-extra";
|
|
61
|
-
import path3 from "path";
|
|
62
|
-
|
|
63
|
-
// src/utils/config.ts
|
|
64
|
-
import fs2 from "fs-extra";
|
|
65
|
-
import path2 from "path";
|
|
66
|
-
var DEFAULT_CONFIG = {
|
|
67
|
-
schema: "./tether/schema.ts",
|
|
68
|
-
functions: "./tether/functions",
|
|
69
|
-
output: "./tether/_generated",
|
|
70
|
-
dev: {
|
|
71
|
-
port: 3001,
|
|
72
|
-
host: "localhost"
|
|
73
|
-
},
|
|
74
|
-
database: {
|
|
75
|
-
walMode: true
|
|
76
|
-
}
|
|
77
|
-
};
|
|
78
|
-
async function loadConfig(cwd = process.cwd()) {
|
|
79
|
-
const configPath = path2.resolve(cwd, "tether.config.ts");
|
|
80
|
-
if (!await fs2.pathExists(configPath)) {
|
|
81
|
-
return DEFAULT_CONFIG;
|
|
82
|
-
}
|
|
83
|
-
const configSource = await fs2.readFile(configPath, "utf-8");
|
|
84
|
-
const config = {};
|
|
85
|
-
const schemaMatch = configSource.match(/schema\s*:\s*['"]([^'"]+)['"]/);
|
|
86
|
-
if (schemaMatch) {
|
|
87
|
-
config.schema = schemaMatch[1];
|
|
88
|
-
}
|
|
89
|
-
const functionsMatch = configSource.match(/functions\s*:\s*['"]([^'"]+)['"]/);
|
|
90
|
-
if (functionsMatch) {
|
|
91
|
-
config.functions = functionsMatch[1];
|
|
92
|
-
}
|
|
93
|
-
const outputMatch = configSource.match(/output\s*:\s*['"]([^'"]+)['"]/);
|
|
94
|
-
if (outputMatch) {
|
|
95
|
-
config.output = outputMatch[1];
|
|
96
|
-
}
|
|
97
|
-
const portMatch = configSource.match(/port\s*:\s*(\d+)/);
|
|
98
|
-
if (portMatch) {
|
|
99
|
-
config.dev = { ...config.dev, port: parseInt(portMatch[1], 10) };
|
|
100
|
-
}
|
|
101
|
-
const hostMatch = configSource.match(/host\s*:\s*['"]([^'"]+)['"]/);
|
|
102
|
-
if (hostMatch) {
|
|
103
|
-
config.dev = { ...config.dev, host: hostMatch[1] };
|
|
104
|
-
}
|
|
105
|
-
return {
|
|
106
|
-
...DEFAULT_CONFIG,
|
|
107
|
-
...config,
|
|
108
|
-
dev: { ...DEFAULT_CONFIG.dev, ...config.dev },
|
|
109
|
-
database: { ...DEFAULT_CONFIG.database, ...config.database }
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
function resolvePath(configPath, cwd = process.cwd()) {
|
|
113
|
-
const normalised = configPath.replace(/^\.\//, "");
|
|
114
|
-
return path2.resolve(cwd, normalised);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// src/commands/deploy.ts
|
|
118
30
|
var isDev = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
119
31
|
var API_URL = isDev ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
|
|
120
32
|
async function deployCommand(options) {
|
|
121
33
|
const credentials = await requireAuth();
|
|
122
|
-
const configPath =
|
|
123
|
-
if (!await
|
|
124
|
-
console.log(
|
|
125
|
-
console.log(
|
|
34
|
+
const configPath = path.resolve(process.cwd(), "tether.config.ts");
|
|
35
|
+
if (!await fs.pathExists(configPath)) {
|
|
36
|
+
console.log(chalk.red("\nError: Not a Tether project"));
|
|
37
|
+
console.log(chalk.dim("Run `tthr init` to create a new project\n"));
|
|
126
38
|
process.exit(1);
|
|
127
39
|
}
|
|
128
|
-
const envPath =
|
|
40
|
+
const envPath = path.resolve(process.cwd(), ".env");
|
|
129
41
|
let projectId;
|
|
130
|
-
if (await
|
|
131
|
-
const envContent = await
|
|
42
|
+
if (await fs.pathExists(envPath)) {
|
|
43
|
+
const envContent = await fs.readFile(envPath, "utf-8");
|
|
132
44
|
const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
|
|
133
45
|
projectId = match?.[1]?.trim();
|
|
134
46
|
}
|
|
135
47
|
if (!projectId) {
|
|
136
|
-
console.log(
|
|
137
|
-
console.log(
|
|
48
|
+
console.log(chalk.red("\nError: Project ID not found"));
|
|
49
|
+
console.log(chalk.dim("Make sure TETHER_PROJECT_ID is set in your .env file\n"));
|
|
138
50
|
process.exit(1);
|
|
139
51
|
}
|
|
140
|
-
console.log(
|
|
141
|
-
console.log(
|
|
142
|
-
console.log(
|
|
52
|
+
console.log(chalk.bold("\n\u26A1 Deploying to Tether\n"));
|
|
53
|
+
console.log(chalk.dim(` Project: ${projectId}`));
|
|
54
|
+
console.log(chalk.dim(` API: ${API_URL}
|
|
143
55
|
`));
|
|
144
56
|
const config = await loadConfig();
|
|
145
57
|
const deploySchema = options.schema || !options.schema && !options.functions;
|
|
@@ -152,29 +64,29 @@ async function deployCommand(options) {
|
|
|
152
64
|
const functionsDir = resolvePath(config.functions);
|
|
153
65
|
await deployFunctionsToServer(projectId, credentials.accessToken, functionsDir, options.dryRun);
|
|
154
66
|
}
|
|
155
|
-
console.log(
|
|
67
|
+
console.log(chalk.green("\n\u2713 Deployment complete\n"));
|
|
156
68
|
}
|
|
157
69
|
async function deploySchemaToServer(projectId, token, schemaPath, dryRun) {
|
|
158
70
|
const spinner = ora("Reading schema...").start();
|
|
159
71
|
try {
|
|
160
|
-
if (!await
|
|
72
|
+
if (!await fs.pathExists(schemaPath)) {
|
|
161
73
|
spinner.warn("No schema file found");
|
|
162
|
-
const relativePath =
|
|
163
|
-
console.log(
|
|
74
|
+
const relativePath = path.relative(process.cwd(), schemaPath);
|
|
75
|
+
console.log(chalk.dim(` Create ${relativePath} to define your database schema
|
|
164
76
|
`));
|
|
165
77
|
return;
|
|
166
78
|
}
|
|
167
|
-
const schemaSource = await
|
|
79
|
+
const schemaSource = await fs.readFile(schemaPath, "utf-8");
|
|
168
80
|
const tables = parseSchema(schemaSource);
|
|
169
81
|
spinner.text = `Found ${tables.length} table(s)`;
|
|
170
82
|
const sql = generateSchemaSQL(tables);
|
|
171
83
|
if (dryRun) {
|
|
172
84
|
spinner.info("Dry run - would deploy schema:");
|
|
173
85
|
for (const table of tables) {
|
|
174
|
-
console.log(
|
|
86
|
+
console.log(chalk.dim(` - ${table.name} (${Object.keys(table.columns).length} columns)`));
|
|
175
87
|
}
|
|
176
|
-
console.log(
|
|
177
|
-
console.log(
|
|
88
|
+
console.log(chalk.bold("\nGenerated SQL:\n"));
|
|
89
|
+
console.log(chalk.cyan(sql));
|
|
178
90
|
return;
|
|
179
91
|
}
|
|
180
92
|
spinner.text = "Deploying schema...";
|
|
@@ -200,20 +112,20 @@ async function deploySchemaToServer(projectId, token, schemaPath, dryRun) {
|
|
|
200
112
|
spinner.succeed(`Schema deployed (${tables.length} table(s))`);
|
|
201
113
|
} catch (error) {
|
|
202
114
|
spinner.fail("Failed to deploy schema");
|
|
203
|
-
console.error(
|
|
115
|
+
console.error(chalk.red(error instanceof Error ? error.message : "Unknown error"));
|
|
204
116
|
}
|
|
205
117
|
}
|
|
206
118
|
async function deployFunctionsToServer(projectId, token, functionsDir, dryRun) {
|
|
207
119
|
const spinner = ora("Reading functions...").start();
|
|
208
120
|
try {
|
|
209
|
-
if (!await
|
|
121
|
+
if (!await fs.pathExists(functionsDir)) {
|
|
210
122
|
spinner.warn("No functions directory found");
|
|
211
|
-
const relativePath =
|
|
212
|
-
console.log(
|
|
123
|
+
const relativePath = path.relative(process.cwd(), functionsDir);
|
|
124
|
+
console.log(chalk.dim(` Create ${relativePath}/ to define your API functions
|
|
213
125
|
`));
|
|
214
126
|
return;
|
|
215
127
|
}
|
|
216
|
-
const files = await
|
|
128
|
+
const files = await fs.readdir(functionsDir);
|
|
217
129
|
const tsFiles = files.filter((f) => f.endsWith(".ts"));
|
|
218
130
|
if (tsFiles.length === 0) {
|
|
219
131
|
spinner.info("No function files found");
|
|
@@ -221,8 +133,8 @@ async function deployFunctionsToServer(projectId, token, functionsDir, dryRun) {
|
|
|
221
133
|
}
|
|
222
134
|
const functions = [];
|
|
223
135
|
for (const file of tsFiles) {
|
|
224
|
-
const filePath =
|
|
225
|
-
const source = await
|
|
136
|
+
const filePath = path.join(functionsDir, file);
|
|
137
|
+
const source = await fs.readFile(filePath, "utf-8");
|
|
226
138
|
const moduleName = file.replace(".ts", "");
|
|
227
139
|
const parsedFunctions = parseFunctions(moduleName, source);
|
|
228
140
|
functions.push(...parsedFunctions);
|
|
@@ -232,7 +144,7 @@ async function deployFunctionsToServer(projectId, token, functionsDir, dryRun) {
|
|
|
232
144
|
spinner.info("Dry run - would deploy functions:");
|
|
233
145
|
for (const fn of functions) {
|
|
234
146
|
const icon = fn.type === "query" ? "\u{1F50D}" : fn.type === "mutation" ? "\u270F\uFE0F" : "\u26A1";
|
|
235
|
-
console.log(
|
|
147
|
+
console.log(chalk.dim(` ${icon} ${fn.name} (${fn.type})`));
|
|
236
148
|
}
|
|
237
149
|
return;
|
|
238
150
|
}
|
|
@@ -260,29 +172,29 @@ async function deployFunctionsToServer(projectId, token, functionsDir, dryRun) {
|
|
|
260
172
|
const mutations = functions.filter((f) => f.type === "mutation");
|
|
261
173
|
const actions = functions.filter((f) => f.type === "action");
|
|
262
174
|
if (queries.length > 0) {
|
|
263
|
-
console.log(
|
|
175
|
+
console.log(chalk.dim(`
|
|
264
176
|
Queries:`));
|
|
265
177
|
for (const fn of queries) {
|
|
266
|
-
console.log(
|
|
178
|
+
console.log(chalk.dim(` - ${fn.name}`));
|
|
267
179
|
}
|
|
268
180
|
}
|
|
269
181
|
if (mutations.length > 0) {
|
|
270
|
-
console.log(
|
|
182
|
+
console.log(chalk.dim(`
|
|
271
183
|
Mutations:`));
|
|
272
184
|
for (const fn of mutations) {
|
|
273
|
-
console.log(
|
|
185
|
+
console.log(chalk.dim(` - ${fn.name}`));
|
|
274
186
|
}
|
|
275
187
|
}
|
|
276
188
|
if (actions.length > 0) {
|
|
277
|
-
console.log(
|
|
189
|
+
console.log(chalk.dim(`
|
|
278
190
|
Actions:`));
|
|
279
191
|
for (const fn of actions) {
|
|
280
|
-
console.log(
|
|
192
|
+
console.log(chalk.dim(` - ${fn.name}`));
|
|
281
193
|
}
|
|
282
194
|
}
|
|
283
195
|
} catch (error) {
|
|
284
196
|
spinner.fail("Failed to deploy functions");
|
|
285
|
-
console.error(
|
|
197
|
+
console.error(chalk.red(error instanceof Error ? error.message : "Unknown error"));
|
|
286
198
|
}
|
|
287
199
|
}
|
|
288
200
|
function parseSchema(source) {
|
|
@@ -451,7 +363,7 @@ function isPackageManagerInstalled(pm) {
|
|
|
451
363
|
}
|
|
452
364
|
async function initCommand(name, options) {
|
|
453
365
|
const credentials = await requireAuth();
|
|
454
|
-
console.log(
|
|
366
|
+
console.log(chalk2.bold("\n\u26A1 Create a new Tether project\n"));
|
|
455
367
|
let projectName = name;
|
|
456
368
|
if (!projectName) {
|
|
457
369
|
const response = await prompts({
|
|
@@ -462,7 +374,7 @@ async function initCommand(name, options) {
|
|
|
462
374
|
});
|
|
463
375
|
projectName = response.name;
|
|
464
376
|
if (!projectName) {
|
|
465
|
-
console.log(
|
|
377
|
+
console.log(chalk2.red("Project name is required"));
|
|
466
378
|
process.exit(1);
|
|
467
379
|
}
|
|
468
380
|
}
|
|
@@ -480,9 +392,9 @@ async function initCommand(name, options) {
|
|
|
480
392
|
});
|
|
481
393
|
const packageManager = pmResponse.packageManager || "npm";
|
|
482
394
|
if (!isPackageManagerInstalled(packageManager)) {
|
|
483
|
-
console.log(
|
|
395
|
+
console.log(chalk2.red(`
|
|
484
396
|
${packageManager} is not installed on your system.`));
|
|
485
|
-
console.log(
|
|
397
|
+
console.log(chalk2.dim(`Please install ${packageManager} and try again.
|
|
486
398
|
`));
|
|
487
399
|
process.exit(1);
|
|
488
400
|
}
|
|
@@ -502,8 +414,8 @@ ${packageManager} is not installed on your system.`));
|
|
|
502
414
|
});
|
|
503
415
|
template = response.template || "nuxt";
|
|
504
416
|
}
|
|
505
|
-
const projectPath =
|
|
506
|
-
if (await
|
|
417
|
+
const projectPath = path2.resolve(process.cwd(), projectName);
|
|
418
|
+
if (await fs2.pathExists(projectPath)) {
|
|
507
419
|
const { overwrite } = await prompts({
|
|
508
420
|
type: "confirm",
|
|
509
421
|
name: "overwrite",
|
|
@@ -511,10 +423,10 @@ ${packageManager} is not installed on your system.`));
|
|
|
511
423
|
initial: false
|
|
512
424
|
});
|
|
513
425
|
if (!overwrite) {
|
|
514
|
-
console.log(
|
|
426
|
+
console.log(chalk2.yellow("Cancelled"));
|
|
515
427
|
process.exit(0);
|
|
516
428
|
}
|
|
517
|
-
await
|
|
429
|
+
await fs2.remove(projectPath);
|
|
518
430
|
}
|
|
519
431
|
const spinner = ora2(`Scaffolding ${template} project...`).start();
|
|
520
432
|
let projectId;
|
|
@@ -562,17 +474,17 @@ ${packageManager} is not installed on your system.`));
|
|
|
562
474
|
} finally {
|
|
563
475
|
process.chdir(originalCwd);
|
|
564
476
|
}
|
|
565
|
-
console.log("\n" +
|
|
477
|
+
console.log("\n" + chalk2.green("\u2713") + " Project created successfully!\n");
|
|
566
478
|
console.log("Next steps:\n");
|
|
567
|
-
console.log(
|
|
479
|
+
console.log(chalk2.cyan(` cd ${projectName}`));
|
|
568
480
|
if (template === "vanilla") {
|
|
569
|
-
console.log(
|
|
481
|
+
console.log(chalk2.cyan(` ${packageManager} install`));
|
|
570
482
|
}
|
|
571
|
-
console.log(
|
|
572
|
-
console.log("\n" +
|
|
483
|
+
console.log(chalk2.cyan(` ${packageManager} run dev`));
|
|
484
|
+
console.log("\n" + chalk2.dim("For more information, visit https://tthr.io/docs\n"));
|
|
573
485
|
} catch (error) {
|
|
574
486
|
spinner.fail("Failed to create project");
|
|
575
|
-
console.error(
|
|
487
|
+
console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
|
|
576
488
|
process.exit(1);
|
|
577
489
|
}
|
|
578
490
|
}
|
|
@@ -589,16 +501,16 @@ function getPackageRunnerCommand(pm) {
|
|
|
589
501
|
}
|
|
590
502
|
}
|
|
591
503
|
async function scaffoldNuxtProject(projectName, projectPath, spinner, packageManager) {
|
|
592
|
-
const parentDir =
|
|
504
|
+
const parentDir = path2.dirname(projectPath);
|
|
593
505
|
const runner = getPackageRunnerCommand(packageManager);
|
|
594
506
|
spinner.stop();
|
|
595
|
-
console.log(
|
|
507
|
+
console.log(chalk2.dim("\nRunning nuxi init...\n"));
|
|
596
508
|
try {
|
|
597
509
|
execSync(`${runner} nuxi@latest init ${projectName} -t minimal --packageManager ${packageManager} --gitInit false`, {
|
|
598
510
|
cwd: parentDir,
|
|
599
511
|
stdio: "inherit"
|
|
600
512
|
});
|
|
601
|
-
if (!await
|
|
513
|
+
if (!await fs2.pathExists(path2.join(projectPath, "package.json"))) {
|
|
602
514
|
throw new Error("Nuxt project was not created successfully - package.json missing");
|
|
603
515
|
}
|
|
604
516
|
spinner.start();
|
|
@@ -611,17 +523,17 @@ async function scaffoldNuxtProject(projectName, projectPath, spinner, packageMan
|
|
|
611
523
|
}
|
|
612
524
|
}
|
|
613
525
|
async function scaffoldNextProject(projectName, projectPath, spinner, packageManager) {
|
|
614
|
-
const parentDir =
|
|
526
|
+
const parentDir = path2.dirname(projectPath);
|
|
615
527
|
const runner = getPackageRunnerCommand(packageManager);
|
|
616
528
|
const pmFlag = packageManager === "npm" ? "--use-npm" : packageManager === "pnpm" ? "--use-pnpm" : packageManager === "yarn" ? "--use-yarn" : "--use-bun";
|
|
617
529
|
spinner.stop();
|
|
618
|
-
console.log(
|
|
530
|
+
console.log(chalk2.dim("\nRunning create-next-app...\n"));
|
|
619
531
|
try {
|
|
620
532
|
execSync(`${runner} create-next-app@latest ${projectName} --typescript --eslint --tailwind --src-dir --app --import-alias "@/*" ${pmFlag}`, {
|
|
621
533
|
cwd: parentDir,
|
|
622
534
|
stdio: "inherit"
|
|
623
535
|
});
|
|
624
|
-
if (!await
|
|
536
|
+
if (!await fs2.pathExists(path2.join(projectPath, "package.json"))) {
|
|
625
537
|
throw new Error("Next.js project was not created successfully - package.json missing");
|
|
626
538
|
}
|
|
627
539
|
spinner.start();
|
|
@@ -634,16 +546,16 @@ async function scaffoldNextProject(projectName, projectPath, spinner, packageMan
|
|
|
634
546
|
}
|
|
635
547
|
}
|
|
636
548
|
async function scaffoldSvelteKitProject(projectName, projectPath, spinner, packageManager) {
|
|
637
|
-
const parentDir =
|
|
549
|
+
const parentDir = path2.dirname(projectPath);
|
|
638
550
|
const runner = getPackageRunnerCommand(packageManager);
|
|
639
551
|
spinner.stop();
|
|
640
|
-
console.log(
|
|
552
|
+
console.log(chalk2.dim("\nRunning sv create...\n"));
|
|
641
553
|
try {
|
|
642
554
|
execSync(`${runner} sv create ${projectName} --template minimal --types ts --no-add-ons --no-install`, {
|
|
643
555
|
cwd: parentDir,
|
|
644
556
|
stdio: "inherit"
|
|
645
557
|
});
|
|
646
|
-
if (!await
|
|
558
|
+
if (!await fs2.pathExists(path2.join(projectPath, "package.json"))) {
|
|
647
559
|
throw new Error("SvelteKit project was not created successfully - package.json missing");
|
|
648
560
|
}
|
|
649
561
|
spinner.start();
|
|
@@ -662,8 +574,8 @@ async function scaffoldSvelteKitProject(projectName, projectPath, spinner, packa
|
|
|
662
574
|
}
|
|
663
575
|
async function scaffoldVanillaProject(projectName, projectPath, spinner) {
|
|
664
576
|
spinner.text = "Creating vanilla TypeScript project...";
|
|
665
|
-
await
|
|
666
|
-
await
|
|
577
|
+
await fs2.ensureDir(projectPath);
|
|
578
|
+
await fs2.ensureDir(path2.join(projectPath, "src"));
|
|
667
579
|
const packageJson = {
|
|
668
580
|
name: projectName,
|
|
669
581
|
version: "0.0.1",
|
|
@@ -680,9 +592,9 @@ async function scaffoldVanillaProject(projectName, projectPath, spinner) {
|
|
|
680
592
|
"@types/node": "latest"
|
|
681
593
|
}
|
|
682
594
|
};
|
|
683
|
-
await
|
|
684
|
-
await
|
|
685
|
-
|
|
595
|
+
await fs2.writeJSON(path2.join(projectPath, "package.json"), packageJson, { spaces: 2 });
|
|
596
|
+
await fs2.writeJSON(
|
|
597
|
+
path2.join(projectPath, "tsconfig.json"),
|
|
686
598
|
{
|
|
687
599
|
compilerOptions: {
|
|
688
600
|
target: "ES2022",
|
|
@@ -703,8 +615,8 @@ async function scaffoldVanillaProject(projectName, projectPath, spinner) {
|
|
|
703
615
|
},
|
|
704
616
|
{ spaces: 2 }
|
|
705
617
|
);
|
|
706
|
-
await
|
|
707
|
-
|
|
618
|
+
await fs2.writeFile(
|
|
619
|
+
path2.join(projectPath, "src", "index.ts"),
|
|
708
620
|
`import { createClient } from '@tthr/client';
|
|
709
621
|
|
|
710
622
|
const tether = createClient({
|
|
@@ -723,8 +635,8 @@ async function main() {
|
|
|
723
635
|
main().catch(console.error);
|
|
724
636
|
`
|
|
725
637
|
);
|
|
726
|
-
await
|
|
727
|
-
|
|
638
|
+
await fs2.writeFile(
|
|
639
|
+
path2.join(projectPath, ".gitignore"),
|
|
728
640
|
`node_modules/
|
|
729
641
|
dist/
|
|
730
642
|
.env
|
|
@@ -734,11 +646,11 @@ dist/
|
|
|
734
646
|
);
|
|
735
647
|
}
|
|
736
648
|
async function addTetherFiles(projectPath, projectId, apiKey, template) {
|
|
737
|
-
await
|
|
738
|
-
await
|
|
649
|
+
await fs2.ensureDir(path2.join(projectPath, "tether"));
|
|
650
|
+
await fs2.ensureDir(path2.join(projectPath, "tether", "functions"));
|
|
739
651
|
const configPackage = template === "nuxt" ? "@tthr/vue" : template === "next" ? "@tthr/react" : template === "sveltekit" ? "@tthr/svelte" : "@tthr/client";
|
|
740
|
-
await
|
|
741
|
-
|
|
652
|
+
await fs2.writeFile(
|
|
653
|
+
path2.join(projectPath, "tether.config.ts"),
|
|
742
654
|
`import { defineConfig } from '${configPackage}';
|
|
743
655
|
|
|
744
656
|
export default defineConfig({
|
|
@@ -759,8 +671,8 @@ export default defineConfig({
|
|
|
759
671
|
});
|
|
760
672
|
`
|
|
761
673
|
);
|
|
762
|
-
await
|
|
763
|
-
|
|
674
|
+
await fs2.writeFile(
|
|
675
|
+
path2.join(projectPath, "tether", "schema.ts"),
|
|
764
676
|
`import { defineSchema, text, integer, timestamp } from '@tthr/schema';
|
|
765
677
|
|
|
766
678
|
export default defineSchema({
|
|
@@ -785,8 +697,8 @@ export default defineSchema({
|
|
|
785
697
|
});
|
|
786
698
|
`
|
|
787
699
|
);
|
|
788
|
-
await
|
|
789
|
-
|
|
700
|
+
await fs2.writeFile(
|
|
701
|
+
path2.join(projectPath, "tether", "functions", "posts.ts"),
|
|
790
702
|
`import { query, mutation, z } from '@tthr/server';
|
|
791
703
|
|
|
792
704
|
// List all posts
|
|
@@ -871,8 +783,8 @@ export const remove = mutation({
|
|
|
871
783
|
});
|
|
872
784
|
`
|
|
873
785
|
);
|
|
874
|
-
await
|
|
875
|
-
|
|
786
|
+
await fs2.writeFile(
|
|
787
|
+
path2.join(projectPath, "tether", "functions", "index.ts"),
|
|
876
788
|
`// Re-export all function modules
|
|
877
789
|
// The Tether SDK uses this file to discover custom functions
|
|
878
790
|
|
|
@@ -880,8 +792,8 @@ export * as posts from './posts';
|
|
|
880
792
|
export * as comments from './comments';
|
|
881
793
|
`
|
|
882
794
|
);
|
|
883
|
-
await
|
|
884
|
-
|
|
795
|
+
await fs2.writeFile(
|
|
796
|
+
path2.join(projectPath, "tether", "functions", "comments.ts"),
|
|
885
797
|
`import { query, mutation, z } from '@tthr/server';
|
|
886
798
|
|
|
887
799
|
// List all comments
|
|
@@ -953,27 +865,27 @@ export const remove = mutation({
|
|
|
953
865
|
TETHER_PROJECT_ID=${projectId}
|
|
954
866
|
TETHER_API_KEY=${apiKey}
|
|
955
867
|
`;
|
|
956
|
-
const envPath =
|
|
957
|
-
if (await
|
|
958
|
-
const existing = await
|
|
959
|
-
await
|
|
868
|
+
const envPath = path2.join(projectPath, ".env");
|
|
869
|
+
if (await fs2.pathExists(envPath)) {
|
|
870
|
+
const existing = await fs2.readFile(envPath, "utf-8");
|
|
871
|
+
await fs2.writeFile(envPath, existing + "\n" + envContent);
|
|
960
872
|
} else {
|
|
961
|
-
await
|
|
873
|
+
await fs2.writeFile(envPath, envContent);
|
|
962
874
|
}
|
|
963
|
-
const gitignorePath =
|
|
875
|
+
const gitignorePath = path2.join(projectPath, ".gitignore");
|
|
964
876
|
const tetherGitignore = `
|
|
965
877
|
# Tether
|
|
966
878
|
_generated/
|
|
967
879
|
.env
|
|
968
880
|
.env.local
|
|
969
881
|
`;
|
|
970
|
-
if (await
|
|
971
|
-
const existing = await
|
|
882
|
+
if (await fs2.pathExists(gitignorePath)) {
|
|
883
|
+
const existing = await fs2.readFile(gitignorePath, "utf-8");
|
|
972
884
|
if (!existing.includes("_generated/")) {
|
|
973
|
-
await
|
|
885
|
+
await fs2.writeFile(gitignorePath, existing + tetherGitignore);
|
|
974
886
|
}
|
|
975
887
|
} else {
|
|
976
|
-
await
|
|
888
|
+
await fs2.writeFile(gitignorePath, tetherGitignore.trim());
|
|
977
889
|
}
|
|
978
890
|
}
|
|
979
891
|
async function configureFramework(projectPath, template) {
|
|
@@ -986,9 +898,9 @@ async function configureFramework(projectPath, template) {
|
|
|
986
898
|
}
|
|
987
899
|
}
|
|
988
900
|
async function configureNuxt(projectPath) {
|
|
989
|
-
const configPath =
|
|
990
|
-
if (!await
|
|
991
|
-
await
|
|
901
|
+
const configPath = path2.join(projectPath, "nuxt.config.ts");
|
|
902
|
+
if (!await fs2.pathExists(configPath)) {
|
|
903
|
+
await fs2.writeFile(
|
|
992
904
|
configPath,
|
|
993
905
|
`// https://nuxt.com/docs/api/configuration/nuxt-config
|
|
994
906
|
export default defineNuxtConfig({
|
|
@@ -1006,7 +918,7 @@ export default defineNuxtConfig({
|
|
|
1006
918
|
);
|
|
1007
919
|
return;
|
|
1008
920
|
}
|
|
1009
|
-
let config = await
|
|
921
|
+
let config = await fs2.readFile(configPath, "utf-8");
|
|
1010
922
|
if (config.includes("modules:")) {
|
|
1011
923
|
config = config.replace(
|
|
1012
924
|
/modules:\s*\[/,
|
|
@@ -1033,11 +945,11 @@ export default defineNuxtConfig({
|
|
|
1033
945
|
`
|
|
1034
946
|
);
|
|
1035
947
|
}
|
|
1036
|
-
await
|
|
948
|
+
await fs2.writeFile(configPath, config);
|
|
1037
949
|
}
|
|
1038
950
|
async function configureNext(projectPath) {
|
|
1039
|
-
const providersPath =
|
|
1040
|
-
await
|
|
951
|
+
const providersPath = path2.join(projectPath, "src", "app", "providers.tsx");
|
|
952
|
+
await fs2.writeFile(
|
|
1041
953
|
providersPath,
|
|
1042
954
|
`'use client';
|
|
1043
955
|
|
|
@@ -1055,9 +967,9 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
1055
967
|
}
|
|
1056
968
|
`
|
|
1057
969
|
);
|
|
1058
|
-
const layoutPath =
|
|
1059
|
-
if (await
|
|
1060
|
-
let layout = await
|
|
970
|
+
const layoutPath = path2.join(projectPath, "src", "app", "layout.tsx");
|
|
971
|
+
if (await fs2.pathExists(layoutPath)) {
|
|
972
|
+
let layout = await fs2.readFile(layoutPath, "utf-8");
|
|
1061
973
|
if (!layout.includes("import { Providers }")) {
|
|
1062
974
|
layout = layout.replace(
|
|
1063
975
|
/^(import.*\n)+/m,
|
|
@@ -1070,24 +982,24 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
1070
982
|
"$1<Providers>$2</Providers>$3"
|
|
1071
983
|
);
|
|
1072
984
|
}
|
|
1073
|
-
await
|
|
985
|
+
await fs2.writeFile(layoutPath, layout);
|
|
1074
986
|
}
|
|
1075
|
-
const envLocalPath =
|
|
987
|
+
const envLocalPath = path2.join(projectPath, ".env.local");
|
|
1076
988
|
const nextEnvContent = `# Tether Configuration (client-side)
|
|
1077
989
|
NEXT_PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
|
|
1078
990
|
`;
|
|
1079
|
-
if (await
|
|
1080
|
-
const existing = await
|
|
1081
|
-
await
|
|
991
|
+
if (await fs2.pathExists(envLocalPath)) {
|
|
992
|
+
const existing = await fs2.readFile(envLocalPath, "utf-8");
|
|
993
|
+
await fs2.writeFile(envLocalPath, existing + "\n" + nextEnvContent);
|
|
1082
994
|
} else {
|
|
1083
|
-
await
|
|
995
|
+
await fs2.writeFile(envLocalPath, nextEnvContent);
|
|
1084
996
|
}
|
|
1085
997
|
}
|
|
1086
998
|
async function configureSvelteKit(projectPath) {
|
|
1087
|
-
const libPath =
|
|
1088
|
-
await
|
|
1089
|
-
await
|
|
1090
|
-
|
|
999
|
+
const libPath = path2.join(projectPath, "src", "lib");
|
|
1000
|
+
await fs2.ensureDir(libPath);
|
|
1001
|
+
await fs2.writeFile(
|
|
1002
|
+
path2.join(libPath, "tether.ts"),
|
|
1091
1003
|
`import { createClient } from '@tthr/svelte';
|
|
1092
1004
|
import { PUBLIC_TETHER_PROJECT_ID, PUBLIC_TETHER_URL } from '$env/static/public';
|
|
1093
1005
|
|
|
@@ -1097,14 +1009,14 @@ export const tether = createClient({
|
|
|
1097
1009
|
});
|
|
1098
1010
|
`
|
|
1099
1011
|
);
|
|
1100
|
-
const envPath =
|
|
1012
|
+
const envPath = path2.join(projectPath, ".env");
|
|
1101
1013
|
const svelteEnvContent = `# Tether Configuration (public)
|
|
1102
1014
|
PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
|
|
1103
1015
|
`;
|
|
1104
|
-
if (await
|
|
1105
|
-
const existing = await
|
|
1016
|
+
if (await fs2.pathExists(envPath)) {
|
|
1017
|
+
const existing = await fs2.readFile(envPath, "utf-8");
|
|
1106
1018
|
if (!existing.includes("PUBLIC_TETHER_")) {
|
|
1107
|
-
await
|
|
1019
|
+
await fs2.writeFile(envPath, existing + "\n" + svelteEnvContent);
|
|
1108
1020
|
}
|
|
1109
1021
|
}
|
|
1110
1022
|
}
|
|
@@ -1154,15 +1066,15 @@ async function installTetherPackages(projectPath, template, packageManager) {
|
|
|
1154
1066
|
stdio: "pipe"
|
|
1155
1067
|
});
|
|
1156
1068
|
} catch (error) {
|
|
1157
|
-
console.warn(
|
|
1069
|
+
console.warn(chalk2.yellow("\nNote: Some Tether packages may not be published yet."));
|
|
1158
1070
|
}
|
|
1159
1071
|
}
|
|
1160
1072
|
async function createDemoPage(projectPath, template) {
|
|
1161
1073
|
if (template === "nuxt") {
|
|
1162
|
-
const nuxt4AppVuePath =
|
|
1163
|
-
const nuxt3AppVuePath =
|
|
1164
|
-
const appVuePath = await
|
|
1165
|
-
await
|
|
1074
|
+
const nuxt4AppVuePath = path2.join(projectPath, "app", "app.vue");
|
|
1075
|
+
const nuxt3AppVuePath = path2.join(projectPath, "app.vue");
|
|
1076
|
+
const appVuePath = await fs2.pathExists(path2.join(projectPath, "app")) ? nuxt4AppVuePath : nuxt3AppVuePath;
|
|
1077
|
+
await fs2.writeFile(
|
|
1166
1078
|
appVuePath,
|
|
1167
1079
|
`<template>
|
|
1168
1080
|
<TetherWelcome />
|
|
@@ -1170,8 +1082,8 @@ async function createDemoPage(projectPath, template) {
|
|
|
1170
1082
|
`
|
|
1171
1083
|
);
|
|
1172
1084
|
} else if (template === "next") {
|
|
1173
|
-
const pagePath =
|
|
1174
|
-
await
|
|
1085
|
+
const pagePath = path2.join(projectPath, "src", "app", "page.tsx");
|
|
1086
|
+
await fs2.writeFile(
|
|
1175
1087
|
pagePath,
|
|
1176
1088
|
`'use client';
|
|
1177
1089
|
|
|
@@ -1281,8 +1193,8 @@ export default function Home() {
|
|
|
1281
1193
|
`
|
|
1282
1194
|
);
|
|
1283
1195
|
} else if (template === "sveltekit") {
|
|
1284
|
-
const pagePath =
|
|
1285
|
-
await
|
|
1196
|
+
const pagePath = path2.join(projectPath, "src", "routes", "+page.svelte");
|
|
1197
|
+
await fs2.writeFile(
|
|
1286
1198
|
pagePath,
|
|
1287
1199
|
`<script lang="ts">
|
|
1288
1200
|
import { onMount } from 'svelte';
|
|
@@ -1548,369 +1460,337 @@ export default function Home() {
|
|
|
1548
1460
|
}
|
|
1549
1461
|
|
|
1550
1462
|
// src/commands/dev.ts
|
|
1551
|
-
import
|
|
1463
|
+
import chalk3 from "chalk";
|
|
1552
1464
|
import ora3 from "ora";
|
|
1553
|
-
import
|
|
1554
|
-
import
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1465
|
+
import fs3 from "fs-extra";
|
|
1466
|
+
import path3 from "path";
|
|
1467
|
+
import { spawn } from "child_process";
|
|
1468
|
+
import { watch } from "chokidar";
|
|
1469
|
+
var frameworkProcess = null;
|
|
1470
|
+
var schemaWatcher = null;
|
|
1471
|
+
var functionsWatcher = null;
|
|
1472
|
+
var isGenerating = false;
|
|
1473
|
+
var pendingGenerate = false;
|
|
1474
|
+
async function runGenerate(spinner) {
|
|
1475
|
+
if (isGenerating) {
|
|
1476
|
+
pendingGenerate = true;
|
|
1477
|
+
return true;
|
|
1562
1478
|
}
|
|
1563
|
-
|
|
1564
|
-
const port = options.port || String(config.dev?.port || 3001);
|
|
1565
|
-
console.log(chalk4.bold("\n\u26A1 Starting Tether development server\n"));
|
|
1566
|
-
const spinner = ora3("Starting server...").start();
|
|
1479
|
+
isGenerating = true;
|
|
1567
1480
|
try {
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
console.log(chalk4.yellow("\n\nShutting down...\n"));
|
|
1575
|
-
process.exit(0);
|
|
1576
|
-
});
|
|
1577
|
-
await new Promise(() => {
|
|
1578
|
-
});
|
|
1481
|
+
const { generateTypes } = await import("./generate-26HERX3T.js");
|
|
1482
|
+
spinner.text = "Regenerating types...";
|
|
1483
|
+
await generateTypes({ silent: true });
|
|
1484
|
+
spinner.succeed("Types regenerated");
|
|
1485
|
+
spinner.start("Watching for changes...");
|
|
1486
|
+
return true;
|
|
1579
1487
|
} catch (error) {
|
|
1580
|
-
spinner.fail("
|
|
1581
|
-
console.error(
|
|
1582
|
-
|
|
1488
|
+
spinner.fail("Type generation failed");
|
|
1489
|
+
console.error(chalk3.red(error instanceof Error ? error.message : "Unknown error"));
|
|
1490
|
+
spinner.start("Watching for changes...");
|
|
1491
|
+
return false;
|
|
1492
|
+
} finally {
|
|
1493
|
+
isGenerating = false;
|
|
1494
|
+
if (pendingGenerate) {
|
|
1495
|
+
pendingGenerate = false;
|
|
1496
|
+
setTimeout(() => runGenerate(spinner), 100);
|
|
1497
|
+
}
|
|
1583
1498
|
}
|
|
1584
1499
|
}
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
const schemaMatch = source.match(/defineSchema\s*\(\s*\{([\s\S]*)\}\s*\)/);
|
|
1594
|
-
if (!schemaMatch) return tables;
|
|
1595
|
-
const schemaContent = schemaMatch[1];
|
|
1596
|
-
const tableStartRegex = /(\w+)\s*:\s*\{/g;
|
|
1597
|
-
let match;
|
|
1598
|
-
while ((match = tableStartRegex.exec(schemaContent)) !== null) {
|
|
1599
|
-
const tableName = match[1];
|
|
1600
|
-
const startOffset = match.index + match[0].length;
|
|
1601
|
-
let braceCount = 1;
|
|
1602
|
-
let endOffset = startOffset;
|
|
1603
|
-
for (let i = startOffset; i < schemaContent.length && braceCount > 0; i++) {
|
|
1604
|
-
const char = schemaContent[i];
|
|
1500
|
+
async function validateSchema(schemaPath) {
|
|
1501
|
+
try {
|
|
1502
|
+
const content = await fs3.readFile(schemaPath, "utf-8");
|
|
1503
|
+
if (!content.includes("defineSchema")) {
|
|
1504
|
+
return { valid: false, error: "Missing defineSchema() call" };
|
|
1505
|
+
}
|
|
1506
|
+
let braceCount = 0;
|
|
1507
|
+
for (const char of content) {
|
|
1605
1508
|
if (char === "{") braceCount++;
|
|
1606
1509
|
else if (char === "}") braceCount--;
|
|
1607
|
-
|
|
1510
|
+
if (braceCount < 0) {
|
|
1511
|
+
return { valid: false, error: "Unmatched closing brace" };
|
|
1512
|
+
}
|
|
1608
1513
|
}
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
tables.push({ name: tableName, columns });
|
|
1612
|
-
}
|
|
1613
|
-
return tables;
|
|
1614
|
-
}
|
|
1615
|
-
function parseColumns(content) {
|
|
1616
|
-
const columns = [];
|
|
1617
|
-
const columnRegex = /(\w+)\s*:\s*(\w+)\s*\(\s*\)([^,\n}]*)/g;
|
|
1618
|
-
let match;
|
|
1619
|
-
while ((match = columnRegex.exec(content)) !== null) {
|
|
1620
|
-
const name = match[1];
|
|
1621
|
-
const schemaType = match[2];
|
|
1622
|
-
const modifiers = match[3] || "";
|
|
1623
|
-
const nullable = !modifiers.includes(".notNull()") && !modifiers.includes(".primaryKey()");
|
|
1624
|
-
const primaryKey = modifiers.includes(".primaryKey()");
|
|
1625
|
-
columns.push({
|
|
1626
|
-
name,
|
|
1627
|
-
type: schemaTypeToTS(schemaType),
|
|
1628
|
-
nullable,
|
|
1629
|
-
primaryKey
|
|
1630
|
-
});
|
|
1631
|
-
}
|
|
1632
|
-
return columns;
|
|
1633
|
-
}
|
|
1634
|
-
function schemaTypeToTS(schemaType) {
|
|
1635
|
-
switch (schemaType) {
|
|
1636
|
-
case "text":
|
|
1637
|
-
return "string";
|
|
1638
|
-
case "integer":
|
|
1639
|
-
return "number";
|
|
1640
|
-
case "real":
|
|
1641
|
-
return "number";
|
|
1642
|
-
case "boolean":
|
|
1643
|
-
return "boolean";
|
|
1644
|
-
case "timestamp":
|
|
1645
|
-
return "string";
|
|
1646
|
-
case "json":
|
|
1647
|
-
return "unknown";
|
|
1648
|
-
case "blob":
|
|
1649
|
-
return "Uint8Array";
|
|
1650
|
-
default:
|
|
1651
|
-
return "unknown";
|
|
1652
|
-
}
|
|
1653
|
-
}
|
|
1654
|
-
function tableNameToInterface(tableName) {
|
|
1655
|
-
let name = tableName;
|
|
1656
|
-
if (name.endsWith("ies")) {
|
|
1657
|
-
name = name.slice(0, -3) + "y";
|
|
1658
|
-
} else if (name.endsWith("s") && !name.endsWith("ss")) {
|
|
1659
|
-
name = name.slice(0, -1);
|
|
1660
|
-
}
|
|
1661
|
-
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
1662
|
-
}
|
|
1663
|
-
function generateDbFile(tables) {
|
|
1664
|
-
const lines = [
|
|
1665
|
-
"// Auto-generated by Tether CLI - do not edit manually",
|
|
1666
|
-
`// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1667
|
-
"",
|
|
1668
|
-
"import { createDatabaseProxy, type TetherDatabase } from '@tthr/client';",
|
|
1669
|
-
""
|
|
1670
|
-
];
|
|
1671
|
-
for (const table of tables) {
|
|
1672
|
-
const interfaceName = tableNameToInterface(table.name);
|
|
1673
|
-
lines.push(`export interface ${interfaceName} {`);
|
|
1674
|
-
for (const col of table.columns) {
|
|
1675
|
-
const typeStr = col.nullable ? `${col.type} | null` : col.type;
|
|
1676
|
-
lines.push(` ${col.name}: ${typeStr};`);
|
|
1514
|
+
if (braceCount !== 0) {
|
|
1515
|
+
return { valid: false, error: "Unmatched opening brace" };
|
|
1677
1516
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
1517
|
+
return { valid: true };
|
|
1518
|
+
} catch (error) {
|
|
1519
|
+
return { valid: false, error: error instanceof Error ? error.message : "Unknown error" };
|
|
1680
1520
|
}
|
|
1681
|
-
lines.push("export interface Schema {");
|
|
1682
|
-
for (const table of tables) {
|
|
1683
|
-
const interfaceName = tableNameToInterface(table.name);
|
|
1684
|
-
lines.push(` ${table.name}: ${interfaceName};`);
|
|
1685
|
-
}
|
|
1686
|
-
lines.push("}");
|
|
1687
|
-
lines.push("");
|
|
1688
|
-
lines.push("// Database client with typed tables");
|
|
1689
|
-
lines.push("// This is a proxy that will be populated by the Tether runtime");
|
|
1690
|
-
lines.push("export const db: TetherDatabase<Schema> = createDatabaseProxy<Schema>();");
|
|
1691
|
-
lines.push("");
|
|
1692
|
-
return lines.join("\n");
|
|
1693
1521
|
}
|
|
1694
|
-
async function
|
|
1695
|
-
const
|
|
1696
|
-
if (!await
|
|
1697
|
-
return
|
|
1522
|
+
async function validateFunctions(functionsDir) {
|
|
1523
|
+
const errors = [];
|
|
1524
|
+
if (!await fs3.pathExists(functionsDir)) {
|
|
1525
|
+
return { valid: true, errors: [] };
|
|
1698
1526
|
}
|
|
1699
|
-
const files = await
|
|
1527
|
+
const files = await fs3.readdir(functionsDir);
|
|
1700
1528
|
for (const file of files) {
|
|
1701
1529
|
if (!file.endsWith(".ts") && !file.endsWith(".js")) continue;
|
|
1702
|
-
const filePath =
|
|
1703
|
-
const stat = await
|
|
1530
|
+
const filePath = path3.join(functionsDir, file);
|
|
1531
|
+
const stat = await fs3.stat(filePath);
|
|
1704
1532
|
if (!stat.isFile()) continue;
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1533
|
+
try {
|
|
1534
|
+
const content = await fs3.readFile(filePath, "utf-8");
|
|
1535
|
+
const hasExports = /export\s+const\s+\w+\s*=\s*(query|mutation|action)\s*\(/.test(content);
|
|
1536
|
+
if (!hasExports && !file.includes("index")) {
|
|
1537
|
+
errors.push(`${file}: No query, mutation, or action exports found`);
|
|
1538
|
+
}
|
|
1539
|
+
let braceCount = 0;
|
|
1540
|
+
for (const char of content) {
|
|
1541
|
+
if (char === "{") braceCount++;
|
|
1542
|
+
else if (char === "}") braceCount--;
|
|
1543
|
+
}
|
|
1544
|
+
if (braceCount !== 0) {
|
|
1545
|
+
errors.push(`${file}: Mismatched braces`);
|
|
1546
|
+
}
|
|
1547
|
+
} catch (error) {
|
|
1548
|
+
errors.push(`${file}: ${error instanceof Error ? error.message : "Read error"}`);
|
|
1714
1549
|
}
|
|
1715
1550
|
}
|
|
1716
|
-
return
|
|
1551
|
+
return { valid: errors.length === 0, errors };
|
|
1717
1552
|
}
|
|
1718
|
-
|
|
1719
|
-
const
|
|
1720
|
-
const
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1553
|
+
function startFrameworkDev(command, port, spinner) {
|
|
1554
|
+
const [cmd, ...args] = command.split(" ");
|
|
1555
|
+
const portArgs = ["--port", port];
|
|
1556
|
+
spinner.text = `Starting ${command}...`;
|
|
1557
|
+
const child = spawn(cmd, [...args, ...portArgs], {
|
|
1558
|
+
cwd: process.cwd(),
|
|
1559
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
1560
|
+
shell: true,
|
|
1561
|
+
env: {
|
|
1562
|
+
...process.env,
|
|
1563
|
+
FORCE_COLOR: "1"
|
|
1724
1564
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
""
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
" * API function reference type for useQuery/useMutation.",
|
|
1735
|
-
' * The _name property contains the function path (e.g., "users.list").',
|
|
1736
|
-
" */",
|
|
1737
|
-
"export interface ApiFunction<TArgs = unknown, TResult = unknown> {",
|
|
1738
|
-
" _name: string;",
|
|
1739
|
-
" _args?: TArgs;",
|
|
1740
|
-
" _result?: TResult;",
|
|
1741
|
-
"}",
|
|
1742
|
-
""
|
|
1743
|
-
];
|
|
1744
|
-
if (moduleMap.size > 0) {
|
|
1745
|
-
for (const [moduleName, fnNames] of moduleMap) {
|
|
1746
|
-
const interfaceName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1) + "Api";
|
|
1747
|
-
lines.push(`export interface ${interfaceName} {`);
|
|
1748
|
-
for (const fnName of fnNames) {
|
|
1749
|
-
lines.push(` ${fnName}: ApiFunction;`);
|
|
1750
|
-
}
|
|
1751
|
-
lines.push("}");
|
|
1752
|
-
lines.push("");
|
|
1565
|
+
});
|
|
1566
|
+
let started = false;
|
|
1567
|
+
child.stdout?.on("data", (data) => {
|
|
1568
|
+
const output = data.toString();
|
|
1569
|
+
process.stdout.write(output);
|
|
1570
|
+
if (!started && (output.includes("Local:") || output.includes("ready") || output.includes("listening"))) {
|
|
1571
|
+
started = true;
|
|
1572
|
+
spinner.succeed(`Framework dev server running`);
|
|
1573
|
+
spinner.start("Watching for changes...");
|
|
1753
1574
|
}
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1575
|
+
});
|
|
1576
|
+
child.stderr?.on("data", (data) => {
|
|
1577
|
+
const output = data.toString();
|
|
1578
|
+
if (!output.includes("ExperimentalWarning") && !output.includes("--trace-warnings")) {
|
|
1579
|
+
process.stderr.write(chalk3.dim(output));
|
|
1758
1580
|
}
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1581
|
+
});
|
|
1582
|
+
child.on("error", (error) => {
|
|
1583
|
+
spinner.fail(`Failed to start framework: ${error.message}`);
|
|
1584
|
+
});
|
|
1585
|
+
child.on("exit", (code) => {
|
|
1586
|
+
if (code !== 0 && code !== null) {
|
|
1587
|
+
console.log(chalk3.yellow(`
|
|
1588
|
+
Framework process exited with code ${code}`));
|
|
1589
|
+
}
|
|
1590
|
+
});
|
|
1591
|
+
return child;
|
|
1592
|
+
}
|
|
1593
|
+
function cleanup() {
|
|
1594
|
+
if (frameworkProcess) {
|
|
1595
|
+
frameworkProcess.kill("SIGTERM");
|
|
1596
|
+
frameworkProcess = null;
|
|
1597
|
+
}
|
|
1598
|
+
if (schemaWatcher) {
|
|
1599
|
+
schemaWatcher.close();
|
|
1600
|
+
schemaWatcher = null;
|
|
1601
|
+
}
|
|
1602
|
+
if (functionsWatcher) {
|
|
1603
|
+
functionsWatcher.close();
|
|
1604
|
+
functionsWatcher = null;
|
|
1605
|
+
}
|
|
1779
1606
|
}
|
|
1780
|
-
async function
|
|
1607
|
+
async function devCommand(options) {
|
|
1781
1608
|
await requireAuth();
|
|
1782
|
-
const configPath =
|
|
1783
|
-
if (!await
|
|
1784
|
-
console.log(
|
|
1785
|
-
console.log(
|
|
1609
|
+
const configPath = path3.resolve(process.cwd(), "tether.config.ts");
|
|
1610
|
+
if (!await fs3.pathExists(configPath)) {
|
|
1611
|
+
console.log(chalk3.red("\nError: Not a Tether project"));
|
|
1612
|
+
console.log(chalk3.dim("Run `tthr init` to create a new project\n"));
|
|
1786
1613
|
process.exit(1);
|
|
1787
1614
|
}
|
|
1788
|
-
|
|
1789
|
-
const
|
|
1615
|
+
const config = await loadConfig();
|
|
1616
|
+
const port = options.port || String(config.dev?.port || 3001);
|
|
1617
|
+
const schemaPath = resolvePath(config.schema);
|
|
1618
|
+
const functionsDir = resolvePath(config.functions);
|
|
1619
|
+
const outputDir = resolvePath(config.output);
|
|
1620
|
+
const framework = config.framework || await detectFramework();
|
|
1621
|
+
const devCommand2 = config.dev?.command || getFrameworkDevCommand(framework);
|
|
1622
|
+
console.log(chalk3.bold("\n\u26A1 Starting Tether development server\n"));
|
|
1623
|
+
if (framework !== "unknown") {
|
|
1624
|
+
console.log(chalk3.dim(` Framework: ${framework}`));
|
|
1625
|
+
}
|
|
1626
|
+
if (devCommand2) {
|
|
1627
|
+
console.log(chalk3.dim(` Dev command: ${devCommand2}`));
|
|
1628
|
+
}
|
|
1629
|
+
console.log(chalk3.dim(` Schema: ${path3.relative(process.cwd(), schemaPath)}`));
|
|
1630
|
+
console.log(chalk3.dim(` Functions: ${path3.relative(process.cwd(), functionsDir)}`));
|
|
1631
|
+
console.log(chalk3.dim(` Output: ${path3.relative(process.cwd(), outputDir)}`));
|
|
1632
|
+
console.log("");
|
|
1633
|
+
const spinner = ora3("Initialising...").start();
|
|
1634
|
+
process.on("SIGINT", () => {
|
|
1635
|
+
spinner.stop();
|
|
1636
|
+
console.log(chalk3.yellow("\n\nShutting down...\n"));
|
|
1637
|
+
cleanup();
|
|
1638
|
+
process.exit(0);
|
|
1639
|
+
});
|
|
1640
|
+
process.on("SIGTERM", () => {
|
|
1641
|
+
cleanup();
|
|
1642
|
+
process.exit(0);
|
|
1643
|
+
});
|
|
1790
1644
|
try {
|
|
1791
|
-
|
|
1792
|
-
const
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
if (!await fs6.pathExists(schemaPath)) {
|
|
1796
|
-
spinner.fail("Schema file not found");
|
|
1797
|
-
console.log(chalk5.dim(`Expected: ${schemaPath}
|
|
1798
|
-
`));
|
|
1799
|
-
process.exit(1);
|
|
1645
|
+
spinner.text = "Validating schema...";
|
|
1646
|
+
const schemaValidation = await validateSchema(schemaPath);
|
|
1647
|
+
if (!schemaValidation.valid) {
|
|
1648
|
+
spinner.warn(`Schema validation warning: ${schemaValidation.error}`);
|
|
1800
1649
|
}
|
|
1801
|
-
|
|
1802
|
-
const
|
|
1803
|
-
if (
|
|
1804
|
-
spinner.warn("
|
|
1805
|
-
|
|
1806
|
-
|
|
1650
|
+
spinner.text = "Validating functions...";
|
|
1651
|
+
const functionsValidation = await validateFunctions(functionsDir);
|
|
1652
|
+
if (!functionsValidation.valid) {
|
|
1653
|
+
spinner.warn("Function validation warnings:");
|
|
1654
|
+
for (const error of functionsValidation.errors) {
|
|
1655
|
+
console.log(chalk3.yellow(` - ${error}`));
|
|
1656
|
+
}
|
|
1807
1657
|
}
|
|
1808
|
-
spinner.text =
|
|
1809
|
-
await
|
|
1810
|
-
await
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
)
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1658
|
+
spinner.text = "Generating types...";
|
|
1659
|
+
const { generateTypes } = await import("./generate-26HERX3T.js");
|
|
1660
|
+
await generateTypes({ silent: true });
|
|
1661
|
+
spinner.succeed("Types generated");
|
|
1662
|
+
spinner.start("Setting up file watchers...");
|
|
1663
|
+
if (await fs3.pathExists(schemaPath)) {
|
|
1664
|
+
schemaWatcher = watch(schemaPath, {
|
|
1665
|
+
ignoreInitial: true,
|
|
1666
|
+
awaitWriteFinish: {
|
|
1667
|
+
stabilityThreshold: 300,
|
|
1668
|
+
pollInterval: 100
|
|
1669
|
+
}
|
|
1670
|
+
});
|
|
1671
|
+
schemaWatcher.on("change", async () => {
|
|
1672
|
+
console.log(chalk3.cyan("\n Schema changed, validating..."));
|
|
1673
|
+
const validation = await validateSchema(schemaPath);
|
|
1674
|
+
if (!validation.valid) {
|
|
1675
|
+
console.log(chalk3.yellow(` Schema error: ${validation.error}`));
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
await runGenerate(spinner);
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
if (await fs3.pathExists(functionsDir)) {
|
|
1682
|
+
functionsWatcher = watch(path3.join(functionsDir, "**/*.{ts,js}"), {
|
|
1683
|
+
ignoreInitial: true,
|
|
1684
|
+
awaitWriteFinish: {
|
|
1685
|
+
stabilityThreshold: 300,
|
|
1686
|
+
pollInterval: 100
|
|
1687
|
+
}
|
|
1688
|
+
});
|
|
1689
|
+
functionsWatcher.on("all", async (event, filePath) => {
|
|
1690
|
+
const relativePath = path3.relative(functionsDir, filePath);
|
|
1691
|
+
console.log(chalk3.cyan(`
|
|
1692
|
+
Function ${event}: ${relativePath}`));
|
|
1693
|
+
const validation = await validateFunctions(functionsDir);
|
|
1694
|
+
if (!validation.valid) {
|
|
1695
|
+
for (const error of validation.errors) {
|
|
1696
|
+
console.log(chalk3.yellow(` Warning: ${error}`));
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
await runGenerate(spinner);
|
|
1700
|
+
});
|
|
1829
1701
|
}
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1702
|
+
spinner.succeed("File watchers ready");
|
|
1703
|
+
if (devCommand2 && !options.skipFramework) {
|
|
1704
|
+
spinner.start(`Starting ${framework} dev server...`);
|
|
1705
|
+
frameworkProcess = startFrameworkDev(devCommand2, port, spinner);
|
|
1706
|
+
} else {
|
|
1707
|
+
spinner.start("Watching for changes...");
|
|
1708
|
+
console.log("\n" + chalk3.cyan(` Tether dev ready`));
|
|
1709
|
+
console.log(chalk3.dim(` Watching schema and functions for changes`));
|
|
1710
|
+
console.log(chalk3.dim(` Press Ctrl+C to stop
|
|
1835
1711
|
`));
|
|
1712
|
+
}
|
|
1713
|
+
await new Promise(() => {
|
|
1714
|
+
});
|
|
1836
1715
|
} catch (error) {
|
|
1837
|
-
spinner.fail("Failed to
|
|
1838
|
-
console.error(
|
|
1716
|
+
spinner.fail("Failed to start dev server");
|
|
1717
|
+
console.error(chalk3.red(error instanceof Error ? error.message : "Unknown error"));
|
|
1718
|
+
cleanup();
|
|
1839
1719
|
process.exit(1);
|
|
1840
1720
|
}
|
|
1841
1721
|
}
|
|
1842
1722
|
|
|
1843
1723
|
// src/commands/login.ts
|
|
1844
|
-
import
|
|
1845
|
-
import
|
|
1724
|
+
import chalk4 from "chalk";
|
|
1725
|
+
import ora4 from "ora";
|
|
1846
1726
|
import os from "os";
|
|
1847
1727
|
var isDev3 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
1848
1728
|
var API_URL3 = isDev3 ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
|
|
1849
1729
|
var AUTH_URL = isDev3 ? "http://localhost:3000/cli" : "https://tthr.io/cli";
|
|
1850
1730
|
async function loginCommand() {
|
|
1851
|
-
console.log(
|
|
1731
|
+
console.log(chalk4.bold("\u26A1 Login to Tether\n"));
|
|
1852
1732
|
const existing = await getCredentials();
|
|
1853
1733
|
if (existing) {
|
|
1854
|
-
console.log(
|
|
1855
|
-
console.log(
|
|
1734
|
+
console.log(chalk4.green("\u2713") + ` Already logged in as ${chalk4.cyan(existing.email)}`);
|
|
1735
|
+
console.log(chalk4.dim("\nRun `tthr logout` to sign out\n"));
|
|
1856
1736
|
return;
|
|
1857
1737
|
}
|
|
1858
|
-
const spinner =
|
|
1738
|
+
const spinner = ora4("Generating authentication code...").start();
|
|
1859
1739
|
try {
|
|
1860
1740
|
const deviceCode = await requestDeviceCode();
|
|
1861
1741
|
spinner.stop();
|
|
1862
1742
|
const authUrl = `${AUTH_URL}/${deviceCode.userCode}`;
|
|
1863
|
-
console.log(
|
|
1864
|
-
console.log(
|
|
1743
|
+
console.log(chalk4.dim("Open this URL in your browser to authenticate:\n"));
|
|
1744
|
+
console.log(chalk4.cyan.bold(` ${authUrl}
|
|
1865
1745
|
`));
|
|
1866
|
-
console.log(
|
|
1746
|
+
console.log(chalk4.dim(`Your code: ${chalk4.white.bold(deviceCode.userCode)}
|
|
1867
1747
|
`));
|
|
1868
1748
|
const credentials = await pollForApproval(deviceCode.deviceCode, deviceCode.interval, deviceCode.expiresIn);
|
|
1869
1749
|
await saveCredentials(credentials);
|
|
1870
|
-
console.log(
|
|
1750
|
+
console.log(chalk4.green("\u2713") + ` Logged in as ${chalk4.cyan(credentials.email)}`);
|
|
1871
1751
|
console.log();
|
|
1872
1752
|
} catch (error) {
|
|
1873
1753
|
spinner.fail("Authentication failed");
|
|
1874
|
-
console.error(
|
|
1875
|
-
console.log(
|
|
1754
|
+
console.error(chalk4.red(error instanceof Error ? error.message : "Unknown error"));
|
|
1755
|
+
console.log(chalk4.dim("\nTry again or visit https://tthr.io/docs/cli for help\n"));
|
|
1876
1756
|
process.exit(1);
|
|
1877
1757
|
}
|
|
1878
1758
|
}
|
|
1879
1759
|
async function logoutCommand() {
|
|
1880
|
-
console.log(
|
|
1760
|
+
console.log(chalk4.bold("\n\u26A1 Logout from Tether\n"));
|
|
1881
1761
|
const credentials = await getCredentials();
|
|
1882
1762
|
if (!credentials) {
|
|
1883
|
-
console.log(
|
|
1763
|
+
console.log(chalk4.dim("Not logged in\n"));
|
|
1884
1764
|
return;
|
|
1885
1765
|
}
|
|
1886
|
-
const spinner =
|
|
1766
|
+
const spinner = ora4("Logging out...").start();
|
|
1887
1767
|
try {
|
|
1888
1768
|
await clearCredentials();
|
|
1889
|
-
spinner.succeed(`Logged out from ${
|
|
1769
|
+
spinner.succeed(`Logged out from ${chalk4.cyan(credentials.email)}`);
|
|
1890
1770
|
console.log();
|
|
1891
1771
|
} catch (error) {
|
|
1892
1772
|
spinner.fail("Logout failed");
|
|
1893
|
-
console.error(
|
|
1773
|
+
console.error(chalk4.red(error instanceof Error ? error.message : "Unknown error"));
|
|
1894
1774
|
process.exit(1);
|
|
1895
1775
|
}
|
|
1896
1776
|
}
|
|
1897
1777
|
async function whoamiCommand() {
|
|
1898
1778
|
const credentials = await getCredentials();
|
|
1899
1779
|
if (!credentials) {
|
|
1900
|
-
console.log(
|
|
1901
|
-
console.log(
|
|
1780
|
+
console.log(chalk4.dim("\nNot logged in"));
|
|
1781
|
+
console.log(chalk4.dim("Run `tthr login` to authenticate\n"));
|
|
1902
1782
|
return;
|
|
1903
1783
|
}
|
|
1904
|
-
console.log(
|
|
1905
|
-
console.log(` Email: ${
|
|
1906
|
-
console.log(` User ID: ${
|
|
1784
|
+
console.log(chalk4.bold("\n\u26A1 Current user\n"));
|
|
1785
|
+
console.log(` Email: ${chalk4.cyan(credentials.email)}`);
|
|
1786
|
+
console.log(` User ID: ${chalk4.dim(credentials.userId)}`);
|
|
1907
1787
|
if (credentials.expiresAt) {
|
|
1908
1788
|
const expiresAt = new Date(credentials.expiresAt);
|
|
1909
1789
|
const now = /* @__PURE__ */ new Date();
|
|
1910
1790
|
if (expiresAt > now) {
|
|
1911
|
-
console.log(` Session: ${
|
|
1791
|
+
console.log(` Session: ${chalk4.green("Active")}`);
|
|
1912
1792
|
} else {
|
|
1913
|
-
console.log(` Session: ${
|
|
1793
|
+
console.log(` Session: ${chalk4.yellow("Expired")}`);
|
|
1914
1794
|
}
|
|
1915
1795
|
}
|
|
1916
1796
|
console.log();
|
|
@@ -1952,20 +1832,20 @@ async function requestDeviceCode() {
|
|
|
1952
1832
|
async function pollForApproval(deviceCode, interval, expiresIn) {
|
|
1953
1833
|
const startTime = Date.now();
|
|
1954
1834
|
const expiresAt = startTime + expiresIn * 1e3;
|
|
1955
|
-
const spinner =
|
|
1835
|
+
const spinner = ora4("").start();
|
|
1956
1836
|
const updateCountdown = () => {
|
|
1957
1837
|
const remaining = Math.max(0, Math.ceil((expiresAt - Date.now()) / 1e3));
|
|
1958
1838
|
const mins = Math.floor(remaining / 60);
|
|
1959
1839
|
const secs = remaining % 60;
|
|
1960
1840
|
const timeStr = mins > 0 ? `${mins}:${secs.toString().padStart(2, "0")}` : `${secs}s`;
|
|
1961
|
-
spinner.text = `Waiting for approval... ${
|
|
1841
|
+
spinner.text = `Waiting for approval... ${chalk4.dim(`(${timeStr} remaining)`)}`;
|
|
1962
1842
|
};
|
|
1963
1843
|
updateCountdown();
|
|
1964
1844
|
const countdownInterval = setInterval(updateCountdown, 1e3);
|
|
1965
1845
|
try {
|
|
1966
1846
|
while (Date.now() < expiresAt) {
|
|
1967
1847
|
await sleep(interval * 1e3);
|
|
1968
|
-
spinner.text = `Checking... ${
|
|
1848
|
+
spinner.text = `Checking... ${chalk4.dim(`(${Math.ceil((expiresAt - Date.now()) / 1e3)}s remaining)`)}`;
|
|
1969
1849
|
const response = await fetch(`${API_URL3}/auth/device/${deviceCode}`, {
|
|
1970
1850
|
method: "GET"
|
|
1971
1851
|
}).catch(() => null);
|
|
@@ -1998,10 +1878,10 @@ function sleep(ms) {
|
|
|
1998
1878
|
}
|
|
1999
1879
|
|
|
2000
1880
|
// src/commands/update.ts
|
|
2001
|
-
import
|
|
2002
|
-
import
|
|
2003
|
-
import
|
|
2004
|
-
import
|
|
1881
|
+
import chalk5 from "chalk";
|
|
1882
|
+
import ora5 from "ora";
|
|
1883
|
+
import fs4 from "fs-extra";
|
|
1884
|
+
import path4 from "path";
|
|
2005
1885
|
import { execSync as execSync2 } from "child_process";
|
|
2006
1886
|
var TETHER_PACKAGES = [
|
|
2007
1887
|
"@tthr/client",
|
|
@@ -2013,13 +1893,13 @@ var TETHER_PACKAGES = [
|
|
|
2013
1893
|
];
|
|
2014
1894
|
function detectPackageManager() {
|
|
2015
1895
|
const cwd = process.cwd();
|
|
2016
|
-
if (
|
|
1896
|
+
if (fs4.existsSync(path4.join(cwd, "bun.lockb")) || fs4.existsSync(path4.join(cwd, "bun.lock"))) {
|
|
2017
1897
|
return "bun";
|
|
2018
1898
|
}
|
|
2019
|
-
if (
|
|
1899
|
+
if (fs4.existsSync(path4.join(cwd, "pnpm-lock.yaml"))) {
|
|
2020
1900
|
return "pnpm";
|
|
2021
1901
|
}
|
|
2022
|
-
if (
|
|
1902
|
+
if (fs4.existsSync(path4.join(cwd, "yarn.lock"))) {
|
|
2023
1903
|
return "yarn";
|
|
2024
1904
|
}
|
|
2025
1905
|
return "npm";
|
|
@@ -2049,14 +1929,14 @@ async function getLatestVersion2(packageName) {
|
|
|
2049
1929
|
}
|
|
2050
1930
|
}
|
|
2051
1931
|
async function updateCommand(options) {
|
|
2052
|
-
const packageJsonPath =
|
|
2053
|
-
if (!await
|
|
2054
|
-
console.log(
|
|
2055
|
-
console.log(
|
|
1932
|
+
const packageJsonPath = path4.resolve(process.cwd(), "package.json");
|
|
1933
|
+
if (!await fs4.pathExists(packageJsonPath)) {
|
|
1934
|
+
console.log(chalk5.red("\nError: No package.json found"));
|
|
1935
|
+
console.log(chalk5.dim("Make sure you're in the root of your project\n"));
|
|
2056
1936
|
process.exit(1);
|
|
2057
1937
|
}
|
|
2058
|
-
console.log(
|
|
2059
|
-
const packageJson = await
|
|
1938
|
+
console.log(chalk5.bold("\n\u26A1 Updating Tether packages\n"));
|
|
1939
|
+
const packageJson = await fs4.readJson(packageJsonPath);
|
|
2060
1940
|
const deps = packageJson.dependencies || {};
|
|
2061
1941
|
const devDeps = packageJson.devDependencies || {};
|
|
2062
1942
|
const installedDeps = [];
|
|
@@ -2069,13 +1949,13 @@ async function updateCommand(options) {
|
|
|
2069
1949
|
}
|
|
2070
1950
|
}
|
|
2071
1951
|
if (installedDeps.length === 0) {
|
|
2072
|
-
console.log(
|
|
2073
|
-
console.log(
|
|
1952
|
+
console.log(chalk5.yellow(" No updatable Tether packages found in package.json"));
|
|
1953
|
+
console.log(chalk5.dim(" Tether packages start with @tthr/ (workspace: dependencies are skipped)\n"));
|
|
2074
1954
|
return;
|
|
2075
1955
|
}
|
|
2076
|
-
console.log(
|
|
1956
|
+
console.log(chalk5.dim(` Found ${installedDeps.length} Tether package(s):
|
|
2077
1957
|
`));
|
|
2078
|
-
const spinner =
|
|
1958
|
+
const spinner = ora5("Checking for updates...").start();
|
|
2079
1959
|
const packagesToUpdate = [];
|
|
2080
1960
|
for (const pkg of installedDeps) {
|
|
2081
1961
|
const latestVersion = await getLatestVersion2(pkg.name);
|
|
@@ -2089,32 +1969,32 @@ async function updateCommand(options) {
|
|
|
2089
1969
|
isDev: pkg.isDev
|
|
2090
1970
|
});
|
|
2091
1971
|
} else {
|
|
2092
|
-
console.log(
|
|
1972
|
+
console.log(chalk5.dim(` ${pkg.name}@${currentClean} (up to date)`));
|
|
2093
1973
|
}
|
|
2094
1974
|
}
|
|
2095
1975
|
}
|
|
2096
1976
|
spinner.stop();
|
|
2097
1977
|
if (packagesToUpdate.length === 0) {
|
|
2098
|
-
console.log(
|
|
1978
|
+
console.log(chalk5.green("\n\u2713 All Tether packages are up to date\n"));
|
|
2099
1979
|
return;
|
|
2100
1980
|
}
|
|
2101
|
-
console.log(
|
|
1981
|
+
console.log(chalk5.cyan("\n Updates available:\n"));
|
|
2102
1982
|
for (const pkg of packagesToUpdate) {
|
|
2103
1983
|
console.log(
|
|
2104
|
-
|
|
1984
|
+
chalk5.white(` ${pkg.name}`) + chalk5.red(` ${pkg.current}`) + chalk5.dim(" \u2192 ") + chalk5.green(`${pkg.latest}`) + (pkg.isDev ? chalk5.dim(" (dev)") : "")
|
|
2105
1985
|
);
|
|
2106
1986
|
}
|
|
2107
1987
|
if (options.dryRun) {
|
|
2108
|
-
console.log(
|
|
1988
|
+
console.log(chalk5.yellow("\n Dry run - no packages were updated\n"));
|
|
2109
1989
|
return;
|
|
2110
1990
|
}
|
|
2111
1991
|
const pm = detectPackageManager();
|
|
2112
|
-
console.log(
|
|
1992
|
+
console.log(chalk5.dim(`
|
|
2113
1993
|
Using ${pm} to update packages...
|
|
2114
1994
|
`));
|
|
2115
1995
|
const depsToUpdate = packagesToUpdate.filter((p) => !p.isDev).map((p) => `${p.name}@latest`);
|
|
2116
1996
|
const devDepsToUpdate = packagesToUpdate.filter((p) => p.isDev).map((p) => `${p.name}@latest`);
|
|
2117
|
-
const updateSpinner =
|
|
1997
|
+
const updateSpinner = ora5("Installing updates...").start();
|
|
2118
1998
|
try {
|
|
2119
1999
|
if (depsToUpdate.length > 0) {
|
|
2120
2000
|
const cmd = getInstallCommand2(pm, depsToUpdate, false);
|
|
@@ -2125,43 +2005,43 @@ async function updateCommand(options) {
|
|
|
2125
2005
|
execSync2(cmd, { stdio: "pipe", cwd: process.cwd() });
|
|
2126
2006
|
}
|
|
2127
2007
|
updateSpinner.succeed("Packages updated successfully");
|
|
2128
|
-
console.log(
|
|
2008
|
+
console.log(chalk5.green(`
|
|
2129
2009
|
\u2713 Updated ${packagesToUpdate.length} package(s)
|
|
2130
2010
|
`));
|
|
2131
2011
|
} catch (error) {
|
|
2132
2012
|
updateSpinner.fail("Failed to update packages");
|
|
2133
|
-
console.error(
|
|
2013
|
+
console.error(chalk5.red(error instanceof Error ? error.message : "Unknown error"));
|
|
2134
2014
|
process.exit(1);
|
|
2135
2015
|
}
|
|
2136
2016
|
}
|
|
2137
2017
|
|
|
2138
2018
|
// src/commands/exec.ts
|
|
2139
|
-
import
|
|
2140
|
-
import
|
|
2141
|
-
import
|
|
2142
|
-
import
|
|
2019
|
+
import chalk6 from "chalk";
|
|
2020
|
+
import ora6 from "ora";
|
|
2021
|
+
import fs5 from "fs-extra";
|
|
2022
|
+
import path5 from "path";
|
|
2143
2023
|
var isDev4 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
2144
2024
|
var API_URL4 = isDev4 ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
|
|
2145
2025
|
async function execCommand(sql) {
|
|
2146
2026
|
if (!sql || sql.trim() === "") {
|
|
2147
|
-
console.log(
|
|
2148
|
-
console.log(
|
|
2027
|
+
console.log(chalk6.red("\nError: SQL query required"));
|
|
2028
|
+
console.log(chalk6.dim('Usage: tthr exec "SELECT * FROM table_name"\n'));
|
|
2149
2029
|
process.exit(1);
|
|
2150
2030
|
}
|
|
2151
2031
|
const credentials = await requireAuth();
|
|
2152
|
-
const envPath =
|
|
2032
|
+
const envPath = path5.resolve(process.cwd(), ".env");
|
|
2153
2033
|
let projectId;
|
|
2154
|
-
if (await
|
|
2155
|
-
const envContent = await
|
|
2034
|
+
if (await fs5.pathExists(envPath)) {
|
|
2035
|
+
const envContent = await fs5.readFile(envPath, "utf-8");
|
|
2156
2036
|
const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
|
|
2157
2037
|
projectId = match?.[1]?.trim();
|
|
2158
2038
|
}
|
|
2159
2039
|
if (!projectId) {
|
|
2160
|
-
console.log(
|
|
2161
|
-
console.log(
|
|
2040
|
+
console.log(chalk6.red("\nError: Project ID not found"));
|
|
2041
|
+
console.log(chalk6.dim("Make sure TETHER_PROJECT_ID is set in your .env file\n"));
|
|
2162
2042
|
process.exit(1);
|
|
2163
2043
|
}
|
|
2164
|
-
const spinner =
|
|
2044
|
+
const spinner = ora6("Executing query...").start();
|
|
2165
2045
|
try {
|
|
2166
2046
|
const response = await fetch(`${API_URL4}/projects/${projectId}/exec`, {
|
|
2167
2047
|
method: "POST",
|
|
@@ -2181,24 +2061,24 @@ async function execCommand(sql) {
|
|
|
2181
2061
|
if (result.columns && result.rows) {
|
|
2182
2062
|
console.log();
|
|
2183
2063
|
if (result.rows.length === 0) {
|
|
2184
|
-
console.log(
|
|
2064
|
+
console.log(chalk6.dim(" No rows returned"));
|
|
2185
2065
|
} else {
|
|
2186
|
-
console.log(
|
|
2187
|
-
console.log(
|
|
2066
|
+
console.log(chalk6.bold(" " + result.columns.join(" ")));
|
|
2067
|
+
console.log(chalk6.dim(" " + result.columns.map(() => "--------").join(" ")));
|
|
2188
2068
|
for (const row of result.rows) {
|
|
2189
2069
|
const values = result.columns.map((col) => {
|
|
2190
2070
|
const val = row[col];
|
|
2191
|
-
if (val === null) return
|
|
2071
|
+
if (val === null) return chalk6.dim("NULL");
|
|
2192
2072
|
if (typeof val === "object") return JSON.stringify(val);
|
|
2193
2073
|
return String(val);
|
|
2194
2074
|
});
|
|
2195
2075
|
console.log(" " + values.join(" "));
|
|
2196
2076
|
}
|
|
2197
|
-
console.log(
|
|
2077
|
+
console.log(chalk6.dim(`
|
|
2198
2078
|
${result.rows.length} row(s)`));
|
|
2199
2079
|
}
|
|
2200
2080
|
} else if (result.rowsAffected !== void 0) {
|
|
2201
|
-
console.log(
|
|
2081
|
+
console.log(chalk6.dim(`
|
|
2202
2082
|
${result.rowsAffected} row(s) affected`));
|
|
2203
2083
|
}
|
|
2204
2084
|
console.log();
|
|
@@ -2212,7 +2092,7 @@ async function execCommand(sql) {
|
|
|
2212
2092
|
var program = new Command();
|
|
2213
2093
|
program.name("tthr").description("Tether CLI - Realtime SQLite for modern applications").version("0.0.1");
|
|
2214
2094
|
program.command("init [name]").description("Create a new Tether project").option("-t, --template <template>", "Project template (vue, svelte, react, vanilla)", "vue").action(initCommand);
|
|
2215
|
-
program.command("dev").description("Start local development server with hot reload").option("-p, --port <port>", "Port to run on", "
|
|
2095
|
+
program.command("dev").description("Start local development server with hot reload").option("-p, --port <port>", "Port to run on").option("--skip-framework", "Only run Tether watchers, skip framework dev server").action(devCommand);
|
|
2216
2096
|
program.command("generate").alias("gen").description("Generate types from schema").action(generateCommand);
|
|
2217
2097
|
program.command("deploy").description("Deploy schema and functions to Tether").option("-s, --schema", "Deploy schema only").option("-f, --functions", "Deploy functions only").option("--dry-run", "Show what would be deployed without deploying").action(deployCommand);
|
|
2218
2098
|
program.command("login").description("Authenticate with Tether").action(loginCommand);
|