You’ve started a new project in which you’re creating a package to release on a package registry and you want to simplify the workflow in which you push some changes to be tested in an app, without a lot of hassle of copying local packages around.
The simplest solution to this is to push to npm, but that can be a bit cluttering, especially if you’re iterating quickly.
This is a predicament that I found myself in recently, and decided it was finally time to check out GitHub Packages. GitHub Package supports a number of different package repository formats such as npm, NuGet, Maven and Docker, and integrates directly with the existing package management tool chain. For this post, we’ll use a npm package, but the concept the same for all registry types.
Creating a Workflow
To do this workflow, we’ll use GitHub Actions as our workflow engine. I’ve blogged in the past on getting started with GitHub Actions, so if you’re new to them I’d suggest using that to brush up on the terminology and structure of a workflow file.
Start by created a workflow file in
.github/workflows and call it
build.yml. We want this workflow to run every time someone pushes to the
main branch, or when a PR is opened against it, so we’ll set that as our trigger:
Next, we’ll create a job that does your normal build process. Remember that this is a Node package, so it’s written for that, but swap it out for
npm calls, or whatever platform you’re targeting:
Building a Package
With the workflow running our standard verification checks, the next job will generate the package. Personally, I like to extract it out to a separate
job so it’s clear which phase of our workflow a failure has happened. This new
job will be called
package and it’ll need the
build job to complete first, which we specify with the
One down-side of doing this as a separate
job is that we’ll need to prepare the artifacts for the package to be created again, as they aren’t available from the
build job (unless you upload them, but that might be really slow if you have a lot of dependencies), so we’ll have to get them again.
For this example, we’re only installing the npm packages, but if it was a TypeScript project you’d want to run the
tsc compilation, .NET projects would need to compile, etc.
With dependencies installed, it’s time to generate the package:
With npm we have a
version command that can be used to bump the version that the package is going to be created, and you can use it to bump each part of the semver string (check out the docs for all options). Since this is happening as part of a CI build, we’ll just tag it as a pre-release package bump, and use the ID of the build as the version suffix, making it unique and auto-incrementing across builds. We’ll also give it the
--no-git-tag-version flag since we don’t need to tag the commit in Git, as that tag isn’t getting pushed (but obviously you can do that if you prefer, I just wouldn’t recommend it as part of a CI build as you’d get a lot of tags!).
If you’re using .NET, here’s the
run step I use:
Finally, we’ll use the upload Action to push the package to the workflow so we can download it from the workflow to do local installs, or use it in our final
job to publish to GitHub Packages.
Publishing a Package
With our package created and appropriately versioned it’s time to put it in GitHub Packages. Again, we’ll use a dedicated job for this, and it’s going to depend on the
package job completion:
You’ll notice that here we have an
if condition on the job and that it’s checking the GitHub context object to ensure that the owner is the organisation that this repo belongs to. The primary reason for this is to reduce the chance of a failed build if someone pushes a PR from a fork, it won’t have access to
secrets.GITHUB_TOKEN, and as such the job would fail to publish, resulting in a failed job. You may want to tweak this condition, or remove it, depending on your exact scenario.
This job also doesn’t use the
actions/checkout Action, since we don’t need the source code. Instead, we use
actions/download-artifact to get the package file created in the
To publish with npm, we’ll setup node, but configure it to use the GitHub Packages registry, which is
https://npm.pkg.github.com/ and define the current organisation as the scope (
We’ll then setup the
.npmrc file, specifying the registry again. This ensures that the publishing of the package will go through to the GitHub Packages endpoint, rather than the public npm registry.
Lastly, we run
npm publish and since we’re publishing the package from an existing
tgz, not from a folder with a
package.json, we have to give it the file path. Since we don’t know what the version number is we can use
ls *.tgz to get it and inline that to the command.
Quick note, GitHub Packages only supports scoped npm packages (ref), so your package name will need to be scoped like
With this done, each build will create a GitHub Package that you can use. You’ll find a full workflow example on my react-foldable project.
The requirement for npm packages to be scoped caught me out initially, but it’s an easy change to make, especially early on in a project.
Ultimately though, this helps give a quicker feedback loop between making a change to a package and being able to integrate it into a project, using the standard infrastructure to consume packages.