crabatool 1.0.817 → 1.0.820

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,480 @@
1
+ const { exec } = require('child_process');
2
+ const util = require('util');
3
+ const path = require('path');
4
+ const execPromise = util.promisify(exec);
5
+
6
+ class GitPushPermissionTester {
7
+ /**
8
+ * @param {Object} options 配置选项
9
+ * @param {string} options.repoPath - Git仓库的绝对或相对路径
10
+ * @param {string} options.targetBranch - 要测试的目标分支(如 'main')
11
+ * @param {string} [options.remote='origin'] - 远程仓库名称
12
+ * @param {boolean} [options.verbose=false] - 是否输出详细日志
13
+ */
14
+ constructor(options) {
15
+ this.repoPath = path.resolve(options.repoPath);
16
+ this.targetBranch = options.targetBranch;
17
+ this.remote = options.remote || 'origin';
18
+ this.verbose = options.verbose || false;
19
+ this.originalCwd = process.cwd(); // 保存原始工作目录
20
+ this.testTag = `push_test_backup_${Date.now()}`;
21
+
22
+ this._log(`📁 测试仓库: ${this.repoPath}`);
23
+ this._log(`🎯 目标分支: ${this.remote}/${this.targetBranch}`);
24
+ }
25
+
26
+ _log(...args) {
27
+ if (this.verbose) {
28
+ console.log(...args);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * 在指定目录执行命令
34
+ */
35
+ async _executeCommand(command, timeout = 30000) {
36
+ this._log(`执行: ${command}`);
37
+
38
+ try {
39
+ const { stdout, stderr } = await execPromise(command, {
40
+ cwd: this.repoPath,
41
+ timeout,
42
+ maxBuffer: 10 * 1024 * 1024,
43
+ encoding: 'utf-8'
44
+ });
45
+
46
+ if (stderr && this.verbose) {
47
+ this._log(`stderr: ${stderr.trim()}`);
48
+ }
49
+
50
+ return { success: true, stdout: stdout.trim(), stderr: stderr.trim() };
51
+ } catch (error) {
52
+ return {
53
+ success: false,
54
+ code: error.code,
55
+ stdout: error.stdout ? error.stdout.trim() : '',
56
+ stderr: error.stderr ? error.stderr.trim() : error.message,
57
+ error: error.message
58
+ };
59
+ }
60
+ }
61
+
62
+ /**
63
+ * 验证仓库状态
64
+ */
65
+ async _validateRepository() {
66
+ this._log('🔍 验证仓库状态...');
67
+
68
+ // 1. 检查目录是否存在Git仓库
69
+ const gitCheck = await this._executeCommand('git rev-parse --is-inside-work-tree');
70
+ if (!gitCheck.success) {
71
+ throw new Error(`路径 ${this.repoPath} 不是一个有效的Git仓库`);
72
+ }
73
+
74
+ // 2. 检查是否在目标分支上
75
+ const branchCheck = await this._executeCommand('git branch --show-current');
76
+ if (!branchCheck.success || branchCheck.stdout !== this.targetBranch) {
77
+ throw new Error(`当前不在 ${this.targetBranch} 分支上,当前分支: ${branchCheck.stdout || '未知'}`);
78
+ }
79
+
80
+ // 3. 检查是否有未提交的更改
81
+ const statusCheck = await this._executeCommand('git status --porcelain');
82
+ if (statusCheck.success && statusCheck.stdout) {
83
+ this._log('⚠️ 警告: 工作区有未提交的更改,测试可能会失败');
84
+ }
85
+
86
+ // 4. 检查远程仓库配置
87
+ const remoteCheck = await this._executeCommand(`git remote get-url ${this.remote}`);
88
+ if (!remoteCheck.success) {
89
+ throw new Error(`远程仓库 ${this.remote} 未配置`);
90
+ }
91
+
92
+ this._log(`🌐 远程仓库: ${remoteCheck.stdout}`);
93
+ return true;
94
+ }
95
+
96
+ /**
97
+ * 执行推送权限测试
98
+ */
99
+ async testPushPermission() {
100
+ try {
101
+ // 切换到目标目录
102
+ this._log(`📂 切换到目录: ${this.repoPath}`);
103
+ process.chdir(this.repoPath);
104
+
105
+ // 验证仓库
106
+ await this._validateRepository();
107
+
108
+ // 保存原始状态
109
+ await this._saveOriginalState();
110
+
111
+ // 创建测试提交
112
+ await this._createTestCommit();
113
+
114
+ // 执行推送测试
115
+ const pushResult = await this._executePushTest();
116
+
117
+ // 恢复原始状态
118
+ await this._restoreOriginalState();
119
+
120
+ // 返回结果
121
+ return this._formatResult(pushResult, true);
122
+
123
+ } catch (error) {
124
+ // 紧急恢复
125
+ await this._emergencyCleanup();
126
+
127
+ // 恢复原始工作目录
128
+ process.chdir(this.originalCwd);
129
+
130
+ return this._formatResult(error, false);
131
+
132
+ } finally {
133
+ // 确保总是恢复原始工作目录
134
+ process.chdir(this.originalCwd);
135
+ this._log('🔄 已恢复原始工作目录');
136
+ }
137
+ }
138
+
139
+ /**
140
+ * 保存原始状态
141
+ */
142
+ async _saveOriginalState() {
143
+ this._log('💾 保存原始状态...');
144
+
145
+ // 获取当前提交
146
+ const commitResult = await this._executeCommand('git rev-parse HEAD');
147
+ if (!commitResult.success) {
148
+ throw new Error('无法获取当前提交ID');
149
+ }
150
+
151
+ const originalCommit = commitResult.stdout;
152
+
153
+ // 创建备份标签
154
+ const tagResult = await this._executeCommand(`git tag ${this.testTag} ${originalCommit}`);
155
+ if (!tagResult.success) {
156
+ throw new Error('创建备份标签失败');
157
+ }
158
+
159
+ this._log(`📌 备份标签: ${this.testTag} -> ${originalCommit}`);
160
+ }
161
+
162
+ /**
163
+ * 创建测试提交
164
+ */
165
+ async _createTestCommit() {
166
+ this._log('📝 创建测试提交...');
167
+
168
+ const commitResult = await this._executeCommand(
169
+ 'git commit --allow-empty -m "[权限测试] 临时提交,将自动回退" --no-verify'
170
+ );
171
+
172
+ if (!commitResult.success) {
173
+ throw new Error(`创建测试提交失败: ${commitResult.stderr}`);
174
+ }
175
+
176
+ this._log('✅ 测试提交已创建');
177
+ }
178
+
179
+ /**
180
+ * 执行推送测试
181
+ */
182
+ async _executePushTest() {
183
+ this._log('🚀 执行推送测试...');
184
+
185
+ const pushResult = await this._executeCommand(
186
+ `git push ${this.remote} ${this.targetBranch}`,
187
+ 60000 // 推送可能需要更长时间
188
+ );
189
+
190
+ return pushResult;
191
+ }
192
+
193
+ /**
194
+ * 恢复原始状态
195
+ */
196
+ async _restoreOriginalState() {
197
+ this._log('🔄 恢复原始状态...');
198
+
199
+ // 1. 重置到原始提交
200
+ const resetResult = await this._executeCommand(`git reset --hard ${this.testTag}`);
201
+ if (!resetResult.success) {
202
+ throw new Error(`重置失败: ${resetResult.stderr}`);
203
+ }
204
+
205
+ // 2. 强制推送到远程(覆盖测试提交)
206
+ const forcePushResult = await this._executeCommand(
207
+ `git push ${this.remote} ${this.targetBranch}`
208
+ );
209
+
210
+ if (!forcePushResult.success) {
211
+ this._log(`⚠️ 强制推送可能失败: ${forcePushResult.stderr}`);
212
+ // 不抛出异常,继续清理
213
+ }
214
+
215
+ // 3. 删除备份标签
216
+ await this._executeCommand(`git tag -d ${this.testTag}`);
217
+
218
+ this._log('✅ 原始状态已恢复');
219
+ }
220
+
221
+ /**
222
+ * 紧急清理
223
+ */
224
+ async _emergencyCleanup() {
225
+ this._log('⚠️ 执行紧急清理...');
226
+
227
+ try {
228
+ // 尝试用备份标签恢复
229
+ await this._executeCommand(`git reset --hard ${this.testTag}`);
230
+
231
+ // 尝试强制推送
232
+ await this._executeCommand(`git push --force ${this.remote} ${this.targetBranch}`);
233
+
234
+ // 删除标签
235
+ await this._executeCommand(`git tag -d ${this.testTag}`);
236
+
237
+ } catch (error) {
238
+ this._log(`❌ 紧急清理失败,可能需要手动干预`);
239
+ this._log(`手动恢复命令:`);
240
+ this._log(` cd ${this.repoPath}`);
241
+ this._log(` git reset --hard ${this.testTag}`);
242
+ this._log(` git push --force ${this.remote} ${this.targetBranch}`);
243
+ }
244
+ }
245
+
246
+ /**
247
+ * 格式化结果
248
+ */
249
+ _formatResult(data, isSuccess) {
250
+ if (isSuccess) {
251
+ return {
252
+ hasPermission: true,
253
+ code: 0,
254
+ message: '✅ 拥有推送权限',
255
+ repoPath: this.repoPath,
256
+ branch: this.targetBranch,
257
+ details: {
258
+ stdout: data.stdout,
259
+ stderr: data.stderr
260
+ },
261
+ timestamp: new Date().toISOString()
262
+ };
263
+ } else {
264
+ const errorType = this._classifyError(data.stderr || data.message, data.code);
265
+
266
+ return {
267
+ hasPermission: false,
268
+ code: data.code || -1,
269
+ errorType,
270
+ message: this._getErrorMessage(errorType),
271
+ repoPath: this.repoPath,
272
+ branch: this.targetBranch,
273
+ details: {
274
+ stdout: data.stdout || '',
275
+ stderr: data.stderr || data.message || '未知错误'
276
+ },
277
+ suggestion: this._getSuggestion(errorType),
278
+ timestamp: new Date().toISOString()
279
+ };
280
+ }
281
+ }
282
+
283
+ /**
284
+ * 错误分类
285
+ */
286
+ _classifyError(stderr, code) {
287
+ const err = stderr.toLowerCase();
288
+
289
+ if (err.includes('permission denied') || err.includes('not allowed')) {
290
+ return 'PERMISSION_DENIED';
291
+ }
292
+ if (err.includes('protected branch')) {
293
+ return 'BRANCH_PROTECTED';
294
+ }
295
+ if (err.includes('authentication') || err.includes('auth failed')) {
296
+ return 'AUTH_FAILED';
297
+ }
298
+ if (err.includes('hook declined')) {
299
+ return 'HOOK_REJECTED';
300
+ }
301
+ if (err.includes('cannot lock ref') || err.includes('would be overwritten')) {
302
+ return 'CONFLICT_ERROR';
303
+ }
304
+ if (code === 128 || err.includes('fatal:')) {
305
+ return 'GIT_FATAL_ERROR';
306
+ }
307
+ if (err.includes('timeout') || code === 124) {
308
+ return 'TIMEOUT_ERROR';
309
+ }
310
+
311
+ return 'UNKNOWN_ERROR';
312
+ }
313
+
314
+ _getErrorMessage(type) {
315
+ const messages = {
316
+ 'PERMISSION_DENIED': '账户对该分支无推送权限',
317
+ 'BRANCH_PROTECTED': '分支受保护,禁止直接推送',
318
+ 'AUTH_FAILED': '身份验证失败',
319
+ 'HOOK_REJECTED': '被服务器钩子脚本拒绝',
320
+ 'CONFLICT_ERROR': '存在冲突或引用被锁定',
321
+ 'GIT_FATAL_ERROR': 'Git命令执行失败',
322
+ 'TIMEOUT_ERROR': '操作超时',
323
+ 'UNKNOWN_ERROR': '未知错误'
324
+ };
325
+ return messages[type] || '检测过程中发生错误';
326
+ }
327
+
328
+ _getSuggestion(type) {
329
+ const suggestions = {
330
+ 'PERMISSION_DENIED': `联系仓库管理员将你的账户添加到 ${this.targetBranch} 分支的推送许可列表`,
331
+ 'BRANCH_PROTECTED': `分支 ${this.targetBranch} 受保护,请使用合并请求(MR/PR)方式提交变更`,
332
+ 'AUTH_FAILED': '检查访问令牌(TOKEN)或SSH密钥是否正确配置,并确保有仓库写入权限',
333
+ 'HOOK_REJECTED': '需要查看服务器pre-receive钩子日志了解具体拒绝原因',
334
+ 'CONFLICT_ERROR': '先执行 git pull 同步最新代码,或检查是否有其他进程在操作Git',
335
+ 'GIT_FATAL_ERROR': '检查Git配置、网络连接和仓库完整性',
336
+ 'TIMEOUT_ERROR': '网络连接较慢,请检查网络或增加超时时间',
337
+ 'UNKNOWN_ERROR': '请查看详细错误输出并联系系统管理员'
338
+ };
339
+ return suggestions[type] || '请检查详细错误信息';
340
+ }
341
+ }
342
+
343
+ /**
344
+ * 批量测试多个仓库的权限
345
+ */
346
+ class BatchPermissionTester {
347
+ constructor() {
348
+ this.results = [];
349
+ }
350
+
351
+ async testRepositories(repositories) {
352
+ for (const repo of repositories) {
353
+ const tester = new GitPushPermissionTester({
354
+ repoPath: repo.path,
355
+ targetBranch: repo.branch || 'main',
356
+ remote: repo.remote || 'origin',
357
+ verbose: repo.verbose || false
358
+ });
359
+
360
+ const result = await tester.testPushPermission();
361
+ this.results.push(result);
362
+
363
+ console.log(`\n${'-'.repeat(50)}`);
364
+ console.log(`仓库: ${repo.path}`);
365
+ console.log(`分支: ${repo.branch || 'main'}`);
366
+ console.log(`权限: ${result.hasPermission ? '✅ 有' : '❌ 无'}`);
367
+ console.log(`信息: ${result.message}`);
368
+ }
369
+
370
+ return this.results;
371
+ }
372
+ }
373
+
374
+ // ============ 使用示例 ============
375
+
376
+ // 示例1: 测试单个仓库
377
+ async function testSingleRepository() {
378
+ const tester = new GitPushPermissionTester({
379
+ repoPath: '/path/to/your/git/repo', // 替换为实际路径
380
+ targetBranch: 'main',
381
+ remote: 'origin',
382
+ verbose: true
383
+ });
384
+
385
+ console.log('开始测试Git推送权限...\n');
386
+
387
+ const result = await tester.testPushPermission();
388
+
389
+ console.log('\n' + '='.repeat(60));
390
+ console.log('测试完成!');
391
+ console.log('='.repeat(60));
392
+ console.log(`仓库路径: ${result.repoPath}`);
393
+ console.log(`目标分支: ${result.branch}`);
394
+ console.log(`权限状态: ${result.hasPermission ? '✅ 有权限' : '❌ 无权限'}`);
395
+ console.log(`详细信息: ${result.message}`);
396
+
397
+ if (!result.hasPermission) {
398
+ console.log(`建议: ${result.suggestion}`);
399
+ console.log(`错误详情: ${result.details.stderr}`);
400
+ }
401
+
402
+ return result;
403
+ }
404
+
405
+ // 示例2: 测试多个仓库
406
+ async function testMultipleRepositories() {
407
+ const repositories = [
408
+ { path: '/projects/repo1', branch: 'main' },
409
+ { path: '/projects/repo2', branch: 'develop' },
410
+ { path: '/projects/repo3', branch: 'production', remote: 'upstream' }
411
+ ];
412
+
413
+ const batchTester = new BatchPermissionTester();
414
+ const results = await batchTester.testRepositories(repositories);
415
+
416
+ // 生成报告
417
+ const summary = {
418
+ total: results.length,
419
+ hasPermission: results.filter(r => r.hasPermission).length,
420
+ noPermission: results.filter(r => !r.hasPermission).length,
421
+ errors: results.filter(r => r.errorType === 'GIT_FATAL_ERROR').length
422
+ };
423
+
424
+ console.log('\n' + '='.repeat(60));
425
+ console.log('批量测试报告');
426
+ console.log('='.repeat(60));
427
+ console.log(`总仓库数: ${summary.total}`);
428
+ console.log(`有权限: ${summary.hasPermission}`);
429
+ console.log(`无权限: ${summary.noPermission}`);
430
+ console.log(`错误数: ${summary.errors}`);
431
+
432
+ return results;
433
+ }
434
+
435
+ // 示例3: 命令行使用
436
+ async function main() {
437
+ // 从命令行参数获取路径和分支
438
+ const args = process.argv.slice(2);
439
+ const repoPath = args[0] || process.cwd();
440
+ const targetBranch = args[1] || 'main';
441
+
442
+ console.log(`测试目录: ${repoPath}`);
443
+ console.log(`目标分支: ${targetBranch}`);
444
+
445
+ const tester = new GitPushPermissionTester({
446
+ repoPath,
447
+ targetBranch,
448
+ verbose: true
449
+ });
450
+
451
+ try {
452
+ const result = await tester.testPushPermission();
453
+
454
+ // 退出码:0=有权限,1=无权限,2=错误
455
+ if (result.hasPermission) {
456
+ console.log('\n✅ 成功: 账户拥有推送权限');
457
+ process.exit(0);
458
+ } else {
459
+ console.log(`\n❌ 失败: ${result.message}`);
460
+ process.exit(1);
461
+ }
462
+ } catch (error) {
463
+ console.error(`\n💥 错误: ${error.message}`);
464
+ process.exit(2);
465
+ }
466
+ }
467
+
468
+ // 根据需求选择执行方式
469
+ if (require.main === module) {
470
+ // 直接运行脚本
471
+ main().catch(console.error);
472
+ }
473
+
474
+ // 导出模块供其他文件引用
475
+ module.exports = {
476
+ GitPushPermissionTester,
477
+ BatchPermissionTester,
478
+ testSingleRepository,
479
+ testMultipleRepositories
480
+ };
@@ -0,0 +1,194 @@
1
+ const { exec } = require('child_process');
2
+ const util = require('util');
3
+ const execPromise = util.promisify(exec);
4
+
5
+ class DirectBranchPushTester {
6
+ constructor(targetBranch, remote = 'origin') {
7
+ this.targetBranch = targetBranch;
8
+ this.remote = remote;
9
+ this.testTag = null; // 用于标记原始提交
10
+ }
11
+
12
+ /**
13
+ * 主测试方法
14
+ * @returns {Promise<Object>} 测试结果
15
+ */
16
+ async testPushPermission() {
17
+ console.log(`🔍 开始测试对 ${this.remote}/${this.targetBranch} 的直接推送权限...`);
18
+
19
+ try {
20
+ // 1. 保存当前状态
21
+ await this._saveOriginalState();
22
+
23
+ // 2. 创建空测试提交
24
+ await this._createTestCommit();
25
+
26
+ // 3. 执行真实推送测试(核心权限检查点)
27
+ const pushResult = await this._executePush();
28
+
29
+ // 4. 无论成功与否,都尝试恢复原始状态
30
+ await this._restoreOriginalState();
31
+
32
+ // 5. 返回格式化的测试结果
33
+ return this._formatSuccessResult(pushResult);
34
+
35
+ } catch (error) {
36
+ // 6. 失败时,尽最大努力恢复并返回错误信息
37
+ await this._emergencyCleanup().catch(() => {});
38
+ return this._formatErrorResult(error);
39
+ }
40
+ }
41
+
42
+ /* ---------- 核心步骤 ---------- */
43
+ async _saveOriginalState() {
44
+ // 获取当前分支最新的提交ID,并创建一个临时标签以便回退
45
+ const { stdout } = await execPromise(`git rev-parse HEAD`);
46
+ const originalCommit = stdout.trim();
47
+ this.testTag = `push_test_backup_${Date.now()}`;
48
+ await execPromise(`git tag ${this.testTag} ${originalCommit}`);
49
+ console.log(`📌 已创建备份标签: ${this.testTag}`);
50
+ }
51
+
52
+ async _createTestCommit() {
53
+ // 创建一个不改变工作区的空提交
54
+ await execPromise(
55
+ `git commit --allow-empty -m "[自动化权限测试] 临时提交,即将回退" --no-verify`
56
+ );
57
+ console.log('📝 已创建空测试提交');
58
+ }
59
+
60
+ async _executePush() {
61
+ try {
62
+ // 关键:执行真实推送
63
+ const { stdout, stderr } = await execPromise(
64
+ `git push ${this.remote} ${this.targetBranch}`,
65
+ { timeout: 30000, maxBuffer: 10 * 1024 * 1024 }
66
+ );
67
+ return { code: 0, stdout, stderr };
68
+ } catch (error) {
69
+ return {
70
+ code: error.code,
71
+ stdout: error.stdout,
72
+ stderr: error.stderr,
73
+ message: error.message
74
+ };
75
+ }
76
+ }
77
+
78
+ async _restoreOriginalState() {
79
+ console.log('🔄 开始恢复分支原始状态...');
80
+
81
+ // 1. 硬重置到原始提交(丢弃测试提交)
82
+ await execPromise(`git reset --hard ${this.testTag}`);
83
+
84
+ // 2. 强制推送到远程(覆盖远程的测试提交)
85
+ await execPromise(`git push --force ${this.remote} ${this.targetBranch}`);
86
+
87
+ // 3. 删除本地备份标签
88
+ await execPromise(`git tag -d ${this.testTag}`);
89
+
90
+ console.log('✅ 分支状态已完全恢复');
91
+ }
92
+
93
+ async _emergencyCleanup() {
94
+ // 应急清理:如果上述流程中断,尝试用备份标签恢复
95
+ if (this.testTag) {
96
+ try {
97
+ await execPromise(`git reset --hard ${this.testTag}`);
98
+ await execPromise(`git push --force ${this.remote} ${this.targetBranch}`);
99
+ await execPromise(`git tag -d ${this.testTag}`);
100
+ } catch (e) {
101
+ console.error('⚠️ 应急清理失败,可能需要手动处理');
102
+ console.error(`手动恢复命令: git reset --hard ${this.testTag} && git push --force ${this.remote} ${this.targetBranch}`);
103
+ }
104
+ }
105
+ }
106
+
107
+ /* ---------- 结果处理 ---------- */
108
+ _formatSuccessResult(pushResult) {
109
+ return {
110
+ hasPermission: true,
111
+ code: 0,
112
+ message: '✅ 拥有直接推送权限',
113
+ details: {
114
+ stdout: pushResult.stdout,
115
+ stderr: pushResult.stderr
116
+ },
117
+ note: '分支已恢复至测试前状态'
118
+ };
119
+ }
120
+
121
+ _formatErrorResult(error) {
122
+ // 分析错误类型
123
+ const errorType = this._classifyError(error.stderr || error.message, error.code);
124
+
125
+ return {
126
+ hasPermission: false,
127
+ code: error.code || -1,
128
+ errorType,
129
+ message: this._getErrorMessage(errorType),
130
+ details: {
131
+ stdout: error.stdout,
132
+ stderr: error.stderr || error.message
133
+ },
134
+ suggestion: this._getSuggestion(errorType),
135
+ note: '分支已恢复至测试前状态'
136
+ };
137
+ }
138
+
139
+ _classifyError(stderr, code) {
140
+ const err = stderr.toLowerCase();
141
+ if (err.includes('permission denied') || err.includes('not allowed')) return 'PERMISSION_DENIED';
142
+ if (err.includes('protected branch')) return 'BRANCH_PROTECTED';
143
+ if (err.includes('authentication')) return 'AUTH_FAILED';
144
+ if (err.includes('hook declined')) return 'HOOK_REJECTED';
145
+ if (code === 128) return 'NETWORK_OR_AUTH_ERROR';
146
+ return 'UNKNOWN_ERROR';
147
+ }
148
+
149
+ _getErrorMessage(type) {
150
+ const messages = {
151
+ 'PERMISSION_DENIED': '账户对该分支无推送权限',
152
+ 'BRANCH_PROTECTED': '分支受保护,禁止直接推送',
153
+ 'AUTH_FAILED': '身份验证失败',
154
+ 'HOOK_REJECTED': '被服务器钩子拒绝',
155
+ 'NETWORK_OR_AUTH_ERROR': '网络或基础认证错误',
156
+ 'UNKNOWN_ERROR': '未知错误'
157
+ };
158
+ return messages[type] || '检测过程中发生错误';
159
+ }
160
+
161
+ _getSuggestion(type) {
162
+ const suggestions = {
163
+ 'PERMISSION_DENIED': '联系仓库管理员将你的账户添加到分支推送许可列表',
164
+ 'BRANCH_PROTECTED': '通常需要创建合并请求(MR/PR),请检查分支保护规则',
165
+ 'AUTH_FAILED': '检查使用的访问令牌(TOKEN)或SSH密钥是否有对应仓库的写入权限',
166
+ 'HOOK_REJECTED': '需查看服务器pre-receive钩子日志了解具体拒绝原因',
167
+ 'NETWORK_OR_AUTH_ERROR': '检查远程仓库地址、网络连接及基础Git认证信息',
168
+ 'UNKNOWN_ERROR': '请提供完整错误信息以便进一步排查'
169
+ };
170
+ return suggestions[type] || '请查看详细错误输出';
171
+ }
172
+ }
173
+ module.exports = DirectBranchPushTester;
174
+
175
+ // /* ---------- 使用示例 ---------- */
176
+ // async function main() {
177
+ // // 测试对 main 分支的推送权限
178
+ // const tester = new DirectBranchPushTester('main', 'origin');
179
+
180
+ // const result = await tester.testPushPermission();
181
+
182
+ // console.log('\n=== 测试结果 ===');
183
+ // console.log(JSON.stringify(result, null, 2));
184
+
185
+ // if (result.hasPermission) {
186
+ // console.log('\n结论: 账户拥有对该分支的直接推送权限。');
187
+ // } else {
188
+ // console.log(`\n结论: ${result.message}`);
189
+ // console.log(`建议: ${result.suggestion}`);
190
+ // }
191
+ // }
192
+
193
+ // // 执行
194
+ // main().catch(console.error);
package/lib/server.js CHANGED
@@ -160,21 +160,6 @@ function createServer(app, options) {
160
160
 
161
161
  app.webPath = webPath; // handler.js里面动态获取当前app的网站路径(多个server实例的时候需要)
162
162
 
163
-
164
- // 业务ap的代理转发(可能转发静态资源,放到前面)
165
- if (config.proxy) {
166
- var { createProxyMiddleware } = require('http-proxy-middleware');
167
- if (Array.isArray(config.proxy)) { // 兼容代理配置是数组
168
- for (const item of config.proxy) {
169
- app.use(createProxyMiddleware(item))
170
- }
171
- } else {
172
- for (let key in config.proxy) {
173
- app.use(key, createProxyMiddleware(config.proxy[key]));
174
- }
175
- }
176
- }
177
-
178
163
  app.use(express.static(webPath, {
179
164
  maxage: '100h', // 强制缓存资源
180
165
  setHeaders: function(res, pathname, stat) {
@@ -195,6 +180,20 @@ function createServer(app, options) {
195
180
  }
196
181
  }));
197
182
 
183
+ // 业务ap的代理转发
184
+ if (config.proxy) {
185
+ var { createProxyMiddleware } = require('http-proxy-middleware');
186
+ if (Array.isArray(config.proxy)) { // 兼容代理配置是数组
187
+ for (const item of config.proxy) {
188
+ app.use(createProxyMiddleware(item))
189
+ }
190
+ } else {
191
+ for (let key in config.proxy) {
192
+ app.use(key, createProxyMiddleware(config.proxy[key]));
193
+ }
194
+ }
195
+ }
196
+
198
197
  var bodyParser = require('body-parser');
199
198
  app.use(bodyParser.json(utils._getJsonDefaultOptions({})));
200
199
  app.use(bodyParser.raw(utils._getJsonDefaultOptions({})));
package/lib/utils.js CHANGED
@@ -9,6 +9,8 @@ var compressing = require('compressing');
9
9
  var axios = require('axios');
10
10
  var os = require('os');
11
11
  const { execSync } = require('child_process');
12
+ const util = require('util');
13
+ const execPromise = util.promisify(execSync);
12
14
 
13
15
  if (!String.prototype.replaceAll) {
14
16
  String.prototype.replaceAll = function(search, replacement) {
@@ -1229,6 +1231,32 @@ class Utils {
1229
1231
  }
1230
1232
  }
1231
1233
  }
1234
+
1235
+
1236
+ /**
1237
+ * 在指定目录执行命令
1238
+ */
1239
+ async executeCommand(command, repoPath = config.webPath, timeout = 30000) {
1240
+ console.log(`执行: ${command}`);
1241
+
1242
+ try {
1243
+ const { stdout, stderr } = await execPromise(command, {
1244
+ cwd: repoPath,
1245
+ timeout,
1246
+ maxBuffer: 10 * 1024 * 1024,
1247
+ encoding: 'utf-8'
1248
+ });
1249
+ return { success: true, stdout: stdout.trim(), stderr: stderr.trim() };
1250
+ } catch (error) {
1251
+ return {
1252
+ success: false,
1253
+ code: error.code,
1254
+ stdout: error.stdout ? error.stdout.trim() : '',
1255
+ stderr: error.stderr ? error.stderr.trim() : error.message,
1256
+ error: error.message
1257
+ };
1258
+ }
1259
+ }
1232
1260
  }
1233
1261
  var utils = new Utils();
1234
1262
  module = module.exports = utils;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crabatool",
3
- "version": "1.0.817",
3
+ "version": "1.0.820",
4
4
  "description": "crabatool",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -13,7 +13,8 @@
13
13
  "sale-doc": "node ./test/test.js -convertJavadoc -targetPath F:/docs/sale -outPath F:/docs/sale.json",
14
14
  "start": "node run.js",
15
15
  "test": "node ./test/test.js",
16
- "run": "node ./test/test.js -run -webPath F:/basicweb/www -refresh true",
16
+ "test_git": "node ./test/git.js -webPath F:/basicweb/www",
17
+ "run": "node ./test/test.js -run -webPath F:/basicweb/www -refresh true -proxy \"{'/shell/framework/':{'target':'http://127.0.0.1:8339','changeOrigin':true,'logLevel':'debug'}}\"",
17
18
  "mergeinitjs": "node ./test/test.js -mergeinitjs",
18
19
  "export": "node ./test/test.js -exportgspx -webPath F:/Beefun/srcs",
19
20
  "checkjs": "node ./test/test.js -checkjs",
@@ -93,13 +93,19 @@ function buildFolder(options) {
93
93
  if (config.exp) {
94
94
  var list = config.exp.split(';');
95
95
  list.forEach(function(item) {
96
+ if (!item) return;
96
97
  var arr = item.split('=');
97
- var key = arr[0].trim();
98
+ var key = (arr[0] || '').trim();
99
+ if (!key) return;
100
+
98
101
  var value;
99
102
  if (key == '$versionDate$') {
100
103
  value = new Date().toString('yyyyMMddhh');
101
- } else {
104
+ } else if (arr.length > 1 && arr[1] != null) {
102
105
  value = arr[1].trim();
106
+ } else {
107
+ utils.log('exp变量格式不正确,已忽略:' + item);
108
+ return;
103
109
  }
104
110
  content = content.replaceAll(key, value);
105
111
 
@@ -280,4 +286,4 @@ function copyByteToCache(filePath, content, stats, options) {
280
286
  utils.debug("拷贝到缓存:" + cachePath);
281
287
  fs.writeFileSync(cachePath, content, 'utf8');
282
288
  fs.utimesSync(cachePath, stats.atime, stats.mtime); // 必须同步文件时间
283
- }
289
+ }
package/tool/upgrade.js CHANGED
@@ -58,7 +58,87 @@ class Upgrade {
58
58
  return { version: version, date: vDate };
59
59
  }
60
60
 
61
- checkUpdateCraba() {
61
+ async checkUpdateCraba() {
62
+ var result = await this.checkGitPermission();
63
+ if (!result) {
64
+ console.log('没有git推送权限,取消craba的更新操作');
65
+ return;
66
+ }
67
+
68
+ console.log('当前git有推送权限,开始检查更新craba');
69
+
70
+ this.checkUpdateCrabaNext();
71
+ }
72
+
73
+ async checkGitPermission() {
74
+ //git commit --allow-empty -m "测试空提交"
75
+ console.log('检查当前git是否有推送权限');
76
+
77
+ console.log('🚀 创建空白提交...');
78
+ const commitResult = await utils.executeCommand(
79
+ 'git commit --allow-empty -m "[检测推送权限测试提交] 空白提交,无需关注" --no-verify'
80
+ );
81
+
82
+ if (!commitResult.success) {
83
+ console.log(`创建测试提交失败: ${commitResult.stderr}`);
84
+ return false;
85
+ }
86
+
87
+ var branchName = config.branchName || config.version;
88
+ branchName = branchName.replace('origin/', '');
89
+
90
+ var gitCmd = `git push origin ${branchName}`;
91
+ console.log('🚀 执行推送测试是否有权限...');
92
+ const pushResult = await utils.executeCommand(
93
+ gitCmd,
94
+ 60000 // 推送可能需要更长时间
95
+ );
96
+
97
+ if (!pushResult.success) {
98
+ console.log(`没有推送权限: ${pushResult.stderr}`);
99
+ return false;
100
+ }
101
+
102
+ if (this.classifyError(pushResult.stderr || pushResult.message, pushResult.code)) {
103
+ return false;
104
+ }
105
+
106
+ return true;
107
+ }
108
+
109
+ /**
110
+ * 错误分类
111
+ */
112
+ classifyError(stderr, code) {
113
+ if (!stderr) return false;
114
+ const err = stderr.toLowerCase();
115
+ if (err.includes('permission denied') || err.includes('not allowed')) {
116
+ return 'PERMISSION_DENIED';
117
+ }
118
+ if (err.includes('protected branch')) {
119
+ return 'BRANCH_PROTECTED';
120
+ }
121
+ if (err.includes('authentication') || err.includes('auth failed')) {
122
+ return 'AUTH_FAILED';
123
+ }
124
+ if (err.includes('hook declined')) {
125
+ return 'HOOK_REJECTED';
126
+ }
127
+ if (err.includes('cannot lock ref') || err.includes('would be overwritten')) {
128
+ return 'CONFLICT_ERROR';
129
+ }
130
+ if (code === 128 || err.includes('fatal:')) {
131
+ return 'GIT_FATAL_ERROR';
132
+ }
133
+ if (err.includes('timeout') || code === 124) {
134
+ return 'TIMEOUT_ERROR';
135
+ }
136
+
137
+ return false;
138
+ }
139
+
140
+
141
+ checkUpdateCrabaNext() {
62
142
  var host = config.SourceHost;
63
143
  var version = config.version; // 默认临时目录下
64
144
  var host = config.host || config.SourceHost;