project-auto-wizard 0.14.0 → 0.15.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.
@@ -117,7 +117,7 @@ def _version_yml_path():
117
117
 
118
118
  def require_version_yml():
119
119
  if not _version_yml_path().is_file():
120
- log("ERROR: version.yml not found")
120
+ log(t("version_manager.err_yml_missing"))
121
121
  sys.exit(1)
122
122
 
123
123
 
@@ -238,7 +238,7 @@ def get_version_code():
238
238
  require_version_yml()
239
239
  code = read_scalar_key("version_code", None)
240
240
  if code is None or code == "" or code == "null":
241
- log("WARNING: version_code field missing, adding default value 1")
241
+ log(t("version_manager.warn_code_missing"))
242
242
  text = read_text()
243
243
  if re.search(r'^version:', text, re.MULTILINE):
244
244
  new_text = re.sub(
@@ -260,7 +260,7 @@ def set_version_code(new_code):
260
260
  if current not in (None, "", "null"):
261
261
  try:
262
262
  if int(new_code) < int(current):
263
- log(f"WARNING: version_code {new_code} is lower than current {current} — writing anyway (regression?)")
263
+ log(t("version_manager.warn_code_regress", new_code=new_code, current=current))
264
264
  except ValueError:
265
265
  pass
266
266
  text = read_text()
@@ -318,7 +318,7 @@ def get_higher_version(v1, v2):
318
318
  def update_version_yml(new_version):
319
319
  if not write_scalar_key("version", new_version):
320
320
  # Without the key nothing was written yet the call looked successful, so the same version came out every time.
321
- log("WARNING: version field missing in version.yml, adding it")
321
+ log(t("version_manager.warn_version_missing"))
322
322
  text = read_text()
323
323
  line = f'version: "{new_version}"\n'
324
324
  if re.search(r'^version_code:', text, re.MULTILINE):
@@ -327,7 +327,7 @@ def update_version_yml(new_version):
327
327
  new_text = text.rstrip("\n") + "\n" + line
328
328
  write_text(new_text)
329
329
  if read_scalar_key("version") != new_version:
330
- raise VersionSyncError(f"failed to write version {new_version} to version.yml")
330
+ raise VersionSyncError(t("version_manager.err_write_failed", version=new_version))
331
331
  today = datetime.date.today().isoformat()
332
332
  user = os.environ.get("GITHUB_ACTOR", "")
333
333
  if not user:
@@ -411,16 +411,16 @@ def sync_maven(path_dir, new_version):
411
411
  text = read_file(root_pom)
412
412
  old_version = _pom_text(text, ["version"])
413
413
  if old_version is None:
414
- log(f"WARNING: spring: {root_pom} has no project <version> (inherited from parent?) — skipping")
414
+ log(t("version_manager.warn_spring_no_version", pom=root_pom))
415
415
  return True
416
416
  if "${" in old_version:
417
417
  # CI-friendly versions such as ${revision} are managed in a property, so leave them alone.
418
- log(f"WARNING: spring: {root_pom} <version> is a property ({old_version}) — skipping")
418
+ log(t("version_manager.warn_spring_property", pom=root_pom, version=old_version))
419
419
  return True
420
420
  new_full = _keep_snapshot(old_version, new_version)
421
421
  new_text, _ = _pom_replace(text, ["version"], new_full)
422
422
  write_file(root_pom, new_text)
423
- log(f"updated: {root_pom}")
423
+ log(t("version_manager.updated", path=root_pom))
424
424
 
425
425
  root_artifact = _pom_text(text, ["artifactId"])
426
426
  for child in sorted(Path(path_dir).glob("*/pom.xml")):
@@ -433,7 +433,7 @@ def sync_maven(path_dir, new_version):
433
433
  if _pom_text(ctext, ["version"]) == old_version:
434
434
  ctext, _ = _pom_replace(ctext, ["version"], new_full)
435
435
  write_file(child, ctext)
436
- log(f"updated: {child}")
436
+ log(t("version_manager.updated", path=child))
437
437
  return True
438
438
 
439
439
 
@@ -509,7 +509,7 @@ def sync_spring(path_dir, new_version):
509
509
  has_pom = sync_maven(path_dir, new_version)
510
510
  if not candidates:
511
511
  if not has_pom:
512
- log(f"WARNING: spring: no build.gradle(.kts) or pom.xml found under {path_dir} — skipping")
512
+ log(t("version_manager.warn_spring_no_files", dir=path_dir))
513
513
  return
514
514
  for gradle_file in candidates:
515
515
  text = read_file(gradle_file)
@@ -522,10 +522,10 @@ def sync_spring(path_dir, new_version):
522
522
  replaced = f"{m.group(1)}{m.group(2)}{_keep_snapshot(m.group(3), new_version)}{m.group(2)}"
523
523
  new_text = new_text[:m.start()] + replaced + new_text[m.end():]
524
524
  if not matches:
525
- log(f"WARNING: spring: no `version = '...'` line in {gradle_file} — skipping")
525
+ log(t("version_manager.warn_spring_no_gradle_line", file=gradle_file))
526
526
  continue
527
527
  write_file(gradle_file, new_text)
528
- log(f"updated: {gradle_file}")
528
+ log(t("version_manager.updated", path=gradle_file))
529
529
 
530
530
 
531
531
  def _pubspec_build_number(path_dir):
@@ -548,7 +548,7 @@ def get_reconciled_version_code():
548
548
  if n is not None
549
549
  ]
