diff --git a/.github/workflows/crates-version-bump.yml b/.github/workflows/crates-version-bump.yml index a80abb4..f2abd7c 100644 --- a/.github/workflows/crates-version-bump.yml +++ b/.github/workflows/crates-version-bump.yml @@ -32,22 +32,10 @@ jobs: - name: Setup Rust uses: dtolnay/rust-toolchain@stable - - name: Cache cargo tools - uses: actions/cache@v3 - id: cache-cargo-tools - with: - path: | - ~/.cargo/bin/cargo-workspaces - ~/.cargo/bin/git-cliff - key: cargo-tools-${{ runner.os }}-v1 - restore-keys: | - cargo-tools-${{ runner.os }}- - - name: Install cargo tools - if: steps.cache-cargo-tools.outputs.cache-hit != 'true' - run: | - cargo install cargo-workspaces - cargo install git-cliff + uses: taiki-e/install-action@v2 + with: + tool: cargo-release,git-cliff - name: Setup Git run: | @@ -59,50 +47,12 @@ jobs: BRANCH="${GITHUB_REF_NAME}" PRE_ID="${{ inputs.pre-id }}" FORCE="${{ inputs.force }}" + REPO_ROOT="$(git rev-parse --show-toplevel)" echo "Running on branch: $BRANCH" echo "Prerelease identifier: $PRE_ID" - echo "Force flag: $FORCE" - echo "Will bump version for ALL crates" - - FORCE_FLAG="" - if [ "$FORCE" = "true" ]; then - FORCE_FLAG="--force '*'" - fi - - CHANGED_CRATES=$(cargo workspaces changed) - - if [[ -z "$CHANGED_CRATES" ]]; then - echo "No crates changed since last tag. Nothing to do." - exit 0 - fi - - case "$BRANCH" in - main) - cargo workspaces version --allow-branch "$BRANCH" --no-global-tag --yes $FORCE_FLAG --no-git-push - ;; - development) - cargo workspaces version --allow-branch "$BRANCH" --no-global-tag prerelease --pre-id "$PRE_ID" --yes $FORCE_FLAG --no-git-push - ;; - *) - echo "❌ This workflow can only be run on 'main' or 'development'." - exit 1 - ;; - esac - - NEW_TAGS=$(git tag --points-at HEAD) - echo "Tags created locally:" - echo "$NEW_TAGS" - - ../../scripts/generate-changelogs.sh "$CHANGED_CRATES" - - echo "Amending commit to include changelogs..." - git commit --amend --no-edit - for tag in $NEW_TAGS; do - echo "Re-applying tag $tag on amended commit..." - git tag -f "$tag" - done + $REPO_ROOT/scripts/bump-version.sh git push origin "$BRANCH" git push origin --tags diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index c9e1bdf..418ad46 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -29,15 +29,21 @@ jobs: restore-keys: | ${{ runner.os }}-cargo- + - name: Install cargo tools + uses: taiki-e/install-action@v2 + with: + tool: cargo-release,git-cliff + - name: Publish quickmark-cli to crates.io run: cargo publish -p quickmark-cli --token ${{ secrets.CRATES_IO_TOKEN }} - name: Generate changelog id: changelog - uses: orhun/git-cliff-action@v4 - with: - config: cliff.toml - args: --include-path "crates/quickmark-cli/**" --tag-pattern "quickmark-cli@*" --latest + run: | + changelog=$(./scripts/latest-changes.sh quickmark-cli) + echo "content<> $GITHUB_OUTPUT + echo "$changelog" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT - name: Create GitHub release uses: softprops/action-gh-release@v2 diff --git a/.github/workflows/release-core.yml b/.github/workflows/release-core.yml index 0e52695..5f557ed 100644 --- a/.github/workflows/release-core.yml +++ b/.github/workflows/release-core.yml @@ -29,14 +29,21 @@ jobs: restore-keys: | ${{ runner.os }}-cargo- + - name: Install cargo tools + uses: taiki-e/install-action@v2 + with: + tool: cargo-release,git-cliff + - name: Publish quickmark-core to crates.io run: cargo publish -p quickmark-core --token ${{ secrets.CRATES_IO_TOKEN }} - name: Generate changelog - uses: orhun/git-cliff-action@v4 - with: - config: cliff.toml - args: --include-path "crates/quickmark-core/**" --tag-pattern "quickmark-core@*" --latest + id: changelog + run: | + changelog=$(./scripts/latest-changes.sh quickmark-core) + echo "content<> $GITHUB_OUTPUT + echo "$changelog" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT - name: Create GitHub release uses: softprops/action-gh-release@v2 diff --git a/.github/workflows/release-server.yml b/.github/workflows/release-server.yml index fb4addb..7ce5390 100644 --- a/.github/workflows/release-server.yml +++ b/.github/workflows/release-server.yml @@ -130,17 +130,19 @@ jobs: cp "$artifact_dir"/*.tar.gz release-archives/ 2>/dev/null || true fi done - # Copy changelog - cp artifacts/changelog/CHANGELOG.md ./ 2>/dev/null || echo "No changelog found" - ls -la release-archives/ - if [ -f CHANGELOG.md ]; then ls -la CHANGELOG.md; fi + + - name: Install cargo tools + uses: taiki-e/install-action@v2 + with: + tool: cargo-release,git-cliff - name: Generate changelog id: changelog - uses: orhun/git-cliff-action@v4 - with: - config: cliff.toml - args: --include-path "crates/quickmark-server/**" --tag-pattern "quickmark-server@*" --latest + run: | + changelog=$(./scripts/latest-changes.sh quickmark-server) + echo "content<> $GITHUB_OUTPUT + echo "$changelog" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT - name: Create release uses: softprops/action-gh-release@v2 diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000..df7f3e0 --- /dev/null +++ b/cliff.toml @@ -0,0 +1 @@ +filter_unconventional = true diff --git a/release.toml b/release.toml new file mode 100644 index 0000000..6aa6ae3 --- /dev/null +++ b/release.toml @@ -0,0 +1,6 @@ +# pre-release-commit-message = "chore: Release {{crate_name}} version {{version}}" +tag-name = "{{crate_name}}@{{version}}" +verify = false +push = false +publish = false +consolidate-commits = true diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh new file mode 100755 index 0000000..d5bb08e --- /dev/null +++ b/scripts/bump-version.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -euo pipefail +# set -x + +# Resolve script directory (so paths.sh works regardless of cwd) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT=$(git rev-parse --show-toplevel) + +source "${SCRIPT_DIR}/lib.sh" +# Todo: automate? +crates=(quickmark-core quickmark-cli quickmark-server) +changed_crates=() + +for crate in "${crates[@]}"; do + echo "Checking version for crate: $crate" + next_ver=$(next_version "$crate") + if [ -n "$next_ver" ]; then + echo "Found changes for $crate, bumping to version: $next_ver" + changed_crates+=("$crate") + if ! cargo release version "$next_ver" -p "$crate" --execute --no-confirm; then + echo "Error: Failed to bump version for crate '$crate'" >&2 + exit 1 + fi + else + echo "No changes detected for crate: $crate" + fi +done + +if [ ${#changed_crates[@]} -eq 0 ]; then + echo "No changes since the last release" + exit 0 +fi + +echo "Creating release commit..." +if ! cargo release commit --execute --no-confirm; then + echo "Error: Failed to create release commit" >&2 + exit 1 +fi + +echo "Creating release tags..." +if ! cargo release tag --execute --no-confirm; then + echo "Error: Failed to create release tags" >&2 + exit 1 +fi + +NEW_TAGS=$(git tag --points-at HEAD) +echo "Tags created locally:" +echo "$NEW_TAGS" + +echo "generating changelogs..." +for crate in "${changed_crates[@]}"; do + CRATE_DIR=$(cargo metadata --format-version 1 --no-deps \ + | jq -r --arg NAME "$crate" '.packages[] | select(.name==$NAME) | .manifest_path' \ + | xargs dirname) + + # Validate that crate directory was found + if [[ -z "$CRATE_DIR" || ! -d "$CRATE_DIR" ]]; then + echo "Error: Could not find directory for crate '$crate'" >&2 + exit 1 + fi + + REL_CRATE_DIR=$(realpath --relative-to="$REPO_ROOT" "$CRATE_DIR") + + # Build include paths array + mapfile -t include_args < <(get_crate_include_args "$crate") + + echo "Generating changelog for $crate..." + if ! git cliff \ + --tag-pattern "$crate@*" \ + "${include_args[@]}" \ + --output "$REL_CRATE_DIR/CHANGELOG.md"; then + echo "Error: Failed to generate changelog for crate '$crate'" >&2 + exit 1 + fi + + git add "$REL_CRATE_DIR/CHANGELOG.md" +done + +echo "Amending commit to include changelogs..." +git commit --amend --no-edit + +for tag in $NEW_TAGS; do + echo "Re-applying tag $tag on amended commit..." + git tag -f "$tag" +done diff --git a/scripts/generate-changelogs.sh b/scripts/generate-changelogs.sh deleted file mode 100755 index 0a08e35..0000000 --- a/scripts/generate-changelogs.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -if [[ $# -eq 0 ]]; then - echo "Usage: $0 [ ...]" - exit 1 -fi - -REPO_ROOT=$(git rev-parse --show-toplevel) -cd "$REPO_ROOT" - -for crate in "$@"; do - echo "Processing crate: $crate" - - # Get crate manifest path from cargo metadata - CRATE_DIR=$(cargo metadata --format-version 1 --no-deps \ - | jq -r --arg NAME "$crate" '.packages[] | select(.name==$NAME) | .manifest_path' \ - | xargs dirname) - - REL_CRATE_DIR=$(realpath --relative-to="$REPO_ROOT" "$CRATE_DIR") - - echo "Generating changelog for $crate in $REL_CRATE_DIR..." - git cliff \ - --tag-pattern "$crate@*" \ - --include-path "$REL_CRATE_DIR/**" \ - --output "$REL_CRATE_DIR/CHANGELOG.md" - - git add "$REL_CRATE_DIR/CHANGELOG.md" -done - -echo "✅ Changelogs staged for crates: $*" diff --git a/scripts/latest-changes.sh b/scripts/latest-changes.sh new file mode 100755 index 0000000..37d3503 --- /dev/null +++ b/scripts/latest-changes.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail +# set -x + +# Resolve script directory (so paths.sh works regardless of cwd) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT=$(git rev-parse --show-toplevel) + +source "${SCRIPT_DIR}/lib.sh" + +if [[ $# -ne 1 ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +CRATE="$1" + +mapfile -t include_args < <(get_crate_include_args "$CRATE") + +changes=`git cliff \ + --tag-pattern "$CRATE@*" \ + "${include_args[@]}" \ + --latest` + +echo "$changes" diff --git a/scripts/lib.sh b/scripts/lib.sh new file mode 100644 index 0000000..b7abd3b --- /dev/null +++ b/scripts/lib.sh @@ -0,0 +1,94 @@ +# Function: get_crate_include_args +# Arguments: +# $1 - crate name (e.g., "quickmark-cli") +# Returns: +# Prints a single-line string with all --include-path arguments for +# the crate and its internal workspace dependencies. +get_crate_include_args() { + local crate="$1" + + # Find git root + local git_root + git_root=$(git rev-parse --show-toplevel) + + # Get workspace metadata + local workspace + workspace=$(cargo metadata --format-version 1 --no-deps) + + # Start with main crate + local paths=() + local crate_path + crate_path=$(jq -r --arg name "$crate" ' + .packages[] | select(.name == $name) | .manifest_path + ' <<<"$workspace") + + # Validate that crate was found + if [[ -z "$crate_path" || "$crate_path" == "null" ]]; then + echo "Error: Could not find crate '$crate' in workspace" >&2 + return 1 + fi + + paths+=("$(realpath --relative-to="$git_root" "$(dirname "$crate_path")")") + + # Add direct workspace dependencies + while read -r dep; do + local dep_path + dep_path=$(jq -r --arg dep "$dep" ' + .packages[] | select(.name == $dep) | .manifest_path + ' <<<"$workspace") + + # Validate that dependency was found + if [[ -z "$dep_path" || "$dep_path" == "null" ]]; then + echo "Warning: Could not find dependency '$dep' in workspace" >&2 + continue + fi + + paths+=("$(realpath --relative-to="$git_root" "$(dirname "$dep_path")")") + done < <(jq -r --arg name "$crate" ' + .packages as $pkgs + | $pkgs[] | select(.name == $name) as $target + | $target.dependencies[] | select(.source == null) | .name + ' <<<"$workspace") + + # deduplicate and convert to --include-path array + local include_args=() + for p in $(printf "%s\n" "${paths[@]}" | sort -u); do + include_args+=(--include-path "$p/**/*") + done + + # print each element on a separate line + printf "%s\n" "${include_args[@]}" +} + +# Function: next_version +# Arguments: +# $1 - crate name (e.g., "quickmark-cli") +# Returns: +# Prints the next version of the crate according to git-cliff, +# considering its dependencies. Returns empty string if no bump. +next_version() { + local crate="$1" + + # Build include paths array + local include_args + mapfile -t include_args < <(get_crate_include_args "$crate") + + # Run git-cliff + local output + output=$(git cliff \ + --tag-pattern "${crate}@*" \ + "${include_args[@]}" \ + --bumped-version 2>&1 || true) + + # Return empty if warning present + if grep -q "WARN" <<<"$output"; then + echo "" + return 0 + fi + + # Extract version after last @ + local version + version=$(grep -E "^.*@" <<<"$output" | tail -n1 | sed 's/.*@//') + + echo "$version" +}