本文来自:
任治桐 极狐(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 公布更新更及时,让研发工作更高效!爱钻研的小明,浅尝到了精英效力的微妙味道。