Distributing NPM packages in Gitlab

Honza
Creative Talks
Published in
4 min readApr 23, 2022

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.

  1. Due to the team size we needed to set some UI maintenance processes, merge requests, code review, code quality checks, you name it…
  2. We wanted to be able to run unit tests
  3. We wanted to use Storybook,
  4. 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 levelone 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.

--

--