diff --git a/.glue/actions/auto/util-Bash-version-bump.sh b/.glue/actions/auto/util-Bash-version-bump.sh index 7025d6f..0763402 100644 --- a/.glue/actions/auto/util-Bash-version-bump.sh +++ b/.glue/actions/auto/util-Bash-version-bump.sh @@ -7,10 +7,14 @@ main() { local newVersion="$1" ensure.nonZero 'newVersion' "$newVersion" - # TODO: show which files changed + # TODO: better output + exec 8> >(xargs -r0 -- grep -l "PROGRAM_VERSION") find . -ignore_readdir_race -regex '\./pkg/.*\.\(sh\|bash\)' -print0 2>/dev/null \ - | xargs -r0 \ - sed -i -e "s|\(PROGRAM_VERSION=\"\).*\(\"\)|\1$newVersion\2|g" + | tee /dev/fd/8 \ + | xargs -r0 -- sed -i -e "s|\(PROGRAM_VERSION=\"\).*\(\"\)|\1$newVersion\2|g" + exec 8>&- + + wait log.info "util-Bash-version-bump: Bump done" } diff --git a/.glue/actions/auto/util-get-version.sh b/.glue/actions/auto/util-get-version.sh deleted file mode 100755 index 953e5a6..0000000 --- a/.glue/actions/auto/util-get-version.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -unset main -main() { - # If the working tree is dirty and there are unstaged changes - # for both tracked and untracked files - local dirty= - if [ -n "$(git status --porcelain)" ]; then - dirty=yes - fi - - # Get the most recent Git tag that specifies a version - local version - if version="$(git describe --match 'v*' --abbrev=0 2>/dev/null)"; then - version="${version/#v/}" - else - version="0.0.0" - fi - - local id="$(git rev-parse --short HEAD)" - version+="+$id${dirty:+-DIRTY}" - REPLY="$version" -} - -main "$@" -unset main diff --git a/.glue/actions/auto/util-release-post.sh b/.glue/actions/auto/util-release-post.sh deleted file mode 100755 index be197f4..0000000 --- a/.glue/actions/auto/util-release-post.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env bash -eval "$GLUE_BOOTSTRAP" -bootstrap || exit - -# @file util-release-post.sh -# @brief Steps to perform after specialized version bumping -# - Ensure a dirty Git working tree -# - Add bumped files to commit with version number -# - Make GitHub release - -unset main -main() { - local -r dryStatus="$1" - local newVersion="$2" - - ensure.cmd 'git' - ensure.cmd 'gh' - - ensure.nonZero 'newVersion' "$newVersion" - - isDry() { - # must be set to 'notDry' to not be dry. - # Defaults to 'not dry' - [ "$dryStatus" != "notDry" ] - } - - if isDry; then - log.info "Running release process in dry mode" - fi - - # Ensure working tree is dirty - if [ -z "$(git status --porcelain)" ]; then - if isDry; then - local cmd="log.warn" - else - local cmd="die" - fi - - "$cmd" 'Working tree is not dirty. Cannot make a release if versions have not been bumped in their respective files' - fi - - # Local Release - if isDry; then - log.info "Skipping Git taging and artifact release" - else - git add -A - git commit -m "chore(release): v$newVersion" - git tag -a "v$newVersion" -m "Release $newVersion" HEAD - git push --follow-tags origin HEAD - - local -a args=() - if [ -f CHANGELOG.md ]; then - args+=("-F" "CHANGELOG.md") - elif [ -f changelog.md ]; then - args+=("-F" "changelog.md") - else - # '-F' is required for non-interactivity - args+=("-F" "") - log.warn 'CHANGELOG.md file not found. Creating empty notes file for release' - fi - - # Remote Release - gh release create "v$newVersion" --target main --title "v$newVersion" "${args[@]}" - fi -} - -main "$@" -unset main - -unbootstrap diff --git a/.glue/actions/auto/util-release-pre.sh b/.glue/actions/auto/util-release-pre.sh deleted file mode 100755 index 5d3564a..0000000 --- a/.glue/actions/auto/util-release-pre.sh +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env bash -eval "$GLUE_BOOTSTRAP" -bootstrap || exit - -# @file util-release-pre.sh -# @brief Steps to perform before specialized version bumping -# @description This does the following -# - Ensures a clean Git working tree -# - Ensures a shared history (no force pushing) -# - Update version in 'glue-auto.toml' - -unset main -main() { - ensure.cmd 'git' - ensure.file 'glue-auto.toml' - - local -r dryStatus="$1" - isDry() { - # must be set to 'notDry' to not be dry. - # Defaults to 'not dry' - [ "$dryStatus" != "notDry" ] - } - - if isDry; then - log.info "Running pre-release process in dry mode" - fi - - # Ensure working tree not dirty - if [ -n "$(git status --porcelain)" ]; then - if isDry; then - local cmd="log.warn" - else - local cmd="die" - fi - - "$cmd" 'Working tree still dirty. Please commit all changes before making a release' - fi - - # Ensure we can push new version and its tags changes without --force-lease - if ! git merge-base --is-ancestor origin/main main; then - if isDry; then - local cmd="log.warn" - else - local cmd="die" - fi - - # main NOT is the same or has new additional commits on top of origin/main" - "$cmd" "Detected that your 'main' branch and it's remote have diverged. Won't initiate release process until histories are shared" - fi - - local newVersion= - if isDry; then - # Calculate new version based on current commit - - # glue useAction(util-get-version.sh) - util.get_action "util-get-version.sh" - source "$REPLY" - newVersion="$REPLY" - else - # Get current version - toml.get_key version glue-auto.toml - local currentVersion="$REPLY" - - # Get new version number - # TODO: make incremenet better - echo "Current Version: $currentVersion" - read -rp 'New Version? ' -ei "$currentVersion" - newVersion="$REPLY" - declare -g REPLY="$newVersion" # explicit - - # Ensure new version is valid (does not already exist) - if [ -n "$(git tag -l "v$newVersion")" ]; then - # TODO: ensure there are no tags that exists that are greater than it - die 'Version already exists in a Git tag' - fi - fi - - sed -i -e "s|\(version[ \t]*=[ \t]*\"\).*\(\"\)|\1${newVersion}\2|g" glue-auto.toml -} - -main "$@" -unset main - -unbootstrap diff --git a/.glue/commands/auto/Bash.build.sh b/.glue/commands/auto/Bash.build.sh index 3095971..bd35c86 100755 --- a/.glue/commands/auto/Bash.build.sh +++ b/.glue/commands/auto/Bash.build.sh @@ -2,13 +2,25 @@ eval "$GLUE_BOOTSTRAP" bootstrap || exit -# glue useAction(util-get-version.sh) -util.get_action 'util-get-version.sh' -source "$REPLY" -declare newVersion="$REPLY" - -# glue useAction(util-Bash-version-bump.sh) -util.get_action 'util-Bash-version-bump.sh' -source "$REPLY" "$newVersion" +unset task +task() { + ensure.file 'glue-auto.toml' + toml.get_key 'version' 'glue-auto.toml' + local newVersion="$REPLY" + + if [ -z "$newVersion" ]; then + util.git_generate_version + newVersion="$REPLY" + fi + + util.general_version_bump "$newVersion" + + # glue useAction(util-Bash-version-bump.sh) + util.get_action 'util-Bash-version-bump.sh' + source "$REPLY" "$newVersion" +} + +task "$@" +unset task unbootstrap diff --git a/.glue/commands/auto/Bash.release.sh b/.glue/commands/auto/Bash.release.sh index a514a0d..2f4d6db 100755 --- a/.glue/commands/auto/Bash.release.sh +++ b/.glue/commands/auto/Bash.release.sh @@ -2,26 +2,92 @@ eval "$GLUE_BOOTSTRAP" bootstrap || exit -# -# glue useAction(util-release-pre.sh) -util.get_action 'util-release-pre.sh' -source "$REPLY" 'notDry' -newVersion="$REPLY" - -# Bash version bump -( - find . -ignore_readdir_race -regex '\./pkg/.*\.\(sh\|bash\)' -print0 \ - | xargs -r0 \ - sed -i -e "s|\(PROGRAM_VERSION=\"\).*\(\"\)|\1${newVersion}\2|g" || : -) || exit - -# glue useAction(util-release-post.sh) -util.get_action 'util-release-post.sh' -source "$REPLY" 'notDry' "$newVersion" - -# glue useAction(result-pacman-package.sh) -util.get_action 'result-pacman-package.sh' -source "$REPLY" - -unset newVersion +unset task +task() { + declare -g RELEASE_STATUS=dry + for arg; do + case "$arg" in + --wet) + # shellcheck disable=SC2034 + RELEASE_STATUS=wet + esac + done + + ensure.cmd 'git' + ensure.cmd 'gh' + ensure.file 'glue-auto.toml' + + if is.dry_release; then + log.info "Running pre-release process in dry mode" + else + ensure.confirm_wet_release + fi + + # Perform build + # TODO: perform build on commit hook etc. + util.get_command 'Bash.build.sh' + source "$REPLY" + + # Ensure tests pass + util.get_command 'Bash.test.sh' + source "$REPLY" + + # Build docs + util.get_command 'Bash.docs.sh' + source "$REPLY" + + ensure.git_working_tree_clean + ensure.git_common_history + + local newVersion= + if is.dry_release; then + toml.get_key 'version' 'glue-auto.toml' + newVersion="$REPLY" + + ensure.nonZero 'newVersion' "$newVersion" + else + toml.get_key 'version' 'glue-auto.toml' + util.prompt_new_version "$REPLY" + newVersion="$REPLY" + + ensure.nonZero 'newVersion' "$newVersion" + ensure.version_is_only_major_minor_patch "$newVersion" + ensure.git_version_tag_validity "$newVersion" + fi + + util.general_version_bump "$newVersion" + + ensure.git_working_tree_dirty + + if is.dry_release; then + log.info "Skipping Git taging and artifact release" + else + git add -A + git commit -m "chore(release): v$newVersion" + git tag -a "v$newVersion" -m "Release $newVersion" HEAD + git push --follow-tags origin HEAD + + local -a args=() + if [ -f CHANGELOG.md ]; then + args+=("-F" "CHANGELOG.md") + elif [ -f changelog.md ]; then + args+=("-F" "changelog.md") + else + # '-F' is required for non-interactivity + args+=("-F" "") + log.warn 'CHANGELOG.md file not found. Creating empty notes file for release' + fi + + # Remote Release + gh release create "v$newVersion" --target main --title "v$newVersion" "${args[@]}" + fi + + # glue useAction(result-pacman-package.sh) + util.get_action 'result-pacman-package.sh' + source "$REPLY" +} + +task "$@" +unset task + unbootstrap diff --git a/.glue/commands/auto/Bash.releaseDry.sh b/.glue/commands/auto/Bash.releaseDry.sh deleted file mode 100755 index cc598ee..0000000 --- a/.glue/commands/auto/Bash.releaseDry.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bash -eval "$GLUE_BOOTSTRAP" -bootstrap || exit - -# glue useAction(util-release-pre.sh) -util.get_action 'util-release-pre.sh' -source "$REPLY" 'dry' -newVersion="$REPLY" - -# Bash version bump -( - find . -ignore_readdir_race -regex '\./pkg/.*\.\(sh\|bash\)' -print0 \ - | xargs -r0 \ - sed -i -e "s|\(PROGRAM_VERSION=\"\).*\(\"\)|\1${newVersion}\2|g" || : -) || exit - -# glue useAction(util-release-post.sh) -util.get_action 'util-release-post.sh' -source "$REPLY" 'dry' "$newVersion" - -# glue useAction(result-pacman-package.sh) -util.get_action 'result-pacman-package.sh' -source "$REPLY" - -unset newVersion - -unbootstrap diff --git a/.glue/common/auto/action.sh b/.glue/common/auto/action.sh index c9044c3..5f41e1c 100644 --- a/.glue/common/auto/action.sh +++ b/.glue/common/auto/action.sh @@ -17,19 +17,20 @@ action.log() { if [ "${currentActionDirname##*/}" = auto ]; then if [[ "${LANG,,?}" == *utf?(-)8 ]]; then - echo "■■■■ 🢂 START ACTION -> auto/${currentAction##*/}" + echo "■■■■ 🢂 START ACTION: 'auto/${currentAction##*/}'" else - echo ":::: => START ACTION -> auto/${currentAction##*/}" + echo ":::: => START ACTION: 'auto/${currentAction##*/}'" fi else if [[ "${LANG,,?}" == *utf?(-)8 ]]; then - echo "■■■■ 🢂 START ACTION -> ${currentAction##*/}" + echo "■■■■ 🢂 START ACTION: '${currentAction##*/}'" else - echo ":::: => START ACTION -> ${currentAction##*/}" + echo ":::: => START ACTION: '${currentAction##*/}'" fi fi - (( extGlobExitStatus != 0 )) && shopt -u extglob - + if (( extGlobExitStatus != 0 )); then + shopt -u extglob + fi } diff --git a/.glue/common/auto/bootstrap.sh b/.glue/common/auto/bootstrap.sh index 23840fc..8d8688f 100644 --- a/.glue/common/auto/bootstrap.sh +++ b/.glue/common/auto/bootstrap.sh @@ -58,7 +58,9 @@ bootstrap() { fi done - (( shoptExitStatus != 0 )) && shopt -u nullglob + if (( shoptExitStatus != 0 )); then + shopt -u nullglob + fi for file in "${filesToSource[@]}"; do source "$file" diff --git a/.glue/common/auto/command.sh b/.glue/common/auto/command.sh index 2b4d818..0dfb51e 100644 --- a/.glue/common/auto/command.sh +++ b/.glue/common/auto/command.sh @@ -7,17 +7,25 @@ command.log() { local currentCommand="${BASH_SOURCE[2]}" local currentCommandDirname="${currentCommand%/*}" + shopt -q extglob + local extGlobExitStatus=$? + shopt -s extglob + if [ "${currentCommandDirname##*/}" = auto ]; then if [[ "${LANG,,?}" == *utf?(-)8 ]]; then - echo "■■ 🢂 START COMMAND -> auto/${currentCommand##*/}" + echo "■■ 🢂 START COMMAND: 'auto/${currentCommand##*/}'" else - echo ":: => START COMMAND -> auto/${currentCommand##*/}" + echo ":: => START COMMAND: 'auto/${currentCommand##*/}'" fi else if [[ "${LANG,,?}" == *utf?(-)8 ]]; then - echo "■■ 🢂 START COMMAND -> ${currentCommand##*/}" + echo "■■ 🢂 START COMMAND: '${currentCommand##*/}'" else - echo ":: => START COMMAND -> ${currentCommand##*/}" + echo ":: => START COMMAND: '${currentCommand##*/}'" fi fi + + if (( extGlobExitStatus != 0 )); then + shopt -u extglob + fi } diff --git a/.glue/common/auto/ensure.sh b/.glue/common/auto/ensure.sh index 4417042..02e9b28 100644 --- a/.glue/common/auto/ensure.sh +++ b/.glue/common/auto/ensure.sh @@ -3,6 +3,8 @@ ensure.cmd() { local cmd="$1" + ensure.nonZero 'cmd' "$cmd" + if ! command -v "$cmd" &>/dev/null; then die "Command '$cmd' not found" fi @@ -13,6 +15,9 @@ ensure.args() { local argNums="$2" shift; shift; + ensure.nonZero 'fnName' "$fnName" + ensure.nonZero 'argNums' "$argNums" + local argNum for argNum in $argNums; do if [ -z "${!argNum}" ]; then @@ -37,6 +42,8 @@ ensure.nonZero() { ensure.file() { local fileName="$1" + ensure.nonZero 'fileName' "$fileName" + if [ ! -f "$fileName" ]; then die "ensure.file: File '$fileName' does not exist" fi @@ -45,7 +52,82 @@ ensure.file() { ensure.dir() { local dirName="$1" + ensure.nonZero 'dirName' "$dirName" + if [ ! -f "$dirName" ]; then die "ensure.file: File '$dirName' does not exist" fi } + +ensure.git_working_tree_dirty() { + if is.git_working_tree_dirty; then + local cmd + if is.dry_release; then + cmd="log.warn" + else + cmd="die" + fi + + "$cmd" 'Git working directory is dirty. Please commit your changes and try again' + fi +} + +ensure.git_working_tree_clean() { + if ! is.git_working_tree_dirty; then + local cmd + if is.dry_release; then + cmd="log.warn" + else + cmd="die" + fi + + "$cmd" 'Git working directory is clean. Changes to tracked files should have been made' + fi +} + +ensure.git_common_history() { + local remote="${1-origin/main}" + local branch="${2:-main}" + + if ! git merge-base --is-ancestor "$remote" "$branch"; then + local cmd + if isDry; then + cmd="log.warn" + else + cmd="die" + fi + + # main NOT is the same or has new additional commits on top of origin/main" + "$cmd" "Detected that your 'main' branch and it's remote have diverged. Won't initiate release process until histories are shared" + fi + +} + +# TODO: ensure there are no tags that exists that are greater than it +ensure.git_version_tag_validity() { + local version="$1" + + ensure.nonZero 'version' "$version" + + if [ -n "$(git tag -l "v$version")" ]; then + die 'Version already exists in a Git tag' + fi +} + +ensure.version_is_only_major_minor_patch() { + local version="$1" + + ensure.nonZero 'version' "$version" + + case "$version" in + *-*|*+*|*_*) + die 'Version string contains more than just major, minor, and patch numbers' + esac +} + +ensure.confirm_wet_release() { + read -rei 'Do wet release? ' + if [[ "$REPLY" != *y* ]]; then + die + fi +} diff --git a/.glue/common/auto/generated.sh b/.glue/common/auto/generated.sh index 94bba7b..fd52075 100644 --- a/.glue/common/auto/generated.sh +++ b/.glue/common/auto/generated.sh @@ -12,12 +12,28 @@ generated.in() { fi mkdir "$GLUE_WD/.glue/generated/$dir" - # TODO: clean up print - echo "-- generated.in: '$GENERATED_DIR'" + + shopt -q extglob + local extGlobExitStatus=$? + shopt -s extglob + + if [[ "${LANG,,?}" == *utf?(-)8 ]]; then + echo "■■■■■■ 🢂 IN GENERATED: '$GENERATED_DIR'" + else + echo "=> IN GENERATED: '$GENERATED_DIR'" + fi + + if (( extGlobExitStatus != 0 )); then + shopt -u extglob + fi } generated.out() { - echo "-- generated.out '$GENERATED_DIR'" + if [[ "${LANG,,?}" == *utf?(-)8 ]]; then + echo "■■■■■■ 🢂 OUT GENERATED: '$GENERATED_DIR'" + else + echo "=> OUT GENERATED: '$GENERATED_DIR'" + fi cd "$GLUE_WD" || error.cd_failed } diff --git a/.glue/common/auto/is.sh b/.glue/common/auto/is.sh new file mode 100644 index 0000000..3626f9f --- /dev/null +++ b/.glue/common/auto/is.sh @@ -0,0 +1,11 @@ +# shellcheck shell=bash + +is.git_working_tree_dirty() { + [ -n "$(git status --porcelain)" ] +} + +# must be set to 'wet' to not be dry, which so +# that it defaults to 'dry' on empty +is.dry_release() { + [ "$RELEASE_STATUS" != 'wet' ] +} diff --git a/.glue/common/auto/util.sh b/.glue/common/auto/util.sh index 02099b8..b446c90 100644 --- a/.glue/common/auto/util.sh +++ b/.glue/common/auto/util.sh @@ -21,6 +21,9 @@ util.get_file() { local dir="$1" local file="$2" + ensure.nonZero 'dir' "$dir" + ensure.nonZero 'file' "$file" + REPLY= if [ -f "$GLUE_WD/.glue/$dir/$file" ]; then REPLY="$GLUE_WD/.glue/$dir/$file" @@ -87,3 +90,44 @@ util.shopt() { shopt "$1" "$2" _util_shopt_data+="$1.$2 " } + +util.prompt_new_version() { + local currentVersion="$1" + + ensure.nonZero 'currentVersion' "$currentVersion" + + # TODO: make incremenet better + REPLY= + echo "Current Version: $currentVersion" + read -rp 'New Version? ' -ei "$currentVersion" +} + +util.git_generate_version() { + REPLY= + + # If the working tree is dirty and there are unstaged changes + # for both tracked and untracked files + local dirty= + if is.git_working_tree_dirty; then + dirty=yes + fi + + # Get the most recent Git tag that specifies a version + local version + if version="$(git describe --match 'v*' --abbrev=0 2>/dev/null)"; then + version="${version/#v/}" + else + version="0.0.0" + fi + + local id + id="$(git rev-parse --short HEAD)" + version+="+$id${dirty:+-DIRTY}" + REPLY="$version" +} + +util.general_version_bump() { + local newVersion="$1" + + sed -i -e "s|\(version[ \t]*=[ \t]*\"\).*\(\"\)|\1${newVersion}\2|g" glue-auto.toml +} diff --git a/.glue/configs/auto/result-pacman-package/dev/PKGBUILD b/.glue/configs/auto/result-pacman-package/dev/PKGBUILD index ec9d36f..30af8d2 100644 --- a/.glue/configs/auto/result-pacman-package/dev/PKGBUILD +++ b/.glue/configs/auto/result-pacman-package/dev/PKGBUILD @@ -12,7 +12,8 @@ sha256sums=() check() { # TODO: bootstrap - ~/repos/glue/glue.sh cmd test + # ~/repos/glue/glue.sh cmd test + : } package() {