DEV Community

Cover image for Create a Github Action! ๐Ÿ”ฅ
Tal Hayut
Tal Hayut

Posted on • Updated on

Create a Github Action! ๐Ÿ”ฅ

I have been meaning to write this post for a while now, and with the github-actions hackathon coming tomorrow, I thought today is a good chance. oh yeah and this is my first post ! ๐Ÿš€

Show me the code

What is it

Github actions allows one to define workflows based on given conditions.
We can think of a workflow as a CI/CD pipeline. This is very similar to existing implementations such as CircleCI, Travis CI, or Gitlab CI/CD.

Comparing to other services

As I explained, github-actions is part of a long list of awesome CI/CD services - which almost all use a .yml as the pipeline configuration file.
Where github-actions differ is in it's ability to configure many small actions under your .github/workflows repo directory - which is so great for managing pipelines and segregating your actions based on different conditions\areas\subjects - however you would like to section it.

Why should you care

If youโ€™re like me, youโ€™d like quick feedback and automated repetitive tasks in terms of developing and publishing code (especially packages).

A quick way to understand our ideal and minimal workflow, is to think about what we donโ€™t want.

What I donโ€™t want:

  1. Manually creating git tags.
  2. Manually bumping the patch version in my package.json.
  3. Manually pushing the package.json version change.
  4. Manually releasing to some registry.

These actions (pun definitely intended) are repetitive and manual. we don't like this. we be smart. we be lazy.

Ready, set, action! ๐ŸŽฌ

Getting our first action running is easy thanks to great documentation and more importantly, many existing open source github actions!

I used the javascript-action template repo to get me started...immediately I noticed that I didn't read enough to know that any action you release has to be compiled.

Wait, what ?! compiled say you ?? no, no sir, you must be confused, see we are in javascript land, what compilation are you talking about ?

So...yeah - a github action is released using a compiled version of your code - which also means all of your dependencies (node_modules in this case) as well.

Bump action

The purpose of the action I created, obviously already existed in other github actions in the Github Marketplace, but they all seemed either too complicated, or doing too many things.

Introducing my very first github-action ! it's a template so you can quickly bootstrap your own github action using my repo ๐ŸŽ‰

GitHub logo tool3 / bump

โชฎ github action for bumping npm package versions

Bump Action

Bump allows you to..well...bump an npm package version using a commit message, and push the package.json update back to the repository.

Create an action from this template

Click the Use this Template and provide the new repo details for your action




github access token


user name
default: the user of the current push)


user email
default: current user email


branch to work against
default: master


use --allow-unrelated-histories deafult: false



the version being tagged and pushed.

Bump strategy

If your head (latest) commit has the keywords #patch, #minor or #major - this action will use that to perform the bump Defaults to patch.


You can consume the action by referencing the v1 branch

  runs-on: ubuntu-latest
  - uses: tool3/bump@v1
      github_token: ${{ secrets.GITHUB_TOKEN }}
      user: 'First Last
Enter fullscreen mode Exit fullscreen mode

Compiling .js

At first, beside being very weird, the whole compilation process annoyed me.
I had to compile my index.js with node_modules present (remember - the compiled version of your code will be a single file with all dependencies already baked into it) every time I wanted to push a new version of my action.

Git hooks to the rescue !

using a simple pre-commit hook - I used zeit/ncc (from the github actions documentation) to compile my .js files before pushing to the repo - this ensured I did not forget to compile when I git pushed later on.

TIP ๐Ÿ’ก
use husky to quickly define a pre-commit git hook which compiles your code using zeit/ncc

Show me the action

In order to get started with creating a github-action, let's first quickly review my current bump repo structure:

โ”œโ”€โ”€ LICENSE
โ”œโ”€โ”€ dist              -> compiled code (this committed and pushed!)
โ”œโ”€โ”€ action.yml        -> action metadata
โ”œโ”€โ”€ index.js.         -> action logic
โ”œโ”€โ”€ node_modules      -> needed for compile time
โ”œโ”€โ”€ package-lock.json
โ””โ”€โ”€ package.json
Enter fullscreen mode Exit fullscreen mode


the dist directory will host our compiled index.js which will be committed and pushed to the repo in order to make this action executable.


this file includes meta information for our action such as:

  • marketplace icon and color
  • input variables definitions
  • output information


I don't feel the need to explain what node_modules are, but what I do feel the need to explain is that it has to exist when you are compiling your code. this might sound logical for folks that are used to compile javascript - but I don't, and it wasn't for me.

Show me the code

my action consists of a single index.js file - 58 lines long - with whitespace of course - and that goes to show that you can create very small actions which do very little - or go all out and have a crazy technodrome-like big action - which I would probably root against.

To the index!

const core = require('@actions/core');
const { exec } = require('@actions/exec');
const github = require('@actions/github');
const { Toolkit } = require('actions-toolkit');

const STRATEGIES = [
]; tools => {
    try {
      // get context
      const { pusher: { email, name }, head_commit: { message } } = github.context.payload;

      // get input credentials
      const inputUser = core.getInput('user');
      const inputEmail = core.getInput('email');
      const inputBranch = core.getInput('branch');
      const unrelated = core.getInput('unrelated');

      const userName = inputUser || name;
      const userEmail = inputEmail || email;

      const defaultStrategy = STRATEGIES.filter(strat => message.includes(strat))[0] || STRATEGIES[0];
      const strategy = defaultStrategy.replace('#', '');
      const commitMessage = message.replace(defaultStrategy, '');

      tools.log(`Latest commit message: ${commitMessage}`);
      tools.log(`Running with ${userName} ${userEmail} and bumping strategy ${strategy}`);
      tools.log(`Branch is ${inputBranch}`);

      // git login and pull
      const pullArgs = ['pull', 'origin', inputBranch, '--tags'];
      if (unrelated) {

      await exec('git', ['config', '--local', '', userName]);
      await exec('git', ['config', '--local', '', userEmail]);
      await exec('git', pullArgs);

      // version by strategy
      await exec('npm', ['version', strategy, '--no-commit-hooks', '-m', `${commitMessage} %s`]);

      // push new version and tag
      await exec('git', ['push', 'origin', `HEAD:${inputBranch}`, '--tags'])

    catch (error) {


Enter fullscreen mode Exit fullscreen mode

Github gives us some packages to access input/output and to get context repository and user information. Read about that in the github-actions documentation

Pardon me for not explaining any part of this code - as it is not really relevant for this post.
I wanted to give you all the steps which were not obvious for me - but the actual code is up to you of course :)

I'll happily answer any questions about my index.js shown above, if such arises.


  • github actions are awesome.
  • you can define many small actions in different .yml files.
  • github actions requires compilation -zeit/ncc is a great option.
  • action.yml meta file in the root directory of your github action.

For those about to code... I salute you!

Happy Coding ๐Ÿ‘จ๐Ÿปโ€๐Ÿ’ป

Discussion (0)