其他

2022.08.02

在 gitlab push 後自動更新版本號與發布 release

需求

最近公司的專案需要上版號,我就突然想到 git 的 release 這個功能。

剛好公司用的是 gitlab,所以希望能搭配 gitlab 的 CI/CD 自動化,在我 push 專案時就自動更新版號 + 更新 CHANGELOG.md + 自動發布 (release)

研究了兩天,終於做成了一個初步的雛形,有興趣的朋友可以參考看看。

申請 token

如果要使用 gitlab 的 CI/CD 功能,需要申請他們的 api token。進入 gitlab 網站後,右上角頭像點選「Edit Profile」➡️「Access Tokens」申請一個 Token name

Scope 可以選擇 api, read_repository, 以及 write_repository 三種即可 (不過我自己是全選 😂)

接著到要自動發佈版本的專案中,選擇「Settings」➡️「CI/CD」➡️「Variables」,將剛剛申請的 token 貼上,Key 欄位寫 GL_TOKEN

撰寫 yml 檔案

接著新增 .gitlab-ci.yml,這個檔案是用來設定 CI/CD 該做哪些指令的檔案,有用過 docker 的朋友應該不陌生。以下只放跟 release 有關的 job:

stages:
- release

release:
  stage: release
  image: node:lts
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH  # 當預設的分支(通常是 main / master)有 push 或 merge 時才會執行該工作
  script:
    - npm install semantic-release @semantic-release/gitlab @semantic-release/changelog @semantic-release/git
    - export GL_TOKEN=${GL_TOKEN}
    - GL_TOKEN=${GL_TOKEN} npx semantic-release
  tags:
    - docker

修改 package.json

有時候 gitlab 的 owner 可能不是你,這個 git 也不是你開的,所以我會在 package.json 將 git 的資訊寫在上面,有時候可以避免 gitlab api 的權限問題:

  "repository": {
    "type": "git",
    "url": "https://gitlab.com/xxx/xxx"
  },

安裝套件

再來要安裝 semantic-release 的相關套件,可以參考在 .gitlab-ci.yml 設定的 npm install 語法:

$ npm install semantic-release @semantic-release/gitlab @semantic-release/changelog @semantic-release/git

新增 release.config.js

semantic-release 的相依套件:commit-analyzer 可以設定符合什麼樣的 commit 規範下,進行 release 的動作,詳細的檔案可以參考 commit-analyzerreadme,裡面寫的蠻詳細的。

GitHub - semantic-release/commit-analyzer: semantic-release plugin to analyze commits with conventional-changelog

GitHub – semantic-release/commit-analyzer: semantic-release plugin to analyze commits with conventional-changelog

:bulb: semantic-release plugin to analyze commits with conventional-changelog – GitHub – semantic-release/commit-analyzer: semantic-release plugin to analyze commits with conventional-changelog

https://github.com/semantic-release/commit-analyzer

而我們在 release.config.js,可以設定 commit 的規則,也可以調整 release 的 template。

預設的 release 規則可以參考這個 js 檔案:https://github.com/semantic-release/commit-analyzer/blob/master/lib/default-release-rules.js

以下的 template 是直接拿 conventional-changelog 這個套件的 function 來調整,有附上原始的 github 位置,供大家參考!

parserOpts = {
  mergePattern: /^Merge pull request #(\d+) from (.*)$/,
  mergeCorrespondence: ["id", "source"]
}

// Copied from https://github.com/conventional-changelog/conventional-changelog/blob/master/packages/conventional-changelog-angular/writer-opts.js#L27
// and modified to support adding all commit types to the release notes
customTransform = (commit, context) => {
  const issues = []

  commit.notes.forEach(note => {
    note.title = `BREAKING CHANGES`;
  })

  if (commit.type === `feat`) {
    commit.type = `✨ Features`
  } else if (commit.type === `fix`) {
    commit.type = `🐞 Bug Fixes`
  } else if (commit.type === `perf`) {
    commit.type = `🎈 Performance Improvements`;
  } else if (commit.type === `revert`) {
    commit.type = `Reverts`
  } else if (commit.type === `docs`) {
    commit.type = `📃 Documentation`
  } else if (commit.type === `style`) {
    commit.type = `🌈 Styles`
  } else if (commit.type === `refactor`) {
    commit.type = `🦄 Code Refactoring`
  } else if (commit.type === `test`) {
    commit.type = `🧪 Tests`
  } else if (commit.type === `build`) {
    commit.type = `🔧 Build System`
  } else if (commit.type === `ci`) {
    commit.type = `🐎 Continuous Integration`
  } else {
    return
  }

  if (commit.scope === `*`) {
    commit.scope = ``
  }

  if (typeof commit.hash === `string`) {
    commit.shortHash = commit.hash.substring(0, 7)
  }

  if (typeof commit.subject === `string`) {
    commit.subject = commit.subject.substring(2)
    let url = context.repository
      ? `${context.host}/${context.owner}/${context.repository}`
      : context.repoUrl;
    if (url) {
      url = `${url}/issues/`
      // Issue URLs.
      commit.subject = commit.subject.replace(/#([0-9]+)/g, (_, issue) => {
        issues.push(issue)
        return `[#${issue}](${url}${issue})`
      });
    }
    if (context.host) {
      // User URLs.
      commit.subject = commit.subject.replace(
        /\B@([a-z0-9](?:-?[a-z0-9/]){0,38})/g,
        (_, username) => {
          if (username.includes("/")) {
            return `@${username}`
          }
          return `[@${username}](${context.host}/${username})`
        }
      )
    }
    commit.subject = `${commit.subject} (by @${commit.committer.name})`
  }

  // remove references that already appear in the subject
  commit.references = commit.references.filter(reference => {
    if (issues.indexOf(reference.issue) === -1) {
      return true
    }

    return false
  });

  return commit
};

