From b2fcf711572237025c898ff54f8f6dfc50fbec44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Victor=20Martinez=20Montan=C3=A9?= Date: Thu, 23 Apr 2026 01:52:28 +0200 Subject: [PATCH] feat: refactor scripts & bump versions (#1) Co-authored-by: JasterV <49537445+JasterV@users.noreply.github.com> Reviewed-on: https://codeberg.org/JasterV/docker/pulls/1 --- .woodpecker/publish-release-plz-image.yml | 2 +- .../publish-release-plz-update-pr-image.yml | 2 +- .woodpecker/publish-rust-ci-image.yml | 2 +- .../publish-rust-magic-release-image.yml | 2 +- rust/Dockerfile | 4 +- rust/magic-release.Dockerfile | 5 +- rust/release-plz-update-pr.Dockerfile | 2 +- rust/release-plz.Dockerfile | 4 +- rust/scripts/magic-release.sh | 248 +++++++++++++----- rust/scripts/update-pr.sh | 193 +++++++++----- 10 files changed, 325 insertions(+), 139 deletions(-) diff --git a/.woodpecker/publish-release-plz-image.yml b/.woodpecker/publish-release-plz-image.yml index f3b9365..c04626b 100644 --- a/.woodpecker/publish-release-plz-image.yml +++ b/.woodpecker/publish-release-plz-image.yml @@ -14,4 +14,4 @@ steps: from_secret: CONTAINER_REGISTRY_TOKEN repo: codeberg.org/jasterv/release-plz dockerfile: "rust/release-plz.Dockerfile" - tags: 0.3.156,0.3,latest + tags: 0.3.157,0.3,latest diff --git a/.woodpecker/publish-release-plz-update-pr-image.yml b/.woodpecker/publish-release-plz-update-pr-image.yml index 60bd3f5..44614bd 100644 --- a/.woodpecker/publish-release-plz-update-pr-image.yml +++ b/.woodpecker/publish-release-plz-update-pr-image.yml @@ -18,4 +18,4 @@ steps: from_secret: CONTAINER_REGISTRY_TOKEN repo: codeberg.org/jasterv/release-plz-update-pr dockerfile: "rust/release-plz-update-pr.Dockerfile" - tags: 0.3.156,0.3,latest + tags: 0.3.157,0.3,latest diff --git a/.woodpecker/publish-rust-ci-image.yml b/.woodpecker/publish-rust-ci-image.yml index 8443041..a458ea0 100644 --- a/.woodpecker/publish-rust-ci-image.yml +++ b/.woodpecker/publish-rust-ci-image.yml @@ -13,4 +13,4 @@ steps: from_secret: CONTAINER_REGISTRY_TOKEN repo: codeberg.org/jasterv/rust-ci dockerfile: "rust/Dockerfile" - tags: 1.93,latest + tags: 1.95,latest diff --git a/.woodpecker/publish-rust-magic-release-image.yml b/.woodpecker/publish-rust-magic-release-image.yml index c8d225b..f5338e7 100644 --- a/.woodpecker/publish-rust-magic-release-image.yml +++ b/.woodpecker/publish-rust-magic-release-image.yml @@ -18,4 +18,4 @@ steps: from_secret: CONTAINER_REGISTRY_TOKEN repo: codeberg.org/jasterv/rust-magic-release dockerfile: "rust/magic-release.Dockerfile" - tags: 1.93,latest + tags: 1.95,latest diff --git a/rust/Dockerfile b/rust/Dockerfile index 3492013..2ce62a0 100644 --- a/rust/Dockerfile +++ b/rust/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.93.1-slim +FROM rust:1.95.0-slim # Install system dependencies for cargo-binstall and common rust crates RUN apt-get update && apt-get install -y --no-install-recommends \ @@ -12,7 +12,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ RUN rustup set profile minimal && \ rustup component add clippy rustfmt rust-src rust-analyzer -RUN rustup default 1.93.1-x86_64-unknown-linux-gnu +RUN rustup default 1.95.0-x86_64-unknown-linux-gnu RUN curl -Lskj https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-x86_64-unknown-linux-musl.tgz | tar -xz -C /usr/local/bin RUN cargo binstall --no-confirm cargo-make cargo-deny cargo-nextest diff --git a/rust/magic-release.Dockerfile b/rust/magic-release.Dockerfile index f3a6373..657e0b0 100644 --- a/rust/magic-release.Dockerfile +++ b/rust/magic-release.Dockerfile @@ -1,4 +1,4 @@ -FROM rust:alpine +FROM rust:1.95.0-alpine # Install system dependencies needed for git operations and the script RUN apk add --no-cache \ @@ -8,7 +8,8 @@ RUN apk add --no-cache \ openssl-dev \ musl-dev \ gcc \ - jq + jq \ + coreutils COPY rust/scripts/magic-release.sh /usr/local/bin/magic-release diff --git a/rust/release-plz-update-pr.Dockerfile b/rust/release-plz-update-pr.Dockerfile index c66dccf..21e07d9 100644 --- a/rust/release-plz-update-pr.Dockerfile +++ b/rust/release-plz-update-pr.Dockerfile @@ -1,4 +1,4 @@ -FROM jasterv/release-plz:0.3.156 +FROM jasterv/release-plz:0.3.157 COPY rust/scripts/update-pr.sh /usr/local/bin/update-pr diff --git a/rust/release-plz.Dockerfile b/rust/release-plz.Dockerfile index 1aa3983..4e21c8b 100644 --- a/rust/release-plz.Dockerfile +++ b/rust/release-plz.Dockerfile @@ -1,4 +1,4 @@ -FROM rust:alpine +FROM rust:1.95.0-alpine # Install system dependencies needed for git operations and the script RUN apk add --no-cache \ @@ -10,7 +10,7 @@ RUN apk add --no-cache \ gcc # Download the pre-compiled release-plz binary (much faster than cargo install) -RUN curl -fsSL https://github.com/release-plz/release-plz/releases/download/release-plz-v0.3.156/release-plz-x86_64-unknown-linux-musl.tar.gz | tar -xz -C /usr/local/bin +RUN curl -fsSL https://github.com/release-plz/release-plz/releases/download/release-plz-v0.3.157/release-plz-x86_64-unknown-linux-musl.tar.gz | tar -xz -C /usr/local/bin # Verify tools are ready RUN cargo --version && release-plz --version diff --git a/rust/scripts/magic-release.sh b/rust/scripts/magic-release.sh index 8265d42..c71f2d8 100755 --- a/rust/scripts/magic-release.sh +++ b/rust/scripts/magic-release.sh @@ -1,27 +1,190 @@ #!/bin/sh set -e -# --- Configuration --- +# Global Configuration TOKEN="${PLUGIN_TOKEN}" CRATES_TOKEN="${PLUGIN_CRATES_IO_TOKEN}" REPO_FULL_NAME="${CI_REPO}" API_URL="https://codeberg.org/api/v1/repos/${REPO_FULL_NAME}" -# Get workspace members in topological order (dependencies first) -# We use 'tac' because 'cargo tree' puts the "roots" at the top. -# Reversing it ensures "leaves" (dependencies) are published first. -ORDERED_CRATES=$(cargo tree --workspace --depth 0 --prefix none --format "{p}" | awk '{print $1}' | tac) +# ------------------------------------------------------------------------------ +# Gets a list of all packages in the workspace in topological order. +# This ensures that "leaves" (dependencies) are listed before the +# projects that rely on them, so they get published first. +# +# Arguments: None +# +# Returns: A space/newline separated list of crate names. +# ------------------------------------------------------------------------------ +get_ordered_crates() { + # 'cargo tree' lists dependencies. + # 'awk' extracts just the package name. + # 'tac' reverses the output list so dependencies are at the top. + cargo tree --workspace --depth 0 --prefix none --format "{p}" | awk '{print $1}' | tac +} -# Get all package metadata in one go -# Use jq to re-sort the metadata to match the ORDERED_NAMES -PACKAGES=$(cargo metadata --format-version 1 --no-deps | jq -r --arg names "$ORDERED_CRATES" ' - ($names | split("\n")) as $order - | [.packages[] | select(.publish != [])] as $pkgs - | $order[] as $name - | $pkgs[] | select(.name == $name) - | "\(.name) \(.version) \(.manifest_path)" -') +# ------------------------------------------------------------------------------ +# Gathers metadata (name, version, path) for all packages that +# are allowed to be published, keeping the required build order. +# +# Arguments: +# $1 - The ordered list of crate names (output of get_ordered_crates) +# +# Returns: A list of packages in the format: "name version manifest_path" +# ------------------------------------------------------------------------------ +get_publishable_packages() { + local ordered_names="$1" + + cargo metadata --format-version 1 --no-deps | jq -r --arg names "$ordered_names" ' + ($names | split("\n")) as $order + | [.packages[] | select(.publish != [])] as $pkgs + | $order[] as $name + | $pkgs[] | select(.name == $name) + | "\(.name) \(.version)" + ' +} +# ------------------------------------------------------------------------------ +# Checks the public crates.io API to see if a specific version of a package already exists. +# +# Arguments: +# $1 - Package name +# $2 - Package version +# +# Returns: 0 (Success/True) if it exists, 1 (Failure/False) if it does not. +# ------------------------------------------------------------------------------ +is_published_on_crates_io() { + local pkg_name="$1" + local pkg_version="$2" + local status_code + + status_code=$(curl -s -o /dev/null -w "%{http_code}" "https://crates.io/api/v1/crates/${pkg_name}/${pkg_version}") + + if [ "$status_code" = "200" ]; then + return 0 # True: It is already published + else + return 1 # False: It is not published yet + fi +} + +# ------------------------------------------------------------------------------ +# Checks if a Git tag exists for the release. +# If it doesn't, it creates the tag locally and pushes it to the remote repository. +# +# Arguments: +# $1 - The tag name (e.g., my-crate-v1.0.0) +# ------------------------------------------------------------------------------ +ensure_git_tag() { + local tag_name="$1" + + # "rev-parse" quietly checks if the tag exists locally or remotely + if git rev-parse "$tag_name" >/dev/null 2>&1; then + echo " - Tag $tag_name already exists locally/remotely." + else + echo " - Tagging $tag_name..." + git tag "$tag_name" + + # Push the tag to Codeberg using the CI token for authentication + git push "https://$TOKEN@codeberg.org/${REPO_FULL_NAME}.git" "$tag_name" + fi +} + +# ------------------------------------------------------------------------------ +# Creates a GitHub/Codeberg-style release block on the repository page. +# It checks the API first to prevent duplicate "409" errors. +# +# Arguments: +# $1 - The tag name (e.g., my-crate-v1.0.0) +# $2 - Package name +# $3 - Package version +# ------------------------------------------------------------------------------ +ensure_codeberg_release() { + local tag_name="$1" + local pkg_name="$2" + local pkg_version="$3" + local release_check + local release_payload + + # Query the API to see if a release already exists for this tag + release_check=$(curl -s -H "Authorization: token $TOKEN" "$API_URL/releases/tags/$tag_name") + + if echo "$release_check" | grep -q "\"id\":"; then + echo " - Codeberg release for $tag_name already exists." + else + echo " - Creating Codeberg release for $tag_name..." + + # Create a multi-line JSON string containing the release details + release_payload=$(cat < /dev/null + fi +} + +# ------------------------------------------------------------------------------ +# Uses the Cargo CLI to publish the package to crates.io. +# +# Arguments: +# $1 - Package name +# ------------------------------------------------------------------------------ +publish_to_crates_io() { + local pkg_name="$1" + + echo " - Publishing $pkg_name to crates.io..." + + # --allow-dirty is used because the CI environment might have modified files + # (like formatting lockfiles) before reaching this step. + cargo publish --token "$CRATES_TOKEN" -p "$pkg_name" --allow-dirty +} + +# ------------------------------------------------------------------------------ +# The main workflow for a single package. +# +# Arguments: +# $1 - Package name +# $2 - Package version +# ------------------------------------------------------------------------------ +process_package() { + local pkg_name="$1" + local pkg_version="$2" + local tag_name="${pkg_name}-v${pkg_version}" + + echo "--- Checking $pkg_name ($pkg_version) ---" + + if is_published_on_crates_io "$pkg_name" "$pkg_version"; then + echo " - $pkg_name v$pkg_version is already published. Skipping." + return 0 + fi + + echo " - New version detected. Proceeding with release..." + + ensure_git_tag "$tag_name" + ensure_codeberg_release "$tag_name" "$pkg_name" "$pkg_version" + publish_to_crates_io "$pkg_name" +} + +# ============================================================================== +# Main Execution Block +# ============================================================================== + +# Determine the correct build order +ORDERED_CRATES=$(get_ordered_crates) + +# Get metadata for all packages we are allowed to publish +PACKAGES=$(get_publishable_packages "$ORDERED_CRATES") + +# Exit early if there's nothing to do if [ -z "$PACKAGES" ]; then echo "No publishable packages found." exit 0 @@ -29,59 +192,12 @@ fi echo "Starting release process..." -# --- Process each package --- -echo "$PACKAGES" | while read -r PKG_NAME PKG_VERSION MANIFEST_PATH; do - - TAG_NAME="${PKG_NAME}-v${PKG_VERSION}" - echo "--- Checking $PKG_NAME ($PKG_VERSION) ---" - - # Check if this version is already on crates.io - status_code=$(curl -s -o /dev/null -w "%{http_code}" "https://crates.io/api/v1/crates/${PKG_NAME}/${PKG_VERSION}") - - if [ "$status_code" = "200" ]; then - echo " - $PKG_NAME v$PKG_VERSION is already published. Skipping." - continue +# Loop through each package line by line and process it +echo "$PACKAGES" | while read -r PKG_NAME PKG_VERSION; do + # Skip empty lines + if [ -n "$PKG_NAME" ]; then + process_package "$PKG_NAME" "$PKG_VERSION" fi - - echo " - New version detected. Proceeding with release..." - - # Create Git Tag if it doesn't exist - if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then - echo " - Tag $TAG_NAME already exists locally/remotely." - else - echo " - Tagging $TAG_NAME..." - git tag "$TAG_NAME" - # Use the token for authenticated push - git push "https://$TOKEN@codeberg.org/${REPO_FULL_NAME}.git" "$TAG_NAME" - fi - - # Create Codeberg Release - # We check if the release exists via API first to avoid 409 errors - RELEASE_CHECK=$(curl -s -H "Authorization: token $TOKEN" "$API_URL/releases/tags/$TAG_NAME") - if echo "$RELEASE_CHECK" | grep -q "\"id\":"; then - echo " - Codeberg release for $TAG_NAME already exists." - else - echo " - Creating Codeberg release for $TAG_NAME..." - RELEASE_PAYLOAD=$(cat < /dev/null - fi - - # Publish to Crates.io - echo " - Publishing $PKG_NAME to crates.io..." - # --allow-dirty handles cases where the CI environment modified files (like lockfiles) - cargo publish --token "$CRATES_TOKEN" -p "$PKG_NAME" --allow-dirty done echo "--- All crates processed ---" diff --git a/rust/scripts/update-pr.sh b/rust/scripts/update-pr.sh index 721fa07..501fc18 100755 --- a/rust/scripts/update-pr.sh +++ b/rust/scripts/update-pr.sh @@ -1,7 +1,9 @@ #!/bin/sh set -e -# --- 1. Configuration & Defaults --- +# ============================================================================== +# Global Configuration & Defaults +# ============================================================================== # Woodpecker settings are passed as PLUGIN_* environment variables TOKEN="${PLUGIN_TOKEN}" BASE_BRANCH="${PLUGIN_BASE_BRANCH:-"main"}" @@ -15,78 +17,145 @@ PR_TITLE="${PLUGIN_PR_TITLE:-"chore: release-plz update"}" REPO_FULL_NAME="${CI_REPO}" # e.g., "username/repo" API_URL="https://codeberg.org/api/v1/repos/${REPO_FULL_NAME}" -# --- 2. Validation --- -if [ -z "$TOKEN" ]; then - echo "Error: 'token' setting is missing. Please provide a Codeberg Access Token." - exit 1 -fi +# ------------------------------------------------------------------------------ +# Checks if the required environment variables are provided before running the rest of the script. +# +# Arguments: None +# ------------------------------------------------------------------------------ +validate_environment() { + if [ -z "$TOKEN" ]; then + echo "Error: 'token' setting is missing. Please provide a Codeberg Access Token." + exit 1 + fi +} -# --- 3. Run release-plz update --- -echo "--- Running release-plz update ---" +# ------------------------------------------------------------------------------ +# Executes the release-plz tool to bump versions and generate changelogs based on conventional commits. +# +# Arguments: None +# ------------------------------------------------------------------------------ +run_release_plz() { + echo "--- Running release-plz update ---" + release-plz update --verbose --manifest-path="$(pwd)/Cargo.toml" +} -release-plz update --verbose --manifest-path="$(pwd)/Cargo.toml" +# ------------------------------------------------------------------------------ +# Checks the current git workspace to see if release-plz modified any files (like Cargo.toml or CHANGELOG.md). +# +# Arguments: None +# +# Returns: 0 (True) if there are modified files, 1 (False) if clean. +# ------------------------------------------------------------------------------ +has_changes() { + if [ -z "$(git status --porcelain)" ]; then + return 1 # False: No changes + else + return 0 # True: Changes exist + fi +} -# Check if any files were changed (Cargo.toml, CHANGELOG.md, etc.) -if [ -z "$(git status --porcelain)" ]; then +# ------------------------------------------------------------------------------ +# Configures the git bot identity, commits the modified files,and force-pushes them to a temporary branch on Codeberg. +# +# Arguments: None +# ------------------------------------------------------------------------------ +commit_and_push_changes() { + echo "Committing changes to branch: $TEMP_BRANCH..." + git checkout -B "$TEMP_BRANCH" + git add . + + # Using environment variables temporarily sets the identity for THIS commit only, + # preventing any accidental overwrites to a developer's global or local git config. + GIT_AUTHOR_NAME="$BOT_NAME" \ + GIT_AUTHOR_EMAIL="$BOT_EMAIL" \ + GIT_COMMITTER_NAME="$BOT_NAME" \ + GIT_COMMITTER_EMAIL="$BOT_EMAIL" \ + git commit -m "$COMMIT_MESSAGE" + + echo "Pushing changes to Codeberg..." + # Force push to update the branch (and the linked PR if it already exists) + git push -f "https://$TOKEN@codeberg.org/${REPO_FULL_NAME}.git" "$TEMP_BRANCH" +} + +# ------------------------------------------------------------------------------ +# Queries the Codeberg API to see if a Pull Request already exists from our temporary branch to the main base branch. +# +# Arguments: None +# +# Returns: 0 (True) if an open PR exists, 1 (False) if it does not. +# ------------------------------------------------------------------------------ +has_open_pull_request() { + local pr_search + + # Search for an open PR from our specific head branch to the base branch + pr_search=$(curl -s -H "Authorization: token $TOKEN" \ + "$API_URL/pulls?state=open&head=$TEMP_BRANCH&base=$BASE_BRANCH") + + # Check if the JSON response contains our branch name in the "head" object + if echo "$pr_search" | grep -q "\"head\":{\"label\":\"$TEMP_BRANCH\""; then + return 0 # True + else + return 1 # False + fi +} + +# ------------------------------------------------------------------------------ +# Constructs a JSON payload and sends it to the Codeberg API to open a brand new Pull Request. +# +# Arguments: None +# ------------------------------------------------------------------------------ +create_pull_request() { + local pr_payload + local create_response + + echo "No open PR found. Creating a new one..." + + pr_payload=$(cat <