@kevisual/cnb 0.0.29 → 0.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,114 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import { execSync } from 'node:child_process';
5
+
6
+ export type KeepAliveData = {
7
+ wsUrl: string;
8
+ cookie: string;
9
+ repo: string;
10
+ pipelineId: string;
11
+ createdTime: number;
12
+ filePath: string;
13
+ pm2Name: string;
14
+ }
15
+
16
+ type KeepAliveCache = {
17
+ data: KeepAliveData[];
18
+ }
19
+
20
+ const keepAliveFilePath = path.join(os.homedir(), '.cnb/keepAliveCache.json');
21
+
22
+ export const runLive = (filePath: string, pm2Name: string) => {
23
+ // 使用 npx 运行命令
24
+ const cmdArgs = `cnb live -c ${filePath}`;
25
+
26
+ // 先停止已存在的同名 pm2 进程
27
+ const stopCmd = `pm2 delete ${pm2Name} 2>/dev/null || true`;
28
+ console.log('停止已存在的进程:', stopCmd);
29
+ try {
30
+ execSync(stopCmd, { stdio: 'inherit' });
31
+ } catch (error) {
32
+ console.log('停止进程失败或进程不存在:', error);
33
+ }
34
+
35
+ // 使用pm2启动
36
+ const pm2Cmd = `pm2 start ev --name ${pm2Name} --no-autorestart -- ${cmdArgs}`;
37
+ console.log('执行命令:', pm2Cmd);
38
+ try {
39
+ const result = execSync(pm2Cmd, { stdio: 'pipe', encoding: 'utf8' });
40
+ console.log(result);
41
+ } catch (error) {
42
+ console.error("状态码:", error.status);
43
+ console.error("错误详情:", error.stderr.toString()); // 这里会显示 ev 命令报的具体错误
44
+ }
45
+ }
46
+
47
+ export const stopLive = (pm2Name: string): boolean => {
48
+ const stopCmd = `pm2 delete ${pm2Name} 2>/dev/null || true`;
49
+ console.log('停止进程:', stopCmd);
50
+ try {
51
+ execSync(stopCmd, { stdio: 'inherit' });
52
+ console.log(`已停止 ${pm2Name} 的保持存活任务`);
53
+ return true;
54
+ } catch (error) {
55
+ console.error('停止进程失败:', error);
56
+ }
57
+ return false;
58
+ }
59
+
60
+ export function getKeepAliveCache(): KeepAliveCache {
61
+ try {
62
+ if (fs.existsSync(keepAliveFilePath)) {
63
+ const data = fs.readFileSync(keepAliveFilePath, 'utf-8');
64
+ const cache = JSON.parse(data) as KeepAliveCache;
65
+ return cache;
66
+ } else {
67
+ return { data: [] };
68
+ }
69
+ } catch (error) {
70
+ console.error('读取保持存活缓存文件失败:', error);
71
+ return { data: [] };
72
+ }
73
+ }
74
+
75
+ export function addKeepAliveData(data: KeepAliveData): KeepAliveCache {
76
+ const cache = getKeepAliveCache();
77
+ cache.data.push(data);
78
+ runLive(data.filePath, data.pm2Name);
79
+ try {
80
+ if (!fs.existsSync(path.dirname(keepAliveFilePath))) {
81
+ fs.mkdirSync(path.dirname(keepAliveFilePath), { recursive: true });
82
+ }
83
+ fs.writeFileSync(keepAliveFilePath, JSON.stringify(cache, null, 2), 'utf-8');
84
+ return cache;
85
+ } catch (error) {
86
+ console.error('写入保持存活缓存文件失败:', error);
87
+ return { data: [] };
88
+ }
89
+ }
90
+
91
+ export function removeKeepAliveData(repo: string, pipelineId: string): KeepAliveCache {
92
+ const cache = getKeepAliveCache();
93
+ cache.data = cache.data.filter(item => item.repo !== repo || item.pipelineId !== pipelineId);
94
+ try {
95
+ fs.writeFileSync(keepAliveFilePath, JSON.stringify(cache, null, 2), 'utf-8');
96
+ return cache;
97
+ } catch (error) {
98
+ console.error('写入保持存活缓存文件失败:', error);
99
+ return { data: [] };
100
+ }
101
+ }
102
+
103
+ export const createLiveData = (data: { wsUrl: string, cookie: string, repo: string, pipelineId: string }): KeepAliveData => {
104
+ const { wsUrl, cookie, repo, pipelineId } = data;
105
+ const createdTime = Date.now();
106
+ const pm2Name = `${repo}__${pipelineId}`.replace(/\//g, '__');
107
+ const filePath = path.join(os.homedir(), '.cnb', `${pm2Name}.json`);
108
+ const _newData = { wss: wsUrl, wsUrl, cookie, repo, pipelineId, createdTime, filePath, pm2Name };
109
+ if (!fs.existsSync(path.dirname(filePath))) {
110
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
111
+ }
112
+ fs.writeFileSync(filePath, JSON.stringify(_newData, null, 2), 'utf-8');
113
+ return _newData;
114
+ }
@@ -1,214 +1,77 @@
1
- import { createSkill, tool } from '@kevisual/router';
1
+ import { tool } from '@kevisual/router';
2
2
  import { app, cnb } from '../../app.ts';
3
- import { nanoid } from 'nanoid';
4
- import dayjs from 'dayjs';
5
- import { createKeepAlive } from '../../../src/keep.ts';
6
-
7
- type AliveInfo = {
8
- startTime: number;
9
- updatedTime?: number;
10
- KeepAlive: ReturnType<typeof createKeepAlive>;
11
- id: string;// 6位唯一标识符
12
- }
13
-
14
- const keepAliveMap = new Map<string, AliveInfo>();
3
+ import { addKeepAliveData, KeepAliveData, removeKeepAliveData, createLiveData } from '../../../src/workspace/keep-file-live.ts';
15
4
 
16
5
  // 保持工作空间存活技能
17
6
  app.route({
18
7
  path: 'cnb',
19
8
  key: 'keep-workspace-alive',
20
- description: '保持工作空间存活技能,参数wsUrl:工作空间访问URLcookie:访问工作空间所需的cookie',
9
+ description: '保持工作空间存活技能,参数repo:代码仓库路径,例如 user/repopipelineId:流水线ID,例如 cnb-708-1ji9sog7o-001',
21
10
  middleware: ['admin-auth'],
22
11
  metadata: {
23
12
  tags: [],
24
13
  ...({
25
14
  args: {
26
- wsUrl: tool.schema.string().describe('工作空间的访问URL'),
27
- cookie: tool.schema.string().describe('访问工作空间所需的cookie')
15
+ repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
16
+ pipelineId: tool.schema.string().describe('流水线ID,例如 cnb-708-1ji9sog7o-001'),
28
17
  }
29
18
  })
30
19
  }
31
20
  }).define(async (ctx) => {
32
- const wsUrl = ctx.query?.wsUrl as string;
33
- const cookie = ctx.query?.cookie as string;
34
- if (!wsUrl) {
35
- ctx.throw(400, '缺少工作空间访问URL参数');
21
+ const repo = ctx.query?.repo as string;
22
+ const pipelineId = ctx.query?.pipelineId as string;
23
+
24
+ if (!repo || !pipelineId) {
25
+ ctx.throw(400, '缺少参数 repo 或 pipelineId');
36
26
  }
37
- if (!cookie) {
38
- ctx.throw(400, '缺少访问工作空间所需的cookie参数');
27
+ const validCookie = await cnb.user.checkCookieValid()
28
+ if (validCookie.code !== 200) {
29
+ ctx.throw(401, 'CNB_COOKIE 环境变量无效或已过期,请重新登录获取新的cookie');
39
30
  }
40
-
41
- // 检测是否已在运行(通过 wsUrl 遍历检查)
42
- const existing = Array.from(keepAliveMap.values()).find(info => (info as AliveInfo).id && (info as any).KeepAlive?.wsUrl === wsUrl);
43
- if (existing) {
44
- ctx.body = { message: `工作空间 ${wsUrl} 的保持存活任务已在运行中`, id: (existing as AliveInfo).id };
45
- return;
31
+ const res = await cnb.workspace.getWorkspaceCookie(repo, pipelineId);
32
+ let wsUrl = `wss://${pipelineId}.cnb.space:443?skipWebSocketFrames=false`;
33
+ let cookie = '';
34
+ if (res.code === 200) {
35
+ cookie = res.data.value;
36
+ console.log(`启动保持工作空间 ${wsUrl} 存活的任务`);
37
+ } else {
38
+ ctx.throw(500, `获取工作空间访问cookie失败: ${res.message}`);
46
39
  }
47
40
 
48
41
  console.log(`启动保持工作空间 ${wsUrl} 存活的任务`);
49
- const keep = createKeepAlive({
50
- wsUrl,
51
- cookie,
52
- onConnect: () => {
53
- console.log(`工作空间 ${wsUrl} 保持存活任务已连接`);
54
- },
55
- onMessage: (data) => {
56
- // 可选:处理收到的消息
57
- // console.log(`工作空间 ${wsUrl} 收到消息: ${data}`);
58
- // 通过 wsUrl 找到对应的 id 并更新时间
59
- for (const info of keepAliveMap.values()) {
60
- if ((info as any).KeepAlive?.wsUrl === wsUrl) {
61
- info.updatedTime = Date.now();
62
- break;
63
- }
64
- }
65
- },
66
- debug: true,
67
- onExit: (code) => {
68
- console.log(`工作空间 ${wsUrl} 保持存活任务已退出,退出码: ${code}`);
69
- // 通过 wsUrl 找到对应的 id 并删除
70
- for (const [id, info] of keepAliveMap.entries()) {
71
- if ((info as any).KeepAlive?.wsUrl === wsUrl) {
72
- keepAliveMap.delete(id);
73
- break;
74
- }
75
- }
76
- }
77
- });
78
42
 
79
- const id = nanoid(6).toLowerCase();
80
- keepAliveMap.set(id, { startTime: Date.now(), updatedTime: Date.now(), KeepAlive: keep, id });
43
+ const config: KeepAliveData = createLiveData({ cookie, repo, pipelineId });
44
+ addKeepAliveData(config);
81
45
 
82
- ctx.body = { content: `已启动保持工作空间 ${wsUrl} 存活的任务`, id };
83
- }).addTo(app);
84
-
85
- // 获取保持工作空间存活任务列表技能
86
- app.route({
87
- path: 'cnb',
88
- key: 'list-keep-alive-tasks',
89
- description: '获取保持工作空间存活任务列表技能',
90
- middleware: ['admin-auth'],
91
- metadata: {
92
- tags: [],
93
- }
94
- }).define(async (ctx) => {
95
- const list = Array.from(keepAliveMap.entries()).map(([id, info]) => {
96
- const now = Date.now();
97
- const duration = Math.floor((now - info.startTime) / 60000); // 分钟
98
- return {
99
- id,
100
- wsUrl: (info as any).KeepAlive?.wsUrl,
101
- startTime: info.startTime,
102
- startTimeStr: dayjs(info.startTime).format('YYYY-MM-DD HH:mm'),
103
- updatedTime: info.updatedTime,
104
- updatedTimeStr: dayjs(info.updatedTime).format('YYYY-MM-DD HH:mm'),
105
- duration,
106
- }
107
- });
108
- ctx.body = { list };
46
+ ctx.body = { content: `已启动保持工作空间 ${wsUrl} 存活的任务`, data: config };
109
47
  }).addTo(app);
110
48
 
111
49
  // 停止保持工作空间存活技能
112
50
  app.route({
113
51
  path: 'cnb',
114
52
  key: 'stop-keep-workspace-alive',
115
- description: '停止保持工作空间存活技能, 参数wsUrl:工作空间访问URL或者id',
53
+ description: '停止保持工作空间存活技能, 参数repo:代码仓库路径,例如 user/repo,pipelineId:流水线ID,例如 cnb-708-1ji9sog7o-001',
116
54
  middleware: ['admin-auth'],
117
55
  metadata: {
118
56
  tags: [],
119
57
  ...({
120
58
  args: {
121
- wsUrl: tool.schema.string().optional().describe('工作空间的访问URL'),
122
- id: tool.schema.string().optional().describe('保持存活任务的唯一标识符'),
59
+ repo: tool.schema.string().describe('代码仓库路径,例如 user/repo'),
60
+ pipelineId: tool.schema.string().describe('流水线ID,例如 cnb-708-1ji9sog7o-001'),
123
61
  }
124
62
  })
125
63
  }
126
64
  }).define(async (ctx) => {
127
- const wsUrl = ctx.query?.wsUrl as string;
128
- const id = ctx.query?.id as string;
129
- if (!wsUrl && !id) {
130
- ctx.throw(400, '缺少工作空间访问URL参数或唯一标识符');
131
- }
132
-
133
- let targetId: string | undefined;
134
- let wsUrlFound: string | undefined;
135
-
136
- if (id) {
137
- const info = keepAliveMap.get(id);
138
- if (info) {
139
- targetId = id;
140
- wsUrlFound = (info as any).KeepAlive?.wsUrl;
141
- }
142
- } else if (wsUrl) {
143
- for (const [key, info] of keepAliveMap.entries()) {
144
- if ((info as any).KeepAlive?.wsUrl === wsUrl) {
145
- targetId = key;
146
- wsUrlFound = wsUrl;
147
- break;
148
- }
149
- }
150
- }
65
+ const repo = ctx.query?.repo as string;
66
+ const pipelineId = ctx.query?.pipelineId as string;
151
67
 
152
- if (targetId) {
153
- const keepAlive = keepAliveMap.get(targetId);
154
- const endTime = Date.now();
155
- const duration = endTime - keepAlive!.startTime;
156
- keepAlive?.KeepAlive?.disconnect();
157
- keepAliveMap.delete(targetId);
158
- ctx.body = { content: `已停止保持工作空间 ${wsUrlFound} 存活的任务,持续时间: ${duration}ms`, id: targetId };
159
- } else {
160
- ctx.body = { content: `没有找到对应的工作空间保持存活任务` };
68
+ if (!repo || !pipelineId) {
69
+ ctx.throw(400, '缺少参数 repo 或 pipelineId');
161
70
  }
71
+ removeKeepAliveData(repo, pipelineId);
72
+ ctx.body = { content: `已停止保持工作空间 ${repo}/${pipelineId} 存活的任务` };
162
73
  }).addTo(app);
163
74
 
164
- app.route({
165
- path: 'cnb',
166
- key: 'reset-keep-workspace-alive',
167
- description: '对存活的工作空间,startTime进行重置',
168
- middleware: ['admin-auth'],
169
- metadata: {
170
- tags: [],
171
- }
172
- }).define(async (ctx) => {
173
- const now = Date.now();
174
- for (const info of keepAliveMap.values()) {
175
- info.startTime = now;
176
- }
177
- ctx.body = { content: `已重置所有存活工作空间的开始时间` };
178
- }).addTo(app);
179
75
 
180
- app.route({
181
- path: 'cnb',
182
- key: 'clear-keep-workspace-alive',
183
- description: '对存活的工作空间,超过5小时的进行清理',
184
- middleware: ['admin-auth'],
185
- metadata: {
186
- tags: [],
187
- }
188
- }).define(async (ctx) => {
189
- const res = clearKeepAlive();
190
- ctx.body = {
191
- content: `已清理所有存活工作空间中超过5小时的任务` + (res.length ? `,清理项:${res.map(i => i.wsUrl).join(', ')}` : ''),
192
- list: res
193
- };
194
- }).addTo(app);
195
76
 
196
- const clearKeepAlive = () => {
197
- const now = Date.now();
198
- let clearedArr: { id: string; wsUrl: string }[] = [];
199
- for (const [id, info] of keepAliveMap.entries()) {
200
- if (now - info.startTime > FIVE_HOURS) {
201
- console.log(`工作空间 ${(info as any).KeepAlive?.wsUrl} 超过5小时,自动停止`);
202
- info.KeepAlive?.disconnect?.();
203
- keepAliveMap.delete(id);
204
- clearedArr.push({ id, wsUrl: (info as any).KeepAlive?.wsUrl });
205
- }
206
- }
207
- return clearedArr;
208
- }
209
77
 
210
- // 每5小时自动清理超时的keepAlive任务
211
- const FIVE_HOURS = 5 * 60 * 60 * 1000;
212
- setInterval(() => {
213
- clearKeepAlive();
214
- }, FIVE_HOURS);