project-auto-wizard 0.14.0 → 0.15.1
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 +3 -1
- package/README.ko.md +3 -1
- package/README.md +4 -2
- package/README.zh-CN.md +3 -1
- package/package.json +1 -1
- package/payload/config/breaking-changes.json +31 -5
- package/payload/config/wizard-prompts.yml +0 -4
- package/payload/scripts/changelog_manager.py +8 -8
- package/payload/scripts/messages.py +460 -24
- package/payload/scripts/version_manager.py +102 -73
- package/payload/version.yml.template +2 -2
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +9 -4
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +62 -30
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +96 -44
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +79 -35
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +71 -35
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +24 -8
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +29 -10
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +57 -23
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +155 -72
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +49 -20
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +162 -62
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +184 -87
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +38 -15
- package/payload/workflows/go/PROJECT-GO-CI.yaml +14 -5
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +375 -120
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +9 -4
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +12 -4
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +375 -120
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +9 -4
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +38 -9
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +19 -5
- package/payload/workflows/spring/PROJECT-SPRING-CI.yml +71 -35
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +9 -6
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +7 -4
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +425 -144
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +7 -4
- package/src/cli/args.js +17 -7
- package/src/commands/doctor.js +16 -0
- package/src/commands/dry-run.js +10 -1
- package/src/commands/full.js +5 -2
- package/src/commands/interactive.js +4 -1
- package/src/commands/status.js +14 -1
- package/src/core/copy/readme.js +58 -2
- package/src/core/copy/simple.js +31 -0
- package/src/core/copy/workflows.js +2 -2
- package/src/core/deploy-style.js +1 -1
- package/src/core/detect.js +3 -2
- package/src/core/types.js +15 -8
- package/src/core/version-yml.js +52 -6
- package/src/i18n/catalog/en/cli.js +1 -0
- package/src/i18n/catalog/en/commands.js +9 -0
- package/src/i18n/catalog/en/copy.js +1 -0
- package/src/i18n/catalog/en/core2.js +3 -0
- package/src/i18n/catalog/ko/cli.js +1 -0
- package/src/i18n/catalog/ko/commands.js +9 -0
- package/src/i18n/catalog/ko/copy.js +1 -0
- package/src/i18n/catalog/ko/core2.js +3 -0
- package/src/index.js +8 -4
- package/src/ui/env-plan.js +1 -1
- package/src/ui/prompts.js +7 -3
- package/src/ui/summary.js +1 -1
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +0 -241
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +0 -330
|
@@ -95,16 +95,33 @@ def read_file(path):
|
|
|
95
95
|
|
|
96
96
|
def write_file(path, text):
|
|
97
97
|
"""Write text preserving the dominant line ending of the existing file
|
|
98
|
-
on disk (LF stays LF, CRLF stays CRLF — never platform-dependent).
|
|
98
|
+
on disk (LF stays LF, CRLF stays CRLF — never platform-dependent).
|
|
99
|
+
Returns False (and leaves the file untouched) when the content is already identical."""
|
|
99
100
|
p = Path(path)
|
|
100
101
|
eol = "\n"
|
|
102
|
+
previous = None
|
|
101
103
|
if p.is_file():
|
|
102
104
|
with open(p, "r", encoding="utf-8", newline="") as f:
|
|
103
|
-
|
|
105
|
+
previous = f.read()
|
|
106
|
+
eol = _detect_eol(previous)
|
|
104
107
|
if eol != "\n":
|
|
105
108
|
text = text.replace("\n", eol)
|
|
109
|
+
if previous == text:
|
|
110
|
+
return False
|
|
106
111
|
with open(p, "w", encoding="utf-8", newline="") as f:
|
|
107
112
|
f.write(text)
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# Files whose content actually changed in this process. Lets the sync check tell "reconciled" from "already in sync".
|
|
117
|
+
_CHANGED_FILES = []
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def write_synced(path, text):
|
|
121
|
+
"""write_file() for version files: logs `updated:` only when the content really changed."""
|
|
122
|
+
if write_file(path, text):
|
|
123
|
+
_CHANGED_FILES.append(str(path))
|
|
124
|
+
log(t("version_manager.updated", path=path))
|
|
108
125
|
|
|
109
126
|
|
|
110
127
|
# ===================================================================
|
|
@@ -117,7 +134,7 @@ def _version_yml_path():
|
|
|
117
134
|
|
|
118
135
|
def require_version_yml():
|
|
119
136
|
if not _version_yml_path().is_file():
|
|
120
|
-
log("
|
|
137
|
+
log(t("version_manager.err_yml_missing"))
|
|
121
138
|
sys.exit(1)
|
|
122
139
|
|
|
123
140
|
|
|
@@ -176,6 +193,21 @@ def get_current_version():
|
|
|
176
193
|
return read_scalar_key("version", "0.0.0")
|
|
177
194
|
|
|
178
195
|
|
|
196
|
+
# Names that used to be separate types. A version.yml written before the merge may still carry them,
|
|
197
|
+
# and the release workflow can run before the wizard rewrites that file, so they are read as the new name.
|
|
198
|
+
TYPE_ALIASES = {"next": "react"}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def _canonical_types(types):
|
|
202
|
+
"""Map aliased type names to their current name, dropping duplicates while keeping order."""
|
|
203
|
+
out = []
|
|
204
|
+
for t in types:
|
|
205
|
+
t = TYPE_ALIASES.get(t, t)
|
|
206
|
+
if t not in out:
|
|
207
|
+
out.append(t)
|
|
208
|
+
return out
|
|
209
|
+
|
|
210
|
+
|
|
179
211
|
def get_project_types_csv():
|
|
180
212
|
"""Return project_types as a list. Supports both:
|
|
181
213
|
project_types: ["a", "b"]
|
|
@@ -196,7 +228,7 @@ def get_project_types_csv():
|
|
|
196
228
|
inner = m.group(1)
|
|
197
229
|
items = re.findall(r'"([^"]*)"|\'([^\']*)\'', inner)
|
|
198
230
|
types = [a or b for a, b in items]
|
|
199
|
-
return [t for t in types if t]
|
|
231
|
+
return _canonical_types([t for t in types if t])
|
|
200
232
|
|
|
201
233
|
# Block list form:
|
|
202
234
|
# project_types:
|
|
@@ -207,7 +239,7 @@ def get_project_types_csv():
|
|
|
207
239
|
block = m.group(1)
|
|
208
240
|
# trailing comments are allowed on list items too
|
|
209
241
|
types = re.findall(r'-[ \t]*["\']?([^"\'#\n]+?)["\']?[ \t]*(?:#.*)?$', block, re.MULTILINE)
|
|
210
|
-
return [t.strip() for t in types if t.strip()]
|
|
242
|
+
return _canonical_types([t.strip() for t in types if t.strip()])
|
|
211
243
|
|
|
212
244
|
return []
|
|
213
245
|
|
|
@@ -221,16 +253,19 @@ def get_type_path(project_type, project_types_list=None):
|
|
|
221
253
|
if not m:
|
|
222
254
|
return "."
|
|
223
255
|
block = m.group(1)
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
256
|
+
# The current name first, then any older name that maps to it (e.g. next -> react).
|
|
257
|
+
names = [project_type] + [old for old, new in TYPE_ALIASES.items() if new == project_type]
|
|
258
|
+
for name in names:
|
|
259
|
+
km = re.search(
|
|
260
|
+
r'^[ \t]+["\']?' + re.escape(name)
|
|
261
|
+
+ r'["\']?:[ \t]*["\']?([^"\'#\n]+?)["\']?[ \t]*(?:#.*)?$',
|
|
262
|
+
block,
|
|
263
|
+
re.MULTILINE,
|
|
264
|
+
)
|
|
265
|
+
if km:
|
|
266
|
+
val = km.group(1).strip()
|
|
267
|
+
if val and val != "null":
|
|
268
|
+
return val
|
|
234
269
|
return "."
|
|
235
270
|
|
|
236
271
|
|
|
@@ -238,7 +273,7 @@ def get_version_code():
|
|
|
238
273
|
require_version_yml()
|
|
239
274
|
code = read_scalar_key("version_code", None)
|
|
240
275
|
if code is None or code == "" or code == "null":
|
|
241
|
-
log("
|
|
276
|
+
log(t("version_manager.warn_code_missing"))
|
|
242
277
|
text = read_text()
|
|
243
278
|
if re.search(r'^version:', text, re.MULTILINE):
|
|
244
279
|
new_text = re.sub(
|
|
@@ -260,7 +295,7 @@ def set_version_code(new_code):
|
|
|
260
295
|
if current not in (None, "", "null"):
|
|
261
296
|
try:
|
|
262
297
|
if int(new_code) < int(current):
|
|
263
|
-
log(
|
|
298
|
+
log(t("version_manager.warn_code_regress", new_code=new_code, current=current))
|
|
264
299
|
except ValueError:
|
|
265
300
|
pass
|
|
266
301
|
text = read_text()
|
|
@@ -318,7 +353,7 @@ def get_higher_version(v1, v2):
|
|
|
318
353
|
def update_version_yml(new_version):
|
|
319
354
|
if not write_scalar_key("version", new_version):
|
|
320
355
|
# Without the key nothing was written yet the call looked successful, so the same version came out every time.
|
|
321
|
-
log("
|
|
356
|
+
log(t("version_manager.warn_version_missing"))
|
|
322
357
|
text = read_text()
|
|
323
358
|
line = f'version: "{new_version}"\n'
|
|
324
359
|
if re.search(r'^version_code:', text, re.MULTILINE):
|
|
@@ -327,7 +362,7 @@ def update_version_yml(new_version):
|
|
|
327
362
|
new_text = text.rstrip("\n") + "\n" + line
|
|
328
363
|
write_text(new_text)
|
|
329
364
|
if read_scalar_key("version") != new_version:
|
|
330
|
-
raise VersionSyncError(
|
|
365
|
+
raise VersionSyncError(t("version_manager.err_write_failed", version=new_version))
|
|
331
366
|
today = datetime.date.today().isoformat()
|
|
332
367
|
user = os.environ.get("GITHUB_ACTOR", "")
|
|
333
368
|
if not user:
|
|
@@ -411,16 +446,15 @@ def sync_maven(path_dir, new_version):
|
|
|
411
446
|
text = read_file(root_pom)
|
|
412
447
|
old_version = _pom_text(text, ["version"])
|
|
413
448
|
if old_version is None:
|
|
414
|
-
log(
|
|
449
|
+
log(t("version_manager.warn_spring_no_version", pom=root_pom))
|
|
415
450
|
return True
|
|
416
451
|
if "${" in old_version:
|
|
417
452
|
# CI-friendly versions such as ${revision} are managed in a property, so leave them alone.
|
|
418
|
-
log(
|
|
453
|
+
log(t("version_manager.warn_spring_property", pom=root_pom, version=old_version))
|
|
419
454
|
return True
|
|
420
455
|
new_full = _keep_snapshot(old_version, new_version)
|
|
421
456
|
new_text, _ = _pom_replace(text, ["version"], new_full)
|
|
422
|
-
|
|
423
|
-
log(f"updated: {root_pom}")
|
|
457
|
+
write_synced(root_pom, new_text)
|
|
424
458
|
|
|
425
459
|
root_artifact = _pom_text(text, ["artifactId"])
|
|
426
460
|
for child in sorted(Path(path_dir).glob("*/pom.xml")):
|
|
@@ -432,8 +466,7 @@ def sync_maven(path_dir, new_version):
|
|
|
432
466
|
ctext, _ = _pom_replace(ctext, ["parent", "version"], new_full)
|
|
433
467
|
if _pom_text(ctext, ["version"]) == old_version:
|
|
434
468
|
ctext, _ = _pom_replace(ctext, ["version"], new_full)
|
|
435
|
-
|
|
436
|
-
log(f"updated: {child}")
|
|
469
|
+
write_synced(child, ctext)
|
|
437
470
|
return True
|
|
438
471
|
|
|
439
472
|
|
|
@@ -509,7 +542,7 @@ def sync_spring(path_dir, new_version):
|
|
|
509
542
|
has_pom = sync_maven(path_dir, new_version)
|
|
510
543
|
if not candidates:
|
|
511
544
|
if not has_pom:
|
|
512
|
-
log(
|
|
545
|
+
log(t("version_manager.warn_spring_no_files", dir=path_dir))
|
|
513
546
|
return
|
|
514
547
|
for gradle_file in candidates:
|
|
515
548
|
text = read_file(gradle_file)
|
|
@@ -522,10 +555,9 @@ def sync_spring(path_dir, new_version):
|
|
|
522
555
|
replaced = f"{m.group(1)}{m.group(2)}{_keep_snapshot(m.group(3), new_version)}{m.group(2)}"
|
|
523
556
|
new_text = new_text[:m.start()] + replaced + new_text[m.end():]
|
|
524
557
|
if not matches:
|
|
525
|
-
log(
|
|
558
|
+
log(t("version_manager.warn_spring_no_gradle_line", file=gradle_file))
|
|
526
559
|
continue
|
|
527
|
-
|
|
528
|
-
log(f"updated: {gradle_file}")
|
|
560
|
+
write_synced(gradle_file, new_text)
|
|
529
561
|
|
|
530
562
|
|
|
531
563
|
def _pubspec_build_number(path_dir):
|
|
@@ -548,7 +580,7 @@ def get_reconciled_version_code():
|
|
|
548
580
|
if n is not None
|
|
549
581
|
]
|
|
550
582
|
if pubspec_codes and max(pubspec_codes) > code:
|
|
551
|
-
log(
|
|
583
|
+
log(t("version_manager.pubspec_ahead", build=max(pubspec_codes), code=code))
|
|
552
584
|
code = max(pubspec_codes)
|
|
553
585
|
set_version_code(code)
|
|
554
586
|
return code
|
|
@@ -557,7 +589,7 @@ def get_reconciled_version_code():
|
|
|
557
589
|
def sync_flutter(path_dir, new_version, version_code):
|
|
558
590
|
target = Path(path_dir) / "pubspec.yaml"
|
|
559
591
|
if not target.is_file():
|
|
560
|
-
log(
|
|
592
|
+
log(t("version_manager.warn_flutter_missing", target=target))
|
|
561
593
|
return
|
|
562
594
|
text = read_file(target)
|
|
563
595
|
full_version = f"{new_version}+{version_code}"
|
|
@@ -566,8 +598,7 @@ def sync_flutter(path_dir, new_version, version_code):
|
|
|
566
598
|
new_text = pattern.sub(r'\1 ' + full_version, text, count=1)
|
|
567
599
|
else:
|
|
568
600
|
new_text = text.rstrip("\n") + f"\nversion: {full_version}\n"
|
|
569
|
-
|
|
570
|
-
log(f"updated: {target}")
|
|
601
|
+
write_synced(target, new_text)
|
|
571
602
|
|
|
572
603
|
|
|
573
604
|
def _json_indent(text):
|
|
@@ -582,20 +613,19 @@ def _json_indent(text):
|
|
|
582
613
|
|
|
583
614
|
def sync_json_version(target, new_version, key_path):
|
|
584
615
|
if not target.is_file():
|
|
585
|
-
log(
|
|
616
|
+
log(t("version_manager.warn_target_missing", target=target))
|
|
586
617
|
return
|
|
587
618
|
raw = read_file(target)
|
|
588
619
|
try:
|
|
589
620
|
data = json.loads(raw)
|
|
590
621
|
except json.JSONDecodeError as e:
|
|
591
622
|
# Skipping and reporting success would silently leave the tag/version.yml and the package version out of step.
|
|
592
|
-
raise VersionSyncError(
|
|
623
|
+
raise VersionSyncError(t("version_manager.err_not_json", target=target, error=e))
|
|
593
624
|
node = data
|
|
594
625
|
for k in key_path[:-1]:
|
|
595
626
|
node = node.setdefault(k, {})
|
|
596
627
|
node[key_path[-1]] = new_version
|
|
597
|
-
|
|
598
|
-
log(f"updated: {target}")
|
|
628
|
+
write_synced(target, json.dumps(data, indent=_json_indent(raw), ensure_ascii=False) + "\n")
|
|
599
629
|
|
|
600
630
|
|
|
601
631
|
_TOML_HEADER_RE = re.compile(r'^[ \t]*\[+[ \t]*([^\]\n]+?)[ \t]*\]+[ \t]*(?:#.*)?$', re.MULTILINE)
|
|
@@ -641,12 +671,11 @@ def sync_python(path_dir, new_version):
|
|
|
641
671
|
spans = _python_version_spans(path_dir)
|
|
642
672
|
if not spans:
|
|
643
673
|
# Setups that keep the version out of the file (e.g. dynamic = ["version"]) have nowhere to write it.
|
|
644
|
-
log(
|
|
674
|
+
log(t("version_manager.warn_python_no_version", dir=path_dir))
|
|
645
675
|
return
|
|
646
676
|
for target, (start, end) in spans:
|
|
647
677
|
text = read_file(target)
|
|
648
|
-
|
|
649
|
-
log(f"updated: {target}")
|
|
678
|
+
write_synced(target, text[:start] + new_version + text[end:])
|
|
650
679
|
|
|
651
680
|
|
|
652
681
|
_PLIST_VERSION_RE = re.compile(r'(<key>CFBundleShortVersionString</key>\s*<string>)([^<]*)(</string>)')
|
|
@@ -673,33 +702,31 @@ def sync_react_native(path_dir, new_version):
|
|
|
673
702
|
continue
|
|
674
703
|
# A build-variable reference such as $(MARKETING_VERSION) is owned by the Xcode settings, so do not overwrite it.
|
|
675
704
|
if m.group(2).strip().startswith("$("):
|
|
676
|
-
log(
|
|
705
|
+
log(t("version_manager.skipped_plist", file=plist_file))
|
|
677
706
|
continue
|
|
678
707
|
new_text = _PLIST_VERSION_RE.sub(lambda mm: mm.group(1) + new_version + mm.group(3), text)
|
|
679
|
-
|
|
680
|
-
log(f"updated: {plist_file}")
|
|
708
|
+
write_synced(plist_file, new_text)
|
|
681
709
|
found_plist = True
|
|
682
710
|
else:
|
|
683
|
-
log(
|
|
711
|
+
log(t("version_manager.warn_rn_ios_missing", dir=ios_dir))
|
|
684
712
|
|
|
685
713
|
gradle_file = Path(path_dir) / "android" / "app" / "build.gradle"
|
|
686
714
|
if gradle_file.is_file():
|
|
687
715
|
text = read_file(gradle_file)
|
|
688
716
|
new_text = re.sub(r'versionName\s+"[^"]*"', f'versionName "{new_version}"', text)
|
|
689
|
-
|
|
690
|
-
log(f"updated: {gradle_file}")
|
|
717
|
+
write_synced(gradle_file, new_text)
|
|
691
718
|
else:
|
|
692
|
-
log(
|
|
719
|
+
log(t("version_manager.warn_rn_gradle_missing", file=gradle_file))
|
|
693
720
|
|
|
694
721
|
if not found_plist and not gradle_file.is_file():
|
|
695
|
-
log(
|
|
722
|
+
log(t("version_manager.warn_rn_no_targets", dir=path_dir))
|
|
696
723
|
|
|
697
724
|
|
|
698
725
|
def sync_for_type(project_type, new_version, version_code_getter):
|
|
699
726
|
path_dir = get_type_path(project_type)
|
|
700
727
|
handler = TYPE_HANDLERS.get(project_type)
|
|
701
728
|
if handler is None:
|
|
702
|
-
log(
|
|
729
|
+
log(t("version_manager.warn_unknown_type", type=project_type))
|
|
703
730
|
return
|
|
704
731
|
handler.sync(path_dir, new_version, version_code_getter)
|
|
705
732
|
|
|
@@ -709,17 +736,17 @@ def sync_all_project_files(new_version):
|
|
|
709
736
|
if not types:
|
|
710
737
|
# No silent fallback: an unreadable project_types used to degrade to
|
|
711
738
|
# "basic" and skip every sync without a word.
|
|
712
|
-
raise SystemExit("
|
|
739
|
+
raise SystemExit(t("version_manager.err_no_types_sync"))
|
|
713
740
|
errors = []
|
|
714
|
-
for
|
|
741
|
+
for ptype in types:
|
|
715
742
|
# Keep syncing the other types when one fails; collect failures and report them via the exit code at the end.
|
|
716
743
|
try:
|
|
717
|
-
sync_for_type(
|
|
744
|
+
sync_for_type(ptype, new_version, get_reconciled_version_code)
|
|
718
745
|
except VersionSyncError as e:
|
|
719
|
-
log(
|
|
720
|
-
errors.append(
|
|
746
|
+
log(t("version_manager.err_type", type=ptype, error=e))
|
|
747
|
+
errors.append(ptype)
|
|
721
748
|
if errors:
|
|
722
|
-
raise VersionSyncError(
|
|
749
|
+
raise VersionSyncError(t("version_manager.err_sync_failed", types=", ".join(errors)))
|
|
723
750
|
|
|
724
751
|
|
|
725
752
|
README_VERSION_LINE_RE = re.compile(
|
|
@@ -749,7 +776,7 @@ def update_readme_version(new_version, path="README.md"):
|
|
|
749
776
|
return False
|
|
750
777
|
lines[i + 1] = new_line
|
|
751
778
|
write_file(p, "\n".join(lines))
|
|
752
|
-
log(
|
|
779
|
+
log(t("version_manager.readme_updated", version=new_version))
|
|
753
780
|
return True
|
|
754
781
|
return False
|
|
755
782
|
|
|
@@ -859,7 +886,6 @@ TYPE_HANDLERS = {
|
|
|
859
886
|
# Only types that need a build number compute version_code (this has a pubspec-adjusting side effect).
|
|
860
887
|
"flutter": TypeHandler(read=_read_flutter, sync=lambda d, v, code: sync_flutter(d, v, code())),
|
|
861
888
|
"react": _PACKAGE_JSON,
|
|
862
|
-
"next": _PACKAGE_JSON,
|
|
863
889
|
"node": _PACKAGE_JSON,
|
|
864
890
|
"python": TypeHandler(read=_read_python, sync=lambda d, v, _code: sync_python(d, v)),
|
|
865
891
|
"react-native": TypeHandler(read=_read_react_native, sync=lambda d, v, _code: sync_react_native(d, v)),
|
|
@@ -880,7 +906,7 @@ def get_project_file_version(project_type):
|
|
|
880
906
|
if handler is not None:
|
|
881
907
|
version = handler.read(path_dir)
|
|
882
908
|
except Exception as e:
|
|
883
|
-
log(
|
|
909
|
+
log(t("version_manager.warn_read_failed", type=project_type, error=e))
|
|
884
910
|
version = None
|
|
885
911
|
|
|
886
912
|
if not version:
|
|
@@ -892,32 +918,35 @@ def sync_versions():
|
|
|
892
918
|
yml_version = get_current_version()
|
|
893
919
|
types = get_project_types_csv()
|
|
894
920
|
if not types:
|
|
895
|
-
raise SystemExit("
|
|
921
|
+
raise SystemExit(t("version_manager.err_no_types_check"))
|
|
896
922
|
primary_type = types[0]
|
|
897
923
|
project_version = get_project_file_version(primary_type)
|
|
898
924
|
|
|
899
|
-
log("Version sync check")
|
|
900
|
-
log(f" version.yml: {yml_version}")
|
|
901
|
-
log(f" project file: {project_version}")
|
|
902
|
-
|
|
903
925
|
if yml_version != project_version:
|
|
926
|
+
# The detail lines are only useful when the two disagree; an in-sync check prints a single line
|
|
927
|
+
# (this check runs several times per job, so a multi-line block each time is just noise).
|
|
928
|
+
log(t("version_manager.sync_check"))
|
|
929
|
+
log(t("version_manager.sync_yml_version", version=yml_version))
|
|
930
|
+
log(t("version_manager.sync_project_version", version=project_version))
|
|
904
931
|
if validate_version(yml_version) and validate_version(project_version):
|
|
905
932
|
higher = get_higher_version(yml_version, project_version)
|
|
906
|
-
log(
|
|
933
|
+
log(t("version_manager.sync_mismatch", version=higher))
|
|
907
934
|
if higher != yml_version:
|
|
908
935
|
update_version_yml(higher)
|
|
909
936
|
if higher != project_version:
|
|
910
937
|
sync_all_project_files(higher)
|
|
911
938
|
return higher
|
|
912
939
|
else:
|
|
913
|
-
log("
|
|
940
|
+
log(t("version_manager.warn_format_invalid"))
|
|
914
941
|
return yml_version
|
|
915
942
|
else:
|
|
916
|
-
types
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
943
|
+
# The primary type matches, but the other types' files may still lag behind; reconcile them quietly.
|
|
944
|
+
changed_before = len(_CHANGED_FILES)
|
|
945
|
+
sync_all_project_files(yml_version)
|
|
946
|
+
if len(_CHANGED_FILES) > changed_before:
|
|
947
|
+
log(t("version_manager.sync_reconciled", version=yml_version))
|
|
948
|
+
else:
|
|
949
|
+
log(t("version_manager.sync_ok", version=yml_version))
|
|
921
950
|
return yml_version
|
|
922
951
|
|
|
923
952
|
|
|
@@ -952,7 +981,7 @@ def cmd_increment(args):
|
|
|
952
981
|
require_version_yml()
|
|
953
982
|
current_version = sync_versions()
|
|
954
983
|
if not validate_version(current_version):
|
|
955
|
-
log(
|
|
984
|
+
log(t("version_manager.err_invalid_version", version=current_version))
|
|
956
985
|
return 1
|
|
957
986
|
bump = getattr(args, "bump", None) or "patch"
|
|
958
987
|
new_version = increment_version(current_version, bump)
|
|
@@ -968,7 +997,7 @@ def cmd_set(args):
|
|
|
968
997
|
require_version_yml()
|
|
969
998
|
new_version = args.version
|
|
970
999
|
if not validate_version(new_version):
|
|
971
|
-
log(
|
|
1000
|
+
log(t("version_manager.err_invalid_version_xyz", version=new_version))
|
|
972
1001
|
return 1
|
|
973
1002
|
update_all_versions(new_version)
|
|
974
1003
|
print(new_version)
|
|
@@ -1017,7 +1046,7 @@ def main(argv=None):
|
|
|
1017
1046
|
try:
|
|
1018
1047
|
return handler(args)
|
|
1019
1048
|
except VersionSyncError as e:
|
|
1020
|
-
log(
|
|
1049
|
+
log(t("version_manager.err_generic", error=e))
|
|
1021
1050
|
return 1
|
|
1022
1051
|
|
|
1023
1052
|
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
# workflows call GitHub Copilot CLI, which consumes GitHub Copilot AI Credits.
|
|
11
11
|
# Falls back to rule-based summaries whenever Copilot is off or unavailable.
|
|
12
12
|
# ===
|
|
13
|
-
# Supported project types: spring, flutter,
|
|
13
|
+
# Supported project types: spring, flutter, react, react-native,
|
|
14
14
|
# react-native-expo, node, python, basic, go
|
|
15
15
|
# Multi-type projects: list every type in the project_types array
|
|
16
16
|
# -> the first entry is the primary type (project_types is the single source of truth)
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
# Synced files per type:
|
|
21
21
|
# - spring: build.gradle / build.gradle.kts / pom.xml
|
|
22
22
|
# - flutter: pubspec.yaml
|
|
23
|
-
# -
|
|
23
|
+
# - react/node: package.json
|
|
24
24
|
# - react-native: Info.plist + build.gradle
|
|
25
25
|
# - react-native-expo: app.json
|
|
26
26
|
# - python: pyproject.toml / setup.py
|
|
@@ -53,11 +53,13 @@ jobs:
|
|
|
53
53
|
- name: Read copilot_ai option from version.yml
|
|
54
54
|
id: copilot_options
|
|
55
55
|
run: |
|
|
56
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
57
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
56
58
|
# scoped to the metadata: -> template: -> options: chain so an
|
|
57
59
|
# unrelated "copilot_ai:" key can never hijack the value
|
|
58
60
|
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
61
|
echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
|
|
60
|
-
echo "
|
|
62
|
+
echo "$(m wf_common.copilot_ai_option copilot_ai="$COPILOT_AI")"
|
|
61
63
|
|
|
62
64
|
- name: Install Copilot CLI
|
|
63
65
|
if: steps.copilot_options.outputs.copilot_ai == 'true'
|
|
@@ -74,6 +76,8 @@ jobs:
|
|
|
74
76
|
COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
|
|
75
77
|
GH_TOKEN: ${{ github.token }}
|
|
76
78
|
run: |
|
|
79
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
80
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
77
81
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
78
82
|
|
|
79
83
|
git fetch origin {{MAIN_BRANCH}}
|
|
@@ -105,7 +109,7 @@ jobs:
|
|
|
105
109
|
fi
|
|
106
110
|
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
111
|
VERSION=${VERSION:-Unreleased}
|
|
108
|
-
echo "
|
|
112
|
+
echo "$(m wf_aisum.expected_version version="$VERSION" current_version="${CURRENT_VERSION:-none}" mode="$MODE" bump="${BUMP:-patch}")"
|
|
109
113
|
|
|
110
114
|
SUMMARY_RESULT=$(python3 .github/scripts/changelog_manager.py ai-summary \
|
|
111
115
|
--commits-file commits.txt \
|
|
@@ -136,10 +140,10 @@ jobs:
|
|
|
136
140
|
--jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$MARKER\"))) | .id" 2>/dev/null | tail -n 1 || true)
|
|
137
141
|
if [ -n "$COMMENT_ID" ]; then
|
|
138
142
|
gh api -X PATCH "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" --input comment_payload.json > /dev/null \
|
|
139
|
-
|| echo "
|
|
143
|
+
|| echo "$(m wf_aisum.comment_update_failed)"
|
|
140
144
|
else
|
|
141
145
|
gh api -X POST "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" --input comment_payload.json > /dev/null \
|
|
142
|
-
|| echo "
|
|
146
|
+
|| echo "$(m wf_aisum.comment_post_failed)"
|
|
143
147
|
fi
|
|
144
148
|
|
|
145
149
|
- name: Link related issue via branch name
|
|
@@ -152,6 +156,7 @@ jobs:
|
|
|
152
156
|
# AUTO-CHANGELOG-CONTROL).
|
|
153
157
|
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
|
154
158
|
run: |
|
|
159
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
155
160
|
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
156
161
|
ISSUE_NUM=$(python3 .github/scripts/issue_helper.py extract-branch-issue "$HEAD_REF")
|
|
157
162
|
|