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.
- package/README.ja.md +2 -0
- package/README.ko.md +2 -0
- package/README.md +3 -1
- package/README.zh-CN.md +2 -0
- package/package.json +1 -1
- package/payload/scripts/changelog_manager.py +8 -8
- package/payload/scripts/messages.py +306 -0
- package/payload/scripts/version_manager.py +46 -46
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +6 -4
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +47 -29
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +38 -35
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +36 -28
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +35 -28
- package/src/commands/doctor.js +5 -0
- package/src/commands/dry-run.js +1 -0
- package/src/core/copy/readme.js +58 -2
- package/src/i18n/catalog/en/commands.js +1 -0
- package/src/i18n/catalog/en/copy.js +1 -0
- package/src/i18n/catalog/ko/commands.js +1 -0
- package/src/i18n/catalog/ko/copy.js +1 -0
- package/src/index.js +4 -3
- package/src/ui/summary.js +1 -1
|
@@ -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("
|
|
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("
|
|
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(
|
|
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("
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
680
|
+
log(t("version_manager.updated", path=plist_file))
|
|
681
681
|
found_plist = True
|
|
682
682
|
else:
|
|
683
|
-
log(
|
|
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(
|
|
690
|
+
log(t("version_manager.updated", path=gradle_file))
|
|
691
691
|
else:
|
|
692
|
-
log(
|
|
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(
|
|
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(
|
|
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("
|
|
712
|
+
raise SystemExit(t("version_manager.err_no_types_sync"))
|
|
713
713
|
errors = []
|
|
714
|
-
for
|
|
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(
|
|
717
|
+
sync_for_type(ptype, new_version, get_reconciled_version_code)
|
|
718
718
|
except VersionSyncError as e:
|
|
719
|
-
log(
|
|
720
|
-
errors.append(
|
|
719
|
+
log(t("version_manager.err_type", type=ptype, error=e))
|
|
720
|
+
errors.append(ptype)
|
|
721
721
|
if errors:
|
|
722
|
-
raise VersionSyncError(
|
|
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(
|
|
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(
|
|
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("
|
|
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("
|
|
900
|
-
log(
|
|
901
|
-
log(
|
|
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(
|
|
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("
|
|
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(
|
|
918
|
+
log(t("version_manager.sync_multi", version=yml_version))
|
|
919
919
|
sync_all_project_files(yml_version)
|
|
920
|
-
log(
|
|
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(
|
|
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(
|
|
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(
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
319
|
-
|| echo "
|
|
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 "
|
|
323
|
-
|| echo "
|
|
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
|
|
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 "
|
|
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 "
|
|
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 "
|
|
403
|
+
echo "$(m wf_changelog.docs_pushed head_branch="$HEAD_BRANCH")"
|
|
386
404
|
break
|
|
387
405
|
else
|
|
388
|
-
echo "
|
|
406
|
+
echo "$(m wf_common.push_failed_syncing)"
|
|
389
407
|
if git pull --rebase origin $HEAD_BRANCH; then
|
|
390
|
-
echo "
|
|
408
|
+
echo "$(m wf_common.rebase_ok)"
|
|
391
409
|
else
|
|
392
|
-
echo "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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
|
|
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
|