mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-11 19:34:02 +01:00
Add scripts/deploy.sh and the deploy command
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Some checks are pending
dependency freshness / dependencies on newest stable (push) Waiting to run
file-length / file length <= 250 lines (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
release-apk / release (APK) (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
Deploys the MCP service, the desktop pacman package and the phone release build, each behind its own health check; exits non-zero unless all pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
203e61d6ac
commit
0e56cddd47
@ -50,6 +50,7 @@ and sync peer-to-peer through a GitHub repo used as dumb storage.
|
||||
- lint: `flutter analyze`
|
||||
- coverage: `flutter test --coverage --reporter=compact`
|
||||
- coverage-gaps: `coverage-gaps coverage/lcov.info`
|
||||
- deploy: `scripts/deploy.sh` (MCP service, desktop pacman package, phone release; exits non-zero unless every health check passes)
|
||||
|
||||
## Architecture
|
||||
|
||||
|
||||
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.0.77+1
|
||||
version: 1.0.79+1
|
||||
|
||||
environment:
|
||||
sdk: ^3.12.2
|
||||
|
||||
160
scripts/deploy.sh
Executable file
160
scripts/deploy.sh
Executable file
@ -0,0 +1,160 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Deploy todo everywhere it runs, then prove each one is live.
|
||||
#
|
||||
# scripts/deploy.sh # all targets: mcp desktop phone
|
||||
# scripts/deploy.sh mcp desktop # only the named targets
|
||||
#
|
||||
# Targets (each has its own health check; any failure makes the exit non-zero,
|
||||
# but the remaining targets still deploy so one unplugged phone does not leave
|
||||
# the desktop on old code):
|
||||
# mcp frozen binary + todo-mcp.service (mcp/install.sh), restart, then a
|
||||
# real initialize + list_tasks against :8735 (mcp/verify_mcp.py).
|
||||
# desktop web build + pacman package todo-flutter (install_arch.sh); health
|
||||
# = installed package and bundled web version.json match pubspec.
|
||||
# phone release APK via phone_deploy.sh (adb install -r only; it has no
|
||||
# uninstall / pm clear path); health = versionName on the Pixel
|
||||
# matches pubspec and the app process is running.
|
||||
#
|
||||
# Idempotent: desktop and phone skip the build when the installed version
|
||||
# already equals pubspec's (every commit bumps the patch version, so an equal
|
||||
# version means equal code); the MCP binary is cheap and always rebuilt.
|
||||
#
|
||||
# Nothing live runs from ~/src/todo (the MCP is a frozen binary, the desktop
|
||||
# app lives in /opt/todo), so this deploys the checkout it sits in and never
|
||||
# pulls kuhy's working tree.
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
readonly ROOT
|
||||
readonly SERIAL="${ADB_SERIAL:-23181JEGR08034}"
|
||||
readonly APP_ID="dev.kuhy.todo"
|
||||
readonly MCP_UNIT="todo-mcp.service"
|
||||
readonly MCP_URL="http://127.0.0.1:8735/mcp"
|
||||
readonly MCP_TOKEN="${HOME}/.config/todo-mcp-client/token"
|
||||
readonly PHONE_DEPLOY="${HOME}/.claude/scripts/phone_deploy.sh"
|
||||
readonly CAPPED="${HOME}/.claude/scripts/capped.sh"
|
||||
readonly WEB_VERSION_JSON="/opt/todo/web/version.json"
|
||||
|
||||
FAILED=()
|
||||
|
||||
say() { echo "[deploy:$1] $2"; }
|
||||
fail() {
|
||||
echo "[deploy:$1] FAIL: $2" >&2
|
||||
FAILED+=("$1")
|
||||
}
|
||||
|
||||
# pubspec `version: 1.0.77+1` -> `1.0.77` (the +build part is CI's).
|
||||
pubspec_version() {
|
||||
sed -n 's/^version:[[:space:]]*\([^+[:space:]]*\).*/\1/p' "$ROOT/pubspec.yaml"
|
||||
}
|
||||
|
||||
# Builds run under the shared resource cap when it exists on this machine.
|
||||
heavy() {
|
||||
if [[ -x "$CAPPED" ]]; then
|
||||
CAP_MEM=4G CAP_CPU_PCT=20 "$CAPPED" "$@"
|
||||
else
|
||||
"$@"
|
||||
fi
|
||||
}
|
||||
|
||||
mcp_pid() { systemctl --user show -p MainPID --value "$MCP_UNIT" 2>/dev/null || echo 0; }
|
||||
|
||||
deploy_mcp() {
|
||||
local old_pid new_pid out
|
||||
old_pid="$(mcp_pid)"
|
||||
heavy "$ROOT/mcp/install.sh" || { fail mcp "mcp/install.sh failed"; return; }
|
||||
# install.sh only restarts a running unit; make sure it is enabled + up.
|
||||
systemctl --user enable --now "$MCP_UNIT" >/dev/null 2>&1 ||
|
||||
{ fail mcp "cannot start $MCP_UNIT"; return; }
|
||||
systemctl --user is-active --quiet "$MCP_UNIT" ||
|
||||
{ fail mcp "$MCP_UNIT not active"; return; }
|
||||
new_pid="$(mcp_pid)"
|
||||
if [[ "$new_pid" == 0 || "$new_pid" == "$old_pid" ]]; then
|
||||
fail mcp "$MCP_UNIT was not restarted (pid $old_pid -> $new_pid)"
|
||||
return
|
||||
fi
|
||||
[[ -s "$MCP_TOKEN" ]] || { fail mcp "no client token at $MCP_TOKEN"; return; }
|
||||
out="$(python3 "$ROOT/mcp/verify_mcp.py" "$MCP_URL" \
|
||||
--token-file "$MCP_TOKEN" --wait 20 2>&1)" ||
|
||||
{ fail mcp "live check: $out"; return; }
|
||||
say mcp "ok: pid $new_pid, ${out//$'\n'/, }"
|
||||
}
|
||||
|
||||
desktop_versions_match() {
|
||||
local want="$1" pkg web
|
||||
pkg="$(pacman -Q todo-flutter 2>/dev/null | awk '{print $2}')"
|
||||
web="$(sed -n 's/.*"version":"\([^"]*\)".*/\1/p' "$WEB_VERSION_JSON" 2>/dev/null)"
|
||||
[[ "${pkg%-*}" == "$want" && "$web" == "$want" ]]
|
||||
}
|
||||
|
||||
deploy_desktop() {
|
||||
local want
|
||||
want="$(pubspec_version)"
|
||||
if desktop_versions_match "$want"; then
|
||||
say desktop "ok: todo-flutter $want already installed"
|
||||
return
|
||||
fi
|
||||
sudo -n true 2>/dev/null ||
|
||||
{ fail desktop "sudo needs a password; run: ! $ROOT/install_arch.sh"; return; }
|
||||
heavy "$ROOT/install_arch.sh" || { fail desktop "install_arch.sh failed"; return; }
|
||||
if desktop_versions_match "$want"; then
|
||||
say desktop "ok: todo-flutter $want installed"
|
||||
else
|
||||
fail desktop "installed version is not $want ($(pacman -Q todo-flutter 2>&1))"
|
||||
fi
|
||||
}
|
||||
|
||||
phone_version() {
|
||||
adb -s "$SERIAL" shell dumpsys package "$APP_ID" 2>/dev/null |
|
||||
sed -n 's/^[[:space:]]*versionName=\([^[:space:]]*\).*/\1/p' | head -n 1
|
||||
}
|
||||
|
||||
phone_health() {
|
||||
local want="$1" got
|
||||
got="$(phone_version)"
|
||||
[[ "$got" == "$want" ]] || { fail phone "versionName on phone is '$got', want $want"; return; }
|
||||
adb -s "$SERIAL" shell pidof "$APP_ID" >/dev/null 2>&1 ||
|
||||
adb -s "$SERIAL" shell monkey -p "$APP_ID" -c android.intent.category.LAUNCHER 1 >/dev/null 2>&1 || true
|
||||
sleep 2
|
||||
adb -s "$SERIAL" shell pidof "$APP_ID" >/dev/null 2>&1 ||
|
||||
{ fail phone "$APP_ID is installed but not running"; return; }
|
||||
say phone "ok: $APP_ID $want running on $SERIAL"
|
||||
}
|
||||
|
||||
deploy_phone() {
|
||||
local want rc=0
|
||||
want="$(pubspec_version)"
|
||||
if [[ "$(adb -s "$SERIAL" get-state 2>/dev/null)" != device ]]; then
|
||||
fail phone "phone unreachable: $SERIAL not attached (plug it in, enable USB debugging)"
|
||||
return
|
||||
fi
|
||||
if [[ "$(phone_version)" == "$want" ]]; then
|
||||
say phone "already at $want, skipping build"
|
||||
else
|
||||
ADB_SERIAL="$SERIAL" heavy "$PHONE_DEPLOY" "$ROOT" --release || rc=$?
|
||||
((rc == 0)) || { fail phone "phone_deploy.sh exited $rc (see its exit-code table)"; return; }
|
||||
fi
|
||||
phone_health "$want"
|
||||
}
|
||||
|
||||
main() {
|
||||
local targets=("$@") t
|
||||
((${#targets[@]})) || targets=(mcp desktop phone)
|
||||
for t in "${targets[@]}"; do
|
||||
case "$t" in
|
||||
mcp | desktop | phone) "deploy_$t" ;;
|
||||
*) echo "usage: $0 [mcp] [desktop] [phone]" >&2; exit 2 ;;
|
||||
esac
|
||||
done
|
||||
if ((${#FAILED[@]})); then
|
||||
echo "[deploy] FAILED: ${FAILED[*]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[deploy] all healthy: ${targets[*]} at $(pubspec_version)"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Reference in New Issue
Block a user