diff --git a/nodejs/package.json b/nodejs/package.json index 4004c63..7583c26 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -29,6 +29,7 @@ }, "scripts": { "build": "tsc", + "prepack": "tsc", "typecheck": "tsc --noEmit", "test": "node ./test.js", "test:verbose": "node ./test.js dump verbose" diff --git a/release.sh b/release.sh index c4e281e..940cac7 100755 --- a/release.sh +++ b/release.sh @@ -1,28 +1,29 @@ #!/usr/bin/env bash -set -e +set -euo pipefail -root=$(cd $(dirname "$0"); pwd -P) +root=$(cd "$(dirname "$0")"; pwd -P) cd "$root" python_metadata=python/pyproject.toml +node_metadata=nodejs/package.json + +# Push to the canonical repository, not a personal fork. +remote=${RELEASE_REMOTE:-$(git remote -v | awk '/web-push-libs\/encrypted-content-encoding/ { print $1; exit }')} +remote=${remote:-origin} + sub='{ s/^.*["'"'"']\([0-9]*\.[0-9]*\.[0-9]*\)["'"'"'].*$/\1/;p; }' -old1=$(sed -n -e '/version *=/'"$sub"'' "$python_metadata") -old2=$(sed -n -e '/"version" *:/'"$sub"'' nodejs/package.json) +old1=$(sed -n -e '/^version *=/'"$sub"'' "$python_metadata") +old2=$(sed -n -e '/"version" *:/'"$sub"'' "$node_metadata") if [[ "$old1" != "$old2" ]]; then echo "Versions aren't the same: $old1 != $old2" 1>&2 exit 1 fi -case "$1" in +case "${1:-}" in +) - IFS=. v=($old1) - new="${v[0]}.${v[1]}.$((${v[2]} + 1))" - - sub='s/\(["'"'"']\)'"$old1"'["'"'"']/\1'"$new"'\1/' - sed -i~ -e '/version *=/'"$sub" "$python_metadata" - sed -i~ -e '/"version" *:/'"$sub" nodejs/package.json + IFS=. read -r -a v <<< "$old1" + new="${v[0]}.${v[1]}.$((v[2] + 1))" ;; - =) new="$old1" ;; @@ -32,17 +33,61 @@ case "$1" in exit 2 esac -pushd "$root"/python -python -m build --sdist --wheel -twine upload dist/http_ece-"$new".tar.gz dist/http_ece-"$new"-*.whl -popd +# Run Python tools through uvx when available, so they don't need to be installed. +if command -v uvx >/dev/null; then + pybuild() { uvx --from build pyproject-build "$@"; } + twine() { uvx twine "$@"; } +else + pybuild() { python3 -m build "$@"; } + twine() { python3 -m twine "$@"; } +fi +# Pre-flight checks, before anything irreversible happens. +if [[ -n "$(git status --porcelain --untracked-files=no)" ]]; then + echo "Working tree has uncommitted changes" 1>&2 + exit 1 +fi +if git rev-parse -q --verify "refs/tags/v$new" >/dev/null || + git ls-remote --exit-code --tags "$remote" "refs/tags/v$new" >/dev/null; then + echo "Tag v$new already exists" 1>&2 + exit 1 +fi +if ! npm whoami >/dev/null 2>&1; then + echo "Not logged in to npm; run 'npm login' first" 1>&2 + exit 1 +fi +echo "Releasing $new (pushing to remote '$remote')" + +if [[ "$1" == "+" ]]; then + # Undo the version bump if anything fails before publishing starts, + # so the release can be retried from a clean tree. + trap 'git checkout -- "$python_metadata" "$node_metadata" nodejs/package-lock.json' EXIT + sub='s/\(["'"'"']\)'"$old1"'["'"'"']/\1'"$new"'\1/' + sed -i -e '/^version *=/'"$sub" "$python_metadata" + sed -i -e '/"version" *:/'"$sub" "$node_metadata" + (cd nodejs && npm install --package-lock-only --ignore-scripts >/dev/null) +fi + +# Build and validate both packages before publishing either of them. pushd "$root"/nodejs -npm publish +npm ci +npm test +npm pack --dry-run # "prepack" builds the TypeScript declarations popd +pushd "$root"/python +rm -rf dist build +pybuild --sdist --wheel +twine check dist/* +popd + +trap - EXIT +twine upload python/dist/http_ece-"$new".tar.gz python/dist/http_ece-"$new"-*.whl +(cd nodejs && npm publish) + if [[ "$1" == "+" ]]; then - git commit -m "Update version to $new" "$python_metadata" nodejs/package.json + git commit -m "Update version to $new" "$python_metadata" "$node_metadata" nodejs/package-lock.json + git push "$remote" HEAD fi git tag -a v"$new" -m "Release version $new" -git push origin v"$new" +git push "$remote" v"$new"