contracts 0.6.8

Design-by-contract attributes
Documentation
#!/usr/bin/env bash

# Updates changelog versions indicated by release-plz.

set -eEuo pipefail

WORKSPACE_METADATA="$(cargo metadata --format-version=1 --no-deps)"

function package_dir_for() {
    local package_name="$1"
    local manifest_path

    manifest_path="$(
        echo "$WORKSPACE_METADATA" \
            | jq -r --arg package_name "$package_name" '.packages[] | select(.name == $package_name) | .manifest_path' \
            | head -n 1
    )"

    if [[ -z "$manifest_path" || "$manifest_path" == "null" ]]; then
        echo "Could not determine package directory for $package_name" >&2
        return 1
    fi

    dirname "$manifest_path"
}

function bump_changelog_version() {
    local package_dir="$1"
    local version="$2"

    local changelog_file="${package_dir}/CHANGELOG.md"
    local readme_file="${package_dir}/README.md"
    local unreleased_line
    local next_heading_line
    local previous_version
    local change_chunk_file
    local line_count

    if [[ ! -f "$changelog_file" ]]; then
        echo "Skipping ${package_dir}: no CHANGELOG.md"
        return 0
    fi

    if awk -v version="$version" '$0 == "## " version { found = 1 } END { exit !found }' "$changelog_file"; then
        echo "Skipping ${changelog_file}: already contains ${version}"
        return 0
    fi

    unreleased_line="$(awk '/^## Unreleased$/ { print NR; exit }' "$changelog_file")"
    if [[ -z "$unreleased_line" ]]; then
        echo "Skipping ${changelog_file}: no '## Unreleased' heading"
        return 0
    fi

    next_heading_line="$(
        awk -v start="$unreleased_line" 'NR > start && /^## / { print NR; exit }' "$changelog_file"
    )"
    if [[ -z "$next_heading_line" ]]; then
        echo "Skipping ${changelog_file}: no version headings found after '## Unreleased'"
        return 0
    fi

    previous_version="$(sed -n "${next_heading_line}s/^## //p" "$changelog_file")"
    change_chunk_file="$(mktemp)"
    line_count="$(wc -l < "$changelog_file" | awk '{ print $1 }')"

    echo "${changelog_file} -> ${version}"

    sed -n "$((unreleased_line + 1)),$((next_heading_line - 1))p" "$changelog_file" \
        | awk '
            {
                lines[NR] = $0
            }
            END {
                first = 1
                while (first <= NR && lines[first] ~ /^[[:space:]]*$/) {
                    first++
                }

                last = NR
                while (last >= first && lines[last] ~ /^[[:space:]]*$/) {
                    last--
                }

                for (i = first; i <= last; i++) {
                    print lines[i]
                }
            }
        ' >"$change_chunk_file"

    if [[ "$(wc -w "$change_chunk_file" | awk '{ print $1 }')" == "0" ]]; then
        printf '%s\n' "- No significant changes since \`${previous_version}\`." >"$change_chunk_file"
    fi

    (
        sed -n "1,${unreleased_line}p" "$changelog_file"
        echo
        echo "## $version"
        echo
        sed -n '1,$p' "$change_chunk_file"
        echo
        sed -n "${next_heading_line},${line_count}p" "$changelog_file"
    ) >"$changelog_file.bak"

    mv "$changelog_file.bak" "$changelog_file"
    rm -f "$change_chunk_file"

    if [[ -f "$readme_file" && -n "$previous_version" ]]; then
        sed -i.bak -E "s#([=/])${previous_version}([/)])#\\1${version}\\2#g" "$readme_file"
        rm -f "${readme_file}.bak"
    fi
}

RELEASE_PLZ_PR_JSON="$1"
PR_NUMBER="$(echo "$RELEASE_PLZ_PR_JSON" | jq -r '.number')"

if [[ -n "$PR_NUMBER" && "$PR_NUMBER" != "null" ]]; then
    gh pr checkout "$PR_NUMBER"
    WORKSPACE_METADATA="$(cargo metadata --format-version=1 --no-deps)"
fi

specs="$(echo "$RELEASE_PLZ_PR_JSON" | jq -r '.releases[] | "\(.package_name):\(.version)"')"

for spec in $specs; do
    name="$(echo "$spec" | cut -d: -f1)"
    version="$(echo "$spec" | cut -d: -f2)"
    package_dir="$(package_dir_for "$name")"

    bump_changelog_version "$package_dir" "$version"
done

if [[ -n "$PR_NUMBER" && "$PR_NUMBER" != "null" ]]; then
    git add --all

    if ! git diff --cached --quiet; then
        git commit -m "docs: update changelog versions"
        git push
    fi
fi