Skip to content

Latest commit

 

History

History
318 lines (247 loc) · 12.3 KB

File metadata and controls

318 lines (247 loc) · 12.3 KB

Release checklist

This is the required finish line for every shipped ReRouted product build (macOS app and/or Linux CLI). Process and policy live in release-lifecycle.md and GOVERNANCE.md. User-facing narrative belongs in CHANGELOG.md before you package.

Docs-only or governance-only merges may land without a new binary, but they still use a branch, tests when applicable, and changelog Unreleased notes when users will notice.

Do not say "done" for a product release until every check below has passed.

1. Prepare the change

Start from the current remote main, create a branch, implement with tests, and update CHANGELOG.md under Unreleased. Give the app a new version so the installed build is unambiguous before packaging.

git fetch origin
git switch main
git pull --ff-only origin main
git switch -c <branch-name>

# After implementation and tests, when this branch will ship:
npm version patch --no-git-tag-version
# Move Unreleased notes into ## [x.y.z] - YYYY-MM-DD in CHANGELOG.md

Use a minor or major bump when the scope warrants it. Do not reuse an already-installed version for a new iteration.

2. Test the branch

npm test
git diff --check
git status --short

Run additional focused checks for the changed area. For UI work, capture or inspect the relevant Electron states on macOS.

3. Commit and push the branch

git add <changed-files>
git commit -m "<message>"
git push -u origin <branch-name>

Record the branch commit:

git rev-parse HEAD

4. Merge and push main

Merge through the repository's normal review path. If a local merge is required, preserve a merge record:

git switch main
git pull --ff-only origin main
git merge --no-ff <branch-name>
git push origin main

Confirm the remote contains the merged commit:

git fetch origin
test "$(git rev-parse main)" = "$(git rev-parse origin/main)"
git log -1 --oneline origin/main

The commit used for packaging is now:

MERGED_COMMIT="$(git rev-parse origin/main)"

5. Build the DMG from merged main

The DMG must be built on an Apple Silicon Mac from the exact merged commit, not from an unmerged branch or a dirty working tree.

set -euo pipefail

ORIGINAL_REPO="$(pwd)"
MERGED_COMMIT="<recorded origin/main SHA>"
git fetch origin
test "$(git rev-parse origin/main)" = "$MERGED_COMMIT"
BUILD_PARENT="$(mktemp -d)"
BUILD_DIR="$BUILD_PARENT/source"
git worktree add --detach "$BUILD_DIR" "$MERGED_COMMIT"
cd "$BUILD_DIR"
test "$(git rev-parse HEAD)" = "$MERGED_COMMIT"
test -z "$(git status --porcelain)"

export PATH=/opt/homebrew/bin:$PATH
npm ci
npm test
export REROUTED_NOTARY_PROFILE=rerouted-notary
export REROUTED_TEAM_ID=APPLE_TEAM_ID

VERSION="$(node -p "require('./package.json').version")"
DMG="dist/ReRouted-${VERSION}-arm64.dmg"
UPDATE_ZIP="dist/ReRouted-${VERSION}-mac-arm64.zip"
rm -f "$DMG" "$UPDATE_ZIP"
npm run package:dmg:release
test -s "$DMG"
test -s "$UPDATE_ZIP"

MOUNT="$(hdiutil attach "$DMG" -nobrowse | awk '/\/Volumes\// {sub(/^.*\/Volumes\//, "/Volumes/"); print; exit}')"
trap 'hdiutil detach "$MOUNT" >/dev/null 2>&1 || true' EXIT

codesign --verify --deep --strict "$MOUNT/ReRouted.app"
test -f "$MOUNT/ReRouted.app/Contents/Resources/LICENSE"
test -f "$MOUNT/LICENSE.txt"
xcrun stapler validate "$MOUNT/ReRouted.app"
spctl --assess --type execute --verbose=4 "$MOUNT/ReRouted.app"
xcrun stapler validate "$DMG"
spctl --assess --type open --context context:primary-signature --verbose=4 "$DMG"
shasum -a 256 "$DMG"

