This semantic-release publishing flow consists of using a bot for publishing releases with signed/verified commits. This setup requires creating a Github App, a user account for the bot to use, and adding the following Github Actions secrets to the repo it is being used in:
NPM_TOKEN
- Refer to “Generating an NPM token” section.
MY_RELEASER_USER_EMAIL
- Refer to the “Create a new Github user account for the bot” section
MY_RELEASER_GPG_KEY_ID
- Refer to “Generating a GPG key” section.
MY_RELEASER_BASE64_GPG_KEY
- Refer to “Generating a GPG key” section.
MY_RELEASER_GPG_PASSPHRASE
- Refer to “Generating a GPG key” section.
MY_RELEASER_ID
- Refer to “Creating a GitHub App” section
MY_RELEASER_PRIVATE_KEY
- Refer to “Creating a GitHub App” section
Username: my-releaser
Email: [email protected]
- Ensure this is an email that is not used on any other Github account
- Ensure you have access to this email so that it can be verified
Once the account is created:
- Go to the account’s settings
- Click “Emails”
- Check “Keep my email addressees private”
- Copy the account’s no-reply email
Example:
[email protected]
- Create a secret in the Github repo you will use this bot in called
MY_RELEASER_USER_EMAIL
- Paste the no-reply email from the previous step
- Go to npmjs.com and create an account, if necessary.
- Click “Access Tokens” from the menu.
- Click “Generate New Token”
- Click “Granular Access Token”
- Give the token a descriptive name that differentiates it from the rest of the tokens in your account, such as “My Releaser Bot”
- Provide a description, such as “Token for My Releaser Bot”
- Provide an expiration date, such as “12/31/2100”
- Set permissions to “Read and write”
- Select the package
- Click “Generate token”
- Copy the “Token”
⚠️ This token will never be visible on the NPM website again.
- Create a secret in the Github repo this will be used and name it
NPM_TOKEN
- Paste the token value from the previous step
Since the bot will need to make commits to bump the package version and update the change-log after a new release, it will require a user account with a GPG key so that the commits it makes are signed/verified.
To create a new GPG key, run the following:
gpg --full-generate-key
- (1) RSA and RSA
- (3072) 3072 bits key size
- (0) key does not expire
- Real name:
My Releaser
- Email address:
[email protected]
- This should be the no-reply email obtained in the “Create a new Github user account for the bot” section
- Comment: GPG key used by the My Releaser bot to sign commits.
- Passphrase: ______
- Create a passphrase that the bot can use
To use an existing GPG key, run the following to list them:
gpg --list-secret-keys --keyid-format=long
# Here is an example output
[keyboxd]
---------
sec rsa3072/3AA5C34371567BD2 2024-07-27 [SC]
0A9EE57EE119572FC9FA8E7C0300EBBCEFFB188D
uid [ultimate] John Doe <[email protected]>
ssb rsa3072/908B69F28954074D 2024-07-27 [E]
# The 3AA5C34371567BD2 value should be saved as MY_RELEASER_GPG_KEY_ID
Run the following to display the base64-encoded secret key:
# The output of this command should be saved as MY_RELEASER_BASE64_GPG_KEY
gpg --export-secret-keys 3AA5C34371567BD2 | base64
- Create a new secret called
MY_RELEASER_GPG_KEY_ID
- Paste the GPG key ID from step 1
- Create a new secret called
MY_RELEASER_BASE64_GPG_KEY
- Paste the base64-encoded secret key from step 1
- Create a new secret called
MY_RELEASER_GPG_PASSPHRASE
- Paste the passphrase associated with the GPG key
- This is the passphrase that was set when creating the GPG key.
In order for the signed commits to be verified, the GPG key must be exported and uploaded to the bot's Github account.
To display the full GPG key, run the following
gpg --armor --export 3AA5C34371567BD2
Then:
- Go to the Github profile settings of the bot’s user account
- Click “SSH and GPG keys”
- Click “New GPG key”
- Paste the GPG key and save.
We need to create an internal GitHub app to act as the release bot with very strict permissions. If the setting is not specified here, it should NOT be checked or filled in when creating the app.
- Got to your Github org homepage
- Click “Settings”
- Click “Developer settings”
- Click the “GitHub Apps” tab
- Click “New GitHub App”
- Configure the app as follows:
- GitHub App name:
- My Releaser (results in a my-releaser slug)
- Description:
- Internal bot to handle automatic releases of packages.
- Homepage URL:
- Callback URL:
- Repository permissions:
- Checks - read/write
- Contents - read/write
- Issues - read/write
- Metadata - read
- Pull requests - read/write
- Secrets - read
- Where can this GitHub App be installed?
- Only on this account (Only allow this GitHub App to be installed on the @your-org account)
- GitHub App name:
- Click “Create GitHub App”
- Make note of the “App ID” to reference later.
- Click “generate a private key”
- This will create a private key and trigger a download of a
.pem
file to your machine - The
.pem
must be stored securely. - If this key is lost, you will have to create a new private key in the Github app settings (you won’t have to create a new app)
- This will create a private key and trigger a download of a
- Click the “Install App” tab and install the app in your organization
- Select the repos that this internal app should have access to.
- Since this is is an internal app with very limited permissions, it should be safe to give it access to all the the org’s repos. However, it probably makes sense to gradually give the app access to more repos as necessary.
- Select the repos that this internal app should have access to.
- In the repo that this bot will be used in, add the following GitHub Actions secrets
MY_RELEASER_ID
- Paste the App ID noted earlier as the value
MY_RELEASER_PRIVATE_KEY
- Paste the contents of the
.pem
file downloaded earlier as the value
- Paste the contents of the
- If the repo has the following protections, we will have to modify the settings so that the
my-releaser
Github app can bypass one of the protections- Protections:
- ☑ Require a pull request before merging
- ☑ Require approvals
- ☑ Require signed commits
- Modify with:
- ☑ Allow specified actors to bypass the required pull requests - Search for and select
my-releaser
as an actor that can bypass this requirement - Repeat the above for every protected branch in the repo (e.g.,main
,beta
, …)
- ☑ Allow specified actors to bypass the required pull requests - Search for and select
- Protections:
The following YAML code should be placed in the projects .github/workflows/release.yml
file of the repo you're using this bot in.
Note that this file makes use of all the environment variables described in the “Overview” section, so it is crucial that they’re all defined in the Github Actions secrets of the repo you're using this bot in.
The only environment variable in this file that does not need to be defined as a Github Actions secret is GITHUB_TOKEN
.
name: release
on:
push:
branches:
- main
- beta
workflow_dispatch:
permissions:
contents: write
issues: write
pull-requests: write
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Generate bot app token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.MY_RELEASER_ID }}
private-key: ${{ secrets.MY_RELEASER_PRIVATE_KEY }}
- name: Checkout
uses: actions/checkout@v4
with:
token: ${{ steps.generate_token.outputs.token }}
fetch-depth: 0
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"
- name: Import GPG key
run: echo $MY_RELEASER_BASE64_GPG_KEY | base64 --decode | gpg --batch --import
env:
MY_RELEASER_BASE64_GPG_KEY: ${{ secrets.MY_RELEASER_BASE64_GPG_KEY }}
- name: Run program that passes the GPG passphrase to the GPG CLI
run: |
rm -rf /tmp/gpg.sh
echo '#!/bin/bash' >> /tmp/gpg.sh
echo 'gpg --batch --pinentry-mode=loopback --passphrase $MY_RELEASER_GPG_PASSPHRASE $@' >> /tmp/gpg.sh
chmod +x /tmp/gpg.sh
- name: Configure GPG for automated release commits
run: |
git config commit.gpgsign true
git config user.signingkey $MY_RELEASER_GPG_KEY_ID
git config gpg.program /tmp/gpg.sh
env:
MY_RELEASER_GPG_KEY_ID: ${{ secrets.MY_RELEASER_GPG_KEY_ID }}
- name: Install dependencies
run: npm install
- name: Run tests
run: npm run test
- name: Build package
run: npm run-script build
env:
NODE_ENV: production
- name: Publish package
run: npx semantic-release
env:
MY_RELEASER_GPG_PASSPHRASE: ${{ secrets.MY_RELEASER_GPG_PASSPHRASE }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
GIT_AUTHOR_EMAIL: ${{ secrets.MY_RELEASER_USER_EMAIL }}
GIT_COMMITTER_EMAIL: ${{ secrets.MY_RELEASER_USER_EMAIL }}