From 6a40900962d9d5fc149bb8700863352414fa75cd Mon Sep 17 00:00:00 2001 From: Matt Weber Date: Wed, 10 Apr 2024 15:48:51 -0400 Subject: [PATCH 1/4] chore: automate releases with pr Closes nodejs/undici#3042 --- .github/workflows/publish-undici-types.yml | 26 ------ .github/workflows/release-create-pr.yml | 79 ++++++++++++++++++ .github/workflows/release.yml | 97 ++++++++++++++++++++++ CONTRIBUTING.md | 7 ++ 4 files changed, 183 insertions(+), 26 deletions(-) delete mode 100644 .github/workflows/publish-undici-types.yml create mode 100644 .github/workflows/release-create-pr.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/publish-undici-types.yml b/.github/workflows/publish-undici-types.yml deleted file mode 100644 index 5ebbce01505..00000000000 --- a/.github/workflows/publish-undici-types.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Publish undici-types - -on: - push: - tags: - - 'v*' - workflow_dispatch: - -permissions: - contents: read - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@9bb56186c3b09b4f86b1c65136769dd318469633 # v4.1.2 - - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 - with: - node-version: lts/* - registry-url: 'https://registry.npmjs.org' - - run: npm install - - run: node scripts/generate-undici-types-package-json.js - - run: npm publish - working-directory: './types' - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release-create-pr.yml b/.github/workflows/release-create-pr.yml new file mode 100644 index 00000000000..b23b633a30f --- /dev/null +++ b/.github/workflows/release-create-pr.yml @@ -0,0 +1,79 @@ +name: Create release PR + +permissions: + contents: read + +on: + workflow_dispatch: + inputs: + version: + description: 'The version number to release (has priority over release_type)' + type: string + release_type: + description: Type of release + type: choice + default: patch + options: + - patch + - minor + - major + +jobs: + create-pr: + runs-on: ubuntu-latest + + permissions: + contents: write + pull-requests: write + + outputs: + version: ${{ steps.bump.outputs.version }} + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Git Config + run: | + git config --global user.email "github-actions@github.com" + git config --global user.name "github-actions" + - name: Change version number and push + id: bump + run: | + npm version ${{ inputs.version || inputs.release_type }} --git-tag-version=false + VERSION=`jq -r ".version" package.json` + RELEASE_BRANCH="release/v$VERSION" + git add -u + git commit -m "Bumped v$VERSION" + git push origin "HEAD:$RELEASE_BRANCH" + echo "version=$VERSION" >> $GITHUB_OUTPUT + - name: Create PR + uses: actions/github-script@v7 + with: + script: | + const defaultBranch = "${{ github.event.repository.default_branch }}" + const { owner, repo } = context.repo + const version = "v${{ steps.bump.outputs.version }}" + + const { data: releases } = await github.rest.repos.listReleases({ + owner, + repo + }) + + const { data: { body } } = await github.rest.repos.generateReleaseNotes({ + owner, + repo, + tag_name: version, + target_commitish: defaultBranch, + previous_tag_name: releases[0]?.tag_name + }); + + await github.rest.pulls.create({ + owner, + repo, + head: `release/${version}`, + base: defaultBranch, + title: `Create release ${version}`, + body + }) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000000..4bd76ae29c0 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,97 @@ +name: Create release + +on: + push: + branches: + - main + paths: + - package.json + +permissions: + contents: read + +jobs: + check-release-version: + runs-on: ubuntu-latest + outputs: + release-version: ${{ steps.set-release-version.outputs.result }} + steps: + - uses: actions/checkout@v4 + - uses: actions/github-script@v7 + id: set-release-version + with: + result-encoding: string + script: | + const { owner, repo } = context.repo + const version = require("./package.json").version + const versionTag = `v${version}` + + const { data: releases } = await github.rest.repos.listReleases({ + owner, + repo + }) + + if (versionTag !== releases[0]?.tag_name) { + return versionTag + } + + release: + runs-on: ubuntu-latest + needs: check-release-version + if: ${{ startsWith(needs.check-release-version.outputs.release-version, 'v') }} + + permissions: + contents: write + id-token: write + + environment: release + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org' + - run: npm install -g npm@latest + - run: npm install + - name: Create NPM release + run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - run: node scripts/generate-undici-types-package-json.js + - run: npm publish --provenance + working-directory: './types' + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - name: Create GitHub release + uses: actions/github-script@v7 + with: + script: | + const defaultBranch = "${{ github.event.repository.default_branch }}" + const { owner, repo } = context.repo + const versionTag = "${{ needs.check-release-version.outputs.release-version }}" + + const { data: releases } = await github.rest.repos.listReleases({ + owner, + repo + }) + + const { data: { body } } = await github.rest.repos.generateReleaseNotes({ + owner, + repo, + tag_name: versionTag, + target_commitish: defaultBranch, + previous_tag_name: releases[0]?.tag_name + }); + + await github.rest.repos.createRelease({ + owner, + repo, + tag_name: versionTag, + target_commitish: defaultBranch, + name: versionTag, + body, + draft: false, + prerelease: false, + generate_release_notes: false + }); \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7a910263bd1..08d81670b73 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,6 +5,7 @@ * [Lint](#lint) * [Test](#test) * [Coverage](#coverage) + * [Releases](#releases) * [Update `WPTs`](#update-wpts) * [Building for externally shared node builtins](#external-builds) * [Developer's Certificate of Origin 1.1](#developers-certificate-of-origin) @@ -166,6 +167,12 @@ npm run test npm run coverage ``` + +### Issuing Releases + +Release is automatic on commit to main which bumps the package.json version field. +Use the "Create release PR" github action to generate a release PR. + ### Building for externally shared node builtins From aef3340c819aa9af131f7a74632030ac2f5cc865 Mon Sep 17 00:00:00 2001 From: Matt Weber Date: Thu, 11 Apr 2024 12:53:13 -0400 Subject: [PATCH 2/4] factor js out of workflows --- .github/workflows/release-create-pr.yml | 26 +----------- .github/workflows/release.yml | 27 +----------- scripts/release.js | 56 +++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 50 deletions(-) create mode 100644 scripts/release.js diff --git a/.github/workflows/release-create-pr.yml b/.github/workflows/release-create-pr.yml index b23b633a30f..98ac1e68c88 100644 --- a/.github/workflows/release-create-pr.yml +++ b/.github/workflows/release-create-pr.yml @@ -53,27 +53,5 @@ jobs: with: script: | const defaultBranch = "${{ github.event.repository.default_branch }}" - const { owner, repo } = context.repo - const version = "v${{ steps.bump.outputs.version }}" - - const { data: releases } = await github.rest.repos.listReleases({ - owner, - repo - }) - - const { data: { body } } = await github.rest.repos.generateReleaseNotes({ - owner, - repo, - tag_name: version, - target_commitish: defaultBranch, - previous_tag_name: releases[0]?.tag_name - }); - - await github.rest.pulls.create({ - owner, - repo, - head: `release/${version}`, - base: defaultBranch, - title: `Create release ${version}`, - body - }) + const versionTag = "v${{ steps.bump.outputs.version }}" + await require('./scripts/release').generatePr({ github, context, defaultBranch, versionTag }) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4bd76ae29c0..bb21a930754 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -68,30 +68,5 @@ jobs: with: script: | const defaultBranch = "${{ github.event.repository.default_branch }}" - const { owner, repo } = context.repo const versionTag = "${{ needs.check-release-version.outputs.release-version }}" - - const { data: releases } = await github.rest.repos.listReleases({ - owner, - repo - }) - - const { data: { body } } = await github.rest.repos.generateReleaseNotes({ - owner, - repo, - tag_name: versionTag, - target_commitish: defaultBranch, - previous_tag_name: releases[0]?.tag_name - }); - - await github.rest.repos.createRelease({ - owner, - repo, - tag_name: versionTag, - target_commitish: defaultBranch, - name: versionTag, - body, - draft: false, - prerelease: false, - generate_release_notes: false - }); \ No newline at end of file + await require('./scripts/release').release({ github, context, defaultBranch, versionTag }) \ No newline at end of file diff --git a/scripts/release.js b/scripts/release.js new file mode 100644 index 00000000000..24beeb95502 --- /dev/null +++ b/scripts/release.js @@ -0,0 +1,56 @@ +'use strict' + +// Called from .github/workflows + +const generateReleaseNotes = async ({ github, owner, repo, versionTag, defaultBranch }) => { + const { data: releases } = await github.rest.repos.listReleases({ + owner, + repo + }) + + const { data: { body } } = await github.rest.repos.generateReleaseNotes({ + owner, + repo, + tag_name: versionTag, + target_commitish: defaultBranch, + previous_tag_name: releases[0]?.tag_name + }) + + return body +} + +const generatePr = async ({ github, context, defaultBranch, versionTag }) => { + const { owner, repo } = context.repo + const releaseNotes = await generateReleaseNotes({ github, owner, repo, versionTag, defaultBranch }) + + await github.rest.pulls.create({ + owner, + repo, + head: `release/${versionTag}`, + base: defaultBranch, + title: `[Release] ${versionTag}`, + body: releaseNotes + }) +} + +const release = async ({ github, context, defaultBranch, versionTag }) => { + const { owner, repo } = context.repo + const releaseNotes = await generateReleaseNotes({ github, owner, repo, versionTag, defaultBranch }) + + await github.rest.repos.createRelease({ + owner, + repo, + tag_name: versionTag, + target_commitish: defaultBranch, + name: versionTag, + body: releaseNotes, + draft: false, + prerelease: false, + generate_release_notes: false + }) +} + +module.exports = { + generatePr, + release +} From 8022a351573e326cc52412518459217efbc0ee79 Mon Sep 17 00:00:00 2001 From: Matt Weber Date: Thu, 11 Apr 2024 12:56:21 -0400 Subject: [PATCH 3/4] remove release PR from release notes --- scripts/release.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/scripts/release.js b/scripts/release.js index 24beeb95502..5b65788f98c 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -16,7 +16,11 @@ const generateReleaseNotes = async ({ github, owner, repo, versionTag, defaultBr previous_tag_name: releases[0]?.tag_name }) - return body + const bodyWithoutReleasePr = body.split('\n') + .filter((line) => !line.includes('[Release] v')) + .join('\n') + + return bodyWithoutReleasePr } const generatePr = async ({ github, context, defaultBranch, versionTag }) => { From ff027a95a1791424b3e2c3e1dc0b0a2161a88140 Mon Sep 17 00:00:00 2001 From: Matt Weber Date: Thu, 11 Apr 2024 12:56:47 -0400 Subject: [PATCH 4/4] delete release branch after release --- scripts/release.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/scripts/release.js b/scripts/release.js index 5b65788f98c..b901f8854e7 100644 --- a/scripts/release.js +++ b/scripts/release.js @@ -52,6 +52,17 @@ const release = async ({ github, context, defaultBranch, versionTag }) => { prerelease: false, generate_release_notes: false }) + + try { + await github.rest.git.deleteRef({ + owner, + repo, + ref: `heads/release/${versionTag}` + }) + } catch (err) { + console.log("Couldn't delete release PR ref") + console.log(err) + } } module.exports = {