#!/usr/bin/env bash set -euo pipefail usage() { cat <<'USAGE' Usage: tools/release/push-release-tag.sh [options] Bumps installable GovOPlaN package versions, commits pending changes in the GovOPlaN release/module repositories, creates an annotated release tag in each repo, and pushes branch+tag. By default the script asks which version part to bump: major X.Y.Z -> (X+1).0.0 minor X.Y.Z -> X.(Y+1).0 subversion X.Y.Z -> X.Y.(Z+1) Options: --bump Version bump: major, minor, subversion, or patch. --version Explicit target version instead of a bump. May equal the current repo versions when they were bumped already. -r, --remote Remote to push to. Defaults to origin. -b, --branch Branch to update in every repo. Defaults to each current branch. -m, --message Commit message. Defaults to "Release v". --tag-message Annotated tag message. Defaults to the commit message. --skip-release-lock Do not regenerate core webui/package-lock.release.json. --warn-migration-audit Run the migration release audit without baseline strictness. --strict-migration-audit Require current Alembic heads to be recorded in the latest migration release baseline. --skip-migration-audit Do not run the release migration audit. --publish-web-catalog After core/modules are pushed, publish a signed release catalog into addideas-govoplan-website. --web-root addideas-govoplan-website checkout for --publish-web-catalog. Defaults to ../addideas-govoplan-website. --catalog-signing-key Catalog signing key for --publish-web-catalog. May be repeated during key rotation. --catalog-channel Catalog channel. Defaults to stable. --catalog-sequence Monotonic catalog sequence. Defaults to UTC timestamp. --catalog-expires-days Catalog expiry window. Defaults to 90. --catalog-public-url Public website URL. Defaults to https://govoplan.add-ideas.de. --build-web-catalog Run npm run build in addideas-govoplan-website before committing the catalog update. --npm npm executable for lockfile generation. -y, --yes Do not prompt before committing, tagging, and pushing. -n, --dry-run Print intended actions without changing files or git state. -h, --help Show this help. Repos: Installable release packages: govoplan-access govoplan-admin govoplan-tenancy govoplan-organizations govoplan-identity govoplan-idm govoplan-policy govoplan-audit govoplan-dashboard govoplan-files govoplan-mail govoplan-campaign govoplan-calendar govoplan-ops govoplan-core Roadmap/scaffold module repositories are tag-only until they have package metadata: addresses, appointments, cases, connectors, dms, erp, fit-connect, forms, identity-trust, ledger, notifications, payments, portal, reporting, scheduling, search, tasks, templates, workflow, xoev, xrechnung, and xta-osci. Examples: tools/release/push-release-tag.sh tools/release/push-release-tag.sh --bump subversion tools/release/push-release-tag.sh --version 0.2.0 --message "Release v0.2.0" USAGE } fail() { echo "error: $*" >&2 exit 1 } META_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" PARENT="$(dirname "$META_ROOT")" ROOT="${GOVOPLAN_CORE_ROOT:-$PARENT/govoplan-core}" ROOT="$(cd "$ROOT" && pwd)" PACKAGE_MODULE_REPOS=( "$PARENT/govoplan-access" "$PARENT/govoplan-admin" "$PARENT/govoplan-approvals" "$PARENT/govoplan-assets" "$PARENT/govoplan-audit" "$PARENT/govoplan-booking" "$PARENT/govoplan-calendar" "$PARENT/govoplan-campaign" "$PARENT/govoplan-certificates" "$PARENT/govoplan-committee" "$PARENT/govoplan-consultation" "$PARENT/govoplan-contracts" "$PARENT/govoplan-dashboard" "$PARENT/govoplan-docs" "$PARENT/govoplan-facilities" "$PARENT/govoplan-files" "$PARENT/govoplan-forms-runtime" "$PARENT/govoplan-grants" "$PARENT/govoplan-helpdesk" "$PARENT/govoplan-identity" "$PARENT/govoplan-idm" "$PARENT/govoplan-inspections" "$PARENT/govoplan-issue-reporting" "$PARENT/govoplan-learning" "$PARENT/govoplan-mail" "$PARENT/govoplan-ops" "$PARENT/govoplan-organizations" "$PARENT/govoplan-permits" "$PARENT/govoplan-policy" "$PARENT/govoplan-procurement" "$PARENT/govoplan-records" "$PARENT/govoplan-resources" "$PARENT/govoplan-risk-compliance" "$PARENT/govoplan-tenancy" "$PARENT/govoplan-transparency" ) TAG_ONLY_MODULE_REPOS=( "$PARENT/govoplan-addresses" "$PARENT/govoplan-appointments" "$PARENT/govoplan-cases" "$PARENT/govoplan-connectors" "$PARENT/govoplan-dms" "$PARENT/govoplan-erp" "$PARENT/govoplan-fit-connect" "$PARENT/govoplan-forms" "$PARENT/govoplan-identity-trust" "$PARENT/govoplan-ledger" "$PARENT/govoplan-notifications" "$PARENT/govoplan-payments" "$PARENT/govoplan-portal" "$PARENT/govoplan-postbox" "$PARENT/govoplan-reporting" "$PARENT/govoplan-scheduling" "$PARENT/govoplan-search" "$PARENT/govoplan-tasks" "$PARENT/govoplan-templates" "$PARENT/govoplan-workflow" "$PARENT/govoplan-xoev" "$PARENT/govoplan-xrechnung" "$PARENT/govoplan-xta-osci" ) MODULE_REPOS=("${PACKAGE_MODULE_REPOS[@]}" "${TAG_ONLY_MODULE_REPOS[@]}") PACKAGE_REPOS=("${PACKAGE_MODULE_REPOS[@]}" "$ROOT") REPOS=( "${MODULE_REPOS[@]}" "$ROOT" ) REMOTE="origin" BRANCH="" BUMP="" TARGET_VERSION="" COMMIT_MESSAGE="" TAG_MESSAGE="" DRY_RUN=0 YES=0 GENERATE_RELEASE_LOCK=1 MIGRATION_AUDIT_MODE="auto" NPM_BIN="${NPM:-}" PUBLISH_WEB_CATALOG=0 WEB_ROOT="$PARENT/addideas-govoplan-website" CATALOG_CHANNEL="stable" CATALOG_SEQUENCE="" CATALOG_EXPIRES_DAYS="90" CATALOG_PUBLIC_URL="https://govoplan.add-ideas.de" BUILD_WEB_CATALOG=0 CATALOG_SIGNING_KEYS=() if [[ -z "${PYTHON:-}" ]]; then if [[ -x "$META_ROOT/.venv/bin/python" ]]; then PYTHON="$META_ROOT/.venv/bin/python" else PYTHON="python3" fi fi if [[ -z "$NPM_BIN" ]]; then if [[ -x "/home/zemion/.nvm/versions/node/v22.22.3/bin/npm" ]]; then NPM_BIN="/home/zemion/.nvm/versions/node/v22.22.3/bin/npm" else NPM_BIN="npm" fi fi export GIT_SSH_COMMAND="${GIT_SSH_COMMAND:-ssh -o BatchMode=yes -o ConnectTimeout=15}" GIT_REMOTE_TIMEOUT="${GIT_REMOTE_TIMEOUT:-30}" normalize_bump() { local value="${1,,}" case "$value" in major|minor|subversion) printf '%s\n' "$value" ;; patch|sub|sub-version) printf '%s\n' "subversion" ;; *) return 1 ;; esac } validate_version() { [[ "$1" =~ ^[0-9]+[.][0-9]+[.][0-9]+$ ]] } version_gt() { local new_major new_minor new_patch old_major old_minor old_patch IFS=. read -r new_major new_minor new_patch <<< "$1" IFS=. read -r old_major old_minor old_patch <<< "$2" (( new_major > old_major )) && return 0 (( new_major < old_major )) && return 1 (( new_minor > old_minor )) && return 0 (( new_minor < old_minor )) && return 1 (( new_patch > old_patch )) && return 0 return 1 } bump_version() { local version="$1" local bump="$2" local major minor patch IFS=. read -r major minor patch <<< "$version" case "$bump" in major) printf '%s.0.0\n' "$((major + 1))" ;; minor) printf '%s.%s.0\n' "$major" "$((minor + 1))" ;; subversion) printf '%s.%s.%s\n' "$major" "$minor" "$((patch + 1))" ;; *) fail "unknown bump kind: $bump" ;; esac } get_project_name() { "$PYTHON" -c 'import pathlib, sys, tomllib; print(tomllib.loads(pathlib.Path(sys.argv[1]).read_text())["project"]["name"])' "$1/pyproject.toml" } get_project_version() { "$PYTHON" -c 'import pathlib, sys, tomllib; print(tomllib.loads(pathlib.Path(sys.argv[1]).read_text())["project"]["version"])' "$1/pyproject.toml" } update_pyproject() { local repo="$1" local version="$2" "$PYTHON" - "$repo/pyproject.toml" "$version" <<'PYCODE' from __future__ import annotations import pathlib import re import sys import tomllib path = pathlib.Path(sys.argv[1]) new_version = sys.argv[2] text = path.read_text() data = tomllib.loads(text) project = data["project"] name = project["name"] old_version = project["version"] text, version_count = re.subn( r'(?m)^version\s*=\s*["\'][^"\']+["\']\s*$', f'version = "{new_version}"', text, count=1, ) if version_count != 1: raise SystemExit(f"could not update project.version in {path}") if name != "govoplan-core": text, dependency_count = re.subn( r'(govoplan-[A-Za-z0-9-]+>=)\d+\.\d+\.\d+', rf'\g<1>{new_version}', text, ) if dependency_count < 1: raise SystemExit(f"could not update govoplan-core dependency in {path}") path.write_text(text) print(f"{name}: {old_version} -> {new_version}") PYCODE } update_manifest_version() { local repo="$1" local project_name="$2" local version="$3" local manifest_paths=() if [[ "$project_name" == "govoplan-core" ]]; then return 0 fi while IFS= read -r -d '' manifest_path; do manifest_paths+=("$manifest_path") done < <(find "$repo/src" -path "*/backend/manifest.py" -print0 2>/dev/null) [[ "${#manifest_paths[@]}" -gt 0 ]] || fail "missing module manifest under $repo/src" for manifest_path in "${manifest_paths[@]}"; do "$PYTHON" - "$manifest_path" "$version" <<'PYCODE' from __future__ import annotations import pathlib import re import sys path = pathlib.Path(sys.argv[1]) new_version = sys.argv[2] text = path.read_text() text, count = re.subn( r'(?m)^(\s*version=)["\'][^"\']+["\'](,?\s*)$', rf'\1"{new_version}"\2', text, count=1, ) if count == 0: text, count = re.subn( r'(?m)^(MODULE_VERSION\s*=\s*)["\'][^"\']+["\'](\s*)$', rf'\1"{new_version}"\2', text, count=1, ) if count != 1: raise SystemExit(f"could not update ModuleManifest.version in {path}") path.write_text(text) PYCODE done } update_webui_package() { local repo="$1" local project_name="$2" local version="$3" local package_path="" local release_package_path="$repo/webui/package.release.json" for package_path in "$repo/package.json" "$repo/webui/package.json"; do [[ -f "$package_path" ]] || continue "$PYTHON" - "$package_path" "$project_name" "$version" <<'PYCODE' from __future__ import annotations import json import pathlib import sys path = pathlib.Path(sys.argv[1]) project_name = sys.argv[2] new_version = sys.argv[3] data = json.loads(path.read_text()) data["version"] = new_version if project_name != "govoplan-core": peers = data.setdefault("peerDependencies", {}) if "@govoplan/core-webui" in peers: peers["@govoplan/core-webui"] = f"^{new_version}" path.write_text(json.dumps(data, indent=2) + "\n") PYCODE done if [[ "$project_name" == "govoplan-core" && -f "$release_package_path" ]]; then "$PYTHON" - "$release_package_path" "$version" <<'PYCODE' from __future__ import annotations import json import pathlib import re import sys path = pathlib.Path(sys.argv[1]) new_version = sys.argv[2] data = json.loads(path.read_text()) data["version"] = new_version dependencies = data.setdefault("dependencies", {}) for name, spec in list(dependencies.items()): if name.startswith("@govoplan/") and isinstance(spec, str) and spec.startswith("git+"): dependencies[name] = re.sub(r"#v\d+\.\d+\.\d+$", f"#v{new_version}", spec) path.write_text(json.dumps(data, indent=2) + "\n") PYCODE fi } update_version_files() { local repo="$1" local version="$2" local project_name="${PROJECT_NAMES[$repo]}" update_pyproject "$repo" "$version" update_manifest_version "$repo" "$project_name" "$version" update_webui_package "$repo" "$project_name" "$version" } refresh_development_webui_lock() { [[ -f "$ROOT/webui/package.json" ]] || return 0 [[ -f "$ROOT/webui/package-lock.json" ]] || return 0 run env PATH="$(dirname "$NPM_BIN"):$PATH" "$NPM_BIN" --prefix "$ROOT/webui" install --package-lock-only --ignore-scripts } generate_release_lock() { if [[ "$GENERATE_RELEASE_LOCK" -eq 0 ]]; then return fi [[ -x "$META_ROOT/tools/release/generate-release-lock.sh" ]] || fail "missing executable release lock generator: $META_ROOT/tools/release/generate-release-lock.sh" run "$META_ROOT/tools/release/generate-release-lock.sh" --core-root "$ROOT" --npm "$NPM_BIN" } run_migration_release_audit() { local audit_script="$META_ROOT/tools/release/release-migration-audit.py" local command=("$PYTHON" "$audit_script" "--track" "release") case "$MIGRATION_AUDIT_MODE" in skip) echo "Migration release audit: skipped" return ;; strict) command+=("--strict") ;; auto) command+=("--strict-if-baseline") ;; warn) ;; *) fail "unknown migration audit mode: $MIGRATION_AUDIT_MODE" ;; esac [[ -f "$audit_script" ]] || fail "missing migration audit helper: $audit_script" run "${command[@]}" } print_command() { printf '+' printf ' %q' "$@" printf '\n' } run() { print_command "$@" if [[ "$DRY_RUN" -eq 0 ]]; then "$@" fi } prompt_for_bump() { local answer="" if [[ -n "$BUMP" || -n "$TARGET_VERSION" ]]; then return fi if [[ ! -t 0 ]]; then fail "pass --bump major|minor|subversion or --version x.y.z for non-interactive use" fi echo "Select version increase:" echo " 1) major X.Y.Z -> (X+1).0.0" echo " 2) minor X.Y.Z -> X.(Y+1).0" echo " 3) subversion X.Y.Z -> X.Y.(Z+1)" printf 'Choice [subversion]: ' read -r answer case "${answer,,}" in ""|3|subversion|patch|sub|sub-version) BUMP="subversion" ;; 1|major) BUMP="major" ;; 2|minor) BUMP="minor" ;; *) fail "unknown version bump: $answer" ;; esac } confirm_release() { local answer="" if [[ "$DRY_RUN" -eq 1 || "$YES" -eq 1 ]]; then return fi if [[ ! -t 0 ]]; then fail "refusing to commit, tag, and push without confirmation; pass --yes for non-interactive use" fi printf 'Commit all changes, tag all repos, and push to %s? [y/N] ' "$REMOTE" read -r answer case "${answer,,}" in y|yes) ;; *) echo "Aborted." exit 1 ;; esac } while [[ $# -gt 0 ]]; do case "$1" in --bump) [[ $# -ge 2 ]] || fail "missing value for $1" BUMP="$(normalize_bump "$2")" || fail "unknown bump kind: $2" shift 2 ;; --version) [[ $# -ge 2 ]] || fail "missing value for $1" TARGET_VERSION="$2" shift 2 ;; -r|--remote) [[ $# -ge 2 ]] || fail "missing value for $1" REMOTE="$2" shift 2 ;; -b|--branch) [[ $# -ge 2 ]] || fail "missing value for $1" BRANCH="$2" shift 2 ;; -m|--message) [[ $# -ge 2 ]] || fail "missing value for $1" COMMIT_MESSAGE="$2" shift 2 ;; --tag-message) [[ $# -ge 2 ]] || fail "missing value for $1" TAG_MESSAGE="$2" shift 2 ;; --skip-release-lock) GENERATE_RELEASE_LOCK=0 shift ;; --warn-migration-audit) MIGRATION_AUDIT_MODE="warn" shift ;; --strict-migration-audit) MIGRATION_AUDIT_MODE="strict" shift ;; --skip-migration-audit) MIGRATION_AUDIT_MODE="skip" shift ;; --publish-web-catalog) PUBLISH_WEB_CATALOG=1 shift ;; --web-root) [[ $# -ge 2 ]] || fail "missing value for $1" WEB_ROOT="$2" shift 2 ;; --catalog-signing-key) [[ $# -ge 2 ]] || fail "missing value for $1" CATALOG_SIGNING_KEYS+=("$2") shift 2 ;; --catalog-channel) [[ $# -ge 2 ]] || fail "missing value for $1" CATALOG_CHANNEL="$2" shift 2 ;; --catalog-sequence) [[ $# -ge 2 ]] || fail "missing value for $1" CATALOG_SEQUENCE="$2" shift 2 ;; --catalog-expires-days) [[ $# -ge 2 ]] || fail "missing value for $1" CATALOG_EXPIRES_DAYS="$2" shift 2 ;; --catalog-public-url) [[ $# -ge 2 ]] || fail "missing value for $1" CATALOG_PUBLIC_URL="$2" shift 2 ;; --build-web-catalog) BUILD_WEB_CATALOG=1 shift ;; --npm) [[ $# -ge 2 ]] || fail "missing value for $1" NPM_BIN="$2" shift 2 ;; -y|--yes) YES=1 shift ;; -n|--dry-run) DRY_RUN=1 shift ;; -h|--help) usage exit 0 ;; *) echo "Unknown argument: $1" >&2 usage >&2 exit 2 ;; esac done if [[ -n "$BUMP" && -n "$TARGET_VERSION" ]]; then fail "use either --bump or --version, not both" fi if [[ "$PUBLISH_WEB_CATALOG" -eq 1 && ${#CATALOG_SIGNING_KEYS[@]} -eq 0 ]]; then fail "--publish-web-catalog requires at least one --catalog-signing-key" fi prompt_for_bump if ! command -v "$PYTHON" >/dev/null 2>&1; then fail "Python interpreter not found: $PYTHON" fi if ! command -v "$NPM_BIN" >/dev/null 2>&1; then fail "npm executable not found: $NPM_BIN" fi if [[ "$PUBLISH_WEB_CATALOG" -eq 1 ]]; then [[ -d "$WEB_ROOT/.git" ]] || fail "addideas-govoplan-website repo not found: $WEB_ROOT" [[ -x "$META_ROOT/tools/release/publish-release-catalog.sh" ]] || fail "missing executable catalog publisher: $META_ROOT/tools/release/publish-release-catalog.sh" fi declare -A PROJECT_NAMES declare -A OLD_VERSIONS declare -A BRANCHES declare -A REPO_KINDS for repo in "${REPOS[@]}"; do [[ -d "$repo/.git" ]] || fail "not a git repo: $repo" if [[ -f "$repo/pyproject.toml" ]]; then REPO_KINDS["$repo"]="package" PROJECT_NAMES["$repo"]="$(get_project_name "$repo")" OLD_VERSIONS["$repo"]="$(get_project_version "$repo")" validate_version "${OLD_VERSIONS[$repo]}" || fail "unsupported version ${OLD_VERSIONS[$repo]} in $repo" else REPO_KINDS["$repo"]="tag-only" PROJECT_NAMES["$repo"]="$(basename "$repo")" OLD_VERSIONS["$repo"]="tag-only" fi if [[ -n "$BRANCH" ]]; then BRANCHES["$repo"]="$BRANCH" else BRANCHES["$repo"]="$(git -C "$repo" symbolic-ref --quiet --short HEAD || true)" fi [[ -n "${BRANCHES[$repo]}" ]] || fail "cannot infer branch for $repo; pass --branch " git -C "$repo" check-ref-format "refs/heads/${BRANCHES[$repo]}" || fail "invalid branch name ${BRANCHES[$repo]} for $repo" git -C "$repo" remote get-url "$REMOTE" >/dev/null || fail "remote $REMOTE not found in $repo" upstream="$(git -C "$repo" rev-parse --abbrev-ref --symbolic-full-name '@{u}' 2>/dev/null || true)" if [[ -n "$upstream" && "$upstream" != "@{u}" ]]; then behind_count="$(git -C "$repo" rev-list --count "HEAD..$upstream")" [[ "$behind_count" == "0" ]] || fail "$repo is behind $upstream by $behind_count commit(s); pull/rebase first" fi done if [[ -z "$TARGET_VERSION" ]]; then BASE_VERSION="${OLD_VERSIONS[$ROOT]}" for repo in "${PACKAGE_REPOS[@]}"; do if [[ "${OLD_VERSIONS[$repo]}" != "$BASE_VERSION" ]]; then fail "repo versions differ; pass --version x.y.z to choose an explicit target" fi done TARGET_VERSION="$(bump_version "$BASE_VERSION" "$BUMP")" fi validate_version "$TARGET_VERSION" || fail "target version must be x.y.z: $TARGET_VERSION" for repo in "${PACKAGE_REPOS[@]}"; do if [[ "$TARGET_VERSION" == "${OLD_VERSIONS[$repo]}" ]]; then continue fi if ! version_gt "$TARGET_VERSION" "${OLD_VERSIONS[$repo]}"; then fail "target version $TARGET_VERSION must be greater than ${OLD_VERSIONS[$repo]} for ${PROJECT_NAMES[$repo]}" fi done TAG="v$TARGET_VERSION" COMMIT_MESSAGE="${COMMIT_MESSAGE:-Release $TAG}" TAG_MESSAGE="${TAG_MESSAGE:-$COMMIT_MESSAGE}" git -C "$ROOT" check-ref-format "refs/tags/$TAG" || fail "invalid tag name: $TAG" for repo in "${REPOS[@]}"; do if git -C "$repo" rev-parse --quiet --verify "refs/tags/$TAG" >/dev/null; then fail "local tag already exists in ${PROJECT_NAMES[$repo]}: $TAG" fi set +e timeout "$GIT_REMOTE_TIMEOUT" git -C "$repo" ls-remote --exit-code --tags "$REMOTE" "refs/tags/$TAG" >/dev/null ls_remote_status=$? set -e if [[ "$ls_remote_status" -eq 0 ]]; then fail "remote tag already exists for ${PROJECT_NAMES[$repo]} on $REMOTE: $TAG" elif [[ "$ls_remote_status" -ne 2 ]]; then fail "could not check remote tags for ${PROJECT_NAMES[$repo]} on $REMOTE" fi done echo "Release plan:" echo " version: $TARGET_VERSION" echo " tag: $TAG" echo " remote: $REMOTE" echo " commit message: $COMMIT_MESSAGE" echo " package repos: ${#PACKAGE_REPOS[@]} versioned" echo " tag-only module repos: ${#TAG_ONLY_MODULE_REPOS[@]} committed/tagged/pushed without package version files" if [[ "$GENERATE_RELEASE_LOCK" -eq 1 ]]; then echo " release lock: regenerate core webui/package-lock.release.json after module tags are pushed" else echo " release lock: skipped" fi echo " migration audit: $MIGRATION_AUDIT_MODE" if [[ "$PUBLISH_WEB_CATALOG" -eq 1 ]]; then echo " web catalog: publish $CATALOG_CHANNEL catalog to $WEB_ROOT after core tag is pushed" else echo " web catalog: skipped" fi echo for repo in "${REPOS[@]}"; do if [[ "${REPO_KINDS[$repo]}" == "package" ]]; then echo "${PROJECT_NAMES[$repo]}: ${OLD_VERSIONS[$repo]} -> $TARGET_VERSION on ${BRANCHES[$repo]}" else echo "${PROJECT_NAMES[$repo]}: tag-only $TAG on ${BRANCHES[$repo]}" fi git -C "$repo" status --short echo done run_migration_release_audit confirm_release for repo in "${PACKAGE_REPOS[@]}"; do if [[ "$DRY_RUN" -eq 1 ]]; then echo "Would update version files in $repo to $TARGET_VERSION" else update_version_files "$repo" "$TARGET_VERSION" fi done refresh_development_webui_lock for repo in "${MODULE_REPOS[@]}"; do run git -C "$repo" add -A if [[ "$DRY_RUN" -eq 0 ]]; then if git -C "$repo" diff --cached --quiet; then if [[ "${REPO_KINDS[$repo]}" == "package" ]]; then if [[ "$TARGET_VERSION" == "${OLD_VERSIONS[$repo]}" ]]; then echo "No staged changes for ${PROJECT_NAMES[$repo]}; version is already $TARGET_VERSION, tagging current HEAD." else fail "no staged changes for ${PROJECT_NAMES[$repo]} after version bump" fi else echo "No staged changes for ${PROJECT_NAMES[$repo]}; tagging current HEAD." fi else run git -C "$repo" commit -m "$COMMIT_MESSAGE" fi else run git -C "$repo" commit -m "$COMMIT_MESSAGE" fi run git -C "$repo" tag -a "$TAG" -m "$TAG_MESSAGE" done for repo in "${MODULE_REPOS[@]}"; do run git -C "$repo" push --atomic "$REMOTE" "HEAD:refs/heads/${BRANCHES[$repo]}" "refs/tags/$TAG" done generate_release_lock run git -C "$ROOT" add -A if [[ "$DRY_RUN" -eq 0 ]]; then if git -C "$ROOT" diff --cached --quiet; then fail "no staged changes for ${PROJECT_NAMES[$ROOT]} after version bump" fi fi run git -C "$ROOT" commit -m "$COMMIT_MESSAGE" run git -C "$ROOT" tag -a "$TAG" -m "$TAG_MESSAGE" run git -C "$ROOT" push --atomic "$REMOTE" "HEAD:refs/heads/${BRANCHES[$ROOT]}" "refs/tags/$TAG" if [[ "$PUBLISH_WEB_CATALOG" -eq 1 ]]; then CATALOG_ARGS=( "$META_ROOT/tools/release/publish-release-catalog.sh" --version "$TARGET_VERSION" --channel "$CATALOG_CHANNEL" --expires-days "$CATALOG_EXPIRES_DAYS" --core-root "$ROOT" --web-root "$WEB_ROOT" --public-base-url "$CATALOG_PUBLIC_URL" --npm "$NPM_BIN" --commit --tag --push --remote "$REMOTE" ) if [[ -n "$BRANCH" ]]; then CATALOG_ARGS+=(--branch "$BRANCH") fi if [[ -n "$CATALOG_SEQUENCE" ]]; then CATALOG_ARGS+=(--sequence "$CATALOG_SEQUENCE") fi if [[ "$BUILD_WEB_CATALOG" -eq 1 ]]; then CATALOG_ARGS+=(--build-web) fi for signing_key in "${CATALOG_SIGNING_KEYS[@]}"; do CATALOG_ARGS+=(--catalog-signing-key "$signing_key") done if [[ "$DRY_RUN" -eq 1 ]]; then CATALOG_ARGS+=(--dry-run) fi run "${CATALOG_ARGS[@]}" fi if [[ "$DRY_RUN" -eq 1 ]]; then echo "Dry run complete for $TAG across all GovOPlaN repos." else echo "Released $TAG across all GovOPlaN repos." fi