Distributing NPM packages in Gitlab
Our project has grown into a moment when we needed to extract the React Native UI components from the app repository, so they could be simply reused as a npm dependencies in another apps 🤔.
A seek for npm registry.
- Due to the team size we needed to set some UI maintenance processes, merge requests, code review, code quality checks, you name it…
- We wanted to be able to run unit tests
- We wanted to use Storybook,
- We wanted to keep the costs down.
Since we host all our repositories on Gitlab, having the npm package registry directly there seemed like a logical thing to do. The registry can be kept as private, so the user has to be authenticated and authorized to access the libraries, or you can share the library as public.
Note: Gitlab also supports other package registries like Maven, Go, PyPI, Ruby Gems, even Helm! .. and more...
Gitlab FTW!
Gitlab Package Registry offers two approaches
- Project level — one Gitlab repository = one npm endpoint
- Instance level—whole Gitlab group is one npm endpoint
The idea is that we’ll have a separate Gitlab group that would have all repositories that we need to ship with npm. So we created a new group called fairo-hq
(fairo was already taken 🤭). Go Instance level Gitlab registry!
Publishing the package
Firstly, the package needs to be published. To publish the package it’s only necessary to have a repository with package.json
and gitlab-ci.yml
. It’s simple as that.
Minimal setup needs at least these to be set:
package.json{
"name": "@fairo-hq/ui",
"version": "0.0.1"
}.gitlab-ci.ymlimage: node:16-alpine
stages:
- publish
publish:
stage: publish
script:
- echo "@${CI_PROJECT_ROOT_NAMESPACE}:registry=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/" >> .npmrc
- echo "${CI_API_V4_URL#https?}/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}" >> .npmrc
- npm publish
Let’s slow down and see what’s happening here.
The first command of the .gitlab-ci.yml
sets the npm config to point our npm scope to the gitlab endpoint.
As Gitlab’s documentation mentiones, we cannot freely choose the name of the scope. The scope is determined by the namespace of the Gitlab group, in our case it’s fairo-hq
. The npm scope shall be @fairo-hq
. The package name (after the slash) is the name of the project, in our case ui
. Then, the full name of the package defined in the package.json
must be @fairo-hq/ui
.
The second command simply adds an access token to the endpoint, which is then always present in the headers.
The third command ultimately publishes the package into the registry. This will success only if there’s not the same version already published. Thus it’s good to maybe combine the Gitlab job with only: tags
option set, so we can use npm version
command to bump the version.
The good news is that everything we’ve set here is stored in Gitlab’s predefined variables and there’s no need to set anything. CI_PROJECT_ROOT_NAMESPACE
's value is our namespace fairo-hq
, CI_API_V4_URL
would probably be https://gitlab.com/api/v4
(or self-hosted alternative), CI_PROJECT_ID
would be your project id and CI_JOB_TOKEN
token has all the permissions needed to write to the package registry. Sweet!
Did I not mention yarn
? You can use it to install dependencies, build your library, tests, etc., but the configuration and publish must be done using npm
. Using yarn config
or .yarnrc
, I’ve always received various 404
or 401
errors, even though yarn
seemed properly configured. Same goes for the library installation, as long as you store your configuration to .npmrc
, the yarn install
command works as expected.
If the pipeline succeeds, you should see your first package in the registry ❤️.
Installing the package
This can be set many ways, depending on the project security requirements, but essentially every developer needs to have these few lines in their .npmrc
. Same as before, the first line to add an endpoint to our @fairo-hq
scope, the rest to assign an access token to it.
Any Gitlab token that can access the group with read_registry
scope will do just fine.
.npmrc"@fairo-hq:registry"="https://gitlab.com/api/v4/packages/npm/"
"//gitlab.com/api/v4/packages/npm/:_authToken"="<<AUTH_TOKEN>>"
"//gitlab.com/api/v4/projects/:_authToken"="<<AUTH_TOKEN>>"
The Catch
In order for npm registry to work for instance level, it’s necessary to include both settings for project and instance level, despite otherwise great Gitlab documentation. It simply doesn’t work without it 😏 (again, all sorts of 400/401 errors).
It’s also worth to mention that project level settings officially require to specify a Project Id, which is not ideal if we want to have instance level npm registry with multiple packages.
The solution is simple (don’t ask me how we figured this out, it was just pure despair and luck 🥹 ).
# per Gitlab's documentation, requires the project id 🤷♂️
//gitlab.example.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}# h̶a̶c̶k̶y̶ Nifty solution
//gitlab.example.com/api/v4/projects/:_authToken=${CI_JOB_TOKEN}
With this utterly random undocumented solution you should be able to install the package using your favorite package manager.
Stay in touch
And that’s it. There are many more topics about npm registry auth, bundling the libraries etc, that’s for another time 😊.
In case the article was helpful we will be grateful if you will follow us here and support us with some claps 👏. In return we will share even more cool tech articles.
Feel free to comment and ask for details, share your pains and learnings.
This article was written by tech enthusiasts Honza & Miro currently working at CreativeDock.