|
1 | 1 | #!/usr/bin/env bash |
2 | | -# release.sh — cut a release entirely from your machine: bump, build, test, |
| 2 | +# release.sh - cut a release entirely from your machine: bump, build, test, |
3 | 3 | # publish to npm, push, and create the GitHub Release. |
4 | 4 | # |
5 | 5 | # Usage: |
6 | | -# ./scripts/release.sh patch # 0.3.0 → 0.3.1 |
7 | | -# ./scripts/release.sh minor # 0.3.0 → 0.4.0 |
8 | | -# ./scripts/release.sh major # 0.3.0 → 1.0.0 |
| 6 | +# ./scripts/release.sh patch # 0.3.0 -> 0.3.1 |
| 7 | +# ./scripts/release.sh minor # 0.3.0 -> 0.4.0 |
| 8 | +# ./scripts/release.sh major # 0.3.0 -> 1.0.0 |
9 | 9 | # ./scripts/release.sh 1.2.3 # explicit version |
10 | 10 | # |
11 | 11 | # Prerequisites (one-time): `npm login` (publishing uses your local npm auth, |
12 | 12 | # including a 2FA OTP prompt if enabled) and `gh auth login` (for the release). |
| 13 | +# |
| 14 | +# Keep this script ASCII-only and brace every "${var}" reference. Under `set -u`, |
| 15 | +# a $var directly abutting a multibyte character (e.g. an ellipsis) is parsed as |
| 16 | +# part of the variable name and crashes with "unbound variable". |
13 | 17 |
|
14 | 18 | set -euo pipefail |
15 | 19 |
|
16 | 20 | ROOT="$(cd "$(dirname "$0")/.." && pwd)" |
17 | | -cd "$ROOT" |
| 21 | +cd "${ROOT}" |
18 | 22 |
|
19 | 23 | current="$(node -p "require('./package.json').version")" |
20 | 24 |
|
21 | | -# ── Resolve the bump ──────────────────────────────────────────────────────── |
| 25 | +# --- Resolve the bump ------------------------------------------------------- |
22 | 26 | if [[ -z "${1:-}" ]]; then |
23 | 27 | echo "Usage: ./scripts/release.sh <patch|minor|major|x.y.z>" >&2 |
24 | | - echo "Current version: $current" >&2 |
| 28 | + echo "Current version: ${current}" >&2 |
25 | 29 | exit 1 |
26 | 30 | fi |
27 | 31 |
|
28 | 32 | bump="$1" |
29 | | -case "$bump" in |
| 33 | +case "${bump}" in |
30 | 34 | patch|minor|major) ;; |
31 | 35 | [0-9]*.[0-9]*.[0-9]*) ;; |
32 | | - *) echo "Error: '$bump' must be patch, minor, major, or an explicit x.y.z" >&2; exit 1 ;; |
| 36 | + *) echo "Error: '${bump}' must be patch, minor, major, or an explicit x.y.z" >&2; exit 1 ;; |
33 | 37 | esac |
34 | 38 |
|
35 | | -# ── Safety checks ─────────────────────────────────────────────────────────── |
| 39 | +# --- Safety checks ---------------------------------------------------------- |
36 | 40 | branch="$(git rev-parse --abbrev-ref HEAD)" |
37 | | -if [[ "$branch" != "main" ]]; then |
38 | | - echo "Error: releases must be cut from 'main' (currently on '$branch')." >&2 |
| 41 | +if [[ "${branch}" != "main" ]]; then |
| 42 | + echo "Error: releases must be cut from 'main' (currently on '${branch}')." >&2 |
39 | 43 | exit 1 |
40 | 44 | fi |
41 | 45 |
|
42 | 46 | if [[ -n "$(git status --porcelain)" ]]; then |
43 | | - echo "Error: working tree is not clean — commit or stash changes first." >&2 |
| 47 | + echo "Error: working tree is not clean - commit or stash changes first." >&2 |
44 | 48 | exit 1 |
45 | 49 | fi |
46 | 50 |
|
47 | | -echo "Syncing with origin/main…" |
| 51 | +echo "Syncing with origin/main..." |
48 | 52 | git fetch --quiet origin main |
49 | 53 | if [[ "$(git rev-parse HEAD)" != "$(git rev-parse origin/main)" ]]; then |
50 | | - echo "Error: local main is not in sync with origin/main — pull/push first." >&2 |
| 54 | + echo "Error: local main is not in sync with origin/main - pull/push first." >&2 |
51 | 55 | exit 1 |
52 | 56 | fi |
53 | 57 |
|
54 | 58 | if ! npm whoami >/dev/null 2>&1; then |
55 | | - echo "Error: not logged in to npm — run 'npm login' first." >&2 |
| 59 | + echo "Error: not logged in to npm - run 'npm login' first." >&2 |
56 | 60 | exit 1 |
57 | 61 | fi |
58 | 62 |
|
59 | | -# ── Validate (before bumping, so a failure leaves no dangling version) ─────── |
60 | | -echo "Running tests…" |
| 63 | +# --- Validate (before bumping, so a failure leaves no dangling version) ----- |
| 64 | +echo "Running tests..." |
61 | 65 | npm test |
62 | | -echo "Building…" |
| 66 | +echo "Building..." |
63 | 67 | npm run build:all |
64 | 68 |
|
65 | | -# ── Bump + tag (local only so far) ────────────────────────────────────────── |
| 69 | +# --- Bump + tag (local only so far) ----------------------------------------- |
66 | 70 | # npm version updates package.json + package-lock.json, commits, and tags vX.Y.Z. |
67 | | -newtag="$(npm version "$bump" -m "Release v%s")" # prints e.g. "v0.4.0" |
68 | | -echo "Bumped $current → $newtag" |
| 71 | +newtag="$(npm version "${bump}" -m "Release v%s")" # prints e.g. "v0.4.0" |
| 72 | +echo "Bumped ${current} -> ${newtag}" |
69 | 73 |
|
70 | | -# ── Publish to npm ────────────────────────────────────────────────────────── |
71 | | -echo "Publishing to npm (enter your 2FA OTP if prompted)…" |
| 74 | +# --- Publish to npm --------------------------------------------------------- |
| 75 | +echo "Publishing to npm (enter your 2FA OTP if prompted)..." |
72 | 76 | if ! npm publish; then |
73 | 77 | echo "" >&2 |
74 | 78 | echo "Error: npm publish failed. The version bump was committed and tagged locally," >&2 |
75 | 79 | echo "but nothing was pushed. To undo and retry:" >&2 |
76 | | - echo " git tag -d $newtag && git reset --hard HEAD~1" >&2 |
| 80 | + echo " git tag -d ${newtag} && git reset --hard HEAD~1" >&2 |
77 | 81 | exit 1 |
78 | 82 | fi |
79 | 83 |
|
80 | | -# ── Point the demo at the just-published version ──────────────────────────── |
81 | | -# The demo's "vendored release" build (served on GitHub Pages) is regenerated from the published npm package pinned in docs/ |
| 84 | +# --- Point the demo at the just-published version --------------------------- |
| 85 | +# The demo's "vendored release" build (served on GitHub Pages) is regenerated |
| 86 | +# from the published npm package pinned in docs/, so bump docs/ to this release. |
82 | 87 | newversion="${newtag#v}" |
83 | 88 | pkg="@raspberrypifoundation/python-friendly-error-messages" |
84 | | -echo "Pointing the demo at $newtag…" |
85 | | -if ( cd "$ROOT/docs" && npm pkg set "dependencies.$pkg=^$newversion" && npm install >/dev/null 2>&1 ); then |
| 89 | +echo "Pointing the demo at ${newtag}..." |
| 90 | +if ( cd "${ROOT}/docs" && npm pkg set "dependencies.${pkg}=^${newversion}" && npm install >/dev/null 2>&1 ); then |
86 | 91 | git add docs/package.json docs/package-lock.json |
87 | | - git commit -m "chore: point demo at $newtag" >/dev/null |
| 92 | + git commit -m "chore: point demo at ${newtag}" >/dev/null |
88 | 93 | else |
89 | | - echo "Warning: couldn't update the demo dependency — the new version may not have" >&2 |
| 94 | + echo "Warning: couldn't update the demo dependency - the new version may not have" >&2 |
90 | 95 | echo "propagated to npm yet. Once it has, run:" >&2 |
91 | | - echo " (cd docs && npm install $pkg@^$newversion) && git commit -am 'chore: point demo at $newtag' && git push" >&2 |
| 96 | + echo " (cd docs && npm install ${pkg}@^${newversion}) && git commit -am 'chore: point demo at ${newtag}' && git push" >&2 |
92 | 97 | git checkout -- docs/package.json docs/package-lock.json 2>/dev/null || true |
93 | 98 | fi |
94 | 99 |
|
95 | | -# ── Push + GitHub Release ─────────────────────────────────────────────────── |
| 100 | +# --- Push + GitHub Release -------------------------------------------------- |
96 | 101 | git push --follow-tags origin main |
97 | 102 |
|
98 | 103 | repo="RaspberryPiFoundation/python-friendly-error-messages" |
99 | 104 | if command -v gh >/dev/null 2>&1; then |
100 | | - if ! gh release create "$newtag" --title "$newtag" --generate-notes; then |
| 105 | + if ! gh release create "${newtag}" --title "${newtag}" --generate-notes; then |
101 | 106 | echo "Warning: GitHub Release creation failed (npm publish + push already succeeded)." >&2 |
102 | | - echo "Create it manually: gh release create $newtag --title $newtag --generate-notes" >&2 |
| 107 | + echo "Create it manually: gh release create ${newtag} --title ${newtag} --generate-notes" >&2 |
103 | 108 | fi |
104 | 109 | else |
105 | | - echo "Warning: gh CLI not found — GitHub Release not created." >&2 |
106 | | - echo "Install gh, then run: gh release create $newtag --title $newtag --generate-notes" >&2 |
| 110 | + echo "Warning: gh CLI not found - GitHub Release not created." >&2 |
| 111 | + echo "Install gh, then run: gh release create ${newtag} --title ${newtag} --generate-notes" >&2 |
107 | 112 | fi |
108 | 113 |
|
109 | 114 | echo "" |
110 | | -echo "✓ Released $newtag" |
111 | | -echo " npm: https://www.npmjs.com/package/@raspberrypifoundation/python-friendly-error-messages/v/${newtag#v}" |
112 | | -echo " GitHub: https://github.com/$repo/releases/tag/$newtag" |
| 115 | +echo "Released ${newtag}" |
| 116 | +echo " npm: https://www.npmjs.com/package/@raspberrypifoundation/python-friendly-error-messages/v/${newversion}" |
| 117 | +echo " GitHub: https://github.com/${repo}/releases/tag/${newtag}" |
0 commit comments