github-actions[bot] commited on
Commit
dd27eb1
·
1 Parent(s): ed42d52

Update from GitHub Actions

Browse files
src/config.js CHANGED
@@ -29,7 +29,7 @@ const config = {
29
  },
30
 
31
  // 要执行的命令
32
- command: 'service cron start',
33
 
34
  // 截图保存目录
35
  screenshotDir: './screenshots',
 
29
  },
30
 
31
  // 要执行的命令
32
+ command: 'service cron start && date',
33
 
34
  // 截图保存目录
35
  screenshotDir: './screenshots',
src/execute-command.js CHANGED
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'url';
3
  import path from 'path';
4
 
5
  import { createBrowserSession, navigateToWebIDE, executeCommandFlow } from './utils/webide-utils.js';
 
6
 
7
  async function executeCommand() {
8
 
@@ -20,16 +21,16 @@ async function executeCommand() {
20
 
21
  // 保持浏览器打开一段时间以便查看结果
22
  if (!config.browserOptions.headless) {
23
- console.log('浏览器将保持打开5秒以便查看结果...');
24
  await page.waitForTimeout(5000);
25
  }
26
 
27
- } catch (error) {
28
- console.error('执行命令过程中发生错误:', error);
29
  } finally {
30
  if (browser) {
31
  await browser.close();
32
- console.log('浏览器已关闭');
33
  }
34
  }
35
  }
@@ -39,7 +40,7 @@ const __filename = fileURLToPath(import.meta.url);
39
  const scriptPath = path.resolve(process.argv[1]);
40
 
41
  if (path.resolve(__filename) === scriptPath) {
42
- executeCommand().catch(console.error);
43
  }
44
 
45
  export { executeCommand };
 
3
  import path from 'path';
4
 
5
  import { createBrowserSession, navigateToWebIDE, executeCommandFlow } from './utils/webide-utils.js';
6
+ import { info, error } from './utils/logger.js';
7
 
8
  async function executeCommand() {
9
 
 
21
 
22
  // 保持浏览器打开一段时间以便查看结果
23
  if (!config.browserOptions.headless) {
24
+ info('浏览器将保持打开5秒以便查看结果...');
25
  await page.waitForTimeout(5000);
26
  }
27
 
28
+ } catch (err) {
29
+ error('执行命令过程中发生错误:', err);
30
  } finally {
31
  if (browser) {
32
  await browser.close();
33
+ info('浏览器已关闭');
34
  }
35
  }
36
  }
 
40
  const scriptPath = path.resolve(process.argv[1]);
41
 
42
  if (path.resolve(__filename) === scriptPath) {
43
+ executeCommand().catch(error);
44
  }
45
 
46
  export { executeCommand };
src/login.js CHANGED
@@ -3,9 +3,10 @@ import fs from 'fs';
3
  import config from './config.js';
4
  import { fileURLToPath } from 'url';
5
  import path from 'path';
6
- import { loadCookies, saveScreenshot, getHumanReadableTimestamp } from './utils/common-utils.js';
 
