From 59e040994b38eb203c84cfe23caed68b7d04775c Mon Sep 17 00:00:00 2001 From: NikoKS Date: Wed, 26 Aug 2026 23:34:46 +0700 Subject: [PATCH] fix: bootstrap apps without version config --- README.md | 7 ++++++- action.yml | 5 +++++ scripts/bump.sh | 7 ++++++- scripts/read_version.py | 32 +++++++++++++++++++++++++++++++- tests/integration.sh | 21 +++++++++++++++------ 5 files changed, 63 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a0ca701..6f29e4a 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,11 @@ jobs: - name: Prepare version id: version - uses: Nurinda/bump-erp-app-version@v1 + # The absolute URL is required when Gitea's DEFAULT_ACTIONS_URL is "github". + uses: https://gitea.cptckp.com/Nurinda/bump-erp-app-version@v1 with: config-file: pyproject.toml + version-file: erp_operations/__init__.py base-branch: main target-branch: develop bump: patch @@ -78,6 +80,7 @@ Gitea may suppress workflows caused by pushes authenticated with its built-in `G | Input | Default | Description | |---|---|---| | `config-file` | `pyproject.toml` | `bump-my-version` TOML configuration | +| `version-file` | empty | App `__init__.py` used to bootstrap a base branch that predates the configuration | | `base-branch` | `main` | Branch providing the baseline version | | `target-branch` | `develop` | Checked-out branch receiving the commit | | `bump` | `patch` | `major`, `minor`, or `patch` | @@ -101,6 +104,8 @@ Gitea may suppress workflows caused by pushes authenticated with its built-in `G The action does not force-push, create tags, publish releases, or call forge APIs. Concurrent release jobs are resolved safely by the remote rejecting a non-fast-forward push. Configure workflow-level concurrency if release PRs can overlap. +For the first versioned release, the base branch might not contain `[tool.bumpversion]` yet. Set `version-file` to the app's `__init__.py`; the action will read its base-branch `__version__` as the bootstrap baseline. Once the configuration reaches the base branch, it becomes the authoritative baseline. + ## Releasing the action Create and push an immutable semantic-version tag: diff --git a/action.yml b/action.yml index 6e7f2a5..1cd380f 100644 --- a/action.yml +++ b/action.yml @@ -5,6 +5,10 @@ inputs: description: Path to the bump-my-version TOML configuration required: false default: pyproject.toml + version-file: + description: App __init__.py used to bootstrap when the base branch has no bump-my-version config + required: false + default: "" base-branch: description: Branch whose version is used as the release baseline required: false @@ -62,6 +66,7 @@ runs: shell: bash env: INPUT_CONFIG_FILE: ${{ inputs.config-file }} + INPUT_VERSION_FILE: ${{ inputs.version-file }} INPUT_BASE_BRANCH: ${{ inputs.base-branch }} INPUT_TARGET_BRANCH: ${{ inputs.target-branch }} INPUT_BUMP: ${{ inputs.bump }} diff --git a/scripts/bump.sh b/scripts/bump.sh index ea26436..1c3d495 100755 --- a/scripts/bump.sh +++ b/scripts/bump.sh @@ -2,6 +2,7 @@ set -euo pipefail : "${INPUT_CONFIG_FILE:?config-file is required}" +: "${INPUT_VERSION_FILE:=}" : "${INPUT_BASE_BRANCH:?base-branch is required}" : "${INPUT_TARGET_BRANCH:?target-branch is required}" : "${INPUT_BUMP:?bump is required}" @@ -45,7 +46,11 @@ git fetch "${INPUT_REMOTE}" \ script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) base_ref="refs/remotes/${INPUT_REMOTE}/${INPUT_BASE_BRANCH}" current_version=$(python3 "${script_dir}/read_version.py" "${INPUT_CONFIG_FILE}") -base_version=$(python3 "${script_dir}/read_version.py" "${INPUT_CONFIG_FILE}" --ref "${base_ref}") +base_version_args=("${INPUT_CONFIG_FILE}" --ref "${base_ref}") +if [[ -n "${INPUT_VERSION_FILE}" ]]; then + base_version_args+=(--fallback-version-file "${INPUT_VERSION_FILE}") +fi +base_version=$(python3 "${script_dir}/read_version.py" "${base_version_args[@]}") comparison=$(python3 - "${current_version}" "${base_version}" <<'PY' import re diff --git a/scripts/read_version.py b/scripts/read_version.py index 6abc7b3..ce0383d 100755 --- a/scripts/read_version.py +++ b/scripts/read_version.py @@ -4,7 +4,9 @@ from __future__ import annotations import argparse +import re import subprocess +import sys import tomllib @@ -12,9 +14,35 @@ def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument("config_file") parser.add_argument("--ref") + parser.add_argument("--fallback-version-file") return parser.parse_args() +def read_bootstrap_version(args: argparse.Namespace, config_error: KeyError) -> str: + if not args.ref or not args.fallback_version_file: + raise SystemExit( + f"Could not read tool.bumpversion.current_version from {args.config_file}: {config_error}" + ) from config_error + + try: + version_content = subprocess.check_output( + ["git", "show", f"{args.ref}:{args.fallback_version_file}"], + text=True, + ) + except (OSError, subprocess.CalledProcessError) as error: + raise SystemExit( + f"Could not load bootstrap version file {args.fallback_version_file}: {error}" + ) from error + + match = re.search(r'^__version__ = "(\d+\.\d+\.\d+)"$', version_content, re.MULTILINE) + if not match: + raise SystemExit(f"Could not read __version__ from bootstrap file {args.fallback_version_file}") + + version = match.group(1) + print(f"Using bootstrap version {version} from {args.fallback_version_file}", file=sys.stderr) + return version + + def main() -> None: args = parse_args() try: @@ -30,7 +58,9 @@ def main() -> None: try: version = tomllib.loads(content.decode())["tool"]["bumpversion"]["current_version"] - except (KeyError, TypeError, UnicodeDecodeError, tomllib.TOMLDecodeError) as error: + except KeyError as error: + version = read_bootstrap_version(args, error) + except (TypeError, UnicodeDecodeError, tomllib.TOMLDecodeError) as error: raise SystemExit( f"Could not read tool.bumpversion.current_version from {args.config_file}: {error}" ) from error diff --git a/tests/integration.sh b/tests/integration.sh index 8915c7e..85399b9 100755 --- a/tests/integration.sh +++ b/tests/integration.sh @@ -12,6 +12,18 @@ git config user.name Test git config user.email test@example.com mkdir erp_operations cat > pyproject.toml <<'TOML' +[project] +name = "erp_operations" +dynamic = ["version"] +TOML +printf '%s\n' '__version__ = "0.0.1"' > erp_operations/__init__.py +git add . +git commit -qm initial +git remote add origin "${test_root}/remote.git" +git push -qu origin main +git switch -qc develop +cat >> pyproject.toml <<'TOML' + [tool.bumpversion] current_version = "0.0.1" commit = false @@ -22,15 +34,12 @@ filename = "erp_operations/__init__.py" search = '__version__ = "{current_version}"' replace = '__version__ = "{new_version}"' TOML -printf '%s\n' '__version__ = "0.0.1"' > erp_operations/__init__.py -git add . -git commit -qm initial -git remote add origin "${test_root}/remote.git" -git push -qu origin main -git switch -qc develop +git add pyproject.toml +git commit -qm 'configure versioning' git push -qu origin develop export INPUT_CONFIG_FILE=pyproject.toml +export INPUT_VERSION_FILE=erp_operations/__init__.py export INPUT_BASE_BRANCH=main export INPUT_TARGET_BRANCH=develop export INPUT_BUMP=patch