tthr 0.0.18 → 0.0.19
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 +200 -133
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,8 +7,8 @@ import { Command } from "commander";
|
|
|
7
7
|
import chalk3 from "chalk";
|
|
8
8
|
import ora2 from "ora";
|
|
9
9
|
import prompts from "prompts";
|
|
10
|
-
import
|
|
11
|
-
import
|
|
10
|
+
import fs4 from "fs-extra";
|
|
11
|
+
import path4 from "path";
|
|
12
12
|
import { execSync } from "child_process";
|
|
13
13
|
|
|
14
14
|
// src/utils/auth.ts
|
|
@@ -57,22 +57,78 @@ async function clearCredentials() {
|
|
|
57
57
|
// src/commands/deploy.ts
|
|
58
58
|
import chalk2 from "chalk";
|
|
59
59
|
import ora from "ora";
|
|
60
|
+
import fs3 from "fs-extra";
|
|
61
|
+
import path3 from "path";
|
|
62
|
+
|
|
63
|
+
// src/utils/config.ts
|
|
60
64
|
import fs2 from "fs-extra";
|
|
61
65
|
import path2 from "path";
|
|
66
|
+
var DEFAULT_CONFIG = {
|
|
67
|
+
schema: "./tether/schema.ts",
|
|
68
|
+
functions: "./tether/functions",
|
|
69
|
+
output: "./tether/_generated",
|
|
70
|
+
dev: {
|
|
71
|
+
port: 3001,
|
|
72
|
+
host: "localhost"
|
|
73
|
+
},
|
|
74
|
+
database: {
|
|
75
|
+
walMode: true
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
async function loadConfig(cwd = process.cwd()) {
|
|
79
|
+
const configPath = path2.resolve(cwd, "tether.config.ts");
|
|
80
|
+
if (!await fs2.pathExists(configPath)) {
|
|
81
|
+
return DEFAULT_CONFIG;
|
|
82
|
+
}
|
|
83
|
+
const configSource = await fs2.readFile(configPath, "utf-8");
|
|
84
|
+
const config = {};
|
|
85
|
+
const schemaMatch = configSource.match(/schema\s*:\s*['"]([^'"]+)['"]/);
|
|
86
|
+
if (schemaMatch) {
|
|
87
|
+
config.schema = schemaMatch[1];
|
|
88
|
+
}
|
|
89
|
+
const functionsMatch = configSource.match(/functions\s*:\s*['"]([^'"]+)['"]/);
|
|
90
|
+
if (functionsMatch) {
|
|
91
|
+
config.functions = functionsMatch[1];
|
|
92
|
+
}
|
|
93
|
+
const outputMatch = configSource.match(/output\s*:\s*['"]([^'"]+)['"]/);
|
|
94
|
+
if (outputMatch) {
|
|
95
|
+
config.output = outputMatch[1];
|
|
96
|
+
}
|
|
97
|
+
const portMatch = configSource.match(/port\s*:\s*(\d+)/);
|
|
98
|
+
if (portMatch) {
|
|
99
|
+
config.dev = { ...config.dev, port: parseInt(portMatch[1], 10) };
|
|
100
|
+
}
|
|
101
|
+
const hostMatch = configSource.match(/host\s*:\s*['"]([^'"]+)['"]/);
|
|
102
|
+
if (hostMatch) {
|
|
103
|
+
config.dev = { ...config.dev, host: hostMatch[1] };
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
...DEFAULT_CONFIG,
|
|
107
|
+
...config,
|
|
108
|
+
dev: { ...DEFAULT_CONFIG.dev, ...config.dev },
|
|
109
|
+
database: { ...DEFAULT_CONFIG.database, ...config.database }
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function resolvePath(configPath, cwd = process.cwd()) {
|
|
113
|
+
const normalised = configPath.replace(/^\.\//, "");
|
|
114
|
+
return path2.resolve(cwd, normalised);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// src/commands/deploy.ts
|
|
62
118
|
var isDev = process.env.NODE_ENV === "development" || process.env.TETHER_DEV === "true";
|
|
63
119
|
var API_URL = isDev ? "http://localhost:3001/api/v1" : "https://tether-api.strands.gg/api/v1";
|
|
64
120
|
async function deployCommand(options) {
|
|
65
121
|
const credentials = await requireAuth();
|
|
66
|
-
const configPath =
|
|
67
|
-
if (!await
|
|
122
|
+
const configPath = path3.resolve(process.cwd(), "tether.config.ts");
|
|
123
|
+
if (!await fs3.pathExists(configPath)) {
|
|
68
124
|
console.log(chalk2.red("\nError: Not a Tether project"));
|
|
69
125
|
console.log(chalk2.dim("Run `tthr init` to create a new project\n"));
|
|
70
126
|
process.exit(1);
|
|
71
127
|
}
|
|
72
|
-
const envPath =
|
|
128
|
+
const envPath = path3.resolve(process.cwd(), ".env");
|
|
73
129
|
let projectId;
|
|
74
|
-
if (await
|
|
75
|
-
const envContent = await
|
|
130
|
+
if (await fs3.pathExists(envPath)) {
|
|
131
|
+
const envContent = await fs3.readFile(envPath, "utf-8");
|
|
76
132
|
const match = envContent.match(/TETHER_PROJECT_ID=(.+)/);
|
|
77
133
|
projectId = match?.[1]?.trim();
|
|
78
134
|
}
|
|
@@ -85,26 +141,30 @@ async function deployCommand(options) {
|
|
|
85
141
|
console.log(chalk2.dim(` Project: ${projectId}`));
|
|
86
142
|
console.log(chalk2.dim(` API: ${API_URL}
|
|
87
143
|
`));
|
|
144
|
+
const config = await loadConfig();
|
|
88
145
|
const deploySchema = options.schema || !options.schema && !options.functions;
|
|
89
146
|
const deployFunctions = options.functions || !options.schema && !options.functions;
|
|
90
147
|
if (deploySchema) {
|
|
91
|
-
|
|
148
|
+
const schemaPath = resolvePath(config.schema);
|
|
149
|
+
await deploySchemaToServer(projectId, credentials.accessToken, schemaPath, options.dryRun);
|
|
92
150
|
}
|
|
93
151
|
if (deployFunctions) {
|
|
94
|
-
|
|
152
|
+
const functionsDir = resolvePath(config.functions);
|
|
153
|
+
await deployFunctionsToServer(projectId, credentials.accessToken, functionsDir, options.dryRun);
|
|
95
154
|
}
|
|
96
155
|
console.log(chalk2.green("\n\u2713 Deployment complete\n"));
|
|
97
156
|
}
|
|
98
|
-
async function deploySchemaToServer(projectId, token, dryRun) {
|
|
157
|
+
async function deploySchemaToServer(projectId, token, schemaPath, dryRun) {
|
|
99
158
|
const spinner = ora("Reading schema...").start();
|
|
100
159
|
try {
|
|
101
|
-
|
|
102
|
-
if (!await fs2.pathExists(schemaPath)) {
|
|
160
|
+
if (!await fs3.pathExists(schemaPath)) {
|
|
103
161
|
spinner.warn("No schema file found");
|
|
104
|
-
|
|
162
|
+
const relativePath = path3.relative(process.cwd(), schemaPath);
|
|
163
|
+
console.log(chalk2.dim(` Create ${relativePath} to define your database schema
|
|
164
|
+
`));
|
|
105
165
|
return;
|
|
106
166
|
}
|
|
107
|
-
const schemaSource = await
|
|
167
|
+
const schemaSource = await fs3.readFile(schemaPath, "utf-8");
|
|
108
168
|
const tables = parseSchema(schemaSource);
|
|
109
169
|
spinner.text = `Found ${tables.length} table(s)`;
|
|
110
170
|
if (dryRun) {
|
|
@@ -140,16 +200,17 @@ async function deploySchemaToServer(projectId, token, dryRun) {
|
|
|
140
200
|
console.error(chalk2.red(error instanceof Error ? error.message : "Unknown error"));
|
|
141
201
|
}
|
|
142
202
|
}
|
|
143
|
-
async function deployFunctionsToServer(projectId, token, dryRun) {
|
|
203
|
+
async function deployFunctionsToServer(projectId, token, functionsDir, dryRun) {
|
|
144
204
|
const spinner = ora("Reading functions...").start();
|
|
145
205
|
try {
|
|
146
|
-
|
|
147
|
-
if (!await fs2.pathExists(functionsDir)) {
|
|
206
|
+
if (!await fs3.pathExists(functionsDir)) {
|
|
148
207
|
spinner.warn("No functions directory found");
|
|
149
|
-
|
|
208
|
+
const relativePath = path3.relative(process.cwd(), functionsDir);
|
|
209
|
+
console.log(chalk2.dim(` Create ${relativePath}/ to define your API functions
|
|
210
|
+
`));
|
|
150
211
|
return;
|
|
151
212
|
}
|
|
152
|
-
const files = await
|
|
213
|
+
const files = await fs3.readdir(functionsDir);
|
|
153
214
|
const tsFiles = files.filter((f) => f.endsWith(".ts"));
|
|
154
215
|
if (tsFiles.length === 0) {
|
|
155
216
|
spinner.info("No function files found");
|
|
@@ -157,8 +218,8 @@ async function deployFunctionsToServer(projectId, token, dryRun) {
|
|
|
157
218
|
}
|
|
158
219
|
const functions = [];
|
|
159
220
|
for (const file of tsFiles) {
|
|
160
|
-
const filePath =
|
|
161
|
-
const source = await
|
|
221
|
+
const filePath = path3.join(functionsDir, file);
|
|
222
|
+
const source = await fs3.readFile(filePath, "utf-8");
|
|
162
223
|
const moduleName = file.replace(".ts", "");
|
|
163
224
|
const parsedFunctions = parseFunctions(moduleName, source);
|
|
164
225
|
functions.push(...parsedFunctions);
|
|
@@ -429,8 +490,8 @@ ${packageManager} is not installed on your system.`));
|
|
|
429
490
|
});
|
|
430
491
|
template = response.template || "nuxt";
|
|
431
492
|
}
|
|
432
|
-
const projectPath =
|
|
433
|
-
if (await
|
|
493
|
+
const projectPath = path4.resolve(process.cwd(), projectName);
|
|
494
|
+
if (await fs4.pathExists(projectPath)) {
|
|
434
495
|
const { overwrite } = await prompts({
|
|
435
496
|
type: "confirm",
|
|
436
497
|
name: "overwrite",
|
|
@@ -441,7 +502,7 @@ ${packageManager} is not installed on your system.`));
|
|
|
441
502
|
console.log(chalk3.yellow("Cancelled"));
|
|
442
503
|
process.exit(0);
|
|
443
504
|
}
|
|
444
|
-
await
|
|
505
|
+
await fs4.remove(projectPath);
|
|
445
506
|
}
|
|
446
507
|
const spinner = ora2(`Scaffolding ${template} project...`).start();
|
|
447
508
|
let projectId;
|
|
@@ -516,7 +577,7 @@ function getPackageRunnerCommand(pm) {
|
|
|
516
577
|
}
|
|
517
578
|
}
|
|
518
579
|
async function scaffoldNuxtProject(projectName, projectPath, spinner, packageManager) {
|
|
519
|
-
const parentDir =
|
|
580
|
+
const parentDir = path4.dirname(projectPath);
|
|
520
581
|
const runner = getPackageRunnerCommand(packageManager);
|
|
521
582
|
spinner.stop();
|
|
522
583
|
console.log(chalk3.dim("\nRunning nuxi init...\n"));
|
|
@@ -525,7 +586,7 @@ async function scaffoldNuxtProject(projectName, projectPath, spinner, packageMan
|
|
|
525
586
|
cwd: parentDir,
|
|
526
587
|
stdio: "inherit"
|
|
527
588
|
});
|
|
528
|
-
if (!await
|
|
589
|
+
if (!await fs4.pathExists(path4.join(projectPath, "package.json"))) {
|
|
529
590
|
throw new Error("Nuxt project was not created successfully - package.json missing");
|
|
530
591
|
}
|
|
531
592
|
spinner.start();
|
|
@@ -538,7 +599,7 @@ async function scaffoldNuxtProject(projectName, projectPath, spinner, packageMan
|
|
|
538
599
|
}
|
|
539
600
|
}
|
|
540
601
|
async function scaffoldNextProject(projectName, projectPath, spinner, packageManager) {
|
|
541
|
-
const parentDir =
|
|
602
|
+
const parentDir = path4.dirname(projectPath);
|
|
542
603
|
const runner = getPackageRunnerCommand(packageManager);
|
|
543
604
|
const pmFlag = packageManager === "npm" ? "--use-npm" : packageManager === "pnpm" ? "--use-pnpm" : packageManager === "yarn" ? "--use-yarn" : "--use-bun";
|
|
544
605
|
spinner.stop();
|
|
@@ -548,7 +609,7 @@ async function scaffoldNextProject(projectName, projectPath, spinner, packageMan
|
|
|
548
609
|
cwd: parentDir,
|
|
549
610
|
stdio: "inherit"
|
|
550
611
|
});
|
|
551
|
-
if (!await
|
|
612
|
+
if (!await fs4.pathExists(path4.join(projectPath, "package.json"))) {
|
|
552
613
|
throw new Error("Next.js project was not created successfully - package.json missing");
|
|
553
614
|
}
|
|
554
615
|
spinner.start();
|
|
@@ -561,7 +622,7 @@ async function scaffoldNextProject(projectName, projectPath, spinner, packageMan
|
|
|
561
622
|
}
|
|
562
623
|
}
|
|
563
624
|
async function scaffoldSvelteKitProject(projectName, projectPath, spinner, packageManager) {
|
|
564
|
-
const parentDir =
|
|
625
|
+
const parentDir = path4.dirname(projectPath);
|
|
565
626
|
const runner = getPackageRunnerCommand(packageManager);
|
|
566
627
|
spinner.stop();
|
|
567
628
|
console.log(chalk3.dim("\nRunning sv create...\n"));
|
|
@@ -570,7 +631,7 @@ async function scaffoldSvelteKitProject(projectName, projectPath, spinner, packa
|
|
|
570
631
|
cwd: parentDir,
|
|
571
632
|
stdio: "inherit"
|
|
572
633
|
});
|
|
573
|
-
if (!await
|
|
634
|
+
if (!await fs4.pathExists(path4.join(projectPath, "package.json"))) {
|
|
574
635
|
throw new Error("SvelteKit project was not created successfully - package.json missing");
|
|
575
636
|
}
|
|
576
637
|
spinner.start();
|
|
@@ -589,8 +650,8 @@ async function scaffoldSvelteKitProject(projectName, projectPath, spinner, packa
|
|
|
589
650
|
}
|
|
590
651
|
async function scaffoldVanillaProject(projectName, projectPath, spinner) {
|
|
591
652
|
spinner.text = "Creating vanilla TypeScript project...";
|
|
592
|
-
await
|
|
593
|
-
await
|
|
653
|
+
await fs4.ensureDir(projectPath);
|
|
654
|
+
await fs4.ensureDir(path4.join(projectPath, "src"));
|
|
594
655
|
const packageJson = {
|
|
595
656
|
name: projectName,
|
|
596
657
|
version: "0.0.1",
|
|
@@ -607,9 +668,9 @@ async function scaffoldVanillaProject(projectName, projectPath, spinner) {
|
|
|
607
668
|
"@types/node": "latest"
|
|
608
669
|
}
|
|
609
670
|
};
|
|
610
|
-
await
|
|
611
|
-
await
|
|
612
|
-
|
|
671
|
+
await fs4.writeJSON(path4.join(projectPath, "package.json"), packageJson, { spaces: 2 });
|
|
672
|
+
await fs4.writeJSON(
|
|
673
|
+
path4.join(projectPath, "tsconfig.json"),
|
|
613
674
|
{
|
|
614
675
|
compilerOptions: {
|
|
615
676
|
target: "ES2022",
|
|
@@ -630,8 +691,8 @@ async function scaffoldVanillaProject(projectName, projectPath, spinner) {
|
|
|
630
691
|
},
|
|
631
692
|
{ spaces: 2 }
|
|
632
693
|
);
|
|
633
|
-
await
|
|
634
|
-
|
|
694
|
+
await fs4.writeFile(
|
|
695
|
+
path4.join(projectPath, "src", "index.ts"),
|
|
635
696
|
`import { createClient } from '@tthr/client';
|
|
636
697
|
|
|
637
698
|
const tether = createClient({
|
|
@@ -650,8 +711,8 @@ async function main() {
|
|
|
650
711
|
main().catch(console.error);
|
|
651
712
|
`
|
|
652
713
|
);
|
|
653
|
-
await
|
|
654
|
-
|
|
714
|
+
await fs4.writeFile(
|
|
715
|
+
path4.join(projectPath, ".gitignore"),
|
|
655
716
|
`node_modules/
|
|
656
717
|
dist/
|
|
657
718
|
.env
|
|
@@ -661,11 +722,11 @@ dist/
|
|
|
661
722
|
);
|
|
662
723
|
}
|
|
663
724
|
async function addTetherFiles(projectPath, projectId, apiKey, template) {
|
|
664
|
-
await
|
|
665
|
-
await
|
|
725
|
+
await fs4.ensureDir(path4.join(projectPath, "tether"));
|
|
726
|
+
await fs4.ensureDir(path4.join(projectPath, "tether", "functions"));
|
|
666
727
|
const configPackage = template === "nuxt" ? "@tthr/vue" : template === "next" ? "@tthr/react" : template === "sveltekit" ? "@tthr/svelte" : "@tthr/client";
|
|
667
|
-
await
|
|
668
|
-
|
|
728
|
+
await fs4.writeFile(
|
|
729
|
+
path4.join(projectPath, "tether.config.ts"),
|
|
669
730
|
`import { defineConfig } from '${configPackage}';
|
|
670
731
|
|
|
671
732
|
export default defineConfig({
|
|
@@ -682,12 +743,12 @@ export default defineConfig({
|
|
|
682
743
|
functions: './tether/functions',
|
|
683
744
|
|
|
684
745
|
// Generated types output
|
|
685
|
-
output: './_generated',
|
|
746
|
+
output: './tether/_generated',
|
|
686
747
|
});
|
|
687
748
|
`
|
|
688
749
|
);
|
|
689
|
-
await
|
|
690
|
-
|
|
750
|
+
await fs4.writeFile(
|
|
751
|
+
path4.join(projectPath, "tether", "schema.ts"),
|
|
691
752
|
`import { defineSchema, text, integer, timestamp } from '@tthr/schema';
|
|
692
753
|
|
|
693
754
|
export default defineSchema({
|
|
@@ -712,8 +773,8 @@ export default defineSchema({
|
|
|
712
773
|
});
|
|
713
774
|
`
|
|
714
775
|
);
|
|
715
|
-
await
|
|
716
|
-
|
|
776
|
+
await fs4.writeFile(
|
|
777
|
+
path4.join(projectPath, "tether", "functions", "posts.ts"),
|
|
717
778
|
`import { query, mutation, z } from '@tthr/server';
|
|
718
779
|
|
|
719
780
|
// List all posts
|
|
@@ -798,8 +859,8 @@ export const remove = mutation({
|
|
|
798
859
|
});
|
|
799
860
|
`
|
|
800
861
|
);
|
|
801
|
-
await
|
|
802
|
-
|
|
862
|
+
await fs4.writeFile(
|
|
863
|
+
path4.join(projectPath, "tether", "functions", "comments.ts"),
|
|
803
864
|
`import { query, mutation, z } from '@tthr/server';
|
|
804
865
|
|
|
805
866
|
// List all comments
|
|
@@ -871,27 +932,27 @@ export const remove = mutation({
|
|
|
871
932
|
TETHER_PROJECT_ID=${projectId}
|
|
872
933
|
TETHER_API_KEY=${apiKey}
|
|
873
934
|
`;
|
|
874
|
-
const envPath =
|
|
875
|
-
if (await
|
|
876
|
-
const existing = await
|
|
877
|
-
await
|
|
935
|
+
const envPath = path4.join(projectPath, ".env");
|
|
936
|
+
if (await fs4.pathExists(envPath)) {
|
|
937
|
+
const existing = await fs4.readFile(envPath, "utf-8");
|
|
938
|
+
await fs4.writeFile(envPath, existing + "\n" + envContent);
|
|
878
939
|
} else {
|
|
879
|
-
await
|
|
940
|
+
await fs4.writeFile(envPath, envContent);
|
|
880
941
|
}
|
|
881
|
-
const gitignorePath =
|
|
942
|
+
const gitignorePath = path4.join(projectPath, ".gitignore");
|
|
882
943
|
const tetherGitignore = `
|
|
883
944
|
# Tether
|
|
884
945
|
_generated/
|
|
885
946
|
.env
|
|
886
947
|
.env.local
|
|
887
948
|
`;
|
|
888
|
-
if (await
|
|
889
|
-
const existing = await
|
|
949
|
+
if (await fs4.pathExists(gitignorePath)) {
|
|
950
|
+
const existing = await fs4.readFile(gitignorePath, "utf-8");
|
|
890
951
|
if (!existing.includes("_generated/")) {
|
|
891
|
-
await
|
|
952
|
+
await fs4.writeFile(gitignorePath, existing + tetherGitignore);
|
|
892
953
|
}
|
|
893
954
|
} else {
|
|
894
|
-
await
|
|
955
|
+
await fs4.writeFile(gitignorePath, tetherGitignore.trim());
|
|
895
956
|
}
|
|
896
957
|
}
|
|
897
958
|
async function configureFramework(projectPath, template) {
|
|
@@ -904,9 +965,9 @@ async function configureFramework(projectPath, template) {
|
|
|
904
965
|
}
|
|
905
966
|
}
|
|
906
967
|
async function configureNuxt(projectPath) {
|
|
907
|
-
const configPath =
|
|
908
|
-
if (!await
|
|
909
|
-
await
|
|
968
|
+
const configPath = path4.join(projectPath, "nuxt.config.ts");
|
|
969
|
+
if (!await fs4.pathExists(configPath)) {
|
|
970
|
+
await fs4.writeFile(
|
|
910
971
|
configPath,
|
|
911
972
|
`// https://nuxt.com/docs/api/configuration/nuxt-config
|
|
912
973
|
export default defineNuxtConfig({
|
|
@@ -924,7 +985,7 @@ export default defineNuxtConfig({
|
|
|
924
985
|
);
|
|
925
986
|
return;
|
|
926
987
|
}
|
|
927
|
-
let config = await
|
|
988
|
+
let config = await fs4.readFile(configPath, "utf-8");
|
|
928
989
|
if (config.includes("modules:")) {
|
|
929
990
|
config = config.replace(
|
|
930
991
|
/modules:\s*\[/,
|
|
@@ -951,11 +1012,11 @@ export default defineNuxtConfig({
|
|
|
951
1012
|
`
|
|
952
1013
|
);
|
|
953
1014
|
}
|
|
954
|
-
await
|
|
1015
|
+
await fs4.writeFile(configPath, config);
|
|
955
1016
|
}
|
|
956
1017
|
async function configureNext(projectPath) {
|
|
957
|
-
const providersPath =
|
|
958
|
-
await
|
|
1018
|
+
const providersPath = path4.join(projectPath, "src", "app", "providers.tsx");
|
|
1019
|
+
await fs4.writeFile(
|
|
959
1020
|
providersPath,
|
|
960
1021
|
`'use client';
|
|
961
1022
|
|
|
@@ -973,9 +1034,9 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
973
1034
|
}
|
|
974
1035
|
`
|
|
975
1036
|
);
|
|
976
|
-
const layoutPath =
|
|
977
|
-
if (await
|
|
978
|
-
let layout = await
|
|
1037
|
+
const layoutPath = path4.join(projectPath, "src", "app", "layout.tsx");
|
|
1038
|
+
if (await fs4.pathExists(layoutPath)) {
|
|
1039
|
+
let layout = await fs4.readFile(layoutPath, "utf-8");
|
|
979
1040
|
if (!layout.includes("import { Providers }")) {
|
|
980
1041
|
layout = layout.replace(
|
|
981
1042
|
/^(import.*\n)+/m,
|
|
@@ -988,24 +1049,24 @@ export function Providers({ children }: { children: React.ReactNode }) {
|
|
|
988
1049
|
"$1<Providers>$2</Providers>$3"
|
|
989
1050
|
);
|
|
990
1051
|
}
|
|
991
|
-
await
|
|
1052
|
+
await fs4.writeFile(layoutPath, layout);
|
|
992
1053
|
}
|
|
993
|
-
const envLocalPath =
|
|
1054
|
+
const envLocalPath = path4.join(projectPath, ".env.local");
|
|
994
1055
|
const nextEnvContent = `# Tether Configuration (client-side)
|
|
995
1056
|
NEXT_PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
|
|
996
1057
|
`;
|
|
997
|
-
if (await
|
|
998
|
-
const existing = await
|
|
999
|
-
await
|
|
1058
|
+
if (await fs4.pathExists(envLocalPath)) {
|
|
1059
|
+
const existing = await fs4.readFile(envLocalPath, "utf-8");
|
|
1060
|
+
await fs4.writeFile(envLocalPath, existing + "\n" + nextEnvContent);
|
|
1000
1061
|
} else {
|
|
1001
|
-
await
|
|
1062
|
+
await fs4.writeFile(envLocalPath, nextEnvContent);
|
|
1002
1063
|
}
|
|
1003
1064
|
}
|
|
1004
1065
|
async function configureSvelteKit(projectPath) {
|
|
1005
|
-
const libPath =
|
|
1006
|
-
await
|
|
1007
|
-
await
|
|
1008
|
-
|
|
1066
|
+
const libPath = path4.join(projectPath, "src", "lib");
|
|
1067
|
+
await fs4.ensureDir(libPath);
|
|
1068
|
+
await fs4.writeFile(
|
|
1069
|
+
path4.join(libPath, "tether.ts"),
|
|
1009
1070
|
`import { createClient } from '@tthr/svelte';
|
|
1010
1071
|
import { PUBLIC_TETHER_PROJECT_ID, PUBLIC_TETHER_URL } from '$env/static/public';
|
|
1011
1072
|
|
|
@@ -1015,14 +1076,14 @@ export const tether = createClient({
|
|
|
1015
1076
|
});
|
|
1016
1077
|
`
|
|
1017
1078
|
);
|
|
1018
|
-
const envPath =
|
|
1079
|
+
const envPath = path4.join(projectPath, ".env");
|
|
1019
1080
|
const svelteEnvContent = `# Tether Configuration (public)
|
|
1020
1081
|
PUBLIC_TETHER_PROJECT_ID=\${TETHER_PROJECT_ID}
|
|
1021
1082
|
`;
|
|
1022
|
-
if (await
|
|
1023
|
-
const existing = await
|
|
1083
|
+
if (await fs4.pathExists(envPath)) {
|
|
1084
|
+
const existing = await fs4.readFile(envPath, "utf-8");
|
|
1024
1085
|
if (!existing.includes("PUBLIC_TETHER_")) {
|
|
1025
|
-
await
|
|
1086
|
+
await fs4.writeFile(envPath, existing + "\n" + svelteEnvContent);
|
|
1026
1087
|
}
|
|
1027
1088
|
}
|
|
1028
1089
|
}
|
|
@@ -1077,10 +1138,10 @@ async function installTetherPackages(projectPath, template, packageManager) {
|
|
|
1077
1138
|
}
|
|
1078
1139
|
async function createDemoPage(projectPath, template) {
|
|
1079
1140
|
if (template === "nuxt") {
|
|
1080
|
-
const nuxt4AppVuePath =
|
|
1081
|
-
const nuxt3AppVuePath =
|
|
1082
|
-
const appVuePath = await
|
|
1083
|
-
await
|
|
1141
|
+
const nuxt4AppVuePath = path4.join(projectPath, "app", "app.vue");
|
|
1142
|
+
const nuxt3AppVuePath = path4.join(projectPath, "app.vue");
|
|
1143
|
+
const appVuePath = await fs4.pathExists(path4.join(projectPath, "app")) ? nuxt4AppVuePath : nuxt3AppVuePath;
|
|
1144
|
+
await fs4.writeFile(
|
|
1084
1145
|
appVuePath,
|
|
1085
1146
|
`<template>
|
|
1086
1147
|
<TetherWelcome />
|
|
@@ -1088,8 +1149,8 @@ async function createDemoPage(projectPath, template) {
|
|
|
1088
1149
|
`
|
|
1089
1150
|
);
|
|
1090
1151
|
} else if (template === "next") {
|
|
1091
|
-
const pagePath =
|
|
1092
|
-
await
|
|
1152
|
+
const pagePath = path4.join(projectPath, "src", "app", "page.tsx");
|
|
1153
|
+
await fs4.writeFile(
|
|
1093
1154
|
pagePath,
|
|
1094
1155
|
`'use client';
|
|
1095
1156
|
|
|
@@ -1199,8 +1260,8 @@ export default function Home() {
|
|
|
1199
1260
|
`
|
|
1200
1261
|
);
|
|
1201
1262
|
} else if (template === "sveltekit") {
|
|
1202
|
-
const pagePath =
|
|
1203
|
-
await
|
|
1263
|
+
const pagePath = path4.join(projectPath, "src", "routes", "+page.svelte");
|
|
1264
|
+
await fs4.writeFile(
|
|
1204
1265
|
pagePath,
|
|
1205
1266
|
`<script lang="ts">
|
|
1206
1267
|
import { onMount } from 'svelte';
|
|
@@ -1468,22 +1529,24 @@ export default function Home() {
|
|
|
1468
1529
|
// src/commands/dev.ts
|
|
1469
1530
|
import chalk4 from "chalk";
|
|
1470
1531
|
import ora3 from "ora";
|
|
1471
|
-
import
|
|
1472
|
-
import
|
|
1532
|
+
import fs5 from "fs-extra";
|
|
1533
|
+
import path5 from "path";
|
|
1473
1534
|
async function devCommand(options) {
|
|
1474
1535
|
await requireAuth();
|
|
1475
|
-
const configPath =
|
|
1476
|
-
if (!await
|
|
1536
|
+
const configPath = path5.resolve(process.cwd(), "tether.config.ts");
|
|
1537
|
+
if (!await fs5.pathExists(configPath)) {
|
|
1477
1538
|
console.log(chalk4.red("\nError: Not a Tether project"));
|
|
1478
1539
|
console.log(chalk4.dim("Run `tthr init` to create a new project\n"));
|
|
1479
1540
|
process.exit(1);
|
|
1480
1541
|
}
|
|
1542
|
+
const config = await loadConfig();
|
|
1543
|
+
const port = options.port || String(config.dev?.port || 3001);
|
|
1481
1544
|
console.log(chalk4.bold("\n\u26A1 Starting Tether development server\n"));
|
|
1482
1545
|
const spinner = ora3("Starting server...").start();
|
|
1483
1546
|
try {
|
|
1484
|
-
spinner.succeed(`Development server running on port ${
|
|
1485
|
-
console.log("\n" + chalk4.cyan(` Local: http://localhost:${
|
|
1486
|
-
console.log(chalk4.cyan(` WebSocket: ws://localhost:${
|
|
1547
|
+
spinner.succeed(`Development server running on port ${port}`);
|
|
1548
|
+
console.log("\n" + chalk4.cyan(` Local: http://localhost:${port}`));
|
|
1549
|
+
console.log(chalk4.cyan(` WebSocket: ws://localhost:${port}/ws
|
|
1487
1550
|
`));
|
|
1488
1551
|
console.log(chalk4.dim(" Press Ctrl+C to stop\n"));
|
|
1489
1552
|
process.on("SIGINT", () => {
|
|
@@ -1502,12 +1565,12 @@ async function devCommand(options) {
|
|
|
1502
1565
|
// src/commands/generate.ts
|
|
1503
1566
|
import chalk5 from "chalk";
|
|
1504
1567
|
import ora4 from "ora";
|
|
1505
|
-
import
|
|
1506
|
-
import
|
|
1568
|
+
import fs6 from "fs-extra";
|
|
1569
|
+
import path6 from "path";
|
|
1507
1570
|
async function generateCommand() {
|
|
1508
1571
|
await requireAuth();
|
|
1509
|
-
const configPath =
|
|
1510
|
-
if (!await
|
|
1572
|
+
const configPath = path6.resolve(process.cwd(), "tether.config.ts");
|
|
1573
|
+
if (!await fs6.pathExists(configPath)) {
|
|
1511
1574
|
console.log(chalk5.red("\nError: Not a Tether project"));
|
|
1512
1575
|
console.log(chalk5.dim("Run `tthr init` to create a new project\n"));
|
|
1513
1576
|
process.exit(1);
|
|
@@ -1515,22 +1578,23 @@ async function generateCommand() {
|
|
|
1515
1578
|
console.log(chalk5.bold("\n\u26A1 Generating types from schema\n"));
|
|
1516
1579
|
const spinner = ora4("Reading schema...").start();
|
|
1517
1580
|
try {
|
|
1518
|
-
const
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1581
|
+
const config = await loadConfig();
|
|
1582
|
+
const schemaPath = resolvePath(config.schema);
|
|
1583
|
+
const outputDir = resolvePath(config.output);
|
|
1584
|
+
if (!await fs6.pathExists(schemaPath)) {
|
|
1521
1585
|
spinner.fail("Schema file not found");
|
|
1522
1586
|
console.log(chalk5.dim(`Expected: ${schemaPath}
|
|
1523
1587
|
`));
|
|
1524
1588
|
process.exit(1);
|
|
1525
1589
|
}
|
|
1526
1590
|
spinner.text = "Generating types...";
|
|
1527
|
-
await
|
|
1528
|
-
await
|
|
1529
|
-
|
|
1591
|
+
await fs6.ensureDir(outputDir);
|
|
1592
|
+
await fs6.writeFile(
|
|
1593
|
+
path6.join(outputDir, "db.ts"),
|
|
1530
1594
|
`// Auto-generated by Tether CLI - do not edit manually
|
|
1531
1595
|
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1532
1596
|
|
|
1533
|
-
import type
|
|
1597
|
+
import { createDatabaseProxy, type TetherDatabase } from '@tthr/client';
|
|
1534
1598
|
|
|
1535
1599
|
export interface Post {
|
|
1536
1600
|
id: string;
|
|
@@ -1555,15 +1619,16 @@ export interface Schema {
|
|
|
1555
1619
|
}
|
|
1556
1620
|
|
|
1557
1621
|
// Database client with typed tables
|
|
1558
|
-
|
|
1622
|
+
// This is a proxy that will be populated by the Tether runtime
|
|
1623
|
+
export const db: TetherDatabase<Schema> = createDatabaseProxy<Schema>();
|
|
1559
1624
|
`
|
|
1560
1625
|
);
|
|
1561
|
-
await
|
|
1562
|
-
|
|
1626
|
+
await fs6.writeFile(
|
|
1627
|
+
path6.join(outputDir, "api.ts"),
|
|
1563
1628
|
`// Auto-generated by Tether CLI - do not edit manually
|
|
1564
1629
|
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1565
1630
|
|
|
1566
|
-
import
|
|
1631
|
+
import { createApiProxy } from '@tthr/client';
|
|
1567
1632
|
import type { Schema } from './db';
|
|
1568
1633
|
|
|
1569
1634
|
export interface PostsApi {
|
|
@@ -1578,22 +1643,24 @@ export interface Api {
|
|
|
1578
1643
|
posts: PostsApi;
|
|
1579
1644
|
}
|
|
1580
1645
|
|
|
1581
|
-
//
|
|
1582
|
-
export
|
|
1646
|
+
// API client proxy - will be populated by the Tether runtime
|
|
1647
|
+
export const api: Api = createApiProxy<Api>();
|
|
1583
1648
|
`
|
|
1584
1649
|
);
|
|
1585
|
-
await
|
|
1586
|
-
|
|
1650
|
+
await fs6.writeFile(
|
|
1651
|
+
path6.join(outputDir, "index.ts"),
|
|
1587
1652
|
`// Auto-generated by Tether CLI - do not edit manually
|
|
1588
1653
|
export * from './db';
|
|
1589
1654
|
export * from './api';
|
|
1590
1655
|
`
|
|
1591
1656
|
);
|
|
1592
1657
|
spinner.succeed("Types generated");
|
|
1658
|
+
const relativeOutput = path6.relative(process.cwd(), outputDir);
|
|
1593
1659
|
console.log("\n" + chalk5.green("\u2713") + " Generated files:");
|
|
1594
|
-
console.log(chalk5.dim(
|
|
1595
|
-
console.log(chalk5.dim(
|
|
1596
|
-
console.log(chalk5.dim(
|
|
1660
|
+
console.log(chalk5.dim(` ${relativeOutput}/db.ts`));
|
|
1661
|
+
console.log(chalk5.dim(` ${relativeOutput}/api.ts`));
|
|
1662
|
+
console.log(chalk5.dim(` ${relativeOutput}/index.ts
|
|
1663
|
+
`));
|
|
1597
1664
|
} catch (error) {
|
|
1598
1665
|
spinner.fail("Failed to generate types");
|
|
1599
1666
|
console.error(chalk5.red(error instanceof Error ? error.message : "Unknown error"));
|
|
@@ -1604,18 +1671,18 @@ export * from './api';
|
|
|
1604
1671
|
// src/commands/migrate.ts
|
|
1605
1672
|
import chalk6 from "chalk";
|
|
1606
1673
|
import ora5 from "ora";
|
|
1607
|
-
import
|
|
1608
|
-
import
|
|
1674
|
+
import fs7 from "fs-extra";
|
|
1675
|
+
import path7 from "path";
|
|
1609
1676
|
import prompts2 from "prompts";
|
|
1610
1677
|
async function migrateCommand(action, options) {
|
|
1611
1678
|
await requireAuth();
|
|
1612
|
-
const configPath =
|
|
1613
|
-
if (!await
|
|
1679
|
+
const configPath = path7.resolve(process.cwd(), "tether.config.ts");
|
|
1680
|
+
if (!await fs7.pathExists(configPath)) {
|
|
1614
1681
|
console.log(chalk6.red("\nError: Not a Tether project"));
|
|
1615
1682
|
console.log(chalk6.dim("Run `tthr init` to create a new project\n"));
|
|
1616
1683
|
process.exit(1);
|
|
1617
1684
|
}
|
|
1618
|
-
const migrationsDir =
|
|
1685
|
+
const migrationsDir = path7.resolve(process.cwd(), "tether", "migrations");
|
|
1619
1686
|
switch (action) {
|
|
1620
1687
|
case "create":
|
|
1621
1688
|
await createMigration(migrationsDir, options.name);
|
|
@@ -1650,11 +1717,11 @@ async function createMigration(migrationsDir, name) {
|
|
|
1650
1717
|
}
|
|
1651
1718
|
const spinner = ora5("Creating migration...").start();
|
|
1652
1719
|
try {
|
|
1653
|
-
await
|
|
1720
|
+
await fs7.ensureDir(migrationsDir);
|
|
1654
1721
|
const timestamp = Date.now();
|
|
1655
1722
|
const filename = `${timestamp}_${migrationName}.sql`;
|
|
1656
|
-
const filepath =
|
|
1657
|
-
await
|
|
1723
|
+
const filepath = path7.join(migrationsDir, filename);
|
|
1724
|
+
await fs7.writeFile(
|
|
1658
1725
|
filepath,
|
|
1659
1726
|
`-- Migration: ${migrationName}
|
|
1660
1727
|
-- Created at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
@@ -1690,12 +1757,12 @@ async function runMigrations(migrationsDir, direction) {
|
|
|
1690
1757
|
`));
|
|
1691
1758
|
const spinner = ora5("Checking migrations...").start();
|
|
1692
1759
|
try {
|
|
1693
|
-
if (!await
|
|
1760
|
+
if (!await fs7.pathExists(migrationsDir)) {
|
|
1694
1761
|
spinner.info("No migrations directory found");
|
|
1695
1762
|
console.log(chalk6.dim("\nRun `tthr migrate create` to create your first migration\n"));
|
|
1696
1763
|
return;
|
|
1697
1764
|
}
|
|
1698
|
-
const files = await
|
|
1765
|
+
const files = await fs7.readdir(migrationsDir);
|
|
1699
1766
|
const migrations = files.filter((f) => f.endsWith(".sql")).sort((a, b) => direction === "up" ? a.localeCompare(b) : b.localeCompare(a));
|
|
1700
1767
|
if (migrations.length === 0) {
|
|
1701
1768
|
spinner.info("No migrations found");
|
|
@@ -1718,12 +1785,12 @@ async function runMigrations(migrationsDir, direction) {
|
|
|
1718
1785
|
async function showStatus(migrationsDir) {
|
|
1719
1786
|
console.log(chalk6.bold("\n\u26A1 Migration status\n"));
|
|
1720
1787
|
try {
|
|
1721
|
-
if (!await
|
|
1788
|
+
if (!await fs7.pathExists(migrationsDir)) {
|
|
1722
1789
|
console.log(chalk6.dim("No migrations directory found"));
|
|
1723
1790
|
console.log(chalk6.dim("\nRun `tthr migrate create` to create your first migration\n"));
|
|
1724
1791
|
return;
|
|
1725
1792
|
}
|
|
1726
|
-
const files = await
|
|
1793
|
+
const files = await fs7.readdir(migrationsDir);
|
|
1727
1794
|
const migrations = files.filter((f) => f.endsWith(".sql")).sort();
|
|
1728
1795
|
if (migrations.length === 0) {
|
|
1729
1796
|
console.log(chalk6.dim("No migrations found"));
|