project-auto-wizard 0.13.2 → 0.14.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.md +1 -1
- package/bin/project-auto-wizard.js +18 -4
- package/package.json +2 -2
- package/payload/config/breaking-changes.json +28 -14
- package/payload/config/wizard-prompts.yml +102 -46
- package/payload/flutter-app/android/fastlane/Fastfile.playstore +44 -32
- package/payload/flutter-app/ios/ExportOptions.plist +8 -8
- package/payload/flutter-app/ios/fastlane/Fastfile +57 -43
- package/payload/scripts/changelog_manager.py +296 -257
- package/payload/scripts/issue_helper.py +54 -50
- package/payload/scripts/messages.py +2279 -0
- package/payload/scripts/truncate_release_notes.py +7 -6
- package/payload/scripts/version_manager.py +64 -61
- package/payload/version.yml.template +2 -1
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +17 -12
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +59 -34
- package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +17 -15
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +17 -10
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +54 -44
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +24 -17
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +194 -184
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +226 -213
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +46 -42
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +334 -241
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +152 -128
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +246 -188
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +353 -238
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +96 -85
- package/payload/workflows/go/PROJECT-GO-CI.yaml +47 -36
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +945 -508
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +155 -133
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +76 -60
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +97 -77
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +62 -47
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +942 -505
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +148 -126
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +86 -70
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +112 -92
- package/payload/workflows/spring/PROJECT-SPRING-CI.yml +112 -82
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +180 -150
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +161 -131
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +970 -549
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +193 -162
- package/src/cli/args.js +98 -52
- package/src/cli/help.js +8 -41
- package/src/commands/doctor.js +85 -89
- package/src/commands/dry-run.js +63 -43
- package/src/commands/full.js +79 -77
- package/src/commands/install-settings.js +16 -15
- package/src/commands/interactive-flutter.js +20 -19
- package/src/commands/interactive.js +135 -132
- package/src/commands/purge.js +47 -42
- package/src/commands/status.js +36 -36
- package/src/commands/uninstall.js +43 -41
- package/src/context.js +12 -11
- package/src/core/assets.js +12 -11
- package/src/core/baseline.js +27 -27
- package/src/core/branches.js +29 -27
- package/src/core/branding.js +7 -6
- package/src/core/breaking-check.js +22 -21
- package/src/core/breaking.js +15 -8
- package/src/core/copy/app-files.js +5 -4
- package/src/core/copy/flutter-app.js +8 -8
- package/src/core/copy/gitignore.js +58 -50
- package/src/core/copy/readme.js +45 -35
- package/src/core/copy/simple.js +16 -15
- package/src/core/copy/workflows.js +131 -124
- package/src/core/deploy-style.js +51 -48
- package/src/core/detect-fs.js +57 -57
- package/src/core/detect.js +67 -65
- package/src/core/errors.js +2 -2
- package/src/core/flutter-doctor.js +17 -16
- package/src/core/flutter-hooks.js +33 -32
- package/src/core/flutter-options.js +34 -33
- package/src/core/fsutil.js +12 -12
- package/src/core/installed-stores.js +4 -4
- package/src/core/logger.js +42 -40
- package/src/core/paths-resolve.js +88 -86
- package/src/core/paths.js +10 -10
- package/src/core/release-options.js +8 -8
- package/src/core/removal-exec.js +14 -13
- package/src/core/removal-plan.js +37 -37
- package/src/core/types.js +47 -47
- package/src/core/verify.js +44 -38
- package/src/core/version-yml.js +76 -68
- package/src/core/wizard-env.js +46 -46
- package/src/core/wizard-labels.js +63 -41
- package/src/i18n/catalog/en/cli.js +50 -0
- package/src/i18n/catalog/en/commands.js +187 -0
- package/src/i18n/catalog/en/copy.js +19 -0
- package/src/i18n/catalog/en/core.js +74 -0
- package/src/i18n/catalog/en/core2.js +15 -0
- package/src/i18n/catalog/en/ui.js +221 -0
- package/src/i18n/catalog/en.js +12 -0
- package/src/i18n/catalog/index.js +5 -0
- package/src/i18n/catalog/ko/cli.js +50 -0
- package/src/i18n/catalog/ko/commands.js +187 -0
- package/src/i18n/catalog/ko/copy.js +19 -0
- package/src/i18n/catalog/ko/core.js +74 -0
- package/src/i18n/catalog/ko/core2.js +15 -0
- package/src/i18n/catalog/ko/ui.js +221 -0
- package/src/i18n/catalog/ko.js +10 -0
- package/src/i18n/index.js +44 -0
- package/src/i18n/languages.js +9 -0
- package/src/index.js +128 -94
- package/src/ui/ansi.js +7 -7
- package/src/ui/banner.js +7 -6
- package/src/ui/env-plan.js +82 -81
- package/src/ui/prompts.js +97 -122
- package/src/ui/readline-engine.js +59 -58
- package/src/ui/status-cards.js +37 -31
- package/src/ui/summary.js +89 -88
|
@@ -31,6 +31,8 @@ Contract:
|
|
|
31
31
|
import argparse
|
|
32
32
|
import sys
|
|
33
33
|
|
|
34
|
+
from messages import t, use_utf8_output
|
|
35
|
+
|
|
34
36
|
|
|
35
37
|
def log(message):
|
|
36
38
|
print(message, file=sys.stderr)
|
|
@@ -47,19 +49,19 @@ def run(path, limit, mode):
|
|
|
47
49
|
with open(path, "r", encoding="utf-8", newline="", errors="replace") as f:
|
|
48
50
|
text = f.read()
|
|
49
51
|
except FileNotFoundError:
|
|
50
|
-
log(
|
|
52
|
+
log(t("truncate.file_missing", path=path))
|
|
51
53
|
return 0
|
|
52
54
|
|
|
53
55
|
length = len(text) if mode == "char" else len(text.encode("utf-8"))
|
|
54
56
|
if length <= limit:
|
|
55
|
-
log(
|
|
57
|
+
log(t("truncate.within_limit", length=length, limit=limit, mode=mode, path=path))
|
|
56
58
|
return 0
|
|
57
59
|
|
|
58
60
|
truncated = truncate_text(text, limit, mode)
|
|
59
61
|
with open(path, "w", encoding="utf-8", newline="") as f:
|
|
60
62
|
f.write(truncated)
|
|
61
63
|
|
|
62
|
-
log(
|
|
64
|
+
log(t("truncate.truncated", length=length, limit=limit, mode=mode, path=path))
|
|
63
65
|
return 0
|
|
64
66
|
|
|
65
67
|
|
|
@@ -72,14 +74,13 @@ def build_parser():
|
|
|
72
74
|
|
|
73
75
|
|
|
74
76
|
def main(argv=None):
|
|
75
|
-
|
|
76
|
-
sys.stderr.reconfigure(errors="replace")
|
|
77
|
+
use_utf8_output()
|
|
77
78
|
|
|
78
79
|
parser = build_parser()
|
|
79
80
|
args = parser.parse_args(argv)
|
|
80
81
|
|
|
81
82
|
if args.limit <= 0:
|
|
82
|
-
log(
|
|
83
|
+
log(t("truncate.limit_positive", limit=args.limit))
|
|
83
84
|
return 1
|
|
84
85
|
|
|
85
86
|
return run(args.file, args.limit, args.mode)
|
|
@@ -38,26 +38,28 @@ import sys
|
|
|
38
38
|
from pathlib import Path
|
|
39
39
|
from typing import Callable, NamedTuple
|
|
40
40
|
|
|
41
|
+
from messages import t, use_utf8_output
|
|
42
|
+
|
|
41
43
|
VERSION_YML = "version.yml"
|
|
42
44
|
|
|
43
45
|
|
|
44
46
|
class VersionSyncError(Exception):
|
|
45
|
-
"""version.yml
|
|
47
|
+
"""The version could not be written to version.yml or a project file — reported via exit 1."""
|
|
46
48
|
SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+$")
|
|
47
49
|
|
|
48
|
-
#
|
|
49
|
-
# version.yml
|
|
50
|
-
#
|
|
50
|
+
# How project-file versions are interpreted — must match the install-time detection (src/core/detect.js coreVersion).
|
|
51
|
+
# version.yml only stores and bumps x.y.z, so 1.2.3-rc.1, 1.2.3+4 and 1.2.0-SNAPSHOT in a file are read as the core x.y.z.
|
|
52
|
+
# Reading them differently would make the value right after install disagree with the one read at the first release.
|
|
51
53
|
_CORE_VERSION_RE = re.compile(r"^v?(\d+\.\d+\.\d+)(?:[-+][0-9A-Za-z.+-]*)?$")
|
|
52
54
|
|
|
53
|
-
# Gradle
|
|
54
|
-
#
|
|
55
|
-
# rc.1
|
|
55
|
+
# Gradle/Maven -SNAPSHOT is not a pre-release of a specific version but an "in-development build" marker (it selects the
|
|
56
|
+
# publishing repository and appears in artifact names). So it is kept and re-appended after a version bump.
|
|
57
|
+
# Pre-releases such as rc.1 or beta.1 belong to that version only, so they are dropped when releasing a new x.y.z.
|
|
56
58
|
SNAPSHOT_SUFFIX = "-SNAPSHOT"
|
|
57
59
|
|
|
58
60
|
|
|
59
61
|
def core_version(value):
|
|
60
|
-
"""x.y.z[-pre][+build]
|
|
62
|
+
"""x.y.z[-pre][+build] -> x.y.z. None if the value is not a version."""
|
|
61
63
|
m = _CORE_VERSION_RE.match(str(value or "").strip())
|
|
62
64
|
return m.group(1) if m else None
|
|
63
65
|
|
|
@@ -287,7 +289,7 @@ def increment_patch(version):
|
|
|
287
289
|
|
|
288
290
|
|
|
289
291
|
def increment_version(version, bump="patch"):
|
|
290
|
-
"""bump: 'major'|'minor'|'patch'.
|
|
292
|
+
"""bump: 'major'|'minor'|'patch'. Defaults to patch when omitted."""
|
|
291
293
|
if bump == "major":
|
|
292
294
|
major, _minor, _patch = version.split(".")
|
|
293
295
|
return f"{int(major) + 1}.0.0"
|
|
@@ -315,7 +317,7 @@ def get_higher_version(v1, v2):
|
|
|
315
317
|
|
|
316
318
|
def update_version_yml(new_version):
|
|
317
319
|
if not write_scalar_key("version", new_version):
|
|
318
|
-
#
|
|
320
|
+
# Without the key nothing was written yet the call looked successful, so the same version came out every time.
|
|
319
321
|
log("WARNING: version field missing in version.yml, adding it")
|
|
320
322
|
text = read_text()
|
|
321
323
|
line = f'version: "{new_version}"\n'
|
|
@@ -365,16 +367,16 @@ _XML_TOKEN_RE = re.compile(
|
|
|
365
367
|
|
|
366
368
|
|
|
367
369
|
def _pom_text_span(text, path):
|
|
368
|
-
"""
|
|
369
|
-
<parent
|
|
370
|
+
"""Text span (start, end) of the element at `path` under the root (<project>). None if absent.
|
|
371
|
+
A <version> inside <parent>/<dependencies> is another artifact's version, so depth tells them apart."""
|
|
370
372
|
stack = []
|
|
371
373
|
start = None
|
|
372
374
|
target = list(path)
|
|
373
375
|
for m in _XML_TOKEN_RE.finditer(text):
|
|
374
376
|
name = m.group(2)
|
|
375
377
|
if not name:
|
|
376
|
-
continue #
|
|
377
|
-
if m.group(1): #
|
|
378
|
+
continue # comment / CDATA / declaration
|
|
379
|
+
if m.group(1): # closing tag
|
|
378
380
|
if start is not None and stack[1:] == target:
|
|
379
381
|
return start, m.start()
|
|
380
382
|
if stack:
|
|
@@ -401,8 +403,8 @@ def _pom_replace(text, path, value):
|
|
|
401
403
|
|
|
402
404
|
|
|
403
405
|
def sync_maven(path_dir, new_version):
|
|
404
|
-
"""
|
|
405
|
-
<parent><version>(
|
|
406
|
+
"""Only change the project's own <version> in pom.xml. A child module follows only the
|
|
407
|
+
<parent><version> that points at the root (and its own <version> if it equaled the root's)."""
|
|
406
408
|
root_pom = Path(path_dir) / "pom.xml"
|
|
407
409
|
if not root_pom.is_file():
|
|
408
410
|
return False
|
|
@@ -412,7 +414,7 @@ def sync_maven(path_dir, new_version):
|
|
|
412
414
|
log(f"WARNING: spring: {root_pom} has no project <version> (inherited from parent?) — skipping")
|
|
413
415
|
return True
|
|
414
416
|
if "${" in old_version:
|
|
415
|
-
# ${revision}
|
|
417
|
+
# CI-friendly versions such as ${revision} are managed in a property, so leave them alone.
|
|
416
418
|
log(f"WARNING: spring: {root_pom} <version> is a property ({old_version}) — skipping")
|
|
417
419
|
return True
|
|
418
420
|
new_full = _keep_snapshot(old_version, new_version)
|
|
@@ -440,12 +442,12 @@ _GRADLE_SHARED_BLOCKS = ("allprojects", "subprojects")
|
|
|
440
442
|
|
|
441
443
|
|
|
442
444
|
def _gradle_code(line, quote=""):
|
|
443
|
-
"""
|
|
444
|
-
|
|
445
|
+
"""Strip the comment from a line and blank out quoted text, so the `//` in a url 'https://...' or
|
|
446
|
+
a brace inside a string cannot disturb block-depth counting. Same rule as detect.js.
|
|
445
447
|
|
|
446
|
-
quote
|
|
447
|
-
(
|
|
448
|
-
|
|
448
|
+
`quote` is the quote that was open when this line started. Triple quotes (\"\"\" ''') span lines, so
|
|
449
|
+
return (code, quote still open at end of line) for the next line to continue. A single-line quote
|
|
450
|
+
is treated as closed at end of line."""
|
|
449
451
|
out, i = [], 0
|
|
450
452
|
while i < len(line):
|
|
451
453
|
ch = line[i]
|
|
@@ -468,14 +470,14 @@ def _gradle_code(line, quote=""):
|
|
|
468
470
|
|
|
469
471
|
|
|
470
472
|
def _gradle_version_matches(text):
|
|
471
|
-
"""
|
|
473
|
+
"""Matches of the project version lines. Reading and syncing share this one place so they touch the same lines.
|
|
472
474
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
475
|
+
If an unindented `version =` exists, only that is used. Only when there is none are indented lines
|
|
476
|
+
inside allprojects/subprojects blocks accepted — mistaking a plugin config block such as `node { version = '20.11.0' }`
|
|
477
|
+
for the project version would make the version jump and break the build config."""
|
|
476
478
|
top, shared = [], []
|
|
477
|
-
stack = [] #
|
|
478
|
-
quote = "" #
|
|
479
|
+
stack = [] # open block names: the last word of each line ending in `{`
|
|
480
|
+
quote = "" # the open quote while inside a multi-line string; lines inside a string are not code
|
|
479
481
|
pos = 0
|
|
480
482
|
for line in text.splitlines(keepends=True):
|
|
481
483
|
m = None if quote else _GRADLE_VERSION_RE.match(line)
|
|
@@ -511,11 +513,11 @@ def sync_spring(path_dir, new_version):
|
|
|
511
513
|
return
|
|
512
514
|
for gradle_file in candidates:
|
|
513
515
|
text = read_file(gradle_file)
|
|
514
|
-
#
|
|
515
|
-
#
|
|
516
|
+
# Only a `version =` at the start of a line is the project version. Without the anchor,
|
|
517
|
+
# dependency version variables such as kotlin_version would change too and break the build.
|
|
516
518
|
matches = _gradle_version_matches(text)
|
|
517
519
|
new_text = text
|
|
518
|
-
#
|
|
520
|
+
# Replace from the end so earlier spans do not shift.
|
|
519
521
|
for m in reversed(matches):
|
|
520
522
|
replaced = f"{m.group(1)}{m.group(2)}{_keep_snapshot(m.group(3), new_version)}{m.group(2)}"
|
|
521
523
|
new_text = new_text[:m.start()] + replaced + new_text[m.end():]
|
|
@@ -527,7 +529,7 @@ def sync_spring(path_dir, new_version):
|
|
|
527
529
|
|
|
528
530
|
|
|
529
531
|
def _pubspec_build_number(path_dir):
|
|
530
|
-
"""pubspec.yaml `version: x.y.z+N
|
|
532
|
+
"""N in pubspec.yaml `version: x.y.z+N`. None if absent."""
|
|
531
533
|
target = Path(path_dir) / "pubspec.yaml"
|
|
532
534
|
if not target.is_file():
|
|
533
535
|
return None
|
|
@@ -536,9 +538,9 @@ def _pubspec_build_number(path_dir):
|
|
|
536
538
|
|
|
537
539
|
|
|
538
540
|
def get_reconciled_version_code():
|
|
539
|
-
"""version.yml
|
|
540
|
-
|
|
541
|
-
|
|
541
|
+
"""The larger of version.yml version_code and the +N in pubspec.yaml.
|
|
542
|
+
If pubspec is ahead (e.g. after a manual local release), the store rejects a lower build number,
|
|
543
|
+
so that value is adopted into version.yml to prevent a regression."""
|
|
542
544
|
code = int(get_version_code())
|
|
543
545
|
types = get_project_types_csv()
|
|
544
546
|
pubspec_codes = [
|
|
@@ -586,7 +588,7 @@ def sync_json_version(target, new_version, key_path):
|
|
|
586
588
|
try:
|
|
587
589
|
data = json.loads(raw)
|
|
588
590
|
except json.JSONDecodeError as e:
|
|
589
|
-
#
|
|
591
|
+
# Skipping and reporting success would silently leave the tag/version.yml and the package version out of step.
|
|
590
592
|
raise VersionSyncError(f"{target} is not valid JSON ({e}) — cannot write version")
|
|
591
593
|
node = data
|
|
592
594
|
for k in key_path[:-1]:
|
|
@@ -601,8 +603,8 @@ _TOML_VERSION_RE = re.compile(r'^[ \t]*version[ \t]*=[ \t]*([\'"])([^\'"\n]*)\1'
|
|
|
601
603
|
|
|
602
604
|
|
|
603
605
|
def _pyproject_version_span(text):
|
|
604
|
-
"""[project]
|
|
605
|
-
|
|
606
|
+
"""Value span of `version` in the [project] or [tool.poetry] section. A `version =` in other sections
|
|
607
|
+
such as [tool.*] is tool config, not the package version."""
|
|
606
608
|
headers = list(_TOML_HEADER_RE.finditer(text))
|
|
607
609
|
for i, h in enumerate(headers):
|
|
608
610
|
if h.group(1) not in ("project", "tool.poetry"):
|
|
@@ -614,13 +616,13 @@ def _pyproject_version_span(text):
|
|
|
614
616
|
return None
|
|
615
617
|
|
|
616
618
|
|
|
617
|
-
# setup(version="x.y.z"). python_requires
|
|
619
|
+
# setup(version="x.y.z"). Other keys such as python_requires / python_version are filtered out by the preceding character.
|
|
618
620
|
_SETUP_PY_VERSION_RE = re.compile(r"""(?<![\w.])version\s*=\s*(['"])([^'"]+)\1""")
|
|
619
621
|
|
|
620
622
|
|
|
621
623
|
def _python_version_spans(path_dir):
|
|
622
|
-
"""
|
|
623
|
-
setup.py
|
|
624
|
+
"""Files and value spans for reading/writing the version. When reading, earlier entries win (pyproject.toml -> setup.py).
|
|
625
|
+
Projects using only setup.py also have their version read at install time, so releases must read and write the same file."""
|
|
624
626
|
spans = []
|
|
625
627
|
pyproject = Path(path_dir) / "pyproject.toml"
|
|
626
628
|
if pyproject.is_file():
|
|
@@ -638,7 +640,7 @@ def _python_version_spans(path_dir):
|
|
|
638
640
|
def sync_python(path_dir, new_version):
|
|
639
641
|
spans = _python_version_spans(path_dir)
|
|
640
642
|
if not spans:
|
|
641
|
-
# dynamic = ["version"]
|
|
643
|
+
# Setups that keep the version out of the file (e.g. dynamic = ["version"]) have nowhere to write it.
|
|
642
644
|
log(f"WARNING: python: no version in pyproject.toml [project]/[tool.poetry] or setup.py under {path_dir} — skipping")
|
|
643
645
|
return
|
|
644
646
|
for target, (start, end) in spans:
|
|
@@ -651,8 +653,8 @@ _PLIST_VERSION_RE = re.compile(r'(<key>CFBundleShortVersionString</key>\s*<strin
|
|
|
651
653
|
|
|
652
654
|
|
|
653
655
|
def _rn_app_plists(ios_dir):
|
|
654
|
-
"""
|
|
655
|
-
|
|
656
|
+
"""Only the app target Info.plist files, sorted by name. Pods, build output and test targets do not carry our version.
|
|
657
|
+
Chosen in one place so reading and syncing see the same set of files."""
|
|
656
658
|
skip = {"Pods", "build"}
|
|
657
659
|
return [
|
|
658
660
|
p for p in sorted(ios_dir.glob("*/Info.plist"))
|
|
@@ -669,7 +671,7 @@ def sync_react_native(path_dir, new_version):
|
|
|
669
671
|
m = _PLIST_VERSION_RE.search(text)
|
|
670
672
|
if not m:
|
|
671
673
|
continue
|
|
672
|
-
# $(MARKETING_VERSION)
|
|
674
|
+
# A build-variable reference such as $(MARKETING_VERSION) is owned by the Xcode settings, so do not overwrite it.
|
|
673
675
|
if m.group(2).strip().startswith("$("):
|
|
674
676
|
log(f"skipped: {plist_file} — CFBundleShortVersionString references a build variable")
|
|
675
677
|
continue
|
|
@@ -710,7 +712,7 @@ def sync_all_project_files(new_version):
|
|
|
710
712
|
raise SystemExit("ERROR: version.yml has no readable project_types — cannot sync project files")
|
|
711
713
|
errors = []
|
|
712
714
|
for t in types:
|
|
713
|
-
#
|
|
715
|
+
# Keep syncing the other types when one fails; collect failures and report them via the exit code at the end.
|
|
714
716
|
try:
|
|
715
717
|
sync_for_type(t, new_version, get_reconciled_version_code)
|
|
716
718
|
except VersionSyncError as e:
|
|
@@ -725,11 +727,11 @@ README_VERSION_LINE_RE = re.compile(
|
|
|
725
727
|
|
|
726
728
|
|
|
727
729
|
def update_readme_version(new_version, path="README.md"):
|
|
728
|
-
"""
|
|
730
|
+
"""Rewrite only the version line right below the AUTO-VERSION-SECTION marker.
|
|
729
731
|
|
|
730
|
-
README
|
|
731
|
-
|
|
732
|
-
|
|
732
|
+
The README update workflow runs after the tag is created, so unless the README is part of the
|
|
733
|
+
version-confirm commit, the README at the tag points one version behind. Everything else, such as
|
|
734
|
+
inserting the marker or line, is left to that workflow; here only an existing line is fixed (date presence is kept)."""
|
|
733
735
|
p = Path(path)
|
|
734
736
|
if not p.is_file():
|
|
735
737
|
return False
|
|
@@ -763,7 +765,7 @@ def update_all_versions(new_version):
|
|
|
763
765
|
# ===================================================================
|
|
764
766
|
|
|
765
767
|
def _read_spring(path_dir):
|
|
766
|
-
# build.gradle
|
|
768
|
+
# If build.gradle has no version (e.g. only an allprojects config), try build.gradle.kts, then pom.xml.
|
|
767
769
|
for name in ("build.gradle", "build.gradle.kts"):
|
|
768
770
|
p = Path(path_dir) / name
|
|
769
771
|
if p.is_file():
|
|
@@ -796,9 +798,9 @@ def _read_package_json(path_dir):
|
|
|
796
798
|
|
|
797
799
|
|
|
798
800
|
def _read_react_native(path_dir):
|
|
799
|
-
"""
|
|
800
|
-
package.json version
|
|
801
|
-
$(MARKETING_VERSION)
|
|
801
|
+
"""Read from the files used at release time (ios/<app>/Info.plist, android/app/build.gradle).
|
|
802
|
+
package.json version is not synced, so using it as the reference would drift on every release.
|
|
803
|
+
Values that are not x.y.z, such as a $(MARKETING_VERSION) reference or the template default "1.0", are skipped."""
|
|
802
804
|
ios_dir = Path(path_dir) / "ios"
|
|
803
805
|
if ios_dir.is_dir():
|
|
804
806
|
for plist in _rn_app_plists(ios_dir):
|
|
@@ -839,8 +841,8 @@ def _sync_none(path_dir, new_version, version_code_getter):
|
|
|
839
841
|
|
|
840
842
|
|
|
841
843
|
class TypeHandler(NamedTuple):
|
|
842
|
-
"""
|
|
843
|
-
sync(path_dir, new_version, version_code_getter)
|
|
844
|
+
"""Per-type version file handling. read(path_dir) returns the version in the file (None if absent),
|
|
845
|
+
sync(path_dir, new_version, version_code_getter) writes the new version to the file."""
|
|
844
846
|
read: Callable
|
|
845
847
|
sync: Callable
|
|
846
848
|
|
|
@@ -850,11 +852,11 @@ _PACKAGE_JSON = TypeHandler(
|
|
|
850
852
|
sync=lambda d, v, _code: sync_json_version(Path(d) / "package.json", v, ["version"]),
|
|
851
853
|
)
|
|
852
854
|
|
|
853
|
-
#
|
|
854
|
-
#
|
|
855
|
+
# Adding one line here is enough for a new type: both reading (sync comparison) and writing follow.
|
|
856
|
+
# Types without a version file (basic, go) get an empty handler so only version.yml is written.
|
|
855
857
|
TYPE_HANDLERS = {
|
|
856
858
|
"spring": TypeHandler(read=_read_spring, sync=lambda d, v, _code: sync_spring(d, v)),
|
|
857
|
-
# build number
|
|
859
|
+
# Only types that need a build number compute version_code (this has a pubspec-adjusting side effect).
|
|
858
860
|
"flutter": TypeHandler(read=_read_flutter, sync=lambda d, v, code: sync_flutter(d, v, code())),
|
|
859
861
|
"react": _PACKAGE_JSON,
|
|
860
862
|
"next": _PACKAGE_JSON,
|
|
@@ -954,7 +956,7 @@ def cmd_increment(args):
|
|
|
954
956
|
return 1
|
|
955
957
|
bump = getattr(args, "bump", None) or "patch"
|
|
956
958
|
new_version = increment_version(current_version, bump)
|
|
957
|
-
# build number
|
|
959
|
+
# Bump the build number first so the following pubspec sync writes the new value too.
|
|
958
960
|
set_version_code(get_reconciled_version_code() + 1)
|
|
959
961
|
update_all_versions(new_version)
|
|
960
962
|
|
|
@@ -988,7 +990,7 @@ def build_parser():
|
|
|
988
990
|
sub.add_parser("get-code")
|
|
989
991
|
p_increment = sub.add_parser("increment")
|
|
990
992
|
p_increment.add_argument("--bump", choices=["major", "minor", "patch"], default="patch",
|
|
991
|
-
help=
|
|
993
|
+
help=t("version.bump_help"))
|
|
992
994
|
sub.add_parser("increment-code")
|
|
993
995
|
sub.add_parser("sync")
|
|
994
996
|
|
|
@@ -999,6 +1001,7 @@ def build_parser():
|
|
|
999
1001
|
|
|
1000
1002
|
|
|
1001
1003
|
def main(argv=None):
|
|
1004
|
+
use_utf8_output()
|
|
1002
1005
|
parser = build_parser()
|
|
1003
1006
|
args = parser.parse_args(argv)
|
|
1004
1007
|
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
version: "{{VERSION}}"
|
|
35
35
|
version_code: {{VERSION_CODE}} # app build number
|
|
36
36
|
project_types: {{PROJECT_TYPES}} # first entry is primary
|
|
37
|
+
language: "{{LANGUAGE}}" # message language: en | ko
|
|
37
38
|
{{PROJECT_PATHS}}
|
|
38
39
|
metadata:
|
|
39
40
|
last_updated: "{{NOW}}"
|
|
@@ -52,6 +53,6 @@ metadata:
|
|
|
52
53
|
options:
|
|
53
54
|
semver_auto: {{OPT_SEMVER_AUTO}}
|
|
54
55
|
copilot_ai: {{OPT_COPILOT_AI}}
|
|
55
|
-
deploy_style: "{{OPT_DEPLOY_STYLE}}" # simple | nginx | traefik | none (
|
|
56
|
+
deploy_style: "{{OPT_DEPLOY_STYLE}}" # simple | nginx | traefik | none (server deploy workflows)
|
|
56
57
|
{{TYPE_OPTIONS}}
|
|
57
58
|
{{DEPLOY}}
|
|
@@ -78,8 +78,9 @@ jobs:
|
|
|
78
78
|
|
|
79
79
|
git fetch origin {{MAIN_BRANCH}}
|
|
80
80
|
git log --pretty=%s "origin/{{MAIN_BRANCH}}..HEAD" > commits.txt
|
|
81
|
-
#
|
|
82
|
-
#
|
|
81
|
+
# Body-footer BREAKING CHANGE: lines are appended after the subject list, one per
|
|
82
|
+
# line. The script reads each line as one entry, so passing the whole body (%B)
|
|
83
|
+
# would turn description lines into change entries.
|
|
83
84
|
git log --pretty=%b "origin/{{MAIN_BRANCH}}..HEAD" | grep -E '^BREAKING[ -]CHANGE[[:space:]]*:' >> commits.txt || true
|
|
84
85
|
git diff --stat "origin/{{MAIN_BRANCH}}...HEAD" > _full_diff_stat.txt
|
|
85
86
|
if [ "$(wc -l < _full_diff_stat.txt)" -gt 49 ]; then
|
|
@@ -89,10 +90,12 @@ jobs:
|
|
|
89
90
|
fi
|
|
90
91
|
rm -f _full_diff_stat.txt
|
|
91
92
|
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
93
|
+
# The header shows the version this PR will get once merged, not the version
|
|
94
|
+
# that is already published. Same rules as whatever bumps the version after the
|
|
95
|
+
# merge: in trunk-based mode RELEASE-PUBLISH picks the bump size from the commits
|
|
96
|
+
# when semver_auto is on, and in pr-flow a PR going straight into {{MAIN_BRANCH}}
|
|
97
|
+
# gets a patch bump from the VERSION-CONTROL safety net. It is only an estimate,
|
|
98
|
+
# so rules alone are used, without any AI-assisted classification.
|
|
96
99
|
CURRENT_VERSION=$(python3 .github/scripts/version_manager.py get 2>/dev/null | tail -n 1)
|
|
97
100
|
MODE=$(python3 -c 'import re; t=open("version.yml",encoding="utf-8").read(); m=re.search(r"metadata:.*?template:.*?branches:.*?mode:\s*\"?([A-Za-z-]+)", t, re.S); print(m.group(1) if m else "pr-flow")' 2>/dev/null || echo "pr-flow")
|
|
98
101
|
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")
|
|
@@ -111,12 +114,12 @@ jobs:
|
|
|
111
114
|
--pr-title "$PR_TITLE" \
|
|
112
115
|
--diff-stat-file diff_stat.txt | tail -n 1)
|
|
113
116
|
ENGINE=$(python3 -c 'import json,sys; print(json.loads(sys.argv[1]).get("engine") or "unknown")' "$SUMMARY_RESULT" 2>/dev/null || echo "unknown")
|
|
114
|
-
# fallback
|
|
117
|
+
# 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
|
|
115
118
|
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 "")
|
|
116
119
|
ENGINE_LINE="$ENGINE${FALLBACK_REASON:+ ($FALLBACK_REASON)}"
|
|
117
120
|
echo "engine: $ENGINE_LINE" >> "$GITHUB_STEP_SUMMARY"
|
|
118
121
|
|
|
119
|
-
#
|
|
122
|
+
# Find this bot's earlier summary comment by its marker and replace it with the latest content so pushes do not pile up comments
|
|
120
123
|
MARKER="<!-- project-auto-wizard:pr-summary -->"
|
|
121
124
|
{
|
|
122
125
|
echo "$MARKER"
|
|
@@ -143,15 +146,17 @@ jobs:
|
|
|
143
146
|
continue-on-error: true
|
|
144
147
|
env:
|
|
145
148
|
GITHUB_TOKEN: ${{ github.token }}
|
|
146
|
-
#
|
|
147
|
-
# git ref
|
|
148
|
-
#
|
|
149
|
+
# Pass the branch name through env instead of interpolating ${{ }} into the run:
|
|
150
|
+
# script - git ref names can contain $, backticks, semicolons and so on, so direct
|
|
151
|
+
# interpolation would be a shell-injection path (same reason as PR_TITLE in
|
|
152
|
+
# AUTO-CHANGELOG-CONTROL).
|
|
149
153
|
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
|
150
154
|
run: |
|
|
155
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
151
156
|
ISSUE_NUM=$(python3 .github/scripts/issue_helper.py extract-branch-issue "$HEAD_REF")
|
|
152
157
|
|
|
153
158
|
if [ -z "$ISSUE_NUM" ]; then
|
|
154
|
-
echo "
|
|
159
|
+
echo "$(m wf_aisum.no_issue_in_branch ref="$HEAD_REF")"
|
|
155
160
|
exit 0
|
|
156
161
|
fi
|
|
157
162
|
|