UPDATE_DIR="$(mktemp -d)"
ditto -x -k "$UPDATE_ZIP" "$UPDATE_DIR"
codesign --verify --deep --strict "$UPDATE_DIR/ReRouted.app"
test -f "$UPDATE_DIR/ReRouted.app/Contents/Resources/LICENSE"
xcrun stapler validate "$UPDATE_DIR/ReRouted.app"
spctl --assess --type execute --verbose=4 "$UPDATE_DIR/ReRouted.app"
shasum -a 256 "$UPDATE_ZIP"
rm -rf "$UPDATE_DIR"

hdiutil detach "$MOUNT"
trap - EXIT

Record the version, merged commit, DMG filename, and SHA-256 before installation.

5b. Build the Linux CLI from merged main

From an exact clean checkout of the same merged commit on Linux:

set -euo pipefail

test "$(git rev-parse HEAD)" = "$MERGED_COMMIT"
test -z "$(git status --porcelain)"
npm ci
npm test
npm run package:linux

CLI_TGZ="dist/ReRouted-${VERSION}-linux-node.tgz"
CLI_LATEST="dist/ReRouted-linux-node.tgz"
test -s "$CLI_TGZ"
test -s "$CLI_LATEST"
CLI_SHA="$(sha256sum "$CLI_TGZ" | awk '{print $1}')"
test "$(sha256sum "$CLI_LATEST" | awk '{print $1}')" = "$CLI_SHA"
tar -tzf "$CLI_TGZ" | grep -Fx 'package/LICENSE'
tar -tzf "$CLI_TGZ" | grep -Fx 'package/src/cli/index.js'
tar -tzf "$CLI_TGZ" | grep -Fx 'package/src/renderer/index.html'
test -z "$(tar -tzf "$CLI_TGZ" | grep -E '(^|/)AGENTS\.md$' || true)"

INSTALL_ROOT="$(mktemp -d)"
mkdir -p "$INSTALL_ROOT/home"
HOME="$INSTALL_ROOT/home" npm install --global --prefix "$INSTALL_ROOT/npm" "$(realpath "$CLI_TGZ")"
test "$("$INSTALL_ROOT/npm/bin/rerouted" --version)" = "$VERSION"
"$INSTALL_ROOT/npm/bin/rerouted" --data-dir "$INSTALL_ROOT/data" --port 54949 --no-interactive >"$INSTALL_ROOT/out" 2>"$INSTALL_ROOT/err" &
CLI_PID=$!
for _ in $(seq 1 30); do
  curl -fsS http://127.0.0.1:54949/health && break
  sleep 1
done
curl -fsS http://127.0.0.1:54949/dashboard/ | grep -F 'web-api.js'
kill -TERM "$CLI_PID"
wait "$CLI_PID"
test ! -e "$INSTALL_ROOT/data/rerouted.pid"
rm -rf "$INSTALL_ROOT"

Record the versioned filename and SHA-256. The stable alias must be byte-for-byte identical.

6. Publish the exact build

Tag the exact commit used for packaging. Create a draft release, upload every verified artifact, and publish only after their server-side digests match. Publishing is the macOS update activation point and makes the stable Linux alias public.

set -euo pipefail

TAG="v${VERSION}"
LOCAL_SHA="$(shasum -a 256 "$DMG" | awk '{print $1}')"
UPDATE_SHA="$(shasum -a 256 "$UPDATE_ZIP" | awk '{print $1}')"
CLI_TGZ="$ORIGINAL_REPO/dist/ReRouted-${VERSION}-linux-node.tgz"
CLI_LATEST="$ORIGINAL_REPO/dist/ReRouted-linux-node.tgz"
CLI_SHA="$(sha256sum "$CLI_TGZ" | awk '{print $1}')"
test "$(sha256sum "$CLI_LATEST" | awk '{print $1}')" = "$CLI_SHA"

