Automatically publish your private NPM packages from monorepo to Gitlab Package Registry
A solution to publish private npm packages and have most of the jobs done automatically so that you only need to care about good code.
Before you read: This article focuses on the automation process, assuming that you have base knowledge on:
🤔 Question
How do I publish only the sub-packages that have been changed when my merge request is merged into the main branch?
💡 Solution
In short, we will use Gitlab CI to automate the process, including:
- Use Lerna's
version
andpublish
commands (they are built on top ofschematic-release
) to (1) determine the next version number, (2) generate the release notes, and (3) publish the package. - Use Gitlab Package Registry to store the packages (4)
Let’s go into detail!
Configure Lerna
(1) Versioning: determine the next version number
Config lerna version
command to increase the package’s version number by adding this into Lerna’s configs:
{
"command": {
"version": {
"conventionalCommits": true,
"createRelease": "gitlab",
"message": "chore(release): publish packages"
}
},
"version": "independent"
}
With that config, Lerna will detect the current package, identifies the current version, and propose the next one based on conventional-commits rules.
"version": "independent"
allows us to increment package versions independently of each other.
(2) Generate release notes
By using "conventionalCommits": true
, Lerna will collect the changes log from your commit messages and add them to package’s CHANGELOG.md
file which you can use as release notes.
(3) Publish the package
As we want to publish the packages onto our private registry, add registry
to publish
command config, so that forwarded npm commands will use the specified registry for your package(s).
{
"command": {
"publish": {
"registry": "https://<YOUR_GITLAB_DOMAIN>/api/v4/projects/<PROJECT_ID>/packages/npm/"
}
}
}
After having the new version number and the release notes, Lerna will update the changed package’s package.json
with that new version number, commit the changes with predefinedmessage
then push the commit to the remote repo. It also creates an official Gitlab release based on the changed packages.
In final lerna.jon
, you can add some other configs to match your own project. Here, I use yarn
as npm client and put my sub-packges in packages/*
.
lerna.jon
{
"command": {
"publish": {
"registry": "https://<YOUR_GITLAB_DOMAIN>/api/v4/projects/<PROJECT_ID>/packages/npm/"
},
"version": {
"conventionalCommits": true,
"createRelease": "gitlab",
"message": "chore(release): publish packages"
}
},
"npmClient": "yarn",
"packages": [
"packages/*"
],
"version": "independent"
}
Configure Gitlab
Now let’s create a Gitlab CI job to automate the above process whenever a merge request is merged into the main branch.
We will put them into 2 stages: build
then release
the packages to Gitlab Private Registry. Don’t forget to install the dependencies before each script.
cache:
key: $CI_COMMIT_REF_SLUG
paths:
- .yarn
before_script:
- yarn install --cache-folder .yarn
stages:
- build
- release
build
stage
Here, I will simply use node:latest
image to build the packages with yarn workspaces run build
command and then store the artifacts
in packages/*
path when the changes are merged into the main branch master
.
build_packages:
image: node:latest
stage: build
script:
- yarn workspaces run build
artifacts:
paths:
- packages/*
expire_in: 1 hour
only:
- master
release
state
Wait for the build_packages
to be done
dependencies:
- build_packages
Prepare the variable to authenticate the commands with Gitlab
variables:
GL_TOKEN: $GITLAB_TOKEN
GL_API_URL: $CI_API_V4_URL
GL_TOKEN
(required) - Your GitLab authentication token (under User Settings > Access Tokens).GL_API_URL
- An absolute URL to the API, including the version. (Default: https://gitlab.com/api/v4). When using self-hosted Gitlab, let’s take it from Gitlab's predefined variable$CI_API_V4_URL
.
Now is the important part, make a script to prepare the needed resources and then run the Lerna publish command in the end. I put a comment to explain what’s needed for each block below:
script:
# If no .npmrc if included in the repo, generate a temporary one that is configured to publish to GitLab's NPM registry
- |
if [[ ! -f .npmrc ]]; then
echo 'No .npmrc found! Creating one now. Please review the following link for more information: https://docs.gitlab.com/ee/user/packages/npm_registry/index.html#project-level-npm-endpoint-1'
{
echo "@${CI_PROJECT_ROOT_NAMESPACE}:registry=${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/npm/"
echo "${CI_API_V4_URL#http*:}/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=\${CI_JOB_TOKEN}"
} >> .npmrc
fi
- echo "Created the following .npmrc:"; cat .npmrc
# put SSH key in `.ssh` and make it accessible, in order to push release changes
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_DEPLOY_KEY" > ~/.ssh/id_rsa; chmod 0600 ~/.ssh/id_rsa
- echo "StrictHostKeyChecking no " > /root/.ssh/config
# Config git instance
- git config --global user.name "$GITLAB_USER_NAME"
- git config --global user.email "$GITLAB_USER_EMAIL"
- echo "setting origin remote to 'git@$CI_SERVER_HOST:$CI_PROJECT_PATH.git'"
- git remote set-url origin "git@$CI_SERVER_HOST:$CI_PROJECT_PATH.git"
- git checkout -B "$CI_COMMIT_REF_NAME" "$CI_COMMIT_SHA"
# Run publish command
- yarn lerna publish -y
The Lerna publish
will only take into account the packages that have been changed and push them to Gitlab, the others unchanged will be ignored.
🎉 Voilà! Until now whenever you merge a merge request to the main branch, Gitlab CI will build and publish your changed packages to Gitlab Package Registry!
In your Gitlab repo, you can find the published packages under Packages & Registries > Package Registry like this:
Enjoy coding! 🤓