@mars-stack/cli 0.2.0 → 1.0.2

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 (175) hide show
  1. package/dist/index.js +137 -12
  2. package/dist/index.js.map +1 -1
  3. package/package.json +4 -3
  4. package/template/.cursor/rules/composition-patterns.mdc +186 -0
  5. package/template/.cursor/rules/data-access.mdc +29 -0
  6. package/template/.cursor/rules/project-structure.mdc +34 -0
  7. package/template/.cursor/rules/security.mdc +25 -0
  8. package/template/.cursor/rules/testing.mdc +24 -0
  9. package/template/.cursor/rules/ui-conventions.mdc +29 -0
  10. package/template/.cursor/skills/add-api-route/SKILL.md +122 -0
  11. package/template/.cursor/skills/add-audit-log/SKILL.md +375 -0
  12. package/template/.cursor/skills/add-blog/SKILL.md +447 -0
  13. package/template/.cursor/skills/add-command-palette/SKILL.md +438 -0
  14. package/template/.cursor/skills/add-component/SKILL.md +158 -0
  15. package/template/.cursor/skills/add-crud-routes/SKILL.md +221 -0
  16. package/template/.cursor/skills/add-e2e-test/SKILL.md +227 -0
  17. package/template/.cursor/skills/add-error-boundary/SKILL.md +472 -0
  18. package/template/.cursor/skills/add-feature/SKILL.md +174 -0
  19. package/template/.cursor/skills/add-middleware/SKILL.md +135 -0
  20. package/template/.cursor/skills/add-page/SKILL.md +151 -0
  21. package/template/.cursor/skills/add-prisma-model/SKILL.md +148 -0
  22. package/template/.cursor/skills/add-protected-resource/SKILL.md +192 -0
  23. package/template/.cursor/skills/add-role/SKILL.md +156 -0
  24. package/template/.cursor/skills/add-server-action/SKILL.md +167 -0
  25. package/template/.cursor/skills/add-webhook/SKILL.md +192 -0
  26. package/template/.cursor/skills/build-complete-feature/SKILL.md +227 -0
  27. package/template/.cursor/skills/build-dashboard/SKILL.md +211 -0
  28. package/template/.cursor/skills/build-data-table/SKILL.md +283 -0
  29. package/template/.cursor/skills/build-form/SKILL.md +231 -0
  30. package/template/.cursor/skills/build-landing-page/SKILL.md +248 -0
  31. package/template/.cursor/skills/configure-ai/SKILL.md +617 -0
  32. package/template/.cursor/skills/configure-analytics/SKILL.md +413 -0
  33. package/template/.cursor/skills/configure-dark-mode/SKILL.md +309 -0
  34. package/template/.cursor/skills/configure-email/SKILL.md +170 -0
  35. package/template/.cursor/skills/configure-email-verification/SKILL.md +333 -0
  36. package/template/.cursor/skills/configure-feature-flags/SKILL.md +361 -0
  37. package/template/.cursor/skills/configure-i18n/SKILL.md +518 -0
  38. package/template/.cursor/skills/configure-jobs/SKILL.md +500 -0
  39. package/template/.cursor/skills/configure-magic-links/SKILL.md +385 -0
  40. package/template/.cursor/skills/configure-multi-tenancy/SKILL.md +611 -0
  41. package/template/.cursor/skills/configure-notifications/SKILL.md +569 -0
  42. package/template/.cursor/skills/configure-oauth/SKILL.md +217 -0
  43. package/template/.cursor/skills/configure-onboarding/SKILL.md +483 -0
  44. package/template/.cursor/skills/configure-payments/SKILL.md +243 -0
  45. package/template/.cursor/skills/configure-realtime/SKILL.md +733 -0
  46. package/template/.cursor/skills/configure-search/SKILL.md +581 -0
  47. package/template/.cursor/skills/configure-storage/SKILL.md +273 -0
  48. package/template/.cursor/skills/configure-two-factor/SKILL.md +518 -0
  49. package/template/.cursor/skills/create-execution-plan/SKILL.md +204 -0
  50. package/template/.cursor/skills/create-seed/SKILL.md +191 -0
  51. package/template/.cursor/skills/deploy-to-vercel/SKILL.md +300 -0
  52. package/template/.cursor/skills/design-tokens/SKILL.md +138 -0
  53. package/template/.cursor/skills/mars-capture-conversation-context/SKILL.md +119 -0
  54. package/template/.cursor/skills/setup-billing/SKILL.md +322 -0
  55. package/template/.cursor/skills/setup-project/SKILL.md +104 -0
  56. package/template/.cursor/skills/setup-teams/SKILL.md +682 -0
  57. package/template/.cursor/skills/test-api-route/SKILL.md +219 -0
  58. package/template/.cursor/skills/update-architecture-docs/SKILL.md +99 -0
  59. package/template/AGENTS.md +104 -0
  60. package/template/ARCHITECTURE.md +102 -0
  61. package/template/docs/QUALITY_SCORE.md +20 -0
  62. package/template/docs/design-docs/conversation-as-system-record.md +70 -0
  63. package/template/docs/design-docs/core-beliefs.md +43 -0
  64. package/template/docs/design-docs/index.md +8 -0
  65. package/template/docs/exec-plans/active/.gitkeep +0 -0
  66. package/template/docs/exec-plans/completed/.gitkeep +0 -0
  67. package/template/docs/exec-plans/tech-debt.md +7 -0
  68. package/template/docs/generated/.gitkeep +0 -0
  69. package/template/docs/product-specs/index.md +7 -0
  70. package/template/docs/references/index.md +18 -0
  71. package/template/e2e/api.spec.ts +20 -0
  72. package/template/e2e/auth.spec.ts +24 -0
  73. package/template/e2e/public.spec.ts +25 -0
  74. package/template/eslint.config.mjs +24 -0
  75. package/template/next-env.d.ts +6 -0
  76. package/template/next.config.ts +45 -0
  77. package/template/package.json +80 -0
  78. package/template/playwright.config.ts +31 -0
  79. package/template/postcss.config.mjs +8 -0
  80. package/template/prisma/generated/prisma/browser.ts +49 -0
  81. package/template/prisma/generated/prisma/client.ts +73 -0
  82. package/template/prisma/generated/prisma/commonInputTypes.ts +406 -0
  83. package/template/prisma/generated/prisma/enums.ts +15 -0
  84. package/template/prisma/generated/prisma/internal/class.ts +254 -0
  85. package/template/prisma/generated/prisma/internal/prismaNamespace.ts +1240 -0
  86. package/template/prisma/generated/prisma/internal/prismaNamespaceBrowser.ts +190 -0
  87. package/template/prisma/generated/prisma/models/Account.ts +1543 -0
  88. package/template/prisma/generated/prisma/models/File.ts +1529 -0
  89. package/template/prisma/generated/prisma/models/Session.ts +1415 -0
  90. package/template/prisma/generated/prisma/models/Subscription.ts +1455 -0
  91. package/template/prisma/generated/prisma/models/User.ts +2235 -0
  92. package/template/prisma/generated/prisma/models/VerificationToken.ts +1099 -0
  93. package/template/prisma/generated/prisma/models.ts +17 -0
  94. package/template/prisma/schema/auth.prisma +69 -0
  95. package/template/prisma/schema/base.prisma +8 -0
  96. package/template/prisma/schema/file.prisma +15 -0
  97. package/template/prisma/schema/subscription.prisma +17 -0
  98. package/template/prisma.config.ts +13 -0
  99. package/template/scripts/check-architecture.ts +221 -0
  100. package/template/scripts/check-doc-freshness.ts +242 -0
  101. package/template/scripts/ensure-db.mjs +291 -0
  102. package/template/scripts/generate-docs.ts +143 -0
  103. package/template/scripts/generate-env-example.ts +89 -0
  104. package/template/scripts/seed.ts +56 -0
  105. package/template/scripts/update-quality-score.ts +263 -0
  106. package/template/src/__tests__/architecture.test.ts +114 -0
  107. package/template/src/app/(auth)/forgotten-password/page.tsx +92 -0
  108. package/template/src/app/(auth)/layout.tsx +11 -0
  109. package/template/src/app/(auth)/register/page.tsx +162 -0
  110. package/template/src/app/(auth)/reset-password/page.tsx +109 -0
  111. package/template/src/app/(auth)/sign-in/page.tsx +122 -0
  112. package/template/src/app/(auth)/verify/[token]/page.tsx +87 -0
  113. package/template/src/app/(auth)/verify/page.tsx +56 -0
  114. package/template/src/app/(protected)/admin/page.tsx +108 -0
  115. package/template/src/app/(protected)/dashboard/loading.tsx +20 -0
  116. package/template/src/app/(protected)/dashboard/page.tsx +22 -0
  117. package/template/src/app/(protected)/layout.tsx +262 -0
  118. package/template/src/app/(protected)/settings/page.tsx +370 -0
  119. package/template/src/app/api/auth/forgot/route.ts +63 -0
  120. package/template/src/app/api/auth/login/route.ts +121 -0
  121. package/template/src/app/api/auth/logout/route.ts +19 -0
  122. package/template/src/app/api/auth/me/route.ts +30 -0
  123. package/template/src/app/api/auth/reset/route.ts +45 -0
  124. package/template/src/app/api/auth/signup/route.ts +85 -0
  125. package/template/src/app/api/auth/verify/route.ts +46 -0
  126. package/template/src/app/api/csrf/route.ts +12 -0
  127. package/template/src/app/api/health/route.ts +10 -0
  128. package/template/src/app/api/protected/admin/users/route.ts +24 -0
  129. package/template/src/app/api/protected/billing/checkout/route.ts +83 -0
  130. package/template/src/app/api/protected/billing/portal/route.ts +39 -0
  131. package/template/src/app/api/protected/files/[fileId]/route.ts +86 -0
  132. package/template/src/app/api/protected/files/upload/route.ts +64 -0
  133. package/template/src/app/api/protected/user/password/route.ts +63 -0
  134. package/template/src/app/api/protected/user/profile/route.ts +35 -0
  135. package/template/src/app/api/protected/user/sessions/[sessionId]/route.ts +33 -0
  136. package/template/src/app/api/protected/user/sessions/route.ts +22 -0
  137. package/template/src/app/api/readiness/route.ts +15 -0
  138. package/template/src/app/api/webhooks/stripe/route.ts +166 -0
  139. package/template/src/app/error.tsx +33 -0
  140. package/template/src/app/layout.tsx +29 -0
  141. package/template/src/app/not-found.tsx +20 -0
  142. package/template/src/app/page.tsx +136 -0
  143. package/template/src/app/privacy/page.tsx +178 -0
  144. package/template/src/app/providers.tsx +8 -0
  145. package/template/src/app/terms/page.tsx +139 -0
  146. package/template/src/config/app.config.ts +70 -0
  147. package/template/src/config/routes.ts +17 -0
  148. package/template/src/features/admin/index.ts +11 -0
  149. package/template/src/features/admin/permissions.ts +64 -0
  150. package/template/src/features/auth/context/AuthContext.tsx +96 -0
  151. package/template/src/features/auth/context/index.ts +2 -0
  152. package/template/src/features/auth/index.ts +3 -0
  153. package/template/src/features/auth/server/consent.ts +66 -0
  154. package/template/src/features/auth/server/session-revocation.ts +20 -0
  155. package/template/src/features/auth/server/sessions.ts +66 -0
  156. package/template/src/features/auth/server/user.ts +166 -0
  157. package/template/src/features/auth/types.ts +19 -0
  158. package/template/src/features/auth/validators.ts +29 -0
  159. package/template/src/features/billing/server/index.ts +66 -0
  160. package/template/src/features/billing/types.ts +43 -0
  161. package/template/src/features/uploads/server/index.ts +49 -0
  162. package/template/src/features/uploads/types.ts +26 -0
  163. package/template/src/lib/core/email/templates/base-layout.ts +122 -0
  164. package/template/src/lib/core/email/templates/index.ts +4 -0
  165. package/template/src/lib/core/email/templates/password-reset-email.ts +42 -0
  166. package/template/src/lib/core/email/templates/verification-email.ts +41 -0
  167. package/template/src/lib/core/email/templates/welcome-email.ts +40 -0
  168. package/template/src/lib/mars.ts +56 -0
  169. package/template/src/lib/prisma.ts +19 -0
  170. package/template/src/proxy.ts +92 -0
  171. package/template/src/styles/brand.css +15 -0
  172. package/template/src/styles/globals.css +7 -0
  173. package/template/tsconfig.json +59 -0
  174. package/template/vitest.config.ts +41 -0
  175. package/template/vitest.setup.ts +24 -0
