tthr 0.0.18 → 0.0.20
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 +331 -181
- 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,133 @@ 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";
|
|
1570
|
+
function parseSchemaFile(source) {
|
|
1571
|
+
const tables = [];
|
|
1572
|
+
const schemaMatch = source.match(/defineSchema\s*\(\s*\{([\s\S]*)\}\s*\)/);
|
|
1573
|
+
if (!schemaMatch) return tables;
|
|
1574
|
+
const schemaContent = schemaMatch[1];
|
|
1575
|
+
const tableStartRegex = /(\w+)\s*:\s*\{/g;
|
|
1576
|
+
let match;
|
|
1577
|
+
while ((match = tableStartRegex.exec(schemaContent)) !== null) {
|
|
1578
|
+
const tableName = match[1];
|
|
1579
|
+
const startOffset = match.index + match[0].length;
|
|
1580
|
+
let braceCount = 1;
|
|
1581
|
+
let endOffset = startOffset;
|
|
1582
|
+
for (let i = startOffset; i < schemaContent.length && braceCount > 0; i++) {
|
|
1583
|
+
const char = schemaContent[i];
|
|
1584
|
+
if (char === "{") braceCount++;
|
|
1585
|
+
else if (char === "}") braceCount--;
|
|
1586
|
+
endOffset = i;
|
|
1587
|
+
}
|
|
1588
|
+
const columnsContent = schemaContent.slice(startOffset, endOffset);
|
|
1589
|
+
const columns = parseColumns(columnsContent);
|
|
1590
|
+
tables.push({ name: tableName, columns });
|
|
1591
|
+
}
|
|
1592
|
+
return tables;
|
|
1593
|
+
}
|
|
1594
|
+
function parseColumns(content) {
|
|
1595
|
+
const columns = [];
|
|
1596
|
+
const columnRegex = /(\w+)\s*:\s*(\w+)\s*\(\s*\)([^,\n}]*)/g;
|
|
1597
|
+
let match;
|
|
1598
|
+
while ((match = columnRegex.exec(content)) !== null) {
|
|
1599
|
+
const name = match[1];
|
|
1600
|
+
const schemaType = match[2];
|
|
1601
|
+
const modifiers = match[3] || "";
|
|
1602
|
+
const nullable = !modifiers.includes(".notNull()") && !modifiers.includes(".primaryKey()");
|
|
1603
|
+
const primaryKey = modifiers.includes(".primaryKey()");
|
|
1604
|
+
columns.push({
|
|
1605
|
+
name,
|
|
1606
|
+
type: schemaTypeToTS(schemaType),
|
|
1607
|
+
nullable,
|
|
1608
|
+
primaryKey
|
|
1609
|
+
});
|
|
1610
|
+
}
|
|
1611
|
+
return columns;
|
|
1612
|
+
}
|
|
1613
|
+
function schemaTypeToTS(schemaType) {
|
|
1614
|
+
switch (schemaType) {
|
|
1615
|
+
case "text":
|
|
1616
|
+
return "string";
|
|
1617
|
+
case "integer":
|
|
1618
|
+
return "number";
|
|
1619
|
+
case "real":
|
|
1620
|
+
return "number";
|
|
1621
|
+
case "boolean":
|
|
1622
|
+
return "boolean";
|
|
1623
|
+
case "timestamp":
|
|
1624
|
+
return "string";
|
|
1625
|
+
case "json":
|
|
1626
|
+
return "unknown";
|
|
1627
|
+
case "blob":
|
|
1628
|
+
return "Uint8Array";
|
|
1629
|
+
default:
|
|
1630
|
+
return "unknown";
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
function tableNameToInterface(tableName) {
|
|
1634
|
+
let name = tableName;
|
|
1635
|
+
if (name.endsWith("ies")) {
|
|
1636
|
+
name = name.slice(0, -3) + "y";
|
|
1637
|
+
} else if (name.endsWith("s") && !name.endsWith("ss")) {
|
|
1638
|
+
name = name.slice(0, -1);
|
|
1639
|
+
}
|
|
1640
|
+
return name.charAt(0).toUpperCase() + name.slice(1);
|
|
1641
|
+
}
|
|
1642
|
+
function generateDbFile(tables) {
|
|
1643
|
+
const lines = [
|
|
1644
|
+
"// Auto-generated by Tether CLI - do not edit manually",
|
|
1645
|
+
`// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1646
|
+
"",
|
|
1647
|
+
"import { createDatabaseProxy, type TetherDatabase } from '@tthr/client';",
|
|
1648
|
+
""
|
|
1649
|
+
];
|
|
1650
|
+
for (const table of tables) {
|
|
1651
|
+
const interfaceName = tableNameToInterface(table.name);
|
|
1652
|
+
lines.push(`export interface ${interfaceName} {`);
|
|
1653
|
+
for (const col of table.columns) {
|
|
1654
|
+
const typeStr = col.nullable ? `${col.type} | null` : col.type;
|
|
1655
|
+
lines.push(` ${col.name}: ${typeStr};`);
|
|
1656
|
+
}
|
|
1657
|
+
lines.push("}");
|
|
1658
|
+
lines.push("");
|
|
1659
|
+
}
|
|
1660
|
+
lines.push("export interface Schema {");
|
|
1661
|
+
for (const table of tables) {
|
|
1662
|
+
const interfaceName = tableNameToInterface(table.name);
|
|
1663
|
+
lines.push(` ${table.name}: ${interfaceName};`);
|
|
1664
|
+
}
|
|
1665
|
+
lines.push("}");
|
|
1666
|
+
lines.push("");
|
|
1667
|
+
lines.push("// Database client with typed tables");
|
|
1668
|
+
lines.push("// This is a proxy that will be populated by the Tether runtime");
|
|
1669
|
+
lines.push("export const db: TetherDatabase<Schema> = createDatabaseProxy<Schema>();");
|
|
1670
|
+
lines.push("");
|
|
1671
|
+
return lines.join("\n");
|
|
1672
|
+
}
|
|
1673
|
+
function generateApiFile(functionsDir, tables) {
|
|
1674
|
+
const lines = [
|
|
1675
|
+
"// Auto-generated by Tether CLI - do not edit manually",
|
|
1676
|
+
`// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
1677
|
+
"",
|
|
1678
|
+
"import { createApiProxy } from '@tthr/client';",
|
|
1679
|
+
"import type { Schema } from './db';",
|
|
1680
|
+
"",
|
|
1681
|
+
"// API type placeholder - will be populated based on your functions",
|
|
1682
|
+
"// eslint-disable-next-line @typescript-eslint/no-empty-object-type",
|
|
1683
|
+
"export interface Api {}",
|
|
1684
|
+
"",
|
|
1685
|
+
"// API client proxy - will be populated by the Tether runtime",
|
|
1686
|
+
"export const api: Api = createApiProxy<Api>();",
|
|
1687
|
+
""
|
|
1688
|
+
];
|
|
1689
|
+
return lines.join("\n");
|
|
1690
|
+
}
|
|
1507
1691
|
async function generateCommand() {
|
|
1508
1692
|
await requireAuth();
|
|
1509
|
-
const configPath =
|
|
1510
|
-
if (!await
|
|
1693
|
+
const configPath = path6.resolve(process.cwd(), "tether.config.ts");
|
|
1694
|
+
if (!await fs6.pathExists(configPath)) {
|
|
1511
1695
|
console.log(chalk5.red("\nError: Not a Tether project"));
|
|
1512
1696
|
console.log(chalk5.dim("Run `tthr init` to create a new project\n"));
|
|
1513
1697
|
process.exit(1);
|
|
@@ -1515,85 +1699,51 @@ async function generateCommand() {
|
|
|
1515
1699
|
console.log(chalk5.bold("\n\u26A1 Generating types from schema\n"));
|
|
1516
1700
|
const spinner = ora4("Reading schema...").start();
|
|
1517
1701
|
try {
|
|
1518
|
-
const
|
|
1519
|
-
const
|
|
1520
|
-
|
|
1702
|
+
const config = await loadConfig();
|
|
1703
|
+
const schemaPath = resolvePath(config.schema);
|
|
1704
|
+
const outputDir = resolvePath(config.output);
|
|
1705
|
+
const functionsDir = resolvePath(config.functions);
|
|
1706
|
+
if (!await fs6.pathExists(schemaPath)) {
|
|
1521
1707
|
spinner.fail("Schema file not found");
|
|
1522
1708
|
console.log(chalk5.dim(`Expected: ${schemaPath}
|
|
1523
1709
|
`));
|
|
1524
1710
|
process.exit(1);
|
|
1525
1711
|
}
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
content: string | null;
|
|
1539
|
-
authorId: string;
|
|
1540
|
-
createdAt: string;
|
|
1541
|
-
updatedAt: string;
|
|
1542
|
-
}
|
|
1543
|
-
|
|
1544
|
-
export interface Comment {
|
|
1545
|
-
id: string;
|
|
1546
|
-
postId: string;
|
|
1547
|
-
content: string;
|
|
1548
|
-
authorId: string;
|
|
1549
|
-
createdAt: string;
|
|
1550
|
-
}
|
|
1551
|
-
|
|
1552
|
-
export interface Schema {
|
|
1553
|
-
posts: Post;
|
|
1554
|
-
comments: Comment;
|
|
1555
|
-
}
|
|
1556
|
-
|
|
1557
|
-
// Database client with typed tables
|
|
1558
|
-
export declare const db: TetherDatabase<Schema>;
|
|
1559
|
-
`
|
|
1712
|
+
const schemaSource = await fs6.readFile(schemaPath, "utf-8");
|
|
1713
|
+
const tables = parseSchemaFile(schemaSource);
|
|
1714
|
+
if (tables.length === 0) {
|
|
1715
|
+
spinner.warn("No tables found in schema");
|
|
1716
|
+
console.log(chalk5.dim(" Make sure your schema uses defineSchema({ ... })\n"));
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
spinner.text = `Generating types for ${tables.length} table(s)...`;
|
|
1720
|
+
await fs6.ensureDir(outputDir);
|
|
1721
|
+
await fs6.writeFile(
|
|
1722
|
+
path6.join(outputDir, "db.ts"),
|
|
1723
|
+
generateDbFile(tables)
|
|
1560
1724
|
);
|
|
1561
|
-
await
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
// Generated at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1565
|
-
|
|
1566
|
-
import type { TetherClient } from '@tthr/client';
|
|
1567
|
-
import type { Schema } from './db';
|
|
1568
|
-
|
|
1569
|
-
export interface PostsApi {
|
|
1570
|
-
list: (args?: { limit?: number }) => Promise<Schema['posts'][]>;
|
|
1571
|
-
get: (args: { id: string }) => Promise<Schema['posts'] | null>;
|
|
1572
|
-
create: (args: { title: string; content?: string }) => Promise<{ id: string }>;
|
|
1573
|
-
update: (args: { id: string; title?: string; content?: string }) => Promise<void>;
|
|
1574
|
-
remove: (args: { id: string }) => Promise<void>;
|
|
1575
|
-
}
|
|
1576
|
-
|
|
1577
|
-
export interface Api {
|
|
1578
|
-
posts: PostsApi;
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
// Typed Tether client
|
|
1582
|
-
export declare const tether: TetherClient<Api>;
|
|
1583
|
-
`
|
|
1725
|
+
await fs6.writeFile(
|
|
1726
|
+
path6.join(outputDir, "api.ts"),
|
|
1727
|
+
generateApiFile(functionsDir, tables)
|
|
1584
1728
|
);
|
|
1585
|
-
await
|
|
1586
|
-
|
|
1729
|
+
await fs6.writeFile(
|
|
1730
|
+
path6.join(outputDir, "index.ts"),
|
|
1587
1731
|
`// Auto-generated by Tether CLI - do not edit manually
|
|
1588
1732
|
export * from './db';
|
|
1589
1733
|
export * from './api';
|
|
1590
1734
|
`
|
|
1591
1735
|
);
|
|
1592
|
-
spinner.succeed(
|
|
1736
|
+
spinner.succeed(`Types generated for ${tables.length} table(s)`);
|
|
1737
|
+
console.log("\n" + chalk5.green("\u2713") + " Tables:");
|
|
1738
|
+
for (const table of tables) {
|
|
1739
|
+
console.log(chalk5.dim(` - ${table.name} (${table.columns.length} columns)`));
|
|
1740
|
+
}
|
|
1741
|
+
const relativeOutput = path6.relative(process.cwd(), outputDir);
|
|
1593
1742
|
console.log("\n" + chalk5.green("\u2713") + " Generated files:");
|
|
1594
|
-
console.log(chalk5.dim(
|
|
1595
|
-
console.log(chalk5.dim(
|
|
1596
|
-
console.log(chalk5.dim(
|
|
1743
|
+
console.log(chalk5.dim(` ${relativeOutput}/db.ts`));
|
|
1744
|
+
console.log(chalk5.dim(` ${relativeOutput}/api.ts`));
|
|
1745
|
+
console.log(chalk5.dim(` ${relativeOutput}/index.ts
|
|
1746
|
+
`));
|
|
1597
1747
|
} catch (error) {
|
|
1598
1748
|
spinner.fail("Failed to generate types");
|
|
1599
1749
|
console.error(chalk5.red(error instanceof Error ? error.message : "Unknown error"));
|
|
@@ -1604,18 +1754,18 @@ export * from './api';
|
|
|
1604
1754
|
// src/commands/migrate.ts
|
|
1605
1755
|
import chalk6 from "chalk";
|
|
1606
1756
|
import ora5 from "ora";
|
|
1607
|
-
import
|
|
1608
|
-
import
|
|
1757
|
+
import fs7 from "fs-extra";
|
|
1758
|
+
import path7 from "path";
|
|
1609
1759
|
import prompts2 from "prompts";
|
|
1610
1760
|
async function migrateCommand(action, options) {
|
|
1611
1761
|
await requireAuth();
|
|
1612
|
-
const configPath =
|
|
1613
|
-
if (!await
|
|
1762
|
+
const configPath = path7.resolve(process.cwd(), "tether.config.ts");
|
|
1763
|
+
if (!await fs7.pathExists(configPath)) {
|
|
1614
1764
|
console.log(chalk6.red("\nError: Not a Tether project"));
|
|
1615
1765
|
console.log(chalk6.dim("Run `tthr init` to create a new project\n"));
|
|
1616
1766
|
process.exit(1);
|
|
1617
1767
|
}
|
|
1618
|
-
const migrationsDir =
|
|
1768
|
+
const migrationsDir = path7.resolve(process.cwd(), "tether", "migrations");
|
|
1619
1769
|
switch (action) {
|
|
1620
1770
|
case "create":
|
|
1621
1771
|
await createMigration(migrationsDir, options.name);
|
|
@@ -1650,11 +1800,11 @@ async function createMigration(migrationsDir, name) {
|
|
|
1650
1800
|
}
|
|
1651
1801
|
const spinner = ora5("Creating migration...").start();
|
|
1652
1802
|
try {
|
|
1653
|
-
await
|
|
1803
|
+
await fs7.ensureDir(migrationsDir);
|
|
1654
1804
|
const timestamp = Date.now();
|
|
1655
1805
|
const filename = `${timestamp}_${migrationName}.sql`;
|
|
1656
|
-
const filepath =
|
|
1657
|
-
await
|
|
1806
|
+
const filepath = path7.join(migrationsDir, filename);
|
|
1807
|
+
await fs7.writeFile(
|
|
1658
1808
|
filepath,
|
|
1659
1809
|
`-- Migration: ${migrationName}
|
|
1660
1810
|
-- Created at: ${(/* @__PURE__ */ new Date()).toISOString()}
|
|
@@ -1690,12 +1840,12 @@ async function runMigrations(migrationsDir, direction) {
|
|
|
1690
1840
|
`));
|
|
1691
1841
|
const spinner = ora5("Checking migrations...").start();
|
|
1692
1842
|
try {
|
|
1693
|
-
if (!await
|
|
1843
|
+
if (!await fs7.pathExists(migrationsDir)) {
|
|
1694
1844
|
spinner.info("No migrations directory found");
|
|
1695
1845
|
console.log(chalk6.dim("\nRun `tthr migrate create` to create your first migration\n"));
|
|
1696
1846
|
return;
|
|
1697
1847
|
}
|
|
1698
|
-
const files = await
|
|
1848
|
+
const files = await fs7.readdir(migrationsDir);
|
|
1699
1849
|
const migrations = files.filter((f) => f.endsWith(".sql")).sort((a, b) => direction === "up" ? a.localeCompare(b) : b.localeCompare(a));
|
|
1700
1850
|
if (migrations.length === 0) {
|
|
1701
1851
|
spinner.info("No migrations found");
|
|
@@ -1718,12 +1868,12 @@ async function runMigrations(migrationsDir, direction) {
|
|
|
1718
1868
|
async function showStatus(migrationsDir) {
|
|
1719
1869
|
console.log(chalk6.bold("\n\u26A1 Migration status\n"));
|
|
1720
1870
|
try {
|
|
1721
|
-
if (!await
|
|
1871
|
+
if (!await fs7.pathExists(migrationsDir)) {
|
|
1722
1872
|
console.log(chalk6.dim("No migrations directory found"));
|
|
1723
1873
|
console.log(chalk6.dim("\nRun `tthr migrate create` to create your first migration\n"));
|
|
1724
1874
|
return;
|
|
1725
1875
|
}
|
|
1726
|
-
const files = await
|
|
1876
|
+
const files = await fs7.readdir(migrationsDir);
|
|
1727
1877
|
const migrations = files.filter((f) => f.endsWith(".sql")).sort();
|
|
1728
1878
|
if (migrations.length === 0) {
|
|
1729
1879
|
console.log(chalk6.dim("No migrations found"));
|