ci(release): replace standard-version with semantic-release (#1666)

[`standard-version`](https://github.com/conventional-changelog/standard-version/) has been deprecated since May 2022, so it is necessary to stop using it for this project.

[**`semantic-release`**](https://github.com/semantic-release/semantic-release) is available as a more capable alternative to help automate the release process:

1. Updating Node/Gem version numbers
2. Generating changelogs
3. Automating GitHub Releases
4. Building Chirpy-gem and pushing it to RubyGems.org
5. Create commits and tags on the `production` branch
6. Merge the `production` branch into the `master` branch

> ⚠️ Note: Step _6_ may be canceled in CD environments due to merge conflicts, so we need to do this step manually in such cases.

Whenever a commit is pushed to the release branch (`production`), all of the above release processes will be triggered.
This commit is contained in:
Cotes Chung 2024-04-14 05:15:27 +08:00 committed by GitHub
parent 8c1be9f2f3
commit bf16d6039a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 194 additions and 126 deletions

View file

@ -1,17 +1,37 @@
name: CD name: CD
on: on:
push: push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
branches: branches:
- docs - production
tags-ignore:
- "**"
jobs: jobs:
launch: release:
permissions:
contents: write
issues: write
pull-requests: write
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- run: | - uses: actions/checkout@v4
curl -X POST -H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GH_PAT }}" \ - uses: ruby/setup-ruby@v1
https://api.github.com/repos/${{ secrets.BUILDER }}/dispatches \ with:
-d '{"event_type":"deploy", "client_payload":{"branch": "${{ github.ref_name }}"}}' ruby-version: 3.2
bundler-cache: true
- uses: actions/setup-node@v4
with:
node-version: latest
- run: npm install
- run: npx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GEM_HOST_API_KEY: ${{ secrets.GEM_HOST_API_KEY }}
publish:
needs: release
uses: ./.github/workflows/publish.yml

17
.github/workflows/publish.yml vendored Normal file
View file

@ -0,0 +1,17 @@
name: Publish
on:
push:
branches:
- docs
workflow_call:
jobs:
launch:
runs-on: ubuntu-latest
steps:
- run: |
curl -X POST -H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${{ secrets.GH_PAT }}" \
https://api.github.com/repos/${{ secrets.BUILDER }}/dispatches \
-d '{"event_type":"deploy", "client_payload":{"branch": "${{ github.ref_name }}"}}'

View file

@ -1,7 +1,5 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
## [6.5.5](https://github.com/cotes2020/jekyll-theme-chirpy/compare/v6.5.4...v6.5.5) (2024-03-23) ## [6.5.5](https://github.com/cotes2020/jekyll-theme-chirpy/compare/v6.5.4...v6.5.5) (2024-03-23)
### Bug Fixes ### Bug Fixes

View file

@ -28,10 +28,15 @@
"@commitlint/config-conventional": "^19.1.0", "@commitlint/config-conventional": "^19.1.0",
"@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-babel": "^6.0.4",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"conventional-changelog-conventionalcommits": "^7.0.2",
"husky": "^9.0.11", "husky": "^9.0.11",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"rollup": "^4.13.2", "rollup": "^4.13.2",
"rollup-plugin-license": "^3.3.1", "rollup-plugin-license": "^3.3.1",
"semantic-release": "^23.0.8",
"stylelint": "^16.3.1", "stylelint": "^16.3.1",
"stylelint-config-standard-scss": "^13.0.0" "stylelint-config-standard-scss": "^13.0.0"
}, },
@ -89,11 +94,22 @@
"media-feature-range-notation": "prefix" "media-feature-range-notation": "prefix"
} }
}, },
"standard-version": { "release": {
"skip": { "branches": [
"commit": true, "production"
"tag": true ],
}, "plugins": [
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits"
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits",
"presetConfig": {
"types": [ "types": [
{ {
"type": "feat", "type": "feat",
@ -106,7 +122,48 @@
{ {
"type": "perf", "type": "perf",
"section": "Improvements" "section": "Improvements"
},
{
"type": "refactor",
"section": "Changes",
"hidden": true
} }
] ]
} }
} }
],
[
"@semantic-release/changelog",
{
"changelogFile": "docs/CHANGELOG.md",
"changelogTitle": "# Changelog"
}
],
[
"@semantic-release/npm",
{
"npmPublish": false
}
],
[
"@semantic-release/exec",
{
"prepareCmd": "bash tools/release --prepare",
"publishCmd": "bash tools/release"
}
],
[
"@semantic-release/git",
{
"assets": [
"docs",
"package.json",
"*.gemspec"
],
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
}
],
"@semantic-release/github"
]
}
}

