Release #8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| workflow_dispatch: | |
| concurrency: | |
| group: release-${{ github.ref }} | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| id-token: write | |
| jobs: | |
| release: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - uses: pnpm/action-setup@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: "22" | |
| cache: "pnpm" | |
| - name: Upgrade npm for trusted publishing | |
| run: | | |
| npm install -g npm@^11.5.1 | |
| npm --version | |
| - run: pnpm install --frozen-lockfile | |
| - run: npm run check | |
| - run: sudo apt-get update && sudo apt-get install -y ripgrep | |
| - run: npm run security | |
| - name: Create release PR or publish | |
| id: changesets | |
| uses: changesets/action@v1 | |
| with: | |
| title: "chore: release" | |
| commit: "chore: release" | |
| version: pnpm version-packages && pnpm install --lockfile-only | |
| publish: pnpm release | |
| createGithubReleases: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| NPM_CONFIG_PROVENANCE: "true" | |
| NPM_TOKEN: ${{ secrets.NPM_TOKEN }} | |
| PROJECT_ALLOW_MAIN_PUSH: "1" | |
| - name: Push semver GitHub tag | |
| if: steps.changesets.outputs.published == 'true' | |
| env: | |
| PROJECT_ALLOW_MAIN_PUSH: "1" | |
| run: | | |
| VERSION="v$(node -p "require('./package.json').version")" | |
| if git rev-parse "$VERSION" >/dev/null 2>&1; then | |
| echo "Tag $VERSION already exists" | |
| exit 0 | |
| fi | |
| git tag "$VERSION" | |
| git push origin "$VERSION" | |
| - name: Generate rich release notes | |
| if: steps.changesets.outputs.published == 'true' | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require("node:fs"); | |
| const { owner, repo } = context.repo; | |
| const pkg = JSON.parse(fs.readFileSync("package.json", "utf8")); | |
| const currentTag = `v${pkg.version}`; | |
| const release = await github.rest.repos.getReleaseByTag({ | |
| owner, | |
| repo, | |
| tag: currentTag, | |
| }); | |
| const tags = await github.paginate(github.rest.repos.listTags, { | |
| owner, | |
| repo, | |
| per_page: 100, | |
| }); | |
| const currentTagIndex = tags.findIndex((tag) => tag.name === currentTag); | |
| let baseRef = null; | |
| if (currentTagIndex >= 0 && currentTagIndex + 1 < tags.length) { | |
| baseRef = tags[currentTagIndex + 1].name; | |
| } else { | |
| const commits = await github.paginate(github.rest.repos.listCommits, { | |
| owner, | |
| repo, | |
| per_page: 100, | |
| }); | |
| baseRef = commits.at(-1)?.sha ?? null; | |
| } | |
| if (!baseRef) { | |
| core.warning("Could not determine comparison base; skipping release note update."); | |
| return; | |
| } | |
| const comparison = await github.rest.repos.compareCommits({ | |
| owner, | |
| repo, | |
| base: baseRef, | |
| head: currentTag, | |
| }); | |
| const commits = comparison.data.commits ?? []; | |
| const commitLog = commits.map((commit) => { | |
| const subject = (commit.commit.message || "").split("\n")[0]; | |
| return `- ${commit.sha.slice(0, 7)} ${subject}`; | |
| }); | |
| const contributorSet = new Set( | |
| commits | |
| .map((commit) => commit.author?.login) | |
| .filter((login) => Boolean(login)) | |
| .map((login) => `@${login}`) | |
| ); | |
| const prNumbers = new Set(); | |
| const prPattern = /#(\d+)/g; | |
| for (const commit of commits) { | |
| const subject = (commit.commit.message || "").split("\n")[0]; | |
| for (const match of subject.matchAll(prPattern)) { | |
| prNumbers.add(Number(match[1])); | |
| } | |
| } | |
| const pullRequests = []; | |
| for (const number of Array.from(prNumbers).sort((a, b) => b - a)) { | |
| try { | |
| const pr = await github.rest.pulls.get({ | |
| owner, | |
| repo, | |
| pull_number: number, | |
| }); | |
| pullRequests.push(`- #${number} ${pr.data.title}`); | |
| } catch (error) { | |
| core.info(`Skipping #${number}; could not load pull request details.`); | |
| } | |
| } | |
| const sections = [ | |
| "## Changelog", | |
| "", | |
| "### Pull Requests", | |
| ...(pullRequests.length > 0 ? pullRequests : ["- none"]), | |
| "", | |
| "### Commit Log", | |
| ...(commitLog.length > 0 ? commitLog : ["- none"]), | |
| "", | |
| "### Contributors", | |
| ...(contributorSet.size > 0 ? Array.from(contributorSet).sort() : ["- unknown"]), | |
| "", | |
| `**Full diff:** ${comparison.data.html_url}`, | |
| ]; | |
| const body = sections.join("\n"); | |
| await github.rest.repos.updateRelease({ | |
| owner, | |
| repo, | |
| release_id: release.data.id, | |
| tag_name: currentTag, | |
| name: release.data.name || currentTag, | |
| body, | |
| }); |