tthr 0.3.10 → 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 +63 -40
- 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)) {
|
|
@@ -200,10 +202,16 @@ async function deployFunctionsToServer(projectId, token, functionsDir, environme
|
|
|
200
202
|
}
|
|
201
203
|
const files = await fs.readdir(functionsDir);
|
|
202
204
|
const tsFiles = files.filter((f) => f.endsWith(".ts") && !f.startsWith("_"));
|
|
205
|
+
const helperFiles = files.filter((f) => f.endsWith(".ts") && f.startsWith("_"));
|
|
203
206
|
if (tsFiles.length === 0) {
|
|
204
207
|
spinner.info("No function files found");
|
|
205
208
|
return;
|
|
206
209
|
}
|
|
210
|
+
const helperSources = /* @__PURE__ */ new Map();
|
|
211
|
+
for (const hf of helperFiles) {
|
|
212
|
+
const hfPath = path.join(functionsDir, hf);
|
|
213
|
+
helperSources.set(hf, await fs.readFile(hfPath, "utf-8"));
|
|
214
|
+
}
|
|
207
215
|
const functions = [];
|
|
208
216
|
for (const file of tsFiles) {
|
|
209
217
|
const filePath = path.join(functionsDir, file);
|
|
@@ -213,9 +221,19 @@ async function deployFunctionsToServer(projectId, token, functionsDir, environme
|
|
|
213
221
|
if (parsedFunctions.length === 0) continue;
|
|
214
222
|
spinner.text = `Bundling ${file}...`;
|
|
215
223
|
const bundledSource = await bundleFunctionFile(filePath);
|
|
224
|
+
let combinedRawSource = source;
|
|
225
|
+
for (const [helperFile, helperSource] of helperSources) {
|
|
226
|
+
const helperBase = helperFile.replace(".ts", "");
|
|
227
|
+
if (source.includes(`"./${helperBase}"`) || source.includes(`'./${helperBase}'`)) {
|
|
228
|
+
combinedRawSource += `
|
|
229
|
+
|
|
230
|
+
// --- ${helperFile} ---
|
|
231
|
+
${helperSource}`;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
216
234
|
for (const fn of parsedFunctions) {
|
|
217
235
|
fn.source = bundledSource;
|
|
218
|
-
fn.rawSource =
|
|
236
|
+
fn.rawSource = combinedRawSource;
|
|
219
237
|
}
|
|
220
238
|
functions.push(...parsedFunctions);
|
|
221
239
|
}
|
|
@@ -229,7 +247,7 @@ async function deployFunctionsToServer(projectId, token, functionsDir, environme
|
|
|
229
247
|
return;
|
|
230
248
|
}
|
|
231
249
|
spinner.text = "Deploying functions...";
|
|
232
|
-
const functionsUrl = `${
|
|
250
|
+
const functionsUrl = `${API_URL4}/projects/${projectId}/env/${environment}/deploy/functions`;
|
|
233
251
|
console.log(chalk.dim(`
|
|
234
252
|
URL: ${functionsUrl}`));
|
|
235
253
|
const response = await fetch(functionsUrl, {
|
|
@@ -567,8 +585,8 @@ function parseFunctions(moduleName, source) {
|
|
|
567
585
|
}
|
|
568
586
|
|
|
569
587
|
// src/commands/init.ts
|
|
570
|
-
var
|
|
571
|
-
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";
|
|
572
590
|
async function getLatestVersion(packageName) {
|
|
573
591
|
try {
|
|
574
592
|
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`);
|
|
@@ -670,7 +688,7 @@ ${packageManager} is not installed on your system.`));
|
|
|
670
688
|
await scaffoldVanillaProject(projectName, projectPath, spinner);
|
|
671
689
|
}
|
|
672
690
|
spinner.text = "Creating project on Tether...";
|
|
673
|
-
const response = await fetch(`${
|
|
691
|
+
const response = await fetch(`${API_URL2}/projects`, {
|
|
674
692
|
method: "POST",
|
|
675
693
|
headers: {
|
|
676
694
|
"Content-Type": "application/json",
|
|
@@ -1227,8 +1245,8 @@ PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
|
|
|
1227
1245
|
}
|
|
1228
1246
|
}
|
|
1229
1247
|
}
|
|
1230
|
-
function getInstallCommand(pm,
|
|
1231
|
-
const devFlag =
|
|
1248
|
+
function getInstallCommand(pm, isDev3 = false) {
|
|
1249
|
+
const devFlag = isDev3 ? pm === "npm" ? "-D" : pm === "yarn" ? "-D" : pm === "pnpm" ? "-D" : "-d" : "";
|
|
1232
1250
|
return `${pm} ${pm === "npm" ? "install" : "add"} ${devFlag}`.trim();
|
|
1233
1251
|
}
|
|
1234
1252
|
async function installTetherPackages(projectPath, template, packageManager) {
|
|
@@ -1673,8 +1691,6 @@ import fs3 from "fs-extra";
|
|
|
1673
1691
|
import path3 from "path";
|
|
1674
1692
|
import { spawn } from "child_process";
|
|
1675
1693
|
import { watch } from "chokidar";
|
|
1676
|
-
var isDev3 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
1677
|
-
var API_BASE = isDev3 ? "http://localhost:3001" : "https://tether-api.strands.gg";
|
|
1678
1694
|
var frameworkProcess = null;
|
|
1679
1695
|
var schemaWatcher = null;
|
|
1680
1696
|
var functionsWatcher = null;
|
|
@@ -1690,7 +1706,7 @@ async function runGenerate(spinner) {
|
|
|
1690
1706
|
}
|
|
1691
1707
|
isGenerating = true;
|
|
1692
1708
|
try {
|
|
1693
|
-
const { generateTypes: generateTypes2 } = await import("./generate-
|
|
1709
|
+
const { generateTypes: generateTypes2 } = await import("./generate-KAPPABOL.js");
|
|
1694
1710
|
spinner.text = "Regenerating types...";
|
|
1695
1711
|
await generateTypes2({ silent: true });
|
|
1696
1712
|
spinner.succeed("Types regenerated");
|
|
@@ -1797,8 +1813,9 @@ async function validateFunctions(functionsDir) {
|
|
|
1797
1813
|
}
|
|
1798
1814
|
return { valid: errors.length === 0, errors };
|
|
1799
1815
|
}
|
|
1800
|
-
function connectToEnvironment(projectId, environment, token, spinner) {
|
|
1801
|
-
const
|
|
1816
|
+
function connectToEnvironment(projectId, environment, token, spinner, apiBase) {
|
|
1817
|
+
const base = apiBase || getApiUrl();
|
|
1818
|
+
const wsBase = base.replace("https://", "wss://").replace("http://", "ws://");
|
|
1802
1819
|
const wsUrl = environment && environment !== "production" ? `${wsBase}/ws/${projectId}/${environment}?token=${encodeURIComponent(token)}` : `${wsBase}/ws/${projectId}?token=${encodeURIComponent(token)}`;
|
|
1803
1820
|
let reconnectAttempts = 0;
|
|
1804
1821
|
const maxReconnectAttempts = 10;
|
|
@@ -1960,7 +1977,7 @@ async function devCommand(options) {
|
|
|
1960
1977
|
}
|
|
1961
1978
|
}
|
|
1962
1979
|
spinner.text = "Generating types...";
|
|
1963
|
-
const { generateTypes: generateTypes2 } = await import("./generate-
|
|
1980
|
+
const { generateTypes: generateTypes2 } = await import("./generate-KAPPABOL.js");
|
|
1964
1981
|
await generateTypes2({ silent: true });
|
|
1965
1982
|
spinner.succeed("Types generated");
|
|
1966
1983
|
if (config.projectId) {
|
|
@@ -2017,7 +2034,7 @@ async function devCommand(options) {
|
|
|
2017
2034
|
}
|
|
2018
2035
|
spinner.succeed("File watchers ready");
|
|
2019
2036
|
if (config.projectId) {
|
|
2020
|
-
connectToEnvironment(config.projectId, environment, credentials.accessToken, spinner);
|
|
2037
|
+
connectToEnvironment(config.projectId, environment, credentials.accessToken, spinner, getApiUrl(config));
|
|
2021
2038
|
}
|
|
2022
2039
|
if (devCmd && !options.skipFramework) {
|
|
2023
2040
|
spinner.start(`Starting ${framework} dev server...`);
|
|
@@ -2045,9 +2062,9 @@ import ora4 from "ora";
|
|
|
2045
2062
|
import os from "os";
|
|
2046
2063
|
import { exec } from "child_process";
|
|
2047
2064
|
import readline from "readline";
|
|
2048
|
-
var
|
|
2049
|
-
var
|
|
2050
|
-
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";
|
|
2051
2068
|
async function loginCommand() {
|
|
2052
2069
|
console.log(chalk4.bold("\u26A1 Login to Tether\n"));
|
|
2053
2070
|
const existing = await getCredentials();
|
|
@@ -2136,7 +2153,7 @@ async function requestDeviceCode() {
|
|
|
2136
2153
|
const userCode = generateUserCode();
|
|
2137
2154
|
const deviceCode = crypto.randomUUID();
|
|
2138
2155
|
const deviceName = os.hostname();
|
|
2139
|
-
const response = await fetch(`${
|
|
2156
|
+
const response = await fetch(`${API_URL3}/auth/device`, {
|
|
2140
2157
|
method: "POST",
|
|
2141
2158
|
headers: { "Content-Type": "application/json" },
|
|
2142
2159
|
body: JSON.stringify({ userCode, deviceCode, deviceName })
|
|
@@ -2170,7 +2187,7 @@ async function pollForApproval(deviceCode, interval, expiresIn) {
|
|
|
2170
2187
|
while (Date.now() < expiresAt) {
|
|
2171
2188
|
await sleep(interval * 1e3);
|
|
2172
2189
|
spinner.text = `Checking... ${chalk4.dim(`(${Math.ceil((expiresAt - Date.now()) / 1e3)}s remaining)`)}`;
|
|
2173
|
-
const response = await fetch(`${
|
|
2190
|
+
const response = await fetch(`${API_URL3}/auth/device/${deviceCode}`, {
|
|
2174
2191
|
method: "GET"
|
|
2175
2192
|
}).catch(() => null);
|
|
2176
2193
|
updateCountdown();
|
|
@@ -2262,18 +2279,18 @@ function detectPackageManager() {
|
|
|
2262
2279
|
}
|
|
2263
2280
|
return "npm";
|
|
2264
2281
|
}
|
|
2265
|
-
function getInstallCommand2(pm, packages,
|
|
2282
|
+
function getInstallCommand2(pm, packages, isDev3) {
|
|
2266
2283
|
const packagesStr = packages.join(" ");
|
|
2267
2284
|
switch (pm) {
|
|
2268
2285
|
case "bun":
|
|
2269
|
-
return
|
|
2286
|
+
return isDev3 ? `bun add -d ${packagesStr}` : `bun add ${packagesStr}`;
|
|
2270
2287
|
case "pnpm":
|
|
2271
|
-
return
|
|
2288
|
+
return isDev3 ? `pnpm add -D ${packagesStr}` : `pnpm add ${packagesStr}`;
|
|
2272
2289
|
case "yarn":
|
|
2273
|
-
return
|
|
2290
|
+
return isDev3 ? `yarn add -D ${packagesStr}` : `yarn add ${packagesStr}`;
|
|
2274
2291
|
case "npm":
|
|
2275
2292
|
default:
|
|
2276
|
-
return
|
|
2293
|
+
return isDev3 ? `npm install -D ${packagesStr}` : `npm install ${packagesStr}`;
|
|
2277
2294
|
}
|
|
2278
2295
|
}
|
|
2279
2296
|
async function getLatestVersion2(packageName) {
|
|
@@ -2382,8 +2399,6 @@ import chalk6 from "chalk";
|
|
|
2382
2399
|
import ora6 from "ora";
|
|
2383
2400
|
import fs5 from "fs-extra";
|
|
2384
2401
|
import path5 from "path";
|
|
2385
|
-
var isDev5 = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
2386
|
-
var API_URL5 = isDev5 ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
|
|
2387
2402
|
async function execCommand(sql) {
|
|
2388
2403
|
if (!sql || sql.trim() === "") {
|
|
2389
2404
|
console.log(chalk6.red("\nError: SQL query required"));
|
|
@@ -2403,9 +2418,11 @@ async function execCommand(sql) {
|
|
|
2403
2418
|
console.log(chalk6.dim("Make sure TETHER_PROJECT_ID is set in your .env file\n"));
|
|
2404
2419
|
process.exit(1);
|
|
2405
2420
|
}
|
|
2421
|
+
const config = await loadConfig();
|
|
2422
|
+
const API_URL4 = `${getApiUrl(config)}/api/v1`;
|
|
2406
2423
|
const spinner = ora6("Executing query...").start();
|
|
2407
2424
|
try {
|
|
2408
|
-
const response = await fetch(`${
|
|
2425
|
+
const response = await fetch(`${API_URL4}/projects/${projectId}/exec`, {
|
|
2409
2426
|
method: "POST",
|
|
2410
2427
|
headers: {
|
|
2411
2428
|
"Content-Type": "application/json",
|
|
@@ -2455,8 +2472,10 @@ import chalk7 from "chalk";
|
|
|
2455
2472
|
import ora7 from "ora";
|
|
2456
2473
|
import fs6 from "fs-extra";
|
|
2457
2474
|
import path6 from "path";
|
|
2458
|
-
|
|
2459
|
-
|
|
2475
|
+
async function resolveApiUrl() {
|
|
2476
|
+
const config = await loadConfig();
|
|
2477
|
+
return `${getApiUrl(config)}/api/v1`;
|
|
2478
|
+
}
|
|
2460
2479
|
async function getProjectId() {
|
|
2461
2480
|
const envPath = path6.resolve(process.cwd(), ".env");
|
|
2462
2481
|
let projectId;
|
|
@@ -2475,9 +2494,10 @@ async function getProjectId() {
|
|
|
2475
2494
|
async function envListCommand() {
|
|
2476
2495
|
const credentials = await requireAuth();
|
|
2477
2496
|
const projectId = await getProjectId();
|
|
2497
|
+
const API_URL4 = await resolveApiUrl();
|
|
2478
2498
|
const spinner = ora7("Fetching environments...").start();
|
|
2479
2499
|
try {
|
|
2480
|
-
const response = await fetch(`${
|
|
2500
|
+
const response = await fetch(`${API_URL4}/projects/${projectId}/environments`, {
|
|
2481
2501
|
headers: {
|
|
2482
2502
|
"Authorization": `Bearer ${credentials.accessToken}`
|
|
2483
2503
|
}
|
|
@@ -2507,6 +2527,7 @@ async function envListCommand() {
|
|
|
2507
2527
|
async function envCreateCommand(name, options) {
|
|
2508
2528
|
const credentials = await requireAuth();
|
|
2509
2529
|
const projectId = await getProjectId();
|
|
2530
|
+
const API_URL4 = await resolveApiUrl();
|
|
2510
2531
|
const normalizedName = name.toLowerCase();
|
|
2511
2532
|
if (!/^[a-z][a-z0-9-_]*$/.test(normalizedName)) {
|
|
2512
2533
|
console.log(chalk7.red("\nError: Invalid environment name"));
|
|
@@ -2520,7 +2541,7 @@ async function envCreateCommand(name, options) {
|
|
|
2520
2541
|
body.cloneFrom = options.from;
|
|
2521
2542
|
spinner.text = `Creating environment '${normalizedName}' from '${options.from}'...`;
|
|
2522
2543
|
}
|
|
2523
|
-
const response = await fetch(`${
|
|
2544
|
+
const response = await fetch(`${API_URL4}/projects/${projectId}/environments`, {
|
|
2524
2545
|
method: "POST",
|
|
2525
2546
|
headers: {
|
|
2526
2547
|
"Content-Type": "application/json",
|
|
@@ -2549,9 +2570,10 @@ async function envCreateCommand(name, options) {
|
|
|
2549
2570
|
async function envDeleteCommand(name) {
|
|
2550
2571
|
const credentials = await requireAuth();
|
|
2551
2572
|
const projectId = await getProjectId();
|
|
2573
|
+
const API_URL4 = await resolveApiUrl();
|
|
2552
2574
|
const spinner = ora7(`Deleting environment '${name}'...`).start();
|
|
2553
2575
|
try {
|
|
2554
|
-
const response = await fetch(`${
|
|
2576
|
+
const response = await fetch(`${API_URL4}/projects/${projectId}/environments/${name}`, {
|
|
2555
2577
|
method: "DELETE",
|
|
2556
2578
|
headers: {
|
|
2557
2579
|
"Authorization": `Bearer ${credentials.accessToken}`
|
|
@@ -2571,9 +2593,10 @@ async function envDeleteCommand(name) {
|
|
|
2571
2593
|
async function envDefaultCommand(name) {
|
|
2572
2594
|
const credentials = await requireAuth();
|
|
2573
2595
|
const projectId = await getProjectId();
|
|
2596
|
+
const API_URL4 = await resolveApiUrl();
|
|
2574
2597
|
const spinner = ora7(`Setting '${name}' as default environment...`).start();
|
|
2575
2598
|
try {
|
|
2576
|
-
const response = await fetch(`${
|
|
2599
|
+
const response = await fetch(`${API_URL4}/projects/${projectId}/environments/default`, {
|
|
2577
2600
|
method: "PATCH",
|
|
2578
2601
|
headers: {
|
|
2579
2602
|
"Content-Type": "application/json",
|