feat: 添加质量门禁工具链 + 类型安全强化
- 新增 quality-gate.sh 质量门禁脚本(tsc/eslint/build/any 计数) - ESLint 新增代码目录 any 规则升级为 error(generator/runtime/skills/memory/providers/vendor) - 修复新增代码中 30+ 处 any 类型(unknown 替代、具体类型定义) - 新增 9 个 NestJS 自定义参数装饰器(@CurrentUser/@SiteId/@MemberId/@ReqId/@CurrentLang/@CurrentChannel/@ClientIp/@UserAgent/@SiteIdStr) - RequestContextService 导出 RequestContextStore 接口和 REQUEST_CONTEXT_KEY 常量 - 请求中间件绑定上下文到 req 对象供装饰器使用 - 配置 husky + lint-staged pre-commit hook - 新增 npm scripts: quality-gate / quality-gate:tsc / quality-gate:lint / quality-gate:build / quality-gate:any - 新增代码目录 ESLint: 0 errors, 37 warnings - tsc --noEmit: 0 errors
This commit is contained in:
1
wwjcloud-nest-v1/.husky/pre-commit
Normal file
1
wwjcloud-nest-v1/.husky/pre-commit
Normal file
@@ -0,0 +1 @@
|
||||
npm test
|
||||
@@ -3,5 +3,8 @@
|
||||
"@types/node": "^24.10.0",
|
||||
"glob": "^11.0.3",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "husky"
|
||||
}
|
||||
}
|
||||
|
||||
6
wwjcloud-nest-v1/wwjcloud/.husky/pre-commit
Normal file
6
wwjcloud-nest-v1/wwjcloud/.husky/pre-commit
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
# 质量门禁 pre-commit hook
|
||||
# 仅对暂存区的 TypeScript 文件执行 lint-staged
|
||||
npx lint-staged
|
||||
@@ -108,6 +108,50 @@ export default tseslint.config(
|
||||
'no-empty': 'warn',
|
||||
},
|
||||
},
|
||||
// ========================================================================
|
||||
// 新增代码(generator/runtime/skills/memory/providers/vendor-gateway)
|
||||
// any 相关规则升级为 error — 严格类型安全
|
||||
// ========================================================================
|
||||
{
|
||||
files: [
|
||||
'libs/wwjcloud-ai/src/generator/**/*.ts',
|
||||
'libs/wwjcloud-ai/src/runtime/**/*.ts',
|
||||
'libs/wwjcloud-ai/src/skills/**/*.ts',
|
||||
'libs/wwjcloud-ai/src/memory/**/*.ts',
|
||||
'libs/wwjcloud-ai/src/providers/**/*.ts',
|
||||
'libs/wwjcloud-boot/src/vendor/gateway/**/*.ts',
|
||||
'libs/wwjcloud-boot/src/vendor/registry/**/*.ts',
|
||||
'libs/wwjcloud-boot/src/vendor/interfaces/**/*.ts',
|
||||
'libs/wwjcloud-boot/src/vendor/errors/**/*.ts',
|
||||
'libs/wwjcloud-boot/src/vendor/provider-factories/**/*.ts',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'error',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'error',
|
||||
'@typescript-eslint/no-unsafe-call': 'error',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'error',
|
||||
'@typescript-eslint/no-unsafe-return': 'error',
|
||||
'@typescript-eslint/no-unsafe-argument': 'error',
|
||||
'@typescript-eslint/no-unsafe-enum-comparison': 'error',
|
||||
'@typescript-eslint/no-base-to-string': 'error',
|
||||
'@typescript-eslint/restrict-template-expressions': 'error',
|
||||
'@typescript-eslint/no-redundant-type-constituents': 'error',
|
||||
'@typescript-eslint/await-thenable': 'error',
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-misused-promises': 'error',
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/require-await': 'error',
|
||||
'@typescript-eslint/ban-ts-comment': 'error',
|
||||
'@typescript-eslint/no-require-imports': 'error',
|
||||
'no-case-declarations': 'error',
|
||||
'no-empty': 'error',
|
||||
'no-useless-catch': 'error',
|
||||
'no-useless-escape': 'error',
|
||||
},
|
||||
},
|
||||
// ========================================================================
|
||||
// 全局默认规则(兜底 — 旧代码为 warn 级别)
|
||||
// ========================================================================
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
|
||||
@@ -84,6 +84,7 @@ ${methodsCode}
|
||||
);
|
||||
const params = this.extractParams(method);
|
||||
const paramName = this.toCamelCase(method.name);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const returnType = 'Promise<any>';
|
||||
|
||||
return ` /**
|
||||
@@ -130,6 +131,7 @@ ${methodsCode}
|
||||
} else if (method.httpMethod === 'GET') {
|
||||
parts.push(`@Query('${param}') ${param}: string`);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
parts.push(`@Body('${param}') ${param}: any`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* 自定义参数装饰器模块
|
||||
*
|
||||
* 充分发挥 NestJS 自定义参数装饰器特性,从 RequestContextService (AsyncLocalStorage)
|
||||
* 中提取请求上下文信息,避免控制器中手动注入 RequestContextService。
|
||||
*
|
||||
* 使用方式:
|
||||
* @CurrentUser() user: UserClaims
|
||||
* @SiteId() siteId: number
|
||||
* @MemberId() memberId: number
|
||||
* @ReqId() requestId: string
|
||||
* @CurrentLang() lang: string
|
||||
* @CurrentChannel() channel: string
|
||||
* @ClientIp() ip: string
|
||||
* @UserAgent() ua: string
|
||||
*/
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
import {
|
||||
REQUEST_CONTEXT_KEY,
|
||||
type RequestContextStore,
|
||||
} from './request-context.service';
|
||||
|
||||
/**
|
||||
* 从 Express Request 对象中提取请求上下文
|
||||
*/
|
||||
function getStore(
|
||||
ctx: ExecutionContext,
|
||||
): RequestContextStore | undefined {
|
||||
const request = ctx.switchToHttp().getRequest();
|
||||
return request[REQUEST_CONTEXT_KEY] as RequestContextStore | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求上下文中提取当前用户信息
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Get('profile')
|
||||
* getProfile(@CurrentUser() user: { userId?: string; username?: string; roles?: string[] }) {
|
||||
* return user;
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const CurrentUser = createParamDecorator(
|
||||
(_data: unknown, ctx: ExecutionContext) => {
|
||||
const rcs = getStore(ctx);
|
||||
if (!rcs) return undefined;
|
||||
return {
|
||||
userId: rcs.userId,
|
||||
username: rcs.username,
|
||||
roles: rcs.roles,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 从请求上下文中提取当前站点ID(数值类型)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Get('config')
|
||||
* getConfig(@SiteId() siteId: number) {
|
||||
* return this.service.getConfig(siteId);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const SiteId = createParamDecorator(
|
||||
(_data: unknown, ctx: ExecutionContext): number => {
|
||||
const rcs = getStore(ctx);
|
||||
return rcs?.siteId ? Number(rcs.siteId) : 0;
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 从请求上下文中提取当前站点ID(字符串类型)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Get('info')
|
||||
* getInfo(@SiteIdStr() siteId: string) {
|
||||
* return this.service.getInfo(siteId);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const SiteIdStr = createParamDecorator(
|
||||
(_data: unknown, ctx: ExecutionContext): string | undefined => {
|
||||
const rcs = getStore(ctx);
|
||||
return rcs?.siteId;
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 从请求上下文中提取当前会员ID
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Get('orders')
|
||||
* getOrders(@MemberId() memberId: number) {
|
||||
* return this.orderService.getByMember(memberId);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const MemberId = createParamDecorator(
|
||||
(_data: unknown, ctx: ExecutionContext): number | undefined => {
|
||||
const rcs = getStore(ctx);
|
||||
return rcs?.memberId;
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 从请求上下文中提取请求ID(链路追踪)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Post('submit')
|
||||
* submit(@ReqId() requestId: string, @Body() dto: SubmitDto) {
|
||||
* this.logger.log(`[${requestId}] Processing submission`);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const ReqId = createParamDecorator(
|
||||
(_data: unknown, ctx: ExecutionContext): string | undefined => {
|
||||
const rcs = getStore(ctx);
|
||||
return rcs?.requestId;
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 从请求上下文中提取当前语言
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Get('content')
|
||||
* getContent(@CurrentLang() lang: string) {
|
||||
* return this.i18nService.translate('hello', { lang });
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const CurrentLang = createParamDecorator(
|
||||
(_data: unknown, ctx: ExecutionContext): string | undefined => {
|
||||
const rcs = getStore(ctx);
|
||||
return rcs?.lang;
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 从请求上下文中提取当前渠道(h5/weapp/pc 等)
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Get('page')
|
||||
* getPage(@CurrentChannel() channel: string) {
|
||||
* return this.diyService.getPage(channel);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const CurrentChannel = createParamDecorator(
|
||||
(_data: unknown, ctx: ExecutionContext): string | undefined => {
|
||||
const rcs = getStore(ctx);
|
||||
return rcs?.channel;
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 从请求上下文中提取客户端IP
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Post('login')
|
||||
* login(@ClientIp() ip: string, @Body() dto: LoginDto) {
|
||||
* return this.authService.login(dto, ip);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const ClientIp = createParamDecorator(
|
||||
(_data: unknown, ctx: ExecutionContext): string | undefined => {
|
||||
const rcs = getStore(ctx);
|
||||
return rcs?.ip;
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* 从请求上下文中提取 User-Agent
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* @Get('device')
|
||||
* getDeviceInfo(@UserAgent() ua: string) {
|
||||
* return this.deviceService.parse(ua);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export const UserAgent = createParamDecorator(
|
||||
(_data: unknown, ctx: ExecutionContext): string | undefined => {
|
||||
const rcs = getStore(ctx);
|
||||
return rcs?.ua;
|
||||
},
|
||||
);
|
||||
@@ -41,20 +41,22 @@ export function buildRequestContextMiddleware(ctx: RequestContextService) {
|
||||
undefined;
|
||||
const ua = (req.headers['user-agent'] as string) || undefined;
|
||||
|
||||
ctx.runWith(
|
||||
{
|
||||
requestId: id,
|
||||
siteId,
|
||||
userId,
|
||||
username,
|
||||
roles,
|
||||
lang,
|
||||
channel,
|
||||
appType,
|
||||
ip,
|
||||
ua,
|
||||
},
|
||||
() => next(),
|
||||
);
|
||||
const store = {
|
||||
requestId: id,
|
||||
siteId,
|
||||
userId,
|
||||
username,
|
||||
roles,
|
||||
lang,
|
||||
channel,
|
||||
appType,
|
||||
ip,
|
||||
ua,
|
||||
};
|
||||
|
||||
// 将上下文绑定到 req 对象,供自定义参数装饰器使用
|
||||
ctx.bindToRequest(req as unknown as Record<string, unknown>);
|
||||
|
||||
ctx.runWith(store, () => next());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,7 +2,8 @@ import { Injectable } from '@nestjs/common';
|
||||
import { AsyncLocalStorage } from 'async_hooks';
|
||||
import { ThreadLocalHolder } from '../context/thread-local-holder';
|
||||
|
||||
interface RequestContextStore {
|
||||
/** 请求上下文存储结构 */
|
||||
export interface RequestContextStore {
|
||||
requestId?: string;
|
||||
siteId?: string;
|
||||
memberId?: number;
|
||||
@@ -16,6 +17,11 @@ interface RequestContextStore {
|
||||
ua?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 挂载到 Express Request 对象上的 key,供自定义参数装饰器使用
|
||||
*/
|
||||
export const REQUEST_CONTEXT_KEY = '__wwj_request_context';
|
||||
|
||||
@Injectable()
|
||||
export class RequestContextService {
|
||||
private readonly storage = new AsyncLocalStorage<RequestContextStore>();
|
||||
@@ -27,6 +33,14 @@ export class RequestContextService {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前上下文绑定到 Express Request 对象上
|
||||
* 供自定义参数装饰器(@CurrentUser, @SiteId 等)使用
|
||||
*/
|
||||
bindToRequest(req: Record<string, unknown>): void {
|
||||
req[REQUEST_CONTEXT_KEY] = this.storage.getStore();
|
||||
}
|
||||
|
||||
getContext(): RequestContextStore | undefined {
|
||||
return this.storage.getStore();
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
FactoryProvider,
|
||||
} from '@nestjs/common';
|
||||
|
||||
export interface HandlerProvider<M = any, R = any> {
|
||||
export interface HandlerProvider<M = unknown, R = unknown> {
|
||||
handle(bean: M): R | Promise<R>;
|
||||
}
|
||||
|
||||
@@ -16,14 +16,14 @@ export const DEFAULT_HANDLER_PROVIDER = 'DEFAULT_HANDLER_PROVIDER';
|
||||
export class HandlerProviderFactory {
|
||||
private handlerProviderMap = new Map<
|
||||
string,
|
||||
Set<new (...args: any[]) => HandlerProvider>
|
||||
Set<new (...args: unknown[]) => HandlerProvider>
|
||||
>();
|
||||
private handlerProviderClassMap = new Map<string, Set<string>>();
|
||||
private handlerProviderModelMap = new Map<string, Set<string>>();
|
||||
|
||||
register<T extends HandlerProvider>(
|
||||
name: string,
|
||||
handlerProviderClass: new (...args: any[]) => T,
|
||||
handlerProviderClass: new (...args: unknown[]) => T,
|
||||
modelType?: string,
|
||||
) {
|
||||
// 根据名称注册
|
||||
@@ -48,6 +48,7 @@ export class HandlerProviderFactory {
|
||||
|
||||
// 同步调用处理器
|
||||
async invoke<M, R>(bean: M): Promise<R[]> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const beanType = (bean as any).constructor?.name || 'Unknown';
|
||||
const handlerProviderClassSet = this.handlerProviderMap.get(beanType);
|
||||
const resultList: R[] = [];
|
||||
@@ -57,7 +58,7 @@ export class HandlerProviderFactory {
|
||||
try {
|
||||
const handlerProvider = new HandlerProviderClass();
|
||||
const result = await handlerProvider.handle(bean);
|
||||
resultList.push(result);
|
||||
resultList.push(result as R);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error invoking handler ${HandlerProviderClass.name}:`,
|
||||
@@ -82,7 +83,7 @@ export class HandlerProviderFactory {
|
||||
|
||||
getHandlerProviderMap(): Map<
|
||||
string,
|
||||
Set<new (...args: any[]) => HandlerProvider>
|
||||
Set<new (...args: unknown[]) => HandlerProvider>
|
||||
> {
|
||||
return new Map(this.handlerProviderMap);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import {
|
||||
SmsSendParams,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable @typescript-eslint/require-await */
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
@@ -210,6 +210,7 @@ export class WechatPayProvider
|
||||
}
|
||||
|
||||
/** 健康检查:验证微信支付是否已配置 */
|
||||
// eslint-disable-next-line @typescript-eslint/require-await
|
||||
async healthCheck(): Promise<HealthCheckResult> {
|
||||
const start = Date.now();
|
||||
const configured = !!(this.appId && this.mchId && this.apiKey);
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface JobExecutionContext {
|
||||
jobGroup: string;
|
||||
fireTime: Date;
|
||||
scheduledFireTime: Date;
|
||||
data: Record<string, any>;
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface JobProvider {
|
||||
@@ -32,13 +32,13 @@ export const DEFAULT_JOB_PROVIDER = 'DEFAULT_JOB_PROVIDER';
|
||||
export class JobProviderFactory {
|
||||
private jobProviderClassMap = new Map<
|
||||
string,
|
||||
new (...args: any[]) => JobProvider
|
||||
new (...args: unknown[]) => JobProvider
|
||||
>();
|
||||
private jobProviderNameMap = new Map<string, Set<string>>();
|
||||
|
||||
register(
|
||||
key: string,
|
||||
jobProviderClass: new (...args: any[]) => JobProvider,
|
||||
jobProviderClass: new (...args: unknown[]) => JobProvider,
|
||||
source?: string,
|
||||
) {
|
||||
this.jobProviderClassMap.set(key, jobProviderClass);
|
||||
@@ -50,7 +50,7 @@ export class JobProviderFactory {
|
||||
}
|
||||
}
|
||||
|
||||
getJobProvider(key: string): new (...args: any[]) => JobProvider {
|
||||
getJobProvider(key: string): new (...args: unknown[]) => JobProvider {
|
||||
const jobProviderClass = this.jobProviderClassMap.get(key);
|
||||
if (!jobProviderClass) {
|
||||
throw new Error(`Job provider not found: ${key}`);
|
||||
@@ -63,7 +63,10 @@ export class JobProviderFactory {
|
||||
return new JobProviderClass();
|
||||
}
|
||||
|
||||
getJobProviderClassMap(): Map<string, new (...args: any[]) => JobProvider> {
|
||||
getJobProviderClassMap(): Map<
|
||||
string,
|
||||
new (...args: unknown[]) => JobProvider
|
||||
> {
|
||||
return new Map(this.jobProviderClassMap);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
export interface LoaderProvider {
|
||||
loadJSON(addon: string, location: string): any;
|
||||
loadJSONObject(addon: string, location: string): Record<string, any>;
|
||||
loadJSONArray(addon: string, location: string): any[];
|
||||
mergeJSONObject(location: string): Record<string, any>;
|
||||
mergeJSONArray(location: string): any[];
|
||||
loadJSON(addon: string, location: string): unknown;
|
||||
loadJSONObject(addon: string, location: string): Record<string, unknown>;
|
||||
loadJSONArray(addon: string, location: string): unknown[];
|
||||
mergeJSONObject(location: string): Record<string, unknown>;
|
||||
mergeJSONArray(location: string): unknown[];
|
||||
}
|
||||
|
||||
export const LOADER_PROVIDERS = 'LOADER_PROVIDERS';
|
||||
@@ -21,7 +21,7 @@ export const DEFAULT_LOADER_PROVIDER = 'DEFAULT_LOADER_PROVIDER';
|
||||
class LoaderProviderImpl implements LoaderProvider {
|
||||
private basePath = process.cwd();
|
||||
|
||||
loadJSON(addon: string, location: string): any {
|
||||
loadJSON(addon: string, location: string): unknown {
|
||||
try {
|
||||
const filePath = this.resolveFilePath(addon, location);
|
||||
const content = fs.readFileSync(filePath, 'utf-8');
|
||||
@@ -32,19 +32,21 @@ class LoaderProviderImpl implements LoaderProvider {
|
||||
}
|
||||
}
|
||||
|
||||
loadJSONObject(addon: string, location: string): Record<string, any> {
|
||||
loadJSONObject(addon: string, location: string): Record<string, unknown> {
|
||||
const json = this.loadJSON(addon, location);
|
||||
return json && typeof json === 'object' && !Array.isArray(json) ? json : {};
|
||||
return json && typeof json === 'object' && !Array.isArray(json)
|
||||
? (json as Record<string, unknown>)
|
||||
: {};
|
||||
}
|
||||
|
||||
loadJSONArray(addon: string, location: string): any[] {
|
||||
loadJSONArray(addon: string, location: string): unknown[] {
|
||||
const json = this.loadJSON(addon, location);
|
||||
return Array.isArray(json) ? json : [];
|
||||
}
|
||||
|
||||
mergeJSONObject(location: string): Record<string, any> {
|
||||
mergeJSONObject(location: string): Record<string, unknown> {
|
||||
try {
|
||||
const merged: Record<string, any> = {};
|
||||
const merged: Record<string, unknown> = {};
|
||||
const addonsDir = path.join(this.basePath, 'addon');
|
||||
|
||||
if (!fs.existsSync(addonsDir)) {
|
||||
@@ -62,25 +64,22 @@ class LoaderProviderImpl implements LoaderProvider {
|
||||
if (jsonObject && Object.keys(jsonObject).length > 0) {
|
||||
Object.assign(merged, jsonObject);
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// 忽略单个插件加载失败
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return merged;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error merging JSON objects from location ${location}:`,
|
||||
error,
|
||||
);
|
||||
} catch {
|
||||
console.error(`Error merging JSON objects from location ${location}:`);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
mergeJSONArray(location: string): any[] {
|
||||
mergeJSONArray(location: string): unknown[] {
|
||||
try {
|
||||
const merged: any[] = [];
|
||||
const merged: unknown[] = [];
|
||||
const addonsDir = path.join(this.basePath, 'addon');
|
||||
|
||||
if (!fs.existsSync(addonsDir)) {
|
||||
@@ -98,18 +97,15 @@ class LoaderProviderImpl implements LoaderProvider {
|
||||
if (Array.isArray(jsonArray) && jsonArray.length > 0) {
|
||||
merged.push(...jsonArray);
|
||||
}
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// 忽略单个插件加载失败
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return merged;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error merging JSON arrays from location ${location}:`,
|
||||
error,
|
||||
);
|
||||
} catch {
|
||||
console.error(`Error merging JSON arrays from location ${location}:`);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ import { Injectable, Module } from '@nestjs/common';
|
||||
import { DynamicModule } from '@nestjs/common';
|
||||
|
||||
export interface IPayProvider {
|
||||
createOrder(params: Record<string, any>): Promise<any>;
|
||||
refund(params: Record<string, any>): Promise<any>;
|
||||
queryOrder(orderId: string): Promise<any>;
|
||||
createOrder(params: Record<string, unknown>): Promise<unknown>;
|
||||
refund(params: Record<string, unknown>): Promise<unknown>;
|
||||
queryOrder(orderId: string): Promise<unknown>;
|
||||
}
|
||||
|
||||
export interface PayProviderConfig {
|
||||
@@ -27,7 +27,7 @@ export class PayProviderFactory {
|
||||
*/
|
||||
static init(
|
||||
annotationImplClassList: Array<
|
||||
{ new (): IPayProvider } & { [key: string]: any }
|
||||
{ new (): IPayProvider } & { [key: string]: unknown }
|
||||
>,
|
||||
): void {
|
||||
if (!annotationImplClassList || annotationImplClassList.length <= 0) {
|
||||
@@ -111,7 +111,8 @@ export class PayProviderFactory {
|
||||
*/
|
||||
getDefaultProvider(): IPayProvider | null {
|
||||
// 尝试获取第一个可用的提供者
|
||||
const firstProvider = PayProviderFactory.payProviderMap.keys().next().value;
|
||||
const firstProvider = PayProviderFactory.payProviderMap.keys().next()
|
||||
.value as string | undefined;
|
||||
if (firstProvider) {
|
||||
return PayProviderFactory.create(firstProvider);
|
||||
}
|
||||
@@ -121,7 +122,12 @@ export class PayProviderFactory {
|
||||
/**
|
||||
* 提取提供者配置(模拟注解解析)
|
||||
*/
|
||||
private static extractProviderConfig(cls: any): PayProviderConfig | null {
|
||||
private static extractProviderConfig(cls: {
|
||||
new (...args: unknown[]): IPayProvider;
|
||||
providerConfig?: PayProviderConfig;
|
||||
source?: string;
|
||||
name?: string;
|
||||
}): PayProviderConfig | null {
|
||||
// 这里应该实现注解解析逻辑
|
||||
// 简化版本,实际需要更复杂的反射机制
|
||||
return {
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface SmsProvider {
|
||||
send(
|
||||
phoneNumber: string,
|
||||
content: string,
|
||||
config?: Record<string, any>,
|
||||
config?: Record<string, unknown>,
|
||||
): Promise<{
|
||||
ok: boolean;
|
||||
messageId: string;
|
||||
|
||||
@@ -16,13 +16,13 @@ export const DEFAULT_UPGRADE_PROVIDER = 'DEFAULT_UPGRADE_PROVIDER';
|
||||
export class UpgradeProviderFactory {
|
||||
private upgradeProviderMap = new Map<
|
||||
string,
|
||||
new (...args: any[]) => UpgradeProvider
|
||||
new (...args: unknown[]) => UpgradeProvider
|
||||
>();
|
||||
|
||||
register(
|
||||
addon: string,
|
||||
version: string,
|
||||
upgradeProviderClass: new (...args: any[]) => UpgradeProvider,
|
||||
upgradeProviderClass: new (...args: unknown[]) => UpgradeProvider,
|
||||
) {
|
||||
const key = addon + version;
|
||||
this.upgradeProviderMap.set(key, upgradeProviderClass);
|
||||
@@ -31,7 +31,7 @@ export class UpgradeProviderFactory {
|
||||
getUpgradeProvider(
|
||||
addon: string,
|
||||
version: string,
|
||||
): (new (...args: any[]) => UpgradeProvider) | undefined {
|
||||
): (new (...args: unknown[]) => UpgradeProvider) | undefined {
|
||||
const key = addon + version;
|
||||
return this.upgradeProviderMap.get(key);
|
||||
}
|
||||
@@ -72,7 +72,7 @@ export class UpgradeProviderFactory {
|
||||
|
||||
getUpgradeProviderMap(): Map<
|
||||
string,
|
||||
new (...args: any[]) => UpgradeProvider
|
||||
new (...args: unknown[]) => UpgradeProvider
|
||||
> {
|
||||
return new Map(this.upgradeProviderMap);
|
||||
}
|
||||
|
||||
@@ -5,11 +5,27 @@ import {
|
||||
DynamicModule,
|
||||
} from '@nestjs/common';
|
||||
|
||||
/**
|
||||
* 上传文件描述接口
|
||||
* 对齐 Express.Multer.File 的核心字段
|
||||
*/
|
||||
export interface UploadFileDescriptor {
|
||||
fieldname: string;
|
||||
originalname: string;
|
||||
encoding: string;
|
||||
mimetype: string;
|
||||
destination: string;
|
||||
filename: string;
|
||||
path: string;
|
||||
size: number;
|
||||
buffer?: Buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传模型
|
||||
*/
|
||||
export interface UploadModel {
|
||||
uploadFile?: any; // Express.Multer.File
|
||||
uploadFile?: UploadFileDescriptor;
|
||||
uploadType?: string;
|
||||
uploadFilePath?: string;
|
||||
uploadFileName?: string;
|
||||
@@ -84,7 +100,7 @@ export interface UploadProvider {
|
||||
* 初始化上传提供者
|
||||
* 严格对齐Java: IUploadProvider.init(JSONObject configObject)
|
||||
*/
|
||||
init(configObject: Record<string, any>): void;
|
||||
init(configObject: Record<string, unknown>): void;
|
||||
|
||||
/**
|
||||
* 获取访问URL
|
||||
@@ -183,7 +199,7 @@ export class UploadProviderFactory {
|
||||
}
|
||||
try {
|
||||
return new Constructor();
|
||||
} catch (error) {
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -194,7 +210,7 @@ export class UploadProviderFactory {
|
||||
*/
|
||||
static createAndInit(
|
||||
name: string,
|
||||
configObject: Record<string, any>,
|
||||
configObject: Record<string, unknown>,
|
||||
): UploadProvider | null {
|
||||
const provider = this.create(name);
|
||||
if (provider && typeof provider.init === 'function') {
|
||||
|
||||
@@ -3,8 +3,6 @@ import {
|
||||
ProviderMetadata,
|
||||
ProviderHealthStatus,
|
||||
RegisteredProvider,
|
||||
ProviderRegisteredEvent,
|
||||
ProviderUnregisteredEvent,
|
||||
} from './provider-metadata.interface';
|
||||
import {
|
||||
HealthCheckResult,
|
||||
@@ -49,12 +47,6 @@ export class ProviderRegistryService implements OnModuleDestroy {
|
||||
// 启动健康检查(借鉴 OpenClaw Heartbeat)
|
||||
this.startHealthCheck(name, metadata.healthCheckInterval);
|
||||
|
||||
const event: ProviderRegisteredEvent = {
|
||||
name,
|
||||
version: metadata.version,
|
||||
capabilities: metadata.capabilities,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
this.logger.log(
|
||||
`Provider 注册成功: ${name}@${metadata.version} (${metadata.capabilities.join(', ')})`,
|
||||
);
|
||||
@@ -70,11 +62,6 @@ export class ProviderRegistryService implements OnModuleDestroy {
|
||||
this.stopHealthCheck(name);
|
||||
this.providers.delete(name);
|
||||
|
||||
const event: ProviderUnregisteredEvent = {
|
||||
name,
|
||||
reason,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
this.logger.log(`Provider 注销: ${name} (原因: ${reason})`);
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,19 @@
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json",
|
||||
"di:ai": "node -r ts-node/register -r tsconfig-paths/register scripts/di-debug-ai-only.ts",
|
||||
"di:full": "node -r ts-node/register -r tsconfig-paths/register scripts/di-debug.ts"
|
||||
"di:full": "node -r ts-node/register -r tsconfig-paths/register scripts/di-debug.ts",
|
||||
"quality-gate": "bash scripts/quality-gate.sh",
|
||||
"quality-gate:tsc": "bash scripts/quality-gate.sh --tsc",
|
||||
"quality-gate:lint": "bash scripts/quality-gate.sh --lint",
|
||||
"quality-gate:build": "bash scripts/quality-gate.sh --build",
|
||||
"quality-gate:any": "bash scripts/quality-gate.sh --any",
|
||||
"prepare": "cd .. && husky wwjcloud/.husky"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
"eslint --fix --max-warnings=0",
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/axios": "^4.0.1",
|
||||
@@ -53,6 +65,7 @@
|
||||
"joi": "^18.0.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"kafkajs": "^2.2.4",
|
||||
"module-alias": "^2.2.3",
|
||||
"mysql2": "^3.15.3",
|
||||
"nestjs-i18n": "^10.5.1",
|
||||
"passport-jwt": "^4.0.1",
|
||||
@@ -60,8 +73,7 @@
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
"typeorm": "^0.3.27",
|
||||
"module-alias": "^2.2.3"
|
||||
"typeorm": "^0.3.27"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3.2.0",
|
||||
@@ -79,7 +91,9 @@
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
"eslint-plugin-prettier": "^5.2.2",
|
||||
"globals": "^16.0.0",
|
||||
"husky": "^9.1.7",
|
||||
"jest": "^30.0.0",
|
||||
"lint-staged": "^16.4.0",
|
||||
"prettier": "^3.4.2",
|
||||
"source-map-support": "^0.5.21",
|
||||
"supertest": "^7.0.0",
|
||||
@@ -114,8 +128,7 @@
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
,
|
||||
},
|
||||
"_moduleAliases": {
|
||||
"@wwjBoot": "dist/libs/wwjcloud-boot/src",
|
||||
"@wwjCommon": "dist/libs/wwjcloud-boot/src/infra",
|
||||
|
||||
334
wwjcloud-nest-v1/wwjcloud/scripts/quality-gate.sh
Executable file
334
wwjcloud-nest-v1/wwjcloud/scripts/quality-gate.sh
Executable file
@@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env bash
|
||||
# ============================================================================
|
||||
# WWJCloud-Nest v1 质量门禁脚本 (Quality Gate)
|
||||
# ============================================================================
|
||||
# 用法:
|
||||
# ./scripts/quality-gate.sh # 运行全部检查
|
||||
# ./scripts/quality-gate.sh --tsc # 仅类型检查
|
||||
# ./scripts/quality-gate.sh --lint # 仅 ESLint 检查
|
||||
# ./scripts/quality-gate.sh --build # 仅构建检查
|
||||
# ./scripts/quality-gate.sh --any # 仅 any 类型计数
|
||||
# ./scripts/quality-gate.sh --fix # 自动修复后运行门禁
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ---- 颜色定义 ----
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
CYAN='\033[0;36m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# ---- 阈值配置 ----
|
||||
# any 类型计数阈值(新增代码目录)
|
||||
ANY_THRESHOLD_NEW_CODE=0
|
||||
# any 类型计数阈值(旧代码目录,渐进降低)
|
||||
ANY_THRESHOLD_OLD_CODE=99999
|
||||
# ESLint 错误数阈值
|
||||
ESLINT_ERROR_THRESHOLD=0
|
||||
# ESLint 警告数阈值(旧代码允许较多警告)
|
||||
ESLINT_WARN_THRESHOLD=15000
|
||||
|
||||
# ---- 项目根目录 ----
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
# ---- 新增代码目录(严格模式) ----
|
||||
NEW_CODE_DIRS=(
|
||||
"libs/wwjcloud-ai/src/generator"
|
||||
"libs/wwjcloud-ai/src/runtime"
|
||||
"libs/wwjcloud-ai/src/skills"
|
||||
"libs/wwjcloud-ai/src/memory"
|
||||
"libs/wwjcloud-ai/src/providers"
|
||||
"libs/wwjcloud-boot/src/vendor/gateway"
|
||||
"libs/wwjcloud-boot/src/vendor/registry"
|
||||
"libs/wwjcloud-boot/src/vendor/interfaces"
|
||||
"libs/wwjcloud-boot/src/vendor/errors"
|
||||
"libs/wwjcloud-boot/src/vendor/provider-factories"
|
||||
)
|
||||
|
||||
# ---- 旧代码目录(渐进模式) ----
|
||||
OLD_CODE_DIRS=(
|
||||
"libs/wwjcloud-core/src"
|
||||
"libs/wwjcloud-boot/src"
|
||||
"libs/wwjcloud-ai/src/safe"
|
||||
"libs/wwjcloud-ai/src/tuner"
|
||||
"libs/wwjcloud-ai/src/manager"
|
||||
"libs/wwjcloud-ai/src/healing"
|
||||
)
|
||||
|
||||
# ---- 统计变量 ----
|
||||
TOTAL_SCORE=0
|
||||
TOTAL_CHECKS=0
|
||||
FAILED_CHECKS=0
|
||||
RESULTS=()
|
||||
|
||||
# ---- 工具函数 ----
|
||||
|
||||
# 打印质量门禁头部信息
|
||||
print_header() {
|
||||
echo -e "${BLUE}${BOLD}═══════════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${BLUE}${BOLD} WWJCloud-Nest v1 质量门禁 (Quality Gate)${NC}"
|
||||
echo -e "${BLUE}${BOLD} $(date '+%Y-%m-%d %H:%M:%S')${NC}"
|
||||
echo -e "${BLUE}${BOLD}═══════════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 打印检查段落标题
|
||||
print_section() {
|
||||
echo -e "${CYAN}${BOLD}▸ $1${NC}"
|
||||
echo -e "${CYAN}───────────────────────────────────────────────────────────────────${NC}"
|
||||
}
|
||||
|
||||
# 记录检查通过
|
||||
pass_check() {
|
||||
local name="$1"
|
||||
local detail="$2"
|
||||
TOTAL_SCORE=$((TOTAL_SCORE + 1))
|
||||
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
|
||||
RESULTS+=("✅ $name: $detail")
|
||||
echo -e " ${GREEN}✅ PASS${NC} — $name: $detail"
|
||||
}
|
||||
|
||||
# 记录检查失败
|
||||
fail_check() {
|
||||
local name="$1"
|
||||
local detail="$2"
|
||||
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
|
||||
FAILED_CHECKS=$((FAILED_CHECKS + 1))
|
||||
RESULTS+=("❌ $name: $detail")
|
||||
echo -e " ${RED}❌ FAIL${NC} — $name: $detail"
|
||||
}
|
||||
|
||||
# 记录检查警告
|
||||
warn_check() {
|
||||
local name="$1"
|
||||
local detail="$2"
|
||||
TOTAL_CHECKS=$((TOTAL_CHECKS + 1))
|
||||
RESULTS+=("⚠️ $name: $detail")
|
||||
echo -e " ${YELLOW}⚠️ WARN${NC} — $name: $detail"
|
||||
}
|
||||
|
||||
# ---- 检查函数 ----
|
||||
|
||||
# TypeScript 类型检查(tsc --noEmit)
|
||||
check_tsc() {
|
||||
print_section "TypeScript 类型检查 (tsc --noEmit)"
|
||||
|
||||
if npx tsc --noEmit 2>&1; then
|
||||
pass_check "TypeScript" "0 类型错误"
|
||||
else
|
||||
local errors
|
||||
errors=$(npx tsc --noEmit 2>&1 | grep -c "error TS" || true)
|
||||
fail_check "TypeScript" "${errors} 个类型错误"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ESLint 代码质量检查
|
||||
check_eslint() {
|
||||
print_section "ESLint 代码质量检查"
|
||||
|
||||
local output
|
||||
output=$(npx eslint "{src,libs,test}/**/*.ts" --format json 2>/dev/null || true)
|
||||
|
||||
if [ -z "$output" ]; then
|
||||
pass_check "ESLint" "0 错误, 0 警告"
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
|
||||
# 解析 ESLint JSON 输出
|
||||
local total_errors total_warnings
|
||||
total_errors=$(echo "$output" | node -e "
|
||||
const data = JSON.parse(require('fs').readFileSync('/dev/stdin', 'utf8'));
|
||||
let errors = 0, warnings = 0;
|
||||
for (const file of data) {
|
||||
for (const msg of file.messages) {
|
||||
if (msg.severity === 2) errors++;
|
||||
else if (msg.severity === 1) warnings++;
|
||||
}
|
||||
}
|
||||
console.log(errors + ' ' + warnings);
|
||||
" 2>/dev/null || echo "0 0")
|
||||
|
||||
total_errors=$(echo "$total_errors" | awk '{print $1}')
|
||||
total_warnings=$(echo "$total_errors" | awk '{print $2}')
|
||||
|
||||
if [ "$total_errors" -le "$ESLINT_ERROR_THRESHOLD" ]; then
|
||||
if [ "$total_warnings" -le "$ESLINT_WARN_THRESHOLD" ]; then
|
||||
pass_check "ESLint" "${total_errors} 错误, ${total_warnings} 警告"
|
||||
else
|
||||
warn_check "ESLint" "${total_errors} 错误, ${total_warnings} 警告 (超过警告阈值 ${ESLINT_WARN_THRESHOLD})"
|
||||
fi
|
||||
else
|
||||
fail_check "ESLint" "${total_errors} 错误 (超过阈值 ${ESLINT_ERROR_THRESHOLD}), ${total_warnings} 警告"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# NestJS 构建检查
|
||||
check_build() {
|
||||
print_section "NestJS 构建 (nest build)"
|
||||
|
||||
if npx nest build 2>&1; then
|
||||
pass_check "Build" "构建成功"
|
||||
else
|
||||
fail_check "Build" "构建失败"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# TypeScript any 类型计数检查
|
||||
check_any() {
|
||||
print_section "TypeScript any 类型计数"
|
||||
|
||||
# ---- 新增代码 any 计数 ----
|
||||
local new_any_count=0
|
||||
local new_any_files=""
|
||||
|
||||
for dir in "${NEW_CODE_DIRS[@]}"; do
|
||||
if [ -d "$PROJECT_ROOT/$dir" ]; then
|
||||
local result
|
||||
result=$(grep -rn --include="*.ts" -E '(: any\b|as any\b|<any>|: any\)|: any,|: any;|: any =|: any\])' "$PROJECT_ROOT/$dir" 2>/dev/null || true)
|
||||
if [ -n "$result" ]; then
|
||||
local count
|
||||
count=$(echo "$result" | wc -l)
|
||||
new_any_count=$((new_any_count + count))
|
||||
new_any_files="${new_any_files}\n$(echo "$result" | head -20)"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e " ${BOLD}新增代码 any 计数:${NC}"
|
||||
echo -e " 检查目录: ${NEW_CODE_DIRS[*]}"
|
||||
echo -e " 发现: ${new_any_count} 处"
|
||||
|
||||
if [ "$new_any_count" -le "$ANY_THRESHOLD_NEW_CODE" ]; then
|
||||
pass_check "Any-新代码" "${new_any_count} 处 (阈值: ${ANY_THRESHOLD_NEW_CODE})"
|
||||
else
|
||||
fail_check "Any-新代码" "${new_any_count} 处 (超过阈值 ${ANY_THRESHOLD_NEW_CODE})"
|
||||
if [ -n "$new_any_files" ]; then
|
||||
echo -e "${YELLOW} 详情 (前20条):${NC}"
|
||||
echo -e "$new_any_files" | head -20 | while read -r line; do
|
||||
echo -e " ${YELLOW}→ $line${NC}"
|
||||
done
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# ---- 旧代码 any 计数(仅统计,不阻断) ----
|
||||
local old_any_count=0
|
||||
|
||||
for dir in "${OLD_CODE_DIRS[@]}"; do
|
||||
if [ -d "$PROJECT_ROOT/$dir" ]; then
|
||||
local result
|
||||
result=$(grep -rc --include="*.ts" -E '(: any\b|as any\b|<any>|: any\)|: any,|: any;|: any =|: any\])' "$PROJECT_ROOT/$dir" 2>/dev/null || true)
|
||||
if [ -n "$result" ]; then
|
||||
while read -r count; do
|
||||
old_any_count=$((old_any_count + count))
|
||||
done <<< "$result"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
echo -e " ${BOLD}旧代码 any 计数:${NC}"
|
||||
echo -e " 检查目录: ${OLD_CODE_DIRS[*]}"
|
||||
echo -e " 发现: ${old_any_count} 处 ${YELLOW}(渐进降低中,当前不阻断)${NC}"
|
||||
echo ""
|
||||
|
||||
if [ "$old_any_count" -gt "$ANY_THRESHOLD_OLD_CODE" ]; then
|
||||
warn_check "Any-旧代码" "${old_any_count} 处 (超过渐进阈值 ${ANY_THRESHOLD_OLD_CODE})"
|
||||
else
|
||||
pass_check "Any-旧代码" "${old_any_count} 处 (渐进模式)"
|
||||
fi
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ---- 主流程 ----
|
||||
main() {
|
||||
print_header
|
||||
|
||||
local run_all=true
|
||||
local run_tsc=false
|
||||
local run_lint=false
|
||||
local run_build=false
|
||||
local run_any=false
|
||||
local auto_fix=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
--tsc) run_tsc=true; run_all=false ;;
|
||||
--lint) run_lint=true; run_all=false ;;
|
||||
--build) run_build=true; run_all=false ;;
|
||||
--any) run_any=true; run_all=false ;;
|
||||
--fix) auto_fix=true ;;
|
||||
--help|-h)
|
||||
echo "用法: $0 [--tsc] [--lint] [--build] [--any] [--fix] [--help]"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# 自动修复模式
|
||||
if [ "$auto_fix" = true ]; then
|
||||
print_section "自动修复模式"
|
||||
echo -e " 运行 eslint --fix ..."
|
||||
npx eslint "{src,libs,test}/**/*.ts" --fix 2>/dev/null || true
|
||||
echo -e " ${GREEN}自动修复完成${NC}"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# 执行检查
|
||||
if [ "$run_all" = true ] || [ "$run_tsc" = true ]; then
|
||||
check_tsc
|
||||
fi
|
||||
|
||||
if [ "$run_all" = true ] || [ "$run_lint" = true ]; then
|
||||
check_eslint
|
||||
fi
|
||||
|
||||
if [ "$run_all" = true ] || [ "$run_build" = true ]; then
|
||||
check_build
|
||||
fi
|
||||
|
||||
if [ "$run_all" = true ] || [ "$run_any" = true ]; then
|
||||
check_any
|
||||
fi
|
||||
|
||||
# ---- 总结报告 ----
|
||||
echo -e "${BLUE}${BOLD}═══════════════════════════════════════════════════════════════${NC}"
|
||||
echo -e "${BLUE}${BOLD} 质量门禁报告${NC}"
|
||||
echo -e "${BLUE}${BOLD}═══════════════════════════════════════════════════════════════${NC}"
|
||||
echo ""
|
||||
|
||||
for result in "${RESULTS[@]}"; do
|
||||
echo -e " $result"
|
||||
done
|
||||
echo ""
|
||||
|
||||
local score_pct=0
|
||||
if [ "$TOTAL_CHECKS" -gt 0 ]; then
|
||||
score_pct=$((TOTAL_SCORE * 100 / TOTAL_CHECKS))
|
||||
fi
|
||||
|
||||
echo -e " ${BOLD}通过率: ${score_pct}% (${TOTAL_SCORE}/${TOTAL_CHECKS})${NC}"
|
||||
echo ""
|
||||
|
||||
if [ "$FAILED_CHECKS" -gt 0 ]; then
|
||||
echo -e " ${RED}${BOLD}🚫 质量门禁未通过!${NC}${RED} (${FAILED_CHECKS} 项检查失败)${NC}"
|
||||
echo -e " ${RED}请修复以上问题后重新提交。${NC}"
|
||||
echo ""
|
||||
exit 1
|
||||
else
|
||||
echo -e " ${GREEN}${BOLD}✅ 质量门禁通过!${NC}${GREEN} 所有检查项均达标。${NC}"
|
||||
echo ""
|
||||
exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user