Skip to content

feature: add unreleased to changelog if necessary #31

feature: add unreleased to changelog if necessary

feature: add unreleased to changelog if necessary #31

Workflow file for this run

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 "$(echo '${{ env.VERSION_HEADER }}Unreleased\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 '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 response = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
});
const comments = response.map(json => json.body)
if (comments.some(comment => comment === 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 }}