test "$(git rev-parse HEAD)" = "$MERGED_COMMIT"
git tag -a "$TAG" "$MERGED_COMMIT" -m "ReRouted ${VERSION}"
git push origin "refs/tags/$TAG:refs/tags/$TAG"

gh release create "$TAG" "$DMG#ReRouted ${VERSION} for Apple Silicon" "$UPDATE_ZIP#ReRouted ${VERSION} in-app update" "$CLI_TGZ#ReRouted ${VERSION} headless Linux CLI" "$CLI_LATEST#ReRouted headless Linux CLI (latest stable)" \
  --repo gitcommit90/rerouted \
  --verify-tag \
  --draft \
  --title "ReRouted ${VERSION}" \
  --generate-notes

test "$(git rev-list -n 1 "$TAG")" = "$MERGED_COMMIT"
REMOTE_DIGEST="$(gh release view "$TAG" --repo gitcommit90/rerouted --json assets --jq ".assets[] | select(.name == \"$(basename "$DMG")\") | .digest")"
REMOTE_UPDATE_DIGEST="$(gh release view "$TAG" --repo gitcommit90/rerouted --json assets --jq ".assets[] | select(.name == \"$(basename "$UPDATE_ZIP")\") | .digest")"
REMOTE_CLI_DIGEST="$(gh release view "$TAG" --repo gitcommit90/rerouted --json assets --jq ".assets[] | select(.name == \"$(basename "$CLI_TGZ")\") | .digest")"
REMOTE_CLI_LATEST_DIGEST="$(gh release view "$TAG" --repo gitcommit90/rerouted --json assets --jq ".assets[] | select(.name == \"$(basename "$CLI_LATEST")\") | .digest")"
test "$REMOTE_DIGEST" = "sha256:$LOCAL_SHA"
test "$REMOTE_UPDATE_DIGEST" = "sha256:$UPDATE_SHA"
test "$REMOTE_CLI_DIGEST" = "sha256:$CLI_SHA"
test "$REMOTE_CLI_LATEST_DIGEST" = "sha256:$CLI_SHA"
gh release edit "$TAG" --repo gitcommit90/rerouted --draft=false --latest

Download the published asset into a clean directory and verify it before installation:

PUBLISHED_DIR="$(mktemp -d)"
gh release download "$TAG" --repo gitcommit90/rerouted --pattern "$(basename "$DMG")" --dir "$PUBLISHED_DIR"
gh release download "$TAG" --repo gitcommit90/rerouted --pattern "$(basename "$UPDATE_ZIP")" --dir "$PUBLISHED_DIR"
gh release download "$TAG" --repo gitcommit90/rerouted --pattern "$(basename "$CLI_TGZ")" --dir "$PUBLISHED_DIR"
gh release download "$TAG" --repo gitcommit90/rerouted --pattern "$(basename "$CLI_LATEST")" --dir "$PUBLISHED_DIR"
PUBLISHED_DMG="$PUBLISHED_DIR/$(basename "$DMG")"
PUBLISHED_UPDATE="$PUBLISHED_DIR/$(basename "$UPDATE_ZIP")"
test "$(shasum -a 256 "$PUBLISHED_DMG" | awk '{print $1}')" = "$LOCAL_SHA"
test "$(shasum -a 256 "$PUBLISHED_UPDATE" | awk '{print $1}')" = "$UPDATE_SHA"
test "$(sha256sum "$PUBLISHED_DIR/$(basename "$CLI_TGZ")" | awk '{print $1}')" = "$CLI_SHA"
test "$(sha256sum "$PUBLISHED_DIR/$(basename "$CLI_LATEST")" | awk '{print $1}')" = "$CLI_SHA"

curl -fsS "https://update.electronjs.org/gitcommit90/rerouted/darwin-arm64/0.0.0" | grep -F "$(basename "$UPDATE_ZIP")"
test "$(curl -sS -o /dev/null -w '%{http_code}' "https://update.electronjs.org/gitcommit90/rerouted/darwin-arm64/${VERSION}")" = "204"

