ai-git-tools 2.0.28 → 2.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.
package/bin/cli.js CHANGED
@@ -34,7 +34,14 @@ program
34
34
  program
35
35
  .command('init')
36
36
  .description('初始化配置檔案 (.ai-git-config.mjs)')
37
- .action(initCommand);
37
+ .action(async (options) => {
38
+ try {
39
+ await initCommand(options);
40
+ process.exit(0);
41
+ } catch (error) {
42
+ process.exit(1);
43
+ }
44
+ });
38
45
 
39
46
  // Commit 命令
40
47
  program
@@ -44,7 +51,14 @@ program
44
51
  .option('-v, --verbose', '顯示詳細輸出')
45
52
  .option('--max-diff <number>', '最大 diff 長度')
46
53
  .option('--max-retries <number>', '最大重試次數')
47
- .action(commitCommand);
54
+ .action(async (options) => {
55
+ try {
56
+ await commitCommand(options);
57
+ process.exit(0);
58
+ } catch (error) {
59
+ process.exit(1);
60
+ }
61
+ });
48
62
 
49
63
  // Commit All 命令
50
64
  program
@@ -54,7 +68,14 @@ program
54
68
  .option('-v, --verbose', '顯示詳細輸出')
55
69
  .option('--max-diff <number>', '最大 diff 長度')
56
70
  .option('--max-retries <number>', '最大重試次數')
57
- .action(commitAllCommand);
71
+ .action(async (options) => {
72
+ try {
73
+ await commitAllCommand(options);
74
+ process.exit(0);
75
+ } catch (error) {
76
+ process.exit(1);
77
+ }
78
+ });
58
79
 
59
80
  // PR 命令
60
81
  program
@@ -66,6 +87,13 @@ program
66
87
  .option('--no-confirm', '跳過確認直接創建')
67
88
  .option('--auto-labels', '自動添加 Labels (預設啟用)')
68
89
  .option('--include-impact', '在 PR 中包含影響範圍分析和注意事項 (預設關閉)')
69
- .action(prCommand);
90
+ .action(async (options) => {
91
+ try {
92
+ await prCommand(options);
93
+ process.exit(0);
94
+ } catch (error) {
95
+ process.exit(1);
96
+ }
97
+ });
70
98
 
71
99
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-git-tools",
3
- "version": "2.0.28",
3
+ "version": "2.0.30",
4
4
  "description": "AI-powered Git automation tools for commit messages and PR generation",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -10,6 +10,70 @@ export class GitHubAPI {
10
10
  constructor() {
11
11
  // 自動從 git remote 偵測組織名稱
12
12
  this.orgName = this.detectOrgFromRemote();
13
+ // 檢測是否為 fork 環境
14
+ this.isFork = this.detectForkEnvironment();
15
+ }
16
+
17
+ /**
18
+ * 檢測是否為 fork 環境(有 upstream remote)
19
+ */
20
+ detectForkEnvironment() {
21
+ try {
22
+ execSync('git remote get-url upstream', {
23
+ encoding: 'utf-8',
24
+ stdio: ['pipe', 'pipe', 'pipe'],
25
+ });
26
+ log.info('偵測到 fork 環境(有 upstream remote)\n');
27
+ return true;
28
+ } catch (error) {
29
+ // 沒有 upstream,不是 fork
30
+ return false;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * 獲取當前 gh CLI 登入的帳號
36
+ */
37
+ getGHAccount() {
38
+ try {
39
+ const account = execSync('gh api user --jq ".login"', {
40
+ encoding: 'utf-8',
41
+ stdio: ['pipe', 'pipe', 'pipe'],
42
+ }).trim();
43
+ return account;
44
+ } catch (error) {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ /**
50
+ * 檢查 gh 帳號是否與倉庫擁有者匹配
51
+ */
52
+ checkAccountMatch() {
53
+ const ghAccount = this.getGHAccount();
54
+ if (!ghAccount) {
55
+ log.warning('⚠️ 無法取得 gh CLI 登入帳號\n');
56
+ return true; // 無法確認就不阻止
57
+ }
58
+
59
+ if (!this.orgName) {
60
+ return true; // 無法確認倉庫擁有者
61
+ }
62
+
63
+ if (ghAccount.toLowerCase() !== this.orgName.toLowerCase()) {
64
+ log.error(`\n❌ 帳號不匹配!\n`);
65
+ console.log(` 倉庫擁有者: ${colors.cyan}${this.orgName}${colors.reset}`);
66
+ console.log(` gh 登入帳號: ${colors.yellow}${ghAccount}${colors.reset}\n`);
67
+ console.log(`${colors.yellow}⚠️ GitHub 不允許非 collaborator 創建 PR${colors.reset}\n`);
68
+ console.log('解決方法:');
69
+ console.log(` 1. ${colors.cyan}切換 gh 帳號到倉庫擁有者${colors.reset}`);
70
+ console.log(` 執行: ${colors.green}gh auth switch${colors.reset}`);
71
+ console.log(` 或: ${colors.green}gh auth login${colors.reset}\n`);
72
+ console.log(` 2. 或者將 ${ghAccount} 加為倉庫 collaborator\n`);
73
+ return false;
74
+ }
75
+
76
+ return true;
13
77
  }
14
78
 
15
79
  /**
@@ -207,6 +271,12 @@ export class GitHubAPI {
207
271
  const bodyFile = '/tmp/pr-body.md';
208
272
  writeFileSync(bodyFile, body);
209
273
 
274
+ // 檢查帳號是否匹配
275
+ if (!this.checkAccountMatch()) {
276
+ this.safeUnlink(bodyFile);
277
+ throw new Error('GitHub 帳號與倉庫擁有者不匹配,無法創建 PR');
278
+ }
279
+
210
280
  try {
211
281
  // 檢查 PR 是否已存在
212
282
  let existingPRUrl = null;
@@ -242,13 +312,21 @@ export class GitHubAPI {
242
312
  // 創建新 PR
243
313
  log.step('正在創建 Pull Request...');
244
314
 
245
- // 如果有 orgName 且是組織 repo,使用完整的分支格式
246
- // 如果沒有 orgName,直接使用分支名稱(gh CLI 會自動處理)
315
+ // 判斷是否需要加 org: 前綴
316
+ // 1. 如果 headBranch 已經包含 ':',直接使用
317
+ // 2. 如果是 fork 環境(有 upstream),需要加 'org:' 前綴
318
+ // 3. 如果不是 fork(在同一倉庫內操作),直接使用分支名稱
247
319
  let headRef = headBranch;
248
320
  if (headBranch.includes(':')) {
321
+ // 用戶已明確指定格式,直接使用
249
322
  headRef = headBranch;
250
- } else if (this.orgName) {
323
+ } else if (this.isFork && this.orgName) {
324
+ // Fork 環境,需要加 org: 前綴以正確標識來源
251
325
  headRef = `${this.orgName}:${headBranch}`;
326
+ log.info(`Fork 環境:使用完整分支格式 ${headRef}\n`);
327
+ } else {
328
+ // 同一倉庫內操作,直接使用分支名稱
329
+ headRef = headBranch;
252
330
  }
253
331
  const createCmd = `gh pr create --base "${baseBranch}" --head "${headRef}" --title "${escapedTitle}" --body-file "${bodyFile}"`;
254
332