Files
bDS2/scripts/verify_blogmark_macos.sh

81 lines
3.3 KiB
Bash
Executable File

#!/bin/bash
# End-to-end blogmark verification against the bundled macOS app.
#
# Launches BDS2.app through LaunchServices with an isolated database, fires
# bds2://new-post deep links the same way a browser bookmarklet does, and
# asserts draft posts show up in the database. Covers both the cold-start
# path (link launches the app, gets queued, drains on shell attach) and the
# already-running path.
#
# The app MUST be launched via LaunchServices (`open -a`): a process started
# by exec'ing Contents/MacOS/bds2 from a shell never receives Apple Events,
# so deep links silently go nowhere. Env vars are injected with
# `launchctl setenv` because `open` does not pass the shell environment.
#
# Usage: scripts/verify_blogmark_macos.sh [path/to/BDS2.app]
set -euo pipefail
APP="$(cd "${1:-dist/macos/BDS2.app}" 2>/dev/null && pwd)" || { echo "FAIL: app bundle not found" >&2; exit 1; }
fail() { echo "FAIL: $*" >&2; exit 1; }
[ -x "$APP/Contents/MacOS/bds2" ] \
|| fail "$APP is not a built bundle. Build with: mix assets.deploy && MIX_ENV=prod mix release bds --overwrite && mix bds.bundle.macos"
# Guard against the regression that broke blogmarks in July 2026: a bundle
# whose server waits for the client's shell_ready event but whose stale
# app.js never sends it. Deep links then queue forever with no error anywhere.
grep -q shell_ready "$APP"/Contents/Resources/rel/lib/bds-*/priv/static/assets/app.js \
|| fail "bundle ships stale app.js (no shell_ready) — run mix assets.deploy BEFORE mix release"
# epmd survives app quit and lives under the .app path, so match the VM only.
# Any running BDS2 (installed or dist) makes LaunchServices routing ambiguous.
pgrep -f "BDS2.app.*beam" >/dev/null && fail "a BDS2.app instance is already running; quit it first"
WORK="$(mktemp -d /tmp/bds2-blogmark-verify.XXXXXX)"
DB="$WORK/bds.db"
STAMP="$(date +%s)"
cleanup() {
# The launcher execs a script that runs beam as a child, so kill by path.
pkill -f "$APP.*beam" 2>/dev/null || true
launchctl unsetenv BDS_DATABASE_PATH
rm -rf "$WORK"
}
trap cleanup EXIT
sql() { sqlite3 -cmd '.timeout 2000' "$DB" "$1" 2>/dev/null || true; }
wait_for_draft() { # $1 = title, $2 = timeout seconds
local i row
for i in $(seq 1 "$2"); do
row="$(sql "select status || '|' || content from posts where title = '$1';")"
if [ -n "$row" ]; then
case "$row" in
draft\|*example.com*) return 0 ;;
*) fail "post '$1' found but wrong shape: $row" ;;
esac
fi
sleep 1
done
return 1
}
launchctl setenv BDS_DATABASE_PATH "$DB"
# Cold start: the deep link itself launches the app. The link must survive
# boot (queued in BDS.Desktop.DeepLink) and import once the shell attaches.
COLD="Blogmark cold $STAMP"
echo "Cold start: launching $APP via deep link ..."
open -a "$APP" "bds2://new-post?title=${COLD// /%20}&url=https%3A%2F%2Fexample.com%2Fcold"
wait_for_draft "$COLD" 120 || fail "cold-start deep link produced no draft within 120s"
echo " ok: cold-start draft created"
# Already running: a second link must import immediately.
WARM="Blogmark warm $STAMP"
open -a "$APP" "bds2://new-post?title=${WARM// /%20}&url=https%3A%2F%2Fexample.com%2Fwarm"
wait_for_draft "$WARM" 30 || fail "deep link to the running app produced no draft within 30s"
echo " ok: already-running draft created"
echo "PASS: blogmark deep links verified against $APP"