@@ -0,0 +1,41 @@
1
+ import { emailLayout, emailButton } from './base-layout';
2
+
3
+ interface VerificationEmailOptions {
4
+ appName: string;
5
+ verifyUrl: string;
6
+ userName?: string;
7
+ }
8
+
9
+ export function verificationEmailHtml({ appName, verifyUrl, userName }: VerificationEmailOptions): { html: string; text: string } {
10
+ const greeting = userName ? `Hi ${userName},` : 'Hi there,';
11
+
12
+ const content = `
13
+ <h1 style="margin: 0 0 16px 0; font-size: 24px; font-weight: 700; color: #111827;">Verify your email address</h1>
14
+ <p style="margin: 0 0 8px 0;">${greeting}</p>
15
+ <p style="margin: 0 0 8px 0;">Thanks for creating an account with ${appName}. Please verify your email address to get started.</p>
16
+ ${emailButton(verifyUrl, 'Verify Email Address')}
17
+ <p style="margin: 0 0 8px 0; font-size: 14px; color: #6b7280;">If the button doesn't work, copy and paste this link into your browser:</p>
18
+ <p style="margin: 0 0 16px 0; font-size: 14px; word-break: break-all; color: #6366f1;">${verifyUrl}</p>
19
+ <p style="margin: 0; font-size: 14px; color: #6b7280;">This link expires in 24 hours. If you didn't create this account, you can safely ignore this email.</p>`;
20
+
21
+ const html = emailLayout({
22
+ appName,
23
+ previewText: `Verify your email address for ${appName}`,
24
+ content,
25
+ });
26
+
27
+ const text = [
28
+ `Verify your email address`,
29
+ ``,
30
+ `${greeting}`,
31
+ ``,
32
+ `Thanks for creating an account with ${appName}. Please verify your email address by opening this link:`,
33
+ ``,
34
+ verifyUrl,
35
+ ``,
36
+ `This link expires in 24 hours.`,
37
+ `If you didn't create this account, you can safely ignore this email.`,
38
+ ].join('\n');
39
+
40
+ return { html, text };
41
+ }
@@ -0,0 +1,40 @@
1
+ import { emailLayout, emailButton } from './base-layout';
2
+
3
+ interface WelcomeEmailOptions {
4
+ appName: string;
5
+ dashboardUrl: string;
6
+ userName?: string;
7
+ }
8
+
9
+ export function welcomeEmailHtml({ appName, dashboardUrl, userName }: WelcomeEmailOptions): { html: string; text: string } {
10
+ const greeting = userName ? `Hi ${userName},` : 'Hi there,';
11
+
12
+ const content = `
13
+ <h1 style="margin: 0 0 16px 0; font-size: 24px; font-weight: 700; color: #111827;">Welcome to ${appName}</h1>
14
+ <p style="margin: 0 0 8px 0;">${greeting}</p>
15
+ <p style="margin: 0 0 8px 0;">Your account is all set up and ready to go. We're excited to have you on board.</p>
16
+ <p style="margin: 0 0 8px 0;">Head to your dashboard to get started:</p>
17
+ ${emailButton(dashboardUrl, 'Go to Dashboard')}
18
+ <p style="margin: 0; font-size: 14px; color: #6b7280;">If you have any questions, feel free to reach out to our support team.</p>`;
19
+
20
+ const html = emailLayout({
21
+ appName,
22
+ previewText: `Welcome to ${appName} — your account is ready`,
23
+ content,
24
+ });
25
+
26
+ const text = [
27
+ `Welcome to ${appName}`,
28
+ ``,
29
+ `${greeting}`,
30
+ ``,
31
+ `Your account is all set up and ready to go. We're excited to have you on board.`,
32
+ ``,
33
+ `Head to your dashboard to get started:`,
34
+ dashboardUrl,
35
+ ``,
36
+ `If you have any questions, feel free to reach out to our support team.`,
37
+ ].join('\n');
38
+
39
+ return { html, text };
40
+ }
@@ -0,0 +1,56 @@
1
+ import 'server-only';
2
+
3
+ import { configureMars } from '@mars-stack/core';
4
+ import { appConfig } from '@/config/app.config';
5
+ import { prisma } from './prisma';
6
+ import { routes } from '@/config/routes';
7
+
8
+ const mars = configureMars({
9
+ appConfig,
10
+ prisma,
11
+ signInRoute: routes.signIn,
12
+ });
13
+
14
+ export const {
15
+ logger,
16
+ appLogger,
17
+ authLogger,
18
+ securityLogger,
19
+ apiLogger,
20
+ logSecurityEvent,
21
+
22
+ sendEmail,
23
+
24
+ env,
25
+ validateJWTSecret,
26
+ getEnvironment,
27
+
28
+ getSession,
29
+ createSession,
30
+ updateSession,
31
+ deleteSession,
32
+ verifySession,
33
+ verifySessionForAPI,
34
+ getCurrentUser,
35
+
36
+ generateCSRFToken,
37
+ verifyCSRFToken,
38
+ requireCSRFToken,
39
+ validateCSRFRequest,
40
+
41
+ withAuth,
42
+ withAuthNoParams,
43
+ withAuthSimple,
44
+ withRole,
45
+ withOwnership,
46
+
47
+ handleApiError,
48
+
49
+ buildOrganizationJsonLd,
50
+ buildWebSiteJsonLd,
51
+ buildSiteNavigationJsonLd,
52
+ buildBreadcrumbListJsonLd,
53
+ buildArticleJsonLd,
54
+
55
+ getBaseUrl,
56
+ } = mars;
@@ -0,0 +1,19 @@
1
+ import 'server-only';
2
+
3
+ import { PrismaClient } from '@db';
4
+ import { PrismaPg } from '@prisma/adapter-pg';
5
+ import { createPrismaProxy, DatabaseError } from '@mars-stack/core/database';
6
+
7
+ export const prisma = createPrismaProxy(() => {
8
+ const connectionString = process.env.DATABASE_URL || '';
9
+
10
+ if (!connectionString) {
11
+ throw new DatabaseError(
12
+ 'DATABASE_URL is not set',
13
+ 'Set DATABASE_URL in your .env file, or run `yarn dev` to start local Postgres.',
14
+ );
15
+ }
16
+
17
+ const adapter = new PrismaPg({ connectionString });
18
+ return new PrismaClient({ adapter });
19
+ });
@@ -0,0 +1,92 @@
1
+ import { decrypt } from '@mars-stack/core/auth/session';
2
+ import { createCSRFProtection } from '@mars-stack/core/auth/csrf';
3
+ import { routes } from '@/config/routes';
4
+ import { NextRequest, NextResponse } from 'next/server';
5
+
6
+ const csrfExemptRoutes = ['/api/csrf', '/api/webhooks', '/api/auth/verify', '/api/auth/reset'];
7
+
8
+ const csrf = createCSRFProtection({
9
+ getJWTSecret: () => {
10
+ const secret = process.env.JWT_SECRET;
11
+ if (!secret || secret.length < 32) {
12
+ throw new Error('JWT_SECRET must be set and at least 32 characters');
13
+ }
14
+ return secret;
15
+ },
16
+ logViolation: (event) => {
17
+ console.warn('[CSRF Violation]', event);
18
+ },
19
+ });
20
+
21
+ const authRoutes = [routes.signIn, routes.signUp, routes.forgotPassword, routes.resetPassword];
22
+
23
+ const protectedRoutes = [routes.dashboard, routes.settings];
24
+
25
+ const adminRoutes = [routes.admin];
26
+
27
+ export async function proxy(request: NextRequest) {
28
+ const { pathname } = request.nextUrl;
29
+
30
+ const isComingSoonMode = process.env.COMING_SOON === 'true';
31
+ const isApiRoute = pathname.startsWith('/api/');
32
+
33
+ if (isComingSoonMode && pathname !== '/coming-soon' && !isApiRoute) {
34
+ return NextResponse.redirect(new URL('/coming-soon', request.url));
35
+ }
36
+
37
+ if (!isComingSoonMode && pathname === '/coming-soon') {
38
+ return NextResponse.redirect(new URL('/', request.url));
39
+ }
40
+
41
+ const jwtSecret = process.env.JWT_SECRET;
42
+ if (!jwtSecret || jwtSecret.length < 32) {
43
+ return new NextResponse('Server configuration error', { status: 500 });
44
+ }
45
+
46
+ const sessionCookie = request.cookies.get('session')?.value;
47
+ const session = await decrypt(sessionCookie, jwtSecret);
48
+
49
+ const requiresCsrf =
50
+ pathname.startsWith('/api/') &&
51
+ ['POST', 'PUT', 'DELETE', 'PATCH'].includes(request.method) &&
52
+ !csrfExemptRoutes.some((route) => pathname.startsWith(route));
53
+
54
+ if (requiresCsrf && !(await csrf.validateCSRFRequest(request, session?.fingerprint))) {
55
+ return new NextResponse('CSRF token validation failed', { status: 403 });
56
+ }
57
+ const isAuthenticated = !!session;
58
+
59
+ if (authRoutes.some((route) => pathname.startsWith(route)) && isAuthenticated) {
60
+ return NextResponse.redirect(new URL(routes.dashboard, request.url));
61
+ }
62
+
63
+ const safeCallbackPath = encodeURIComponent(
64
+ request.nextUrl.pathname + request.nextUrl.search,
65
+ );
66
+
67
+ if (protectedRoutes.some((route) => pathname.startsWith(route)) && !isAuthenticated) {
68
+ const url = new URL(routes.signIn, request.url);
69
+ url.searchParams.set('callbackUrl', safeCallbackPath);
70
+ return NextResponse.redirect(url);
71
+ }
72
+
73
+ if (adminRoutes.some((route) => pathname.startsWith(route))) {
74
+ if (!isAuthenticated) {
75
+ const url = new URL(routes.signIn, request.url);
76
+ url.searchParams.set('callbackUrl', safeCallbackPath);
77
+ return NextResponse.redirect(url);
78
+ }
79
+
80
+ if (session?.role !== 'admin') {
81
+ return NextResponse.redirect(new URL(routes.dashboard, request.url));
82
+ }
83
+ }
84
+
85
+ return NextResponse.next();
86
+ }
87
+
88
+ export const config = {
89
+ matcher: [
90
+ '/((?!_next/static|_next/image|favicon.ico|images).*)',
91
+ ],
92
+ };
@@ -0,0 +1,15 @@
1
+ /* Generated by Mars — brand palette derived from blue-600 */
2
+
3
+ :root {
4
+ --brand-50: oklch(0.970 0.015 263);
5
+ --brand-100: oklch(0.930 0.032 263);
6
+ --brand-200: oklch(0.880 0.060 263);
7
+ --brand-300: oklch(0.810 0.108 263);
8
+ --brand-400: oklch(0.710 0.155 263);
9
+ --brand-500: oklch(0.620 0.215 263);
10
+ --brand-600: oklch(0.550 0.215 263);
11
+ --brand-700: oklch(0.490 0.204 263);
12
+ --brand-800: oklch(0.420 0.176 263);
13
+ --brand-900: oklch(0.380 0.129 263);
14
+ --brand-950: oklch(0.280 0.099 263);
15
+ }
@@ -0,0 +1,7 @@
1
+ @import 'tailwindcss';
2
+ @import '@mars-stack/ui/styles/index.css';
3
+ @import '@mars-stack/ui/styles/meta-modern-saas.css';
4
+ @import './brand.css';
5
+ /* Monorepo: deps at root (../../../). Scaffolded: deps at project root (../../). */
6
+ @source "../../../node_modules/@mars-stack/ui/dist/**/*.js";
7
+ @source "../../node_modules/@mars-stack/ui/dist/**/*.js";
@@ -0,0 +1,59 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": [
5
+ "dom",
6
+ "dom.iterable",
7
+ "esnext"
8
+ ],
9
+ "allowJs": true,
10
+ "skipLibCheck": true,
11
+ "strict": true,
12
+ "noEmit": true,
13
+ "esModuleInterop": true,
14
+ "module": "esnext",
15
+ "moduleResolution": "bundler",
16
+ "resolveJsonModule": true,
17
+ "isolatedModules": true,
18
+ "jsx": "react-jsx",
19
+ "incremental": true,
20
+ "plugins": [
21
+ {
22
+ "name": "next"
23
+ }
24
+ ],
25
+ "paths": {
26
+ "@/*": [
27
+ "./src/*"
28
+ ],
29
+ "@/lib/*": [
30
+ "./src/lib/*"
31
+ ],
32
+ "@/features/*": [
33
+ "./src/features/*"
34
+ ],
35
+ "@/config/*": [
36
+ "./src/config/*"
37
+ ],
38
+ "@db": [
39
+ "./prisma/generated/prisma/client"
40
+ ]
41
+ }
42
+ },
43
+ "include": [
44
+ "next-env.d.ts",
45
+ "**/*.ts",
46
+ "**/*.tsx",
47
+ ".next/types/**/*.ts",
48
+ ".next/dev/types/**/*.ts"
49
+ ],
50
+ "exclude": [
51
+ "node_modules",
52
+ "vitest.config.ts",
53
+ "vitest.setup.ts",
54
+ "playwright.config.ts",
55
+ "e2e",
56
+ "**/*.test.ts",
57
+ "**/*.test.tsx"
58
+ ]
59
+ }
@@ -0,0 +1,41 @@
1
+ import { defineConfig } from 'vitest/config';
2
+ import react from '@vitejs/plugin-react';
3
+ import { resolve } from 'path';
4
+
5
+ export default defineConfig({
6
+ plugins: [react()],
7
+ test: {
8
+ environment: 'jsdom',
9
+ globals: true,
10
+ setupFiles: ['./vitest.setup.ts'],
11
+ include: ['src/**/*.test.{ts,tsx}'],
12
+ passWithNoTests: true,
13
+ coverage: {
14
+ provider: 'v8',
15
+ reporter: ['text', 'json', 'html'],
16
+ include: ['src/**/*.{ts,tsx}'],
17
+ exclude: [
18
+ 'src/**/*.test.{ts,tsx}',
19
+ 'src/**/*.d.ts',
20
+ 'src/app/**/layout.tsx',
21
+ 'src/styles/**',
22
+ 'src/config/**',
23
+ ],
24
+ thresholds: {
25
+ statements: 70,
26
+ branches: 70,
27
+ functions: 70,
28
+ lines: 70,
29
+ },
30
+ },
31
+ },
32
+ resolve: {
33
+ alias: {
34
+ '@': resolve(__dirname, './src'),
35
+ '@/core': resolve(__dirname, './src/core'),
36
+ '@/shared': resolve(__dirname, './src/shared'),
37
+ '@/features': resolve(__dirname, './src/features'),
38
+ '@/config': resolve(__dirname, './src/config'),
39
+ },
40
+ },
41
+ });
@@ -0,0 +1,24 @@
1
+ import '@testing-library/jest-dom';
2
+
3
+ vi.mock('server-only', () => ({}));
4
+
5
+ vi.mock('next/navigation', () => ({
6
+ useRouter: () => ({
7
+ push: vi.fn(),
8
+ replace: vi.fn(),
9
+ back: vi.fn(),
10
+ refresh: vi.fn(),
11
+ }),
12
+ usePathname: () => '/',
13
+ useSearchParams: () => new URLSearchParams(),
14
+ redirect: vi.fn(),
15
+ }));
16
+
17
+ vi.mock('next/headers', () => ({
18
+ cookies: () => ({
19
+ get: vi.fn(),
20
+ set: vi.fn(),
21
+ delete: vi.fn(),
22
+ }),
23
+ headers: () => new Headers(),
24
+ }));