diff --git a/CHANGELOG.md b/CHANGELOG.md index 5702251..0dc13d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # CHANGELOG +> [!NOTE] +> Changes from here on align with the tagged version at surpher/PactSwiftServer + ## 1.0.0 - v1.0.0 * d08c413 - chore: Change to use updated package API diff --git a/Support/Scripts/CI/version_numbers b/Support/Scripts/CI/version_numbers.sh similarity index 100% rename from Support/Scripts/CI/version_numbers rename to Support/Scripts/CI/version_numbers.sh diff --git a/Support/Scripts/Config/config.sh b/Support/Scripts/Config/config.sh new file mode 100644 index 0000000..40dc9ce --- /dev/null +++ b/Support/Scripts/Config/config.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2034 + +# Xcode version to use +# Only care about major and minor +MIN_XCODE_VERSION="16.1" +XCODE_VERSION_MIN_SUGGESTED="16.1" +XCODE_VERSION_MIN_SUPPORTED="16.0" + +# Project configuration +CONFIGURATION_FILE="Configurations/Project-Shared.xcconfig" +CHANGE_LOG="CHANGELOG.md" +TAG_MESSAGE_FILE="TAG_MESSAGE_FILE.md" +XCFRAMEWORK_NAME="PactSwiftMockServer.xcframework" + +REMOTE_NAME="PactSwiftServer" +RELEASE_REPO_NAME="PactSwiftServer" +DEFAULT_REPO_NAME="PactSwiftMockServer" +REPO_OWNER="surpher" +REMOTE_REPO_BASE="git@github.com:$REPO_OWNER" diff --git a/Support/Scripts/release b/Support/Scripts/release index 961022c..a15da12 100755 --- a/Support/Scripts/release +++ b/Support/Scripts/release @@ -67,41 +67,36 @@ fi # "import" source "$RELEASE_SOURCE_DIR/utils.sh" -source "$RELEASE_SOURCE_DIR/CI/version_numbers" +source "$RELEASE_SOURCE_DIR/CI/version_numbers.sh" +source "$RELEASE_SOURCE_DIR/Config/config.sh" SHOULD_BUILD_RUST_BIN=${SHOULD_BUILD_RUST_BIN:-true} -CONFIGURATION_FILE="Configurations/Project-Shared.xcconfig" -RELEASE_NOTES="CHANGELOG.md" -TAG_MESSAGE_FILE="TAG_MESSAGE_FILE.md" -XCFRAMEWORK_NAME="PactSwiftMockServer.xcframework" +DRY_RUN=false CURRENT_BRANCH=$(git branch --show-current) RELEASE_VERSION_PART="" RELEASE_DESCRIPTION="" -DRY_RUN=false - -REMOTE_NAME="releases" -RELEASE_REPO_NAME="PactSwiftServer" -DEFAULT_REPO_NAME="PactSwiftMockServer" -REPO_OWNER="surpher" -REMOTE_REPO_BASE="git@github.com:$REPO_OWNER" - LATEST_TAG=$(latest_tag) +CHANGE_LOG=${CHANGE_LOG:-} +REMOTE_NAME=${REMOTE_NAME:-} #################### # Utilities #################### -function die { - echo "๐Ÿšจ [ERROR] $*" - echo - exit 1 +function execute_cmd { + local is_dry_run=$DRY_RUN + if [ "$is_dry_run" = true ]; then + echo -e "${CYAN}DRY RUN:$NOCOLOR $*" + else + executeCommand "$@" + fi } function git_clean_orphan { echo -e "๐Ÿ›€ Cleaning up the branch..." - EXCEPT_FILES="${RELEASE_NOTES} ${XCFRAMEWORK_NAME}" + EXCEPT_FILES="${CHANGE_LOG} ${XCFRAMEWORK_NAME}" EXCEPT_FILES="${EXCEPT_FILES// /|}" git ls-files | grep -v -E "$EXCEPT_FILES" | xargs git rm -f } @@ -128,11 +123,11 @@ function git_check_remote_branch { } function git_checkout_new_release_candidate_branch { - local REMOTE_NAME="$1" - local VERSION_BRANCH="$2" + local remote_name="$1" + local release_version_branch="$2" - git fetch "$REMOTE_NAME" - git checkout --orphan "$VERSION_BRANCH" + git fetch "$remote_name" + git checkout --orphan "$release_version_branch" } function github_open_draft_pull_request { @@ -150,70 +145,85 @@ function github_open_draft_pull_request { if command -v gh &> /dev/null; then echo "๐Ÿ—ƒ๏ธ Opening a draft pull request..." - if [ "$DRY_RUN" = false ]; then - for git_command in "${GIT_COMMANDS[@]}"; do - echo "$git_command" - eval "$git_command" - done - else - echo "๐Ÿธ Dry run..." - for git_command in "${GIT_COMMANDS[@]}"; do - echo "$git_command" - done - fi + for git_command in "${GIT_COMMANDS[@]}"; do + execute_cmd "$git_command" + done else echo "๐Ÿคท 'gh' not installed." - echo "Open https://github.com/$REPO_OWNER/$RELEASE_REPO_NAME/pulls and open one manually..." + echo " Open https://github.com/$REPO_OWNER/$RELEASE_REPO_NAME/pulls and open one manually..." + echo " See https://cli.github.com/ for more information..." fi } function update_version_file { - local MARKETING_VERSION="MARKETING_VERSION = $*" - sed -i '' "2s/.*/$MARKETING_VERSION/" "$CONFIGURATION_FILE" + local marketing_version="MARKETING_VERSION = $*" + local config_file=$CONFIGURATION_FILE + sed -i '' "2s/.*/$marketing_version/" "$config_file" } function git_create_and_push_new_version { - local REMOTE_NAME="$1" - local REMOTE_BRANCH="$2" - local RELEASE_NAME="$3" - - local GIT_COMMANDS=( - "git add ${RELEASE_NOTES} ${XCFRAMEWORK_NAME}" - "git commit -S -m \"$RELEASE_NAME\"" + local remote_name="$1" + local remote_branch="$2" + local release_name="$3" + local version_tag=$VERSION_TAG + local change_log=$CHANGE_LOG + local xcframework=$XCFRAMEWORK_NAME + + # Generate a release changelog for github_open_draft_pull_request function to use + generate_release_changelog + + # [NOTE]: ###################################################################################### + # The github workflow in surpher/PactSwiftServer repo tags the release when the PR is merged! + ################################################################################################ + + local git_commands=( + "git add ${change_log} ${xcframework}" + "git commit -S -m \"$release_name\"" "git stash -u" - "git merge $REMOTE_NAME/main --allow-unrelated-histories -X theirs -S --no-edit" + "git merge $remote_name/main --allow-unrelated-histories -X theirs -S --no-edit" "git stash pop" - "git tag $VERSION_TAG -F ${TAG_MESSAGE_FILE}" - "git push $REMOTE_NAME $REMOTE_BRANCH" + "git push $remote_name $remote_branch" ) git_clean_orphan - if [ "$DRY_RUN" = false ]; then - for command in "${GIT_COMMANDS[@]}"; do - echo "โš™ $command" - eval "$command" - done - else - echo "๐Ÿธ DRY RUN..." - for command in "${GIT_COMMANDS[@]}"; do - echo "๐Ÿ‘ป $command" - done - fi + for command in "${git_commands[@]}"; do + execute_cmd "$command" + done } function cleanup { local git_clean="git clean -f -d -x" + local current_branch=$CURRENT_BRANCH + local release_version_branch=$VERSION_BRANCH + echo "๐Ÿงน Cleanning up..." - echo "$git_clean" - eval "$git_clean" + execute_cmd "$git_clean" + + echo "โฎ๏ธ Checking out $current_branch..." + git checkout --force "$current_branch" - echo "โฎ๏ธ Checking out $CURRENT_BRANCH..." - git checkout "$CURRENT_BRANCH" + echo -e "๐Ÿงน Removing $YELLOW$release_version_branch$NOCOLOR" + git branch -D "$release_version_branch" } -function generate_changelog { - echo "๐Ÿ“ Generating release notes in $RELEASE_NOTES..." +function generate_release_changelog { + echo "๐Ÿ“ Generating release notes..." + local release_notes_title= + release_notes_title=$(head -n 1 "$CHANGE_LOG") + local release_notes_existing= + release_notes_existing=$(tail -n +2 "$CHANGE_LOG") + + { + echo "$release_notes_title" + echo + cat "${TAG_MESSAGE_FILE}" + echo "$release_notes_existing" + } > "$CHANGE_LOG" +} + +function update_pactswiftmockserver_changelog { + echo "๐Ÿ“ Generating release notes in $CHANGE_LOG..." # Prepare the title for this release echo "## ${VERSION_TAG}" > "${TAG_MESSAGE_FILE}" @@ -223,28 +233,30 @@ function generate_changelog { echo -e "๐Ÿชต Logging '${LATEST_TAG}..HEAD'..." git log --pretty='* %h - %s (%an)' "${LATEST_TAG}"..HEAD >> "${TAG_MESSAGE_FILE}" - RELEASE_NOTES_TITLE=$(head -n 1 "$RELEASE_NOTES") - RELEASE_NOTES_EXISTING=$(tail -n +2 "$RELEASE_NOTES") + local release_notes_title= + release_notes_title=$(head -n 1 "$CHANGE_LOG") + local release_notes_existing= + release_notes_existing=$(tail -n +2 "$CHANGE_LOG") # Inject the new commits between title and last release { - echo "$RELEASE_NOTES_TITLE" + echo "$release_notes_title" echo cat "${TAG_MESSAGE_FILE}" - echo "$RELEASE_NOTES_EXISTING" - } > "$RELEASE_NOTES" + echo "$release_notes_existing" + } > "$CHANGE_LOG" } function git_commit_changelog_and_xcconfig { local marketing_version="${MARKETING_VERSION:-1}" - echo "๐Ÿฉน Committing $RELEASE_NOTES to git..." - git add "${RELEASE_NOTES}" "${CONFIGURATION_FILE}" + echo "๐Ÿฉน Committing $CHANGE_LOG to git..." + git add "${CHANGE_LOG}" "${CONFIGURATION_FILE}" git commit -m "$marketing_version: Release notes" } function git_reset_changelog { - echo "๐Ÿ”™ Resetting last commit" - git reset --soft HEAD~1 + echo "๐Ÿ”™ Dropping last commit..." + git reset --hard HEAD~1 git status } @@ -256,7 +268,7 @@ function git_push_changelog { ################## # Pre-checks ################## -while getopts ":v:d:-:" opt; do +while getopts ":v:d:h:-:" opt; do case ${opt} in v) RELEASE_VERSION_PART=$OPTARG @@ -264,16 +276,22 @@ while getopts ":v:d:-:" opt; do d) RELEASE_DESCRIPTION=$OPTARG ;; + h) + show_help + ;; -) case "${OPTARG}" in dry-run) DRY_RUN=true - shift 2 + shift ;; description) RELEASE_DESCRIPTION=$1 shift 2 ;; + help) + show_help + ;; *) echo "Invalid option: --${OPTARG}" >&2 show_help @@ -309,10 +327,10 @@ echo "๐ŸŒŽ Adding '$REMOTE_NAME' remote..." git_add_remote "$REMOTE_NAME" "$REMOTE_REPO_BASE/$RELEASE_REPO_NAME.git" # Generate release notes -echo "๐Ÿ“ Generate changelog" -generate_changelog +echo "๐Ÿ“ Update changelog for PactSwiftMockServer" +update_pactswiftmockserver_changelog -# Bump up the MARKETING_VERSION in xcconfig +# Bump up the MARKETING_VERSION in xcconfig so XCFramework uses the right version echo -e "๐Ÿ‘Š Bumping up MARKETING_VERSION in xcconfig to $MARKETING_VERSION..." update_version_file "$MARKETING_VERSION" @@ -345,12 +363,12 @@ github_open_draft_pull_request "$MARKETING_VERSION" "$VERSION_BRANCH" # Cleanup ################## if [ "$DRY_RUN" = true ]; then - cmd="git checkout --force $CURRENT_BRANCH" + cmd="git clean -f -d -x && git checkout --force $CURRENT_BRANCH" echo "๐Ÿธ Dry run done" read -r -p "Clean up what has been generated during dry run? [Y/n]" -n 1 USER_INPUT echo if [[ $USER_INPUT =~ ^[Yy]$ ]]; then - eval "$cmd" + execute_cmd "$cmd" git_reset_changelog else echo "๐Ÿ™Œ Leaving everything that's been generated during dry run as is..." diff --git a/Support/Scripts/release_revert b/Support/Scripts/release_revert new file mode 100755 index 0000000..a85997a --- /dev/null +++ b/Support/Scripts/release_revert @@ -0,0 +1,228 @@ +#!/usr/bin/env bash +# shellcheck disable=SC1091 + +set -eu +set -o pipefail + +RELEASE_SOURCE_DIR="${BASH_SOURCE[0]%/*}" + +# "import" +source "$RELEASE_SOURCE_DIR/utils.sh" +source "$RELEASE_SOURCE_DIR/CI/version_numbers.sh" +source "$RELEASE_SOURCE_DIR/Config/config.sh" + +function show_help { + echo "Reverts the attempt of releasing a new XCFramework." + echo + echo "Usage:" + echo " $0 --pr-number --tag --branch-name [--dry-run]" + echo + echo "Options:" + echo " -p, --pr-number The name of the open pull request at https://github.com/surpher/PactSwiftServer/pulls" + echo " -t, --tag The tag associated with attempted release" + echo " -b, --branch-name The name of the local branch associated with attempted release" + echo " --dry-run Prints the commands that would have been executed" + echo " -h, --help Prints this message" + exit 2 +} + +# Check if we can even do it... +is_tool_installed gh + +# Parameters +REVERT_DRY_RUN=${REVERT_DRY_RUN:-false} +TO_DELETE_BRANCH= +TO_DELETE_PR_NUMBER= +TO_DELETE_TAG= + +# Scirpt inputs +while getopts ":p:t:b:h:-:" opt; do + case ${opt} in + p) + TO_DELETE_PR_NUMBER=$OPTARG + ;; + t) + TO_DELETE_TAG=$OPTARG + ;; + b) + TO_DELETE_BRANCH=$OPTARG + ;; + h) + show_help + ;; + -) + case "${OPTARG}" in + dry-run) + REVERT_DRY_RUN=true + shift + ;; + pr-number) + TO_DELETE_PR_NUMBER=$2 + shift 2 + ;; + tag) + TO_DELETE_TAG=$2 + shift 2 + ;; + branch-name) + TO_DELETE_BRANCH=$2 + shift 2 + ;; + help) + show_help + ;; + *) + echo "Invalid option: --${OPTARG}" >&2 + show_help + ;; + esac + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + show_help + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + show_help + ;; + esac +done + +########################## +# Utility funcs +########################## + +# Execute command depending on dry run option +function execute_cmd { + local is_dry_run=$REVERT_DRY_RUN + if [ "$is_dry_run" = true ]; then + echo -e "${CYAN}DRY RUN:$NOCOLOR $*" + else + executeCommand "$@" + fi +} + +# Close the pull request and delete the associated branch +function close_pull_request_delete_branch { + local pr_number="$1" + execute_cmd "gh config set prompt disabled" + execute_cmd "gh repo set-default $REPO_OWNER/$RELEASE_REPO_NAME" + execute_cmd "gh pr close $pr_number --delete-branch" + execute_cmd "gh repo set-default $REPO_OWNER/$DEFAULT_REPO_NAME" + execute_cmd "gh config set prompt enabled" + + # Check if closing PR and deleting branch was successful + if [ $? -ne 0 ]; then + die "Failed to close pull request #$pr_number or delete the associated branch." + fi +} + +function is_branch_in_local { + local branch=$1 + local existed_in_local= + existed_in_local=$(git branch --list ${branch})$(git branch --list ${branch}) + + if [[ -z ${existed_in_local} ]]; then + echo "Branch does not exist locally." + return 0 + else + echo "Branch exists locally." + return 1 + fi +} + +# Remove local branch +function delete_local_branch { + local local_branch="$1" + + if is_branch_in_local "$local_branch"; then + execute_cmd "git branch -d $local_branch" + + if [ $? -ne 0 ]; then + git branch -D "$local_branch" + if [ $? -ne 0 ]; then + echo -e "${YELLOW}Failed to delete local branch '$local_branch'$NOCOLOR" + fi + if [ "$REVERT_DRY_RUN" = true ]; then + echo -e "${CYAN}DRY RUN:$NOCOLOR $LIGHT_BLUE$local_branch would be force deleted$NOCOLOR" + else + echo "Branch '$local_branch' deleted" + fi + fi + else + echo "Branch '$local_branch' does not exist locally." + fi +} + +# Remove the tag +function remove_asssociated_tags { + local tags= + local tag_to_delete="$1" + tags=$(git tag --list | grep "$tag_to_delete") + + if [ -n "$tags" ]; then + for tag in $tags; do + execute_cmd "git tag -d \"$tag\"" + execute_cmd "git push origin --delete \"$tag\"" + if [ $? -ne 0 ]; then + die "Failed to delete tag '$tag'." + fi + if [ "$REVERT_DRY_RUN" = true ]; then + echo -e "${CYAN}DRY RUN:$NOCOLOR Tag $LIGHT_BLUE$tag would be deleted$NOCOLOR" + else + echo "Deleted tag '$tag'" + fi + done + else + echo "No tags found matching '#$tag_to_delete'" + fi +} + +# Reset the last commit here to revert the updated CHANGELOG +function git_reset_last_commit { + local reset_type="soft" + local value=$1 + + if [[ "$value" == "soft" ]]; then + reset_type="soft" + elif [[ "$value" == "hard" ]]; then + reset_type="hard" + else + die "Invalid value. Use either 'soft' or 'hard'." + fi + + execute_cmd "git reset --$reset_type HEAD~1" +} + +################### +# Script execution +################### + +# Closing the opened PR +close_pull_request_delete_branch "$TO_DELETE_PR_NUMBER" + +# Removing tags +remove_asssociated_tags "$TO_DELETE_TAG" + +# Deleting local branch +delete_local_branch "$TO_DELETE_BRANCH" + +# Reset last commit +git_reset_last_commit "soft" + +# We're done(ish) +if [ "$REVERT_DRY_RUN" = true ]; then + # A list of results + REVERTED_RESULTS=( + "๐Ÿ‘ Done:" + " - closed pull request $LIGHT_BLUE#$TO_DELETE_PR_NUMBER$NOCOLOR," + " - deleted the associated branch," + " - removed any associated tags," + " - deleted local branch $LIGHT_BLUE$TO_DELETE_BRANCH$NOCOLOR." + ) + + for message in "${REVERTED_RESULTS[@]}"; do + echo -e "${CYAN}DRY RUN:$NOCOLOR $message" + done +fi +exit 0 diff --git a/Support/Scripts/utils.sh b/Support/Scripts/utils.sh index 8ba01c9..15447b8 100644 --- a/Support/Scripts/utils.sh +++ b/Support/Scripts/utils.sh @@ -18,15 +18,32 @@ # IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -set -euo pipefail +set -eu +set -o pipefail + +PACT_SWIFT_MOCK_SERVER_CONFIG="${BASH_SOURCE[0]%/*}/Config/config.sh" + +function die { + echo -e "๐Ÿšจ ${RED}error:${NOCOLOR} $*" + exit 1 +} + +function is_tool_installed { + local tool=$1 + # shellcheck disable=SC2086 + if ! command -v $1 >/dev/null 2>&1; then + die "'$YELLOW$1$NOCOLOR' not installed! + ๐Ÿ’ก See https://cli.github.com/ to install it." + fi + return 0 +} function executeCommand { if [ $# -eq 0 ]; then - echo -e "No command provided" - exit 1 + die "No command provided" else - COMMAND="$1" - printf "๐Ÿค– Executing:\n '%s'\n" "$COMMAND" + local COMMAND="$1" + printf "๐Ÿค– ${LIGHT_BLUE}Executing:${NOCOLOR}\n '%s'\n" "$COMMAND" eval "$COMMAND" fi } @@ -48,11 +65,6 @@ function fileExists { } # Xcode version number -# Only care about major and minor -MIN_XCODE_VERSION="16.1" -XCODE_VERSION_MIN_SUGGESTED="16.1" -XCODE_VERSION_MIN_SUPPORTED="16.0" - function __check_xcode_version_number { local major=${1:-0} local minor=${2:-0}