Enjeu

Publier un paquet NPM aussi bien sur NPMJS que sur les différentes échelles (projet et instance) du Gitlab registry via la CI Gitlab. Il est tout à fait possible de faire soit l'un, soit l'autre, soit les deux.

Prérequis

Le paquet en question peut-être aussi bien public que scoped pour une publication uniquement sur NPM. Il sera nécessairement scoped sur le registry Gitlab et, de ce fait, sur NPMJS.

⚠️ La publication d'un paquet scoped et privé sur NPMJS est payante, votre paquet sera donc public par défaut.

Dans notre cas, nous traiterons une double publication registry Gitlab et NPMJS, notre publication sera donc scoped et publique.

Pour cela il faut :
- Une organisation Gitlab du nom du scope suivant la convention
- Un paquet préfixé du nom du namespace NPM dans lequel vous voulez publier (e.g @mynamespace/mypackagename)
- Un token NPMJS que vous ajouterez en variable d'environnement de vôtre CI Gitlab (e.g $NPM_TOKEN).

Je suggère d'utiliser un token de type Automation qui est dédié aux workflows automatisés.

1. Configuration

Je n'aborde pas les étapes de test mais vous recommande fortement de tester votre code ainsi que de suivre des normes de formatage et de présentation standards grâce a Eslint et Prettier. Il est aussi intéressant de développer vos librairies avec Typescript afin de fournir un typage aux futurs utilisateurs. Cela facilitera également la scalabilité de votre projet.

Une fois votre projet initialisé, créez un fichier .gitlab-ci.yml à la racine de votre projet.

1.1. Image Docker

L'image Docker n'a pas une grande incidence sur l'étape de publication, essayez toutefois d'utiliser une image LTS light dans la mesure du possible :

ymlimage: node:lts-alpine

1.2. Définition des étapes et paramétrage de la mise en cache

image: node:lts-alpine

stages:
  - test
  - build
  - deploy

# Configuration de la mise en cache des dépendances pour persister leur installation au sein des différentes étapes
cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
        - node_modules/


# Installation des dépendances avant l'exécution des jobs
before_script:
  - npm i

# Vos étapes de tests...

1.3. Build et artifacts

Peu de choses à dire niveau build, pensez simplement à bien ajouter les différents chemins de vos artifacts. À la fin le paquet correspondra a ces fichiers/dossiers, le package.json et le README.md :

# Vos étapes de tests...

build:
  stage: build
  script:
    - npm run build
  artifacts:
    paths:
      - dist
    # - vos autres dossiers/fichiers a inclure
    

2. Déploiement Gitlab registry

Nous voulons déployer notre librairie sur le registry Gitlab, aux échelles du projet et de l'instance de notre organisation. Il est nécessaire de :
- Attendre la fin du build
- Charger les artifacts
- Configurer l'URL du package
- S'authentifier
- Configurer la publication a l'échelle du projet
- Configurer la publication a l'échelle de l'organisation
- Publier

publish_gitlab_registry:
  stage: deploy
  needs:
    - job: build
      artifacts: true
  dependencies:
    - build
  script:
    # Configuration de l'URL du package, remplacer l'organisation par le nom de votre organisation, le CI_PROJECT_ID est fourni par la CI
    - npm config set ${ORGANIZATION}:registry https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/

    # Configuration de la publication a l'échelle du projet, le CI_JOB_TOKEN est fourni et suffit a l'authentification
    - npm config set -- '//gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken' "${CI_JOB_TOKEN}"

    # Configuration de la publication a l'échelle de l'instance, le CI_JOB_TOKEN est fourni et suffit a l'authentification
    - npm config set -- '//gitlab.com/api/v4/packages/npm/:_authToken' "${CI_JOB_TOKEN}"

    # Publication
    - npm publish

  # Nous conseillons de ne pas trigger la publication a chaque CI, ici la règle demande si le tag existe, restreignant ainsi la publication au tag  
  rules:
    - if: $CI_COMMIT_TAG

3. Déploiement sur NPMJS

Enfin pour publier sur NPMJS ce paquet scoped (suivant le nom du paquet dans le package.json), la logique est la même, seule la configuration change :

publish_npm:
  stage: deploy
  needs:
    - job: build
      artifacts: true
  dependencies:
    - build
  script:
    # Configuration pour la publication sur NPMJS, NPM_TOKEN est le token NPM de type automation généré plus tôt et ajouté aux variables d'environnement de votre CI Gitlab
    - npm config set -- '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"

    # Etant donné que le paquet est scope, il est nécessaire de spécifier le fait qu'il est public
    - npm publish --access public
  rules:
    - if: $CI_COMMIT_TAG

Résumé .gitlab-ci.yml :

image: node:lts-alpine

stages:
  - test
  - build
  - deploy

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/

before_script:
  - npm install

# Vos étapes de tests...

build:
  stage: build
  script:
    - npm run build
  artifacts:
    paths:
      - dist

publish_gitlab_registry:
  stage: deploy
  needs:
    - job: build
      artifacts: true
  dependencies:
    - build
  script:
    - npm config set ${ORANIZATION}: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 config set -- '//gitlab.com/api/v4/packages/npm/:_authToken' "${CI_JOB_TOKEN}"
    - npm publish
  rules:
    - if: $CI_COMMIT_TAG

publish_npm:
  stage: deploy
  needs:
    - job: build
      artifacts: true
  dependencies:
    - build
  script:
    - npm config set -- '//registry.npmjs.org/:_authToken' "${NPM_TOKEN}"
    - npm publish --access public
  rules:
    - if: $CI_COMMIT_TAG

4. La suite

4.1. NPMJS

La publication sur NPMJS vous notifiera par mail, vous pourrez ensuite utiliser le paquet depuis n'importe quel projet en l'installant comme n'importe quelle autre dépendance :

npm install @myscope/mypackage

⚠️ Il est impossible de publier plusieurs fois le même paquet avec la même version, pensez donc à bump la version dans le package.json à chaque publication️

Vous pouvez aussi gérer votre paquet depuis votre compte NPMJS.

4.2. Gitlab registry

Vous retrouverez votre paquet dans le repository de votre projet dans Packages & Registries -> Package Registry

Il vous suffira de paramétrer votre authentification au registry selon votre préférence. J'utilise personnellement un personnal access token avec un scope api. Une fois authentifié, vous pouvez installer votre paquet comme n'importe quelle autre dépendance :

npm install @myscope/mypackage

Tout devrait fonctionner pour peu que votre paquet soit fonctionnel !