feat: support config-free ERP app versioning

This commit is contained in:
NikoKS committed 2026-08-26 23:45:11 +07:00
1 parent 59e040994b
commit b6e6b3e8d0
5 files changed
+32 -102

No files matched your search

+5 -20
View File
@@ -2,7 +2,7 @@
A forge-neutral composite action for preparing a versioned Frappe/ERPNext app before merging its release branch. It uses [`bump-my-version`](https://callowayproject.github.io/bump-my-version/) and ordinary Git commands, without `gh` or GitHub API calls.
The action compares `[tool.bumpversion].current_version` on the checked-out target branch with the configured base branch:
The action compares the app's `__version__` on the checked-out target branch with the configured base branch:
- Equal: bump, commit, and optionally push the new version.
- Target newer than base: do nothing, making PR synchronization events idempotent.
@@ -13,22 +13,9 @@ The action compares `[tool.bumpversion].current_version` on the checked-out targ
- A Linux runner with Bash, Git, and Python 3.11 or newer.
- A clean checkout of the branch that should receive the version commit.
- Persisted Git credentials with permission to push that branch.
- Stable `MAJOR.MINOR.PATCH` versions.
- A `bump-my-version` configuration that includes every version file.
- A version file containing a stable `MAJOR.MINOR.PATCH` assignment such as `__version__ = "0.0.1"`.
Example `pyproject.toml` for a Frappe app:
```toml
[tool.bumpversion]
current_version = "0.0.1"
commit = false
tag = false
[[tool.bumpversion.files]]
filename = "erp_operations/__init__.py"
search = '__version__ = "{current_version}"'
replace = '__version__ = "{new_version}"'
```
No `bump-my-version` configuration is required in the consuming app. The action passes the current version, search/replace templates, and version file directly to the CLI.
## Gitea Actions usage
@@ -61,7 +48,6 @@ jobs:
# 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
@@ -79,8 +65,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 |
| `version-file` | required | App `__init__.py` containing the authoritative `__version__` |
| `base-branch` | `main` | Branch providing the baseline version |
| `target-branch` | `develop` | Checked-out branch receiving the commit |
| `bump` | `patch` | `major`, `minor`, or `patch` |
@@ -104,7 +89,7 @@ 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.
The configured `version-file` is the only version source. The action reads it from both branches and commits only the version change produced by `bump-my-version`.
## Releasing the action
+2 -8
View File
@@ -1,14 +1,9 @@
name: Bump ERP app version
description: Bump a Frappe/ERPNext app version on a release branch using bump-my-version
inputs:
config-file:
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: ""
description: App __init__.py containing a stable MAJOR.MINOR.PATCH __version__ assignment
required: true
base-branch:
description: Branch whose version is used as the release baseline
required: false
@@ -65,7 +60,6 @@ runs:
id: bump
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 }}
+12 -13
View File
@@ -1,8 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
: "${INPUT_CONFIG_FILE:?config-file is required}"
: "${INPUT_VERSION_FILE:=}"
: "${INPUT_VERSION_FILE:?version-file is required}"
: "${INPUT_BASE_BRANCH:?base-branch is required}"
: "${INPUT_TARGET_BRANCH:?target-branch is required}"
: "${INPUT_BUMP:?bump is required}"
@@ -24,8 +23,8 @@ case "${INPUT_BUMP}" in
*) echo "bump must be major, minor, or patch" >&2; exit 1 ;;
esac
if [[ ! -f "${INPUT_CONFIG_FILE}" ]]; then
echo "Configuration file not found: ${INPUT_CONFIG_FILE}" >&2
if [[ ! -f "${INPUT_VERSION_FILE}" ]]; then
echo "Version file not found: ${INPUT_VERSION_FILE}" >&2
exit 1
fi
@@ -45,12 +44,8 @@ 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_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[@]}")
current_version=$(python3 "${script_dir}/read_version.py" "${INPUT_VERSION_FILE}")
base_version=$(python3 "${script_dir}/read_version.py" "${INPUT_VERSION_FILE}" --ref "${base_ref}")
comparison=$(python3 - "${current_version}" "${base_version}" <<'PY'
import re
@@ -96,13 +91,17 @@ git config user.email "${INPUT_GIT_USER_EMAIL}"
message=${INPUT_COMMIT_MESSAGE//\{version\}/\{new_version\}}
"${venv_dir}/bin/bump-my-version" bump \
--config-file "${INPUT_CONFIG_FILE}" \
--current-version "${current_version}" \
--search '__version__ = "{current_version}"' \
--replace '__version__ = "{new_version}"' \
--no-configured-files \
--commit \
--no-tag \
--message "${message}" \
"${INPUT_BUMP}"
"${INPUT_BUMP}" \
"${INPUT_VERSION_FILE}"
new_version=$(python3 "${script_dir}/read_version.py" "${INPUT_CONFIG_FILE}")
new_version=$(python3 "${script_dir}/read_version.py" "${INPUT_VERSION_FILE}")
commit=$(git rev-parse HEAD)
if [[ "${INPUT_PUSH}" == "true" ]]; then
+12 -45
View File
@@ -1,73 +1,40 @@
#!/usr/bin/env python3
"""Read bump-my-version's current_version from a local file or Git ref."""
"""Read a Frappe app's stable __version__ from a local file or Git ref."""
from __future__ import annotations
import argparse
import re
import subprocess
import sys
import tomllib
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser()
parser.add_argument("config_file")
parser.add_argument("version_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:
if args.ref:
content = subprocess.check_output(
["git", "show", f"{args.ref}:{args.config_file}"],
["git", "show", f"{args.ref}:{args.version_file}"],
text=True,
)
else:
with open(args.config_file, "rb") as config_file:
content = config_file.read()
with open(args.version_file, encoding="utf-8") as version_file:
content = version_file.read()
except (OSError, subprocess.CalledProcessError) as error:
raise SystemExit(f"Could not load {args.config_file}: {error}") from error
raise SystemExit(f"Could not load {args.version_file}: {error}") from error
try:
version = tomllib.loads(content.decode())["tool"]["bumpversion"]["current_version"]
except KeyError as error:
version = read_bootstrap_version(args, error)
except (TypeError, UnicodeDecodeError, tomllib.TOMLDecodeError) as error:
match = re.search(r'^__version__ = "(\d+\.\d+\.\d+)"$', content, re.MULTILINE)
if not match:
raise SystemExit(
f"Could not read tool.bumpversion.current_version from {args.config_file}: {error}"
) from error
if not isinstance(version, str):
raise SystemExit("tool.bumpversion.current_version must be a string")
print(version)
f"Could not read a stable MAJOR.MINOR.PATCH __version__ from {args.version_file}"
)
print(match.group(1))
if __name__ == "__main__":
+1 -16
View File
@@ -22,23 +22,8 @@ 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
tag = false
[[tool.bumpversion.files]]
filename = "erp_operations/__init__.py"
search = '__version__ = "{current_version}"'
replace = '__version__ = "{new_version}"'
TOML
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
@@ -53,7 +38,7 @@ export RUNNER_TEMP="${test_root}"
export GITHUB_OUTPUT="${test_root}/output"
"${repo_root}/scripts/bump.sh"
grep -q '^current_version = "0.0.2"$' pyproject.toml
! grep -q '^\[tool.bumpversion\]' pyproject.toml
grep -q '^__version__ = "0.0.2"$' erp_operations/__init__.py
grep -q '^bumped=true$' "${GITHUB_OUTPUT}"
grep -q '^previous-version=0.0.1$' "${GITHUB_OUTPUT}"