Continuously Deploying to GitLab's NPM Package Registry

In a previous article, we explored how to continuously deploy to the npmjs.com package registry. This is all well and good, but an advantage of CI/CD is that we can easily deploy wherever we want. This article will explore how to deploy an npm package to both npmjs.com and GitLab's own package registry, including how to change package names when necessary.

Starting point: ordinary CI/CD for npm

The earlier article built out a complete .gitlab.yml file for an npm package, and it leverages environment variables in a way that makes it very transferrable between projects. I will be adding a stage to this pipeline, so I recommend familiarizing yourself with this pipeline as a starting point before proceeding. The comments in the first stage provide a kind of legend for the file in case you're not familiar with .gitlab.yml attributes.

image: node:latest

compile: # arbitrary name to identify the script
  stage: build # indicates its chronological order in the pipeline
  script: 
    - npm ci # the recommended best practice for CI/CD (as opposed to npm i)
    - npm run build 
  only: 
    - dev # only run this script for the dev branch

test:
  stage: test
  script:
    - npm ci
    - npm run build
    - npm run test
  only:
    - dev

merge:
  only:
    - dev
  script:
    - git remote set-url origin https://merge-token:${MERGE_TOKEN}@gitlab.com/${CI_PROJECT_NAMESPACE}/${CI_PROJECT_NAME}.git
    - git pull origin main
    - git checkout main
    - git merge origin/dev
    - git push origin main
  stage: deploy

deploy:
  only:
    - main
  stage: deploy
  script:
    - echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc
    - npm publish

If you're still confused, refer to the previous article, which builds this file step by step (or, as always, open an issue).

Scaffolding our new deployment step

Let's start by creating a new deployment script which runs on the main branch in the deployment stage.

gitlab-deploy:
  only:
    - main
  script:
    - echo "Hello, world."
  stage: deploy

So now we've defined the circumstances under which we want to run.

Managing your package namespace and name

I would expect that you'd want to publish your package under the same name on GitLab as npmjs.com, but the GitLab package registry requires that all packages be namespaced by their repository namespace.

So we need to swap out our old package name with our new namespaced package name. For me, since I am working in ReScript and need to rename in bsconfig.json as well as the package files, I've found the easiest way to reconfigure my namespace is through the npm package file-find-replace-cli.

(You could also use sed if you're smart enough, but I've decided my life is too short for that.)

file-find-replace-cli uses a json file to define its commands. This can be predefined and checked into source control, but it's more fun to echo it out from environment variables (which will make our .gitlab.yml more reusable).

gitlab-deploy:
  only:
    - main
  script:
    - npm i -g file-find-replace-cli
    - echo "{ \"find\":\"$CI_PROJECT_NAME\"," >> replace.json
    - echo "\"replace\":\"@$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME\" }" >> replace.json
  stage: deploy

With the package installed and the replace.json file in place, you can run the find-replace command against your package.json and package-lock.json files using package*.json (and bsconfig.json if you, like me, are using ReScript).

gitlab-deploy:
  only:
    - main
  script:
    - npm i -g file-find-replace-cli
    - echo "{ \"find\":\"$CI_PROJECT_NAME\"," >> replace.json
    - echo "\"replace\":\"@$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME\" }" >> replace.json
    - find-replace replace.json -f 'bsconfig.json' -f 'package*.json'
    - npm config set @${CI_PROJECT_NAMESPACE}:registry https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/
    - npm config set -- '//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken' "${CI_JOB_TOKEN}"
    - npm publish
  stage: deploy

Next, set the package registry to GitLab for the namespace you just changed to, and authenticate with the built-in CI_JOB_TOKEN, and, finally, publish.

gitlab-deploy:
  only:
    - main
  script:
    - npm i -g file-find-replace-cli
    - echo "{ \"find\":\"$CI_PROJECT_NAME\"," >> replace.json
    - echo "\"replace\":\"w$CI_PROJECT_NAMESPACE/$CI_PROJECT_NAME\" }" >> replace.json
    - find-replace replace.json -f 'bsconfig.json' -f 'package*.json'
    - npm config set @${CI_PROJECT_NAMESPACE}:registry https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/
    - npm config set -- '//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken' "${CI_JOB_TOKEN}"
    - npm publish
  stage: deploy

Publish or perish

I hope this has been useful to developers trying to deploy to GitLab's registry or deploy to multiple registries. The default npmjs.org registry is an invaluable developer resource, but it's always a good idea to have multiple baskets for one's proverbial eggs. (More on this in a future article.)