module.exports = {
  branches: 'main',
  parserOpts,
  writerOpts: { transform: customTransform },
  plugins: [
    "@semantic-release/commit-analyzer", { preset: 'angular' },
    "@semantic-release/release-notes-generator",
    '@semantic-release/changelog', { changelogFile: 'CHANGELOG.md' },
    "@semantic-release/git",
    '@semantic-release/npm',
    '@semantic-release/gitlab',
    {
      assets: [
        'package.json',
        'packag-lock.json',
        'CHANGELOG.md'
      ]
    }
  ]
}

我修改的地方如下:

  1. release 的 template 中,加上原本有在用的 emoji。
  2. 移除列表的 emoji (不然會太多 emoji)
  3. 增加 commit 的發佈者 (commiter)

如何使用

以上全部設定好後,我們只要在 main 分支有任何更動 (push / merge),就會自動進入 CI/CD。再來會根據你的 commit type 自動跳版號。

版號的規範可以參考 Conventional Commits

進入 CI/CD 後,如果設定都正常,他會從 running 顯示成 passed

進入 gitlab 的 release 觀看,也會自動幫你 release,並且加上之前的 git commit 紀錄。

如果有對應到 patch / minor / major 的話,就會自動跳版號。

如何加上 emoji

這個是我卡了很久的部分,兩天的研究中,大概有一半的時間都在處理這個 XDD。

因為我用的 commit 規範是 angular,而他的 template 是 <type> 開頭,而我以往的 commit template 是 <icon><type>

所以我的 type 和 emoji 不管怎麼設定都不會匹配,我甚至把 icon 寫成 code,把 type 做成正則表達式,依然是沒有匹配。

最後我只好從 vscode 的擴充套件下手,直接去修改我的 commit template。

我用的擴充套件是 git-commit-plugin

git-commit-plugin - Visual Studio Marketplace

git-commit-plugin – Visual Studio Marketplace

Extension for Visual Studio Code – Automatically generate git commit

https://marketplace.visualstudio.com/items?itemName=redjue.git-commit-plugin

最後我將 icon 移到 subject 的前面,讓 typescope 格式保持不變,這樣他才能抓得到 type,而我也還是能享有 emoji 的美麗 XD。

"GitCommitPlugin.Templates": [
    {
        "templateName": "Angular",
        "templateContent": "<type>(<scope>):<space><icon><space><subject><enter><body><enter><footer>",
        "default": true
    }
  ]

雖然不是一個很好的解法,但是我覺得網路上的資料我都找遍了,這是我目前能想到的最佳解。

讓網站也能自動更新版號

我希望能將版號放在網站上,這邊透過打 Gitlab 的 API 來取得最新的版號。

我將 Gitlab 的 API 文件全部看過一遍,覺得最符合需求的應該是這隻 API

GET /projects/:id/releases

雖然沒有取得最新版號的 API,但可以用上述這隻取得 release 的所有資料,然後再顯示第一筆的 tag name 即可。

onMounted(() => {
  axios.get('https://gitlab.com/api/v4/projects/:id/releases', { headers: { 'PRIVATE-TOKEN': 'YOUR_GL_TOKEN' } }).then((res) => {
    version.value = res.data[0].name
  })
})
Line 2: :id 是 gitlab 的 repo Project ID,可以在 Gitlab Repo 首頁看到 (以下會附圖)
Line 2: YOUR_GL_TOKEN 就是一開始申請的 Access Tokens,我們可以用它來打 Gitlab 的 API

▼ Project ID

Gitlab 的 API 位置

如果是自己架設 Gitlab,那 API 位置就是自架的 Gitlab 位置唷。

後記

以上,如果有任何問題,都歡迎留言討論 😊。

實際案例

08.05 更新:

  1. 移除列表的 emoji
  2. 增加 commit 的發佈者 (commiter)

(已同步更新在前面的 release.config.js)

08.04 更新:

今天用在正式專案上了,漂漂亮亮的 release,看得心情好好喔 XDDD

不過我發現 emoji 有點太多,哈哈,然後希望能在每個 commit 後面加上 date 以及 author,如果有研究出來,再來更新這篇文章!

歡迎給我點鼓勵,讓我知道你來過 :)

82
4
19
guest
0 則留言
Inline Feedbacks
View all comments
Copyright (C) MUKI space* / Reborn Theme All Rights Reserved.