Over the last few months, we’ve been working on a new Global Design System (GDS) for Salable. But as part of this process, we’ve also had to make several decisions regarding how we structure, contribute and set up packages for Salable. The driving force for this is because some of our packages (starting with the GDS) will be open-sourced for developers outside of the Salable team to work on and contribute to.
With this in mind, we needed to decide on and implement the fundamentals used across each package starting with the git/branch structure and release workflow which we will be covering in more detail in this post.
Depending on the git structure implemented on a project it can make contributing to and maintaining an open source project a pleasant experience or a logistical nightmare. We wanted to make a structure and system that abstracted as much logistical and organization work away from individual developers as possible, so they can focus on developing and not admin activities.
A second important consideration was the ability to maintain and actively work on multiple versions of the code, without causing conflicts or impeding the work of the developer. We want to allow developers to easily work on any of the currently supported versions of the package whether that be the current/latest one, or any historical ones still being maintained.
Abstracting Release Activities
To achieve the first motive of abstracting the logistical work away from the developer, we implemented the semantic release package using GitHub Actions. The automation this package offers is truly amazing, here are a few of the things we implemented with this package:
Automatic git tags and GitHub releases triggered via pushes to certain branches or PR merges into those branches.
Automatic NPM releases following the git tags and releases above.
Automatic semver releases based on the commit messages;
Automatic detection and calculation of the next version based on the current release in that branch, including support for multiple major versions.
By combining all of the above features, all developers need to do is write code and merge PRs, and all of the release-related activities are handled for them by the semantic release package.
As mentioned earlier, one of the key requirements for us is to be able to support multiple active versions of a package. While we have the release tooling and automation that supports it, we also need to rethink our git branch structure. Here is a hypothetical representation of the model we will be using.
This diagram was designed to be used with the semantic release package mentioned in the last section. Each commit or merge into one of the target branches (main, 1.x, 2.x) will trigger the GitHub Action and trigger a new release of the package relative to the previous version.
NOTE: I haven’t included PR branches for clarity but in the actual implementation there are PR branches for each new commit node on this diagram and we never directly push to the target branches outlined, to avoid pushing unstable or incomplete code.
As you can see with this branch structure, we have a high level of flexibility to share code between releases as needed but we also have the rigidity to support multiple active versions that can be independently tagged and released.
As with most things development related, this structure and our implementation isn’t set in stone and will likely evolve as our needs and requirements change with the packages. But for right now, combining this git structure and the semantic release package has improved the efficiency and streamlined our release process dramatically.