feat: 重构保持工作空间存活功能,更新参数和添加数据管理,新增测试用例
This commit is contained in:
114
agent/routes/workspace/keep-file-live.ts
Normal file
114
agent/routes/workspace/keep-file-live.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import path from 'node:path';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
|
||||||
|
export type KeepAliveData = {
|
||||||
|
wsUrl: string;
|
||||||
|
cookie: string;
|
||||||
|
repo: string;
|
||||||
|
pipelineId: string;
|
||||||
|
createdTime: number;
|
||||||
|
filePath: string;
|
||||||
|
pm2Name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeepAliveCache = {
|
||||||
|
data: KeepAliveData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const keepAliveFilePath = path.join(os.homedir(), '.cnb/keepAliveCache.json');
|
||||||
|
|
||||||
|
export const runLive = (filePath: string, pm2Name: string) => {
|
||||||
|
// 使用 npx 运行命令
|
||||||
|
const cmdArgs = `cnb live -c ${filePath}`;
|
||||||
|
|
||||||
|
// 先停止已存在的同名 pm2 进程
|
||||||
|
const stopCmd = `pm2 delete ${pm2Name} 2>/dev/null || true`;
|
||||||
|
console.log('停止已存在的进程:', stopCmd);
|
||||||
|
try {
|
||||||
|
execSync(stopCmd, { stdio: 'inherit' });
|
||||||
|
} catch (error) {
|
||||||
|
console.log('停止进程失败或进程不存在:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用pm2启动
|
||||||
|
const pm2Cmd = `pm2 start ev --name ${pm2Name} --no-autorestart -- ${cmdArgs}`;
|
||||||
|
console.log('执行命令:', pm2Cmd);
|
||||||
|
try {
|
||||||
|
const result = execSync(pm2Cmd, { stdio: 'pipe', encoding: 'utf8' });
|
||||||
|
console.log(result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("状态码:", error.status);
|
||||||
|
console.error("错误详情:", error.stderr.toString()); // 这里会显示 ev 命令报的具体错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopLive = (pm2Name: string): boolean => {
|
||||||
|
const stopCmd = `pm2 delete ${pm2Name} 2>/dev/null || true`;
|
||||||
|
console.log('停止进程:', stopCmd);
|
||||||
|
try {
|
||||||
|
execSync(stopCmd, { stdio: 'inherit' });
|
||||||
|
console.log(`已停止 ${pm2Name} 的保持存活任务`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('停止进程失败:', error);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getKeepAliveCache(): KeepAliveCache {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(keepAliveFilePath)) {
|
||||||
|
const data = fs.readFileSync(keepAliveFilePath, 'utf-8');
|
||||||
|
const cache = JSON.parse(data) as KeepAliveCache;
|
||||||
|
return cache;
|
||||||
|
} else {
|
||||||
|
return { data: [] };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('读取保持存活缓存文件失败:', error);
|
||||||
|
return { data: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addKeepAliveData(data: KeepAliveData): KeepAliveCache {
|
||||||
|
const cache = getKeepAliveCache();
|
||||||
|
cache.data.push(data);
|
||||||
|
runLive(data.filePath, data.pm2Name);
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(path.dirname(keepAliveFilePath))) {
|
||||||
|
fs.mkdirSync(path.dirname(keepAliveFilePath), { recursive: true });
|
||||||
|
}
|
||||||
|
fs.writeFileSync(keepAliveFilePath, JSON.stringify(cache, null, 2), 'utf-8');
|
||||||
|
return cache;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('写入保持存活缓存文件失败:', error);
|
||||||
|
return { data: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeKeepAliveData(repo: string, pipelineId: string): KeepAliveCache {
|
||||||
|
const cache = getKeepAliveCache();
|
||||||
|
cache.data = cache.data.filter(item => item.repo !== repo || item.pipelineId !== pipelineId);
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(keepAliveFilePath, JSON.stringify(cache, null, 2), 'utf-8');
|
||||||
|
return cache;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('写入保持存活缓存文件失败:', error);
|
||||||
|
return { data: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createLiveData = (data: { wsUrl: string, cookie: string, repo: string, pipelineId: string }): KeepAliveData => {
|
||||||
|
const { wsUrl, cookie, repo, pipelineId } = data;
|
||||||
|
const createdTime = Date.now();
|
||||||
|
const pm2Name = `${repo}__${pipelineId}`.replace(/\//g, '__');
|
||||||
|
const filePath = path.join(os.homedir(), '.cnb', `${pm2Name}.json`);
|
||||||
|
const _newData = { wss: wsUrl, wsUrl, cookie, repo, pipelineId, createdTime, filePath, pm2Name };
|
||||||
|
if (!fs.existsSync(path.dirname(filePath))) {
|
||||||
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||||
|
}
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(_newData, null, 2), 'utf-8');
|
||||||
|
return _newData;
|
||||||
|
}
|
||||||
@@ -1,214 +1,77 @@
|
|||||||
import { createSkill, tool } from '@kevisual/router';
|
import { tool } from '@kevisual/router';
|
||||||
import { app, cnb } from '../../app.ts';
|
import { app, cnb } from '../../app.ts';
|
||||||
import { nanoid } from 'nanoid';
|
import { addKeepAliveData, KeepAliveData, removeKeepAliveData, createLiveData } from '../../../src/workspace/keep-file-live.ts';
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import { createKeepAlive } from '../../../src/keep.ts';
|
|
||||||
|
|
||||||
type AliveInfo = {
|
|
||||||
startTime: number;
|
|
||||||
updatedTime?: number;
|
|
||||||
KeepAlive: ReturnType<typeof createKeepAlive>;
|
|
||||||
id: string;// 6位唯一标识符
|
|
||||||
}
|
|
||||||
|
|
||||||
const keepAliveMap = new Map<string, AliveInfo>();
|
|
||||||
|
|
||||||
// 保持工作空间存活技能
|
// 保持工作空间存活技能
|
||||||
app.route({
|
app.route({
|
||||||
path: 'cnb',
|
path: 'cnb',
|
||||||
key: 'keep-workspace-alive',
|
key: 'keep-workspace-alive',
|
||||||
description: '保持工作空间存活技能,参数wsUrl:工作空间访问URL,cookie:访问工作空间所需的cookie',
|
description: '保持工作空间存活技能,参数repo:代码仓库路径,例如 user/repo,pipelineId:流水线ID,例如 cnb-708-1ji9sog7o-001',
|
||||||
middleware: ['admin-auth'],
|
middleware: ['admin-auth'],
|
||||||
metadata: {
|
metadata: {
|
||||||
tags: [],
|
tags: [],
|
||||||
...({
|
...({
|
||||||
args: {
|
args: {
|
||||||
wsUrl: tool.schema.string().describe('工作空间的访问URL'),
|
repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
|
||||||
cookie: tool.schema.string().describe('访问工作空间所需的cookie')
|
pipelineId: tool.schema.string().describe('流水线ID,例如 cnb-708-1ji9sog7o-001'),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
const wsUrl = ctx.query?.wsUrl as string;
|
const repo = ctx.query?.repo as string;
|
||||||
const cookie = ctx.query?.cookie as string;
|
const pipelineId = ctx.query?.pipelineId as string;
|
||||||
if (!wsUrl) {
|
|
||||||
ctx.throw(400, '缺少工作空间访问URL参数');
|
|
||||||
}
|
|
||||||
if (!cookie) {
|
|
||||||
ctx.throw(400, '缺少访问工作空间所需的cookie参数');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测是否已在运行(通过 wsUrl 遍历检查)
|
if (!repo || !pipelineId) {
|
||||||
const existing = Array.from(keepAliveMap.values()).find(info => (info as AliveInfo).id && (info as any).KeepAlive?.wsUrl === wsUrl);
|
ctx.throw(400, '缺少参数 repo 或 pipelineId');
|
||||||
if (existing) {
|
}
|
||||||
ctx.body = { message: `工作空间 ${wsUrl} 的保持存活任务已在运行中`, id: (existing as AliveInfo).id };
|
const validCookie = await cnb.user.checkCookieValid()
|
||||||
return;
|
if (validCookie.code !== 200) {
|
||||||
|
ctx.throw(401, 'CNB_COOKIE 环境变量无效或已过期,请重新登录获取新的cookie');
|
||||||
|
}
|
||||||
|
const res = await cnb.workspace.getWorkspaceCookie(repo, pipelineId);
|
||||||
|
let wsUrl = `wss://${pipelineId}.cnb.space:443?skipWebSocketFrames=false`;
|
||||||
|
let cookie = '';
|
||||||
|
if (res.code === 200) {
|
||||||
|
cookie = res.data.value;
|
||||||
|
console.log(`启动保持工作空间 ${wsUrl} 存活的任务`);
|
||||||
|
} else {
|
||||||
|
ctx.throw(500, `获取工作空间访问cookie失败: ${res.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`启动保持工作空间 ${wsUrl} 存活的任务`);
|
console.log(`启动保持工作空间 ${wsUrl} 存活的任务`);
|
||||||
const keep = createKeepAlive({
|
|
||||||
wsUrl,
|
|
||||||
cookie,
|
|
||||||
onConnect: () => {
|
|
||||||
console.log(`工作空间 ${wsUrl} 保持存活任务已连接`);
|
|
||||||
},
|
|
||||||
onMessage: (data) => {
|
|
||||||
// 可选:处理收到的消息
|
|
||||||
// console.log(`工作空间 ${wsUrl} 收到消息: ${data}`);
|
|
||||||
// 通过 wsUrl 找到对应的 id 并更新时间
|
|
||||||
for (const info of keepAliveMap.values()) {
|
|
||||||
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
|
|
||||||
info.updatedTime = Date.now();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
debug: true,
|
|
||||||
onExit: (code) => {
|
|
||||||
console.log(`工作空间 ${wsUrl} 保持存活任务已退出,退出码: ${code}`);
|
|
||||||
// 通过 wsUrl 找到对应的 id 并删除
|
|
||||||
for (const [id, info] of keepAliveMap.entries()) {
|
|
||||||
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
|
|
||||||
keepAliveMap.delete(id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const id = nanoid(6).toLowerCase();
|
const config: KeepAliveData = createLiveData({ wsUrl, cookie, repo, pipelineId });
|
||||||
keepAliveMap.set(id, { startTime: Date.now(), updatedTime: Date.now(), KeepAlive: keep, id });
|
addKeepAliveData(config);
|
||||||
|
|
||||||
ctx.body = { content: `已启动保持工作空间 ${wsUrl} 存活的任务`, id };
|
ctx.body = { content: `已启动保持工作空间 ${wsUrl} 存活的任务`, data: config };
|
||||||
}).addTo(app);
|
|
||||||
|
|
||||||
// 获取保持工作空间存活任务列表技能
|
|
||||||
app.route({
|
|
||||||
path: 'cnb',
|
|
||||||
key: 'list-keep-alive-tasks',
|
|
||||||
description: '获取保持工作空间存活任务列表技能',
|
|
||||||
middleware: ['admin-auth'],
|
|
||||||
metadata: {
|
|
||||||
tags: [],
|
|
||||||
}
|
|
||||||
}).define(async (ctx) => {
|
|
||||||
const list = Array.from(keepAliveMap.entries()).map(([id, info]) => {
|
|
||||||
const now = Date.now();
|
|
||||||
const duration = Math.floor((now - info.startTime) / 60000); // 分钟
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
wsUrl: (info as any).KeepAlive?.wsUrl,
|
|
||||||
startTime: info.startTime,
|
|
||||||
startTimeStr: dayjs(info.startTime).format('YYYY-MM-DD HH:mm'),
|
|
||||||
updatedTime: info.updatedTime,
|
|
||||||
updatedTimeStr: dayjs(info.updatedTime).format('YYYY-MM-DD HH:mm'),
|
|
||||||
duration,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ctx.body = { list };
|
|
||||||
}).addTo(app);
|
}).addTo(app);
|
||||||
|
|
||||||
// 停止保持工作空间存活技能
|
// 停止保持工作空间存活技能
|
||||||
app.route({
|
app.route({
|
||||||
path: 'cnb',
|
path: 'cnb',
|
||||||
key: 'stop-keep-workspace-alive',
|
key: 'stop-keep-workspace-alive',
|
||||||
description: '停止保持工作空间存活技能, 参数wsUrl:工作空间访问URL或者id',
|
description: '停止保持工作空间存活技能, 参数repo:代码仓库路径,例如 user/repo,pipelineId:流水线ID,例如 cnb-708-1ji9sog7o-001',
|
||||||
middleware: ['admin-auth'],
|
middleware: ['admin-auth'],
|
||||||
metadata: {
|
metadata: {
|
||||||
tags: [],
|
tags: [],
|
||||||
...({
|
...({
|
||||||
args: {
|
args: {
|
||||||
wsUrl: tool.schema.string().optional().describe('工作空间的访问URL'),
|
repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
|
||||||
id: tool.schema.string().optional().describe('保持存活任务的唯一标识符'),
|
pipelineId: tool.schema.string().describe('流水线ID,例如 cnb-708-1ji9sog7o-001'),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}).define(async (ctx) => {
|
}).define(async (ctx) => {
|
||||||
const wsUrl = ctx.query?.wsUrl as string;
|
const repo = ctx.query?.repo as string;
|
||||||
const id = ctx.query?.id as string;
|
const pipelineId = ctx.query?.pipelineId as string;
|
||||||
if (!wsUrl && !id) {
|
|
||||||
ctx.throw(400, '缺少工作空间访问URL参数或唯一标识符');
|
|
||||||
}
|
|
||||||
|
|
||||||
let targetId: string | undefined;
|
if (!repo || !pipelineId) {
|
||||||
let wsUrlFound: string | undefined;
|
ctx.throw(400, '缺少参数 repo 或 pipelineId');
|
||||||
|
|
||||||
if (id) {
|
|
||||||
const info = keepAliveMap.get(id);
|
|
||||||
if (info) {
|
|
||||||
targetId = id;
|
|
||||||
wsUrlFound = (info as any).KeepAlive?.wsUrl;
|
|
||||||
}
|
|
||||||
} else if (wsUrl) {
|
|
||||||
for (const [key, info] of keepAliveMap.entries()) {
|
|
||||||
if ((info as any).KeepAlive?.wsUrl === wsUrl) {
|
|
||||||
targetId = key;
|
|
||||||
wsUrlFound = wsUrl;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (targetId) {
|
|
||||||
const keepAlive = keepAliveMap.get(targetId);
|
|
||||||
const endTime = Date.now();
|
|
||||||
const duration = endTime - keepAlive!.startTime;
|
|
||||||
keepAlive?.KeepAlive?.disconnect();
|
|
||||||
keepAliveMap.delete(targetId);
|
|
||||||
ctx.body = { content: `已停止保持工作空间 ${wsUrlFound} 存活的任务,持续时间: ${duration}ms`, id: targetId };
|
|
||||||
} else {
|
|
||||||
ctx.body = { content: `没有找到对应的工作空间保持存活任务` };
|
|
||||||
}
|
}
|
||||||
|
removeKeepAliveData(repo, pipelineId);
|
||||||
|
ctx.body = { content: `已停止保持工作空间 ${repo}/${pipelineId} 存活的任务` };
|
||||||
}).addTo(app);
|
}).addTo(app);
|
||||||
|
|
||||||
app.route({
|
|
||||||
path: 'cnb',
|
|
||||||
key: 'reset-keep-workspace-alive',
|
|
||||||
description: '对存活的工作空间,startTime进行重置',
|
|
||||||
middleware: ['admin-auth'],
|
|
||||||
metadata: {
|
|
||||||
tags: [],
|
|
||||||
}
|
|
||||||
}).define(async (ctx) => {
|
|
||||||
const now = Date.now();
|
|
||||||
for (const info of keepAliveMap.values()) {
|
|
||||||
info.startTime = now;
|
|
||||||
}
|
|
||||||
ctx.body = { content: `已重置所有存活工作空间的开始时间` };
|
|
||||||
}).addTo(app);
|
|
||||||
|
|
||||||
app.route({
|
|
||||||
path: 'cnb',
|
|
||||||
key: 'clear-keep-workspace-alive',
|
|
||||||
description: '对存活的工作空间,超过5小时的进行清理',
|
|
||||||
middleware: ['admin-auth'],
|
|
||||||
metadata: {
|
|
||||||
tags: [],
|
|
||||||
}
|
|
||||||
}).define(async (ctx) => {
|
|
||||||
const res = clearKeepAlive();
|
|
||||||
ctx.body = {
|
|
||||||
content: `已清理所有存活工作空间中超过5小时的任务` + (res.length ? `,清理项:${res.map(i => i.wsUrl).join(', ')}` : ''),
|
|
||||||
list: res
|
|
||||||
};
|
|
||||||
}).addTo(app);
|
|
||||||
|
|
||||||
const clearKeepAlive = () => {
|
|
||||||
const now = Date.now();
|
|
||||||
let clearedArr: { id: string; wsUrl: string }[] = [];
|
|
||||||
for (const [id, info] of keepAliveMap.entries()) {
|
|
||||||
if (now - info.startTime > FIVE_HOURS) {
|
|
||||||
console.log(`工作空间 ${(info as any).KeepAlive?.wsUrl} 超过5小时,自动停止`);
|
|
||||||
info.KeepAlive?.disconnect?.();
|
|
||||||
keepAliveMap.delete(id);
|
|
||||||
clearedArr.push({ id, wsUrl: (info as any).KeepAlive?.wsUrl });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return clearedArr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 每5小时自动清理超时的keepAlive任务
|
|
||||||
const FIVE_HOURS = 5 * 60 * 60 * 1000;
|
|
||||||
setInterval(() => {
|
|
||||||
clearKeepAlive();
|
|
||||||
}, FIVE_HOURS);
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@kevisual/cnb",
|
"name": "@kevisual/cnb",
|
||||||
"version": "0.0.29",
|
"version": "0.0.30",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -52,6 +52,7 @@
|
|||||||
"./opencode": "./dist/opencode.js",
|
"./opencode": "./dist/opencode.js",
|
||||||
"./keep": "./dist/keep.js",
|
"./keep": "./dist/keep.js",
|
||||||
"./keep.ts": "./src/keep.ts",
|
"./keep.ts": "./src/keep.ts",
|
||||||
|
"./keep-file-live.ts": "./src/workspace/keep-file-live.ts",
|
||||||
"./routes": "./dist/routes.js",
|
"./routes": "./dist/routes.js",
|
||||||
"./src/*": "./src/*",
|
"./src/*": "./src/*",
|
||||||
"./agent/*": "./agent/*"
|
"./agent/*": "./agent/*"
|
||||||
|
|||||||
136
src/workspace/keep-file-live.ts
Normal file
136
src/workspace/keep-file-live.ts
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import path from 'node:path';
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import os from 'node:os';
|
||||||
|
import { execSync } from 'node:child_process';
|
||||||
|
|
||||||
|
export type KeepAliveData = {
|
||||||
|
wsUrl: string;
|
||||||
|
cookie: string;
|
||||||
|
repo: string;
|
||||||
|
pipelineId: string;
|
||||||
|
createdTime: number;
|
||||||
|
filePath: string;
|
||||||
|
pm2Name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeepAliveCache = {
|
||||||
|
data: KeepAliveData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const keepAliveFilePath = path.join(os.homedir(), '.cnb/keepAliveCache.json');
|
||||||
|
|
||||||
|
export const runLive = (filePath: string, pm2Name: string) => {
|
||||||
|
// 使用 npx 运行命令
|
||||||
|
const cmdArgs = `cnb live -c ${filePath}`;
|
||||||
|
|
||||||
|
// 先停止已存在的同名 pm2 进程
|
||||||
|
const stopCmd = `pm2 delete ${pm2Name} 2>/dev/null || true`;
|
||||||
|
console.log('停止已存在的进程:', stopCmd);
|
||||||
|
try {
|
||||||
|
execSync(stopCmd, { stdio: 'inherit' });
|
||||||
|
} catch (error) {
|
||||||
|
console.log('停止进程失败或进程不存在:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用pm2启动
|
||||||
|
const pm2Cmd = `pm2 start ev --name ${pm2Name} --no-autorestart -- ${cmdArgs}`;
|
||||||
|
console.log('执行命令:', pm2Cmd);
|
||||||
|
try {
|
||||||
|
const result = execSync(pm2Cmd, { stdio: 'pipe', encoding: 'utf8' });
|
||||||
|
console.log(result);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("状态码:", error.status);
|
||||||
|
console.error("错误详情:", error.stderr.toString()); // 这里会显示 ev 命令报的具体错误
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const stopLive = (pm2Name: string): boolean => {
|
||||||
|
const stopCmd = `pm2 delete ${pm2Name} 2>/dev/null || true`;
|
||||||
|
console.log('停止进程:', stopCmd);
|
||||||
|
try {
|
||||||
|
execSync(stopCmd, { stdio: 'inherit' });
|
||||||
|
console.log(`已停止 ${pm2Name} 的保持存活任务`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('停止进程失败:', error);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getKeepAliveCache(): KeepAliveCache {
|
||||||
|
try {
|
||||||
|
if (fs.existsSync(keepAliveFilePath)) {
|
||||||
|
const data = fs.readFileSync(keepAliveFilePath, 'utf-8');
|
||||||
|
const cache = JSON.parse(data) as KeepAliveCache;
|
||||||
|
return cache;
|
||||||
|
} else {
|
||||||
|
return { data: [] };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('读取保持存活缓存文件失败:', error);
|
||||||
|
return { data: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addKeepAliveData(data: KeepAliveData): KeepAliveCache {
|
||||||
|
const cache = getKeepAliveCache();
|
||||||
|
cache.data.push(data);
|
||||||
|
runLive(data.filePath, data.pm2Name);
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(path.dirname(keepAliveFilePath))) {
|
||||||
|
fs.mkdirSync(path.dirname(keepAliveFilePath), { recursive: true });
|
||||||
|
}
|
||||||
|
fs.writeFileSync(keepAliveFilePath, JSON.stringify(cache, null, 2), 'utf-8');
|
||||||
|
return cache;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('写入保持存活缓存文件失败:', error);
|
||||||
|
return { data: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeKeepAliveData(repo: string, pipelineId: string): KeepAliveCache {
|
||||||
|
const cache = getKeepAliveCache();
|
||||||
|
const item = cache.data.find(item => item.repo === repo && item.pipelineId === pipelineId);
|
||||||
|
if (item) {
|
||||||
|
stopLive(item.pm2Name);
|
||||||
|
}
|
||||||
|
cache.data = cache.data.filter(item => item.repo !== repo || item.pipelineId !== pipelineId);
|
||||||
|
try {
|
||||||
|
fs.writeFileSync(keepAliveFilePath, JSON.stringify(cache, null, 2), 'utf-8');
|
||||||
|
return cache;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('写入保持存活缓存文件失败:', error);
|
||||||
|
return { data: [] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createLiveData = (data: { wsUrl: string, cookie: string, repo: string, pipelineId: string }): KeepAliveData => {
|
||||||
|
const { wsUrl, cookie, repo, pipelineId } = data;
|
||||||
|
const createdTime = Date.now();
|
||||||
|
const pm2Name = `${repo}__${pipelineId}`.replace(/\//g, '__');
|
||||||
|
const filePath = path.join(os.homedir(), '.cnb', `${pm2Name}.json`);
|
||||||
|
const _newData = { wss: wsUrl, wsUrl, cookie, repo, pipelineId, createdTime, filePath, pm2Name };
|
||||||
|
if (!fs.existsSync(path.dirname(filePath))) {
|
||||||
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||||
|
}
|
||||||
|
fs.writeFileSync(filePath, JSON.stringify(_newData, null, 2), 'utf-8');
|
||||||
|
return _newData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class KeepAliveManager {
|
||||||
|
static getCache() {
|
||||||
|
return getKeepAliveCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
static add(data: KeepAliveData) {
|
||||||
|
return addKeepAliveData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static createLiveData(data: { wsUrl: string, cookie: string, repo: string, pipelineId: string }): KeepAliveData {
|
||||||
|
return createLiveData(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static remove(repo: string, pipelineId: string) {
|
||||||
|
return removeKeepAliveData(repo, pipelineId);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
test/keep-file-live.ts
Normal file
19
test/keep-file-live.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { addKeepAliveData, createLiveData, getKeepAliveCache } from '../agent/routes/workspace/keep-file-live';
|
||||||
|
|
||||||
|
const repo = 'kevisual/dev-env';
|
||||||
|
const pipelineId = 'cnb-708-1ji9sog7o-001';
|
||||||
|
|
||||||
|
const testData = createLiveData({
|
||||||
|
wsUrl: "wss://cnb-708-1ji9sog7o-001.cnb.space:443?skipWebSocketFrames=false",
|
||||||
|
cookie: "orange:workspace:cookie-session:cnb-708-1ji9sog7o-001=3dc03d84-5617-4e44-a6b9-38ce4398aea5",
|
||||||
|
repo: repo,
|
||||||
|
pipelineId: pipelineId
|
||||||
|
});
|
||||||
|
|
||||||
|
addKeepAliveData(testData);
|
||||||
|
|
||||||
|
// 运行后可以在 ~/.cnb/kevisual_dev-env_cnb-708-1ji9sog7o-001.json 中看到保持存活的数据
|
||||||
|
// 同时可以通过 pm2 list 命令看到对应的保持存活的进程
|
||||||
|
|
||||||
|
// 注意:如果要测试停止保持存活,可以调用 stopLive(testData.pm2Name) 来停止对应的进程
|
||||||
|
// 例如:stopLive('kevisual_dev-env_cnb-708-1ji9sog7o-001');
|
||||||
@@ -12,4 +12,5 @@ createKeepAlive({
|
|||||||
wsUrl: config.wss,
|
wsUrl: config.wss,
|
||||||
cookie: config.cookie,
|
cookie: config.cookie,
|
||||||
debug: true,
|
debug: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user