From 02382efe635b495492361ee30e7b73e4af577515 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 20 Jun 2026 15:53:01 +0000 Subject: [PATCH] feat(deploy): add merge-and-deploy flow for #vacation-deploy agent The deploy agent now merges the feature branch into main, pushes main, then deploys main to the Firebase prod project (vacation-app-21706). Adds scripts/merge-and-deploy.sh (with DRY_RUN + PR-number support) and documents the flow in docs/deploy.md. Co-authored-by: Joe M --- docs/deploy.md | 33 +++++++++++++ scripts/merge-and-deploy.sh | 95 +++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100755 scripts/merge-and-deploy.sh diff --git a/docs/deploy.md b/docs/deploy.md index df8b9fd..c3a871a 100644 --- a/docs/deploy.md +++ b/docs/deploy.md @@ -2,6 +2,39 @@ Automated deploy reads credentials from **environment variables**, never from committed files. +## #vacation-deploy agent flow (merge + deploy) + +The `#vacation-deploy` Slack channel triggers the **Paradise Planner – Deploy Agent** +automation. The agent's job is now a single two-step flow: **merge the feature branch +into `main`, then deploy `main` to the Firebase prod project.** + +Run it with one command (it merges, pushes `main`, and deploys): + +```bash +scripts/merge-and-deploy.sh # e.g. cursor/my-feature-c593 +scripts/merge-and-deploy.sh # e.g. 9 — resolves the PR head branch via gh +``` + +What the script does: + +1. `git fetch` and check out `main`, fast-forward to the latest remote `main`. +2. Merge the feature branch (or PR head) into `main` with a merge commit. +3. Push `main` to the remote. +4. Deploy `main` to Firebase (`hosting` by default). + +Options (environment variables): + +| Variable | Default | Purpose | +|----------|---------|---------| +| `DEPLOY_TARGETS` | `hosting` | Comma list of targets, e.g. `hosting,functions`. | +| `FIREBASE_PROJECT` | `vacation-app-21706` | Target Firebase project. | +| `MAIN_BRANCH` | `main` | Branch to merge into and deploy. | +| `DRY_RUN` | `0` | `1` = merge locally only; skip push + deploy (safe rehearsal). | + +Requires `FIREBASE_TOKEN` (or `GOOGLE_APPLICATION_CREDENTIALS`) — already provided as a +Cloud Agent secret. If the merge hits a conflict the script stops before pushing or +deploying so the conflict can be resolved manually. + ## Credentials (do not commit) | Variable | Where to set | How to get | diff --git a/scripts/merge-and-deploy.sh b/scripts/merge-and-deploy.sh new file mode 100755 index 0000000..3305120 --- /dev/null +++ b/scripts/merge-and-deploy.sh @@ -0,0 +1,95 @@ +#!/usr/bin/env bash +# Merge a feature branch into main, then deploy main to the Firebase prod project. +# +# This is the canonical entrypoint for the #vacation-deploy agent flow: +# 1. Merge the given feature branch (or PR head) into the main branch. +# 2. Push the updated main branch to the remote. +# 3. Deploy main to Firebase (Hosting by default, optionally Functions). +# +# Requires FIREBASE_TOKEN (CI token) or GOOGLE_APPLICATION_CREDENTIALS (service account). +# +# Usage: +# scripts/merge-and-deploy.sh # merge a branch +# scripts/merge-and-deploy.sh 9 # merge PR #9 (resolves head branch via gh) +# DEPLOY_BRANCH=my-branch scripts/merge-and-deploy.sh +# DEPLOY_TARGETS="hosting,functions" scripts/merge-and-deploy.sh my-branch +# DRY_RUN=1 scripts/merge-and-deploy.sh my-branch # do everything locally, skip push + deploy +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" + +# Local dev convenience: load .env for FIREBASE_TOKEN. Cloud agents inject it as env. +if [[ -f "$ROOT/.env" ]]; then + set -a + # shellcheck disable=SC1091 + source "$ROOT/.env" + set +a +fi + +REF="${1:-${DEPLOY_BRANCH:-${DEPLOY_PR:-}}}" +MAIN_BRANCH="${MAIN_BRANCH:-main}" +REMOTE="${GIT_REMOTE:-origin}" +PROJECT="${FIREBASE_PROJECT:-vacation-app-21706}" +DEPLOY_TARGETS="${DEPLOY_TARGETS:-hosting}" +DRY_RUN="${DRY_RUN:-0}" + +if [[ -z "$REF" ]]; then + echo "Usage: scripts/merge-and-deploy.sh (or set DEPLOY_BRANCH / DEPLOY_PR)" >&2 + exit 1 +fi + +if [[ -z "${FIREBASE_TOKEN:-}" && -z "${GOOGLE_APPLICATION_CREDENTIALS:-}" ]]; then + echo "Error: set FIREBASE_TOKEN or GOOGLE_APPLICATION_CREDENTIALS before deploying." >&2 + echo " Cloud: Cursor dashboard -> Cloud Agents -> Secrets -> FIREBASE_TOKEN" >&2 + exit 1 +fi + +# Resolve a PR number to its head branch (requires gh). A purely numeric REF is a PR. +BRANCH="$REF" +if [[ "$REF" =~ ^[0-9]+$ ]]; then + echo "==> Resolving PR #$REF head branch via gh..." + BRANCH="$(gh pr view "$REF" --json headRefName -q .headRefName)" + echo " PR #$REF -> $BRANCH" +fi + +echo "==> Fetching $REMOTE..." +git fetch "$REMOTE" --prune + +if ! git rev-parse --verify "$REMOTE/$BRANCH" >/dev/null 2>&1; then + echo "Error: branch '$BRANCH' not found on remote '$REMOTE'." >&2 + exit 1 +fi + +echo "==> Checking out $MAIN_BRANCH..." +git checkout "$MAIN_BRANCH" +git pull --ff-only "$REMOTE" "$MAIN_BRANCH" + +echo "==> Merging $BRANCH into $MAIN_BRANCH..." +git merge --no-ff --no-edit "$REMOTE/$BRANCH" \ + -m "Merge branch '$BRANCH' into $MAIN_BRANCH (automated #vacation-deploy)" + +if [[ "$DRY_RUN" == "1" ]]; then + echo "==> DRY_RUN=1: merge completed locally; skipping push and deploy." + echo " HEAD is now $(git rev-parse --short HEAD) on $MAIN_BRANCH." + exit 0 +fi + +echo "==> Pushing $MAIN_BRANCH to $REMOTE..." +git push "$REMOTE" "$MAIN_BRANCH" + +# Deploy Hosting (build + publish) when requested. +if [[ ",$DEPLOY_TARGETS," == *",hosting,"* ]]; then + echo "==> Deploying Hosting to $PROJECT..." + FIREBASE_PROJECT="$PROJECT" "$ROOT/scripts/deploy-hosting.sh" +fi + +# Deploy Functions when requested. +if [[ ",$DEPLOY_TARGETS," == *",functions,"* ]]; then + echo "==> Deploying Functions to $PROJECT..." + npm --prefix "$ROOT/functions" install + npx firebase-tools@latest deploy --only functions --project "$PROJECT" --non-interactive +fi + +echo "==> Done. Deployed $MAIN_BRANCH ($DEPLOY_TARGETS) to $PROJECT." +echo " Smoke test: https://$PROJECT.web.app"