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

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

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

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

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

View File

@@ -1,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);
});

View File

@@ -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')

View File

@@ -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>;

View File

@@ -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(),
};
}
}

View File

@@ -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>;

View File

@@ -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] };
}

View File

@@ -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';

View File

@@ -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>;

View File

@@ -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 || '',
);
// 返回新创建的流水线

View File

@@ -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验证架构

View File

@@ -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 },

View File

@@ -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')

View File

@@ -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,
};
}
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>;
};

View File

@@ -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 {};

View File

@@ -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

View File

@@ -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];

View File

@@ -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

View File

@@ -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';
/**

View File

@@ -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;
}

View File

@@ -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);

View File

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

View File

@@ -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';
/**

View File

@@ -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);

View File

@@ -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';
/**
* 初始化中间件

View File

@@ -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`);
});
}
}

View File

@@ -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,
]);
// 输出注册的路由信息

View File

@@ -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 {

View File

@@ -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.

View File

@@ -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?

View File

@@ -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;
}

View File

@@ -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]);

View File

@@ -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');

View File

@@ -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>
);

View File

@@ -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;

View File

@@ -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>
);

View File

@@ -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;

View File

@@ -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>
);

View File

@@ -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>
);

View File

@@ -3,6 +3,7 @@ import {
Card,
Space,
Tag,
Tooltip,
Typography,
} from '@arco-design/web-react';
import {

View File

@@ -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';

View File

@@ -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 对象
}

View File

@@ -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,