Skip to content
Merged
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
143 changes: 56 additions & 87 deletions .github/workflows/prepare-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,46 +7,12 @@ on:
permissions: {}

jobs:
load-packages:
if: "!contains(github.event.head_commit.message, 'chore: prepare release')"
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.parse.outputs.matrix }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Parse packages from knope.toml
id: parse
shell: python3 {0}
run: |
import tomllib, json, os

with open("knope.toml", "rb") as f:
config = tomllib.load(f)

packages = config.get("packages", {})
matrix = [
{"package": name, "changelog": pkg["changelog"]}
for name, pkg in packages.items()
]

with open(os.environ["GITHUB_OUTPUT"], "a") as out:
out.write(f"matrix={json.dumps(matrix)}\n")

prepare-release:
needs: load-packages
if: needs.load-packages.outputs.matrix != '[]'
if: "!contains(github.event.head_commit.message, 'chore: prepare release')"
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
strategy:
# Run each package independently so one with no changes doesn't block others
fail-fast: false
matrix:
include: ${{ fromJSON(needs.load-packages.outputs.matrix) }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
Expand All @@ -59,63 +25,66 @@ jobs:
with:
version: 0.22.1

- name: Switch to release branch
shell: bash
run: git switch -c release/${{ matrix.package }}

- name: Prepare Release
id: knope
shell: bash
run: |
if knope prepare-release --verbose; then
echo "released=true" >> "$GITHUB_OUTPUT"
else
echo "released=false" >> "$GITHUB_OUTPUT"
fi
run: knope prepare-release --verbose
continue-on-error: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Commit and push release branch
if: steps.knope.outputs.released == 'true'
shell: bash
run: |
git commit -m "chore: prepare release ${{ matrix.package }}"
git push --force --set-upstream origin release/${{ matrix.package }}

- name: Read version and changelog
id: meta
if: steps.knope.outputs.released == 'true'
- name: Enrich changelog with PR attribution
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const fs = require("fs");
const changelog = "CHANGELOG.md";

if (!fs.existsSync(changelog)) return;

let content = fs.readFileSync(changelog, "utf8");
const marker = /<!-- commit:([0-9a-f]+) -->/g;
const hashes = [...new Set([...content.matchAll(marker)].map(m => m[1]))];

if (hashes.length === 0) return;

const { owner, repo } = context.repo;

for (const hash of hashes) {
let replacement = "";
try {
// Find PRs associated with this commit. Knope uses the commit that
// first introduced the changeset file, so this reliably points to
// the original PR regardless of later edits on the branch.
const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({
owner, repo, commit_sha: hash,
});
const pr = prs.data[0];
if (pr) {
replacement = `([#${pr.number}](${pr.html_url}) by @${pr.user.login})`;
} else {
// Commit exists but has no associated PR (e.g. direct push to dev)
replacement = `(\`${hash}\`)`;
}
} catch {
replacement = `(\`${hash}\`)`;
}
content = content.replaceAll(`<!-- commit:${hash} -->`, replacement);
}

fs.writeFileSync(changelog, content);

- name: Amend release commit with enriched changelog
shell: bash
run: |
# Read the top version header from the changelog knope just wrote.
VERSION=$(awk '/^## [0-9]/{print $2; exit}' ${{ matrix.changelog }})
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

CHANGELOG=$(awk "/^## $VERSION/{found=1; next} found && /^## /{exit} found{print}" ${{ matrix.changelog }})
{
echo "changelog<<EOF"
echo "$CHANGELOG"
echo "EOF"
} >> "$GITHUB_OUTPUT"
# Only amend if the changelog was actually staged by knope
if git diff --cached --name-only | grep -q "CHANGELOG.md"; then
git add CHANGELOG.md
git commit --amend --no-edit
elif git diff --name-only HEAD | grep -q "CHANGELOG.md"; then
git add CHANGELOG.md
git commit --amend --no-edit
fi

- name: Create or update release PR
if: steps.knope.outputs.released == 'true'
- name: Push release branch
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PACKAGE: ${{ matrix.package }}
VERSION: ${{ steps.meta.outputs.version }}
CHANGELOG: ${{ steps.meta.outputs.changelog }}
run: |
BRANCH="release/${PACKAGE}"
TITLE="chore: prepare release ${PACKAGE} ${VERSION}"
BODY="> [!IMPORTANT]
> Merging this PR will create a new release.

${CHANGELOG}"

if gh pr view "$BRANCH" --json number -q '.number' &>/dev/null; then
gh pr edit "$BRANCH" --title "$TITLE" --body "$BODY"
else
gh pr create --head "$BRANCH" --base dev --title "$TITLE" --body "$BODY"
fi
run: git push --force --set-upstream origin release
10 changes: 1 addition & 9 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ permissions: {}

jobs:
release:
# Matches any release/<package> branch pattern
if: startsWith(github.head_ref, 'release/') && github.event.pull_request.merged == true
if: github.head_ref == 'release' && github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: write
Expand All @@ -20,13 +19,6 @@ jobs:
fetch-depth: 0
persist-credentials: false

- name: Resolve package name from branch
id: branch
shell: bash
run: |
# Strips the "release/" prefix to get the package name, e.g. release/sable -> sable
echo "package=${GITHUB_HEAD_REF#release/}" >> "$GITHUB_OUTPUT"

- uses: knope-dev/action@407e9ef7c272d2dd53a4e71e39a7839e29933c48 # v2.1.0
with:
version: 0.22.1
Expand Down
40 changes: 34 additions & 6 deletions knope.toml
Original file line number Diff line number Diff line change
@@ -1,25 +1,51 @@
[packages.sable]
[package]
versioned_files = ["package.json", "package-lock.json"]
changelog = "CHANGELOG.md"
extra_changelog_sections = [
{ name = "Documentation", types = ["docs"] },
{ name = "Notes", types = ["note"] },
]
# assets = "marker for GitHub bot" // TODO: add this later once we have assets
# assets = "marker" // TODO: add this later once we have assets

[changes]
ignore_conventional_commits = true

[[workflows]]
name = "prepare-release"
help_text = "Bump version and update changelog for the next release (used by CI)"
help_text = "Prepare a release PR"

[[workflows.steps]]
type = "Command"
command = "git switch -c release"

[[workflows.steps]]
type = "PrepareRelease"

[[workflows.steps]]
type = "Command"
command = "git commit -m \"chore: prepare release\""

[[workflows.steps]]
type = "Command"
command = "git push --force --set-upstream origin release"

[[workflows.steps]]
type = "CreatePullRequest"
base = "dev"

[workflows.steps.title]
template = "chore: prepare release $version"

[workflows.steps.body]
template = """
> [!IMPORTANT]
> Merging this PR will create a new release.

$changelog"""

[[workflows]]
name = "release"
help_text = "Create a GitHub release for the sable package (used by CI after PR merge)"
help_text = "Create a GitHub release"

[[workflows.steps]]
type = "Release"
Expand All @@ -36,6 +62,8 @@ owner = "SableClient"
repo = "Sable"

[release_notes]
# The <!-- commit:$commit_hash --> marker is used by prepare-release.yml
change_templates = [
"* $summary",
]
"### $summary <!-- commit:$commit_hash -->\n\n$details",
"* $summary <!-- commit:$commit_hash -->",
]
Loading