daodou-command 1.0.0

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.
package/.daodourc ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ build: {
3
+ // Jenkins 基础配置
4
+ jenkinsUrl: 'your-jenkins-url',
5
+ jenkinsBase: 'your-jenkins-base',
6
+ jenkinsToken: 'your-jenkins-token',
7
+ jenkinsUsername: 'your-jenkins-username',
8
+ jenkinsPassword: 'your-jenkins-password',
9
+
10
+ // 构建任务配置
11
+ jobName: 'my-project-test', // 必须配置的任务名称
12
+
13
+ // 构建参数配置
14
+ buildParams: {
15
+ token: 'your-jenkins-token',
16
+ BUILD_ENV: 'test',
17
+ version: '0.0.1'
18
+ }
19
+ },
20
+ lang: {
21
+ defaultLang: 'en',
22
+ defaultDir: './public/locales',
23
+ fileName: 'common.json',
24
+ // 代理相关配置
25
+ proxyListUrl: 'https://free-proxy-list.net/',
26
+ proxyTestUrl: 'https://httpbin.org/ip'
27
+ },
28
+ }
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # 刀豆命令行工具 (Daodou CLI)
2
+
3
+ 一个功能强大的命令行工具,支持自动化构建和多语言管理。
4
+
5
+ ## 安装
6
+
7
+ ```bash
8
+ npm install -g daodou-command
9
+ ```
10
+
11
+ ## 功能特性
12
+
13
+ ### 🚀 构建功能
14
+ - 自动检测Git分支
15
+ - 一键触发Jenkins构建
16
+ - 实时监听构建进度
17
+
18
+ ### 🌐 多语言管理
19
+ - 支持多语言文件管理
20
+ - 自动翻译功能(Google Translate API)
21
+ - 多代理轮换绕过API限制
22
+
23
+ ## 快速开始
24
+
25
+ ### 构建项目
26
+ ```bash
27
+ # 在项目根目录下执行
28
+ cd your-project
29
+ dao build
30
+ ```
31
+
32
+ ### 多语言管理
33
+ ```bash
34
+ # 添加多语言项(自动翻译)
35
+ dao lang add "hello world"
36
+
37
+ # 添加多语言项(指定值)
38
+ dao lang add "welcome" "欢迎"
39
+
40
+ # 删除多语言项
41
+ dao lang remove "hello world"
42
+ ```
43
+
44
+ ## 配置
45
+
46
+ 首次运行会自动生成 `.daodourc` 配置文件:
47
+
48
+ ```json5
49
+ {
50
+ build: {
51
+ jenkinsUrl: 'your-jenkins-url',
52
+ jenkinsBase: 'your-jenkins-base',
53
+ jenkinsToken: 'your-jenkins-token',
54
+ jenkinsUsername: 'your-username',
55
+ jenkinsPassword: 'your-password',
56
+ jobName: 'your-job-name',
57
+
58
+ buildParams: {
59
+ token: 'your-jenkins-token',
60
+ BUILD_ENV: 'test',
61
+ version: '0.0.1'
62
+ }
63
+ },
64
+
65
+ lang: {
66
+ defaultLang: 'en',
67
+ defaultDir: './public/locales',
68
+ fileName: 'common.json'
69
+ }
70
+ }
71
+ ```
72
+
73
+ ## 命令用法
74
+
75
+ ### 构建命令
76
+ ```bash
77
+ dao build # 自动检测分支并构建
78
+ dao build --branch feature # 指定分支构建
79
+ dao build --help # 查看帮助
80
+ ```
81
+
82
+ ### 多语言命令
83
+ ```bash
84
+ dao lang add "key" "value" # 添加多语言项
85
+ dao lang remove "key" # 删除多语言项
86
+ dao lang add "key" --lang zh # 只处理特定语言
87
+ dao lang --help # 查看帮助
88
+ ```
89
+
90
+ ## 多语言文件结构
91
+
92
+ 工具会自动扫描 `/public/locales/[lang]/common.json` 文件:
93
+
94
+ ```
95
+ public/
96
+ └── locales/
97
+ ├── en/
98
+ │ └── common.json
99
+ ├── zh/
100
+ │ └── common.json
101
+ └── ja/
102
+ └── common.json
103
+ ```
104
+
105
+ ## 常见问题
106
+
107
+ - **Jenkins配置**: 请编辑 `.daodourc` 文件,填写真实的Jenkins配置
108
+ - **多语言目录**: 工具会提示目录不存在,不会自动创建
109
+ - **翻译失败**: 工具会自动重试和切换代理
110
+
111
+ ## 开发
112
+
113
+ ```bash
114
+ git clone <repository-url>
115
+ cd daodou-command
116
+ npm install
117
+ npm link
118
+ ```
119
+
120
+ ---
121
+
122
+ **刀豆团队** - 让开发更简单 🚀
package/bin/daodou.js ADDED
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Command } = require('commander');
4
+ const chalk = require('chalk');
5
+ const buildCommand = require('../lib/commands/build');
6
+ const langCommand = require('../lib/commands/lang');
7
+
8
+ const program = new Command();
9
+
10
+ // 设置程序信息
11
+ program
12
+ .name('dao')
13
+ .description('刀豆命令行工具 - 自动化构建和部署')
14
+ .version('1.0.0', '-v, --version');
15
+
16
+ // 添加 build 命令
17
+ program
18
+ .command('build')
19
+ .description('构建项目 - 自动检测Git分支并调用Jenkins打包')
20
+ .option('-j, --jenkins-url <url>', 'Jenkins服务器地址')
21
+ .option('-u, --username <username>', 'Jenkins用户名')
22
+ .option('-t, --token <token>', 'Jenkins API Token')
23
+ .option('-j, --job-name <name>', 'Jenkins任务名称')
24
+ .option('-b, --branch <branch>', '指定分支名称(默认从Git自动检测)')
25
+ .option('-p, --parameters <params>', '额外的构建参数 (JSON格式)')
26
+ .action(async (options) => {
27
+ try {
28
+ await buildCommand.execute(options);
29
+ } catch (error) {
30
+ console.error(chalk.red('构建失败:'), error.message);
31
+ process.exit(1);
32
+ }
33
+ });
34
+
35
+ // 添加 lang 命令
36
+ const langCmd = program
37
+ .command('lang')
38
+ .description('多语言文件管理工具');
39
+
40
+ langCmd
41
+ .command('add <key> [value]')
42
+ .description('添加多语言项')
43
+ .option('-l, --lang <language>', '指定语言(如 en、zh、ja)')
44
+ .option('-d, --dir <directory>', '指定目录(默认为 ./public/locales)')
45
+ .action(async (key, value, options) => {
46
+ try {
47
+ await langCommand.add(key, value, options);
48
+ } catch (error) {
49
+ console.error(chalk.red('添加失败:'), error.message);
50
+ process.exit(1);
51
+ }
52
+ });
53
+
54
+ langCmd
55
+ .command('remove <key>')
56
+ .description('删除多语言项')
57
+ .option('-l, --lang <language>', '指定语言(如 en、zh、ja)')
58
+ .option('-d, --dir <directory>', '指定目录(默认为 ./public/locales)')
59
+ .action(async (key, options) => {
60
+ try {
61
+ await langCommand.remove(key, options);
62
+ } catch (error) {
63
+ console.error(chalk.red('删除失败:'), error.message);
64
+ process.exit(1);
65
+ }
66
+ });
67
+
68
+ // 添加帮助信息
69
+ program.addHelpText('after', `
70
+ 示例:
71
+ $ dao build # 自动检测Git分支并构建
72
+ $ dao build --branch feature/new-feature # 指定分支构建
73
+ $ dao build --jenkins-url http://jenkins.example.com
74
+ $ dao build --parameters '{"param1":"value1"}'
75
+
76
+ $ dao lang add "hello"
77
+ $ dao lang add "hello" "Hello World"
78
+ $ dao lang add "world" --lang en
79
+ $ dao lang add "测试" --dir ./i18n
80
+ $ dao lang remove "hello"
81
+ $ dao lang remove "world" --lang en
82
+
83
+ 注意:
84
+ - build 命令会自动检测当前Git分支,无需手动指定
85
+ - 如需指定特定分支,请使用 --branch 参数
86
+ - 确保在Git仓库目录中运行 build 命令
87
+
88
+ 环境变量:
89
+ JENKINS_URL Jenkins服务器地址
90
+ JENKINS_USERNAME Jenkins用户名
91
+ JENKINS_TOKEN Jenkins API Token
92
+ JENKINS_JOB_NAME Jenkins任务名称
93
+ `);
94
+
95
+ // 解析命令行参数
96
+ program.parse();
@@ -0,0 +1,423 @@
1
+ const chalk = require('chalk');
2
+ const ora = require('ora');
3
+ const inquirer = require('inquirer');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+ const { getCurrentBranch } = require('../utils/git');
7
+ const { BrowserAuth } = require('../utils/browser');
8
+ const { ConfigManager } = require('../utils/config');
9
+ const axios = require('axios');
10
+ const os = require('os');
11
+
12
+ // 配置管理类,负责加载、校验、创建配置文件
13
+ class BuildConfigManager {
14
+ constructor() {
15
+ this.configManager = new ConfigManager();
16
+ this.localConfig = {};
17
+ }
18
+
19
+ checkAndCreateConfig() {
20
+ try {
21
+ // 检查是否有配置文件
22
+ if (!fs.existsSync(this.configManager.projectConfigFile) && !fs.existsSync(this.configManager.configFile)) {
23
+ this.configManager.createDefaultConfig();
24
+ console.log('📝 已为你创建配置文件');
25
+ console.log('💡 请编辑该文件,将 your-xxx 替换为你的实际配置信息');
26
+ console.log('📍 文件位置:' + this.configManager.projectConfigFile);
27
+ console.log('🔄 填写完成后请重新运行命令');
28
+ process.exit(0);
29
+ }
30
+
31
+ this.loadConfig();
32
+ this.checkTemplateValues();
33
+ } catch (e) {
34
+ console.error('❌ 读取配置文件失败,请检查文件格式或内容是否正确。');
35
+ process.exit(1);
36
+ }
37
+ }
38
+
39
+ loadConfig() {
40
+ this.localConfig = this.configManager.getBuildConfigWithDefault();
41
+ }
42
+
43
+ checkTemplateValues() {
44
+ const templateValues = [
45
+ 'your-jenkins-url',
46
+ 'your-jenkins-base',
47
+ 'your-jenkins-token',
48
+ 'your-jenkins-username',
49
+ 'your-jenkins-password'
50
+ ];
51
+ const hasTemplateValues = Object.values(this.localConfig).some(value => templateValues.includes(value));
52
+ if (hasTemplateValues) {
53
+ console.log(chalk.yellow('⚠️ 检测到配置文件包含模板值,请修改以下变量:'));
54
+ Object.entries(this.localConfig).forEach(([key, value]) => {
55
+ if (templateValues.includes(value)) {
56
+ console.log(chalk.cyan(` ${key}=${value}`));
57
+ }
58
+ });
59
+ console.log(chalk.green('📍 文件位置:' + this.configManager.projectConfigFile));
60
+ console.log(chalk.cyan('🔄 修改完成后请重新运行命令'));
61
+ process.exit(0);
62
+ }
63
+ }
64
+
65
+ validateConfig() {
66
+ const requiredKeys = ['jenkinsBase', 'jenkinsToken', 'jenkinsUrl', 'jobName'];
67
+ const missingKeys = [];
68
+
69
+ for (const key of requiredKeys) {
70
+ if (!this.localConfig[key]) {
71
+ missingKeys.push(key);
72
+ }
73
+ }
74
+
75
+ if (missingKeys.length > 0) {
76
+ console.error('❌ 配置信息缺失,请检查以下配置:');
77
+ missingKeys.forEach(key => {
78
+ console.error(` - ${key}`);
79
+ });
80
+ console.error('\n💡 提示:');
81
+ console.error(' 1. 请在项目配置文件 (.daodourc) 中取消注释并填写上述配置');
82
+ if (missingKeys.includes('jobName')) {
83
+ console.error(' 2. jobName 参数必须从本地配置文件设置,不能使用全局配置');
84
+ } else {
85
+ console.error(' 2. 或者确保全局配置文件 (~/.daodou/config.json) 中包含这些配置');
86
+ }
87
+ console.error(' 3. 项目配置优先于全局配置');
88
+ process.exit(1);
89
+ }
90
+
91
+ // 验证构建参数配置
92
+ if (this.localConfig.buildParams) {
93
+ const buildParams = this.localConfig.buildParams;
94
+ if (typeof buildParams !== 'object') {
95
+ console.error('❌ buildParams 必须是对象格式');
96
+ process.exit(1);
97
+ }
98
+ }
99
+ }
100
+ }
101
+
102
+ class BuildCommand {
103
+ constructor() {
104
+ this.configManager = new BuildConfigManager();
105
+ this.browserAuth = new BrowserAuth();
106
+ this.jenkinsConfig = {
107
+ baseUrl: null,
108
+ auth: null
109
+ };
110
+ }
111
+
112
+ async execute(options = {}) {
113
+ // 配置检查与加载
114
+ this.configManager.checkAndCreateConfig();
115
+ this.configManager.validateConfig();
116
+ const config = this.configManager.localConfig;
117
+ this.jenkinsConfig.baseUrl = config.jenkinsUrl;
118
+ this.jenkinsBase = config.jenkinsBase;
119
+ this.jenkinsToken = config.jenkinsToken;
120
+ console.log(chalk.blue('🔧 刀豆构建工具启动中...\n'));
121
+
122
+ // 1. 获取分支名称(强制从Git获取,不依赖配置)
123
+ const branch = await this.getBranch(options);
124
+ console.log(chalk.green(`✅ 当前分支: ${chalk.cyan(branch)}\n`));
125
+
126
+ // 2. 浏览器登录Jenkins
127
+ await this.browserAuth.ensureLogin();
128
+
129
+ // 3. 设置Jenkins认证
130
+ this.setupJenkinsAuth();
131
+
132
+ // 4. 检查 Jenkins 会话有效性
133
+ await this.ensureJenkinsSession();
134
+
135
+ // 5. 构建任务名称和URL
136
+ const jobName = this.buildJobName(config);
137
+ const jenkinsUrl = this.buildJobUrl(config, jobName);
138
+ const params = this.buildParams(config, branch);
139
+
140
+ // 7. 确认参数
141
+ console.log(chalk.yellow('📋 构建参数:'));
142
+ console.log(` jobName: ${jobName}`);
143
+ Object.entries(params).forEach(([key, value]) => {
144
+ if (key === 'token') return; // 不显示 token
145
+ console.log(` ${chalk.cyan(key)}: ${chalk.magenta(value)}`);
146
+ });
147
+
148
+ await inquirer.prompt([
149
+ {
150
+ type: 'input',
151
+ name: 'confirm',
152
+ message: chalk.yellow('按回车确认开始构建,Ctrl+C 取消...'),
153
+ default: undefined,
154
+ transformer: () => ''
155
+ }
156
+ ]);
157
+ // 只要不是 Ctrl+C 终止就继续
158
+
159
+ // 8. 记录本地计时起点
160
+ const buildStartTime = Date.now();
161
+ // 9. 触发Jenkins构建并监听进度
162
+ await this.triggerAndMonitorBuild(jobName, params, buildStartTime);
163
+ }
164
+
165
+ /**
166
+ * 构建任务名称
167
+ */
168
+ buildJobName(config) {
169
+ return config.jobName;
170
+ }
171
+
172
+ /**
173
+ * 构建任务URL
174
+ */
175
+ buildJobUrl(config, jobName) {
176
+ // 使用默认拼接方式
177
+ return `${this.jenkinsBase}/${encodeURIComponent(jobName)}/buildWithParameters`;
178
+ }
179
+
180
+ /**
181
+ * 构建参数
182
+ */
183
+ buildParams(config, branch) {
184
+ const buildParams = config.buildParams || {
185
+ token: this.jenkinsToken,
186
+ GIT_BRANCH: branch
187
+ };
188
+
189
+ // 确保 GIT_BRANCH 参数使用当前分支
190
+ const finalParams = { ...buildParams };
191
+ finalParams.GIT_BRANCH = branch;
192
+
193
+ return finalParams;
194
+ }
195
+
196
+ /**
197
+ * 获取分支名称(强制从Git获取)
198
+ * @param {Object} options 命令行选项
199
+ * @returns {Promise<string>} 分支名称
200
+ */
201
+ async getBranch(options) {
202
+ // 如果命令行指定了分支,优先使用命令行参数
203
+ if (options.branch) {
204
+ console.log(chalk.yellow(`⚠️ 使用命令行指定的分支: ${options.branch}`));
205
+ return options.branch;
206
+ }
207
+
208
+ // 否则强制从Git获取当前分支
209
+ const spinner = ora('检测当前Git分支...').start();
210
+ try {
211
+ const branch = await getCurrentBranch();
212
+ spinner.succeed('分支检测完成');
213
+ return branch;
214
+ } catch (error) {
215
+ spinner.fail('分支检测失败');
216
+ throw new Error(`无法检测当前Git分支: ${error.message}\n💡 请确保当前目录是Git仓库,或使用 --branch 参数指定分支`);
217
+ }
218
+ }
219
+
220
+ async detectBranch() {
221
+ const spinner = ora('检测当前分支...').start();
222
+ try {
223
+ const branch = await getCurrentBranch();
224
+ spinner.succeed('分支检测完成');
225
+ return branch;
226
+ } catch (error) {
227
+ spinner.fail('分支检测失败');
228
+ throw new Error(`无法检测当前分支: ${error.message}`);
229
+ }
230
+ }
231
+
232
+ setupJenkinsAuth() {
233
+ // 使用浏览器获取的cookies进行认证
234
+ const cookies = this.browserAuth.cookies;
235
+ const cookieString = cookies.map(c => `${c.name}=${c.value}`).join('; ');
236
+ this.jenkinsConfig.auth = {
237
+ headers: {
238
+ Cookie: cookieString,
239
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
240
+ }
241
+ };
242
+ }
243
+
244
+ async ensureJenkinsSession() {
245
+ // 先用当前 cookie 访问 Jenkins 首页,确认是否有效
246
+ const cookieString = this.browserAuth.cookies
247
+ ? this.browserAuth.cookies.map(c => `${c.name}=${c.value}`).join('; ')
248
+ : '';
249
+ try {
250
+ const resp = await axios.get(this.jenkinsConfig.baseUrl, {
251
+ headers: {
252
+ Cookie: cookieString,
253
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
254
+ },
255
+ maxRedirects: 0,
256
+ validateStatus: s => s < 400 || s === 401 || s === 403 || s === 302
257
+ });
258
+ // 302 跳转到登录页、401/403 都视为未登录
259
+ if (resp.status === 401 || resp.status === 403) throw new Error('未登录');
260
+ if (resp.status === 302 && resp.headers.location && resp.headers.location.includes('casdoor')) throw new Error('未登录');
261
+ // 其它情况视为已登录
262
+ return true;
263
+ } catch (e) {
264
+ // 自动重新登录
265
+ console.log(chalk.yellow('⚠️ Cookie 已失效,自动重新登录...'));
266
+ await this.browserAuth.login();
267
+ this.setupJenkinsAuth();
268
+ return true;
269
+ }
270
+ }
271
+
272
+ async triggerAndMonitorBuild(jobName, params, buildStartTime) {
273
+ // 1. 触发构建
274
+ const spinner = ora('正在触发Jenkins构建...').start();
275
+ try {
276
+ const queueId = await this.triggerBuild(jobName, params);
277
+ spinner.succeed(chalk.green(`✅ Jenkins构建已触发,队列号: ${queueId}`));
278
+ // 2. 监听队列,获取build number
279
+ await this.monitorQueueAndBuild(jobName, queueId, buildStartTime);
280
+ } catch (error) {
281
+ spinner.fail('❌ Jenkins构建触发失败');
282
+ console.error(chalk.red('构建失败:'), error.message);
283
+ }
284
+ }
285
+
286
+ async triggerBuild(jobName, params) {
287
+ try {
288
+ const url = `${this.jenkinsConfig.baseUrl}job/${encodeURIComponent(jobName)}/buildWithParameters`;
289
+ let headers = { ...this.jenkinsConfig.auth.headers };
290
+ // 1. 获取 crumb(防止 403)
291
+ try {
292
+ const crumbResp = await axios.get(`${this.jenkinsConfig.baseUrl}crumbIssuer/api/json`, {
293
+ headers,
294
+ timeout: 10000
295
+ });
296
+ const { crumb, crumbRequestField } = crumbResp.data;
297
+ headers[crumbRequestField] = crumb;
298
+ } catch (e) {
299
+ // 如果 crumb 获取失败,继续尝试(部分 Jenkins 关闭了 CSRF 防护)
300
+ }
301
+ // 2. 触发构建
302
+ const response = await axios.post(url, null, {
303
+ headers,
304
+ params: params,
305
+ timeout: 30000
306
+ });
307
+ if (response.status === 201) {
308
+ // 从响应头中获取队列ID
309
+ const location = response.headers.location;
310
+ const queueId = location ? location.replace(/\/$/, '').split('/').pop() : null;
311
+ if (!queueId) {
312
+ console.log(chalk.yellow('⚠️ 未能正确获取队列号,可能是Jenkins配置问题。'));
313
+ }
314
+ return queueId;
315
+ } else {
316
+ throw new Error('触发构建失败');
317
+ }
318
+ } catch (error) {
319
+ console.log(chalk.red('❌ Jenkins构建触发失败: ' + error.message));
320
+ console.log(chalk.yellow('💡 请检查Jenkins权限、Token、参数设置,或联系管理员。'));
321
+ throw new Error(`触发构建失败: ${error.message}`);
322
+ }
323
+ }
324
+
325
+ async monitorQueueAndBuild(jobName, queueId, buildStartTime) {
326
+ const spinner = ora('等待Jenkins分配构建号...').start();
327
+ let buildNumber = null;
328
+ let count = 0;
329
+ while (!buildNumber && count < 60) { // 最多等2分钟
330
+ try {
331
+ const response = await axios.get(`${this.jenkinsConfig.baseUrl}queue/item/${queueId}/api/json`, {
332
+ ...this.jenkinsConfig.auth,
333
+ timeout: 10000
334
+ });
335
+ const item = response.data;
336
+ if (item.executable && item.executable.number) {
337
+ buildNumber = item.executable.number;
338
+ spinner.succeed(chalk.green(`✅ 已分配构建号: ${buildNumber}`));
339
+ break;
340
+ }
341
+ spinner.text = '等待Jenkins分配构建号...';
342
+ } catch (e) {
343
+ spinner.text = '队列查询中...';
344
+ }
345
+ await new Promise(r => setTimeout(r, 2000));
346
+ count++;
347
+ }
348
+ if (!buildNumber) {
349
+ spinner.fail('❌ 超时未分配构建号');
350
+ return;
351
+ }
352
+ // 监听构建进度
353
+ await this.monitorBuild(jobName, buildNumber, buildStartTime);
354
+ }
355
+
356
+ async monitorBuild(jobName, buildNumber, buildStartTime) {
357
+ const spinner = ora({
358
+ text: 'Jenkins构建中... [BUILDING] 0s',
359
+ spinner: 'dots'
360
+ }).start();
361
+ let building = true;
362
+ let lastLog = '';
363
+ let lastLogTime = 0;
364
+ while (building) {
365
+ try {
366
+ const response = await axios.get(`${this.jenkinsConfig.baseUrl}job/${encodeURIComponent(jobName)}/${buildNumber}/api/json`, {
367
+ ...this.jenkinsConfig.auth,
368
+ timeout: 10000
369
+ });
370
+ const buildInfo = response.data;
371
+ building = buildInfo.building;
372
+ // 计时
373
+ const duration = building
374
+ ? Math.floor((Date.now() - buildStartTime) / 1000)
375
+ : Math.floor((Date.now() - buildStartTime) / 1000);
376
+ spinner.text = `Jenkins构建中... [${buildInfo.result || 'BUILDING'}] ${duration}s`;
377
+ // 实时输出日志片段(每3秒刷新一次)
378
+ const now = Date.now();
379
+ if (now - lastLogTime > 2900) {
380
+ try {
381
+ const logResponse = await axios.get(`${this.jenkinsConfig.baseUrl}job/${encodeURIComponent(jobName)}/${buildNumber}/consoleText`, {
382
+ ...this.jenkinsConfig.auth,
383
+ timeout: 10000
384
+ });
385
+ const log = logResponse.data;
386
+ const lines = log.split('\n');
387
+ const lastLines = lines.slice(-5).join('\n');
388
+ if (lastLines !== lastLog) {
389
+ spinner.stop();
390
+ process.stdout.write(chalk.gray(lastLines) + '\n');
391
+ spinner.start();
392
+ spinner.text = `Jenkins构建中... [${buildInfo.result || 'BUILDING'}] ${duration}s`;
393
+ lastLog = lastLines;
394
+ }
395
+ lastLogTime = now;
396
+ } catch (logError) {
397
+ // 日志获取失败不影响主流程
398
+ }
399
+ }
400
+ if (!building) {
401
+ spinner.stop();
402
+ const totalSeconds = Math.floor((Date.now() - buildStartTime) / 1000);
403
+ const min = Math.floor(totalSeconds / 60);
404
+ const sec = totalSeconds % 60;
405
+ const timeStr = min > 0 ? `${min}分${sec}秒` : `${sec}秒`;
406
+ if (buildInfo.result === 'SUCCESS') {
407
+ console.log(chalk.green('🎉 Jenkins构建成功!'));
408
+ console.log(chalk.green(`⏱️ 总用时: ${timeStr}`));
409
+ } else {
410
+ console.log(chalk.red(`❌ Jenkins构建失败: ${buildInfo.result}`));
411
+ console.log(chalk.red(`⏱️ 总用时: ${timeStr}`));
412
+ }
413
+ break;
414
+ }
415
+ } catch (e) {
416
+ spinner.text = 'Jenkins构建中...';
417
+ }
418
+ await new Promise(r => setTimeout(r, 500)); // 500ms刷新一次
419
+ }
420
+ }
421
+ }
422
+
423
+ module.exports = new BuildCommand();