feat: 实现环境变量预设功能 & 移除稀疏检出

## 后端改动
- 添加 Project.envPresets 字段(JSON 格式)
- 移除 Deployment.env 字段,统一使用 envVars
- 更新部署 DTO,支持 envVars (Record<string, string>)
- pipeline-runner 支持解析并注入 envVars 到环境
- 移除稀疏检出模板和相关环境变量
- 优化代码格式(Biome lint & format)

## 前端改动
- 新增 EnvPresetsEditor 组件(支持单选/多选/输入框类型)
- 项目创建/编辑界面集成环境预设编辑器
- 部署界面基于预设动态生成环境变量表单
- 移除稀疏检出表单项
- 项目详情页添加环境变量预设配置 tab
- 优化部署界面布局(基本参数 & 环境变量分区)

## 文档
- 添加完整文档目录结构(docs/)
- 创建设计文档 design-0005(部署流程重构)
- 添加 API 文档、架构设计文档等

## 数据库
- 执行 prisma db push 同步 schema 变更
This commit is contained in:
2026-01-03 22:59:20 +08:00
parent c40532c757
commit d22fdc9618
71 changed files with 9611 additions and 5849 deletions

View File

@@ -1,6 +1,10 @@
import type Koa from 'koa';
import KoaRouter from '@koa/router';
import { getRouteMetadata, getControllerPrefix, type RouteMetadata } from '../decorators/route.ts';
import type Koa from 'koa';
import {
getControllerPrefix,
getRouteMetadata,
type RouteMetadata,
} from '../decorators/route.ts';
import { createSuccessResponse } from '../middlewares/exception.ts';
/**
@@ -33,7 +37,7 @@ export class RouteScanner {
* 注册多个控制器类
*/
registerControllers(controllers: ControllerClass[]): void {
controllers.forEach(controller => this.registerController(controller));
controllers.forEach((controller) => this.registerController(controller));
}
/**
@@ -50,9 +54,12 @@ export class RouteScanner {
const routes: RouteMetadata[] = getRouteMetadata(ControllerClass);
// 注册每个路由
routes.forEach(route => {
routes.forEach((route) => {
const fullPath = this.buildFullPath(controllerPrefix, route.path);
const handler = this.wrapControllerMethod(controllerInstance, route.propertyKey);
const handler = this.wrapControllerMethod(
controllerInstance,
route.propertyKey,
);
// 根据HTTP方法注册路由
switch (route.method) {
@@ -87,10 +94,10 @@ export class RouteScanner {
let fullPath = '';
if (cleanControllerPrefix) {
fullPath += '/' + cleanControllerPrefix;
fullPath += `/${cleanControllerPrefix}`;
}
if (cleanRoutePath) {
fullPath += '/' + cleanRoutePath;
fullPath += `/${cleanRoutePath}`;
}
// 如果路径为空,返回根路径
@@ -105,11 +112,11 @@ export class RouteScanner {
// 调用控制器方法
const method = instance[methodName];
if (typeof method !== 'function') {
ctx.throw(401, 'Not Found')
ctx.throw(401, 'Not Found');
}
// 绑定this并调用方法
const result = await method.call(instance, ctx, next) ?? null;
const result = (await method.call(instance, ctx, next)) ?? null;
ctx.body = createSuccessResponse(result);
};
@@ -133,19 +140,29 @@ export class RouteScanner {
/**
* 获取已注册的路由信息(用于调试)
*/
getRegisteredRoutes(): Array<{ method: string; path: string; controller: string; action: string }> {
const routes: Array<{ method: string; path: string; controller: string; action: string }> = [];
getRegisteredRoutes(): Array<{
method: string;
path: string;
controller: string;
action: string;
}> {
const routes: Array<{
method: string;
path: string;
controller: string;
action: string;
}> = [];
this.controllers.forEach(ControllerClass => {
this.controllers.forEach((ControllerClass) => {
const controllerPrefix = getControllerPrefix(ControllerClass);
const routeMetadata = getRouteMetadata(ControllerClass);
routeMetadata.forEach(route => {
routeMetadata.forEach((route) => {
routes.push({
method: route.method,
path: this.buildFullPath(controllerPrefix, route.path),
controller: ControllerClass.name,
action: route.propertyKey
action: route.propertyKey,
});
});
});