550
550
  if pubspec_codes and max(pubspec_codes) > code:
551
- log(f"pubspec.yaml build number {max(pubspec_codes)} is ahead of version_code {code} — adopting it")
551
+ log(t("version_manager.pubspec_ahead", build=max(pubspec_codes), code=code))
552
552
  code = max(pubspec_codes)
553
553
  set_version_code(code)
554
554
  return code
@@ -557,7 +557,7 @@ def get_reconciled_version_code():
557
557
  def sync_flutter(path_dir, new_version, version_code):
558
558
  target = Path(path_dir) / "pubspec.yaml"
559
559
  if not target.is_file():
560
- log(f"WARNING: flutter: {target} not found — skipping")
560
+ log(t("version_manager.warn_flutter_missing", target=target))
561
561
  return
562
562
  text = read_file(target)
563
563
  full_version = f"{new_version}+{version_code}"
@@ -567,7 +567,7 @@ def sync_flutter(path_dir, new_version, version_code):
567
567
  else:
568
568
  new_text = text.rstrip("\n") + f"\nversion: {full_version}\n"
569
569
  write_file(target, new_text)
570
- log(f"updated: {target}")
570
+ log(t("version_manager.updated", path=target))
571
571
 
572
572
 
573
573
  def _json_indent(text):
@@ -582,20 +582,20 @@ def _json_indent(text):
582
582
 
583
583
  def sync_json_version(target, new_version, key_path):
584
584
  if not target.is_file():
585
- log(f"WARNING: {target} not found — skipping")
585
+ log(t("version_manager.warn_target_missing", target=target))
586
586
  return
587
587
  raw = read_file(target)
588
588
  try:
589
589
  data = json.loads(raw)
590
590
  except json.JSONDecodeError as e:
591
591
  # Skipping and reporting success would silently leave the tag/version.yml and the package version out of step.
592
- raise VersionSyncError(f"{target} is not valid JSON ({e}) — cannot write version")
592
+ raise VersionSyncError(t("version_manager.err_not_json", target=target, error=e))
593
593
  node = data
594
594
  for k in key_path[:-1]:
595
595
  node = node.setdefault(k, {})
596
596
  node[key_path[-1]] = new_version
597
597
  write_file(target, json.dumps(data, indent=_json_indent(raw), ensure_ascii=False) + "\n")
598
- log(f"updated: {target}")
598
+ log(t("version_manager.updated", path=target))
599
599
 
600
600
 
601
601
  _TOML_HEADER_RE = re.compile(r'^[ \t]*\[+[ \t]*([^\]\n]+?)[ \t]*\]+[ \t]*(?:#.*)?$', re.MULTILINE)
@@ -641,12 +641,12 @@ def sync_python(path_dir, new_version):
641
641
  spans = _python_version_spans(path_dir)
642
642
  if not spans:
643
643
  # Setups that keep the version out of the file (e.g. dynamic = ["version"]) have nowhere to write it.
644
- log(f"WARNING: python: no version in pyproject.toml [project]/[tool.poetry] or setup.py under {path_dir} — skipping")
644
+ log(t("version_manager.warn_python_no_version", dir=path_dir))
645
645
  return
646
646
  for target, (start, end) in spans:
647
647
  text = read_file(target)
648
648
  write_file(target, text[:start] + new_version + text[end:])
649
- log(f"updated: {target}")
649
+ log(t("version_manager.updated", path=target))
650
650
 
651
651
 
652
652
  _PLIST_VERSION_RE = re.compile(r'(<key>CFBundleShortVersionString</key>\s*<string>)([^<]*)(</string>)')
@@ -673,33 +673,33 @@ def sync_react_native(path_dir, new_version):
673
673
  continue
