Publish with Github

Something I want to do on most of my versioned projects is handle the versioning and committing as part of the merge/push to the main branch. So I’m putting this here as a reminder to myself and help to anyone else who is doing this with github actions.

First, you’ll need the script to do the work. Mine looks like this:

1#! /bin/bash
2
3# Check for required environment variables
4if [ -z "$GIT_EMAIL" ]; then
5  echo "❌ GIT_EMAIL is not set. Please set GIT_EMAIL."
6  exit 1
7fi
8
9if [ -z "$GIT_NAME" ]; then
10  echo "❌ GIT_NAME is not set. Please set GIT_NAME."
11  exit 1
12fi
13
14if [ -z "$NPM_TOKEN" ]; then
15  echo "❌ NPM_TOKEN is not set. Please set NPM_TOKEN."
16  exit 1
17fi
18
19if [ -z "$GIT_COMMIT_TOKEN" ]; then 
20    echo "❌ GIT_COMMIT_TOKEN is not set. Please set GIT_COMMIT_TOKEN."
21    exit 1
22fi
23
24# Make sure we're on the master branch
25if [ "$(git rev-parse --abbrev-ref HEAD)" != "main" ]; then
26  echo "❌ Not on main branch. Please switch to the main branch before deploying."
27  exit 1
28fi
29
30# Setup the git config if not already setup
31if [ -z "$(git config --global user.email)" ]; then
32    echo "🎯 Setting github config user"
33    git config --global user.email "${GIT_EMAIL}"
34    git config --global user.name "${GIT_NAME}"  
35    git remote set-url origin https://x-access-token:$GIT_COMMIT_TOKEN@github.com/kshehadeh/cmdq
36fi
37
38# Setup npmrc publish
39npm config list | grep -q //registry.npmjs.org/:_authToken
40result=$?
41if [ $result -ne 0 ]; then
42    echo "🎯 Setting npm auth token"
43    echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc
44fi
45
46git remote -v
47
48# # Bump the version
49echo "🎯 Incrementing version..."
50npm run increment
51
52# Build, and publish
53echo "🎯 Installing packages..."
54npm install
55
56echo "🎯 Running build..."
57npm run build
58
59echo "🎯 Publishing to NPM..."
60npm publish --access public
61
62# Commit the version bump
63echo "🎯 Committing version change..."
64git add .
65git commit -m "Bump version"
66git push
67

The important parts of this are the configuration changes that ensure that git and npm have permission to alter upstream state.

The GIT_COMMIT_TOKEN in Github Actions is just the GITHUB_TOKEN that is automatically available to you in secrets.GITHUB_TOKEN. This, by default, does not have permission to write to the repo. In order to allow for it to do so, you will need to add the following to your github action yaml:

1permissions:
2    contents: write
3

So that my deploy.yaml looks like this:

1name: Deploy
2
3on:
4    push:
5        branches:
6            - main
7
8jobs:
9    deploy:
10        runs-on: ubuntu-latest
11        env:
12            NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
13            GIT_EMAIL: ${{ secrets.GIT_EMAIL }}
14            GIT_NAME: ${{ secrets.GIT_NAME }}
15            GIT_COMMIT_TOKEN: ${{ secrets.GITHUB_TOKEN }}
16
17        permissions:
18            contents: write
19
20        steps:
21            - name: Checkout repository
22              uses: actions/checkout@v2
23
24            - name: Set up Node.js
25              uses: actions/setup-node@v2
26              with:
27                  node-version: "20"
28
29            - name: Run deploy script
30              run: npm run deploy
31
32            
33

npm run deploy simply executes deploy.sh which contains the shell script above.

What If Your Branch is Protected

If your branch is protected and pushing to your default branch is not allowed and you don’t want to automate a pull request flow, you have to go through some hoops.

Create a PAT or use an App Token

At Under Armour we have created a custom Github app that we use for a variety of tasks. One of the advantages of this is that we can generate tokens that are specific to this app and then give permission to the app to perform certain tasks.

Using the official actions/create-github-app-token task, we can create a token with permission to push to the default branch as part of a github action then use that token with the checkout action.

1steps:
2    - uses: actions/create-github-app-token@v1
3      id: app-token
4      with:
5        app-id: ${{ vars.QB_APP_ID }}
6        private-key: ${{ secrets.QB_APP_PRIVATE_KEY }}
7
8    - uses: actions/checkout@v2
9      with:
10        token: ${{ steps.app-token.outputs.token }}
11        ref: ${{ github.head_ref }}

Then in your repo settings, make sure to establish a bypass for this particular app.

src
💡
It’s usually better to use an app token than just using a personal token because it’s not tied to a particular person. If that person leaves, the action will continue to work.

But there’s a problem with this approach. If your action is triggered by a push to the branch, then this will cause an infinite loop of action runs.

Github Actions explicitly looks for git activity that is associated with the GITHUB_TOKEN from an action and will not retrigger an event. When using a token that is different, it will trigger push even again and start the action all over again. This is very bad. But you can avoid it by using a special commit message that tells Github not to trigger the event.

By including [skip-ci] in the commit message, it instructs github not to trigger a push or pull_request event for this particular commit.