create-kuckit-app 2.1.0 → 2.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-kuckit-app",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "Create a new Kuckit application",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -40,12 +40,14 @@
40
40
  },
41
41
  "scripts": {
42
42
  "build": "tsdown",
43
+ "test:e2e": "playwright test --config e2e/playwright.config.ts",
43
44
  "prepublishOnly": "npm run build && node ../../scripts/resolve-workspace-protocols.cjs && node ../../scripts/check-no-workspace-protocol.cjs"
44
45
  },
45
46
  "dependencies": {
46
47
  "commander": "^13.1.0"
47
48
  },
48
49
  "devDependencies": {
50
+ "@playwright/test": "^1.52.0",
49
51
  "@types/node": "^22.13.13",
50
52
  "tsdown": "catalog:"
51
53
  }
@@ -9,9 +9,9 @@
9
9
  },
10
10
  "dependencies": {
11
11
  "dotenv": "^17.0.0",
12
- "@kuckit/app-server": "^3.0.2",
13
- "@kuckit/sdk": "^3.0.2",
14
- "@kuckit/db": "^3.0.2"
12
+ "@kuckit/app-server": "^5.0.0",
13
+ "@kuckit/sdk": "^5.0.0",
14
+ "@kuckit/db": "^5.0.0"
15
15
  },
16
16
  "devDependencies": {
17
17
  "typescript": "^5.8.2",
@@ -9,8 +9,8 @@
9
9
  "check-types": "tsc --noEmit"
10
10
  },
