本文来自:
任治桐 极狐(GitLab)  前端工程师

NPM 是 Node.js 的包管理工具,用来装置各种 Node.js 的扩大。本文将分享如何通过极狐GitLab,让 NPM 依赖公布更新更加疾速和自动化,让你轻松治理依赖,领有更多工夫专一于外围工作!

少年小明之懊恼

在开发团队日常工作中,不可避免的会依赖大量第三方模块;同时,团队外部也会公布一些公共模块到外部或内部源中,不便跨团队复用。但往往会遇到和小明一样的懊恼。

小明同学负责外部公共 NPM 模块公布和降级工作。他每天的工作是这样的:

1. 开发同学告诉小明某个公共模块代码有更新;

2. 小明打包外部公共模块;

3. 公布到 NPM 源;

4. 在开发群里告诉各个团队降级到最新版本。

慢慢的,每个团队都被繁琐小事缠身,低效:

  • 开发同学须要常常查看依赖的 NPM 模块是否有更新;
  • 公共模块的保护同学更新代码后须要决定是否公布版本;
  • 版本更新后还须要告诉各个团队;
  • 各个团队还是容易呈现更新不及时、容易脱漏等问题。

终于有一天,小明同学眉头一皱;计上心来:如果当前更新公共模块代码可能依据肯定规定,自动更新版本号→主动公布日志→主动公布到 NPM 源→主动将公司内所有依赖该模块的代码库更新为最新版本,岂不乐哉?

通过一番摸索,小明发现,通过极狐GitLab CI 和第三方工具联合,就能够达到目标。一起实际吧!

NPM 主动公布-操作指南

咱们晓得,NPM 包版本标准为 Semantic Versioning ,即为 major.minor.patch 格局数字组成。

那么,如果咱们能够辨认开发人员的 git commit message ,通过对提交信息进行形式化约定,就能够主动生成新的合乎 Semantic Versioning 的版本号。而后,将新版本号更新到咱们的 package.json 文件中,最初公布到 NPM 源中即可。

commitlint

首先,咱们须要通过 commitlint 或相似工具,强制规范化团队的 git commit message ,通过如下命令将 commitlint 装置到我的项目中:

yarn add --dev @commitlint/cli @commitlint/config-conventional# 设置 commitlint 配置文件应用 conventional configecho "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js

咱们采纳 @commitlint/config-conventional 作为惯例配置,它默认采纳 Angular Commit Message 标准,也可自行批改。默认 git commit message 格局如下:

<type>(<scope>): <short summary>   │  └─⫸ 可选类型: build|ci|docs|feat|fix|perf|refactor|test|chore

流水线配置

之后,须要在极狐GitLab CI 流水线中执行 commitlint ,在我的项目根目录新建一个 .gitlab-ci.yml 文件,增加如下代码:

# .gitlab-ci.ymlstages:  - lintworkflow:  rules:    - if: $CI_MERGE_REQUEST_IID    - if: $CI_COMMIT_TAG    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHdefault:  image: node:16  cache:    paths:      - node_modules/      - .yarn.yarn_install:  before_script:    - yarn install --frozen-lockfile --check-files --cache-folder .yarnlint:commit:  extends: .yarn_install  stage: lint  rules:    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'  script:    - yarn commitlint --from ${CI_MERGE_REQUEST_DIFF_BASE_SHA} --to HEAD --verbose

如果有不合乎 commitlint 标准的 git commit message,极狐GitLab CI 就会阻止该 MR 合入。

这样,咱们就领有了合乎约定的提交信息。

semantic-release

Semantic-release 是一个能够依据约定提交信息类型生成新版本号、极狐GitLab Changelog,还提供了大量插件用于公布 NPM 的工具。

首先装置 semantic-release 到咱们的我的项目:

yarn add --dev semantic-release

与此同时,咱们还想同步 publish 到 NPM 源,接着将更新的版本号推送到主分支,最初创立极狐GitLab Changelog。所以还须要装置如下 semantic-release plugins:

yarn add --dev @semantic-release/changelog @semantic-release/git @semantic-release/gitlab
因为 @semantic-release/npm 插件曾经是 semantic-release 的一部分,所以不须要独自装置,参见文档 。

