feat(server): 支持稀疏检出路径并完善部署执行队列
- 在部署DTO中添加sparseCheckoutPaths字段支持稀疏检出路径 - 数据模型Deployment新增稀疏检出路径字段及相关数据库映射 - 部署创建时支持设置稀疏检出路径字段 - 部署重试接口实现,支持复制原始部署记录并加入执行队列 - 新增流水线模板初始化与基于模板创建流水线接口 - 优化应用初始化流程,确保执行队列和流水线模板正确加载 - 添加启动日志,提示执行队列初始化完成
This commit is contained in:
@@ -1,13 +1,32 @@
|
||||
import Koa from 'koa';
|
||||
import { initMiddlewares } from './middlewares/index.ts';
|
||||
import { log } from './libs/logger.ts';
|
||||
import { ExecutionQueue } from './libs/execution-queue.ts';
|
||||
import { initializePipelineTemplates } from './libs/pipeline-template.ts';
|
||||
|
||||
const app = new Koa();
|
||||
// 初始化应用
|
||||
async function initializeApp() {
|
||||
// 初始化流水线模板
|
||||
await initializePipelineTemplates();
|
||||
|
||||
initMiddlewares(app);
|
||||
// 初始化执行队列
|
||||
const executionQueue = ExecutionQueue.getInstance();
|
||||
await executionQueue.initialize();
|
||||
|
||||
const PORT = process.env.PORT || 3001;
|
||||
const app = new Koa();
|
||||
|
||||
app.listen(PORT, () => {
|
||||
log.info('APP', 'Server started at port %d', PORT);
|
||||
initMiddlewares(app);
|
||||
|
||||
const PORT = process.env.PORT || 3001;
|
||||
|
||||
app.listen(PORT, () => {
|
||||
log.info('APP', 'Server started at port %d', PORT);
|
||||
log.info('QUEUE', 'Execution queue initialized');
|
||||
});
|
||||
}
|
||||
|
||||
// 启动应用
|
||||
initializeApp().catch(error => {
|
||||
console.error('Failed to start application:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ export const createDeploymentSchema = z.object({
|
||||
commitHash: z.string().min(1, { message: '提交哈希不能为空' }),
|
||||
commitMessage: z.string().min(1, { message: '提交信息不能为空' }),
|
||||
env: z.string().optional(),
|
||||
sparseCheckoutPaths: z.string().optional(), // 添加稀疏检出路径字段
|
||||
});
|
||||
|
||||
export type ListDeploymentsQuery = z.infer<typeof listDeploymentsQuerySchema>;
|
||||
|
||||
@@ -3,6 +3,7 @@ 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';
|
||||
|
||||
@Controller('/deployments')
|
||||
export class DeploymentController {
|
||||
@@ -50,12 +51,69 @@ export class DeploymentController {
|
||||
},
|
||||
pipelineId: body.pipelineId,
|
||||
env: body.env || 'dev',
|
||||
sparseCheckoutPaths: body.sparseCheckoutPaths || '', // 添加稀疏检出路径
|
||||
buildLog: '',
|
||||
createdBy: 'system', // TODO: get from user
|
||||
updatedBy: 'system',
|
||||
valid: 1,
|
||||
},
|
||||
});
|
||||
|
||||
// 将新创建的部署任务添加到执行队列
|
||||
const executionQueue = ExecutionQueue.getInstance();
|
||||
await executionQueue.addTask(result.id, result.pipelineId);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// 添加重新执行部署的接口
|
||||
@Post('/:id/retry')
|
||||
async retry(ctx: Context) {
|
||||
const { id } = ctx.params;
|
||||
|
||||
// 获取原始部署记录
|
||||
const originalDeployment = await prisma.deployment.findUnique({
|
||||
where: { id: Number(id) }
|
||||
});
|
||||
|
||||
if (!originalDeployment) {
|
||||
ctx.status = 404;
|
||||
ctx.body = {
|
||||
code: 404,
|
||||
message: '部署记录不存在',
|
||||
data: null,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建一个新的部署记录,复制原始记录的信息
|
||||
const newDeployment = await prisma.deployment.create({
|
||||
data: {
|
||||
branch: originalDeployment.branch,
|
||||
commitHash: originalDeployment.commitHash,
|
||||
commitMessage: originalDeployment.commitMessage,
|
||||
status: 'pending',
|
||||
projectId: originalDeployment.projectId,
|
||||
pipelineId: originalDeployment.pipelineId,
|
||||
env: originalDeployment.env,
|
||||
sparseCheckoutPaths: originalDeployment.sparseCheckoutPaths,
|
||||
buildLog: '',
|
||||
createdBy: 'system',
|
||||
updatedBy: 'system',
|
||||
valid: 1,
|
||||
},
|
||||
});
|
||||
|
||||
// 将新创建的部署任务添加到执行队列
|
||||
const executionQueue = ExecutionQueue.getInstance();
|
||||
await executionQueue.addTask(newDeployment.id, newDeployment.pipelineId);
|
||||
|
||||
ctx.body = {
|
||||
code: 0,
|
||||
message: '重新执行任务已创建',
|
||||
data: newDeployment,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Controller, Get, Post, Put, Delete } from '../../decorators/route.ts';
|
||||
import { prisma } from '../../libs/prisma.ts';
|
||||
import { log } from '../../libs/logger.ts';
|
||||
import { BusinessError } from '../../middlewares/exception.ts';
|
||||
import { getAvailableTemplates, createPipelineFromTemplate } from '../../libs/pipeline-template.ts';
|
||||
import {
|
||||
createPipelineSchema,
|
||||
updatePipelineSchema,
|
||||
@@ -43,6 +44,18 @@ export class PipelineController {
|
||||
return pipelines;
|
||||
}
|
||||
|
||||
// GET /api/pipelines/templates - 获取可用的流水线模板
|
||||
@Get('/templates')
|
||||
async getTemplates(ctx: Context) {
|
||||
try {
|
||||
const templates = await getAvailableTemplates();
|
||||
return templates;
|
||||
} catch (error) {
|
||||
console.error('Failed to get templates:', error);
|
||||
throw new BusinessError('获取模板失败', 3002, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// GET /api/pipelines/:id - 获取单个流水线
|
||||
@Get('/:id')
|
||||
async get(ctx: Context) {
|
||||
@@ -92,6 +105,60 @@ export class PipelineController {
|
||||
return pipeline;
|
||||
}
|
||||
|
||||
// POST /api/pipelines/from-template - 基于模板创建流水线
|
||||
@Post('/from-template')
|
||||
async createFromTemplate(ctx: Context) {
|
||||
try {
|
||||
const { templateId, projectId, name, description } = ctx.request.body as {
|
||||
templateId: number;
|
||||
projectId: number;
|
||||
name: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
// 验证必要参数
|
||||
if (!templateId || !projectId || !name) {
|
||||
throw new BusinessError('缺少必要参数', 3003, 400);
|
||||
}
|
||||
|
||||
// 基于模板创建流水线
|
||||
const newPipelineId = await createPipelineFromTemplate(
|
||||
templateId,
|
||||
projectId,
|
||||
name,
|
||||
description || ''
|
||||
);
|
||||
|
||||
// 返回新创建的流水线
|
||||
const pipeline = await prisma.pipeline.findUnique({
|
||||
where: { id: newPipelineId },
|
||||
include: {
|
||||
steps: {
|
||||
where: {
|
||||
valid: 1,
|
||||
},
|
||||
orderBy: {
|
||||
order: 'asc',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!pipeline) {
|
||||
throw new BusinessError('创建流水线失败', 3004, 500);
|
||||
}
|
||||
|
||||
log.info('pipeline', 'Created pipeline from template: %s', pipeline.name);
|
||||
return pipeline;
|
||||
} catch (error) {
|
||||
console.error('Failed to create pipeline from template:', error);
|
||||
if (error instanceof BusinessError) {
|
||||
throw error;
|
||||
}
|
||||
throw new BusinessError('基于模板创建流水线失败', 3005, 500);
|
||||
}
|
||||
}
|
||||
|
||||
// PUT /api/pipelines/:id - 更新流水线
|
||||
@Put('/:id')
|
||||
async update(ctx: Context) {
|
||||
|
||||
@@ -20,7 +20,7 @@ const config: runtime.GetPrismaClientConfig = {
|
||||
"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 // 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 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",
|
||||
"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 // 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": {},
|
||||
@@ -28,7 +28,7 @@ const config: runtime.GetPrismaClientConfig = {
|
||||
}
|
||||
}
|
||||
|
||||
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\":\"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\":\"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\":\"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\":{}}")
|
||||
|
||||
async function decodeBase64AsWasm(wasmBase64: string): Promise<WebAssembly.Module> {
|
||||
const { Buffer } = await import('node:buffer')
|
||||
|
||||
@@ -885,6 +885,7 @@ export const DeploymentScalarFieldEnum = {
|
||||
commitHash: 'commitHash',
|
||||
commitMessage: 'commitMessage',
|
||||
buildLog: 'buildLog',
|
||||
sparseCheckoutPaths: 'sparseCheckoutPaths',
|
||||
startedAt: 'startedAt',
|
||||
finishedAt: 'finishedAt',
|
||||
valid: 'valid',
|
||||
|
||||
@@ -142,6 +142,7 @@ export const DeploymentScalarFieldEnum = {
|
||||
commitHash: 'commitHash',
|
||||
commitMessage: 'commitMessage',
|
||||
buildLog: 'buildLog',
|
||||
sparseCheckoutPaths: 'sparseCheckoutPaths',
|
||||
startedAt: 'startedAt',
|
||||
finishedAt: 'finishedAt',
|
||||
valid: 'valid',
|
||||
|
||||
@@ -48,6 +48,7 @@ export type DeploymentMinAggregateOutputType = {
|
||||
commitHash: string | null
|
||||
commitMessage: string | null
|
||||
buildLog: string | null
|
||||
sparseCheckoutPaths: string | null
|
||||
startedAt: Date | null
|
||||
finishedAt: Date | null
|
||||
valid: number | null
|
||||
@@ -67,6 +68,7 @@ export type DeploymentMaxAggregateOutputType = {
|
||||
commitHash: string | null
|
||||
commitMessage: string | null
|
||||
buildLog: string | null
|
||||
sparseCheckoutPaths: string | null
|
||||
startedAt: Date | null
|
||||
finishedAt: Date | null
|
||||
valid: number | null
|
||||
@@ -86,6 +88,7 @@ export type DeploymentCountAggregateOutputType = {
|
||||
commitHash: number
|
||||
commitMessage: number
|
||||
buildLog: number
|
||||
sparseCheckoutPaths: number
|
||||
startedAt: number
|
||||
finishedAt: number
|
||||
valid: number
|
||||
@@ -121,6 +124,7 @@ export type DeploymentMinAggregateInputType = {
|
||||
commitHash?: true
|
||||
commitMessage?: true
|
||||
buildLog?: true
|
||||
sparseCheckoutPaths?: true
|
||||
startedAt?: true
|
||||
finishedAt?: true
|
||||
valid?: true
|
||||
@@ -140,6 +144,7 @@ export type DeploymentMaxAggregateInputType = {
|
||||
commitHash?: true
|
||||
commitMessage?: true
|
||||
buildLog?: true
|
||||
sparseCheckoutPaths?: true
|
||||
startedAt?: true
|
||||
finishedAt?: true
|
||||
valid?: true
|
||||
@@ -159,6 +164,7 @@ export type DeploymentCountAggregateInputType = {
|
||||
commitHash?: true
|
||||
commitMessage?: true
|
||||
buildLog?: true
|
||||
sparseCheckoutPaths?: true
|
||||
startedAt?: true
|
||||
finishedAt?: true
|
||||
valid?: true
|
||||
@@ -265,6 +271,7 @@ export type DeploymentGroupByOutputType = {
|
||||
commitHash: string | null
|
||||
commitMessage: string | null
|
||||
buildLog: string | null
|
||||
sparseCheckoutPaths: string | null
|
||||
startedAt: Date
|
||||
finishedAt: Date | null
|
||||
valid: number
|
||||
@@ -307,6 +314,7 @@ export type DeploymentWhereInput = {
|
||||
commitHash?: Prisma.StringNullableFilter<"Deployment"> | string | null
|
||||
commitMessage?: Prisma.StringNullableFilter<"Deployment"> | string | null
|
||||
buildLog?: Prisma.StringNullableFilter<"Deployment"> | string | null
|
||||
sparseCheckoutPaths?: Prisma.StringNullableFilter<"Deployment"> | string | null
|
||||
startedAt?: Prisma.DateTimeFilter<"Deployment"> | Date | string
|
||||
finishedAt?: Prisma.DateTimeNullableFilter<"Deployment"> | Date | string | null
|
||||
valid?: Prisma.IntFilter<"Deployment"> | number
|
||||
@@ -327,6 +335,7 @@ export type DeploymentOrderByWithRelationInput = {
|
||||
commitHash?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
commitMessage?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
buildLog?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
sparseCheckoutPaths?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
startedAt?: Prisma.SortOrder
|
||||
finishedAt?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
valid?: Prisma.SortOrder
|
||||
@@ -350,6 +359,7 @@ export type DeploymentWhereUniqueInput = Prisma.AtLeast<{
|
||||
commitHash?: Prisma.StringNullableFilter<"Deployment"> | string | null
|
||||
commitMessage?: Prisma.StringNullableFilter<"Deployment"> | string | null
|
||||
buildLog?: Prisma.StringNullableFilter<"Deployment"> | string | null
|
||||
sparseCheckoutPaths?: Prisma.StringNullableFilter<"Deployment"> | string | null
|
||||
startedAt?: Prisma.DateTimeFilter<"Deployment"> | Date | string
|
||||
finishedAt?: Prisma.DateTimeNullableFilter<"Deployment"> | Date | string | null
|
||||
valid?: Prisma.IntFilter<"Deployment"> | number
|
||||
@@ -370,6 +380,7 @@ export type DeploymentOrderByWithAggregationInput = {
|
||||
commitHash?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
commitMessage?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
buildLog?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
sparseCheckoutPaths?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
startedAt?: Prisma.SortOrder
|
||||
finishedAt?: Prisma.SortOrderInput | Prisma.SortOrder
|
||||
valid?: Prisma.SortOrder
|
||||
@@ -397,6 +408,7 @@ export type DeploymentScalarWhereWithAggregatesInput = {
|
||||
commitHash?: Prisma.StringNullableWithAggregatesFilter<"Deployment"> | string | null
|
||||
commitMessage?: Prisma.StringNullableWithAggregatesFilter<"Deployment"> | string | null
|
||||
buildLog?: Prisma.StringNullableWithAggregatesFilter<"Deployment"> | string | null
|
||||
sparseCheckoutPaths?: Prisma.StringNullableWithAggregatesFilter<"Deployment"> | string | null
|
||||
startedAt?: Prisma.DateTimeWithAggregatesFilter<"Deployment"> | Date | string
|
||||
finishedAt?: Prisma.DateTimeNullableWithAggregatesFilter<"Deployment"> | Date | string | null
|
||||
valid?: Prisma.IntWithAggregatesFilter<"Deployment"> | number
|
||||
@@ -415,6 +427,7 @@ export type DeploymentCreateInput = {
|
||||
commitHash?: string | null
|
||||
commitMessage?: string | null
|
||||
buildLog?: string | null
|
||||
sparseCheckoutPaths?: string | null
|
||||
startedAt?: Date | string
|
||||
finishedAt?: Date | string | null
|
||||
valid?: number
|
||||
@@ -434,6 +447,7 @@ export type DeploymentUncheckedCreateInput = {
|
||||
commitHash?: string | null
|
||||
commitMessage?: string | null
|
||||
buildLog?: string | null
|
||||
sparseCheckoutPaths?: string | null
|
||||
startedAt?: Date | string
|
||||
finishedAt?: Date | string | null
|
||||
valid?: number
|
||||
@@ -452,6 +466,7 @@ export type DeploymentUpdateInput = {
|
||||
commitHash?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
commitMessage?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
buildLog?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sparseCheckoutPaths?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
startedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
finishedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
valid?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
@@ -471,6 +486,7 @@ export type DeploymentUncheckedUpdateInput = {
|
||||
commitHash?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
commitMessage?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
buildLog?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sparseCheckoutPaths?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
startedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
finishedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
valid?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
@@ -490,6 +506,7 @@ export type DeploymentCreateManyInput = {
|
||||
commitHash?: string | null
|
||||
commitMessage?: string | null
|
||||
buildLog?: string | null
|
||||
sparseCheckoutPaths?: string | null
|
||||
startedAt?: Date | string
|
||||
finishedAt?: Date | string | null
|
||||
valid?: number
|
||||
@@ -508,6 +525,7 @@ export type DeploymentUpdateManyMutationInput = {
|
||||
commitHash?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
commitMessage?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
buildLog?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sparseCheckoutPaths?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
startedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
finishedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
valid?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
@@ -526,6 +544,7 @@ export type DeploymentUncheckedUpdateManyInput = {
|
||||
commitHash?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
commitMessage?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
buildLog?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sparseCheckoutPaths?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
startedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
finishedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
valid?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
@@ -555,6 +574,7 @@ export type DeploymentCountOrderByAggregateInput = {
|
||||
commitHash?: Prisma.SortOrder
|
||||
commitMessage?: Prisma.SortOrder
|
||||
buildLog?: Prisma.SortOrder
|
||||
sparseCheckoutPaths?: Prisma.SortOrder
|
||||
startedAt?: Prisma.SortOrder
|
||||
finishedAt?: Prisma.SortOrder
|
||||
valid?: Prisma.SortOrder
|
||||
@@ -581,6 +601,7 @@ export type DeploymentMaxOrderByAggregateInput = {
|
||||
commitHash?: Prisma.SortOrder
|
||||
commitMessage?: Prisma.SortOrder
|
||||
buildLog?: Prisma.SortOrder
|
||||
sparseCheckoutPaths?: Prisma.SortOrder
|
||||
startedAt?: Prisma.SortOrder
|
||||
finishedAt?: Prisma.SortOrder
|
||||
valid?: Prisma.SortOrder
|
||||
@@ -600,6 +621,7 @@ export type DeploymentMinOrderByAggregateInput = {
|
||||
commitHash?: Prisma.SortOrder
|
||||
commitMessage?: Prisma.SortOrder
|
||||
buildLog?: Prisma.SortOrder
|
||||
sparseCheckoutPaths?: Prisma.SortOrder
|
||||
startedAt?: Prisma.SortOrder
|
||||
finishedAt?: Prisma.SortOrder
|
||||
valid?: Prisma.SortOrder
|
||||
@@ -671,6 +693,7 @@ export type DeploymentCreateWithoutProjectInput = {
|
||||
commitHash?: string | null
|
||||
commitMessage?: string | null
|
||||
buildLog?: string | null
|
||||
sparseCheckoutPaths?: string | null
|
||||
startedAt?: Date | string
|
||||
finishedAt?: Date | string | null
|
||||
valid?: number
|
||||
@@ -689,6 +712,7 @@ export type DeploymentUncheckedCreateWithoutProjectInput = {
|
||||
commitHash?: string | null
|
||||
commitMessage?: string | null
|
||||
buildLog?: string | null
|
||||
sparseCheckoutPaths?: string | null
|
||||
startedAt?: Date | string
|
||||
finishedAt?: Date | string | null
|
||||
valid?: number
|
||||
@@ -735,6 +759,7 @@ export type DeploymentScalarWhereInput = {
|
||||
commitHash?: Prisma.StringNullableFilter<"Deployment"> | string | null
|
||||
commitMessage?: Prisma.StringNullableFilter<"Deployment"> | string | null
|
||||
buildLog?: Prisma.StringNullableFilter<"Deployment"> | string | null
|
||||
sparseCheckoutPaths?: Prisma.StringNullableFilter<"Deployment"> | string | null
|
||||
startedAt?: Prisma.DateTimeFilter<"Deployment"> | Date | string
|
||||
finishedAt?: Prisma.DateTimeNullableFilter<"Deployment"> | Date | string | null
|
||||
valid?: Prisma.IntFilter<"Deployment"> | number
|
||||
@@ -754,6 +779,7 @@ export type DeploymentCreateManyProjectInput = {
|
||||
commitHash?: string | null
|
||||
commitMessage?: string | null
|
||||
buildLog?: string | null
|
||||
sparseCheckoutPaths?: string | null
|
||||
startedAt?: Date | string
|
||||
finishedAt?: Date | string | null
|
||||
valid?: number
|
||||
@@ -771,6 +797,7 @@ export type DeploymentUpdateWithoutProjectInput = {
|
||||
commitHash?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
commitMessage?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
buildLog?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sparseCheckoutPaths?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
startedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
finishedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
valid?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
@@ -789,6 +816,7 @@ export type DeploymentUncheckedUpdateWithoutProjectInput = {
|
||||
commitHash?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
commitMessage?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
buildLog?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sparseCheckoutPaths?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
startedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
finishedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
valid?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
@@ -807,6 +835,7 @@ export type DeploymentUncheckedUpdateManyWithoutProjectInput = {
|
||||
commitHash?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
commitMessage?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
buildLog?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
sparseCheckoutPaths?: Prisma.NullableStringFieldUpdateOperationsInput | string | null
|
||||
startedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string
|
||||
finishedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
valid?: Prisma.IntFieldUpdateOperationsInput | number
|
||||
@@ -827,6 +856,7 @@ export type DeploymentSelect<ExtArgs extends runtime.Types.Extensions.InternalAr
|
||||
commitHash?: boolean
|
||||
commitMessage?: boolean
|
||||
buildLog?: boolean
|
||||
sparseCheckoutPaths?: boolean
|
||||
startedAt?: boolean
|
||||
finishedAt?: boolean
|
||||
valid?: boolean
|
||||
@@ -847,6 +877,7 @@ export type DeploymentSelectCreateManyAndReturn<ExtArgs extends runtime.Types.Ex
|
||||
commitHash?: boolean
|
||||
commitMessage?: boolean
|
||||
buildLog?: boolean
|
||||
sparseCheckoutPaths?: boolean
|
||||
startedAt?: boolean
|
||||
finishedAt?: boolean
|
||||
valid?: boolean
|
||||
@@ -867,6 +898,7 @@ export type DeploymentSelectUpdateManyAndReturn<ExtArgs extends runtime.Types.Ex
|
||||
commitHash?: boolean
|
||||
commitMessage?: boolean
|
||||
buildLog?: boolean
|
||||
sparseCheckoutPaths?: boolean
|
||||
startedAt?: boolean
|
||||
finishedAt?: boolean
|
||||
valid?: boolean
|
||||
@@ -887,6 +919,7 @@ export type DeploymentSelectScalar = {
|
||||
commitHash?: boolean
|
||||
commitMessage?: boolean
|
||||
buildLog?: boolean
|
||||
sparseCheckoutPaths?: boolean
|
||||
startedAt?: boolean
|
||||
finishedAt?: boolean
|
||||
valid?: boolean
|
||||
@@ -898,7 +931,7 @@ export type DeploymentSelectScalar = {
|
||||
pipelineId?: boolean
|
||||
}
|
||||
|
||||
export type DeploymentOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "branch" | "env" | "status" | "commitHash" | "commitMessage" | "buildLog" | "startedAt" | "finishedAt" | "valid" | "createdAt" | "updatedAt" | "createdBy" | "updatedBy" | "projectId" | "pipelineId", ExtArgs["result"]["deployment"]>
|
||||
export type DeploymentOmit<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = runtime.Types.Extensions.GetOmit<"id" | "branch" | "env" | "status" | "commitHash" | "commitMessage" | "buildLog" | "sparseCheckoutPaths" | "startedAt" | "finishedAt" | "valid" | "createdAt" | "updatedAt" | "createdBy" | "updatedBy" | "projectId" | "pipelineId", ExtArgs["result"]["deployment"]>
|
||||
export type DeploymentInclude<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = {
|
||||
Project?: boolean | Prisma.Deployment$ProjectArgs<ExtArgs>
|
||||
}
|
||||
@@ -922,6 +955,7 @@ export type $DeploymentPayload<ExtArgs extends runtime.Types.Extensions.Internal
|
||||
commitHash: string | null
|
||||
commitMessage: string | null
|
||||
buildLog: string | null
|
||||
sparseCheckoutPaths: string | null
|
||||
startedAt: Date
|
||||
finishedAt: Date | null
|
||||
valid: number
|
||||
@@ -1362,6 +1396,7 @@ export interface DeploymentFieldRefs {
|
||||
readonly commitHash: Prisma.FieldRef<"Deployment", 'String'>
|
||||
readonly commitMessage: Prisma.FieldRef<"Deployment", 'String'>
|
||||
readonly buildLog: Prisma.FieldRef<"Deployment", 'String'>
|
||||
readonly sparseCheckoutPaths: Prisma.FieldRef<"Deployment", 'String'>
|
||||
readonly startedAt: Prisma.FieldRef<"Deployment", 'DateTime'>
|
||||
readonly finishedAt: Prisma.FieldRef<"Deployment", 'DateTime'>
|
||||
readonly valid: Prisma.FieldRef<"Deployment", 'Int'>
|
||||
|
||||
233
apps/server/libs/execution-queue.ts
Normal file
233
apps/server/libs/execution-queue.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
import { PipelineRunner } from '../runners/index.ts';
|
||||
import { prisma } from './prisma.ts';
|
||||
|
||||
// 存储正在运行的部署任务
|
||||
const runningDeployments = new Set<number>();
|
||||
|
||||
// 存储待执行的任务队列
|
||||
const pendingQueue: Array<{
|
||||
deploymentId: number;
|
||||
pipelineId: number;
|
||||
}> = [];
|
||||
|
||||
// 定时器ID
|
||||
let pollingTimer: NodeJS.Timeout | null = null;
|
||||
|
||||
// 轮询间隔(毫秒)
|
||||
const POLLING_INTERVAL = 30000; // 30秒
|
||||
|
||||
/**
|
||||
* 执行队列管理器
|
||||
*/
|
||||
export class ExecutionQueue {
|
||||
private static instance: ExecutionQueue;
|
||||
private isProcessing = false;
|
||||
private isPolling = false;
|
||||
|
||||
private constructor() {}
|
||||
|
||||
/**
|
||||
* 获取执行队列的单例实例
|
||||
*/
|
||||
public static getInstance(): ExecutionQueue {
|
||||
if (!ExecutionQueue.instance) {
|
||||
ExecutionQueue.instance = new ExecutionQueue();
|
||||
}
|
||||
return ExecutionQueue.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化执行队列,包括恢复未完成的任务
|
||||
*/
|
||||
public async initialize(): Promise<void> {
|
||||
console.log('Initializing execution queue...');
|
||||
// 恢复未完成的任务
|
||||
await this.recoverPendingDeployments();
|
||||
|
||||
// 启动定时轮询
|
||||
this.startPolling();
|
||||
|
||||
console.log('Execution queue initialized');
|
||||
}
|
||||
|
||||
/**
|
||||
* 从数据库中恢复未完成的部署任务
|
||||
*/
|
||||
private async recoverPendingDeployments(): Promise<void> {
|
||||
try {
|
||||
console.log('Recovering pending deployments from database...');
|
||||
|
||||
// 查询数据库中状态为pending的部署任务
|
||||
const pendingDeployments = await prisma.deployment.findMany({
|
||||
where: {
|
||||
status: 'pending',
|
||||
valid: 1
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
pipelineId: true
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Found ${pendingDeployments.length} pending deployments`);
|
||||
|
||||
// 将这些任务添加到执行队列中
|
||||
for (const deployment of pendingDeployments) {
|
||||
await this.addTask(deployment.id, deployment.pipelineId);
|
||||
}
|
||||
|
||||
console.log('Pending deployments recovery completed');
|
||||
} catch (error) {
|
||||
console.error('Failed to recover pending deployments:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动定时轮询机制
|
||||
*/
|
||||
private startPolling(): void {
|
||||
if (this.isPolling) {
|
||||
console.log('Polling is already running');
|
||||
return;
|
||||
}
|
||||
|
||||
this.isPolling = true;
|
||||
console.log(`Starting polling with interval ${POLLING_INTERVAL}ms`);
|
||||
|
||||
// 立即执行一次检查
|
||||
this.checkPendingDeployments();
|
||||
|
||||
// 设置定时器定期检查
|
||||
pollingTimer = setInterval(() => {
|
||||
this.checkPendingDeployments();
|
||||
}, POLLING_INTERVAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止定时轮询机制
|
||||
*/
|
||||
public stopPolling(): void {
|
||||
if (pollingTimer) {
|
||||
clearInterval(pollingTimer);
|
||||
pollingTimer = null;
|
||||
this.isPolling = false;
|
||||
console.log('Polling stopped');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查数据库中的待处理部署任务
|
||||
*/
|
||||
private async checkPendingDeployments(): Promise<void> {
|
||||
try {
|
||||
console.log('Checking for pending deployments in database...');
|
||||
|
||||
// 查询数据库中状态为pending的部署任务
|
||||
const pendingDeployments = await prisma.deployment.findMany({
|
||||
where: {
|
||||
status: 'pending',
|
||||
valid: 1
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
pipelineId: true
|
||||
}
|
||||
});
|
||||
|
||||
console.log(`Found ${pendingDeployments.length} pending deployments in polling`);
|
||||
|
||||
// 检查这些任务是否已经在队列中,如果没有则添加
|
||||
for (const deployment of pendingDeployments) {
|
||||
// 检查是否已经在运行队列中
|
||||
if (!runningDeployments.has(deployment.id)) {
|
||||
console.log(`Adding deployment ${deployment.id} to queue from polling`);
|
||||
await this.addTask(deployment.id, deployment.pipelineId);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to check pending deployments:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将部署任务添加到执行队列
|
||||
* @param deploymentId 部署ID
|
||||
* @param pipelineId 流水线ID
|
||||
*/
|
||||
public async addTask(deploymentId: number, pipelineId: number): Promise<void> {
|
||||
// 检查是否已经在运行队列中
|
||||
if (runningDeployments.has(deploymentId)) {
|
||||
console.log(`Deployment ${deploymentId} is already queued or running`);
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加到运行队列
|
||||
runningDeployments.add(deploymentId);
|
||||
|
||||
// 添加到待执行队列
|
||||
pendingQueue.push({ deploymentId, pipelineId });
|
||||
|
||||
// 开始处理队列(如果尚未开始)
|
||||
if (!this.isProcessing) {
|
||||
this.processQueue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理执行队列中的任务
|
||||
*/
|
||||
private async processQueue(): Promise<void> {
|
||||
this.isProcessing = true;
|
||||
|
||||
while (pendingQueue.length > 0) {
|
||||
const task = pendingQueue.shift();
|
||||
|
||||
if (task) {
|
||||
try {
|
||||
// 执行流水线
|
||||
await this.executePipeline(task.deploymentId, task.pipelineId);
|
||||
} catch (error) {
|
||||
console.error('执行流水线失败:', error);
|
||||
// 这里可以添加更多的错误处理逻辑
|
||||
} finally {
|
||||
// 从运行队列中移除
|
||||
runningDeployments.delete(task.deploymentId);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加一个小延迟以避免过度占用资源
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
this.isProcessing = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行流水线
|
||||
* @param deploymentId 部署ID
|
||||
* @param pipelineId 流水线ID
|
||||
*/
|
||||
private async executePipeline(deploymentId: number, pipelineId: number): Promise<void> {
|
||||
try {
|
||||
const runner = new PipelineRunner(deploymentId);
|
||||
await runner.run(pipelineId);
|
||||
} catch (error) {
|
||||
console.error('执行流水线失败:', error);
|
||||
// 错误处理可以在这里添加,比如更新部署状态为失败
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队列状态
|
||||
*/
|
||||
public getQueueStatus(): {
|
||||
pendingCount: number;
|
||||
runningCount: number;
|
||||
} {
|
||||
return {
|
||||
pendingCount: pendingQueue.length,
|
||||
runningCount: runningDeployments.size
|
||||
};
|
||||
}
|
||||
}
|
||||
247
apps/server/libs/pipeline-template.ts
Normal file
247
apps/server/libs/pipeline-template.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
import { prisma } from './prisma.ts';
|
||||
|
||||
// 默认流水线模板
|
||||
export interface PipelineTemplate {
|
||||
name: string;
|
||||
description: string;
|
||||
steps: Array<{
|
||||
name: string;
|
||||
order: number;
|
||||
script: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
// 系统默认的流水线模板
|
||||
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,
|
||||
script: '# 安装项目依赖\nnpm install',
|
||||
},
|
||||
{
|
||||
name: 'Run Tests',
|
||||
order: 2,
|
||||
script: '# 运行测试\nnpm test',
|
||||
},
|
||||
{
|
||||
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# 这里可以添加具体的部署命令',
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
/**
|
||||
* 初始化系统默认流水线模板
|
||||
*/
|
||||
export async function initializePipelineTemplates(): Promise<void> {
|
||||
console.log('Initializing pipeline templates...');
|
||||
|
||||
try {
|
||||
// 检查是否已经存在模板流水线
|
||||
const existingTemplates = await prisma.pipeline.findMany({
|
||||
where: {
|
||||
name: {
|
||||
in: DEFAULT_PIPELINE_TEMPLATES.map(template => template.name)
|
||||
},
|
||||
valid: 1
|
||||
}
|
||||
});
|
||||
|
||||
// 如果没有现有的模板,则创建默认模板
|
||||
if (existingTemplates.length === 0) {
|
||||
console.log('Creating default pipeline templates...');
|
||||
|
||||
for (const template of DEFAULT_PIPELINE_TEMPLATES) {
|
||||
// 创建模板流水线(使用负数ID表示模板)
|
||||
const pipeline = await prisma.pipeline.create({
|
||||
data: {
|
||||
name: template.name,
|
||||
description: template.description,
|
||||
createdBy: 'system',
|
||||
updatedBy: 'system',
|
||||
valid: 1,
|
||||
projectId: null // 模板不属于任何特定项目
|
||||
}
|
||||
});
|
||||
|
||||
// 创建模板步骤
|
||||
for (const step of template.steps) {
|
||||
await prisma.step.create({
|
||||
data: {
|
||||
name: step.name,
|
||||
order: step.order,
|
||||
script: step.script,
|
||||
pipelineId: pipeline.id,
|
||||
createdBy: 'system',
|
||||
updatedBy: 'system',
|
||||
valid: 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Created template: ${template.name}`);
|
||||
}
|
||||
} else {
|
||||
console.log('Pipeline templates already exist, skipping initialization');
|
||||
}
|
||||
|
||||
console.log('Pipeline templates initialization completed');
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize pipeline templates:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用的流水线模板
|
||||
*/
|
||||
export async function getAvailableTemplates(): Promise<Array<{id: number, name: string, description: string}>> {
|
||||
try {
|
||||
const templates = await prisma.pipeline.findMany({
|
||||
where: {
|
||||
projectId: null, // 模板流水线没有关联的项目
|
||||
valid: 1
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
description: true
|
||||
}
|
||||
});
|
||||
|
||||
// 处理可能为null的description字段
|
||||
return templates.map(template => ({
|
||||
id: template.id,
|
||||
name: template.name,
|
||||
description: template.description || ''
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Failed to get pipeline templates:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于模板创建新的流水线
|
||||
* @param templateId 模板ID
|
||||
* @param projectId 项目ID
|
||||
* @param pipelineName 新流水线名称
|
||||
* @param pipelineDescription 新流水线描述
|
||||
*/
|
||||
export async function createPipelineFromTemplate(
|
||||
templateId: number,
|
||||
projectId: number,
|
||||
pipelineName: string,
|
||||
pipelineDescription: string
|
||||
): Promise<number> {
|
||||
try {
|
||||
// 获取模板流水线及其步骤
|
||||
const templatePipeline = await prisma.pipeline.findUnique({
|
||||
where: {
|
||||
id: templateId,
|
||||
projectId: null, // 确保是模板流水线
|
||||
valid: 1
|
||||
},
|
||||
include: {
|
||||
steps: {
|
||||
where: {
|
||||
valid: 1
|
||||
},
|
||||
orderBy: {
|
||||
order: 'asc'
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!templatePipeline) {
|
||||
throw new Error(`Template with id ${templateId} not found`);
|
||||
}
|
||||
|
||||
// 创建新的流水线
|
||||
const newPipeline = await prisma.pipeline.create({
|
||||
data: {
|
||||
name: pipelineName,
|
||||
description: pipelineDescription,
|
||||
projectId: projectId,
|
||||
createdBy: 'system',
|
||||
updatedBy: 'system',
|
||||
valid: 1
|
||||
}
|
||||
});
|
||||
|
||||
// 复制模板步骤到新流水线
|
||||
for (const templateStep of templatePipeline.steps) {
|
||||
await prisma.step.create({
|
||||
data: {
|
||||
name: templateStep.name,
|
||||
order: templateStep.order,
|
||||
script: templateStep.script,
|
||||
pipelineId: newPipeline.id,
|
||||
createdBy: 'system',
|
||||
updatedBy: 'system',
|
||||
valid: 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
console.log(`Created pipeline from template ${templateId}: ${newPipeline.name}`);
|
||||
return newPipeline.id;
|
||||
} catch (error) {
|
||||
console.error('Failed to create pipeline from template:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ 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 { Authorization } from './authorization.ts';
|
||||
|
||||
/**
|
||||
* 初始化中间件
|
||||
|
||||
Binary file not shown.
@@ -79,6 +79,7 @@ model Deployment {
|
||||
commitHash String?
|
||||
commitMessage String?
|
||||
buildLog String?
|
||||
sparseCheckoutPaths String? // 稀疏检出路径,用于monorepo项目
|
||||
startedAt DateTime @default(now())
|
||||
finishedAt DateTime?
|
||||
valid Int @default(1)
|
||||
|
||||
3
apps/server/runners/index.ts
Normal file
3
apps/server/runners/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { PipelineRunner } from './pipeline-runner';
|
||||
|
||||
export { PipelineRunner };
|
||||
28
apps/server/runners/mq-interface.ts
Normal file
28
apps/server/runners/mq-interface.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
// MQ集成接口设计 (暂不实现)
|
||||
// 该接口用于将来通过消息队列触发流水线执行
|
||||
|
||||
export interface MQPipelineMessage {
|
||||
deploymentId: number;
|
||||
pipelineId: number;
|
||||
// 其他可能需要的参数
|
||||
triggerUser?: string;
|
||||
environment?: string;
|
||||
}
|
||||
|
||||
export interface MQRunnerInterface {
|
||||
/**
|
||||
* 发送流水线执行消息到MQ
|
||||
* @param message 流水线执行消息
|
||||
*/
|
||||
sendPipelineExecutionMessage(message: MQPipelineMessage): Promise<void>;
|
||||
|
||||
/**
|
||||
* 监听MQ消息并执行流水线
|
||||
*/
|
||||
listenForPipelineMessages(): void;
|
||||
|
||||
/**
|
||||
* 停止监听MQ消息
|
||||
*/
|
||||
stopListening(): void;
|
||||
}
|
||||
254
apps/server/runners/pipeline-runner.ts
Normal file
254
apps/server/runners/pipeline-runner.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
import { $ } from 'zx';
|
||||
import { prisma } from '../libs/prisma.ts';
|
||||
import type { Step } from '../generated/client.ts';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
export class PipelineRunner {
|
||||
private deploymentId: number;
|
||||
private workspace: string;
|
||||
|
||||
constructor(deploymentId: number) {
|
||||
this.deploymentId = deploymentId;
|
||||
// 从环境变量获取工作空间路径,默认为/tmp/foka-ci/workspace
|
||||
this.workspace = process.env.PIPELINE_WORKSPACE || '/tmp/foka-ci/workspace';
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行流水线
|
||||
* @param pipelineId 流水线ID
|
||||
*/
|
||||
async run(pipelineId: number): Promise<void> {
|
||||
// 获取流水线及其步骤
|
||||
const pipeline = await prisma.pipeline.findUnique({
|
||||
where: { id: pipelineId },
|
||||
include: {
|
||||
steps: { where: { valid: 1 }, orderBy: { order: 'asc' } },
|
||||
Project: true // 同时获取关联的项目信息
|
||||
}
|
||||
});
|
||||
|
||||
if (!pipeline) {
|
||||
throw new Error(`Pipeline with id ${pipelineId} not found`);
|
||||
}
|
||||
|
||||
// 获取部署信息
|
||||
const deployment = await prisma.deployment.findUnique({
|
||||
where: { id: this.deploymentId }
|
||||
});
|
||||
|
||||
if (!deployment) {
|
||||
throw new Error(`Deployment with id ${this.deploymentId} not found`);
|
||||
}
|
||||
|
||||
// 确保工作空间目录存在
|
||||
await this.ensureWorkspace();
|
||||
|
||||
// 创建项目目录(在工作空间内)
|
||||
const projectDir = path.join(this.workspace, `project-${pipelineId}`);
|
||||
await this.ensureProjectDirectory(projectDir);
|
||||
|
||||
// 更新部署状态为running
|
||||
await prisma.deployment.update({
|
||||
where: { id: this.deploymentId },
|
||||
data: { status: 'running' }
|
||||
});
|
||||
|
||||
let logs = '';
|
||||
let hasError = false;
|
||||
|
||||
try {
|
||||
// 依次执行每个步骤
|
||||
for (const [index, step] of pipeline.steps.entries()) {
|
||||
// 准备环境变量
|
||||
const envVars = this.prepareEnvironmentVariables(pipeline, deployment, projectDir);
|
||||
|
||||
// 记录开始执行步骤的日志,包含脚本内容(合并为一行,并用括号括起脚本内容)
|
||||
const startLog = `[${new Date().toISOString()}] 开始执行步骤 ${index + 1}/${pipeline.steps.length}: ${step.name}\n`;
|
||||
logs += startLog;
|
||||
|
||||
// 实时更新日志
|
||||
await prisma.deployment.update({
|
||||
where: { id: this.deploymentId },
|
||||
data: { buildLog: logs }
|
||||
});
|
||||
|
||||
// 执行步骤(传递环境变量和项目目录)
|
||||
const stepLog = await this.executeStep(step, envVars, projectDir);
|
||||
logs += stepLog + '\n';
|
||||
|
||||
// 记录步骤执行完成的日志
|
||||
const endLog = `[${new Date().toISOString()}] 步骤 "${step.name}" 执行完成\n`;
|
||||
logs += endLog;
|
||||
|
||||
// 实时更新日志
|
||||
await prisma.deployment.update({
|
||||
where: { id: this.deploymentId },
|
||||
data: { buildLog: logs }
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
hasError = true;
|
||||
logs += `[${new Date().toISOString()}] Error: ${(error as Error).message}\n`;
|
||||
|
||||
// 记录错误日志
|
||||
await prisma.deployment.update({
|
||||
where: { id: this.deploymentId },
|
||||
data: {
|
||||
buildLog: logs,
|
||||
status: 'failed'
|
||||
}
|
||||
});
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
// 更新最终状态
|
||||
if (!hasError) {
|
||||
await prisma.deployment.update({
|
||||
where: { id: this.deploymentId },
|
||||
data: {
|
||||
buildLog: logs,
|
||||
status: 'success',
|
||||
finishedAt: new Date()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 准备环境变量
|
||||
* @param pipeline 流水线信息
|
||||
* @param deployment 部署信息
|
||||
* @param projectDir 项目目录路径
|
||||
*/
|
||||
private prepareEnvironmentVariables(pipeline: any, deployment: any, projectDir: string): Record<string, string> {
|
||||
const envVars: Record<string, string> = {};
|
||||
|
||||
// 项目相关信息
|
||||
if (pipeline.Project) {
|
||||
envVars.REPOSITORY_URL = pipeline.Project.repository || '';
|
||||
envVars.PROJECT_NAME = pipeline.Project.name || '';
|
||||
}
|
||||
|
||||
// 部署相关信息
|
||||
envVars.BRANCH_NAME = deployment.branch || '';
|
||||
envVars.COMMIT_HASH = deployment.commitHash || '';
|
||||
|
||||
// 稀疏检出路径(如果有配置的话)
|
||||
envVars.SPARSE_CHECKOUT_PATHS = deployment.sparseCheckoutPaths || '';
|
||||
|
||||
// 工作空间路径和项目路径
|
||||
envVars.WORKSPACE = this.workspace;
|
||||
envVars.PROJECT_DIR = projectDir;
|
||||
|
||||
return envVars;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为日志添加时间戳前缀
|
||||
* @param message 日志消息
|
||||
* @param isError 是否为错误日志
|
||||
* @returns 带时间戳的日志消息
|
||||
*/
|
||||
private addTimestamp(message: string, isError = false): string {
|
||||
const timestamp = new Date().toISOString();
|
||||
if (isError) {
|
||||
return `[${timestamp}] [ERROR] ${message}`;
|
||||
}
|
||||
return `[${timestamp}] ${message}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为多行日志添加时间戳前缀
|
||||
* @param content 多行日志内容
|
||||
* @param isError 是否为错误日志
|
||||
* @returns 带时间戳的多行日志消息
|
||||
*/
|
||||
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';
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保工作空间目录存在
|
||||
*/
|
||||
private async ensureWorkspace(): Promise<void> {
|
||||
try {
|
||||
// 检查目录是否存在,如果不存在则创建
|
||||
if (!fs.existsSync(this.workspace)) {
|
||||
// 创建目录包括所有必要的父目录
|
||||
fs.mkdirSync(this.workspace, { recursive: true });
|
||||
}
|
||||
|
||||
// 检查目录是否可写
|
||||
fs.accessSync(this.workspace, fs.constants.W_OK);
|
||||
} catch (error) {
|
||||
throw new Error(`无法访问或创建工作空间目录 "${this.workspace}": ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保项目目录存在
|
||||
* @param projectDir 项目目录路径
|
||||
*/
|
||||
private async ensureProjectDirectory(projectDir: string): Promise<void> {
|
||||
try {
|
||||
// 检查目录是否存在,如果不存在则创建
|
||||
if (!fs.existsSync(projectDir)) {
|
||||
fs.mkdirSync(projectDir, { recursive: true });
|
||||
}
|
||||
|
||||
// 检查目录是否可写
|
||||
fs.accessSync(projectDir, fs.constants.W_OK);
|
||||
} catch (error) {
|
||||
throw new Error(`无法访问或创建项目目录 "${projectDir}": ${(error as Error).message}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行单个步骤
|
||||
* @param step 步骤对象
|
||||
* @param envVars 环境变量
|
||||
* @param projectDir 项目目录路径
|
||||
*/
|
||||
private async executeStep(step: Step, envVars: Record<string, string>, projectDir: string): Promise<string> {
|
||||
let logs = '';
|
||||
|
||||
try {
|
||||
// 添加步骤开始执行的时间戳
|
||||
logs += this.addTimestamp(`开始执行步骤 "${step.name}"`) + '\n';
|
||||
|
||||
// 使用zx执行脚本,设置项目目录为工作目录和环境变量
|
||||
const script = step.script;
|
||||
|
||||
// 通过bash -c执行脚本,确保环境变量能被正确解析
|
||||
const result = await $({
|
||||
cwd: projectDir,
|
||||
env: { ...process.env, ...envVars }
|
||||
})`bash -c ${script}`;
|
||||
|
||||
if (result.stdout) {
|
||||
// 为stdout中的每一行添加时间戳
|
||||
logs += this.addTimestampToLines(result.stdout);
|
||||
}
|
||||
|
||||
if (result.stderr) {
|
||||
// 为stderr中的每一行添加时间戳和错误标记
|
||||
logs += this.addTimestampToLines(result.stderr, true);
|
||||
}
|
||||
|
||||
// 添加步骤执行完成的时间戳
|
||||
logs += this.addTimestamp(`步骤 "${step.name}" 执行完成`) + '\n';
|
||||
} catch (error) {
|
||||
logs += this.addTimestamp(`Error executing step "${step.name}": ${(error as Error).message}`) + '\n';
|
||||
throw error;
|
||||
}
|
||||
|
||||
return logs;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user