* chore(release): bump 0.8.2 -> 0.8.3 Version bumped across desktop/package.json, Cargo.toml (+ only the `gridline` entry in Cargo.lock — the sqlx family is untouched), tauri.conf.json, both landing-page `const version` strings, the README's two download tables and 'current release' banners, the MAINTENANCE comment, and the version tests. Adds the 0.8.3 changelog entry and README release note covering the external client-tool resolution fix (issue #44), the MariaDB client-option fix, the four restyled tool pages, and the app-icon revert. Historical references (older changelog entries, '(v0.8.2)' feature notes) are deliberately left alone. * test(ui): scope CopyButton's unhandled-rejection capture to its own error The listener is process-global and Vitest runs several test files per worker, so an unrelated file's rejection could be recorded and fail this test. Filter the capture to the error this test creates, and stop the listener in a finally so a failing assertion cannot leak it into later tests. Verified non-vacuous: against a CopyButton without the try/catch both 'stays quiet' tests still fail with the expected assertion.
250 lines
11 KiB
Bash
Executable File
250 lines
11 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# LOCAL FALLBACK: build Gridline for macOS on this machine and upload the signed
|
|
# + notarized DMGs to the GitHub release for a given tag.
|
|
#
|
|
# macOS is normally built, Developer-ID signed and notarized by GitHub Actions
|
|
# (.github/workflows/release.yml) using the APPLE_* repository secrets, together
|
|
# with the Windows and Linux installers. Reach for this script when you want a
|
|
# signed build without spending CI minutes, or when Actions is unavailable.
|
|
#
|
|
# Usage:
|
|
# TAG=v0.8.3 ./scripts/release-mac.sh
|
|
#
|
|
# The release is keyed by tag: the Actions job creates the draft on a tag push,
|
|
# and this script finds it (creating it only if it somehow doesn't exist) and
|
|
# attaches/replaces the two DMGs — Gridline_<ver>_aarch64.dmg and
|
|
# Gridline_<ver>_x64.dmg. Uploads use --clobber, so a locally signed DMG
|
|
# supersedes the CI one; publish the draft afterwards on GitHub.
|
|
#
|
|
# Auth: the `gh` CLI (GitHub is the only release target; the Gitea mirror is
|
|
# code-only).
|
|
|
|
set -euo pipefail
|
|
|
|
# ---- config ---------------------------------------------------------------
|
|
GH_REPO="${GH_REPO:-AdrianBonpin/gridline}"
|
|
TAG="${TAG:-${1:-}}"
|
|
|
|
if [ -z "$TAG" ]; then
|
|
echo "usage: TAG=vX.Y.Z ./scripts/release-mac.sh" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ---- resolve auth (gh CLI) -------------------------------------------------
|
|
if ! command -v gh >/dev/null 2>&1; then
|
|
echo "error: gh CLI not found (needed to upload release assets)" >&2
|
|
echo " install it: https://cli.github.com" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# ---- macOS signing + notarization -----------------------------------------
|
|
# Requires a "Developer ID Application" certificate in the login keychain and
|
|
# an App Store Connect API key for notarization. See the README's "macOS
|
|
# signing" section for how to create both.
|
|
#
|
|
# Notarization credentials are read from ~/.config/gridline/notarize.env
|
|
# (never committed). Create it with:
|
|
# APPLE_API_KEY=<Key ID>
|
|
# APPLE_API_ISSUER=<Issuer ID>
|
|
# APPLE_API_KEY_PATH=/absolute/path/to/AuthKey_<KeyID>.p8
|
|
# (or drop the .p8 at ~/.appstoreconnect/private_keys/AuthKey_<KeyID>.p8 and
|
|
# omit APPLE_API_KEY_PATH — tauri auto-searches that location.)
|
|
|
|
# 1. Resolve the Developer ID Application signing identity from the keychain.
|
|
SIGNING_IDENTITY="$(security find-identity -v -p codesigning 2>/dev/null \
|
|
| grep -i 'Developer ID Application' \
|
|
| sed -E 's/.*"([^"]+)".*/\1/' \
|
|
| head -1 || true)"
|
|
if [ -z "$SIGNING_IDENTITY" ]; then
|
|
echo "ERROR: No 'Developer ID Application' certificate found in the login keychain." >&2
|
|
echo " Create one at https://developer.apple.com/account/resources/certificates/list" >&2
|
|
echo " (Certificates, IDs & Profiles -> + -> Developer ID Application), download the" >&2
|
|
echo " .cer, and double-click it to install into your login keychain." >&2
|
|
echo " Verify with: security find-identity -v -p codesigning" >&2
|
|
exit 1
|
|
fi
|
|
export APPLE_SIGNING_IDENTITY="$SIGNING_IDENTITY"
|
|
echo ">> Signing identity: $SIGNING_IDENTITY"
|
|
|
|
# 2. Load notarization credentials.
|
|
NOTARIZE_ENV="${HOME}/.config/gridline/notarize.env"
|
|
if [ -f "$NOTARIZE_ENV" ]; then
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
source "$NOTARIZE_ENV"
|
|
set +a
|
|
fi
|
|
if [ -z "${APPLE_API_KEY:-}" ] || [ -z "${APPLE_API_ISSUER:-}" ]; then
|
|
if [ "${GRIDLINE_SKIP_NOTARIZE:-0}" = "1" ]; then
|
|
echo ">> WARNING: notarization credentials missing; GRIDLINE_SKIP_NOTARIZE=1 set." >&2
|
|
echo " Building signed but NOT notarized (test-only — users will still see a prompt)." >&2
|
|
else
|
|
echo "ERROR: Notarization credentials not configured." >&2
|
|
echo " Create ${NOTARIZE_ENV} with APPLE_API_KEY and APPLE_API_ISSUER" >&2
|
|
echo " (and APPLE_API_KEY_PATH, or place the .p8 at ~/.appstoreconnect/private_keys/)." >&2
|
|
echo " See the README's 'macOS signing' section for the App Store Connect API key setup." >&2
|
|
exit 1
|
|
fi
|
|
else
|
|
echo ">> Notarization: API key ${APPLE_API_KEY} (issuer ${APPLE_API_ISSUER})"
|
|
fi
|
|
|
|
# ---- build bundled DB tools (macOS) ----------------------------------------
|
|
WORKSPACE="$(cd "$(dirname "$0")/.." && pwd)"
|
|
OUT_PG="${WORKSPACE}/desktop/src-tauri/resources/pg_tools"
|
|
OUT_MY="${WORKSPACE}/desktop/src-tauri/resources/mysql_tools"
|
|
|
|
echo ">> Building PostgreSQL client tools..."
|
|
mkdir -p "$OUT_PG"
|
|
PG_VER="16.4"
|
|
curl -fsSL "https://ftp.postgresql.org/pub/source/v${PG_VER}/postgresql-${PG_VER}.tar.bz2" -o /tmp/pg.tar.bz2
|
|
# Idempotent re-runs: clear stale build dirs left by previous invocations.
|
|
rm -rf "/tmp/postgresql-${PG_VER}" /tmp/pgbuild
|
|
tar -xf /tmp/pg.tar.bz2 -C /tmp
|
|
cd /tmp/postgresql-${PG_VER}
|
|
ac_cv_func_strchrnul=no ./configure --prefix=/tmp/pgbuild --without-readline --without-icu CFLAGS="-O2"
|
|
sed -i '' 's/strchrnul/pg_strchrnul/g' src/port/snprintf.c
|
|
make -C src/backend generated-headers
|
|
make -C src/interfaces/libpq all
|
|
make -j"$(sysctl -n hw.ncpu)" -C src/bin/pg_dump all
|
|
make -j"$(sysctl -n hw.ncpu)" -C src/bin/psql all
|
|
cp src/bin/pg_dump/pg_dump src/bin/pg_dump/pg_restore "$OUT_PG"/
|
|
cp src/bin/psql/psql "$OUT_PG"/
|
|
cp src/interfaces/libpq/libpq.5.dylib "$OUT_PG"/libpq.5.dylib
|
|
for b in pg_dump pg_restore psql; do
|
|
libpq=$(otool -L "$OUT_PG/$b" | awk '/libpq/ {print $1; exit}')
|
|
install_name_tool -change "$libpq" "@loader_path/libpq.5.dylib" "$OUT_PG/$b"
|
|
done
|
|
# install_name_tool invalidates the linker's signature; re-sign with the
|
|
# Developer ID identity + hardened runtime + secure timestamp so the bundle
|
|
# passes notarization (Apple rejects ad-hoc-signed nested executables).
|
|
for f in "$OUT_PG"/pg_dump "$OUT_PG"/pg_restore "$OUT_PG"/psql "$OUT_PG"/libpq.5.dylib; do
|
|
codesign --force --options runtime --timestamp --sign "$SIGNING_IDENTITY" "$f"
|
|
done
|
|
|
|
echo ">> Building MariaDB client tools..."
|
|
mkdir -p "$OUT_MY"
|
|
MARIADB_VER="11.4.5"
|
|
# Idempotent re-runs: git clone fails if the destination is non-empty (e.g.
|
|
# left over from an earlier build on the same machine). Clean it first.
|
|
rm -rf /tmp/mariadb-server
|
|
git clone --depth 1 --branch "mariadb-${MARIADB_VER}" https://github.com/MariaDB/server.git /tmp/mariadb-server
|
|
cd /tmp/mariadb-server
|
|
if [ "$(uname -m)" = arm64 ]; then
|
|
SSL_DIR="/opt/homebrew/opt/openssl"
|
|
else
|
|
SSL_DIR="/usr/local/opt/openssl"
|
|
fi
|
|
[ -d "$SSL_DIR" ] || brew install openssl
|
|
cmake -DCMAKE_BUILD_TYPE=Release \
|
|
-DWITHOUT_SERVER=ON \
|
|
-DWITHOUT_TOKUDB=1 \
|
|
-DWITHOUT_ROCKSDB=1 \
|
|
-DWITHOUT_MROONGA=1 \
|
|
-DWITHOUT_SPIDER=1 \
|
|
-DWITHOUT_SEQUENCE=1 \
|
|
-DWITH_UNIT_TESTS=OFF \
|
|
-DWITH_SSL="$SSL_DIR" \
|
|
-DWITH_ZLIB=bundled .
|
|
make -j"$(sysctl -n hw.ncpu)" mariadb-dump mariadb
|
|
for b in mariadb-dump mariadb; do
|
|
src=$(find client -maxdepth 1 -type f -name "$b" -print -quit 2>/dev/null || true)
|
|
test -n "$src" || src=$(find . -type f -name "$b" -print -quit || true)
|
|
test -n "$src" || { echo "build produced no $b binary"; exit 1; }
|
|
cp "$src" "$OUT_MY"/
|
|
done
|
|
for pass in 1 2 3 4 5 6; do
|
|
for dylib in "$OUT_MY"/*.dylib "$OUT_MY"/mariadb-dump "$OUT_MY"/mariadb; do
|
|
[ -f "$dylib" ] || continue
|
|
otool -L "$dylib" | awk 'NR>1 {print $1}' | grep -E '^/(usr/local|opt/homebrew)' | while read -r lib; do
|
|
base=$(basename "$lib")
|
|
if [ ! -f "$OUT_MY/$base" ]; then cp "$lib" "$OUT_MY/$base" 2>/dev/null || true; fi
|
|
install_name_tool -change "$lib" "@loader_path/$base" "$dylib" 2>/dev/null || true
|
|
done || true
|
|
codesign --force --options runtime --timestamp --sign "$SIGNING_IDENTITY" "$dylib"
|
|
done
|
|
done
|
|
|
|
# ---- build the app (both arches) ------------------------------------------
|
|
cd "${WORKSPACE}/desktop"
|
|
echo ">> Installing frontend dependencies..."
|
|
bun install --frozen-lockfile
|
|
|
|
echo ">> Building aarch64 (Apple Silicon)..."
|
|
bun run tauri build --target aarch64-apple-darwin
|
|
echo ">> Building x86_64 (Intel)..."
|
|
bun run tauri build --target x86_64-apple-darwin
|
|
|
|
VERSION="${TAG#v}"
|
|
BUNDLE_ARM="${WORKSPACE}/desktop/src-tauri/target/aarch64-apple-darwin/release/bundle"
|
|
BUNDLE_INTEL="${WORKSPACE}/desktop/src-tauri/target/x86_64-apple-darwin/release/bundle"
|
|
|
|
# ---- verify signing + notarization -----------------------------------------
|
|
# Confirm both .app bundles are properly signed and carry a stapled
|
|
# notarization ticket before we upload them. spctl can lag right after
|
|
# notarization (assessment cache), so stapler validate is the authoritative
|
|
# check; a pending spctl is only a warning.
|
|
for APP in \
|
|
"${BUNDLE_ARM}/macos/Gridline.app" \
|
|
"${BUNDLE_INTEL}/macos/Gridline.app"; do
|
|
[ -d "$APP" ] || { echo "missing $APP" >&2; exit 1; }
|
|
echo ">> Verifying $APP"
|
|
codesign --verify --deep --strict --verbose=2 "$APP" \
|
|
|| { echo "codesign verify FAILED" >&2; exit 1; }
|
|
xcrun stapler validate "$APP" \
|
|
|| { echo "notarization ticket not stapled" >&2; exit 1; }
|
|
spctl --assess --type execute --verbose=4 "$APP" \
|
|
|| echo " (spctl assessment pending — normal right after notarization; stapler validate passed)"
|
|
done
|
|
|
|
# ---- notarize + staple the DMGs --------------------------------------------
|
|
# Notarizing the .app is not enough. When a user opens the downloaded DMG,
|
|
# Gatekeeper assesses the DMG itself — and an unnotarized DMG is rejected as
|
|
# "Unnotarized Developer ID" even though the app inside it is fine. Submit each
|
|
# DMG to the same notary service and staple the ticket into the DMG.
|
|
if [ "${GRIDLINE_SKIP_NOTARIZE:-0}" = "1" ]; then
|
|
echo ">> Skipping DMG notarization (GRIDLINE_SKIP_NOTARIZE=1)."
|
|
else
|
|
API_KEY_PATH="${APPLE_API_KEY_PATH:-${HOME}/.appstoreconnect/private_keys/AuthKey_${APPLE_API_KEY}.p8}"
|
|
[ -f "$API_KEY_PATH" ] || {
|
|
echo "ERROR: notarization API key not found at $API_KEY_PATH" >&2
|
|
echo " Set APPLE_API_KEY_PATH in ${NOTARIZE_ENV} or place the .p8 at the default location." >&2
|
|
exit 1
|
|
}
|
|
for DMG in \
|
|
"${BUNDLE_ARM}/dmg/Gridline_${VERSION}_aarch64.dmg" \
|
|
"${BUNDLE_INTEL}/dmg/Gridline_${VERSION}_x64.dmg"; do
|
|
[ -f "$DMG" ] || { echo "missing $DMG" >&2; exit 1; }
|
|
echo ">> Notarizing $(basename "$DMG") (this can take a few minutes)"
|
|
xcrun notarytool submit "$DMG" \
|
|
--key "$API_KEY_PATH" \
|
|
--key-id "$APPLE_API_KEY" \
|
|
--issuer "$APPLE_API_ISSUER" \
|
|
--wait
|
|
xcrun stapler staple "$DMG" \
|
|
|| { echo "failed to staple $(basename "$DMG")" >&2; exit 1; }
|
|
xcrun stapler validate "$DMG" \
|
|
|| { echo "notarization ticket not stapled to $(basename "$DMG")" >&2; exit 1; }
|
|
done
|
|
fi
|
|
|
|
# ---- create/ensure release + upload DMGs -----------------------------------
|
|
cd "${WORKSPACE}"
|
|
|
|
echo ">> Ensuring release ${TAG} exists on ${GH_REPO}..."
|
|
if ! gh release view "${TAG}" --repo "${GH_REPO}" >/dev/null 2>&1; then
|
|
gh release create "${TAG}" --repo "${GH_REPO}" --title "Gridline ${VERSION}" \
|
|
--notes "Gridline ${VERSION} — macOS (built locally, signed + notarized)." --draft
|
|
else
|
|
echo ">> Release ${TAG} already exists."
|
|
fi
|
|
|
|
# --clobber replaces the CI-built (ad-hoc) DMGs with this signed + notarized pair.
|
|
echo ">> Uploading macOS DMGs (replacing any existing ones)..."
|
|
gh release upload "${TAG}" --repo "${GH_REPO}" --clobber \
|
|
"${BUNDLE_ARM}/dmg/Gridline_${VERSION}_aarch64.dmg" \
|
|
"${BUNDLE_INTEL}/dmg/Gridline_${VERSION}_x64.dmg"
|
|
|
|
echo "Done. Review + publish the draft: https://github.com/${GH_REPO}/releases/tag/${TAG}"
|