674
674
  # A build-variable reference such as $(MARKETING_VERSION) is owned by the Xcode settings, so do not overwrite it.
675
675
  if m.group(2).strip().startswith("$("):
676
- log(f"skipped: {plist_file} — CFBundleShortVersionString references a build variable")
676
+ log(t("version_manager.skipped_plist", file=plist_file))
677
677
  continue
678
678
  new_text = _PLIST_VERSION_RE.sub(lambda mm: mm.group(1) + new_version + mm.group(3), text)
679
679
  write_file(plist_file, new_text)
680
- log(f"updated: {plist_file}")
680
+ log(t("version_manager.updated", path=plist_file))
681
681
  found_plist = True
682
682
  else:
683
- log(f"WARNING: react-native: {ios_dir} not found — skipping")
683
+ log(t("version_manager.warn_rn_ios_missing", dir=ios_dir))
684
684
 
685
685
  gradle_file = Path(path_dir) / "android" / "app" / "build.gradle"
686
686
  if gradle_file.is_file():
687
687
  text = read_file(gradle_file)
688
688
  new_text = re.sub(r'versionName\s+"[^"]*"', f'versionName "{new_version}"', text)
689
689
  write_file(gradle_file, new_text)
690
- log(f"updated: {gradle_file}")
690
+ log(t("version_manager.updated", path=gradle_file))
691
691
  else:
692
- log(f"WARNING: react-native: {gradle_file} not found — skipping")
692
+ log(t("version_manager.warn_rn_gradle_missing", file=gradle_file))
693
693
 
694
694
  if not found_plist and not gradle_file.is_file():
695
- log(f"WARNING: react-native: no target files found under {path_dir}")
695
+ log(t("version_manager.warn_rn_no_targets", dir=path_dir))
696
696
 
697
697
 
698
698
  def sync_for_type(project_type, new_version, version_code_getter):
699
699
  path_dir = get_type_path(project_type)
700
700
  handler = TYPE_HANDLERS.get(project_type)
701
701
  if handler is None:
702
- log(f"WARNING: unknown project type: {project_type} — skipping")
702
+ log(t("version_manager.warn_unknown_type", type=project_type))
703
703
  return
704
704
  handler.sync(path_dir, new_version, version_code_getter)
705
705
 
@@ -709,17 +709,17 @@ def sync_all_project_files(new_version):
709
709
  if not types:
710
710
  # No silent fallback: an unreadable project_types used to degrade to
711
711
  # "basic" and skip every sync without a word.
712
- raise SystemExit("ERROR: version.yml has no readable project_types — cannot sync project files")
712
+ raise SystemExit(t("version_manager.err_no_types_sync"))
713
713
  errors = []
714
- for t in types:
714
+ for ptype in types:
715
715
  # Keep syncing the other types when one fails; collect failures and report them via the exit code at the end.
716
716
  try:
717
- sync_for_type(t, new_version, get_reconciled_version_code)
717
+ sync_for_type(ptype, new_version, get_reconciled_version_code)
718
718
  except VersionSyncError as e:
719
- log(f"ERROR: {t}: {e}")
720
- errors.append(t)
719
+ log(t("version_manager.err_type", type=ptype, error=e))
720
+ errors.append(ptype)
721
721
  if errors:
722
- raise VersionSyncError(f"project file sync failed for: {', '.join(errors)}")
722
+ raise VersionSyncError(t("version_manager.err_sync_failed", types=", ".join(errors)))
723
723
 
724
724
 
