mmdflux 2.2.0

Render Mermaid diagrams as Unicode text, ASCII, SVG, and MMDS JSON.
Documentation
name: Release Plan

on:
  workflow_dispatch:
    inputs:
      mode:
        description: "Plan (preview only) or Release (execute bumps)"
        required: true
        default: "plan"
        type: choice
        options:
          - plan
          - release
      package:
        description: "Package to release (empty = all with changes)"
        required: false
        type: choice
        options:
          - ""
          - mmdflux
          - mmds-core
          - mmds-excalidraw
          - mmds-tldraw

permissions:
  contents: write
  actions: write

concurrency:
  group: release-plan
  cancel-in-progress: false

jobs:
  plan:
    name: Release Plan
    if: inputs.mode == 'plan'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Install cargo-binstall
        uses: cargo-bins/cargo-binstall@main

      - name: Install cocogitto
        run: cargo binstall --no-confirm cocogitto@6.5.0

      - name: Generate release plan
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          set -euo pipefail

          PLAN_FILE="${RUNNER_TEMP}/release-plan.md"

          # Write to both step summary and artifact file
          out() { tee -a "$PLAN_FILE" >> "$GITHUB_STEP_SUMMARY"; }

          # Package definitions: name:tag_prefix
          PACKAGES="mmdflux:mmdflux-v mmds-core:mmds-core-v mmds-excalidraw:mmds-excalidraw-v mmds-tldraw:mmds-tldraw-v"

          HEAD_SHA=$(git rev-parse HEAD)
          HEAD_SHORT=$(git rev-parse --short HEAD)
          REPO="${GITHUB_REPOSITORY}"
          BRANCH="${GITHUB_REF_NAME}"

          # --- Header ---
          {
            echo "# Release Plan"
            echo ""
            echo "## Branch Status"
            echo ""
            echo "**Commit:** [\`${HEAD_SHORT}\`](https://github.com/${REPO}/commit/${HEAD_SHA}) on \`${BRANCH}\`"
            echo ""
            echo "### CI Workflows"
            echo ""
            echo "| Workflow | Status | Commit |"
            echo "|----------|--------|--------|"
          } | out

          # Show only the most recent run per workflow name
          gh run list --branch "${BRANCH}" --limit 20 \
            --json name,conclusion,headSha,status \
            --jq '[group_by(.name)[] | sort_by(.headSha) | last] | sort_by(.name)[] | "| \(.name) | \(if .conclusion != "" and .conclusion != null then .conclusion else .status end) | `\(.headSha[0:7])` |"' \
            | out

          # --- Package Reports ---
          {
            echo ""
            echo "## Packages"
            echo ""
          } | out

          bump_commands=""

          for entry in $PACKAGES; do
            pkg="${entry%%:*}"
            tag_prefix="${entry##*:}"

            {
              echo "### ${pkg}"
              echo ""
            } | out

            # Find latest tag for this package that is an ancestor of HEAD
            latest_tag=$(git describe --tags --match "${tag_prefix}*" --abbrev=0 2>/dev/null || echo "")

            if [ -z "$latest_tag" ]; then
              {
                echo "No previous release tag found."
                echo ""
              } | out
              continue
            fi

            current_version="${latest_tag#"${tag_prefix}"}"

            # Dry-run to check for a pending bump
            dry_output=$(cog bump --package "$pkg" --auto --dry-run --skip-untracked 2>&1 || true)

            if echo "$dry_output" | grep -q "No conventional commits"; then
              {
                echo "**Current version:** ${current_version}"
                echo ""
                echo "No unreleased changes."
                echo ""
              } | out
              continue
            fi

            # Extract next version from dry-run output
            next_version=$(echo "$dry_output" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | tail -1 || echo "unknown")

            {
              echo "**Current version:** ${current_version} **-->** ${next_version}"
              echo ""
            } | out

            bump_commands="${bump_commands}cog bump --package ${pkg} --auto\n"

            # Changelog preview
            changelog=$(cog changelog "${latest_tag}..HEAD" 2>&1 || echo "(changelog generation failed)")

            if [ -n "$changelog" ]; then
              {
                echo "<details>"
                echo "<summary>Changelog preview</summary>"
                echo ""
                echo "$changelog"
                echo ""
                echo "</details>"
                echo ""
              } | out
            fi
          done

          # --- Next Steps ---
          {
            echo "---"
            echo ""
          } | out

          if [ -n "$bump_commands" ]; then
            {
              echo "## Next Steps"
              echo ""
              echo "To execute this release, re-run this workflow with **mode: release**."
              echo ""
              echo "Or run locally:"
              echo ""
              echo '```bash'
              echo -e "$bump_commands"
              echo '```'
            } | out
          else
            echo "No packages have unreleased changes." | out
          fi

      - name: Upload release plan
        uses: actions/upload-artifact@v5
        with:
          name: release-plan
          path: ${{ runner.temp }}/release-plan.md
          retention-days: 90

  release:
    name: Execute Release
    if: inputs.mode == 'release'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
        with:
          fetch-depth: 0

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Install cargo-binstall
        uses: cargo-bins/cargo-binstall@main

      - name: Install tools
        run: cargo binstall --no-confirm cocogitto@6.5.0 cargo-edit

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "22"

      - name: Import GPG key
        uses: crazy-max/ghaction-import-gpg@v6
        with:
          gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
          git_user_signingkey: true
          git_commit_gpgsign: true
          git_config_global: true
          git_tag_gpgsign: true
          git_committer_name: "Kevin Swiber"
          git_committer_email: "kswiber@gmail.com"

      - name: Execute release
        env:
          GH_TOKEN: ${{ github.token }}
          INPUT_PACKAGE: ${{ inputs.package }}
        run: |
          set -euo pipefail

          PLAN_FILE="${RUNNER_TEMP}/release-plan.md"
          out() { tee -a "$PLAN_FILE" >> "$GITHUB_STEP_SUMMARY"; }

          # The CI bump profile (defined in cog.toml) skips lint/test and
          # handles git push + workflow dispatch via per-package post_bump_hooks.
          if [ -n "${INPUT_PACKAGE}" ]; then
            cog bump --package "${INPUT_PACKAGE}" --auto --hook-profile ci
          else
            cog bump --auto --hook-profile ci
          fi

          {
            echo "# Release Complete"
            echo ""
            echo "Release executed via \`cog bump --auto --hook-profile ci\`."
            echo "See workflow logs for details on which packages were bumped and which workflows were dispatched."
          } | out

      - name: Upload release report
        if: always()
        uses: actions/upload-artifact@v5
        with:
          name: release-plan
          path: ${{ runner.temp }}/release-plan.md
          retention-days: 90