最近在把公司內部用的一個庫釋出到內網的npm私服上,僅僅是釋出的話是比較簡單的,但這個庫是由多個人一起維護的,而且npm私服只有一套,所以生產環境和開發環境,用的是同一個,那麼,我們的需求來了:如何設計npm包的開發流程和自動化釋出流程。 這一套流程我們想要的達成如下目標
- 支援團隊協作,有開發提交程式碼,其他人能及時同步到最新程式碼
- 能夠支援常規迭代和hotfix上線
- 同一版本開發、測試、生產環境的引用包的版本號都是一致的
- 自動化釋出,減少人工操作
先說下如何搭建npm私服和如何發包,不過這不是我們這篇要講的重點,所以我們就簡單介紹
npm私服搭建
目前比較主流的有 nexus
和 Verdaccio
,因為 Verdaccio
要更輕量些,而且也滿足我們的需求,所以我們選擇它。 Verdaccio
搭建非常簡單,直接使用預設配置就行
npm install -g verdaccio
or
yarn global add verdaccio
verdaccio
複製程式碼
就這樣,私服就搭建完成啦。
npm包釋出
有了私服,我們先註冊到本地的npm中
npm set registry http://localhost:4873
然後新增使用者,如果是首次,則會新建使用者
npm adduser --registry http://localhost:4873
最後是釋出
npm publish
設定版本號有兩種方式,一種是人工修改 package.json
中的 version
// package.json
{
"name": "project",
"version": "1.0.0",
}
複製程式碼
另一種是通過 npm version
來修改版本號,提供有這如下引數
npm version [<newversion> | major | minor | patch | premajor | preminor | prepatch | prerelease [--preid=<prerelease-id>] | from-git]
複製程式碼
詳細解釋如下:
newversion:自定義版本號,除了已經發布的版本號,想寫啥寫啥
major:升級主版本號 1.0.0 -> 2.0.0
minor:升級次版本號 1.0.0 -> 1.1.0
patch:升級補丁號 1.0.0 -> 1.0.1
premajor:預備主版本
preminor:預備次版本
prerelease:預釋出版本
複製程式碼
在執行npm version
後,會產生一條新的提交記錄,比如說執行 npm version 2.0.0
完後 ,檢視log,會發現一條 commit message 為 2.0.0
的提交記錄,至於為啥會生成這條記錄呢?很簡單,因為npm version
這行命令其實是修改了 package.json 中的 version ,修改後並提交,所以就有這條新的提交記錄。要是想自定義提交記錄,可以這麼寫 npm version 2.0.0 -m "Upgrade to %s for reasons"
其中%s
就是修改後的版本號。
gitlab CI
我們打算使用 gitlab CI 來實現自動化釋出,我們也簡單介紹下 gitlab CI 的一些步驟和配置項。
- 專案開啟CI,選擇一個公共的 runner,要是沒有,那就只能自己弄一個。
- 專案根目錄新建
.gitlab-ci.yml
,寫入配置,詳細的配置可以檢視 gtilab官網
# 定義 stages
stages:
- build
定義 job
job1:
stage: build
script:
- echo 'xxx'
複製程式碼
- 配置完成
設計階段
我們要說的重點來啦,通過畫圖的方式來給大家介紹我們的設計思路。先來設定些約定
分支的約定
專案的分支的規劃,設定三個分支
feature
分支:常規功能迭代master
分支:穩定分支hotfix
分支:當有緊急修改時,建立該分支,作為臨時修復分支,這樣可以不影響常規功能的開發
版本號的約定
我們採用 大版本:小版本:次版本號
這種方式,每一次feature分支上的每次提交都觸發此版本號升級,比如當前feature分支的版本號是 1.1.5
那麼下一次提交就會升級為 1.1.6
併發布npm,小版本號跟著常規需求迭代往上升級,比如說當前是 1.1.8
,週期性的需求在明天上線,那麼上線後,版本號就升級為 1.2.0
併發布npm,大家發現 1.1.8
和 1.2.0
是同樣的程式碼,沒錯,之所以這麼做是因為小版本號對應的是一個週期的常規修改,那麼升級的 1.2.0
就是作為下一次的常規需求的版本號,這樣一來,此版本號對應的是每一次提交,小版本號對應的是當前的開發階段,這兩個我們都可以通過CI來觸發修改,不需要人工參與 ,剩下還有個大版本號,這個只有在大改版的情況下才由人工修改,一般來說升級大版本號的頻率是比較低的,人工來來修改完全是OK的。
大家會發現每一次提交就觸發 npm version patch & npm publish
感覺太頻繁了,但為了能滿足團隊協作,只好做些小小的讓步。所以此版本號在這裡的作用並不是用來區分版本的,小版本號才是真正用來做版本區分的,那麼在引用這個npm就要這麼來控制版本號,舉個栗子
"my-package": "~1.2.0"
複製程式碼
鎖定大版本號和小版本號,不管我們開發過程中提交了多少次,我們引用都是最新的。
時序圖
畫了開發、釋出的時序圖,如下。
開發流程時序圖
開發時序圖如下
釋出流程時序圖
編碼實現
主要是CI的編碼,如下
# .gitlab-ci.yml
# 定義 stages
stages:
- publish
# 定義 job
job2:
stage: publish
before_script:
- cd /home/node/MY #進到專案目錄
- git checkout .
- git checkout $CI_COMMIT_REF_NAME || git checkout -b $CI_COMMIT_REF_NAME
- git pull -f -X theirs origin $CI_COMMIT_REF_NAME
- yarn
script:
- node publish.js
複製程式碼
// publish.js
var shell = require('shelljs');
var git = require('git-last-commit');
var featureBranchName = 'feature-npm';
// 判斷文字是否是版本號格式
function checkCommitMessage(subject) {
return /^\d+.\d+.\d+$/g.test(subject)
}
// 獲取最近一次提交,判斷是否是版本號格式,若不是,則進行釋出,
git.getLastCommit(function(err, commit) {
console.log(commit);
const { subject, sanitizedSubject } = commit;
shell.exec('echo $CI_COMMIT_REF_NAME');
if (!checkCommitMessage(subject)) {
if (sanitizedSubject.indexOf(`Merge-branch-${featureBranchName}-into-master`) > -1) {
shell.exec('git checkout .');
shell.exec(`git checkout ${featureBranchName} || git checkout -b ${featureBranchName}`);
shell.exec(`git pull -f -X theirs origin ${featureBranchName}`);
shell.exec('npm version minor');
shell.exec('npm publish');
shell.exec(`git push origin ${featureBranchName}`);
} else {
shell.exec('npm version patch');
shell.exec('npm publish');
shell.exec('git push origin $CI_COMMIT_REF_NAME');
}
}
});
複製程式碼
// package.json
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"prepublish": "build script"
},
複製程式碼
釋出指令碼里有個函式 checkCommitMessage
用於判斷最新的提交message是不是版本號格式,因為我們最後會有一步push的操作,push後會再一次觸發CI,所以為了防止死迴圈,我們通過提交的message作為是否觸發CI的依據,這麼一來我們就要規範message的格式,我的建議是按照這個格式 U ${修改的具體的內容}
。
小結
這個方案不是個普適性的方案,畢竟每個團隊的開發流程都不一樣,目前這個方案是適合我們目前的場景的。如果有小夥伴的場景跟我們的一致,可以嘗試下,如果小夥伴們有更好的方案,歡迎一起交流呀~