ai-project-manage-cli 8.1.27 → 8.1.28

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/dist/index.js CHANGED
@@ -1436,7 +1436,7 @@ function resolveHomeRepositoryAbsolutePaths(workdirInput, entries) {
1436
1436
  }
1437
1437
 
1438
1438
  // src/utils/git.ts
1439
- import { execFile } from "child_process";
1439
+ import { execFile, spawn } from "child_process";
1440
1440
  import { dirname as dirname3, join as join8 } from "path";
1441
1441
  import { fileURLToPath as fileURLToPath3 } from "url";
1442
1442
  import { promisify } from "util";
@@ -1505,7 +1505,9 @@ async function execGit(cwd, args, quietOrOptions = false) {
1505
1505
  const e = err;
1506
1506
  if (e.killed || e.signal === "SIGTERM" || e.code === "ETIMEDOUT") {
1507
1507
  throw new Error(
1508
- `[apm] git ${args.join(" ")} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
1508
+ `[apm] git ${args.join(
1509
+ " "
1510
+ )} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
1509
1511
  );
1510
1512
  }
1511
1513
  const detail = (e.stderr ?? e.message ?? String(err)).trim();
@@ -1528,33 +1530,67 @@ async function runGitScript(name, options) {
1528
1530
  const timeoutMs = options.timeoutMs ?? GIT_NETWORK_TIMEOUT_MS;
1529
1531
  const script = resolveGitScript(name);
1530
1532
  const args = [script, ...options.args ?? []];
1531
- try {
1532
- const { stdout, stderr } = await execFileAsync("bash", args, {
1533
+ return new Promise((resolve5, reject) => {
1534
+ const child = spawn("bash", args, {
1533
1535
  cwd: options.cwd,
1534
1536
  env: gitExecEnv(),
1535
- encoding: "utf8",
1536
- maxBuffer: 10 * 1024 * 1024,
1537
- timeout: timeoutMs,
1538
- killSignal: "SIGTERM"
1537
+ stdio: ["ignore", "pipe", "pipe"]
1539
1538
  });
1540
- if (stdout.trim()) process.stdout.write(stdout);
1541
- if (stderr.trim()) process.stderr.write(stderr);
1542
- return true;
1543
- } catch (err) {
1544
- const e = err;
1545
- if (e.stdout?.trim()) process.stdout.write(e.stdout);
1546
- if (e.stderr?.trim()) process.stderr.write(e.stderr);
1547
- if (e.code === skipCode) {
1548
- return false;
1549
- }
1550
- if (e.killed || e.signal === "SIGTERM" || e.code === "ETIMEDOUT") {
1551
- throw new Error(
1552
- `[apm] ${name} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
1553
- );
1554
- }
1555
- const detail = (e.stderr ?? e.message ?? String(err)).trim();
1556
- throw new Error(`[apm] ${name} \u5931\u8D25${detail ? `: ${detail}` : ""}`);
1557
- }
1539
+ let stderrBuf = "";
1540
+ let settled = false;
1541
+ let timedOut = false;
1542
+ const timer = timeoutMs > 0 ? setTimeout(() => {
1543
+ timedOut = true;
1544
+ child.kill("SIGTERM");
1545
+ const forceKill = setTimeout(() => {
1546
+ child.kill("SIGKILL");
1547
+ }, 2e3);
1548
+ forceKill.unref();
1549
+ }, timeoutMs) : null;
1550
+ const finish = (fn) => {
1551
+ if (settled) return;
1552
+ settled = true;
1553
+ if (timer) clearTimeout(timer);
1554
+ fn();
1555
+ };
1556
+ child.stdout?.on("data", (chunk) => {
1557
+ process.stdout.write(chunk);
1558
+ });
1559
+ child.stderr?.on("data", (chunk) => {
1560
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
1561
+ stderrBuf += text;
1562
+ process.stderr.write(chunk);
1563
+ });
1564
+ child.on("error", (err) => {
1565
+ finish(() => reject(new Error(`[apm] ${name} \u542F\u52A8\u5931\u8D25: ${err.message}`)));
1566
+ });
1567
+ child.on("close", (code, signal) => {
1568
+ finish(() => {
1569
+ if (timedOut || signal === "SIGTERM" || signal === "SIGKILL") {
1570
+ reject(
1571
+ new Error(
1572
+ `[apm] ${name} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u770B\u4E0A\u65B9\u6700\u540E\u4E00\u6B65\u65E5\u5FD7\u5B9A\u4F4D\u5361\u70B9\uFF08\u591A\u4E3A ls-remote/fetch/push\uFF09\uFF1B\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH \u540E\u53EF\u7A0D\u540E\u91CD\u8BD5`
1573
+ )
1574
+ );
1575
+ return;
1576
+ }
1577
+ if (code === skipCode) {
1578
+ resolve5(false);
1579
+ return;
1580
+ }
1581
+ if (code === 0) {
1582
+ resolve5(true);
1583
+ return;
1584
+ }
1585
+ const detail = stderrBuf.trim();
1586
+ reject(
1587
+ new Error(
1588
+ `[apm] ${name} \u5931\u8D25${detail ? `: ${detail}` : code != null ? ` (exit ${code})` : ""}`
1589
+ )
1590
+ );
1591
+ });
1592
+ });
1593
+ });
1558
1594
  }
1559
1595
  async function commitWorkingTreeIfDirty(cwd, message) {
1560
1596
  await ensureGitRepo(cwd);
@@ -6,8 +6,11 @@ set -euo pipefail
6
6
 
7
7
  baseline="${1:?用法: ensure_baseline.sh <baseline-branch>}"
8
8
 
9
+ echo "[apm] ensure_baseline: fetch origin ${baseline} …"
9
10
  git fetch origin "$baseline"
11
+ echo "[apm] ensure_baseline: ls-remote origin ${baseline} …"
10
12
  if git ls-remote --heads origin "$baseline" | grep -q .; then
13
+ echo "[apm] ensure_baseline: origin/${baseline} 已确认"
11
14
  exit 0
12
15
  fi
13
16
 
@@ -9,9 +9,12 @@ baseline="${2:?用法: ensure_feature_branch.sh <branch> <baseline-branch>}"
9
9
  SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
10
10
 
11
11
  git rev-parse --git-dir >/dev/null
12
+ origin_url="$(git config --get remote.origin.url 2>/dev/null || true)"
13
+ echo "[apm] ensure_feature_branch: branch=${branch} baseline=${baseline}${origin_url:+ origin=${origin_url}}"
12
14
 
13
15
  # 有未提交改动时先 stash(含 untracked),再切/建分支,避免污染目标分支工作区
14
16
  if [[ -n "$(git status --porcelain)" ]]; then
17
+ echo "[apm] ensure_feature_branch: stash 未提交改动 …"
15
18
  git stash push -u -m "apm: ensure ${branch}"
16
19
  echo "[apm] 已 stash 未提交改动,再确认分支 ${branch}"
17
20
  fi
@@ -20,16 +23,26 @@ current="$(git rev-parse --abbrev-ref HEAD)"
20
23
  if [[ "$current" == "$branch" ]]; then
21
24
  echo "[apm] 已在分支 ${branch}"
22
25
  else
26
+ echo "[apm] ensure_feature_branch: 当前 ${current} → 准备 ${branch}"
23
27
  bash "$SCRIPT_DIR/ensure_baseline.sh" "$baseline"
24
28
 
29
+ echo "[apm] ensure_feature_branch: ls-remote origin ${branch} …"
25
30
  if git ls-remote --heads origin "$branch" | grep -q .; then
31
+ echo "[apm] ensure_feature_branch: 远程已有 ${branch},fetch + checkout …"
26
32
  git fetch origin "$branch"
27
33
  git checkout -B "$branch" "origin/${branch}"
28
34
  elif git show-ref --verify --quiet "refs/heads/${branch}"; then
35
+ echo "[apm] ensure_feature_branch: 本地已有 ${branch},checkout …"
29
36
  git checkout "$branch"
30
37
  echo "[apm] 分支 ${branch} 已存在,跳过创建"
31
38
  else
39
+ # 先切到远程基线(便于 post-checkout 等 hook 装依赖),再基于 origin/<baseline> 建特性分支(不基于本地基线)
40
+ echo "[apm] ensure_feature_branch: 本地/远程均无 ${branch},先切基线 origin/${baseline} …"
41
+ echo "[apm] ensure_feature_branch: fetch origin ${baseline} …"
32
42
  git fetch origin "$baseline"
43
+ echo "[apm] ensure_feature_branch: checkout -B ${baseline} origin/${baseline} …"
44
+ git checkout -B "$baseline" "origin/${baseline}"
45
+ echo "[apm] ensure_feature_branch: 基于 origin/${baseline} 创建 ${branch} …"
33
46
  set +e
34
47
  git checkout --no-track -b "$branch" "origin/${baseline}"
35
48
  create_status=$?
@@ -44,14 +57,19 @@ else
44
57
  else
45
58
  exit "$create_status"
46
59
  fi
60
+ else
61
+ echo "[apm] ensure_feature_branch: 已创建本地分支 ${branch}"
47
62
  fi
48
63
  fi
49
64
  fi
50
65
 
51
66
  # 本地已在目标分支时也要补推:上次 push 挂死/超时后常见「本地有、远端无」
67
+ echo "[apm] ensure_feature_branch: 再次 ls-remote 确认远程是否已有 ${branch} …"
52
68
  if ! git ls-remote --heads origin "$branch" | grep -q .; then
53
69
  echo "[apm] 远程尚无 ${branch},正在 push …"
54
70
  bash "$SCRIPT_DIR/push_code.sh" "$branch"
71
+ else
72
+ echo "[apm] ensure_feature_branch: 远程已有 ${branch},跳过 push"
55
73
  fi
56
74
 
57
75
  echo "[apm] 已就绪分支 ${branch}"
@@ -17,5 +17,8 @@ if ! git config --get remote.origin.url >/dev/null 2>&1; then
17
17
  exit 2
18
18
  fi
19
19
 
20
+ origin_url="$(git config --get remote.origin.url)"
21
+ echo "[apm] push: git push -u origin ${branch}(${origin_url})…"
20
22
  # 显式 origin + -u:避免裸 git push 因 upstream 与当前分支名不一致失败
21
23
  git push -u origin "$branch"
24
+ echo "[apm] push: 完成 ${branch}"
@@ -359,7 +359,7 @@ async function ensureWorkspaceApmDirForInit(cwd = resolveWorkdirPath()) {
359
359
  }
360
360
 
361
361
  // src/utils/git.ts
362
- import { execFile } from "child_process";
362
+ import { execFile, spawn } from "child_process";
363
363
  import { dirname, join as join3 } from "path";
364
364
  import { fileURLToPath } from "url";
365
365
  import { promisify } from "util";
@@ -428,7 +428,9 @@ async function execGit(cwd, args, quietOrOptions = false) {
428
428
  const e = err;
429
429
  if (e.killed || e.signal === "SIGTERM" || e.code === "ETIMEDOUT") {
430
430
  throw new Error(
431
- `[apm] git ${args.join(" ")} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
431
+ `[apm] git ${args.join(
432
+ " "
433
+ )} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
432
434
  );
433
435
  }
434
436
  const detail = (e.stderr ?? e.message ?? String(err)).trim();
@@ -458,33 +460,67 @@ async function runGitScript(name, options) {
458
460
  const timeoutMs = options.timeoutMs ?? GIT_NETWORK_TIMEOUT_MS;
459
461
  const script = resolveGitScript(name);
460
462
  const args = [script, ...options.args ?? []];
461
- try {
462
- const { stdout, stderr } = await execFileAsync("bash", args, {
463
+ return new Promise((resolve7, reject) => {
464
+ const child = spawn("bash", args, {
463
465
  cwd: options.cwd,
464
466
  env: gitExecEnv(),
465
- encoding: "utf8",
466
- maxBuffer: 10 * 1024 * 1024,
467
- timeout: timeoutMs,
468
- killSignal: "SIGTERM"
467
+ stdio: ["ignore", "pipe", "pipe"]
469
468
  });
470
- if (stdout.trim()) process.stdout.write(stdout);
471
- if (stderr.trim()) process.stderr.write(stderr);
472
- return true;
473
- } catch (err) {
474
- const e = err;
475
- if (e.stdout?.trim()) process.stdout.write(e.stdout);
476
- if (e.stderr?.trim()) process.stderr.write(e.stderr);
477
- if (e.code === skipCode) {
478
- return false;
479
- }
480
- if (e.killed || e.signal === "SIGTERM" || e.code === "ETIMEDOUT") {
481
- throw new Error(
482
- `[apm] ${name} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH\uFF1B\u53EF\u7A0D\u540E\u91CD\u8BD5`
483
- );
484
- }
485
- const detail = (e.stderr ?? e.message ?? String(err)).trim();
486
- throw new Error(`[apm] ${name} \u5931\u8D25${detail ? `: ${detail}` : ""}`);
487
- }
469
+ let stderrBuf = "";
470
+ let settled = false;
471
+ let timedOut = false;
472
+ const timer = timeoutMs > 0 ? setTimeout(() => {
473
+ timedOut = true;
474
+ child.kill("SIGTERM");
475
+ const forceKill = setTimeout(() => {
476
+ child.kill("SIGKILL");
477
+ }, 2e3);
478
+ forceKill.unref();
479
+ }, timeoutMs) : null;
480
+ const finish = (fn) => {
481
+ if (settled) return;
482
+ settled = true;
483
+ if (timer) clearTimeout(timer);
484
+ fn();
485
+ };
486
+ child.stdout?.on("data", (chunk) => {
487
+ process.stdout.write(chunk);
488
+ });
489
+ child.stderr?.on("data", (chunk) => {
490
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
491
+ stderrBuf += text;
492
+ process.stderr.write(chunk);
493
+ });
494
+ child.on("error", (err) => {
495
+ finish(() => reject(new Error(`[apm] ${name} \u542F\u52A8\u5931\u8D25: ${err.message}`)));
496
+ });
497
+ child.on("close", (code, signal) => {
498
+ finish(() => {
499
+ if (timedOut || signal === "SIGTERM" || signal === "SIGKILL") {
500
+ reject(
501
+ new Error(
502
+ `[apm] ${name} \u8D85\u65F6\uFF08>${timeoutMs}ms\uFF09\uFF0C\u8BF7\u770B\u4E0A\u65B9\u6700\u540E\u4E00\u6B65\u65E5\u5FD7\u5B9A\u4F4D\u5361\u70B9\uFF08\u591A\u4E3A ls-remote/fetch/push\uFF09\uFF1B\u68C0\u67E5\u5230\u8FDC\u7A0B\u7684\u7F51\u7EDC/SSH \u540E\u53EF\u7A0D\u540E\u91CD\u8BD5`
503
+ )
504
+ );
505
+ return;
506
+ }
507
+ if (code === skipCode) {
508
+ resolve7(false);
509
+ return;
510
+ }
511
+ if (code === 0) {
512
+ resolve7(true);
513
+ return;
514
+ }
515
+ const detail = stderrBuf.trim();
516
+ reject(
517
+ new Error(
518
+ `[apm] ${name} \u5931\u8D25${detail ? `: ${detail}` : code != null ? ` (exit ${code})` : ""}`
519
+ )
520
+ );
521
+ });
522
+ });
523
+ });
488
524
  }
489
525
  async function branchHasChangesAgainstBase(cwd, baseBranch, headBranch) {
490
526
  return runGitScript("branch_has_changes.sh", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "8.1.27",
3
+ "version": "8.1.28",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,