docker/rust/scripts/magic-release.sh
Victor Martinez Montané b2fcf71157 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
2026-04-23 01:52:28 +02:00

203 lines
6.9 KiB
Bash
Executable file

#!/bin/sh
set -e
# 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}"
# ------------------------------------------------------------------------------
# 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
}
# ------------------------------------------------------------------------------
# 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
fi
echo "Starting release process..."
# 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
done
echo "--- All crates processed ---"