summaryrefslogtreecommitdiff
path: root/support/xyz.sh
blob: 8fd8d2dbf20b2e0f19be0f0edb9e1a3d000eb5b0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
#!/usr/bin/env bash

# Slightly modified version of xyz that publishes from the build directory,
# rather than the root.

set -e

usage="
Usage: xyz [options]

Publish a new version of the npm package in the current working directory.
This involves updating the version number in package.json, committing this
change (along with any staged changes), tagging the commit, pushing to the
remote git repository, and finally publishing to the public npm registry.

If present, component.json is updated along with package.json.

Options:

-b --branch <name>
        Specify the branch from which new versions must be published.
        xyz aborts if run from any other branch to prevent accidental
        publication of feature branches. 'master' is assumed if this
        option is omitted.

-i --increment <level>
        Specify the level of the current version number to increment.
        Valid levels: 'major', 'minor', 'patch', 'premajor', 'preminor',
        'prepatch', and 'prerelease'. 'patch' is assumed if this option
        is omitted.

-m --message <template>
        Specify the format of the commit (and tag) message.
        'X.Y.Z' acts as a placeholder for the version number.
        'Version X.Y.Z' is assumed if this option is omitted.

-r --repo <repository>
        Specify the remote repository to which to 'git push'.
        The value must be either a URL or the name of a remote.
        The latter is not recommended: it relies on local state.
        'origin' is assumed if this option is omitted.

-s --script <path>
        Specify a script to be run after the confirmation prompt.
        It is passed VERSION and PREVIOUS_VERSION as environment
        variables. xyz aborts if the script's exit code is not 0.

-t --tag <template>
        Specify the format of the tag name. As with --message,
        'X.Y.Z' acts as a placeholder for the version number.
        'vX.Y.Z' is assumed if this option is omitted.

--dry-run
        Print the commands without evaluating them.

-v --version
        Print xyz's version number and exit.
"

# http://stackoverflow.com/a/246128/312785
path="${BASH_SOURCE[0]}"
while [ -h "$path" ] ; do
  dir="$(cd -P "$(dirname "$path")" && pwd)"
  path="$(readlink "$path")"
  [[ $path == /* ]] || path="$dir/$path"
done
dir="$(cd -P "$(dirname "$path")" && pwd)"

branch=master
increment=patch
message_template='Version X.Y.Z'
repo=origin
declare -a scripts
tag_template=vX.Y.Z
dry_run=false

while (($# > 0)) ; do
  option="$1"
  shift

  case "$option" in
    -h|--help)
      echo "$usage"
      exit
      ;;
    -v|--version)
      node -p "require('$dir/package.json').version"
      exit
      ;;
    -b|--branch)    branch="$1"           ; shift ;;
    -i|--increment) increment="$1"        ; shift ;;
    -m|--message)   message_template="$1" ; shift ;;
    -r|--repo)      repo="$1"             ; shift ;;
    -s|--script)    scripts+=("$1")       ; shift ;;
    -t|--tag)       tag_template="$1"     ; shift ;;
    --dry-run)      dry_run=true          ;;
    *)
      echo "Unrecognized option $option" >&2
      exit 1
  esac
done

case "$increment" in
  major|minor|patch|premajor|preminor|prepatch|prerelease) ;;
  *) echo "Invalid --increment" >&2 ; exit 1 ;;
esac

[[ $(git rev-parse --abbrev-ref HEAD) == $branch ]] ||
  (echo "Current branch does not match specified --branch" >&2 ; exit 1)

git diff-files --quiet ||
  (echo "Working directory contains unstaged changes" >&2 ; exit 1)

name=$(node -p "require('./package.json').name" 2>/dev/null) ||
  (echo "Cannot read package name" >&2 ; exit 1)

version=$(node -p "require('./package.json').version" 2>/dev/null) ||
  (echo "Cannot read package version" >&2 ; exit 1)

next_version=$("$dir/node_modules/.bin/semver" -i "$increment" "$version") ||
  (echo "Cannot increment version number" >&2 ; exit 1)

message="${message_template//X.Y.Z/$next_version}"
tag="${tag_template//X.Y.Z/$next_version}"

bold=$(tput bold)
reset=$(tput sgr0)
printf "Current version is ${bold}${version}${reset}. "
printf "Press [enter] to publish ${bold}${name}@${next_version}${reset}."
read -s # suppress user input
echo    # output \n since [enter] output was suppressed

run() {
  echo "$1"
  if [[ $dry_run == false ]] ; then
    eval "$1"
  fi
}

# Prune before running tests to catch dependencies that have been
# installed but not specified in the project's `package.json` file.

run "npm prune"
run "npm test"

for script in "${scripts[@]}" ; do
  [[ $script == /* ]] || script="$(pwd)/$script"
  run "VERSION=$next_version PREVIOUS_VERSION=$version '$script'"
done

inc() {
  if [[ -f "$1" ]] ; then
    run "node -e '$(echo "
      var o = require('./$1'); o.version = '$next_version';
      require('fs').writeFileSync('./$1', JSON.stringify(o, null, 2) + '\n');
    " | tr "'" '"' | tr -d "\n" | tr -s " " | sed -e "s/^ *//" -e "s/ *$//")'"
    run "git add '$1'"
  fi
}

inc package.json

run "./sync-package-managers.js"
run "git add --force *.json"

run "git commit --message '$message'"
run "git tag --annotate '$tag' --message '$message'"
run "git push '$repo' 'refs/heads/$branch' 'refs/tags/$tag'"

#cd build/
#run "npm pack"