chore: 初始化 monorepo 项目脚手架
- 配置 pnpm workspace + Turborepo - 创建 Next.js 15 前端应用 (apps/web) - 创建 NestJS 10 后端应用 (apps/api) - 集成 Prisma ORM + Swagger + JWT 认证 - 添加共享包 (packages/shared, eslint-config, typescript-config) - 添加项目文档 (README, CLAUDE.md, docs/design.md) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
42
packages/eslint-config/index.js
Normal file
42
packages/eslint-config/index.js
Normal file
@@ -0,0 +1,42 @@
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'import'],
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:import/recommended',
|
||||
'plugin:import/typescript',
|
||||
'prettier',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'warn',
|
||||
{ argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
|
||||
],
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
|
||||
'newlines-between': 'always',
|
||||
alphabetize: { order: 'asc', caseInsensitive: true },
|
||||
},
|
||||
],
|
||||
'import/no-unresolved': 'off',
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
alwaysTryTypes: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
ignorePatterns: ['node_modules/', 'dist/', '.next/', '.turbo/'],
|
||||
};
|
||||
13
packages/eslint-config/nest.js
Normal file
13
packages/eslint-config/nest.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
module.exports = {
|
||||
extends: ['./index.js'],
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
},
|
||||
};
|
||||
8
packages/eslint-config/next.js
Normal file
8
packages/eslint-config/next.js
Normal file
@@ -0,0 +1,8 @@
|
||||
/** @type {import("eslint").Linter.Config} */
|
||||
module.exports = {
|
||||
extends: ['./index.js', 'next/core-web-vitals'],
|
||||
rules: {
|
||||
'@next/next/no-html-link-for-pages': 'off',
|
||||
'react/jsx-key': 'error',
|
||||
},
|
||||
};
|
||||
23
packages/eslint-config/package.json
Normal file
23
packages/eslint-config/package.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@seclusion/eslint-config",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
"index.js",
|
||||
"next.js",
|
||||
"nest.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^8.18.2",
|
||||
"@typescript-eslint/parser": "^8.18.2",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-import": "^2.31.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.0.0 || ^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.57.0"
|
||||
}
|
||||
}
|
||||
37
packages/shared/package.json
Normal file
37
packages/shared/package.json
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@seclusion/shared",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"./types": {
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"import": "./dist/types/index.mjs",
|
||||
"require": "./dist/types/index.js"
|
||||
},
|
||||
"./utils": {
|
||||
"types": "./dist/utils/index.d.ts",
|
||||
"import": "./dist/utils/index.mjs",
|
||||
"require": "./dist/utils/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"dev": "tsup --watch",
|
||||
"clean": "rm -rf dist",
|
||||
"lint": "eslint src --ext .ts,.tsx"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@seclusion/eslint-config": "workspace:*",
|
||||
"@seclusion/typescript-config": "workspace:*",
|
||||
"tsup": "^8.3.5",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
5
packages/shared/src/index.ts
Normal file
5
packages/shared/src/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// 导出所有类型
|
||||
export * from './types';
|
||||
|
||||
// 导出所有工具函数
|
||||
export * from './utils';
|
||||
61
packages/shared/src/types/index.ts
Normal file
61
packages/shared/src/types/index.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
// 通用 API 响应类型
|
||||
export interface ApiResponse<T = unknown> {
|
||||
success: boolean;
|
||||
data?: T;
|
||||
message?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
// 分页请求参数
|
||||
export interface PaginationParams {
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
sortBy?: string;
|
||||
sortOrder?: 'asc' | 'desc';
|
||||
}
|
||||
|
||||
// 分页响应
|
||||
export interface PaginatedResponse<T> {
|
||||
items: T[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
// 用户基础类型
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
// 用户创建请求
|
||||
export interface CreateUserDto {
|
||||
email: string;
|
||||
password: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
// 用户登录请求
|
||||
export interface LoginDto {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
// 认证响应
|
||||
export interface AuthResponse {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
user: Omit<User, 'createdAt' | 'updatedAt'>;
|
||||
}
|
||||
|
||||
// Token 载荷
|
||||
export interface TokenPayload {
|
||||
sub: string;
|
||||
email: string;
|
||||
iat?: number;
|
||||
exp?: number;
|
||||
}
|
||||
75
packages/shared/src/utils/index.ts
Normal file
75
packages/shared/src/utils/index.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
// 日期格式化
|
||||
export function formatDate(date: Date | string, format = 'YYYY-MM-DD'): string {
|
||||
const d = typeof date === 'string' ? new Date(date) : date;
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
const hours = String(d.getHours()).padStart(2, '0');
|
||||
const minutes = String(d.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(d.getSeconds()).padStart(2, '0');
|
||||
|
||||
return format
|
||||
.replace('YYYY', String(year))
|
||||
.replace('MM', month)
|
||||
.replace('DD', day)
|
||||
.replace('HH', hours)
|
||||
.replace('mm', minutes)
|
||||
.replace('ss', seconds);
|
||||
}
|
||||
|
||||
// 延迟函数
|
||||
export function delay(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
// 生成随机 ID
|
||||
export function generateId(length = 12): string {
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let result = '';
|
||||
for (let i = 0; i < length; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// 深拷贝
|
||||
export function deepClone<T>(obj: T): T {
|
||||
if (obj === null || typeof obj !== 'object') {
|
||||
return obj;
|
||||
}
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
}
|
||||
|
||||
// 判断是否为空对象
|
||||
export function isEmpty(obj: unknown): boolean {
|
||||
if (obj === null || obj === undefined) return true;
|
||||
if (typeof obj === 'string') return obj.trim().length === 0;
|
||||
if (Array.isArray(obj)) return obj.length === 0;
|
||||
if (typeof obj === 'object') return Object.keys(obj).length === 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 安全解析 JSON
|
||||
export function safeJsonParse<T>(json: string, fallback: T): T {
|
||||
try {
|
||||
return JSON.parse(json) as T;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
// 首字母大写
|
||||
export function capitalize(str: string): string {
|
||||
if (!str) return '';
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
// 驼峰转短横线
|
||||
export function camelToKebab(str: string): string {
|
||||
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
||||
}
|
||||
|
||||
// 短横线转驼峰
|
||||
export function kebabToCamel(str: string): string {
|
||||
return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||
}
|
||||
9
packages/shared/tsconfig.json
Normal file
9
packages/shared/tsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "@seclusion/typescript-config/base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
13
packages/shared/tsup.config.ts
Normal file
13
packages/shared/tsup.config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: {
|
||||
index: 'src/index.ts',
|
||||
'types/index': 'src/types/index.ts',
|
||||
'utils/index': 'src/utils/index.ts',
|
||||
},
|
||||
format: ['cjs', 'esm'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
sourcemap: true,
|
||||
});
|
||||
26
packages/typescript-config/base.json
Normal file
26
packages/typescript-config/base.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2022"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist", ".next", ".turbo"]
|
||||
}
|
||||
14
packages/typescript-config/nestjs.json
Normal file
14
packages/typescript-config/nestjs.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"target": "ES2022",
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true
|
||||
}
|
||||
}
|
||||
18
packages/typescript-config/nextjs.json
Normal file
18
packages/typescript-config/nextjs.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "./base.json",
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
||||
"jsx": "preserve",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowJs": true,
|
||||
"noEmit": true,
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
14
packages/typescript-config/package.json
Normal file
14
packages/typescript-config/package.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "@seclusion/typescript-config",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"files": [
|
||||
"base.json",
|
||||
"nextjs.json",
|
||||
"nestjs.json"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user