@react-spa-scaffold/mcp 2.2.0 → 2.3.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.
Files changed (112) hide show
  1. package/dist/constants.d.ts +3 -0
  2. package/dist/constants.d.ts.map +1 -1
  3. package/dist/constants.js +3 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/features/definitions/database.d.ts +3 -0
  6. package/dist/features/definitions/database.d.ts.map +1 -0
  7. package/dist/features/definitions/database.js +45 -0
  8. package/dist/features/definitions/database.js.map +1 -0
  9. package/dist/features/definitions/deployment.d.ts +3 -0
  10. package/dist/features/definitions/deployment.d.ts.map +1 -0
  11. package/dist/features/definitions/deployment.js +14 -0
  12. package/dist/features/definitions/deployment.js.map +1 -0
  13. package/dist/features/definitions/index.d.ts +2 -0
  14. package/dist/features/definitions/index.d.ts.map +1 -1
  15. package/dist/features/definitions/index.js +2 -0
  16. package/dist/features/definitions/index.js.map +1 -1
  17. package/dist/features/registry.d.ts.map +1 -1
  18. package/dist/features/registry.js +3 -1
  19. package/dist/features/registry.js.map +1 -1
  20. package/dist/features/types.test.js +4 -2
  21. package/dist/features/types.test.js.map +1 -1
  22. package/dist/resources/docs.d.ts.map +1 -1
  23. package/dist/resources/docs.js +5 -0
  24. package/dist/resources/docs.js.map +1 -1
  25. package/dist/tools/add-features.js +1 -1
  26. package/dist/tools/add-features.js.map +1 -1
  27. package/dist/utils/docs.d.ts.map +1 -1
  28. package/dist/utils/docs.js +2 -0
  29. package/dist/utils/docs.js.map +1 -1
  30. package/dist/utils/scaffold/claude-md/index.d.ts.map +1 -1
  31. package/dist/utils/scaffold/claude-md/index.js +3 -1
  32. package/dist/utils/scaffold/claude-md/index.js.map +1 -1
  33. package/dist/utils/scaffold/claude-md/sections.d.ts +2 -0
  34. package/dist/utils/scaffold/claude-md/sections.d.ts.map +1 -1
  35. package/dist/utils/scaffold/claude-md/sections.js +132 -2
  36. package/dist/utils/scaffold/claude-md/sections.js.map +1 -1
  37. package/dist/utils/scaffold/compute.js +1 -1
  38. package/dist/utils/scaffold/compute.js.map +1 -1
  39. package/dist/utils/scaffold/generators.d.ts +2 -2
  40. package/dist/utils/scaffold/generators.d.ts.map +1 -1
  41. package/dist/utils/scaffold/generators.js +57 -22
  42. package/dist/utils/scaffold/generators.js.map +1 -1
  43. package/package.json +1 -1
  44. package/templates/.env.example +40 -12
  45. package/templates/.github/workflows/ci.yml +4 -1
  46. package/templates/.github/workflows/deploy.yml +59 -0
  47. package/templates/CLAUDE.md +177 -1
  48. package/templates/docs/AUTHENTICATION.md +325 -0
  49. package/templates/docs/DEPLOYMENT.md +268 -0
  50. package/templates/docs/E2E_TESTING.md +81 -4
  51. package/templates/docs/SUPABASE_INTEGRATION.md +310 -0
  52. package/templates/docs/TESTING.md +195 -77
  53. package/templates/e2e/auth/auth.setup.ts +60 -0
  54. package/templates/e2e/fixtures/index.ts +11 -0
  55. package/templates/e2e/tests/profile.auth.spec.ts +103 -0
  56. package/templates/e2e/tests/profile.spec.ts +64 -0
  57. package/templates/e2e/tests/register-form.spec.ts +38 -0
  58. package/templates/gitignore +5 -0
  59. package/templates/package.json +8 -0
  60. package/templates/playwright.config.ts +33 -3
  61. package/templates/src/App.tsx +32 -19
  62. package/templates/src/components/layout/Header.test.tsx +17 -1
  63. package/templates/src/components/layout/Header.tsx +11 -0
  64. package/templates/src/components/shared/AccountButton/AccountButton.test.tsx +3 -3
  65. package/templates/src/components/shared/ProfileSync/ProfileSync.test.tsx +44 -0
  66. package/templates/src/components/shared/ProfileSync/ProfileSync.tsx +104 -0
  67. package/templates/src/components/shared/ProfileSync/index.ts +1 -0
  68. package/templates/src/components/shared/ProtectedRoute/ProtectedRoute.test.tsx +3 -3
  69. package/templates/src/components/shared/index.ts +1 -0
  70. package/templates/src/contexts/performanceContext.tsx +3 -3
  71. package/templates/src/contexts/supabaseContext.test.tsx +59 -0
  72. package/templates/src/contexts/supabaseContext.tsx +87 -0
  73. package/templates/src/hooks/index.ts +17 -0
  74. package/templates/src/hooks/supabase/index.ts +12 -0
  75. package/templates/src/hooks/supabase/useProfiles.test.tsx +207 -0
  76. package/templates/src/hooks/supabase/useProfiles.ts +213 -0
  77. package/templates/src/hooks/supabase/useSupabaseQuery.test.tsx +150 -0
  78. package/templates/src/hooks/supabase/useSupabaseQuery.ts +91 -0
  79. package/templates/src/lib/api.test.ts +30 -38
  80. package/templates/src/lib/api.ts +1 -7
  81. package/templates/src/lib/config.ts +54 -4
  82. package/templates/src/lib/env.ts +36 -14
  83. package/templates/src/lib/index.ts +4 -2
  84. package/templates/src/lib/routes.ts +1 -0
  85. package/templates/src/lib/sentry.ts +13 -10
  86. package/templates/src/lib/supabase/client.ts +58 -0
  87. package/templates/src/lib/supabase/index.ts +5 -0
  88. package/templates/src/main.tsx +17 -39
  89. package/templates/src/mocks/constants.ts +31 -0
  90. package/templates/src/mocks/fixtures/index.ts +3 -1
  91. package/templates/src/mocks/fixtures/profiles.ts +55 -0
  92. package/templates/src/mocks/fixtures/users.ts +91 -0
  93. package/templates/src/mocks/handlers/index.ts +2 -1
  94. package/templates/src/mocks/handlers/supabase.ts +64 -0
  95. package/templates/src/mocks/handlers/todos.ts +1 -1
  96. package/templates/src/mocks/index.ts +6 -0
  97. package/templates/src/pages/Profile.test.tsx +263 -0
  98. package/templates/src/pages/Profile.tsx +171 -0
  99. package/templates/src/pages/index.ts +1 -0
  100. package/templates/src/stores/preferencesStore.ts +2 -1
  101. package/templates/src/test/clerkMock.tsx +49 -9
  102. package/templates/src/test/fetchMock.ts +58 -0
  103. package/templates/src/test/index.ts +49 -3
  104. package/templates/src/test/mocks.ts +128 -1
  105. package/templates/src/test/providers.tsx +7 -4
  106. package/templates/src/test/supabaseMock.ts +112 -0
  107. package/templates/src/test-setup.ts +26 -0
  108. package/templates/src/types/database.ts +46 -0
  109. package/templates/src/types/index.ts +1 -0
  110. package/templates/src/types/supabase.ts +167 -0
  111. package/templates/src/vite-env.d.ts +6 -0
  112. package/templates/supabase/migrations/20260104000000_create_profiles_table.sql +67 -0
