Update version_branch.yml

This commit is contained in:
2025-12-23 18:13:00 -06:00
parent f14872f594
commit 17b8d5f254

View File

@@ -27,494 +27,493 @@
name: Create version branch and bump versions name: Create version branch and bump versions
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
new_version: new_version:
description: "New version in format NN.NN.NN (example 03.01.00)" description: "New version in format NN.NN.NN (example 03.01.00)"
required: true required: true
type: string type: string
version_text: version_text:
description: "Optional version label text (example: LTS, RC1, hotfix)" description: "Optional version label text (example: LTS, RC1, hotfix)"
required: false required: false
default: "" default: ""
type: string type: string
report_only: report_only:
description: "Report only mode (no branch creation, no file writes, report output only)" description: "Report only mode (no branch creation, no file writes, report output only)"
required: false required: false
default: "false" default: "false"
type: choice type: choice
options: options:
- "true" - "true"
- "false" - "false"
commit_changes: commit_changes:
description: "Commit and push changes (forced to true when report_only=false)" description: "Commit and push changes (forced to true when report_only=false)"
required: false required: false
default: "true" default: "true"
type: choice type: choice
options: options:
- "true" - "true"
- "false" - "false"
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.repository }}-${{ github.event.inputs.new_version }} group: ${{ github.workflow }}-${{ github.repository }}-${{ github.event.inputs.new_version }}
cancel-in-progress: false cancel-in-progress: false
permissions: permissions:
contents: write contents: write
defaults: defaults:
run: run:
shell: bash shell: bash
jobs: jobs:
version-bump: version-bump:
name: Version branch and bump name: Version branch and bump
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
NEW_VERSION: ${{ github.event.inputs.new_version }} NEW_VERSION: ${{ github.event.inputs.new_version }}
VERSION_TEXT: ${{ github.event.inputs.version_text }} VERSION_TEXT: ${{ github.event.inputs.version_text }}
REPORT_ONLY: ${{ github.event.inputs.report_only }} REPORT_ONLY: ${{ github.event.inputs.report_only }}
COMMIT_CHANGES: ${{ github.event.inputs.commit_changes }} COMMIT_CHANGES: ${{ github.event.inputs.commit_changes }}
BASE_BRANCH: ${{ github.ref_name }} BASE_BRANCH: ${{ github.ref_name }}
BRANCH_PREFIX: dev/ BRANCH_PREFIX: dev/
ERROR_LOG: /tmp/version_branch_errors.log ERROR_LOG: /tmp/version_branch_errors.log
CI_HELPERS: /tmp/moko_ci_helpers.sh CI_HELPERS: /tmp/moko_ci_helpers.sh
REPORT_PATH: ${{ runner.temp }}/version-bump-report.json REPORT_PATH: ${{ runner.temp }}/version-bump-report.json
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
ref: ${{ github.ref_name }} ref: ${{ github.ref_name }}
- name: Init CI helpers - name: Init CI helpers
run: | run: |
set -Eeuo pipefail set -Eeuo pipefail
: > "$ERROR_LOG" : > "$ERROR_LOG"
cat > "$CI_HELPERS" <<'SH' cat > "$CI_HELPERS" <<'SH'
set -Eeuo pipefail set -Eeuo pipefail
moko_init() { moko_init() {
local step_name="${1:-step}" local step_name="${1:-step}"
export PS4='+ ['"${step_name}"':${BASH_SOURCE##*/}:${LINENO}] ' export PS4='+ ['"${step_name}"':${BASH_SOURCE##*/}:${LINENO}] '
set -x set -x
trap "moko_on_err '${step_name}' \"\$LINENO\" \"\$BASH_COMMAND\"" ERR trap "moko_on_err '${step_name}' \"\$LINENO\" \"\$BASH_COMMAND\"" ERR
} }
moko_on_err() { moko_on_err() {
local step_name="$1" local step_name="$1"
local line_no="$2" local line_no="$2"
local last_cmd="$3" local last_cmd="$3"
echo "[FATAL] ${step_name} failed at line ${line_no}" >&2 echo "[FATAL] ${step_name} failed at line ${line_no}" >&2
echo "[FATAL] Last command: ${last_cmd}" >&2 echo "[FATAL] Last command: ${last_cmd}" >&2
if [[ -n "${ERROR_LOG:-}" ]]; then if [[ -n "${ERROR_LOG:-}" ]]; then
echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | ${step_name} | line ${line_no} | ${last_cmd}" >> "$ERROR_LOG" || true echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) | ${step_name} | line ${line_no} | ${last_cmd}" >> "$ERROR_LOG" || true
fi fi
} }
moko_bool() { moko_bool() {
local v="${1:-false}" local v="${1:-false}"
[[ "${v}" == "true" ]] [[ "${v}" == "true" ]]
} }
moko_trim() { moko_trim() {
local s="${1:-}" local s="${1:-}"
s="${s#${s%%[![:space:]]*}}" s="${s#${s%%[![:space:]]*}}"
s="${s%${s##*[![:space:]]}}" s="${s%${s##*[![:space:]]}}"
printf '%s' "$s" printf '%s' "$s"
} }
SH SH
chmod 0755 "$CI_HELPERS" chmod 0755 "$CI_HELPERS"
- name: Validate inputs and policy locks - name: Validate inputs and policy locks
run: | run: |
source "$CI_HELPERS" source "$CI_HELPERS"
moko_init "Validate inputs and policy locks" moko_init "Validate inputs and policy locks"
VERSION_TEXT="$(moko_trim "${VERSION_TEXT}")" VERSION_TEXT="$(moko_trim "${VERSION_TEXT}")"
echo "[INFO] Inputs received:" echo "[INFO] Inputs received:"
echo " NEW_VERSION=${NEW_VERSION}" echo " NEW_VERSION=${NEW_VERSION}"
echo " VERSION_TEXT=${VERSION_TEXT}" echo " VERSION_TEXT=${VERSION_TEXT}"
echo " REPORT_ONLY=${REPORT_ONLY}" echo " REPORT_ONLY=${REPORT_ONLY}"
echo " COMMIT_CHANGES=${COMMIT_CHANGES}" echo " COMMIT_CHANGES=${COMMIT_CHANGES}"
echo " BASE_BRANCH=${BASE_BRANCH}" echo " BASE_BRANCH=${BASE_BRANCH}"
echo " BRANCH_PREFIX=${BRANCH_PREFIX}" echo " BRANCH_PREFIX=${BRANCH_PREFIX}"
[[ -n "${NEW_VERSION}" ]] || { echo "[ERROR] new_version missing" >&2; exit 2; } [[ -n "${NEW_VERSION}" ]] || { echo "[ERROR] new_version missing" >&2; exit 2; }
[[ "${NEW_VERSION}" =~ ^[0-9]{2}[.][0-9]{2}[.][0-9]{2}$ ]] || { echo "[ERROR] Invalid version format: ${NEW_VERSION}" >&2; exit 2; } [[ "${NEW_VERSION}" =~ ^[0-9]{2}[.][0-9]{2}[.][0-9]{2}$ ]] || { echo "[ERROR] Invalid version format: ${NEW_VERSION}" >&2; exit 2; }
if [[ "${BRANCH_PREFIX}" != "dev/" ]]; then if [[ "${BRANCH_PREFIX}" != "dev/" ]]; then
echo "[FATAL] BRANCH_PREFIX is locked by policy. Expected 'dev/' but got '${BRANCH_PREFIX}'." >&2 echo "[FATAL] BRANCH_PREFIX is locked by policy. Expected 'dev/' but got '${BRANCH_PREFIX}'." >&2
exit 2 exit 2
fi fi
if ! moko_bool "${REPORT_ONLY}" && [[ "${COMMIT_CHANGES}" != "true" ]]; then if ! moko_bool "${REPORT_ONLY}" && [[ "${COMMIT_CHANGES}" != "true" ]]; then
echo "[FATAL] commit_changes must be 'true' when report_only is 'false' to ensure the branch is auditable." >&2 echo "[FATAL] commit_changes must be 'true' when report_only is 'false' to ensure the branch is auditable." >&2
exit 2 exit 2
fi fi
if [[ -n "${VERSION_TEXT}" ]]; then if [[ -n "${VERSION_TEXT}" ]]; then
if [[ ! "${VERSION_TEXT}" =~ ^[A-Za-z0-9._-]{1,32}$ ]]; then if [[ ! "${VERSION_TEXT}" =~ ^[A-Za-z0-9._-]{1,32}$ ]]; then
echo "[FATAL] version_text must match ^[A-Za-z0-9._-]{1,32}$ when set." >&2 echo "[FATAL] version_text must match ^[A-Za-z0-9._-]{1,32}$ when set." >&2
exit 2 exit 2
fi fi
fi fi
git ls-remote --exit-code --heads origin "${BASE_BRANCH}" >/dev/null 2>&1 || { git ls-remote --exit-code --heads origin "${BASE_BRANCH}" >/dev/null 2>&1 || {
echo "[ERROR] Base branch does not exist on origin: ${BASE_BRANCH}" >&2 echo "[ERROR] Base branch does not exist on origin: ${BASE_BRANCH}" >&2
echo "[INFO] Remote branches:" >&2 echo "[INFO] Remote branches:" >&2
git ls-remote --heads origin | awk '{sub("refs/heads/","",$2); print $2}' >&2 git ls-remote --heads origin | awk '{sub("refs/heads/","",$2); print $2}' >&2
exit 2 exit 2
} }
echo "VERSION_TEXT=${VERSION_TEXT}" >> "$GITHUB_ENV" echo "VERSION_TEXT=${VERSION_TEXT}" >> "$GITHUB_ENV"
- name: Sanity check workflow file (no literal tabs or control chars) - name: Sanity check workflow file (no literal tabs or control chars)
run: | run: |
source "$CI_HELPERS" source "$CI_HELPERS"
moko_init "Sanity check workflow file" moko_init "Sanity check workflow file"
python3 - <<'PY' python3 - <<'PY'
from pathlib import Path from pathlib import Path
target = Path('.github/workflows/version_branch.yml') target = Path('.github/workflows/version_branch.yml')
if not target.exists(): if not target.exists():
raise SystemExit('[FATAL] Missing workflow file: .github/workflows/version_branch.yml') raise SystemExit('[FATAL] Missing workflow file: .github/workflows/version_branch.yml')
data = target.read_bytes() data = target.read_bytes()
# Disallow literal tab (0x09) and other ASCII control characters except LF (0x0A) and CR (0x0D). # Disallow literal tab (0x09) and other ASCII control characters except LF (0x0A) and CR (0x0D).
# Report line numbers without printing the raw characters. # Report line numbers without printing the raw characters.
def byte_to_line(blob: bytes, idx: int) -> int: def byte_to_line(blob: bytes, idx: int) -> int:
return blob[:idx].count(b' return blob[:idx].count(b'\n') + 1
') + 1
bad = []
bad = [] for i, b in enumerate(data):
for i, b in enumerate(data): if b == 0x09:
if b == 0x09: bad.append(('TAB', i, b))
bad.append(('TAB', i, b)) elif b < 0x20 and b not in (0x0A, 0x0D):
elif b < 0x20 and b not in (0x0A, 0x0D): bad.append(('CTRL', i, b))
bad.append(('CTRL', i, b))
if bad:
if bad: print('[ERROR] Disallowed characters detected in workflow file:')
print('[ERROR] Disallowed characters detected in workflow file:') for kind, off, val in bad[:200]:
for kind, off, val in bad[:200]: line_no = byte_to_line(data, off)
line_no = byte_to_line(data, off) if kind == 'TAB':
if kind == 'TAB': print(f' line {line_no}: TAB_PRESENT')
print(f' line {line_no}: TAB_PRESENT') else:
else: print(f' line {line_no}: CTRL_0x{val:02X}_PRESENT')
print(f' line {line_no}: CTRL_0x{val:02X}_PRESENT') raise SystemExit(2)
raise SystemExit(2)
print('[INFO] Sanity check passed')
print('[INFO] Sanity check passed') PY
PY
- name: Enterprise policy gate
- name: Enterprise policy gate run: |
run: | source "$CI_HELPERS"
source "$CI_HELPERS" moko_init "Enterprise policy gate"
moko_init "Enterprise policy gate"
required=(
required=( "LICENSE.md"
"LICENSE.md" "CONTRIBUTING.md"
"CONTRIBUTING.md" "CODE_OF_CONDUCT.md"
"CODE_OF_CONDUCT.md" "SECURITY.md"
"SECURITY.md" "GOVERNANCE.md"
"GOVERNANCE.md" "CHANGELOG.md"
"CHANGELOG.md" )
)
missing=0
missing=0 for f in "${required[@]}"; do
for f in "${required[@]}"; do if [[ ! -f "${f}" ]]; then
if [[ ! -f "${f}" ]]; then echo "[ERROR] Missing required file: ${f}" >&2
echo "[ERROR] Missing required file: ${f}" >&2 missing=1
missing=1 continue
continue fi
fi if [[ ! -s "${f}" ]]; then
if [[ ! -s "${f}" ]]; then echo "[ERROR] Required file is empty: ${f}" >&2
echo "[ERROR] Required file is empty: ${f}" >&2 missing=1
missing=1 continue
continue fi
fi done
done
if [[ "${missing}" -ne 0 ]]; then
if [[ "${missing}" -ne 0 ]]; then echo "[FATAL] Policy gate failed. Add missing governance artifacts before versioning." >&2
echo "[FATAL] Policy gate failed. Add missing governance artifacts before versioning." >&2 exit 2
exit 2 fi
fi
echo "[INFO] Policy gate passed"
echo "[INFO] Policy gate passed"
- name: Branch namespace collision defense
- name: Branch namespace collision defense run: |
run: | source "$CI_HELPERS"
source "$CI_HELPERS" moko_init "Branch namespace collision defense"
moko_init "Branch namespace collision defense"
PREFIX_TOP="${BRANCH_PREFIX%%/*}"
PREFIX_TOP="${BRANCH_PREFIX%%/*}" if git ls-remote --exit-code --heads origin "${PREFIX_TOP}" >/dev/null 2>&1; then
if git ls-remote --exit-code --heads origin "${PREFIX_TOP}" >/dev/null 2>&1; then echo "[FATAL] Branch namespace collision detected: '${PREFIX_TOP}' exists on origin." >&2
echo "[FATAL] Branch namespace collision detected: '${PREFIX_TOP}' exists on origin." >&2 exit 2
exit 2 fi
fi
- name: Configure git identity
- name: Configure git identity if: ${{ env.REPORT_ONLY != 'true' }}
if: ${{ env.REPORT_ONLY != 'true' }} run: |
run: | source "$CI_HELPERS"
source "$CI_HELPERS" moko_init "Configure git identity"
moko_init "Configure git identity"
git config user.name "github-actions[bot]"
git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Create version branch (local)
- name: Create version branch (local) if: ${{ env.REPORT_ONLY != 'true' }}
if: ${{ env.REPORT_ONLY != 'true' }} run: |
run: | source "$CI_HELPERS"
source "$CI_HELPERS" moko_init "Create version branch (local)"
moko_init "Create version branch (local)"
BRANCH_NAME="${BRANCH_PREFIX}${NEW_VERSION}"
BRANCH_NAME="${BRANCH_PREFIX}${NEW_VERSION}" echo "[INFO] Creating local branch: ${BRANCH_NAME} from origin/${BASE_BRANCH}"
echo "[INFO] Creating local branch: ${BRANCH_NAME} from origin/${BASE_BRANCH}"
git fetch --all --tags --prune
git fetch --all --tags --prune
if git ls-remote --exit-code --heads origin "${BRANCH_NAME}" >/dev/null 2>&1; then
if git ls-remote --exit-code --heads origin "${BRANCH_NAME}" >/dev/null 2>&1; then echo "[FATAL] Branch already exists on origin: ${BRANCH_NAME}" >&2
echo "[FATAL] Branch already exists on origin: ${BRANCH_NAME}" >&2 exit 2
exit 2 fi
fi
git checkout -B "${BRANCH_NAME}" "origin/${BASE_BRANCH}"
git checkout -B "${BRANCH_NAME}" "origin/${BASE_BRANCH}" echo "BRANCH_NAME=${BRANCH_NAME}" >> "$GITHUB_ENV"
echo "BRANCH_NAME=${BRANCH_NAME}" >> "$GITHUB_ENV"
- name: Enforce update feed files absent (update.xml, updates.xml)
- name: Enforce update feed files absent (update.xml, updates.xml) if: ${{ env.REPORT_ONLY != 'true' }}
if: ${{ env.REPORT_ONLY != 'true' }} run: |
run: | source "$CI_HELPERS"
source "$CI_HELPERS" moko_init "Enforce update feed deletion"
moko_init "Enforce update feed deletion"
git rm -f --ignore-unmatch update.xml updates.xml || true
git rm -f --ignore-unmatch update.xml updates.xml || true rm -f update.xml updates.xml || true
rm -f update.xml updates.xml || true
if [[ -f update.xml || -f updates.xml ]]; then
if [[ -f update.xml || -f updates.xml ]]; then echo "[FATAL] update feed files still present after deletion attempt." >&2
echo "[FATAL] update feed files still present after deletion attempt." >&2 ls -la update.xml updates.xml 2>/dev/null || true
ls -la update.xml updates.xml 2>/dev/null || true exit 2
exit 2 fi
fi
- name: Preflight discovery (governed version markers outside .github)
- name: Preflight discovery (governed version markers outside .github) run: |
run: | source "$CI_HELPERS"
source "$CI_HELPERS" moko_init "Preflight discovery"
moko_init "Preflight discovery"
COUNT=$(grep -RIn --exclude-dir=.git --exclude-dir=.github -i -E "VERSION[[:space:]]*:[[:space:]]*[0-9]{2}[.][0-9]{2}[.][0-9]{2}" . | wc -l || true)
COUNT=$(grep -RIn --exclude-dir=.git --exclude-dir=.github -i -E "VERSION[[:space:]]*:[[:space:]]*[0-9]{2}[.][0-9]{2}[.][0-9]{2}" . | wc -l || true) COUNT2=$(grep -RIn --exclude-dir=.git --exclude-dir=.github "<version" . | wc -l || true)
COUNT2=$(grep -RIn --exclude-dir=.git --exclude-dir=.github "<version" . | wc -l || true)
echo "[INFO] VERSION: hits (repo wide): ${COUNT}"
echo "[INFO] VERSION: hits (repo wide): ${COUNT}" echo "[INFO] <version> hits (repo wide): ${COUNT2}"
echo "[INFO] <version> hits (repo wide): ${COUNT2}"
if [[ "${COUNT}" -eq 0 && "${COUNT2}" -eq 0 ]]; then
if [[ "${COUNT}" -eq 0 && "${COUNT2}" -eq 0 ]]; then echo "[FATAL] No governed version markers found outside .github" >&2
echo "[FATAL] No governed version markers found outside .github" >&2 exit 2
exit 2 fi
fi
- name: Bump versions and update manifest dates (targeted, excluding .github)
- name: Bump versions and update manifest dates (targeted, excluding .github) run: |
run: | source "$CI_HELPERS"
source "$CI_HELPERS" moko_init "Version bump"
moko_init "Version bump"
python3 - <<'PY'
python3 - <<'PY' import json
import json import os
import os import re
import re from pathlib import Path
from pathlib import Path from collections import defaultdict
from collections import defaultdict from datetime import datetime, timezone
from datetime import datetime, timezone
new_version = (os.environ.get("NEW_VERSION") or "").strip()
new_version = (os.environ.get("NEW_VERSION") or "").strip() version_text = (os.environ.get("VERSION_TEXT") or "").strip()
version_text = (os.environ.get("VERSION_TEXT") or "").strip() report_only = (os.environ.get("REPORT_ONLY") or "").strip().lower() == "true"
report_only = (os.environ.get("REPORT_ONLY") or "").strip().lower() == "true" report_path = (os.environ.get("REPORT_PATH") or "").strip()
report_path = (os.environ.get("REPORT_PATH") or "").strip()
stamp = datetime.now(timezone.utc).strftime("%Y-%m-%d")
stamp = datetime.now(timezone.utc).strftime("%Y-%m-%d") root = Path(".").resolve()
root = Path(".").resolve()
# Use escape sequences only. Do not introduce literal tab characters.
# Use escape sequences only. Do not introduce literal tab characters. header_re = re.compile(r"(?im)(VERSION[ \t]*:[ \t]*)([0-9]{2}[.][0-9]{2}[.][0-9]{2})")
header_re = re.compile(r"(?im)(VERSION[ ]*:[ ]*)([0-9]{2}[.][0-9]{2}[.][0-9]{2})") manifest_marker_re = re.compile(r"(?is)<extension\b")
manifest_marker_re = re.compile(r"(?is)<extension") xml_version_re = re.compile(r"(?is)(<version[ \t]*>)([^<]*?)(</version[ \t]*>)")
xml_version_re = re.compile(r"(?is)(<version[ ]*>)([^<]*?)(</version[ ]*>)") xml_date_res = [
xml_date_res = [ re.compile(r"(?is)(<creationDate[ \t]*>)([^<]*?)(</creationDate[ \t]*>)"),
re.compile(r"(?is)(<creationDate[ ]*>)([^<]*?)(</creationDate[ ]*>)"), re.compile(r"(?is)(<date[ \t]*>)([^<]*?)(</date[ \t]*>)"),
re.compile(r"(?is)(<date[ ]*>)([^<]*?)(</date[ ]*>)"), re.compile(r"(?is)(<releaseDate[ \t]*>)([^<]*?)(</releaseDate[ \t]*>)"),
re.compile(r"(?is)(<releaseDate[ ]*>)([^<]*?)(</releaseDate[ ]*>)"), ]
]
skip_ext = {
skip_ext = { ".json", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".pdf",
".json", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".pdf", ".zip", ".7z", ".tar", ".gz", ".woff", ".woff2", ".ttf", ".otf",
".zip", ".7z", ".tar", ".gz", ".woff", ".woff2", ".ttf", ".otf", ".mp3", ".mp4",
".mp3", ".mp4", }
} skip_dirs = {".git", ".github", "node_modules", "vendor", ".venv", "dist", "build"}
skip_dirs = {".git", ".github", "node_modules", "vendor", ".venv", "dist", "build"}
counters = defaultdict(int)
counters = defaultdict(int) updated_files = []
updated_files = [] updated_manifests = []
updated_manifests = [] would_update_files = []
would_update_files = [] would_update_manifests = []
would_update_manifests = []
exclude_root = {"update.xml", "updates.xml"}
exclude_root = {"update.xml", "updates.xml"}
def should_skip(p: Path) -> bool:
def should_skip(p: Path) -> bool: if p.suffix.lower() in skip_ext:
if p.suffix.lower() in skip_ext: counters["skipped_by_ext"] += 1
counters["skipped_by_ext"] += 1 return True
return True parts = {x.lower() for x in p.parts}
parts = {x.lower() for x in p.parts} if any(d in parts for d in skip_dirs):
if any(d in parts for d in skip_dirs): counters["skipped_by_dir"] += 1
counters["skipped_by_dir"] += 1 return True
return True return False
return False
for p in root.rglob("*"):
for p in root.rglob("*"): if not p.is_file():
if not p.is_file(): continue
continue if should_skip(p):
if should_skip(p): continue
continue
if p.parent == root and p.name.lower() in exclude_root:
if p.parent == root and p.name.lower() in exclude_root: counters["skipped_release_artifacts"] += 1
counters["skipped_release_artifacts"] += 1 continue
continue
try:
try: original = p.read_text(encoding="utf-8", errors="replace")
original = p.read_text(encoding="utf-8", errors="replace") except Exception:
except Exception: counters["skipped_read_error"] += 1
counters["skipped_read_error"] += 1 continue
continue
text = original
text = original
text, n1 = header_re.subn(lambda m: m.group(1) + new_version, text)
text, n1 = header_re.subn(lambda m: m.group(1) + new_version, text) if n1:
if n1: counters["header_replacements"] += n1
counters["header_replacements"] += n1
is_manifest = (p.suffix.lower() == ".xml" and manifest_marker_re.search(original) is not None)
is_manifest = (p.suffix.lower() == ".xml" and manifest_marker_re.search(original) is not None) if is_manifest:
if is_manifest: text, n2 = xml_version_re.subn(lambda m: m.group(1) + new_version + m.group(3), text)
text, n2 = xml_version_re.subn(lambda m: m.group(1) + new_version + m.group(3), text) if n2:
if n2: counters["xml_version_replacements"] += n2
counters["xml_version_replacements"] += n2
for rx in xml_date_res:
for rx in xml_date_res: text, n3 = rx.subn(lambda m: m.group(1) + stamp + m.group(3), text)
text, n3 = rx.subn(lambda m: m.group(1) + stamp + m.group(3), text) if n3:
if n3: counters["xml_date_replacements"] += n3
counters["xml_date_replacements"] += n3
if text != original:
if text != original: would_update_files.append(str(p))
would_update_files.append(str(p)) if is_manifest:
if is_manifest: would_update_manifests.append(str(p))
would_update_manifests.append(str(p))
if not report_only:
if not report_only: p.write_text(text, encoding="utf-8")
p.write_text(text, encoding="utf-8") updated_files.append(str(p))
updated_files.append(str(p)) if is_manifest:
if is_manifest: updated_manifests.append(str(p))
updated_manifests.append(str(p))
report = {
report = { "mode": "report_only" if report_only else "apply",
"mode": "report_only" if report_only else "apply", "new_version": new_version,
"new_version": new_version, "version_text": version_text,
"version_text": version_text, "stamp_utc": stamp,
"stamp_utc": stamp, "counters": dict(counters),
"counters": dict(counters), "updated_files": updated_files,
"updated_files": updated_files, "updated_manifests": updated_manifests,
"updated_manifests": updated_manifests, "would_update_files": would_update_files,
"would_update_files": would_update_files, "would_update_manifests": would_update_manifests,
"would_update_manifests": would_update_manifests, }
}
Path(report_path).write_text(json.dumps(report, indent=2), encoding="utf-8")
Path(report_path).write_text(json.dumps(report, indent=2), encoding="utf-8")
print("[INFO] Report written to:", report_path)
print("[INFO] Report written to:", report_path) print("[INFO] Mode:", report["mode"])
print("[INFO] Mode:", report["mode"]) print("[INFO] Would update files:", len(would_update_files))
print("[INFO] Would update files:", len(would_update_files)) print("[INFO] Would update manifests:", len(would_update_manifests))
print("[INFO] Would update manifests:", len(would_update_manifests)) print("[INFO] Updated files:", len(updated_files))
print("[INFO] Updated files:", len(updated_files)) print("[INFO] Updated manifests:", len(updated_manifests))
print("[INFO] Updated manifests:", len(updated_manifests)) PY
PY
- name: Commit changes
- name: Commit changes if: ${{ env.REPORT_ONLY != 'true' }}
if: ${{ env.REPORT_ONLY != 'true' }} run: |
run: | source "$CI_HELPERS"
source "$CI_HELPERS" moko_init "Commit changes"
moko_init "Commit changes"
if [[ -z "$(git status --porcelain=v1)" ]]; then
if [[ -z "$(git status --porcelain=v1)" ]]; then echo "[INFO] No changes detected. Skipping commit."
echo "[INFO] No changes detected. Skipping commit." exit 0
exit 0 fi
fi
git add -A
git add -A
MSG="chore(release): bump version to ${NEW_VERSION}"
MSG="chore(release): bump version to ${NEW_VERSION}" if [[ -n "${VERSION_TEXT}" ]]; then
if [[ -n "${VERSION_TEXT}" ]]; then MSG="${MSG} (${VERSION_TEXT})"
MSG="${MSG} (${VERSION_TEXT})" fi
fi
git commit -m "${MSG}"
git commit -m "${MSG}"
- name: Push branch
- name: Push branch if: ${{ env.REPORT_ONLY != 'true' }}
if: ${{ env.REPORT_ONLY != 'true' }} run: |
run: | source "$CI_HELPERS"
source "$CI_HELPERS" moko_init "Push branch"
moko_init "Push branch"
if [[ -z "${BRANCH_NAME:-}" ]]; then
if [[ -z "${BRANCH_NAME:-}" ]]; then echo "[FATAL] BRANCH_NAME not set." >&2
echo "[FATAL] BRANCH_NAME not set." >&2 exit 2
exit 2 fi
fi
git push --set-upstream origin "${BRANCH_NAME}"
git push --set-upstream origin "${BRANCH_NAME}"
- name: Publish audit trail
- name: Publish audit trail if: always()
if: always() run: |
run: | source "$CI_HELPERS"
source "$CI_HELPERS" moko_init "Publish audit trail"
moko_init "Publish audit trail"
echo "# Version branch run" >> "$GITHUB_STEP_SUMMARY"
echo "# Version branch run" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY" echo "- Repository: $GITHUB_REPOSITORY" >> "$GITHUB_STEP_SUMMARY"
echo "- Repository: $GITHUB_REPOSITORY" >> "$GITHUB_STEP_SUMMARY" echo "- Base branch: ${BASE_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
echo "- Base branch: ${BASE_BRANCH}" >> "$GITHUB_STEP_SUMMARY" echo "- Branch prefix: ${BRANCH_PREFIX}" >> "$GITHUB_STEP_SUMMARY"
echo "- Branch prefix: ${BRANCH_PREFIX}" >> "$GITHUB_STEP_SUMMARY" echo "- New version: ${NEW_VERSION}" >> "$GITHUB_STEP_SUMMARY"
echo "- New version: ${NEW_VERSION}" >> "$GITHUB_STEP_SUMMARY" echo "- Version text: ${VERSION_TEXT}" >> "$GITHUB_STEP_SUMMARY"
echo "- Version text: ${VERSION_TEXT}" >> "$GITHUB_STEP_SUMMARY" echo "- Report only: ${REPORT_ONLY}" >> "$GITHUB_STEP_SUMMARY"
echo "- Report only: ${REPORT_ONLY}" >> "$GITHUB_STEP_SUMMARY" echo "- Commit changes: ${COMMIT_CHANGES}" >> "$GITHUB_STEP_SUMMARY"
echo "- Commit changes: ${COMMIT_CHANGES}" >> "$GITHUB_STEP_SUMMARY" echo "- New branch: ${BRANCH_NAME:-}" >> "$GITHUB_STEP_SUMMARY"
echo "- New branch: ${BRANCH_NAME:-}" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "## Version bump report" >> "$GITHUB_STEP_SUMMARY"
echo "## Version bump report" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
if [[ -f "${REPORT_PATH}" ]]; then
if [[ -f "${REPORT_PATH}" ]]; then echo "\`\`\`json" >> "$GITHUB_STEP_SUMMARY"
echo "\`\`\`json" >> "$GITHUB_STEP_SUMMARY" head -c 12000 "${REPORT_PATH}" >> "$GITHUB_STEP_SUMMARY" || true
head -c 12000 "${REPORT_PATH}" >> "$GITHUB_STEP_SUMMARY" || true echo "" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY" echo "\`\`\`" >> "$GITHUB_STEP_SUMMARY"
echo "\`\`\`" >> "$GITHUB_STEP_SUMMARY" else
else echo "Report file not found at: ${REPORT_PATH}" >> "$GITHUB_STEP_SUMMARY"
echo "Report file not found at: ${REPORT_PATH}" >> "$GITHUB_STEP_SUMMARY" fi
fi
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY" echo "## Error summary" >> "$GITHUB_STEP_SUMMARY"
echo "## Error summary" >> "$GITHUB_STEP_SUMMARY" echo "" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
if [[ -f "$ERROR_LOG" && -s "$ERROR_LOG" ]]; then
if [[ -f "$ERROR_LOG" && -s "$ERROR_LOG" ]]; then echo "\`\`\`" >> "$GITHUB_STEP_SUMMARY"
echo "\`\`\`" >> "$GITHUB_STEP_SUMMARY" tail -n 200 "$ERROR_LOG" >> "$GITHUB_STEP_SUMMARY" || true
tail -n 200 "$ERROR_LOG" >> "$GITHUB_STEP_SUMMARY" || true echo "\`\`\`" >> "$GITHUB_STEP_SUMMARY"
echo "\`\`\`" >> "$GITHUB_STEP_SUMMARY" else
else echo "No errors recorded." >> "$GITHUB_STEP_SUMMARY"
echo "No errors recorded." >> "$GITHUB_STEP_SUMMARY" fi
fi