| 📦 Turbo JS 91999d1 k33g 8h ago | 1 | #!/bin/bash |
| 2 | : <<'COMMENT' |
| 3 | Releasing turbo-js is tagging it. Pushing the tag starts the Release workflow, |
| 4 | which builds the binaries and publishes the release page with them. |
| 5 | |
| 6 | 1. Set TAG and ABOUT in release.env |
| 7 | 2. Run this script: ./01-release.tag.sh (make check, commit, push, tag, push the tag) |
| 8 | 3. Watch the "Release" workflow on Rickub (Actions tab): the tag push starts |
| 9 | it; it cross-compiles the binaries with ./02-build-releases.sh and publishes |
| 10 | the release page with them, using the job's own token. No personal token is |
| 11 | needed, and nothing else has to be run by hand. |
| 12 | COMMENT |
| 13 | |
| 14 | # Without this, a failing step is ignored and the next one runs anyway. That is |
| 15 | # not theoretical: `git tag` refusing a tag that already existed was skipped in |
| 16 | # silence, and the `git push` below then pushed the OLD tag — so a release was |
| 17 | # cut from a commit nobody meant, and 02 was left to notice. |
| 18 | set -euo pipefail |
| 19 | |
| 20 | if [ ! -f release.env ]; then |
| 21 | echo "❌ release.env is missing" |
| 22 | echo "💡 Create it with the version you are publishing:" |
| 23 | echo ' TAG=v1.0.0' |
| 24 | echo ' ABOUT="Turbo JS"' |
| 25 | exit 1 |
| 26 | fi |
| 27 | |
| 28 | set -o allexport |
| 29 | # shellcheck source=/dev/null |
| 30 | source release.env |
| 31 | set +o allexport |
| 32 | |
| 33 | echo "Releasing turbo-js ${TAG}: ${ABOUT}" |
| 34 | |
| 35 | # A tag that is not vMAJOR.MINOR.PATCH[-prerelease] is a typo — and worse than |
| 36 | # a typo: the workflow only starts on v*, and the module proxy will not serve |
| 37 | # `go install …@TAG` for a tag it cannot read as a version. |
| 38 | if ! [[ "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then |
| 39 | echo "❌ TAG must look like v1.2.3 or v1.2.3-rc.1, got '${TAG}' (check release.env)" |
| 40 | exit 1 |
| 41 | fi |
| 42 | |
| 43 | # The whole suite has to pass before a version exists that people will |
| 44 | # download. A tag the proxy has cached is the wrong place to find out. |
| 45 | # |
| 46 | # The marker matters: the suite includes tests that run *this script* against a |
| 47 | # throwaway clone, and without it `make check` would run those, which would run |
| 48 | # this script, which would run `make check`. They skip themselves when they see |
| 49 | # it. Exported, so it reaches the test binary through make and go test. |
| 50 | export TURBO_JS_RELEASING=1 |
| 51 | echo "→ make check" |
| 52 | make --no-print-directory check |
| 53 | |
| 54 | # tagExists reports whether TAG is already taken, here or on the remote. The |
| 55 | # remote matters on its own: a tag deleted locally after a failed attempt still |
| 56 | # exists there, and pushing a new one at a different commit is rejected. |
| 57 | tagExists() { |
| 58 | if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then |
| 59 | printf 'locally, on %s\n' "$(git rev-parse --short "${TAG}^{commit}")" |
| 60 | return 0 |
| 61 | fi |
| 62 | if ! remote="$(git ls-remote --tags origin "refs/tags/${TAG}" 2>/dev/null)"; then |
| 63 | return 1 # the remote is unreachable; the push below will say so |
| 64 | fi |
| 65 | if [ -n "${remote}" ]; then |
| 66 | # No commit is named here on purpose: for an annotated tag ls-remote |
| 67 | # gives the tag object, not the commit, and printing that as if it were |
| 68 | # the commit sends the reader looking for a SHA they will never find. |
| 69 | printf 'on origin\n' |
| 70 | return 0 |
| 71 | fi |
| 72 | return 1 |
| 73 | } |
| 74 | |
| 75 | if where="$(tagExists)"; then |
| 76 | echo "❌ ${TAG} already exists ${where}" |
| 77 | echo "💡 A published version is not yours to move: the module proxy caches" |
| 78 | echo " what it fetched for \`go install\`, and the release page already" |
| 79 | echo " carries binaries with that number. Bump TAG in release.env instead." |
| 80 | exit 1 |
| 81 | fi |
| 82 | |
| 83 | # The editor must not be published with a replace directive in it: the module |
| 84 | # proxy serves the go.mod as written, and `go install …@TAG` would then look |
| 85 | # for turbo-core in a directory that does not exist on the installer's machine. |
| 86 | if grep -qE '^[[:space:]]*replace[[:space:]]' go.mod; then |
| 87 | echo "❌ go.mod has a replace directive, which a published module must not" |
| 88 | grep -nE '^[[:space:]]*replace[[:space:]]' go.mod |
| 89 | exit 1 |
| 90 | fi |
| 91 | |
| 92 | find . -name '.DS_Store' -type f -delete |
| 93 | |
| 94 | git add . |
| 95 | |
| 96 | # Nothing to commit is not a failure — the work may already be committed — but |
| 97 | # under `set -e` a plain `git commit` would stop the release right here. |
| 98 | if git diff --cached --quiet; then |
| 99 | echo "Nothing to commit; releasing what is already on HEAD" |
| 100 | else |
| 101 | git commit -m "📦 ${ABOUT}" |
| 102 | fi |
| 103 | |
| 104 | git push origin "$(git rev-parse --abbrev-ref HEAD)" |
| 105 | |
| 106 | # The tag goes on after the push, so a rejected push never leaves a tag behind |
| 107 | # pointing at a commit the remote has never seen. |
| 108 | git tag -a "${TAG}" -m "${ABOUT}" |
| 109 | git push origin "${TAG}" |
| 110 | |
| 111 | echo "✅ turbo-js ${TAG} published" |
| 112 | echo "💡 The tag push started the Release workflow; it builds the binaries and" |
| 113 | echo " creates the release page. Watch it on the repository's Actions tab." |
| 114 | echo "💡 To see what it will build without publishing anything:" |
| 115 | echo " ./02-build-releases.sh ${TAG}" |