tthr 0.3.21 → 0.3.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +176 -143
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17,23 +17,53 @@ import {
|
|
|
17
17
|
|
|
18
18
|
// src/index.ts
|
|
19
19
|
import { Command } from "commander";
|
|
20
|
-
import
|
|
20
|
+
import fs9 from "fs-extra";
|
|
21
21
|
import { fileURLToPath } from "url";
|
|
22
|
-
import
|
|
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
|
|
29
|
-
import
|
|
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
|
|
36
|
-
import
|
|
65
|
+
import fs2 from "fs-extra";
|
|
66
|
+
import path2 from "path";
|
|
37
67
|
import * as esbuild from "esbuild";
|
|
38
68
|
async function describeHttpError(response, action) {
|
|
39
69
|
const status = response.status;
|
|
@@ -73,8 +103,8 @@ async function describeHttpError(response, action) {
|
|
|
73
103
|
}
|
|
74
104
|
async function deployCommand(options) {
|
|
75
105
|
const credentials = await requireAuth();
|
|
76
|
-
const configPath =
|
|
77
|
-
if (!await
|
|
106
|
+
const configPath = path2.resolve(process.cwd(), "tether.config.ts");
|
|
107
|
+
if (!await fs2.pathExists(configPath)) {
|
|
78
108
|
console.log(chalk.red("\nError: Not a Tether project"));
|
|
79
109
|
console.log(chalk.dim("Run `tthr init` to create a new project\n"));
|
|
80
110
|
process.exit(1);
|
|
@@ -82,9 +112,9 @@ async function deployCommand(options) {
|
|
|
82
112
|
const config = await loadConfig();
|
|
83
113
|
let projectId = config.projectId;
|
|
84
114
|
if (!projectId) {
|
|
85
|
-
const envPath =
|
|
86
|
-
if (await
|
|
87
|
-
const envContent = await
|
|
115
|
+
const envPath = path2.resolve(process.cwd(), ".env");
|
|
116
|
+
if (await fs2.pathExists(envPath)) {
|
|
117
|
+
const envContent = await fs2.readFile(envPath, "utf-8");
|
|
88
118
|
const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
|
|
89
119
|
projectId = match?.[1]?.trim();
|
|
90
120
|
}
|
|
@@ -99,7 +129,8 @@ async function deployCommand(options) {
|
|
|
99
129
|
console.log(chalk.bold("\n\u26A1 Deploying to Tether\n"));
|
|
100
130
|
console.log(chalk.dim(` Project: ${projectId}`));
|
|
101
131
|
console.log(chalk.dim(` Environment: ${environment}`));
|
|
102
|
-
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
|
|
103
134
|
`));
|
|
104
135
|
console.log(chalk.dim(" Generating types..."));
|
|
105
136
|
try {
|
|
@@ -124,15 +155,15 @@ async function deploySchemaToServer(projectId, token, schemaPath, environment, d
|
|
|
124
155
|
const API_URL2 = apiUrl || `${await resolveApiUrl()}/api/v1`;
|
|
125
156
|
const spinner = ora("Reading schema...").start();
|
|
126
157
|
try {
|
|
127
|
-
if (!await
|
|
158
|
+
if (!await fs2.pathExists(schemaPath)) {
|
|
128
159
|
spinner.warn("No schema file found");
|
|
129
|
-
const relativePath =
|
|
160
|
+
const relativePath = path2.relative(process.cwd(), schemaPath);
|
|
130
161
|
console.log(chalk.dim(` Create ${relativePath} to define your database schema
|
|
131
162
|
`));
|
|
132
163
|
return;
|
|
133
164
|
}
|
|
134
165
|
console.log(chalk.dim(` Schema path: ${schemaPath}`));
|
|
135
|
-
const schemaSource = await
|
|
166
|
+
const schemaSource = await fs2.readFile(schemaPath, "utf-8");
|
|
136
167
|
console.log(chalk.dim(` Schema source length: ${schemaSource.length} chars`));
|
|
137
168
|
console.log(chalk.dim(` Parsing schema...`));
|
|
138
169
|
const tables = parseSchema(schemaSource);
|
|
@@ -220,14 +251,14 @@ async function deployFunctionsToServer(projectId, token, functionsDir, environme
|
|
|
220
251
|
const API_URL2 = apiUrl || `${await resolveApiUrl()}/api/v1`;
|
|
221
252
|
const spinner = ora("Reading functions...").start();
|
|
222
253
|
try {
|
|
223
|
-
if (!await
|
|
254
|
+
if (!await fs2.pathExists(functionsDir)) {
|
|
224
255
|
spinner.warn("No functions directory found");
|
|
225
|
-
const relativePath =
|
|
256
|
+
const relativePath = path2.relative(process.cwd(), functionsDir);
|
|
226
257
|
console.log(chalk.dim(` Create ${relativePath}/ to define your API functions
|
|
227
258
|
`));
|
|
228
259
|
return;
|
|
229
260
|
}
|
|
230
|
-
const files = await
|
|
261
|
+
const files = await fs2.readdir(functionsDir);
|
|
231
262
|
const tsFiles = files.filter((f) => f.endsWith(".ts") && !f.startsWith("_"));
|
|
232
263
|
const helperFiles = files.filter((f) => f.endsWith(".ts") && f.startsWith("_"));
|
|
233
264
|
if (tsFiles.length === 0) {
|
|
@@ -236,13 +267,13 @@ async function deployFunctionsToServer(projectId, token, functionsDir, environme
|
|
|
236
267
|
}
|
|
237
268
|
const helperSources = /* @__PURE__ */ new Map();
|
|
238
269
|
for (const hf of helperFiles) {
|
|
239
|
-
const hfPath =
|
|
240
|
-
helperSources.set(hf, await
|
|
270
|
+
const hfPath = path2.join(functionsDir, hf);
|
|
271
|
+
helperSources.set(hf, await fs2.readFile(hfPath, "utf-8"));
|
|
241
272
|
}
|
|
242
273
|
const functions = [];
|
|
243
274
|
for (const file of tsFiles) {
|
|
244
|
-
const filePath =
|
|
245
|
-
const source = await
|
|
275
|
+
const filePath = path2.join(functionsDir, file);
|
|
276
|
+
const source = await fs2.readFile(filePath, "utf-8");
|
|
246
277
|
const moduleName = file.replace(".ts", "");
|
|
247
278
|
const parsedFunctions = parseFunctions(moduleName, source);
|
|
248
279
|
if (parsedFunctions.length === 0) continue;
|
|
@@ -676,8 +707,8 @@ ${packageManager} is not installed on your system.`));
|
|
|
676
707
|
});
|
|
677
708
|
template = response.template || "nuxt";
|
|
678
709
|
}
|
|
679
|
-
const projectPath =
|
|
680
|
-
if (await
|
|
710
|
+
const projectPath = path3.resolve(process.cwd(), projectName);
|
|
711
|
+
if (await fs3.pathExists(projectPath)) {
|
|
681
712
|
const { overwrite } = await prompts({
|
|
682
713
|
type: "confirm",
|
|
683
714
|
name: "overwrite",
|
|
@@ -688,7 +719,7 @@ ${packageManager} is not installed on your system.`));
|
|
|
688
719
|
console.log(chalk2.yellow("Cancelled"));
|
|
689
720
|
process.exit(0);
|
|
690
721
|
}
|
|
691
|
-
await
|
|
722
|
+
await fs3.remove(projectPath);
|
|
692
723
|
}
|
|
693
724
|
const spinner = ora2(`Scaffolding ${template} project...`).start();
|
|
694
725
|
let projectId;
|
|
@@ -764,7 +795,7 @@ function getPackageRunnerCommand(pm) {
|
|
|
764
795
|
}
|
|
765
796
|
}
|
|
766
797
|
async function scaffoldNuxtProject(projectName, projectPath, spinner, packageManager) {
|
|
767
|
-
const parentDir =
|
|
798
|
+
const parentDir = path3.dirname(projectPath);
|
|
768
799
|
const runner = getPackageRunnerCommand(packageManager);
|
|
769
800
|
spinner.stop();
|
|
770
801
|
console.log(chalk2.dim("\nRunning nuxi init...\n"));
|
|
@@ -773,7 +804,7 @@ async function scaffoldNuxtProject(projectName, projectPath, spinner, packageMan
|
|
|
773
804
|
cwd: parentDir,
|
|
774
805
|
stdio: "inherit"
|
|
775
806
|
});
|
|
776
|
-
if (!await
|
|
807
|
+
if (!await fs3.pathExists(path3.join(projectPath, "package.json"))) {
|
|
777
808
|
throw new Error("Nuxt project was not created successfully - package.json missing");
|
|
778
809
|
}
|
|
779
810
|
spinner.start();
|
|
@@ -786,7 +817,7 @@ async function scaffoldNuxtProject(projectName, projectPath, spinner, packageMan
|
|
|
786
817
|
}
|
|
787
818
|
}
|
|
788
819
|
async function scaffoldNextProject(projectName, projectPath, spinner, packageManager) {
|
|
789
|
-
const parentDir =
|
|
820
|
+
const parentDir = path3.dirname(projectPath);
|
|
790
821
|
const runner = getPackageRunnerCommand(packageManager);
|
|
791
822
|
const pmFlag = packageManager === "npm" ? "--use-npm" : packageManager === "pnpm" ? "--use-pnpm" : packageManager === "yarn" ? "--use-yarn" : "--use-bun";
|
|
792
823
|
spinner.stop();
|
|
@@ -796,7 +827,7 @@ async function scaffoldNextProject(projectName, projectPath, spinner, packageMan
|
|
|
796
827
|
cwd: parentDir,
|
|
797
828
|
stdio: "inherit"
|
|
798
829
|
});
|
|
799
|
-
if (!await
|
|
830
|
+
if (!await fs3.pathExists(path3.join(projectPath, "package.json"))) {
|
|
800
831
|
throw new Error("Next.js project was not created successfully - package.json missing");
|
|
801
832
|
}
|
|
802
833
|
spinner.start();
|
|
@@ -809,7 +840,7 @@ async function scaffoldNextProject(projectName, projectPath, spinner, packageMan
|
|
|
809
840
|
}
|
|
810
841
|
}
|
|
811
842
|
async function scaffoldSvelteKitProject(projectName, projectPath, spinner, packageManager) {
|
|
812
|
-
const parentDir =
|
|
843
|
+
const parentDir = path3.dirname(projectPath);
|
|
813
844
|
const runner = getPackageRunnerCommand(packageManager);
|
|
814
845
|
spinner.stop();
|
|
815
846
|
console.log(chalk2.dim("\nRunning sv create...\n"));
|
|
@@ -818,7 +849,7 @@ async function scaffoldSvelteKitProject(projectName, projectPath, spinner, packa
|
|
|
818
849
|
cwd: parentDir,
|
|
819
850
|
stdio: "inherit"
|
|
820
851
|
});
|
|
821
|
-
if (!await
|
|
852
|
+
if (!await fs3.pathExists(path3.join(projectPath, "package.json"))) {
|
|
822
853
|
throw new Error("SvelteKit project was not created successfully - package.json missing");
|
|
823
854
|
}
|
|
824
855
|
spinner.start();
|
|
@@ -837,8 +868,8 @@ async function scaffoldSvelteKitProject(projectName, projectPath, spinner, packa
|
|
|
837
868
|
}
|
|
838
869
|
async function scaffoldVanillaProject(projectName, projectPath, spinner) {
|
|
839
870
|
spinner.text = "Creating vanilla TypeScript project...";
|
|
840
|
-
await
|
|
841
|
-
await
|
|
871
|
+
await fs3.ensureDir(projectPath);
|
|
872
|
+
await fs3.ensureDir(path3.join(projectPath, "src"));
|
|
842
873
|
const packageJson = {
|
|
843
874
|
name: projectName,
|
|
844
875
|
version: "0.0.1",
|
|
@@ -855,9 +886,9 @@ async function scaffoldVanillaProject(projectName, projectPath, spinner) {
|
|
|
855
886
|
"@types/node": "latest"
|
|
856
887
|
}
|
|
857
888
|
};
|
|
858
|
-
await
|
|
859
|
-
await
|
|
860
|
-
|
|
889
|
+
await fs3.writeJSON(path3.join(projectPath, "package.json"), packageJson, { spaces: 2 });
|
|
890
|
+
await fs3.writeJSON(
|
|
891
|
+
path3.join(projectPath, "tsconfig.json"),
|
|
861
892
|
{
|
|
862
893
|
compilerOptions: {
|
|
863
894
|
target: "ES2022",
|
|
@@ -878,8 +909,8 @@ async function scaffoldVanillaProject(projectName, projectPath, spinner) {
|
|
|
878
909
|
},
|
|
879
910
|
{ spaces: 2 }
|
|
880
911
|
);
|
|
881
|
-
await
|
|
882
|
-
|
|
912
|
+
await fs3.writeFile(
|
|
913
|
+
path3.join(projectPath, "src", "index.ts"),
|
|
883
914
|
`import { createClient } from '@tthr/client';
|
|
884
915
|
|
|
885
916
|
const tether = createClient({
|
|
@@ -898,8 +929,8 @@ async function main() {
|
|
|
898
929
|
main().catch(console.error);
|
|
899
930
|
`
|
|
900
931
|
);
|
|
901
|
-
await
|
|
902
|
-
|
|
932
|
+
await fs3.writeFile(
|
|
933
|
+
path3.join(projectPath, ".gitignore"),
|
|
903
934
|
`node_modules/
|
|
904
935
|
dist/
|
|
905
936
|
.env
|
|
@@ -909,11 +940,11 @@ dist/
|
|
|
909
940
|
);
|
|
910
941
|
}
|
|
911
942
|
async function addTetherFiles(projectPath, projectId, apiKey, template) {
|
|
912
|
-
await
|
|
913
|
-
await
|
|
943
|
+
await fs3.ensureDir(path3.join(projectPath, "tether"));
|
|
944
|
+
await fs3.ensureDir(path3.join(projectPath, "tether", "functions"));
|
|
914
945
|
const configPackage = template === "nuxt" ? "@tthr/vue" : template === "next" ? "@tthr/react" : template === "sveltekit" ? "@tthr/svelte" : "@tthr/client";
|
|
915
|
-
await
|
|
916
|
-
|
|
946
|
+
await fs3.writeFile(
|
|
947
|
+
path3.join(projectPath, "tether.config.ts"),
|
|
917
948
|
`import { defineConfig } from '${configPackage}';
|
|
918
949
|
|
|
919
950
|
export default defineConfig({
|
|
@@ -925,8 +956,8 @@ export default defineConfig({
|
|
|
925
956
|
});
|
|
926
957
|
`
|
|
927
958
|
);
|
|
928
|
-
await
|
|
929
|
-
|
|
959
|
+
await fs3.writeFile(
|
|
960
|
+
path3.join(projectPath, "tether", "schema.ts"),
|
|
930
961
|
`import { defineSchema, text, timestamp } from '@tthr/schema';
|
|
931
962
|
|
|
932
963
|
export default defineSchema({
|
|
@@ -948,8 +979,8 @@ export default defineSchema({
|
|
|
948
979
|
});
|
|
949
980
|
`
|
|
950
981
|
);
|
|
951
|
-
await
|
|
952
|
-
|
|
982
|
+
await fs3.writeFile(
|
|
983
|
+
path3.join(projectPath, "tether", "functions", "posts.ts"),
|
|
953
984
|
`import { query, mutation, z } from '../_generated/db';
|
|
954
985
|
|
|
955
986
|
// List all posts
|
|
@@ -1030,8 +1061,8 @@ export const remove = mutation({
|
|
|
1030
1061
|
});
|
|
1031
1062
|
`
|
|
1032
1063
|
);
|
|
1033
|
-
await
|
|
1034
|
-
|
|
1064
|
+
await fs3.writeFile(
|
|
1065
|
+
path3.join(projectPath, "tether", "functions", "index.ts"),
|
|
1035
1066
|
`// Re-export all function modules
|
|
1036
1067
|
// The Tether SDK uses this file to discover custom functions
|
|
1037
1068
|
|
|
@@ -1039,8 +1070,8 @@ export * as posts from './posts';
|
|
|
1039
1070
|
export * as comments from './comments';
|
|
1040
1071
|
`
|
|
1041
1072
|
);
|
|
1042
|
-
await
|
|
1043
|
-
|
|
1073
|
+
await fs3.writeFile(
|
|
1074
|
+
path3.join(projectPath, "tether", "functions", "comments.ts"),
|
|
1044
1075
|
`import { query, mutation, z } from '../_generated/db';
|
|
1045
1076
|
|
|
1046
1077
|
// List all comments
|
|
@@ -1108,26 +1139,26 @@ export const remove = mutation({
|
|
|
1108
1139
|
TETHER_PROJECT_ID=${projectId}
|
|
1109
1140
|
TETHER_API_KEY=${apiKey}
|
|
1110
1141
|
`;
|
|
1111
|
-
const envPath =
|
|
1112
|
-
if (await
|
|
1113
|
-
const existing = await
|
|
1114
|
-
await
|
|
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);
|
|
1115
1146
|
} else {
|
|
1116
|
-
await
|
|
1147
|
+
await fs3.writeFile(envPath, envContent);
|
|
1117
1148
|
}
|
|
1118
|
-
const gitignorePath =
|
|
1149
|
+
const gitignorePath = path3.join(projectPath, ".gitignore");
|
|
1119
1150
|
const tetherGitignore = `
|
|
1120
1151
|
# Tether
|
|
1121
1152
|
.env
|
|
1122
1153
|
.env.local
|
|
1123
1154
|
`;
|
|
1124
|
-
if (await
|
|
1125
|
-
const existing = await
|
|
1155
|
+
if (await fs3.pathExists(gitignorePath)) {
|
|
1156
|
+
const existing = await fs3.readFile(gitignorePath, "utf-8");
|
|
1126
1157
|
if (!existing.includes("_generated/")) {
|
|
1127
|
-
await
|
|
1158
|
+
await fs3.writeFile(gitignorePath, existing + tetherGitignore);
|
|
1128
1159
|
}
|
|
1129
1160
|
} else {
|
|
1130
|
-
await
|
|
1161
|
+
await fs3.writeFile(gitignorePath, tetherGitignore.trim());
|
|
1131
1162
|
}
|
|
1132
1163
|
}
|
|
1133
1164
|
async function configureFramework(projectPath, template) {
|
|
@@ -1140,9 +1171,9 @@ async function configureFramework(projectPath, template) {
|
|
|
1140
1171
|
}
|
|
1141
1172
|
}
|
|
1142
1173
|
async function configureNuxt(projectPath) {
|
|
1143
|
-
const configPath =
|
|
1144
|
-
if (!await
|
|
1145
|
-
await
|
|
1174
|
+
const configPath = path3.join(projectPath, "nuxt.config.ts");
|
|
1175
|
+
if (!await fs3.pathExists(configPath)) {
|
|
1176
|
+
await fs3.writeFile(
|
|
1146
1177
|
configPath,
|
|
1147
1178
|
`// https://nuxt.com/docs/api/configuration/nuxt-config
|
|
1148
1179
|
export default defineNuxtConfig({
|
|
@@ -1160,7 +1191,7 @@ export default defineNuxtConfig({
|
|
|
1160
1191
|
);
|
|
1161
1192
|
return;
|
|
1162
1193
|
}
|
|
1163
|
-
let config = await
|
|
1194
|
+
let config = await fs3.readFile(configPath, "utf-8");
|
|
1164
1195
|
if (config.includes("modules:")) {
|
|
1165
1196
|
config = config.replace(
|
|
1166
1197
|
/modules:\s*\[/,
|
|
@@ -1187,11 +1218,11 @@ export default defineNuxtConfig({
|
|
|
1187
1218
|
`
|
|
1188
1219
|
);
|
|
1189
1220
|
}
|
|
1190
|
-
await
|
|
1221
|
+
await fs3.writeFile(configPath, config);
|
|
1191
1222
|
}
|
|
1192
1223
|
async function configureNext(projectPath) {
|
|
1193
|
-
const providersPath =
|
|
1194
|
-
await
|
|
1224
|
+
const providersPath = path3.join(projectPath, "src", "app", "providers.tsx");
|
|
1225
|
+
await fs3.writeFile(
|
|
1195
1226
|
providersPath,
|
|
1196
1227
|
`'use client';
|
|
1197
1228
|
|
|
@@ -1209,9 +1240,9 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
1209
1240
|
}
|
|
1210
1241
|
`
|
|
1211
1242
|
);
|
|
1212
|
-
const layoutPath =
|
|
1213
|
-
if (await
|
|
1214
|
-
let layout = await
|
|
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");
|
|
1215
1246
|
if (!layout.includes("import { Providers }")) {
|
|
1216
1247
|
layout = layout.replace(
|
|
1217
1248
|
/^(import.*\n)+/m,
|
|
@@ -1224,24 +1255,24 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
1224
1255
|
"$1<Providers>$2</Providers>$3"
|
|
1225
1256
|
);
|
|
1226
1257
|
}
|
|
1227
|
-
await
|
|
1258
|
+
await fs3.writeFile(layoutPath, layout);
|
|
1228
1259
|
}
|
|
1229
|
-
const envLocalPath =
|
|
1260
|
+
const envLocalPath = path3.join(projectPath, ".env.local");
|
|
1230
1261
|
const nextEnvContent = `# Tether Configuration (client-side)
|
|
1231
1262
|
NEXT_PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
|
|
1232
1263
|
`;
|
|
1233
|
-
if (await
|
|
1234
|
-
const existing = await
|
|
1235
|
-
await
|
|
1264
|
+
if (await fs3.pathExists(envLocalPath)) {
|
|
1265
|
+
const existing = await fs3.readFile(envLocalPath, "utf-8");
|
|
1266
|
+
await fs3.writeFile(envLocalPath, existing + "\n" + nextEnvContent);
|
|
1236
1267
|
} else {
|
|
1237
|
-
await
|
|
1268
|
+
await fs3.writeFile(envLocalPath, nextEnvContent);
|
|
1238
1269
|
}
|
|
1239
1270
|
}
|
|
1240
1271
|
async function configureSvelteKit(projectPath) {
|
|
1241
|
-
const libPath =
|
|
1242
|
-
await
|
|
1243
|
-
await
|
|
1244
|
-
|
|
1272
|
+
const libPath = path3.join(projectPath, "src", "lib");
|
|
1273
|
+
await fs3.ensureDir(libPath);
|
|
1274
|
+
await fs3.writeFile(
|
|
1275
|
+
path3.join(libPath, "tether.ts"),
|
|
1245
1276
|
`import { createClient } from '@tthr/svelte';
|
|
1246
1277
|
import { PUBLIC_TETHER_PROJECT_ID, PUBLIC_TETHER_URL } from '$env/static/public';
|
|
1247
1278
|
|
|
@@ -1251,14 +1282,14 @@ export const tether = createClient({
|
|
|
1251
1282
|
});
|
|
1252
1283
|
`
|
|
1253
1284
|
);
|
|
1254
|
-
const envPath =
|
|
1285
|
+
const envPath = path3.join(projectPath, ".env");
|
|
1255
1286
|
const svelteEnvContent = `# Tether Configuration (public)
|
|
1256
1287
|
PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
|
|
1257
1288
|
`;
|
|
1258
|
-
if (await
|
|
1259
|
-
const existing = await
|
|
1289
|
+
if (await fs3.pathExists(envPath)) {
|
|
1290
|
+
const existing = await fs3.readFile(envPath, "utf-8");
|
|
1260
1291
|
if (!existing.includes("PUBLIC_TETHER_")) {
|
|
1261
|
-
await
|
|
1292
|
+
await fs3.writeFile(envPath, existing + "\n" + svelteEnvContent);
|
|
1262
1293
|
}
|
|
1263
1294
|
}
|
|
1264
1295
|
}
|
|
@@ -1313,10 +1344,10 @@ async function installTetherPackages(projectPath, template, packageManager) {
|
|
|
1313
1344
|
}
|
|
1314
1345
|
async function createDemoPage(projectPath, template) {
|
|
1315
1346
|
if (template === "nuxt") {
|
|
1316
|
-
const nuxt4AppVuePath =
|
|
1317
|
-
const nuxt3AppVuePath =
|
|
1318
|
-
const appVuePath = await
|
|
1319
|
-
await
|
|
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(
|
|
1320
1351
|
appVuePath,
|
|
1321
1352
|
`<template>
|
|
1322
1353
|
<TetherWelcome />
|
|
@@ -1324,8 +1355,8 @@ async function createDemoPage(projectPath, template) {
|
|
|
1324
1355
|
`
|
|
1325
1356
|
);
|
|
1326
1357
|
} else if (template === "next") {
|
|
1327
|
-
const pagePath =
|
|
1328
|
-
await
|
|
1358
|
+
const pagePath = path3.join(projectPath, "src", "app", "page.tsx");
|
|
1359
|
+
await fs3.writeFile(
|
|
1329
1360
|
pagePath,
|
|
1330
1361
|
`'use client';
|
|
1331
1362
|
|
|
@@ -1435,8 +1466,8 @@ export default function Home() {
|
|
|
1435
1466
|
`
|
|
1436
1467
|
);
|
|
1437
1468
|
} else if (template === "sveltekit") {
|
|
1438
|
-
const pagePath =
|
|
1439
|
-
await
|
|
1469
|
+
const pagePath = path3.join(projectPath, "src", "routes", "+page.svelte");
|
|
1470
|
+
await fs3.writeFile(
|
|
1440
1471
|
pagePath,
|
|
1441
1472
|
`<script lang="ts">
|
|
1442
1473
|
import { onMount } from 'svelte';
|
|
@@ -1704,8 +1735,8 @@ export default function Home() {
|
|
|
1704
1735
|
// src/commands/dev.ts
|
|
1705
1736
|
import chalk3 from "chalk";
|
|
1706
1737
|
import ora3 from "ora";
|
|
1707
|
-
import
|
|
1708
|
-
import
|
|
1738
|
+
import fs4 from "fs-extra";
|
|
1739
|
+
import path4 from "path";
|
|
1709
1740
|
import { spawn } from "child_process";
|
|
1710
1741
|
import { watch } from "chokidar";
|
|
1711
1742
|
var frameworkProcess = null;
|
|
@@ -1779,7 +1810,7 @@ async function runDeploy(what, projectId, token, schemaPath, functionsDir, envir
|
|
|
1779
1810
|
}
|
|
1780
1811
|
async function validateSchema(schemaPath) {
|
|
1781
1812
|
try {
|
|
1782
|
-
const content = await
|
|
1813
|
+
const content = await fs4.readFile(schemaPath, "utf-8");
|
|
1783
1814
|
if (!content.includes("defineSchema")) {
|
|
1784
1815
|
return { valid: false, error: "Missing defineSchema() call" };
|
|
1785
1816
|
}
|
|
@@ -1801,17 +1832,17 @@ async function validateSchema(schemaPath) {
|
|
|
1801
1832
|
}
|
|
1802
1833
|
async function validateFunctions(functionsDir) {
|
|
1803
1834
|
const errors = [];
|
|
1804
|
-
if (!await
|
|
1835
|
+
if (!await fs4.pathExists(functionsDir)) {
|
|
1805
1836
|
return { valid: true, errors: [] };
|
|
1806
1837
|
}
|
|
1807
|
-
const files = await
|
|
1838
|
+
const files = await fs4.readdir(functionsDir);
|
|
1808
1839
|
for (const file of files) {
|
|
1809
1840
|
if (!file.endsWith(".ts") && !file.endsWith(".js")) continue;
|
|
1810
|
-
const filePath =
|
|
1811
|
-
const stat = await
|
|
1841
|
+
const filePath = path4.join(functionsDir, file);
|
|
1842
|
+
const stat = await fs4.stat(filePath);
|
|
1812
1843
|
if (!stat.isFile()) continue;
|
|
1813
1844
|
try {
|
|
1814
|
-
const content = await
|
|
1845
|
+
const content = await fs4.readFile(filePath, "utf-8");
|
|
1815
1846
|
const hasExports = /export\s+const\s+\w+\s*=\s*(query|mutation)\s*\(/.test(content);
|
|
1816
1847
|
if (!hasExports && !file.includes("index")) {
|
|
1817
1848
|
errors.push(`${file}: No query or mutation exports found`);
|
|
@@ -1930,17 +1961,17 @@ function cleanup() {
|
|
|
1930
1961
|
}
|
|
1931
1962
|
async function devCommand(options) {
|
|
1932
1963
|
const credentials = await requireAuth();
|
|
1933
|
-
const configPath =
|
|
1934
|
-
if (!await
|
|
1964
|
+
const configPath = path4.resolve(process.cwd(), "tether.config.ts");
|
|
1965
|
+
if (!await fs4.pathExists(configPath)) {
|
|
1935
1966
|
console.log(chalk3.red("\nError: Not a Tether project"));
|
|
1936
1967
|
console.log(chalk3.dim("Run `tthr init` to create a new project\n"));
|
|
1937
1968
|
process.exit(1);
|
|
1938
1969
|
}
|
|
1939
1970
|
const config = await loadConfig();
|
|
1940
1971
|
if (!config.projectId) {
|
|
1941
|
-
const envPath =
|
|
1942
|
-
if (await
|
|
1943
|
-
const envContent = await
|
|
1972
|
+
const envPath = path4.resolve(process.cwd(), ".env");
|
|
1973
|
+
if (await fs4.pathExists(envPath)) {
|
|
1974
|
+
const envContent = await fs4.readFile(envPath, "utf-8");
|
|
1944
1975
|
const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
|
|
1945
1976
|
if (match) config.projectId = match[1].trim();
|
|
1946
1977
|
}
|
|
@@ -1962,9 +1993,9 @@ async function devCommand(options) {
|
|
|
1962
1993
|
console.log(chalk3.dim(` Dev command: ${devCmd}`));
|
|
1963
1994
|
}
|
|
1964
1995
|
console.log(chalk3.dim(` Environment: ${environment}${isLocal ? " (local)" : " (cloud)"}`));
|
|
1965
|
-
console.log(chalk3.dim(` Schema: ${
|
|
1966
|
-
console.log(chalk3.dim(` Functions: ${
|
|
1967
|
-
console.log(chalk3.dim(` Output: ${
|
|
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)}`));
|
|
1968
1999
|
if (options.extraArgs && options.extraArgs.length > 0) {
|
|
1969
2000
|
console.log(chalk3.dim(` Extra args: ${options.extraArgs.join(" ")}`));
|
|
1970
2001
|
}
|
|
@@ -2005,7 +2036,7 @@ async function devCommand(options) {
|
|
|
2005
2036
|
spinner.warn("No project ID configured \u2014 skipping auto-deploy");
|
|
2006
2037
|
}
|
|
2007
2038
|
spinner.start("Setting up file watchers...");
|
|
2008
|
-
if (await
|
|
2039
|
+
if (await fs4.pathExists(schemaPath)) {
|
|
2009
2040
|
schemaWatcher = watch(schemaPath, {
|
|
2010
2041
|
ignoreInitial: true,
|
|
2011
2042
|
awaitWriteFinish: {
|
|
@@ -2026,8 +2057,8 @@ async function devCommand(options) {
|
|
|
2026
2057
|
}
|
|
2027
2058
|
});
|
|
2028
2059
|
}
|
|
2029
|
-
if (await
|
|
2030
|
-
functionsWatcher = watch(
|
|
2060
|
+
if (await fs4.pathExists(functionsDir)) {
|
|
2061
|
+
functionsWatcher = watch(path4.join(functionsDir, "**/*.{ts,js}"), {
|
|
2031
2062
|
ignoreInitial: true,
|
|
2032
2063
|
awaitWriteFinish: {
|
|
2033
2064
|
stabilityThreshold: 300,
|
|
@@ -2035,7 +2066,7 @@ async function devCommand(options) {
|
|
|
2035
2066
|
}
|
|
2036
2067
|
});
|
|
2037
2068
|
functionsWatcher.on("all", async (event, filePath) => {
|
|
2038
|
-
const relativePath =
|
|
2069
|
+
const relativePath = path4.relative(functionsDir, filePath);
|
|
2039
2070
|
console.log(chalk3.cyan(`
|
|
2040
2071
|
Function ${event}: ${relativePath}`));
|
|
2041
2072
|
const validation = await validateFunctions(functionsDir);
|
|
@@ -2090,7 +2121,8 @@ async function loginCommand() {
|
|
|
2090
2121
|
console.log(chalk4.dim("\nRun `tthr logout` to sign out\n"));
|
|
2091
2122
|
return;
|
|
2092
2123
|
}
|
|
2093
|
-
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
|
|
2094
2126
|
`));
|
|
2095
2127
|
const spinner = ora4("Generating authentication code...").start();
|
|
2096
2128
|
try {
|
|
@@ -2265,8 +2297,8 @@ function openBrowser(url) {
|
|
|
2265
2297
|
// src/commands/update.ts
|
|
2266
2298
|
import chalk5 from "chalk";
|
|
2267
2299
|
import ora5 from "ora";
|
|
2268
|
-
import
|
|
2269
|
-
import
|
|
2300
|
+
import fs5 from "fs-extra";
|
|
2301
|
+
import path5 from "path";
|
|
2270
2302
|
import { execSync as execSync2 } from "child_process";
|
|
2271
2303
|
var TETHER_PACKAGES = [
|
|
2272
2304
|
"@tthr/client",
|
|
@@ -2278,21 +2310,21 @@ var TETHER_PACKAGES = [
|
|
|
2278
2310
|
];
|
|
2279
2311
|
function detectPackageManager() {
|
|
2280
2312
|
let dir = process.cwd();
|
|
2281
|
-
const root =
|
|
2313
|
+
const root = path5.parse(dir).root;
|
|
2282
2314
|
while (dir !== root) {
|
|
2283
|
-
if (
|
|
2315
|
+
if (fs5.existsSync(path5.join(dir, "bun.lockb")) || fs5.existsSync(path5.join(dir, "bun.lock"))) {
|
|
2284
2316
|
return "bun";
|
|
2285
2317
|
}
|
|
2286
|
-
if (
|
|
2318
|
+
if (fs5.existsSync(path5.join(dir, "pnpm-lock.yaml"))) {
|
|
2287
2319
|
return "pnpm";
|
|
2288
2320
|
}
|
|
2289
|
-
if (
|
|
2321
|
+
if (fs5.existsSync(path5.join(dir, "yarn.lock"))) {
|
|
2290
2322
|
return "yarn";
|
|
2291
2323
|
}
|
|
2292
|
-
if (
|
|
2324
|
+
if (fs5.existsSync(path5.join(dir, "package-lock.json"))) {
|
|
2293
2325
|
return "npm";
|
|
2294
2326
|
}
|
|
2295
|
-
dir =
|
|
2327
|
+
dir = path5.dirname(dir);
|
|
2296
2328
|
}
|
|
2297
2329
|
return "npm";
|
|
2298
2330
|
}
|
|
@@ -2321,14 +2353,14 @@ async function getLatestVersion2(packageName) {
|
|
|
2321
2353
|
}
|
|
2322
2354
|
}
|
|
2323
2355
|
async function updateCommand(options) {
|
|
2324
|
-
const packageJsonPath =
|
|
2325
|
-
if (!await
|
|
2356
|
+
const packageJsonPath = path5.resolve(process.cwd(), "package.json");
|
|
2357
|
+
if (!await fs5.pathExists(packageJsonPath)) {
|
|
2326
2358
|
console.log(chalk5.red("\nError: No package.json found"));
|
|
2327
2359
|
console.log(chalk5.dim("Make sure you're in the root of your project\n"));
|
|
2328
2360
|
process.exit(1);
|
|
2329
2361
|
}
|
|
2330
2362
|
console.log(chalk5.bold("\n\u26A1 Updating Tether packages\n"));
|
|
2331
|
-
const packageJson = await
|
|
2363
|
+
const packageJson = await fs5.readJson(packageJsonPath);
|
|
2332
2364
|
const deps = packageJson.dependencies || {};
|
|
2333
2365
|
const devDeps = packageJson.devDependencies || {};
|
|
2334
2366
|
const installedDeps = [];
|
|
@@ -2414,8 +2446,8 @@ async function updateCommand(options) {
|
|
|
2414
2446
|
// src/commands/exec.ts
|
|
2415
2447
|
import chalk6 from "chalk";
|
|
2416
2448
|
import ora6 from "ora";
|
|
2417
|
-
import
|
|
2418
|
-
import
|
|
2449
|
+
import fs6 from "fs-extra";
|
|
2450
|
+
import path6 from "path";
|
|
2419
2451
|
async function execCommand(sql) {
|
|
2420
2452
|
if (!sql || sql.trim() === "") {
|
|
2421
2453
|
console.log(chalk6.red("\nError: SQL query required"));
|
|
@@ -2423,10 +2455,10 @@ async function execCommand(sql) {
|
|
|
2423
2455
|
process.exit(1);
|
|
2424
2456
|
}
|
|
2425
2457
|
const credentials = await requireAuth();
|
|
2426
|
-
const envPath =
|
|
2458
|
+
const envPath = path6.resolve(process.cwd(), ".env");
|
|
2427
2459
|
let projectId;
|
|
2428
|
-
if (await
|
|
2429
|
-
const envContent = await
|
|
2460
|
+
if (await fs6.pathExists(envPath)) {
|
|
2461
|
+
const envContent = await fs6.readFile(envPath, "utf-8");
|
|
2430
2462
|
const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
|
|
2431
2463
|
projectId = match?.[1]?.trim();
|
|
2432
2464
|
}
|
|
@@ -2487,17 +2519,17 @@ async function execCommand(sql) {
|
|
|
2487
2519
|
// src/commands/env.ts
|
|
2488
2520
|
import chalk7 from "chalk";
|
|
2489
2521
|
import ora7 from "ora";
|
|
2490
|
-
import
|
|
2491
|
-
import
|
|
2522
|
+
import fs7 from "fs-extra";
|
|
2523
|
+
import path7 from "path";
|
|
2492
2524
|
async function resolveApiUrl2() {
|
|
2493
2525
|
const config = await loadConfig();
|
|
2494
2526
|
return `${getApiUrl2(config)}/api/v1`;
|
|
2495
2527
|
}
|
|
2496
2528
|
async function getProjectId() {
|
|
2497
|
-
const envPath =
|
|
2529
|
+
const envPath = path7.resolve(process.cwd(), ".env");
|
|
2498
2530
|
let projectId;
|
|
2499
|
-
if (await
|
|
2500
|
-
const envContent = await
|
|
2531
|
+
if (await fs7.pathExists(envPath)) {
|
|
2532
|
+
const envContent = await fs7.readFile(envPath, "utf-8");
|
|
2501
2533
|
const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
|
|
2502
2534
|
projectId = match?.[1]?.trim();
|
|
2503
2535
|
}
|
|
@@ -2654,15 +2686,15 @@ function maskApiKey(key) {
|
|
|
2654
2686
|
// src/commands/migrate.ts
|
|
2655
2687
|
import chalk8 from "chalk";
|
|
2656
2688
|
import ora8 from "ora";
|
|
2657
|
-
import
|
|
2658
|
-
import
|
|
2689
|
+
import fs8 from "fs-extra";
|
|
2690
|
+
import path8 from "path";
|
|
2659
2691
|
import readline2 from "readline";
|
|
2660
2692
|
async function migrateSystemColumnsCommand(options) {
|
|
2661
2693
|
const credentials = await requireAuth();
|
|
2662
|
-
const envPath =
|
|
2694
|
+
const envPath = path8.resolve(process.cwd(), ".env");
|
|
2663
2695
|
let projectId;
|
|
2664
|
-
if (await
|
|
2665
|
-
const envContent = await
|
|
2696
|
+
if (await fs8.pathExists(envPath)) {
|
|
2697
|
+
const envContent = await fs8.readFile(envPath, "utf-8");
|
|
2666
2698
|
const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
|
|
2667
2699
|
projectId = match?.[1]?.trim();
|
|
2668
2700
|
}
|
|
@@ -2800,9 +2832,10 @@ function askConfirmation(prompt) {
|
|
|
2800
2832
|
}
|
|
2801
2833
|
|
|
2802
2834
|
// src/index.ts
|
|
2835
|
+
loadDotenv();
|
|
2803
2836
|
var __filename = fileURLToPath(import.meta.url);
|
|
2804
|
-
var __dirname =
|
|
2805
|
-
var pkg =
|
|
2837
|
+
var __dirname = path9.dirname(__filename);
|
|
2838
|
+
var pkg = fs9.readJsonSync(path9.resolve(__dirname, "../package.json"));
|
|
2806
2839
|
var program = new Command();
|
|
2807
2840
|
program.name("tthr").description("Tether CLI - Realtime SQLite for modern applications").version(pkg.version).enablePositionalOptions();
|
|
2808
2841
|
program.command("init [name]").description("Create a new Tether project").option("-t, --template <template>", "Project template (vue, svelte, react, vanilla)", "vue").action(initCommand);
|