Skip to content

Commit 19a9f00

Browse files
committed
feat: add release script (for managing npm publishing and GH releases)
1 parent 461186d commit 19a9f00

4 files changed

Lines changed: 161 additions & 5 deletions

File tree

.github/workflows/ci.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout
14+
uses: actions/checkout@v4
15+
16+
- name: Set up Node
17+
uses: actions/setup-node@v4
18+
with:
19+
node-version: 22
20+
cache: npm
21+
22+
- name: Install dependencies
23+
run: npm ci
24+
25+
- name: Build
26+
run: npm run build:all
27+
28+
- name: Test
29+
run: npm test

README.md

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# Python Friendly Error Messages
22

33
Todo:
4-
- Set up automated testing and publishing through GitHub Actions
54
- Accessibility of output HTML
65

76
A small library that explains Python error messages in a friendlier way, inspired by [p5.js's Friendly Error System](https://p5js.org/contribute/friendly_error_system/).
@@ -116,9 +115,36 @@ You can now import, and use it, elsewhere (see Usage notes).
116115

117116
The package is published to: https://www.npmjs.com/package/@raspberrypifoundation/python-friendly-error-messages
118117

119-
## Publishing
118+
## Releasing
119+
120+
Releases are (currently) generated and published to npm from your local machine, so it can use your npm auth and 2FA OTP rather than a long-lived CI token (CI publishing is a possible future enhancement).
121+
122+
One command does everything:
120123

121124
```bash
122-
npm login
123-
npm publish
125+
./scripts/release.sh patch # 0.3.0 → 0.3.1
126+
./scripts/release.sh minor # 0.3.0 → 0.4.0
127+
./scripts/release.sh major # 0.3.0 → 1.0.0
128+
./scripts/release.sh 1.2.3 # explicit version
124129
```
130+
131+
The script:
132+
133+
1. checks you're on a clean `main` in sync with `origin`, and logged in to npm,
134+
2. runs the tests and build,
135+
3. bumps the version (updating `package.json` / `package-lock.json`), commits, and tags `vX.Y.Z`,
136+
4. publishes to npm (prompting for your 2FA OTP if enabled),
137+
5. pushes the commit and tag, and
138+
6. creates a GitHub Release with notes generated from the commits/PRs since the previous tag.
139+
140+
If `npm publish` fails, nothing is pushed — the script prints how to undo the local
141+
bump and retry.
142+
143+
### Prerequisites (one-time)
144+
145+
- `npm login` — publishing uses your local npm credentials (the package publishes
146+
publicly via `publishConfig.access: "public"`).
147+
- `gh auth login` — the GitHub Release is created with the `gh` CLI.
148+
149+
Tests still run automatically on every push to `main` and on pull requests via
150+
[`.github/workflows/ci.yml`](.github/workflows/ci.yml).

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
"dist",
1616
"copydecks"
1717
],
18+
"publishConfig": {
19+
"access": "public"
20+
},
1821
"scripts": {
1922
"clean": "rimraf dist",
2023
"prebuild": "npm run clean",
@@ -25,7 +28,8 @@
2528
"dev:build": "npm run build:assets && tsc -p tsconfig.json --watch & esbuild src/index.browser.ts --bundle --format=esm --outfile=dist/index.browser.js --loader:.json=json --watch",
2629
"test": "vitest run",
2730
"dev:test": "vitest",
28-
"regen:traces": "node scripts/regenerate-demo-traces.mjs"
31+
"regen:traces": "node scripts/regenerate-demo-traces.mjs",
32+
"release": "scripts/release.sh"
2933
},
3034
"devDependencies": {
3135
"cpy-cli": "^6.0.0",

scripts/release.sh

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
#!/usr/bin/env bash
2+
# release.sh — cut a release entirely from your machine: bump, build, test,
3+
# publish to npm, push, and create the GitHub Release.
4+
#
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
9+
# ./scripts/release.sh 1.2.3 # explicit version
10+
#
11+
# Prerequisites (one-time): `npm login` (publishing uses your local npm auth,
12+
# including a 2FA OTP prompt if enabled) and `gh auth login` (for the release).
13+
14+
set -euo pipefail
15+
16+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
17+
cd "$ROOT"
18+
19+
current="$(node -p "require('./package.json').version")"
20+
21+
# ── Resolve the bump ────────────────────────────────────────────────────────
22+
if [[ -z "${1:-}" ]]; then
23+
echo "Usage: ./scripts/release.sh <patch|minor|major|x.y.z>" >&2
24+
echo "Current version: $current" >&2
25+
exit 1
26+
fi
27+
28+
bump="$1"
29+
case "$bump" in
30+
patch|minor|major) ;;
31+
[0-9]*.[0-9]*.[0-9]*) ;;
32+
*) echo "Error: '$bump' must be patch, minor, major, or an explicit x.y.z" >&2; exit 1 ;;
33+
esac
34+
35+
# ── Safety checks ───────────────────────────────────────────────────────────
36+
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
39+
exit 1
40+
fi
41+
42+
if [[ -n "$(git status --porcelain)" ]]; then
43+
echo "Error: working tree is not clean — commit or stash changes first." >&2
44+
exit 1
45+
fi
46+
47+
echo "Syncing with origin/main…"
48+
git fetch --quiet origin main
49+
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
51+
exit 1
52+
fi
53+
54+
if ! npm whoami >/dev/null 2>&1; then
55+
echo "Error: not logged in to npm — run 'npm login' first." >&2
56+
exit 1
57+
fi
58+
59+
# ── Validate (before bumping, so a failure leaves no dangling version) ───────
60+
echo "Running tests…"
61+
npm test
62+
echo "Building…"
63+
npm run build:all
64+
65+
# ── Bump + tag (local only so far) ──────────────────────────────────────────
66+
# 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"
69+
70+
# ── Publish to npm ──────────────────────────────────────────────────────────
71+
echo "Publishing to npm (enter your 2FA OTP if prompted)…"
72+
if ! npm publish; then
73+
echo "" >&2
74+
echo "Error: npm publish failed. The version bump was committed and tagged locally," >&2
75+
echo "but nothing was pushed. To undo and retry:" >&2
76+
echo " git tag -d $newtag && git reset --hard HEAD~1" >&2
77+
exit 1
78+
fi
79+
80+
# ── Push + GitHub Release ───────────────────────────────────────────────────
81+
git push --follow-tags origin main
82+
83+
repo="RaspberryPiFoundation/python-friendly-error-messages"
84+
if command -v gh >/dev/null 2>&1; then
85+
if ! gh release create "$newtag" --title "$newtag" --generate-notes; then
86+
echo "Warning: GitHub Release creation failed (npm publish + push already succeeded)." >&2
87+
echo "Create it manually: gh release create $newtag --title $newtag --generate-notes" >&2
88+
fi
89+
else
90+
echo "Warning: gh CLI not found — GitHub Release not created." >&2
91+
echo "Install gh, then run: gh release create $newtag --title $newtag --generate-notes" >&2
92+
fi
93+
94+
echo ""
95+
echo "✓ Released $newtag"
96+
echo " npm: https://www.npmjs.com/package/@raspberrypifoundation/python-friendly-error-messages/v/${newtag#v}"
97+
echo " GitHub: https://github.com/$repo/releases/tag/$newtag"

0 commit comments

Comments
 (0)