@tthr/cli 0.0.4 → 0.0.5
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/index.js +448 -470
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
|
-
import
|
|
8
|
-
import
|
|
7
|
+
import chalk3 from "chalk";
|
|
8
|
+
import ora2 from "ora";
|
|
9
9
|
import prompts from "prompts";
|
|
10
|
-
import
|
|
11
|
-
import
|
|
10
|
+
import fs3 from "fs-extra";
|
|
11
|
+
import path3 from "path";
|
|
12
12
|
import { execSync } from "child_process";
|
|
13
13
|
|
|
14
14
|
// src/utils/auth.ts
|
|
@@ -54,9 +54,284 @@ async function clearCredentials() {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// src/commands/
|
|
57
|
+
// src/commands/deploy.ts
|
|
58
|
+
import chalk2 from "chalk";
|
|
59
|
+
import ora from "ora";
|
|
60
|
+
import fs2 from "fs-extra";
|
|
61
|
+
import path2 from "path";
|
|
58
62
|
var isDev = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
59
63
|
var API_URL = isDev ? "http://localhost:3001/api/v1" : "https://tthr.io/api/v1";
|
|
64
|
+
async function deployCommand(options) {
|
|
65
|
+
const credentials = await requireAuth();
|
|
66
|
+
const configPath = path2.resolve(process.cwd(), "tether.config.ts");
|
|
67
|
+
if (!await fs2.pathExists(configPath)) {
|
|
68
|
+
console.log(chalk2.red("\nError: Not a Tether project"));
|
|
69
|
+
console.log(chalk2.dim("Run `tthr init` to create a new project\n"));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
const envPath = path2.resolve(process.cwd(), ".env");
|
|
73
|
+
let projectId;
|
|
74
|
+
if (await fs2.pathExists(envPath)) {
|
|
75
|
+
const envContent = await fs2.readFile(envPath, "utf-8");
|
|
76
|
+
const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
|
|
77
|
+
projectId = match?.[1]?.trim();
|
|
78
|
+
}
|
|
79
|
+
if (!projectId) {
|
|
80
|
+
console.log(chalk2.red("\nError: Project ID not found"));
|
|
81
|
+
console.log(chalk2.dim("Make sure TETHER_PROJECT_ID is set in your .env file\n"));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
console.log(chalk2.bold("\n\u26A1 Deploying to Tether\n"));
|
|
85
|
+
console.log(chalk2.dim(` Project: ${projectId}`));
|
|
86
|
+
console.log(chalk2.dim(` API: ${API_URL}
|
|
87
|
+
`));
|
|
88
|
+
const deploySchema = options.schema || !options.schema && !options.functions;
|
|
89
|
+
const deployFunctions = options.functions || !options.schema && !options.functions;
|
|
90
|
+
if (deploySchema) {
|
|
91
|
+
await deploySchemaToServer(projectId, credentials.accessToken, options.dryRun);
|
|
92
|
+
}
|
|
93
|
+
if (deployFunctions) {
|
|
94
|
+
await deployFunctionsToServer(projectId, credentials.accessToken, options.dryRun);
|
|
95
|
+
}
|
|
96
|
+
console.log(chalk2.green("\n\u2713 Deployment complete\n"));
|
|
97
|
+
}
|
|
98
|
+
async function deploySchemaToServer(projectId, token, dryRun) {
|
|
99
|
+
const spinner = ora("Reading schema...").start();
|
|
100
|
+
try {
|
|
101
|
+
const schemaPath = path2.resolve(process.cwd(), "tether", "schema.ts");
|
|
102
|
+
if (!await fs2.pathExists(schemaPath)) {
|
|
103
|
+
spinner.warn("No schema file found");
|
|
104
|
+
console.log(chalk2.dim(" Create tether/schema.ts to define your database schema\n"));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const schemaSource = await fs2.readFile(schemaPath, "utf-8");
|
|
108
|
+
const tables = parseSchema(schemaSource);
|
|
109
|
+
spinner.text = `Found ${tables.length} table(s)`;
|
|
110
|
+
if (dryRun) {
|
|
111
|
+
spinner.info("Dry run - would deploy schema:");
|
|
112
|
+
for (const table of tables) {
|
|
113
|
+
console.log(chalk2.dim(` - ${table.name}`));
|
|
114
|
+
}
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const sql = generateSchemaSQL(tables);
|
|
118
|
+
spinner.text = "Deploying schema...";
|
|
119
|
+
const response = await fetch(`${API_URL}/${projectId}/deploy/schema`, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
"Authorization": `Bearer ${token}`
|
|
124
|
+
},
|
|
125
|
+
body: JSON.stringify({
|
|
126
|
+
sql,
|
|
127
|
+
tables: tables.map((t) => t.name)
|
|
128
|
+
})
|
|
129
|
+
});
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
const error = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
132
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
133
|
+
}
|
|
134
|
+
spinner.succeed(`Schema deployed (${tables.length} table(s))`);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
spinner.fail("Failed to deploy schema");
|
|
137
|
+
console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
async function deployFunctionsToServer(projectId, token, dryRun) {
|
|
141
|
+
const spinner = ora("Reading functions...").start();
|
|
142
|
+
try {
|
|
143
|
+
const functionsDir = path2.resolve(process.cwd(), "tether", "functions");
|
|
144
|
+
if (!await fs2.pathExists(functionsDir)) {
|
|
145
|
+
spinner.warn("No functions directory found");
|
|
146
|
+
console.log(chalk2.dim(" Create tether/functions/ to define your API functions\n"));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const files = await fs2.readdir(functionsDir);
|
|
150
|
+
const tsFiles = files.filter((f) => f.endsWith(".ts"));
|
|
151
|
+
if (tsFiles.length === 0) {
|
|
152
|
+
spinner.info("No function files found");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const functions = [];
|
|
156
|
+
for (const file of tsFiles) {
|
|
157
|
+
const filePath = path2.join(functionsDir, file);
|
|
158
|
+
const source = await fs2.readFile(filePath, "utf-8");
|
|
159
|
+
const moduleName = file.replace(".ts", "");
|
|
160
|
+
const parsedFunctions = parseFunctions(moduleName, source);
|
|
161
|
+
functions.push(...parsedFunctions);
|
|
162
|
+
}
|
|
163
|
+
spinner.text = `Found ${functions.length} function(s)`;
|
|
164
|
+
if (dryRun) {
|
|
165
|
+
spinner.info("Dry run - would deploy functions:");
|
|
166
|
+
for (const fn of functions) {
|
|
167
|
+
const icon = fn.type === "query" ? "\u{1F50D}" : fn.type === "mutation" ? "\u270F\uFE0F" : "\u26A1";
|
|
168
|
+
console.log(chalk2.dim(` ${icon} ${fn.name} (${fn.type})`));
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
spinner.text = "Deploying functions...";
|
|
173
|
+
const response = await fetch(`${API_URL}/${projectId}/deploy/functions`, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers: {
|
|
176
|
+
"Content-Type": "application/json",
|
|
177
|
+
"Authorization": `Bearer ${token}`
|
|
178
|
+
},
|
|
179
|
+
body: JSON.stringify({
|
|
180
|
+
functions: functions.map((fn) => ({
|
|
181
|
+
name: fn.name,
|
|
182
|
+
type: fn.type,
|
|
183
|
+
source: fn.source
|
|
184
|
+
}))
|
|
185
|
+
})
|
|
186
|
+
});
|
|
187
|
+
if (!response.ok) {
|
|
188
|
+
const error = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
189
|
+
throw new Error(error.error || `HTTP ${response.status}`);
|
|
190
|
+
}
|
|
191
|
+
spinner.succeed(`Functions deployed (${functions.length} function(s))`);
|
|
192
|
+
const queries = functions.filter((f) => f.type === "query");
|
|
193
|
+
const mutations = functions.filter((f) => f.type === "mutation");
|
|
194
|
+
const actions = functions.filter((f) => f.type === "action");
|
|
195
|
+
if (queries.length > 0) {
|
|
196
|
+
console.log(chalk2.dim(`
|
|
197
|
+
Queries:`));
|
|
198
|
+
for (const fn of queries) {
|
|
199
|
+
console.log(chalk2.dim(` - ${fn.name}`));
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (mutations.length > 0) {
|
|
203
|
+
console.log(chalk2.dim(`
|
|
204
|
+
Mutations:`));
|
|
205
|
+
for (const fn of mutations) {
|
|
206
|
+
console.log(chalk2.dim(` - ${fn.name}`));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (actions.length > 0) {
|
|
210
|
+
console.log(chalk2.dim(`
|
|
211
|
+
Actions:`));
|
|
212
|
+
for (const fn of actions) {
|
|
213
|
+
console.log(chalk2.dim(` - ${fn.name}`));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
} catch (error) {
|
|
217
|
+
spinner.fail("Failed to deploy functions");
|
|
218
|
+
console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function parseSchema(source) {
|
|
222
|
+
const tables = [];
|
|
223
|
+
const schemaMatch = source.match(/defineSchema\s*\(\s*\{([\s\S]*)\}\s*\)/);
|
|
224
|
+
if (!schemaMatch) return tables;
|
|
225
|
+
const schemaContent = schemaMatch[1];
|
|
226
|
+
const tableRegex = /(\w+)\s*:\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/g;
|
|
227
|
+
let match;
|
|
228
|
+
while ((match = tableRegex.exec(schemaContent)) !== null) {
|
|
229
|
+
const tableName = match[1];
|
|
230
|
+
const columnsContent = match[2];
|
|
231
|
+
const columns = {};
|
|
232
|
+
const columnRegex = /(\w+)\s*:\s*(\w+)\s*\(\s*\)([^,\n]*)/g;
|
|
233
|
+
let colMatch;
|
|
234
|
+
while ((colMatch = columnRegex.exec(columnsContent)) !== null) {
|
|
235
|
+
const colName = colMatch[1];
|
|
236
|
+
const colType = colMatch[2];
|
|
237
|
+
const modifiers = colMatch[3];
|
|
238
|
+
columns[colName] = {
|
|
239
|
+
type: colType,
|
|
240
|
+
primaryKey: modifiers.includes(".primaryKey()"),
|
|
241
|
+
notNull: modifiers.includes(".notNull()"),
|
|
242
|
+
unique: modifiers.includes(".unique()"),
|
|
243
|
+
hasDefault: modifiers.includes(".default("),
|
|
244
|
+
references: modifiers.match(/\.references\s*\(\s*['"]([^'"]+)['"]\s*\)/)?.[1]
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
tables.push({ name: tableName, columns });
|
|
248
|
+
}
|
|
249
|
+
return tables;
|
|
250
|
+
}
|
|
251
|
+
function generateSchemaSQL(tables) {
|
|
252
|
+
const statements = [];
|
|
253
|
+
for (const table of tables) {
|
|
254
|
+
const columnDefs = [];
|
|
255
|
+
for (const [colName, colDef] of Object.entries(table.columns)) {
|
|
256
|
+
const def = colDef;
|
|
257
|
+
let sqlType = "TEXT";
|
|
258
|
+
switch (def.type) {
|
|
259
|
+
case "text":
|
|
260
|
+
sqlType = "TEXT";
|
|
261
|
+
break;
|
|
262
|
+
case "integer":
|
|
263
|
+
sqlType = "INTEGER";
|
|
264
|
+
break;
|
|
265
|
+
case "real":
|
|
266
|
+
sqlType = "REAL";
|
|
267
|
+
break;
|
|
268
|
+
case "blob":
|
|
269
|
+
sqlType = "BLOB";
|
|
270
|
+
break;
|
|
271
|
+
case "timestamp":
|
|
272
|
+
sqlType = "TEXT";
|
|
273
|
+
break;
|
|
274
|
+
case "boolean":
|
|
275
|
+
sqlType = "INTEGER";
|
|
276
|
+
break;
|
|
277
|
+
case "json":
|
|
278
|
+
sqlType = "TEXT";
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
let colSql = `${colName} ${sqlType}`;
|
|
282
|
+
if (def.primaryKey) colSql += " PRIMARY KEY";
|
|
283
|
+
if (def.notNull) colSql += " NOT NULL";
|
|
284
|
+
if (def.unique) colSql += " UNIQUE";
|
|
285
|
+
if (def.hasDefault && def.type === "timestamp") {
|
|
286
|
+
colSql += " DEFAULT (datetime('now'))";
|
|
287
|
+
}
|
|
288
|
+
if (def.references) {
|
|
289
|
+
const [refTable, refCol] = def.references.split(".");
|
|
290
|
+
colSql += ` REFERENCES ${refTable}(${refCol})`;
|
|
291
|
+
}
|
|
292
|
+
columnDefs.push(colSql);
|
|
293
|
+
}
|
|
294
|
+
statements.push(
|
|
295
|
+
`CREATE TABLE IF NOT EXISTS ${table.name} (
|
|
296
|
+
${columnDefs.join(",\n ")}
|
|
297
|
+
);`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
return statements.join("\n\n");
|
|
301
|
+
}
|
|
302
|
+
function parseFunctions(moduleName, source) {
|
|
303
|
+
const functions = [];
|
|
304
|
+
const exportRegex = /export\s+const\s+(\w+)\s*=\s*(query|mutation|action)\s*\(/g;
|
|
305
|
+
let match;
|
|
306
|
+
while ((match = exportRegex.exec(source)) !== null) {
|
|
307
|
+
const fnName = match[1];
|
|
308
|
+
const fnType = match[2];
|
|
309
|
+
const startIndex = match.index + match[0].length;
|
|
310
|
+
let depth = 1;
|
|
311
|
+
let endIndex = startIndex;
|
|
312
|
+
for (let i = startIndex; i < source.length && depth > 0; i++) {
|
|
313
|
+
const char = source[i];
|
|
314
|
+
if (char === "(" || char === "{") {
|
|
315
|
+
depth++;
|
|
316
|
+
} else if (char === ")" || char === "}") {
|
|
317
|
+
depth--;
|
|
318
|
+
}
|
|
319
|
+
endIndex = i;
|
|
320
|
+
}
|
|
321
|
+
const fnBody = source.slice(startIndex, endIndex);
|
|
322
|
+
functions.push({
|
|
323
|
+
name: `${moduleName}.${fnName}`,
|
|
324
|
+
type: fnType,
|
|
325
|
+
file: `${moduleName}.ts`,
|
|
326
|
+
source: `${fnType}(${fnBody})`
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
return functions;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/commands/init.ts
|
|
333
|
+
var isDev2 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
334
|
+
var API_URL2 = isDev2 ? "http://localhost:3001/api/v1" : "https://tthr.io/api/v1";
|
|
60
335
|
async function getLatestVersion(packageName) {
|
|
61
336
|
try {
|
|
62
337
|
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
|
|
@@ -71,7 +346,7 @@ async function getLatestVersion(packageName) {
|
|
|
71
346
|
}
|
|
72
347
|
async function initCommand(name, options) {
|
|
73
348
|
const credentials = await requireAuth();
|
|
74
|
-
console.log(
|
|
349
|
+
console.log(chalk3.bold("\n\u26A1 Create a new Tether project\n"));
|
|
75
350
|
let projectName = name;
|
|
76
351
|
if (!projectName) {
|
|
77
352
|
const response = await prompts({
|
|
@@ -82,7 +357,7 @@ async function initCommand(name, options) {
|
|
|
82
357
|
});
|
|
83
358
|
projectName = response.name;
|
|
84
359
|
if (!projectName) {
|
|
85
|
-
console.log(
|
|
360
|
+
console.log(chalk3.red("Project name is required"));
|
|
86
361
|
process.exit(1);
|
|
87
362
|
}
|
|
88
363
|
}
|
|
@@ -102,8 +377,8 @@ async function initCommand(name, options) {
|
|
|
102
377
|
});
|
|
103
378
|
template = response.template || "nuxt";
|
|
104
379
|
}
|
|
105
|
-
const projectPath =
|
|
106
|
-
if (await
|
|
380
|
+
const projectPath = path3.resolve(process.cwd(), projectName);
|
|
381
|
+
if (await fs3.pathExists(projectPath)) {
|
|
107
382
|
const { overwrite } = await prompts({
|
|
108
383
|
type: "confirm",
|
|
109
384
|
name: "overwrite",
|
|
@@ -111,16 +386,16 @@ async function initCommand(name, options) {
|
|
|
111
386
|
initial: false
|
|
112
387
|
});
|
|
113
388
|
if (!overwrite) {
|
|
114
|
-
console.log(
|
|
389
|
+
console.log(chalk3.yellow("Cancelled"));
|
|
115
390
|
process.exit(0);
|
|
116
391
|
}
|
|
117
|
-
await
|
|
392
|
+
await fs3.remove(projectPath);
|
|
118
393
|
}
|
|
119
|
-
const spinner =
|
|
394
|
+
const spinner = ora2("Creating project on Tether...").start();
|
|
120
395
|
let projectId;
|
|
121
396
|
let apiKey;
|
|
122
397
|
try {
|
|
123
|
-
const response = await fetch(`${
|
|
398
|
+
const response = await fetch(`${API_URL2}/projects`, {
|
|
124
399
|
method: "POST",
|
|
125
400
|
headers: {
|
|
126
401
|
"Content-Type": "application/json",
|
|
@@ -153,33 +428,39 @@ async function initCommand(name, options) {
|
|
|
153
428
|
await installTetherPackages(projectPath, template);
|
|
154
429
|
spinner.text = "Creating demo page...";
|
|
155
430
|
await createDemoPage(projectPath, template);
|
|
156
|
-
spinner.text = "Running initial migration...";
|
|
157
|
-
await runInitialMigration(projectPath, projectId, apiKey);
|
|
158
431
|
spinner.succeed("Project created successfully!");
|
|
159
|
-
console.log("
|
|
432
|
+
console.log("");
|
|
433
|
+
const originalCwd = process.cwd();
|
|
434
|
+
process.chdir(projectPath);
|
|
435
|
+
try {
|
|
436
|
+
await deployCommand({});
|
|
437
|
+
} finally {
|
|
438
|
+
process.chdir(originalCwd);
|
|
439
|
+
}
|
|
440
|
+
console.log("\n" + chalk3.green("\u2713") + " Project created successfully!\n");
|
|
160
441
|
console.log("Next steps:\n");
|
|
161
|
-
console.log(
|
|
442
|
+
console.log(chalk3.cyan(` cd ${projectName}`));
|
|
162
443
|
if (template === "vanilla") {
|
|
163
|
-
console.log(
|
|
444
|
+
console.log(chalk3.cyan(" npm install"));
|
|
164
445
|
}
|
|
165
|
-
console.log(
|
|
166
|
-
console.log("\n" +
|
|
446
|
+
console.log(chalk3.cyan(" npm run dev"));
|
|
447
|
+
console.log("\n" + chalk3.dim("For more information, visit https://tthr.io/docs\n"));
|
|
167
448
|
} catch (error) {
|
|
168
449
|
spinner.fail("Failed to create project");
|
|
169
|
-
console.error(
|
|
450
|
+
console.error(chalk3.red(error instanceof Error ? error.message : "Unknown error"));
|
|
170
451
|
process.exit(1);
|
|
171
452
|
}
|
|
172
453
|
}
|
|
173
454
|
async function scaffoldNuxtProject(projectName, projectPath, spinner) {
|
|
174
|
-
const parentDir =
|
|
455
|
+
const parentDir = path3.dirname(projectPath);
|
|
175
456
|
spinner.stop();
|
|
176
|
-
console.log(
|
|
457
|
+
console.log(chalk3.dim("\nRunning nuxi init...\n"));
|
|
177
458
|
try {
|
|
178
459
|
execSync(`npx nuxi@latest init ${projectName} --packageManager npm --gitInit false`, {
|
|
179
460
|
cwd: parentDir,
|
|
180
461
|
stdio: "inherit"
|
|
181
462
|
});
|
|
182
|
-
if (!await
|
|
463
|
+
if (!await fs3.pathExists(path3.join(projectPath, "package.json"))) {
|
|
183
464
|
throw new Error("Nuxt project was not created successfully - package.json missing");
|
|
184
465
|
}
|
|
185
466
|
spinner.start();
|
|
@@ -192,15 +473,15 @@ async function scaffoldNuxtProject(projectName, projectPath, spinner) {
|
|
|
192
473
|
}
|
|
193
474
|
}
|
|
194
475
|
async function scaffoldNextProject(projectName, projectPath, spinner) {
|
|
195
|
-
const parentDir =
|
|
476
|
+
const parentDir = path3.dirname(projectPath);
|
|
196
477
|
spinner.stop();
|
|
197
|
-
console.log(
|
|
478
|
+
console.log(chalk3.dim("\nRunning create-next-app...\n"));
|
|
198
479
|
try {
|
|
199
480
|
execSync(`npx create-next-app@latest ${projectName} --typescript --eslint --tailwind --src-dir --app --import-alias "@/*" --use-npm`, {
|
|
200
481
|
cwd: parentDir,
|
|
201
482
|
stdio: "inherit"
|
|
202
483
|
});
|
|
203
|
-
if (!await
|
|
484
|
+
if (!await fs3.pathExists(path3.join(projectPath, "package.json"))) {
|
|
204
485
|
throw new Error("Next.js project was not created successfully - package.json missing");
|
|
205
486
|
}
|
|
206
487
|
spinner.start();
|
|
@@ -213,15 +494,15 @@ async function scaffoldNextProject(projectName, projectPath, spinner) {
|
|
|
213
494
|
}
|
|
214
495
|
}
|
|
215
496
|
async function scaffoldSvelteKitProject(projectName, projectPath, spinner) {
|
|
216
|
-
const parentDir =
|
|
497
|
+
const parentDir = path3.dirname(projectPath);
|
|
217
498
|
spinner.stop();
|
|
218
|
-
console.log(
|
|
499
|
+
console.log(chalk3.dim("\nRunning sv create...\n"));
|
|
219
500
|
try {
|
|
220
501
|
execSync(`npx sv create ${projectName} --template minimal --types ts --no-add-ons --no-install`, {
|
|
221
502
|
cwd: parentDir,
|
|
222
503
|
stdio: "inherit"
|
|
223
504
|
});
|
|
224
|
-
if (!await
|
|
505
|
+
if (!await fs3.pathExists(path3.join(projectPath, "package.json"))) {
|
|
225
506
|
throw new Error("SvelteKit project was not created successfully - package.json missing");
|
|
226
507
|
}
|
|
227
508
|
spinner.start();
|
|
@@ -235,8 +516,8 @@ async function scaffoldSvelteKitProject(projectName, projectPath, spinner) {
|
|
|
235
516
|
}
|
|
236
517
|
async function scaffoldVanillaProject(projectName, projectPath, spinner) {
|
|
237
518
|
spinner.text = "Creating vanilla TypeScript project...";
|
|
238
|
-
await
|
|
239
|
-
await
|
|
519
|
+
await fs3.ensureDir(projectPath);
|
|
520
|
+
await fs3.ensureDir(path3.join(projectPath, "src"));
|
|
240
521
|
const packageJson = {
|
|
241
522
|
name: projectName,
|
|
242
523
|
version: "0.0.1",
|
|
@@ -253,9 +534,9 @@ async function scaffoldVanillaProject(projectName, projectPath, spinner) {
|
|
|
253
534
|
"@types/node": "latest"
|
|
254
535
|
}
|
|
255
536
|
};
|
|
256
|
-
await
|
|
257
|
-
await
|
|
258
|
-
|
|
537
|
+
await fs3.writeJSON(path3.join(projectPath, "package.json"), packageJson, { spaces: 2 });
|
|
538
|
+
await fs3.writeJSON(
|
|
539
|
+
path3.join(projectPath, "tsconfig.json"),
|
|
259
540
|
{
|
|
260
541
|
compilerOptions: {
|
|
261
542
|
target: "ES2022",
|
|
@@ -276,8 +557,8 @@ async function scaffoldVanillaProject(projectName, projectPath, spinner) {
|
|
|
276
557
|
},
|
|
277
558
|
{ spaces: 2 }
|
|
278
559
|
);
|
|
279
|
-
await
|
|
280
|
-
|
|
560
|
+
await fs3.writeFile(
|
|
561
|
+
path3.join(projectPath, "src", "index.ts"),
|
|
281
562
|
`import { createClient } from '@tthr/client';
|
|
282
563
|
|
|
283
564
|
const tether = createClient({
|
|
@@ -296,8 +577,8 @@ async function main() {
|
|
|
296
577
|
main().catch(console.error);
|
|
297
578
|
`
|
|
298
579
|
);
|
|
299
|
-
await
|
|
300
|
-
|
|
580
|
+
await fs3.writeFile(
|
|
581
|
+
path3.join(projectPath, ".gitignore"),
|
|
301
582
|
`node_modules/
|
|
302
583
|
dist/
|
|
303
584
|
.env
|
|
@@ -307,11 +588,11 @@ dist/
|
|
|
307
588
|
);
|
|
308
589
|
}
|
|
309
590
|
async function addTetherFiles(projectPath, projectId, apiKey, template) {
|
|
310
|
-
await
|
|
311
|
-
await
|
|
591
|
+
await fs3.ensureDir(path3.join(projectPath, "tether"));
|
|
592
|
+
await fs3.ensureDir(path3.join(projectPath, "tether", "functions"));
|
|
312
593
|
const configPackage = template === "nuxt" ? "@tthr/vue" : template === "next" ? "@tthr/react" : template === "sveltekit" ? "@tthr/svelte" : "@tthr/client";
|
|
313
|
-
await
|
|
314
|
-
|
|
594
|
+
await fs3.writeFile(
|
|
595
|
+
path3.join(projectPath, "tether.config.ts"),
|
|
315
596
|
`import { defineConfig } from '${configPackage}';
|
|
316
597
|
|
|
317
598
|
export default defineConfig({
|
|
@@ -332,8 +613,8 @@ export default defineConfig({
|
|
|
332
613
|
});
|
|
333
614
|
`
|
|
334
615
|
);
|
|
335
|
-
await
|
|
336
|
-
|
|
616
|
+
await fs3.writeFile(
|
|
617
|
+
path3.join(projectPath, "tether", "schema.ts"),
|
|
337
618
|
`import { defineSchema, text, integer, timestamp } from '@tthr/schema';
|
|
338
619
|
|
|
339
620
|
export default defineSchema({
|
|
@@ -358,8 +639,8 @@ export default defineSchema({
|
|
|
358
639
|
});
|
|
359
640
|
`
|
|
360
641
|
);
|
|
361
|
-
await
|
|
362
|
-
|
|
642
|
+
await fs3.writeFile(
|
|
643
|
+
path3.join(projectPath, "tether", "functions", "posts.ts"),
|
|
363
644
|
`import { query, mutation, z } from '@tthr/server';
|
|
364
645
|
|
|
365
646
|
// List all posts
|
|
@@ -446,30 +727,30 @@ export const remove = mutation({
|
|
|
446
727
|
);
|
|
447
728
|
const envContent = `# Tether Configuration
|
|
448
729
|
TETHER_PROJECT_ID=${projectId}
|
|
449
|
-
TETHER_URL=${
|
|
730
|
+
TETHER_URL=${isDev2 ? "http://localhost:3001" : "https://tthr.io"}
|
|
450
731
|
TETHER_API_KEY=${apiKey}
|
|
451
732
|
`;
|
|
452
|
-
const envPath =
|
|
453
|
-
if (await
|
|
454
|
-
const existing = await
|
|
455
|
-
await
|
|
733
|
+
const envPath = path3.join(projectPath, ".env");
|
|
734
|
+
if (await fs3.pathExists(envPath)) {
|
|
735
|
+
const existing = await fs3.readFile(envPath, "utf-8");
|
|
736
|
+
await fs3.writeFile(envPath, existing + "\n" + envContent);
|
|
456
737
|
} else {
|
|
457
|
-
await
|
|
738
|
+
await fs3.writeFile(envPath, envContent);
|
|
458
739
|
}
|
|
459
|
-
const gitignorePath =
|
|
740
|
+
const gitignorePath = path3.join(projectPath, ".gitignore");
|
|
460
741
|
const tetherGitignore = `
|
|
461
742
|
# Tether
|
|
462
743
|
_generated/
|
|
463
744
|
.env
|
|
464
745
|
.env.local
|
|
465
746
|
`;
|
|
466
|
-
if (await
|
|
467
|
-
const existing = await
|
|
747
|
+
if (await fs3.pathExists(gitignorePath)) {
|
|
748
|
+
const existing = await fs3.readFile(gitignorePath, "utf-8");
|
|
468
749
|
if (!existing.includes("_generated/")) {
|
|
469
|
-
await
|
|
750
|
+
await fs3.writeFile(gitignorePath, existing + tetherGitignore);
|
|
470
751
|
}
|
|
471
752
|
} else {
|
|
472
|
-
await
|
|
753
|
+
await fs3.writeFile(gitignorePath, tetherGitignore.trim());
|
|
473
754
|
}
|
|
474
755
|
}
|
|
475
756
|
async function configureFramework(projectPath, template) {
|
|
@@ -482,9 +763,9 @@ async function configureFramework(projectPath, template) {
|
|
|
482
763
|
}
|
|
483
764
|
}
|
|
484
765
|
async function configureNuxt(projectPath) {
|
|
485
|
-
const configPath =
|
|
486
|
-
if (!await
|
|
487
|
-
await
|
|
766
|
+
const configPath = path3.join(projectPath, "nuxt.config.ts");
|
|
767
|
+
if (!await fs3.pathExists(configPath)) {
|
|
768
|
+
await fs3.writeFile(
|
|
488
769
|
configPath,
|
|
489
770
|
`// https://nuxt.com/docs/api/configuration/nuxt-config
|
|
490
771
|
export default defineNuxtConfig({
|
|
@@ -502,7 +783,7 @@ export default defineNuxtConfig({
|
|
|
502
783
|
);
|
|
503
784
|
return;
|
|
504
785
|
}
|
|
505
|
-
let config = await
|
|
786
|
+
let config = await fs3.readFile(configPath, "utf-8");
|
|
506
787
|
if (config.includes("modules:")) {
|
|
507
788
|
config = config.replace(
|
|
508
789
|
/modules:\s*\[/,
|
|
@@ -529,11 +810,11 @@ export default defineNuxtConfig({
|
|
|
529
810
|
`
|
|
530
811
|
);
|
|
531
812
|
}
|
|
532
|
-
await
|
|
813
|
+
await fs3.writeFile(configPath, config);
|
|
533
814
|
}
|
|
534
815
|
async function configureNext(projectPath) {
|
|
535
|
-
const providersPath =
|
|
536
|
-
await
|
|
816
|
+
const providersPath = path3.join(projectPath, "src", "app", "providers.tsx");
|
|
817
|
+
await fs3.writeFile(
|
|
537
818
|
providersPath,
|
|
538
819
|
`'use client';
|
|
539
820
|
|
|
@@ -551,9 +832,9 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
551
832
|
}
|
|
552
833
|
`
|
|
553
834
|
);
|
|
554
|
-
const layoutPath =
|
|
555
|
-
if (await
|
|
556
|
-
let layout = await
|
|
835
|
+
const layoutPath = path3.join(projectPath, "src", "app", "layout.tsx");
|
|
836
|
+
if (await fs3.pathExists(layoutPath)) {
|
|
837
|
+
let layout = await fs3.readFile(layoutPath, "utf-8");
|
|
557
838
|
if (!layout.includes("import { Providers }")) {
|
|
558
839
|
layout = layout.replace(
|
|
559
840
|
/^(import.*\n)+/m,
|
|
@@ -566,25 +847,25 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
566
847
|
"$1<Providers>$2</Providers>$3"
|
|
567
848
|
);
|
|
568
849
|
}
|
|
569
|
-
await
|
|
850
|
+
await fs3.writeFile(layoutPath, layout);
|
|
570
851
|
}
|
|
571
|
-
const envLocalPath =
|
|
852
|
+
const envLocalPath = path3.join(projectPath, ".env.local");
|
|
572
853
|
const nextEnvContent = `# Tether Configuration (client-side)
|
|
573
854
|
NEXT_PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
|
|
574
855
|
NEXT_PUBLIC_TETHER_URL=\${TETHER_URL}
|
|
575
856
|
`;
|
|
576
|
-
if (await
|
|
577
|
-
const existing = await
|
|
578
|
-
await
|
|
857
|
+
if (await fs3.pathExists(envLocalPath)) {
|
|
858
|
+
const existing = await fs3.readFile(envLocalPath, "utf-8");
|
|
859
|
+
await fs3.writeFile(envLocalPath, existing + "\n" + nextEnvContent);
|
|
579
860
|
} else {
|
|
580
|
-
await
|
|
861
|
+
await fs3.writeFile(envLocalPath, nextEnvContent);
|
|
581
862
|
}
|
|
582
863
|
}
|
|
583
864
|
async function configureSvelteKit(projectPath) {
|
|
584
|
-
const libPath =
|
|
585
|
-
await
|
|
586
|
-
await
|
|
587
|
-
|
|
865
|
+
const libPath = path3.join(projectPath, "src", "lib");
|
|
866
|
+
await fs3.ensureDir(libPath);
|
|
867
|
+
await fs3.writeFile(
|
|
868
|
+
path3.join(libPath, "tether.ts"),
|
|
588
869
|
`import { createClient } from '@tthr/svelte';
|
|
589
870
|
import { PUBLIC_TETHER_PROJECT_ID, PUBLIC_TETHER_URL } from '$env/static/public';
|
|
590
871
|
|
|
@@ -594,15 +875,15 @@ export const tether = createClient({
|
|
|
594
875
|
});
|
|
595
876
|
`
|
|
596
877
|
);
|
|
597
|
-
const envPath =
|
|
878
|
+
const envPath = path3.join(projectPath, ".env");
|
|
598
879
|
const svelteEnvContent = `# Tether Configuration (public)
|
|
599
880
|
PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
|
|
600
881
|
PUBLIC_TETHER_URL=\${TETHER_URL}
|
|
601
882
|
`;
|
|
602
|
-
if (await
|
|
603
|
-
const existing = await
|
|
883
|
+
if (await fs3.pathExists(envPath)) {
|
|
884
|
+
const existing = await fs3.readFile(envPath, "utf-8");
|
|
604
885
|
if (!existing.includes("PUBLIC_TETHER_")) {
|
|
605
|
-
await
|
|
886
|
+
await fs3.writeFile(envPath, existing + "\n" + svelteEnvContent);
|
|
606
887
|
}
|
|
607
888
|
}
|
|
608
889
|
}
|
|
@@ -646,15 +927,15 @@ async function installTetherPackages(projectPath, template) {
|
|
|
646
927
|
stdio: "pipe"
|
|
647
928
|
});
|
|
648
929
|
} catch (error) {
|
|
649
|
-
console.warn(
|
|
930
|
+
console.warn(chalk3.yellow("\nNote: Some Tether packages may not be published yet."));
|
|
650
931
|
}
|
|
651
932
|
}
|
|
652
933
|
async function createDemoPage(projectPath, template) {
|
|
653
934
|
if (template === "nuxt") {
|
|
654
|
-
const nuxt4AppVuePath =
|
|
655
|
-
const nuxt3AppVuePath =
|
|
656
|
-
const appVuePath = await
|
|
657
|
-
await
|
|
935
|
+
const nuxt4AppVuePath = path3.join(projectPath, "app", "app.vue");
|
|
936
|
+
const nuxt3AppVuePath = path3.join(projectPath, "app.vue");
|
|
937
|
+
const appVuePath = await fs3.pathExists(path3.join(projectPath, "app")) ? nuxt4AppVuePath : nuxt3AppVuePath;
|
|
938
|
+
await fs3.writeFile(
|
|
658
939
|
appVuePath,
|
|
659
940
|
`<template>
|
|
660
941
|
<TetherWelcome />
|
|
@@ -662,8 +943,8 @@ async function createDemoPage(projectPath, template) {
|
|
|
662
943
|
`
|
|
663
944
|
);
|
|
664
945
|
} else if (template === "next") {
|
|
665
|
-
const pagePath =
|
|
666
|
-
await
|
|
946
|
+
const pagePath = path3.join(projectPath, "src", "app", "page.tsx");
|
|
947
|
+
await fs3.writeFile(
|
|
667
948
|
pagePath,
|
|
668
949
|
`'use client';
|
|
669
950
|
|
|
@@ -773,8 +1054,8 @@ export default function Home() {
|
|
|
773
1054
|
`
|
|
774
1055
|
);
|
|
775
1056
|
} else if (template === "sveltekit") {
|
|
776
|
-
const pagePath =
|
|
777
|
-
await
|
|
1057
|
+
const pagePath = path3.join(projectPath, "src", "routes", "+page.svelte");
|
|
1058
|
+
await fs3.writeFile(
|
|
778
1059
|
pagePath,
|
|
779
1060
|
`<script lang="ts">
|
|
780
1061
|
import { onMount } from 'svelte';
|
|
@@ -1038,109 +1319,69 @@ export default function Home() {
|
|
|
1038
1319
|
);
|
|
1039
1320
|
}
|
|
1040
1321
|
}
|
|
1041
|
-
async function runInitialMigration(projectPath, projectId, apiKey) {
|
|
1042
|
-
const migrationSql = `
|
|
1043
|
-
CREATE TABLE IF NOT EXISTS posts (
|
|
1044
|
-
id TEXT PRIMARY KEY,
|
|
1045
|
-
title TEXT NOT NULL,
|
|
1046
|
-
content TEXT,
|
|
1047
|
-
authorId TEXT NOT NULL,
|
|
1048
|
-
createdAt TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1049
|
-
updatedAt TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1050
|
-
);
|
|
1051
|
-
|
|
1052
|
-
CREATE TABLE IF NOT EXISTS comments (
|
|
1053
|
-
id TEXT PRIMARY KEY,
|
|
1054
|
-
postId TEXT NOT NULL REFERENCES posts(id),
|
|
1055
|
-
content TEXT NOT NULL,
|
|
1056
|
-
authorId TEXT NOT NULL,
|
|
1057
|
-
createdAt TEXT NOT NULL DEFAULT (datetime('now'))
|
|
1058
|
-
);
|
|
1059
|
-
|
|
1060
|
-
CREATE INDEX IF NOT EXISTS idx_posts_authorId ON posts(authorId);
|
|
1061
|
-
CREATE INDEX IF NOT EXISTS idx_posts_createdAt ON posts(createdAt);
|
|
1062
|
-
CREATE INDEX IF NOT EXISTS idx_comments_postId ON comments(postId);
|
|
1063
|
-
`;
|
|
1064
|
-
try {
|
|
1065
|
-
const response = await fetch(`${API_URL}/${projectId}/mutation`, {
|
|
1066
|
-
method: "POST",
|
|
1067
|
-
headers: {
|
|
1068
|
-
"Content-Type": "application/json",
|
|
1069
|
-
"Authorization": `Bearer ${apiKey}`
|
|
1070
|
-
},
|
|
1071
|
-
body: JSON.stringify({
|
|
1072
|
-
function: "_migrate",
|
|
1073
|
-
args: { sql: migrationSql }
|
|
1074
|
-
})
|
|
1075
|
-
});
|
|
1076
|
-
if (!response.ok) {
|
|
1077
|
-
}
|
|
1078
|
-
} catch {
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
1322
|
|
|
1082
1323
|
// src/commands/dev.ts
|
|
1083
|
-
import
|
|
1084
|
-
import
|
|
1085
|
-
import
|
|
1086
|
-
import
|
|
1324
|
+
import chalk4 from "chalk";
|
|
1325
|
+
import ora3 from "ora";
|
|
1326
|
+
import fs4 from "fs-extra";
|
|
1327
|
+
import path4 from "path";
|
|
1087
1328
|
async function devCommand(options) {
|
|
1088
1329
|
await requireAuth();
|
|
1089
|
-
const configPath =
|
|
1090
|
-
if (!await
|
|
1091
|
-
console.log(
|
|
1092
|
-
console.log(
|
|
1330
|
+
const configPath = path4.resolve(process.cwd(), "tether.config.ts");
|
|
1331
|
+
if (!await fs4.pathExists(configPath)) {
|
|
1332
|
+
console.log(chalk4.red("\nError: Not a Tether project"));
|
|
1333
|
+
console.log(chalk4.dim("Run `tthr init` to create a new project\n"));
|
|
1093
1334
|
process.exit(1);
|
|
1094
1335
|
}
|
|
1095
|
-
console.log(
|
|
1096
|
-
const spinner =
|
|
1336
|
+
console.log(chalk4.bold("\n\u26A1 Starting Tether development server\n"));
|
|
1337
|
+
const spinner = ora3("Starting server...").start();
|
|
1097
1338
|
try {
|
|
1098
1339
|
spinner.succeed(`Development server running on port ${options.port}`);
|
|
1099
|
-
console.log("\n" +
|
|
1100
|
-
console.log(
|
|
1340
|
+
console.log("\n" + chalk4.cyan(` Local: http://localhost:${options.port}`));
|
|
1341
|
+
console.log(chalk4.cyan(` WebSocket: ws://localhost:${options.port}/ws
|
|
1101
1342
|
`));
|
|
1102
|
-
console.log(
|
|
1343
|
+
console.log(chalk4.dim(" Press Ctrl+C to stop\n"));
|
|
1103
1344
|
process.on("SIGINT", () => {
|
|
1104
|
-
console.log(
|
|
1345
|
+
console.log(chalk4.yellow("\n\nShutting down...\n"));
|
|
1105
1346
|
process.exit(0);
|
|
1106
1347
|
});
|
|
1107
1348
|
await new Promise(() => {
|
|
1108
1349
|
});
|
|
1109
1350
|
} catch (error) {
|
|
1110
1351
|
spinner.fail("Failed to start server");
|
|
1111
|
-
console.error(
|
|
1352
|
+
console.error(chalk4.red(error instanceof Error ? error.message : "Unknown error"));
|
|
1112
1353
|
process.exit(1);
|
|
1113
1354
|
}
|
|
1114
1355
|
}
|
|
1115
1356
|
|
|
1116
1357
|
// src/commands/generate.ts
|
|
1117
|
-
import
|
|
1118
|
-
import
|
|
1119
|
-
import
|
|
1120
|
-
import
|
|
1358
|
+
import chalk5 from "chalk";
|
|
1359
|
+
import ora4 from "ora";
|
|
1360
|
+
import fs5 from "fs-extra";
|
|
1361
|
+
import path5 from "path";
|
|
1121
1362
|
async function generateCommand() {
|
|
1122
1363
|
await requireAuth();
|
|
1123
|
-
const configPath =
|
|
1124
|
-
if (!await
|
|
1125
|
-
console.log(
|
|
1126
|
-
console.log(
|
|
1364
|
+
const configPath = path5.resolve(process.cwd(), "tether.config.ts");
|
|
1365
|
+
if (!await fs5.pathExists(configPath)) {
|
|
1366
|
+
console.log(chalk5.red("\nError: Not a Tether project"));
|
|
1367
|
+
console.log(chalk5.dim("Run `tthr init` to create a new project\n"));
|
|
1127
1368
|
process.exit(1);
|
|
1128
1369
|
}
|
|
1129
|
-
console.log(
|
|
1130
|
-
const spinner =
|
|
1370
|
+
console.log(chalk5.bold("\n\u26A1 Generating types from schema\n"));
|
|
1371
|
+
const spinner = ora4("Reading schema...").start();
|
|
1131
1372
|
try {
|
|
1132
|
-
const schemaPath =
|
|
1133
|
-
const outputDir =
|
|
1134
|
-
if (!await
|
|
1373
|
+
const schemaPath = path5.resolve(process.cwd(), "tether", "schema.ts");
|
|
1374
|
+
const outputDir = path5.resolve(process.cwd(), "_generated");
|
|
1375
|
+
if (!await fs5.pathExists(schemaPath)) {
|
|
1135
1376
|
spinner.fail("Schema file not found");
|
|
1136
|
-
console.log(
|
|
1377
|
+
console.log(chalk5.dim(`Expected: ${schemaPath}
|
|
1137
1378
|
`));
|
|
1138
1379
|
process.exit(1);
|
|
1139
1380
|
}
|
|
1140
1381
|
spinner.text = "Generating types...";
|
|
1141
|
-
await
|
|
1142
|
-
await
|
|
1143
|
-
|
|
1382
|
+
await fs5.ensureDir(outputDir);
|
|
1383
|
+
await fs5.writeFile(
|
|
1384
|
+
path5.join(outputDir, "db.ts"),
|
|
1144
1385
|
`// Auto-generated by Tether CLI - do not edit manually
|
|
1145
1386
|
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1146
1387
|
|
|
@@ -1172,8 +1413,8 @@ export interface Schema {
|
|
|
1172
1413
|
export declare const db: TetherDatabase<Schema>;
|
|
1173
1414
|
`
|
|
1174
1415
|
);
|
|
1175
|
-
await
|
|
1176
|
-
|
|
1416
|
+
await fs5.writeFile(
|
|
1417
|
+
path5.join(outputDir, "api.ts"),
|
|
1177
1418
|
`// Auto-generated by Tether CLI - do not edit manually
|
|
1178
1419
|
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1179
1420
|
|
|
@@ -1196,40 +1437,40 @@ export interface Api {
|
|
|
1196
1437
|
export declare const tether: TetherClient<Api>;
|
|
1197
1438
|
`
|
|
1198
1439
|
);
|
|
1199
|
-
await
|
|
1200
|
-
|
|
1440
|
+
await fs5.writeFile(
|
|
1441
|
+
path5.join(outputDir, "index.ts"),
|
|
1201
1442
|
`// Auto-generated by Tether CLI - do not edit manually
|
|
1202
1443
|
export * from './db';
|
|
1203
1444
|
export * from './api';
|
|
1204
1445
|
`
|
|
1205
1446
|
);
|
|
1206
1447
|
spinner.succeed("Types generated");
|
|
1207
|
-
console.log("\n" +
|
|
1208
|
-
console.log(
|
|
1209
|
-
console.log(
|
|
1210
|
-
console.log(
|
|
1448
|
+
console.log("\n" + chalk5.green("\u2713") + " Generated files:");
|
|
1449
|
+
console.log(chalk5.dim(" _generated/db.ts"));
|
|
1450
|
+
console.log(chalk5.dim(" _generated/api.ts"));
|
|
1451
|
+
console.log(chalk5.dim(" _generated/index.ts\n"));
|
|
1211
1452
|
} catch (error) {
|
|
1212
1453
|
spinner.fail("Failed to generate types");
|
|
1213
|
-
console.error(
|
|
1454
|
+
console.error(chalk5.red(error instanceof Error ? error.message : "Unknown error"));
|
|
1214
1455
|
process.exit(1);
|
|
1215
1456
|
}
|
|
1216
1457
|
}
|
|
1217
1458
|
|
|
1218
1459
|
// src/commands/migrate.ts
|
|
1219
|
-
import
|
|
1220
|
-
import
|
|
1221
|
-
import
|
|
1222
|
-
import
|
|
1460
|
+
import chalk6 from "chalk";
|
|
1461
|
+
import ora5 from "ora";
|
|
1462
|
+
import fs6 from "fs-extra";
|
|
1463
|
+
import path6 from "path";
|
|
1223
1464
|
import prompts2 from "prompts";
|
|
1224
1465
|
async function migrateCommand(action, options) {
|
|
1225
1466
|
await requireAuth();
|
|
1226
|
-
const configPath =
|
|
1227
|
-
if (!await
|
|
1228
|
-
console.log(
|
|
1229
|
-
console.log(
|
|
1467
|
+
const configPath = path6.resolve(process.cwd(), "tether.config.ts");
|
|
1468
|
+
if (!await fs6.pathExists(configPath)) {
|
|
1469
|
+
console.log(chalk6.red("\nError: Not a Tether project"));
|
|
1470
|
+
console.log(chalk6.dim("Run `tthr init` to create a new project\n"));
|
|
1230
1471
|
process.exit(1);
|
|
1231
1472
|
}
|
|
1232
|
-
const migrationsDir =
|
|
1473
|
+
const migrationsDir = path6.resolve(process.cwd(), "tether", "migrations");
|
|
1233
1474
|
switch (action) {
|
|
1234
1475
|
case "create":
|
|
1235
1476
|
await createMigration(migrationsDir, options.name);
|
|
@@ -1247,7 +1488,7 @@ async function migrateCommand(action, options) {
|
|
|
1247
1488
|
}
|
|
1248
1489
|
}
|
|
1249
1490
|
async function createMigration(migrationsDir, name) {
|
|
1250
|
-
console.log(
|
|
1491
|
+
console.log(chalk6.bold("\n\u26A1 Create migration\n"));
|
|
1251
1492
|
let migrationName = name;
|
|
1252
1493
|
if (!migrationName) {
|
|
1253
1494
|
const response = await prompts2({
|
|
@@ -1258,17 +1499,17 @@ async function createMigration(migrationsDir, name) {
|
|
|
1258
1499
|
});
|
|
1259
1500
|
migrationName = response.name;
|
|
1260
1501
|
if (!migrationName) {
|
|
1261
|
-
console.log(
|
|
1502
|
+
console.log(chalk6.red("Migration name is required"));
|
|
1262
1503
|
process.exit(1);
|
|
1263
1504
|
}
|
|
1264
1505
|
}
|
|
1265
|
-
const spinner =
|
|
1506
|
+
const spinner = ora5("Creating migration...").start();
|
|
1266
1507
|
try {
|
|
1267
|
-
await
|
|
1508
|
+
await fs6.ensureDir(migrationsDir);
|
|
1268
1509
|
const timestamp = Date.now();
|
|
1269
1510
|
const filename = `${timestamp}_${migrationName}.sql`;
|
|
1270
|
-
const filepath =
|
|
1271
|
-
await
|
|
1511
|
+
const filepath = path6.join(migrationsDir, filename);
|
|
1512
|
+
await fs6.writeFile(
|
|
1272
1513
|
filepath,
|
|
1273
1514
|
`-- Migration: ${migrationName}
|
|
1274
1515
|
-- Created at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
@@ -1289,335 +1530,72 @@ async function createMigration(migrationsDir, name) {
|
|
|
1289
1530
|
`
|
|
1290
1531
|
);
|
|
1291
1532
|
spinner.succeed("Migration created");
|
|
1292
|
-
console.log(
|
|
1533
|
+
console.log(chalk6.dim(`
|
|
1293
1534
|
${filepath}
|
|
1294
1535
|
`));
|
|
1295
1536
|
} catch (error) {
|
|
1296
1537
|
spinner.fail("Failed to create migration");
|
|
1297
|
-
console.error(
|
|
1538
|
+
console.error(chalk6.red(error instanceof Error ? error.message : "Unknown error"));
|
|
1298
1539
|
process.exit(1);
|
|
1299
1540
|
}
|
|
1300
1541
|
}
|
|
1301
1542
|
async function runMigrations(migrationsDir, direction) {
|
|
1302
|
-
console.log(
|
|
1543
|
+
console.log(chalk6.bold(`
|
|
1303
1544
|
\u26A1 Running migrations (${direction})
|
|
1304
1545
|
`));
|
|
1305
|
-
const spinner =
|
|
1546
|
+
const spinner = ora5("Checking migrations...").start();
|
|
1306
1547
|
try {
|
|
1307
|
-
if (!await
|
|
1548
|
+
if (!await fs6.pathExists(migrationsDir)) {
|
|
1308
1549
|
spinner.info("No migrations directory found");
|
|
1309
|
-
console.log(
|
|
1550
|
+
console.log(chalk6.dim("\nRun `tthr migrate create` to create your first migration\n"));
|
|
1310
1551
|
return;
|
|
1311
1552
|
}
|
|
1312
|
-
const files = await
|
|
1553
|
+
const files = await fs6.readdir(migrationsDir);
|
|
1313
1554
|
const migrations = files.filter((f) => f.endsWith(".sql")).sort((a, b) => direction === "up" ? a.localeCompare(b) : b.localeCompare(a));
|
|
1314
1555
|
if (migrations.length === 0) {
|
|
1315
1556
|
spinner.info("No migrations found");
|
|
1316
|
-
console.log(
|
|
1557
|
+
console.log(chalk6.dim("\nRun `tthr migrate create` to create your first migration\n"));
|
|
1317
1558
|
return;
|
|
1318
1559
|
}
|
|
1319
1560
|
spinner.text = `Found ${migrations.length} migration(s)`;
|
|
1320
1561
|
spinner.succeed(`Would run ${migrations.length} migration(s)`);
|
|
1321
1562
|
console.log("\nMigrations to run:");
|
|
1322
1563
|
for (const migration of migrations) {
|
|
1323
|
-
console.log(
|
|
1564
|
+
console.log(chalk6.dim(` ${migration}`));
|
|
1324
1565
|
}
|
|
1325
|
-
console.log(
|
|
1566
|
+
console.log(chalk6.yellow("\nNote: Migration execution not yet implemented\n"));
|
|
1326
1567
|
} catch (error) {
|
|
1327
1568
|
spinner.fail("Failed to run migrations");
|
|
1328
|
-
console.error(
|
|
1569
|
+
console.error(chalk6.red(error instanceof Error ? error.message : "Unknown error"));
|
|
1329
1570
|
process.exit(1);
|
|
1330
1571
|
}
|
|
1331
1572
|
}
|
|
1332
1573
|
async function showStatus(migrationsDir) {
|
|
1333
|
-
console.log(
|
|
1574
|
+
console.log(chalk6.bold("\n\u26A1 Migration status\n"));
|
|
1334
1575
|
try {
|
|
1335
|
-
if (!await
|
|
1336
|
-
console.log(
|
|
1337
|
-
console.log(
|
|
1576
|
+
if (!await fs6.pathExists(migrationsDir)) {
|
|
1577
|
+
console.log(chalk6.dim("No migrations directory found"));
|
|
1578
|
+
console.log(chalk6.dim("\nRun `tthr migrate create` to create your first migration\n"));
|
|
1338
1579
|
return;
|
|
1339
1580
|
}
|
|
1340
|
-
const files = await
|
|
1581
|
+
const files = await fs6.readdir(migrationsDir);
|
|
1341
1582
|
const migrations = files.filter((f) => f.endsWith(".sql")).sort();
|
|
1342
1583
|
if (migrations.length === 0) {
|
|
1343
|
-
console.log(
|
|
1344
|
-
console.log(
|
|
1584
|
+
console.log(chalk6.dim("No migrations found"));
|
|
1585
|
+
console.log(chalk6.dim("\nRun `tthr migrate create` to create your first migration\n"));
|
|
1345
1586
|
return;
|
|
1346
1587
|
}
|
|
1347
1588
|
console.log(`Found ${migrations.length} migration(s):
|
|
1348
1589
|
`);
|
|
1349
1590
|
for (const migration of migrations) {
|
|
1350
|
-
console.log(
|
|
1591
|
+
console.log(chalk6.yellow(" \u25CB") + ` ${migration} ${chalk6.dim("(pending)")}`);
|
|
1351
1592
|
}
|
|
1352
1593
|
console.log();
|
|
1353
1594
|
} catch (error) {
|
|
1354
|
-
console.error(chalk5.red(error instanceof Error ? error.message : "Unknown error"));
|
|
1355
|
-
process.exit(1);
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
// src/commands/deploy.ts
|
|
1360
|
-
import chalk6 from "chalk";
|
|
1361
|
-
import ora5 from "ora";
|
|
1362
|
-
import fs6 from "fs-extra";
|
|
1363
|
-
import path6 from "path";
|
|
1364
|
-
var isDev2 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
1365
|
-
var API_URL2 = isDev2 ? "http://localhost:3001/api/v1" : "https://tthr.io/api/v1";
|
|
1366
|
-
async function deployCommand(options) {
|
|
1367
|
-
const credentials = await requireAuth();
|
|
1368
|
-
const configPath = path6.resolve(process.cwd(), "tether.config.ts");
|
|
1369
|
-
if (!await fs6.pathExists(configPath)) {
|
|
1370
|
-
console.log(chalk6.red("\nError: Not a Tether project"));
|
|
1371
|
-
console.log(chalk6.dim("Run `tthr init` to create a new project\n"));
|
|
1372
|
-
process.exit(1);
|
|
1373
|
-
}
|
|
1374
|
-
const envPath = path6.resolve(process.cwd(), ".env");
|
|
1375
|
-
let projectId;
|
|
1376
|
-
if (await fs6.pathExists(envPath)) {
|
|
1377
|
-
const envContent = await fs6.readFile(envPath, "utf-8");
|
|
1378
|
-
const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
|
|
1379
|
-
projectId = match?.[1]?.trim();
|
|
1380
|
-
}
|
|
1381
|
-
if (!projectId) {
|
|
1382
|
-
console.log(chalk6.red("\nError: Project ID not found"));
|
|
1383
|
-
console.log(chalk6.dim("Make sure TETHER_PROJECT_ID is set in your .env file\n"));
|
|
1384
|
-
process.exit(1);
|
|
1385
|
-
}
|
|
1386
|
-
console.log(chalk6.bold("\n\u26A1 Deploying to Tether\n"));
|
|
1387
|
-
console.log(chalk6.dim(` Project: ${projectId}`));
|
|
1388
|
-
console.log(chalk6.dim(` API: ${API_URL2}
|
|
1389
|
-
`));
|
|
1390
|
-
const deploySchema = options.schema || !options.schema && !options.functions;
|
|
1391
|
-
const deployFunctions = options.functions || !options.schema && !options.functions;
|
|
1392
|
-
if (deploySchema) {
|
|
1393
|
-
await deploySchemaToServer(projectId, credentials.accessToken, options.dryRun);
|
|
1394
|
-
}
|
|
1395
|
-
if (deployFunctions) {
|
|
1396
|
-
await deployFunctionsToServer(projectId, credentials.accessToken, options.dryRun);
|
|
1397
|
-
}
|
|
1398
|
-
console.log(chalk6.green("\n\u2713 Deployment complete\n"));
|
|
1399
|
-
}
|
|
1400
|
-
async function deploySchemaToServer(projectId, token, dryRun) {
|
|
1401
|
-
const spinner = ora5("Reading schema...").start();
|
|
1402
|
-
try {
|
|
1403
|
-
const schemaPath = path6.resolve(process.cwd(), "tether", "schema.ts");
|
|
1404
|
-
if (!await fs6.pathExists(schemaPath)) {
|
|
1405
|
-
spinner.warn("No schema file found");
|
|
1406
|
-
console.log(chalk6.dim(" Create tether/schema.ts to define your database schema\n"));
|
|
1407
|
-
return;
|
|
1408
|
-
}
|
|
1409
|
-
const schemaSource = await fs6.readFile(schemaPath, "utf-8");
|
|
1410
|
-
const tables = parseSchema(schemaSource);
|
|
1411
|
-
spinner.text = `Found ${tables.length} table(s)`;
|
|
1412
|
-
if (dryRun) {
|
|
1413
|
-
spinner.info("Dry run - would deploy schema:");
|
|
1414
|
-
for (const table of tables) {
|
|
1415
|
-
console.log(chalk6.dim(` - ${table.name}`));
|
|
1416
|
-
}
|
|
1417
|
-
return;
|
|
1418
|
-
}
|
|
1419
|
-
const sql = generateSchemaSQL(tables);
|
|
1420
|
-
spinner.text = "Deploying schema...";
|
|
1421
|
-
const response = await fetch(`${API_URL2}/${projectId}/mutation`, {
|
|
1422
|
-
method: "POST",
|
|
1423
|
-
headers: {
|
|
1424
|
-
"Content-Type": "application/json",
|
|
1425
|
-
"Authorization": `Bearer ${token}`
|
|
1426
|
-
},
|
|
1427
|
-
body: JSON.stringify({
|
|
1428
|
-
function: "_migrate",
|
|
1429
|
-
args: { sql }
|
|
1430
|
-
})
|
|
1431
|
-
});
|
|
1432
|
-
if (!response.ok) {
|
|
1433
|
-
const error = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
1434
|
-
throw new Error(error.error || `HTTP ${response.status}`);
|
|
1435
|
-
}
|
|
1436
|
-
spinner.succeed(`Schema deployed (${tables.length} table(s))`);
|
|
1437
|
-
} catch (error) {
|
|
1438
|
-
spinner.fail("Failed to deploy schema");
|
|
1439
1595
|
console.error(chalk6.red(error instanceof Error ? error.message : "Unknown error"));
|
|
1596
|
+
process.exit(1);
|
|
1440
1597
|
}
|
|
1441
1598
|
}
|
|
1442
|
-
async function deployFunctionsToServer(projectId, token, dryRun) {
|
|
1443
|
-
const spinner = ora5("Reading functions...").start();
|
|
1444
|
-
try {
|
|
1445
|
-
const functionsDir = path6.resolve(process.cwd(), "tether", "functions");
|
|
1446
|
-
if (!await fs6.pathExists(functionsDir)) {
|
|
1447
|
-
spinner.warn("No functions directory found");
|
|
1448
|
-
console.log(chalk6.dim(" Create tether/functions/ to define your API functions\n"));
|
|
1449
|
-
return;
|
|
1450
|
-
}
|
|
1451
|
-
const files = await fs6.readdir(functionsDir);
|
|
1452
|
-
const tsFiles = files.filter((f) => f.endsWith(".ts"));
|
|
1453
|
-
if (tsFiles.length === 0) {
|
|
1454
|
-
spinner.info("No function files found");
|
|
1455
|
-
return;
|
|
1456
|
-
}
|
|
1457
|
-
const functions = [];
|
|
1458
|
-
for (const file of tsFiles) {
|
|
1459
|
-
const filePath = path6.join(functionsDir, file);
|
|
1460
|
-
const source = await fs6.readFile(filePath, "utf-8");
|
|
1461
|
-
const moduleName = file.replace(".ts", "");
|
|
1462
|
-
const parsedFunctions = parseFunctions(moduleName, source);
|
|
1463
|
-
functions.push(...parsedFunctions);
|
|
1464
|
-
}
|
|
1465
|
-
spinner.text = `Found ${functions.length} function(s)`;
|
|
1466
|
-
if (dryRun) {
|
|
1467
|
-
spinner.info("Dry run - would deploy functions:");
|
|
1468
|
-
for (const fn of functions) {
|
|
1469
|
-
const icon = fn.type === "query" ? "\u{1F50D}" : fn.type === "mutation" ? "\u270F\uFE0F" : "\u26A1";
|
|
1470
|
-
console.log(chalk6.dim(` ${icon} ${fn.name} (${fn.type})`));
|
|
1471
|
-
}
|
|
1472
|
-
return;
|
|
1473
|
-
}
|
|
1474
|
-
spinner.text = "Deploying functions...";
|
|
1475
|
-
const response = await fetch(`${API_URL2}/${projectId}/deploy/functions`, {
|
|
1476
|
-
method: "POST",
|
|
1477
|
-
headers: {
|
|
1478
|
-
"Content-Type": "application/json",
|
|
1479
|
-
"Authorization": `Bearer ${token}`
|
|
1480
|
-
},
|
|
1481
|
-
body: JSON.stringify({
|
|
1482
|
-
functions: functions.map((fn) => ({
|
|
1483
|
-
name: fn.name,
|
|
1484
|
-
type: fn.type,
|
|
1485
|
-
source: fn.source
|
|
1486
|
-
}))
|
|
1487
|
-
})
|
|
1488
|
-
});
|
|
1489
|
-
if (!response.ok) {
|
|
1490
|
-
const error = await response.json().catch(() => ({ error: "Unknown error" }));
|
|
1491
|
-
throw new Error(error.error || `HTTP ${response.status}`);
|
|
1492
|
-
}
|
|
1493
|
-
spinner.succeed(`Functions deployed (${functions.length} function(s))`);
|
|
1494
|
-
const queries = functions.filter((f) => f.type === "query");
|
|
1495
|
-
const mutations = functions.filter((f) => f.type === "mutation");
|
|
1496
|
-
const actions = functions.filter((f) => f.type === "action");
|
|
1497
|
-
if (queries.length > 0) {
|
|
1498
|
-
console.log(chalk6.dim(`
|
|
1499
|
-
Queries:`));
|
|
1500
|
-
for (const fn of queries) {
|
|
1501
|
-
console.log(chalk6.dim(` - ${fn.name}`));
|
|
1502
|
-
}
|
|
1503
|
-
}
|
|
1504
|
-
if (mutations.length > 0) {
|
|
1505
|
-
console.log(chalk6.dim(`
|
|
1506
|
-
Mutations:`));
|
|
1507
|
-
for (const fn of mutations) {
|
|
1508
|
-
console.log(chalk6.dim(` - ${fn.name}`));
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
if (actions.length > 0) {
|
|
1512
|
-
console.log(chalk6.dim(`
|
|
1513
|
-
Actions:`));
|
|
1514
|
-
for (const fn of actions) {
|
|
1515
|
-
console.log(chalk6.dim(` - ${fn.name}`));
|
|
1516
|
-
}
|
|
1517
|
-
}
|
|
1518
|
-
} catch (error) {
|
|
1519
|
-
spinner.fail("Failed to deploy functions");
|
|
1520
|
-
console.error(chalk6.red(error instanceof Error ? error.message : "Unknown error"));
|
|
1521
|
-
}
|
|
1522
|
-
}
|
|
1523
|
-
function parseSchema(source) {
|
|
1524
|
-
const tables = [];
|
|
1525
|
-
const schemaMatch = source.match(/defineSchema\s*\(\s*\{([\s\S]*)\}\s*\)/);
|
|
1526
|
-
if (!schemaMatch) return tables;
|
|
1527
|
-
const schemaContent = schemaMatch[1];
|
|
1528
|
-
const tableRegex = /(\w+)\s*:\s*\{([^}]+(?:\{[^}]*\}[^}]*)*)\}/g;
|
|
1529
|
-
let match;
|
|
1530
|
-
while ((match = tableRegex.exec(schemaContent)) !== null) {
|
|
1531
|
-
const tableName = match[1];
|
|
1532
|
-
const columnsContent = match[2];
|
|
1533
|
-
const columns = {};
|
|
1534
|
-
const columnRegex = /(\w+)\s*:\s*(\w+)\s*\(\s*\)([^,\n]*)/g;
|
|
1535
|
-
let colMatch;
|
|
1536
|
-
while ((colMatch = columnRegex.exec(columnsContent)) !== null) {
|
|
1537
|
-
const colName = colMatch[1];
|
|
1538
|
-
const colType = colMatch[2];
|
|
1539
|
-
const modifiers = colMatch[3];
|
|
1540
|
-
columns[colName] = {
|
|
1541
|
-
type: colType,
|
|
1542
|
-
primaryKey: modifiers.includes(".primaryKey()"),
|
|
1543
|
-
notNull: modifiers.includes(".notNull()"),
|
|
1544
|
-
unique: modifiers.includes(".unique()"),
|
|
1545
|
-
hasDefault: modifiers.includes(".default("),
|
|
1546
|
-
references: modifiers.match(/\.references\s*\(\s*['"]([^'"]+)['"]\s*\)/)?.[1]
|
|
1547
|
-
};
|
|
1548
|
-
}
|
|
1549
|
-
tables.push({ name: tableName, columns });
|
|
1550
|
-
}
|
|
1551
|
-
return tables;
|
|
1552
|
-
}
|
|
1553
|
-
function generateSchemaSQL(tables) {
|
|
1554
|
-
const statements = [];
|
|
1555
|
-
for (const table of tables) {
|
|
1556
|
-
const columnDefs = [];
|
|
1557
|
-
for (const [colName, colDef] of Object.entries(table.columns)) {
|
|
1558
|
-
const def = colDef;
|
|
1559
|
-
let sqlType = "TEXT";
|
|
1560
|
-
switch (def.type) {
|
|
1561
|
-
case "text":
|
|
1562
|
-
sqlType = "TEXT";
|
|
1563
|
-
break;
|
|
1564
|
-
case "integer":
|
|
1565
|
-
sqlType = "INTEGER";
|
|
1566
|
-
break;
|
|
1567
|
-
case "real":
|
|
1568
|
-
sqlType = "REAL";
|
|
1569
|
-
break;
|
|
1570
|
-
case "blob":
|
|
1571
|
-
sqlType = "BLOB";
|
|
1572
|
-
break;
|
|
1573
|
-
case "timestamp":
|
|
1574
|
-
sqlType = "TEXT";
|
|
1575
|
-
break;
|
|
1576
|
-
case "boolean":
|
|
1577
|
-
sqlType = "INTEGER";
|
|
1578
|
-
break;
|
|
1579
|
-
case "json":
|
|
1580
|
-
sqlType = "TEXT";
|
|
1581
|
-
break;
|
|
1582
|
-
}
|
|
1583
|
-
let colSql = `${colName} ${sqlType}`;
|
|
1584
|
-
if (def.primaryKey) colSql += " PRIMARY KEY";
|
|
1585
|
-
if (def.notNull) colSql += " NOT NULL";
|
|
1586
|
-
if (def.unique) colSql += " UNIQUE";
|
|
1587
|
-
if (def.hasDefault && def.type === "timestamp") {
|
|
1588
|
-
colSql += " DEFAULT (datetime('now'))";
|
|
1589
|
-
}
|
|
1590
|
-
if (def.references) {
|
|
1591
|
-
const [refTable, refCol] = def.references.split(".");
|
|
1592
|
-
colSql += ` REFERENCES ${refTable}(${refCol})`;
|
|
1593
|
-
}
|
|
1594
|
-
columnDefs.push(colSql);
|
|
1595
|
-
}
|
|
1596
|
-
statements.push(
|
|
1597
|
-
`CREATE TABLE IF NOT EXISTS ${table.name} (
|
|
1598
|
-
${columnDefs.join(",\n ")}
|
|
1599
|
-
);`
|
|
1600
|
-
);
|
|
1601
|
-
}
|
|
1602
|
-
return statements.join("\n\n");
|
|
1603
|
-
}
|
|
1604
|
-
function parseFunctions(moduleName, source) {
|
|
1605
|
-
const functions = [];
|
|
1606
|
-
const fnRegex = /export\s+const\s+(\w+)\s*=\s*(query|mutation|action)\s*\(\s*\{([\s\S]*?)\}\s*\)/g;
|
|
1607
|
-
let match;
|
|
1608
|
-
while ((match = fnRegex.exec(source)) !== null) {
|
|
1609
|
-
const fnName = match[1];
|
|
1610
|
-
const fnType = match[2];
|
|
1611
|
-
const fnBody = match[3];
|
|
1612
|
-
functions.push({
|
|
1613
|
-
name: `${moduleName}.${fnName}`,
|
|
1614
|
-
type: fnType,
|
|
1615
|
-
file: `${moduleName}.ts`,
|
|
1616
|
-
source: `${fnType}({${fnBody}})`
|
|
1617
|
-
});
|
|
1618
|
-
}
|
|
1619
|
-
return functions;
|
|
1620
|
-
}
|
|
1621
1599
|
|
|
1622
1600
|
// src/commands/login.ts
|
|
1623
1601
|
import chalk7 from "chalk";
|