tthr 0.3.11 → 0.3.12
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-2QPN6NDY.js +721 -0
- package/dist/generate-KAPPABOL.js +8 -0
- package/dist/index.js +46 -39
- package/package.json +1 -1
|
@@ -0,0 +1,721 @@
|
|
|
1
|
+
// src/commands/generate.ts
|
|
2
|
+
import chalk2 from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import fs3 from "fs-extra";
|
|
5
|
+
import path3 from "path";
|
|
6
|
+
|
|
7
|
+
// src/utils/auth.ts
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import fs from "fs-extra";
|
|
10
|
+
import path from "path";
|
|
11
|
+
var CONFIG_DIR = path.join(process.env.HOME || process.env.USERPROFILE || "", ".tether");
|
|
12
|
+
var CREDENTIALS_FILE = path.join(CONFIG_DIR, "credentials.json");
|
|
13
|
+
var isDev = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
14
|
+
var API_URL = isDev ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
|
|
15
|
+
var REFRESH_THRESHOLD_DAYS = 7;
|
|
16
|
+
async function getCredentials() {
|
|
17
|
+
try {
|
|
18
|
+
if (await fs.pathExists(CREDENTIALS_FILE)) {
|
|
19
|
+
return await fs.readJSON(CREDENTIALS_FILE);
|
|
20
|
+
}
|
|
21
|
+
} catch {
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
async function refreshSession(credentials) {
|
|
26
|
+
try {
|
|
27
|
+
const response = await fetch(`${API_URL}/auth/session/refresh`, {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
"Authorization": `Bearer ${credentials.accessToken}`
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const data = await response.json();
|
|
38
|
+
const updated = {
|
|
39
|
+
...credentials,
|
|
40
|
+
expiresAt: data.expiresAt
|
|
41
|
+
};
|
|
42
|
+
await saveCredentials(updated);
|
|
43
|
+
return updated;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function requireAuth() {
|
|
49
|
+
const credentials = await getCredentials();
|
|
50
|
+
if (!credentials) {
|
|
51
|
+
console.error(chalk.red("\n\u2717 Not logged in\n"));
|
|
52
|
+
console.log(chalk.dim("Run `tthr login` to authenticate\n"));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
if (credentials.expiresAt) {
|
|
56
|
+
const expiresAt = new Date(credentials.expiresAt);
|
|
57
|
+
const now = /* @__PURE__ */ new Date();
|
|
58
|
+
if (now > expiresAt) {
|
|
59
|
+
await clearCredentials();
|
|
60
|
+
console.error(chalk.red("\n\u2717 Session expired \u2014 you have been signed out\n"));
|
|
61
|
+
console.log(chalk.dim("Run `tthr login` to authenticate\n"));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
const daysUntilExpiry = (expiresAt.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24);
|
|
65
|
+
if (daysUntilExpiry <= REFRESH_THRESHOLD_DAYS) {
|
|
66
|
+
const refreshed = await refreshSession(credentials);
|
|
67
|
+
if (refreshed) {
|
|
68
|
+
return refreshed;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return credentials;
|
|
73
|
+
}
|
|
74
|
+
async function saveCredentials(credentials) {
|
|
75
|
+
await fs.ensureDir(CONFIG_DIR);
|
|
76
|
+
await fs.writeJSON(CREDENTIALS_FILE, credentials, { spaces: 2, mode: 384 });
|
|
77
|
+
}
|
|
78
|
+
async function clearCredentials() {
|
|
79
|
+
try {
|
|
80
|
+
await fs.remove(CREDENTIALS_FILE);
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/utils/config.ts
|
|
86
|
+
import fs2 from "fs-extra";
|
|
87
|
+
import path2 from "path";
|
|
88
|
+
var DEFAULT_CONFIG = {
|
|
89
|
+
schema: "./tether/schema.ts",
|
|
90
|
+
functions: "./tether/functions",
|
|
91
|
+
output: "./tether/_generated",
|
|
92
|
+
dev: {
|
|
93
|
+
port: 3001,
|
|
94
|
+
host: "localhost"
|
|
95
|
+
},
|
|
96
|
+
database: {
|
|
97
|
+
walMode: true
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
async function loadConfig(cwd = process.cwd()) {
|
|
101
|
+
const configPath = path2.resolve(cwd, "tether.config.ts");
|
|
102
|
+
if (!await fs2.pathExists(configPath)) {
|
|
103
|
+
return DEFAULT_CONFIG;
|
|
104
|
+
}
|
|
105
|
+
const configSource = await fs2.readFile(configPath, "utf-8");
|
|
106
|
+
const config = {};
|
|
107
|
+
const schemaMatch = configSource.match(/schema\s*:\s*['"]([^'"]+)['"]/);
|
|
108
|
+
if (schemaMatch) {
|
|
109
|
+
config.schema = schemaMatch[1];
|
|
110
|
+
}
|
|
111
|
+
const functionsMatch = configSource.match(/functions\s*:\s*['"]([^'"]+)['"]/);
|
|
112
|
+
if (functionsMatch) {
|
|
113
|
+
config.functions = functionsMatch[1];
|
|
114
|
+
}
|
|
115
|
+
const outputMatch = configSource.match(/output\s*:\s*['"]([^'"]+)['"]/);
|
|
116
|
+
if (outputMatch) {
|
|
117
|
+
config.output = outputMatch[1];
|
|
118
|
+
}
|
|
119
|
+
const projectIdMatch = configSource.match(/projectId\s*:\s*['"]([^'"]+)['"]/);
|
|
120
|
+
if (projectIdMatch) {
|
|
121
|
+
config.projectId = projectIdMatch[1];
|
|
122
|
+
}
|
|
123
|
+
const urlMatch = configSource.match(/(?:^|\n)\s*url\s*:\s*['"]([^'"]+)['"]/);
|
|
124
|
+
if (urlMatch) {
|
|
125
|
+
config.url = urlMatch[1];
|
|
126
|
+
}
|
|
127
|
+
const envMatch = configSource.match(/environment\s*:\s*['"]([^'"]+)['"]/);
|
|
128
|
+
if (envMatch) {
|
|
129
|
+
config.environment = envMatch[1];
|
|
130
|
+
}
|
|
131
|
+
const portMatch = configSource.match(/port\s*:\s*(\d+)/);
|
|
132
|
+
if (portMatch) {
|
|
133
|
+
config.dev = { ...config.dev, port: parseInt(portMatch[1], 10) };
|
|
134
|
+
}
|
|
135
|
+
const hostMatch = configSource.match(/host\s*:\s*['"]([^'"]+)['"]/);
|
|
136
|
+
if (hostMatch) {
|
|
137
|
+
config.dev = { ...config.dev, host: hostMatch[1] };
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
...DEFAULT_CONFIG,
|
|
141
|
+
...config,
|
|
142
|
+
dev: { ...DEFAULT_CONFIG.dev, ...config.dev },
|
|
143
|
+
database: { ...DEFAULT_CONFIG.database, ...config.database }
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async function resolveEnvironmentFromApiKey(cwd = process.cwd()) {
|
|
147
|
+
const envPath = path2.resolve(cwd, ".env");
|
|
148
|
+
if (!await fs2.pathExists(envPath)) return void 0;
|
|
149
|
+
const envContent = await fs2.readFile(envPath, "utf-8");
|
|
150
|
+
const match = envContent.match(/TETHER_API_KEY=\s*(tthr_([^_]+)_[0-9a-f]+)/);
|
|
151
|
+
if (!match) return void 0;
|
|
152
|
+
const prefix = match[2];
|
|
153
|
+
switch (prefix) {
|
|
154
|
+
case "dev":
|
|
155
|
+
return "development";
|
|
156
|
+
case "prod":
|
|
157
|
+
return "production";
|
|
158
|
+
default:
|
|
159
|
+
return prefix;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
var DEFAULT_API_URL = "https://tether-api.strands.gg";
|
|
163
|
+
function getApiUrl(config) {
|
|
164
|
+
if (process.env.TETHER_API_URL) {
|
|
165
|
+
return process.env.TETHER_API_URL.replace(/\/+$/, "");
|
|
166
|
+
}
|
|
167
|
+
if (config?.url) {
|
|
168
|
+
return config.url.replace(/\/+$/, "");
|
|
169
|
+
}
|
|
170
|
+
const isDev3 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
171
|
+
return isDev3 ? "http://localhost:3001" : DEFAULT_API_URL;
|
|
172
|
+
}
|
|
173
|
+
function resolvePath(configPath, cwd = process.cwd()) {
|
|
174
|
+
const normalised = configPath.replace(/^\.\//, "");
|
|
175
|
+
return path2.resolve(cwd, normalised);
|
|
176
|
+
}
|
|
177
|
+
async function detectFramework(cwd = process.cwd()) {
|
|
178
|
+
const packageJsonPath = path2.resolve(cwd, "package.json");
|
|
179
|
+
if (!await fs2.pathExists(packageJsonPath)) {
|
|
180
|
+
return "unknown";
|
|
181
|
+
}
|
|
182
|
+
try {
|
|
183
|
+
const packageJson = await fs2.readJson(packageJsonPath);
|
|
184
|
+
const deps = {
|
|
185
|
+
...packageJson.dependencies,
|
|
186
|
+
...packageJson.devDependencies
|
|
187
|
+
};
|
|
188
|
+
if (deps.nuxt || deps["@nuxt/kit"]) {
|
|
189
|
+
return "nuxt";
|
|
190
|
+
}
|
|
191
|
+
if (deps.next) {
|
|
192
|
+
return "next";
|
|
193
|
+
}
|
|
194
|
+
if (deps["@sveltejs/kit"]) {
|
|
195
|
+
return "sveltekit";
|
|
196
|
+
}
|
|
197
|
+
if (deps.vite && !deps.nuxt && !deps.next && !deps["@sveltejs/kit"]) {
|
|
198
|
+
return "vite";
|
|
199
|
+
}
|
|
200
|
+
return "vanilla";
|
|
201
|
+
} catch {
|
|
202
|
+
return "unknown";
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function getFrameworkDevCommand(framework) {
|
|
206
|
+
switch (framework) {
|
|
207
|
+
case "nuxt":
|
|
208
|
+
return "nuxt dev";
|
|
209
|
+
case "next":
|
|
210
|
+
return "next dev";
|
|
211
|
+
case "sveltekit":
|
|
212
|
+
return "vite dev";
|
|
213
|
+
case "vite":
|
|
214
|
+
return "vite dev";
|
|
215
|
+
case "vanilla":
|
|
216
|
+
return null;
|
|
217
|
+
// No default dev server for vanilla
|
|
218
|
+
default:
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/commands/generate.ts
|
|
224
|
+
var isDev2 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
225
|
+
var API_URL2 = isDev2 ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
|
|
226
|
+
function parseSchemaFile(source) {
|
|
227
|
+
const tables = [];
|
|
228
|
+
const schemaMatch = source.match(/defineSchema\s*\(\s*\{([\s\S]*)\}\s*\)/);
|
|
229
|
+
if (!schemaMatch) return tables;
|
|
230
|
+
const schemaContent = schemaMatch[1];
|
|
231
|
+
const tableStartRegex = /(\w+)\s*:\s*\{/g;
|
|
232
|
+
let match;
|
|
233
|
+
while ((match = tableStartRegex.exec(schemaContent)) !== null) {
|
|
234
|
+
const tableName = match[1];
|
|
235
|
+
const startOffset = match.index + match[0].length;
|
|
236
|
+
let braceCount = 1;
|
|
237
|
+
let endOffset = startOffset;
|
|
238
|
+
for (let i = startOffset; i < schemaContent.length && braceCount > 0; i++) {
|
|
239
|
+
const char = schemaContent[i];
|
|
240
|
+
if (char === "{") braceCount++;
|
|
241
|
+
else if (char === "}") braceCount--;
|
|
242
|
+
endOffset = i;
|
|
243
|
+
}
|
|
244
|
+
const columnsContent = schemaContent.slice(startOffset, endOffset);
|
|
245
|
+
const columns = parseColumns(columnsContent);
|
|
246
|
+
tables.push({ name: tableName, columns });
|
|
247
|
+
}
|
|
248
|
+
return tables;
|
|
249
|
+
}
|
|
250
|
+
function parseColumns(content) {
|
|
251
|
+
const columns = [];
|
|
252
|
+
const columnRegex = /(\w+)\s*:\s*(\w+)(?:<([^>]+)>)?\s*\(\s*\)((?:\[.*?\]|[^,\n}])*)/g;
|
|
253
|
+
let match;
|
|
254
|
+
while ((match = columnRegex.exec(content)) !== null) {
|
|
255
|
+
const name = match[1];
|
|
256
|
+
const schemaType = match[2];
|
|
257
|
+
const genericType = match[3];
|
|
258
|
+
const modifiers = match[4] || "";
|
|
259
|
+
const nullable = !modifiers.includes(".notNull()");
|
|
260
|
+
const oneOfMatch = modifiers.match(/\.oneOf\s*\(\s*\[(.*?)\]\s*\)/);
|
|
261
|
+
let oneOf;
|
|
262
|
+
if (oneOfMatch) {
|
|
263
|
+
oneOf = [...oneOfMatch[1].matchAll(/['"]([^'"]+)['"]/g)].map((m) => m[1]);
|
|
264
|
+
}
|
|
265
|
+
columns.push({
|
|
266
|
+
name,
|
|
267
|
+
type: schemaTypeToTS(schemaType),
|
|
268
|
+
nullable,
|
|
269
|
+
jsonType: genericType,
|
|
270
|
+
// Store the generic type for json columns
|
|
271
|
+
oneOf
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
return columns;
|
|
275
|
+
}
|
|
276
|
+
function schemaTypeToTS(schemaType) {
|
|
277
|
+
switch (schemaType) {
|
|
278
|
+
case "text":
|
|
279
|
+
return "string";
|
|
280
|
+
case "integer":
|
|
281
|
+
return "number";
|
|
282
|
+
case "real":
|
|
283
|
+
return "number";
|
|
284
|
+
case "boolean":
|
|
285
|
+
return "boolean";
|
|
286
|
+
case "timestamp":
|
|
287
|
+
return "string";
|
|
288
|
+
case "json":
|
|
289
|
+
return "unknown";
|
|
290
|
+
case "blob":
|
|
291
|
+
return "Uint8Array";
|
|
292
|
+
case "asset":
|
|
293
|
+
return "TetherAsset";
|
|
294
|
+
// Asset object returned by API
|
|
295
|
+
default:
|
|
296
|
+
return "unknown";
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
function extractSingleType(source, typeName) {
|
|
300
|
+
const interfaceStart = new RegExp(`(?:export\\s+)?interface\\s+${typeName}\\s*\\{`);
|
|
301
|
+
const interfaceMatch = interfaceStart.exec(source);
|
|
302
|
+
if (interfaceMatch) {
|
|
303
|
+
const braceStart = interfaceMatch.index + interfaceMatch[0].length;
|
|
304
|
+
let braceCount = 1;
|
|
305
|
+
let endIdx = braceStart;
|
|
306
|
+
for (let i = braceStart; i < source.length && braceCount > 0; i++) {
|
|
307
|
+
if (source[i] === "{") braceCount++;
|
|
308
|
+
else if (source[i] === "}") braceCount--;
|
|
309
|
+
endIdx = i;
|
|
310
|
+
}
|
|
311
|
+
return source.slice(interfaceMatch.index, endIdx + 1).replace(/^export\s+/, "");
|
|
312
|
+
}
|
|
313
|
+
const typeStart = new RegExp(`(?:export\\s+)?type\\s+${typeName}\\s*=`);
|
|
314
|
+
const typeMatch = typeStart.exec(source);
|
|
315
|
+
if (typeMatch) {
|
|
316
|
+
const semiIdx = source.indexOf(";", typeMatch.index);
|
|
317
|
+
if (semiIdx !== -1) {
|
|
318
|
+
return source.slice(typeMatch.index, semiIdx + 1).replace(/^export\s+/, "");
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
function extractTypeDefinitions(source, typeNames) {
|
|
324
|
+
if (typeNames.size === 0) return [];
|
|
325
|
+
const allDefinedTypes = /* @__PURE__ */ new Set();
|
|
326
|
+
const typeDefRegex = /(?:export\s+)?(?:interface|type)\s+(\w+)/g;
|
|
327
|
+
let m;
|
|
328
|
+
while ((m = typeDefRegex.exec(source)) !== null) {
|
|
329
|
+
allDefinedTypes.add(m[1]);
|
|
330
|
+
}
|
|
331
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
332
|
+
const queue = [...typeNames];
|
|
333
|
+
while (queue.length > 0) {
|
|
334
|
+
const name = queue.shift();
|
|
335
|
+
if (resolved.has(name)) continue;
|
|
336
|
+
const block = extractSingleType(source, name);
|
|
337
|
+
if (!block) continue;
|
|
338
|
+
resolved.set(name, block);
|
|
339
|
+
for (const definedType of allDefinedTypes) {
|
|
340
|
+
if (!resolved.has(definedType) && !queue.includes(definedType)) {
|
|
341
|
+
const refRegex = new RegExp(`\\b${definedType}\\b`);
|
|
342
|
+
if (refRegex.test(block)) {
|
|
343
|
+
queue.push(definedType);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const ordered = [];
|
|
349
|
+
const visited = /* @__PURE__ */ new Set();
|
|
350
|
+
function visit(name) {
|
|
351
|
+
if (visited.has(name)) return;
|
|
352
|
+
visited.add(name);
|
|
353
|
+
const block = resolved.get(name);
|
|
354
|
+
if (!block) return;
|
|
355
|
+
for (const dep of resolved.keys()) {
|
|
356
|
+
if (dep !== name && new RegExp(`\\b${dep}\\b`).test(block)) {
|
|
357
|
+
visit(dep);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
ordered.push(name);
|
|
361
|
+
}
|
|
362
|
+
for (const name of resolved.keys()) {
|
|
363
|
+
visit(name);
|
|
364
|
+
}
|
|
365
|
+
return ordered.map((name) => resolved.get(name));
|
|
366
|
+
}
|
|
367
|
+
function tableNameToInterface(tableName) {
|
|
368
|
+
let name = tableName;
|
|
369
|
+
if (name.endsWith("ies")) {
|
|
370
|
+
name = name.slice(0, -3) + "y";
|
|
371
|
+
} else if (name.endsWith("s") && !name.endsWith("ss")) {
|
|
372
|
+
name = name.slice(0, -1);
|
|
373
|
+
}
|
|
374
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
375
|
+
}
|
|
376
|
+
function generateDbFile(tables, schemaSource) {
|
|
377
|
+
const TS_PRIMITIVES = /* @__PURE__ */ new Set(["number", "string", "boolean", "unknown", "any", "void", "never", "undefined", "null", "object"]);
|
|
378
|
+
const jsonTypes = /* @__PURE__ */ new Set();
|
|
379
|
+
for (const table of tables) {
|
|
380
|
+
for (const col of table.columns) {
|
|
381
|
+
if (col.jsonType) {
|
|
382
|
+
const baseType = col.jsonType.replace(/\s*\|\s*null/g, "").replace(/\[\]/g, "").trim();
|
|
383
|
+
if (!TS_PRIMITIVES.has(baseType)) {
|
|
384
|
+
jsonTypes.add(baseType);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const lines = [
|
|
390
|
+
"// Auto-generated by Tether CLI - do not edit manually",
|
|
391
|
+
`// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
392
|
+
"",
|
|
393
|
+
"import { createDatabaseProxy, type TetherDatabase } from '@tthr/client';",
|
|
394
|
+
"import {",
|
|
395
|
+
" type QueryDefinition,",
|
|
396
|
+
" type MutationDefinition,",
|
|
397
|
+
" z,",
|
|
398
|
+
"} from '@tthr/server';",
|
|
399
|
+
"import type { TetherEnv } from './env';"
|
|
400
|
+
];
|
|
401
|
+
if (jsonTypes.size > 0) {
|
|
402
|
+
const typeBlocks = extractTypeDefinitions(schemaSource, jsonTypes);
|
|
403
|
+
if (typeBlocks.length > 0) {
|
|
404
|
+
lines.push("");
|
|
405
|
+
lines.push("// Schema types (inlined from schema.ts)");
|
|
406
|
+
for (const block of typeBlocks) {
|
|
407
|
+
lines.push(block);
|
|
408
|
+
lines.push("");
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
lines.push("");
|
|
413
|
+
lines.push("// Asset type returned by the API for asset columns");
|
|
414
|
+
lines.push("export interface TetherAsset {");
|
|
415
|
+
lines.push(" id: string;");
|
|
416
|
+
lines.push(" filename: string;");
|
|
417
|
+
lines.push(" contentType: string;");
|
|
418
|
+
lines.push(" size: number;");
|
|
419
|
+
lines.push(" url: string;");
|
|
420
|
+
lines.push(" createdAt: string;");
|
|
421
|
+
lines.push("}");
|
|
422
|
+
lines.push("");
|
|
423
|
+
for (const table of tables) {
|
|
424
|
+
const interfaceName = tableNameToInterface(table.name);
|
|
425
|
+
lines.push(`export interface ${interfaceName} {`);
|
|
426
|
+
lines.push(" _id: string;");
|
|
427
|
+
for (const col of table.columns) {
|
|
428
|
+
let colType;
|
|
429
|
+
if (col.oneOf && col.oneOf.length > 0) {
|
|
430
|
+
colType = col.oneOf.map((v) => `'${v}'`).join(" | ");
|
|
431
|
+
} else {
|
|
432
|
+
colType = col.jsonType || col.type;
|
|
433
|
+
}
|
|
434
|
+
const typeStr = col.nullable ? `${colType} | null` : colType;
|
|
435
|
+
const optional = col.nullable ? "?" : "";
|
|
436
|
+
lines.push(` ${col.name}${optional}: ${typeStr};`);
|
|
437
|
+
}
|
|
438
|
+
lines.push(" _createdAt: string;");
|
|
439
|
+
lines.push(" _updatedAt?: string | null;");
|
|
440
|
+
lines.push(" _deletedAt?: string | null;");
|
|
441
|
+
lines.push("}");
|
|
442
|
+
lines.push("");
|
|
443
|
+
}
|
|
444
|
+
lines.push("export interface Schema {");
|
|
445
|
+
for (const table of tables) {
|
|
446
|
+
const interfaceName = tableNameToInterface(table.name);
|
|
447
|
+
lines.push(` ${table.name}: ${interfaceName};`);
|
|
448
|
+
}
|
|
449
|
+
lines.push("}");
|
|
450
|
+
lines.push("");
|
|
451
|
+
lines.push("// Database client with typed tables");
|
|
452
|
+
lines.push("// This is a proxy that will be populated by the Tether runtime");
|
|
453
|
+
lines.push("export const db: TetherDatabase<Schema> = createDatabaseProxy<Schema>();");
|
|
454
|
+
lines.push("");
|
|
455
|
+
lines.push("// ============================================================================");
|
|
456
|
+
lines.push("// Typed function wrappers - use these instead of importing from @tthr/server");
|
|
457
|
+
lines.push("// This ensures the `db` and `env` parameters in handlers are properly typed");
|
|
458
|
+
lines.push("// ============================================================================");
|
|
459
|
+
lines.push("");
|
|
460
|
+
lines.push("/**");
|
|
461
|
+
lines.push(" * Define a query function with typed database and environment variable access.");
|
|
462
|
+
lines.push(" * The `db` and `env` parameters in the handler will have full type safety.");
|
|
463
|
+
lines.push(" */");
|
|
464
|
+
lines.push("export function query<TArgs = void, TResult = unknown>(");
|
|
465
|
+
lines.push(" definition: QueryDefinition<TArgs, TResult, Schema, TetherEnv>");
|
|
466
|
+
lines.push("): QueryDefinition<TArgs, TResult, Schema, TetherEnv> {");
|
|
467
|
+
lines.push(" return definition;");
|
|
468
|
+
lines.push("}");
|
|
469
|
+
lines.push("");
|
|
470
|
+
lines.push("/**");
|
|
471
|
+
lines.push(" * Define a mutation function with typed database and environment variable access.");
|
|
472
|
+
lines.push(" * The `db` and `env` parameters in the handler will have full type safety.");
|
|
473
|
+
lines.push(" */");
|
|
474
|
+
lines.push("export function mutation<TArgs = void, TResult = unknown>(");
|
|
475
|
+
lines.push(" definition: MutationDefinition<TArgs, TResult, Schema, TetherEnv>");
|
|
476
|
+
lines.push("): MutationDefinition<TArgs, TResult, Schema, TetherEnv> {");
|
|
477
|
+
lines.push(" return definition;");
|
|
478
|
+
lines.push("}");
|
|
479
|
+
lines.push("");
|
|
480
|
+
lines.push("// Re-export z for convenience");
|
|
481
|
+
lines.push("export { z };");
|
|
482
|
+
lines.push("");
|
|
483
|
+
return lines.join("\n");
|
|
484
|
+
}
|
|
485
|
+
async function parseFunctionsDir(functionsDir) {
|
|
486
|
+
const functions = [];
|
|
487
|
+
if (!await fs3.pathExists(functionsDir)) {
|
|
488
|
+
return functions;
|
|
489
|
+
}
|
|
490
|
+
const files = await fs3.readdir(functionsDir);
|
|
491
|
+
for (const file of files) {
|
|
492
|
+
if (!file.endsWith(".ts") && !file.endsWith(".js")) continue;
|
|
493
|
+
const filePath = path3.join(functionsDir, file);
|
|
494
|
+
const stat = await fs3.stat(filePath);
|
|
495
|
+
if (!stat.isFile()) continue;
|
|
496
|
+
const moduleName = file.replace(/\.(ts|js)$/, "");
|
|
497
|
+
const source = await fs3.readFile(filePath, "utf-8");
|
|
498
|
+
const exportRegex = /export\s+const\s+(\w+)\s*=\s*(query|mutation)\s*\(/g;
|
|
499
|
+
let match;
|
|
500
|
+
while ((match = exportRegex.exec(source)) !== null) {
|
|
501
|
+
functions.push({
|
|
502
|
+
name: match[1],
|
|
503
|
+
moduleName
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return functions;
|
|
508
|
+
}
|
|
509
|
+
async function generateApiFile(functionsDir) {
|
|
510
|
+
const functions = await parseFunctionsDir(functionsDir);
|
|
511
|
+
const moduleMap = /* @__PURE__ */ new Map();
|
|
512
|
+
for (const fn of functions) {
|
|
513
|
+
if (!moduleMap.has(fn.moduleName)) {
|
|
514
|
+
moduleMap.set(fn.moduleName, []);
|
|
515
|
+
}
|
|
516
|
+
moduleMap.get(fn.moduleName).push(fn.name);
|
|
517
|
+
}
|
|
518
|
+
const lines = [
|
|
519
|
+
"// Auto-generated by Tether CLI - do not edit manually",
|
|
520
|
+
`// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
521
|
+
"",
|
|
522
|
+
"import { createApiProxy } from '@tthr/client';",
|
|
523
|
+
"",
|
|
524
|
+
"/**",
|
|
525
|
+
" * API function reference type for useQuery/useMutation.",
|
|
526
|
+
' * The _name property contains the function path (e.g., "users.list").',
|
|
527
|
+
" */",
|
|
528
|
+
"export interface ApiFunction<TArgs = unknown, TResult = unknown> {",
|
|
529
|
+
" _name: string;",
|
|
530
|
+
" _args?: TArgs;",
|
|
531
|
+
" _result?: TResult;",
|
|
532
|
+
"}",
|
|
533
|
+
""
|
|
534
|
+
];
|
|
535
|
+
if (moduleMap.size > 0) {
|
|
536
|
+
for (const [moduleName, fnNames] of moduleMap) {
|
|
537
|
+
const interfaceName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1) + "Api";
|
|
538
|
+
lines.push(`export interface ${interfaceName} {`);
|
|
539
|
+
for (const fnName of fnNames) {
|
|
540
|
+
lines.push(` ${fnName}: ApiFunction;`);
|
|
541
|
+
}
|
|
542
|
+
lines.push("}");
|
|
543
|
+
lines.push("");
|
|
544
|
+
}
|
|
545
|
+
lines.push("export interface Api {");
|
|
546
|
+
for (const moduleName of moduleMap.keys()) {
|
|
547
|
+
const interfaceName = moduleName.charAt(0).toUpperCase() + moduleName.slice(1) + "Api";
|
|
548
|
+
lines.push(` ${moduleName}: ${interfaceName};`);
|
|
549
|
+
}
|
|
550
|
+
lines.push("}");
|
|
551
|
+
} else {
|
|
552
|
+
lines.push("/**");
|
|
553
|
+
lines.push(" * Flexible API type that allows access to any function path.");
|
|
554
|
+
lines.push(" * Functions are accessed as api.moduleName.functionName");
|
|
555
|
+
lines.push(" * The actual types depend on your function definitions.");
|
|
556
|
+
lines.push(" */");
|
|
557
|
+
lines.push("export type Api = {");
|
|
558
|
+
lines.push(" [module: string]: {");
|
|
559
|
+
lines.push(" [fn: string]: ApiFunction;");
|
|
560
|
+
lines.push(" };");
|
|
561
|
+
lines.push("};");
|
|
562
|
+
}
|
|
563
|
+
lines.push("");
|
|
564
|
+
lines.push("// API client proxy - provides typed access to your functions");
|
|
565
|
+
lines.push("// On the client: returns { _name } references for useQuery/useMutation");
|
|
566
|
+
lines.push("// In Tether functions: calls the actual function implementation");
|
|
567
|
+
lines.push("export const api = createApiProxy<Api>();");
|
|
568
|
+
lines.push("");
|
|
569
|
+
return lines.join("\n");
|
|
570
|
+
}
|
|
571
|
+
async function fetchEnvVarKeys(projectId, environment) {
|
|
572
|
+
const credentials = await getCredentials();
|
|
573
|
+
if (!credentials) return [];
|
|
574
|
+
const envPath = environment !== "production" ? `/projects/${projectId}/env/${environment}/env-vars` : `/projects/${projectId}/env-vars`;
|
|
575
|
+
try {
|
|
576
|
+
const response = await fetch(`${API_URL2}${envPath}`, {
|
|
577
|
+
headers: {
|
|
578
|
+
"Authorization": `Bearer ${credentials.accessToken}`
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
if (!response.ok) return [];
|
|
582
|
+
const data = await response.json();
|
|
583
|
+
return (data.envVars || []).map((v) => v.key).sort();
|
|
584
|
+
} catch {
|
|
585
|
+
return [];
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
function generateEnvFile(keys) {
|
|
589
|
+
const lines = [
|
|
590
|
+
"// Auto-generated by Tether CLI - do not edit manually",
|
|
591
|
+
`// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
592
|
+
"",
|
|
593
|
+
"/**",
|
|
594
|
+
" * Typed environment variables for this project.",
|
|
595
|
+
" * Keys are fetched from the Tether API \u2014 values are never exposed.",
|
|
596
|
+
' * Use `env.get("KEY")` in your functions to access them.',
|
|
597
|
+
" */",
|
|
598
|
+
""
|
|
599
|
+
];
|
|
600
|
+
if (keys.length > 0) {
|
|
601
|
+
lines.push("export type EnvVarKey =");
|
|
602
|
+
for (let i = 0; i < keys.length; i++) {
|
|
603
|
+
const sep = i === keys.length - 1 ? ";" : "";
|
|
604
|
+
lines.push(` | '${keys[i]}'${sep}`);
|
|
605
|
+
}
|
|
606
|
+
lines.push("");
|
|
607
|
+
lines.push("export interface TetherEnv {");
|
|
608
|
+
for (const key of keys) {
|
|
609
|
+
lines.push(` ${key}: string;`);
|
|
610
|
+
}
|
|
611
|
+
lines.push("}");
|
|
612
|
+
} else {
|
|
613
|
+
lines.push("export type EnvVarKey = string;");
|
|
614
|
+
lines.push("");
|
|
615
|
+
lines.push("export interface TetherEnv {");
|
|
616
|
+
lines.push(" [key: string]: string;");
|
|
617
|
+
lines.push("}");
|
|
618
|
+
}
|
|
619
|
+
lines.push("");
|
|
620
|
+
return lines.join("\n");
|
|
621
|
+
}
|
|
622
|
+
async function generateTypes(options = {}) {
|
|
623
|
+
const config = await loadConfig();
|
|
624
|
+
const schemaPath = resolvePath(config.schema);
|
|
625
|
+
const outputDir = resolvePath(config.output);
|
|
626
|
+
const functionsDir = resolvePath(config.functions);
|
|
627
|
+
if (!await fs3.pathExists(schemaPath)) {
|
|
628
|
+
throw new Error(`Schema file not found: ${schemaPath}`);
|
|
629
|
+
}
|
|
630
|
+
const schemaSource = await fs3.readFile(schemaPath, "utf-8");
|
|
631
|
+
const tables = parseSchemaFile(schemaSource);
|
|
632
|
+
const environment = config.environment || await resolveEnvironmentFromApiKey() || "development";
|
|
633
|
+
let projectId;
|
|
634
|
+
const envFilePath = path3.resolve(process.cwd(), ".env");
|
|
635
|
+
if (await fs3.pathExists(envFilePath)) {
|
|
636
|
+
const envContent = await fs3.readFile(envFilePath, "utf-8");
|
|
637
|
+
const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
|
|
638
|
+
projectId = match?.[1]?.trim();
|
|
639
|
+
}
|
|
640
|
+
const envVarKeys = projectId ? await fetchEnvVarKeys(projectId, environment) : [];
|
|
641
|
+
await fs3.ensureDir(outputDir);
|
|
642
|
+
await fs3.writeFile(
|
|
643
|
+
path3.join(outputDir, "db.ts"),
|
|
644
|
+
generateDbFile(tables, schemaSource)
|
|
645
|
+
);
|
|
646
|
+
await fs3.writeFile(
|
|
647
|
+
path3.join(outputDir, "api.ts"),
|
|
648
|
+
await generateApiFile(functionsDir)
|
|
649
|
+
);
|
|
650
|
+
await fs3.writeFile(
|
|
651
|
+
path3.join(outputDir, "env.ts"),
|
|
652
|
+
generateEnvFile(envVarKeys)
|
|
653
|
+
);
|
|
654
|
+
await fs3.writeFile(
|
|
655
|
+
path3.join(outputDir, "index.ts"),
|
|
656
|
+
`// Auto-generated by Tether CLI - do not edit manually
|
|
657
|
+
export * from './db';
|
|
658
|
+
export * from './api';
|
|
659
|
+
export * from './env';
|
|
660
|
+
`
|
|
661
|
+
);
|
|
662
|
+
return { tables, envVarKeys, outputDir };
|
|
663
|
+
}
|
|
664
|
+
async function generateCommand() {
|
|
665
|
+
await requireAuth();
|
|
666
|
+
const configPath = path3.resolve(process.cwd(), "tether.config.ts");
|
|
667
|
+
if (!await fs3.pathExists(configPath)) {
|
|
668
|
+
console.log(chalk2.red("\nError: Not a Tether project"));
|
|
669
|
+
console.log(chalk2.dim("Run `tthr init` to create a new project\n"));
|
|
670
|
+
process.exit(1);
|
|
671
|
+
}
|
|
672
|
+
console.log(chalk2.bold("\n\u26A1 Generating types from schema\n"));
|
|
673
|
+
const spinner = ora("Reading schema...").start();
|
|
674
|
+
try {
|
|
675
|
+
spinner.text = "Generating types...";
|
|
676
|
+
const { tables, envVarKeys, outputDir } = await generateTypes();
|
|
677
|
+
if (tables.length === 0) {
|
|
678
|
+
spinner.warn("No tables found in schema");
|
|
679
|
+
console.log(chalk2.dim(" Make sure your schema uses defineSchema({ ... })\n"));
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
spinner.succeed(`Types generated for ${tables.length} table(s)`);
|
|
683
|
+
console.log("\n" + chalk2.green("\u2713") + " Tables:");
|
|
684
|
+
for (const table of tables) {
|
|
685
|
+
console.log(chalk2.dim(` - ${table.name} (${table.columns.length} columns)`));
|
|
686
|
+
}
|
|
687
|
+
if (envVarKeys.length > 0) {
|
|
688
|
+
console.log("\n" + chalk2.green("\u2713") + ` Environment variables: ${envVarKeys.length} key(s)`);
|
|
689
|
+
for (const key of envVarKeys) {
|
|
690
|
+
console.log(chalk2.dim(` - ${key}`));
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
const relativeOutput = path3.relative(process.cwd(), outputDir);
|
|
694
|
+
console.log("\n" + chalk2.green("\u2713") + " Generated files:");
|
|
695
|
+
console.log(chalk2.dim(` ${relativeOutput}/db.ts`));
|
|
696
|
+
console.log(chalk2.dim(` ${relativeOutput}/api.ts`));
|
|
697
|
+
console.log(chalk2.dim(` ${relativeOutput}/env.ts`));
|
|
698
|
+
console.log(chalk2.dim(` ${relativeOutput}/index.ts
|
|
699
|
+
`));
|
|
700
|
+
} catch (error) {
|
|
701
|
+
spinner.fail("Failed to generate types");
|
|
702
|
+
console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
|
|
703
|
+
process.exit(1);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
export {
|
|
708
|
+
API_URL,
|
|
709
|
+
getCredentials,
|
|
710
|
+
requireAuth,
|
|
711
|
+
saveCredentials,
|
|
712
|
+
clearCredentials,
|
|
713
|
+
loadConfig,
|
|
714
|
+
resolveEnvironmentFromApiKey,
|
|
715
|
+
getApiUrl,
|
|
716
|
+
resolvePath,
|
|
717
|
+
detectFramework,
|
|
718
|
+
getFrameworkDevCommand,
|
|
719
|
+
generateTypes,
|
|
720
|
+
generateCommand
|
|
721
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
detectFramework,
|
|
6
6
|
generateCommand,
|
|
7
7
|
generateTypes,
|
|
8
|
+
getApiUrl,
|
|
8
9
|
getCredentials,
|
|
9
10
|
getFrameworkDevCommand,
|
|
10
11
|
loadConfig,
|
|
@@ -12,7 +13,7 @@ import {
|
|
|
12
13
|
resolveEnvironmentFromApiKey,
|
|
13
14
|
resolvePath,
|
|
14
15
|
saveCredentials
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-2QPN6NDY.js";
|
|
16
17
|
|
|
17
18
|
// src/index.ts
|
|
18
19
|
import { Command } from "commander";
|
|
@@ -34,8 +35,6 @@ import ora from "ora";
|
|
|
34
35
|
import fs from "fs-extra";
|
|
35
36
|
import path from "path";
|
|
36
37
|
import * as esbuild from "esbuild";
|
|
37
|
-
var isDev = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
38
|
-
var API_URL2 = isDev ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
|
|
39
38
|
async function deployCommand(options) {
|
|
40
39
|
const credentials = await requireAuth();
|
|
41
40
|
const configPath = path.resolve(process.cwd(), "tether.config.ts");
|
|
@@ -60,10 +59,11 @@ async function deployCommand(options) {
|
|
|
60
59
|
process.exit(1);
|
|
61
60
|
}
|
|
62
61
|
const environment = options.env || config.environment || await resolveEnvironmentFromApiKey() || "development";
|
|
62
|
+
const API_URL4 = `${getApiUrl(config)}/api/v1`;
|
|
63
63
|
console.log(chalk.bold("\n\u26A1 Deploying to Tether\n"));
|
|
64
64
|
console.log(chalk.dim(` Project: ${projectId}`));
|
|
65
65
|
console.log(chalk.dim(` Environment: ${environment}`));
|
|
66
|
-
console.log(chalk.dim(` API: ${
|
|
66
|
+
console.log(chalk.dim(` API: ${API_URL4}
|
|
67
67
|
`));
|
|
68
68
|
console.log(chalk.dim(" Generating types..."));
|
|
69
69
|
try {
|
|
@@ -84,7 +84,8 @@ async function deployCommand(options) {
|
|
|
84
84
|
}
|
|
85
85
|
console.log(chalk.green("\n\u2713 Deployment complete\n"));
|
|
86
86
|
}
|
|
87
|
-
async function deploySchemaToServer(projectId, token, schemaPath, environment, dryRun) {
|
|
87
|
+
async function deploySchemaToServer(projectId, token, schemaPath, environment, dryRun, apiUrl) {
|
|
88
|
+
const API_URL4 = apiUrl || `${getApiUrl()}/api/v1`;
|
|
88
89
|
const spinner = ora("Reading schema...").start();
|
|
89
90
|
try {
|
|
90
91
|
if (!await fs.pathExists(schemaPath)) {
|
|
@@ -114,7 +115,7 @@ async function deploySchemaToServer(projectId, token, schemaPath, environment, d
|
|
|
114
115
|
return;
|
|
115
116
|
}
|
|
116
117
|
spinner.text = "Deploying schema...";
|
|
117
|
-
const schemaUrl = `${
|
|
118
|
+
const schemaUrl = `${API_URL4}/projects/${projectId}/env/${environment}/deploy/schema`;
|
|
118
119
|
console.log(chalk.dim(`
|
|
119
120
|
URL: ${schemaUrl}`));
|
|
120
121
|
const rlsConfigs = tables.filter((t) => t.rls).map((t) => ({
|
|
@@ -188,7 +189,8 @@ ${messages.join("\n")}`);
|
|
|
188
189
|
}
|
|
189
190
|
return result.outputFiles[0].text;
|
|
190
191
|
}
|
|
191
|
-
async function deployFunctionsToServer(projectId, token, functionsDir, environment, dryRun) {
|
|
192
|
+
async function deployFunctionsToServer(projectId, token, functionsDir, environment, dryRun, apiUrl) {
|
|
193
|
+
const API_URL4 = apiUrl || `${getApiUrl()}/api/v1`;
|
|
192
194
|
const spinner = ora("Reading functions...").start();
|
|
193
195
|
try {
|
|
194
196
|
if (!await fs.pathExists(functionsDir)) {
|
|
@@ -245,7 +247,7 @@ ${helperSource}`;
|
|
|
245
247
|
return;
|
|
246
248
|
}
|
|
247
249
|
spinner.text = "Deploying functions...";
|
|
248
|
-
const functionsUrl = `${
|
|
250
|
+
const functionsUrl = `${API_URL4}/projects/${projectId}/env/${environment}/deploy/functions`;
|
|
249
251
|
console.log(chalk.dim(`
|
|
250
252
|
URL: ${functionsUrl}`));
|
|
251
253
|
const response = await fetch(functionsUrl, {
|
|
@@ -583,8 +585,8 @@ function parseFunctions(moduleName, source) {
|
|
|
583
585
|
}
|
|
584
586
|
|
|
585
587
|
// src/commands/init.ts
|
|
586
|
-
var
|
|
587
|
-
var
|
|
588
|
+
var isDev = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
589
|
+
var API_URL2 = isDev ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
|
|
588
590
|
async function getLatestVersion(packageName) {
|
|
589
591
|
try {
|
|
590
592
|
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
|
|
@@ -686,7 +688,7 @@ ${packageManager} is not installed on your system.`));
|
|
|
686
688
|
await scaffoldVanillaProject(projectName, projectPath, spinner);
|
|
687
689
|
}
|
|
688
690
|
spinner.text = "Creating project on Tether...";
|
|
689
|
-
const response = await fetch(`${
|
|
691
|
+
const response = await fetch(`${API_URL2}/projects`, {
|
|
690
692
|
method: "POST",
|
|
691
693
|
headers: {
|
|
692
694
|
"Content-Type": "application/json",
|
|
@@ -1243,8 +1245,8 @@ PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
|
|
|
1243
1245
|
}
|
|
1244
1246
|
}
|
|
1245
1247
|
}
|
|
1246
|
-
function getInstallCommand(pm,
|
|
1247
|
-
const devFlag =
|
|
1248
|
+
function getInstallCommand(pm, isDev3 = false) {
|
|
1249
|
+
const devFlag = isDev3 ? pm === "npm" ? "-D" : pm === "yarn" ? "-D" : pm === "pnpm" ? "-D" : "-d" : "";
|
|
1248
1250
|
return `${pm} ${pm === "npm" ? "install" : "add"} ${devFlag}`.trim();
|
|
1249
1251
|
}
|
|
1250
1252
|
async function installTetherPackages(projectPath, template, packageManager) {
|
|
@@ -1689,8 +1691,6 @@ import fs3 from "fs-extra";
|
|
|
1689
1691
|
import path3 from "path";
|
|
1690
1692
|
import { spawn } from "child_process";
|
|
1691
1693
|
import { watch } from "chokidar";
|
|
1692
|
-
var isDev3 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
1693
|
-
var API_BASE = isDev3 ? "http://localhost:3001" : "https://tether-api.strands.gg";
|
|
1694
1694
|
var frameworkProcess = null;
|
|
1695
1695
|
var schemaWatcher = null;
|
|
1696
1696
|
var functionsWatcher = null;
|
|
@@ -1706,7 +1706,7 @@ async function runGenerate(spinner) {
|
|
|
1706
1706
|
}
|
|
1707
1707
|
isGenerating = true;
|
|
1708
1708
|
try {
|
|
1709
|
-
const { generateTypes: generateTypes2 } = await import("./generate-
|
|
1709
|
+
const { generateTypes: generateTypes2 } = await import("./generate-KAPPABOL.js");
|
|
1710
1710
|
spinner.text = "Regenerating types...";
|
|
1711
1711
|
await generateTypes2({ silent: true });
|
|
1712
1712
|
spinner.succeed("Types regenerated");
|
|
@@ -1813,8 +1813,9 @@ async function validateFunctions(functionsDir) {
|
|
|
1813
1813
|
}
|
|
1814
1814
|
return { valid: errors.length === 0, errors };
|
|
1815
1815
|
}
|
|
1816
|
-
function connectToEnvironment(projectId, environment, token, spinner) {
|
|
1817
|
-
const
|
|
1816
|
+
function connectToEnvironment(projectId, environment, token, spinner, apiBase) {
|
|
1817
|
+
const base = apiBase || getApiUrl();
|
|
1818
|
+
const wsBase = base.replace("https://", "wss://").replace("http://", "ws://");
|
|
1818
1819
|
const wsUrl = environment && environment !== "production" ? `${wsBase}/ws/${projectId}/${environment}?token=${encodeURIComponent(token)}` : `${wsBase}/ws/${projectId}?token=${encodeURIComponent(token)}`;
|
|
1819
1820
|
let reconnectAttempts = 0;
|
|
1820
1821
|
const maxReconnectAttempts = 10;
|
|
@@ -1976,7 +1977,7 @@ async function devCommand(options) {
|
|
|
1976
1977
|
}
|
|
1977
1978
|
}
|
|
1978
1979
|
spinner.text = "Generating types...";
|
|
1979
|
-
const { generateTypes: generateTypes2 } = await import("./generate-
|
|
1980
|
+
const { generateTypes: generateTypes2 } = await import("./generate-KAPPABOL.js");
|
|
1980
1981
|
await generateTypes2({ silent: true });
|
|
1981
1982
|
spinner.succeed("Types generated");
|
|
1982
1983
|
if (config.projectId) {
|
|
@@ -2033,7 +2034,7 @@ async function devCommand(options) {
|
|
|
2033
2034
|
}
|
|
2034
2035
|
spinner.succeed("File watchers ready");
|
|
2035
2036
|
if (config.projectId) {
|
|
2036
|
-
connectToEnvironment(config.projectId, environment, credentials.accessToken, spinner);
|
|
2037
|
+
connectToEnvironment(config.projectId, environment, credentials.accessToken, spinner, getApiUrl(config));
|
|
2037
2038
|
}
|
|
2038
2039
|
if (devCmd && !options.skipFramework) {
|
|
2039
2040
|
spinner.start(`Starting ${framework} dev server...`);
|
|
@@ -2061,9 +2062,9 @@ import ora4 from "ora";
|
|
|
2061
2062
|
import os from "os";
|
|
2062
2063
|
import { exec } from "child_process";
|
|
2063
2064
|
import readline from "readline";
|
|
2064
|
-
var
|
|
2065
|
-
var
|
|
2066
|
-
var AUTH_URL =
|
|
2065
|
+
var isDev2 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
2066
|
+
var API_URL3 = `${getApiUrl()}/api/v1`;
|
|
2067
|
+
var AUTH_URL = isDev2 ? "http://localhost:3000/cli" : "https://tthr.io/cli";
|
|
2067
2068
|
async function loginCommand() {
|
|
2068
2069
|
console.log(chalk4.bold("\u26A1 Login to Tether\n"));
|
|
2069
2070
|
const existing = await getCredentials();
|
|
@@ -2152,7 +2153,7 @@ async function requestDeviceCode() {
|
|
|
2152
2153
|
const userCode = generateUserCode();
|
|
2153
2154
|
const deviceCode = crypto.randomUUID();
|
|
2154
2155
|
const deviceName = os.hostname();
|
|
2155
|
-
const response = await fetch(`${
|
|
2156
|
+
const response = await fetch(`${API_URL3}/auth/device`, {
|
|
2156
2157
|
method: "POST",
|
|
2157
2158
|
headers: { "Content-Type": "application/json" },
|
|
2158
2159
|
body: JSON.stringify({ userCode, deviceCode, deviceName })
|
|
@@ -2186,7 +2187,7 @@ async function pollForApproval(deviceCode, interval, expiresIn) {
|
|
|
2186
2187
|
while (Date.now() < expiresAt) {
|
|
2187
2188
|
await sleep(interval * 1e3);
|
|
2188
2189
|
spinner.text = `Checking... ${chalk4.dim(`(${Math.ceil((expiresAt - Date.now()) / 1e3)}s remaining)`)}`;
|
|
2189
|
-
const response = await fetch(`${
|
|
2190
|
+
const response = await fetch(`${API_URL3}/auth/device/${deviceCode}`, {
|
|
2190
2191
|
method: "GET"
|
|
2191
2192
|
}).catch(() => null);
|
|
2192
2193
|
updateCountdown();
|
|
@@ -2278,18 +2279,18 @@ function detectPackageManager() {
|
|
|
2278
2279
|
}
|
|
2279
2280
|
return "npm";
|
|
2280
2281
|
}
|
|
2281
|
-
function getInstallCommand2(pm, packages,
|
|
2282
|
+
function getInstallCommand2(pm, packages, isDev3) {
|
|
2282
2283
|
const packagesStr = packages.join(" ");
|
|
2283
2284
|
switch (pm) {
|
|
2284
2285
|
case "bun":
|
|
2285
|
-
return
|
|
2286
|
+
return isDev3 ? `bun add -d ${packagesStr}` : `bun add ${packagesStr}`;
|
|
2286
2287
|
case "pnpm":
|
|
2287
|
-
return
|
|
2288
|
+
return isDev3 ? `pnpm add -D ${packagesStr}` : `pnpm add ${packagesStr}`;
|
|
2288
2289
|
case "yarn":
|
|
2289
|
-
return
|
|
2290
|
+
return isDev3 ? `yarn add -D ${packagesStr}` : `yarn add ${packagesStr}`;
|
|
2290
2291
|
case "npm":
|
|
2291
2292
|
default:
|
|
2292
|
-
return
|
|
2293
|
+
return isDev3 ? `npm install -D ${packagesStr}` : `npm install ${packagesStr}`;
|
|
2293
2294
|
}
|
|
2294
2295
|
}
|
|
2295
2296
|
async function getLatestVersion2(packageName) {
|
|
@@ -2398,8 +2399,6 @@ import chalk6 from "chalk";
|
|
|
2398
2399
|
import ora6 from "ora";
|
|
2399
2400
|
import fs5 from "fs-extra";
|
|
2400
2401
|
import path5 from "path";
|
|
2401
|
-
var isDev5 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
2402
|
-
var API_URL5 = isDev5 ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
|
|
2403
2402
|
async function execCommand(sql) {
|
|
2404
2403
|
if (!sql || sql.trim() === "") {
|
|
2405
2404
|
console.log(chalk6.red("\nError: SQL query required"));
|
|
@@ -2419,9 +2418,11 @@ async function execCommand(sql) {
|
|
|
2419
2418
|
console.log(chalk6.dim("Make sure TETHER_PROJECT_ID is set in your .env file\n"));
|
|
2420
2419
|
process.exit(1);
|
|
2421
2420
|
}
|
|
2421
|
+
const config = await loadConfig();
|
|
2422
|
+
const API_URL4 = `${getApiUrl(config)}/api/v1`;
|
|
2422
2423
|
const spinner = ora6("Executing query...").start();
|
|
2423
2424
|
try {
|
|
2424
|
-
const response = await fetch(`${
|
|
2425
|
+
const response = await fetch(`${API_URL4}/projects/${projectId}/exec`, {
|
|
2425
2426
|
method: "POST",
|
|
2426
2427
|
headers: {
|
|
2427
2428
|
"Content-Type": "application/json",
|
|
@@ -2471,8 +2472,10 @@ import chalk7 from "chalk";
|
|
|
2471
2472
|
import ora7 from "ora";
|
|
2472
2473
|
import fs6 from "fs-extra";
|
|
2473
2474
|
import path6 from "path";
|
|
2474
|
-
|
|
2475
|
-
|
|
2475
|
+
async function resolveApiUrl() {
|
|
2476
|
+
const config = await loadConfig();
|
|
2477
|
+
return `${getApiUrl(config)}/api/v1`;
|
|
2478
|
+
}
|
|
2476
2479
|
async function getProjectId() {
|
|
2477
2480
|
const envPath = path6.resolve(process.cwd(), ".env");
|
|
2478
2481
|
let projectId;
|
|
@@ -2491,9 +2494,10 @@ async function getProjectId() {
|
|
|
2491
2494
|
async function envListCommand() {
|
|
2492
2495
|
const credentials = await requireAuth();
|
|
2493
2496
|
const projectId = await getProjectId();
|
|
2497
|
+
const API_URL4 = await resolveApiUrl();
|
|
2494
2498
|
const spinner = ora7("Fetching environments...").start();
|
|
2495
2499
|
try {
|
|
2496
|
-
const response = await fetch(`${
|
|
2500
|
+
const response = await fetch(`${API_URL4}/projects/${projectId}/environments`, {
|
|
2497
2501
|
headers: {
|
|
2498
2502
|
"Authorization": `Bearer ${credentials.accessToken}`
|
|
2499
2503
|
}
|
|
@@ -2523,6 +2527,7 @@ async function envListCommand() {
|
|
|
2523
2527
|
async function envCreateCommand(name, options) {
|
|
2524
2528
|
const credentials = await requireAuth();
|
|
2525
2529
|
const projectId = await getProjectId();
|
|
2530
|
+
const API_URL4 = await resolveApiUrl();
|
|
2526
2531
|
const normalizedName = name.toLowerCase();
|
|
2527
2532
|
if (!/^[a-z][a-z0-9-_]*$/.test(normalizedName)) {
|
|
2528
2533
|
console.log(chalk7.red("\nError: Invalid environment name"));
|
|
@@ -2536,7 +2541,7 @@ async function envCreateCommand(name, options) {
|
|
|
2536
2541
|
body.cloneFrom = options.from;
|
|
2537
2542
|
spinner.text = `Creating environment '${normalizedName}' from '${options.from}'...`;
|
|
2538
2543
|
}
|
|
2539
|
-
const response = await fetch(`${
|
|
2544
|
+
const response = await fetch(`${API_URL4}/projects/${projectId}/environments`, {
|
|
2540
2545
|
method: "POST",
|
|
2541
2546
|
headers: {
|
|
2542
2547
|
"Content-Type": "application/json",
|
|
@@ -2565,9 +2570,10 @@ async function envCreateCommand(name, options) {
|
|
|
2565
2570
|
async function envDeleteCommand(name) {
|
|
2566
2571
|
const credentials = await requireAuth();
|
|
2567
2572
|
const projectId = await getProjectId();
|
|
2573
|
+
const API_URL4 = await resolveApiUrl();
|
|
2568
2574
|
const spinner = ora7(`Deleting environment '${name}'...`).start();
|
|
2569
2575
|
try {
|
|
2570
|
-
const response = await fetch(`${
|
|
2576
|
+
const response = await fetch(`${API_URL4}/projects/${projectId}/environments/${name}`, {
|
|
2571
2577
|
method: "DELETE",
|
|
2572
2578
|
headers: {
|
|
2573
2579
|
"Authorization": `Bearer ${credentials.accessToken}`
|
|
@@ -2587,9 +2593,10 @@ async function envDeleteCommand(name) {
|
|
|
2587
2593
|
async function envDefaultCommand(name) {
|
|
2588
2594
|
const credentials = await requireAuth();
|
|
2589
2595
|
const projectId = await getProjectId();
|
|
2596
|
+
const API_URL4 = await resolveApiUrl();
|
|
2590
2597
|
const spinner = ora7(`Setting '${name}' as default environment...`).start();
|
|
2591
2598
|
try {
|
|
2592
|
-
const response = await fetch(`${
|
|
2599
|
+
const response = await fetch(`${API_URL4}/projects/${projectId}/environments/default`, {
|
|
2593
2600
|
method: "PATCH",
|
|
2594
2601
|
headers: {
|
|
2595
2602
|
"Content-Type": "application/json",
|