Commit 3b95eb84 authored by Pierre Smeyers's avatar Pierre Smeyers
Browse files

Merge branch 'feat/publish' into 'master'

npm publish support (Angular port)

Closes #24 and #4

See merge request to-be-continuous/node!51
parents 04daa742 a271c645
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ stages:
variables:
  GITLAB_CI_FILES: "templates/gitlab-ci-node.yml"
  BASH_SHELLCHECK_FILES: "*.sh"
  LYCHEE_EXTRA_OPTS: "--exclude .acme.corp"

semantic-release:
  rules:
+88 −1
Original line number Diff line number Diff line
@@ -23,11 +23,40 @@ The Node.js template uses some global configuration used throughout all jobs.
|------------------------|--------------------------------------------------------------------------------------------------|-------------------|
| `NODE_IMAGE`           | The Docker image used to run Node.js <br/>:warning: **set the version required by your project** | `registry.hub.docker.com/library/node:lts-alpine` |
| `NODE_MANAGER`         | The package manager used by your project (npm or yarn)<br/>**If undefined, automatic detection** | _none_            |
| `NODE_CONFIG_REGISTRY` | npm [registry](https://docs.npmjs.com/cli/v8/using-npm/registry)                                 | _none_            |
| `NODE_CONFIG_REGISTRY` | Main npm [registry](https://docs.npmjs.com/cli/v8/using-npm/registry) to use                     | _none_            |
| `NODE_CONFIG_SCOPED_REGISTRIES` | Space separated list of npm [scoped registries](https://docs.npmjs.com/cli/v8/using-npm/scope#associating-a-scope-with-a-registry) (formatted as `@somescope:https://some.npm.registry/some/repo @anotherscope:https://another.npm.registry/another/repo`) | _none_ |
| `NODE_PROJECT_DIR`     | Node project root directory                                                                      | `.`               |
| `NODE_SOURCE_DIR`      | Sources directory                                                                                | `src`             |
| `NODE_INSTALL_EXTRA_OPTS`| Extra options to install project dependencies (either [`npm ci`](https://docs.npmjs.com/cli/ci.html/) or [`yarn install`](https://yarnpkg.com/cli/install)) | _none_ |

### Using scoped registries

Scoped registries allow to pull and publish packages using multiple registries.

Examples:

* `npm install foo` installs `foo` package from https://www.npmjs.com/ by default,
* `npm install @angular/core` installs `@angular/core` package from https://www.npmjs.com/ if no npm registry associated to scope `@angular` is declared,
* `npm install @acme-corp/bar` installs `@acme-corp/bar` package from https://registry.acme.corp/npm if this registry url is associated to scope `@acme-corp`.

First of all, be aware that the Node.js template automatically configures the [GitLab's project-level npm packages registry](https://docs.gitlab.com/ee/user/packages/npm_registry/) associated to a scope corresponding to the root of the project (ex: project `https://gitlab.example.com/my-org/engineering-group/team-amazing/analytics` will have GitLab's project-level npm packages registry scope `@my-org`).
Therefore, GitLab's project-level npm packages registry can freely be used both to install packages (with the right scope) or even to [publish](#node-publish-job) your own packages.

You may configure additional [scoped registries](https://docs.npmjs.com/cli/v8/using-npm/scope#associating-a-scope-with-a-registry) with the `$NODE_CONFIG_SCOPED_REGISTRIES` variable.
The value is expected as a (whitespace-separated) list of `@registry-scope:registry-url`.

The Node.js template also supports authentication tokens for each, simply by defining `NODE_REGISTRY_<SCOPE>_AUTH` (as project or group secret variables).

:warning: The `<SCOPE>` part is the `registry-scope` transformed in [SCREAMING_SNAKE_CASE](https://en.wikipedia.org/wiki/Snake_case) (uppercase words separated by underscores).

Example: 

```yml
variables:
  NODE_CONFIG_SCOPED_REGISTRIES: "@public-repo:https://public.npm.registry/some/repo @my-priv-repo:https://private.npm.registry/another/repo"
  # NODE_REGISTRY_MY_PRIV_REPO_AUTH set as a project secret variables
```

## Jobs

### `node-lint` job
@@ -303,3 +332,61 @@ It is bound to the `test` stage, and uses the following variables:
| `NODE_SBOM_DISABLED` | Set to `true` to disable this job | _none_ |
| `NODE_SBOM_VERSION` | The version of @cyclonedx/cyclonedx-npm used to emit SBOM | _none_ (uses latest) |
| `NODE_SBOM_OPTS` | Options for @cyclonedx/cyclonedx-npm used for SBOM analysis | `--omit dev` |

### `node-publish` job

This job [publishes](https://docs.npmjs.com/cli/v8/commands/npm-publish) the project packages to a npm registry.

This job is bound to the `publish` stage and is disabled by default.
When enabled, it is executed on a Git tag with a semantic versioning pattern (`v?[0-9]+\.[0-9]+\.[0-9]+`, _configurable_).

It uses the following variables:

| Name                       | description                                                                 | default value     |
|----------------------------|-----------------------------------------------------------------------------|-------------------|
| `NODE_PUBLISH_ENABLED`     | Set to `true` to enable the publish job                                     | _none_ (disabled) |
| `NODE_PUBLISH_ARGS`        | npm [publish](https://docs.npmjs.com/cli/v8/commands/npm-publish) extra arguments<br/>yarn [publish](https://classic.yarnpkg.com/lang/en/docs/cli/publish/) extra arguments | _none_ |
| :lock: `NODE_PUBLISH_TOKEN`| npm publication registry authentication token                              | _none_ |

#### Configure the target registry

The target registry url for publication shall be configured in the `publishConfig` of your `package.json` file. Examples:

* for an unscoped package:
    ```json
    {
      "name": "my-package",
      // ...
      "publishConfig": {
        "registry": "https://registry.acme.corp/npm"
      }
      // ...
    }
    ```
* for a scoped package:
    ```json
    {
      "name": "@acme/my-package",
      // ...
      "publishConfig": {
        "@acme:registry": "https://registry.acme.corp/npm"
      }
      // ...
    }
    ```

Then simply declare the registry authentication token with :lock: `NODE_PUBLISH_TOKEN`.

:information_source: it is not mandatory to declare the registry if you wish to use the GitLab 
project-level npm packages registry (it is declared by default by the template, with the required credentials). All you have to do to is to make sure your npm package name 
 [uses the right scope](https://docs.gitlab.com/ee/user/packages/npm_registry/#naming-convention).
For example, if your project is `https://gitlab.example.com/my-org/engineering-group/team-amazing/analytics`, the root namespace is `my-org`, and your package name must have the `@my-org` scope (probable package fullname: `@my-org/analytics`).

#### Exclude resources from package

Don't forget to exclude undesired folders and files from the package resources (simply add them to your `.gitignore` or `.npmignore` file):

* the `.npm/` or `.yarn/` folder, that is used internally by the Node template to store `npm` or `yarn` cache (depending on the package manager you're actually using),
* the `reports/` folder, that is used by most _to be continuous_ to output all kind of reports,
* the Node.js build output dir (if any),
* any other undesired file & folder that you don't want to appear in your published package(s).
+23 −0
Original line number Diff line number Diff line
@@ -33,6 +33,11 @@
      "default": "src",
      "advanced": true
    },
    {
      "name": "NODE_CONFIG_SCOPED_REGISTRIES",
      "description": "Space separated list of NPM [scoped registries](https://docs.npmjs.com/cli/v8/using-npm/scope#associating-a-scope-with-a-registry) (formatted as `@somescope:https://some.npm.registry/some/repo @anotherscope:https://another.npm.registry/another/repo`)",
      "advanced": true
    },
    {
      "name": "NODE_BUILD_ARGS",
      "description": "npm [run script](https://docs.npmjs.com/cli/v8/commands/npm-run-script) arguments - yarn [run script](https://classic.yarnpkg.com/en/docs/cli/run) arguments",
@@ -116,6 +121,24 @@
          "advanced": true
        }
      ]
    },
    {
      "id": "publish",
      "name": "Publish",
      "description": "[publishes](https://docs.npmjs.com/cli/v8/commands/npm-publish) the project package to a npm registry",
      "enable_with": "NODE_PUBLISH_ENABLED",
      "variables": [
        {
          "name": "NODE_PUBLISH_ARGS",
          "description": "npm [publish](https://docs.npmjs.com/cli/v8/commands/npm-publish) extra arguments - yarn [publish](https://classic.yarnpkg.com/lang/en/docs/cli/publish/) extra arguments",
          "advanced": true
        },
        {
          "name": "NODE_PUBLISH_TOKEN",
          "description": "npm publication registry authentication token",
          "secret": true
        }
      ]
    }
  ]
}
+74 −0
Original line number Diff line number Diff line
@@ -86,6 +86,8 @@ variables:
  PROD_REF: '/^(master|main)$/'
  # default integration ref name (pattern)
  INTEG_REF: '/^develop$/'
  # default release tag name (pattern)
  RELEASE_REF: '/^v?[0-9]+\.[0-9]+\.[0-9]+$/'

# ==================================================
# Stages definition
@@ -93,6 +95,7 @@ variables:
stages:
  - build
  - test
  - publish

.node-scripts: &node-scripts |
  # BEGSCRIPT
@@ -281,6 +284,38 @@ stages:
    fi
  }

  function configure_scoped_registries() {
    # declare GitLab project-level registry
    gitlab_registry_no_proto="//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/"
    # /!\ always use npm config, as yarn uses it...
    npm config set "@${CI_PROJECT_ROOT_NAMESPACE}:registry" "${CI_SERVER_PROTOCOL}:${gitlab_registry_no_proto}"
    npm config set "${gitlab_registry_no_proto}:_authToken" "${CI_JOB_TOKEN}"
    log_info "GitLab project-level registry configured..."
    log_info "  ... it can be freely used to install/publish packages with scope \\e[33;1m@${CI_PROJECT_ROOT_NAMESPACE}\\e[0m"

    # declare extra scoped registries
    if [[ "$NODE_CONFIG_SCOPED_REGISTRIES" ]]
    then
      log_info "Configuring extra scoped registries..."
      for scoped_registry in $NODE_CONFIG_SCOPED_REGISTRIES
      do
        reg_scope=${scoped_registry%%:*}
        reg_url=${scoped_registry#*:}
        log_info "  ... set scope \\e[33;1m${reg_scope}\\e[0m with registry: $reg_url"
        # /!\ always use npm config, as yarn uses it...
        npm config set "${reg_scope}:registry" "${reg_url}"
        reg_scope_ssc=$(echo "$reg_scope" | tr '[:lower:]' '[:upper:]' | tr -d '@' | tr '[:punct:]' '_')
        reg_auth=$(eval echo "\$NODE_REGISTRY_${reg_scope_ssc}_AUTH")
        if [[ "${reg_auth}" ]]
        then
          reg_url_no_proto=${reg_url#*:}
          log_info "  ... set auth token for scope \\e[33;1m${reg_scope}\\e[0m registry"
          npm config set "${reg_url_no_proto%/}/:_authToken" "${reg_auth}"
        fi
      done
    fi
  }

  function sonar_lint_report() {
    if [[ "$SONAR_HOST_URL" ]] || [[ "$SONAR_URL" ]]
    then
@@ -291,6 +326,29 @@ stages:
    fi
  }

  function configure_publish() {
    # get package scope+name, and target registry url
    pkg_fullname=$(node -pe "require('./package.json').name")
    publish_registry_key=$(node -pe "parts='$pkg_fullname'.split('/');parts.length>1?parts[0]+':registry':'registry'")
    publish_registry_url=$(node -pe "require('./package.json').publishConfig?.['$publish_registry_key'] || ''")
    if [[ -z "$publish_registry_url" ]]
    then
      log_info "No publish registry url found in your package.json file"
      log_info "Publish will only work if you're using GitLab project-level registry (use scope \\e[33;1m@${CI_PROJECT_ROOT_NAMESPACE}\\e[0m)"
      log_info "Otherwise, declare the target registry url under \\e[33;1m'publishConfig' > '$publish_registry_key'\\e[0m key in your package.json file..."
    else
      log_info "Publish to \\e[33;1m$publish_registry_url\\e[0m..."
      # maybe configure token
      if [[ "$NODE_PUBLISH_TOKEN" ]]
      then
        publish_registry_no_proto=${publish_registry_url#*:}
        # /!\ always use npm config, as yarn uses it...
        npm config set "${publish_registry_no_proto%/}/:_authToken" "$NODE_PUBLISH_TOKEN"
        log_info " ... auth token set"
      fi
    fi
  }

  unscope_variables

  # ENDSCRIPT
@@ -318,7 +376,9 @@ stages:
    - cd ${NODE_PROJECT_DIR}
    - guess_node_manager_system
    - config_registry=${NODE_CONFIG_REGISTRY:-$NPM_CONFIG_REGISTRY}
    # NPM_CONFIG_REGISTRY is not supported by old npm versions: force with cli
    - if [[ "$config_registry" ]]; then $NODE_MANAGER config set registry $config_registry; fi
    - configure_scoped_registries
    - |
      case "$NODE_MANAGER" in
      npm)
@@ -463,3 +523,17 @@ node-sbom:
    - if: '$NODE_SBOM_DISABLED == "true"'
      when: never
    - !reference [.test-policy, rules]

node-publish:
  extends: .node-base
  stage: publish
  script:
    - configure_publish
    - if [[ "$NODE_MANAGER" == "yarn" ]]; then _publish_opts="--non-interactive"; fi
    - $NODE_MANAGER publish ${TRACE+--verbose} $_publish_opts ${NODE_PUBLISH_ARGS}
  rules:
    # exclude if $NODE_PUBLISH_ENABLED not set
    - if: '$NODE_PUBLISH_ENABLED != "true"'
      when: never
    # on tag with release pattern: auto
    - if: '$CI_COMMIT_TAG =~ $RELEASE_REF'