aodw-skill 0.7.3

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.
Files changed (188) hide show
  1. package/.aodw/01-core/ai-interaction-rules.md +218 -0
  2. package/.aodw/01-core/ai-knowledge-rules.md +302 -0
  3. package/.aodw/01-core/ai-project-overview-rules.md +284 -0
  4. package/.aodw/01-core/aodw-constitution-summary.md +20 -0
  5. package/.aodw/01-core/aodw-constitution.md +419 -0
  6. package/.aodw/01-core/csf-thinking-framework.md +373 -0
  7. package/.aodw/01-core/git-discipline.md +226 -0
  8. package/.aodw/01-core/module-doc-rules.md +90 -0
  9. package/.aodw/02-workflow/aodw-development-stages.md +235 -0
  10. package/.aodw/02-workflow/rt-id-generation-rules.md +267 -0
  11. package/.aodw/02-workflow/rt-manager-summary.md +15 -0
  12. package/.aodw/02-workflow/rt-manager.md +399 -0
  13. package/.aodw/02-workflow/spec-full-profile-summary.md +13 -0
  14. package/.aodw/02-workflow/spec-full-profile.md +391 -0
  15. package/.aodw/02-workflow/spec-lite-profile.md +313 -0
  16. package/.aodw/02-workflow/ui-workflow-rules.md +334 -0
  17. package/.aodw/03-standards/ai-coding-rules-common.md +89 -0
  18. package/.aodw/03-standards/ai-coding-rules.md +370 -0
  19. package/.aodw/03-standards/stacks/java-springboot/ai-coding-rules-backend.md +100 -0
  20. package/.aodw/03-standards/stacks/python-fastapi/ai-coding-rules-backend.md +612 -0
  21. package/.aodw/03-standards/stacks/react-typescript/ai-coding-rules-frontend.md +291 -0
  22. package/.aodw/03-standards/stacks/vue2/ai-coding-rules-frontend.md +97 -0
  23. package/.aodw/03-standards/ui-kit/ui-kit.md +163 -0
  24. package/.aodw/04-auditors/aodw-development-auditor-rules.md +470 -0
  25. package/.aodw/04-auditors/aodw-full-auditor-rules.md +365 -0
  26. package/.aodw/04-auditors/aodw-requirement-auditor-rules.md +408 -0
  27. package/.aodw/05-tooling/ai-tools-init-rules.md +465 -0
  28. package/.aodw/06-project/ai-overview.md +116 -0
  29. package/.aodw/06-project/modules-index.yaml +11 -0
  30. package/.aodw/07-optimization/token-usage-analysis.md +253 -0
  31. package/.aodw/README.md +26 -0
  32. package/.aodw/RELEASE-CHECKLIST.md +144 -0
  33. package/.aodw/config.yaml +2 -0
  34. package/.aodw/manifest.yaml +98 -0
  35. package/.aodw/templates/SOURCE-TO-DISTRIBUTION-GUIDE.md +276 -0
  36. package/.aodw/templates/TEMPLATE-APPLICATION-GUIDE.md +246 -0
  37. package/.aodw/templates/aodw-kernel-loader-template.md +70 -0
  38. package/.aodw/templates/audit-report-template.md +232 -0
  39. package/.aodw/templates/changelog-template.md +16 -0
  40. package/.aodw/templates/checklists/coding-standards-template.md +110 -0
  41. package/.aodw/templates/csf-review-template.md +201 -0
  42. package/.aodw/templates/impact-template.md +17 -0
  43. package/.aodw/templates/invariants-template.md +12 -0
  44. package/.aodw/templates/module-readme-template.md +39 -0
  45. package/.aodw/templates/plan-lite-template.md +11 -0
  46. package/.aodw/templates/rt-decision-template.md +13 -0
  47. package/.aodw/templates/rt-intake-template.md +33 -0
  48. package/.aodw/templates/rt-meta-template.yaml +43 -0
  49. package/.aodw/templates/spec-lite-template.md +17 -0
  50. package/.aodw/templates/tests-template.md +13 -0
  51. package/.aodw/templates/tools-config/README.md +80 -0
  52. package/.aodw/templates/tools-config/backend/black.config.template.toml +6 -0
  53. package/.aodw/templates/tools-config/backend/pre-commit.config.template.yaml +16 -0
  54. package/.aodw/templates/tools-config/backend/ruff.config.template.toml +23 -0
  55. package/.aodw/templates/tools-config/frontend/eslint.config.template.json +113 -0
  56. package/.aodw/templates/tools-config/frontend/prettier.config.template.json +10 -0
  57. package/.aodw/templates/tools-config/frontend/tsconfig.paths.template.json +11 -0
  58. package/.aodw/workflow-guide.md +51 -0
  59. package/AODW_Adapters/README.md +143 -0
  60. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-check.md +7 -0
  61. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-done.md +7 -0
  62. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-full.md +7 -0
  63. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-governance.md +7 -0
  64. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-impact.md +7 -0
  65. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-init.md +7 -0
  66. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-invariants.md +7 -0
  67. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-lite.md +7 -0
  68. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-module.md +7 -0
  69. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-new.md +7 -0
  70. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-open.md +7 -0
  71. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-pause.md +7 -0
  72. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-resume.md +7 -0
  73. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-tests.md +7 -0
  74. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw-upgrade.md +7 -0
  75. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/.agent/rules/aodw.md +35 -0
  76. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-check.md +16 -0
  77. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-done.md +16 -0
  78. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-full.md +14 -0
  79. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-governance.md +13 -0
  80. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-impact.md +13 -0
  81. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-init.md +13 -0
  82. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-invariants.md +13 -0
  83. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-lite.md +14 -0
  84. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-module.md +13 -0
  85. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-new.md +30 -0
  86. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-open.md +10 -0
  87. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-pause.md +12 -0
  88. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-resume.md +12 -0
  89. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-tests.md +13 -0
  90. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw-upgrade.md +12 -0
  91. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/antigravity/global_workflows/aodw.md +18 -0
  92. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/claude/CLAUDE.md +17 -0
  93. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-check.md +30 -0
  94. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-done.md +52 -0
  95. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-full.md +31 -0
  96. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-governance.md +34 -0
  97. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-impact.md +25 -0
  98. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-init.md +75 -0
  99. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-invariants.md +29 -0
  100. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-lite.md +23 -0
  101. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-module.md +24 -0
  102. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-new.md +70 -0
  103. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-open.md +19 -0
  104. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-pause.md +19 -0
  105. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-resume.md +20 -0
  106. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-tests.md +26 -0
  107. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw-upgrade.md +27 -0
  108. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/aodw.md +69 -0
  109. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/deploypromote.md +20 -0
  110. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/commands/featuretotester.md +32 -0
  111. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/deploy/feature_to_master_push_test_local.sh +390 -0
  112. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/deploy/promote_only.sh +210 -0
  113. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/deploy/rollback_prod.sh +99 -0
  114. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/cursor/.cursor/rules/aodw.mdc +26 -0
  115. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-check.md +29 -0
  116. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-done.md +52 -0
  117. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-full.md +30 -0
  118. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-governance.md +33 -0
  119. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-impact.md +24 -0
  120. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-init.md +75 -0
  121. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-invariants.md +28 -0
  122. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-lite.md +22 -0
  123. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-module.md +23 -0
  124. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-new.md +92 -0
  125. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-open.md +18 -0
  126. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-pause.md +18 -0
  127. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-resume.md +19 -0
  128. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-tests.md +25 -0
  129. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw-upgrade.md +26 -0
  130. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/.agent/rules/aodw.md +68 -0
  131. package/AODW_Adapters/_backup/v3.1.0-pre-refactor/gemini/GEMINI.md +17 -0
  132. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-analyze.md +15 -0
  133. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-complete.md +15 -0
  134. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-control.md +14 -0
  135. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-decide.md +16 -0
  136. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-governance.md +7 -0
  137. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-implement.md +16 -0
  138. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-init.md +7 -0
  139. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-intake.md +15 -0
  140. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-open.md +7 -0
  141. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-simplified.md +107 -0
  142. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/.agent/rules/aodw-verify.md +14 -0
  143. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-analyze.md +24 -0
  144. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-complete.md +23 -0
  145. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-control.md +21 -0
  146. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-decide.md +26 -0
  147. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-governance.md +13 -0
  148. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-implement.md +21 -0
  149. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-init.md +13 -0
  150. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-intake.md +28 -0
  151. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-open.md +10 -0
  152. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw-verify.md +20 -0
  153. package/AODW_Adapters/_backup/v4.0.0-simplified/antigravity/global_workflows/aodw.md +18 -0
  154. package/AODW_Adapters/antigravity/.agent/rules/aodw.md +74 -0
  155. package/AODW_Adapters/claude/CLAUDE.md +70 -0
  156. package/AODW_Adapters/cursor/.cursor/commands/README.md +37 -0
  157. package/AODW_Adapters/cursor/.cursor/rules/aodw.mdc +77 -0
  158. package/AODW_Adapters/gemini/.agent/rules/aodw-analyze.md +15 -0
  159. package/AODW_Adapters/gemini/.agent/rules/aodw-complete.md +15 -0
  160. package/AODW_Adapters/gemini/.agent/rules/aodw-control.md +14 -0
  161. package/AODW_Adapters/gemini/.agent/rules/aodw-decide.md +16 -0
  162. package/AODW_Adapters/gemini/.agent/rules/aodw-governance.md +33 -0
  163. package/AODW_Adapters/gemini/.agent/rules/aodw-implement.md +16 -0
  164. package/AODW_Adapters/gemini/.agent/rules/aodw-init.md +75 -0
  165. package/AODW_Adapters/gemini/.agent/rules/aodw-intake.md +15 -0
  166. package/AODW_Adapters/gemini/.agent/rules/aodw-open.md +18 -0
  167. package/AODW_Adapters/gemini/.agent/rules/aodw-verify.md +14 -0
  168. package/AODW_Adapters/gemini/.agent/rules/aodw.md +70 -0
  169. package/AODW_Adapters/gemini/GEMINI.md +17 -0
  170. package/AODW_Adapters/general/.github/copilot-instructions.md +34 -0
  171. package/AODW_Adapters/general/AGENTS.md +70 -0
  172. package/README.md +118 -0
  173. package/bin/aodw.js +627 -0
  174. package/bin/commands/init-overview.js +801 -0
  175. package/bin/commands/init-tools.js +811 -0
  176. package/bin/commands/new.js +235 -0
  177. package/bin/commands/serve.js +79 -0
  178. package/bin/processors/index.js +109 -0
  179. package/bin/update-adapters-from-template.js +89 -0
  180. package/bin/utils/config.js +56 -0
  181. package/docs/README.md +26 -0
  182. package/docs/adapter-evaluation.md +55 -0
  183. package/docs/backend-guidelines.md +335 -0
  184. package/docs/frontend-guidelines.md +266 -0
  185. package/docs/installation-variants.md +88 -0
  186. package/docs/migration-guide-0.2.0.md +250 -0
  187. package/docs/platform-matrix.md +83 -0
  188. package/package.json +40 -0
