tthr 0.3.15 → 0.3.17

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