1 背景
目前部门维护自己的组件库(miui-desgin),并发布到了公司私有的npm库(由JFrog artifactory搭建)。组件库几乎是照搬了antd的文件组织结构和打包方式,而组件的使用说明文档也是使用了dumi。而组件库和文档的发布仍采取本地执行命令并发布的方式,虽然前期制定了发布流程和规范,但从结果上看,依然的存在很多的问题。例如:发布环境不统一、发布流程无法保证、发布流程需要一定的学习成本、每次发布都需要一定的时间成本等。并且随着项目越来越复杂,参与人数越来越多,以上问题会表现的更加的突出,最终导致的正式包内容的不可控。 
而要解决解决手动发布组件库的问题,可以借助Gitlab CICD的能力,将组件库的发布和文档的部署过程搬到线上来执行。不过由于大部分的开源项目在Github上,且npm库的发布也并没有采用自动化,因此可以参考的开源组件自动发布流程并不多。不过借助网络上的零散资料和自身部门对组件库管理的规范,整理出如下的UI库自动发布方案。
2 方案
2.1 手动发布流程
组件库的发布包含两项发布工作,其一是UI库的发布,其二是组件使用文档的发布。根据部门制定的组件库发布规范,手动发的流程大体如下图所示:
 UI组件库接入Gitlab CICD实践_Gitlab CICD](https://file.cfanz.cn/uploads/png/2022/06/13/15/22V319402C.png)
⚠ 说明:
- 更新version:由发布者按照语义版本控制规范自行修改package.json中的version字段的属性值。
- 添加版本tag:按版本号加v表示tag名称(例如v1.1.1)。
2.2 发布流程拆分
根据部门目前UI库的发布过程,结合Gitlab的CICD能力,可以将整个发布流程拆分为三个任务,分别是代码测试任务、UI库发布任务和文档打包部署任务。每个任务具体负责的工作如下图所示: 
 UI组件库接入Gitlab CICD实践_Gitlab CICD_02](https://file.cfanz.cn/uploads/png/2022/06/13/15/S4VcJC7fY9.png)
相比于手动发布流程,自动发布流程添加了code test和推送release到Gitlab两个子任务,这是为了更规范的发布UI库和文档。 
2.3 自动发布流程
借助Gitlab的CICD能力,当代码合并到master之后,会自动触发工作流。然后工作流会依次执行代码测试任务、UI库发布任务和文档打包部署任务。同时为了保证UI库和文档发布的严谨,在UI库发布任务之前添加了审批环节,需要手动触发该任务的执行并经过审批后,才会执行UI库的发布和文档的部署。 
 UI组件库接入Gitlab CICD实践_Gitlab CICD_03](https://file.cfanz.cn/uploads/png/2022/06/13/15/f8W0c33V92.png)
 UI组件库接入Gitlab CICD实践_Gitlab CICD_04](https://file.cfanz.cn/uploads/png/2022/06/13/15/6RNcD5aPc5.png)
2.4 UI库版本生成规则
UI库的发布过程借助了semantic-release工具,其完成了UI库发布任务中除了打包外的所有子任务。UI库版本的生成严格执行语义版本控制规范(X.Y.Z),并通过分析上次发布代码(当前最新tag的代码)到当前代码新增的commit message来确定待发布的版本号。 
版本生成规则如下:
- fix或perf类型的提交,会将PATCH版本号加1
- feat类型的提交,会将MINOR版本号加1
- 包含breaking change字符串的提交,会将MAJOR号加1
如果一次发布过程中包含多个提交,则使用所有提交对应版本号中的最大值作为待发布的版本号。例如原版本号为1.2.3,当前代码相比于1.2.3的代码新增了1个fix和1个feat提交,当前代码的版本号为1.3.0(即1.2.4和1.3.0中取较大的值)。 
如果一次发布过程中的提交都没有命中版本生成规则,UI库发布的任务依然会执行,但由于没有UI库要发布的内容,会跳过本次的发布(不执行push、release、publish等操作)。从实际效果上看,这种情况下只会执行代码测试和文档的打包部署。这种场景对于只修改和发布说明文档很有意义,可以避免无意义的UI库版本的升级。 
2.5 代码推送权限控制
为了更好的规范代码提交和发布流程,应该禁止普通开发者直接往master提交代码,而是通过新建分支进行开发,之后再发送merge请求合并到master,经过代码review和代码测试之后,最终合并进入master,并触发组件库发布流程。Gitlab本身支持设置保护分支和允许推送到master的角色,一般情况下,可以设置只允许Maintainers提交代码到master,这样普通开发者便无法直接提交到master。
 UI组件库接入Gitlab CICD实践_semantic-release_05](https://file.cfanz.cn/uploads/png/2022/06/13/15/E7ORTN3a56.png)
但由于历史原因,部门成员所在的组整体被赋予了Maintainers角色,为了禁止开发者往master提交代码,roles需要选择No one,而这又会导致release-bot反向推送到master失败。不过除了设置角色之外,Gitlab还支持选择deploy key。
 UI组件库接入Gitlab CICD实践_自动发布_06](https://file.cfanz.cn/uploads/png/2022/06/13/15/Z6Xc62933b.png)
可以按照官方文档的提示生成并配置deploy key,同时将私钥配置到项目CICD配置的环境变量中.。通过yml文件的如下配置,可以在UI库发布阶段,使用deploy key来反推代码到master。
before_script
mkdir -p <sub>/.ssh
    # 将生成私钥写入.ssh中
echo "$RELEASE_PRIVATE_KEY" > </sub>/.ssh/id_ed25519; chmod 0600 ~/.ssh/id_ed25519
echo "StrictHostKeyChecking no " > /root/.ssh/config
    # 重置origin
git remote rm origin
git remote add origin git@$CI_SERVER_HOST:$CI_PROJECT_PATH.git
npm config set registry https://xxxx.xiaomi.net/xxxx/npm/mi-npm3 流程源码
3.1 依赖
项目使用semantic-release工具来发布UI库,需要提前安装以下的依赖。
- @semantic-release
- @semantic-release/commit-analyzer
- @semantic-release/release-notes-generator
- @semantic-release/changelog
- @semantic-release/gitlab
- @semantic-release/npm
- @semantic-release/git
3.2 CICD环境变量
需要配置的环境变量有三个:
- NPM_TOKEN:@semantic-release/npm发布包到私有源会默认读取该参数。
- GITLAB_TOKEN:@semantic-release/gitlab发布release到Gitlab会读取该参数。
- RELEASE_PRIVATE_KEY:deploy key对应的私钥,用于反向推送版本修改信息和changelog到master。
3.3 YML配置
# 规则
.rules
&is-merge-request-to-production
    if$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == $PRODUCTION_BRANCH
  # 生产分支 + 不是由发布机器人反推的commit
&is-release
    if$CI_COMMIT_REF_NAME != $PRODUCTION_BRANCH || $CI_COMMIT_TITLE =<sub> /^chore\srelease\s/
    whennever
# 设置环境变量默认值
variables
  # 打包反向推送tag、changelog的git用户名
  GIT_AUTHOR_NAME'release-bot'
  PRODUCTION_BRANCH'master'
  # 审批人
  CI_APPROVERxxx@xiaomi.com
default
  imagenode14-alpine
  before_script
    # 设置为小米私有源
npm config set registry https://xxxx.xiaomi.net/xxxx/npm/mi-npm
   
cache
  key$CI_PROJECT_ID
  paths
node_modules
stages
test
publish_npm
deploy_docs
test
  stagetest
  script
yarn
yarn test
  rules
*is-merge-request-to-production
*is-release
whenon_success
build_and_publish_npm
  stagepublish_npm
  # node14 + git 环境
  imagetimbru31/node-alpine-git14
  approvaltrue
  before_script
mkdir -p </sub>/.ssh
echo "$RELEASE_PRIVATE_KEY" > <sub>/.ssh/id_ed25519; chmod 0600 </sub>/.ssh/id_ed25519
echo "StrictHostKeyChecking no " > /root/.ssh/config
git remote rm origin
git remote add origin git@$CI_SERVER_HOST:$CI_PROJECT_PATH.git
npm config set registry https://xxxx.xiaomi.net/xxxx/npm/mi-npm
  script
yarn
yarn build
yarn semantic-release
  rules
*is-release
whenmanual
build_and_deploy_docs
  stagedeploy_docs
  script
yarn
yarn deploy:site
  rules
*is-release
whenon_success3.4 releaserc配置
{
    "branches": ["master"],
    "plugins": [
      "@semantic-release/commit-analyzer",
      "@semantic-release/release-notes-generator",
      [
        "@semantic-release/changelog",
        {
          "changelogFile": "CHANGELOG.md"
        }
      ],
      "@semantic-release/gitlab",
      "@semantic-release/npm",
      [
        "@semantic-release/git",
        {
          "assets": ["package.json", "CHANGELOG.md"],
          "message": "chore: release ${nextRelease.version}"
        }
      ]
    ]
  }4 结语
通过以上的改造,基本上解决了手动发布带来的所有问题,使得整个发布过程变得更加的规范可控。同时对于开发者来说,也无需花费时间来学习发布的流程,组件库的发布对于开发者来说只需要点击按钮申请发布就可以了。需要注意的是,在该流程下,changelog和版本号的生成严重的依赖commit message,为了更好的规范commit message的格式,可以借助lint-staged插件在代码提交时对其进行校验(miui-design项目中已添加该配置,因此本文不再赘述)。
不过以上的方案也存在一个问题,就是只能发布正式版本的包而无法发布alpha或者beta版本的包,我猜测这可能也是开源库没有采用自动化发布的原因之一。不过由于目前部门维护的组件库并不会发布alpha或者beta版本,因此暂时没有这个问题,以后有机会再研究一下。










