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:
@@ -1,8 +1,8 @@
|
||||
import Koa from 'koa';
|
||||
import { initMiddlewares } from './middlewares/index.ts';
|
||||
import { log } from './libs/logger.ts';
|
||||
import { ExecutionQueue } from './libs/execution-queue.ts';
|
||||
import { log } from './libs/logger.ts';
|
||||
import { initializePipelineTemplates } from './libs/pipeline-template.ts';
|
||||
import { initMiddlewares } from './middlewares/index.ts';
|
||||
|
||||
// 初始化应用
|
||||
async function initializeApp() {
|
||||
@@ -26,7 +26,7 @@ async function initializeApp() {
|
||||
}
|
||||
|
||||
// 启动应用
|
||||
initializeApp().catch(error => {
|
||||
initializeApp().catch((error) => {
|
||||
console.error('Failed to start application:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type { Context } from 'koa';
|
||||
import { Controller, Get, Post } from '../../decorators/route.ts';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import { log } from '../../libs/logger.ts';
|
||||
import { gitea } from '../../libs/gitea.ts';
|
||||
import { log } from '../../libs/logger.ts';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import { loginSchema } from './dto.ts';
|
||||
|
||||
@Controller('/auth')
|
||||
|
||||
@@ -12,8 +12,7 @@ export const createDeploymentSchema = z.object({
|
||||
branch: z.string().min(1, { message: '分支不能为空' }),
|
||||
commitHash: z.string().min(1, { message: '提交哈希不能为空' }),
|
||||
commitMessage: z.string().min(1, { message: '提交信息不能为空' }),
|
||||
env: z.string().optional(),
|
||||
sparseCheckoutPaths: z.string().optional(), // 添加稀疏检出路径字段
|
||||
envVars: z.record(z.string()).optional(), // 环境变量 key-value 对象
|
||||
});
|
||||
|
||||
export type ListDeploymentsQuery = z.infer<typeof listDeploymentsQuerySchema>;
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import type { Context } from 'koa';
|
||||
import { Controller, Get, Post } from '../../decorators/route.ts';
|
||||
import type { Prisma } from '../../generated/client.ts';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import type { Context } from 'koa';
|
||||
import { listDeploymentsQuerySchema, createDeploymentSchema } from './dto.ts';
|
||||
import { ExecutionQueue } from '../../libs/execution-queue.ts';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import { createDeploymentSchema, listDeploymentsQuerySchema } from './dto.ts';
|
||||
|
||||
@Controller('/deployments')
|
||||
export class DeploymentController {
|
||||
@Get('')
|
||||
async list(ctx: Context) {
|
||||
const { page, pageSize, projectId } = listDeploymentsQuerySchema.parse(ctx.query);
|
||||
const { page, pageSize, projectId } = listDeploymentsQuerySchema.parse(
|
||||
ctx.query,
|
||||
);
|
||||
const where: Prisma.DeploymentWhereInput = {
|
||||
valid: 1,
|
||||
};
|
||||
@@ -50,8 +52,7 @@ export class DeploymentController {
|
||||
connect: { id: body.projectId },
|
||||
},
|
||||
pipelineId: body.pipelineId,
|
||||
env: body.env || 'dev',
|
||||
sparseCheckoutPaths: body.sparseCheckoutPaths || '', // 添加稀疏检出路径
|
||||
envVars: body.envVars ? JSON.stringify(body.envVars) : null,
|
||||
buildLog: '',
|
||||
createdBy: 'system', // TODO: get from user
|
||||
updatedBy: 'system',
|
||||
@@ -73,7 +74,7 @@ export class DeploymentController {
|
||||
|
||||
// 获取原始部署记录
|
||||
const originalDeployment = await prisma.deployment.findUnique({
|
||||
where: { id: Number(id) }
|
||||
where: { id: Number(id) },
|
||||
});
|
||||
|
||||
if (!originalDeployment) {
|
||||
@@ -82,7 +83,7 @@ export class DeploymentController {
|
||||
code: 404,
|
||||
message: '部署记录不存在',
|
||||
data: null,
|
||||
timestamp: Date.now()
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
return;
|
||||
}
|
||||
@@ -96,8 +97,7 @@ export class DeploymentController {
|
||||
status: 'pending',
|
||||
projectId: originalDeployment.projectId,
|
||||
pipelineId: originalDeployment.pipelineId,
|
||||
env: originalDeployment.env,
|
||||
sparseCheckoutPaths: originalDeployment.sparseCheckoutPaths,
|
||||
envVars: originalDeployment.envVars,
|
||||
buildLog: '',
|
||||
createdBy: 'system',
|
||||
updatedBy: 'system',
|
||||
@@ -113,7 +113,7 @@ export class DeploymentController {
|
||||
code: 0,
|
||||
message: '重新执行任务已创建',
|
||||
data: newDeployment,
|
||||
timestamp: Date.now()
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const getCommitsQuerySchema = z.object({
|
||||
projectId: z.coerce.number().int().positive({ message: 'Project ID is required' }),
|
||||
projectId: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.positive({ message: 'Project ID is required' }),
|
||||
branch: z.string().optional(),
|
||||
});
|
||||
|
||||
export const getBranchesQuerySchema = z.object({
|
||||
projectId: z.coerce.number().int().positive({ message: 'Project ID is required' }),
|
||||
projectId: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.positive({ message: 'Project ID is required' }),
|
||||
});
|
||||
|
||||
export type GetCommitsQuery = z.infer<typeof getCommitsQuerySchema>;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Context } from 'koa';
|
||||
import { Controller, Get } from '../../decorators/route.ts';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import { gitea } from '../../libs/gitea.ts';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import { BusinessError } from '../../middlewares/exception.ts';
|
||||
import { getCommitsQuerySchema, getBranchesQuerySchema } from './dto.ts';
|
||||
import { getBranchesQuerySchema, getCommitsQuerySchema } from './dto.ts';
|
||||
|
||||
@Controller('/git')
|
||||
export class GitController {
|
||||
@@ -33,7 +33,11 @@ export class GitController {
|
||||
console.log('Access token present:', !!accessToken);
|
||||
|
||||
if (!accessToken) {
|
||||
throw new BusinessError('Gitea access token not found. Please login again.', 1004, 401);
|
||||
throw new BusinessError(
|
||||
'Gitea access token not found. Please login again.',
|
||||
1004,
|
||||
401,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -65,7 +69,11 @@ export class GitController {
|
||||
const accessToken = ctx.session?.gitea?.access_token;
|
||||
|
||||
if (!accessToken) {
|
||||
throw new BusinessError('Gitea access token not found. Please login again.', 1004, 401);
|
||||
throw new BusinessError(
|
||||
'Gitea access token not found. Please login again.',
|
||||
1004,
|
||||
401,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -85,7 +93,7 @@ export class GitController {
|
||||
|
||||
// Handle SCP-like syntax: git@host:owner/repo.git
|
||||
if (!cleanUrl.includes('://') && cleanUrl.includes(':')) {
|
||||
const scpMatch = cleanUrl.match(/:([^\/]+)\/([^\/]+?)(\.git)?$/);
|
||||
const scpMatch = cleanUrl.match(/:([^/]+)\/([^/]+?)(\.git)?$/);
|
||||
if (scpMatch) {
|
||||
return { owner: scpMatch[1], repo: scpMatch[2] };
|
||||
}
|
||||
@@ -96,13 +104,15 @@ export class GitController {
|
||||
const urlObj = new URL(cleanUrl);
|
||||
const parts = urlObj.pathname.split('/').filter(Boolean);
|
||||
if (parts.length >= 2) {
|
||||
const repo = parts.pop()!.replace(/\.git$/, '');
|
||||
const owner = parts.pop()!;
|
||||
return { owner, repo };
|
||||
const repo = parts.pop()?.replace(/\.git$/, '');
|
||||
const owner = parts.pop();
|
||||
if (repo && owner) {
|
||||
return { owner, repo };
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (_e) {
|
||||
// Fallback to simple regex
|
||||
const match = cleanUrl.match(/([^\/]+)\/([^\/]+?)(\.git)?$/);
|
||||
const match = cleanUrl.match(/([^/]+)\/([^/]+?)(\.git)?$/);
|
||||
if (match) {
|
||||
return { owner: match[1], repo: match[2] };
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
// 控制器统一导出
|
||||
export { ProjectController } from './project/index.ts';
|
||||
export { UserController } from './user/index.ts';
|
||||
|
||||
export { AuthController } from './auth/index.ts';
|
||||
export { DeploymentController } from './deployment/index.ts';
|
||||
export { PipelineController } from './pipeline/index.ts';
|
||||
export { StepController } from './step/index.ts'
|
||||
export { GitController } from './git/index.ts';
|
||||
export { PipelineController } from './pipeline/index.ts';
|
||||
export { ProjectController } from './project/index.ts';
|
||||
export { StepController } from './step/index.ts';
|
||||
export { UserController } from './user/index.ts';
|
||||
|
||||
@@ -2,36 +2,59 @@ import { z } from 'zod';
|
||||
|
||||
// 定义验证架构
|
||||
export const createPipelineSchema = z.object({
|
||||
name: z.string({
|
||||
message: '流水线名称必须是字符串',
|
||||
}).min(1, { message: '流水线名称不能为空' }).max(100, { message: '流水线名称不能超过100个字符' }),
|
||||
name: z
|
||||
.string({
|
||||
message: '流水线名称必须是字符串',
|
||||
})
|
||||
.min(1, { message: '流水线名称不能为空' })
|
||||
.max(100, { message: '流水线名称不能超过100个字符' }),
|
||||
|
||||
description: z.string({
|
||||
message: '流水线描述必须是字符串',
|
||||
}).max(500, { message: '流水线描述不能超过500个字符' }).optional(),
|
||||
description: z
|
||||
.string({
|
||||
message: '流水线描述必须是字符串',
|
||||
})
|
||||
.max(500, { message: '流水线描述不能超过500个字符' })
|
||||
.optional(),
|
||||
|
||||
projectId: z.number({
|
||||
message: '项目ID必须是数字',
|
||||
}).int().positive({ message: '项目ID必须是正整数' }).optional(),
|
||||
projectId: z
|
||||
.number({
|
||||
message: '项目ID必须是数字',
|
||||
})
|
||||
.int()
|
||||
.positive({ message: '项目ID必须是正整数' })
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const updatePipelineSchema = z.object({
|
||||
name: z.string({
|
||||
message: '流水线名称必须是字符串',
|
||||
}).min(1, { message: '流水线名称不能为空' }).max(100, { message: '流水线名称不能超过100个字符' }).optional(),
|
||||
name: z
|
||||
.string({
|
||||
message: '流水线名称必须是字符串',
|
||||
})
|
||||
.min(1, { message: '流水线名称不能为空' })
|
||||
.max(100, { message: '流水线名称不能超过100个字符' })
|
||||
.optional(),
|
||||
|
||||
description: z.string({
|
||||
message: '流水线描述必须是字符串',
|
||||
}).max(500, { message: '流水线描述不能超过500个字符' }).optional(),
|
||||
description: z
|
||||
.string({
|
||||
message: '流水线描述必须是字符串',
|
||||
})
|
||||
.max(500, { message: '流水线描述不能超过500个字符' })
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const pipelineIdSchema = z.object({
|
||||
id: z.coerce.number().int().positive({ message: '流水线 ID 必须是正整数' }),
|
||||
});
|
||||
|
||||
export const listPipelinesQuerySchema = z.object({
|
||||
projectId: z.coerce.number().int().positive({ message: '项目ID必须是正整数' }).optional(),
|
||||
}).optional();
|
||||
export const listPipelinesQuerySchema = z
|
||||
.object({
|
||||
projectId: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.positive({ message: '项目ID必须是正整数' })
|
||||
.optional(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
// 类型
|
||||
export type CreatePipelineInput = z.infer<typeof createPipelineSchema>;
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import type { Context } from 'koa';
|
||||
import { Controller, Get, Post, Put, Delete } from '../../decorators/route.ts';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import { Controller, Delete, Get, Post, Put } from '../../decorators/route.ts';
|
||||
import { log } from '../../libs/logger.ts';
|
||||
import {
|
||||
createPipelineFromTemplate,
|
||||
getAvailableTemplates,
|
||||
} from '../../libs/pipeline-template.ts';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import { BusinessError } from '../../middlewares/exception.ts';
|
||||
import { getAvailableTemplates, createPipelineFromTemplate } from '../../libs/pipeline-template.ts';
|
||||
import {
|
||||
createPipelineSchema,
|
||||
updatePipelineSchema,
|
||||
pipelineIdSchema,
|
||||
listPipelinesQuerySchema,
|
||||
pipelineIdSchema,
|
||||
updatePipelineSchema,
|
||||
} from './dto.ts';
|
||||
|
||||
@Controller('/pipelines')
|
||||
@@ -46,7 +49,7 @@ export class PipelineController {
|
||||
|
||||
// GET /api/pipelines/templates - 获取可用的流水线模板
|
||||
@Get('/templates')
|
||||
async getTemplates(ctx: Context) {
|
||||
async getTemplates(_ctx: Context) {
|
||||
try {
|
||||
const templates = await getAvailableTemplates();
|
||||
return templates;
|
||||
@@ -126,7 +129,7 @@ export class PipelineController {
|
||||
templateId,
|
||||
projectId,
|
||||
name,
|
||||
description || ''
|
||||
description || '',
|
||||
);
|
||||
|
||||
// 返回新创建的流水线
|
||||
|
||||
@@ -5,46 +5,83 @@ import { projectDirSchema } from '../../libs/path-validator.js';
|
||||
* 创建项目验证架构
|
||||
*/
|
||||
export const createProjectSchema = z.object({
|
||||
name: z.string({
|
||||
message: '项目名称必须是字符串',
|
||||
}).min(2, { message: '项目名称至少2个字符' }).max(50, { message: '项目名称不能超过50个字符' }),
|
||||
name: z
|
||||
.string({
|
||||
message: '项目名称必须是字符串',
|
||||
})
|
||||
.min(2, { message: '项目名称至少2个字符' })
|
||||
.max(50, { message: '项目名称不能超过50个字符' }),
|
||||
|
||||
description: z.string({
|
||||
message: '项目描述必须是字符串',
|
||||
}).max(200, { message: '项目描述不能超过200个字符' }).optional(),
|
||||
description: z
|
||||
.string({
|
||||
message: '项目描述必须是字符串',
|
||||
})
|
||||
.max(200, { message: '项目描述不能超过200个字符' })
|
||||
.optional(),
|
||||
|
||||
repository: z.string({
|
||||
message: '仓库地址必须是字符串',
|
||||
}).url({ message: '请输入有效的仓库地址' }).min(1, { message: '仓库地址不能为空' }),
|
||||
repository: z
|
||||
.string({
|
||||
message: '仓库地址必须是字符串',
|
||||
})
|
||||
.url({ message: '请输入有效的仓库地址' })
|
||||
.min(1, { message: '仓库地址不能为空' }),
|
||||
|
||||
projectDir: projectDirSchema,
|
||||
|
||||
envPresets: z.string().optional(), // JSON 字符串格式
|
||||
});
|
||||
|
||||
/**
|
||||
* 更新项目验证架构
|
||||
*/
|
||||
export const updateProjectSchema = z.object({
|
||||
name: z.string({
|
||||
message: '项目名称必须是字符串',
|
||||
}).min(2, { message: '项目名称至少2个字符' }).max(50, { message: '项目名称不能超过50个字符' }).optional(),
|
||||
name: z
|
||||
.string({
|
||||
message: '项目名称必须是字符串',
|
||||
})
|
||||
.min(2, { message: '项目名称至少2个字符' })
|
||||
.max(50, { message: '项目名称不能超过50个字符' })
|
||||
.optional(),
|
||||
|
||||
description: z.string({
|
||||
message: '项目描述必须是字符串',
|
||||
}).max(200, { message: '项目描述不能超过200个字符' }).optional(),
|
||||
description: z
|
||||
.string({
|
||||
message: '项目描述必须是字符串',
|
||||
})
|
||||
.max(200, { message: '项目描述不能超过200个字符' })
|
||||
.optional(),
|
||||
|
||||
repository: z.string({
|
||||
message: '仓库地址必须是字符串',
|
||||
}).url({ message: '请输入有效的仓库地址' }).min(1, { message: '仓库地址不能为空' }).optional(),
|
||||
repository: z
|
||||
.string({
|
||||
message: '仓库地址必须是字符串',
|
||||
})
|
||||
.url({ message: '请输入有效的仓库地址' })
|
||||
.min(1, { message: '仓库地址不能为空' })
|
||||
.optional(),
|
||||
|
||||
envPresets: z.string().optional(), // JSON 字符串格式
|
||||
});
|
||||
|
||||
/**
|
||||
* 项目列表查询参数验证架构
|
||||
*/
|
||||
export const listProjectQuerySchema = z.object({
|
||||
page: z.coerce.number().int().min(1, { message: '页码必须大于0' }).optional().default(1),
|
||||
limit: z.coerce.number().int().min(1, { message: '每页数量必须大于0' }).max(100, { message: '每页数量不能超过100' }).optional().default(10),
|
||||
name: z.string().optional(),
|
||||
}).optional();
|
||||
export const listProjectQuerySchema = z
|
||||
.object({
|
||||
page: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.min(1, { message: '页码必须大于0' })
|
||||
.optional()
|
||||
.default(1),
|
||||
limit: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.min(1, { message: '每页数量必须大于0' })
|
||||
.max(100, { message: '每页数量不能超过100' })
|
||||
.optional()
|
||||
.default(10),
|
||||
name: z.string().optional(),
|
||||
})
|
||||
.optional();
|
||||
|
||||
/**
|
||||
* 项目ID验证架构
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { Context } from 'koa';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import { log } from '../../libs/logger.ts';
|
||||
import { BusinessError } from '../../middlewares/exception.ts';
|
||||
import { Controller, Get, Post, Put, Delete } from '../../decorators/route.ts';
|
||||
import { Controller, Delete, Get, Post, Put } from '../../decorators/route.ts';
|
||||
import { GitManager } from '../../libs/git-manager.ts';
|
||||
import { log } from '../../libs/logger.ts';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import { BusinessError } from '../../middlewares/exception.ts';
|
||||
import {
|
||||
createProjectSchema,
|
||||
updateProjectSchema,
|
||||
listProjectQuerySchema,
|
||||
projectIdSchema,
|
||||
updateProjectSchema,
|
||||
} from './dto.ts';
|
||||
|
||||
@Controller('/projects')
|
||||
@@ -135,6 +135,7 @@ export class ProjectController {
|
||||
description: validatedData.description || '',
|
||||
repository: validatedData.repository,
|
||||
projectDir: validatedData.projectDir,
|
||||
envPresets: validatedData.envPresets,
|
||||
createdBy: 'system',
|
||||
updatedBy: 'system',
|
||||
valid: 1,
|
||||
@@ -182,6 +183,9 @@ export class ProjectController {
|
||||
if (validatedData.repository !== undefined) {
|
||||
updateData.repository = validatedData.repository;
|
||||
}
|
||||
if (validatedData.envPresets !== undefined) {
|
||||
updateData.envPresets = validatedData.envPresets;
|
||||
}
|
||||
|
||||
const project = await prisma.project.update({
|
||||
where: { id },
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { Context } from 'koa';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import { Controller, Delete, Get, Post, Put } from '../../decorators/route.ts';
|
||||
import { log } from '../../libs/logger.ts';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import { BusinessError } from '../../middlewares/exception.ts';
|
||||
import { Controller, Get, Post, Put, Delete } from '../../decorators/route.ts';
|
||||
import {
|
||||
createStepSchema,
|
||||
updateStepSchema,
|
||||
stepIdSchema,
|
||||
listStepsQuerySchema,
|
||||
stepIdSchema,
|
||||
updateStepSchema,
|
||||
} from './dto.ts';
|
||||
|
||||
@Controller('/steps')
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import type { Context } from 'koa';
|
||||
import { Controller, Get, Post, Put, Delete } from '../../decorators/route.ts';
|
||||
import { Controller, Delete, Get, Post, Put } from '../../decorators/route.ts';
|
||||
import { BusinessError } from '../../middlewares/exception.ts';
|
||||
import {
|
||||
userIdSchema,
|
||||
createUserSchema,
|
||||
updateUserSchema,
|
||||
searchUserQuerySchema,
|
||||
updateUserSchema,
|
||||
userIdSchema,
|
||||
} from './dto.ts';
|
||||
|
||||
/**
|
||||
@@ -13,14 +13,18 @@ import {
|
||||
*/
|
||||
@Controller('/user')
|
||||
export class UserController {
|
||||
|
||||
@Get('/list')
|
||||
async list(ctx: Context) {
|
||||
async list(_ctx: Context) {
|
||||
// 模拟用户列表数据
|
||||
const users = [
|
||||
{ id: 1, name: 'Alice', email: 'alice@example.com', status: 'active' },
|
||||
{ id: 2, name: 'Bob', email: 'bob@example.com', status: 'inactive' },
|
||||
{ id: 3, name: 'Charlie', email: 'charlie@example.com', status: 'active' }
|
||||
{
|
||||
id: 3,
|
||||
name: 'Charlie',
|
||||
email: 'charlie@example.com',
|
||||
status: 'active',
|
||||
},
|
||||
];
|
||||
|
||||
return users;
|
||||
@@ -33,10 +37,10 @@ export class UserController {
|
||||
// 模拟根据ID查找用户
|
||||
const user = {
|
||||
id,
|
||||
name: 'User ' + id,
|
||||
name: `User ${id}`,
|
||||
email: `user${id}@example.com`,
|
||||
status: 'active',
|
||||
createdAt: new Date().toISOString()
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
if (id > 100) {
|
||||
@@ -55,7 +59,7 @@ export class UserController {
|
||||
id: Date.now(),
|
||||
...body,
|
||||
createdAt: new Date().toISOString(),
|
||||
status: body.status
|
||||
status: body.status,
|
||||
};
|
||||
|
||||
return newUser;
|
||||
@@ -70,7 +74,7 @@ export class UserController {
|
||||
const updatedUser = {
|
||||
id,
|
||||
...body,
|
||||
updatedAt: new Date().toISOString()
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
return updatedUser;
|
||||
@@ -88,7 +92,7 @@ export class UserController {
|
||||
return {
|
||||
success: true,
|
||||
message: `用户 ${id} 已删除`,
|
||||
deletedAt: new Date().toISOString()
|
||||
deletedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -99,25 +103,26 @@ export class UserController {
|
||||
// 模拟搜索逻辑
|
||||
let results = [
|
||||
{ id: 1, name: 'Alice', email: 'alice@example.com', status: 'active' },
|
||||
{ id: 2, name: 'Bob', email: 'bob@example.com', status: 'inactive' }
|
||||
{ id: 2, name: 'Bob', email: 'bob@example.com', status: 'inactive' },
|
||||
];
|
||||
|
||||
if (keyword) {
|
||||
results = results.filter(user =>
|
||||
user.name.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
user.email.toLowerCase().includes(keyword.toLowerCase())
|
||||
results = results.filter(
|
||||
(user) =>
|
||||
user.name.toLowerCase().includes(keyword.toLowerCase()) ||
|
||||
user.email.toLowerCase().includes(keyword.toLowerCase()),
|
||||
);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
results = results.filter(user => user.status === status);
|
||||
results = results.filter((user) => user.status === status);
|
||||
}
|
||||
|
||||
return {
|
||||
keyword,
|
||||
status,
|
||||
total: results.length,
|
||||
results
|
||||
results,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,17 +25,24 @@ const metadataStore = new WeakMap<any, Map<string | symbol, any>>();
|
||||
/**
|
||||
* 设置元数据(降级方案)
|
||||
*/
|
||||
function setMetadata<T = any>(key: string | symbol, value: T, target: any): void {
|
||||
function setMetadata<T = any>(
|
||||
key: string | symbol,
|
||||
value: T,
|
||||
target: any,
|
||||
): void {
|
||||
if (!metadataStore.has(target)) {
|
||||
metadataStore.set(target, new Map());
|
||||
}
|
||||
metadataStore.get(target)!.set(key, value);
|
||||
metadataStore.get(target)?.set(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取元数据(降级方案)
|
||||
*/
|
||||
function getMetadata<T = any>(key: string | symbol, target: any): T | undefined {
|
||||
function getMetadata<T = any>(
|
||||
key: string | symbol,
|
||||
target: any,
|
||||
): T | undefined {
|
||||
return metadataStore.get(target)?.get(key);
|
||||
}
|
||||
|
||||
@@ -43,24 +50,28 @@ function getMetadata<T = any>(key: string | symbol, target: any): T | undefined
|
||||
* 创建HTTP方法装饰器的工厂函数(TC39标准)
|
||||
*/
|
||||
function createMethodDecorator(method: HttpMethod) {
|
||||
return function (path: string = '') {
|
||||
return function <This, Args extends any[], Return>(
|
||||
return (path: string = '') =>
|
||||
<This, Args extends any[], Return>(
|
||||
target: (this: This, ...args: Args) => Return,
|
||||
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>
|
||||
) {
|
||||
context: ClassMethodDecoratorContext<
|
||||
This,
|
||||
(this: This, ...args: Args) => Return
|
||||
>,
|
||||
) => {
|
||||
// 在类初始化时执行
|
||||
context.addInitializer(function () {
|
||||
// 使用 this.constructor 时需要类型断言
|
||||
const ctor = (this as any).constructor;
|
||||
|
||||
// 获取现有的路由元数据
|
||||
const existingRoutes: RouteMetadata[] = getMetadata(ROUTE_METADATA_KEY, ctor) || [];
|
||||
const existingRoutes: RouteMetadata[] =
|
||||
getMetadata(ROUTE_METADATA_KEY, ctor) || [];
|
||||
|
||||
// 添加新的路由元数据
|
||||
const newRoute: RouteMetadata = {
|
||||
method,
|
||||
path,
|
||||
propertyKey: String(context.name)
|
||||
propertyKey: String(context.name),
|
||||
};
|
||||
|
||||
existingRoutes.push(newRoute);
|
||||
@@ -71,7 +82,6 @@ function createMethodDecorator(method: HttpMethod) {
|
||||
|
||||
return target;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,10 +119,10 @@ export const Patch = createMethodDecorator('PATCH');
|
||||
* @param prefix 路由前缀
|
||||
*/
|
||||
export function Controller(prefix: string = '') {
|
||||
return function <T extends abstract new (...args: any) => any>(
|
||||
return <T extends abstract new (...args: any) => any>(
|
||||
target: T,
|
||||
context: ClassDecoratorContext<T>
|
||||
) {
|
||||
context: ClassDecoratorContext<T>,
|
||||
) => {
|
||||
// 在类初始化时保存控制器前缀
|
||||
context.addInitializer(function () {
|
||||
setMetadata('prefix', prefix, this);
|
||||
|
||||
@@ -1,44 +1,43 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This file should be your main import to use Prisma-related types and utilities in a browser.
|
||||
* This file should be your main import to use Prisma-related types and utilities in a browser.
|
||||
* Use it to get access to models, enums, and input types.
|
||||
*
|
||||
*
|
||||
* This file does not contain a `PrismaClient` class, nor several other helpers that are intended as server-side only.
|
||||
* See `client.ts` for the standard, server-side entry point.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
import * as Prisma from './internal/prismaNamespaceBrowser.ts'
|
||||
export { Prisma }
|
||||
export * as $Enums from './enums.ts'
|
||||
import * as Prisma from './internal/prismaNamespaceBrowser.ts';
|
||||
export { Prisma };
|
||||
export * as $Enums from './enums.ts';
|
||||
export * from './enums.ts';
|
||||
/**
|
||||
* Model Project
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Project = Prisma.ProjectModel
|
||||
export type Project = Prisma.ProjectModel;
|
||||
/**
|
||||
* Model User
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type User = Prisma.UserModel
|
||||
export type User = Prisma.UserModel;
|
||||
/**
|
||||
* Model Pipeline
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Pipeline = Prisma.PipelineModel
|
||||
export type Pipeline = Prisma.PipelineModel;
|
||||
/**
|
||||
* Model Step
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Step = Prisma.StepModel
|
||||
export type Step = Prisma.StepModel;
|
||||
/**
|
||||
* Model Deployment
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Deployment = Prisma.DeploymentModel
|
||||
export type Deployment = Prisma.DeploymentModel;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This file should be your main import to use Prisma. Through it you get access to all the models, enums, and input types.
|
||||
* If you're looking for something you can import in the client-side of your application, please refer to the `browser.ts` file instead.
|
||||
@@ -10,21 +9,22 @@
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
import * as process from 'node:process'
|
||||
import * as path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
globalThis['__dirname'] = path.dirname(fileURLToPath(import.meta.url))
|
||||
import * as path from 'node:path';
|
||||
import * as process from 'node:process';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import * as runtime from "@prisma/client/runtime/client"
|
||||
import * as $Enums from "./enums.ts"
|
||||
import * as $Class from "./internal/class.ts"
|
||||
import * as Prisma from "./internal/prismaNamespace.ts"
|
||||
globalThis['__dirname'] = path.dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export * as $Enums from './enums.ts'
|
||||
export * from "./enums.ts"
|
||||
import * as runtime from '@prisma/client/runtime/client';
|
||||
import * as $Enums from './enums.ts';
|
||||
import * as $Class from './internal/class.ts';
|
||||
import * as Prisma from './internal/prismaNamespace.ts';
|
||||
|
||||
export * as $Enums from './enums.ts';
|
||||
export * from './enums.ts';
|
||||
/**
|
||||
* ## Prisma Client
|
||||
*
|
||||
*
|
||||
* Type-safe database client for TypeScript
|
||||
* @example
|
||||
* ```
|
||||
@@ -32,35 +32,41 @@ export * from "./enums.ts"
|
||||
* // Fetch zero or more Projects
|
||||
* const projects = await prisma.project.findMany()
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Read more in our [docs](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client).
|
||||
*/
|
||||
export const PrismaClient = $Class.getPrismaClientClass()
|
||||
export type PrismaClient<LogOpts extends Prisma.LogLevel = never, OmitOpts extends Prisma.PrismaClientOptions["omit"] = Prisma.PrismaClientOptions["omit"], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>
|
||||
export { Prisma }
|
||||
export const PrismaClient = $Class.getPrismaClientClass();
|
||||
export type PrismaClient<
|
||||
LogOpts extends Prisma.LogLevel = never,
|
||||
OmitOpts extends
|
||||
Prisma.PrismaClientOptions['omit'] = Prisma.PrismaClientOptions['omit'],
|
||||
ExtArgs extends
|
||||
runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs,
|
||||
> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>;
|
||||
export { Prisma };
|
||||
|
||||
/**
|
||||
* Model Project
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Project = Prisma.ProjectModel
|
||||
export type Project = Prisma.ProjectModel;
|
||||
/**
|
||||
* Model User
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type User = Prisma.UserModel
|
||||
export type User = Prisma.UserModel;
|
||||
/**
|
||||
* Model Pipeline
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Pipeline = Prisma.PipelineModel
|
||||
export type Pipeline = Prisma.PipelineModel;
|
||||
/**
|
||||
* Model Step
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Step = Prisma.StepModel
|
||||
export type Step = Prisma.StepModel;
|
||||
/**
|
||||
* Model Deployment
|
||||
*
|
||||
*
|
||||
*/
|
||||
export type Deployment = Prisma.DeploymentModel
|
||||
export type Deployment = Prisma.DeploymentModel;
|
||||
|
||||
@@ -1,402 +1,426 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This file exports various common sort, input & filter types that are not directly linked to a particular model.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
import type * as runtime from "@prisma/client/runtime/client"
|
||||
import * as $Enums from "./enums.ts"
|
||||
import type * as Prisma from "./internal/prismaNamespace.ts"
|
||||
|
||||
import type * as runtime from '@prisma/client/runtime/client';
|
||||
import * as $Enums from './enums.ts';
|
||||
import type * as Prisma from './internal/prismaNamespace.ts';
|
||||
|
||||
export type IntFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
in?: number[]
|
||||
notIn?: number[]
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntFilter<$PrismaModel> | number
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
in?: number[];
|
||||
notIn?: number[];
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedIntFilter<$PrismaModel> | number;
|
||||
};
|
||||
|
||||
export type StringFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
in?: string[]
|
||||
notIn?: string[]
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
in?: string[];
|
||||
notIn?: string[];
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedStringFilter<$PrismaModel> | string;
|
||||
};
|
||||
|
||||
export type StringNullableFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | null
|
||||
notIn?: string[] | null
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null;
|
||||
in?: string[] | null;
|
||||
notIn?: string[] | null;
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null;
|
||||
};
|
||||
|
||||
export type DateTimeFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[]
|
||||
notIn?: Date[] | string[]
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
in?: Date[] | string[];
|
||||
notIn?: Date[] | string[];
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string;
|
||||
};
|
||||
|
||||
export type SortOrderInput = {
|
||||
sort: Prisma.SortOrder
|
||||
nulls?: Prisma.NullsOrder
|
||||
}
|
||||
sort: Prisma.SortOrder;
|
||||
nulls?: Prisma.NullsOrder;
|
||||
};
|
||||
|
||||
export type IntWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
in?: number[]
|
||||
notIn?: number[]
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
in?: number[];
|
||||
notIn?: number[];
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>;
|
||||
_sum?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type StringWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
in?: string[]
|
||||
notIn?: string[]
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
in?: string[];
|
||||
notIn?: string[];
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedStringFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedStringFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | null
|
||||
notIn?: string[] | null
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null;
|
||||
in?: string[] | null;
|
||||
notIn?: string[] | null;
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
not?:
|
||||
| Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel>
|
||||
| string
|
||||
| null;
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[]
|
||||
notIn?: Date[] | string[]
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
in?: Date[] | string[];
|
||||
notIn?: Date[] | string[];
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type BoolFilter<$PrismaModel = never> = {
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
|
||||
}
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean;
|
||||
};
|
||||
|
||||
export type BoolWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||
}
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedBoolFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedBoolFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type IntNullableFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | null
|
||||
notIn?: number[] | null
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null;
|
||||
in?: number[] | null;
|
||||
notIn?: number[] | null;
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null;
|
||||
};
|
||||
|
||||
export type IntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | null
|
||||
notIn?: number[] | null
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null;
|
||||
in?: number[] | null;
|
||||
notIn?: number[] | null;
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?:
|
||||
| Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel>
|
||||
| number
|
||||
| null;
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>;
|
||||
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type DateTimeNullableFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | null
|
||||
notIn?: Date[] | string[] | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null;
|
||||
in?: Date[] | string[] | null;
|
||||
notIn?: Date[] | string[] | null;
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?:
|
||||
| Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
| Date
|
||||
| string
|
||||
| null;
|
||||
};
|
||||
|
||||
export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | null
|
||||
notIn?: Date[] | string[] | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null;
|
||||
in?: Date[] | string[] | null;
|
||||
notIn?: Date[] | string[] | null;
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?:
|
||||
| Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel>
|
||||
| Date
|
||||
| string
|
||||
| null;
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedIntFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
in?: number[]
|
||||
notIn?: number[]
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntFilter<$PrismaModel> | number
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
in?: number[];
|
||||
notIn?: number[];
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedIntFilter<$PrismaModel> | number;
|
||||
};
|
||||
|
||||
export type NestedStringFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
in?: string[]
|
||||
notIn?: string[]
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
in?: string[];
|
||||
notIn?: string[];
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedStringFilter<$PrismaModel> | string;
|
||||
};
|
||||
|
||||
export type NestedStringNullableFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | null
|
||||
notIn?: string[] | null
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null;
|
||||
in?: string[] | null;
|
||||
notIn?: string[] | null;
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null;
|
||||
};
|
||||
|
||||
export type NestedDateTimeFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[]
|
||||
notIn?: Date[] | string[]
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
in?: Date[] | string[];
|
||||
notIn?: Date[] | string[];
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string;
|
||||
};
|
||||
|
||||
export type NestedIntWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
in?: number[]
|
||||
notIn?: number[]
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
in?: number[];
|
||||
notIn?: number[];
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_avg?: Prisma.NestedFloatFilter<$PrismaModel>;
|
||||
_sum?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedFloatFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
in?: number[]
|
||||
notIn?: number[]
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedFloatFilter<$PrismaModel> | number
|
||||
}
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
in?: number[];
|
||||
notIn?: number[];
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedFloatFilter<$PrismaModel> | number;
|
||||
};
|
||||
|
||||
export type NestedStringWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
in?: string[]
|
||||
notIn?: string[]
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
in?: string[];
|
||||
notIn?: string[];
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedStringFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedStringFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | null
|
||||
notIn?: string[] | null
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||
}
|
||||
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null;
|
||||
in?: string[] | null;
|
||||
notIn?: string[] | null;
|
||||
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>;
|
||||
not?:
|
||||
| Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel>
|
||||
| string
|
||||
| null;
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedIntNullableFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | null
|
||||
notIn?: number[] | null
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null;
|
||||
in?: number[] | null;
|
||||
notIn?: number[] | null;
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null;
|
||||
};
|
||||
|
||||
export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
in?: Date[] | string[]
|
||||
notIn?: Date[] | string[]
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
in?: Date[] | string[];
|
||||
notIn?: Date[] | string[];
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedBoolFilter<$PrismaModel = never> = {
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
|
||||
}
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean;
|
||||
};
|
||||
|
||||
export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||
}
|
||||
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean;
|
||||
_count?: Prisma.NestedIntFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedBoolFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedBoolFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | null
|
||||
notIn?: number[] | null
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
}
|
||||
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null;
|
||||
in?: number[] | null;
|
||||
notIn?: number[] | null;
|
||||
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>;
|
||||
not?:
|
||||
| Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel>
|
||||
| number
|
||||
| null;
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>;
|
||||
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
export type NestedFloatNullableFilter<$PrismaModel = never> = {
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | null
|
||||
notIn?: number[] | null
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null
|
||||
}
|
||||
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null;
|
||||
in?: number[] | null;
|
||||
notIn?: number[] | null;
|
||||
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>;
|
||||
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null;
|
||||
};
|
||||
|
||||
export type NestedDateTimeNullableFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | null
|
||||
notIn?: Date[] | string[] | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
|
||||
}
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null;
|
||||
in?: Date[] | string[] | null;
|
||||
notIn?: Date[] | string[] | null;
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?:
|
||||
| Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
| Date
|
||||
| string
|
||||
| null;
|
||||
};
|
||||
|
||||
export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||
in?: Date[] | string[] | null
|
||||
notIn?: Date[] | string[] | null
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
|
||||
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null;
|
||||
in?: Date[] | string[] | null;
|
||||
notIn?: Date[] | string[] | null;
|
||||
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>;
|
||||
not?:
|
||||
| Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel>
|
||||
| Date
|
||||
| string
|
||||
| null;
|
||||
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>;
|
||||
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>;
|
||||
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>;
|
||||
};
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This file exports all enum related types from the schema.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
|
||||
* This file exports all enum related types from the schema.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
|
||||
// This file is empty because there are no enums in the schema.
|
||||
export {}
|
||||
export {};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* WARNING: This is an internal file that is subject to change!
|
||||
*
|
||||
@@ -11,49 +10,58 @@
|
||||
* Please import the `PrismaClient` class from the `client.ts` file instead.
|
||||
*/
|
||||
|
||||
import * as runtime from "@prisma/client/runtime/client"
|
||||
import type * as Prisma from "./prismaNamespace.ts"
|
||||
|
||||
import * as runtime from '@prisma/client/runtime/client';
|
||||
import type * as Prisma from './prismaNamespace.ts';
|
||||
|
||||
const config: runtime.GetPrismaClientConfig = {
|
||||
"previewFeatures": [],
|
||||
"clientVersion": "7.0.0",
|
||||
"engineVersion": "0c19ccc313cf9911a90d99d2ac2eb0280c76c513",
|
||||
"activeProvider": "sqlite",
|
||||
"inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\ngenerator client {\n provider = \"prisma-client\"\n output = \"../generated\"\n}\n\ndatasource db {\n provider = \"sqlite\"\n}\n\nmodel Project {\n id Int @id @default(autoincrement())\n name String\n description String?\n repository String\n projectDir String @unique // 项目工作目录路径(必填)\n // Relations\n deployments Deployment[]\n pipelines Pipeline[]\n\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n}\n\nmodel User {\n id Int @id @default(autoincrement())\n username String\n login String\n email String\n avatar_url String?\n active Boolean @default(true)\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String @default(\"system\")\n updatedBy String @default(\"system\")\n}\n\nmodel Pipeline {\n id Int @id @default(autoincrement())\n name String\n description String?\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n\n // Relations\n projectId Int?\n Project Project? @relation(fields: [projectId], references: [id])\n steps Step[]\n}\n\nmodel Step {\n id Int @id @default(autoincrement())\n name String\n order Int\n script String // 执行的脚本命令\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n\n pipelineId Int\n pipeline Pipeline @relation(fields: [pipelineId], references: [id])\n}\n\nmodel Deployment {\n id Int @id @default(autoincrement())\n branch String\n env String?\n status String // pending, running, success, failed, cancelled\n commitHash String?\n commitMessage String?\n buildLog String?\n sparseCheckoutPaths String? // 稀疏检出路径,用于monorepo项目\n startedAt DateTime @default(now())\n finishedAt DateTime?\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n\n projectId Int\n Project Project? @relation(fields: [projectId], references: [id])\n pipelineId Int\n}\n",
|
||||
"runtimeDataModel": {
|
||||
"models": {},
|
||||
"enums": {},
|
||||
"types": {}
|
||||
}
|
||||
}
|
||||
previewFeatures: [],
|
||||
clientVersion: '7.0.0',
|
||||
engineVersion: '0c19ccc313cf9911a90d99d2ac2eb0280c76c513',
|
||||
activeProvider: 'sqlite',
|
||||
inlineSchema:
|
||||
'// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\ngenerator client {\n provider = "prisma-client"\n output = "../generated"\n}\n\ndatasource db {\n provider = "sqlite"\n}\n\nmodel Project {\n id Int @id @default(autoincrement())\n name String\n description String?\n repository String\n projectDir String @unique // 项目工作目录路径(必填)\n envPresets String? // 环境预设配置(JSON格式)\n // Relations\n deployments Deployment[]\n pipelines Pipeline[]\n\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n}\n\nmodel User {\n id Int @id @default(autoincrement())\n username String\n login String\n email String\n avatar_url String?\n active Boolean @default(true)\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String @default("system")\n updatedBy String @default("system")\n}\n\nmodel Pipeline {\n id Int @id @default(autoincrement())\n name String\n description String?\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n\n // Relations\n projectId Int?\n Project Project? @relation(fields: [projectId], references: [id])\n steps Step[]\n}\n\nmodel Step {\n id Int @id @default(autoincrement())\n name String\n order Int\n script String // 执行的脚本命令\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n\n pipelineId Int\n pipeline Pipeline @relation(fields: [pipelineId], references: [id])\n}\n\nmodel Deployment {\n id Int @id @default(autoincrement())\n branch String\n envVars String? // 环境变量(JSON格式),统一存储所有配置\n status String // pending, running, success, failed, cancelled\n commitHash String?\n commitMessage String?\n buildLog String?\n sparseCheckoutPaths String? // 稀疏检出路径,用于monorepo项目\n startedAt DateTime @default(now())\n finishedAt DateTime?\n valid Int @default(1)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n createdBy String\n updatedBy String\n\n projectId Int\n Project Project? @relation(fields: [projectId], references: [id])\n pipelineId Int\n}\n',
|
||||
runtimeDataModel: {
|
||||
models: {},
|
||||
enums: {},
|
||||
types: {},
|
||||
},
|
||||
};
|
||||
|
||||
config.runtimeDataModel = JSON.parse("{\"models\":{\"Project\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"repository\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectDir\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"deployments\",\"kind\":\"object\",\"type\":\"Deployment\",\"relationName\":\"DeploymentToProject\"},{\"name\":\"pipelines\",\"kind\":\"object\",\"type\":\"Pipeline\",\"relationName\":\"PipelineToProject\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"User\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"login\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"avatar_url\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"active\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Pipeline\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"description\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"Project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"PipelineToProject\"},{\"name\":\"steps\",\"kind\":\"object\",\"type\":\"Step\",\"relationName\":\"PipelineToStep\"}],\"dbName\":null},\"Step\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"order\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"script\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"pipelineId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"pipeline\",\"kind\":\"object\",\"type\":\"Pipeline\",\"relationName\":\"PipelineToStep\"}],\"dbName\":null},\"Deployment\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"branch\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"env\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"commitHash\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"commitMessage\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"buildLog\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sparseCheckoutPaths\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"startedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"finishedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"valid\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"updatedBy\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"projectId\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"Project\",\"kind\":\"object\",\"type\":\"Project\",\"relationName\":\"DeploymentToProject\"},{\"name\":\"pipelineId\",\"kind\":\"scalar\",\"type\":\"Int\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
|
||||
config.runtimeDataModel = JSON.parse(
|
||||
'{"models":{"Project":{"fields":[{"name":"id","kind":"scalar","type":"Int"},{"name":"name","kind":"scalar","type":"String"},{"name":"description","kind":"scalar","type":"String"},{"name":"repository","kind":"scalar","type":"String"},{"name":"projectDir","kind":"scalar","type":"String"},{"name":"envPresets","kind":"scalar","type":"String"},{"name":"deployments","kind":"object","type":"Deployment","relationName":"DeploymentToProject"},{"name":"pipelines","kind":"object","type":"Pipeline","relationName":"PipelineToProject"},{"name":"valid","kind":"scalar","type":"Int"},{"name":"createdAt","kind":"scalar","type":"DateTime"},{"name":"updatedAt","kind":"scalar","type":"DateTime"},{"name":"createdBy","kind":"scalar","type":"String"},{"name":"updatedBy","kind":"scalar","type":"String"}],"dbName":null},"User":{"fields":[{"name":"id","kind":"scalar","type":"Int"},{"name":"username","kind":"scalar","type":"String"},{"name":"login","kind":"scalar","type":"String"},{"name":"email","kind":"scalar","type":"String"},{"name":"avatar_url","kind":"scalar","type":"String"},{"name":"active","kind":"scalar","type":"Boolean"},{"name":"valid","kind":"scalar","type":"Int"},{"name":"createdAt","kind":"scalar","type":"DateTime"},{"name":"updatedAt","kind":"scalar","type":"DateTime"},{"name":"createdBy","kind":"scalar","type":"String"},{"name":"updatedBy","kind":"scalar","type":"String"}],"dbName":null},"Pipeline":{"fields":[{"name":"id","kind":"scalar","type":"Int"},{"name":"name","kind":"scalar","type":"String"},{"name":"description","kind":"scalar","type":"String"},{"name":"valid","kind":"scalar","type":"Int"},{"name":"createdAt","kind":"scalar","type":"DateTime"},{"name":"updatedAt","kind":"scalar","type":"DateTime"},{"name":"createdBy","kind":"scalar","type":"String"},{"name":"updatedBy","kind":"scalar","type":"String"},{"name":"projectId","kind":"scalar","type":"Int"},{"name":"Project","kind":"object","type":"Project","relationName":"PipelineToProject"},{"name":"steps","kind":"object","type":"Step","relationName":"PipelineToStep"}],"dbName":null},"Step":{"fields":[{"name":"id","kind":"scalar","type":"Int"},{"name":"name","kind":"scalar","type":"String"},{"name":"order","kind":"scalar","type":"Int"},{"name":"script","kind":"scalar","type":"String"},{"name":"valid","kind":"scalar","type":"Int"},{"name":"createdAt","kind":"scalar","type":"DateTime"},{"name":"updatedAt","kind":"scalar","type":"DateTime"},{"name":"createdBy","kind":"scalar","type":"String"},{"name":"updatedBy","kind":"scalar","type":"String"},{"name":"pipelineId","kind":"scalar","type":"Int"},{"name":"pipeline","kind":"object","type":"Pipeline","relationName":"PipelineToStep"}],"dbName":null},"Deployment":{"fields":[{"name":"id","kind":"scalar","type":"Int"},{"name":"branch","kind":"scalar","type":"String"},{"name":"envVars","kind":"scalar","type":"String"},{"name":"status","kind":"scalar","type":"String"},{"name":"commitHash","kind":"scalar","type":"String"},{"name":"commitMessage","kind":"scalar","type":"String"},{"name":"buildLog","kind":"scalar","type":"String"},{"name":"sparseCheckoutPaths","kind":"scalar","type":"String"},{"name":"startedAt","kind":"scalar","type":"DateTime"},{"name":"finishedAt","kind":"scalar","type":"DateTime"},{"name":"valid","kind":"scalar","type":"Int"},{"name":"createdAt","kind":"scalar","type":"DateTime"},{"name":"updatedAt","kind":"scalar","type":"DateTime"},{"name":"createdBy","kind":"scalar","type":"String"},{"name":"updatedBy","kind":"scalar","type":"String"},{"name":"projectId","kind":"scalar","type":"Int"},{"name":"Project","kind":"object","type":"Project","relationName":"DeploymentToProject"},{"name":"pipelineId","kind":"scalar","type":"Int"}],"dbName":null}},"enums":{},"types":{}}',
|
||||
);
|
||||
|
||||
async function decodeBase64AsWasm(wasmBase64: string): Promise<WebAssembly.Module> {
|
||||
const { Buffer } = await import('node:buffer')
|
||||
const wasmArray = Buffer.from(wasmBase64, 'base64')
|
||||
return new WebAssembly.Module(wasmArray)
|
||||
async function decodeBase64AsWasm(
|
||||
wasmBase64: string,
|
||||
): Promise<WebAssembly.Module> {
|
||||
const { Buffer } = await import('node:buffer');
|
||||
const wasmArray = Buffer.from(wasmBase64, 'base64');
|
||||
return new WebAssembly.Module(wasmArray);
|
||||
}
|
||||
|
||||
config.compilerWasm = {
|
||||
getRuntime: async () => await import("@prisma/client/runtime/query_compiler_bg.sqlite.mjs"),
|
||||
getRuntime: async () =>
|
||||
await import('@prisma/client/runtime/query_compiler_bg.sqlite.mjs'),
|
||||
|
||||
getQueryCompilerWasmModule: async () => {
|
||||
const { wasm } = await import("@prisma/client/runtime/query_compiler_bg.sqlite.wasm-base64.mjs")
|
||||
return await decodeBase64AsWasm(wasm)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const { wasm } = await import(
|
||||
'@prisma/client/runtime/query_compiler_bg.sqlite.wasm-base64.mjs'
|
||||
);
|
||||
return await decodeBase64AsWasm(wasm);
|
||||
},
|
||||
};
|
||||
|
||||
export type LogOptions<ClientOptions extends Prisma.PrismaClientOptions> =
|
||||
'log' extends keyof ClientOptions ? ClientOptions['log'] extends Array<Prisma.LogLevel | Prisma.LogDefinition> ? Prisma.GetEvents<ClientOptions['log']> : never : never
|
||||
'log' extends keyof ClientOptions
|
||||
? ClientOptions['log'] extends Array<Prisma.LogLevel | Prisma.LogDefinition>
|
||||
? Prisma.GetEvents<ClientOptions['log']>
|
||||
: never
|
||||
: never;
|
||||
|
||||
export interface PrismaClientConstructor {
|
||||
/**
|
||||
/**
|
||||
* ## Prisma Client
|
||||
*
|
||||
*
|
||||
* Type-safe database client for TypeScript
|
||||
* @example
|
||||
* ```
|
||||
@@ -61,21 +69,28 @@ export interface PrismaClientConstructor {
|
||||
* // Fetch zero or more Projects
|
||||
* const projects = await prisma.project.findMany()
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Read more in our [docs](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client).
|
||||
*/
|
||||
|
||||
new <
|
||||
Options extends Prisma.PrismaClientOptions = Prisma.PrismaClientOptions,
|
||||
LogOpts extends LogOptions<Options> = LogOptions<Options>,
|
||||
OmitOpts extends Prisma.PrismaClientOptions['omit'] = Options extends { omit: infer U } ? U : Prisma.PrismaClientOptions['omit'],
|
||||
ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
|
||||
>(options: Prisma.Subset<Options, Prisma.PrismaClientOptions> ): PrismaClient<LogOpts, OmitOpts, ExtArgs>
|
||||
OmitOpts extends Prisma.PrismaClientOptions['omit'] = Options extends {
|
||||
omit: infer U;
|
||||
}
|
||||
? U
|
||||
: Prisma.PrismaClientOptions['omit'],
|
||||
ExtArgs extends
|
||||
runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs,
|
||||
>(
|
||||
options: Prisma.Subset<Options, Prisma.PrismaClientOptions>,
|
||||
): PrismaClient<LogOpts, OmitOpts, ExtArgs>;
|
||||
}
|
||||
|
||||
/**
|
||||
* ## Prisma Client
|
||||
*
|
||||
*
|
||||
* Type-safe database client for TypeScript
|
||||
* @example
|
||||
* ```
|
||||
@@ -83,18 +98,24 @@ export interface PrismaClientConstructor {
|
||||
* // Fetch zero or more Projects
|
||||
* const projects = await prisma.project.findMany()
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Read more in our [docs](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client).
|
||||
*/
|
||||
|
||||
export interface PrismaClient<
|
||||
in LogOpts extends Prisma.LogLevel = never,
|
||||
in out OmitOpts extends Prisma.PrismaClientOptions['omit'] = undefined,
|
||||
in out ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
|
||||
in out ExtArgs extends
|
||||
runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs,
|
||||
> {
|
||||
[K: symbol]: { types: Prisma.TypeMap<ExtArgs>['other'] }
|
||||
[K: symbol]: { types: Prisma.TypeMap<ExtArgs>['other'] };
|
||||
|
||||
$on<V extends LogOpts>(eventType: V, callback: (event: V extends 'query' ? Prisma.QueryEvent : Prisma.LogEvent) => void): PrismaClient;
|
||||
$on<V extends LogOpts>(
|
||||
eventType: V,
|
||||
callback: (
|
||||
event: V extends 'query' ? Prisma.QueryEvent : Prisma.LogEvent,
|
||||
) => void,
|
||||
): PrismaClient;
|
||||
|
||||
/**
|
||||
* Connect with the database
|
||||
@@ -106,7 +127,7 @@ export interface PrismaClient<
|
||||
*/
|
||||
$disconnect(): runtime.Types.Utils.JsPromise<void>;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Executes a prepared raw query and returns the number of affected rows.
|
||||
* @example
|
||||
* ```
|
||||
@@ -115,7 +136,10 @@ export interface PrismaClient<
|
||||
*
|
||||
* Read more in our [docs](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/raw-database-access).
|
||||
*/
|
||||
$executeRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<number>;
|
||||
$executeRaw<T = unknown>(
|
||||
query: TemplateStringsArray | Prisma.Sql,
|
||||
...values: any[]
|
||||
): Prisma.PrismaPromise<number>;
|
||||
|
||||
/**
|
||||
* Executes a raw query and returns the number of affected rows.
|
||||
@@ -127,7 +151,10 @@ export interface PrismaClient<
|
||||
*
|
||||
* Read more in our [docs](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/raw-database-access).
|
||||
*/
|
||||
$executeRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<number>;
|
||||
$executeRawUnsafe<T = unknown>(
|
||||
query: string,
|
||||
...values: any[]
|
||||
): Prisma.PrismaPromise<number>;
|
||||
|
||||
/**
|
||||
* Performs a prepared raw query and returns the `SELECT` data.
|
||||
@@ -138,7 +165,10 @@ export interface PrismaClient<
|
||||
*
|
||||
* Read more in our [docs](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/raw-database-access).
|
||||
*/
|
||||
$queryRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<T>;
|
||||
$queryRaw<T = unknown>(
|
||||
query: TemplateStringsArray | Prisma.Sql,
|
||||
...values: any[]
|
||||
): Prisma.PrismaPromise<T>;
|
||||
|
||||
/**
|
||||
* Performs a raw query and returns the `SELECT` data.
|
||||
@@ -150,8 +180,10 @@ export interface PrismaClient<
|
||||
*
|
||||
* Read more in our [docs](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/raw-database-access).
|
||||
*/
|
||||
$queryRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<T>;
|
||||
|
||||
$queryRawUnsafe<T = unknown>(
|
||||
query: string,
|
||||
...values: any[]
|
||||
): Prisma.PrismaPromise<T>;
|
||||
|
||||
/**
|
||||
* Allows the running of a sequence of read/write operations that are guaranteed to either succeed or fail as a whole.
|
||||
@@ -163,68 +195,88 @@ export interface PrismaClient<
|
||||
* prisma.user.create({ data: { name: 'Alice' } }),
|
||||
* ])
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Read more in our [docs](https://www.prisma.io/docs/concepts/components/prisma-client/transactions).
|
||||
*/
|
||||
$transaction<P extends Prisma.PrismaPromise<any>[]>(arg: [...P], options?: { isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<runtime.Types.Utils.UnwrapTuple<P>>
|
||||
$transaction<P extends Prisma.PrismaPromise<any>[]>(
|
||||
arg: [...P],
|
||||
options?: { isolationLevel?: Prisma.TransactionIsolationLevel },
|
||||
): runtime.Types.Utils.JsPromise<runtime.Types.Utils.UnwrapTuple<P>>;
|
||||
|
||||
$transaction<R>(fn: (prisma: Omit<PrismaClient, runtime.ITXClientDenyList>) => runtime.Types.Utils.JsPromise<R>, options?: { maxWait?: number, timeout?: number, isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<R>
|
||||
$transaction<R>(
|
||||
fn: (
|
||||
prisma: Omit<PrismaClient, runtime.ITXClientDenyList>,
|
||||
) => runtime.Types.Utils.JsPromise<R>,
|
||||
options?: {
|
||||
maxWait?: number;
|
||||
timeout?: number;
|
||||
isolationLevel?: Prisma.TransactionIsolationLevel;
|
||||
},
|
||||
): runtime.Types.Utils.JsPromise<R>;
|
||||
|
||||
$extends: runtime.Types.Extensions.ExtendsHook<"extends", Prisma.TypeMapCb<OmitOpts>, ExtArgs, runtime.Types.Utils.Call<Prisma.TypeMapCb<OmitOpts>, {
|
||||
extArgs: ExtArgs
|
||||
}>>
|
||||
$extends: runtime.Types.Extensions.ExtendsHook<
|
||||
'extends',
|
||||
Prisma.TypeMapCb<OmitOpts>,
|
||||
ExtArgs,
|
||||
runtime.Types.Utils.Call<
|
||||
Prisma.TypeMapCb<OmitOpts>,
|
||||
{
|
||||
extArgs: ExtArgs;
|
||||
}
|
||||
>
|
||||
>;
|
||||
|
||||
/**
|
||||
/**
|
||||
* `prisma.project`: Exposes CRUD operations for the **Project** model.
|
||||
* Example usage:
|
||||
* ```ts
|
||||
* // Fetch zero or more Projects
|
||||
* const projects = await prisma.project.findMany()
|
||||
* ```
|
||||
*/
|
||||
* Example usage:
|
||||
* ```ts
|
||||
* // Fetch zero or more Projects
|
||||
* const projects = await prisma.project.findMany()
|
||||
* ```
|
||||
*/
|
||||
get project(): Prisma.ProjectDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||
|
||||
/**
|
||||
* `prisma.user`: Exposes CRUD operations for the **User** model.
|
||||
* Example usage:
|
||||
* ```ts
|
||||
* // Fetch zero or more Users
|
||||
* const users = await prisma.user.findMany()
|
||||
* ```
|
||||
*/
|
||||
* Example usage:
|
||||
* ```ts
|
||||
* // Fetch zero or more Users
|
||||
* const users = await prisma.user.findMany()
|
||||
* ```
|
||||
*/
|
||||
get user(): Prisma.UserDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||
|
||||
/**
|
||||
* `prisma.pipeline`: Exposes CRUD operations for the **Pipeline** model.
|
||||
* Example usage:
|
||||
* ```ts
|
||||
* // Fetch zero or more Pipelines
|
||||
* const pipelines = await prisma.pipeline.findMany()
|
||||
* ```
|
||||
*/
|
||||
* Example usage:
|
||||
* ```ts
|
||||
* // Fetch zero or more Pipelines
|
||||
* const pipelines = await prisma.pipeline.findMany()
|
||||
* ```
|
||||
*/
|
||||
get pipeline(): Prisma.PipelineDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||
|
||||
/**
|
||||
* `prisma.step`: Exposes CRUD operations for the **Step** model.
|
||||
* Example usage:
|
||||
* ```ts
|
||||
* // Fetch zero or more Steps
|
||||
* const steps = await prisma.step.findMany()
|
||||
* ```
|
||||
*/
|
||||
* Example usage:
|
||||
* ```ts
|
||||
* // Fetch zero or more Steps
|
||||
* const steps = await prisma.step.findMany()
|
||||
* ```
|
||||
*/
|
||||
get step(): Prisma.StepDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||
|
||||
/**
|
||||
* `prisma.deployment`: Exposes CRUD operations for the **Deployment** model.
|
||||
* Example usage:
|
||||
* ```ts
|
||||
* // Fetch zero or more Deployments
|
||||
* const deployments = await prisma.deployment.findMany()
|
||||
* ```
|
||||
*/
|
||||
* Example usage:
|
||||
* ```ts
|
||||
* // Fetch zero or more Deployments
|
||||
* const deployments = await prisma.deployment.findMany()
|
||||
* ```
|
||||
*/
|
||||
get deployment(): Prisma.DeploymentDelegate<ExtArgs, { omit: OmitOpts }>;
|
||||
}
|
||||
|
||||
export function getPrismaClientClass(): PrismaClientConstructor {
|
||||
return runtime.getPrismaClient(config) as unknown as PrismaClientConstructor
|
||||
return runtime.getPrismaClient(config) as unknown as PrismaClientConstructor;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,7 @@
|
||||
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* WARNING: This is an internal file that is subject to change!
|
||||
*
|
||||
@@ -15,61 +14,65 @@
|
||||
* model files in the `model` directory!
|
||||
*/
|
||||
|
||||
import * as runtime from "@prisma/client/runtime/index-browser"
|
||||
import * as runtime from '@prisma/client/runtime/index-browser';
|
||||
|
||||
export type * from '../models.ts'
|
||||
export type * from './prismaNamespace.ts'
|
||||
|
||||
export const Decimal = runtime.Decimal
|
||||
export type * from '../models.ts';
|
||||
export type * from './prismaNamespace.ts';
|
||||
|
||||
export const Decimal = runtime.Decimal;
|
||||
|
||||
export const NullTypes = {
|
||||
DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull),
|
||||
JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull),
|
||||
AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull),
|
||||
}
|
||||
DbNull: runtime.NullTypes.DbNull as new (
|
||||
secret: never,
|
||||
) => typeof runtime.DbNull,
|
||||
JsonNull: runtime.NullTypes.JsonNull as new (
|
||||
secret: never,
|
||||
) => typeof runtime.JsonNull,
|
||||
AnyNull: runtime.NullTypes.AnyNull as new (
|
||||
secret: never,
|
||||
) => typeof runtime.AnyNull,
|
||||
};
|
||||
/**
|
||||
* Helper for filtering JSON entries that have `null` on the database (empty on the db)
|
||||
*
|
||||
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||
*/
|
||||
export const DbNull = runtime.DbNull
|
||||
export const DbNull = runtime.DbNull;
|
||||
|
||||
/**
|
||||
* Helper for filtering JSON entries that have JSON `null` values (not empty on the db)
|
||||
*
|
||||
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||
*/
|
||||
export const JsonNull = runtime.JsonNull
|
||||
export const JsonNull = runtime.JsonNull;
|
||||
|
||||
/**
|
||||
* Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull`
|
||||
*
|
||||
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||
*/
|
||||
export const AnyNull = runtime.AnyNull
|
||||
|
||||
export const AnyNull = runtime.AnyNull;
|
||||
|
||||
export const ModelName = {
|
||||
Project: 'Project',
|
||||
User: 'User',
|
||||
Pipeline: 'Pipeline',
|
||||
Step: 'Step',
|
||||
Deployment: 'Deployment'
|
||||
} as const
|
||||
Deployment: 'Deployment',
|
||||
} as const;
|
||||
|
||||
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||
export type ModelName = (typeof ModelName)[keyof typeof ModelName];
|
||||
|
||||
/*
|
||||
* Enums
|
||||
*/
|
||||
|
||||
export const TransactionIsolationLevel = {
|
||||
Serializable: 'Serializable'
|
||||
} as const
|
||||
|
||||
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
|
||||
Serializable: 'Serializable',
|
||||
} as const;
|
||||
|
||||
export type TransactionIsolationLevel =
|
||||
(typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel];
|
||||
|
||||
export const ProjectScalarFieldEnum = {
|
||||
id: 'id',
|
||||
@@ -77,15 +80,16 @@ export const ProjectScalarFieldEnum = {
|
||||
description: 'description',
|
||||
repository: 'repository',
|
||||
projectDir: 'projectDir',
|
||||
envPresets: 'envPresets',
|
||||
valid: 'valid',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
createdBy: 'createdBy',
|
||||
updatedBy: 'updatedBy'
|
||||
} as const
|
||||
|
||||
export type ProjectScalarFieldEnum = (typeof ProjectScalarFieldEnum)[keyof typeof ProjectScalarFieldEnum]
|
||||
updatedBy: 'updatedBy',
|
||||
} as const;
|
||||
|
||||
export type ProjectScalarFieldEnum =
|
||||
(typeof ProjectScalarFieldEnum)[keyof typeof ProjectScalarFieldEnum];
|
||||
|
||||
export const UserScalarFieldEnum = {
|
||||
id: 'id',
|
||||
@@ -98,11 +102,11 @@ export const UserScalarFieldEnum = {
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt',
|
||||
createdBy: 'createdBy',
|
||||
updatedBy: 'updatedBy'
|
||||
} as const
|
||||
|
||||
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum]
|
||||
updatedBy: 'updatedBy',
|
||||
} as const;
|
||||
|
||||
export type UserScalarFieldEnum =
|
||||
(typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum];
|
||||
|
||||
export const PipelineScalarFieldEnum = {
|
||||
id: 'id',
|
||||
@@ -113,11 +117,11 @@ export const PipelineScalarFieldEnum = {
|
||||
updatedAt: 'updatedAt',
|
||||
createdBy: 'createdBy',
|
||||
updatedBy: 'updatedBy',
|
||||
projectId: 'projectId'
|
||||
} as const
|
||||
|
||||
export type PipelineScalarFieldEnum = (typeof PipelineScalarFieldEnum)[keyof typeof PipelineScalarFieldEnum]
|
||||
projectId: 'projectId',
|
||||
} as const;
|
||||
|
||||
export type PipelineScalarFieldEnum =
|
||||
(typeof PipelineScalarFieldEnum)[keyof typeof PipelineScalarFieldEnum];
|
||||
|
||||
export const StepScalarFieldEnum = {
|
||||
id: 'id',
|
||||
@@ -129,16 +133,16 @@ export const StepScalarFieldEnum = {
|
||||
updatedAt: 'updatedAt',
|
||||
createdBy: 'createdBy',
|
||||
updatedBy: 'updatedBy',
|
||||
pipelineId: 'pipelineId'
|
||||
} as const
|
||||
|
||||
export type StepScalarFieldEnum = (typeof StepScalarFieldEnum)[keyof typeof StepScalarFieldEnum]
|
||||
pipelineId: 'pipelineId',
|
||||
} as const;
|
||||
|
||||
export type StepScalarFieldEnum =
|
||||
(typeof StepScalarFieldEnum)[keyof typeof StepScalarFieldEnum];
|
||||
|
||||
export const DeploymentScalarFieldEnum = {
|
||||
id: 'id',
|
||||
branch: 'branch',
|
||||
env: 'env',
|
||||
envVars: 'envVars',
|
||||
status: 'status',
|
||||
commitHash: 'commitHash',
|
||||
commitMessage: 'commitMessage',
|
||||
@@ -152,24 +156,22 @@ export const DeploymentScalarFieldEnum = {
|
||||
createdBy: 'createdBy',
|
||||
updatedBy: 'updatedBy',
|
||||
projectId: 'projectId',
|
||||
pipelineId: 'pipelineId'
|
||||
} as const
|
||||
|
||||
export type DeploymentScalarFieldEnum = (typeof DeploymentScalarFieldEnum)[keyof typeof DeploymentScalarFieldEnum]
|
||||
pipelineId: 'pipelineId',
|
||||
} as const;
|
||||
|
||||
export type DeploymentScalarFieldEnum =
|
||||
(typeof DeploymentScalarFieldEnum)[keyof typeof DeploymentScalarFieldEnum];
|
||||
|
||||
export const SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
} as const
|
||||
|
||||
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
|
||||
desc: 'desc',
|
||||
} as const;
|
||||
|
||||
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder];
|
||||
|
||||
export const NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
} as const
|
||||
|
||||
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]
|
||||
last: 'last',
|
||||
} as const;
|
||||
|
||||
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder];
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
|
||||
export type * from './commonInputTypes.ts';
|
||||
export type * from './models/Deployment.ts';
|
||||
export type * from './models/Pipeline.ts';
|
||||
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||
/* eslint-disable */
|
||||
// biome-ignore-all lint: generated file
|
||||
// @ts-nocheck
|
||||
// @ts-nocheck
|
||||
/*
|
||||
* This is a barrel export file for all models and their related types.
|
||||
*
|
||||
* 🟢 You can import this file directly.
|
||||
*/
|
||||
export type * from './models/Project.ts'
|
||||
export type * from './models/User.ts'
|
||||
export type * from './models/Pipeline.ts'
|
||||
export type * from './models/Step.ts'
|
||||
export type * from './models/Deployment.ts'
|
||||
export type * from './commonInputTypes.ts'
|
||||
export type * from './models/Project.ts';
|
||||
export type * from './models/Step.ts';
|
||||
export type * from './models/User.ts';
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -3,9 +3,9 @@
|
||||
* 封装 Git 操作:克隆、更新、分支切换等
|
||||
*/
|
||||
|
||||
import { $ } from 'zx';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { $ } from 'zx';
|
||||
import { log } from './logger';
|
||||
|
||||
/**
|
||||
|
||||
@@ -38,26 +38,23 @@ class Gitea {
|
||||
clientId: process.env.GITEA_CLIENT_ID!,
|
||||
clientSecret: process.env.GITEA_CLIENT_SECRET!,
|
||||
redirectUri: process.env.GITEA_REDIRECT_URI!,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async getToken(code: string) {
|
||||
const { giteaUrl, clientId, clientSecret, redirectUri } = this.config;
|
||||
console.log('this.config', this.config);
|
||||
const response = await fetch(
|
||||
`${giteaUrl}/login/oauth/access_token`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: redirectUri,
|
||||
}),
|
||||
},
|
||||
);
|
||||
const response = await fetch(`${giteaUrl}/login/oauth/access_token`, {
|
||||
method: 'POST',
|
||||
headers: this.getHeaders(),
|
||||
body: JSON.stringify({
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret,
|
||||
code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: redirectUri,
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
console.log(await response.json());
|
||||
throw new Error(`Fetch failed: ${response.status}`);
|
||||
@@ -108,19 +105,23 @@ class Gitea {
|
||||
* @param accessToken 访问令牌
|
||||
* @param sha 分支名称或提交SHA
|
||||
*/
|
||||
async getCommits(owner: string, repo: string, accessToken: string, sha?: string) {
|
||||
const url = new URL(`${this.config.giteaUrl}/api/v1/repos/${owner}/${repo}/commits`);
|
||||
async getCommits(
|
||||
owner: string,
|
||||
repo: string,
|
||||
accessToken: string,
|
||||
sha?: string,
|
||||
) {
|
||||
const url = new URL(
|
||||
`${this.config.giteaUrl}/api/v1/repos/${owner}/${repo}/commits`,
|
||||
);
|
||||
if (sha) {
|
||||
url.searchParams.append('sha', sha);
|
||||
}
|
||||
|
||||
const response = await fetch(
|
||||
url.toString(),
|
||||
{
|
||||
method: 'GET',
|
||||
headers: this.getHeaders(accessToken),
|
||||
},
|
||||
);
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
method: 'GET',
|
||||
headers: this.getHeaders(accessToken),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`Fetch failed: ${response.status}`);
|
||||
}
|
||||
@@ -133,7 +134,7 @@ class Gitea {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
if (accessToken) {
|
||||
headers['Authorization'] = `token ${accessToken}`;
|
||||
headers.Authorization = `token ${accessToken}`;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
@@ -17,11 +17,6 @@ export const DEFAULT_PIPELINE_TEMPLATES: PipelineTemplate[] = [
|
||||
name: 'Git Clone Pipeline',
|
||||
description: '默认的Git克隆流水线,用于从仓库克隆代码',
|
||||
steps: [
|
||||
{
|
||||
name: 'Clone Repository',
|
||||
order: 0,
|
||||
script: '# 克隆指定commit的代码\ngit init\ngit remote add origin $REPOSITORY_URL\ngit fetch --depth 1 origin $COMMIT_HASH\ngit checkout -q FETCH_HEAD\n\n# 显示当前提交信息\ngit log --oneline -1',
|
||||
},
|
||||
{
|
||||
name: 'Install Dependencies',
|
||||
order: 1,
|
||||
@@ -36,51 +31,21 @@ export const DEFAULT_PIPELINE_TEMPLATES: PipelineTemplate[] = [
|
||||
name: 'Build Project',
|
||||
order: 3,
|
||||
script: '# 构建项目\nnpm run build',
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Sparse Checkout Pipeline',
|
||||
description: '稀疏检出流水线,适用于monorepo项目,只获取指定目录的代码',
|
||||
steps: [
|
||||
{
|
||||
name: 'Sparse Checkout Repository',
|
||||
order: 0,
|
||||
script: '# 进行稀疏检出指定目录的代码\ngit init\ngit remote add origin $REPOSITORY_URL\ngit config core.sparseCheckout true\necho "$SPARSE_CHECKOUT_PATHS" > .git/info/sparse-checkout\ngit fetch --depth 1 origin $COMMIT_HASH\ngit checkout -q FETCH_HEAD\n\n# 显示当前提交信息\ngit log --oneline -1',
|
||||
},
|
||||
{
|
||||
name: 'Install Dependencies',
|
||||
order: 1,
|
||||
script: '# 安装项目依赖\nnpm install',
|
||||
},
|
||||
{
|
||||
name: 'Run Tests',
|
||||
order: 2,
|
||||
script: '# 运行测试\nnpm test',
|
||||
},
|
||||
{
|
||||
name: 'Build Project',
|
||||
order: 3,
|
||||
script: '# 构建项目\nnpm run build',
|
||||
}
|
||||
]
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Simple Deploy Pipeline',
|
||||
description: '简单的部署流水线,包含基本的构建和部署步骤',
|
||||
steps: [
|
||||
{
|
||||
name: 'Clone Repository',
|
||||
order: 0,
|
||||
script: '# 克隆指定commit的代码\ngit init\ngit remote add origin $REPOSITORY_URL\ngit fetch --depth 1 origin $COMMIT_HASH\ngit checkout -q FETCH_HEAD',
|
||||
},
|
||||
{
|
||||
name: 'Build and Deploy',
|
||||
order: 1,
|
||||
script: '# 构建并部署项目\nnpm run build\n\n# 部署到目标服务器\n# 这里可以添加具体的部署命令',
|
||||
}
|
||||
]
|
||||
}
|
||||
script:
|
||||
'# 构建并部署项目\nnpm run build\n\n# 部署到目标服务器\n# 这里可以添加具体的部署命令',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -94,10 +59,10 @@ export async function initializePipelineTemplates(): Promise<void> {
|
||||
const existingTemplates = await prisma.pipeline.findMany({
|
||||
where: {
|
||||
name: {
|
||||
in: DEFAULT_PIPELINE_TEMPLATES.map(template => template.name)
|
||||
in: DEFAULT_PIPELINE_TEMPLATES.map((template) => template.name),
|
||||
},
|
||||
valid: 1
|
||||
}
|
||||
valid: 1,
|
||||
},
|
||||
});
|
||||
|
||||
// 如果没有现有的模板,则创建默认模板
|
||||
@@ -113,8 +78,8 @@ export async function initializePipelineTemplates(): Promise<void> {
|
||||
createdBy: 'system',
|
||||
updatedBy: 'system',
|
||||
valid: 1,
|
||||
projectId: null // 模板不属于任何特定项目
|
||||
}
|
||||
projectId: null, // 模板不属于任何特定项目
|
||||
},
|
||||
});
|
||||
|
||||
// 创建模板步骤
|
||||
@@ -127,8 +92,8 @@ export async function initializePipelineTemplates(): Promise<void> {
|
||||
pipelineId: pipeline.id,
|
||||
createdBy: 'system',
|
||||
updatedBy: 'system',
|
||||
valid: 1
|
||||
}
|
||||
valid: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -148,25 +113,27 @@ export async function initializePipelineTemplates(): Promise<void> {
|
||||
/**
|
||||
* 获取所有可用的流水线模板
|
||||
*/
|
||||
export async function getAvailableTemplates(): Promise<Array<{id: number, name: string, description: string}>> {
|
||||
export async function getAvailableTemplates(): Promise<
|
||||
Array<{ id: number; name: string; description: string }>
|
||||
> {
|
||||
try {
|
||||
const templates = await prisma.pipeline.findMany({
|
||||
where: {
|
||||
projectId: null, // 模板流水线没有关联的项目
|
||||
valid: 1
|
||||
valid: 1,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
description: true
|
||||
}
|
||||
description: true,
|
||||
},
|
||||
});
|
||||
|
||||
// 处理可能为null的description字段
|
||||
return templates.map(template => ({
|
||||
return templates.map((template) => ({
|
||||
id: template.id,
|
||||
name: template.name,
|
||||
description: template.description || ''
|
||||
description: template.description || '',
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Failed to get pipeline templates:', error);
|
||||
@@ -185,7 +152,7 @@ export async function createPipelineFromTemplate(
|
||||
templateId: number,
|
||||
projectId: number,
|
||||
pipelineName: string,
|
||||
pipelineDescription: string
|
||||
pipelineDescription: string,
|
||||
): Promise<number> {
|
||||
try {
|
||||
// 获取模板流水线及其步骤
|
||||
@@ -193,18 +160,18 @@ export async function createPipelineFromTemplate(
|
||||
where: {
|
||||
id: templateId,
|
||||
projectId: null, // 确保是模板流水线
|
||||
valid: 1
|
||||
valid: 1,
|
||||
},
|
||||
include: {
|
||||
steps: {
|
||||
where: {
|
||||
valid: 1
|
||||
valid: 1,
|
||||
},
|
||||
orderBy: {
|
||||
order: 'asc'
|
||||
}
|
||||
}
|
||||
}
|
||||
order: 'asc',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!templatePipeline) {
|
||||
@@ -219,8 +186,8 @@ export async function createPipelineFromTemplate(
|
||||
projectId: projectId,
|
||||
createdBy: 'system',
|
||||
updatedBy: 'system',
|
||||
valid: 1
|
||||
}
|
||||
valid: 1,
|
||||
},
|
||||
});
|
||||
|
||||
// 复制模板步骤到新流水线
|
||||
@@ -233,12 +200,14 @@ export async function createPipelineFromTemplate(
|
||||
pipelineId: newPipeline.id,
|
||||
createdBy: 'system',
|
||||
updatedBy: 'system',
|
||||
valid: 1
|
||||
}
|
||||
valid: 1,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Created pipeline from template ${templateId}: ${newPipeline.name}`);
|
||||
console.log(
|
||||
`Created pipeline from template ${templateId}: ${newPipeline.name}`,
|
||||
);
|
||||
return newPipeline.id;
|
||||
} catch (error) {
|
||||
console.error('Failed to create pipeline from template:', error);
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import bodyParser from 'koa-bodyparser';
|
||||
import type Koa from 'koa';
|
||||
import bodyParser from 'koa-bodyparser';
|
||||
import type { Middleware } from './types.ts';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type Koa from 'koa';
|
||||
import { z } from 'zod';
|
||||
import type { Middleware } from './types.ts';
|
||||
import { log } from '../libs/logger.ts';
|
||||
import type { Middleware } from './types.ts';
|
||||
|
||||
/**
|
||||
* 统一响应体结构
|
||||
@@ -58,15 +58,26 @@ export class Exception implements Middleware {
|
||||
const errorMessage = firstError?.message || '参数验证失败';
|
||||
const fieldPath = firstError?.path?.join('.') || 'unknown';
|
||||
|
||||
log.info('Exception', 'Zod validation failed: %s at %s', errorMessage, fieldPath);
|
||||
this.sendResponse(ctx, 1003, errorMessage, {
|
||||
field: fieldPath,
|
||||
validationErrors: error.issues.map(issue => ({
|
||||
field: issue.path.join('.'),
|
||||
message: issue.message,
|
||||
code: issue.code,
|
||||
}))
|
||||
}, 400);
|
||||
log.info(
|
||||
'Exception',
|
||||
'Zod validation failed: %s at %s',
|
||||
errorMessage,
|
||||
fieldPath,
|
||||
);
|
||||
this.sendResponse(
|
||||
ctx,
|
||||
1003,
|
||||
errorMessage,
|
||||
{
|
||||
field: fieldPath,
|
||||
validationErrors: error.issues.map((issue) => ({
|
||||
field: issue.path.join('.'),
|
||||
message: issue.message,
|
||||
code: issue.code,
|
||||
})),
|
||||
},
|
||||
400,
|
||||
);
|
||||
} else if (error instanceof BusinessError) {
|
||||
// 业务异常
|
||||
this.sendResponse(ctx, error.code, error.message, null, error.httpStatus);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Router } from './router.ts';
|
||||
import { Exception } from './exception.ts';
|
||||
import { BodyParser } from './body-parser.ts';
|
||||
import { Session } from './session.ts';
|
||||
import { CORS } from './cors.ts';
|
||||
import { HttpLogger } from './logger.ts';
|
||||
import type Koa from 'koa';
|
||||
import { Authorization } from './authorization.ts';
|
||||
import { BodyParser } from './body-parser.ts';
|
||||
import { CORS } from './cors.ts';
|
||||
import { Exception } from './exception.ts';
|
||||
import { HttpLogger } from './logger.ts';
|
||||
import { Router } from './router.ts';
|
||||
import { Session } from './session.ts';
|
||||
|
||||
/**
|
||||
* 初始化中间件
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Koa, { type Context } from 'koa';
|
||||
import type Koa from 'koa';
|
||||
import type { Context } from 'koa';
|
||||
import { log } from '../libs/logger.ts';
|
||||
import type { Middleware } from './types.ts';
|
||||
|
||||
@@ -8,7 +9,7 @@ export class HttpLogger implements Middleware {
|
||||
const start = Date.now();
|
||||
await next();
|
||||
const ms = Date.now() - start;
|
||||
log.info('HTTP', `${ctx.method} ${ctx.url} - ${ms}ms`)
|
||||
log.info('HTTP', `${ctx.method} ${ctx.url} - ${ms}ms`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import KoaRouter from '@koa/router';
|
||||
import type Koa from 'koa';
|
||||
import type { Middleware } from './types.ts';
|
||||
import { RouteScanner } from '../libs/route-scanner.ts';
|
||||
import {
|
||||
ProjectController,
|
||||
UserController,
|
||||
AuthController,
|
||||
DeploymentController,
|
||||
GitController,
|
||||
PipelineController,
|
||||
ProjectController,
|
||||
StepController,
|
||||
GitController
|
||||
UserController,
|
||||
} from '../controllers/index.ts';
|
||||
import { log } from '../libs/logger.ts';
|
||||
import { RouteScanner } from '../libs/route-scanner.ts';
|
||||
import type { Middleware } from './types.ts';
|
||||
|
||||
export class Router implements Middleware {
|
||||
private router: KoaRouter;
|
||||
@@ -45,7 +45,7 @@ export class Router implements Middleware {
|
||||
DeploymentController,
|
||||
PipelineController,
|
||||
StepController,
|
||||
GitController
|
||||
GitController,
|
||||
]);
|
||||
|
||||
// 输出注册的路由信息
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import session from 'koa-session';
|
||||
import type Koa from 'koa';
|
||||
import session from 'koa-session';
|
||||
import type { Middleware } from './types.ts';
|
||||
|
||||
export class Session implements Middleware {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type Koa from 'koa';
|
||||
import type Koa from 'koa';
|
||||
|
||||
export abstract class Middleware {
|
||||
abstract apply(app: Koa, options?: unknown): void;
|
||||
|
||||
Binary file not shown.
@@ -16,6 +16,7 @@ model Project {
|
||||
description String?
|
||||
repository String
|
||||
projectDir String @unique // 项目工作目录路径(必填)
|
||||
envPresets String? // 环境预设配置(JSON格式)
|
||||
// Relations
|
||||
deployments Deployment[]
|
||||
pipelines Pipeline[]
|
||||
@@ -75,7 +76,7 @@ model Step {
|
||||
model Deployment {
|
||||
id Int @id @default(autoincrement())
|
||||
branch String
|
||||
env String?
|
||||
envVars String? // 环境变量(JSON格式),统一存储所有配置
|
||||
status String // pending, running, success, failed, cancelled
|
||||
commitHash String?
|
||||
commitMessage String?
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { $ } from 'zx';
|
||||
import { prisma } from '../libs/prisma.ts';
|
||||
import type { Step } from '../generated/client.ts';
|
||||
import { GitManager, WorkspaceDirStatus } from '../libs/git-manager.ts';
|
||||
import { log } from '../libs/logger.ts';
|
||||
import { prisma } from '../libs/prisma.ts';
|
||||
|
||||
export class PipelineRunner {
|
||||
private readonly TAG = 'PipelineRunner';
|
||||
@@ -81,7 +81,7 @@ export class PipelineRunner {
|
||||
|
||||
// 执行步骤
|
||||
const stepLog = await this.executeStep(step, envVars);
|
||||
logs += stepLog + '\n';
|
||||
logs += `${stepLog}\n`;
|
||||
|
||||
// 记录步骤执行完成的日志
|
||||
const endLog = `[${new Date().toISOString()}] 步骤 "${step.name}" 执行完成\n`;
|
||||
@@ -215,12 +215,18 @@ export class PipelineRunner {
|
||||
envVars.BRANCH_NAME = deployment.branch || '';
|
||||
envVars.COMMIT_HASH = deployment.commitHash || '';
|
||||
|
||||
// 稀疏检出路径(如果有配置的话)
|
||||
envVars.SPARSE_CHECKOUT_PATHS = deployment.sparseCheckoutPaths || '';
|
||||
// 注入用户配置的环境变量
|
||||
if (deployment.envVars) {
|
||||
try {
|
||||
const userEnvVars = JSON.parse(deployment.envVars);
|
||||
Object.assign(envVars, userEnvVars);
|
||||
} catch (error) {
|
||||
console.error('解析环境变量失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 工作空间路径(使用配置的项目目录)
|
||||
envVars.WORKSPACE = this.projectDir;
|
||||
envVars.PROJECT_DIR = this.projectDir;
|
||||
|
||||
return envVars;
|
||||
}
|
||||
@@ -248,13 +254,11 @@ export class PipelineRunner {
|
||||
private addTimestampToLines(content: string, isError = false): string {
|
||||
if (!content) return '';
|
||||
|
||||
return (
|
||||
content
|
||||
.split('\n')
|
||||
.filter((line) => line.trim() !== '')
|
||||
.map((line) => this.addTimestamp(line, isError))
|
||||
.join('\n') + '\n'
|
||||
);
|
||||
return `${content
|
||||
.split('\n')
|
||||
.filter((line) => line.trim() !== '')
|
||||
.map((line) => this.addTimestamp(line, isError))
|
||||
.join('\n')}\n`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -270,7 +274,7 @@ export class PipelineRunner {
|
||||
|
||||
try {
|
||||
// 添加步骤开始执行的时间戳
|
||||
logs += this.addTimestamp(`执行脚本: ${step.script}`) + '\n';
|
||||
logs += `${this.addTimestamp(`执行脚本: ${step.script}`)}\n`;
|
||||
|
||||
// 使用zx执行脚本,设置项目目录为工作目录和环境变量
|
||||
const script = step.script;
|
||||
@@ -291,10 +295,10 @@ export class PipelineRunner {
|
||||
logs += this.addTimestampToLines(result.stderr, true);
|
||||
}
|
||||
|
||||
logs += this.addTimestamp(`步骤执行完成`) + '\n';
|
||||
logs += `${this.addTimestamp(`步骤执行完成`)}\n`;
|
||||
} catch (error) {
|
||||
const errorMsg = `Error executing step "${step.name}": ${(error as Error).message}`;
|
||||
logs += this.addTimestamp(errorMsg, true) + '\n';
|
||||
logs += `${this.addTimestamp(errorMsg, true)}\n`;
|
||||
log.error(this.TAG, errorMsg);
|
||||
throw error;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import type React from 'react';
|
||||
import { useEffect, useCallback } from 'react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
export function useAsyncEffect(
|
||||
effect: () => Promise<void | (() => void)>,
|
||||
effect: () => Promise<undefined | (() => void)>,
|
||||
deps: React.DependencyList,
|
||||
) {
|
||||
const callback = useCallback(effect, [...deps]);
|
||||
@@ -11,7 +11,7 @@ export function useAsyncEffect(
|
||||
const cleanupPromise = callback();
|
||||
return () => {
|
||||
if (cleanupPromise instanceof Promise) {
|
||||
cleanupPromise.then(cleanup => cleanup && cleanup());
|
||||
cleanupPromise.then((cleanup) => cleanup?.());
|
||||
}
|
||||
};
|
||||
}, [callback]);
|
||||
|
||||
@@ -2,7 +2,7 @@ import App from '@pages/App';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import { BrowserRouter } from 'react-router';
|
||||
import { useGlobalStore } from './stores/global';
|
||||
import '@arco-design/web-react/es/_util/react-19-adapter'
|
||||
import '@arco-design/web-react/es/_util/react-19-adapter';
|
||||
|
||||
const rootEl = document.getElementById('root');
|
||||
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
Input,
|
||||
Message,
|
||||
Modal,
|
||||
Select,
|
||||
} from '@arco-design/web-react';
|
||||
import { formatDateTime } from '../../../../utils/time';
|
||||
import { IconDelete, IconPlus } from '@arco-design/web-react/icon';
|
||||
import { Form, Input, Message, Modal, Select } from '@arco-design/web-react';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import type { Branch, Commit, Pipeline } from '../../types';
|
||||
import { formatDateTime } from '../../../../utils/time';
|
||||
import type { Branch, Commit, Pipeline, Project } from '../../types';
|
||||
import { detailService } from '../service';
|
||||
|
||||
interface EnvPreset {
|
||||
key: string;
|
||||
label: string;
|
||||
type: 'select' | 'multiselect' | 'input';
|
||||
required?: boolean;
|
||||
options?: Array<{ label: string; value: string }>;
|
||||
}
|
||||
|
||||
interface DeployModalProps {
|
||||
visible: boolean;
|
||||
onCancel: () => void;
|
||||
onOk: () => void;
|
||||
pipelines: Pipeline[];
|
||||
projectId: number;
|
||||
project?: Project | null;
|
||||
}
|
||||
|
||||
function DeployModal({
|
||||
@@ -26,12 +27,29 @@ function DeployModal({
|
||||
onOk,
|
||||
pipelines,
|
||||
projectId,
|
||||
project,
|
||||
}: DeployModalProps) {
|
||||
const [form] = Form.useForm();
|
||||
const [branches, setBranches] = useState<Branch[]>([]);
|
||||
const [commits, setCommits] = useState<Commit[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [branchLoading, setBranchLoading] = useState(false);
|
||||
const [envPresets, setEnvPresets] = useState<EnvPreset[]>([]);
|
||||
|
||||
// 解析项目环境预设
|
||||
useEffect(() => {
|
||||
if (project?.envPresets) {
|
||||
try {
|
||||
const presets = JSON.parse(project.envPresets);
|
||||
setEnvPresets(presets);
|
||||
} catch (error) {
|
||||
console.error('解析环境预设失败:', error);
|
||||
setEnvPresets([]);
|
||||
}
|
||||
} else {
|
||||
setEnvPresets([]);
|
||||
}
|
||||
}, [project]);
|
||||
|
||||
const fetchCommits = useCallback(
|
||||
async (branch: string) => {
|
||||
@@ -91,16 +109,27 @@ function DeployModal({
|
||||
try {
|
||||
const values = await form.validate();
|
||||
const selectedCommit = commits.find((c) => c.sha === values.commitHash);
|
||||
const selectedPipeline = pipelines.find((p) => p.id === values.pipelineId);
|
||||
const selectedPipeline = pipelines.find(
|
||||
(p) => p.id === values.pipelineId,
|
||||
);
|
||||
|
||||
if (!selectedCommit || !selectedPipeline) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 格式化环境变量
|
||||
const env = values.envVars
|
||||
?.map((item: { key: string; value: string }) => `${item.key}=${item.value}`)
|
||||
.join('\n');
|
||||
// 收集所有环境变量(从预设项中提取)
|
||||
const envVars: Record<string, string> = {};
|
||||
for (const preset of envPresets) {
|
||||
const value = values[preset.key];
|
||||
if (value !== undefined && value !== null) {
|
||||
// 对于 multiselect,将数组转为逗号分隔的字符串
|
||||
if (preset.type === 'multiselect' && Array.isArray(value)) {
|
||||
envVars[preset.key] = value.join(',');
|
||||
} else {
|
||||
envVars[preset.key] = String(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await detailService.createDeployment({
|
||||
projectId,
|
||||
@@ -108,8 +137,7 @@ function DeployModal({
|
||||
branch: values.branch,
|
||||
commitHash: selectedCommit.sha,
|
||||
commitMessage: selectedCommit.commit.message,
|
||||
env: env,
|
||||
sparseCheckoutPaths: values.sparseCheckoutPaths,
|
||||
envVars, // 提交所有环境变量
|
||||
});
|
||||
|
||||
Message.success('部署任务已创建');
|
||||
@@ -128,126 +156,162 @@ function DeployModal({
|
||||
onCancel={onCancel}
|
||||
autoFocus={false}
|
||||
focusLock={true}
|
||||
style={{ width: 650 }}
|
||||
>
|
||||
<Form form={form} layout="vertical">
|
||||
<Form.Item
|
||||
label="选择流水线"
|
||||
field="pipelineId"
|
||||
rules={[{ required: true, message: '请选择流水线' }]}
|
||||
>
|
||||
<Select placeholder="请选择流水线">
|
||||
{pipelines.map((pipeline) => (
|
||||
<Select.Option key={pipeline.id} value={pipeline.id}>
|
||||
{pipeline.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
{/* 基本参数 */}
|
||||
<div className="mb-4 pb-4 border-b border-gray-200">
|
||||
<div className="text-sm font-semibold text-gray-700 mb-3">
|
||||
基本参数
|
||||
</div>
|
||||
|
||||
<Form.Item
|
||||
label="选择分支"
|
||||
field="branch"
|
||||
rules={[{ required: true, message: '请选择分支' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择分支"
|
||||
loading={branchLoading}
|
||||
onChange={handleBranchChange}
|
||||
<Form.Item
|
||||
label="选择流水线"
|
||||
field="pipelineId"
|
||||
rules={[{ required: true, message: '请选择流水线' }]}
|
||||
>
|
||||
{branches.map((branch) => (
|
||||
<Select.Option key={branch.name} value={branch.name}>
|
||||
{branch.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="选择提交"
|
||||
field="commitHash"
|
||||
rules={[{ required: true, message: '请选择提交记录' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择提交记录"
|
||||
loading={loading}
|
||||
renderFormat={(option) => {
|
||||
const commit = commits.find((c) => c.sha === option?.value);
|
||||
return commit ? commit.sha.substring(0, 7) : '';
|
||||
}}
|
||||
>
|
||||
{commits.map((commit) => (
|
||||
<Select.Option key={commit.sha} value={commit.sha}>
|
||||
<div className="flex flex-col py-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-mono font-medium">
|
||||
{commit.sha.substring(0, 7)}
|
||||
</span>
|
||||
<span className="text-gray-500 text-xs">
|
||||
{formatDateTime(commit.commit.author.date)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-gray-600 text-sm truncate">
|
||||
{commit.commit.message}
|
||||
</div>
|
||||
<div className="text-gray-400 text-xs">
|
||||
{commit.commit.author.name}
|
||||
</div>
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="稀疏检出路径(用于monorepo项目,每行一个路径)"
|
||||
field="sparseCheckoutPaths"
|
||||
tooltip="在monorepo项目中,指定需要检出的目录路径,每行一个路径。留空则检出整个仓库。"
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder={`例如:\n/packages/frontend\n/packages/backend`}
|
||||
autoSize={{ minRows: 2, maxRows: 6 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<div className="mb-2 font-medium text-gray-700">环境变量</div>
|
||||
<Form.List field="envVars">
|
||||
{(fields, { add, remove }) => (
|
||||
<div>
|
||||
{fields.map((item, index) => (
|
||||
<div key={item.key} className="flex items-center gap-2 mb-2">
|
||||
<Form.Item
|
||||
field={`${item.field}.key`}
|
||||
noStyle
|
||||
rules={[{ required: true, message: '请输入变量名' }]}
|
||||
>
|
||||
<Input placeholder="变量名" />
|
||||
</Form.Item>
|
||||
<span className="text-gray-400">=</span>
|
||||
<Form.Item
|
||||
field={`${item.field}.value`}
|
||||
noStyle
|
||||
rules={[{ required: true, message: '请输入变量值' }]}
|
||||
>
|
||||
<Input placeholder="变量值" />
|
||||
</Form.Item>
|
||||
<Button
|
||||
icon={<IconDelete />}
|
||||
status="danger"
|
||||
onClick={() => remove(index)}
|
||||
/>
|
||||
</div>
|
||||
<Select placeholder="请选择流水线">
|
||||
{pipelines.map((pipeline) => (
|
||||
<Select.Option key={pipeline.id} value={pipeline.id}>
|
||||
{pipeline.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
<Button
|
||||
type="dashed"
|
||||
long
|
||||
onClick={() => add()}
|
||||
icon={<IconPlus />}
|
||||
>
|
||||
添加环境变量
|
||||
</Button>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="选择分支"
|
||||
field="branch"
|
||||
rules={[{ required: true, message: '请选择分支' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择分支"
|
||||
loading={branchLoading}
|
||||
onChange={handleBranchChange}
|
||||
>
|
||||
{branches.map((branch) => (
|
||||
<Select.Option key={branch.name} value={branch.name}>
|
||||
{branch.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="选择提交"
|
||||
field="commitHash"
|
||||
rules={[{ required: true, message: '请选择提交记录' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="请选择提交记录"
|
||||
loading={loading}
|
||||
renderFormat={(option) => {
|
||||
const commit = commits.find((c) => c.sha === option?.value);
|
||||
return commit ? commit.sha.substring(0, 7) : '';
|
||||
}}
|
||||
>
|
||||
{commits.map((commit) => (
|
||||
<Select.Option key={commit.sha} value={commit.sha}>
|
||||
<div className="flex flex-col py-1">
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="font-mono font-medium">
|
||||
{commit.sha.substring(0, 7)}
|
||||
</span>
|
||||
<span className="text-gray-500 text-xs">
|
||||
{formatDateTime(commit.commit.author.date)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-gray-600 text-sm truncate">
|
||||
{commit.commit.message}
|
||||
</div>
|
||||
<div className="text-gray-400 text-xs">
|
||||
{commit.commit.author.name}
|
||||
</div>
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</div>
|
||||
|
||||
{/* 环境变量预设 */}
|
||||
{envPresets.length > 0 && (
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-gray-700 mb-3">
|
||||
环境变量
|
||||
</div>
|
||||
)}
|
||||
</Form.List>
|
||||
{envPresets.map((preset) => {
|
||||
if (preset.type === 'select' && preset.options) {
|
||||
return (
|
||||
<Form.Item
|
||||
key={preset.key}
|
||||
label={preset.label}
|
||||
field={preset.key}
|
||||
rules={
|
||||
preset.required
|
||||
? [{ required: true, message: `请选择${preset.label}` }]
|
||||
: []
|
||||
}
|
||||
>
|
||||
<Select placeholder={`请选择${preset.label}`}>
|
||||
{preset.options.map((option) => (
|
||||
<Select.Option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
||||
if (preset.type === 'multiselect' && preset.options) {
|
||||
return (
|
||||
<Form.Item
|
||||
key={preset.key}
|
||||
label={preset.label}
|
||||
field={preset.key}
|
||||
rules={
|
||||
preset.required
|
||||
? [{ required: true, message: `请选择${preset.label}` }]
|
||||
: []
|
||||
}
|
||||
>
|
||||
<Select
|
||||
mode="multiple"
|
||||
placeholder={`请选择${preset.label}`}
|
||||
allowClear
|
||||
>
|
||||
{preset.options.map((option) => (
|
||||
<Select.Option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
||||
if (preset.type === 'input') {
|
||||
return (
|
||||
<Form.Item
|
||||
key={preset.key}
|
||||
label={preset.label}
|
||||
field={preset.key}
|
||||
rules={
|
||||
preset.required
|
||||
? [{ required: true, message: `请输入${preset.label}` }]
|
||||
: []
|
||||
}
|
||||
>
|
||||
<Input placeholder={`请输入${preset.label}`} />
|
||||
</Form.Item>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
import { Button, Checkbox, Input, Select, Space } from '@arco-design/web-react';
|
||||
import { IconDelete, IconPlus } from '@arco-design/web-react/icon';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export interface EnvPreset {
|
||||
key: string;
|
||||
label: string;
|
||||
type: 'select' | 'multiselect' | 'input';
|
||||
required?: boolean; // 是否必填
|
||||
options?: Array<{ label: string; value: string }>;
|
||||
}
|
||||
|
||||
interface EnvPresetsEditorProps {
|
||||
value?: EnvPreset[];
|
||||
onChange?: (value: EnvPreset[]) => void;
|
||||
}
|
||||
|
||||
function EnvPresetsEditor({ value = [], onChange }: EnvPresetsEditorProps) {
|
||||
const [presets, setPresets] = useState<EnvPreset[]>(value);
|
||||
|
||||
// 当外部 value 变化时同步到内部状态
|
||||
useEffect(() => {
|
||||
setPresets(value);
|
||||
}, [value]);
|
||||
|
||||
const handleAddPreset = () => {
|
||||
const newPreset: EnvPreset = {
|
||||
key: '',
|
||||
label: '',
|
||||
type: 'select',
|
||||
options: [{ label: '', value: '' }],
|
||||
};
|
||||
const newPresets = [...presets, newPreset];
|
||||
setPresets(newPresets);
|
||||
onChange?.(newPresets);
|
||||
};
|
||||
|
||||
const handleRemovePreset = (index: number) => {
|
||||
const newPresets = presets.filter((_, i) => i !== index);
|
||||
setPresets(newPresets);
|
||||
onChange?.(newPresets);
|
||||
};
|
||||
|
||||
const handlePresetChange = (
|
||||
index: number,
|
||||
field: keyof EnvPreset,
|
||||
val: string | boolean | EnvPreset['type'] | EnvPreset['options'],
|
||||
) => {
|
||||
const newPresets = [...presets];
|
||||
newPresets[index] = { ...newPresets[index], [field]: val };
|
||||
setPresets(newPresets);
|
||||
onChange?.(newPresets);
|
||||
};
|
||||
|
||||
const handleAddOption = (presetIndex: number) => {
|
||||
const newPresets = [...presets];
|
||||
if (!newPresets[presetIndex].options) {
|
||||
newPresets[presetIndex].options = [];
|
||||
}
|
||||
newPresets[presetIndex].options?.push({ label: '', value: '' });
|
||||
setPresets(newPresets);
|
||||
onChange?.(newPresets);
|
||||
};
|
||||
|
||||
const handleRemoveOption = (presetIndex: number, optionIndex: number) => {
|
||||
const newPresets = [...presets];
|
||||
newPresets[presetIndex].options = newPresets[presetIndex].options?.filter(
|
||||
(_, i) => i !== optionIndex,
|
||||
);
|
||||
setPresets(newPresets);
|
||||
onChange?.(newPresets);
|
||||
};
|
||||
|
||||
const handleOptionChange = (
|
||||
presetIndex: number,
|
||||
optionIndex: number,
|
||||
field: 'label' | 'value',
|
||||
val: string,
|
||||
) => {
|
||||
const newPresets = [...presets];
|
||||
if (newPresets[presetIndex].options) {
|
||||
newPresets[presetIndex].options![optionIndex][field] = val;
|
||||
setPresets(newPresets);
|
||||
onChange?.(newPresets);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{presets.map((preset, presetIndex) => (
|
||||
<div
|
||||
key={`preset-${preset.key || presetIndex}`}
|
||||
className="border border-gray-200 rounded p-4"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="font-medium text-gray-700">
|
||||
预设项 #{presetIndex + 1}
|
||||
</div>
|
||||
<Button
|
||||
size="small"
|
||||
status="danger"
|
||||
icon={<IconDelete />}
|
||||
onClick={() => handleRemovePreset(presetIndex)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Space direction="vertical" style={{ width: '100%' }}>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Input
|
||||
placeholder="变量名 (key)"
|
||||
value={preset.key}
|
||||
onChange={(val) => handlePresetChange(presetIndex, 'key', val)}
|
||||
/>
|
||||
<Input
|
||||
placeholder="显示名称 (label)"
|
||||
value={preset.label}
|
||||
onChange={(val) =>
|
||||
handlePresetChange(presetIndex, 'label', val)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<Select
|
||||
placeholder="选择类型"
|
||||
value={preset.type}
|
||||
onChange={(val) => handlePresetChange(presetIndex, 'type', val)}
|
||||
>
|
||||
<Select.Option value="select">单选</Select.Option>
|
||||
<Select.Option value="multiselect">多选</Select.Option>
|
||||
<Select.Option value="input">输入框</Select.Option>
|
||||
</Select>
|
||||
|
||||
<div className="flex items-center">
|
||||
<Checkbox
|
||||
checked={preset.required || false}
|
||||
onChange={(checked) =>
|
||||
handlePresetChange(presetIndex, 'required', checked)
|
||||
}
|
||||
>
|
||||
必填项
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(preset.type === 'select' || preset.type === 'multiselect') && (
|
||||
<div className="mt-2">
|
||||
<div className="text-sm text-gray-600 mb-2">选项:</div>
|
||||
{preset.options?.map((option, optionIndex) => (
|
||||
<div
|
||||
key={`option-${option.value || optionIndex}`}
|
||||
className="flex items-center gap-2 mb-2"
|
||||
>
|
||||
<Input
|
||||
size="small"
|
||||
placeholder="显示文本"
|
||||
value={option.label}
|
||||
onChange={(val) =>
|
||||
handleOptionChange(
|
||||
presetIndex,
|
||||
optionIndex,
|
||||
'label',
|
||||
val,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
size="small"
|
||||
placeholder="值"
|
||||
value={option.value}
|
||||
onChange={(val) =>
|
||||
handleOptionChange(
|
||||
presetIndex,
|
||||
optionIndex,
|
||||
'value',
|
||||
val,
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
status="danger"
|
||||
icon={<IconDelete />}
|
||||
onClick={() =>
|
||||
handleRemoveOption(presetIndex, optionIndex)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
size="small"
|
||||
type="dashed"
|
||||
long
|
||||
icon={<IconPlus />}
|
||||
onClick={() => handleAddOption(presetIndex)}
|
||||
>
|
||||
添加选项
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</Space>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<Button type="dashed" long icon={<IconPlus />} onClick={handleAddPreset}>
|
||||
添加环境预设
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EnvPresetsEditor;
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from '@arco-design/web-react';
|
||||
import {
|
||||
IconCode,
|
||||
IconCommand,
|
||||
IconCopy,
|
||||
IconDelete,
|
||||
IconEdit,
|
||||
@@ -49,9 +50,12 @@ import { useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router';
|
||||
import { useAsyncEffect } from '../../../hooks/useAsyncEffect';
|
||||
import { formatDateTime } from '../../../utils/time';
|
||||
import type { Deployment, Pipeline, Project, Step, WorkspaceDirStatus, WorkspaceStatus } from '../types';
|
||||
import type { Deployment, Pipeline, Project, Step } from '../types';
|
||||
import DeployModal from './components/DeployModal';
|
||||
import DeployRecordItem from './components/DeployRecordItem';
|
||||
import EnvPresetsEditor, {
|
||||
type EnvPreset,
|
||||
} from './components/EnvPresetsEditor';
|
||||
import PipelineStepItem from './components/PipelineStepItem';
|
||||
import { detailService } from './service';
|
||||
|
||||
@@ -84,7 +88,8 @@ function ProjectDetailPage() {
|
||||
null,
|
||||
);
|
||||
const [pipelineModalVisible, setPipelineModalVisible] = useState(false);
|
||||
const [editingPipeline, setEditingPipeline] = useState<PipelineWithEnabled | null>(null);
|
||||
const [editingPipeline, setEditingPipeline] =
|
||||
useState<PipelineWithEnabled | null>(null);
|
||||
const [form] = Form.useForm();
|
||||
const [pipelineForm] = Form.useForm();
|
||||
const [deployRecords, setDeployRecords] = useState<Deployment[]>([]);
|
||||
@@ -92,12 +97,18 @@ function ProjectDetailPage() {
|
||||
|
||||
// 流水线模板相关状态
|
||||
const [isCreatingFromTemplate, setIsCreatingFromTemplate] = useState(false);
|
||||
const [selectedTemplateId, setSelectedTemplateId] = useState<number | null>(null);
|
||||
const [templates, setTemplates] = useState<Array<{id: number, name: string, description: string}>>([]);
|
||||
const [selectedTemplateId, setSelectedTemplateId] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
const [templates, setTemplates] = useState<
|
||||
Array<{ id: number; name: string; description: string }>
|
||||
>([]);
|
||||
|
||||
// 项目设置相关状态
|
||||
const [projectEditModalVisible, setProjectEditModalVisible] = useState(false);
|
||||
const [isEditingProject, setIsEditingProject] = useState(false);
|
||||
const [projectForm] = Form.useForm();
|
||||
const [envPresets, setEnvPresets] = useState<EnvPreset[]>([]);
|
||||
const [envPresetsLoading, setEnvPresetsLoading] = useState(false);
|
||||
|
||||
const { id } = useParams();
|
||||
|
||||
@@ -172,8 +183,14 @@ function ProjectDetailPage() {
|
||||
setDeployRecords(records);
|
||||
|
||||
// 如果当前选中的记录正在运行,则更新选中记录
|
||||
const selectedRecord = records.find((r: Deployment) => r.id === selectedRecordId);
|
||||
if (selectedRecord && (selectedRecord.status === 'running' || selectedRecord.status === 'pending')) {
|
||||
const selectedRecord = records.find(
|
||||
(r: Deployment) => r.id === selectedRecordId,
|
||||
);
|
||||
if (
|
||||
selectedRecord &&
|
||||
(selectedRecord.status === 'running' ||
|
||||
selectedRecord.status === 'pending')
|
||||
) {
|
||||
// 保持当前选中状态,但更新数据
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -354,14 +371,15 @@ function ProjectDetailPage() {
|
||||
selectedTemplateId,
|
||||
Number(id),
|
||||
values.name,
|
||||
values.description || ''
|
||||
values.description || '',
|
||||
);
|
||||
|
||||
// 更新本地状态 - 需要转换步骤数据结构
|
||||
const transformedSteps = newPipeline.steps?.map(step => ({
|
||||
...step,
|
||||
enabled: step.valid === 1
|
||||
})) || [];
|
||||
const transformedSteps =
|
||||
newPipeline.steps?.map((step) => ({
|
||||
...step,
|
||||
enabled: step.valid === 1,
|
||||
})) || [];
|
||||
|
||||
const pipelineWithDefaults = {
|
||||
...newPipeline,
|
||||
@@ -592,6 +610,21 @@ function ProjectDetailPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// 解析环境变量预设
|
||||
useEffect(() => {
|
||||
if (detail?.envPresets) {
|
||||
try {
|
||||
const presets = JSON.parse(detail.envPresets);
|
||||
setEnvPresets(presets);
|
||||
} catch (error) {
|
||||
console.error('解析环境变量预设失败:', error);
|
||||
setEnvPresets([]);
|
||||
}
|
||||
} else {
|
||||
setEnvPresets([]);
|
||||
}
|
||||
}, [detail]);
|
||||
|
||||
// 项目设置相关函数
|
||||
const handleEditProject = () => {
|
||||
if (detail) {
|
||||
@@ -600,16 +633,21 @@ function ProjectDetailPage() {
|
||||
description: detail.description,
|
||||
repository: detail.repository,
|
||||
});
|
||||
setProjectEditModalVisible(true);
|
||||
setIsEditingProject(true);
|
||||
}
|
||||
};
|
||||
|
||||
const handleProjectEditSuccess = async () => {
|
||||
const handleCancelEditProject = () => {
|
||||
setIsEditingProject(false);
|
||||
projectForm.resetFields();
|
||||
};
|
||||
|
||||
const handleSaveProject = async () => {
|
||||
try {
|
||||
const values = await projectForm.validate();
|
||||
await detailService.updateProject(Number(id), values);
|
||||
Message.success('项目更新成功');
|
||||
setProjectEditModalVisible(false);
|
||||
setIsEditingProject(false);
|
||||
|
||||
// 刷新项目详情
|
||||
if (id) {
|
||||
@@ -622,6 +660,27 @@ function ProjectDetailPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSaveEnvPresets = async () => {
|
||||
try {
|
||||
setEnvPresetsLoading(true);
|
||||
await detailService.updateProject(Number(id), {
|
||||
envPresets: JSON.stringify(envPresets),
|
||||
});
|
||||
Message.success('环境变量预设保存成功');
|
||||
|
||||
// 刷新项目详情
|
||||
if (id) {
|
||||
const projectDetail = await detailService.getProjectDetail(Number(id));
|
||||
setDetail(projectDetail);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存环境变量预设失败:', error);
|
||||
Message.error('保存环境变量预设失败');
|
||||
} finally {
|
||||
setEnvPresetsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteProject = () => {
|
||||
Modal.confirm({
|
||||
title: '删除项目',
|
||||
@@ -671,7 +730,7 @@ function ProjectDetailPage() {
|
||||
);
|
||||
|
||||
// 获取选中的流水线
|
||||
const selectedPipeline = pipelines.find(
|
||||
const _selectedPipeline = pipelines.find(
|
||||
(pipeline) => pipeline.id === selectedPipelineId,
|
||||
);
|
||||
|
||||
@@ -681,11 +740,13 @@ function ProjectDetailPage() {
|
||||
const k = 1024;
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
|
||||
return `${(bytes / k ** i).toFixed(2)} ${sizes[i]}`;
|
||||
};
|
||||
|
||||
// 获取工作目录状态标签
|
||||
const getWorkspaceStatusTag = (status: string): { text: string; color: string } => {
|
||||
const getWorkspaceStatusTag = (
|
||||
status: string,
|
||||
): { text: string; color: string } => {
|
||||
const statusMap: Record<string, { text: string; color: string }> = {
|
||||
not_created: { text: '未创建', color: 'gray' },
|
||||
empty: { text: '空目录', color: 'orange' },
|
||||
@@ -703,7 +764,15 @@ function ProjectDetailPage() {
|
||||
const statusInfo = getWorkspaceStatusTag(workspaceStatus.status as string);
|
||||
|
||||
return (
|
||||
<Card className="mb-6" title={<Space><IconFolder />工作目录状态</Space>}>
|
||||
<Card
|
||||
className="mb-6"
|
||||
title={
|
||||
<Space>
|
||||
<IconFolder />
|
||||
工作目录状态
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Descriptions
|
||||
column={2}
|
||||
data={[
|
||||
@@ -717,7 +786,9 @@ function ProjectDetailPage() {
|
||||
},
|
||||
{
|
||||
label: '目录大小',
|
||||
value: workspaceStatus.size ? formatSize(workspaceStatus.size) : '-',
|
||||
value: workspaceStatus.size
|
||||
? formatSize(workspaceStatus.size)
|
||||
: '-',
|
||||
},
|
||||
{
|
||||
label: '当前分支',
|
||||
@@ -727,16 +798,24 @@ function ProjectDetailPage() {
|
||||
label: '最后提交',
|
||||
value: workspaceStatus.gitInfo?.lastCommit ? (
|
||||
<Space direction="vertical" size="mini">
|
||||
<Typography.Text code>{workspaceStatus.gitInfo.lastCommit}</Typography.Text>
|
||||
<Typography.Text type="secondary">{workspaceStatus.gitInfo.lastCommitMessage}</Typography.Text>
|
||||
<Typography.Text code>
|
||||
{workspaceStatus.gitInfo.lastCommit}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary">
|
||||
{workspaceStatus.gitInfo.lastCommitMessage}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
) : '-',
|
||||
) : (
|
||||
'-'
|
||||
),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{workspaceStatus.error && (
|
||||
<div className="mt-4 p-3 bg-red-50 border border-red-200 rounded">
|
||||
<Typography.Text type="danger">{workspaceStatus.error}</Typography.Text>
|
||||
<Typography.Text type="danger">
|
||||
{workspaceStatus.error}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
@@ -763,7 +842,15 @@ function ProjectDetailPage() {
|
||||
size="large"
|
||||
className="h-full flex flex-col [&>.arco-tabs-content]:flex-1 [&>.arco-tabs-content]:overflow-hidden [&>.arco-tabs-content_.arco-tabs-content-inner]:h-full [&>.arco-tabs-pane]:h-full"
|
||||
>
|
||||
<Tabs.TabPane title={<Space><IconHistory />部署记录</Space>} key="deployRecords">
|
||||
<Tabs.TabPane
|
||||
title={
|
||||
<Space>
|
||||
<IconHistory />
|
||||
部署记录
|
||||
</Space>
|
||||
}
|
||||
key="deployRecords"
|
||||
>
|
||||
<div className="grid grid-cols-5 gap-6 h-full">
|
||||
{/* 左侧部署记录列表 */}
|
||||
<div className="col-span-2 space-y-4 h-full flex flex-col">
|
||||
@@ -813,7 +900,9 @@ function ProjectDetailPage() {
|
||||
type="primary"
|
||||
icon={<IconRefresh />}
|
||||
size="small"
|
||||
onClick={() => handleRetryDeployment(selectedRecord.id)}
|
||||
onClick={() =>
|
||||
handleRetryDeployment(selectedRecord.id)
|
||||
}
|
||||
>
|
||||
重新执行
|
||||
</Button>
|
||||
@@ -838,7 +927,15 @@ function ProjectDetailPage() {
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane title={<Space><IconCode />流水线</Space>} key="pipeline">
|
||||
<Tabs.TabPane
|
||||
title={
|
||||
<Space>
|
||||
<IconCode />
|
||||
流水线
|
||||
</Space>
|
||||
}
|
||||
key="pipeline"
|
||||
>
|
||||
<div className="grid grid-cols-5 gap-6 h-full">
|
||||
{/* 左侧流水线列表 */}
|
||||
<div className="col-span-2 space-y-4">
|
||||
@@ -951,9 +1048,7 @@ function ProjectDetailPage() {
|
||||
{pipeline.description}
|
||||
</Typography.Text>
|
||||
<div className="flex items-center justify-between text-xs text-gray-500">
|
||||
<span>
|
||||
{pipeline.steps?.length || 0} 个步骤
|
||||
</span>
|
||||
<span>{pipeline.steps?.length || 0} 个步骤</span>
|
||||
<span>{formatDateTime(pipeline.updatedAt)}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1005,7 +1100,11 @@ function ProjectDetailPage() {
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext
|
||||
items={selectedPipeline.steps?.map(step => step.id) || []}
|
||||
items={
|
||||
selectedPipeline.steps?.map(
|
||||
(step) => step.id,
|
||||
) || []
|
||||
}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
<div className="space-y-3 max-h-[calc(100vh-300px)] overflow-y-auto">
|
||||
@@ -1046,48 +1145,140 @@ function ProjectDetailPage() {
|
||||
</Tabs.TabPane>
|
||||
|
||||
{/* 项目设置标签页 */}
|
||||
<Tabs.TabPane key="settings" title={<Space><IconSettings />项目设置</Space>}>
|
||||
<Tabs.TabPane
|
||||
key="settings"
|
||||
title={
|
||||
<Space>
|
||||
<IconSettings />
|
||||
项目设置
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<div className="p-6">
|
||||
<Card title="项目信息" className="mb-4">
|
||||
<Descriptions
|
||||
column={1}
|
||||
data={[
|
||||
{
|
||||
label: '项目名称',
|
||||
value: detail?.name,
|
||||
},
|
||||
{
|
||||
label: '项目描述',
|
||||
value: detail?.description || '-',
|
||||
},
|
||||
{
|
||||
label: 'Git 仓库',
|
||||
value: detail?.repository,
|
||||
},
|
||||
{
|
||||
label: '工作目录',
|
||||
value: detail?.projectDir || '-',
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
value: formatDateTime(detail?.createdAt),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<div className="mt-4 flex gap-2">
|
||||
<Button type="primary" onClick={handleEditProject}>
|
||||
编辑项目
|
||||
</Button>
|
||||
<Button status="danger" onClick={handleDeleteProject}>
|
||||
删除项目
|
||||
</Button>
|
||||
</div>
|
||||
{!isEditingProject ? (
|
||||
<>
|
||||
<Descriptions
|
||||
column={1}
|
||||
data={[
|
||||
{
|
||||
label: '项目名称',
|
||||
value: detail?.name,
|
||||
},
|
||||
{
|
||||
label: '项目描述',
|
||||
value: detail?.description || '-',
|
||||
},
|
||||
{
|
||||
label: 'Git 仓库',
|
||||
value: detail?.repository,
|
||||
},
|
||||
{
|
||||
label: '工作目录',
|
||||
value: detail?.projectDir || '-',
|
||||
},
|
||||
{
|
||||
label: '创建时间',
|
||||
value: formatDateTime(detail?.createdAt),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<div className="mt-4 flex gap-2">
|
||||
<Button type="primary" onClick={handleEditProject}>
|
||||
编辑项目
|
||||
</Button>
|
||||
<Button status="danger" onClick={handleDeleteProject}>
|
||||
删除项目
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Form form={projectForm} layout="vertical">
|
||||
<Form.Item
|
||||
field="name"
|
||||
label="项目名称"
|
||||
rules={[
|
||||
{ required: true, message: '请输入项目名称' },
|
||||
{ minLength: 2, message: '项目名称至少2个字符' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="例如:我的应用" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
field="description"
|
||||
label="项目描述"
|
||||
rules={[
|
||||
{ maxLength: 200, message: '描述不能超过200个字符' },
|
||||
]}
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder="请输入项目描述"
|
||||
rows={3}
|
||||
maxLength={200}
|
||||
showWordLimit
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
field="repository"
|
||||
label="Git 仓库地址"
|
||||
rules={[{ required: true, message: '请输入仓库地址' }]}
|
||||
>
|
||||
<Input placeholder="例如:https://github.com/user/repo.git" />
|
||||
</Form.Item>
|
||||
<div className="text-sm text-gray-500 mb-4">
|
||||
<strong>工作目录:</strong> {detail?.projectDir || '-'}
|
||||
</div>
|
||||
<div className="text-sm text-gray-500 mb-4">
|
||||
<strong>创建时间:</strong>{' '}
|
||||
{formatDateTime(detail?.createdAt)}
|
||||
</div>
|
||||
</Form>
|
||||
<div className="mt-4 flex gap-2">
|
||||
<Button type="primary" onClick={handleSaveProject}>
|
||||
保存
|
||||
</Button>
|
||||
<Button onClick={handleCancelEditProject}>取消</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Card>
|
||||
|
||||
{/* 工作目录状态 */}
|
||||
{renderWorkspaceStatus()}
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
|
||||
{/* 环境变量预设标签页 */}
|
||||
<Tabs.TabPane
|
||||
key="envPresets"
|
||||
title={
|
||||
<Space>
|
||||
<IconCommand />
|
||||
环境变量
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<div className="p-6">
|
||||
<Card
|
||||
title="环境变量预设"
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleSaveEnvPresets}
|
||||
loading={envPresetsLoading}
|
||||
>
|
||||
保存预设
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<div className="text-sm text-gray-600 mb-4">
|
||||
配置项目的环境变量预设,在部署时可以选择这些预设值。支持单选、多选和输入框类型。
|
||||
</div>
|
||||
<EnvPresetsEditor value={envPresets} onChange={setEnvPresets} />
|
||||
</Card>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
@@ -1139,7 +1330,9 @@ function ProjectDetailPage() {
|
||||
<Select.Option key={template.id} value={template.id}>
|
||||
<div>
|
||||
<div>{template.name}</div>
|
||||
<div className="text-xs text-gray-500">{template.description}</div>
|
||||
<div className="text-xs text-gray-500">
|
||||
{template.description}
|
||||
</div>
|
||||
</div>
|
||||
</Select.Option>
|
||||
))}
|
||||
@@ -1155,10 +1348,7 @@ function ProjectDetailPage() {
|
||||
>
|
||||
<Input placeholder="例如:前端部署流水线、Docker部署流水线..." />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
field="description"
|
||||
label="流水线描述"
|
||||
>
|
||||
<Form.Item field="description" label="流水线描述">
|
||||
<Input.TextArea
|
||||
placeholder="描述这个流水线的用途和特点..."
|
||||
rows={3}
|
||||
@@ -1176,10 +1366,7 @@ function ProjectDetailPage() {
|
||||
>
|
||||
<Input placeholder="例如:前端部署流水线、Docker部署流水线..." />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
field="description"
|
||||
label="流水线描述"
|
||||
>
|
||||
<Form.Item field="description" label="流水线描述">
|
||||
<Input.TextArea
|
||||
placeholder="描述这个流水线的用途和特点..."
|
||||
rows={3}
|
||||
@@ -1228,47 +1415,6 @@ function ProjectDetailPage() {
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
{/* 编辑项目模态框 */}
|
||||
<Modal
|
||||
title="编辑项目"
|
||||
visible={projectEditModalVisible}
|
||||
onOk={handleProjectEditSuccess}
|
||||
onCancel={() => setProjectEditModalVisible(false)}
|
||||
style={{ width: 500 }}
|
||||
>
|
||||
<Form form={projectForm} layout="vertical">
|
||||
<Form.Item
|
||||
field="name"
|
||||
label="项目名称"
|
||||
rules={[
|
||||
{ required: true, message: '请输入项目名称' },
|
||||
{ minLength: 2, message: '项目名称至少2个字符' },
|
||||
]}
|
||||
>
|
||||
<Input placeholder="例如:我的应用" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
field="description"
|
||||
label="项目描述"
|
||||
rules={[{ maxLength: 200, message: '描述不能超过200个字符' }]}
|
||||
>
|
||||
<Input.TextArea
|
||||
placeholder="请输入项目描述"
|
||||
rows={3}
|
||||
maxLength={200}
|
||||
showWordLimit
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
field="repository"
|
||||
label="Git 仓库地址"
|
||||
rules={[{ required: true, message: '请输入仓库地址' }]}
|
||||
>
|
||||
<Input placeholder="例如:https://github.com/user/repo.git" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<DeployModal
|
||||
visible={deployModalVisible}
|
||||
onCancel={() => setDeployModalVisible(false)}
|
||||
@@ -1286,6 +1432,7 @@ function ProjectDetailPage() {
|
||||
}}
|
||||
pipelines={pipelines}
|
||||
projectId={Number(id)}
|
||||
project={detail}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { type APIResponse, net } from '@shared';
|
||||
import type { Branch, Commit, Deployment, Pipeline, Project, Step, CreateDeploymentRequest } from '../types';
|
||||
import type {
|
||||
Branch,
|
||||
Commit,
|
||||
CreateDeploymentRequest,
|
||||
Deployment,
|
||||
Pipeline,
|
||||
Project,
|
||||
Step,
|
||||
} from '../types';
|
||||
|
||||
class DetailService {
|
||||
async getProject(id: string) {
|
||||
@@ -19,7 +27,9 @@ class DetailService {
|
||||
|
||||
// 获取可用的流水线模板
|
||||
async getPipelineTemplates() {
|
||||
const { data } = await net.request<APIResponse<{id: number, name: string, description: string}[]>>({
|
||||
const { data } = await net.request<
|
||||
APIResponse<{ id: number; name: string; description: string }[]>
|
||||
>({
|
||||
url: '/api/pipelines/templates',
|
||||
});
|
||||
return data;
|
||||
@@ -59,7 +69,7 @@ class DetailService {
|
||||
templateId: number,
|
||||
projectId: number,
|
||||
name: string,
|
||||
description?: string
|
||||
description?: string,
|
||||
) {
|
||||
const { data } = await net.request<APIResponse<Pipeline>>({
|
||||
url: '/api/pipelines/from-template',
|
||||
@@ -68,7 +78,7 @@ class DetailService {
|
||||
templateId,
|
||||
projectId,
|
||||
name,
|
||||
description
|
||||
description,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
import { Button, Form, Input, Message, Modal } from '@arco-design/web-react';
|
||||
import {
|
||||
Button,
|
||||
Collapse,
|
||||
Form,
|
||||
Input,
|
||||
Message,
|
||||
Modal,
|
||||
} from '@arco-design/web-react';
|
||||
import { useState } from 'react';
|
||||
import { projectService } from '../service';
|
||||
import EnvPresetsEditor from '../../detail/components/EnvPresetsEditor';
|
||||
import type { Project } from '../../types';
|
||||
import { projectService } from '../service';
|
||||
|
||||
interface CreateProjectModalProps {
|
||||
visible: boolean;
|
||||
@@ -22,7 +30,15 @@ function CreateProjectModal({
|
||||
const values = await form.validate();
|
||||
setLoading(true);
|
||||
|
||||
const newProject = await projectService.create(values);
|
||||
// 序列化环境预设
|
||||
const submitData = {
|
||||
...values,
|
||||
envPresets: values.envPresets
|
||||
? JSON.stringify(values.envPresets)
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const newProject = await projectService.create(submitData);
|
||||
|
||||
Message.success('项目创建成功');
|
||||
onSuccess(newProject);
|
||||
@@ -114,7 +130,9 @@ function CreateProjectModal({
|
||||
if (value.includes('..') || value.includes('~')) {
|
||||
return cb('不能包含路径遍历字符(.. 或 ~)');
|
||||
}
|
||||
if (/[<>:"|?*\x00-\x1f]/.test(value)) {
|
||||
// 检查非法字符(控制字符 0x00-0x1F)
|
||||
// biome-ignore lint/suspicious/noControlCharactersInRegex: 需要检测路径中的控制字符
|
||||
if (/[<>:"|?*\u0000-\u001f]/.test(value)) {
|
||||
return cb('路径包含非法字符');
|
||||
}
|
||||
cb();
|
||||
@@ -124,6 +142,14 @@ function CreateProjectModal({
|
||||
>
|
||||
<Input placeholder="请输入绝对路径,如: /data/projects/my-app" />
|
||||
</Form.Item>
|
||||
|
||||
<Collapse defaultActiveKey={[]} style={{ marginTop: 16 }}>
|
||||
<Collapse.Item header="环境变量预设配置(可选)" name="envPresets">
|
||||
<Form.Item field="envPresets" noStyle>
|
||||
<EnvPresetsEditor />
|
||||
</Form.Item>
|
||||
</Collapse.Item>
|
||||
</Collapse>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,17 @@
|
||||
import { Button, Form, Input, Message, Modal } from '@arco-design/web-react';
|
||||
import {
|
||||
Button,
|
||||
Collapse,
|
||||
Form,
|
||||
Input,
|
||||
Message,
|
||||
Modal,
|
||||
} from '@arco-design/web-react';
|
||||
import React, { useState } from 'react';
|
||||
import EnvPresetsEditor, {
|
||||
type EnvPreset,
|
||||
} from '../../detail/components/EnvPresetsEditor';
|
||||
import type { Project } from '../../types';
|
||||
import { projectService } from '../service';
|
||||
import type { Project } from '../types';
|
||||
|
||||
interface EditProjectModalProps {
|
||||
visible: boolean;
|
||||
@@ -22,10 +32,20 @@ function EditProjectModal({
|
||||
// 当项目信息变化时,更新表单数据
|
||||
React.useEffect(() => {
|
||||
if (project && visible) {
|
||||
let envPresets: EnvPreset[] = [];
|
||||
try {
|
||||
if (project.envPresets) {
|
||||
envPresets = JSON.parse(project.envPresets);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析环境预设失败:', error);
|
||||
}
|
||||
|
||||
form.setFieldsValue({
|
||||
name: project.name,
|
||||
description: project.description,
|
||||
repository: project.repository,
|
||||
envPresets,
|
||||
});
|
||||
}
|
||||
}, [project, visible, form]);
|
||||
@@ -37,7 +57,18 @@ function EditProjectModal({
|
||||
|
||||
if (!project) return;
|
||||
|
||||
const updatedProject = await projectService.update(project.id, values);
|
||||
// 序列化环境预设
|
||||
const submitData = {
|
||||
...values,
|
||||
envPresets: values.envPresets
|
||||
? JSON.stringify(values.envPresets)
|
||||
: undefined,
|
||||
};
|
||||
|
||||
const updatedProject = await projectService.update(
|
||||
project.id,
|
||||
submitData,
|
||||
);
|
||||
|
||||
Message.success('项目更新成功');
|
||||
onSuccess(updatedProject);
|
||||
@@ -111,6 +142,14 @@ function EditProjectModal({
|
||||
>
|
||||
<Input placeholder="请输入仓库地址,如: https://github.com/user/repo" />
|
||||
</Form.Item>
|
||||
|
||||
<Collapse defaultActiveKey={[]} style={{ marginTop: 16 }}>
|
||||
<Collapse.Item header="环境变量预设配置" name="envPresets">
|
||||
<Form.Item field="envPresets" noStyle>
|
||||
<EnvPresetsEditor />
|
||||
</Form.Item>
|
||||
</Collapse.Item>
|
||||
</Collapse>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
Card,
|
||||
Space,
|
||||
Tag,
|
||||
Tooltip,
|
||||
Typography,
|
||||
} from '@arco-design/web-react';
|
||||
import {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, Grid, Message, Typography } from '@arco-design/web-react';
|
||||
import { Button, Grid, Typography } from '@arco-design/web-react';
|
||||
import { IconPlus } from '@arco-design/web-react/icon';
|
||||
import { useState } from 'react';
|
||||
import { useAsyncEffect } from '../../../hooks/useAsyncEffect';
|
||||
|
||||
@@ -36,6 +36,7 @@ export interface Project {
|
||||
description: string;
|
||||
repository: string;
|
||||
projectDir: string; // 项目工作目录路径(必填)
|
||||
envPresets?: string; // 环境预设配置(JSON格式)
|
||||
valid: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
@@ -77,12 +78,11 @@ export interface Pipeline {
|
||||
export interface Deployment {
|
||||
id: number;
|
||||
branch: string;
|
||||
env?: string;
|
||||
envVars?: string; // JSON 字符串
|
||||
status: string;
|
||||
commitHash?: string;
|
||||
commitMessage?: string;
|
||||
buildLog?: string;
|
||||
sparseCheckoutPaths?: string; // 稀疏检出路径,用于monorepo项目
|
||||
startedAt: string;
|
||||
finishedAt?: string;
|
||||
valid: number;
|
||||
@@ -127,6 +127,5 @@ export interface CreateDeploymentRequest {
|
||||
branch: string;
|
||||
commitHash: string;
|
||||
commitMessage: string;
|
||||
env?: string;
|
||||
sparseCheckoutPaths?: string; // 稀疏检出路径,用于monorepo项目
|
||||
envVars?: Record<string, string>; // 环境变量 key-value 对象
|
||||
}
|
||||
|
||||
@@ -20,7 +20,11 @@ class Net {
|
||||
(error) => {
|
||||
console.log('error', error);
|
||||
// 对于DELETE请求返回204状态码的情况,视为成功
|
||||
if (error.response && error.response.status === 204 && error.config.method === 'delete') {
|
||||
if (
|
||||
error.response &&
|
||||
error.response.status === 204 &&
|
||||
error.config.method === 'delete'
|
||||
) {
|
||||
// 创建一个模拟的成功响应
|
||||
return Promise.resolve({
|
||||
...error.response,
|
||||
|
||||
Reference in New Issue
Block a user