diff --git a/.github/workflows/create-release-notes.yml b/.github/workflows/create-release-notes.yml new file mode 100644 index 000000000..97576387c --- /dev/null +++ b/.github/workflows/create-release-notes.yml @@ -0,0 +1,41 @@ +name: 'Release notification' +on: + push: + tags: + - release-* +run-name: Generating release notes for ${{ github.base_ref }} +permissions: + contents: write +jobs: + create-publish-release-notes: + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + fetch-tags: true + + - name: Generate and update the release notes + run: | + all_tags=$(git tag -l --sort=-creatordate) + latest_tag=$(echo "$all_tags" | sed -n '1p') + previous_tag=$(echo "$all_tags" | sed -n '2p') + + release_notes=$(git log "$previous_tag..$latest_tag" --oneline) + release_notes=$(echo "$release_notes" | python3\ + ./scripts/create-release-notes.py\ + "$previous_tag"\ + "$latest_tag"\ + ) + + if gh release view "$latest_tag" &>/dev/null; then + echo "Release exists. Updating..." + gh release edit "$latest_tag" --notes "$release_notes" + else + echo "Creating new release..." + gh release create "$latest_tag" --notes "$release_notes" + fi + diff --git a/.github/workflows/pr-title-check.yml b/.github/workflows/pr-title-check.yml index 0cfb3baba..6841eb87c 100644 --- a/.github/workflows/pr-title-check.yml +++ b/.github/workflows/pr-title-check.yml @@ -18,12 +18,8 @@ jobs: // Titel moet beginnen met mijn-xxxx-chore/feature/bug! todo mijn- patroon moet weggehaald worden wanneer de workflows van mijn-7620 gemerged zijn const jiraPattern = /^MIJN-\d+-(CHORE|FEATURE|BUG)/i const dependabotPattern = /^Build\(deps(-dev)?\): bump/ - const disallowedQuotesPattern = /"/ - if (disallowedQuotesPattern.test(prTitle)) { - console.log('The PR title contains double quotes, which are not allowed!') - core.setFailed('PR title not valid. Please remove double quotes from the title.') - } else if (!(jiraPattern.test(prTitle) || dependabotPattern.test(prTitle))) { + if (!(jiraPattern.test(prTitle) || dependabotPattern.test(prTitle))) { console.log('The PR title does not match JIRA ticket format!') core.setFailed('PR title not valid. Please make sure this PR uses the JIRA ticket or dependabot format.') } else { diff --git a/.github/workflows/release-notification.yml b/.github/workflows/release-notification.yml deleted file mode 100644 index da7978c9b..000000000 --- a/.github/workflows/release-notification.yml +++ /dev/null @@ -1,97 +0,0 @@ -name: 'Release notification' -on: create - -permissions: - contents: write -jobs: - create-notification: - runs-on: ubuntu-latest - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - fetch-tags: true - - - name: Fetch all tags - run: git fetch --tags - - - name: Get latest and previous tags - id: tags - run: | - all_tags=$(git tag -l --sort=-creatordate) - latest_tag=$(echo "$all_tags" | sed -n '1p') - previous_tag=$(echo "$all_tags" | sed -n '2p') - echo "latest_tag=$latest_tag" >> $GITHUB_OUTPUT - echo "previous_tag=$previous_tag" >> $GITHUB_OUTPUT - - # Debug output - echo "Latest tag: $latest_tag" - echo "Previous tag: $previous_tag" - - - name: Generate Release Notes - run: | - echo "Changes between ${{ steps.tags.outputs.previous_tag }} and ${{ steps.tags.outputs.latest_tag }}:" > release-notes.txt - git log ${{ steps.tags.outputs.previous_tag }}..${{ steps.tags.outputs.latest_tag }} --oneline >> release-notes.txt - RELEASE_NOTES=$(cat release-notes.txt) - echo "RELEASE_NOTES<> $GITHUB_ENV - echo "$RELEASE_NOTES" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - - name: Create or Update Release - run: | - if gh release view ${{ steps.tags.outputs.latest_tag }} &>/dev/null; then - echo "Release exists. Updating..." - gh release edit ${{ steps.tags.outputs.latest_tag }} --notes "$RELEASE_NOTES" - else - echo "Creating new release..." - gh release create ${{ steps.tags.outputs.latest_tag }} --notes "$RELEASE_NOTES" - fi - - - name: Divide Notes into Chores, Features, and Bugs - id: categorize_notes - run: | - echo "${{ env.RELEASE_NOTES }}" > release_notes.txt - grep -iE 'CHORE|MIJN-[0-9]+-CHORE' release_notes.txt | sort -u > chores.txt || true - grep -iE 'FEATURE:|MIJN-[0-9]+-FEATURE' release_notes.txt > features.txt || true - grep -iE 'BUG:|MIJN-[0-9]+-BUG' release_notes.txt > bugs.txt || true - grep -iE 'MIJN-[0-9]+($|[^-])' release_notes.txt > mijn.txt || true - - - name: Output Categorized Notes - run: | - append_to_github_output() { - if [ -s "$1.txt" ]; then - echo "$1<> "$GITHUB_OUTPUT" - cat "$1.txt" >> "$GITHUB_OUTPUT" - echo "EOF" >> "$GITHUB_OUTPUT" - fi - } - append_to_github_output 'chores' - append_to_github_output 'features' - append_to_github_output 'bugs' - append_to_github_output 'mijn' - - - name: Create Customized Release Notes - id: custom_release_notes - run: | - { - echo "## Release Notes" - echo "### Chores" - echo "${{ steps.categorize_notes.outputs.chores }}" - echo "### Features" - echo "${{ steps.categorize_notes.outputs.features }}" - echo "### Bugs" - echo "${{ steps.categorize_notes.outputs.bugs }}" - echo "### Mijn" - echo "${{ steps.categorize_notes.outputs.mijn }}" - } > custom_release_notes.txt - CUSTOM_NOTES=$(cat custom_release_notes.txt) - echo "CUSTOM_NOTES<> $GITHUB_ENV - echo "$CUSTOM_NOTES" >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - - name: Update Release with Custom Notes - run: | - gh release edit ${{ steps.tags.outputs.latest_tag }} --notes "${{ env.CUSTOM_NOTES }}" diff --git a/scripts/create-release-notes.py b/scripts/create-release-notes.py new file mode 100644 index 000000000..49ae6872a --- /dev/null +++ b/scripts/create-release-notes.py @@ -0,0 +1,92 @@ +import sys +import re +from functools import reduce +import argparse + +parser = argparse.ArgumentParser( + description="Create release notes for a github workflow." +) + +parser.add_argument("previous_release_tag") +parser.add_argument("latest_release_tag") + +args = parser.parse_args() + +release_notes = sys.stdin.read() +if not release_notes: + raise Exception("Release notes read from the stdin are empty") + +categories = { + "features": { + "pattern": re.compile(r'MIJN-[0-9]+-FEATURE', flags=re.IGNORECASE), + "commits": [] + }, + "chores": { + "pattern": re.compile(r'MIJN-[0-9]+-CHORE', flags=re.IGNORECASE), + "commits": [] + }, + "bugs": { + "pattern": re.compile(r'MIJN-[0-9]+-BUG', flags=re.IGNORECASE), + "commits": [] + }, +} + +other = { + # Must start with a commit hash + "pattern": re.compile(r'^[a-z\d]+\b'), + "commits": [] +} + +# Sort release notes +# ================== +for line in release_notes.split('\n'): + def identify(acc, category): + if categories[category]["pattern"].search(line): + return category + return acc + + category = reduce(identify, categories, None) + + try: + categories[category]["commits"].append(line) + except KeyError: + if other["pattern"].match(line): + other["commits"].append(line) + +# Format +# ================================ +categories["other"] = other + +RELEASE_SUFFIX = 'release-' +previous_version = args.previous_release_tag.removeprefix(RELEASE_SUFFIX) +latest_version = args.latest_release_tag.removeprefix(RELEASE_SUFFIX) + +# Lines entered here are at the top of the release notes. +release_notes = [ + f"Here are the updates between the {previous_version} and {latest_version} releases.\n", +] + +release_notes_size = len(release_notes) + +def format_category(acc, category): + commits = categories[category]["commits"] + if not commits: + return acc + + # Add title in markdown. + acc.append(f"## {category.title()}\n") + + for commit in commits: + acc.append(commit) + + # Make sure a newline is added after the block of commits. + acc.append("") + + return acc + +release_notes = reduce(format_category, categories, release_notes) +if release_notes_size == len(release_notes): + raise Exception("Not a single line added to the release notes.") + +print('\n'.join(release_notes)) +