本文来自:
任治桐 极狐(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 config
echo "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.yml
stages:
- lint
workflow:
rules:
- if: $CI_MERGE_REQUEST_IID
- if: $CI_COMMIT_TAG
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
default:
image: node:16
cache:
paths:
- node_modules/
- .yarn
.yarn_install:
before_script:
- yarn install --frozen-lockfile --check-files --cache-folder .yarn
lint: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
- release
workflow:
rules:
- if: $CI_MERGE_REQUEST_IID
- if: $CI_COMMIT_TAG
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
default:
image: node:16
cache:
paths:
- node_modules/
- .yarn
.yarn_install:
before_script:
- yarn install --frozen-lockfile --check-files --cache-folder .yarn
lint: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
lint:prettier:
extends: .yarn_install
stage: lint
script:
- yarn lint:prettier
jest:test:
extends: .yarn_install
stage: test
script:
- yarn test
semantic-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 Requests
semantic-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 公布更新更及时,让研发工作更高效!爱钻研的小明,浅尝到了精英效力的微妙味道。😁