diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ab4b4b8..d8e6c30 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -115,6 +115,7 @@ jobs: filters: | code: - 'template-files/**' + - name: Comment on PR if: github.event_name == 'pull_request' && steps.filter.outputs.code == 'true' uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # v2.9.0 @@ -130,6 +131,43 @@ jobs: ${{ steps.templates-error.outputs.summary }} GITHUB_TOKEN: ${{ secrets.SANDBOX_TEMPLATE_TOKEN }} + prepare-release: + runs-on: ubuntu-latest + steps: + - name: Filter Changes + uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2 + id: filter + with: + filters: | + code: + - 'prepare-release/**' + + - name: Checkout Source + if: steps.filter.outputs.code == 'true' + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Prepare Major Release + if: steps.filter.outputs.code == 'true' + uses: ./prepare-release + with: + repository: conda-sandbox/releases + version: ${{ github.event.pull_request.number }}.0.0 + branch: ${{ github.event.pull_request.number }}.0.x + branch-sha: main + token: ${{ secrets.SANDBOX_RELEASES_TOKEN }} + fork-token: ${{ secrets.FORK_TOKEN }} + + - name: Prepare Patch Release + if: steps.filter.outputs.code == 'true' + uses: ./prepare-release + with: + repository: conda-sandbox/releases + version: 0.0.${{ github.event.pull_request.number }} + branch: 0.0.x + branch-sha: main + token: ${{ secrets.SANDBOX_RELEASES_TOKEN }} + fork-token: ${{ secrets.FORK_TOKEN }} + # required check analyze: needs: [pytest, read-file, template-files] diff --git a/prepare-release/README.md b/prepare-release/README.md new file mode 100644 index 0000000..d3e5ee9 --- /dev/null +++ b/prepare-release/README.md @@ -0,0 +1,79 @@ +# Prepare Release + +This action prepares a release on GitHub: +1. Running `towncrier` to update the changelog. + +## Action Inputs + +| Name | Description | Default | +| ---- | ----------- | ------- | +| `version` | Version to release. | **Required** | +| `branch` | Target branch to use for the release. | `${{ github.even.repository.default_branch` | +| `changelog-author` | Git-format author to use for the changelog commits. | @conda-bot | +| `token` | GitHub token to create release branch if missing and the pull request.
Fine-grained PAT: `pull-request: write; contents: write` | `${{ github.token }}` | +| `fork-token` | GitHub token to create and push to the fork/branch.
Fine-grained PAT: `administration: write; contents: write` | `${{ github.token }}` | + +## Sample Workflows + +### Basic Workflow + +```yaml +name: Prepare Release + +on: + workflow_dispatch: + inputs: + version: + description: The version to release. + required: true + +permissions: + contents: write + pull-requests: write + +jobs: + prepare: + runs-on: ubuntu-latest + steps: + - name: Prepare Release + uses: conda/actions/prepare-release + with: + version: ${{ inputs.version }} +``` + +### Dynamic Branch Workflow + +```yaml +name: Prepare Release + +on: + workflow_dispatch: + inputs: + version: + description: The version to release. + required: true + +permissions: + contents: write + pull-requests: write + +jobs: + prepare: + runs-on: ubuntu-latest + steps: + - name: Get Branch + shell: python + run: | + from os import environ + from pathlib import Path + + # derive the branch from the version by dropping the `PATCH` and using `.x` + branch = "${{ inputs.version }}".rsplit(".", 1)[0] + Path(environ["GITHUB_ENV"]).write_text(f"BRANCH={branch}.x") + + - name: Prepare Release + uses: conda/actions/prepare-release + with: + version: ${{ inputs.version }} + branch: ${{ env.BRANCH }} +``` diff --git a/prepare-release/action.yml b/prepare-release/action.yml new file mode 100644 index 0000000..1bcabf2 --- /dev/null +++ b/prepare-release/action.yml @@ -0,0 +1,129 @@ +name: Prepare Release +description: Prepares a release by running towncrier and creating a PR with the changes. +inputs: + version: + description: The version to release. + required: true + branch: + description: The target branch to use for the release. + default: ${{ github.event.repository.default_branch }} + branch-sha: + description: The SHA from which to create the target branch if missing. + default: ${{ github.event.repository.default_branch }} + changelog-author: + description: Git-format author to use for the changelog commits. + default: Conda Bot <18747875+conda-bot@users.noreply.github.com> + token: + description: | + GitHub token to create release branch if missing and the pull request. + Fine-grained PAT: `pull-request: write; contents: write` + default: ${{ github.token }} + fork-token: + description: | + GitHub token to create and push to the fork. + Fine-grained PAT: `administration: write; contents: write` + # default: ${{ inputs.token }} + comment-blurb: + description: Comment to add to the PR. + default: ✂️ snip snip ✂️ the making of a new release. + repository: + description: The repository to use for the release. + default: ${{ github.repository }} + +runs: + using: composite + steps: + - name: Checkout Source + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + path: .github_cache/actions/prepare-release/${{ inputs.repository }} + repository: ${{ inputs.repository }} + token: ${{ inputs.token }} + + - name: Create Branch + shell: bash + run: | + if ! git -C ${SOURCE} fetch origin "${{ inputs.branch }}"; then + # if branch doesn't exist, create it from the default branch + gh api \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + "/repos/${{ inputs.repository }}/git/refs" \ + -f ref="refs/heads/${{ inputs.branch }}" \ + -f sha="$(git -C ${SOURCE} rev-parse ${{ inputs.branch-sha }})" + fi + git -C ${SOURCE} fetch origin + git -C ${SOURCE} checkout "${{ inputs.branch }}" + git -C ${SOURCE} pull origin "${{ inputs.branch }}" + env: + GH_TOKEN: ${{ inputs.token }} + SOURCE: .github_cache/actions/prepare-release/${{ inputs.repository }} + + # `hashFiles` only works on files within the working directory, since `requirements.txt` + # is not in the working directory we need to manually compute the SHA256 hash + - name: Compute Hash + id: hash + shell: bash + run: echo hash=$(sha256sum ${{ github.action_path }}/requirements.txt | awk '{print $1}') >> $GITHUB_OUTPUT + + - name: Pip Cache + uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2 + with: + path: ~/.cache/pip + key: ${{ github.workflow }}-prepare-release-${{ steps.hash.outputs.hash }} + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '>=3.9' + + - name: Pip Install + shell: bash + run: pip install --quiet -r ${{ github.action_path }}/requirements.txt + + - name: Pip List + shell: bash + run: pip list + + - name: Run Towncrier + shell: bash + run: >- + towncrier build + --version=${{ inputs.version }} + --dir=.github_cache/actions/prepare-release/${{ inputs.repository }} + + # - name: Detect Contributors + + # - name: Detect First-Time Contributors + + - name: Create Fork + id: create-fork + uses: conda/actions/create-fork@7873f9d7c90877290866eb893b8f6eff2e88429a # v25.1.2 + with: + token: ${{ inputs.fork-token || inputs.token }} + + - name: Create PR + uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5 + with: + # push to the fork + branch-token: ${{ inputs.fork-token || inputs.token }} + push-to-fork: ${{ steps.create-fork.outputs.fork }} + branch: changelog-${{ inputs.version }} + commit-message: Changelog ${{ inputs.version }} + author: ${{ inputs.changelog-author }} + committer: ${{ inputs.changelog-author }} + # create PR + token: ${{ inputs.token }} + base: ${{ inputs.branch }} + delete-branch: true + title: Changelog ${{ inputs.version }} + # GitHub flavored markdown reinvents how paragraphs work, adjoined lines of text are not + # concatenated so instead we rely on YAML multi-line + extra newlines + body: >- + ${{ inputs.comment-blurb }} + + + PR generated by ${{ github.html_url }}/actions/runs/${{ github.run_id }}, + triggered by ${{ github.event_name == 'schedule' && 'cron' || format('@{0}', github.triggering_actor) }} + via ${{ github.event_name }}. diff --git a/prepare-release/requirements.txt b/prepare-release/requirements.txt new file mode 100644 index 0000000..0d2d2d7 --- /dev/null +++ b/prepare-release/requirements.txt @@ -0,0 +1 @@ +towncrier