forgedev 1.0.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 (99) hide show
  1. package/CLAUDE.md +38 -0
  2. package/LICENSE +21 -0
  3. package/README.md +246 -0
  4. package/bin/devforge.js +4 -0
  5. package/package.json +33 -0
  6. package/src/claude-configurator.js +260 -0
  7. package/src/cli.js +119 -0
  8. package/src/composer.js +214 -0
  9. package/src/doctor-checks.js +743 -0
  10. package/src/doctor-prompts.js +295 -0
  11. package/src/doctor.js +281 -0
  12. package/src/guided.js +315 -0
  13. package/src/index.js +148 -0
  14. package/src/init-mode.js +134 -0
  15. package/src/prompts.js +155 -0
  16. package/src/recommender.js +186 -0
  17. package/src/scanner.js +368 -0
  18. package/src/uat-generator.js +189 -0
  19. package/src/utils.js +57 -0
  20. package/templates/auth/jwt-custom/backend/app/api/auth.py.template +45 -0
  21. package/templates/auth/jwt-custom/backend/app/api/deps.py.template +16 -0
  22. package/templates/auth/jwt-custom/backend/app/core/security.py.template +34 -0
  23. package/templates/auth/nextauth/src/app/api/auth/[...nextauth]/route.ts.template +3 -0
  24. package/templates/auth/nextauth/src/lib/auth.ts.template +30 -0
  25. package/templates/auth/nextauth/src/middleware.ts.template +14 -0
  26. package/templates/backend/fastapi/backend/Dockerfile.template +12 -0
  27. package/templates/backend/fastapi/backend/app/__init__.py +0 -0
  28. package/templates/backend/fastapi/backend/app/api/__init__.py +0 -0
  29. package/templates/backend/fastapi/backend/app/api/health.py.template +32 -0
  30. package/templates/backend/fastapi/backend/app/core/__init__.py +0 -0
  31. package/templates/backend/fastapi/backend/app/core/config.py.template +25 -0
  32. package/templates/backend/fastapi/backend/app/core/errors.py +37 -0
  33. package/templates/backend/fastapi/backend/app/core/retry.py +32 -0
  34. package/templates/backend/fastapi/backend/app/main.py.template +58 -0
  35. package/templates/backend/fastapi/backend/app/models/__init__.py +0 -0
  36. package/templates/backend/fastapi/backend/app/schemas/__init__.py +0 -0
  37. package/templates/backend/fastapi/backend/pyproject.toml.template +19 -0
  38. package/templates/backend/fastapi/backend/requirements.txt.template +14 -0
  39. package/templates/base/.gitignore.template +29 -0
  40. package/templates/base/README.md.template +25 -0
  41. package/templates/claude-code/agents/code-quality-reviewer.md +41 -0
  42. package/templates/claude-code/agents/production-readiness.md +55 -0
  43. package/templates/claude-code/agents/security-reviewer.md +41 -0
  44. package/templates/claude-code/agents/spec-validator.md +34 -0
  45. package/templates/claude-code/agents/uat-validator.md +37 -0
  46. package/templates/claude-code/claude-md/base.md +33 -0
  47. package/templates/claude-code/claude-md/fastapi.md +12 -0
  48. package/templates/claude-code/claude-md/fullstack.md +12 -0
  49. package/templates/claude-code/claude-md/nextjs.md +11 -0
  50. package/templates/claude-code/commands/audit-security.md +11 -0
  51. package/templates/claude-code/commands/audit-spec.md +9 -0
  52. package/templates/claude-code/commands/audit-wiring.md +17 -0
  53. package/templates/claude-code/commands/done.md +19 -0
  54. package/templates/claude-code/commands/generate-prd.md +45 -0
  55. package/templates/claude-code/commands/generate-uat.md +35 -0
  56. package/templates/claude-code/commands/help.md +26 -0
  57. package/templates/claude-code/commands/next.md +20 -0
  58. package/templates/claude-code/commands/optimize-claude-md.md +31 -0
  59. package/templates/claude-code/commands/pre-pr.md +19 -0
  60. package/templates/claude-code/commands/run-uat.md +21 -0
  61. package/templates/claude-code/commands/status.md +24 -0
  62. package/templates/claude-code/commands/verify-all.md +11 -0
  63. package/templates/claude-code/hooks/polyglot.json +36 -0
  64. package/templates/claude-code/hooks/python.json +36 -0
  65. package/templates/claude-code/hooks/scripts/autofix-polyglot.sh +16 -0
  66. package/templates/claude-code/hooks/scripts/autofix-python.sh +14 -0
  67. package/templates/claude-code/hooks/scripts/autofix-typescript.sh +14 -0
  68. package/templates/claude-code/hooks/scripts/guard-protected-files.sh +21 -0
  69. package/templates/claude-code/hooks/typescript.json +36 -0
  70. package/templates/claude-code/skills/ai-prompts/SKILL.md +43 -0
  71. package/templates/claude-code/skills/fastapi/SKILL.md +38 -0
  72. package/templates/claude-code/skills/nextjs/SKILL.md +39 -0
  73. package/templates/claude-code/skills/playwright/SKILL.md +37 -0
  74. package/templates/claude-code/skills/security-api/SKILL.md +47 -0
  75. package/templates/claude-code/skills/security-web/SKILL.md +41 -0
  76. package/templates/database/prisma-postgres/.env.example +1 -0
  77. package/templates/database/prisma-postgres/prisma/schema.prisma.template +18 -0
  78. package/templates/database/sqlalchemy-postgres/.env.example +1 -0
  79. package/templates/database/sqlalchemy-postgres/backend/alembic/env.py.template +40 -0
  80. package/templates/database/sqlalchemy-postgres/backend/alembic/versions/.gitkeep +0 -0
  81. package/templates/database/sqlalchemy-postgres/backend/alembic.ini.template +36 -0
  82. package/templates/database/sqlalchemy-postgres/backend/app/db/__init__.py +0 -0
  83. package/templates/database/sqlalchemy-postgres/backend/app/db/base.py +5 -0
  84. package/templates/database/sqlalchemy-postgres/backend/app/db/session.py.template +48 -0
  85. package/templates/frontend/nextjs/next.config.ts.template +7 -0
  86. package/templates/frontend/nextjs/package.json.template +41 -0
  87. package/templates/frontend/nextjs/postcss.config.mjs +7 -0
  88. package/templates/frontend/nextjs/src/app/api/health/route.ts.template +10 -0
  89. package/templates/frontend/nextjs/src/app/globals.css +1 -0
  90. package/templates/frontend/nextjs/src/app/layout.tsx.template +22 -0
  91. package/templates/frontend/nextjs/src/app/page.tsx.template +10 -0
  92. package/templates/frontend/nextjs/src/lib/db.ts.template +40 -0
  93. package/templates/frontend/nextjs/src/lib/errors.ts +28 -0
  94. package/templates/frontend/nextjs/src/lib/utils.ts +6 -0
  95. package/templates/frontend/nextjs/tsconfig.json +23 -0
  96. package/templates/infra/docker-compose/docker-compose.yml.template +19 -0
  97. package/templates/testing/playwright/e2e/example.spec.ts.template +15 -0
  98. package/templates/testing/playwright/playwright.config.ts.template +22 -0
  99. package/templates/testing/vitest/src/__tests__/example.test.ts.template +12 -0
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: Security (API)
3
+ description: API security best practices
4
+ ---
5
+
6
+ # API Security
7
+
8
+ ## Authentication & Authorization
9
+ - Use JWT tokens with short expiration (15-30 min)
10
+ - Implement refresh token rotation
11
+ - Hash passwords with bcrypt (passlib)
12
+ - Use `Depends(get_current_user)` on all protected endpoints
13
+ - Implement role-based access control (RBAC)
14
+
15
+ ## Input Validation
16
+ - Validate all input with Pydantic models
17
+ - Set max lengths on string fields
18
+ - Validate email formats, URLs, phone numbers
19
+ - Reject unexpected fields (Pydantic does this by default)
20
+ - Validate file uploads (size, type, extension)
21
+
22
+ ## SQL Injection Prevention
23
+ - Use SQLAlchemy ORM — never raw SQL strings
24
+ - If raw SQL needed, use `text()` with bound parameters
25
+ - Never interpolate user input into queries
26
+
27
+ ## Rate Limiting
28
+ - Implement per-IP rate limiting on auth endpoints
29
+ - Use sliding window or token bucket algorithms
30
+ - Return `429 Too Many Requests` with `Retry-After` header
31
+
32
+ ## CORS
33
+ - Whitelist specific origins, never use `*` in production
34
+ - Restrict allowed methods and headers
35
+ - Set `allow_credentials=True` only when needed
36
+
37
+ ## Error Handling
38
+ - Never expose stack traces to clients
39
+ - Use generic error messages for auth failures
40
+ - Log detailed errors server-side only
41
+ - Return structured error responses: `{ error: { code, message } }`
42
+
43
+ ## Secrets Management
44
+ - Store secrets in environment variables, never in code
45
+ - Use `.env.example` for documentation (no real values)
46
+ - Rotate secrets regularly
47
+ - Never log secrets or include in error responses
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: Security (Web)
3
+ description: Web application security best practices
4
+ ---
5
+
6
+ # Web Application Security
7
+
8
+ ## XSS Prevention
9
+ - React escapes by default — never use `dangerouslySetInnerHTML`
10
+ - Sanitize user input before rendering
11
+ - Use Content Security Policy headers
12
+ - Validate URLs before using in `href` or `src`
13
+
14
+ ## CSRF Protection
15
+ - Use SameSite cookies (Lax or Strict)
16
+ - NextAuth handles CSRF tokens automatically
17
+ - For custom forms, include CSRF tokens in hidden fields
18
+
19
+ ## Authentication
20
+ - Store tokens in httpOnly cookies, never localStorage
21
+ - Use secure, SameSite cookies in production
22
+ - Implement proper session expiration
23
+ - Rate limit login attempts
24
+
25
+ ## Headers
26
+ - Set `X-Content-Type-Options: nosniff`
27
+ - Set `X-Frame-Options: DENY`
28
+ - Set `Strict-Transport-Security` for HTTPS
29
+ - Configure CSP to restrict script sources
30
+
31
+ ## Input Validation
32
+ - Validate all user input with Zod schemas
33
+ - Validate on both client and server
34
+ - Never trust client-side validation alone
35
+ - Sanitize file uploads (check MIME type, size, extension)
36
+
37
+ ## Data Exposure
38
+ - Never return sensitive fields (passwords, tokens) in API responses
39
+ - Use Prisma `select` or `omit` to control returned fields
40
+ - Never log sensitive data
41
+ - Strip internal IDs from client-facing responses when possible
@@ -0,0 +1 @@
1
+ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/{{PROJECT_NAME_SNAKE}}"
@@ -0,0 +1,18 @@
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "postgresql"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ // Add your models below
11
+ // Example:
12
+ // model User {
13
+ // id String @id @default(cuid())
14
+ // email String @unique
15
+ // name String?
16
+ // createdAt DateTime @default(now())
17
+ // updatedAt DateTime @updatedAt
18
+ // }
@@ -0,0 +1 @@
1
+ DATABASE_URL="postgresql+asyncpg://postgres:postgres@localhost:5432/{{PROJECT_NAME_SNAKE}}"
@@ -0,0 +1,40 @@
1
+ import asyncio
2
+ from logging.config import fileConfig
3
+
4
+ from alembic import context
5
+ from sqlalchemy.ext.asyncio import create_async_engine
6
+
7
+ from app.core.config import settings
8
+ from app.db.base import Base
9
+
10
+ config = context.config
11
+ if config.config_file_name is not None:
12
+ fileConfig(config.config_file_name)
13
+
14
+ target_metadata = Base.metadata
15
+
16
+
17
+ def run_migrations_offline() -> None:
18
+ url = settings.database_url
19
+ context.configure(url=url, target_metadata=target_metadata, literal_binds=True)
20
+ with context.begin_transaction():
21
+ context.run_migrations()
22
+
23
+
24
+ def do_run_migrations(connection):
25
+ context.configure(connection=connection, target_metadata=target_metadata)
26
+ with context.begin_transaction():
27
+ context.run_migrations()
28
+
29
+
30
+ async def run_migrations_online() -> None:
31
+ connectable = create_async_engine(settings.database_url)
32
+ async with connectable.connect() as connection:
33
+ await connection.run_sync(do_run_migrations)
34
+ await connectable.dispose()
35
+
36
+
37
+ if context.is_offline_mode():
38
+ run_migrations_offline()
39
+ else:
40
+ asyncio.run(run_migrations_online())
@@ -0,0 +1,36 @@
1
+ [alembic]
2
+ script_location = alembic
3
+ sqlalchemy.url = postgresql+asyncpg://postgres:postgres@localhost:5432/{{PROJECT_NAME_SNAKE}}
4
+
5
+ [loggers]
6
+ keys = root,sqlalchemy,alembic
7
+
8
+ [handlers]
9
+ keys = console
10
+
11
+ [formatters]
12
+ keys = generic
13
+
14
+ [logger_root]
15
+ level = WARN
16
+ handlers = console
17
+
18
+ [logger_sqlalchemy]
19
+ level = WARN
20
+ handlers =
21
+ qualname = sqlalchemy.engine
22
+
23
+ [logger_alembic]
24
+ level = INFO
25
+ handlers =
26
+ qualname = alembic
27
+
28
+ [handler_console]
29
+ class = StreamHandler
30
+ args = (sys.stderr,)
31
+ level = NOTSET
32
+ formatter = generic
33
+
34
+ [formatter_generic]
35
+ format = %(levelname)-5.5s [%(name)s] %(message)s
36
+ datefmt = %H:%M:%S
@@ -0,0 +1,5 @@
1
+ from sqlalchemy.orm import DeclarativeBase
2
+
3
+
4
+ class Base(DeclarativeBase):
5
+ pass
@@ -0,0 +1,48 @@
1
+ import asyncio
2
+ import logging
3
+
4
+ from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
5
+
6
+ from app.core.config import settings
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ engine = create_async_engine(
11
+ settings.database_url,
12
+ echo=settings.debug,
13
+ pool_size=5,
14
+ max_overflow=10,
15
+ pool_pre_ping=True,
16
+ )
17
+
18
+ async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
19
+
20
+
21
+ async def connect_with_retry(
22
+ engine, max_retries: int = 3, base_delay: float = 1.0
23
+ ) -> None:
24
+ """Attempt database connection with exponential backoff."""
25
+ for attempt in range(1, max_retries + 1):
26
+ try:
27
+ async with engine.connect() as conn:
28
+ await conn.execute(
29
+ __import__("sqlalchemy").text("SELECT 1")
30
+ )
31
+ logger.info("Database connected successfully")
32
+ return
33
+ except Exception as e:
34
+ if attempt == max_retries:
35
+ logger.error(f"Database connection failed after {max_retries} attempts: {e}")
36
+ raise
37
+ delay = base_delay * (2 ** (attempt - 1))
38
+ logger.warning(
39
+ f"Database connection attempt {attempt}/{max_retries} failed. "
40
+ f"Retrying in {delay:.1f}s..."
41
+ )
42
+ await asyncio.sleep(delay)
43
+
44
+
45
+ async def get_db():
46
+ """Dependency for FastAPI endpoints."""
47
+ async with async_session() as session:
48
+ yield session
@@ -0,0 +1,7 @@
1
+ import type { NextConfig } from 'next';
2
+
3
+ const nextConfig: NextConfig = {
4
+ // {{PROJECT_NAME}} configuration
5
+ };
6
+
7
+ export default nextConfig;
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "next build",
8
+ "start": "next start",
9
+ "lint": "eslint .",
10
+ "typecheck": "tsc --noEmit",
11
+ "test": "vitest run",
12
+ "test:watch": "vitest",
13
+ "test:e2e": "playwright test",
14
+ "db:push": "prisma db push",
15
+ "db:studio": "prisma studio",
16
+ "db:generate": "prisma generate"
17
+ },
18
+ "dependencies": {
19
+ "next": "^15.3.0",
20
+ "react": "^19.1.0",
21
+ "react-dom": "^19.1.0",
22
+ "@prisma/client": "^6.6.0",
23
+ "clsx": "^2.1.1",
24
+ "tailwind-merge": "^3.2.0"
25
+ },
26
+ "devDependencies": {
27
+ "typescript": "^5.8.3",
28
+ "@types/node": "^22.14.0",
29
+ "@types/react": "^19.1.0",
30
+ "@types/react-dom": "^19.1.0",
31
+ "tailwindcss": "^4.1.3",
32
+ "@tailwindcss/postcss": "^4.1.3",
33
+ "postcss": "^8.5.3",
34
+ "eslint": "^9.25.0",
35
+ "eslint-config-next": "^15.3.0",
36
+ "prisma": "^6.6.0",
37
+ "vitest": "^3.1.1",
38
+ "@playwright/test": "^1.52.0",
39
+ "@testing-library/react": "^16.3.0"
40
+ }
41
+ }
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1,10 @@
1
+ import { NextResponse } from 'next/server';
2
+
3
+ export async function GET() {
4
+ return NextResponse.json({
5
+ status: 'ok',
6
+ timestamp: new Date().toISOString(),
7
+ uptime: process.uptime(),
8
+ service: '{{PROJECT_NAME}}',
9
+ });
10
+ }
@@ -0,0 +1 @@
1
+ @import "tailwindcss";
@@ -0,0 +1,22 @@
1
+ import type { Metadata } from 'next';
2
+ import { Inter } from 'next/font/google';
3
+ import './globals.css';
4
+
5
+ const inter = Inter({ subsets: ['latin'] });
6
+
7
+ export const metadata: Metadata = {
8
+ title: '{{PROJECT_NAME_PASCAL}}',
9
+ description: '{{STACK_DESCRIPTION}}',
10
+ };
11
+
12
+ export default function RootLayout({
13
+ children,
14
+ }: {
15
+ children: React.ReactNode;
16
+ }) {
17
+ return (
18
+ <html lang="en">
19
+ <body className={inter.className}>{children}</body>
20
+ </html>
21
+ );
22
+ }
@@ -0,0 +1,10 @@
1
+ export default function Home() {
2
+ return (
3
+ <main className="flex min-h-screen flex-col items-center justify-center p-24">
4
+ <h1 className="text-4xl font-bold mb-4">{{PROJECT_NAME_PASCAL}}</h1>
5
+ <p className="text-lg text-gray-600">
6
+ Your project is ready. Start building!
7
+ </p>
8
+ </main>
9
+ );
10
+ }
@@ -0,0 +1,40 @@
1
+ import { PrismaClient } from '@prisma/client';
2
+
3
+ const MAX_RETRIES = 3;
4
+ const RETRY_DELAY_MS = 1000;
5
+
6
+ async function connectWithRetry(prisma: PrismaClient, retries = MAX_RETRIES): Promise<void> {
7
+ for (let attempt = 1; attempt <= retries; attempt++) {
8
+ try {
9
+ await prisma.$connect();
10
+ return;
11
+ } catch (error) {
12
+ if (attempt === retries) throw error;
13
+ const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1);
14
+ console.warn(`Database connection attempt ${attempt} failed. Retrying in ${delay}ms...`);
15
+ await new Promise((resolve) => setTimeout(resolve, delay));
16
+ }
17
+ }
18
+ }
19
+
20
+ const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
21
+
22
+ export const prisma =
23
+ globalForPrisma.prisma ??
24
+ new PrismaClient({
25
+ log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
26
+ });
27
+
28
+ if (process.env.NODE_ENV !== 'production') {
29
+ globalForPrisma.prisma = prisma;
30
+ }
31
+
32
+ // Graceful shutdown
33
+ const shutdownHandler = async () => {
34
+ await prisma.$disconnect();
35
+ };
36
+
37
+ process.on('SIGTERM', shutdownHandler);
38
+ process.on('SIGINT', shutdownHandler);
39
+
40
+ export { connectWithRetry };
@@ -0,0 +1,28 @@
1
+ import { NextResponse } from 'next/server';
2
+
3
+ export class AppError extends Error {
4
+ constructor(
5
+ message: string,
6
+ public statusCode: number = 500,
7
+ public code: string = 'INTERNAL_ERROR'
8
+ ) {
9
+ super(message);
10
+ this.name = 'AppError';
11
+ }
12
+ }
13
+
14
+ export function errorResponse(error: unknown) {
15
+ if (error instanceof AppError) {
16
+ return NextResponse.json(
17
+ { error: { code: error.code, message: error.message } },
18
+ { status: error.statusCode }
19
+ );
20
+ }
21
+
22
+ // Never leak internal error details
23
+ console.error('Unhandled error:', error);
24
+ return NextResponse.json(
25
+ { error: { code: 'INTERNAL_ERROR', message: 'An unexpected error occurred' } },
26
+ { status: 500 }
27
+ );
28
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "preserve",
15
+ "incremental": true,
16
+ "plugins": [{ "name": "next" }],
17
+ "paths": {
18
+ "@/*": ["./src/*"]
19
+ }
20
+ },
21
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
22
+ "exclude": ["node_modules"]
23
+ }
@@ -0,0 +1,19 @@
1
+ services:
2
+ postgres:
3
+ image: postgres:17-alpine
4
+ environment:
5
+ POSTGRES_DB: {{PROJECT_NAME_SNAKE}}
6
+ POSTGRES_USER: postgres
7
+ POSTGRES_PASSWORD: postgres
8
+ ports:
9
+ - "5432:5432"
10
+ volumes:
11
+ - postgres_data:/var/lib/postgresql/data
12
+ healthcheck:
13
+ test: ["CMD-SHELL", "pg_isready -U postgres"]
14
+ interval: 5s
15
+ timeout: 5s
16
+ retries: 5
17
+
18
+ volumes:
19
+ postgres_data:
@@ -0,0 +1,15 @@
1
+ import { test, expect } from '@playwright/test';
2
+
3
+ test.describe('{{PROJECT_NAME_PASCAL}} E2E', () => {
4
+ test('home page loads', async ({ page }) => {
5
+ await page.goto('/');
6
+ await expect(page).toHaveTitle(/{{PROJECT_NAME_PASCAL}}/);
7
+ });
8
+
9
+ test('health endpoint responds', async ({ request }) => {
10
+ const response = await request.get('/api/health');
11
+ expect(response.ok()).toBeTruthy();
12
+ const body = await response.json();
13
+ expect(body.status).toBe('ok');
14
+ });
15
+ });
@@ -0,0 +1,22 @@
1
+ import { defineConfig, devices } from '@playwright/test';
2
+
3
+ export default defineConfig({
4
+ testDir: './e2e',
5
+ fullyParallel: true,
6
+ forbidOnly: !!process.env.CI,
7
+ retries: process.env.CI ? 2 : 0,
8
+ workers: process.env.CI ? 1 : undefined,
9
+ reporter: 'html',
10
+ use: {
11
+ baseURL: 'http://localhost:3000',
12
+ trace: 'on-first-retry',
13
+ },
14
+ projects: [
15
+ { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
16
+ ],
17
+ webServer: {
18
+ command: 'npm run dev',
19
+ url: 'http://localhost:3000',
20
+ reuseExistingServer: !process.env.CI,
21
+ },
22
+ });
@@ -0,0 +1,12 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ describe('{{PROJECT_NAME_PASCAL}}', () => {
4
+ it('should pass a basic test', () => {
5
+ expect(1 + 1).toBe(2);
6
+ });
7
+
8
+ it('should have the project name defined', () => {
9
+ const projectName = '{{PROJECT_NAME}}';
10
+ expect(projectName).toBeTruthy();
11
+ });
12
+ });