Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions create-release/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Create Release

This action creates a release on GitHub:
1. Archiving the current repository state as a tarball (`.tar.gz`). While GitHub also does this for releases those archives are unfortunately not stable and cannot be relied on.
2. Computing the checksum for the archived tarball.
3. Extracting the release notes from 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` |
| `archive-name` | Name of the git archive to create. | `${{ github.event.repository.name }}-${{ inputs.version }}` |
| `output-directory` | Directory for the release artifacts. | `release` |
| `release-notes` | Name of the release notes to create. | `RELEASE_NOTES.md` |
| `token` | GitHub token to create the release.<br>Fine-grained PAT: `contents: write` | `${{ github.token }}` |

## Sample Workflows

### Basic Workflow

```yaml
name: Create Release

on:
workflow_dispatch:
inputs:
version:
description: The version to release.
required: true

permissions:
contents: write

jobs:
create:
runs-on: ubuntu-latest
steps:
- name: Create Release
uses: conda/actions/create-release
with:
version: ${{ inputs.version }}
branch: main
```

### Dynamic Branch Workflow

```yaml
name: Create Release

on:
workflow_dispatch:
inputs:
version:
description: The version to release.
required: true

permissions:
contents: write

jobs:
create:
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: Create Release
uses: conda/actions/create-release
with:
version: ${{ inputs.version }}
branch: ${{ env.BRANCH }}
```
81 changes: 81 additions & 0 deletions create-release/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Create Release
description: Creates a release by archiving the source and creating a release on GitHub.
inputs:
version:
description: Version to release.
required: true
branch:
description: Target branch for the release.
default: ${{ github.event.repository.default_branch }}
archive-name:
description: Name of the git archive.
default: ${{ github.event.repository.name }}-${{ inputs.version }}
output-directory:
description: Directory for the release artifacts.
default: release
release-notes:
description: Path to the release notes.
default: RELEASE_NOTES.md
token:
description: 'GitHub token to create the release. Fine-grained PAT: `contents: write`'
default: ${{ github.token }}
runs:
using: composite
steps:
- name: Checkout Source
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Create Release Directory
shell: bash
run: mkdir -p ${{ inputs.output-directory }}

- name: Archive Source
shell: bash
run: >
git archive
--prefix="${{ inputs.archive-name }}/"
--output="${{ inputs.output-directory }}/${{ inputs.archive-name }}.tar.gz"
HEAD

- name: Compute Checksum
shell: bash
run: >
sha256sum "${{ inputs.output-directory }}/${{ inputs.archive-name }}.tar.gz"
| awk '{print $1}'
> "${{ inputs.output-directory }}/${{ inputs.archive-name }}.tar.gz.sha256sum"

- name: Load Pip Cache
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: ~/.cache/pip
# invalidate the cache anytime a workflow changes
key: ${{ github.workflow }}-${{ hashFiles('.github/workflows/*') }}

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '>=3.7'

- name: Pip List
shell: bash
run: pip list

- name: Get Release Notes
shell: bash
run: >
python ${{ github.action_path }}/get_release_notes.py
--input="${{ inputs.changelog }}"
--version="${{ inputs.version }}"
--output="${{ inputs.output-directory }}/${{ inputs.release-notes }}"

- name: Create Release
shell: bash
env:
GH_TOKEN: ${{ input.token }}
run: >
gh release create
--notes-file "${{ inputs.output-directory }}/${{ inputs.release-notes }}"
--target "${{ inputs.branch }}"
--title "${{ inputs.version }}"
"${{ inputs.version }}"
${{ inputs.output-directory }}/${{ env.ARCHIVE_NAME }}.*
54 changes: 54 additions & 0 deletions create-release/get_release_notes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from __future__ import annotations

import re
from pathlib import Path
from argparse import ArgumentParser, ArgumentTypeError


def get_input(value: str) -> Path:
path = Path(value)
if not path.exists():
raise ArgumentTypeError(f"{value!r} does not exist")
return path


def get_output(value: str) -> Path:
path = Path(value)
path.parent.mkdir(parents=True, exist_ok=True)
return path


def get_version(value: str) -> str:
if not value:
raise ArgumentTypeError("must be a non-empty string")
return value


parser = ArgumentParser()
parser.add_argument("--input", required=True, type=get_input)
parser.add_argument("--output", required=True, type=get_output)
parser.add_argument("--version", required=True, type=get_version)
params = parser.parse_args()

text = params.input.read_text()
pattern = re.compile(
rf"""
\n+
(
\#\#\s+ # markdown header
{re.escape(params.version)}\s+ # version number
\(\d\d\d\d-\d\d-\d\d\) # release date
)\n+
(
.+? # release notes
)\n+
(
\#\#\s+ # markdown header
\d+\.\d+\.\d+\s+ # version number
\(\d\d\d\d-\d\d-\d\d\) # release date
)\n+
""",
flags=re.VERBOSE | re.DOTALL,
)
notes = match.group(2) if (match := pattern.search(text)) else ""
params.output.write_text(notes)
Loading