feature: add Unreleased to changelog if necessary #39
Workflow file for this run
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: gitflow | |
on: | |
pull_request: | |
types: | |
- opened | |
- synchronize | |
- reopened | |
- closed | |
branches: | |
- main | |
- develop | |
workflow_call: | |
inputs: | |
MAIN_BRANCH: | |
description: 'Name of main branch' | |
type: string | |
default: 'main' | |
required: false | |
DEVELOP_BRANCH: | |
description: 'Name of develop branch' | |
type: string | |
default: 'develop' | |
required: false | |
FEATURE_BRANCHES: | |
description: 'Names of feature branch (seperator is " ")' | |
type: string | |
default: 'feature refactor fix change update document test chore' | |
required: false | |
RELEASE_BRANCHES: | |
description: 'Names of release branch (seperator is " ")' | |
type: string | |
default: 'release hotfix' | |
required: false | |
NORELEASE_BRANCHES: | |
description: 'Names of norelease branch (seperator is " ")' | |
type: string | |
default: 'norelease' | |
required: false | |
VERSION_EXPRESSION: | |
description: 'Regular expression of version on changelog' | |
type: string | |
default: '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*' | |
required: false | |
VERSION_HEADER: | |
description: 'Header of version on changelog' | |
type: string | |
default: '## ' | |
required: false | |
CHANGELOG: | |
description: 'Name of changelog file' | |
type: string | |
default: 'changelog.md' | |
required: false | |
secrets: | |
TOKEN: | |
description: 'GitHub token (Default: GitHub Action token)' | |
required: false | |
GITHUB_APP_ID: | |
description: 'GitHub app id for fetching GitHub token' | |
required: false | |
GITHUB_APP_PRIVATE_KEY: | |
description: 'GitHub app private key for fetching GitHub token' | |
required: false | |
GITHUB_APP_OWNER: | |
description: 'GitHub app owner for fetching GitHub token' | |
required: false | |
env: | |
MAIN_BRANCH: ${{ inputs.MAIN_BRANCH || 'main' }} | |
DEVELOP_BRANCH: ${{ inputs.DEVELOP_BRANCH || 'develop' }} | |
FEATURE_BRANCHES: ${{ inputs.FEATURE_BRANCHES || 'feature refactor fix change update document test chore' }} | |
RELEASE_BRANCHES: ${{ inputs.RELEASE_BRANCHES || 'release hotfix' }} | |
NORELEASE_BRANCHES: ${{ inputs.NORELEASE_BRANCHES || 'norelease' }} | |
VERSION_EXPRESSION: ${{ inputs.VERSION_EXPRESSION || '[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*' }} | |
VERSION_HEADER: ${{ inputs.VERSION_HEADER || '## ' }} | |
CHANGELOG: ${{ inputs.CHANGELOG || 'changelog.md' }} | |
TOKEN: ${{ secrets.TOKEN || github.token }} | |
GITHUB_APP_ID: ${{ secrets.GITHUB_APP_ID }} | |
GITHUB_APP_PRIVATE_KEY: ${{ secrets.GITHUB_APP_PRIVATE_KEY }} | |
GITHUB_APP_OWNER: ${{ secrets.GITHUB_APP_OWNER }} | |
SOURCE_BRANCH: ${{ github.event.pull_request.head.ref }} | |
SOURCE_COMMIT: ${{ github.event.pull_request.head.sha }} | |
DESTINATION_BRANCH: ${{ github.event.pull_request.base.ref }} | |
DESTINATION_COMMIT: ${{ github.event.pull_request.base.sha }} | |
jobs: | |
gitflow: | |
runs-on: ubuntu-latest | |
steps: | |
- name: Fetching GitHub Token | |
id: fetching-github-token | |
if: ${{ env.GITHUB_APP_ID && env.GITHUB_APP_PRIVATE_KEY && env.GITHUB_APP_OWNER }} | |
uses: actions/create-github-app-token@v1 | |
with: | |
app-id: ${{ env.GITHUB_APP_ID }} | |
private-key: ${{ env.GITHUB_APP_PRIVATE_KEY }} | |
owner: ${{ env.GITHUB_APP_OWNER }} | |
- name: Using GitHub Token | |
if: ${{ env.GITHUB_APP_ID && env.GITHUB_APP_PRIVATE_KEY && env.GITHUB_APP_OWNER }} | |
run: | | |
echo '::add-mask::${{ steps.fetching-github-token.outputs.token }}' | |
echo 'TOKEN=${{ steps.fetching-github-token.outputs.token }}' >> $GITHUB_ENV | |
- name: Check branch | |
id: check-branch | |
run: | | |
if [[ $( | |
if [[ '${{ env.DESTINATION_BRANCH }}' != '${{ env.DEVELOP_BRANCH }}' ]]; then | |
echo 'false' | |
fi | |
for FEATURE_BRANCH in $(echo '${{ env.FEATURE_BRANCHES }}'); do | |
if [[ '${{ env.SOURCE_BRANCH }}' =~ ^"$FEATURE_BRANCH"/.*$ ]]; then | |
echo 'true' | |
fi | |
done | |
) == true ]]; then | |
echo 'type=feature' >> $GITHUB_OUTPUT | |
elif [[ $( | |
if [[ '${{ env.DESTINATION_BRANCH }}' != '${{ env.MAIN_BRANCH }}' ]]; then | |
echo 'false' | |
fi | |
for RELEASE_BRANCH in $(echo '${{ env.RELEASE_BRANCHES }}'); do | |
if [[ '${{ env.SOURCE_BRANCH }}' =~ ^"$RELEASE_BRANCH"/.*$ ]]; then | |
echo 'true' | |
fi | |
done | |
) == true ]]; then | |
echo 'type=release' >> $GITHUB_OUTPUT | |
elif [[ $( | |
if [[ '${{ env.DESTINATION_BRANCH }}' != '${{ env.MAIN_BRANCH }}' ]]; then | |
echo 'false' | |
fi | |
for NORELEASE_BRANCH in $(echo '${{ env.NORELEASE_BRANCHES }}'); do | |
if [[ '${{ env.SOURCE_BRANCH }}' =~ ^"$NORELEASE_BRANCH"/.*$ ]]; then | |
echo 'true' | |
fi | |
done | |
) == true ]]; then | |
echo 'type=norelease' >> $GITHUB_OUTPUT | |
elif [[ $( | |
if [[ '${{ env.DESTINATION_BRANCH }}' != '${{ env.DEVELOP_BRANCH }}' ]]; then | |
echo 'false' | |
fi | |
for RELEASE_BRANCH in $(echo '${{ env.RELEASE_BRANCHES }}'); do | |
if [[ '${{ env.SOURCE_BRANCH }}' =~ ^"$RELEASE_BRANCH"/.*$ ]]; then | |
echo 'true' | |
fi | |
done | |
for NORELEASE_BRANCH in $(echo '${{ env.NORELEASE_BRANCHES }}'); do | |
if [[ '${{ env.SOURCE_BRANCH }}' =~ ^"$NORELEASE_BRANCH"/.*$ ]]; then | |
echo 'true' | |
fi | |
done | |
) == true ]]; then | |
echo 'type=update' >> $GITHUB_OUTPUT | |
else | |
exit 1 | |
fi | |
- name: If fail on check branch | |
if: failure() && steps.check-branch.outcome == 'failure' | |
uses: actions/github-script@v6 | |
with: | |
script: | | |
const message = [ | |
'Invalid pull request: ', | |
'only ({RELEASE_TYPE}/ => ${{ env.MAIN_BRANCH }}) ', | |
'or ({NORELEASE_TYPE}/ => ${{ env.MAIN_BRANCH }}) ', | |
'or ({FEATURE_TYPE}/ => ${{ env.DEVELOP_BRANCH }}) is allowed', | |
].join('') | |
github.rest.issues.createComment({ | |
issue_number: context.issue.number, | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
body: message, | |
}) | |
github.rest.pulls.update({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
pull_number: context.issue.number, | |
state: 'closed', | |
}) | |
github-token: ${{ env.TOKEN }} | |
- name: Checkout | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ env.SOURCE_BRANCH }} | |
token: ${{ env.TOKEN }} | |
- name: Checkout commit | |
run: | | |
git fetch origin ${{ env.SOURCE_COMMIT }} || : | |
git fetch origin ${{ env.DESTINATION_COMMIT }} || : | |
- name: Check feature branch | |
id: check-feature-branch | |
if: | | |
github.event.pull_request.merged != true | |
&& steps.check-branch.outcome == 'success' | |
&& steps.check-branch.outputs.type == 'feature' | |
run: | | |
cat ${{ env.CHANGELOG }} \ | |
| grep '^${{ env.VERSION_HEADER }}Unreleased$' \ | |
|| ( \ | |
echo "$(printf '${{ env.VERSION_HEADER }}Unreleased\n\n' | cat - ${{ env.CHANGELOG }})" > ${{ env.CHANGELOG }}; \ | |
git config user.name github-actions; \ | |
git config user.email [email protected]; \ | |
git add ${{ env.CHANGELOG }}; \ | |
git commit -m "document: add Unreleased to ${{ env.CHANGELOG }}"; \ | |
git push; \ | |
) \ | |
&& (echo 'warning=changelog-not-modified' >> $GITHUB_OUTPUT; exit 0) \ | |
|| (echo 'failure=changelog-not-contains-unreleased' >> $GITHUB_OUTPUT; exit 1) | |
git diff --name-only ${{ env.DESTINATION_COMMIT }} \ | |
| grep --perl-regexp 'changelog\.md' \ | |
|| (echo 'warning=changelog-not-modified' >> $GITHUB_OUTPUT; exit 0) | |
- name: If fail on check feature branch | |
if: failure() && steps.check-feature-branch.outputs.failure != null | |
uses: actions/github-script@v6 | |
with: | |
script: | | |
const message = (() => { | |
if ('${{ steps.check-feature-branch.outputs.failure }}' === 'changelog-not-contains-unreleased') { | |
return [ | |
`Invalid pull request: `, | |
`'${{ env.VERSION_HEADER }}Unreleased' `, | |
`should be existed on ${{ env.CHANGELOG }}.`, | |
].join('') | |
} | |
})() | |
github.rest.issues.createComment({ | |
issue_number: context.issue.number, | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
body: message, | |
}) | |
github.rest.pulls.update({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
pull_number: context.issue.number, | |
state: 'closed', | |
}) | |
github-token: ${{ env.TOKEN }} | |
- name: If warn on check feature branch | |
if: always() && steps.check-feature-branch.outputs.warning != null | |
uses: actions/github-script@v6 | |
with: | |
script: | | |
const message = (() => { | |
if ('${{ steps.check-feature-branch.outputs.warning }}' === 'changelog-not-modified') { | |
return `I recommend that you should modify ${{ env.CHANGELOG }}.` | |
} | |
})() | |
const { data: comments } = await github.rest.issues.listComments({ | |
issue_number: context.issue.number, | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
}) | |
if (comments.some(comment => comment.body === message)) { | |
return | |
} | |
github.rest.issues.createComment({ | |
issue_number: context.issue.number, | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
body: message, | |
}) | |
github-token: ${{ env.TOKEN }} | |
- name: Check release branch | |
id: check-release-branch | |
if: | | |
github.event.pull_request.merged != true | |
&& steps.check-branch.outcome == 'success' | |
&& steps.check-branch.outputs.type == 'release' | |
run: | | |
VERSION=$( | |
echo '${{ env.SOURCE_BRANCH }}' \ | |
| grep '${{ env.VERSION_EXPRESSION }}' --only-matching | |
) \ | |
&& (echo "version=$VERSION" >> $GITHUB_OUTPUT) \ | |
|| (echo 'failure=version-not-found' >> $GITHUB_OUTPUT; exit 1) | |
cat ${{ env.CHANGELOG }} \ | |
| grep '^${{ env.VERSION_HEADER }}Unreleased$' \ | |
&& (echo 'failure=changelog-contains-unreleased' >> $GITHUB_OUTPUT; exit 1) \ | |
|| : | |
cat ${{ env.CHANGELOG }} \ | |
| grep "^${{ env.VERSION_HEADER }}$VERSION$" \ | |
|| (echo 'failure=changelog-not-contains-version' >> $GITHUB_OUTPUT; exit 1) | |
CHANGELOG=$( | |
cat ${{ env.CHANGELOG }} \ | |
| perl -0pe "s/(.*\n)*${{ env.VERSION_HEADER }}$VERSION\n//g" \ | |
| perl -0pe 's/(\n?)${{ env.VERSION_HEADER }}.*\n(.*\n)*/\1/g' \ | |
| perl -0pe 's/^\n*//g' \ | |
| perl -0pe 's/\n*$//g' | |
) \ | |
&& ! [[ -z $CHANGELOG ]] \ | |
|| (echo 'failure=changelog-is-empty' >> $GITHUB_OUTPUT; exit 1) | |
- name: If fail on check release branch | |
if: failure() && steps.check-release-branch.outputs.failure != null | |
uses: actions/github-script@v6 | |
with: | |
script: | | |
const message = (() => { | |
if ('${{ steps.check-release-branch.outputs.failure }}' === 'version-not-found') { | |
return `Invalid pull request: source branch should satisfy '{RELEASE_TYPE}/0.0.0' format.` | |
} | |
else if ('${{ steps.check-release-branch.outputs.failure }}' === 'changelog-contains-unreleased') { | |
return [ | |
`Invalid pull request: `, | |
`'${{ env.VERSION_HEADER }}Unreleased' `, | |
`should not be existed on ${{ env.CHANGELOG }}.`, | |
].join('') | |
} | |
else if ('${{ steps.check-release-branch.outputs.failure }}' === 'changelog-not-contains-version') { | |
const version = '${{ steps.check-release-branch.outputs.version }}' | |
return [ | |
`Invalid pull request: `, | |
`'${{ env.VERSION_HEADER }}${version}' `, | |
`should not be existed on ${{ env.CHANGELOG }}.`, | |
].join('') | |
} | |
else if ('${{ steps.check-release-branch.outputs.failure }}' === 'changelog-is-empty') { | |
return `Invalid pull request: changelog is empty.` | |
} | |
})() | |
github.rest.issues.createComment({ | |
issue_number: context.issue.number, | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
body: message, | |
}) | |
github.rest.pulls.update({ | |
owner: context.repo.owner, | |
repo: context.repo.repo, | |
pull_number: context.issue.number, | |
state: 'closed', | |
}) | |
github-token: ${{ env.TOKEN }} | |
- name: Finish feature branch | |
id: finish-feature-branch | |
if: | | |
github.event.pull_request.merged == true | |
&& steps.check-branch.outcome == 'success' | |
&& steps.check-branch.outputs.type == 'feature' | |
run: | | |
git push origin --delete ${{ env.SOURCE_BRANCH }} \ | |
|| : | |
- name: Finish release branch | |
id: finish-release-branch | |
if: | | |
github.event.pull_request.merged == true | |
&& steps.check-branch.outcome == 'success' | |
&& steps.check-branch.outputs.type == 'release' | |
run: | | |
VERSION=$( | |
echo '${{ env.SOURCE_BRANCH }}' \ | |
| grep '${{ env.VERSION_EXPRESSION }}' --only-matching | |
) | |
CHANGELOG=$( | |
cat ${{ env.CHANGELOG }} \ | |
| perl -0pe "s/(.*\n)*${{ env.VERSION_HEADER }}$VERSION\n//g" \ | |
| perl -0pe 's/(\n?)${{ env.VERSION_HEADER }}.*\n(.*\n)*/\1/g' \ | |
| perl -0pe 's/^\n*//g' \ | |
| perl -0pe 's/\n*$//g' | |
) | |
gh release create $VERSION \ | |
--generate-notes \ | |
--notes '## Changelog'$'\n'"$CHANGELOG"$'\n' \ | |
--target '${{ env.MAIN_BRANCH }}' \ | |
--latest | |
git checkout -b '${{ env.SOURCE_BRANCH }}' '${{ env.SOURCE_COMMIT }}' \ | |
&& git push -u origin '${{ env.SOURCE_BRANCH }}' \ | |
|| : | |
PULL_REQUEST_NUMBER=$( | |
gh pr create \ | |
--head '${{ env.SOURCE_BRANCH }}' \ | |
--base '${{ env.DEVELOP_BRANCH }}' \ | |
--title "update: ${{ env.SOURCE_BRANCH }} to ${{ env.DEVELOP_BRANCH }}" \ | |
--body "update: ${{ env.SOURCE_BRANCH }} to ${{ env.DEVELOP_BRANCH }}" \ | |
| grep '\/pull\/[0-9][0-9]*' --only-matching \ | |
| grep '[0-9][0-9]*' --only-matching \ | |
|| : | |
) | |
gh pr merge $PULL_REQUEST_NUMBER \ | |
--subject "update: ${{ env.SOURCE_BRANCH }} to ${{ env.DEVELOP_BRANCH }}" \ | |
--admin \ | |
--merge \ | |
&& git push origin --delete ${{ env.SOURCE_BRANCH }} \ | |
|| : | |
env: | |
GH_TOKEN: ${{ env.TOKEN }} | |
- name: Finish norelease branch | |
id: finish-norelease-branch | |
if: | | |
github.event.pull_request.merged == true | |
&& steps.check-branch.outcome == 'success' | |
&& steps.check-branch.outputs.type == 'norelease' | |
run: | | |
git checkout -b '${{ env.SOURCE_BRANCH }}' '${{ env.SOURCE_COMMIT }}' \ | |
&& git push -u origin '${{ env.SOURCE_BRANCH }}' \ | |
|| : | |
PULL_REQUEST_NUMBER=$( | |
gh pr create \ | |
--head '${{ env.SOURCE_BRANCH }}' \ | |
--base '${{ env.DEVELOP_BRANCH }}' \ | |
--title "update: ${{ env.SOURCE_BRANCH }} to ${{ env.DEVELOP_BRANCH }}" \ | |
--body "update: ${{ env.SOURCE_BRANCH }} to ${{ env.DEVELOP_BRANCH }}" \ | |
| grep '\/pull\/[0-9][0-9]*' --only-matching \ | |
| grep '[0-9][0-9]*' --only-matching \ | |
|| : | |
) | |
gh pr merge $PULL_REQUEST_NUMBER \ | |
--subject "update: ${{ env.SOURCE_BRANCH }} to ${{ env.DEVELOP_BRANCH }}" \ | |
--admin \ | |
--merge \ | |
&& git push origin --delete ${{ env.SOURCE_BRANCH }} \ | |
|| : | |
env: | |
GH_TOKEN: ${{ env.TOKEN }} |