@amirulabu/create-recurring-rabbit-app 0.2.19 → 0.2.26
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/README.md +3 -3
- package/dist/index.js +146 -61
- package/package.json +7 -2
- package/templates/default/.npmrc +5 -0
- package/templates/default/.nvmrc +1 -0
- package/templates/default/README.md +3 -3
- package/templates/default/postcss.config.js +5 -0
- package/templates/default/src/components/ui/button.tsx +1 -1
- package/templates/default/src/components/ui/input.tsx +1 -1
- package/templates/default/src/{app/globals.css → globals.css} +4 -5
- package/templates/default/src/{app → routes}/__root.tsx +1 -3
- package/templates/default/src/{app → routes}/api/auth/get-session.ts +12 -10
- package/templates/default/src/routes/api/trpc.ts +19 -0
- package/templates/default/src/server/api/routers/user.ts +44 -0
- package/templates/default/src/server/db/seed.ts +5 -0
- package/templates/default/tsconfig.json +2 -7
- package/templates/default/vite.config.ts +23 -0
- package/templates/default/app.config.ts +0 -29
- package/templates/default/src/app/api/trpc.server.ts +0 -12
- package/templates/default/src/app/client.tsx +0 -9
- package/templates/default/src/app/ssr.tsx +0 -9
- package/templates/default/tailwind.config.js +0 -46
- /package/templates/default/src/{app/routeTree.gen.ts → routeTree.gen.ts} +0 -0
- /package/templates/default/src/{app/router.ts → router.ts} +0 -0
- /package/templates/default/src/{app → routes}/api/auth/$.ts +0 -0
- /package/templates/default/src/{app → routes}/api/health.ts +0 -0
- /package/templates/default/src/{app → routes}/auth/forgot-password.tsx +0 -0
- /package/templates/default/src/{app → routes}/auth/login.tsx +0 -0
- /package/templates/default/src/{app → routes}/auth/register.tsx +0 -0
- /package/templates/default/src/{app → routes}/auth/reset-password.tsx +0 -0
- /package/templates/default/src/{app → routes}/auth/verify-email.tsx +0 -0
- /package/templates/default/src/{app → routes}/dashboard/index.tsx +0 -0
- /package/templates/default/src/{app → routes}/dashboard/settings.tsx +0 -0
- /package/templates/default/src/{app → routes}/index.tsx +0 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> A production-ready CLI tool that scaffolds an opinionated micro‑SaaS starter with TanStack Start, tRPC, Drizzle, Better-auth, and Tailwind CSS.
|
|
4
4
|
|
|
5
|
-
**Current Version**: v0.2.
|
|
5
|
+
**Current Version**: v0.2.34 | **Status**: ✅ Production Ready - Ready for Public Launch
|
|
6
6
|
|
|
7
7
|
## Quick Start
|
|
8
8
|
|
|
@@ -29,7 +29,7 @@ The generated starter includes everything you need to build a micro-SaaS:
|
|
|
29
29
|
- 🔧 **Developer Tools** - Hot reload, ESLint, Prettier, bundle analyzer, performance monitoring
|
|
30
30
|
- 📦 **CI/CD** - GitHub Actions workflows for automated testing and validation
|
|
31
31
|
- 📝 **Documentation** - Comprehensive guides, ADRs, and JSDoc comments
|
|
32
|
-
- 🧪 **Testing** - Vitest integration with
|
|
32
|
+
- 🧪 **Testing** - Vitest integration with 30 passing tests
|
|
33
33
|
|
|
34
34
|
## CLI Usage
|
|
35
35
|
|
|
@@ -67,7 +67,7 @@ create-recurring-rabbit-app/
|
|
|
67
67
|
│ └── templates/ # App templates
|
|
68
68
|
│ └── default/ # Default template
|
|
69
69
|
│ ├── src/
|
|
70
|
-
│ │ ├──
|
|
70
|
+
│ │ ├── routes/ # TanStack Start routes
|
|
71
71
|
│ │ ├── server/ # tRPC + DB + Auth
|
|
72
72
|
│ │ ├── components/ # UI components
|
|
73
73
|
│ │ └── lib/ # Shared utilities
|
package/dist/index.js
CHANGED
|
@@ -36,9 +36,9 @@ async function generatePackageJson(targetDir, config) {
|
|
|
36
36
|
description: config.description ?? "A micro-SaaS built with TanStack Start, tRPC, and Drizzle",
|
|
37
37
|
type: "module",
|
|
38
38
|
scripts: {
|
|
39
|
-
dev: "
|
|
40
|
-
build: "
|
|
41
|
-
start: "
|
|
39
|
+
dev: "vite dev --port 3000",
|
|
40
|
+
build: "vite build",
|
|
41
|
+
start: "node .output/server/index.mjs",
|
|
42
42
|
test: "vitest",
|
|
43
43
|
"test:run": "vitest run",
|
|
44
44
|
"db:generate": "drizzle-kit generate",
|
|
@@ -50,35 +50,37 @@ async function generatePackageJson(targetDir, config) {
|
|
|
50
50
|
lint: "eslint .",
|
|
51
51
|
"lint:fix": "eslint . --fix",
|
|
52
52
|
format: 'prettier --write "src/**/*.{ts,tsx,json,css}"',
|
|
53
|
-
clean: "rm -rf .vinxi dist data/*.db data/*.db-shm data/*.db-wal",
|
|
54
|
-
"build:analyze": "ANALYZE=true vinxi build",
|
|
53
|
+
clean: "rm -rf .vinxi dist node_modules/.vite data/*.db data/*.db-shm data/*.db-wal",
|
|
55
54
|
lighthouse: "lhci autorun",
|
|
56
55
|
...config.scripts
|
|
57
56
|
},
|
|
57
|
+
engines: {
|
|
58
|
+
node: ">=22.0.0",
|
|
59
|
+
pnpm: ">=9.0.0"
|
|
60
|
+
},
|
|
58
61
|
dependencies: {
|
|
59
62
|
"@paralleldrive/cuid2": "^2.2.0",
|
|
60
63
|
"@radix-ui/react-slot": "^1.0.2",
|
|
61
64
|
"@tanstack/react-query": "^5.0.0",
|
|
62
|
-
"@tanstack/react-router": "~1.
|
|
65
|
+
"@tanstack/react-router": "~1.121.0",
|
|
63
66
|
"@tanstack/react-query-devtools": "^5.0.0",
|
|
64
|
-
"@tanstack/start": "
|
|
65
|
-
"@trpc/client": "^11.
|
|
66
|
-
"@trpc/react-query": "^11.
|
|
67
|
-
"@trpc/server": "^11.
|
|
67
|
+
"@tanstack/react-start": "~1.121.0",
|
|
68
|
+
"@trpc/client": "^11.8.1",
|
|
69
|
+
"@trpc/react-query": "^11.8.1",
|
|
70
|
+
"@trpc/server": "^11.8.1",
|
|
68
71
|
"@t3-oss/env-core": "^0.10.0",
|
|
69
|
-
"better-auth": "^1.
|
|
72
|
+
"better-auth": "^1.4.17",
|
|
70
73
|
"better-sqlite3": "^12.0.0",
|
|
71
74
|
"class-variance-authority": "^0.7.0",
|
|
72
75
|
clsx: "^2.1.0",
|
|
73
76
|
dotenv: "^16.4.0",
|
|
74
|
-
"drizzle-orm": "^0.
|
|
77
|
+
"drizzle-orm": "^0.45.1",
|
|
75
78
|
postgres: "^3.4.0",
|
|
76
79
|
react: "^18.2.0",
|
|
77
80
|
"react-dom": "^18.2.0",
|
|
78
81
|
resend: "^3.2.0",
|
|
79
82
|
"tailwind-merge": "^2.2.0",
|
|
80
|
-
|
|
81
|
-
zod: "^3.22.0",
|
|
83
|
+
zod: "^3.22.4",
|
|
82
84
|
...config.dependencies
|
|
83
85
|
},
|
|
84
86
|
devDependencies: {
|
|
@@ -95,38 +97,40 @@ async function generatePackageJson(targetDir, config) {
|
|
|
95
97
|
eslint: "^9.39.2",
|
|
96
98
|
"eslint-plugin-react": "^7.37.0",
|
|
97
99
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
98
|
-
"drizzle-kit": "^0.31.
|
|
100
|
+
"drizzle-kit": "^0.31.8",
|
|
99
101
|
globals: "^15.0.0",
|
|
100
|
-
postcss: "^
|
|
102
|
+
"@tailwindcss/postcss": "^4.0.0",
|
|
101
103
|
prettier: "^3.2.5",
|
|
102
104
|
"rollup-plugin-visualizer": "^5.12.0",
|
|
103
|
-
tailwindcss: "^
|
|
105
|
+
tailwindcss: "^4.1.18",
|
|
104
106
|
tsx: "^4.7.0",
|
|
105
107
|
typescript: "^5.3.3",
|
|
106
|
-
|
|
107
|
-
|
|
108
|
+
vite: "6",
|
|
109
|
+
"vite-tsconfig-paths": "^4.0.0",
|
|
110
|
+
vitest: "^4.0.18",
|
|
108
111
|
...config.devDependencies
|
|
109
112
|
},
|
|
110
113
|
pnpm: {
|
|
111
114
|
overrides: {
|
|
112
|
-
|
|
113
|
-
"@tanstack/react-
|
|
114
|
-
"@tanstack/react-start-
|
|
115
|
-
"@tanstack/react-start-
|
|
116
|
-
"@tanstack/
|
|
117
|
-
"@tanstack/router-
|
|
118
|
-
"@tanstack/router-
|
|
119
|
-
"@tanstack/
|
|
120
|
-
"@tanstack/start-
|
|
121
|
-
"@tanstack/start-
|
|
122
|
-
"@tanstack/server-
|
|
123
|
-
"@tanstack/
|
|
124
|
-
"@tanstack/
|
|
125
|
-
"@tanstack/start-
|
|
126
|
-
"@tanstack/start-server-functions-
|
|
127
|
-
"@tanstack/start-server-functions-
|
|
128
|
-
"@tanstack/
|
|
129
|
-
"@tanstack/
|
|
115
|
+
zod: "^3.22.4",
|
|
116
|
+
"@tanstack/react-router": "~1.121.0",
|
|
117
|
+
"@tanstack/react-start-client": "~1.121.0",
|
|
118
|
+
"@tanstack/react-start-plugin": "~1.121.0",
|
|
119
|
+
"@tanstack/react-start-server": "~1.121.0",
|
|
120
|
+
"@tanstack/router-core": "~1.121.0",
|
|
121
|
+
"@tanstack/router-generator": "~1.121.0",
|
|
122
|
+
"@tanstack/router-plugin": "~1.121.0",
|
|
123
|
+
"@tanstack/start-client-core": "~1.121.0",
|
|
124
|
+
"@tanstack/start-plugin-core": "~1.121.0",
|
|
125
|
+
"@tanstack/start-server-core": "~1.121.0",
|
|
126
|
+
"@tanstack/server-functions-plugin": "~1.121.0",
|
|
127
|
+
"@tanstack/directive-functions-plugin": "~1.121.0",
|
|
128
|
+
"@tanstack/start-api-routes": "~1.121.0",
|
|
129
|
+
"@tanstack/start-server-functions-client": "~1.121.0",
|
|
130
|
+
"@tanstack/start-server-functions-fetcher": "~1.121.0",
|
|
131
|
+
"@tanstack/start-server-functions-handler": "~1.121.0",
|
|
132
|
+
"@tanstack/router-utils": "~1.121.0",
|
|
133
|
+
"@tanstack/history": "~1.121.0"
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
};
|
|
@@ -167,7 +171,7 @@ async function detectPackageManager() {
|
|
|
167
171
|
function getInstallCommand(packageManager) {
|
|
168
172
|
switch (packageManager) {
|
|
169
173
|
case "pnpm":
|
|
170
|
-
return "pnpm install --ignore-workspace";
|
|
174
|
+
return "pnpm install --ignore-workspace --shamefully-hoist=false";
|
|
171
175
|
case "yarn":
|
|
172
176
|
return "yarn";
|
|
173
177
|
case "npm":
|
|
@@ -187,29 +191,17 @@ function installDependencies(projectPath, packageManager) {
|
|
|
187
191
|
if (packageManager === "pnpm") {
|
|
188
192
|
const rebuildSpinner = ora("Building native modules...").start();
|
|
189
193
|
try {
|
|
190
|
-
execSync("
|
|
191
|
-
cwd: projectPath
|
|
194
|
+
execSync("npx node-gyp rebuild", {
|
|
195
|
+
cwd: `${projectPath}/node_modules/better-sqlite3`,
|
|
192
196
|
stdio: "inherit"
|
|
193
197
|
});
|
|
194
198
|
rebuildSpinner.succeed(chalk2.green("\u2713 Native modules built"));
|
|
195
199
|
} catch {
|
|
196
|
-
rebuildSpinner.
|
|
197
|
-
|
|
200
|
+
rebuildSpinner.fail(chalk2.red("\u2717 Native module build failed"));
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Failed to build native modules. This is required for database functionality.
|
|
203
|
+
Try running manually: cd ${projectPath}/node_modules/better-sqlite3 && npx node-gyp rebuild`
|
|
198
204
|
);
|
|
199
|
-
try {
|
|
200
|
-
execSync("npx node-gyp rebuild --directory node_modules/better-sqlite3", {
|
|
201
|
-
cwd: projectPath,
|
|
202
|
-
stdio: "inherit"
|
|
203
|
-
});
|
|
204
|
-
rebuildSpinner.succeed(chalk2.green("\u2713 Native modules built"));
|
|
205
|
-
} catch {
|
|
206
|
-
rebuildSpinner.fail(chalk2.red("\u2717 Native module build failed"));
|
|
207
|
-
throw new Error(
|
|
208
|
-
`Failed to build native modules. This is required for database functionality.
|
|
209
|
-
Try running manually: cd ${projectPath} && pnpm rebuild better-sqlite3
|
|
210
|
-
Or use npm instead: cd ${projectPath} && npm install`
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
205
|
}
|
|
214
206
|
}
|
|
215
207
|
} catch {
|
|
@@ -223,10 +215,10 @@ function validateNodeVersion() {
|
|
|
223
215
|
const nodeVersion = process.version.replace("v", "");
|
|
224
216
|
const versionParts = nodeVersion.split(".");
|
|
225
217
|
const majorVersion = parseInt(versionParts[0] ?? "0", 10);
|
|
226
|
-
const validVersions = [
|
|
218
|
+
const validVersions = [22, 24];
|
|
227
219
|
if (!validVersions.includes(majorVersion)) {
|
|
228
220
|
throw new Error(
|
|
229
|
-
`Node.js v${majorVersion} is not supported. Please use Node.js
|
|
221
|
+
`Node.js v${majorVersion} is not supported. Please use Node.js 22 or 24 (latest LTS versions). Current version: v${nodeVersion}`
|
|
230
222
|
);
|
|
231
223
|
}
|
|
232
224
|
}
|
|
@@ -519,6 +511,47 @@ async function cleanupProject(projectPath) {
|
|
|
519
511
|
console.warn(`Warning: Could not cleanup ${projectPath}: ${error.message}`);
|
|
520
512
|
}
|
|
521
513
|
}
|
|
514
|
+
function startDevServer(options) {
|
|
515
|
+
const { projectPath, packageManager, openBrowser = true } = options;
|
|
516
|
+
const spinner = ora("Starting development server...").start();
|
|
517
|
+
try {
|
|
518
|
+
const devCommand = packageManager === "npm" ? "npm" : packageManager;
|
|
519
|
+
const args = ["run", "dev"];
|
|
520
|
+
const devServer = spawn(devCommand, args, {
|
|
521
|
+
cwd: projectPath,
|
|
522
|
+
stdio: "inherit",
|
|
523
|
+
shell: true
|
|
524
|
+
});
|
|
525
|
+
devServer.stdout?.on("data", (data) => {
|
|
526
|
+
const output = data.toString();
|
|
527
|
+
if (output.includes("Local:") || output.includes("listening")) {
|
|
528
|
+
spinner.succeed(chalk2.green("\u2713 Development server started"));
|
|
529
|
+
console.log("\n" + output.trim());
|
|
530
|
+
if (openBrowser) {
|
|
531
|
+
setTimeout(() => {
|
|
532
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
533
|
+
spawn(openCmd, ["http://localhost:3000"], { stdio: "ignore", shell: true });
|
|
534
|
+
}, 2e3);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
devServer.stderr?.on("data", (data) => {
|
|
539
|
+
console.error(data.toString());
|
|
540
|
+
});
|
|
541
|
+
devServer.on("error", (error) => {
|
|
542
|
+
spinner.fail(chalk2.red("\u2717 Failed to start development server"));
|
|
543
|
+
throw new Error(`Dev server error: ${error.message}`);
|
|
544
|
+
});
|
|
545
|
+
devServer.on("close", (code) => {
|
|
546
|
+
if (code !== 0 && code !== null) {
|
|
547
|
+
spinner.fail(chalk2.red("\u2717 Development server exited with error"));
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
} catch (error) {
|
|
551
|
+
spinner.fail(chalk2.red("\u2717 Failed to start development server"));
|
|
552
|
+
throw error;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
522
555
|
|
|
523
556
|
// src/commands/create.ts
|
|
524
557
|
var getPackageRoot = () => {
|
|
@@ -597,6 +630,16 @@ async function initProjectDatabase(projectPath) {
|
|
|
597
630
|
throw error;
|
|
598
631
|
}
|
|
599
632
|
}
|
|
633
|
+
function startDevelopmentServer(projectPath, packageManager) {
|
|
634
|
+
try {
|
|
635
|
+
startDevServer({ projectPath, packageManager, openBrowser: true });
|
|
636
|
+
} catch (error) {
|
|
637
|
+
console.error("\nFailed to start development server. You can start it manually by running:");
|
|
638
|
+
console.log(` cd ${projectPath}`);
|
|
639
|
+
console.log(` ${packageManager === "npm" ? "npm run dev" : `${packageManager} dev`}`);
|
|
640
|
+
throw error;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
600
643
|
function displaySuccess(projectName, packageManager) {
|
|
601
644
|
console.log(getSuccessMessage());
|
|
602
645
|
}
|
|
@@ -606,11 +649,45 @@ function displayError(error) {
|
|
|
606
649
|
console.log(getTroubleshootingTips());
|
|
607
650
|
}
|
|
608
651
|
}
|
|
609
|
-
|
|
610
|
-
|
|
652
|
+
var MIN_NODE_VERSION = 22;
|
|
653
|
+
var SUPPORTED_VERSIONS = [22, 24];
|
|
654
|
+
function checkNodeVersion() {
|
|
655
|
+
const nodeVersion = process.version.slice(1);
|
|
656
|
+
const majorVersion = parseInt(nodeVersion.split(".")[0] || "0", 10);
|
|
657
|
+
if (majorVersion < MIN_NODE_VERSION) {
|
|
658
|
+
console.error(
|
|
659
|
+
chalk2.red(
|
|
660
|
+
"\u274C Error: create-recurring-rabbit-stack requires Node.js 22 or higher (LTS versions only)"
|
|
661
|
+
)
|
|
662
|
+
);
|
|
663
|
+
console.error(chalk2.red(`Current version: v${nodeVersion}`));
|
|
664
|
+
console.error(chalk2.yellow("\nPlease upgrade to Node.js 22.x or 24.x (LTS versions)"));
|
|
665
|
+
console.error(chalk2.gray("\nQuick upgrade using nvm:"));
|
|
666
|
+
console.error(chalk2.gray(" nvm install 24"));
|
|
667
|
+
console.error(chalk2.gray(" nvm use 24"));
|
|
668
|
+
console.error(chalk2.gray("\nOr download from: https://nodejs.org/"));
|
|
669
|
+
process.exit(1);
|
|
670
|
+
}
|
|
671
|
+
const isSupported = SUPPORTED_VERSIONS.includes(majorVersion);
|
|
672
|
+
if (!isSupported) {
|
|
673
|
+
console.warn(
|
|
674
|
+
chalk2.yellow(
|
|
675
|
+
`\u26A0\uFE0F Warning: Node.js v${nodeVersion} is not an officially supported LTS version`
|
|
676
|
+
)
|
|
677
|
+
);
|
|
678
|
+
console.warn(chalk2.yellow(`Supported versions: ${SUPPORTED_VERSIONS.join(", ")}`));
|
|
679
|
+
console.warn(chalk2.gray("\nThe CLI may still work, but issues may occur."));
|
|
680
|
+
if (process.env.CI === "true") {
|
|
681
|
+
console.error(chalk2.red("\n\u274C CI/CD requires Node.js 22 or 24"));
|
|
682
|
+
process.exit(1);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
console.log(chalk2.green(`\u2705 Node.js version check passed: v${nodeVersion}`));
|
|
686
|
+
}
|
|
687
|
+
checkNodeVersion();
|
|
611
688
|
program.name("create-recurring-rabbit-app").description(
|
|
612
689
|
"Scaffold a production-ready micro-SaaS with TanStack Start, tRPC, Drizzle, and Better-auth"
|
|
613
|
-
).version("0.0.0-alpha").argument("[app-name]", "Name of your application", "my-saas-app").action(async (appName) => {
|
|
690
|
+
).version("0.0.0-alpha").argument("[app-name]", "Name of your application", "my-saas-app").option("--no-server", "Skip starting development server (useful for testing)").action(async (appName, options) => {
|
|
614
691
|
let hasError = false;
|
|
615
692
|
try {
|
|
616
693
|
const targetPath = process.cwd();
|
|
@@ -618,6 +695,14 @@ program.name("create-recurring-rabbit-app").description(
|
|
|
618
695
|
await installProjectDependencies(projectPath, packageManager);
|
|
619
696
|
await initProjectDatabase(projectPath);
|
|
620
697
|
displaySuccess(appName, packageManager);
|
|
698
|
+
if (options.server !== false) {
|
|
699
|
+
console.log(chalk2.cyan("\nStarting development server..."));
|
|
700
|
+
console.log(chalk2.gray("Press Ctrl+C to stop the server\n"));
|
|
701
|
+
startDevelopmentServer(projectPath, packageManager);
|
|
702
|
+
} else {
|
|
703
|
+
console.log(chalk2.gray("\nSkipping development server start (--no-server flag set)\n"));
|
|
704
|
+
process.exit(0);
|
|
705
|
+
}
|
|
621
706
|
} catch (error) {
|
|
622
707
|
hasError = true;
|
|
623
708
|
if (error instanceof CLIError) {
|
package/package.json
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amirulabu/create-recurring-rabbit-app",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.26",
|
|
4
4
|
"description": "CLI tool to scaffold micro-SaaS apps with TanStack Start, tRPC, Drizzle, and Better-auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"create-recurring-rabbit-app": "./bin/index.js"
|
|
8
8
|
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/amirulabu/recurring-rabbit-app.git"
|
|
12
|
+
},
|
|
9
13
|
"publishConfig": {
|
|
10
14
|
"access": "public"
|
|
11
15
|
},
|
|
@@ -35,7 +39,8 @@
|
|
|
35
39
|
"@vitest/ui": "^1.0.4"
|
|
36
40
|
},
|
|
37
41
|
"engines": {
|
|
38
|
-
"node": ">=
|
|
42
|
+
"node": ">=22.0.0",
|
|
43
|
+
"pnpm": ">=9.0.0"
|
|
39
44
|
},
|
|
40
45
|
"scripts": {
|
|
41
46
|
"dev": "tsx src/index.ts",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
24
|
|
@@ -27,7 +27,7 @@ pnpm dev
|
|
|
27
27
|
|
|
28
28
|
```
|
|
29
29
|
src/
|
|
30
|
-
├──
|
|
30
|
+
├── routes/ # TanStack Start routes
|
|
31
31
|
├── server/ # tRPC API and database
|
|
32
32
|
├── components/ # Reusable UI components
|
|
33
33
|
└── lib/ # Shared utilities
|
|
@@ -88,7 +88,7 @@ Analyze your bundle size to identify large dependencies and optimize performance
|
|
|
88
88
|
pnpm build:analyze
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
-
This will build your application and generate a `stats.html` file in build output directory (`.
|
|
91
|
+
This will build your application and generate a `stats.html` file in build output directory (`.output`). Open this file in your browser to explore:
|
|
92
92
|
|
|
93
93
|
- **Treemap view** - Visual representation of module sizes
|
|
94
94
|
- **Gzip/Brotli sizes** - Real-world compression impact
|
|
@@ -202,7 +202,7 @@ export const postsRouter = router({
|
|
|
202
202
|
|
|
203
203
|
**New Pages**
|
|
204
204
|
|
|
205
|
-
1. Add route file in `src/
|
|
205
|
+
1. Add route file in `src/routes/[route].tsx`
|
|
206
206
|
2. Implement component with TanStack Start conventions
|
|
207
207
|
3. Add navigation in layout components
|
|
208
208
|
|
|
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from 'class-variance-authority'
|
|
|
5
5
|
import { cn } from '@/lib/utils'
|
|
6
6
|
|
|
7
7
|
const buttonVariants = cva(
|
|
8
|
-
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-
|
|
8
|
+
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
9
9
|
{
|
|
10
10
|
variants: {
|
|
11
11
|
variant: {
|
|
@@ -10,7 +10,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
10
10
|
<input
|
|
11
11
|
type={type}
|
|
12
12
|
className={cn(
|
|
13
|
-
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-
|
|
13
|
+
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
|
|
14
14
|
className
|
|
15
15
|
)}
|
|
16
16
|
ref={ref}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
@
|
|
2
|
-
@tailwind components;
|
|
3
|
-
@tailwind utilities;
|
|
1
|
+
@import 'tailwindcss';
|
|
4
2
|
|
|
5
3
|
@layer base {
|
|
6
4
|
:root {
|
|
@@ -47,9 +45,10 @@
|
|
|
47
45
|
|
|
48
46
|
@layer base {
|
|
49
47
|
* {
|
|
50
|
-
|
|
48
|
+
border-color: hsl(var(--border));
|
|
51
49
|
}
|
|
52
50
|
body {
|
|
53
|
-
|
|
51
|
+
background-color: hsl(var(--background));
|
|
52
|
+
color: hsl(var(--foreground));
|
|
54
53
|
}
|
|
55
54
|
}
|
|
@@ -5,14 +5,12 @@ import { useState } from 'react'
|
|
|
5
5
|
import { httpBatchLink } from '@trpc/client'
|
|
6
6
|
import { TRPCProvider, trpc } from '@/lib/api'
|
|
7
7
|
import { env } from '@/lib/env'
|
|
8
|
-
import '@/
|
|
8
|
+
import '@/globals.css'
|
|
9
9
|
|
|
10
10
|
export const Route = createRootRoute({
|
|
11
11
|
component: RootComponent,
|
|
12
12
|
})
|
|
13
13
|
|
|
14
|
-
export const rootRoute = Route
|
|
15
|
-
|
|
16
14
|
function RootComponent() {
|
|
17
15
|
const [queryClient] = useState(() => new QueryClient())
|
|
18
16
|
const [trpcClient] = useState(() =>
|
|
@@ -11,16 +11,18 @@ import { auth } from '@/server/auth/config'
|
|
|
11
11
|
*/
|
|
12
12
|
export const Route = createFileRoute('/api/auth/get-session')({
|
|
13
13
|
server: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
handlers: {
|
|
15
|
+
GET: async ({ request }: { request: Request }) => {
|
|
16
|
+
try {
|
|
17
|
+
const session = await auth.api.getSession({
|
|
18
|
+
headers: request.headers,
|
|
19
|
+
})
|
|
20
|
+
return Response.json(session)
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error('Failed to get session:', error)
|
|
23
|
+
return Response.json({ user: null, session: null }, { status: 401 })
|
|
24
|
+
}
|
|
25
|
+
},
|
|
24
26
|
},
|
|
25
27
|
},
|
|
26
28
|
} as any)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
+
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
|
|
3
|
+
import { appRouter } from '@/server/api/root'
|
|
4
|
+
import { createTRPCContext } from '@/server/api/trpc'
|
|
5
|
+
|
|
6
|
+
export const Route = createFileRoute('/api/trpc', {
|
|
7
|
+
server: {
|
|
8
|
+
handlers: {
|
|
9
|
+
POST: async ({ request }: { request: Request }) => {
|
|
10
|
+
return await fetchRequestHandler({
|
|
11
|
+
endpoint: '/api/trpc',
|
|
12
|
+
req: request,
|
|
13
|
+
router: appRouter,
|
|
14
|
+
createContext: (opts) => createTRPCContext(opts),
|
|
15
|
+
})
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
} as any)
|
|
@@ -4,7 +4,30 @@ import { db } from '@/server/db'
|
|
|
4
4
|
import { users } from '@/server/db/schema'
|
|
5
5
|
import { eq } from 'drizzle-orm'
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* User Router
|
|
9
|
+
*
|
|
10
|
+
* Handles user profile operations including retrieval and updates.
|
|
11
|
+
* All procedures are protected and require authenticated sessions.
|
|
12
|
+
*
|
|
13
|
+
* @module routers/user
|
|
14
|
+
*/
|
|
15
|
+
|
|
7
16
|
export const userRouter = router({
|
|
17
|
+
/**
|
|
18
|
+
* Get Profile
|
|
19
|
+
*
|
|
20
|
+
* Retrieves the authenticated user's profile from the database.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const profile = await trpc.user.getProfile.query()
|
|
25
|
+
* // Returns: { id: string, name: string, email: string, ... }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* @throws Will throw if user session is invalid or database query fails
|
|
29
|
+
* @returns The user's profile object or null if not found
|
|
30
|
+
*/
|
|
8
31
|
getProfile: protectedProcedure.query(async ({ ctx }) => {
|
|
9
32
|
const result = await (db as any)
|
|
10
33
|
.select()
|
|
@@ -14,6 +37,27 @@ export const userRouter = router({
|
|
|
14
37
|
return result[0] ?? null
|
|
15
38
|
}),
|
|
16
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Update Profile
|
|
42
|
+
*
|
|
43
|
+
* Updates the authenticated user's profile name.
|
|
44
|
+
*
|
|
45
|
+
* @param input.name - The new display name (1-100 characters)
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* const updatedProfile = await trpc.user.updateProfile.mutate({
|
|
50
|
+
* name: 'John Doe'
|
|
51
|
+
* })
|
|
52
|
+
* // Returns: { id: string, name: 'John Doe', email: string, ... }
|
|
53
|
+
* ```
|
|
54
|
+
*
|
|
55
|
+
* @throws Will throw if:
|
|
56
|
+
* - Name validation fails (not 1-100 characters)
|
|
57
|
+
* - User session is invalid
|
|
58
|
+
* - Database update fails
|
|
59
|
+
* @returns The updated user profile object
|
|
60
|
+
*/
|
|
17
61
|
updateProfile: protectedProcedure
|
|
18
62
|
.input(
|
|
19
63
|
z.object({
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"resolveJsonModule": true,
|
|
9
9
|
"allowJs": true,
|
|
10
10
|
"checkJs": false,
|
|
11
|
-
"outDir": "
|
|
11
|
+
"outDir": "./dist",
|
|
12
12
|
"rootDir": "./src",
|
|
13
13
|
"removeComments": true,
|
|
14
14
|
"noEmit": true,
|
|
@@ -27,15 +27,10 @@
|
|
|
27
27
|
"noImplicitOverride": true,
|
|
28
28
|
"esModuleInterop": true,
|
|
29
29
|
"skipLibCheck": true,
|
|
30
|
-
"plugins": [
|
|
31
|
-
{
|
|
32
|
-
"name": "@tanstack/start/plugin"
|
|
33
|
-
}
|
|
34
|
-
],
|
|
35
30
|
"paths": {
|
|
36
31
|
"@/*": ["./src/*"]
|
|
37
32
|
}
|
|
38
33
|
},
|
|
39
34
|
"include": ["src/**/*"],
|
|
40
|
-
"exclude": ["node_modules", "dist", ".vinxi"]
|
|
35
|
+
"exclude": ["node_modules", "dist", ".vinxi", ".vite"]
|
|
41
36
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { defineConfig } from 'vite'
|
|
2
|
+
import tsconfigPaths from 'vite-tsconfig-paths'
|
|
3
|
+
import { visualizer } from 'rollup-plugin-visualizer'
|
|
4
|
+
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
|
5
|
+
|
|
6
|
+
export default defineConfig({
|
|
7
|
+
plugins: [
|
|
8
|
+
tsconfigPaths(),
|
|
9
|
+
tanstackStart(),
|
|
10
|
+
visualizer({
|
|
11
|
+
filename: 'stats.html',
|
|
12
|
+
open: process.env.ANALYZE === 'true',
|
|
13
|
+
gzipSize: true,
|
|
14
|
+
brotliSize: true,
|
|
15
|
+
template: 'treemap',
|
|
16
|
+
}),
|
|
17
|
+
],
|
|
18
|
+
resolve: {
|
|
19
|
+
alias: {
|
|
20
|
+
'@': '/src',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
})
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { defineConfig } from '@tanstack/start/config'
|
|
2
|
-
import { visualizer } from 'rollup-plugin-visualizer'
|
|
3
|
-
import path from 'path'
|
|
4
|
-
import { fileURLToPath } from 'url'
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
7
|
-
|
|
8
|
-
export default defineConfig({
|
|
9
|
-
tsr: {
|
|
10
|
-
appDirectory: 'src/app',
|
|
11
|
-
routesDirectory: 'src/app',
|
|
12
|
-
},
|
|
13
|
-
vite: {
|
|
14
|
-
plugins: [
|
|
15
|
-
visualizer({
|
|
16
|
-
filename: 'stats.html',
|
|
17
|
-
open: process.env.ANALYZE === 'true',
|
|
18
|
-
gzipSize: true,
|
|
19
|
-
brotliSize: true,
|
|
20
|
-
template: 'treemap',
|
|
21
|
-
}),
|
|
22
|
-
],
|
|
23
|
-
resolve: {
|
|
24
|
-
alias: {
|
|
25
|
-
'@': path.resolve(__dirname, './src'),
|
|
26
|
-
},
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
})
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'
|
|
2
|
-
import { appRouter } from '@/server/api/root'
|
|
3
|
-
import { createTRPCContext } from '@/server/api/trpc'
|
|
4
|
-
|
|
5
|
-
export default async function handler(req: Request) {
|
|
6
|
-
return fetchRequestHandler({
|
|
7
|
-
endpoint: '/api/trpc',
|
|
8
|
-
req,
|
|
9
|
-
router: appRouter,
|
|
10
|
-
createContext: (opts) => createTRPCContext(opts),
|
|
11
|
-
})
|
|
12
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/** @type {import('tailwindcss').Config} */
|
|
2
|
-
module.exports = {
|
|
3
|
-
darkMode: ['class'],
|
|
4
|
-
content: ['./src/**/*.{ts,tsx}'],
|
|
5
|
-
theme: {
|
|
6
|
-
extend: {
|
|
7
|
-
colors: {
|
|
8
|
-
border: 'hsl(var(--border))',
|
|
9
|
-
input: 'hsl(var(--input))',
|
|
10
|
-
ring: 'hsl(var(--ring))',
|
|
11
|
-
background: 'hsl(var(--background))',
|
|
12
|
-
foreground: 'hsl(var(--foreground))',
|
|
13
|
-
primary: {
|
|
14
|
-
DEFAULT: 'hsl(var(--primary))',
|
|
15
|
-
foreground: 'hsl(var(--primary-foreground))',
|
|
16
|
-
},
|
|
17
|
-
secondary: {
|
|
18
|
-
DEFAULT: 'hsl(var(--secondary))',
|
|
19
|
-
foreground: 'hsl(var(--secondary-foreground))',
|
|
20
|
-
},
|
|
21
|
-
destructive: {
|
|
22
|
-
DEFAULT: 'hsl(var(--destructive))',
|
|
23
|
-
foreground: 'hsl(var(--destructive-foreground))',
|
|
24
|
-
},
|
|
25
|
-
muted: {
|
|
26
|
-
DEFAULT: 'hsl(var(--muted))',
|
|
27
|
-
foreground: 'hsl(var(--muted-foreground))',
|
|
28
|
-
},
|
|
29
|
-
accent: {
|
|
30
|
-
DEFAULT: 'hsl(var(--accent))',
|
|
31
|
-
foreground: 'hsl(var(--accent-foreground))',
|
|
32
|
-
},
|
|
33
|
-
card: {
|
|
34
|
-
DEFAULT: 'hsl(var(--card))',
|
|
35
|
-
foreground: 'hsl(var(--card-foreground))',
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
borderRadius: {
|
|
39
|
-
lg: 'var(--radius)',
|
|
40
|
-
md: 'calc(var(--radius) - 2px)',
|
|
41
|
-
sm: 'calc(var(--radius) - 4px)',
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
plugins: [require('tailwindcss-animate')],
|
|
46
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|