tthr 0.3.14 → 0.3.16

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",
@@ -1243,8 +1244,8 @@ PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
1243
1244
  }
1244
1245
  }
1245
1246
  }
1246
- function getInstallCommand(pm, isDev2 = false) {
1247
- const devFlag = isDev2 ? pm === "npm" ? "-D" : pm === "yarn" ? "-D" : pm === "pnpm" ? "-D" : "-d" : "";
1247
+ function getInstallCommand(pm, isDev = false) {
1248
+ const devFlag = isDev ? pm === "npm" ? "-D" : pm === "yarn" ? "-D" : pm === "pnpm" ? "-D" : "-d" : "";
1248
1249
  return `${pm} ${pm === "npm" ? "install" : "add"} ${devFlag}`.trim();
1249
1250
  }
1250
1251
  async function installTetherPackages(projectPath, template, packageManager) {
@@ -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,10 +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 isDev = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
2065
- var API_URL2 = `${getApiUrl()}/api/v1`;
2066
- var AUTH_URL = isDev ? "http://localhost:3000/cli" : "https://tthr.io/cli";
2065
+ var API_URL = "";
2067
2066
  async function loginCommand() {
2067
+ API_URL = `${await resolveApiUrl()}/api/v1`;
2068
2068
  console.log(chalk4.bold("\u26A1 Login to Tether\n"));
2069
2069
  const existing = await getCredentials();
2070
2070
  if (existing) {
@@ -2076,7 +2076,7 @@ async function loginCommand() {
2076
2076
  try {
2077
2077
  const deviceCode = await requestDeviceCode();
2078
2078
  spinner.stop();
2079
- const authUrl = `${AUTH_URL}/${deviceCode.userCode}`;
2079
+ const authUrl = `${deviceCode.authUrl}/${deviceCode.userCode}`;
2080
2080
  console.log(chalk4.dim("Authenticate in your browser:\n"));
2081
2081
  console.log(chalk4.cyan.bold(` ${authUrl}
2082
2082
  `));
@@ -2092,7 +2092,7 @@ async function loginCommand() {
2092
2092
  } catch (error) {
2093
2093
  spinner.fail("Authentication failed");
2094
2094
  console.error(chalk4.red(error instanceof Error ? error.message : "Unknown error"));
2095
- console.log(chalk4.dim("\nTry again or visit https://tthr.io/docs/cli for help\n"));
2095
+ console.log(chalk4.dim("\nTry again or visit the docs for help\n"));
2096
2096
  process.exit(1);
2097
2097
  }
2098
2098
  }
@@ -2152,7 +2152,7 @@ async function requestDeviceCode() {
2152
2152
  const userCode = generateUserCode();
2153
2153
  const deviceCode = crypto.randomUUID();
2154
2154
  const deviceName = os.hostname();
2155
- const response = await fetch(`${API_URL2}/auth/device`, {
2155
+ const response = await fetch(`${API_URL}/auth/device`, {
2156
2156
  method: "POST",
2157
2157
  headers: { "Content-Type": "application/json" },
2158
2158
  body: JSON.stringify({ userCode, deviceCode, deviceName })
@@ -2160,13 +2160,15 @@ async function requestDeviceCode() {
2160
2160
  if (response?.ok) {
2161
2161
  return await response.json();
2162
2162
  }
2163
+ const baseUrl = API_URL.replace(/\/api\/v1$/, "");
2163
2164
  return {
2164
2165
  deviceCode,
2165
2166
  userCode,
2166
2167
  expiresIn: 60,
2167
2168
  // 1 minute
2168
- interval: 5
2169
+ interval: 5,
2169
2170
  // Poll every 5 seconds
2171
+ authUrl: `${baseUrl}/cli`
2170
2172
  };
2171
2173
  }
2172
2174
  async function pollForApproval(deviceCode, interval, expiresIn) {
@@ -2186,7 +2188,7 @@ async function pollForApproval(deviceCode, interval, expiresIn) {
2186
2188
  while (Date.now() < expiresAt) {
2187
2189
  await sleep(interval * 1e3);
2188
2190
  spinner.text = `Checking... ${chalk4.dim(`(${Math.ceil((expiresAt - Date.now()) / 1e3)}s remaining)`)}`;
2189
- const response = await fetch(`${API_URL2}/auth/device/${deviceCode}`, {
2191
+ const response = await fetch(`${API_URL}/auth/device/${deviceCode}`, {
2190
2192
  method: "GET"
2191
2193
  }).catch(() => null);
2192
2194
  updateCountdown();
@@ -2278,18 +2280,18 @@ function detectPackageManager() {
2278
2280
  }
2279
2281
  return "npm";
2280
2282
  }
2281
- function getInstallCommand2(pm, packages, isDev2) {
2283
+ function getInstallCommand2(pm, packages, isDev) {
2282
2284
  const packagesStr = packages.join(" ");
2283
2285
  switch (pm) {
2284
2286
  case "bun":
2285
- return isDev2 ? `bun add -d ${packagesStr}` : `bun add ${packagesStr}`;
2287
+ return isDev ? `bun add -d ${packagesStr}` : `bun add ${packagesStr}`;
2286
2288
  case "pnpm":
2287
- return isDev2 ? `pnpm add -D ${packagesStr}` : `pnpm add ${packagesStr}`;
2289
+ return isDev ? `pnpm add -D ${packagesStr}` : `pnpm add ${packagesStr}`;
2288
2290
  case "yarn":
2289
- return isDev2 ? `yarn add -D ${packagesStr}` : `yarn add ${packagesStr}`;
2291
+ return isDev ? `yarn add -D ${packagesStr}` : `yarn add ${packagesStr}`;
2290
2292
  case "npm":
2291
2293
  default:
2292
- return isDev2 ? `npm install -D ${packagesStr}` : `npm install ${packagesStr}`;
2294
+ return isDev ? `npm install -D ${packagesStr}` : `npm install ${packagesStr}`;
2293
2295
  }
2294
2296
  }
2295
2297
  async function getLatestVersion2(packageName) {
@@ -2418,10 +2420,10 @@ async function execCommand(sql) {
2418
2420
  process.exit(1);
2419
2421
  }
2420
2422
  const config = await loadConfig();
2421
- const API_URL3 = `${getApiUrl(config)}/api/v1`;
2423
+ const API_URL2 = `${getApiUrl2(config)}/api/v1`;
2422
2424
  const spinner = ora6("Executing query...").start();
2423
2425
  try {
2424
- const response = await fetch(`${API_URL3}/projects/${projectId}/exec`, {
2426
+ const response = await fetch(`${API_URL2}/projects/${projectId}/exec`, {
2425
2427
  method: "POST",
2426
2428
  headers: {
2427
2429
  "Content-Type": "application/json",
@@ -2471,9 +2473,9 @@ import chalk7 from "chalk";
2471
2473
  import ora7 from "ora";
2472
2474
  import fs6 from "fs-extra";
2473
2475
  import path6 from "path";
2474
- async function resolveApiUrl() {
2476
+ async function resolveApiUrl2() {
2475
2477
  const config = await loadConfig();
2476
- return `${getApiUrl(config)}/api/v1`;
2478
+ return `${getApiUrl2(config)}/api/v1`;
2477
2479
  }
2478
2480
  async function getProjectId() {
2479
2481
  const envPath = path6.resolve(process.cwd(), ".env");
@@ -2493,10 +2495,10 @@ async function getProjectId() {
2493
2495
  async function envListCommand() {
2494
2496
  const credentials = await requireAuth();
2495
2497
  const projectId = await getProjectId();
2496
- const API_URL3 = await resolveApiUrl();
2498
+ const API_URL2 = await resolveApiUrl2();
2497
2499
  const spinner = ora7("Fetching environments...").start();
2498
2500
  try {
2499
- const response = await fetch(`${API_URL3}/projects/${projectId}/environments`, {
2501
+ const response = await fetch(`${API_URL2}/projects/${projectId}/environments`, {
2500
2502
  headers: {
2501
2503
  "Authorization": `Bearer ${credentials.accessToken}`
2502
2504
  }
@@ -2526,7 +2528,7 @@ async function envListCommand() {
2526
2528
  async function envCreateCommand(name, options) {
2527
2529
  const credentials = await requireAuth();
2528
2530
  const projectId = await getProjectId();
2529
- const API_URL3 = await resolveApiUrl();
2531
+ const API_URL2 = await resolveApiUrl2();
2530
2532
  const normalizedName = name.toLowerCase();
2531
2533
  if (!/^[a-z][a-z0-9-_]*$/.test(normalizedName)) {
2532
2534
  console.log(chalk7.red("\nError: Invalid environment name"));
@@ -2540,7 +2542,7 @@ async function envCreateCommand(name, options) {
2540
2542
  body.cloneFrom = options.from;
2541
2543
  spinner.text = `Creating environment '${normalizedName}' from '${options.from}'...`;
2542
2544
  }
2543
- const response = await fetch(`${API_URL3}/projects/${projectId}/environments`, {
2545
+ const response = await fetch(`${API_URL2}/projects/${projectId}/environments`, {
2544
2546
  method: "POST",
2545
2547
  headers: {
2546
2548
  "Content-Type": "application/json",
@@ -2569,10 +2571,10 @@ async function envCreateCommand(name, options) {
2569
2571
  async function envDeleteCommand(name) {
2570
2572
  const credentials = await requireAuth();
2571
2573
  const projectId = await getProjectId();
2572
- const API_URL3 = await resolveApiUrl();
2574
+ const API_URL2 = await resolveApiUrl2();
2573
2575
  const spinner = ora7(`Deleting environment '${name}'...`).start();
2574
2576
  try {
2575
- const response = await fetch(`${API_URL3}/projects/${projectId}/environments/${name}`, {
2577
+ const response = await fetch(`${API_URL2}/projects/${projectId}/environments/${name}`, {
2576
2578
  method: "DELETE",
2577
2579
  headers: {
2578
2580
  "Authorization": `Bearer ${credentials.accessToken}`
@@ -2592,10 +2594,10 @@ async function envDeleteCommand(name) {
2592
2594
  async function envDefaultCommand(name) {
2593
2595
  const credentials = await requireAuth();
2594
2596
  const projectId = await getProjectId();
2595
- const API_URL3 = await resolveApiUrl();
2597
+ const API_URL2 = await resolveApiUrl2();
2596
2598
  const spinner = ora7(`Setting '${name}' as default environment...`).start();
2597
2599
  try {
2598
- const response = await fetch(`${API_URL3}/projects/${projectId}/environments/default`, {
2600
+ const response = await fetch(`${API_URL2}/projects/${projectId}/environments/default`, {
2599
2601
  method: "PATCH",
2600
2602
  headers: {
2601
2603
  "Content-Type": "application/json",
@@ -2661,8 +2663,8 @@ async function migrateSystemColumnsCommand(options) {
2661
2663
  const spinner = ora8("Analysing database tables...").start();
2662
2664
  try {
2663
2665
  const config = await loadConfig();
2664
- const API_URL3 = `${getApiUrl(config)}/api/v1`;
2665
- const baseUrl = `${API_URL3}/projects/${projectId}/env/${environment}/migrate/system-columns`;
2666
+ const API_URL2 = `${getApiUrl2(config)}/api/v1`;
2667
+ const baseUrl = `${API_URL2}/projects/${projectId}/env/${environment}/migrate/system-columns`;
2666
2668
  const dryRunResponse = await fetch(`${baseUrl}?dry_run=true`, {
2667
2669
  method: "POST",
2668
2670
  headers: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tthr",
3
- "version": "0.3.14",
3
+ "version": "0.3.16",
4
4
  "description": "Tether CLI - project scaffolding and deployment",
5
5
  "type": "module",
6
6
  "bin": {