须要留神的是,通常状况下,咱们只需针对主分支流水线执行:

  • 更新 package.json 版本号;
  • 推送 NPM 源;
  • 生成 changelog。

而在 MR 流水线中,咱们只需确保可能失常生成 changelog 即可。为此,针对 MR 执行 --dry-run 模式,而后在 semantic-release 配置文件中辨别执行步骤。

默认状况下,咱们的提交信息会依照如下规定更新版本号:

残缺的匹配规定参见 default-release-rules.js :

/** * Default `releaseRules` rules for common commit formats, following conventions. * * @type {Array} */module.exports = [  {breaking: true, release: 'major'},  {revert: true, release: 'patch'},  // Angular  {type: 'feat', release: 'minor'},  {type: 'fix', release: 'patch'},  {type: 'perf', release: 'patch'},  // Atom  {emoji: ':racehorse:', release: 'patch'},  {emoji: ':bug:', release: 'patch'},  {emoji: ':penguin:', release: 'patch'},  {emoji: ':apple:', release: 'patch'},  {emoji: ':checkered_flag:', release: 'patch'},  // Ember  {tag: 'BUGFIX', release: 'patch'},  {tag: 'FEATURE', release: 'minor'},  {tag: 'SECURITY', release: 'patch'},  // ESLint  {tag: 'Breaking', release: 'major'},  {tag: 'Fix', release: 'patch'},  {tag: 'Update', release: 'minor'},  {tag: 'New', release: 'minor'},  // Express  {component: 'perf', release: 'patch'},  {component: 'deps', release: 'patch'},  // JSHint  {type: 'FEAT', release: 'minor'},  {type: 'FIX', release: 'patch'},];

如需批改上述规定,就须要增加 semantic-release 配置文件,新建 release.config.js 文件到我的项目根目录下:

const { execSync } = require('child_process');const isDryRun = () => {  return process.argv.includes('--dry-run'); // 通过命令行参数判断以后模式};const getCurrentBranch = () => {  return execSync('git rev-parse --abbrev-ref HEAD').toString().trim();};// MR运行配置const getDryRunConfig = () => {  return {    branches: getCurrentBranch(),    plugins: [      [        '@semantic-release/commit-analyzer',        {          preset: 'conventionalCommits',          releaseRules: [            { type: 'feat', release: 'minor' },            { type: 'revert', release: 'patch' },            { type: 'docs', release: 'patch' },            { type: 'style', release: 'patch' },            { type: 'chore', release: 'patch' },            { type: 'refactor', release: 'patch' },            { type: 'test', release: 'patch' },            { type: 'build', release: 'patch' },            { type: 'ci', release: 'patch' },            { type: 'improvement', release: 'patch' },          ],        },      ],      [        '@semantic-release/release-notes-generator',        {          preset: 'conventionalCommits',          presetConfig: {            types: [              { type: 'feat', section: 'Features' },              { type: 'fix', section: 'Bug Fixes' },              { type: 'perf', section: 'Performance Improvements' },              { type: 'revert', section: 'Reverts' },              { type: 'docs', section: 'Documentation', hidden: false },              { type: 'style', section: 'Styles', hidden: true },              { type: 'chore', section: 'Miscellaneous Chores' },              { type: 'refactor', section: 'Code Refactors', hidden: false },              { type: 'test', section: 'Tests', hidden: true },              { type: 'build', section: 'Build System', hidden: false },              { type: 'ci', section: 'CI/CD', hidden: false },              { type: 'improvement', section: 'Improvements', hidden: false },            ],          },        },      ],    ],  };};// 主分支运行配置const defaultConfig = {  branches: ['main'],  plugins: [    [      '@semantic-release/commit-analyzer',      {        preset: 'conventionalCommits',        releaseRules: [          { type: 'feat', release: 'minor' },          { type: 'revert', release: 'patch' },          { type: 'docs', release: 'patch' },          { type: 'style', release: 'patch' },          { type: 'chore', release: 'patch' },          { type: 'refactor', release: 'patch' },          { type: 'test', release: 'patch' },          { type: 'build', release: 'patch' },          { type: 'ci', release: 'patch' },          { type: 'improvement', release: 'patch' },        ],      },    ],    [      '@semantic-release/release-notes-generator',      {        preset: 'conventionalCommits',        presetConfig: {          types: [            { type: 'feat', section: 'Features' },            { type: 'fix', section: 'Bug Fixes' },            { type: 'perf', section: 'Performance Improvements' },            { type: 'revert', section: 'Reverts' },            { type: 'docs', section: 'Documentation', hidden: false },            { type: 'style', section: 'Styles', hidden: true },            { type: 'chore', section: 'Miscellaneous Chores' },            { type: 'refactor', section: 'Code Refactors', hidden: false },            { type: 'test', section: 'Tests', hidden: true },            { type: 'build', section: 'Build System', hidden: false },            { type: 'ci', section: 'CI/CD', hidden: false },            { type: 'improvement', section: 'Improvements', hidden: false },          ],        },      },    ],    '@semantic-release/changelog', // 仅有主分支须要更新极狐GitLab changelog    '@semantic-release/npm', // 仅有主分支须要npm publish    '@semantic-release/git',    ['@semantic-release/gitlab', { gitlabUrl: 'https://jihulab.com' }], // 须要指定gitlabUrl 为极狐GitLab地址  ],  success: false,  fail: false,};module.exports = isDryRun() ? getDryRunConfig() : defaultConfig;

流水线配置

此时,就能够进入上文提到的 .gitlab-ci.yml 文件,增加 semantic-release 相干的 stage:

stages:  - lint  - test  - releaseworkflow:  rules:    - if: $CI_MERGE_REQUEST_IID    - if: $CI_COMMIT_TAG    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCHdefault:  image: node:16  cache:    paths:      - node_modules/      - .yarn.yarn_install:  before_script:    - yarn install --frozen-lockfile --check-files --cache-folder .yarnlint:commit:  extends: .yarn_install  stage: lint  rules:    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'  script:    - yarn commitlint --from ${CI_MERGE_REQUEST_DIFF_BASE_SHA} --to HEAD --verboselint:prettier:  extends: .yarn_install  stage: lint  script:    - yarn lint:prettierjest:test:  extends: .yarn_install  stage: test  script:    - yarn testsemantic-release:  extends: .yarn_install  stage: release  rules:    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH  script:    - git config --global http.emptyAuth true    - yarn semantic-release# Run a dry run on Merge Requestssemantic-release-dry-run:  needs: ['jest:test']  script:    - git config --global http.emptyAuth true    - yarn semantic-release --branches $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME --dry-run --no-ci  rules:    - if: $CI_MERGE_REQUEST_IID

上面,咱们就须要进入极狐GitLab CI 设置页面,增加对应的变量。

首先返回 https://www.npmjs.com/ 生成 access token 用来公布 NPM 模块:

而后,进入极狐GitLab 我的项目设置 → 拜访令牌生成我的项目 access token,用来推送版本号更新,生成 release note 等:

最初,返回极狐GitLab 我的项目设置 → CI/CD → 变量新增  NPM_TOKEN,GITLAB_TOKEN 两个变量,别离输出方才生成的 npm access token 和 我的项目 access token 。切记设为爱护和暗藏变量,否则会存在 token 泄露的危险。

此时,咱们所有筹备工作都实现了。上面就提交一个 MR 试验一下:

该 MR 蕴含了一个 chore 类型的 commit message ,期待流水线通过后,合入该 MR。此时能够看到创立了一条主分支流水线,并且执行了 semantic-release 将版本号更新到 13.3.2 后 publish 到 NPM 源:

此时进入我的项目发布页面,能够看到 semantic-release 依据提交信息生成的极狐GitLab release-note:

至此,通过极狐GitLab CI 和第三方工具的联合,咱们实现了:

  • 依据提交信息自动更新版本号;
  • 主动公布 NPM 模块;
  • 主动生成极狐GitLab release-note。

新的 NPM 模块公布流程,如下图所示:

上面让咱们持续尝试,优化我的项目依赖 NPM 模块版本更新流程。

NPM 模块自动更新-操作指南

随着咱们依赖的 NPM 模块增多,频繁手动更新版本也是一件麻烦事。上面就来介绍,如何通过极狐GitLab + renovate bot 自动更新第三方依赖版本号。

Renovate Bot

Renovate bot 是一款可能自动更新依赖版本的工具,不仅实用于 NPM 依赖,同样实用于 Docker、Ruby gem 等多种依赖。

Renovate bot 官网提供了一个 renovate-runner 我的项目,来帮忙咱们托管本人的 renovate bot。因为官网的 renovate-runner 位于 GitLab.com 。为了不便大家拜访,我将这个我的项目镜像到了极狐GitLab 里,镜像我的项目地址位于:https://jihulab.com/gitlab-cn/frontend/renovate-runner。

那么如何基于 renovate-runner 我的项目托管本人的 renovate 机器人呢?

1. 咱们须要创立一个本人的 my-renovate-bot 我的项目。我的项目名字可自拟。

2. 在 my-renovate-bot 创立 .gitlab-ci.yml 文件:

include:    - project: 'gitlab-cn/frontend/renovate-runner'      file: '/templates/renovate-dind.gitlab-ci.yml'variables:  RENOVATE_ONBOARDING: 'false'  RENOVATE_REQUIRE_CONFIG: 'ignored'

配置项含意如下:

  • project 配置为 renovate-runner 我的项目地址;
  • file 为咱们须要应用流水线模板;
  • variables 能够对模板中变量的默认值进行笼罩。

3. 在 my-renovate-bot 下创立一个 renovate bot 配置文件 config.js :

module.exports = {    endpoint: 'https://jihulab.com/api/v4/',    platform: 'gitlab',    labels: ['renovate', 'dependencies', 'automated'],    includeForks: true,    extends: ['config:base'],    rangeStrategy: 'pin',    enabledManagers: ["npm", "regex"],    repositories: [{         repository: 'your project path',         bumpVersion: true,        internalChecksFilter: "strict",        stabilityDays: 30,        reviewersFromCodeOwners: true,    }]}

其中 repositories 的配置有两种形式,如上文所示,增加在 config.js 中能够针对不同我的项目应用不同的配置项。如果不须要针对各个我的项目独自定制,则能够在 my-renovate-bot 我的项目设置→ CI/CD 变量中增加 RENOVATE_EXTRA_FLAGS 配置项,以空格分隔多个项目名称即可:

4. 生成 RENOVATE_TOKEN 来不便 renovate bot 拜访我的项目、创立 MR 。如果是集体应用,能够返回集体设置页面创立 Access Token 即可:

5. 为了不便 renovate bot 创立 MR 时携带上本次更新的 changelog,咱们还须要返回 GitHub 生成一个 GITHUB_COM_TOKEN 。

设置定时工作

至此,咱们的配置筹备工作曾经实现。下一步返回 my-renovate-bot 我的项目 → CI/CD → 打算 页面设置定时工作。咱们能够抉择每天凌晨主动执行 renovate bot ,它会主动扫描咱们的 package.json 文件,获取最新依赖版本,而后创立更新版本号的 MR 。

留神:如果咱们抉择自定义执行工夫,须要依照 Cron 语法 输出。大家能够应用这个工具 不便生成本人的 Cron 表达式。

到这里,咱们所有的配置工作曾经实现。上面就尝试触发 renovate bot 进行测试。返回咱们在 config.js 或 RENOVATE_EXTRA_FLAGS 中配置的我的项目 MR 页面,能够看到 renovate-bot 曾经帮忙咱们创立了三个 MR ,别离进行了固定版本号,降级 path 版本,降级 major 版本的操作。

进入 MR 详情,能够看到,renovate bot 帮忙咱们抓取了具体的 changelog 。表格中的字段含意别离是:

  • Age - 该版本公布至今的工夫;
  • Adoption - 该版本在应用 renovate bot 的我的项目中承受装置的比例;
  • Passing - 该版本通过测试的更新的百分比;
  • Confidence - 该版本的可信度。

最初,咱们只须要查看最新的 MR,抉择是否须要合并即可。

总结

如上图所示,咱们曾经实现了通过极狐GitLab 和 renovate bot 联合自动更新 NPM 依赖版本号的性能。

通过和第一局部所述 NPM 主动公布相结合,就能够实现不须要人工染指的 NPM 包主动公布 + 自动更新的工作流了,很大水平上节约开发工夫,缩小沟通老本,让 NPM 公布更新更及时,让研发工作更高效!爱钻研的小明,浅尝到了精英效力的微妙味道。