mirror of
https://github.com/taiki-e/install-action.git
synced 2026-04-08 18:05:42 +08:00
450 lines
17 KiB
YAML
450 lines
17 KiB
YAML
name: Release
|
|
|
|
# Adapted from https://github.com/taiki-e/github-actions/blob/HEAD/.github/workflows/action-release.yml.
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
target:
|
|
description: Package to be released
|
|
required: true
|
|
type: choice
|
|
options:
|
|
- install-action
|
|
- install-action-manifest-schema
|
|
version:
|
|
description: Version to be increased
|
|
required: true
|
|
type: choice
|
|
options:
|
|
- patch
|
|
- minor
|
|
- major
|
|
|
|
defaults:
|
|
run:
|
|
shell: bash --noprofile --norc -CeEuxo pipefail {0}
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
prepare:
|
|
if: github.repository_owner == 'taiki-e' && inputs.target == 'install-action'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
|
|
- uses: taiki-e/install-action@7a562dfa955aa2e4d5b0fd6ebd57ff9715c07b0b # v2.73.0
|
|
with:
|
|
tool: parse-changelog
|
|
fallback: none
|
|
- id: check
|
|
run: |
|
|
set +x
|
|
IFS=$'\n\t'
|
|
trap -- 's=$?; printf >&2 "%s\n" "${0##*/}:${LINENO}: \`${BASH_COMMAND}\` exit with ${s}"; exit ${s}' ERR
|
|
retry() {
|
|
for i in {1..10}; do
|
|
if "$@"; then
|
|
return 0
|
|
else
|
|
sleep "${i}"
|
|
fi
|
|
done
|
|
"$@"
|
|
}
|
|
bail() {
|
|
printf '::error::%s\n' "$*"
|
|
exit 1
|
|
}
|
|
if { sed --help 2>&1 || true; } | grep -Eq -e '-i extension'; then
|
|
in_place=(-i '')
|
|
else
|
|
in_place=(-i)
|
|
fi
|
|
|
|
# shellcheck disable=SC2153
|
|
version="${VERSION}"
|
|
printf '%s\n' "version(input): ${version}"
|
|
# shellcheck disable=SC2153
|
|
tag_prefix="${TAG_PREFIX}"
|
|
printf '%s\n' "tag_prefix: ${tag_prefix}"
|
|
# shellcheck disable=SC2153
|
|
changelog="${CHANGELOG}"
|
|
printf '%s\n' "changelog: ${changelog}"
|
|
|
|
# Get the current date.
|
|
release_date=$(date -u '+%Y-%m-%d')
|
|
printf '%s\n' "release-date: ${release_date}"
|
|
printf '%s\n' "release-date=${release_date}" >>"${GITHUB_OUTPUT}"
|
|
|
|
# Get the current revision.
|
|
rev=$(git rev-parse HEAD)
|
|
printf '%s\n' "rev: ${rev}"
|
|
printf '%s\n' "rev=${rev}" >>"${GITHUB_OUTPUT}"
|
|
|
|
prev_version=$(parse-changelog --title-no-link "${changelog}" | cut -d' ' -f1)
|
|
|
|
# Determine the new version number and tag name.
|
|
case "${version}" in
|
|
major | minor | patch)
|
|
if [[ ! "${prev_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
bail "pre-release/build-metadata"
|
|
fi
|
|
major="${prev_version%%.*}"
|
|
minor_patch="${prev_version#*.}"
|
|
minor="${minor_patch%%.*}"
|
|
patch="${minor_patch#*.}"
|
|
case "${version}" in
|
|
major) version="$((major+1)).0.0" ;;
|
|
minor) version="${major}.$((minor+1)).0" ;;
|
|
patch) version="${major}.${minor}.$((patch+1))" ;;
|
|
esac
|
|
;;
|
|
*) version="${version#v}" ;;
|
|
esac
|
|
if [[ ! "${version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z\.-]+)?(\+[0-9A-Za-z\.-]+)?$ ]]; then
|
|
bail "invalid version format '${version}'"
|
|
fi
|
|
printf '%s\n' "version: ${version}"
|
|
printf '%s\n' "version=${version}" >>"${GITHUB_OUTPUT}"
|
|
tag="${tag_prefix}${version}"
|
|
printf '%s\n' "tag: ${tag}"
|
|
printf '%s\n' "tag=${tag}" >>"${GITHUB_OUTPUT}"
|
|
|
|
# Make sure the same release has not been created in the past.
|
|
if gh release view "${tag}" &>/dev/null; then
|
|
bail "tag '${tag}' has already been created and pushed"
|
|
fi
|
|
# Make sure that the release was created from an allowed branch.
|
|
if ! git branch | grep -Eq '\* '"${BRANCH}"'$'; then
|
|
bail "current branch is not '${BRANCH}'"
|
|
fi
|
|
|
|
changed_paths=()
|
|
retry git fetch origin --tags &>/dev/null
|
|
tags=$(git --no-pager tag | { grep -E "^${tag_prefix}[0-9]+" || true; })
|
|
if [[ -n "${tags}" ]]; then
|
|
printf 'has-tags=true\n' >>"${GITHUB_OUTPUT}"
|
|
# Make sure the same release does not exist in changelog.
|
|
if grep -Eq "^## \\[${version//./\\.}\\]" "${changelog}"; then
|
|
bail "release ${version} already exist in ${changelog}"
|
|
fi
|
|
if grep -Eq "^\\[${version//./\\.}\\]: " "${changelog}"; then
|
|
bail "link to ${version} already exist in ${changelog}"
|
|
fi
|
|
|
|
# Update changelog.
|
|
changed_paths+=("${changelog}")
|
|
remote_url=$(grep -E '^\[Unreleased\]: https://' "${changelog}" | sed -E 's/^\[Unreleased\]: //; s/\.\.\.HEAD$//')
|
|
prev_tag="${remote_url#*/compare/}"
|
|
remote_url="${remote_url%/compare/*}"
|
|
sed -E "${in_place[@]}" \
|
|
-e "s/^## \\[Unreleased\\]/## [Unreleased]\\n\\n## [${version}] - ${release_date}/" \
|
|
-e "s#^\[Unreleased\]: https://.*#[Unreleased]: ${remote_url}/compare/${tag}...HEAD\\n[${version}]: ${remote_url}/compare/${prev_tag}...${tag}#" "${changelog}"
|
|
if ! grep -Eq "^## \\[${version//./\\.}\\] - ${release_date}$" "${changelog}"; then
|
|
bail "failed to update ${changelog}"
|
|
fi
|
|
if ! grep -Eq "^\\[${version//./\\.}\\]: " "${changelog}"; then
|
|
bail "failed to update ${changelog}"
|
|
fi
|
|
else
|
|
# Make sure the release exists in changelog.
|
|
if ! grep -Eq "^## \\[${version//./\\.}\\] - ${release_date}$" "${changelog}"; then
|
|
bail "release ${version} does not exist in ${changelog} or has wrong release date"
|
|
fi
|
|
if ! grep -Eq "^\\[${version//./\\.}\\]: " "${changelog}"; then
|
|
bail "link to ${version} does not exist in ${changelog}"
|
|
fi
|
|
fi
|
|
# Make sure that a valid release note for this version exists.
|
|
# https://github.com/taiki-e/parse-changelog
|
|
changes=$(parse-changelog "${changelog}" "${version}")
|
|
if [[ -z "${changes}" ]]; then
|
|
bail "changelog for ${version} has no body"
|
|
fi
|
|
printf '============== CHANGELOG ==============\n'
|
|
printf '%s\n' "${changes}"
|
|
printf '=======================================\n'
|
|
|
|
if [[ -n "${tags}" ]]; then
|
|
git -c color.ui=always diff "${changed_paths[@]}"
|
|
git add "${changed_paths[@]}"
|
|
fi
|
|
# Make sure that there is no unintended change.
|
|
git add -N .
|
|
git -c color.ui=always diff --exit-code
|
|
|
|
(
|
|
set -x
|
|
git show HEAD --shortstat
|
|
)
|
|
env:
|
|
VERSION: ${{ inputs.version }}
|
|
TAG_PREFIX: v
|
|
CHANGELOG: CHANGELOG.md
|
|
BRANCH: main
|
|
outputs:
|
|
has-tags: ${{ steps.check.outputs.has-tags }}
|
|
release-date: ${{ steps.check.outputs.release-date }}
|
|
rev: ${{ steps.check.outputs.rev }}
|
|
tag: ${{ steps.check.outputs.tag }}
|
|
version: ${{ steps.check.outputs.version }}
|
|
|
|
release:
|
|
if: github.repository_owner == 'taiki-e' && inputs.target == 'install-action'
|
|
needs: prepare
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 60
|
|
environment:
|
|
name: release
|
|
deployment: false
|
|
permissions:
|
|
contents: write # for taiki-e/create-gh-release-action
|
|
steps:
|
|
- uses: taiki-e/checkout-action@7d1e50e93dc4fb3bba58f85018fadf77898aee8b # v1.4.2
|
|
- uses: taiki-e/install-action@7a562dfa955aa2e4d5b0fd6ebd57ff9715c07b0b # v2.73.0
|
|
with:
|
|
tool: parse-changelog
|
|
fallback: none
|
|
- uses: actions/create-github-app-token@f8d387b68d61c58ab83c6c016672934102569859 # v3.0.0
|
|
id: push-token
|
|
with:
|
|
app-id: ${{ secrets.PUSH_TOKEN_APP_CLIENT_ID }}
|
|
private-key: ${{ secrets.PUSH_TOKEN_APP_PRIVATE_KEY }}
|
|
- name: Create and push release commit and tag
|
|
id: push
|
|
run: |
|
|
set +x
|
|
IFS=$'\n\t'
|
|
trap -- 's=$?; printf >&2 "%s\n" "${0##*/}:${LINENO}: \`${BASH_COMMAND}\` exit with ${s}"; exit ${s}' ERR
|
|
retry() {
|
|
for i in {1..10}; do
|
|
if "$@"; then
|
|
return 0
|
|
else
|
|
sleep "${i}"
|
|
fi
|
|
done
|
|
"$@"
|
|
}
|
|
bail() {
|
|
printf '::error::%s\n' "$*"
|
|
exit 1
|
|
}
|
|
if { sed --help 2>&1 || true; } | grep -Eq -e '-i extension'; then
|
|
in_place=(-i '')
|
|
else
|
|
in_place=(-i)
|
|
fi
|
|
|
|
git config user.name 'Taiki Endo'
|
|
git config user.email 'te316e89@gmail.com'
|
|
|
|
# shellcheck disable=SC2153
|
|
version="${VERSION}"
|
|
# shellcheck disable=SC2153
|
|
tag="${TAG}"
|
|
# shellcheck disable=SC2153
|
|
changelog="${CHANGELOG}"
|
|
# shellcheck disable=SC2153
|
|
release_date="${RELEASE_DATE}"
|
|
|
|
# Make sure the current revision is same as prepare step.
|
|
# --unshallow is necessary to successfully push the
|
|
# "releases/${major_version_tag}" branch in the subsequent step.
|
|
retry git fetch origin --unshallow &>/dev/null
|
|
rev=$(git rev-parse HEAD)
|
|
if [[ "${rev}" != "${PREPARE_REV}" ]]; then
|
|
bail "revision difference between prepare step"
|
|
fi
|
|
|
|
# Make sure the same release has not been created in the past.
|
|
if gh release view "${tag}" &>/dev/null; then
|
|
bail "tag '${tag}' has already been created and pushed"
|
|
fi
|
|
# Make sure that the release was created from an allowed branch.
|
|
if ! git branch | grep -Eq '\* '"${BRANCH}"'$'; then
|
|
bail "current branch is not '${BRANCH}'"
|
|
fi
|
|
|
|
changed_paths=()
|
|
if [[ "${HAS_TAGS}" == "true" ]]; then
|
|
# Update changelog.
|
|
changed_paths+=("${changelog}")
|
|
remote_url=$(grep -E '^\[Unreleased\]: https://' "${changelog}" | sed -E 's/^\[Unreleased\]: //; s/\.\.\.HEAD$//')
|
|
prev_tag="${remote_url#*/compare/}"
|
|
remote_url="${remote_url%/compare/*}"
|
|
sed -E "${in_place[@]}" \
|
|
-e "s/^## \\[Unreleased\\]/## [Unreleased]\\n\\n## [${version}] - ${release_date}/" \
|
|
-e "s#^\[Unreleased\]: https://.*#[Unreleased]: ${remote_url}/compare/${tag}...HEAD\\n[${version}]: ${remote_url}/compare/${prev_tag}...${tag}#" "${changelog}"
|
|
if ! grep -Eq "^## \\[${version//./\\.}\\] - ${release_date}$" "${changelog}"; then
|
|
bail "failed to update ${changelog}"
|
|
fi
|
|
if ! grep -Eq "^\\[${version//./\\.}\\]: " "${changelog}"; then
|
|
bail "failed to update ${changelog}"
|
|
fi
|
|
fi
|
|
changes=$(parse-changelog "${changelog}" "${version}")
|
|
printf '============== CHANGELOG ==============\n'
|
|
printf '%s\n' "${changes}"
|
|
printf '=======================================\n'
|
|
|
|
if [[ "${HAS_TAGS}" == "true" ]]; then
|
|
# Create a release commit.
|
|
(
|
|
set -x
|
|
git add "${changed_paths[@]}"
|
|
git commit -m "Release ${version}"
|
|
)
|
|
fi
|
|
|
|
prev_credential_helper=$(git config get --local credential.helper || true)
|
|
if [[ -n "${prev_credential_helper}" ]]; then
|
|
printf 'credential helper is already set (%s)\n' "${prev_credential_helper}"
|
|
else
|
|
protocol="${GITHUB_SERVER_URL%%://*}"
|
|
hostname="${GITHUB_SERVER_URL#*://}"
|
|
(
|
|
set -x
|
|
git config --local credential.helper cache
|
|
)
|
|
git credential approve <<EOF
|
|
protocol=${protocol}
|
|
host=${hostname}
|
|
username=${GITHUB_ACTOR}
|
|
password=${PUSH_TOKEN}
|
|
EOF
|
|
# Remove credential helper config on exit.
|
|
trap -- '(set -x; git credential-cache exit; git config --local --unset credential.helper || true)' EXIT
|
|
fi
|
|
|
|
(
|
|
set -x
|
|
git tag "${tag}"
|
|
retry git push origin HEAD
|
|
|
|
major_version_tag="v${version%%.*}"
|
|
git branch "releases/${major_version_tag}"
|
|
git tag -f "${major_version_tag}"
|
|
refs=("refs/tags/${tag}" "refs/heads/releases/${major_version_tag}" "+refs/tags/${major_version_tag}")
|
|
|
|
tools=()
|
|
for tool in tools/codegen/base/*.json; do
|
|
tool="${tool##*/}"
|
|
tools+=("${tool%.*}")
|
|
done
|
|
# Aliases.
|
|
# NB: Update case for aliases in main.sh, tool input option in test-alias job
|
|
# in .github/workflows/ci.yml, and match for alias for tools/codegen/src/tools-markdown.rs.
|
|
tools+=(
|
|
nextest
|
|
taplo-cli
|
|
typos-cli
|
|
wasm-bindgen-cli
|
|
wasmtime-cli
|
|
)
|
|
# Non-manifest-based tools.
|
|
tools+=(valgrind)
|
|
|
|
branches=()
|
|
for tool in "${tools[@]}"; do
|
|
git checkout -b "releases/${tool}"
|
|
sed -E "${in_place[@]}" action.yml \
|
|
-e "s/required: true/required: false/g" \
|
|
-e "s/# default: #publish:tool/default: ${tool}/g"
|
|
git add action.yml
|
|
git commit -m "${tool}"
|
|
git tag -f "${tool}"
|
|
git checkout refs/tags/"${tag}"
|
|
refs+=("+refs/heads/releases/${tool}" "+refs/tags/${tool}")
|
|
branches+=("releases/${tool}")
|
|
done
|
|
|
|
retry git push origin --atomic "${refs[@]}"
|
|
git branch -d "releases/${major_version_tag}"
|
|
git branch -D "${branches[@]}"
|
|
|
|
schema_workspace=/tmp/workspace
|
|
rm -rf -- "${schema_workspace}"
|
|
# Checkout manifest-schema branch
|
|
schema_version="$(cargo metadata --format-version=1 --no-deps | jq -r '.packages[] | select(.name == "install-action-manifest-schema") | .version')"
|
|
if [[ "${schema_version}" == "0."* ]]; then
|
|
schema_version="0.$(cut -d. -f2 <<<"${schema_version}")"
|
|
else
|
|
schema_version="$(cut -d. -f1 <<<"${schema_version}")"
|
|
fi
|
|
schema_branch="manifest-schema-${schema_version}"
|
|
|
|
git worktree add --force "${schema_workspace}"
|
|
(
|
|
cd -- "${schema_workspace}"
|
|
if git fetch origin "${schema_branch}"; then
|
|
git checkout "origin/${schema_branch}" -B "${schema_branch}"
|
|
elif ! git checkout "${schema_branch}"; then
|
|
# New branch with no history. Credit: https://stackoverflow.com/a/13969482
|
|
git checkout --orphan "${schema_branch}"
|
|
git rm -rf -- . || true
|
|
git commit -m 'Initial commit' --allow-empty
|
|
fi
|
|
)
|
|
|
|
# Copy over schema
|
|
cp -- ./manifests/* "${schema_workspace}"
|
|
|
|
(
|
|
cd -- "${schema_workspace}"
|
|
# Stage changes
|
|
git add .
|
|
# Detect changes, then commit and push if changes exist
|
|
if [[ "$(git status --porcelain=v1 | LC_ALL=C wc -l)" != "0" ]]; then
|
|
git commit -m 'Update manifest schema'
|
|
retry git push origin HEAD
|
|
fi
|
|
)
|
|
|
|
rm -rf -- "${schema_workspace}"
|
|
git worktree prune
|
|
# TODO: get branch in schema_workspace dir instead
|
|
git branch -D "${schema_branch}" "${schema_workspace##*/}"
|
|
)
|
|
env:
|
|
VERSION: ${{ needs.prepare.outputs.version }}
|
|
RELEASE_DATE: ${{ needs.prepare.outputs.release-date }}
|
|
HAS_TAGS: ${{ needs.prepare.outputs.has-tags }}
|
|
TAG: ${{ needs.prepare.outputs.tag }}
|
|
CHANGELOG: CHANGELOG.md
|
|
BRANCH: main
|
|
PREPARE_REV: ${{ needs.prepare.outputs.rev }}
|
|
# Note that if we use secrets.GITHUB_TOKEN, the pushed commit/tag cannot trigger other workflows.
|
|
PUSH_TOKEN: ${{ steps.push-token.outputs.token }}
|
|
- uses: taiki-e/create-gh-release-action@f67cf7b2dadd83d36b981e674a6b08063997ce68 # v1.10.0
|
|
with:
|
|
changelog: CHANGELOG.md
|
|
title: $version
|
|
branch: main
|
|
ref: refs/tags/${{ needs.prepare.outputs.tag }}
|
|
|
|
release-manifest-schema:
|
|
if: github.repository_owner == 'taiki-e' && inputs.target == 'install-action-manifest-schema'
|
|
uses: taiki-e/github-actions/.github/workflows/rust-release.yml@606d6c78d69927c489d319133073a03157928a7a # main
|
|
permissions:
|
|
contents: write # for taiki-e/create-gh-release-action
|
|
id-token: write # for rust-lang/crates-io-auth-action
|
|
attestations: write # unused (used when options for uploading binaries are set)
|
|
secrets:
|
|
PUSH_TOKEN_APP_CLIENT_ID: ${{ secrets.PUSH_TOKEN_APP_CLIENT_ID }}
|
|
PUSH_TOKEN_APP_PRIVATE_KEY: ${{ secrets.PUSH_TOKEN_APP_PRIVATE_KEY }}
|
|
with:
|
|
version: ${{ inputs.version }}
|
|
tag-prefix: install-action-manifest-schema-
|
|
crates: tools/manifest-schema
|
|
changelog: tools/manifest-schema/CHANGELOG.md
|
|
title: $prefix $version
|
|
prefix: install-action-manifest-schema
|