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
This commit is contained in:
Victor Martinez Montané 2026-04-23 01:52:28 +02:00
parent f2c866c11a
commit b2fcf71157
10 changed files with 325 additions and 139 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 <<EOF
{
"tag_name": "$tag_name",
"name": "$pkg_name v$pkg_version",
"body": "Automated release for crate **$pkg_name** at version \`$pkg_version\`.\n\nSee CHANGELOG.md for details.",
"draft": false,
"prerelease": false
}
EOF
)
# Send the JSON payload to the API via a POST request
curl -s -X 'POST' "$API_URL/releases" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "$release_payload" > /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 <<EOF
{
"tag_name": "$TAG_NAME",
"name": "$PKG_NAME v$PKG_VERSION",
"body": "Automated release for crate **$PKG_NAME** at version \`$PKG_VERSION\`. \n\nSee CHANGELOG.md for details.",
"draft": false,
"prerelease": false
}
EOF
)
curl -s -X 'POST' "$API_URL/releases" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "$RELEASE_PAYLOAD" > /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 ---"

View file

@ -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 <<EOF
{
"base": "$BASE_BRANCH",
"head": "$TEMP_BRANCH",
"title": "$PR_TITLE",
"body": "This is an automated PR generated by [release-plz](https://github.com/release-plz/release-plz) via Woodpecker CI."
}
EOF
)
create_response=$(curl -s -X 'POST' "$API_URL/pulls" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "$pr_payload")
# Verify if creation was successful by looking for an ID in the response
if echo "$create_response" | grep -q "\"id\":"; then
echo "Successfully created Pull Request!"
else
echo "Failed to create Pull Request. Response:"
echo "$create_response"
exit 1
fi
}
# ==============================================================================
# Main Execution Block
# ==============================================================================
validate_environment
run_release_plz
# If there are no changes, we can exit cleanly right away.
if ! has_changes; then
echo "No changes detected. Repository is up to date."
exit 0
fi
echo "Changes detected. Preparing to update Codeberg..."
commit_and_push_changes
# --- 4. Git Setup & Push ---
# Configure identity for the commit
git config --global user.email "$BOT_EMAIL"
git config --global user.name "$BOT_NAME"
# Create or switch to the feature branch
git checkout -B "$TEMP_BRANCH"
git add .
git commit -m "$COMMIT_MESSAGE"
# Force push to update the branch (and the linked PR if it exists)
echo "Pushing changes to branch: $TEMP_BRANCH..."
git push -f "https://$TOKEN@codeberg.org/${REPO_FULL_NAME}.git" "$TEMP_BRANCH"
# --- 5. Pull Request Management ---
echo "Checking for existing Pull Request..."
# 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 search result contains our branch name
PR_EXISTS=$(echo "$PR_SEARCH" | grep -q "\"head\":{\"label\":\"$TEMP_BRANCH\"" && echo "yes" || echo "no")
if [ "$PR_EXISTS" = "no" ]; then
echo "No open PR found. Creating a new one..."
# Construct the JSON payload for the new PR
PR_PAYLOAD=$(cat <<EOF
{
"base": "$BASE_BRANCH",
"head": "$TEMP_BRANCH",
"title": "$PR_TITLE",
"body": "This is an automated PR generated by [release-plz](https://github.com/MarcoIeni/release-plz) via Woodpecker CI."
}
EOF
)
CREATE_RESPONSE=$(curl -s -X 'POST' "$API_URL/pulls" \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d "$PR_PAYLOAD")
# Check if creation was successful
if echo "$CREATE_RESPONSE" | grep -q "\"id\":"; then
echo "Successfully created Pull Request!"
else
echo "Failed to create Pull Request. Response:"
echo "$CREATE_RESPONSE"
exit 1
fi
if has_open_pull_request; then
echo "An open Pull Request already exists, it has been updated."
else
echo "An open Pull Request already exists. Codeberg has updated it via the push."
create_pull_request
fi
echo "--- Release-plz plugin task completed ---"