@kevisual/cnb 0.0.28 → 0.0.30

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({ wsUrl, 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);
package/dist/keep.js CHANGED
@@ -28,7 +28,7 @@ var __export = (target, all) => {
28
28
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
29
29
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
30
30
 
31
- // node_modules/ws/lib/constants.js
31
+ // node_modules/.pnpm/@kevisual+ws@8.19.0/node_modules/@kevisual/ws/lib/constants.js
32
32
  var require_constants = __commonJS((exports, module) => {
33
33
  var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"];
34
34
  var hasBlob = typeof Blob !== "undefined";
@@ -48,7 +48,7 @@ var require_constants = __commonJS((exports, module) => {
48
48
  };
49
49
  });
50
50
 
51
- // node_modules/ws/lib/buffer-util.js
51
+ // node_modules/.pnpm/@kevisual+ws@8.19.0/node_modules/@kevisual/ws/lib/buffer-util.js
52
52
  var require_buffer_util = __commonJS((exports, module) => {
53
53
  var { EMPTY_BUFFER } = require_constants();
54
54
  var FastBuffer = Buffer[Symbol.species];
@@ -109,7 +109,7 @@ var require_buffer_util = __commonJS((exports, module) => {
109
109
  };
110
110
  });
111
111
 
112
- // node_modules/ws/lib/limiter.js
112
+ // node_modules/.pnpm/@kevisual+ws@8.19.0/node_modules/@kevisual/ws/lib/limiter.js
113
113
  var require_limiter = __commonJS((exports, module) => {
114
114
  var kDone = Symbol("kDone");
115
115
  var kRun = Symbol("kRun");
@@ -141,7 +141,7 @@ var require_limiter = __commonJS((exports, module) => {
141
141
  module.exports = Limiter;
142
142
  });
143
143
 
144
- // node_modules/ws/lib/permessage-deflate.js
144
+ // node_modules/.pnpm/@kevisual+ws@8.19.0/node_modules/@kevisual/ws/lib/permessage-deflate.js
145
145
  var require_permessage_deflate = __commonJS((exports, module) => {
146
146
  var zlib = __require("zlib");
147
147
  var bufferUtil = require_buffer_util();
@@ -405,7 +405,7 @@ var require_permessage_deflate = __commonJS((exports, module) => {
405
405
  }
406
406
  });
407
407
 
408
- // node_modules/ws/lib/validation.js
408
+ // node_modules/.pnpm/@kevisual+ws@8.19.0/node_modules/@kevisual/ws/lib/validation.js
409
409
  var require_validation = __commonJS((exports, module) => {
410
410
  var { isUtf8 } = __require("buffer");
411
411
  var { hasBlob } = require_constants();
@@ -585,7 +585,7 @@ var require_validation = __commonJS((exports, module) => {
585
585
  }
586
586
  });
587
587
 
588
- // node_modules/ws/lib/receiver.js
588
+ // node_modules/.pnpm/@kevisual+ws@8.19.0/node_modules/@kevisual/ws/lib/receiver.js
589
589
  var require_receiver = __commonJS((exports, module) => {
590
590
  var { Writable } = __require("stream");
591
591
  var PerMessageDeflate = require_permessage_deflate();
@@ -966,7 +966,7 @@ var require_receiver = __commonJS((exports, module) => {
966
966
  module.exports = Receiver;
967
967
  });
968
968
 
969
- // node_modules/ws/lib/sender.js
969
+ // node_modules/.pnpm/@kevisual+ws@8.19.0/node_modules/@kevisual/ws/lib/sender.js
970
970
  var require_sender = __commonJS((exports, module) => {
971
971
  var { Duplex } = __require("stream");
972
972
  var { randomFillSync } = __require("crypto");
@@ -1320,7 +1320,7 @@ var require_sender = __commonJS((exports, module) => {
1320
1320
  }
1321
1321
  });
1322
1322
 
1323
- // node_modules/ws/lib/event-target.js
1323
+ // node_modules/.pnpm/@kevisual+ws@8.19.0/node_modules/@kevisual/ws/lib/event-target.js
1324
1324
  var require_event_target = __commonJS((exports, module) => {
1325
1325
  var { kForOnEventAttribute, kListener } = require_constants();
1326
1326
  var kCode = Symbol("kCode");
@@ -1471,7 +1471,7 @@ var require_event_target = __commonJS((exports, module) => {
1471
1471
  }
1472
1472
  });
1473
1473
 
1474
- // node_modules/ws/lib/extension.js
1474
+ // node_modules/.pnpm/@kevisual+ws@8.19.0/node_modules/@kevisual/ws/lib/extension.js
1475
1475
  var require_extension = __commonJS((exports, module) => {
1476
1476
  var { tokenChars } = require_validation();
1477
1477
  function push(dest, name, elem) {
@@ -1636,7 +1636,7 @@ var require_extension = __commonJS((exports, module) => {
1636
1636
  module.exports = { format, parse };
1637
1637
  });
1638
1638
 
1639
- // node_modules/ws/lib/websocket.js
1639
+ // node_modules/.pnpm/@kevisual+ws@8.19.0/node_modules/@kevisual/ws/lib/websocket.js
1640
1640
  var require_websocket = __commonJS((exports, module) => {
1641
1641
  var EventEmitter = __require("events");
1642
1642
  var https = __require("https");
@@ -2398,7 +2398,7 @@ var require_websocket = __commonJS((exports, module) => {
2398
2398
  }
2399
2399
  });
2400
2400
 
2401
- // node_modules/ws/lib/stream.js
2401
+ // node_modules/.pnpm/@kevisual+ws@8.19.0/node_modules/@kevisual/ws/lib/stream.js
2402
2402
  var require_stream = __commonJS((exports, module) => {
2403
2403
  var WebSocket = require_websocket();
2404
2404
  var { Duplex } = __require("stream");
@@ -2501,7 +2501,7 @@ var require_stream = __commonJS((exports, module) => {
2501
2501
  module.exports = createWebSocketStream;
2502
2502
  });
2503
2503
 
2504
- // node_modules/ws/lib/subprotocol.js
2504
+ // node_modules/.pnpm/@kevisual+ws@8.19.0/node_modules/@kevisual/ws/lib/subprotocol.js
2505
2505
  var require_subprotocol = __commonJS((exports, module) => {
2506
2506
  var { tokenChars } = require_validation();
2507
2507
  function parse(header) {
@@ -2546,7 +2546,7 @@ var require_subprotocol = __commonJS((exports, module) => {
2546
2546
  module.exports = { parse };
2547
2547
  });
2548
2548
 
2549
- // node_modules/ws/lib/websocket-server.js
2549
+ // node_modules/.pnpm/@kevisual+ws@8.19.0/node_modules/@kevisual/ws/lib/websocket-server.js
2550
2550
  var require_websocket_server = __commonJS((exports, module) => {
2551
2551
  var EventEmitter = __require("events");
2552
2552
  var http = __require("http");
@@ -2848,7 +2848,7 @@ var require_websocket_server = __commonJS((exports, module) => {
2848
2848
  }
2849
2849
  });
2850
2850
 
2851
- // node_modules/ws/wrapper.mjs
2851
+ // node_modules/.pnpm/@kevisual+ws@8.19.0/node_modules/@kevisual/ws/wrapper.mjs
2852
2852
  var exports_wrapper = {};
2853
2853
  __export(exports_wrapper, {
2854
2854
  default: () => wrapper_default,