tthr 0.3.23 → 0.3.25

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.
@@ -87,23 +87,31 @@ async function resolveEnvironmentFromApiKey(cwd = process.cwd()) {
87
87
  }
88
88
  }
89
89
  var DEFAULT_API_URL = "https://api.tetherdb.co.uk";
90
+ function normaliseApiBase(raw) {
91
+ const trimmed = raw.trim().replace(/\/+$/, "");
92
+ try {
93
+ return new URL(trimmed).origin;
94
+ } catch {
95
+ return trimmed;
96
+ }
97
+ }
90
98
  function getApiUrl(config) {
91
99
  if (process.env.TETHER_API_URL) {
92
- return process.env.TETHER_API_URL.replace(/\/+$/, "");
100
+ return normaliseApiBase(process.env.TETHER_API_URL);
93
101
  }
94
102
  if (config?.url) {
95
- return config.url.replace(/\/+$/, "");
103
+ return normaliseApiBase(config.url);
96
104
  }
97
105
  const isDev = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
98
106
  return isDev ? "http://localhost:3001" : DEFAULT_API_URL;
99
107
  }
100
108
  async function resolveApiUrl(config) {
101
109
  if (process.env.TETHER_API_URL) {
102
- return process.env.TETHER_API_URL.replace(/\/+$/, "");
110
+ return normaliseApiBase(process.env.TETHER_API_URL);
103
111
  }
104
112
  const resolved = config ?? await loadConfig();
105
113
  if (resolved?.url) {
106
- return resolved.url.replace(/\/+$/, "");
114
+ return normaliseApiBase(resolved.url);
107
115
  }
108
116
  const isDev = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
109
117
  return isDev ? "http://localhost:3001" : DEFAULT_API_URL;
@@ -233,6 +241,19 @@ async function clearCredentials() {
233
241
  }
234
242
 
235
243
  // src/commands/generate.ts
244
+ function stripTimestamp(source) {
245
+ return source.replace(/^\/\/ Generated at: .*$\n?/m, "");
246
+ }
247
+ async function writeIfChanged(filePath, content) {
248
+ if (await fs3.pathExists(filePath)) {
249
+ const existing = await fs3.readFile(filePath, "utf-8");
250
+ if (stripTimestamp(existing) === stripTimestamp(content)) {
251
+ return false;
252
+ }
253
+ }
254
+ await fs3.writeFile(filePath, content);
255
+ return true;
256
+ }
236
257
  function parseSchemaFile(source) {
237
258
  const tables = [];
238
259
  const schemaMatch = source.match(/defineSchema\s*\(\s*\{([\s\S]*)\}\s*\)/);
@@ -651,19 +672,19 @@ async function generateTypes(options = {}) {
651
672
  const apiUrl = `${await resolveApiUrl(config)}/api/v1`;
652
673
  const envVarKeys = projectId ? await fetchEnvVarKeys(projectId, environment, apiUrl) : [];
653
674
  await fs3.ensureDir(outputDir);
654
- await fs3.writeFile(
675
+ await writeIfChanged(
655
676
  path3.join(outputDir, "db.ts"),
656
677
  generateDbFile(tables, schemaSource)
657
678
  );
658
- await fs3.writeFile(
679
+ await writeIfChanged(
659
680
  path3.join(outputDir, "api.ts"),
660
681
  await generateApiFile(functionsDir)
661
682
  );
662
- await fs3.writeFile(
683
+ await writeIfChanged(
663
684
  path3.join(outputDir, "env.ts"),
664
685
  generateEnvFile(envVarKeys)
665
686
  );
666
- await fs3.writeFile(
687
+ await writeIfChanged(
667
688
  path3.join(outputDir, "index.ts"),
668
689
  `// Auto-generated by Tether CLI - do not edit manually
669
690
  export * from './db';
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  generateCommand,
3
3
  generateTypes
4
- } from "./chunk-5S4QNCO2.js";
4
+ } from "./chunk-4X6M7K3X.js";
5
5
  export {
6
6
  generateCommand,
7
7
  generateTypes
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  resolveEnvironmentFromApiKey,
14
14
  resolvePath,
15
15
  saveCredentials
16
- } from "./chunk-5S4QNCO2.js";
16
+ } from "./chunk-4X6M7K3X.js";
17
17
 
18
18
  // src/index.ts
19
19
  import { Command } from "commander";
@@ -1754,7 +1754,7 @@ async function runGenerate(spinner) {
1754
1754
  }
1755
1755
  isGenerating = true;
1756
1756
  try {
1757
- const { generateTypes: generateTypes2 } = await import("./generate-FEUPHOUR.js");
1757
+ const { generateTypes: generateTypes2 } = await import("./generate-VX2WJHYQ.js");
1758
1758
  spinner.text = "Regenerating types...";
1759
1759
  await generateTypes2({ silent: true });
1760
1760
  spinner.succeed("Types regenerated");
@@ -2026,7 +2026,7 @@ async function devCommand(options) {
2026
2026
  }
2027
2027
  }
2028
2028
  spinner.text = "Generating types...";
2029
- const { generateTypes: generateTypes2 } = await import("./generate-FEUPHOUR.js");
2029
+ const { generateTypes: generateTypes2 } = await import("./generate-VX2WJHYQ.js");
2030
2030
  await generateTypes2({ silent: true });
2031
2031
  spinner.succeed("Types generated");
2032
2032
  if (config.projectId) {
@@ -2207,24 +2207,28 @@ async function requestDeviceCode() {
2207
2207
  const userCode = generateUserCode();
2208
2208
  const deviceCode = crypto.randomUUID();
2209
2209
  const deviceName = os.hostname();
2210
- const response = await fetch(`${API_URL}/auth/device`, {
2211
- method: "POST",
2212
- headers: { "Content-Type": "application/json" },
2213
- body: JSON.stringify({ userCode, deviceCode, deviceName })
2214
- }).catch(() => null);
2215
- if (response?.ok) {
2216
- return await response.json();
2217
- }
2218
- const baseUrl = API_URL.replace(/\/api\/v1$/, "");
2219
- return {
2220
- deviceCode,
2221
- userCode,
2222
- expiresIn: 60,
2223
- // 1 minute
2224
- interval: 5,
2225
- // Poll every 5 seconds
2226
- authUrl: `${baseUrl}/cli`
2227
- };
2210
+ let response;
2211
+ try {
2212
+ response = await fetch(`${API_URL}/auth/device`, {
2213
+ method: "POST",
2214
+ headers: { "Content-Type": "application/json" },
2215
+ body: JSON.stringify({ userCode, deviceCode, deviceName })
2216
+ });
2217
+ } catch (err) {
2218
+ throw new Error(
2219
+ `Could not reach the Tether API at ${API_URL}: ${err instanceof Error ? err.message : String(err)}
2220
+ Set TETHER_API_URL in your shell or .env.local if your server is elsewhere.`
2221
+ );
2222
+ }
2223
+ if (!response.ok) {
2224
+ const body = await response.text().catch(() => "");
2225
+ throw new Error(
2226
+ `Tether API at ${API_URL} returned ${response.status} ${response.statusText} when requesting a device code.
2227
+ ` + (body ? `Response: ${body.slice(0, 300)}
2228
+ ` : "") + `Check that TETHER_API_URL points at a Tether server (not a dashboard / proxy).`
2229
+ );
2230
+ }
2231
+ return await response.json();
2228
2232
  }
2229
2233
  async function pollForApproval(deviceCode, interval, expiresIn) {
2230
2234
  const startTime = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tthr",
3
- "version": "0.3.23",
3
+ "version": "0.3.25",
4
4
  "description": "Tether CLI - project scaffolding and deployment",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,12 +17,6 @@
17
17
  "files": [
18
18
  "dist"
19
19
  ],
20
- "scripts": {
21
- "build": "tsup src/index.ts --format esm --dts --clean",
22
- "dev": "tsup src/index.ts --format esm --watch --clean",
23
- "typecheck": "tsc --noEmit",
24
- "start": "TETHER_DEV=true node dist/index.js"
25
- },
26
20
  "dependencies": {
27
21
  "chalk": "^5.4.1",
28
22
  "chokidar": "^5.0.0",
@@ -38,5 +32,11 @@
38
32
  "@types/prompts": "^2.4.9",
39
33
  "tsup": "^8.3.6",
40
34
  "typescript": "^5.7.2"
35
+ },
36
+ "scripts": {
37
+ "build": "tsup src/index.ts --format esm --dts --clean",
38
+ "dev": "tsup src/index.ts --format esm --watch --clean",
39
+ "typecheck": "tsc --noEmit",
40
+ "start": "TETHER_DEV=true node dist/index.js"
41
41
  }
42
- }
42
+ }