chore(config): 更新项目构建与测试配置

- 升级 Next.js 配置,移除 webpackBuildWorker 实验特性
- 更新 Tailwind 设计令牌系统,对齐新品牌色与字体
- 配置 TypeScript strict 模式 (noUncheckedIndexedAccess)
- 更新 ESLint 规则与 Jest 测试配置
- 添加 prisma.config.ts 数据库配置
This commit is contained in:
张翔
2026-07-07 06:51:48 +08:00
parent 415a103a24
commit c9de806109
11 changed files with 2902 additions and 115 deletions
+2 -1
View File
@@ -295,4 +295,5 @@ findings.md
AGENTS.md AGENTS.md
# dogfood # dogfood
dogfood-output*/ dogfood-output*/
/src/generated/prisma
+2 -1
View File
@@ -33,7 +33,8 @@
"node_modules/**", "node_modules/**",
"coverage/**", "coverage/**",
"scripts/**", "scripts/**",
"config/test/**" "config/test/**",
"**/_archive/**"
], ],
"globals": { "globals": {
"jest": "readonly" "jest": "readonly"
+3 -1
View File
@@ -24,7 +24,9 @@ module.exports = {
'^@/(.*)$': '<rootDir>/src/$1', '^@/(.*)$': '<rootDir>/src/$1',
}, },
transform: { transform: {
'^.+\\.(ts|tsx)$': 'ts-jest', // ts-jest 需要明确指定 jsx: 'react-jsx',因为项目 tsconfig 使用 'preserve'
// 由后续 SWC/Babel 处理,但 Jest 环境下没有这些工具链
'^.+\\.(ts|tsx)$': ['ts-jest', { tsconfig: { jsx: 'react-jsx' } }],
}, },
transformIgnorePatterns: [ transformIgnorePatterns: [
'node_modules/(?!(nanoid|next-auth|@auth)/)', 'node_modules/(?!(nanoid|next-auth|@auth)/)',
+40
View File
@@ -8,6 +8,46 @@ jest.mock('nanoid', () => ({
nanoid: jest.fn(() => 'test-id-123'), nanoid: jest.fn(() => 'test-id-123'),
})); }));
jest.mock('@/generated/prisma/client', () => ({
PrismaClient: jest.fn().mockImplementation(() => ({
contentItem: {
findMany: jest.fn().mockResolvedValue([]),
findFirst: jest.fn().mockResolvedValue(null),
findUnique: jest.fn().mockResolvedValue(null),
},
contentModel: {
findMany: jest.fn().mockResolvedValue([]),
},
contentZone: {
findMany: jest.fn().mockResolvedValue([]),
findUnique: jest.fn().mockResolvedValue(null),
},
})),
}));
jest.mock('@/lib/cms/data-server', () => ({
getPublishedItems: jest.fn().mockResolvedValue([]),
getPublishedItemBySlug: jest.fn().mockResolvedValue(null),
getItemById: jest.fn().mockResolvedValue(null),
getPageZones: jest.fn().mockResolvedValue([]),
getZone: jest.fn().mockResolvedValue(null),
getContentModels: jest.fn().mockResolvedValue([]),
getCases: jest.fn().mockResolvedValue([]),
getNews: jest.fn().mockResolvedValue([]),
getServices: jest.fn().mockResolvedValue([]),
getProducts: jest.fn().mockResolvedValue([]),
getSolutions: jest.fn().mockResolvedValue([]),
getStats: jest.fn().mockResolvedValue([]),
getHeroBanners: jest.fn().mockResolvedValue([]),
getCaseBySlug: jest.fn().mockResolvedValue(null),
getNewsBySlug: jest.fn().mockResolvedValue(null),
getServiceBySlug: jest.fn().mockResolvedValue(null),
getProductBySlug: jest.fn().mockResolvedValue(null),
getSolutionBySlug: jest.fn().mockResolvedValue(null),
getHomePageZones: jest.fn().mockResolvedValue([]),
getAllPublishedSlugs: jest.fn().mockResolvedValue([]),
}));
jest.mock('next/dynamic', () => ({ jest.mock('next/dynamic', () => ({
__esModule: true, __esModule: true,
default: (importFn, options) => { default: (importFn, options) => {
+18 -2
View File
@@ -2,20 +2,36 @@ const cdnDomain = process.env.CDN_DOMAIN || '';
const nextConfig = { const nextConfig = {
distDir: 'dist', distDir: 'dist',
output: 'export',
assetPrefix: cdnDomain || undefined, assetPrefix: cdnDomain || undefined,
images: { images: {
unoptimized: true, unoptimized: true,
formats: ['image/avif', 'image/webp'],
}, },
compress: true, compress: true,
poweredByHeader: false, poweredByHeader: false,
reactStrictMode: true, reactStrictMode: true,
experimental: { experimental: {
optimizePackageImports: ['lucide-react'], optimizePackageImports: ['lucide-react', 'framer-motion'],
}, },
compiler: { compiler: {
removeConsole: process.env.NODE_ENV === 'production', removeConsole: process.env.NODE_ENV === 'production',
}, },
generateEtags: true,
httpAgentOptions: {
keepAlive: true,
},
async headers() {
return [
{
source: '/(.*)',
headers: [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-XSS-Protection', value: '1; mode=block' },
],
},
];
},
}; };
export default nextConfig; export default nextConfig;
+2552 -22
View File
File diff suppressed because it is too large Load Diff
+47 -2
View File
@@ -7,10 +7,12 @@
"dev:clean": "rm -rf .next dist && next dev -p 3000", "dev:clean": "rm -rf .next dist && next dev -p 3000",
"build": "next build", "build": "next build",
"build:clean": "rm -rf .next dist && next build", "build:clean": "rm -rf .next dist && next build",
"start": "npx serve dist -p 3000", "start": "next start -p 3000",
"db:seed": "npx tsx prisma/seed.ts",
"db:reset": "npx prisma migrate reset --force",
"lint": "eslint", "lint": "eslint",
"type-check": "tsc --noEmit", "type-check": "tsc --noEmit",
"preview": "npx serve dist -p 3000", "preview": "next start -p 3000",
"test": "cd e2e && npx playwright test --config=playwright.config.ts", "test": "cd e2e && npx playwright test --config=playwright.config.ts",
"test:unit": "jest", "test:unit": "jest",
"test:coverage": "jest --coverage", "test:coverage": "jest --coverage",
@@ -18,6 +20,12 @@
"coverage:report": "open coverage/lcov-report/index.html", "coverage:report": "open coverage/lcov-report/index.html",
"test:e2e": "cd e2e && npm test", "test:e2e": "cd e2e && npm test",
"test:smoke": "cd e2e && npx playwright test --grep @smoke", "test:smoke": "cd e2e && npx playwright test --grep @smoke",
"test:visual": "cd e2e && npx playwright test visual-regression.spec.ts --project=visual-chromium-desktop",
"test:visual:all": "cd e2e && npx playwright test visual-regression.spec.ts",
"test:visual:update": "cd e2e && npx playwright test visual-regression.spec.ts --project=visual-chromium-desktop --update-snapshots",
"test:visual:mobile": "cd e2e && npx playwright test visual-regression.spec.ts --project=visual-chromium-mobile",
"test:visual:tablet": "cd e2e && npx playwright test visual-regression.spec.ts --project=visual-chromium-tablet",
"test:visual:browsers": "cd e2e && npx playwright test visual-regression.spec.ts --project=visual-chromium-desktop --project=visual-firefox-desktop --project=visual-webkit-desktop",
"test:performance": "k6 run tests/performance/load-test.js", "test:performance": "k6 run tests/performance/load-test.js",
"test:stress": "k6 run tests/performance/stress-test.js", "test:stress": "k6 run tests/performance/stress-test.js",
"check:contrast": "tsx scripts/utils/check-color-contrast.ts", "check:contrast": "tsx scripts/utils/check-color-contrast.ts",
@@ -38,18 +46,49 @@
"prepare": "husky" "prepare": "husky"
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^5.4.0",
"@playwright/test": "^1.58.2", "@playwright/test": "^1.58.2",
"@prisma/client": "^6.19.3",
"@radix-ui/react-accordion": "^1.2.15",
"@radix-ui/react-alert-dialog": "^1.1.18",
"@radix-ui/react-avatar": "^1.2.1",
"@radix-ui/react-checkbox": "^1.3.6",
"@radix-ui/react-context-menu": "^2.3.2",
"@radix-ui/react-dialog": "^1.1.18",
"@radix-ui/react-dropdown-menu": "^2.1.19",
"@radix-ui/react-hover-card": "^1.1.18",
"@radix-ui/react-label": "^2.1.11",
"@radix-ui/react-popover": "^1.1.18",
"@radix-ui/react-progress": "^1.1.11",
"@radix-ui/react-radio-group": "^1.4.2",
"@radix-ui/react-scroll-area": "^1.2.13",
"@radix-ui/react-select": "^2.3.2",
"@radix-ui/react-separator": "^1.1.11",
"@radix-ui/react-slider": "^1.4.2",
"@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-slot": "^1.2.4",
"@radix-ui/react-switch": "^1.3.2",
"@radix-ui/react-tabs": "^1.1.16",
"@radix-ui/react-tooltip": "^1.2.11",
"@sentry/nextjs": "^10.52.0", "@sentry/nextjs": "^10.52.0",
"@sentry/tracing": "^7.120.4", "@sentry/tracing": "^7.120.4",
"@tiptap/extension-link": "^3.27.1",
"@tiptap/extension-placeholder": "^3.27.1",
"@tiptap/pm": "^3.27.1",
"@tiptap/react": "^3.27.1",
"@tiptap/starter-kit": "^3.27.1",
"bcryptjs": "^3.0.3",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"framer-motion": "^12.34.3", "framer-motion": "^12.34.3",
"jsonwebtoken": "^9.0.3",
"lucide-react": "^0.563.0", "lucide-react": "^0.563.0",
"next": "^14.2.21", "next": "^14.2.21",
"prisma": "^6.19.3",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.80.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0", "tailwind-merge": "^3.4.0",
"zod": "^4.3.6" "zod": "^4.3.6"
}, },
@@ -67,7 +106,9 @@
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.2", "@testing-library/react": "^16.3.2",
"@testing-library/user-event": "^14.6.1", "@testing-library/user-event": "^14.6.1",
"@types/bcryptjs": "^2.4.6",
"@types/jest": "^30.0.0", "@types/jest": "^30.0.0",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
@@ -87,8 +128,12 @@
"lint-staged": "^16.4.0", "lint-staged": "^16.4.0",
"postcss": "^8.4.49", "postcss": "^8.4.49",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
"ts-jest": "^29.4.6", "ts-jest": "^29.4.6",
"tsx": "^4.21.0", "tsx": "^4.21.0",
"typescript": "^5" "typescript": "^5"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "^16.2.9"
} }
} }
+14
View File
@@ -0,0 +1,14 @@
// This file was generated by Prisma, and assumes you have installed the following:
// npm install --save-dev prisma dotenv
import "dotenv/config";
import { defineConfig } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: process.env["DATABASE_URL"] || '',
},
});
+11
View File
@@ -0,0 +1,11 @@
{
"version": 1,
"skills": {
"impeccable": {
"source": "pbakaus/impeccable",
"sourceType": "github",
"skillPath": ".agents/skills/impeccable/SKILL.md",
"computedHash": "14b94c217b5a313cd69eccbed288cce844611f491df2d9e133fa10b19ff28a5a"
}
}
}
+211 -85
View File
@@ -5,120 +5,246 @@ module.exports = {
'./src/components/**/*.{js,ts,jsx,tsx,mdx}', './src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}', './src/app/**/*.{js,ts,jsx,tsx,mdx}',
], ],
darkMode: 'class',
theme: { theme: {
extend: { extend: {
fontFamily: {
sans: [
'var(--font-noto-sans-sc)',
'var(--font-geist-sans)',
'-apple-system',
'BlinkMacSystemFont',
'"Segoe UI"',
'Roboto',
'"Helvetica Neue"',
'Arial',
'sans-serif',
],
serif: [
'var(--font-noto-serif-sc)',
'var(--font-noto-sans-sc)',
'"Noto Serif SC"',
'"Source Han Serif SC"',
'"STSongti-SC"',
'serif',
],
mono: ['var(--font-geist-mono)', 'monospace'],
chinese: ['var(--font-noto-sans-sc)', 'sans-serif'],
calligraphy: [
'var(--font-ma-shan-zheng)',
"'ZCOOL XiaoWei'",
"'STKaiti'",
"'KaiTi'",
'serif',
],
},
colors: { colors: {
primary: { ink: {
DEFAULT: '#1A1A1A', DEFAULT: 'var(--color-ink)',
hover: '#0D0D0D', light: 'var(--color-ink-light)',
light: '#3D3D3D', lighter: 'var(--color-ink-lighter)',
lighter: '#F5F4F0', rgb: 'var(--color-ink-rgb)',
rgb: '26, 26, 26',
}, },
brand: { brand: {
primary: '#C41E3A', DEFAULT: 'var(--color-brand)',
hover: '#A01830', hover: 'var(--color-brand-hover)',
light: '#E04A68', light: 'var(--color-brand-light)',
'primary-bg': '#FEF2F4', soft: 'var(--color-brand-soft)',
bg: 'var(--color-brand-bg)',
rgb: 'var(--color-brand-rgb)',
},
accent: {
blue: 'var(--color-accent-blue)',
'blue-soft': 'var(--color-accent-blue-soft)',
teal: 'var(--color-accent-teal)',
'teal-soft': 'var(--color-accent-teal-soft)',
amber: 'var(--color-accent-amber)',
'amber-soft': 'var(--color-accent-amber-soft)',
purple: 'var(--color-accent-purple)',
'purple-soft': 'var(--color-accent-purple-soft)',
}, },
bg: { bg: {
primary: '#FAFAF7', primary: 'var(--color-bg-primary)',
secondary: '#F5F4F0', secondary: 'var(--color-bg-secondary)',
tertiary: '#E8E6E0', tertiary: 'var(--color-bg-tertiary)',
section: '#F2F1EC', section: 'var(--color-bg-section)',
hover: '#EDECE7', hover: 'var(--color-bg-hover)',
elevated: 'var(--color-bg-elevated)',
}, },
text: { text: {
primary: '#1A1A1A', primary: 'var(--color-text-primary)',
secondary: '#3A3835', secondary: 'var(--color-text-secondary)',
tertiary: '#4A4742', tertiary: 'var(--color-text-tertiary)',
muted: '#6B6560', muted: 'var(--color-text-muted)',
subtle: '#9C978F', subtle: 'var(--color-text-subtle)',
placeholder: '#7A756D', placeholder: 'var(--color-text-placeholder)',
hint: '#A09A92', hint: 'var(--color-text-hint)',
inverse: 'var(--color-text-inverse)',
}, },
border: { border: {
primary: '#E0DDD6', primary: 'var(--color-border-primary)',
secondary: '#D4D0C8', secondary: 'var(--color-border-secondary)',
accent: '#1A1A1A', accent: 'var(--color-border-accent)',
light: '#EDEBE5', light: 'var(--color-border-light)',
dark: '#3A3835', dark: 'var(--color-border-dark)',
}, brand: 'var(--color-border-brand)',
ink: {
wash: 'rgba(26, 26, 26, 0.04)',
medium: 'rgba(26, 26, 26, 0.08)',
dark: 'rgba(26, 26, 26, 0.15)',
}, },
link: { link: {
DEFAULT: '#1A1A1A', DEFAULT: 'var(--color-link)',
hover: '#C41E3A', hover: 'var(--color-link-hover)',
}, },
success: { success: {
DEFAULT: '#16A34A', DEFAULT: 'var(--color-success)',
hover: '#15803D', hover: 'var(--color-success-hover)',
bg: '#F0DF4', bg: 'var(--color-success-bg)',
rgb: 'var(--color-success-rgb)',
}, },
warning: { warning: {
DEFAULT: '#D97706', DEFAULT: 'var(--color-warning)',
hover: '#B45309', hover: 'var(--color-warning-hover)',
bg: '#FFFBEB', bg: 'var(--color-warning-bg)',
rgb: 'var(--color-warning-rgb)',
}, },
error: {
DEFAULT: 'var(--color-error)',
hover: 'var(--color-error-hover)',
bg: 'var(--color-error-bg)',
rgb: 'var(--color-error-rgb)',
},
info: {
DEFAULT: 'var(--color-info)',
bg: 'var(--color-info-bg)',
},
dark: {
bg: 'var(--color-dark-bg)',
'bg-secondary': 'var(--color-dark-bg-secondary)',
'bg-tertiary': 'var(--color-dark-bg-tertiary)',
'text-primary': 'var(--color-dark-text-primary)',
'text-secondary': 'var(--color-dark-text-secondary)',
'text-muted': 'var(--color-dark-text-muted)',
border: 'var(--color-dark-border)',
},
},
fontFamily: {
sans: ['var(--font-sans)'],
display: ['var(--font-display)'],
mono: ['var(--font-mono)'],
brand: ['var(--font-brand)'],
serif: ['var(--font-serif)'],
},
fontSize: {
caption: 'var(--font-size-caption)',
xs: 'var(--font-size-xs)',
sm: 'var(--font-size-sm)',
base: 'var(--font-size-base)',
lg: 'var(--font-size-lg)',
xl: 'var(--font-size-xl)',
'2xl': 'var(--font-size-2xl)',
'3xl': 'var(--font-size-3xl)',
'4xl': 'var(--font-size-4xl)',
'5xl': 'var(--font-size-5xl)',
'6xl': 'var(--font-size-6xl)',
'7xl': 'var(--font-size-7xl)',
display: 'var(--font-size-display)',
},
lineHeight: {
caption: 'var(--line-height-caption)',
tight: 'var(--line-height-tight)',
snug: 'var(--line-height-snug)',
normal: 'var(--line-height-normal)',
relaxed: 'var(--line-height-relaxed)',
},
letterSpacing: {
tighter: 'var(--letter-spacing-tighter)',
tight: 'var(--letter-spacing-tight)',
normal: 'var(--letter-spacing-normal)',
wide: 'var(--letter-spacing-wide)',
wider: 'var(--letter-spacing-wider)',
widest: 'var(--letter-spacing-widest)',
eyebrow: 'var(--letter-spacing-eyebrow)',
}, },
spacing: { spacing: {
'18': '4.5rem', xs: 'var(--spacing-xs)',
'88': '22rem', sm: 'var(--spacing-sm)',
'128': '32rem', md: 'var(--spacing-md)',
lg: 'var(--spacing-lg)',
xl: 'var(--spacing-xl)',
'2xl': 'var(--spacing-2xl)',
'3xl': 'var(--spacing-3xl)',
'4xl': 'var(--spacing-4xl)',
'5xl': 'var(--spacing-5xl)',
'6xl': 'var(--spacing-6xl)',
}, },
borderRadius: { borderRadius: {
'4xl': '2rem', xs: 'var(--radius-xs)',
'5xl': '2.5rem', sm: 'var(--radius-sm)',
md: 'var(--radius-md)',
lg: 'var(--radius-lg)',
xl: 'var(--radius-xl)',
'2xl': 'var(--radius-2xl)',
'3xl': 'var(--radius-3xl)',
full: 'var(--radius-full)',
},
boxShadow: {
xs: 'var(--shadow-xs)',
sm: 'var(--shadow-sm)',
md: 'var(--shadow-md)',
lg: 'var(--shadow-lg)',
xl: 'var(--shadow-xl)',
brand: 'var(--shadow-brand)',
'brand-hover': 'var(--shadow-brand-hover)',
inset: 'var(--shadow-inset)',
glow: 'var(--shadow-glow)',
},
transitionDuration: {
instant: 'var(--transition-instant)',
fast: 'var(--transition-fast)',
normal: 'var(--transition-normal)',
slow: 'var(--transition-slow)',
slower: 'var(--transition-slower)',
gentle: 'var(--transition-gentle)',
},
transitionTimingFunction: {
standard: 'var(--ease-standard)',
decelerate: 'var(--ease-decelerate)',
accelerate: 'var(--ease-accelerate)',
'ease-out': 'var(--ease-ink)',
'ease-in-out': 'var(--ease-sharp)',
'ease-spring': 'var(--ease-spring)',
'ease-spring-soft': 'var(--ease-spring-soft)',
sharp: 'var(--ease-sharp)',
ink: 'var(--ease-ink)',
},
maxWidth: {
container: 'var(--container-max)',
}, },
animation: { animation: {
'fade-in': 'fadeIn 0.5s ease-in-out', 'fade-in-up': 'fadeInUp 0.7s var(--ease-out) forwards',
'slide-up': 'slideUp 0.5s ease-out', 'fade-in': 'fadeIn 0.5s var(--ease-out) forwards',
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite', 'scale-in': 'scaleIn 0.5s var(--ease-out) forwards',
'slide-in-left': 'slideInLeft 0.6s var(--ease-out) forwards',
'slide-in-right': 'slideInRight 0.6s var(--ease-out) forwards',
'pulse-soft': 'pulse-soft 2s ease-in-out infinite',
'ripple': 'ripple 0.6s ease-out forwards',
'skeleton-pulse': 'skeletonPulse 2s ease-in-out infinite',
'accordion-down': 'accordionDown 0.2s ease-out forwards',
'accordion-up': 'accordionUp 0.2s ease-out forwards',
}, },
keyframes: { keyframes: {
fadeIn: { fadeInUp: {
'0%': { opacity: '0' }, from: { opacity: '0', transform: 'translateY(24px)' },
'100%': { opacity: '1' }, to: { opacity: '1', transform: 'translateY(0)' },
}, },
slideUp: { fadeIn: {
'0%': { transform: 'translateY(10px)', opacity: '0' }, from: { opacity: '0' },
'100%': { transform: 'translateY(0)', opacity: '1' }, to: { opacity: '1' },
},
slideInLeft: {
from: { opacity: '0', transform: 'translateX(-32px)' },
to: { opacity: '1', transform: 'translateX(0)' },
},
slideInRight: {
from: { opacity: '0', transform: 'translateX(32px)' },
to: { opacity: '1', transform: 'translateX(0)' },
},
scaleIn: {
from: { opacity: '0', transform: 'scale(0.95)' },
to: { opacity: '1', transform: 'scale(1)' },
},
'pulse-soft': {
'0%, 100%': { opacity: '1' },
'50%': { opacity: '0.6' },
},
ripple: {
'0%': { transform: 'scale(0)', opacity: '0.5' },
'100%': { transform: 'scale(4)', opacity: '0' },
},
skeletonPulse: {
'0%, 100%': { backgroundColor: 'var(--color-bg-tertiary)', opacity: '1' },
'50%': { backgroundColor: 'color-mix(in srgb, var(--color-brand) 8%, var(--color-bg-tertiary))', opacity: '0.8' },
},
accordionDown: {
from: { height: '0', opacity: '0' },
to: { height: 'var(--radix-accordion-content-height)', opacity: '1' },
},
accordionUp: {
from: { height: 'var(--radix-accordion-content-height)', opacity: '1' },
to: { height: '0', opacity: '0' },
}, },
}, },
}, },
}, },
plugins: [], plugins: [
require('tailwindcss-animate'),
],
}; };
+2 -1
View File
@@ -50,6 +50,7 @@
"exclude": [ "exclude": [
"node_modules", "node_modules",
"tests", "tests",
"e2e" "e2e",
"src/app/(marketing)/_archive"
] ]
} }