git -C "$ORIGINAL_REPO" worktree remove --force "$BUILD_DIR"
rmdir "$BUILD_PARENT"

7. Put the published build on macair

The current fleet SSH alias is macair. Transfer the DMG if it was built elsewhere:

scp "$PUBLISHED_DMG" "macair:~/Downloads/"

On macair, quit the running app, mount the DMG, replace the application bundle, unmount, and relaunch. Do not delete its Application Support directory.

ssh macair

set -euo pipefail
VERSION="<version>"
DMG="$HOME/Downloads/ReRouted-${VERSION}-arm64.dmg"
MOUNT="$(hdiutil attach "$DMG" -nobrowse | awk '/\/Volumes\// {sub(/^.*\/Volumes\//, "/Volumes/"); print; exit}')"
trap 'hdiutil detach "$MOUNT" >/dev/null 2>&1 || true' EXIT

osascript -e 'tell application "ReRouted" to quit' || true
for _ in {1..10}; do
  pgrep -f "/Applications/ReRouted.app/Contents/MacOS/ReRouted" >/dev/null || break
  sleep 1
done
if pgrep -f "/Applications/ReRouted.app/Contents/MacOS/ReRouted" >/dev/null; then
  pkill -TERM -f "/Applications/ReRouted.app/Contents/MacOS/ReRouted"
  sleep 2
fi
test -z "$(pgrep -f "/Applications/ReRouted.app/Contents/MacOS/ReRouted" || true)"

rm -rf "/Applications/ReRouted.app"
ditto "$MOUNT/ReRouted.app" "/Applications/ReRouted.app"

codesign --verify --deep --strict "/Applications/ReRouted.app"
xcrun stapler validate "/Applications/ReRouted.app"
spctl --assess --type execute --verbose=4 "/Applications/ReRouted.app"

hdiutil detach "$MOUNT"
trap - EXIT
open -a "/Applications/ReRouted.app"

Only the application bundle is replaced. Do not remove the user's Application Support directory, config, or usage data.

8. Verify the MacBook Air

ssh macair '
  INSTALLED=$(plutil -extract CFBundleShortVersionString raw /Applications/ReRouted.app/Contents/Info.plist)
  printf "installed=%s\n" "$INSTALLED"
  codesign --verify --deep --strict /Applications/ReRouted.app
  xcrun stapler validate /Applications/ReRouted.app
  spctl --assess --type execute --verbose=4 /Applications/ReRouted.app
  pgrep -fl "/Applications/ReRouted.app/Contents/MacOS/ReRouted"
  curl -fsS http://127.0.0.1:4949/health
'

The installed version must match package.json, the process must be running from /Applications/ReRouted.app, and the local health endpoint must answer successfully.

8b. Verify the public Linux CLI

Install the exact public ReRouted-linux-node.tgz URL into a clean temporary npm prefix and repeat the version, /health, /dashboard/, SIGTERM, and PID-lock checks from step 5b. The installed rerouted --version must match package.json, and the public download digest must equal CLI_SHA.

9. Final evidence

The completion report must contain all five values:

Version:        <package version>
Merged commit:  <origin/main commit SHA>
DMG:            ReRouted-<version>-arm64.dmg
DMG SHA-256:    <sha256>
Update ZIP:     ReRouted-<version>-mac-arm64.zip (<sha256>)
Linux CLI:      ReRouted-<version>-linux-node.tgz (<sha256>)
Release:        <GitHub release URL and matching asset digest>
Gatekeeper:     Developer ID signature, app ticket, and DMG ticket verified
MacBook Air:    installed version verified, process running, health check passed
Linux CLI:      public tarball installed, version/health/dashboard/shutdown verified

If the DMG was not rebuilt, signed, notarized, and stapled; the Linux CLI was not packaged and publicly smoke-tested; the MacBook Air was not updated; the code was not committed and pushed; or the change was not merged to main, the iteration is not complete. State the missing step plainly and do not use the word "done."