View file

@ -1,56 +1,47 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# Release a new version to the GitLab flow production branch.
#
# For a new major/minor version, bump version on the main branch, and then merge into the production branch.
#
# For a patch version, bump the version number on the patch branch, then merge that branch into the main branch
# and production branch.
#
#
# Usage: run on the default, release or the patch branch
#
# Requires: Git, NPM and RubyGems # Requires: Git, NPM and RubyGems
set -eu set -eu
opt_pre=false # preview mode option opt_pre=false # option for bump gem version
opt_pkg=false # option for building gem package
working_branch="$(git branch --show-current)" MAIN_BRANCH="master"
RELEASE_BRANCH="production"
DEFAULT_BRANCH="$(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@')"
PROD_BRANCH="production"
GEM_SPEC="jekyll-theme-chirpy.gemspec" GEM_SPEC="jekyll-theme-chirpy.gemspec"
NODE_CONFIG="package.json" NODE_SPEC="package.json"
CHANGE_LOG="docs/CHANGELOG.md" CHANGELOG="docs/CHANGELOG.md"
CONFIG="_config.yml"
JS_DIST="assets/js/dist" JS_DIST="assets/js/dist"
BACKUP_PATH="$(mktemp -d)"
FILES=( FILES=(
"$GEM_SPEC" "$GEM_SPEC"
"$NODE_CONFIG" "$NODE_SPEC"
"$CHANGELOG"
"$CONFIG"
) )
TOOLS=( TOOLS=(
"git" "git"
"npm" "npm"
"standard-version"
"gem" "gem"
) )
help() { help() {
echo "A tool to release new version Chirpy gem" echo -e "A tool to release new version Chirpy gem.\nThis tool will:"
echo " 1. Build a new gem and publish it to RubyGems.org"
echo " 2. Merge the release branch into the default branch"
echo echo
echo "Usage:" echo "Usage:"
echo
echo " bash ./tools/release [options]" echo " bash ./tools/release [options]"
echo echo
echo "Options:" echo "Options:"
echo " -p, --preview Enable preview mode, only package, and will not modify the branches" echo " --prepare Preparation for release"
echo " -h, --help Print this information." echo " -p, --package Build a gem package only, for local packaging in case of auto-publishing failure"
echo " -h, --help Display this help message"
} }
_check_cli() { _check_cli() {
@ -70,11 +61,9 @@ _check_git() {
exit 1 exit 1
fi fi
$opt_pre || ( $opt_pkg || (
if [[ $working_branch != "$DEFAULT_BRANCH" && if [[ "$(git branch --show-current)" != "$RELEASE_BRANCH" ]]; then
$working_branch != hotfix/* && echo "> Abort: Please run the tool in the '$RELEASE_BRANCH' branch."
$working_branch != "$PROD_BRANCH" ]]; then
echo "> Abort: Please run on the default, release or patch branch."
exit 1 exit 1
fi fi
) )
@ -103,108 +92,95 @@ check() {
_check_node_packages _check_node_packages
} }
# Auto-generate a new version number to the file 'package.json' ## Bump new version to gem-spec file
bump_node() { _bump_version() {
bump="standard-version -i $CHANGE_LOG" _version="$(grep '"version":' "$NODE_SPEC" | sed 's/.*: "//;s/".*//')"
sed -i "s/[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+/$_version/" "$GEM_SPEC"
echo "> Bump gem version to $_version"
}
if $opt_pre; then _improve_changelog() {
bump="$bump -p rc"
fi
eval "$bump"
# Change heading of Patch version to heading level 2 (a bug from `standard-version`)
sed -i "s/^### \[/## \[/g" "$CHANGE_LOG"
# Replace multiple empty lines with a single empty line # Replace multiple empty lines with a single empty line
sed -i "/^$/N;/^\n$/D" "$CHANGE_LOG" sed -i '/^$/N;/^\n$/D' "$CHANGELOG"
# Escape left angle brackets of HTML tag in the changelog as they break the markdown structure. e.g., '<hr>'
sed -i -E 's/\s(<[a-z])/ \\\1/g' "$CHANGELOG"
} }
## Bump new version to gem config file prepare() {
bump_gem() { _bump_version
_ver="$1" _improve_changelog
if $opt_pre; then
_ver="${1/-/.}"
fi
sed -i "s/[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+/$_ver/" "$GEM_SPEC"
}
# Creates a new tag on the production branch with the given version number.
# Also commits the changes and merges the production branch into the default branch.
branch() {
_version="$1" # X.Y.Z
git add .
git commit -m "chore(release): $_version"
# Create a new tag on production branch
echo -e "> Create tag v$_version\n"
git tag "v$_version"
git checkout "$DEFAULT_BRANCH"
git merge --no-ff --no-edit "$PROD_BRANCH"
if [[ $working_branch == hotfix/* ]]; then
# delete the patch branch
git branch -D "$working_branch"
fi
} }
## Build a Gem package ## Build a Gem package
build_gem() { build_gem() {
git checkout "$PROD_BRANCH" if $opt_pkg; then
BACKUP_PATH="$(mktemp -d)"
cp "$JS_DIST"/* "$BACKUP_PATH"
fi
# Remove unnecessary theme settings # Remove unnecessary theme settings
sed -i "s/^cdn:.*/cdn:/;s/^avatar:.*/avatar:/" _config.yml sed -i "s/^cdn:.*/cdn:/;s/^avatar:.*/avatar:/" $CONFIG
rm -f ./*.gem rm -f ./*.gem
npm run build npm run build
git add "$JS_DIST" -f # add JS distribution files to gem git add "$JS_DIST" -f # add JS distribution files to gem
gem build "$GEM_SPEC" gem build "$GEM_SPEC"
cp "$JS_DIST"/* "$BACKUP_PATH"
# Resume the settings # resume the settings
git reset git reset
git checkout . git checkout .
if $opt_pkg; then
# restore the dist files for future development # restore the dist files for future development
mkdir -p "$JS_DIST" && cp "$BACKUP_PATH"/* "$JS_DIST" mkdir -p "$JS_DIST" && cp "$BACKUP_PATH"/* "$JS_DIST"
rm -rf "$BACKUP_PATH"
fi
}
# back to the default branch # Push the gem to RubyGems.org (using $GEM_HOST_API_KEY)
git checkout "$DEFAULT_BRANCH" push_gem() {
gem push ./*.gem
}
## Merge the release branch into the default branch
merge() {
git fetch origin "$MAIN_BRANCH"
git checkout -b "$MAIN_BRANCH" origin/"$MAIN_BRANCH"
git merge --no-ff --no-edit "$RELEASE_BRANCH" || (
git merge --abort
echo -e "\n> Conflict detected. Aborting merge.\n"
exit 0
)
git push origin "$MAIN_BRANCH"
} }
main() { main() {
check check
if [[ $opt_pre = false && $working_branch != "$PROD_BRANCH" ]]; then if $opt_pre; then
git checkout "$PROD_BRANCH" prepare
git merge --no-ff --no-edit "$working_branch" exit 0
fi fi
bump_node
_version="$(grep '"version":' "$NODE_CONFIG" | sed 's/.*: "//;s/".*//')"
bump_gem "$_version"
if [[ $opt_pre = false ]]; then
branch "$_version"
fi
echo -e "> Build the gem package for v$_version\n"
build_gem build_gem
$opt_pkg && exit 0
push_gem
merge
} }
while (($#)); do while (($#)); do
opt="$1" opt="$1"
case $opt in case $opt in
-p | --preview) --prepare)
opt_pre=true opt_pre=true
shift shift
;; ;;
-p | --package)
opt_pkg=true
shift
;;
-h | --help) -h | --help)
help help
exit 0 exit 0