@amirulabu/create-recurring-rabbit-app 0.2.16 → 0.2.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 CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { program } from 'commander';
3
- import path from 'path';
3
+ import path6 from 'path';
4
4
  import { fileURLToPath } from 'url';
5
- import { existsSync, promises, mkdirSync, readFileSync } from 'fs';
5
+ import { existsSync, readFileSync, promises, mkdirSync } from 'fs';
6
6
  import ora from 'ora';
7
7
  import fs from 'fs/promises';
8
8
  import { execSync, spawn } from 'child_process';
@@ -16,10 +16,10 @@ async function copyTemplateFiles(templateDir, targetDir) {
16
16
  const entries = await fs.readdir(src);
17
17
  await fs.mkdir(dest, { recursive: true });
18
18
  for (const entry of entries) {
19
- await copyRecursive(path.join(src, entry), path.join(dest, entry));
19
+ await copyRecursive(path6.join(src, entry), path6.join(dest, entry));
20
20
  }
21
21
  } else {
22
- await fs.mkdir(path.dirname(dest), { recursive: true });
22
+ await fs.mkdir(path6.dirname(dest), { recursive: true });
23
23
  await fs.copyFile(src, dest);
24
24
  }
25
25
  };
@@ -29,7 +29,7 @@ async function copyDirectory(src, dest) {
29
29
  await copyTemplateFiles(src, dest);
30
30
  }