725
725
  README_VERSION_LINE_RE = re.compile(
@@ -749,7 +749,7 @@ def update_readme_version(new_version, path="README.md"):
749
749
  return False
750
750
  lines[i + 1] = new_line
751
751
  write_file(p, "\n".join(lines))
752
- log(f"README.md version line -> v{new_version}")
752
+ log(t("version_manager.readme_updated", version=new_version))
753
753
  return True
754
754
  return False
755
755
 
@@ -880,7 +880,7 @@ def get_project_file_version(project_type):
880
880
  if handler is not None:
881
881
  version = handler.read(path_dir)
882
882
  except Exception as e:
883
- log(f"WARNING: failed reading project file for {project_type}: {e}")
883
+ log(t("version_manager.warn_read_failed", type=project_type, error=e))
884
884
  version = None
885
885
 
886
886
  if not version:
@@ -892,32 +892,32 @@ def sync_versions():
892
892
  yml_version = get_current_version()
893
893
  types = get_project_types_csv()
894
894
  if not types:
895
- raise SystemExit("ERROR: version.yml has no readable project_types — cannot sync versions")
895
+ raise SystemExit(t("version_manager.err_no_types_check"))
896
896
  primary_type = types[0]
897
897
  project_version = get_project_file_version(primary_type)
898
898
 
899
- log("Version sync check")
900
- log(f" version.yml: {yml_version}")
901
- log(f" project file: {project_version}")
899
+ log(t("version_manager.sync_check"))
900
+ log(t("version_manager.sync_yml_version", version=yml_version))
901
+ log(t("version_manager.sync_project_version", version=project_version))
902
902
 
903
903
  if yml_version != project_version:
904
904
  if validate_version(yml_version) and validate_version(project_version):
905
905
  higher = get_higher_version(yml_version, project_version)
906
- log(f"Version mismatch detected, syncing to higher version: {higher}")
906
+ log(t("version_manager.sync_mismatch", version=higher))
907
907
  if higher != yml_version:
908
908
  update_version_yml(higher)
909
909
  if higher != project_version:
910
910
  sync_all_project_files(higher)
911
911
  return higher
912
912
  else:
913
- log("WARNING: version format invalid, cannot sync")
913
+ log(t("version_manager.warn_format_invalid"))
914
914
  return yml_version
915
915
  else:
916
916
  types = get_project_types_csv()
917
917
  if types:
918
- log(f"Multi-type — reconciling all type files to version.yml version: {yml_version}")
918
+ log(t("version_manager.sync_multi", version=yml_version))
919
919
  sync_all_project_files(yml_version)
920
- log(f"Version already in sync: {yml_version}")
920
+ log(t("version_manager.sync_ok", version=yml_version))
921
921
  return yml_version
922
922
 
923
923
 
@@ -952,7 +952,7 @@ def cmd_increment(args):
952
952
  require_version_yml()
953
953
  current_version = sync_versions()
954
954
  if not validate_version(current_version):
955
- log(f"ERROR: invalid version format: {current_version}")
955
+ log(t("version_manager.err_invalid_version", version=current_version))
956
956
  return 1
957
957
  bump = getattr(args, "bump", None) or "patch"
958
958
  new_version = increment_version(current_version, bump)
@@ -968,7 +968,7 @@ def cmd_set(args):
968
968
  require_version_yml()
969
969
  new_version = args.version
970
970
  if not validate_version(new_version):
971
- log(f"ERROR: invalid version format: {new_version} (must be x.y.z)")
971
+ log(t("version_manager.err_invalid_version_xyz", version=new_version))
972
972
  return 1
973
973
  update_all_versions(new_version)
974
974
  print(new_version)
@@ -1017,7 +1017,7 @@ def main(argv=None):
1017
1017
  try:
1018
1018
  return handler(args)
1019
1019
  except VersionSyncError as e:
1020
- log(f"ERROR: {e}")
1020
+ log(t("version_manager.err_generic", error=e))
1021
1021
  return 1
1022
1022
 
1023
1023
 
@@ -53,11 +53,12 @@ jobs:
53
53
  - name: Read copilot_ai option from version.yml
54
54
  id: copilot_options
55
55
  run: |
56
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
56
57
  # scoped to the metadata: -> template: -> options: chain so an
57
58
  # unrelated "copilot_ai:" key can never hijack the value
58
59
  COPILOT_AI=$(python3 -c 'import re; t=open("version.yml",encoding="utf-8").read(); m=re.search(r"metadata:.*?template:.*?options:.*?copilot_ai:\s*\"?(true|false)", t, re.S); print(m.group(1) if m else "false")' 2>/dev/null || echo "false")
59
60
  echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
60
- echo "copilot_ai option: $COPILOT_AI"
61
+ echo "$(m wf_common.copilot_ai_option copilot_ai="$COPILOT_AI")"
61
62
 
62
63
  - name: Install Copilot CLI
63
64
  if: steps.copilot_options.outputs.copilot_ai == 'true'
@@ -74,6 +75,7 @@ jobs:
74
75
  COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
75
76
  GH_TOKEN: ${{ github.token }}
76
77
  run: |
78
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
77
79
  PR_NUMBER=${{ github.event.pull_request.number }}
78
80
 
79
81
  git fetch origin {{MAIN_BRANCH}}
@@ -105,7 +107,7 @@ jobs:
105
107
  fi
106
108
  VERSION=$(python3 -c 'import sys; sys.path.insert(0, ".github/scripts"); from version_manager import increment_version; print(increment_version(sys.argv[1], sys.argv[2]))' "$CURRENT_VERSION" "${BUMP:-patch}" 2>/dev/null || echo "")
107
109
  VERSION=${VERSION:-Unreleased}
108
- echo "expected next version: $VERSION (current: ${CURRENT_VERSION:-none}, mode: $MODE, bump: ${BUMP:-patch})"
110
+ echo "$(m wf_aisum.expected_version version="$VERSION" current_version="${CURRENT_VERSION:-none}" mode="$MODE" bump="${BUMP:-patch}")"
109
111
 
110
112
  SUMMARY_RESULT=$(python3 .github/scripts/changelog_manager.py ai-summary \
111
113
  --commits-file commits.txt \
@@ -136,10 +138,10 @@ jobs:
136
138
  --jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$MARKER\"))) | .id" 2>/dev/null | tail -n 1 || true)
137
139
  if [ -n "$COMMENT_ID" ]; then
138
140
  gh api -X PATCH "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" --input comment_payload.json > /dev/null \
139
- || echo "comment update failed — continuing (this workflow never blocks the PR)"
141
+ || echo "$(m wf_aisum.comment_update_failed)"
140
142
  else
141
143
  gh api -X POST "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" --input comment_payload.json > /dev/null \
142
- || echo "comment post failed — continuing (this workflow never blocks the PR)"
144
+ || echo "$(m wf_aisum.comment_post_failed)"
143
145
  fi
144
146
 
145
147
  - name: Link related issue via branch name
@@ -85,16 +85,27 @@ jobs:
85
85
  github.event.pull_request.head.ref == '{{DEVELOP_BRANCH}}'
86
86
  runs-on: ubuntu-latest
87
87
  permissions:
88
+ contents: read
88
89
  pull-requests: read
89
90
  outputs:
90
91
  open: ${{ steps.state.outputs.open }}
91
92
  steps:
93
+ # Only the message catalog and language setting are needed for the log wording
94
+ - name: Checkout message catalog
95
+ uses: actions/checkout@v7
96
+ with:
97
+ sparse-checkout: |
98
+ .github/scripts/messages.py
99
+ version.yml
100
+ sparse-checkout-cone-mode: false
101
+
92
102
  - name: Read the current PR state
93
103
  id: state
94
104
  env:
95
105
  GH_TOKEN: ${{ github.token }}
96
106
  GH_REPO: ${{ github.repository }}
97
107
  run: |
108
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
98
109
  PR_NUMBER=${{ github.event.pull_request.number }}
99
110
  # A push to {{DEVELOP_BRANCH}} mid-pipeline queues a run that later reprocesses the
100
111
  # already-merged PR - an empty summary comment, duplicate CHANGELOG entries and a
@@ -104,10 +115,10 @@ jobs:
104
115
  STATE=$(gh pr view "$PR_NUMBER" --json state -q .state 2>/dev/null || echo "UNKNOWN")
105
116
  if [ "$STATE" = "MERGED" ] || [ "$STATE" = "CLOSED" ]; then
106
117
  echo "open=false" >> "$GITHUB_OUTPUT"
107
- echo "::notice::PR #$PR_NUMBER is already $STATE — skipping the release pipeline"
118
+ echo "::notice::$(m wf_changelog.pr_not_open pr_number="$PR_NUMBER" state="$STATE")"
108
119
  else
109
120
  echo "open=true" >> "$GITHUB_OUTPUT"
110
- echo "PR #$PR_NUMBER state: $STATE"
121
+ echo "$(m wf_changelog.pr_state pr_number="$PR_NUMBER" state="$STATE")"
111
122
  fi
112
123
 
113
124
  changelog-and-merge:
@@ -131,18 +142,20 @@ jobs:
131
142
  - name: Read semver_auto option from version.yml
132
143
  id: semver_options
133
144
  run: |
145
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
134
146
  SEMVER_AUTO=$(python3 -c 'import re; t=open("version.yml",encoding="utf-8").read(); m=re.search(r"metadata:.*?template:.*?options:.*?semver_auto:\s*\"?(true|false)", t, re.S); print(m.group(1) if m else "false")' 2>/dev/null || echo "false")
135
147
  echo "semver_auto=$SEMVER_AUTO" >> $GITHUB_OUTPUT
136
- echo "semver_auto option: $SEMVER_AUTO"
148
+ echo "$(m wf_common.semver_auto_option semver_auto="$SEMVER_AUTO")"
137
149
 
138
150
  - name: Read copilot_ai option from version.yml
139
151
  id: copilot_options
140
152
  run: |
153
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
141
154
  # scoped to the metadata: -> template: -> options: chain so an
142
155
  # unrelated "copilot_ai:" key can never hijack the value
143
156
  COPILOT_AI=$(python3 -c 'import re; t=open("version.yml",encoding="utf-8").read(); m=re.search(r"metadata:.*?template:.*?options:.*?copilot_ai:\s*\"?(true|false)", t, re.S); print(m.group(1) if m else "false")' 2>/dev/null || echo "false")
144
157
  echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
145
- echo "copilot_ai option: $COPILOT_AI"
158
+ echo "$(m wf_common.copilot_ai_option copilot_ai="$COPILOT_AI")"
146
159
 
147
160
  - name: Install Copilot CLI
148
161
  if: steps.copilot_options.outputs.copilot_ai == 'true'
@@ -152,6 +165,7 @@ jobs:
152
165
  - name: Collect commits since last release
153
166
  id: collect
154
167
  run: |
168
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
155
169
  git fetch origin {{MAIN_BRANCH}}
156
170
  git log --pretty=%s "origin/{{MAIN_BRANCH}}..HEAD" > commits.txt
157
171
  # Body-footer BREAKING CHANGE: lines are appended after the subject list, one per
@@ -165,7 +179,7 @@ jobs:
165
179
  cp _full_diff_stat.txt diff_stat.txt
166
180
  fi
167
181
  rm -f _full_diff_stat.txt
168
- echo "commits to summarize: $(wc -l < commits.txt)"
182
+ echo "$(m wf_changelog.commits_to_summarize n="$(wc -l < commits.txt)")"
169
183
 
170
184
  - name: Collect issues merged into {{DEVELOP_BRANCH}}
171
185
  continue-on-error: true
@@ -211,6 +225,7 @@ jobs:
211
225
  GITHUB_TOKEN: ${{ github.token }}
212
226
  COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
213
227
  run: |
228
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
214
229
  # Idempotency: never raise a version this PR already confirmed (not yet published).
215
230
  # Looking only at the HEAD commit message lets the guard slip when more commits
216
231
  # pile on top of the confirm commit (extra {{DEVELOP_BRANCH}} pushes, a
@@ -227,7 +242,7 @@ jobs:
227
242
  else
228
243
  BUMP="patch"
229
244
  fi
230
- echo "bump level: $BUMP"
245
+ echo "$(m wf_common.bump_level bump="$BUMP")"
231
246
 
232
247
  if [ -n "$MAIN_VERSION" ] && [ -n "$HEAD_VERSION" ] && version_gt "$HEAD_VERSION" "$MAIN_VERSION" && ! is_tagged "$HEAD_VERSION"; then
233
248
  # Already-confirmed version - recompute from the {{MAIN_BRANCH}} version and raise it
@@ -237,22 +252,22 @@ jobs:
237
252
  TARGET=$(python3 -c 'import sys; sys.path.insert(0, ".github/scripts"); from version_manager import increment_version; print(increment_version(sys.argv[1], sys.argv[2]))' "$MAIN_VERSION" "$BUMP")
238
253
  if version_gt "$TARGET" "$HEAD_VERSION"; then
239
254
  NEW_VERSION=$(python3 .github/scripts/version_manager.py set "$TARGET" | tail -n 1)
240
- echo "confirmed version raised: $HEAD_VERSION -> $NEW_VERSION ($BUMP from {{MAIN_BRANCH}} $MAIN_VERSION)"
255
+ echo "$(m wf_changelog.version_raised head_version="$HEAD_VERSION" new_version="$NEW_VERSION" bump="$BUMP" main_branch="{{MAIN_BRANCH}}" main_version="$MAIN_VERSION")"
241
256
  else
242
257
  NEW_VERSION="$HEAD_VERSION"
243
- echo "version already confirmed and not yet released — reusing $NEW_VERSION ({{MAIN_BRANCH}} is $MAIN_VERSION)"
258
+ echo "$(m wf_changelog.version_reuse_unreleased new_version="$NEW_VERSION" main_branch="{{MAIN_BRANCH}}" main_version="$MAIN_VERSION")"
244
259
  fi
245
260
  elif [ -z "$MAIN_VERSION" ] && printf '%s\n' "$HEAD_MSG" | grep -q "^chore(version): confirm v.* \[skip ci\]"; then
246
261
  # Re-run of a first release where {{MAIN_BRANCH}} has no version.yml yet
247
262
  NEW_VERSION="$HEAD_VERSION"
248
- echo "version already confirmed by a previous run — reusing $NEW_VERSION (no re-increment)"
263
+ echo "$(m wf_changelog.version_reuse_previous new_version="$NEW_VERSION")"
249
264
  else
250
265
  NEW_VERSION=$(python3 .github/scripts/version_manager.py increment --bump "$BUMP" | tail -n 1)
251
266
  python3 .github/scripts/version_manager.py sync
252
- echo "release version confirmed: $NEW_VERSION ($BUMP)"
267
+ echo "$(m wf_changelog.version_confirmed new_version="$NEW_VERSION" bump="$BUMP")"
253
268
  fi
254
269
  if [ -z "$NEW_VERSION" ]; then
255
- echo "version bump failed"
270
+ echo "$(m wf_common.version_bump_failed)"
256
271
  exit 1
257
272
  fi
258
273
  echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
@@ -269,6 +284,7 @@ jobs:
269
284
  GITHUB_TOKEN: ${{ github.token }}
270
285
  COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
271
286
  run: |
287
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
272
288
  VERSION=$(python3 .github/scripts/version_manager.py get | tail -n 1)
273
289
 
274
290
  SUMMARY_RESULT=$(python3 .github/scripts/changelog_manager.py ai-summary \
@@ -281,7 +297,7 @@ jobs:
281
297
  # On fallback, append the reason (the script also emits it as ::warning::) to the engine line so the cause is visible without opening the job log
282
298
  FALLBACK_REASON=$(python3 -c 'import html,json,sys; print(html.escape((json.loads(sys.argv[1]).get("fallback_reason") or "")[:200]))' "$SUMMARY_RESULT" 2>/dev/null || echo "")
283
299
  ENGINE_LINE="$ENGINE${FALLBACK_REASON:+ ($FALLBACK_REASON)}"
284
- echo "summary engine: $ENGINE_LINE"
300
+ echo "$(m wf_changelog.summary_engine engine_line="$ENGINE_LINE")"
285
301
  echo "engine: $ENGINE_LINE" >> "$GITHUB_STEP_SUMMARY"
286
302
  echo "engine=$ENGINE" >> $GITHUB_OUTPUT
287
303
  echo "fallback_reason=$FALLBACK_REASON" >> $GITHUB_OUTPUT
@@ -296,6 +312,7 @@ jobs:
296
312
  FALLBACK_REASON: ${{ steps.summary.outputs.fallback_reason }}
297
313
  GH_TOKEN: ${{ github.token }}
298
314
  run: |
315
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
299
316
  PR_NUMBER=${{ github.event.pull_request.number }}
300
317
  # Find this bot's earlier summary comment by its marker and replace it so re-runs and re-syncs do not pile up comments
301
318
  MARKER="<!-- project-auto-wizard:pr-summary -->"
@@ -315,12 +332,12 @@ jobs:
315
332
  --jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$MARKER\"))) | .id" 2>/dev/null | tail -n 1 || true)
316
333
  if [ -n "$COMMENT_ID" ]; then
317
334
  gh api -X PATCH "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" --input comment_payload.json > /dev/null \
318
- && echo "summary comment updated (#${COMMENT_ID})" \
319
- || echo "comment update failed — continuing (this workflow never blocks the release)"
335
+ && echo "$(m wf_changelog.comment_updated comment_id="${COMMENT_ID}")" \
336
+ || echo "$(m wf_changelog.comment_update_failed)"
320
337
  else
321
338
  gh api -X POST "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" --input comment_payload.json > /dev/null \
322
- && echo "summary comment posted" \
323
- || echo "comment post failed — continuing (this workflow never blocks the release)"
339
+ && echo "$(m wf_changelog.comment_posted)" \
340
+ || echo "$(m wf_changelog.comment_post_failed)"
324
341
  fi
325
342
 
326
343
  rm -f comment_body.md comment_payload.json
@@ -345,6 +362,7 @@ jobs:
345
362
  env:
346
363
  GH_TOKEN: ${{ github.token }}
347
364
  run: |
365
+ m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
348
366
  HEAD_BRANCH="${{ github.event.pull_request.head.ref }}"
349
367
  VERSION="${{ steps.bump.outputs.new_version }}"
350
368
 
@@ -356,7 +374,7 @@ jobs:
356
374
  # {{DEVELOP_BRANCH}} after the merge).
357
375
  PR_STATE=$(gh pr view "${{ github.event.pull_request.number }}" --json state -q .state 2>/dev/null || echo "UNKNOWN")
358
376
  if [ "$PR_STATE" = "MERGED" ] || [ "$PR_STATE" = "CLOSED" ]; then
359
- echo "::notice::PR is already $PR_STATE — not pushing release docs"
377
+ echo "::notice::$(m wf_changelog.pr_closed_skip_docs pr_state="$PR_STATE")"
360
378
  exit 0
361
379
  fi
362
380
 
@@ -367,7 +385,7 @@ jobs:
367
385
  git ls-files -z --others --exclude-standard -- CHANGELOG.json CHANGELOG.md | xargs -0 -r git add --
368
386
 
369
387
  if git diff --staged --quiet; then
370
- echo "no changes to commit"
388
+ echo "$(m wf_common.no_changes)"
371
389
  else
372
390
  git commit -m "chore(version): confirm v${VERSION} and update release docs [skip ci]"
373
391
 
@@ -378,18 +396,18 @@ jobs:
378
396
 
379
397
  while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
380
398
  RETRY_COUNT=$((RETRY_COUNT + 1))
381
- echo "push attempt $RETRY_COUNT/$MAX_RETRIES..."
399
+ echo "$(m wf_common.push_attempt retry_count="$RETRY_COUNT" max_retries="$MAX_RETRIES")"
382
400
 
383
401
  if git push origin HEAD:$HEAD_BRANCH; then
384
402
  PUSH_SUCCESS=true
385
- echo "release docs pushed to $HEAD_BRANCH"
403
+ echo "$(m wf_changelog.docs_pushed head_branch="$HEAD_BRANCH")"
386
404
  break
387
405
  else
388
- echo "push failed, syncing with remote..."
406
+ echo "$(m wf_common.push_failed_syncing)"
389
407
  if git pull --rebase origin $HEAD_BRANCH; then
390
- echo "rebase ok, retrying push..."
408
+ echo "$(m wf_common.rebase_ok)"
391
409
  else
392
- echo "rebase failed, manual conflict resolution required"
410
+ echo "$(m wf_common.rebase_failed)"
393
411
  git rebase --abort 2>/dev/null || true
394
412
  exit 1
395
413
  fi
@@ -397,7 +415,7 @@ jobs:
397
415
  done
398
416
 
399
417
  if [ "$PUSH_SUCCESS" = false ]; then
400
- echo "push failed after $MAX_RETRIES attempts"
418
+ echo "$(m wf_common.push_gave_up max_retries="$MAX_RETRIES")"
401
419
  exit 1
402
420
  fi
403
421
  fi
@@ -424,18 +442,18 @@ jobs:
424
442
  MAX_ATTEMPTS=4
425
443
  for ATTEMPT in $(seq 1 $MAX_ATTEMPTS); do
426
444
  if [ "$(gh pr view "$PR_NUMBER" --json state -q .state 2>/dev/null)" = "MERGED" ]; then
427
- echo "PR #$PR_NUMBER is already merged"
445
+ echo "$(m wf_changelog.pr_already_merged pr_number="$PR_NUMBER")"
428
446
  MERGED=true
429
447
  break
430
448
  fi
431
449
  if gh pr merge "$PR_NUMBER" --auto --merge --subject "$SUBJECT" 2> "$ERR_FILE"; then
432
- echo "automerge enabled: $SUBJECT"
450
+ echo "$(m wf_changelog.automerge_enabled subject="$SUBJECT")"
433
451
  MERGED=true
434
452
  break
435
453
  fi
436
454
  cat "$ERR_FILE" >&2
437
455
  if gh pr merge "$PR_NUMBER" --merge --subject "$SUBJECT" 2> "$ERR_FILE"; then
438
- echo "automerge unavailable — merged directly: $SUBJECT"
456
+ echo "$(m wf_changelog.automerge_unavailable subject="$SUBJECT")"
439
457
  MERGED=true
440
458
  break
441
459
  fi
@@ -451,7 +469,7 @@ jobs:
451
469
  && echo "head branch updated from base" || true
452
470
  fi
453
471
  WAIT=$((ATTEMPT * 10))
454
- echo "merge attempt $ATTEMPT/$MAX_ATTEMPTS failed — retrying in ${WAIT}s"
472
+ echo "$(m wf_changelog.merge_retry attempt="$ATTEMPT" max_attempts="$MAX_ATTEMPTS" wait="${WAIT}")"
455
473
  sleep "$WAIT"
456
474
  done
457
475
 
@@ -465,7 +483,7 @@ jobs:
465
483
  elif [ "$ALLOW_MERGE_COMMIT" = "false" ]; then
466
484
  echo "::error::$(m wf_changelog.merge_commit_disabled)"
467
485
  else
468
- echo "::error::PR merge failed after $MAX_ATTEMPTS attempts (allow_merge_commit=$ALLOW_MERGE_COMMIT) — check branch protection rules, required checks, and token permissions"
486
+ echo "::error::$(m wf_changelog.merge_gave_up max_attempts="$MAX_ATTEMPTS" allow_merge_commit="$ALLOW_MERGE_COMMIT")"
469
487
  fi
470
488
  exit 1
471
489
  fi