@@ -0,0 +1,390 @@
1
+ #!/usr/bin/env bash
2
+ # 只处理【本地存在、远端不存在】的当前功能分支:
3
+ # 1) 合并到本地主干(master/main,取决于配置)
4
+ # 2) 推送主干到测试环境(server/master)
5
+ # 3) 删除本地功能分支
6
+ # 不触碰任何其他本地功能分支;不尝试任何远端功能分支操作。
7
+ set -euo pipefail
8
+
9
+ CONFIG_FILE="${CONFIG_FILE:-project-config.json}"
10
+
11
+ # 可选开关
12
+ MERGE_NO_FF=${MERGE_NO_FF:-true} # true: --no-ff 保留合并记录;false: --ff-only
13
+ DELETE_LOCAL_FEATURE=${DELETE_LOCAL_FEATURE:-true} # 是否删除本地功能分支
14
+
15
+ need() { command -v "$1" >/dev/null 2>&1 || { echo "❌ 缺少依赖:$1"; exit 1; }; }
16
+ need git
17
+ need jq
18
+
19
+ echo "🚀 本地功能分支 → master → 推送测试(只处理当前分支)"
20
+
21
+ # ========== 错误处理和回滚机制 ==========
22
+ # 保存关键状态
23
+ ORIGINAL_BRANCH=""
24
+ ORIGINAL_COMMIT=""
25
+ MERGE_COMMIT=""
26
+ STASHED=0
27
+ ROLLBACK_NEEDED=false
28
+ PUSHED_TO_REMOTE=false
29
+ ERROR_OCCURRED=false
30
+
31
+ # 错误处理函数
32
+ handle_error() {
33
+ # 防止错误处理函数本身出错导致无限循环
34
+ if [ "$ERROR_OCCURRED" = "true" ]; then
35
+ echo "❌ 错误处理过程中再次发生错误,强制退出"
36
+ exit 1
37
+ fi
38
+ ERROR_OCCURRED=true
39
+
40
+ local exit_code=${1:-$?}
41
+ local line_number=${2:-$LINENO}
42
+ local error_message="${3:-未知错误}"
43
+
44
+ echo ""
45
+ echo "❌ ========================================"
46
+ echo "❌ 脚本执行失败!"
47
+ echo "❌ ========================================"
48
+ echo "❌ 错误位置:第 ${line_number} 行"
49
+ echo "❌ 错误信息:${error_message}"
50
+ echo "❌ 退出码:${exit_code}"
51
+ echo ""
52
+
53
+ # 执行回滚
54
+ if [ "$ROLLBACK_NEEDED" = "true" ]; then
55
+ echo "🔄 开始执行回滚..."
56
+ rollback_changes
57
+ else
58
+ echo "ℹ️ 无需回滚(未执行关键操作)"
59
+ fi
60
+
61
+ # 恢复工作状态
62
+ restore_work_state
63
+
64
+ echo ""
65
+ echo "❌ ========================================"
66
+ echo "❌ 脚本执行失败,请检查错误信息并手动处理"
67
+ echo "❌ ========================================"
68
+ echo ""
69
+ exit $exit_code
70
+ }
71
+
72
+ # 回滚函数
73
+ rollback_changes() {
74
+ echo "🔄 执行回滚操作..."
75
+
76
+ # 如果已经合并,尝试撤销合并
77
+ if [ -n "$MERGE_COMMIT" ] && [ "$MERGE_COMMIT" != "" ] && [ -n "$ORIGINAL_COMMIT" ]; then
78
+ echo "🔄 撤销合并提交:$MERGE_COMMIT"
79
+ echo "🔄 回滚到提交:$ORIGINAL_COMMIT"
80
+ set +e
81
+ git reset --hard "$ORIGINAL_COMMIT" 2>/dev/null
82
+ RESET_RESULT=$?
83
+ set -e
84
+
85
+ if [ $RESET_RESULT -eq 0 ]; then
86
+ echo "✅ 已成功撤销合并"
87
+ else
88
+ echo "⚠️ 无法自动撤销合并,请手动执行:"
89
+ echo " git reset --hard $ORIGINAL_COMMIT"
90
+ fi
91
+ fi
92
+
93
+ # 如果已经推送到远程,提示用户
94
+ if [ "$PUSHED_TO_REMOTE" = "true" ]; then
95
+ echo ""
96
+ echo "⚠️ ========================================"
97
+ echo "⚠️ 重要提示:代码已推送到远程仓库"
98
+ echo "⚠️ ========================================"
99
+ echo "⚠️ 如果需要回滚远程分支,请手动执行:"
100
+ if [ -n "$ORIGINAL_COMMIT" ]; then
101
+ echo " git push $REMOTE_NAME $ORIGINAL_COMMIT:$REMOTE_MASTER --force"
102
+ else
103
+ echo " # 请先确定要回滚到的提交,然后执行强制推送"
104
+ fi
105
+ echo "⚠️ ========================================"
106
+ echo ""
107
+ fi
108
+ }
109
+
110
+ # 恢复工作状态
111
+ restore_work_state() {
112
+ echo "🔙 恢复工作状态..."
113
+
114
+ # 切回原分支
115
+ if [ -n "$ORIGINAL_BRANCH" ] && [ "$ORIGINAL_BRANCH" != "" ]; then
116
+ echo "🔙 尝试切回原分支:$ORIGINAL_BRANCH"
117
+ set +e
118
+ git checkout "$ORIGINAL_BRANCH" >/dev/null 2>&1
119
+ CHECKOUT_RESULT=$?
120
+ set -e
121
+
122
+ if [ $CHECKOUT_RESULT -eq 0 ]; then
123
+ echo "✅ 已切回原分支:$ORIGINAL_BRANCH"
124
+ else
125
+ echo "⚠️ 无法切回原分支,当前分支:$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo '未知')"
126
+ fi
127
+ fi
128
+
129
+ # 恢复暂存的修改
130
+ if [ $STASHED -eq 1 ]; then
131
+ echo "📥 恢复暂存的修改..."
132
+ set +e
133
+ git stash pop >/dev/null 2>&1
134
+ STASH_RESULT=$?
135
+ set -e
136
+
137
+ if [ $STASH_RESULT -eq 0 ]; then
138
+ echo "✅ 已恢复暂存的修改"
139
+ else
140
+ echo "⚠️ 恢复暂存时发生冲突,请手动处理:"
141
+ echo " git stash list"
142
+ echo " git stash pop"
143
+ fi
144
+ fi
145
+ }
146
+
147
+ # 注册错误处理(使用 EXIT 而不是 ERR,因为 ERR 在 set -e 时可能不会触发)
148
+ trap 'if [ $? -ne 0 ] && [ "$ERROR_OCCURRED" != "true" ]; then handle_error $? $LINENO "脚本执行过程中发生错误"; fi' EXIT
149
+ trap 'handle_error 130 $LINENO "脚本被中断(Ctrl+C)"' INT TERM
150
+
151
+ # ========== 首次初始化配置 ==========
152
+ if [ ! -f "$CONFIG_FILE" ]; then
153
+ echo "🧩 首次运行,初始化配置文件:$CONFIG_FILE"
154
+ read -r -p "本地主干分支名(默认 master): " project_branch
155
+ project_branch=${project_branch:-master}
156
+
157
+ read -r -p "远程名(默认 server): " remote_name
158
+ remote_name=${remote_name:-server}
159
+
160
+ read -r -p "服务器测试分支名(默认 master): " remote_master
161
+ remote_master=${remote_master:-master}
162
+
163
+ read -r -p "服务器生产分支名(默认 release): " remote_release
164
+ remote_release=${remote_release:-release}
165
+
166
+ # 与 promote_only.sh 保持同一套字段(方便后续发布)
167
+ read -r -p "是否使用 QA 标签发布?(y/N 默认否): " use_qa
168
+ use_qa_tag=false
169
+ if [[ "$use_qa" == "y" || "$use_qa" == "Y" ]]; then
170
+ use_qa_tag=true
171
+ fi
172
+ read -r -p "QA 标签前缀(默认 qa-ok/): " qa_tag_prefix
173
+ qa_tag_prefix=${qa_tag_prefix:-qa-ok/}
174
+ read -r -p "生产标签前缀(默认 prod-): " prod_tag_prefix
175
+ prod_tag_prefix=${prod_tag_prefix:-prod-}
176
+
177
+ cat > "$CONFIG_FILE" <<JSON
178
+ {
179
+ "project_branch": "$project_branch",
180
+ "remote_name": "$remote_name",
181
+ "remote_master": "$remote_master",
182
+ "remote_release": "$remote_release",
183
+ "use_qa_tag": $use_qa_tag,
184
+ "qa_tag_prefix": "$qa_tag_prefix",
185
+ "prod_tag_prefix": "$prod_tag_prefix"
186
+ }
187
+ JSON
188
+ echo "✅ 已创建配置文件:$CONFIG_FILE"
189
+ fi
190
+
191
+ # ========== 读取配置 ==========
192
+ PROJECT_BRANCH=$(jq -r '.project_branch' "$CONFIG_FILE")
193
+ REMOTE_NAME=$(jq -r '.remote_name' "$CONFIG_FILE")
194
+ REMOTE_MASTER=$(jq -r '.remote_master' "$CONFIG_FILE")
195
+
196
+ # 验证配置是否读取成功
197
+ if [ -z "$PROJECT_BRANCH" ] || [ -z "$REMOTE_NAME" ] || [ -z "$REMOTE_MASTER" ]; then
198
+ handle_error 1 $LINENO "配置读取失败,请检查 $CONFIG_FILE。确保配置文件中包含 project_branch、remote_name 和 remote_master 字段"
199
+ fi
200
+
201
+ # 验证配置值是否有效
202
+ if [ "$PROJECT_BRANCH" = "null" ] || [ "$REMOTE_NAME" = "null" ] || [ "$REMOTE_MASTER" = "null" ]; then
203
+ handle_error 1 $LINENO "配置值无效(null),请检查 $CONFIG_FILE"
204
+ fi
205
+
206
+ echo "📦 配置:local=${PROJECT_BRANCH:-未定义} remote=${REMOTE_NAME:-未定义} test=${REMOTE_MASTER:-未定义}"
207
+
208
+ # ========== 确定"当前功能分支" ==========
209
+ CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo HEAD)"
210
+ if [ "$CURRENT_BRANCH" = "HEAD" ]; then
211
+ handle_error 1 $LINENO "无法确定当前 Git 分支,请确保在 Git 仓库中运行此脚本"
212
+ fi
213
+
214
+ FEATURE_BRANCH="$CURRENT_BRANCH"
215
+ ORIGINAL_BRANCH="$CURRENT_BRANCH"
216
+
217
+ # 保存当前提交(用于回滚)
218
+ ORIGINAL_COMMIT="$(git rev-parse HEAD 2>/dev/null)"
219
+ if [ -z "$ORIGINAL_COMMIT" ]; then
220
+ handle_error 1 $LINENO "无法获取当前提交哈希,请确保在有效的 Git 仓库中运行"
221
+ fi
222
+
223
+ # 如果当前已经在主干分支,跳过合并,直接推送
224
+ SKIP_MERGE=false
225
+ if [ "$FEATURE_BRANCH" = "$PROJECT_BRANCH" ]; then
226
+ echo "ℹ️ 当前分支就是主干(${PROJECT_BRANCH:-未定义}),跳过合并步骤,直接推送"
227
+ SKIP_MERGE=true
228
+ fi
229
+
230
+ # ========== 保护现场(stash) ==========
231
+ if [ "$SKIP_MERGE" = "false" ]; then
232
+ if ! git diff --quiet || ! git diff --cached --quiet; then
233
+ echo "💾 暂存当前未提交修改..."
234
+ git add -A
235
+ if ! git stash push -m "auto-stash-before-feature-merge $(date +%F_%T)"; then
236
+ handle_error $? $LINENO "暂存未提交修改失败:git stash push"
237
+ fi
238
+ STASHED=1
239
+ fi
240
+
241
+ # ========== 更新本地主干,减少合并冲突 ==========
242
+ echo "🔄 同步本地主干 ${PROJECT_BRANCH:-未定义} ..."
243
+ if ! git fetch "$REMOTE_NAME" --prune; then
244
+ handle_error $? $LINENO "获取远程仓库信息失败:git fetch ${REMOTE_NAME:-未定义} --prune。请检查网络连接和远程配置"
245
+ fi
246
+
247
+ if ! git checkout "$PROJECT_BRANCH"; then
248
+ handle_error $? $LINENO "切换到主干分支失败:git checkout ${PROJECT_BRANCH:-未定义}"
249
+ fi
250
+
251
+ # 保存合并前的提交(用于回滚)
252
+ ORIGINAL_COMMIT="$(git rev-parse HEAD 2>/dev/null)"
253
+ if [ -z "$ORIGINAL_COMMIT" ]; then
254
+ handle_error 1 $LINENO "无法获取主干分支的提交哈希"
255
+ fi
256
+
257
+ if ! git pull "$REMOTE_NAME" "$PROJECT_BRANCH"; then
258
+ handle_error $? $LINENO "拉取远程主干分支失败:git pull ${REMOTE_NAME:-未定义} ${PROJECT_BRANCH:-未定义}。可能存在冲突,请手动解决"
259
+ fi
260
+
261
+ # ========== 合并当前功能分支到主干 ==========
262
+ echo "🔁 合并 ${FEATURE_BRANCH:-未定义} → ${PROJECT_BRANCH:-未定义}"
263
+ set +e
264
+ if [ "$MERGE_NO_FF" = "true" ]; then
265
+ git merge --no-ff --no-edit "$FEATURE_BRANCH"
266
+ M=$?
267
+ else
268
+ git merge --ff-only "$FEATURE_BRANCH"
269
+ M=$?
270
+ fi
271
+ set -e
272
+
273
+ if [ $M -ne 0 ]; then
274
+ handle_error $M $LINENO "合并失败或冲突:git merge ${FEATURE_BRANCH:-未定义} → ${PROJECT_BRANCH:-未定义}。请解决冲突后重试"
275
+ fi
276
+
277
+ # 保存合并后的提交(用于回滚)
278
+ MERGE_COMMIT="$(git rev-parse HEAD 2>/dev/null)"
279
+ if [ -z "$MERGE_COMMIT" ]; then
280
+ handle_error 1 $LINENO "合并后无法获取提交哈希"
281
+ fi
282
+ ROLLBACK_NEEDED=true
283
+
284
+ echo "✅ 合并成功,合并提交:$MERGE_COMMIT"
285
+ else
286
+ # 已经在主干分支,只需要更新即可
287
+ echo "🔄 同步本地主干 ${PROJECT_BRANCH:-未定义} ..."
288
+ if ! git fetch "$REMOTE_NAME" --prune; then
289
+ handle_error $? $LINENO "获取远程仓库信息失败:git fetch ${REMOTE_NAME:-未定义} --prune。请检查网络连接和远程配置"
290
+ fi
291
+
292
+ if ! git pull "$REMOTE_NAME" "$PROJECT_BRANCH"; then
293
+ handle_error $? $LINENO "拉取远程主干分支失败:git pull ${REMOTE_NAME:-未定义} ${PROJECT_BRANCH:-未定义}。可能存在冲突,请手动解决"
294
+ fi
295
+
296
+ # 保存当前提交(用于回滚)
297
+ ORIGINAL_COMMIT="$(git rev-parse HEAD 2>/dev/null)"
298
+ if [ -z "$ORIGINAL_COMMIT" ]; then
299
+ handle_error 1 $LINENO "无法获取当前提交哈希"
300
+ fi
301
+ fi
302
+
303
+ # ========== 推送主干到测试环境 ==========
304
+ # 确保变量已设置(防止在合并过程中丢失)
305
+ if [ -z "$REMOTE_MASTER" ] || [ "$REMOTE_MASTER" = "null" ]; then
306
+ REMOTE_MASTER=$(jq -r '.remote_master' "$CONFIG_FILE")
307
+ if [ -z "$REMOTE_MASTER" ] || [ "$REMOTE_MASTER" = "null" ]; then
308
+ handle_error 1 $LINENO "无法从配置文件读取 remote_master,请检查 $CONFIG_FILE"
309
+ fi
310
+ fi
311
+ if [ -z "$REMOTE_NAME" ] || [ "$REMOTE_NAME" = "null" ]; then
312
+ REMOTE_NAME=$(jq -r '.remote_name' "$CONFIG_FILE")
313
+ if [ -z "$REMOTE_NAME" ] || [ "$REMOTE_NAME" = "null" ]; then
314
+ handle_error 1 $LINENO "无法从配置文件读取 remote_name,请检查 $CONFIG_FILE"
315
+ fi
316
+ fi
317
+
318
+ # 验证远程仓库是否存在
319
+ if ! git remote get-url "$REMOTE_NAME" >/dev/null 2>&1; then
320
+ handle_error 1 $LINENO "远程仓库 '${REMOTE_NAME:-未定义}' 不存在,请检查 Git 远程配置:git remote -v"
321
+ fi
322
+
323
+ echo "⏫ 推送 ${PROJECT_BRANCH:-未定义} → ${REMOTE_NAME:-未定义}/${REMOTE_MASTER:-未定义}(测试环境)"
324
+ if ! git push "$REMOTE_NAME" "$PROJECT_BRANCH:$REMOTE_MASTER"; then
325
+ handle_error $? $LINENO "推送到远程失败:git push ${REMOTE_NAME:-未定义} ${PROJECT_BRANCH:-未定义}:${REMOTE_MASTER:-未定义}。请检查网络连接和权限"
326
+ fi
327
+
328
+ # 推送成功后,标记需要回滚(如果后续步骤失败)
329
+ PUSHED_TO_REMOTE=true
330
+ ROLLBACK_NEEDED=true
331
+
332
+ # ========== 删除本地功能分支(仅本地!不动远端) ==========
333
+ if [ "$SKIP_MERGE" = "false" ] && [ "$DELETE_LOCAL_FEATURE" = "true" ]; then
334
+ echo "🧹 删除本地功能分支:$FEATURE_BRANCH"
335
+ # 先确保当前不在该分支
336
+ if ! git checkout "$PROJECT_BRANCH"; then
337
+ # 删除分支失败不是致命错误,但需要记录
338
+ echo "⚠️ 切换到主干分支失败(删除功能分支前),跳过删除操作"
339
+ else
340
+ set +e
341
+ git branch -d "$FEATURE_BRANCH" 2>/dev/null
342
+ DELETE_RESULT=$?
343
+ set -e
344
+
345
+ if [ $DELETE_RESULT -ne 0 ]; then
346
+ echo "⚠️ 无法安全删除(可能未完全合并),执行强制删除"
347
+ set +e
348
+ git branch -D "$FEATURE_BRANCH" 2>/dev/null
349
+ DELETE_RESULT=$?
350
+ set -e
351
+ if [ $DELETE_RESULT -eq 0 ]; then
352
+ echo "✅ 已强制删除功能分支:$FEATURE_BRANCH"
353
+ else
354
+ echo "⚠️ 删除功能分支失败,但这不是致命错误,请手动删除:git branch -D $FEATURE_BRANCH"
355
+ fi
356
+ else
357
+ echo "✅ 已删除功能分支:$FEATURE_BRANCH"
358
+ fi
359
+ fi
360
+ elif [ "$SKIP_MERGE" = "false" ]; then
361
+ echo "ℹ️ 按配置保留本地功能分支:$FEATURE_BRANCH"
362
+ fi
363
+
364
+ # 所有操作成功,清除错误处理 trap(避免在正常退出时触发错误处理)
365
+ trap - EXIT INT TERM
366
+
367
+ # 清除回滚标记
368
+ ROLLBACK_NEEDED=false
369
+ PUSHED_TO_REMOTE=false
370
+
371
+ # 恢复工作状态(成功时)
372
+ if [ "$SKIP_MERGE" = "false" ] && [ -n "$ORIGINAL_BRANCH" ] && [ "$ORIGINAL_BRANCH" != "$PROJECT_BRANCH" ]; then
373
+ # 如果原分支还存在且不是主干,切回原分支
374
+ if git show-ref --verify --quiet "refs/heads/$ORIGINAL_BRANCH" 2>/dev/null; then
375
+ echo "🔙 切回原分支:$ORIGINAL_BRANCH"
376
+ git checkout "$ORIGINAL_BRANCH" >/dev/null 2>&1 || {
377
+ echo "⚠️ 无法切回原分支,但操作已成功完成"
378
+ }
379
+ fi
380
+ fi
381
+
382
+ echo ""
383
+ echo "✅ ========================================"
384
+ if [ "$SKIP_MERGE" = "true" ]; then
385
+ echo "✅ 完成:直接推送 ${PROJECT_BRANCH:-未定义} → 测试环境"
386
+ else
387
+ echo "✅ 完成:${FEATURE_BRANCH:-未定义} → ${PROJECT_BRANCH:-未定义} → 推送测试"
388
+ fi
389
+ echo "✅ ========================================"
390
+ echo ""
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ CONFIG_FILE="${CONFIG_FILE:-project-config.json}"
5
+
6
+ # ===== 工具检查 =====
7
+ need() { command -v "$1" >/dev/null 2>&1 || { echo "❌ 缺少依赖:$1"; exit 1; }; }
8
+ need git
9
+ need jq
10
+
11
+ echo "🚀 Promote (测试 → 生产) 自动发布脚本启动..."
12
+
13
+ # ===== 首次运行:初始化配置 =====
14
+ if [ ! -f "$CONFIG_FILE" ]; then
15
+ echo "🧩 首次运行,开始初始化配置文件:$CONFIG_FILE"
16
+ read -r -p "本地主干分支名(默认 master): " project_branch
17
+ project_branch=${project_branch:-master}
18
+
19
+ # 默认远程名改为 server
20
+ read -r -p "远程名(默认 server): " remote_name
21
+ remote_name=${remote_name:-server}
22
+
23
+ read -r -p "服务器测试分支名(默认 master): " remote_master
24
+ remote_master=${remote_master:-master}
25
+
26
+ read -r -p "服务器生产分支名(默认 release): " remote_release
27
+ remote_release=${remote_release:-release}
28
+
29
+ read -r -p "是否使用 QA 标签发布?(y/N 默认否): " use_qa
30
+ use_qa_tag=false
31
+ if [[ "$use_qa" == "y" || "$use_qa" == "Y" ]]; then
32
+ use_qa_tag=true
33
+ fi
34
+
35
+ read -r -p "QA 标签前缀(默认 qa-ok/): " qa_tag_prefix
36
+ qa_tag_prefix=${qa_tag_prefix:-qa-ok/}
37
+
38
+ read -r -p "生产标签前缀(默认 prod-): " prod_tag_prefix
39
+ prod_tag_prefix=${prod_tag_prefix:-prod-}
40
+
41
+ cat > "$CONFIG_FILE" <<JSON
42
+ {
43
+ "project_branch": "$project_branch",
44
+ "remote_name": "$remote_name",
45
+ "remote_master": "$remote_master",
46
+ "remote_release": "$remote_release",
47
+ "use_qa_tag": $use_qa_tag,
48
+ "qa_tag_prefix": "$qa_tag_prefix",
49
+ "prod_tag_prefix": "$prod_tag_prefix"
50
+ }
51
+ JSON
52
+ echo "✅ 已创建配置文件:$CONFIG_FILE"
53
+ fi
54
+
55
+ # ===== 读取配置 =====
56
+ PROJECT_BRANCH=$(jq -r '.project_branch' "$CONFIG_FILE")
57
+ REMOTE_NAME=$(jq -r '.remote_name' "$CONFIG_FILE")
58
+ REMOTE_MASTER=$(jq -r '.remote_master' "$CONFIG_FILE")
59
+ REMOTE_RELEASE=$(jq -r '.remote_release' "$CONFIG_FILE")
60
+ USE_QA_TAG=$(jq -r '.use_qa_tag' "$CONFIG_FILE")
61
+ QA_TAG_PREFIX=$(jq -r '.qa_tag_prefix' "$CONFIG_FILE")
62
+ PROD_TAG_PREFIX=$(jq -r '.prod_tag_prefix' "$CONFIG_FILE")
63
+
64
+ echo "📦 使用配置: local=$PROJECT_BRANCH, remote=$REMOTE_NAME, test=$REMOTE_MASTER, prod=$REMOTE_RELEASE"
65
+
66
+ # ===== 保存当前开发状态 =====
67
+ CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD || echo HEAD)"
68
+ STASHED=0
69
+ if ! git diff --quiet || ! git diff --cached --quiet; then
70
+ echo "💾 暂存本地未提交变更..."
71
+ git add -A
72
+ git stash push -m "auto-stash-before-promote $(date +%F_%T)"
73
+ STASHED=1
74
+ fi
75
+
76
+ # ===== 临时分支名称 =====
77
+ TMP="__tmp_release_promote__"
78
+
79
+ # ===== 清理函数:确保临时分支和文件被清理 =====
80
+ cleanup() {
81
+ local exit_code=$?
82
+ echo ""
83
+ echo "🧹 清理临时资源..."
84
+
85
+ # 切换到非临时分支(避免删除当前所在分支)
86
+ if [ "$(git rev-parse --abbrev-ref HEAD)" = "$TMP" ]; then
87
+ git checkout "$PROJECT_BRANCH" >/dev/null 2>&1 || git checkout "$CURRENT_BRANCH" >/dev/null 2>&1 || true
88
+ fi
89
+
90
+ # 删除临时分支(如果存在)
91
+ if git show-ref --verify --quiet "refs/heads/$TMP"; then
92
+ echo "🗑️ 删除临时分支: $TMP"
93
+ git branch -D "$TMP" >/dev/null 2>&1 || true
94
+ fi
95
+
96
+ # 清理可能存在的其他临时分支(兼容旧版本)
97
+ for old_tmp in tmp_release_sync __tmp_release_promote__; do
98
+ if git show-ref --verify --quiet "refs/heads/$old_tmp"; then
99
+ echo "🗑️ 删除遗留临时分支: $old_tmp"
100
+ git branch -D "$old_tmp" >/dev/null 2>&1 || true
101
+ fi
102
+ done
103
+
104
+ # 删除临时变更日志文件
105
+ rm -f .promote_changelog_*.txt || true
106
+
107
+ # 恢复原工作分支和暂存
108
+ restore
109
+
110
+ if [ $exit_code -ne 0 ]; then
111
+ echo "⚠️ 脚本执行失败(退出码: $exit_code)"
112
+ fi
113
+ exit $exit_code
114
+ }
115
+
116
+ restore() {
117
+ echo "🔙 恢复原工作分支: $CURRENT_BRANCH"
118
+ git checkout "$CURRENT_BRANCH" >/dev/null 2>&1 || true
119
+ if [ $STASHED -eq 1 ]; then
120
+ echo "📥 恢复暂存变更..."
121
+ git stash pop || echo "⚠️ 恢复发生冲突,请手动处理。"
122
+ fi
123
+ }
124
+
125
+ # 注册清理函数(在 EXIT、INT、TERM 信号时执行)
126
+ trap cleanup EXIT INT TERM
127
+
128
+ # ===== 脚本开始时清理可能存在的旧临时分支 =====
129
+ echo "🔍 检查并清理可能存在的旧临时分支..."
130
+ for old_tmp in tmp_release_sync __tmp_release_promote__; do
131
+ if git show-ref --verify --quiet "refs/heads/$old_tmp" 2>/dev/null; then
132
+ echo "🧹 发现遗留临时分支: $old_tmp,正在清理..."
133
+ if [ "$(git rev-parse --abbrev-ref HEAD)" = "$old_tmp" ]; then
134
+ git checkout "$PROJECT_BRANCH" >/dev/null 2>&1 || git checkout "$CURRENT_BRANCH" >/dev/null 2>&1 || true
135
+ fi
136
+ git branch -D "$old_tmp" >/dev/null 2>&1 || true
137
+ fi
138
+ done
139
+
140
+ # ===== 获取远端最新信息 =====
141
+ git fetch "$REMOTE_NAME" --prune
142
+
143
+ # ===== 确定测试基线(QA 标签或 HEAD) =====
144
+ if [ "$USE_QA_TAG" = "true" ]; then
145
+ TEST_BASE_COMMIT=$(git ls-remote --tags "$REMOTE_NAME" | awk -v pfx="refs/tags/$QA_TAG_PREFIX" '$2 ~ pfx {print $1 " " $2}' | sort -k2 | tail -n1 | awk '{print $1}')
146
+ [ -n "$TEST_BASE_COMMIT" ] || { echo "❌ 未找到任何 ${QA_TAG_PREFIX}* 标签"; exit 1; }
147
+ echo "✅ 使用 QA 标签作为基线: $TEST_BASE_COMMIT"
148
+ else
149
+ TEST_BASE_COMMIT=$(git ls-remote "$REMOTE_NAME" "$REMOTE_MASTER" | awk '{print $1}')
150
+ [ -n "$TEST_BASE_COMMIT" ] || { echo "❌ 获取 $REMOTE_NAME/$REMOTE_MASTER 失败"; exit 1; }
151
+ echo "✅ 使用 $REMOTE_NAME/$REMOTE_MASTER HEAD 作为基线: $TEST_BASE_COMMIT"
152
+ fi
153
+
154
+ # ===== 查找上一个生产标签 =====
155
+ LAST_PROD_TAG=$(git ls-remote --tags "$REMOTE_NAME" | awk -v pfx="refs/tags/$PROD_TAG_PREFIX" '$2 ~ pfx {print $2}' | sed 's#refs/tags/##' | sort | tail -n1 || true)
156
+ echo "ℹ️ 上一个生产标签: ${LAST_PROD_TAG:-<无>}"
157
+
158
+ # ===== 临时分支合并测试基线到生产 =====
159
+ # 检查远程 release 分支是否存在
160
+ if git ls-remote --heads "$REMOTE_NAME" "$REMOTE_RELEASE" | grep -q .; then
161
+ echo "📥 远程生产分支存在,基于远程分支创建临时分支..."
162
+ # 基于远程 release 分支创建临时分支
163
+ git checkout -B "$TMP" "$REMOTE_NAME/$REMOTE_RELEASE"
164
+ else
165
+ echo "📥 远程生产分支不存在,基于当前分支创建..."
166
+ git checkout -B "$TMP"
167
+ fi
168
+
169
+ set +e
170
+ git merge --no-ff --no-edit "$TEST_BASE_COMMIT"
171
+ MERGE_STATUS=$?
172
+ set -e
173
+ if [ $MERGE_STATUS -ne 0 ]; then
174
+ echo "❌ 合并冲突,请手动解决后再运行(保留在 $TMP)"
175
+ exit 1
176
+ fi
177
+
178
+ # ===== 生成标签 & 变更日志 =====
179
+ SHORTSHA=$(git rev-parse --short "$TEST_BASE_COMMIT")
180
+ NEW_PROD_TAG="${PROD_TAG_PREFIX}$(date +%Y%m%d-%H%M)-${SHORTSHA}"
181
+ CHANGELOG_FILE=".promote_changelog_${NEW_PROD_TAG}.txt"
182
+ if [ -n "$LAST_PROD_TAG" ]; then
183
+ git log --pretty=format:'- %h %s (%an, %ad)' --date=short "${LAST_PROD_TAG}..$TEST_BASE_COMMIT" > "$CHANGELOG_FILE"
184
+ else
185
+ git log --pretty=format:'- %h %s (%an, %ad)' --date=short "$TEST_BASE_COMMIT" > "$CHANGELOG_FILE"
186
+ fi
187
+ echo "📝 变更日志写入: $CHANGELOG_FILE"
188
+
189
+ # ===== 推送到生产分支 + 打标签 =====
190
+ echo "🚀 推送到生产分支: $REMOTE_RELEASE"
191
+ git push "$REMOTE_NAME" "$TMP:$REMOTE_RELEASE"
192
+
193
+ echo "🏷️ 推送生产标签: $NEW_PROD_TAG"
194
+ git tag -a "$NEW_PROD_TAG" -m "Promote $REMOTE_MASTER → $REMOTE_RELEASE
195
+ Base: $TEST_BASE_COMMIT
196
+ Changelog:
197
+ $(cat "$CHANGELOG_FILE")"
198
+ git push "$REMOTE_NAME" "$NEW_PROD_TAG"
199
+
200
+ # ===== 清理现场(cleanup 函数会自动处理,这里显式清理以确保及时释放) =====
201
+ if [ "$(git rev-parse --abbrev-ref HEAD)" = "$TMP" ]; then
202
+ git checkout "$PROJECT_BRANCH" >/dev/null 2>&1 || true
203
+ fi
204
+ if git show-ref --verify --quiet "refs/heads/$TMP" 2>/dev/null; then
205
+ git branch -D "$TMP" >/dev/null 2>&1 || true
206
+ fi
207
+ rm -f "$CHANGELOG_FILE" || true
208
+
209
+ echo "✅ 发布完成!新生产标签: $NEW_PROD_TAG"
210
+ echo "ℹ️ 回滚提示:可回滚到上一个生产标签:${LAST_PROD_TAG:-<无>}"