Semantic Action

Perform any action based on commits since last action.

Semantic release

I love semantic-release and use it for every one of my own NPM packages. I do not publish a new version to NPM from local box. Instead, the CI publishes a new version but only if the tests pass and there are changes to publish. CI determines if there are changes (and what the version bump should be) based on commit messages. There are several conventions, but I follow the simplest one from simple-commit-message.

  • If there is a commit with patch: something ... or fix: something - the 3rd number z in the version's triple x.y.z should be incremented by one.
  • If a commit has form feat: .... or minor: ... then the second number is incremented.
  • Finally, if the commit starts with major: ... or break: ... then this is a major API change, thus the package gets x+1.y.z bump
  • If there are multiple commits, the largest one wins.

Usually I push every 1-2 commits to the master, releasing new versions often. Thanks to gitub-post-release plugin, every published NPM version gets a corresponding GitHub release with nice release summary.

Any issue closed by the commits as part of the release also gets a comment to notify users that the new version that solves the issue has been published.

Issue comment

I love love love semantic-release. I even created my own Yeoman generator for Node projects that has yo node-bahmutov:release command. It runs semantic-release-cli setup and installs my own release plugins I often need. Overall, I almost never think about the publishing mechanics, instead I spend time thinking about what the roadmap should be, what features to implement and in what order they should be released. The NPM release is just shipping and handling - it just happens.

Shipping a new version

Yet I found myself limited by what semantic-release can do. Partly because the project itself has been dormant (hope we can reinvigorate it with Gregor and Hutson), but partly because it is really closely tied to TravisCI, GitHub and NPM registry.

Semantic action

What I really want to do is to drive any action based on semantic analysis of commit messages. Not everything we do is a new NPM module version; often we need to deploy a static website or a server. To do this we need to make semantic-release a little more generic. So I forked it into semantic-action to freely experiment and change - the beauty of open source is that it freely allows everyone to play and adapt other projects to your needs.

Any automation is a replacement for a manual step. So what do we do manually that might benefit from semantic-action? The simplest thing that I often do is publishing a static Hexo blog, just like this one. See this repo test-semantic-action for the source code for the simplest case; it is deployed to GitHub static pages hosted at https://glebbahmutov.com/test-semantic-deploy/.

You can see the deploy command in the .circleci/config.yml executed on CircleCI.

1
- run: npm run semantic-deploy || true

That is it - the CI command is super simple, because we already connected the CI to GitHub via SSH keys, so the new static content can be pushed. But if you look at the CircleCI log there is a lot going on, even discounting verbose logging I have turned on. The NPM script command has 3 parts:

1
2
3
4
5
6
{
"scripts": {
"deploy": "hexo deploy",
"semantic-deploy": "semantic-action pre && npm run deploy && semantic-action post"
}

}

Notice the "pre" and "post" scripts - they are controlled by semantic-action and can be customized by using plugins.

Pre is the safety switch

The "pre" step looks at the environment and tries to decide if a semantic action is necessary. Usually the "pre" step will halt if the environment variable CI is undefined - forcing us to only run this action on the continuous integration server. The original semantic-release also looks for NPM_TOKEN and GH_TOKEN environment variables, because it assumes we are going to publish to NPM registry and upload the release notes on GitHub. My semantic-action removes this check, because we are not necessarily publishing to NPM, and if there is such action, the action itself would check.

The most important thing the "pre" step does - it inspects the commits since the previous semantic action to decide if there are new public commits. For this, it needs two things:

  1. Commit SHA when the semantic action ran last time. semantic-release used the version tag fetched from NPM registry to find it. In my case I extracted this to a plugin. For example, if you deploy commit SHA with your static site as a json file, you can fetch it during "pre" step. Just tell it in the "release" object of the package.json to use url-to-sha plugin and point at the deployed URL

    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "release": {
    "getLastRelease": {
    "path": "url-to-sha",
    "url": "https://glebbahmutov.com/test-semantic-deploy/build.json",
    "property": "id"
    }

    }

    }

    I generate the build.json file during the deployment using git-last module and NPM script command

    1
    2
    3
    4
    5
    {
    "scripts": {
    "postgenerate": "git-last -f public/build.json && cat public/build.json"
    }

    }

    You can see the deployed version yourself

    1
    2
    3
    4
    5
    6
    7
    $ curl https://glebbahmutov.com/test-semantic-deploy/build.json
    {
    "id": "60839bb21998cb30772c01b3a0f6efe17d1892aa",
    "short": "60839bb",
    "savedAt": "2017-08-04T05:57:38.675Z",
    "version": "1.3.0"
    }

    I find having such easy way to see what I have deployed that most of my servers have version-middleware installed.

  2. List of commits since the last action commit SHA. This is pretty standard and just uses Git command.

Action

Between "pre" and "post" lies the real action. The command could be anything - npm publish, hexo deploy, immutable deploy using now, git push [email protected] - any command that you execute manually is fair game for automation. Could be even a very simple tedious task - create a tag in the repo to mark when specific feature or fix commit was pushed.

Post is for notifications

The "post" step is for running tasks after semantic action has happened. For example we might want to upload other artifacts to storage or to Docker Hub, or push a new tag, or send an email, or post a GIF to a Slack channel.

In the coming months I hope to have a flexible declarative way (all via a JSON object in the package.json file) to execute arbitrary deployment actions based on commits and files affected by these commits. For example for larger monorepos we might want to deploy static documentation but only if the semantic commits have touched files in the docs folder.

If I can borrow an image from semantic-release README:

Automate all the things