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..98ac1e68c88
--- /dev/null
+++ b/.github/workflows/release-create-pr.yml
@@ -0,0 +1,57 @@
+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 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
new file mode 100644
index 00000000000..bb21a930754
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,72 @@
+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 versionTag = "${{ needs.check-release-version.outputs.release-version }}"
+ await require('./scripts/release').release({ github, context, defaultBranch, versionTag })
\ 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
diff --git a/scripts/release.js b/scripts/release.js
new file mode 100644
index 00000000000..b901f8854e7
--- /dev/null
+++ b/scripts/release.js
@@ -0,0 +1,71 @@
+'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
+ })
+
+ const bodyWithoutReleasePr = body.split('\n')
+ .filter((line) => !line.includes('[Release] v'))
+ .join('\n')
+
+ return bodyWithoutReleasePr
+}
+
+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
+ })
+
+ 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 = {
+ generatePr,
+ release
+}