7
  async function login() {
8
- console.log('启动浏览器...');
9
  const browser = await chromium.launch(config.browserOptions);
10
  const context = await browser.newContext();
11
 
@@ -15,55 +16,55 @@ async function login() {
15
  const page = await context.newPage();
16
 
17
  try {
18
- console.log(`导航到登录页面:${config.webideUrl}...`);
19
  // 首先访问主页面,通常会重定向到登录页面
20
  await page.goto(config.webideUrl);
21
 
22
  // 等待页面加载
23
  await page.waitForTimeout(config.waitTimes.pageLoad);
24
 
25
- console.log('当前页面URL:', page.url());
26
- console.log('页面标题:', await page.title());
27
 
28
  // 检查是否已经登录(如果页面包含编辑器元素,说明已登录)
29
  const isLoggedIn = await page.locator(config.selectors.editor).count() > 0;
30
 
31
  if (isLoggedIn) {
32
- console.log('检测到已经登录状态,保存cookie...');
33
  } else {
34
- console.log('需要登录,请在浏览器中手动完成登录过程...');
35
- console.log('登录完成后,请按 Enter 键继续...');
36
 
37
  // 等待用户手动登录
38
  await waitForUserInput();
39
 
40
  // 等待登录完成,检查是否出现编辑器界面
41
- console.log('等待登录完成...');
42
  try {
43
  await page.goto(config.webideUrl);
44
  await page.waitForSelector(config.selectors.editor, {
45
  timeout: 60000
46
  });
47
 
48
- } catch (error) {
49
- console.log('未检测到编辑器界面,但继续保存cookie...');
50
  }
51
  }
52
 
53
  // 保存cookies
54
  const cookies = await context.cookies();
55
  fs.writeFileSync(config.cookieFile, JSON.stringify(cookies, null, 2));
56
- console.log(`Cookies已保存到 ${config.cookieFile}`);
57
- console.log(`保存了 ${cookies.length} 个cookies`);
58
 
59
  // 显示保存的cookie信息(仅显示名称,不显示值)
60
- console.log('保存的cookie名称:');
61
  cookies.forEach(cookie => {
62
- console.log(` - ${cookie.name} (域名: ${cookie.domain})`);
63
  });
64
 
65
- } catch (error) {
66
- console.error('登录过程中发生错误:', error);
67
  } finally {
68
  await browser.close();
69
  }
@@ -90,6 +91,6 @@ const __filename = fileURLToPath(import.meta.url);
90
  const scriptPath = path.resolve(process.argv[1]);
91
 
92
  if (path.resolve(__filename) === scriptPath) {
93
- login().catch(console.error);
94
  }
95
 
 
3
  import config from './config.js';
4
  import { fileURLToPath } from 'url';
5
  import path from 'path';
6
+ import { loadCookies } from './utils/common-utils.js';
7
+ import { info, error } from './utils/logger.js';
8
  async function login() {
9
+ info('启动浏览器...');
10
  const browser = await chromium.launch(config.browserOptions);
11
  const context = await browser.newContext();
12
 
 
16
  const page = await context.newPage();
17
 
18
  try {
19
+ info(`导航到登录页面:${config.webideUrl}...`);
20
  // 首先访问主页面,通常会重定向到登录页面
21
  await page.goto(config.webideUrl);
22
 
23
  // 等待页面加载
24
  await page.waitForTimeout(config.waitTimes.pageLoad);
25
 
26
+ info('当前页面URL:', page.url());
27
+ info('页面标题:', await page.title());
28
 
29
  // 检查是否已经登录(如果页面包含编辑器元素,说明已登录)
30
  const isLoggedIn = await page.locator(config.selectors.editor).count() > 0;
31
 
32
  if (isLoggedIn) {
33
+ info('检测到已经登录状态,保存cookie...');
34
  } else {
35
+ info('需要登录,请在浏览器中手动完成登录过程...');
36
+ info('登录完成后,请按 Enter 键继续...');
37
 
38
  // 等待用户手动登录
39
  await waitForUserInput();
40
 
41
  // 等待登录完成,检查是否出现编辑器界面
42
+ info('等待登录完成...');
43
  try {
44
  await page.goto(config.webideUrl);
45
  await page.waitForSelector(config.selectors.editor, {
46
  timeout: 60000
47
  });
48
 
49
+ } catch (err) {
50
+ info('未检测到编辑器界面,但继续保存cookie...');
51
  }
52
  }
53
 
54
  // 保存cookies
55
  const cookies = await context.cookies();
56
  fs.writeFileSync(config.cookieFile, JSON.stringify(cookies, null, 2));
57
+ info(`Cookies已保存到 ${config.cookieFile}`);
58
+ info(`保存了 ${cookies.length} 个cookies`);
59
 
60
  // 显示保存的cookie信息(仅显示名称,不显示值)
61
+ info('保存的cookie名称:');
62
  cookies.forEach(cookie => {
63
+ info(` - ${cookie.name} (域名: ${cookie.domain})`);
64
  });
65
 
66
+ } catch (err) {
67
+ error('登录过程中发生错误:', err);
68
  } finally {
69
  await browser.close();
70
  }
 
91
  const scriptPath = path.resolve(process.argv[1]);
92
 
93
  if (path.resolve(__filename) === scriptPath) {
94
+ login().catch(error);
95
  }
96
 
src/scheduler.js CHANGED
@@ -1,21 +1,22 @@
1
  import config from './config.js';
2
  import { fileURLToPath } from 'url';
3
  import path from 'path';
4
- import { getHumanReadableTimestamp, log, logError } from './utils/common-utils.js';
5
  import { createBrowserSession, navigateToWebIDE, executeCommandFlow } from './utils/webide-utils.js';
 
6
 
7
  // 执行单次命令的函数
8
  async function executeCommandOnce(page) {
9
- log(`[${getHumanReadableTimestamp()}] 开始执行命令...`);
10
  return executeCommandFlow(page, 'scheduler');
11
  }
12
 
13
  // 主调度器函数
14
  async function startScheduler() {
15
 
16
- log(`[${getHumanReadableTimestamp()}] 启动调度器...`);
17
  const intervalSeconds = Math.round(config.schedulerInterval / 1000);
18
- log(`调度器将每${intervalSeconds}秒执行一次命令以防止编辑器休眠`);
19
 
20
  let browser;
21
  try {
@@ -33,43 +34,43 @@ async function startScheduler() {
33
  const intervalId = setInterval(async () => {
34
  try {
35
  // 重新导航到页面以确保页面活跃
36
- log(`[${getHumanReadableTimestamp()}] 重新导航到WebIDE页面...`);
37
  await navigateToWebIDE(page);
38
 
39
  // 执行命令
40
  await executeCommandOnce(page);
41
- } catch (error) {
42
- logError(`[${getHumanReadableTimestamp()}] 定时任务执行失败:`, error);
43
  }
44
  }, config.schedulerInterval);
45
 
46
- log(`[${getHumanReadableTimestamp()}] 调度器已启动,将每${intervalSeconds}秒执行一次命令`);
47
- log('按 Ctrl+C 停止调度器');
48
 
49
  // 监听进程退出信号
50
  process.on('SIGINT', async () => {
51
- log(`\n[${getHumanReadableTimestamp()}] 收到停止信号,正在关闭调度器...`);
52
  clearInterval(intervalId);
53
  if (browser) {
54
  await browser.close();
55
  }
56
- log('调度器已停止,浏览器已关闭');
57
  process.exit(0);
58
  });
59
 
60
  // 保持进程运行
61
  process.on('SIGTERM', async () => {
62
- log(`\n[${getHumanReadableTimestamp()}] 收到终止信号,正在关闭调度器...`);
63
  clearInterval(intervalId);
64
  if (browser) {
65
  await browser.close();
66
  }
67
- log('调度器已停止,浏览器已关闭');
68
  process.exit(0);
69
  });
70
 
71
- } catch (error) {
72
- logError(`[${getHumanReadableTimestamp()}] 调度器启动失败:`, error);
73
  if (browser) {
74
  await browser.close();
75
  }
@@ -81,7 +82,7 @@ const __filename = fileURLToPath(import.meta.url);
81
  const scriptPath = path.resolve(process.argv[1]);
82
 
83
  if (path.resolve(__filename) === scriptPath) {
84
- startScheduler().catch(console.error);
85
  }
86
 
87
  export { startScheduler };
 
1
  import config from './config.js';
2
  import { fileURLToPath } from 'url';
3
  import path from 'path';
4
+ import { getHumanReadableTimestamp } from './utils/common-utils.js';
5
  import { createBrowserSession, navigateToWebIDE, executeCommandFlow } from './utils/webide-utils.js';
6
+ import { info, error } from './utils/logger.js';
7
 
8
  // 执行单次命令的函数
9
  async function executeCommandOnce(page) {
10
+ info(`[${getHumanReadableTimestamp()}] 开始执行命令...`);
11
  return executeCommandFlow(page, 'scheduler');
12
  }
13
 
14
  // 主调度器函数
15
  async function startScheduler() {
16
 
17
+ info(`[${getHumanReadableTimestamp()}] 启动调度器...`);
18
  const intervalSeconds = Math.round(config.schedulerInterval / 1000);
19
+ info(`调度器将每${intervalSeconds}秒执行一次命令以防止编辑器休眠`);
20
 
21
  let browser;
22
  try {
 
34
  const intervalId = setInterval(async () => {
35
  try {
36
  // 重新导航到页面以确保页面活跃
37
+ info(`[${getHumanReadableTimestamp()}] 重新导航到WebIDE页面...`);
38
  await navigateToWebIDE(page);
39
 
40
  // 执行命令
41
  await executeCommandOnce(page);
42
+ } catch (err) {
43
+ error(`[${getHumanReadableTimestamp()}] 定时任务执行失败:`, err);
44
  }
45
  }, config.schedulerInterval);
46
 
47
+ info(`[${getHumanReadableTimestamp()}] 调度器已启动,将每${intervalSeconds}秒执行一次命令`);
48
+ info('按 Ctrl+C 停止调度器');
49
 
50
  // 监听进程退出信号
51
  process.on('SIGINT', async () => {
52
+ info(`\n[${getHumanReadableTimestamp()}] 收到停止信号,正在关闭调度器...`);
53
  clearInterval(intervalId);
54
  if (browser) {
55
  await browser.close();
56
  }
57
+ info('调度器已停止,浏览器已关闭');
58
  process.exit(0);
59
  });
60
 
61
  // 保持进程运行
62
  process.on('SIGTERM', async () => {
63
+ info(`\n[${getHumanReadableTimestamp()}] 收到终止信号,正在关闭调度器...`);
64
  clearInterval(intervalId);
65
  if (browser) {
66
  await browser.close();
67
  }
68
+ info('调度器已停止,浏览器已关闭');
69
  process.exit(0);
70
  });
71
 
72
+ } catch (err) {
73
+ error(`[${getHumanReadableTimestamp()}] 调度器启动失败:`, err);
74
  if (browser) {
75
  await browser.close();
76
  }
 
82
  const scriptPath = path.resolve(process.argv[1]);
83
 
84
  if (path.resolve(__filename) === scriptPath) {
85
+ startScheduler().catch(error);
86
  }
87
 
88
  export { startScheduler };
src/start-services.js CHANGED
@@ -6,7 +6,7 @@
6
  */
7
 
8
  import { spawn } from 'child_process';
9
- import { log, logError } from './utils/common-utils.js';
10
 
11
  // 存储子进程
12
  const processes = [];
 
6
  */
7
 
8
  import { spawn } from 'child_process';
9
+ import { info as log, error as logError } from './utils/logger.js';
10
 
11
  // 存储子进程
12
  const processes = [];
src/utils/common-utils.js CHANGED
@@ -1,99 +1,6 @@
1
  import fs from 'fs';
2
  import path from 'path';
3
-
4
- // 日志文件路径
5
- const LOG_FILE = './logs/app.log';
6
- const LOG_DIR = './logs';
7
-
8
- /**
9
- * 确保日志目录存在
10
- */
11
- function ensureLogDirectory() {
12
- if (!fs.existsSync(LOG_DIR)) {
13
- fs.mkdirSync(LOG_DIR, { recursive: true });
14
- }
15
- }
16
-
17
- /**
18
- * 写入日志到文件
19
- * @param {string} level - 日志级别 (INFO, ERROR, WARN)
20
- * @param {string} message - 日志消息
21
- */
22
- export function writeLog(level, message) {
23
- ensureLogDirectory();
24
- const timestamp = new Date().toISOString();
25
- const logEntry = `[${timestamp}] [${level}] ${message}\n`;
26
-
27
- try {
28
- fs.appendFileSync(LOG_FILE, logEntry);
29
- } catch (error) {
30
- console.error('写入日志文件失败:', error);
31
- }
32
- }
33
-
34
- /**
35
- * 增强的 console.log,同时输出到控制台和文件
36
- * @param {...any} args - 要记录的参数
37
- */
38
- export function log(...args) {
39
- const message = args.map(arg =>
40
- typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
41
- ).join(' ');
42
-
43
- console.log(...args);
44
- writeLog('INFO', message);
45
- }
46
-
47
- /**
48
- * 增强的 console.error,同时输出到控制台和文件
49
- * @param {...any} args - 要记录的参数
50
- */
51
- export function logError(...args) {
52
- const message = args.map(arg =>
53
- typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
54
- ).join(' ');
55
-
56
- console.error(...args);
57
- writeLog('ERROR', message);
58
- }
59
-
60
- /**
61
- * 读取最近的日志
62
- * @param {number} lines - 要读取的行数,默认100行
63
- * @returns {Array} 日志行数组
64
- */
65
- export function getRecentLogs(lines = 100) {
66
- try {
67
- if (!fs.existsSync(LOG_FILE)) {
68
- return [];
69
- }
70
-
71
- const content = fs.readFileSync(LOG_FILE, 'utf8');
72
- const logLines = content.trim().split('\n').filter(line => line.length > 0);
73
-
74
- // 返回最后 N 行
75
- return logLines.slice(-lines);
76
- } catch (error) {
77
- console.error('读取日志文件失败:', error);
78
- return [];
79
- }
80
- }
81
-
82
- /**
83
- * 清空日志文件
84
- * @returns {boolean} 是否成功清空
85
- */
86
- export function clearLogFile() {
87
- try {
88
- ensureLogDirectory();
89
- fs.writeFileSync(LOG_FILE, '');
90
- console.log('日志文件已清空');
91
- return true;
92
- } catch (error) {
93
- console.error('清空日志文件失败:', error);
94
- return false;
95
- }
96
- }
97
 
98
  /**
99
  * 创建人类可读的时间戳
@@ -118,7 +25,7 @@ export function getHumanReadableTimestamp() {
118
  export function ensureScreenshotDirectory(dir) {
119
  if (!fs.existsSync(dir)) {
120
  fs.mkdirSync(dir, { recursive: true });
121
- console.log(`创建截图目录: ${dir}`);
122
  }
123
  }
124
 
@@ -131,20 +38,20 @@ export function ensureScreenshotDirectory(dir) {
131
  export function checkCookieAvailability(cookieFile, cookiesFromEnv) {
132
  // 优先检查环境变量
133
  if (cookiesFromEnv) {
134
- console.log('发现环境变量COOKIES,将使用环境变量中的cookies');
135
  try {
136
  JSON.parse(cookiesFromEnv);
137
  return true;
138
- } catch (error) {
139
- console.error('环境变量COOKIES格式无效:', error.message);
140
- console.log('将尝试使用cookie文件...');
141
  }
142
  }
143
 
144
  // 检查cookie文件
145
  if (!fs.existsSync(cookieFile)) {
146
- console.error(`Cookie文件不存在: ${cookieFile}`);
147
- console.log('请先运行 npm run login 进行登录,或设置环境变量COOKIES');
148
  return false;
149
  }
150
  return true;
@@ -157,8 +64,8 @@ export function checkCookieAvailability(cookieFile, cookiesFromEnv) {
157
  */
158
  export function checkCookieFile(cookieFile) {
159
  if (!fs.existsSync(cookieFile)) {
160
- console.error(`Cookie文件不存在: ${cookieFile}`);
161
- console.log('请先运行 npm run login 进行登录');
162
  return false;
163
  }
164
  return true;
@@ -174,23 +81,23 @@ export function loadCookies(cookieFile, cookiesFromEnv) {
174
  try {
175
  // 优先使用环境变量
176
  if (cookiesFromEnv) {
177
- console.log('从环境变量COOKIES加载cookies...');
178
  const cookies = JSON.parse(cookiesFromEnv);
179
- console.log(`已从环境变量加载 ${cookies.length} 个cookies`);
180
  return cookies;
181
  }
182
 
183
  // 使用文件
184
  if (fs.existsSync(cookieFile)) {
185
- console.log(`从文件加载cookies: ${cookieFile}`);
186
  const cookies = JSON.parse(fs.readFileSync(cookieFile, 'utf8'));
187
- console.log(`已从文件加载 ${cookies.length} 个cookies`);
188
  return cookies;
189
  }
190
  return [];
191
- } catch (error) {
192
- console.error('读取 Cookie 失败:', error);
193
- throw error;
194
  }
195
  }
196
 
@@ -204,8 +111,8 @@ export function loadCookies(cookieFile, cookiesFromEnv) {
204
  export async function saveScreenshot(page, screenshotDir, prefix = 'screenshot') {
205
  ensureScreenshotDirectory(screenshotDir);
206
  const timestamp = getHumanReadableTimestamp();
207
- const screenshotPath = path.join(screenshotDir, `${prefix}-${timestamp}.png`);
208
  await page.screenshot({ path: screenshotPath });
209
- console.log(`截图已保存: ${screenshotPath}`);
210
  return screenshotPath;
211
  }
 
1
  import fs from 'fs';
2
  import path from 'path';
3
+ import { info, error } from './logger.js';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  /**
6
  * 创建人类可读的时间戳
 
25
  export function ensureScreenshotDirectory(dir) {
26
  if (!fs.existsSync(dir)) {
27
  fs.mkdirSync(dir, { recursive: true });
28
+ info(`创建截图目录: ${dir}`);
29
  }
30
  }
31
 
 
38
  export function checkCookieAvailability(cookieFile, cookiesFromEnv) {
39
  // 优先检查环境变量
40
  if (cookiesFromEnv) {
41
+ info('发现环境变量COOKIES,将使用环境变量中的cookies');
42
  try {
43
  JSON.parse(cookiesFromEnv);
44
  return true;
45
+ } catch (err) {
46
+ error('环境变量COOKIES格式无效:', err.message);
47
+ info('将尝试使用cookie文件...');
48
  }
49
  }
50
 
51
  // 检查cookie文件
52
  if (!fs.existsSync(cookieFile)) {
53
+ error(`Cookie文件不存在: ${cookieFile}`);
54
+ info('请先运行 npm run login 进行登录,或设置环境变量COOKIES');
55
  return false;
56
  }
57
  return true;
 
64
  */
65
  export function checkCookieFile(cookieFile) {
66
  if (!fs.existsSync(cookieFile)) {
67
+ error(`Cookie文件不存在: ${cookieFile}`);
68
+ info('请先运行 npm run login 进行登录');
69
  return false;
70
  }
71
  return true;
 
81
  try {
82
  // 优先使用环境变量
83
  if (cookiesFromEnv) {
84
+ info('从环境变量COOKIES加载cookies...');
85
  const cookies = JSON.parse(cookiesFromEnv);
86
+ info(`已从环境变量加载 ${cookies.length} 个cookies`);
87
  return cookies;
88
  }
89
 
90
  // 使用文件
91
  if (fs.existsSync(cookieFile)) {
92
+ info(`从文件加载cookies: ${cookieFile}`);
93
  const cookies = JSON.parse(fs.readFileSync(cookieFile, 'utf8'));
94
+ info(`已从文件加载 ${cookies.length} 个cookies`);
95
  return cookies;
96
  }
97
  return [];
98
+ } catch (err) {
99
+ error('读取 Cookie 失败:', err);
100
+ throw err;
101
  }
102
  }
103
 
 
111
  export async function saveScreenshot(page, screenshotDir, prefix = 'screenshot') {
112
  ensureScreenshotDirectory(screenshotDir);
113
  const timestamp = getHumanReadableTimestamp();
114
+ const screenshotPath = path.join(screenshotDir, `${prefix}.png`);
115
  await page.screenshot({ path: screenshotPath });
116
+ info(`截图已保存: ${screenshotPath}`);
117
  return screenshotPath;
118
  }
src/utils/logger.js ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'fs';
2
+
3
+ // 日志级别枚举
4
+ export const LogLevel = {
5
+ DEBUG: 'DEBUG',
6
+ INFO: 'INFO',
7
+ WARN: 'WARN',
8
+ ERROR: 'ERROR'
9
+ };
10
+
11
+ // 日志配置
12
+ const LOG_CONFIG = {
13
+ logFile: './logs/app.log',
14
+ logDir: './logs',
15
+ enableConsole: true,
16
+ enableFile: true,
17
+ logLevel: LogLevel.INFO
18
+ };
19
+
20
+ /**
21
+ * 确保日志目录存在
22
+ */
23
+ function ensureLogDirectory() {
24
+ if (!fs.existsSync(LOG_CONFIG.logDir)) {
25
+ fs.mkdirSync(LOG_CONFIG.logDir, { recursive: true });
26
+ }
27
+ }
28
+
29
+ /**
30
+ * 格式化日志消息
31
+ * @param {...any} args - 要记录的参数
32
+ * @returns {string} 格式化后的消息
33
+ */
34
+ function formatMessage(...args) {
35
+ return args.map(arg =>
36
+ typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
37
+ ).join(' ');
38
+ }
39
+
40
+ /**
41
+ * 写入日志到文件
42
+ * @param {string} level - 日志级别
43
+ * @param {string} message - 日志消息
44
+ */
45
+ function writeToFile(level, message) {
46
+ if (!LOG_CONFIG.enableFile) return;
47
+
48
+ ensureLogDirectory();
49
+ const timestamp = new Date().toISOString();
50
+ const logEntry = `[${timestamp}] [${level}] ${message}\n`;
51
+
52
+ try {
53
+ fs.appendFileSync(LOG_CONFIG.logFile, logEntry);
54
+ } catch (error) {
55
+ // 避免循环调用,直接使用console
56
+ console.error('写入日志文件失败:', error);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * 输出到控制台
62
+ * @param {string} level - 日志级别
63
+ * @param {...any} args - 要记录的参数
64
+ */
65
+ function writeToConsole(level, ...args) {
66
+ if (!LOG_CONFIG.enableConsole) return;
67
+
68
+ switch (level) {
69
+ case LogLevel.DEBUG:
70
+ console.debug(...args);
71
+ break;
72
+ case LogLevel.INFO:
73
+ console.log(...args);
74
+ break;
75
+ case LogLevel.WARN:
76
+ console.warn(...args);
77
+ break;
78
+ case LogLevel.ERROR:
79
+ console.error(...args);
80
+ break;
81
+ default:
82
+ console.log(...args);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * 通用日志记录函数
88
+ * @param {string} level - 日志级别
89
+ * @param {...any} args - 要记录的参数
90
+ */
91
+ function writeLog(level, ...args) {
92
+ const message = formatMessage(...args);
93
+
94
+ // 输出到控制台
95
+ writeToConsole(level, ...args);
96
+
97
+ // 写入文件
98
+ writeToFile(level, message);
99
+ }
100
+
101
+ /**
102
+ * DEBUG级别日志
103
+ * @param {...any} args - 要记录的参数
104
+ */
105
+ export function debug(...args) {
106
+ writeLog(LogLevel.DEBUG, ...args);
107
+ }
108
+
109
+ /**
110
+ * INFO级别日志(替代console.log)
111
+ * @param {...any} args - 要记录的参数
112
+ */
113
+ export function info(...args) {
114
+ writeLog(LogLevel.INFO, ...args);
115
+ }
116
+
117
+ /**
118
+ * WARN级别日志(替代console.warn)
119
+ * @param {...any} args - 要记录的参数
120
+ */
121
+ export function warn(...args) {
122
+ writeLog(LogLevel.WARN, ...args);
123
+ }
124
+
125
+ /**
126
+ * ERROR级别日志(替代console.error)
127
+ * @param {...any} args - 要记录的参数
128
+ */
129
+ export function error(...args) {
130
+ writeLog(LogLevel.ERROR, ...args);
131
+ }
132
+
133
+ /**
134
+ * 读取最近的日志
135
+ * @param {number} lines - 要读取的行数,默认100行
136
+ * @returns {Array} 日志行数组
137
+ */
138
+ export function getRecentLogs(lines = 100) {
139
+ try {
140
+ if (!fs.existsSync(LOG_CONFIG.logFile)) {
141
+ return [];
142
+ }
143
+
144
+ const content = fs.readFileSync(LOG_CONFIG.logFile, 'utf8');
145
+ const logLines = content.trim().split('\n').filter(line => line.length > 0);
146
+
147
+ // 返回最后 N 行
148
+ return logLines.slice(-lines);
149
+ } catch (err) {
150
+ console.error('读取日志文件失败:', err);
151
+ return [];
152
+ }
153
+ }
154
+
155
+ /**
156
+ * 清空日志文件
157
+ * @returns {boolean} 是否成功清空
158
+ */
159
+ export function clearLogFile() {
160
+ try {
161
+ ensureLogDirectory();
162
+ fs.writeFileSync(LOG_CONFIG.logFile, '');
163
+ info('日志文件已清空');
164
+ return true;
165
+ } catch (err) {
166
+ error('清空日志文件失败:', err);
167
+ return false;
168
+ }
169
+ }
170
+
171
+ /**
172
+ * 配置日志设置
173
+ * @param {Object} config - 日志配置
174
+ */
175
+ export function configureLogger(config = {}) {
176
+ Object.assign(LOG_CONFIG, config);
177
+ }
178
+
179
+ // 为了向后兼容,导出原有的函数名
180
+ export const log = info;
181
+ export const logError = error;
182
+
183
+ // 默认导出logger对象
184
+ export default {
185
+ debug,
186
+ info,
187
+ warn,
188
+ error,
189
+ log: info,
190
+ logError: error,
191
+ getRecentLogs,
192
+ clearLogFile,
193
+ configureLogger,
194
+ LogLevel
195
+ };
src/utils/webide-utils.js CHANGED
@@ -1,6 +1,7 @@
1
  import { chromium } from 'playwright';
2
  import config from '../config.js';
3
  import { loadCookies, saveScreenshot, getHumanReadableTimestamp } from './common-utils.js';
 
4
 
5
  /**
6
  * 创建浏览器实例和上下文
@@ -9,7 +10,7 @@ import { loadCookies, saveScreenshot, getHumanReadableTimestamp } from './common
9
  * @returns {Object} { browser, context, page }
10
  */
11
  export async function createBrowserSession(cookieFile, cookiesFromEnv) {
12
- console.log('启动浏览器...');
13
  const browser = await chromium.launch(config.browserOptions);
14
  const context = await browser.newContext();
15
 
@@ -27,24 +28,24 @@ export async function createBrowserSession(cookieFile, cookiesFromEnv) {
27
  * @param {Object} page - Playwright 页面对象
28
  */
29
  export async function navigateToWebIDE(page) {
30
- console.log('导航到WebIDE页面...');
31
  await page.goto(config.webideUrl);
32
 
33
  // 等待页面加载
34
  await page.waitForTimeout(config.waitTimes.pageLoad);
35
 
36
- console.log('当前页面URL:', page.url());
37
- console.log('页面标题:', await page.title());
38
 
39
  // 检查是否成功登录
40
  try {
41
  await page.waitForSelector(config.selectors.editor, {
42
  timeout: 60000
43
  });
44
- console.log('成功进入WebIDE界面');
45
  return true;
46
- } catch (error) {
47
- console.log('警告: 未检测到编辑器界面,可能需要重新登录');
48
  return false;
49
  }
50
  }
@@ -57,14 +58,14 @@ export async function handleModalDialog(page) {
57
  try {
58
  const dialogButton = await page.waitForSelector(config.selectors.dialogButton, { timeout: 30000 });
59
  if (dialogButton && await dialogButton.isVisible()) {
60
- console.log('发现模态对话框按钮,点击处理...');
61
  await dialogButton.click();
62
  await page.waitForTimeout(500);
63
  return true;
64
  }
65
- } catch (error) {
66
  // 没有找到对话框按钮,继续执行
67
- console.log('未发现模态对话框,继续执行...');
68
  }
69
  return false;
70
  }
@@ -75,7 +76,7 @@ export async function handleModalDialog(page) {
75
  * @returns {Object|null} 终端元素或 null
76
  */
77
  export async function openTerminal(page) {
78
- console.log('尝试打开终端 (Ctrl+~)...');
79
 
80
  // 确保页面获得焦点
81
  await page.click('body');
@@ -97,17 +98,17 @@ export async function openTerminal(page) {
97
  try {
98
  terminalElement = await page.waitForSelector(selector, { timeout: 2000 });
99
  if (terminalElement) {
100
- console.log(`找到终端元素: ${selector}`);
101
  terminalFound = true;
102
  break;
103
  }
104
- } catch (error) {
105
  // 继续尝试下一个选择器
106
  }
107
  }
108
 
109
  if (!terminalFound) {
110
- console.log('未找到终端元素,尝试直接输入命令...');
111
  return null;
112
  } else {
113
  // 点击终端区域确保焦点
@@ -123,7 +124,7 @@ export async function openTerminal(page) {
123
  * @param {string} command - 要执行的命令
124
  */
125
  export async function executeTerminalCommand(page, command) {
126
- console.log(`执行命令: ${command}`);
127
 
128
  // 输入命令
129
  await page.keyboard.type(command);
@@ -135,7 +136,7 @@ export async function executeTerminalCommand(page, command) {
135
  // 等待命令执行
136
  await page.waitForTimeout(config.waitTimes.commandExecution);
137
 
138
- console.log('命令已执行');
139
  }
140
 
141
  /**
@@ -156,12 +157,12 @@ export async function executeCommandFlow(page, screenshotPrefix = 'screenshot')
156
  await executeTerminalCommand(page, config.command);
157
 
158
  // 截图保存执行结果
159
- // const screenshotDir = config.screenshotDir || './screenshots';
160
- // const screenshotPath = await saveScreenshot(page, screenshotDir, screenshotPrefix);
161
 
162
  return true;
163
- } catch (error) {
164
- console.error(`[${getHumanReadableTimestamp()}] 执行命令时发生错误:`, error);
165
  return false;
166
  }
167
  }
 
1
  import { chromium } from 'playwright';
2
  import config from '../config.js';
3
  import { loadCookies, saveScreenshot, getHumanReadableTimestamp } from './common-utils.js';
4
+ import { info, error } from './logger.js';
5
 
6
  /**
7
  * 创建浏览器实例和上下文
 
10
  * @returns {Object} { browser, context, page }
11
  */
12
  export async function createBrowserSession(cookieFile, cookiesFromEnv) {
13
+ info('启动浏览器...');
14
  const browser = await chromium.launch(config.browserOptions);
15
  const context = await browser.newContext();
16
 
 
28
  * @param {Object} page - Playwright 页面对象
29
  */
30
  export async function navigateToWebIDE(page) {
31
+ info('导航到WebIDE页面...');
32
  await page.goto(config.webideUrl);
33
 
34
  // 等待页面加载
35
  await page.waitForTimeout(config.waitTimes.pageLoad);
36
 
37
+ info('当前页面URL:', page.url());
38
+ info('页面标题:', await page.title());
39
 
40
  // 检查是否成功登录
41
  try {
42
  await page.waitForSelector(config.selectors.editor, {
43
  timeout: 60000
44
  });
45
+ info('成功进入WebIDE界面');
46
  return true;
47
+ } catch (err) {
48
+ info('警告: 未检测到编辑器界面,可能需要重新登录');
49
  return false;
50
  }
51
  }
 
58
  try {
59
  const dialogButton = await page.waitForSelector(config.selectors.dialogButton, { timeout: 30000 });
60
  if (dialogButton && await dialogButton.isVisible()) {
61
+ info('发现模态对话框按钮,点击处理...');
62
  await dialogButton.click();
63
  await page.waitForTimeout(500);
64
  return true;
65
  }
66
+ } catch (err) {
67
  // 没有找到对话框按钮,继续执行
68
+ info('未发现模态对话框,继续执行...');
69
  }
70
  return false;
71
  }
 
76
  * @returns {Object|null} 终端元素或 null
77
  */
78
  export async function openTerminal(page) {
79
+ info('尝试打开终端 (Ctrl+~)...');
80
 
81
  // 确保页面获得焦点
82
  await page.click('body');
 
98
  try {
99
  terminalElement = await page.waitForSelector(selector, { timeout: 2000 });
100
  if (terminalElement) {
101
+ info(`找到终端元素: ${selector}`);
102
  terminalFound = true;
103
  break;
104
  }
105
+ } catch (err) {
106
  // 继续尝试下一个选择器
107
  }
108
  }
109
 
110
  if (!terminalFound) {
111
+ info('未找到终端元素,尝试直接输入命令...');
112
  return null;
113
  } else {
114
  // 点击终端区域确保焦点
 
124
  * @param {string} command - 要执行的命令
125
  */
126
  export async function executeTerminalCommand(page, command) {
127
+ info(`执行命令: ${command}`);
128
 
129
  // 输入命令
130
  await page.keyboard.type(command);
 
136
  // 等待命令执行
137
  await page.waitForTimeout(config.waitTimes.commandExecution);
138
 
139
+ info('命令已执行');
140
  }
141
 
142
  /**
 
157
  await executeTerminalCommand(page, config.command);
158
 
159
  // 截图保存执行结果
160
+ const screenshotDir = config.screenshotDir || './screenshots';
161
+ const screenshotPath = await saveScreenshot(page, screenshotDir, screenshotPrefix);
162
 
163
  return true;
164
+ } catch (err) {
165
+ error(`[${getHumanReadableTimestamp()}] 执行命令时发生错误:`, err);
166
  return false;
167
  }
168
  }
src/web-server.js CHANGED
@@ -1,9 +1,15 @@
1
  import { Hono } from 'hono';
2
  import { serve } from '@hono/node-server';
3
- import { getRecentLogs, log, logError, clearLogFile } from './utils/common-utils.js';
 
 
4
  import config from './config.js';
5
 
6
  const app = new Hono();
 
 
 
 
7
  // 静态 HTML 页面
8
  const indexHTML = `
9
  <!DOCTYPE html>
@@ -129,6 +135,23 @@ const indexHTML = `
129
  color: #6c757d;
130
  font-style: italic;
131
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  </style>
133
  </head>
134
  <body>
@@ -160,6 +183,11 @@ const indexHTML = `
160
  <div class="status" id="status">
161
  准备就绪
162
  </div>
 
 
 
 
 
163
  </div>
164
 
165
  <script>
 
1
  import { Hono } from 'hono';
2
  import { serve } from '@hono/node-server';
3
+ import { serveStatic } from '@hono/node-server/serve-static';
4
+ import { getRecentLogs, clearLogFile } from './utils/logger.js';
5
+ import { info as log, error as logError } from './utils/logger.js';
6
  import config from './config.js';
7
 
8
  const app = new Hono();
9
+
10
+ // 静态文件服务 - 提供 screenshots 目录
11
+ app.use('/screenshots/*', serveStatic({ root: './' }));
12
+
13
  // 静态 HTML 页面
14
  const indexHTML = `
15
  <!DOCTYPE html>
 
135
  color: #6c757d;
136
  font-style: italic;
137
  }
138
+ .screenshot-section {
139
+ padding: 20px;
140
+ border-bottom: 1px solid #eee;
141
+ text-align: center;
142
+ }
143
+ .screenshot-section h2 {
144
+ margin: 0 0 15px 0;
145
+ color: #333;
146
+ font-size: 1.2em;
147
+ }
148
+ .screenshot-section img {
149
+ max-width: 100%;
150
+ height: auto;
151
+ border: 1px solid #ddd;
152
+ border-radius: 4px;
153
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
154
+ }
155
  </style>
156
  </head>
157
  <body>
 
183
  <div class="status" id="status">
184
  准备就绪
185
  </div>
186
+ <div class="screenshot-section">
187
+ <h2>📸 最新截图</h2>
188
+ <img src="/screenshots/scheduler.png" alt="Scheduler Screenshot" onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
189
+ <div style="display: none; color: #6c757d; font-style: italic;">截图文件不存在或无法加载</div>
190
+ </div>
191
  </div>
192
 
193
  <script>