@@ -0,0 +1,325 @@
1
+ # Authentication
2
+
3
+ Clerk authentication integration with shadcn theming and Supabase token injection.
4
+ For quick-start usage, see [CLAUDE.md](../CLAUDE.md#authentication-clerk).
5
+
6
+ ---
7
+
8
+ ## Why Clerk
9
+
10
+ This project uses Clerk instead of Supabase Auth for:
11
+
12
+ - **Production-ready UI** - Pre-built `SignInButton`, `UserButton` components with modal flows
13
+ - **Superior OAuth** - 20+ providers vs ~10, dashboard configuration vs code
14
+ - **Automatic session management** - Cross-tab sync, token refresh handled transparently
15
+ - **Separation of concerns** - Clerk = authentication (who), Supabase = authorization + data (what)
16
+
17
+ ---
18
+
19
+ ## Architecture
20
+
21
+ ```
22
+ ClerkProvider (ClerkThemeProvider wrapper)
23
+
24
+ ├── useAuth() → { isLoaded, isSignedIn, userId, getToken }
25
+ ├── useUser() → { user: { id, email, fullName, imageUrl } }
26
+ └── useSession() → { session.getToken() } → JWT Token
27
+
28
+
29
+ SupabaseProvider injects token
30
+
31
+
32
+ Supabase validates JWT
33
+ auth.uid() = Clerk user_id
34
+ ```
35
+
36
+ **Authentication Flow:**
37
+
38
+ 1. User clicks Sign In → Clerk modal opens
39
+ 2. User authenticates (email, OAuth, etc.)
40
+ 3. Clerk creates session, `useAuth()` returns `isSignedIn: true`
41
+ 4. `useSession().getToken()` provides JWT for Supabase
42
+ 5. RLS policies grant access via `auth.uid()`
43
+
44
+ ---
45
+
46
+ ## Setup
47
+
48
+ ### 1. Environment Variable
49
+
50
+ ```bash
51
+ VITE_CLERK_PUBLISHABLE_KEY=pk_test_xxxxx
52
+ ```
53
+
54
+ Get from [Clerk Dashboard](https://dashboard.clerk.com) → API Keys.
55
+
56
+ ### 2. Dashboard Setup
57
+
58
+ 1. Create application at [clerk.com](https://clerk.com)
59
+ 2. Configure sign-in methods (Email, Google, GitHub, etc.)
60
+ 3. **For Supabase**: Integrations → Supabase → Activate (adds `role: authenticated` claim)
61
+ 4. Copy Clerk domain → Supabase Dashboard → Authentication → Add Clerk provider
62
+
63
+ ### 3. Provider Hierarchy
64
+
65
+ ```tsx
66
+ // main.tsx - SupabaseProvider MUST be inside ClerkProvider
67
+ <ClerkThemeProvider publishableKey={CLERK_PUBLISHABLE_KEY}>
68
+ <SupabaseProvider>
69
+ <App />
70
+ </SupabaseProvider>
71
+ </ClerkThemeProvider>
72
+ ```
73
+
74
+ ---
75
+
76
+ ## File Structure
77
+
78
+ ```
79
+ src/
80
+ ├── contexts/
81
+ │ └── clerkContext.tsx # ClerkThemeProvider with shadcn theme
82
+ ├── components/shared/
83
+ │ ├── AccountButton/ # Sign in / User button
84
+ │ ├── ProtectedRoute/ # Auth guard wrapper
85
+ │ └── ProfileSync/ # Auto-sync Clerk → Supabase
86
+ ├── test/
87
+ │ └── clerkMock.tsx # Comprehensive Clerk mocks
88
+ ├── mocks/
89
+ │ ├── constants.ts # Mock user/session constants
90
+ │ └── fixtures/users.ts # Mock user factories
91
+ └── index.css # Includes @clerk/themes/shadcn.css
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Theme Configuration
97
+
98
+ ### ClerkThemeProvider
99
+
100
+ ```typescript
101
+ // src/contexts/clerkContext.tsx
102
+ import { ClerkProvider } from '@clerk/react-router';
103
+ import { shadcn } from '@clerk/themes';
104
+
105
+ const appearance: Appearance = {
106
+ baseTheme: shadcn,
107
+ variables: {
108
+ fontFamily: '"Inter Variable", sans-serif',
109
+ borderRadius: '0.45rem',
110
+ },
111
+ elements: {
112
+ modalBackdrop: 'backdrop-blur-sm',
113
+ modalContent: 'sm:max-w-md max-sm:min-h-svh max-sm:min-w-full max-sm:rounded-none',
114
+ card: 'max-sm:rounded-none max-sm:shadow-none',
115
+ },
116
+ };
117
+
118
+ export function ClerkThemeProvider({ children, publishableKey }: Props) {
119
+ return (
120
+ <ClerkProvider publishableKey={publishableKey} afterSignOutUrl="/" appearance={appearance}>
121
+ {children}
122
+ </ClerkProvider>
123
+ );
124
+ }
125
+ ```
126
+
127
+ ### CSS Import
128
+
129
+ ```css
130
+ /* src/index.css */
131
+ @import '@clerk/themes/shadcn.css';
132
+ ```
133
+
134
+ Enables automatic light/dark mode adaptation with shadcn CSS variables.
135
+
136
+ ---
137
+
138
+ ## Components
139
+
140
+ | Component | Location | Purpose |
141
+ | -------------------- | ----------------------------------- | ---------------------------------------------------- |
142
+ | `ClerkThemeProvider` | `contexts/clerkContext.tsx` | Clerk wrapper with shadcn theme |
143
+ | `AccountButton` | `components/shared/AccountButton/` | Sign-in button (logged out) / UserButton (logged in) |
144
+ | `ProtectedRoute` | `components/shared/ProtectedRoute/` | Auth guard, redirects to sign-in |
145
+ | `ProfileSync` | `components/shared/ProfileSync/` | Auto-syncs Clerk user → Supabase profiles |
146
+
147
+ ### AccountButton
148
+
149
+ Shows `SignInButton` when logged out, `UserButton` when logged in. Displays skeleton while loading.
150
+
151
+ ### ProtectedRoute
152
+
153
+ ```tsx
154
+ <Route
155
+ path="/dashboard"
156
+ element={
157
+ <ProtectedRoute>
158
+ <DashboardPage />
159
+ </ProtectedRoute>
160
+ }
161
+ />
162
+ ```
163
+
164
+ Returns `<PageLoading />` while checking auth, `<RedirectToSignIn />` if not authenticated.
165
+
166
+ ### ProfileSync
167
+
168
+ Invisible component that syncs Clerk user data to Supabase on sign-in:
169
+
170
+ - `id` → Clerk user ID
171
+ - `email` → Primary email
172
+ - `full_name` → Full name
173
+ - `avatar_url` → Profile image
174
+
175
+ ---
176
+
177
+ ## Hooks Reference
178
+
179
+ All hooks imported from `@clerk/react-router`:
180
+
181
+ | Hook | Key Returns | Use Case |
182
+ | -------------- | ------------------------------------------------------------- | -------------------------------------------- |
183
+ | `useAuth()` | `isLoaded`, `isSignedIn`, `userId`, `sessionId`, `getToken()` | Auth state without user details |
184
+ | `useUser()` | `isLoaded`, `user` | User profile (id, email, fullName, imageUrl) |
185
+ | `useSession()` | `isLoaded`, `session.getToken()` | JWT token for API calls |
186
+ | `useClerk()` | `signOut({ redirectUrl })` | Programmatic sign-out |
187
+
188
+ ### User Object Properties
189
+
190
+ | Property | Type | Description |
191
+ | ---------------------------------------- | ---------------- | ------------- |
192
+ | `user.id` | `string` | Clerk user ID |
193
+ | `user.primaryEmailAddress?.emailAddress` | `string` | Email |
194
+ | `user.fullName` | `string \| null` | Full name |
195
+ | `user.imageUrl` | `string` | Avatar URL |
196
+
197
+ For usage examples, see [CLAUDE.md](../CLAUDE.md#authentication-clerk).
198
+
199
+ ---
200
+
201
+ ## Supabase Integration
202
+
203
+ Clerk tokens are injected into Supabase for authenticated database access:
204
+
205
+ ```typescript
206
+ // src/contexts/supabaseContext.tsx
207
+ const { session } = useSession();
208
+
209
+ const supabase = useMemo(
210
+ () =>
211
+ createSupabaseClient(async () => {
212
+ if (!session) return null;
213
+ return session.getToken(); // Clerk JWT injected
214
+ }),
215
+ [session?.id], // Only recreate on sign in/out, not every render
216
+ );
217
+ ```
218
+
219
+ **Key points:**
220
+
221
+ - JWT includes `sub` (user ID) and `role: authenticated` claims
222
+ - Supabase `auth.uid()` equals Clerk user ID
223
+ - RLS policies enforce user-scoped access
224
+
225
+ See [SUPABASE_INTEGRATION.md](./SUPABASE_INTEGRATION.md) for full details.
226
+
227
+ ---
228
+
229
+ ## Testing
230
+
231
+ ### Mock Utilities
232
+
233
+ Import from `@/test`:
234
+
235
+ | Utility | Purpose |
236
+ | ---------------------------- | ---------------------------------------- |
237
+ | `setMockClerkSignedIn(bool)` | Set sign-in status |
238
+ | `setMockClerkLoaded(bool)` | Set loading state |
239
+ | `setMockClerkState({ ... })` | Set multiple values |
240
+ | `setMockClerkUser({ ... })` | Customize mock user |
241
+ | `resetClerkMocks()` | Reset to defaults (call in `beforeEach`) |
242
+
243
+ ### Mock Components
244
+
245
+ | Component | Test ID |
246
+ | ------------------ | --------------------- |
247
+ | `SignInButton` | `sign-in-button` |
248
+ | `SignUpButton` | `sign-up-button` |
249
+ | `UserButton` | `user-button` |
250
+ | `RedirectToSignIn` | `redirect-to-sign-in` |
251
+
252
+ ### Mock Constants & Fixtures
253
+
254
+ ```typescript
255
+ // src/mocks/constants.ts
256
+ export const MOCK_USER = {
257
+ id: 'user_123',
258
+ email: 'test@example.com',
259
+ fullName: 'Test User',
260
+ avatarUrl: 'https://example.com/avatar.jpg',
261
+ };
262
+ export const MOCK_SESSION_ID = 'sess_123';
263
+ export const MOCK_AUTH_TOKEN = 'mock-auth-token';
264
+ ```
265
+
266
+ ```typescript
267
+ // src/mocks/fixtures/users.ts
268
+ import { createUser, createUsers } from '@/test';
269
+
270
+ const user = createUser({ fullName: 'Jane Doe' }); // Single user with overrides
271
+ const users = createUsers(3); // Array of mock users
272
+ ```
273
+
274
+ ### E2E Testing
275
+
276
+ For Playwright tests requiring authentication, use `@clerk/testing`:
277
+
278
+ ```bash
279
+ CLERK_SECRET_KEY=sk_test_xxxxx
280
+ E2E_CLERK_USER_USERNAME=test@example.com
281
+ E2E_CLERK_USER_PASSWORD=your-password
282
+ ```
283
+
284
+ See [E2E_TESTING.md](./E2E_TESTING.md#authenticated-testing) for full details.
285
+
286
+ ---
287
+
288
+ ## Troubleshooting
289
+
290
+ | Issue | Cause | Fix |
291
+ | ------------------------------------------- | -------------------------- | ----------------------------------------------------- |
292
+ | "Missing VITE_CLERK_PUBLISHABLE_KEY" | Env var not set | Add to `.env`, restart dev server |
293
+ | UI not matching theme | Missing CSS import | Add `@import '@clerk/themes/shadcn.css'` to index.css |
294
+ | "useAuth must be used within ClerkProvider" | Component outside provider | Check `main.tsx` provider order |
295
+ | Modal not opening | Missing `mode="modal"` | Use `<SignInButton mode="modal">` |
296
+ | Supabase not getting tokens | Wrong provider order | SupabaseProvider must be inside ClerkProvider |
297
+ | User data undefined | Checking before loaded | Wait for `isLoaded === true` before accessing user |
298
+
299
+ ### Debug Auth State
300
+
301
+ ```typescript
302
+ const { isLoaded, isSignedIn, userId } = useAuth();
303
+ console.log({ isLoaded, isSignedIn, userId });
304
+ ```
305
+
306
+ ### Debug Token Claims
307
+
308
+ ```typescript
309
+ const { session } = useSession();
310
+ const token = await session?.getToken();
311
+ if (token) {
312
+ const payload = JSON.parse(atob(token.split('.')[1]));
313
+ console.log(payload); // { sub: "user_xxx", role: "authenticated", ... }
314
+ }
315
+ ```
316
+
317
+ ---
318
+
319
+ ## Resources
320
+
321
+ - [Clerk Documentation](https://clerk.com/docs)
322
+ - [Clerk React Router Integration](https://clerk.com/docs/references/react-router/overview)
323
+ - [Clerk Supabase Integration](https://clerk.com/docs/integrations/databases/supabase)
324
+ - [Clerk Appearance Customization](https://clerk.com/docs/customization/overview)
325
+ - [SUPABASE_INTEGRATION.md](./SUPABASE_INTEGRATION.md) - Database integration with Clerk tokens
@@ -0,0 +1,268 @@
1
+ # Deployment Guide
2
+
3
+ Automated deployment to Netlify with GitHub Actions for preview and production environments.
4
+
5
+ ---
6
+
7
+ ## Overview
8
+
9
+ ```
10
+ PR Created/Updated → GitHub Actions → Build → Netlify Preview
11
+
12
+ Comment with preview URL on PR
13
+
14
+ Push to main → GitHub Actions → Build → Netlify Production
15
+ ```
16
+
17
+ **Features:**
18
+
19
+ - Automatic preview deploys for pull requests
20
+ - Production deploys on push to main/master
21
+ - PR comments with preview URLs
22
+ - Manual deploy via workflow_dispatch
23
+ - Security headers pre-configured
24
+
25
+ **Note:** Enable branch protection rules to require CI to pass before merging to main.
26
+
27
+ ---
28
+
29
+ ## Netlify Setup
30
+
31
+ ### 1. Create Netlify Site
32
+
33
+ 1. Go to [app.netlify.com](https://app.netlify.com) and sign in
34
+ 2. Click "Add new site" → "Import an existing project"
35
+ 3. Connect your GitHub repository
36
+ 4. Configure build settings (auto-detected from `netlify.toml`):
37
+ - Build command: `npm run build`
38
+ - Publish directory: `dist`
39
+ 5. Click "Deploy site"
40
+
41
+ ### 2. Get API Credentials
42
+
43
+ 1. **Personal Access Token** (for `NETLIFY_AUTH_TOKEN`):
44
+ - User Settings → Applications → Personal access tokens
45
+ - Click "New access token", name it, and copy the token
46
+
47
+ 2. **Site ID** (for `NETLIFY_SITE_ID`):
48
+ - Site Settings → General → Site details → Site ID
49
+
50
+ ### 3. Add GitHub Secrets
51
+
52
+ Go to your repository → Settings → Secrets and variables → Actions → New repository secret:
53
+
54
+ | Secret Name | Description |
55
+ | -------------------- | -------------------------------- |
56
+ | `NETLIFY_AUTH_TOKEN` | Personal access token from above |
57
+ | `NETLIFY_SITE_ID` | Site ID from above |
58
+
59
+ ---
60
+
61
+ ## Environment Variables
62
+
63
+ ### Build-Time Variables
64
+
65
+ Set in Netlify Dashboard → Site Settings → Environment variables:
66
+
67
+ | Variable | Required | Description |
68
+ | ---------------------------- | ------------- | --------------------- |
69
+ | `VITE_CLERK_PUBLISHABLE_KEY` | If using auth | Clerk publishable key |
70
+ | `VITE_SUPABASE_DATABASE_URL` | If using db | Supabase project URL |
71
+ | `VITE_SUPABASE_ANON_KEY` | If using db | Supabase anon key |
72
+
73
+ ### Context-Specific Variables
74
+
75
+ Use Netlify CLI to set variables for specific contexts:
76
+
77
+ ```bash
78
+ # Set for all contexts
79
+ netlify env:set VAR_NAME value
80
+
81
+ # Set for production only
82
+ netlify env:set VAR_NAME value --context production
83
+
84
+ # Set for deploy previews only
85
+ netlify env:set VAR_NAME value --context deploy-preview
86
+ ```
87
+
88
+ ---
89
+
90
+ ## Supabase Integration
91
+
92
+ If using the database feature, connect Supabase to Netlify for automatic environment variable sync.
93
+
94
+ ### Extension Setup
95
+
96
+ 1. **Netlify Dashboard** → Extensions → Search "Supabase" → Install
97
+ 2. **Project Settings** → General → Supabase → Connect
98
+ 3. Authorize with Supabase and select your project
99
+ 4. For Vite projects:
100
+ - Framework: Select "Other"
101
+ - Environment variable prefix: Enter `VITE_`
102
+
103
+ ### Auto-Configured Variables
104
+
105
+ After connecting, these are automatically injected:
106
+
107
+ | Variable | Description |
108
+ | ---------------------------- | ---------------------------------------- |
109
+ | `VITE_SUPABASE_DATABASE_URL` | Project URL |
110
+ | `VITE_SUPABASE_ANON_KEY` | Client API key |
111
+ | `SUPABASE_SERVICE_ROLE_KEY` | Server-side only (not exposed to client) |
112
+
113
+ ### Local Development
114
+
115
+ Run `netlify dev` to inject Supabase variables locally:
116
+
117
+ ```bash
118
+ npm install -g netlify-cli
119
+ netlify login
120
+ netlify link # Link to your Netlify site
121
+ netlify dev # Starts dev server with injected env vars
122
+ ```
123
+
124
+ ---
125
+
126
+ ## Preview Deploys
127
+
128
+ Every pull request automatically gets a preview deployment:
129
+
130
+ 1. Open a PR against main/master
131
+ 2. GitHub Actions builds and deploys to Netlify
132
+ 3. Bot comments on PR with preview URL
133
+ 4. Preview updates on each push to the PR
134
+ 5. Preview is deleted when PR is closed
135
+
136
+ ### Preview URL Pattern
137
+
138
+ - PR previews: `https://pr-{number}--{site-name}.netlify.app`
139
+ - Branch deploys: `https://{branch}--{site-name}.netlify.app`
140
+
141
+ ---
142
+
143
+ ## Production Deploys
144
+
145
+ Pushing to main/master triggers production deployment:
146
+
147
+ 1. Deploy workflow builds the app
148
+ 2. Deploys to production URL: `https://your-site.netlify.app`
149
+
150
+ **Important:** Enable branch protection rules on main/master to require CI to pass before merging. This ensures production only gets code that passed all checks.
151
+
152
+ ### Manual Deploys
153
+
154
+ Use workflow_dispatch for manual production deploys:
155
+
156
+ 1. Go to Actions → Deploy → Run workflow
157
+ 2. Select branch
158
+ 3. Click "Run workflow"
159
+
160
+ ---
161
+
162
+ ## Configuration
163
+
164
+ ### netlify.toml
165
+
166
+ The `netlify.toml` file in your project root configures:
167
+
168
+ - **Build settings**: Command and publish directory
169
+ - **Redirects**: SPA fallback to index.html
170
+ - **Headers**: Security headers and caching rules
171
+ - **Context overrides**: Environment-specific settings
172
+
173
+ ### Customizing Headers
174
+
175
+ Add custom headers in `netlify.toml`:
176
+
177
+ ```toml
178
+ [[headers]]
179
+ for = "/api/*"
180
+ [headers.values]
181
+ Access-Control-Allow-Origin = "https://example.com"
182
+ ```
183
+
184
+ ### Redirect Rules
185
+
186
+ Add redirects before the SPA fallback:
187
+
188
+ ```toml
189
+ [[redirects]]
190
+ from = "/old-path"
191
+ to = "/new-path"
192
+ status = 301
193
+
194
+ # SPA fallback (keep last)
195
+ [[redirects]]
196
+ from = "/*"
197
+ to = "/index.html"
198
+ status = 200
199
+ ```
200
+
201
+ ---
202
+
203
+ ## CLI Commands
204
+
205
+ ```bash
206
+ # Install Netlify CLI
207
+ npm install -g netlify-cli
208
+
209
+ # Login and link
210
+ netlify login
211
+ netlify link
212
+
213
+ # Local development with Netlify env vars
214
+ netlify dev
215
+
216
+ # Manual deploys
217
+ npm run deploy:preview # Deploy preview build
218
+ npm run deploy:prod # Deploy to production
219
+
220
+ # Environment variables
221
+ netlify env:list # List all variables
222
+ netlify env:set KEY value # Set variable
223
+ netlify env:get KEY # Get variable value
224
+ netlify env:unset KEY # Remove variable
225
+
226
+ # Build locally
227
+ netlify build # Test production build
228
+ netlify build --context deploy-preview # Test preview build
229
+
230
+ # Check status
231
+ netlify status
232
+ ```
233
+
234
+ ---
235
+
236
+ ## Troubleshooting
237
+
238
+ | Issue | Cause | Solution |
239
+ | ---------------------------------- | ---------------------------- | --------------------------------------------- |
240
+ | Deploy fails with "Site not found" | Missing `NETLIFY_SITE_ID` | Add secret to GitHub repository |
241
+ | Deploy fails with "Unauthorized" | Invalid `NETLIFY_AUTH_TOKEN` | Regenerate token in Netlify |
242
+ | Preview not commenting on PR | Missing permissions | Check workflow has `pull-requests: write` |
243
+ | Env vars undefined in build | Not set in Netlify | Add to Netlify Dashboard or use `netlify env` |
244
+ | 404 on page refresh | SPA fallback not working | Check `netlify.toml` has `/* -> /index.html` |
245
+ | Production deploy not triggered | CI workflow failed | Check CI workflow status first |
246
+
247
+ ### Debug Build Locally
248
+
249
+ Test production build locally:
250
+
251
+ ```bash
252
+ npm run build
253
+ npx serve dist
254
+
255
+ # Or with Netlify CLI:
256
+ netlify build
257
+ netlify deploy --dir=dist
258
+ ```
259
+
260
+ ---
261
+
262
+ ## Resources
263
+
264
+ - [Netlify Documentation](https://docs.netlify.com/)
265
+ - [Netlify CLI Reference](https://cli.netlify.com/)
266
+ - [File-based Configuration](https://docs.netlify.com/configure-builds/file-based-configuration/)
267
+ - [GitHub Actions for Netlify](https://github.com/nwtgck/actions-netlify)
268
+ - [Netlify Supabase Extension](https://www.netlify.com/integrations/supabase/)
@@ -10,16 +10,22 @@
10
10
 
11
11
  ```
12
12
  e2e/
13
+ ├── auth/
14
+ │ └── auth.setup.ts # Clerk authentication setup
13
15
  ├── fixtures/
14
16
  │ └── index.ts # setupPage, setupCleanPage, test, expect
15
17
  ├── tests/ # Functional E2E tests
16
18
  │ ├── home.spec.ts # Page structure, accessibility
17
19
  │ ├── theme.spec.ts # Theme toggle, persistence
18
20
  │ ├── language.spec.ts # Language switcher
19
- └── navigation.spec.ts # Routing, 404
20
- └── performance/ # Performance regression tests
21
- ├── setup.ts # Performance test fixture
22
- └── home.spec.ts # Home page performance tests
21
+ ├── navigation.spec.ts # Routing, 404
22
+ │ ├── profile.spec.ts # Unauthenticated profile tests
23
+ │ └── profile.auth.spec.ts # Authenticated profile tests
24
+ ├── performance/ # Performance regression tests
25
+ │ ├── setup.ts # Performance test fixture
26
+ │ └── home.spec.ts # Home page performance tests
27
+ └── .clerk/ # Auth state storage (gitignored)
28
+ └── user.json # Saved auth state for tests
23
29
  ```
24
30
 
25
31
  ## Imports
@@ -111,8 +117,79 @@ npm run e2e:all # Run both desktop and mobile
111
117
  npm run e2e:ui # Interactive UI mode
112
118
  npm run e2e:perf # Run performance tests
113
119
  npm run e2e:perf:ui # Performance tests with interactive UI
120
+
121
+ # Authenticated tests (requires credentials)
122
+ npx playwright test --project=authenticated
123
+ ```
124
+
125
+ ## Authenticated Testing
126
+
127
+ Tests requiring authentication use `@clerk/testing` with Playwright. These tests run with a real authenticated user session.
128
+
129
+ ### Setup
130
+
131
+ 1. Install the testing package (already included):
132
+
133
+ ```bash
134
+ npm install -D @clerk/testing
135
+ ```
136
+
137
+ 2. Set environment variables in `.env`:
138
+
139
+ ```bash
140
+ CLERK_SECRET_KEY=sk_test_xxxxx
141
+ E2E_CLERK_USER_USERNAME=test@example.com
142
+ E2E_CLERK_USER_PASSWORD=your-test-password
143
+ ```
144
+
145
+ 3. Create a test user in your Clerk dashboard with the above credentials.
146
+
147
+ ### File Naming Convention
148
+
149
+ - `*.spec.ts` - Regular tests (run in `desktop`/`mobile` projects)
150
+ - `*.auth.spec.ts` - Authenticated tests (run in `authenticated` project only)
151
+
152
+ ### Writing Authenticated Tests
153
+
154
+ ```typescript
155
+ // e2e/tests/my-feature.auth.spec.ts
156
+ import { expect, test } from '@playwright/test';
157
+ import { existsSync } from 'fs';
158
+ import { dirname, join } from 'path';
159
+ import { fileURLToPath } from 'url';
160
+
161
+ const __dirname = dirname(fileURLToPath(import.meta.url));
162
+ const authFile = join(__dirname, '../.clerk/user.json');
163
+ const hasAuthState = existsSync(authFile);
164
+
165
+ test.describe('My Authenticated Feature', () => {
166
+ // Skip if auth state doesn't exist
167
+ test.skip(!hasAuthState, 'Authentication required');
168
+
169
+ test.beforeEach(async ({ page }) => {
170
+ // User is already authenticated via storageState
171
+ await page.goto('/protected-page');
172
+ });
173
+
174
+ test('can access protected content', async ({ page }) => {
175
+ await expect(page.getByText('Protected Content')).toBeVisible();
176
+ });
177
+ });
114
178
  ```
115
179
 
180
+ ### How It Works
181
+
182
+ 1. **Setup project** runs `auth.setup.ts` which:
183
+ - Calls `clerkSetup()` to get a testing token
184
+ - Signs in with test credentials
185
+ - Saves auth state to `e2e/.clerk/user.json`
186
+
187
+ 2. **Authenticated project** uses the saved state:
188
+ - Loads `storageState` from `user.json`
189
+ - Tests run with pre-authenticated session
190
+
191
+ 3. **Tests skip gracefully** when credentials aren't configured
192
+
116
193
  ## Mobile Testing
117
194
 
118
195
  Tests run on both desktop (Chrome) and mobile (Pixel 5) viewports. Use the `isMobile` fixture for device-specific behavior.