diff --git a/.github/workflows/changelog-builder.json b/.github/workflows/changelog-builder.json deleted file mode 100644 index 8aa820d..0000000 --- a/.github/workflows/changelog-builder.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "categories": [], - "empty_template": "- No changes", - "pr_template": "- ${{TITLE}} #${{NUMBER}}", - "sort": {"on_property": "title", "order": "ASC"}, - "template": "${{UNCATEGORIZED}}" -} diff --git a/.github/workflows/next_version.py b/.github/workflows/next_version.py deleted file mode 100644 index 5add857..0000000 --- a/.github/workflows/next_version.py +++ /dev/null @@ -1,7 +0,0 @@ -import semver -import sys - -release = len(sys.argv) > 2 and sys.argv[2] or "patch" -version = semver.VersionInfo.parse(sys.argv[1].lstrip("v")) - -print(getattr(version, f"bump_{release}")()) diff --git a/.github/workflows/process_changelog.py b/.github/workflows/process_changelog.py deleted file mode 100644 index 366fd7b..0000000 --- a/.github/workflows/process_changelog.py +++ /dev/null @@ -1,31 +0,0 @@ -import os -import re -import sys - -GITHUB_REPOSITORY = os.environ.get("GITHUB_REPOSITORY", "") - -repo_url = f"https://github.com/{GITHUB_REPOSITORY}" - -lines = [] - -for line in sys.stdin.readlines(): - match = re.match(rf"^- (.*) #([0-9]+)$", line) - - if match is None: - lines.append((line.lstrip("- "), [])) - continue - - title, pr = match.groups() - - if lines and title == lines[-1][0]: - lines[-1][1].append(pr) - continue - - lines.append((title, [pr])) - -print( - "\n".join( - f"- {title} {' '.join(f'[#{pr}]({repo_url}/pull/{pr})' for pr in prs)}" - for title, prs in lines - ) -) diff --git a/.github/workflows/release-builder.yml b/.github/workflows/release-builder.yml index d558ecc..839e492 100644 --- a/.github/workflows/release-builder.yml +++ b/.github/workflows/release-builder.yml @@ -57,6 +57,7 @@ jobs: PROJECT_NAME: worf PROJECT_TYPE: package SLACK_BOT_TOKEN: ${{ secrets.SLACK_SIX_BOT_TOKEN }} + SLACK_CHANNEL: C0306G288J0 TARGET_NAME: pypi.org TARGET_URL: https://pypi.org/project/worf/ @@ -65,30 +66,10 @@ jobs: with: fetch-depth: 0 - - uses: actions/setup-python@v4 - - - run: pip install requests - - - id: build_images_message - run: echo "message=$(python .github/workflows/release_message.py)" >> $GITHUB_OUTPUT - env: - MESSAGE_TEMPLATE: images - - - if: steps.build_images_message.outputs.message - id: send_images_message - uses: slackapi/slack-github-action@v1.23.0 + - id: message + uses: gundotio/release-builder/slack-message@main with: - channel-id: C0306G288J0 - payload: ${{ steps.build_images_message.outputs.message }} - - - id: build_release_message - run: echo "message=$(python .github/workflows/release_message.py)" >> $GITHUB_OUTPUT - - - id: send_release_message - uses: slackapi/slack-github-action@v1.23.0 - with: - channel-id: C0306G288J0 - payload: ${{ steps.build_release_message.outputs.message }} + channel-id: ${{ env.SLACK_CHANNEL }} - run: script/build @@ -107,17 +88,11 @@ jobs: TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - if: always() - id: rebuild_release_message - run: echo "message=$(python .github/workflows/release_message.py)" >> $GITHUB_OUTPUT - env: - DEPLOY_STATUS: ${{ steps.release.conclusion }} - - - if: always() - uses: slackapi/slack-github-action@v1.23.0 + uses: gundotio/release-builder/slack-message@main with: - channel-id: C0306G288J0 - payload: ${{ steps.rebuild_release_message.outputs.message }} - update-ts: ${{ steps.send_release_message.outputs.ts }} + channel-id: ${{ steps.message.outputs.channel-id }} + message-id: ${{ steps.message.outputs.message-id }} + status: ${{ steps.release.conclusion }} build_changelog: if: always() && needs.publish_release.result != 'failure' @@ -127,35 +102,18 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - - run: pip install semver - - - id: builder - uses: mikepenz/release-changelog-builder-action@v3 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - id: changelog + uses: gundotio/release-builder/build-changelog@main with: - configuration: .github/workflows/changelog-builder.json - outputFile: NEXT.md - toTag: HEAD - - - id: process - run: | - echo 'changelog<> $GITHUB_OUTPUT - cat NEXT.md | python .github/workflows/process_changelog.py >> $GITHUB_OUTPUT - echo 'EOF' >> $GITHUB_OUTPUT - - - id: next - run: | - echo "version=$(python .github/workflows/next_version.py ${{ steps.builder.outputs.fromTag }} ${{ inputs.release || 'patch' }})" >> $GITHUB_OUTPUT + release: ${{ inputs.release || 'patch' }} outputs: - has_prs: ${{ steps.builder.outputs.categorized_prs || steps.builder.outputs.uncategorized_prs }} - previous_version: ${{ steps.builder.outputs.fromTag }} - next_version: ${{ steps.next.outputs.version }} - notes: ${{ steps.process.outputs.changelog }} - release: ${{ inputs.release || 'patch' }} + has_prs: ${{ steps.changelog.outputs.has_prs }} + previous_version: ${{ steps.changelog.outputs.previous_version }} + next_version: ${{ steps.changelog.outputs.next_version }} + notes: ${{ steps.changelog.outputs.notes }} + release: ${{ steps.changelog.outputs.release }} create_pr: if: always() && inputs.force_pr || needs.build_changelog.outputs.has_prs @@ -168,33 +126,12 @@ jobs: with: ssh-key: ${{ secrets.SSH_PRIVATE_KEY }} - - id: commit - uses: pr-mpt/actions-commit-hash@v1 - - run: | - echo -e "# Changelog\n" > NEXT.md - echo -e "All notable changes to this project will be documented in this file. See [standard-version](${{ github.server_url }}/conventional-changelog/standard-version) for commit guidelines.\n" >> NEXT.md - echo -e "${{ needs.build_changelog.outputs.release == 'patch' && '###' || '##' }} [${{ needs.build_changelog.outputs.next_version }}](${{ github.server_url }}/${{ github.repository }}/compare/${{ needs.build_changelog.outputs.previous_version }}...v${{ needs.build_changelog.outputs.next_version }}) ($(date +'%Y-%m-%d'))\n" >> NEXT.md - echo -e "${{ needs.build_changelog.outputs.notes }}\n" >> NEXT.md - cat CHANGELOG.md | sed -e '1,4d' >> NEXT.md && mv NEXT.md CHANGELOG.md echo '__version__ = "v${{ needs.build_changelog.outputs.next_version }}"' > worf/__init__.py - - uses: peter-evans/create-pull-request@v4 + - uses: gundotio/release-builder/create-pull-request@main with: - title: Release ${{ needs.build_changelog.outputs.next_version }} - body: | - ## Release [${{ needs.build_changelog.outputs.next_version }}](${{ github.server_url }}/${{ github.repository }}/compare/${{ needs.build_changelog.outputs.previous_version }}...${{ steps.commit.outputs.short }}) - - ${{ needs.build_changelog.outputs.notes }} - -
- View source -
${{ needs.build_changelog.outputs.notes }}
-
- author: GitHub - base: master - branch: release - committer: GitHub - commit-message: | - chore(release): ${{ needs.build_changelog.outputs.next_version }} - delete-branch: true + next_version: ${{ needs.build_changelog.outputs.next_version }} + notes: ${{ needs.build_changelog.outputs.notes }} + previous_version: ${{ needs.build_changelog.outputs.previous_version }} + release: ${{ needs.build_changelog.outputs.release }} diff --git a/.github/workflows/release_message.py b/.github/workflows/release_message.py deleted file mode 100644 index 5b40eb1..0000000 --- a/.github/workflows/release_message.py +++ /dev/null @@ -1,215 +0,0 @@ -import json -import os -import re -import requests -import sys - -DEPLOY_STATUS = os.environ.get("DEPLOY_STATUS", "") -GITHUB_ACTOR = os.environ.get("GITHUB_ACTOR", "") -GITHUB_REPOSITORY = os.environ.get("GITHUB_REPOSITORY", "") -GITHUB_RUN_ID = os.environ.get("GITHUB_RUN_ID", "") -GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN", "") -MESSAGE_TEMPLATE = os.environ.get("MESSAGE_TEMPLATE", "") -PROJECT_NAME = os.environ.get("PROJECT_NAME", "") -PROJECT_TYPE = os.environ.get("PROJECT_TYPE", "") -TARGET_NAME = os.environ.get("TARGET_NAME", "") -TARGET_URL = os.environ.get("TARGET_URL", "") - -github = requests.Session() -github.headers = { - "Accept": "application/json", - "Authorization": f"Bearer {GITHUB_TOKEN}", -} - -github_url = f"https://github.com/{GITHUB_REPOSITORY}" - -action_status = dict( - failure=":cross:", - pending=":buffering:", - skipped="๐Ÿš€", - success="๐Ÿš€", -).get(DEPLOY_STATUS or "pending") -action_url = f"{github_url}/actions/runs/{GITHUB_RUN_ID}" - -image_html_re = re.compile(r'') -image_md_re = re.compile(r"!\[(.*?)\]\((.*?)\)") - -release_re = re.compile( - r""" - ^\#+\s - \[(?P.*?)\]\((?P.*?)\)\s - \((?P.*?)\)$ - """, - re.VERBOSE, -) - - -def build_message(): - user = github.get(f"https://api.github.com/users/{GITHUB_ACTOR}").json() - - actor = user["name"] - actor_link = f"[{actor}]({user['html_url']})" - project = f"{PROJECT_NAME} {release['version']}".strip() - project_link = f"[{project}]({release['compare_url']})" - run_link = f"[{action_status}]({action_url})" - target = TARGET_NAME - target_link = f"[{target}]({TARGET_URL})" - verb = "released" if PROJECT_TYPE == "package" else "deployed" - - if MESSAGE_TEMPLATE == "images": - images = get_images(release) - - if not images: - return - - return { - "text": f"๐Ÿš€ {actor} {verb} {project} to {target}", - "blocks": [ - { - "type": "image", - "title": { - "type": "plain_text", - "text": text, - }, - "image_url": url, - "alt_text": text, - } - for text, url in images - ], - } - - return { - "username": user["name"], - "icon_url": user["avatar_url"], - "text": f"๐Ÿš€ {actor} {verb} {project} to {target}", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": truncate_message( - transform_markdown( - f"{run_link} {actor_link} {verb} {project_link} to {target_link}\n{release['notes']}" - ), - ), - }, - } - ], - "unfurl_links": False, - "unfurl_media": False, - } - - -def find_images(text): - return image_html_re.findall(text) + image_md_re.findall(text) - - -def get_images(release): - images = [] - - response = github.get( - f"https://api.github.com/repos/{GITHUB_REPOSITORY}/pulls", - params=dict( - per_page=100, - state="closed", - ), - ) - - pulls = {pull["number"]: pull for pull in response.json()} - - for pull in release["pulls"]: - pull = pulls.get(pull) - - if not pull: - continue - - images.extend( - [ - (f"{pull['title']} #{pull['number']}: {text}".strip(": "), url) - for text, url in find_images(pull["body"] or "") - if "badge" not in url - ] - ) - - return images - - -def get_release(): - heading = None - notes = [] - - with open("CHANGELOG.md", "r") as f: - lines = f.readlines() - heading = lines[4] - for line in lines[6:]: - if line.strip(): - notes.append(line.strip()) - continue - break - - release = release_re.match(heading).groupdict() - - pulls = list( - map(int, re.findall(rf"{re.escape(github_url)}/pull/([0-9]+)", "".join(notes))) - ) - - notes = "\n".join(notes) - - return dict(**release, notes=notes, pulls=pulls) - - -def transform_markdown(text): - """Transform markdown into Slack mrkdwn""" - text = text.replace("\r\n", "\n") - # preserve **markdown bold** - text = text.replace("**", "\\*\\*") - # convert images and links into slack links - text = re.sub(r"!?\[([^\[\]]*?)\]\(([^\(\)]*?)\)", r"<\2|\1>", text) - # convert lists into bullets - text = re.sub( - r"(?<=^) *[ยทโ€ขโ—\-\*๏‚ท๏‚งโžค]+\s*(.*)", - r"โ€ˆ*โ€ข*โ€ˆ \1", - text, - flags=re.MULTILINE, - ) - # convert headings into bold - text = re.sub( - r"(?<=^)\n*[#=_]+ *(.*?) *[#=_]* *\n*$", - r"\n*\1*\n", - text, - flags=re.MULTILINE, - ) - # convert indentation into code blocks - text = re.sub(r"((?:\n {4}.*)+)", r"\n```\1\n```", text) - text = re.sub(r"^ {4}", r"", text, flags=re.MULTILINE) - # restore **markdown bold** as *slack bold* - text = text.replace("\\*\\*", "*") - # single space after periods otherwise sentences can wrap weird - text = re.sub(r"\. {2,}", ". ", text) - return text - - -def truncate_message(message, max_length=3000): - message_lines = message.split("\n") - extra_lines = [] - - heading_anchor = f"{release['version']}-{release['date']}".replace(".", "") - changelog_url = f"{github_url}/blob/master/CHANGELOG.md#{heading_anchor}" - more_line = "" - - while len("\n".join(message_lines)) > max_length - len(more_line): - extra_lines.append(message_lines.pop()) - more_line = transform_markdown(f"+ [{len(extra_lines)} more]({changelog_url})") - - if more_line: - message_lines.append(more_line) - - return "\n".join(message_lines) - - -if __name__ == "__main__": - release = get_release() - message = build_message() - - if message: - print(json.dumps(message))