#!/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) 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" # ------------------------------------------------------------------------------ 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 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 ---"