tthr 0.3.20 → 0.3.22

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.
Files changed (2) hide show
  1. package/dist/index.js +214 -163
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -17,28 +17,94 @@ import {
17
17
 
18
18
  // src/index.ts
19
19
  import { Command } from "commander";
20
- import fs8 from "fs-extra";
20
+ import fs9 from "fs-extra";
21
21
  import { fileURLToPath } from "url";
22
- import path8 from "path";
22
+ import path9 from "path";
23
+
24
+ // src/utils/dotenv.ts
25
+ import fs from "fs-extra";
26
+ import path from "path";
27
+ function loadDotenv(cwd = process.cwd()) {
28
+ applyEnvFile(path.resolve(cwd, ".env.local"));
29
+ }
30
+ function applyEnvFile(envPath) {
31
+ if (!fs.existsSync(envPath)) return;
32
+ let content;
33
+ try {
34
+ content = fs.readFileSync(envPath, "utf-8");
35
+ } catch {
36
+ return;
37
+ }
38
+ for (const rawLine of content.split("\n")) {
39
+ const line = rawLine.trim();
40
+ if (!line || line.startsWith("#")) continue;
41
+ const eq = line.indexOf("=");
42
+ if (eq < 0) continue;
43
+ const key = line.slice(0, eq).trim();
44
+ if (!key || !/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
45
+ if (Object.prototype.hasOwnProperty.call(process.env, key)) continue;
46
+ let value = line.slice(eq + 1).trim();
47
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
48
+ value = value.slice(1, -1);
49
+ }
50
+ process.env[key] = value;
51
+ }
52
+ }
23
53
 
24
54
  // src/commands/init.ts
25
55
  import chalk2 from "chalk";
26
56
  import ora2 from "ora";
27
57
  import prompts from "prompts";
28
- import fs2 from "fs-extra";
29
- import path2 from "path";
58
+ import fs3 from "fs-extra";
59
+ import path3 from "path";
30
60
  import { execSync } from "child_process";
31
61
 
32
62
  // src/commands/deploy.ts
33
63
  import chalk from "chalk";
34
64
  import ora from "ora";
35
- import fs from "fs-extra";
36
- import path from "path";
65
+ import fs2 from "fs-extra";
66
+ import path2 from "path";
37
67
  import * as esbuild from "esbuild";
68
+ async function describeHttpError(response, action) {
69
+ const status = response.status;
70
+ const contentType = response.headers.get("content-type") ?? "";
71
+ const text = await response.text().catch(() => "");
72
+ if (contentType.includes("application/json")) {
73
+ try {
74
+ const json = JSON.parse(text);
75
+ if (typeof json?.error === "string") return json.error;
76
+ } catch {
77
+ }
78
+ }
79
+ const looksLikeHtml = contentType.includes("text/html") || /^\s*<(!doctype|html)/i.test(text);
80
+ if (looksLikeHtml) {
81
+ const hint = (() => {
82
+ switch (status) {
83
+ case 401:
84
+ case 403:
85
+ return "Authentication failed or insufficient permissions. Try `tthr login` first.";
86
+ case 404:
87
+ return `Route or resource not found. Common causes: wrong project ID, environment doesn't exist on this server, or TETHER_API_URL points at the wrong host.`;
88
+ case 429:
89
+ return "Rate-limited by the server or upstream proxy. Wait a moment and retry.";
90
+ case 502:
91
+ case 503:
92
+ case 504:
93
+ return "Server is unreachable, restarting, or under load. Check status and retry.";
94
+ default:
95
+ return `Upstream proxy returned a generic ${status} page (likely a Cloudflare / nginx fallback).`;
96
+ }
97
+ })();
98
+ return `Failed to ${action}: ${hint} (HTTP ${status})`;
99
+ }
100
+ const trimmed = text.trim();
101
+ if (trimmed) return `${trimmed.slice(0, 300)}${trimmed.length > 300 ? "\u2026" : ""} (HTTP ${status})`;
102
+ return `HTTP ${status}: ${response.statusText || "request failed"}`;
103
+ }
38
104
  async function deployCommand(options) {
39
105
  const credentials = await requireAuth();
40
- const configPath = path.resolve(process.cwd(), "tether.config.ts");
41
- if (!await fs.pathExists(configPath)) {
106
+ const configPath = path2.resolve(process.cwd(), "tether.config.ts");
107
+ if (!await fs2.pathExists(configPath)) {
42
108
  console.log(chalk.red("\nError: Not a Tether project"));
43
109
  console.log(chalk.dim("Run `tthr init` to create a new project\n"));
44
110
  process.exit(1);
@@ -46,9 +112,9 @@ async function deployCommand(options) {
46
112
  const config = await loadConfig();
47
113
  let projectId = config.projectId;
48
114
  if (!projectId) {
49
- const envPath = path.resolve(process.cwd(), ".env");
50
- if (await fs.pathExists(envPath)) {
51
- const envContent = await fs.readFile(envPath, "utf-8");
115
+ const envPath = path2.resolve(process.cwd(), ".env");
116
+ if (await fs2.pathExists(envPath)) {
117
+ const envContent = await fs2.readFile(envPath, "utf-8");
52
118
  const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
53
119
  projectId = match?.[1]?.trim();
54
120
  }
@@ -63,7 +129,8 @@ async function deployCommand(options) {
63
129
  console.log(chalk.bold("\n\u26A1 Deploying to Tether\n"));
64
130
  console.log(chalk.dim(` Project: ${projectId}`));
65
131
  console.log(chalk.dim(` Environment: ${environment}`));
66
- console.log(chalk.dim(` API: ${API_URL2}
132
+ console.log(chalk.dim(` API: ${API_URL2}`));
133
+ console.log(chalk.dim(` \u21B3 override with TETHER_API_URL in your shell or .env.local
67
134
  `));
68
135
  console.log(chalk.dim(" Generating types..."));
69
136
  try {
@@ -88,15 +155,15 @@ async function deploySchemaToServer(projectId, token, schemaPath, environment, d
88
155
  const API_URL2 = apiUrl || `${await resolveApiUrl()}/api/v1`;
89
156
  const spinner = ora("Reading schema...").start();
90
157
  try {
91
- if (!await fs.pathExists(schemaPath)) {
158
+ if (!await fs2.pathExists(schemaPath)) {
92
159
  spinner.warn("No schema file found");
93
- const relativePath = path.relative(process.cwd(), schemaPath);
160
+ const relativePath = path2.relative(process.cwd(), schemaPath);
94
161
  console.log(chalk.dim(` Create ${relativePath} to define your database schema
95
162
  `));
96
163
  return;
97
164
  }
98
165
  console.log(chalk.dim(` Schema path: ${schemaPath}`));
99
- const schemaSource = await fs.readFile(schemaPath, "utf-8");
166
+ const schemaSource = await fs2.readFile(schemaPath, "utf-8");
100
167
  console.log(chalk.dim(` Schema source length: ${schemaSource.length} chars`));
101
168
  console.log(chalk.dim(` Parsing schema...`));
102
169
  const tables = parseSchema(schemaSource);
@@ -146,16 +213,7 @@ async function deploySchemaToServer(projectId, token, schemaPath, environment, d
146
213
  console.log(chalk.dim(` Response: ${response.status} ${response.statusText}`));
147
214
  console.log(chalk.dim(` Content-Type: ${response.headers.get("content-type")}`));
148
215
  if (!response.ok) {
149
- const text = await response.text();
150
- console.log(chalk.dim(` Body: ${text.slice(0, 500)}`));
151
- let errorMessage;
152
- try {
153
- const error = JSON.parse(text);
154
- errorMessage = error.error || `HTTP ${response.status}`;
155
- } catch {
156
- errorMessage = text || `HTTP ${response.status}: ${response.statusText}`;
157
- }
158
- throw new Error(errorMessage);
216
+ throw new Error(await describeHttpError(response, "deploy schema"));
159
217
  }
160
218
  spinner.succeed(`Schema deployed (${tables.length} table(s))`);
161
219
  } catch (error) {
@@ -193,14 +251,14 @@ async function deployFunctionsToServer(projectId, token, functionsDir, environme
193
251
  const API_URL2 = apiUrl || `${await resolveApiUrl()}/api/v1`;
194
252
  const spinner = ora("Reading functions...").start();
195
253
  try {
196
- if (!await fs.pathExists(functionsDir)) {
254
+ if (!await fs2.pathExists(functionsDir)) {
197
255
  spinner.warn("No functions directory found");
198
- const relativePath = path.relative(process.cwd(), functionsDir);
256
+ const relativePath = path2.relative(process.cwd(), functionsDir);
199
257
  console.log(chalk.dim(` Create ${relativePath}/ to define your API functions
200
258
  `));
201
259
  return;
202
260
  }
203
- const files = await fs.readdir(functionsDir);
261
+ const files = await fs2.readdir(functionsDir);
204
262
  const tsFiles = files.filter((f) => f.endsWith(".ts") && !f.startsWith("_"));
205
263
  const helperFiles = files.filter((f) => f.endsWith(".ts") && f.startsWith("_"));
206
264
  if (tsFiles.length === 0) {
@@ -209,13 +267,13 @@ async function deployFunctionsToServer(projectId, token, functionsDir, environme
209
267
  }
210
268
  const helperSources = /* @__PURE__ */ new Map();
211
269
  for (const hf of helperFiles) {
212
- const hfPath = path.join(functionsDir, hf);
213
- helperSources.set(hf, await fs.readFile(hfPath, "utf-8"));
270
+ const hfPath = path2.join(functionsDir, hf);
271
+ helperSources.set(hf, await fs2.readFile(hfPath, "utf-8"));
214
272
  }
215
273
  const functions = [];
216
274
  for (const file of tsFiles) {
217
- const filePath = path.join(functionsDir, file);
218
- const source = await fs.readFile(filePath, "utf-8");
275
+ const filePath = path2.join(functionsDir, file);
276
+ const source = await fs2.readFile(filePath, "utf-8");
219
277
  const moduleName = file.replace(".ts", "");
220
278
  const parsedFunctions = parseFunctions(moduleName, source);
221
279
  if (parsedFunctions.length === 0) continue;
@@ -269,16 +327,7 @@ ${helperSource}`;
269
327
  console.log(chalk.dim(` Response: ${response.status} ${response.statusText}`));
270
328
  console.log(chalk.dim(` Content-Type: ${response.headers.get("content-type")}`));
271
329
  if (!response.ok) {
272
- const text = await response.text();
273
- console.log(chalk.dim(` Body: ${text.slice(0, 500)}`));
274
- let errorMessage;
275
- try {
276
- const error = JSON.parse(text);
277
- errorMessage = error.error || `HTTP ${response.status}`;
278
- } catch {
279
- errorMessage = text || `HTTP ${response.status}: ${response.statusText}`;
280
- }
281
- throw new Error(errorMessage);
330
+ throw new Error(await describeHttpError(response, "deploy functions"));
282
331
  }
283
332
  spinner.succeed(`Functions deployed (${functions.length} function(s))`);
284
333
  const queries = functions.filter((f) => f.type === "query");
@@ -658,8 +707,8 @@ ${packageManager} is not installed on your system.`));
658
707
  });
659
708
  template = response.template || "nuxt";
660
709
  }
661
- const projectPath = path2.resolve(process.cwd(), projectName);
662
- if (await fs2.pathExists(projectPath)) {
710
+ const projectPath = path3.resolve(process.cwd(), projectName);
711
+ if (await fs3.pathExists(projectPath)) {
663
712
  const { overwrite } = await prompts({
664
713
  type: "confirm",
665
714
  name: "overwrite",
@@ -670,7 +719,7 @@ ${packageManager} is not installed on your system.`));
670
719
  console.log(chalk2.yellow("Cancelled"));
671
720
  process.exit(0);
672
721
  }
673
- await fs2.remove(projectPath);
722
+ await fs3.remove(projectPath);
674
723
  }
675
724
  const spinner = ora2(`Scaffolding ${template} project...`).start();
676
725
  let projectId;
@@ -746,7 +795,7 @@ function getPackageRunnerCommand(pm) {
746
795
  }
747
796
  }
748
797
  async function scaffoldNuxtProject(projectName, projectPath, spinner, packageManager) {
749
- const parentDir = path2.dirname(projectPath);
798
+ const parentDir = path3.dirname(projectPath);
750
799
  const runner = getPackageRunnerCommand(packageManager);
751
800
  spinner.stop();
752
801
  console.log(chalk2.dim("\nRunning nuxi init...\n"));
@@ -755,7 +804,7 @@ async function scaffoldNuxtProject(projectName, projectPath, spinner, packageMan
755
804
  cwd: parentDir,
756
805
  stdio: "inherit"
757
806
  });
758
- if (!await fs2.pathExists(path2.join(projectPath, "package.json"))) {
807
+ if (!await fs3.pathExists(path3.join(projectPath, "package.json"))) {
759
808
  throw new Error("Nuxt project was not created successfully - package.json missing");
760
809
  }
761
810
  spinner.start();
@@ -768,7 +817,7 @@ async function scaffoldNuxtProject(projectName, projectPath, spinner, packageMan
768
817
  }
769
818
  }
770
819
  async function scaffoldNextProject(projectName, projectPath, spinner, packageManager) {
771
- const parentDir = path2.dirname(projectPath);
820
+ const parentDir = path3.dirname(projectPath);
772
821
  const runner = getPackageRunnerCommand(packageManager);
773
822
  const pmFlag = packageManager === "npm" ? "--use-npm" : packageManager === "pnpm" ? "--use-pnpm" : packageManager === "yarn" ? "--use-yarn" : "--use-bun";
774
823
  spinner.stop();
@@ -778,7 +827,7 @@ async function scaffoldNextProject(projectName, projectPath, spinner, packageMan
778
827
  cwd: parentDir,
779
828
  stdio: "inherit"
780
829
  });
781
- if (!await fs2.pathExists(path2.join(projectPath, "package.json"))) {
830
+ if (!await fs3.pathExists(path3.join(projectPath, "package.json"))) {
782
831
  throw new Error("Next.js project was not created successfully - package.json missing");
783
832
  }
784
833
  spinner.start();
@@ -791,7 +840,7 @@ async function scaffoldNextProject(projectName, projectPath, spinner, packageMan
791
840
  }
792
841
  }
793
842
  async function scaffoldSvelteKitProject(projectName, projectPath, spinner, packageManager) {
794
- const parentDir = path2.dirname(projectPath);
843
+ const parentDir = path3.dirname(projectPath);
795
844
  const runner = getPackageRunnerCommand(packageManager);
796
845
  spinner.stop();
797
846
  console.log(chalk2.dim("\nRunning sv create...\n"));
@@ -800,7 +849,7 @@ async function scaffoldSvelteKitProject(projectName, projectPath, spinner, packa
800
849
  cwd: parentDir,
801
850
  stdio: "inherit"
802
851
  });
803
- if (!await fs2.pathExists(path2.join(projectPath, "package.json"))) {
852
+ if (!await fs3.pathExists(path3.join(projectPath, "package.json"))) {
804
853
  throw new Error("SvelteKit project was not created successfully - package.json missing");
805
854
  }
806
855
  spinner.start();
@@ -819,8 +868,8 @@ async function scaffoldSvelteKitProject(projectName, projectPath, spinner, packa
819
868
  }
820
869
  async function scaffoldVanillaProject(projectName, projectPath, spinner) {
821
870
  spinner.text = "Creating vanilla TypeScript project...";
822
- await fs2.ensureDir(projectPath);
823
- await fs2.ensureDir(path2.join(projectPath, "src"));
871
+ await fs3.ensureDir(projectPath);
872
+ await fs3.ensureDir(path3.join(projectPath, "src"));
824
873
  const packageJson = {
825
874
  name: projectName,
826
875
  version: "0.0.1",
@@ -837,9 +886,9 @@ async function scaffoldVanillaProject(projectName, projectPath, spinner) {
837
886
  "@types/node": "latest"
838
887
  }
839
888
  };
840
- await fs2.writeJSON(path2.join(projectPath, "package.json"), packageJson, { spaces: 2 });
841
- await fs2.writeJSON(
842
- path2.join(projectPath, "tsconfig.json"),
889
+ await fs3.writeJSON(path3.join(projectPath, "package.json"), packageJson, { spaces: 2 });
890
+ await fs3.writeJSON(
891
+ path3.join(projectPath, "tsconfig.json"),
843
892
  {
844
893
  compilerOptions: {
845
894
  target: "ES2022",
@@ -860,8 +909,8 @@ async function scaffoldVanillaProject(projectName, projectPath, spinner) {
860
909
  },
861
910
  { spaces: 2 }
862
911
  );
863
- await fs2.writeFile(
864
- path2.join(projectPath, "src", "index.ts"),
912
+ await fs3.writeFile(
913
+ path3.join(projectPath, "src", "index.ts"),
865
914
  `import { createClient } from '@tthr/client';
866
915
 
867
916
  const tether = createClient({
@@ -880,8 +929,8 @@ async function main() {
880
929
  main().catch(console.error);
881
930
  `
882
931
  );
883
- await fs2.writeFile(
884
- path2.join(projectPath, ".gitignore"),
932
+ await fs3.writeFile(
933
+ path3.join(projectPath, ".gitignore"),
885
934
  `node_modules/
886
935
  dist/
887
936
  .env
@@ -891,11 +940,11 @@ dist/
891
940
  );
892
941
  }
893
942
  async function addTetherFiles(projectPath, projectId, apiKey, template) {
894
- await fs2.ensureDir(path2.join(projectPath, "tether"));
895
- await fs2.ensureDir(path2.join(projectPath, "tether", "functions"));
943
+ await fs3.ensureDir(path3.join(projectPath, "tether"));
944
+ await fs3.ensureDir(path3.join(projectPath, "tether", "functions"));
896
945
  const configPackage = template === "nuxt" ? "@tthr/vue" : template === "next" ? "@tthr/react" : template === "sveltekit" ? "@tthr/svelte" : "@tthr/client";
897
- await fs2.writeFile(
898
- path2.join(projectPath, "tether.config.ts"),
946
+ await fs3.writeFile(
947
+ path3.join(projectPath, "tether.config.ts"),
899
948
  `import { defineConfig } from '${configPackage}';
900
949
 
901
950
  export default defineConfig({
@@ -907,8 +956,8 @@ export default defineConfig({
907
956
  });
908
957
  `
909
958
  );
910
- await fs2.writeFile(
911
- path2.join(projectPath, "tether", "schema.ts"),
959
+ await fs3.writeFile(
960
+ path3.join(projectPath, "tether", "schema.ts"),
912
961
  `import { defineSchema, text, timestamp } from '@tthr/schema';
913
962
 
914
963
  export default defineSchema({
@@ -930,8 +979,8 @@ export default defineSchema({
930
979
  });
931
980
  `
932
981
  );
933
- await fs2.writeFile(
934
- path2.join(projectPath, "tether", "functions", "posts.ts"),
982
+ await fs3.writeFile(
983
+ path3.join(projectPath, "tether", "functions", "posts.ts"),
935
984
  `import { query, mutation, z } from '../_generated/db';
936
985
 
937
986
  // List all posts
@@ -1012,8 +1061,8 @@ export const remove = mutation({
1012
1061
  });
1013
1062
  `
1014
1063
  );
1015
- await fs2.writeFile(
1016
- path2.join(projectPath, "tether", "functions", "index.ts"),
1064
+ await fs3.writeFile(
1065
+ path3.join(projectPath, "tether", "functions", "index.ts"),
1017
1066
  `// Re-export all function modules
1018
1067
  // The Tether SDK uses this file to discover custom functions
1019
1068
 
@@ -1021,8 +1070,8 @@ export * as posts from './posts';
1021
1070
  export * as comments from './comments';
1022
1071
  `
1023
1072
  );
1024
- await fs2.writeFile(
1025
- path2.join(projectPath, "tether", "functions", "comments.ts"),
1073
+ await fs3.writeFile(
1074
+ path3.join(projectPath, "tether", "functions", "comments.ts"),
1026
1075
  `import { query, mutation, z } from '../_generated/db';
1027
1076
 
1028
1077
  // List all comments
@@ -1090,26 +1139,26 @@ export const remove = mutation({
1090
1139
  TETHER_PROJECT_ID=${projectId}
1091
1140
  TETHER_API_KEY=${apiKey}
1092
1141
  `;
1093
- const envPath = path2.join(projectPath, ".env");
1094
- if (await fs2.pathExists(envPath)) {
1095
- const existing = await fs2.readFile(envPath, "utf-8");
1096
- await fs2.writeFile(envPath, existing + "\n" + envContent);
1142
+ const envPath = path3.join(projectPath, ".env");
1143
+ if (await fs3.pathExists(envPath)) {
1144
+ const existing = await fs3.readFile(envPath, "utf-8");
1145
+ await fs3.writeFile(envPath, existing + "\n" + envContent);
1097
1146
  } else {
1098
- await fs2.writeFile(envPath, envContent);
1147
+ await fs3.writeFile(envPath, envContent);
1099
1148
  }
1100
- const gitignorePath = path2.join(projectPath, ".gitignore");
1149
+ const gitignorePath = path3.join(projectPath, ".gitignore");
1101
1150
  const tetherGitignore = `
1102
1151
  # Tether
1103
1152
  .env
1104
1153
  .env.local
1105
1154
  `;
1106
- if (await fs2.pathExists(gitignorePath)) {
1107
- const existing = await fs2.readFile(gitignorePath, "utf-8");
1155
+ if (await fs3.pathExists(gitignorePath)) {
1156
+ const existing = await fs3.readFile(gitignorePath, "utf-8");
1108
1157
  if (!existing.includes("_generated/")) {
1109
- await fs2.writeFile(gitignorePath, existing + tetherGitignore);
1158
+ await fs3.writeFile(gitignorePath, existing + tetherGitignore);
1110
1159
  }
1111
1160
  } else {
1112
- await fs2.writeFile(gitignorePath, tetherGitignore.trim());
1161
+ await fs3.writeFile(gitignorePath, tetherGitignore.trim());
1113
1162
  }
1114
1163
  }
1115
1164
  async function configureFramework(projectPath, template) {
@@ -1122,9 +1171,9 @@ async function configureFramework(projectPath, template) {
1122
1171
  }
1123
1172
  }
1124
1173
  async function configureNuxt(projectPath) {
1125
- const configPath = path2.join(projectPath, "nuxt.config.ts");
1126
- if (!await fs2.pathExists(configPath)) {
1127
- await fs2.writeFile(
1174
+ const configPath = path3.join(projectPath, "nuxt.config.ts");
1175
+ if (!await fs3.pathExists(configPath)) {
1176
+ await fs3.writeFile(
1128
1177
  configPath,
1129
1178
  `// https://nuxt.com/docs/api/configuration/nuxt-config
1130
1179
  export default defineNuxtConfig({
@@ -1142,7 +1191,7 @@ export default defineNuxtConfig({
1142
1191
  );
1143
1192
  return;
1144
1193
  }
1145
- let config = await fs2.readFile(configPath, "utf-8");
1194
+ let config = await fs3.readFile(configPath, "utf-8");
1146
1195
  if (config.includes("modules:")) {
1147
1196
  config = config.replace(
1148
1197
  /modules:\s*\[/,
@@ -1169,11 +1218,11 @@ export default defineNuxtConfig({
1169
1218
  `
1170
1219
  );
1171
1220
  }
1172
- await fs2.writeFile(configPath, config);
1221
+ await fs3.writeFile(configPath, config);
1173
1222
  }
1174
1223
  async function configureNext(projectPath) {
1175
- const providersPath = path2.join(projectPath, "src", "app", "providers.tsx");
1176
- await fs2.writeFile(
1224
+ const providersPath = path3.join(projectPath, "src", "app", "providers.tsx");
1225
+ await fs3.writeFile(
1177
1226
  providersPath,
1178
1227
  `'use client';
1179
1228
 
@@ -1191,9 +1240,9 @@ export function Providers({ children }: { children: React.ReactNode }) {
1191
1240
  }
1192
1241
  `
1193
1242
  );
1194
- const layoutPath = path2.join(projectPath, "src", "app", "layout.tsx");
1195
- if (await fs2.pathExists(layoutPath)) {
1196
- let layout = await fs2.readFile(layoutPath, "utf-8");
1243
+ const layoutPath = path3.join(projectPath, "src", "app", "layout.tsx");
1244
+ if (await fs3.pathExists(layoutPath)) {
1245
+ let layout = await fs3.readFile(layoutPath, "utf-8");
1197
1246
  if (!layout.includes("import { Providers }")) {
1198
1247
  layout = layout.replace(
1199
1248
  /^(import.*\n)+/m,
@@ -1206,24 +1255,24 @@ export function Providers({ children }: { children: React.ReactNode }) {
1206
1255
  "$1<Providers>$2</Providers>$3"
1207
1256
  );
1208
1257
  }
1209
- await fs2.writeFile(layoutPath, layout);
1258
+ await fs3.writeFile(layoutPath, layout);
1210
1259
  }
1211
- const envLocalPath = path2.join(projectPath, ".env.local");
1260
+ const envLocalPath = path3.join(projectPath, ".env.local");
1212
1261
  const nextEnvContent = `# Tether Configuration (client-side)
1213
1262
  NEXT_PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
1214
1263
  `;
1215
- if (await fs2.pathExists(envLocalPath)) {
1216
- const existing = await fs2.readFile(envLocalPath, "utf-8");
1217
- await fs2.writeFile(envLocalPath, existing + "\n" + nextEnvContent);
1264
+ if (await fs3.pathExists(envLocalPath)) {
1265
+ const existing = await fs3.readFile(envLocalPath, "utf-8");
1266
+ await fs3.writeFile(envLocalPath, existing + "\n" + nextEnvContent);
1218
1267
  } else {
1219
- await fs2.writeFile(envLocalPath, nextEnvContent);
1268
+ await fs3.writeFile(envLocalPath, nextEnvContent);
1220
1269
  }
1221
1270
  }
1222
1271
  async function configureSvelteKit(projectPath) {
1223
- const libPath = path2.join(projectPath, "src", "lib");
1224
- await fs2.ensureDir(libPath);
1225
- await fs2.writeFile(
1226
- path2.join(libPath, "tether.ts"),
1272
+ const libPath = path3.join(projectPath, "src", "lib");
1273
+ await fs3.ensureDir(libPath);
1274
+ await fs3.writeFile(
1275
+ path3.join(libPath, "tether.ts"),
1227
1276
  `import { createClient } from '@tthr/svelte';
1228
1277
  import { PUBLIC_TETHER_PROJECT_ID, PUBLIC_TETHER_URL } from '$env/static/public';
1229
1278
 
@@ -1233,14 +1282,14 @@ export const tether = createClient({
1233
1282
  });
1234
1283
  `
1235
1284
  );
1236
- const envPath = path2.join(projectPath, ".env");
1285
+ const envPath = path3.join(projectPath, ".env");
1237
1286
  const svelteEnvContent = `# Tether Configuration (public)
1238
1287
  PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
1239
1288
  `;
1240
- if (await fs2.pathExists(envPath)) {
1241
- const existing = await fs2.readFile(envPath, "utf-8");
1289
+ if (await fs3.pathExists(envPath)) {
1290
+ const existing = await fs3.readFile(envPath, "utf-8");
1242
1291
  if (!existing.includes("PUBLIC_TETHER_")) {
1243
- await fs2.writeFile(envPath, existing + "\n" + svelteEnvContent);
1292
+ await fs3.writeFile(envPath, existing + "\n" + svelteEnvContent);
1244
1293
  }
1245
1294
  }
1246
1295
  }
@@ -1295,10 +1344,10 @@ async function installTetherPackages(projectPath, template, packageManager) {
1295
1344
  }
1296
1345
  async function createDemoPage(projectPath, template) {
1297
1346
  if (template === "nuxt") {
1298
- const nuxt4AppVuePath = path2.join(projectPath, "app", "app.vue");
1299
- const nuxt3AppVuePath = path2.join(projectPath, "app.vue");
1300
- const appVuePath = await fs2.pathExists(path2.join(projectPath, "app")) ? nuxt4AppVuePath : nuxt3AppVuePath;
1301
- await fs2.writeFile(
1347
+ const nuxt4AppVuePath = path3.join(projectPath, "app", "app.vue");
1348
+ const nuxt3AppVuePath = path3.join(projectPath, "app.vue");
1349
+ const appVuePath = await fs3.pathExists(path3.join(projectPath, "app")) ? nuxt4AppVuePath : nuxt3AppVuePath;
1350
+ await fs3.writeFile(
1302
1351
  appVuePath,
1303
1352
  `<template>
1304
1353
  <TetherWelcome />
@@ -1306,8 +1355,8 @@ async function createDemoPage(projectPath, template) {
1306
1355
  `
1307
1356
  );
1308
1357
  } else if (template === "next") {
1309
- const pagePath = path2.join(projectPath, "src", "app", "page.tsx");
1310
- await fs2.writeFile(
1358
+ const pagePath = path3.join(projectPath, "src", "app", "page.tsx");
1359
+ await fs3.writeFile(
1311
1360
  pagePath,
1312
1361
  `'use client';
1313
1362
 
@@ -1417,8 +1466,8 @@ export default function Home() {
1417
1466
  `
1418
1467
  );
1419
1468
  } else if (template === "sveltekit") {
1420
- const pagePath = path2.join(projectPath, "src", "routes", "+page.svelte");
1421
- await fs2.writeFile(
1469
+ const pagePath = path3.join(projectPath, "src", "routes", "+page.svelte");
1470
+ await fs3.writeFile(
1422
1471
  pagePath,
1423
1472
  `<script lang="ts">
1424
1473
  import { onMount } from 'svelte';
@@ -1686,8 +1735,8 @@ export default function Home() {
1686
1735
  // src/commands/dev.ts
1687
1736
  import chalk3 from "chalk";
1688
1737
  import ora3 from "ora";
1689
- import fs3 from "fs-extra";
1690
- import path3 from "path";
1738
+ import fs4 from "fs-extra";
1739
+ import path4 from "path";
1691
1740
  import { spawn } from "child_process";
1692
1741
  import { watch } from "chokidar";
1693
1742
  var frameworkProcess = null;
@@ -1761,7 +1810,7 @@ async function runDeploy(what, projectId, token, schemaPath, functionsDir, envir
1761
1810
  }
1762
1811
  async function validateSchema(schemaPath) {
1763
1812
  try {
1764
- const content = await fs3.readFile(schemaPath, "utf-8");
1813
+ const content = await fs4.readFile(schemaPath, "utf-8");
1765
1814
  if (!content.includes("defineSchema")) {
1766
1815
  return { valid: false, error: "Missing defineSchema() call" };
1767
1816
  }
@@ -1783,17 +1832,17 @@ async function validateSchema(schemaPath) {
1783
1832
  }
1784
1833
  async function validateFunctions(functionsDir) {
1785
1834
  const errors = [];
1786
- if (!await fs3.pathExists(functionsDir)) {
1835
+ if (!await fs4.pathExists(functionsDir)) {
1787
1836
  return { valid: true, errors: [] };
1788
1837
  }
1789
- const files = await fs3.readdir(functionsDir);
1838
+ const files = await fs4.readdir(functionsDir);
1790
1839
  for (const file of files) {
1791
1840
  if (!file.endsWith(".ts") && !file.endsWith(".js")) continue;
1792
- const filePath = path3.join(functionsDir, file);
1793
- const stat = await fs3.stat(filePath);
1841
+ const filePath = path4.join(functionsDir, file);
1842
+ const stat = await fs4.stat(filePath);
1794
1843
  if (!stat.isFile()) continue;
1795
1844
  try {
1796
- const content = await fs3.readFile(filePath, "utf-8");
1845
+ const content = await fs4.readFile(filePath, "utf-8");
1797
1846
  const hasExports = /export\s+const\s+\w+\s*=\s*(query|mutation)\s*\(/.test(content);
1798
1847
  if (!hasExports && !file.includes("index")) {
1799
1848
  errors.push(`${file}: No query or mutation exports found`);
@@ -1912,17 +1961,17 @@ function cleanup() {
1912
1961
  }
1913
1962
  async function devCommand(options) {
1914
1963
  const credentials = await requireAuth();
1915
- const configPath = path3.resolve(process.cwd(), "tether.config.ts");
1916
- if (!await fs3.pathExists(configPath)) {
1964
+ const configPath = path4.resolve(process.cwd(), "tether.config.ts");
1965
+ if (!await fs4.pathExists(configPath)) {
1917
1966
  console.log(chalk3.red("\nError: Not a Tether project"));
1918
1967
  console.log(chalk3.dim("Run `tthr init` to create a new project\n"));
1919
1968
  process.exit(1);
1920
1969
  }
1921
1970
  const config = await loadConfig();
1922
1971
  if (!config.projectId) {
1923
- const envPath = path3.resolve(process.cwd(), ".env");
1924
- if (await fs3.pathExists(envPath)) {
1925
- const envContent = await fs3.readFile(envPath, "utf-8");
1972
+ const envPath = path4.resolve(process.cwd(), ".env");
1973
+ if (await fs4.pathExists(envPath)) {
1974
+ const envContent = await fs4.readFile(envPath, "utf-8");
1926
1975
  const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
1927
1976
  if (match) config.projectId = match[1].trim();
1928
1977
  }
@@ -1944,9 +1993,9 @@ async function devCommand(options) {
1944
1993
  console.log(chalk3.dim(` Dev command: ${devCmd}`));
1945
1994
  }
1946
1995
  console.log(chalk3.dim(` Environment: ${environment}${isLocal ? " (local)" : " (cloud)"}`));
1947
- console.log(chalk3.dim(` Schema: ${path3.relative(process.cwd(), schemaPath)}`));
1948
- console.log(chalk3.dim(` Functions: ${path3.relative(process.cwd(), functionsDir)}`));
1949
- console.log(chalk3.dim(` Output: ${path3.relative(process.cwd(), outputDir)}`));
1996
+ console.log(chalk3.dim(` Schema: ${path4.relative(process.cwd(), schemaPath)}`));
1997
+ console.log(chalk3.dim(` Functions: ${path4.relative(process.cwd(), functionsDir)}`));
1998
+ console.log(chalk3.dim(` Output: ${path4.relative(process.cwd(), outputDir)}`));
1950
1999
  if (options.extraArgs && options.extraArgs.length > 0) {
1951
2000
  console.log(chalk3.dim(` Extra args: ${options.extraArgs.join(" ")}`));
1952
2001
  }
@@ -1987,7 +2036,7 @@ async function devCommand(options) {
1987
2036
  spinner.warn("No project ID configured \u2014 skipping auto-deploy");
1988
2037
  }
1989
2038
  spinner.start("Setting up file watchers...");
1990
- if (await fs3.pathExists(schemaPath)) {
2039
+ if (await fs4.pathExists(schemaPath)) {
1991
2040
  schemaWatcher = watch(schemaPath, {
1992
2041
  ignoreInitial: true,
1993
2042
  awaitWriteFinish: {
@@ -2008,8 +2057,8 @@ async function devCommand(options) {
2008
2057
  }
2009
2058
  });
2010
2059
  }
2011
- if (await fs3.pathExists(functionsDir)) {
2012
- functionsWatcher = watch(path3.join(functionsDir, "**/*.{ts,js}"), {
2060
+ if (await fs4.pathExists(functionsDir)) {
2061
+ functionsWatcher = watch(path4.join(functionsDir, "**/*.{ts,js}"), {
2013
2062
  ignoreInitial: true,
2014
2063
  awaitWriteFinish: {
2015
2064
  stabilityThreshold: 300,
@@ -2017,7 +2066,7 @@ async function devCommand(options) {
2017
2066
  }
2018
2067
  });
2019
2068
  functionsWatcher.on("all", async (event, filePath) => {
2020
- const relativePath = path3.relative(functionsDir, filePath);
2069
+ const relativePath = path4.relative(functionsDir, filePath);
2021
2070
  console.log(chalk3.cyan(`
2022
2071
  Function ${event}: ${relativePath}`));
2023
2072
  const validation = await validateFunctions(functionsDir);
@@ -2072,7 +2121,8 @@ async function loginCommand() {
2072
2121
  console.log(chalk4.dim("\nRun `tthr logout` to sign out\n"));
2073
2122
  return;
2074
2123
  }
2075
- console.log(chalk4.dim(` URL: ${API_URL}
2124
+ console.log(chalk4.dim(` URL: ${API_URL}`));
2125
+ console.log(chalk4.dim(` \u21B3 override with TETHER_API_URL in your shell or .env.local
2076
2126
  `));
2077
2127
  const spinner = ora4("Generating authentication code...").start();
2078
2128
  try {
@@ -2247,8 +2297,8 @@ function openBrowser(url) {
2247
2297
  // src/commands/update.ts
2248
2298
  import chalk5 from "chalk";
2249
2299
  import ora5 from "ora";
2250
- import fs4 from "fs-extra";
2251
- import path4 from "path";
2300
+ import fs5 from "fs-extra";
2301
+ import path5 from "path";
2252
2302
  import { execSync as execSync2 } from "child_process";
2253
2303
  var TETHER_PACKAGES = [
2254
2304
  "@tthr/client",
@@ -2260,21 +2310,21 @@ var TETHER_PACKAGES = [
2260
2310
  ];
2261
2311
  function detectPackageManager() {
2262
2312
  let dir = process.cwd();
2263
- const root = path4.parse(dir).root;
2313
+ const root = path5.parse(dir).root;
2264
2314
  while (dir !== root) {
2265
- if (fs4.existsSync(path4.join(dir, "bun.lockb")) || fs4.existsSync(path4.join(dir, "bun.lock"))) {
2315
+ if (fs5.existsSync(path5.join(dir, "bun.lockb")) || fs5.existsSync(path5.join(dir, "bun.lock"))) {
2266
2316
  return "bun";
2267
2317
  }
2268
- if (fs4.existsSync(path4.join(dir, "pnpm-lock.yaml"))) {
2318
+ if (fs5.existsSync(path5.join(dir, "pnpm-lock.yaml"))) {
2269
2319
  return "pnpm";
2270
2320
  }
2271
- if (fs4.existsSync(path4.join(dir, "yarn.lock"))) {
2321
+ if (fs5.existsSync(path5.join(dir, "yarn.lock"))) {
2272
2322
  return "yarn";
2273
2323
  }
2274
- if (fs4.existsSync(path4.join(dir, "package-lock.json"))) {
2324
+ if (fs5.existsSync(path5.join(dir, "package-lock.json"))) {
2275
2325
  return "npm";
2276
2326
  }
2277
- dir = path4.dirname(dir);
2327
+ dir = path5.dirname(dir);
2278
2328
  }
2279
2329
  return "npm";
2280
2330
  }
@@ -2303,14 +2353,14 @@ async function getLatestVersion2(packageName) {
2303
2353
  }
2304
2354
  }
2305
2355
  async function updateCommand(options) {
2306
- const packageJsonPath = path4.resolve(process.cwd(), "package.json");
2307
- if (!await fs4.pathExists(packageJsonPath)) {
2356
+ const packageJsonPath = path5.resolve(process.cwd(), "package.json");
2357
+ if (!await fs5.pathExists(packageJsonPath)) {
2308
2358
  console.log(chalk5.red("\nError: No package.json found"));
2309
2359
  console.log(chalk5.dim("Make sure you're in the root of your project\n"));
2310
2360
  process.exit(1);
2311
2361
  }
2312
2362
  console.log(chalk5.bold("\n\u26A1 Updating Tether packages\n"));
2313
- const packageJson = await fs4.readJson(packageJsonPath);
2363
+ const packageJson = await fs5.readJson(packageJsonPath);
2314
2364
  const deps = packageJson.dependencies || {};
2315
2365
  const devDeps = packageJson.devDependencies || {};
2316
2366
  const installedDeps = [];
@@ -2396,8 +2446,8 @@ async function updateCommand(options) {
2396
2446
  // src/commands/exec.ts
2397
2447
  import chalk6 from "chalk";
2398
2448
  import ora6 from "ora";
2399
- import fs5 from "fs-extra";
2400
- import path5 from "path";
2449
+ import fs6 from "fs-extra";
2450
+ import path6 from "path";
2401
2451
  async function execCommand(sql) {
2402
2452
  if (!sql || sql.trim() === "") {
2403
2453
  console.log(chalk6.red("\nError: SQL query required"));
@@ -2405,10 +2455,10 @@ async function execCommand(sql) {
2405
2455
  process.exit(1);
2406
2456
  }
2407
2457
  const credentials = await requireAuth();
2408
- const envPath = path5.resolve(process.cwd(), ".env");
2458
+ const envPath = path6.resolve(process.cwd(), ".env");
2409
2459
  let projectId;
2410
- if (await fs5.pathExists(envPath)) {
2411
- const envContent = await fs5.readFile(envPath, "utf-8");
2460
+ if (await fs6.pathExists(envPath)) {
2461
+ const envContent = await fs6.readFile(envPath, "utf-8");
2412
2462
  const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
2413
2463
  projectId = match?.[1]?.trim();
2414
2464
  }
@@ -2469,17 +2519,17 @@ async function execCommand(sql) {
2469
2519
  // src/commands/env.ts
2470
2520
  import chalk7 from "chalk";
2471
2521
  import ora7 from "ora";
2472
- import fs6 from "fs-extra";
2473
- import path6 from "path";
2522
+ import fs7 from "fs-extra";
2523
+ import path7 from "path";
2474
2524
  async function resolveApiUrl2() {
2475
2525
  const config = await loadConfig();
2476
2526
  return `${getApiUrl2(config)}/api/v1`;
2477
2527
  }
2478
2528
  async function getProjectId() {
2479
- const envPath = path6.resolve(process.cwd(), ".env");
2529
+ const envPath = path7.resolve(process.cwd(), ".env");
2480
2530
  let projectId;
2481
- if (await fs6.pathExists(envPath)) {
2482
- const envContent = await fs6.readFile(envPath, "utf-8");
2531
+ if (await fs7.pathExists(envPath)) {
2532
+ const envContent = await fs7.readFile(envPath, "utf-8");
2483
2533
  const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
2484
2534
  projectId = match?.[1]?.trim();
2485
2535
  }
@@ -2636,15 +2686,15 @@ function maskApiKey(key) {
2636
2686
  // src/commands/migrate.ts
2637
2687
  import chalk8 from "chalk";
2638
2688
  import ora8 from "ora";
2639
- import fs7 from "fs-extra";
2640
- import path7 from "path";
2689
+ import fs8 from "fs-extra";
2690
+ import path8 from "path";
2641
2691
  import readline2 from "readline";
2642
2692
  async function migrateSystemColumnsCommand(options) {
2643
2693
  const credentials = await requireAuth();
2644
- const envPath = path7.resolve(process.cwd(), ".env");
2694
+ const envPath = path8.resolve(process.cwd(), ".env");
2645
2695
  let projectId;
2646
- if (await fs7.pathExists(envPath)) {
2647
- const envContent = await fs7.readFile(envPath, "utf-8");
2696
+ if (await fs8.pathExists(envPath)) {
2697
+ const envContent = await fs8.readFile(envPath, "utf-8");
2648
2698
  const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
2649
2699
  projectId = match?.[1]?.trim();
2650
2700
  }
@@ -2782,9 +2832,10 @@ function askConfirmation(prompt) {
2782
2832
  }
2783
2833
 
2784
2834
  // src/index.ts
2835
+ loadDotenv();
2785
2836
  var __filename = fileURLToPath(import.meta.url);
2786
- var __dirname = path8.dirname(__filename);
2787
- var pkg = fs8.readJsonSync(path8.resolve(__dirname, "../package.json"));
2837
+ var __dirname = path9.dirname(__filename);
2838
+ var pkg = fs9.readJsonSync(path9.resolve(__dirname, "../package.json"));
2788
2839
  var program = new Command();
2789
2840
  program.name("tthr").description("Tether CLI - Realtime SQLite for modern applications").version(pkg.version).enablePositionalOptions();
2790
2841
  program.command("init [name]").description("Create a new Tether project").option("-t, --template <template>", "Project template (vue, svelte, react, vanilla)", "vue").action(initCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tthr",
3
- "version": "0.3.20",
3
+ "version": "0.3.22",
4
4
  "description": "Tether CLI - project scaffolding and deployment",
5
5
  "type": "module",
6
6
  "bin": {