#!/usr/bin/env bash # # CLI tool for GitHub release management # # Note: # For conventional-github-releaser to work you'll need to setup a new GitHub # app token, then add the `CONVENTIONAL_GITHUB_RELEASER_TOKEN` environment # variable to your shell. You can find more details here: # https://github.com/stevemao/$conventionalGithubReleaser#setup-token-for-cli # # Usage: # githubrelease -h # # defaults to conventional-recommended-bump # and optional argument preset `angular`/ `jquery` ... # defaults to conventional-commits-detector #------------------------------------------------------------------------------- # Does the following: # 1. pull from remote origin for master and develop branches # 2. rebase master onto develop # 3. rebase develop onto master # 4. grabs the short SHA of master # 5. updates changelog # 6. bumps version number # 7. commits changes # 8. creates a version tag # 9. pushes master and tag to remote # 10. rebase master onto develop # 11. pushes develop to remote # 12. creates a release on github # 13. output message "Landed in " #------------------------------------------------------------------------------- set -e chalk="$(_githubreleasehelper -p chalk)" conventionalChangelog="$(_githubreleasehelper -p conventional-changelog)" conventionalCommitsDetector="$(_githubreleasehelper -p conventional-commits-detector)" conventionalGithubReleaser="$(_githubreleasehelper -p conventional-github-releaser)" conventionalRecommendedBump="$(_githubreleasehelper -p conventional-recommended-bump)" json="$(_githubreleasehelper -p json)" trash="$(_githubreleasehelper -p trash)" shortsha="" # ------------------------------------------------------------------------------ # Helper commands # ------------------------------------------------------------------------------ # Universal logger for this script log() { local output="$counter" [[ $counter -lt 10 ]] && output="$counter " $chalk cyan "[step: $output] $1" [[ "$1" != "startOnMaster" ]] && [[ "$1" != "startOnDevelop" ]] && increment } # Step counter increment() { ((counter+=1)) } # Get the current Git branch gitCurrBranch() { ref=$(command git symbolic-ref HEAD 2> /dev/null) || \ ref=$(command git rev-parse --short HEAD 2> /dev/null) || return echo "${ref#refs/heads/}" } # ------------------------------------------------------------------------------ # Script commands # ------------------------------------------------------------------------------ # Switch the current branch to startOnMaster() { log "startOnMaster" if [ $(gitCurrBranch) != $master ]; then git checkout $master fi } # Switch the current branch to startOnDevelop() { log "startOnDevelop" if [ $(gitCurrBranch) != $develop ]; then git checkout $develop fi } # Pull latest changes from and using rebase updateBranches() { log "updateBranches" git pull --rebase origin $master && git checkout $develop && git pull --rebase origin $develop } # Rebase the branch onto rebaseDevelopOntoMaster() { log "rebaseDevelopOntoMaster" git rebase $develop } # Run npm tests runTests() { log "runTests" if [[ -z $argnotrashnodemodules ]]; then $trash node_modules &>/dev/null fi ${npmclient} install --silent ${npmclient} run test } # Get the short sha getShortSha() { echo "$(git rev-parse --short HEAD)" } # Make a backup of the package.json backupPackage() { log "backupPackage" cp package.json _package.json [[ -f package-lock.json ]] && cp package-lock.json _package-lock.json [[ -f yarn.lock ]] && cp yarn.lock _yarn.lock } # Use npm to bump the version of the package.json bumpPackage() { log "bumpPackage" if [[ "${npmclient}" == "yarn" ]]; then ${npmclient} version --no-git-tag-version --new-version ${bump} &>/dev/null else ${npmclient} version --no-git-tag-version ${bump} &>/dev/null fi } # Generate a changelog entry and save it to updateChangelog() { log "updateChangelog" local changelog="./CHANGELOG.md" if [[ ! -f "$changelog" ]]; then touch "$changelog" fi && $conventionalChangelog -i CHANGELOG.md -s -p ${preset} if [[ -z $argnoprompt ]]; then echo "" $chalk yellow "Review your \"$changelog\"." $chalk yellow "Would you like to continue?" select yn in "Yes" "No"; do case $yn in Yes ) break;; No ) exit;; esac done fi } # Add the to Git and commit it addChangelog() { log "addChangelog" local version=$(cat package.json | $json version) git add CHANGELOG.md git commit -m "docs(CHANGELOG): $version" } # Reset the package.json to the backup made earlier resetPackage() { log "resetPackage" mv -f _package.json package.json [[ -f package-lock.json ]] && mv -f _package-lock.json package-lock.json [[ -f yarn.lock ]] && mv -f _yarn.lock yarn.lock } # Using npm, bump the package.json version, create new tag, and commit change # to Git npmBump() { if [[ -z $argnogittag ]]; then log "npmBump: create tag" if [[ "${npmclient}" == "yarn" ]]; then ${npmclient} config set version-git-message "chore(release): %s" ${npmclient} version --new-version ${bump} else ${npmclient} version ${bump} -m "chore(release): %s" fi else log "npmBump: only bump" if [[ "${npmclient}" == "yarn" ]]; then ${npmclient} version --no-git-tag-version --new-version ${bump} &>/dev/null else ${npmclient} version --no-git-tag-version ${bump} &>/dev/null fi local version=$(cat package.json | $json version) git add package.json git commit -m "chore(release): $version" fi } # Rebase the branch onto rebaseMasterOntoDevelop() { log "rebaseMasterOntoDevelop" git rebase $master } # Push and branches and all tags to remote origin pushAll() { log "pushAll" git push origin $master && git push origin $develop && git push --tags } # Cut a release on GitHub and append the changelog to the GitHub release conventionalGitHubReleaser() { if [[ -z $argnogittag ]]; then log "conventionalGitHubReleaser: cut a release on GitHub" $conventionalGithubReleaser -p ${preset} else log "conventionalGitHubReleaser: skip release on GitHub" fi } landedMessage() { log "landedMessage" $chalk green "Landed in $shortsha" } # ------------------------------------------------------------------------------ # # ------------------------------------------------------------------------------ # Defaults defaultremote="origin" defaultmaster="master" defaultdevelop="develop" defaultchangelog="CHANGELOG.md" defaultnpmclient="npm" defaultstep=1 # Usage descriptions flag_h="Show this message." flag_v="Display the version of this script." flag_f="Display the flags used when showing config." flag_l="Generate a changelog entry and save it to ." flag_p="Push , , and tags to ." flag_u="Update and branches from using rebase." flag_n="Do not display any prompts" flag_t="Do not trash the node_modules folder before running npm run test." flag_T="Do not create a git tag and do no run conventionalGithubReleaser." args_c="Location of the . Defaults to 'CHANGELOG.md'." args_C="The NPM client to use. Defaults to 'npm'." args_m="The branch. Defaults to 'master'." args_d="The branch. Defaults to 'develop'." args_r="The push and pull from. Defaults to 'origin'." args_P="The preset style used to generate the changelog. Examples include" args_P2="['angular' | 'jquery' ...]. Defaults to 'conventional-recommended-bump'." args_s="Allows you to start at a specific step. Useful for when the script" args_s2="fails before completing all steps. See AVAILABLE STEPS below." args_b="The semver you would like to use for the release (default: patch)." args_b2="Can be: [ major | minor | patch | v0.0.0 ]" usage() { cat << EOF usage: githubrelease options Release a npm package to github. FLAG OPTIONS: -h $flag_h -v $flag_v -f $flag_f -l $flag_l -p $flag_p -u $flag_u -n $flag_n -t $flag_t -T $flag_T ARGUMENT OPTIONS: -c $args_c -C $args_C -m $args_m -d $args_d -r $args_r -P $args_P $args_P2 -s $args_s $args_s2 -b $args_b $args_b2 AVAILABLE STEPS: 1 Update and branches from using rebase. 2 Push , , and tags to . 3 Run 'npm test' 4 Rebase onto . 5 Make a copy of 'package.json' to '_package.json'. 6 Temporarily bump the semver of 'package.json'. 7 Save a changelog entry to . 8 Commit update to Git. 9 Reset 'package.json' by moveing '_package.json' to 'package.json'. 10 Run 'npm version' command, which bumps 'package.json' and creates a tag (only creates a tag if '-T' was not passed). 11 Rebase onto . 12 Push , , and tags to . 13 Run 'conventional-github-releaser' to create a release in GitHub. 14 Output landed message which contains the short sha of HEAD. EXAMPLES: 1. Update all branches 2. Push changes from all branches 3. Cut a release githubrelease -u githubrelease -p githubrelease -b patch EOF } # Argument variables argflags= arglog= argpull= argpush= argnoprompt= argnotrashnodemodules= argnogittag= argchangelog= argnpmclient= argmaster= argdevelop= argremote= argpreset= argstep= argbump= # Variables to use in script changelog= npmclient= master= develop= remote= preset= step= bump= flagschosen=() # Grab command line arguments and flags while getopts "hvflpuntTc:C:r:m:d:P:s:b:" OPTION do case $OPTION in # Flags h) usage exit 1 ;; v) echo "githubrelease v$(_githubreleasehelper --version)" exit 1 ;; f) argflags=1 flagschosen+=("[-f] $flag_f") ;; l) arglog=1 flagschosen+=("[-l] $flag_l") ;; p) argpush=1 flagschosen+=("[-p] $flag_p") ;; u) argpull=1 flagschosen+=("[-u] $flag_u") ;; n) argnoprompt=1 flagschosen+=("[-n] $flag_n") ;; t) argnotrashnodemodules=1 flagschosen+=("[-t] $flag_t") ;; T) argnogittag=1 flagschosen+=("[-T] $flag_T") ;; # Flags with args c) argchangelog=$OPTARG flagschosen+=("[-c] $args_c") ;; C) argnpmclient=$OPTARG flagschosen+=("[-C] $args_C") ;; r) argremote=$OPTARG flagschosen+=("[-r] $args_r") ;; m) argmaster=$OPTARG flagschosen+=("[-m] $args_m") ;; d) argdevelop=$OPTARG flagschosen+=("[-d] $args_d") ;; P) argpreset=$OPTARG flagschosen+=("[-P] $args_P $args_P2") ;; s) argstep=$OPTARG flagschosen+=("[-s] $args_s $args_s2") ;; b) argbump=$OPTARG flagschosen+=("[-b] $args_b $args_b2") ;; ?) usage exit ;; esac done printflags() { if [[ -n $argflags ]]; then echo "" echo "" echo "Flags Chosen:" for desc in "${flagschosen[@]}"; do echo "$desc" done fi } # Parse arguments and apply defaults if an argument was not passed [[ -n $argchangelog ]] && changelog=$argchangelog || changelog=$defaultchangelog [[ -n $argnpmclient ]] && npmclient=$argnpmclient || npmclient=$defaultnpmclient [[ -n $argremote ]] && remote=$argremote || remote=$defaultremote [[ -n $argmaster ]] && master=$argmaster || master=$defaultmaster [[ -n $argdevelop ]] && develop=$argdevelop || develop=$defaultdevelop [[ -n $argpreset ]] && preset=$argpreset || preset=$($conventionalCommitsDetector) [[ -n $argstep ]] && step=$argstep || step=$defaultstep # Check preset and default to "angular" [[ "$($conventionalRecommendedBump -p ${preset} 2>&1)" == *"does not exist"* ]] && preset="angular" [[ -n $argbump ]] && bump=$argbump || bump=$($conventionalRecommendedBump -p ${preset} 2> /dev/null) counter=$step # Config setup info currVersion=$(cat package.json | $json version) nextVersion=$(_githubreleasehelper --inc $currVersion --release $bump) # Output config setup $chalk cyan "========== CONFIG ==========" $chalk white << EOF log : $arglog pull : $argpull push : $argpush changelog : $changelog npmclient : $npmclient remote : $remote master : $master develop : $develop preset : $preset bump : $bump /dev/null)", chosen: "$bump"> currVersion : $currVersion nextVersion : $nextVersion counter : $counter step : $step noprompts : $argnoprompt nogittag : $argnogittag notrashnodemodules: $argnotrashnodemodules $(printflags) EOF $chalk cyan "============================" if [[ -z $argnoprompt ]]; then echo "" $chalk yellow "Based on the config above would you like to continue?" select yn in "Yes" "No"; do case $yn in Yes ) break;; No ) exit;; esac done fi # githubrelease -l # githubrelease -l -b minor if [[ -n $arglog ]]; then backupPackage bumpPackage updateChangelog resetPackage exit 1 fi # githubrelease -u if [[ -n $argpull ]]; then startOnMaster && updateBranches && exit 1 fi # githubrelease -p if [[ -n $argpush ]]; then pushAll && exit 1 fi # Run the release script if [[ $step -le 1 ]]; then startOnMaster && updateBranches; fi && # Step 1 if [[ $step -le 2 ]]; then pushAll; fi && # Step 2 if [[ $step -le 3 ]]; then startOnDevelop && runTests; fi && # Step 3 if [[ $step -le 4 ]]; then startOnMaster && rebaseDevelopOntoMaster; fi && # Step 4 if [[ $step -ge 5 ]]; then startOnMaster; fi && # Step >= 5 if [[ $step -ge 5 ]]; then shortsha="$(getShortSha)"; fi && # Step >= 5 if [[ $step -le 5 ]]; then backupPackage; fi && # Step 5 if [[ $step -le 6 ]]; then bumpPackage; fi && # Step 6 if [[ $step -le 7 ]]; then updateChangelog; fi && # Step 7 if [[ $step -le 8 ]]; then addChangelog; fi && # Step 8 if [[ $step -le 9 ]]; then resetPackage; fi && # Step 9 if [[ $step -le 10 ]]; then npmBump; fi && # Step 10 if [[ $step -le 11 ]]; then startOnDevelop && rebaseMasterOntoDevelop; fi && # Step 11 if [[ $step -le 12 ]]; then pushAll; fi && # Step 12 if [[ $step -le 13 ]]; then conventionalGitHubReleaser; fi && # Step 13 if [[ $step -le 14 ]]; then landedMessage; fi && # Step 14 $chalk green "All done!" || $chalk red "Epic fail!"