31
31
  async function generatePackageJson(targetDir, config) {
32
- const packageJsonPath = path.join(targetDir, "package.json");
32
+ const packageJsonPath = path6.join(targetDir, "package.json");
33
33
  const packageJson = {
34
34
  name: config.name,
35
35
  version: config.version ?? "0.1.0",
@@ -47,8 +47,8 @@ async function generatePackageJson(targetDir, config) {
47
47
  "db:studio": "drizzle-kit studio",
48
48
  "db:seed": "tsx src/server/db/seed.ts",
49
49
  typecheck: "tsc --noEmit",
50
- lint: "eslint . --ext .ts,.tsx",
51
- "lint:fix": "eslint . --ext .ts,.tsx --fix",
50
+ lint: "eslint .",
51
+ "lint:fix": "eslint . --fix",
52
52
  format: 'prettier --write "src/**/*.{ts,tsx,json,css}"',
53
53
  clean: "rm -rf .vinxi dist data/*.db data/*.db-shm data/*.db-wal",
54
54
  "build:analyze": "ANALYZE=true vinxi build",
@@ -82,19 +82,21 @@ async function generatePackageJson(targetDir, config) {
82
82
  ...config.dependencies
83
83
  },
84
84
  devDependencies: {
85
+ "@eslint/js": "^9.39.2",
85
86
  "@lhci/cli": "^0.12.0",
86
87
  "@types/better-sqlite3": "^7.6.9",
87
88
  "@types/node": "^20.11.0",
88
89
  "@types/react": "^18.2.0",
89
90
  "@types/react-dom": "^18.2.0",
90
- "@typescript-eslint/eslint-plugin": "^6.0.0",
91
- "@typescript-eslint/parser": "^6.0.0",
91
+ "@typescript-eslint/eslint-plugin": "^8.0.0",
92
+ "@typescript-eslint/parser": "^8.0.0",
92
93
  "@vitejs/plugin-react": "^4.3.0",
93
94
  "@vitest/coverage-v8": "^1.6.0",
94
- eslint: "^8.56.0",
95
- "eslint-plugin-react": "^7.32.0",
96
- "eslint-plugin-react-hooks": "^4.6.0",
95
+ eslint: "^9.39.2",
96
+ "eslint-plugin-react": "^7.37.0",
97
+ "eslint-plugin-react-hooks": "^5.0.0",
97
98
  "drizzle-kit": "^0.31.0",
99
+ globals: "^15.0.0",
98
100
  postcss: "^8.4.0",
99
101
  prettier: "^3.2.5",
100
102
  "rollup-plugin-visualizer": "^5.12.0",
@@ -232,7 +234,7 @@ function generateSecret(length = 32) {
232
234
  return crypto.randomBytes(length).toString("base64");
233
235
  }
234
236
  async function generateEnvFile(targetDir, options = {}) {
235
- const envPath = path.join(targetDir, ".env.local");
237
+ const envPath = path6.join(targetDir, ".env.local");
236
238
  const secret = generateSecret(32);
237
239
  const content = [
238
240
  `# Database`,
@@ -255,7 +257,7 @@ async function generateEnvFile(targetDir, options = {}) {
255
257
  await fs.writeFile(envPath, content);
256
258
  }
257
259
  function loadEnvFile(projectPath) {
258
- const envPath = path.join(projectPath, ".env.local");
260
+ const envPath = path6.join(projectPath, ".env.local");
259
261
  const env = {};
260
262
  try {
261
263
  const content = readFileSync(envPath, "utf-8");
@@ -278,7 +280,7 @@ function loadEnvFile(projectPath) {
278
280
  async function initializeDatabase(projectPath) {
279
281
  const spinner = ora("Initializing database...").start();
280
282
  try {
281
- const dataDir = path.join(projectPath, "data");
283
+ const dataDir = path6.join(projectPath, "data");
282
284
  if (!existsSync(dataDir)) {
283
285
  mkdirSync(dataDir, { recursive: true });
284
286
  spinner.text = "Data directory created";
@@ -324,7 +326,7 @@ ${errorOutput}`));
324
326
  }
325
327
  async function runSeedScript(projectPath, env) {
326
328
  return new Promise((resolve, reject) => {
327
- const seedScriptPath = path.join(projectPath, "src/server/db/seed.ts");
329
+ const seedScriptPath = path6.join(projectPath, "src/server/db/seed.ts");
328
330
  const command = "npx";
329
331
  const args = ["tsx", seedScriptPath];
330
332
  const child = spawn(command, args, {
@@ -502,7 +504,7 @@ function validateProjectName(name) {
502
504
  if (RESERVED_NAMES.has(name)) {
503
505
  throw new CLIError(`Invalid project name: "${name}" is a reserved name`);
504
506
  }
505
- const parts = name.split(path.sep);
507
+ const parts = name.split(path6.sep);
506
508
  const baseName = parts[parts.length - 1];
507
509
  if (baseName !== name) {
508
510
  throw new CLIError("Invalid project name: cannot contain path separators");
@@ -519,16 +521,32 @@ async function cleanupProject(projectPath) {
519
521
  }
520
522
 
521
523
  // src/commands/create.ts
522
- var __filename2 = fileURLToPath(import.meta.url);
523
- var __dirname2 = path.dirname(__filename2);
524
- var TEMPLATE_DIR = path.join(__dirname2, "../../templates/default");
524
+ var getPackageRoot = () => {
525
+ const currentFile = fileURLToPath(import.meta.url);
526
+ let currentDir = path6.dirname(currentFile);
527
+ while (currentDir !== path6.parse(currentDir).root) {
528
+ const packageJsonPath = path6.join(currentDir, "package.json");
529
+ if (existsSync(packageJsonPath)) {
530
+ try {
531
+ const pkg = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
532
+ if (pkg.name === "@amirulabu/create-recurring-rabbit-app") {
533
+ return currentDir;
534
+ }
535
+ } catch {
536
+ }
537
+ }
538
+ currentDir = path6.dirname(currentDir);
539
+ }
540
+ throw new Error("Could not find package root");
541
+ };
542
+ var TEMPLATE_DIR = path6.join(getPackageRoot(), "templates/default");
525
543
  async function scaffoldProject(projectName, targetPath) {
526
544
  const spinner = ora("Creating project structure...").start();
527
545
  let projectPath = "";
528
546
  let projectCreated = false;
529
547
  try {
530
548
  validateProjectName(projectName);
531
- projectPath = path.join(targetPath, projectName);
549
+ projectPath = path6.join(targetPath, projectName);
532
550
  if (existsSync(projectPath)) {
533
551
  spinner.fail(`Directory ${projectName} already exists. Please choose a different name.`);
534
552
  throw new Error(`Directory ${projectName} already exists`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amirulabu/create-recurring-rabbit-app",
3
- "version": "0.2.16",
3
+ "version": "0.2.19",
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": {
@@ -0,0 +1,62 @@
1
+ import js from '@eslint/js'
2
+ import tseslint from '@typescript-eslint/eslint-plugin'
3
+ import tsparser from '@typescript-eslint/parser'
4
+ import react from 'eslint-plugin-react'
5
+ import reactHooks from 'eslint-plugin-react-hooks'
6
+ import globals from 'globals'
7
+
8
+ export default [
9
+ {
10
+ ignores: [
11
+ 'node_modules/',
12
+ 'dist/',
13
+ '.vinxi/',
14
+ 'build/',
15
+ 'coverage/',
16
+ 'data/',
17
+ ],
18
+ },
19
+ js.configs.recommended,
20
+ {
21
+ files: ['**/*.ts', '**/*.tsx'],
22
+ languageOptions: {
23
+ parser: tsparser,
24
+ parserOptions: {
25
+ ecmaVersion: 'latest',
26
+ sourceType: 'module',
27
+ ecmaFeatures: {
28
+ jsx: true,
29
+ },
30
+ },
31
+ globals: {
32
+ ...globals.browser,
33
+ ...globals.node,
34
+ },
35
+ },
36
+ plugins: {
37
+ '@typescript-eslint': tseslint,
38
+ react,
39
+ 'react-hooks': reactHooks,
40
+ },
41
+ settings: {
42
+ react: {
43
+ version: 'detect',
44
+ },
45
+ },
46
+ rules: {
47
+ ...tseslint.configs.recommended.rules,
48
+ ...react.configs.recommended.rules,
49
+ ...reactHooks.configs.recommended.rules,
50
+ '@typescript-eslint/no-unused-vars': [
51
+ 'error',
52
+ {
53
+ argsIgnorePattern: '^_',
54
+ },
55
+ ],
56
+ '@typescript-eslint/no-explicit-any': 'error',
57
+ 'react/react-in-jsx-scope': 'error',
58
+ 'react-hooks/rules-of-hooks': 'error',
59
+ 'react-hooks/exhaustive-deps': 'warn',
60
+ },
61
+ },
62
+ ]
@@ -4,7 +4,6 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
4
4
  import { useState } from 'react'
5
5
  import { httpBatchLink } from '@trpc/client'
6
6
  import { TRPCProvider, trpc } from '@/lib/api'
7
- import type { AppRouter } from '@/server/api/root'
8
7
  import { env } from '@/lib/env'
9
8
  import '@/app/globals.css'
10
9
 
@@ -12,6 +11,8 @@ export const Route = createRootRoute({
12
11
  component: RootComponent,
13
12
  })
14
13
 
14
+ export const rootRoute = Route
15
+
15
16
  function RootComponent() {
16
17
  const [queryClient] = useState(() => new QueryClient())
17
18
  const [trpcClient] = useState(() =>
@@ -1,7 +1,7 @@
1
1
  import { createFileRoute } from '@tanstack/react-router'
2
2
  import { auth } from '@/server/auth/config'
3
3
 
4
- export const Route = createFileRoute('/api/auth/_')({
4
+ export const Route = createFileRoute('/api/auth/$')({
5
5
  server: {
6
6
  handlers: {
7
7
  GET: async ({ request }: { request: Request }) => {
@@ -12,4 +12,4 @@ export const Route = createFileRoute('/api/auth/_')({
12
12
  },
13
13
  },
14
14
  },
15
- })
15
+ } as any)
@@ -23,4 +23,4 @@ export const Route = createFileRoute('/api/auth/get-session')({
23
23
  }
24
24
  },
25
25
  },
26
- })
26
+ } as any)
@@ -8,7 +8,7 @@ export const Route = createFileRoute('/api/health')({
8
8
  try {
9
9
  const startTime = Date.now()
10
10
 
11
- await db.select({ count: 1 }).limit(1)
11
+ await (db as any).select().limit(1)
12
12
  const dbLatency = Date.now() - startTime
13
13
 
14
14
  return Response.json({
@@ -41,4 +41,4 @@ export const Route = createFileRoute('/api/health')({
41
41
  },
42
42
  },
43
43
  },
44
- })
44
+ } as any)
@@ -1,6 +1,5 @@
1
1
  import { createFileRoute, useRouter } from '@tanstack/react-router'
2
2
  import { useState } from 'react'
3
- import { authClient } from '@/lib/auth'
4
3
  import { Button } from '@/components/ui/button'
5
4
  import { Input } from '@/components/ui/input'
6
5
  import {
@@ -11,7 +10,7 @@ import {
11
10
  CardTitle,
12
11
  } from '@/components/ui/card'
13
12
 
14
- export const Route = createFileRoute()({
13
+ export const Route = createFileRoute('/auth/forgot-password')({
15
14
  component: ForgotPassword,
16
15
  })
17
16
 
@@ -29,10 +28,22 @@ function ForgotPassword() {
29
28
  setError('')
30
29
 
31
30
  try {
32
- await authClient.forgetPassword({
33
- email,
31
+ // Better-auth v1.4.17: Send password reset request to server
32
+ const response = await fetch('/api/auth/forget-password', {
33
+ method: 'POST',
34
+ headers: {
35
+ 'Content-Type': 'application/json',
36
+ },
37
+ body: JSON.stringify({
38
+ email,
39
+ redirectTo: '/auth/reset-password',
40
+ }),
34
41
  })
35
42
 
43
+ if (!response.ok) {
44
+ throw new Error('Failed to send reset email')
45
+ }
46
+
36
47
  setMessage(
37
48
  'If an account with this email exists, you will receive a password reset link.'
38
49
  )
@@ -1,7 +1,7 @@
1
1
  import { createFileRoute, Link } from '@tanstack/react-router'
2
2
  import { LoginForm } from '@/components/features/auth/login-form'
3
3
 
4
- export const Route = createFileRoute()({
4
+ export const Route = createFileRoute('/auth/login')({
5
5
  component: Login,
6
6
  })
7
7
 
@@ -1,7 +1,7 @@
1
1
  import { createFileRoute, Link } from '@tanstack/react-router'
2
2
  import { RegisterForm } from '@/components/features/auth/register-form'
3
3
 
4
- export const Route = createFileRoute()({
4
+ export const Route = createFileRoute('/auth/register')({
5
5
  component: Register,
6
6
  })
7
7
 
@@ -11,7 +11,7 @@ import {
11
11
  CardTitle,
12
12
  } from '@/components/ui/card'
13
13
 
14
- export const Route = createFileRoute()({
14
+ export const Route = createFileRoute('/auth/reset-password')({
15
15
  component: ResetPassword,
16
16
  })
17
17
 
@@ -9,7 +9,7 @@ import {
9
9
  CardTitle,
10
10
  } from '@/components/ui/card'
11
11
 
12
- export const Route = createFileRoute()({
12
+ export const Route = createFileRoute('/auth/verify-email')({
13
13
  component: VerifyEmail,
14
14
  })
15
15
 
@@ -26,6 +26,7 @@ function VerifyEmail() {
26
26
  }, 3000)
27
27
  return () => clearTimeout(timeout)
28
28
  }
29
+ return undefined
29
30
  }, [success, router])
30
31
 
31
32
  return (
@@ -1,7 +1,6 @@
1
1
  import { createFileRoute, useRouter, redirect } from '@tanstack/react-router'
2
2
  import React, { useState } from 'react'
3
- import { useQuery, useMutation } from '@tanstack/react-query'
4
- import { useTRPC } from '@/lib/api'
3
+ import { trpc } from '@/lib/api'
5
4
  import { signOut } from '@/lib/auth'
6
5
  import { Button } from '@/components/ui/button'
7
6
  import { Input } from '@/components/ui/input'
@@ -47,9 +46,8 @@ export const Route = createFileRoute('/dashboard/settings')({
47
46
 
48
47
  function Settings() {
49
48
  const router = useRouter()
50
- const trpc = useTRPC()
51
- const { data: user } = useQuery(trpc.user.getProfile.queryOptions())
52
- const updateProfile = useMutation(trpc.user.updateProfile.mutationOptions())
49
+ const { data: user } = trpc.user.getProfile.useQuery()
50
+ const updateProfileMutation = trpc.user.updateProfile.useMutation()
53
51
 
54
52
  const [name, setName] = useState('')
55
53
  const [loading, setLoading] = useState(false)
@@ -69,7 +67,7 @@ function Settings() {
69
67
  setError('')
70
68
 
71
69
  try {
72
- const result = await updateProfile.mutateAsync({
70
+ const result = await updateProfileMutation.mutateAsync({
73
71
  name,
74
72
  })
75
73
 
@@ -1,6 +1,6 @@
1
1
  import { createFileRoute, Link } from '@tanstack/react-router'
2
2
 
3
- export const Route = createFileRoute()({
3
+ export const Route = createFileRoute('/')({
4
4
  component: Index,
5
5
  })
6
6
 
@@ -0,0 +1,43 @@
1
+ /* prettier-ignore-start */
2
+
3
+ /* eslint-disable */
4
+
5
+ // @ts-nocheck
6
+
7
+ // noinspection JSUnusedGlobalSymbols
8
+
9
+ /**
10
+ * This file is auto-generated by TanStack Router.
11
+ * Do not edit this file manually.
12
+ *
13
+ * This is a stub file for type-checking purposes.
14
+ * The actual route tree is generated at build/dev time by TanStack Router.
15
+ *
16
+ * When the dev server runs, this file will be replaced with the full route tree.
17
+ */
18
+
19
+ import { rootRoute } from './__root'
20
+
21
+ // Stub route tree - this allows TypeScript to compile without errors
22
+ // The real route tree with proper types will be generated when you run the dev server
23
+ export const routeTree = rootRoute.addChildren([])
24
+
25
+ // Type augmentation to allow any route ID during development
26
+ declare module '@tanstack/react-router' {
27
+ interface FileRoutesByPath {
28
+ '/': any
29
+ '/_client': any
30
+ '/_ssr': any
31
+ '/api/health': any
32
+ '/api/auth/$': any
33
+ '/api/auth/_': any
34
+ '/api/auth/get-session': any
35
+ '/auth/login': any
36
+ '/auth/register': any
37
+ '/auth/forgot-password': any
38
+ '/auth/reset-password': any
39
+ '/auth/verify-email': any
40
+ '/dashboard/': any
41
+ '/dashboard/settings': any
42
+ }
43
+ }
@@ -186,7 +186,7 @@ export const { signOut } = authClient
186
186
  * }
187
187
  * ```
188
188
  */
189
- export const { useSession, forgetPassword, resetPassword } = authClient
189
+ export const { useSession, resetPassword } = authClient
190
190
 
191
191
  /**
192
192
  * Custom hook for common authentication patterns
@@ -214,12 +214,12 @@ export const { useSession, forgetPassword, resetPassword } = authClient
214
214
  * ```
215
215
  */
216
216
  export function useAuth() {
217
- const { data: session, isLoading, isPending } = useSession()
217
+ const { data: session, isPending } = useSession()
218
218
 
219
219
  return {
220
220
  user: session?.user ?? null,
221
221
  isAuthenticated: !!session?.user,
222
- isLoading: isLoading || isPending,
222
+ isLoading: isPending,
223
223
  signOut,
224
224
  }
225
225
  }
@@ -5,11 +5,11 @@ import { eq, and, gte, sql } from 'drizzle-orm'
5
5
 
6
6
  export const dashboardRouter = router({
7
7
  getStats: protectedProcedure.query(async ({ ctx }) => {
8
- const [totalUsersResult] = (await db
8
+ const [totalUsersResult] = (await (db as any)
9
9
  .select({ count: sql<number>`count(*)` })
10
10
  .from(users)) as any
11
11
 
12
- const [activeUsersResult] = (await db
12
+ const [activeUsersResult] = (await (db as any)
13
13
  .select({ count: sql<number>`count(*)` })
14
14
  .from(users)
15
15
  .where(
@@ -26,7 +26,7 @@ export const dashboardRouter = router({
26
26
  }),
27
27
 
28
28
  getUserProfile: protectedProcedure.query(async ({ ctx }) => {
29
- const [user] = (await db
29
+ const [user] = (await (db as any)
30
30
  .select()
31
31
  .from(users)
32
32
  .where(eq(users.id, ctx.user.id))
@@ -6,11 +6,11 @@ import { eq } from 'drizzle-orm'
6
6
 
7
7
  export const userRouter = router({
8
8
  getProfile: protectedProcedure.query(async ({ ctx }) => {
9
- const result = await (db
9
+ const result = await (db as any)
10
10
  .select()
11
11
  .from(users)
12
12
  .where(eq(users.id, ctx.user.id))
13
- .limit(1) as any)
13
+ .limit(1)
14
14
  return result[0] ?? null
15
15
  }),
16
16
 
@@ -21,11 +21,11 @@ export const userRouter = router({
21
21
  })
22
22
  )
23
23
  .mutation(async ({ ctx, input }) => {
24
- const result = await (db
24
+ const result = await (db as any)
25
25
  .update(users)
26
26
  .set({ name: input.name, updatedAt: new Date() })
27
27
  .where(eq(users.id, ctx.user.id))
28
- .returning() as any)
28
+ .returning()
29
29
  return result[0]
30
30
  }),
31
31
  })
@@ -108,10 +108,10 @@ export async function seedDatabase() {
108
108
  ]
109
109
 
110
110
  try {
111
- const result = await (db
111
+ const result = await (db as any)
112
112
  .insert(users)
113
113
  .values(sampleUsers)
114
- .returning() as any)
114
+ .returning()
115
115
  console.log(`✓ Inserted ${result.length} users`)
116
116
  console.log('✓ Database seeded successfully')
117
117
  } catch (error: any) {
@@ -1,35 +0,0 @@
1
- {
2
- "$schema": "https://json.schemastore.org/eslintrc",
3
- "extends": [
4
- "eslint:recommended",
5
- "plugin:@typescript-eslint/recommended",
6
- "plugin:react/recommended",
7
- "plugin:react-hooks/recommended"
8
- ],
9
- "plugins": ["@typescript-eslint", "react", "react-hooks"],
10
- "parser": "@typescript-eslint/parser",
11
- "parserOptions": {
12
- "ecmaVersion": "latest",
13
- "sourceType": "module",
14
- "ecmaFeatures": {
15
- "jsx": true
16
- }
17
- },
18
- "settings": {
19
- "react": {
20
- "version": "detect"
21
- }
22
- },
23
- "rules": {
24
- "@typescript-eslint/no-unused-vars": [
25
- "error",
26
- {
27
- "argsIgnorePattern": "^_"
28
- }
29
- ],
30
- "@typescript-eslint/no-explicit-any": "error",
31
- "react/react-in-jsx-scope": "error",
32
- "react-hooks/rules-of-hooks": "error",
33
- "react-hooks/exhaustive-deps": "warn"
34
- }
35
- }