11
11
  "dependencies": {
12
- "@kuckit/app-web": "^3.0.2",
13
- "@kuckit/sdk-react": "^3.0.2",
12
+ "@kuckit/app-web": "^5.0.0",
13
+ "@kuckit/sdk-react": "^5.0.0",
14
14
  "@radix-ui/react-avatar": "^1.1.10",
15
15
  "@radix-ui/react-collapsible": "^1.1.11",
16
16
  "@radix-ui/react-dialog": "^1.1.14",
@@ -51,7 +51,7 @@ export function NavUser() {
51
51
  <span className="truncate font-semibold">{user?.name || 'User'}</span>
52
52
  <span className="truncate text-xs text-muted-foreground">{user?.email || ''}</span>
53
53
  </div>
54
- <ChevronsUpDown className="ml-auto size-4" />
54
+ <ChevronsUpDown className="ml-auto size-4" aria-hidden="true" />
55
55
  </SidebarMenuButton>
56
56
  </DropdownMenuTrigger>
57
57
  <DropdownMenuContent
@@ -61,12 +61,12 @@ export function NavUser() {
61
61
  sideOffset={4}
62
62
  >
63
63
  <DropdownMenuItem onClick={() => navigate({ to: '/dashboard/settings' })}>
64
- <User className="mr-2 h-4 w-4" />
64
+ <User className="mr-2 h-4 w-4" aria-hidden="true" />
65
65
  Settings
66
66
  </DropdownMenuItem>
67
67
  <DropdownMenuSeparator />
68
68
  <DropdownMenuItem onClick={handleLogout}>
69
- <LogOut className="mr-2 h-4 w-4" />
69
+ <LogOut className="mr-2 h-4 w-4" aria-hidden="true" />
70
70
  Log out
71
71
  </DropdownMenuItem>
72
72
  </DropdownMenuContent>
@@ -254,6 +254,7 @@ function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps<t
254
254
  data-slot="sidebar-trigger"
255
255
  variant="ghost"
256
256
  size="icon"
257
+ aria-label="Toggle sidebar"
257
258
  className={cn('size-7', className)}
258
259
  onClick={(event) => {
259
260
  onClick?.(event)
@@ -261,7 +262,7 @@ function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps<t
261
262
  }}
262
263
  {...props}
263
264
  >
264
- <PanelLeftIcon />
265
+ <PanelLeftIcon aria-hidden="true" />
265
266
  <span className="sr-only">Toggle Sidebar</span>
266
267
  </Button>
267
268
  )
@@ -24,7 +24,13 @@ export const Route = createRootRouteWithContext<RouterAppContext>()({
24
24
 
25
25
  function RootComponent() {
26
26
  return (
27
- <div style={{ minHeight: '100vh' }}>
27
+ <div className="min-h-dvh">
28
+ <a
29
+ href="#main-content"
30
+ className="sr-only focus:not-sr-only focus:fixed focus:left-4 focus:top-4 focus:z-50 focus:rounded-md focus:bg-background focus:px-3 focus:py-2 focus:text-sm focus:text-foreground focus:shadow"
31
+ >
32
+ Skip to content
33
+ </a>
28
34
  <Outlet />
29
35
  </div>
30
36
  )
@@ -1,5 +1,6 @@
1
1
  import { createFileRoute, Link } from '@tanstack/react-router'
2
2
  import { useAuth } from '@kuckit/app-web'
3
+ import { Button } from '@/components/ui/button'
3
4
 
4
5
  export const Route = createFileRoute('/')({
5
6
  component: HomePage,
@@ -11,36 +12,30 @@ function HomePage() {
11
12
 
12
13
  if (isPending) {
13
14
  return (
14
- <div style={{ padding: '2rem', fontFamily: 'system-ui' }}>
15
- <p>Loading...</p>
16
- </div>
15
+ <main id="main-content" className="mx-auto max-w-3xl px-6 py-12">
16
+ <p className="text-sm text-muted-foreground">Loading…</p>
17
+ </main>
17
18
  )
18
19
  }
19
20
 
20
21
  return (
21
- <div style={{ padding: '2rem', fontFamily: 'system-ui' }}>
22
- <h1>Welcome to __APP_NAME__</h1>
22
+ <main id="main-content" className="mx-auto max-w-3xl px-6 py-12">
23
+ <h1 className="text-3xl font-semibold tracking-tight text-pretty">Welcome to __APP_NAME__</h1>
23
24
  {session?.user ? (
24
- <div>
25
- <p>Hello, {session.user.name || session.user.email}!</p>
26
- <button
27
- onClick={() => authClient.signOut()}
28
- style={{
29
- padding: '0.5rem 1rem',
30
- cursor: 'pointer',
31
- }}
32
- >
33
- Sign Out
34
- </button>
25
+ <div className="mt-6 space-y-4">
26
+ <p className="text-sm text-muted-foreground">
27
+ Hello, {session.user.name || session.user.email}!
28
+ </p>
29
+ <Button onClick={() => authClient.signOut()}>Sign Out</Button>
35
30
  </div>
36
31
  ) : (
37
- <div>
38
- <p>Your Kuckit application is ready!</p>
39
- <Link to="/login" style={{ color: 'blue', textDecoration: 'underline' }}>
40
- Sign In
41
- </Link>
32
+ <div className="mt-6 space-y-4">
33
+ <p className="text-sm text-muted-foreground">Your Kuckit application is ready.</p>
34
+ <Button asChild>
35
+ <Link to="/login">Sign In</Link>
36
+ </Button>
42
37
  </div>
43
38
  )}
44
- </div>
39
+ </main>
45
40
  )
46
41
  }
@@ -1,6 +1,8 @@
1
1
  import { createFileRoute, useNavigate } from '@tanstack/react-router'
2
- import { useState } from 'react'
2
+ import { useId, useState } from 'react'
3
3
  import { useAuth } from '@kuckit/app-web'
4
+ import { Button } from '@/components/ui/button'
5
+ import { Input } from '@/components/ui/input'
4
6
 
5
7
  export const Route = createFileRoute('/login')({
6
8
  component: LoginPage,
@@ -15,6 +17,10 @@ function LoginPage() {
15
17
  const [name, setName] = useState('')
16
18
  const [error, setError] = useState('')
17
19
  const [loading, setLoading] = useState(false)
20
+ const emailId = useId()
21
+ const passwordId = useId()
22
+ const nameId = useId()
23
+ const errorId = useId()
18
24
 
19
25
  const handleSubmit = async (e: React.FormEvent) => {
20
26
  e.preventDefault()
@@ -36,73 +42,98 @@ function LoginPage() {
36
42
  }
37
43
  navigate({ to: '/' })
38
44
  } catch (err) {
39
- setError(err instanceof Error ? err.message : 'Authentication failed')
45
+ setError(
46
+ err instanceof Error
47
+ ? err.message
48
+ : 'Authentication failed. Check your details and try again.'
49
+ )
40
50
  } finally {
41
51
  setLoading(false)
42
52
  }
43
53
  }
44
54
 
45
55
  return (
46
- <div style={{ padding: '2rem', fontFamily: 'system-ui', maxWidth: '400px', margin: '0 auto' }}>
47
- <h1>{isSignUp ? 'Create Account' : 'Sign In'}</h1>
56
+ <main
57
+ id="main-content"
58
+ className="mx-auto flex min-h-dvh w-full max-w-md flex-col justify-center px-6 py-12"
59
+ >
60
+ <h1 className="text-3xl font-semibold tracking-tight text-pretty">
61
+ {isSignUp ? 'Create Account' : 'Sign In'}
62
+ </h1>
63
+ <p className="mt-2 text-sm text-muted-foreground">Use your email and password to continue.</p>
48
64
 
49
65
  <form
50
66
  onSubmit={handleSubmit}
51
- style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}
67
+ className="mt-8 space-y-6"
68
+ aria-describedby={error ? errorId : undefined}
52
69
  >
53
70
  {isSignUp && (
54
- <input
55
- type="text"
56
- placeholder="Name"
57
- value={name}
58
- onChange={(e) => setName(e.target.value)}
71
+ <div className="space-y-2">
72
+ <label className="text-sm font-medium" htmlFor={nameId}>
73
+ Full Name
74
+ </label>
75
+ <Input
76
+ id={nameId}
77
+ name="name"
78
+ type="text"
79
+ autoComplete="name"
80
+ placeholder="Sam Rivera…"
81
+ value={name}
82
+ onChange={(e) => setName(e.target.value)}
83
+ required
84
+ />
85
+ </div>
86
+ )}
87
+ <div className="space-y-2">
88
+ <label className="text-sm font-medium" htmlFor={emailId}>
89
+ Email
90
+ </label>
91
+ <Input
92
+ id={emailId}
93
+ name="email"
94
+ type="email"
95
+ autoComplete={isSignUp ? 'email' : 'username'}
96
+ inputMode="email"
97
+ spellCheck={false}
98
+ placeholder="you@company.com…"
99
+ value={email}
100
+ onChange={(e) => setEmail(e.target.value)}
59
101
  required
60
- style={{ padding: '0.5rem', fontSize: '1rem' }}
61
102
  />
62
- )}
63
- <input
64
- type="email"
65
- placeholder="Email"
66
- value={email}
67
- onChange={(e) => setEmail(e.target.value)}
68
- required
69
- style={{ padding: '0.5rem', fontSize: '1rem' }}
70
- />
71
- <input
72
- type="password"
73
- placeholder="Password"
74
- value={password}
75
- onChange={(e) => setPassword(e.target.value)}
76
- required
77
- style={{ padding: '0.5rem', fontSize: '1rem' }}
78
- />
103
+ </div>
104
+ <div className="space-y-2">
105
+ <label className="text-sm font-medium" htmlFor={passwordId}>
106
+ Password
107
+ </label>
108
+ <Input
109
+ id={passwordId}
110
+ name="password"
111
+ type="password"
112
+ autoComplete={isSignUp ? 'new-password' : 'current-password'}
113
+ placeholder="At least 8 characters…"
114
+ value={password}
115
+ onChange={(e) => setPassword(e.target.value)}
116
+ required
117
+ />
118
+ </div>
79
119
 
80
- {error && <p style={{ color: 'red' }}>{error}</p>}
120
+ {error && (
121
+ <p id={errorId} className="text-sm text-destructive" aria-live="polite">
122
+ {error}
123
+ </p>
124
+ )}
81
125
 
82
- <button
83
- type="submit"
84
- disabled={loading}
85
- style={{ padding: '0.5rem 1rem', fontSize: '1rem', cursor: 'pointer' }}
86
- >
87
- {loading ? 'Loading...' : isSignUp ? 'Create Account' : 'Sign In'}
88
- </button>
126
+ <Button type="submit" className="w-full" disabled={loading}>
127
+ {loading ? 'Loading…' : isSignUp ? 'Create Account' : 'Sign In'}
128
+ </Button>
89
129
  </form>
90
130
 
91
- <p style={{ marginTop: '1rem' }}>
92
- {isSignUp ? 'Already have an account? ' : "Don't have an account? "}
93
- <button
94
- onClick={() => setIsSignUp(!isSignUp)}
95
- style={{
96
- background: 'none',
97
- border: 'none',
98
- color: 'blue',
99
- cursor: 'pointer',
100
- textDecoration: 'underline',
101
- }}
102
- >
131
+ <p className="mt-6 text-sm text-muted-foreground">
132
+ {isSignUp ? 'Already have an account?' : "Don't have an account?"}{' '}
133
+ <Button variant="link" type="button" onClick={() => setIsSignUp(!isSignUp)}>
103
134
  {isSignUp ? 'Sign In' : 'Create Account'}
104
- </button>
135
+ </Button>
105
136
  </p>
106
- </div>
137
+ </main>
107
138
  )
108
139
  }
@@ -4,6 +4,7 @@
4
4
  "lib": ["DOM", "DOM.Iterable", "ESNext"],
5
5
  "jsx": "react-jsx",
6
6
  "noEmit": true,
7
+ "types": ["vite/client"],
7
8
  "baseUrl": ".",
8
9
  "paths": {
9
10
  "@/*": ["./src/*"]
@@ -1,5 +1,7 @@
1
1
  import { defineConfig } from 'drizzle-kit'
2
2
  import dotenv from 'dotenv'
3
+ import * as fs from 'fs'
4
+ import * as path from 'path'
3
5
  import { dirname, resolve } from 'path'
4
6
  import { fileURLToPath } from 'url'
5
7
  import { getModuleSchemaPaths } from '@kuckit/db/schema-discovery'
@@ -26,8 +28,22 @@ function buildDatabaseUrl(): string {
26
28
  )
27
29
  }
28
30
 
31
+ function normalizeSchemaPath(schemaPath: string): string {
32
+ if (schemaPath.includes(`${path.sep}dist${path.sep}`)) {
33
+ return `${schemaPath}/*.js`
34
+ }
35
+ if (fs.existsSync(path.join(schemaPath, 'auth.js'))) {
36
+ return `${schemaPath}/*.js`
37
+ }
38
+ return schemaPath
39
+ }
40
+
41
+ const shouldNormalizeSchemas = process.env.DRIZZLE_SCHEMA_NORMALIZE !== 'false'
42
+
29
43
  export default defineConfig({
30
- schema: getModuleSchemaPaths(),
44
+ schema: shouldNormalizeSchemas
45
+ ? getModuleSchemaPaths().map(normalizeSchemaPath)
46
+ : getModuleSchemaPaths(),
31
47
  out: './drizzle',
32
48
  dialect: 'postgresql',
33
49
  dbCredentials: { url: buildDatabaseUrl() },
@@ -32,12 +32,12 @@
32
32
  ]
33
33
  },
34
34
  "dependencies": {
35
- "@kuckit/sdk": "^3.0.2",
36
- "@kuckit/api": "^3.0.2",
37
- "@kuckit/auth": "^3.0.2",
38
- "@kuckit/db": "^3.0.2",
39
- "@kuckit/domain": "^3.0.2",
40
- "@kuckit/infrastructure": "^3.0.2",
35
+ "@kuckit/sdk": "^5.0.0",
36
+ "@kuckit/api": "^5.0.0",
37
+ "@kuckit/auth": "^5.0.0",
38
+ "@kuckit/db": "^5.0.0",
39
+ "@kuckit/domain": "^5.0.0",
40
+ "@kuckit/infrastructure": "^5.0.0",
41
41
  "awilix": "^12.0.5",
42
42
  "dotenv": "^17.0.0",
43
43
  "drizzle-orm": "^0.44.0",
@@ -19,15 +19,15 @@
19
19
  "id": "items",
20
20
  "server": ".",
21
21
  "client": "./client",
22
- "schemaDir": "src/server/adapters"
22
+ "schemaDir": "src/server/schema"
23
23
  },
24
24
  "peerDependencies": {
25
25
  "typescript": "^5"
26
26
  },
27
27
  "dependencies": {
28
- "@kuckit/sdk": "^3.0.2",
29
- "@kuckit/sdk-react": "^3.0.2",
30
- "@kuckit/api": "^3.0.2",
28
+ "@kuckit/sdk": "^5.0.0",
29
+ "@kuckit/sdk-react": "^5.0.0",
30
+ "@kuckit/api": "^5.0.0",
31
31
  "@orpc/server": "^1.10.0",
32
32
  "@orpc/zod": "^1.10.0",
33
33
  "drizzle-orm": "^0.44.0",