Skip to content

feat(ci): isolated local API stack for Maestro e2e (local Postgres + wrangler dev)#2460

Merged
mikib0 merged 16 commits into
developmentfrom
feat/local-api-e2e
May 21, 2026
Merged

feat(ci): isolated local API stack for Maestro e2e (local Postgres + wrangler dev)#2460
mikib0 merged 16 commits into
developmentfrom
feat/local-api-e2e

Conversation

@mikib0

@mikib0 mikib0 commented May 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

Replaces the shared Neon dev DB dependency in both Maestro e2e CI jobs with a fully isolated, per-run local stack: a local Postgres instance + wrangler dev running inside the CI runner. No cloud DB is touched during e2e runs.

What changed

CI (e2e-tests.yml)

  • e2e-gate: now gates on E2E_BETTER_AUTH_SECRET instead of NEON_DEV_DATABASE_URL
  • Both jobs: new local API setup sequence before the app build —
    1. Start Postgres via ikalnytskyi/action-setup-postgres (works on both macos-15 and ubuntu-latest without Docker)
    2. Write packages/api/.dev.vars from a mix of hardcoded local values and repo secrets
    3. Run DB migrations against local Postgres
    4. Seed the e2e test user into local Postgres
    5. Start wrangler dev -e dev --ip 0.0.0.0 in the background; poll /health until ready
  • iOS job: ikalnytskyi/action-setup-postgres handles Postgres on macos-15 (no Docker needed on macOS runners)
  • Android job: --ip 0.0.0.0 on wrangler dev so the emulator can reach the host via 10.0.2.2; apps/expo/lib/utils/getApiUrl.ts already rewrites localhost → 10.0.2.2 at runtime
  • App build: apps/expo/.env.local is written before eas build --local with EXPO_PUBLIC_API_URL=http://localhost:8787; .env.local has highest priority in Expo's env hierarchy and overrides the EAS "preview" environment's API URL
  • Cache keys: suffixed with local-api to keep local-API builds in a separate slot from any cached deployed-API builds
  • wrangler dev log is uploaded as a CI artifact on failure for easier debugging
  • Old "Seed E2E test user in dev DB" steps (which used NEON_DEV_DATABASE_URL) removed from both jobs

packages/api/ (local dev support)

  • docker-compose.e2e.yml: Postgres 16 on port 5435 for local Maestro runs (avoids conflict with a developer's existing Postgres on 5432)
  • scripts/e2e-local-init.sh: generates .dev.vars.e2e by reading the developer's existing .dev.vars and overriding only the DB and API URLs
  • scripts/e2e-local-start.sh: starts Docker Postgres → migrates → seeds → launches wrangler dev
  • scripts/e2e-local-stop.sh: tears down containers (--volumes to wipe data)
  • package.json: dev:e2e, dev:e2e:init, dev:e2e:stop, dev:e2e:reset scripts
  • .dev.vars.e2e.example: committed template (no real secrets)
  • README.e2e-local.md: usage guide for running Maestro e2e locally

Why no wsproxy

packages/api/src/db/index.ts createConnection() detects a standard postgres://localhost/... URL and routes to pg.Pool (node-postgres) automatically — the Neon WebSocket driver is bypassed entirely for local connections.

Secrets

Secret Change
E2E_BETTER_AUTH_SECRET Add — any random string ≥ 32 chars
WEATHER_API_KEY Optional — improves trip-detail test fidelity
OPENWEATHER_KEY Optional — same
NEON_DEV_DATABASE_URL Can be removed — no longer referenced

Test plan

  • Add E2E_BETTER_AUTH_SECRET to repo secrets (Settings → Secrets → Actions)
  • Trigger the workflow via workflow_dispatch and confirm both jobs reach the "Run Maestro" step
  • On failure, download the *-wrangler-dev-log artifact and confirm the API started cleanly
  • Locally: run bun run --filter @packrat/api dev:e2e:init && bun run --filter @packrat/api dev:e2e, then curl http://localhost:8787/health{"status":"ok"}

Summary by CodeRabbit

  • New Features

    • Local E2E support that boots a self-contained API stack and exposes a local dev endpoint for device/emulator testing.
  • Chores

    • CI workflow runs PR/push E2E with local-target env gating, dedicated cache keys, local DB/service orchestration, start/stop/reset scripts, and updated ignore rules.
  • Documentation

    • Developer guide for initializing, running, and troubleshooting local E2E environments.
  • Tests

    • Robusted E2E flows: resilient UI handling, deterministic test-user seeding/upsert, log upload on failures, and improved cleanup.

Review Change Stack

mikib0 added 2 commits May 20, 2026 11:39
Adds an isolated local API environment so Maestro e2e flows run against a
Docker Postgres DB instead of the shared Neon dev DB.

- docker-compose.e2e.yml: Postgres 16 on port 5435 (packrat_e2e DB)
- scripts/e2e-local-init.sh: generates .dev.vars.e2e from existing .dev.vars,
  overriding only NEON_DATABASE_URL*, EXPO_PUBLIC_API_URL, BETTER_AUTH_URL
- scripts/e2e-local-start.sh: starts Docker, runs migrations, seeds e2e user,
  launches wrangler dev with --env-file .dev.vars.e2e --ip 0.0.0.0
- scripts/e2e-local-stop.sh: tears down containers (--volumes for full reset)
- package.json: dev:e2e, dev:e2e:init, dev:e2e:stop, dev:e2e:reset scripts
- .dev.vars.e2e.example: committed template with local DB URL + placeholders
- README.e2e-local.md: usage guide and architecture overview
Removes the shared Neon dev DB dependency from both e2e jobs.
Each CI runner now spins up its own isolated Postgres instance
(ikalnytskyi/action-setup-postgres), runs migrations, seeds the
e2e user, and starts wrangler dev against the local DB before
Maestro tests run.

Key changes:
- e2e-gate: gates on E2E_BETTER_AUTH_SECRET instead of NEON_DEV_DATABASE_URL
- Both jobs: Start local Postgres → write .dev.vars → migrate → seed
  → start wrangler dev (background, health-checked) → build app → run Maestro
- iOS: ikalnytskyi/action-setup-postgres works on macos-15 without Docker
- Android: wrangler dev runs with --ip 0.0.0.0 so the emulator can reach it
  via 10.0.2.2 (apps/expo/lib/utils/getApiUrl.ts handles the substitution)
- apps/expo/.env.local written before eas build --local to pin
  EXPO_PUBLIC_API_URL=http://localhost:8787, overriding the EAS preview env
- Cache keys suffixed with 'local-api' to stay separate from deployed-API builds
- Schema-valid stubs for AI/email/R2/OAuth vars (not exercised by master flow)
- wrangler dev log uploaded as artifact on failure for easier debugging
- Old 'Seed E2E test user in dev DB' steps (using NEON_DEV_DATABASE_URL) removed

New required GitHub Secret: E2E_BETTER_AUTH_SECRET (32+ char random string)
Optional secrets for trip-detail fidelity: WEATHER_API_KEY, OPENWEATHER_KEY
Removed secret dependency: NEON_DEV_DATABASE_URL
@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file api ci/cd labels May 20, 2026
@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Switches mobile E2E to a local API stack: starts a Docker Postgres, generates .dev.vars.e2e, runs migrations and E2E user seed, launches wrangler dev, and runs Maestro iOS/Android tests from GitHub Actions (adds pull_request trigger and gates on E2E creds).

Changes

Local E2E Testing Infrastructure

Layer / File(s) Summary
Local infrastructure foundation
packages/api/docker-compose.e2e.yml, packages/api/.dev.vars.e2e.example, packages/api/package.json, packages/api/.gitignore, packages/api/README.e2e-local.md
Defines a Postgres 16 service on port 5435, a .dev.vars.e2e template with local URLs and credentials, new npm e2e scripts, and docs; .dev.vars.e2e is gitignored.
E2E control and bootstrap scripts
packages/api/scripts/e2e-local-init.sh, packages/api/scripts/e2e-local-start.sh, packages/api/scripts/e2e-local-stop.sh
Init creates .dev.vars.e2e from a base file with local overrides. Start validates Docker, launches Postgres, waits for health, runs migrations, seeds E2E user, and starts wrangler dev bound to LAN. Stop tears down the stack and optionally removes volumes.
Auth and DB runtime changes
packages/api/src/auth/index.ts, packages/api/src/db/index.ts, packages/api/src/db/seed-e2e-user.ts
getAuth caching changed to Map keyed by NEON_DATABASE_URL and uses createConnection(..., useNeonHttp:true). Standard Postgres uses pg.Pool with timeouts and evicts pools on error. AUTH_KV.put enforces min TTL 60s. Seed script unifies userId and upserts credential account rows.
Maestro flows updates
.maestro/flows/auth/login-flow.yaml, .maestro/flows/setup/clear-state.yaml, .maestro/flows/packs/create-pack-flow.yaml
Adds pre-auth overlay dismissal (Error loading app, Expo dev sheet), conditional sign-in handling, iOS step to reselect DEVELOPMENT SERVERS after clearing state, and a visibility-based wait before pack creation.
CI workflow structure and jobs
.github/workflows/e2e-tests.yml, packages/api/wrangler.jsonc
Adds pull_request trigger and path filters, centralizes E2E_DB_URL/E2E_API_URL/MAESTRO_CLI_NO_ANALYTICS, changes prerequisites to require E2E credentials, reworks iOS/Android jobs to start local Postgres/wrangler, pins Expo API URL for local runs, adjusts build cache keys, and updates Maestro invocation/cleanup.

Sequence Diagram (high-level E2E execution):

sequenceDiagram
  participant GHA as GitHub Actions
  participant Docker as Docker
  participant Postgres as postgres-e2e
  participant Wrangler as wrangler
  participant Maestro as Maestro
  GHA->>Docker: start postgres-e2e
  Docker->>Postgres: expose 5435 and mount volume
  GHA->>GHA: generate `.dev.vars.e2e`, run migrations & seed
  GHA->>Wrangler: start wrangler dev (bg)
  GHA->>Wrangler: poll /health
  GHA->>Maestro: run platform E2E
  Maestro->>Wrangler: API requests
  Wrangler->>Postgres: DB queries
  GHA->>Wrangler: stop wrangler dev
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

mobile

Suggested reviewers

  • andrew-bierman
  • Isthisanmol
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: isolating the e2e test infrastructure from cloud resources by introducing a local API stack with Postgres and wrangler dev.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/local-api-e2e

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/e2e-tests.yml (1)

77-77: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Pin third-party actions to full commit SHAs.

All third-party actions should be pinned to a commit SHA rather than a mutable tag to prevent supply-chain attacks. Affected actions include:

Action Lines
actions/checkout@v6 77, 427
maxim-lobanov/setup-xcode@v1 80
oven-sh/setup-bun@v2 85, 430
actions/cache@v4 90, 214, 224, 260, 434, 556, 566, 606, 629
ikalnytskyi/action-setup-postgres@v6 105, 450
expo/expo-github-action@v8 203, 544
actions/setup-java@v5 209, 550
actions/upload-artifact@v7 360, 368, 376, 678, 686, 694
reactivecircus/android-emulator-runner@v2.37.0 617, 646

Example fix for checkout:

-        uses: actions/checkout@v6
+        uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

As per coding guidelines: "Pin third-party actions to a full commit SHA (not a mutable tag) to prevent supply-chain attacks."

Also applies to: 80-80, 85-85, 90-90, 105-105, 203-203, 209-209, 360-360, 427-427, 450-450, 544-544, 617-617, 646-646

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/e2e-tests.yml at line 77, Replace all third-party GitHub
Actions that use mutable tags with their corresponding full commit SHAs: for
each occurrence of actions/checkout@v6, maxim-lobanov/setup-xcode@v1,
oven-sh/setup-bun@v2, actions/cache@v4, ikalnytskyi/action-setup-postgres@v6,
expo/expo-github-action@v8, actions/setup-java@v5, actions/upload-artifact@v7,
and reactivecircus/android-emulator-runner@v2.37.0 in the workflow, look up the
latest trusted commit SHA on the action's repository and replace the tag (e.g.,
actions/checkout@v6) with the full commit reference (e.g.,
actions/checkout@<full-sha>); ensure you update every instance listed (including
the repeats at the lines referenced in the review) so no mutable tags remain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/e2e-tests.yml:
- Around line 510-515: The inline env assignments before the bun command in the
"Seed E2E test user" step expose the workflow to template injection; change this
step to use a proper GitHub Actions env block instead of prefixing the run line
with environment variable assignments—move NEON_DATABASE_URL, E2E_TEST_EMAIL,
and E2E_TEST_PASSWORD into an env: map for the step and keep the run command as
bun run --filter `@packrat/api` db:seed:e2e-user so the step name "Seed E2E test
user" and the bun invocation remain but variables are passed safely via the env
block.
- Around line 166-169: The Run DB migrations step sets NEON_DATABASE_URL inline;
change it to use the step-level env block for consistency by removing the inline
prefix and adding an env: NEON_DATABASE_URL: ${{ env.E2E_DB_URL }} entry for
that job step (the step that runs `bun run --filter `@packrat/api` db:migrate`) so
the migration command runs unchanged but the variable is provided via the env
block.
- Around line 171-176: The workflow step "Seed E2E test user" currently injects
secrets via inline shell expansion (${ { env.TEST_EMAIL }}, ${ {
env.TEST_PASSWORD }}) which can be interpreted by the shell; instead, move
NEON_DATABASE_URL, E2E_TEST_EMAIL and E2E_TEST_PASSWORD into the step's env:
block and reference them from the command without inline template expansion so
the bun command (bun run --filter `@packrat/api` db:seed:e2e-user) receives the
values only through environment variables; update the step to stop using ${...}
inside the run script and populate env: E2E_TEST_EMAIL: ${{ env.TEST_EMAIL }},
E2E_TEST_PASSWORD: ${{ env.TEST_PASSWORD }}, NEON_DATABASE_URL: ${{
env.E2E_DB_URL }}.

In `@packages/api/docker-compose.e2e.yml`:
- Around line 8-10: The docker-compose is exposing Postgres with
POSTGRES_HOST_AUTH_METHOD: trust and a host-mapped port "5435:5432"; remove the
insecure trust auth and require a password by deleting or replacing
POSTGRES_HOST_AUTH_METHOD with POSTGRES_PASSWORD (or set proper auth), and scope
the port binding to loopback by changing the ports mapping to bind to 127.0.0.1
(i.e., use the host bind "127.0.0.1:5435:5432" instead of "5435:5432") so the DB
is not reachable from external interfaces; update any test/start scripts to use
the new password env if needed.

In `@packages/api/package.json`:
- Around line 28-31: The package.json scripts block is out of the expected key
order which is failing CI; open packages/api/package.json and run the project's
JSON formatter/linter (or run the same command used by CI, e.g., npm run format
or prettier --write) so the scripts keys (including "dev:e2e", "dev:e2e:init",
"dev:e2e:stop", "dev:e2e:reset") are serialized into the canonical order; commit
the formatted package.json so the CI ordering check passes.

In `@packages/api/README.e2e-local.md`:
- Around line 30-35: The README has unlabeled fenced code blocks (e.g., the
ASCII diagram starting with "iOS Simulator ──────► localhost:8787 (wrangler
dev)" and the subsequent command/code block) triggering MD040; add explicit
fenced code block languages (for example ```text or ```bash) to those blocks so
markdownlint no longer warns, keeping the existing block content identical and
only adding the language token after the opening backticks.

In `@packages/api/scripts/e2e-local-init.sh`:
- Around line 44-53: The current while-read replacement only updates existing
keys and omits them if missing; modify the script around the while loop that
reads from "$SOURCE" to track presence of each target key (NEON_DATABASE_URL,
NEON_DATABASE_URL_READONLY, EXPO_PUBLIC_API_URL, BETTER_AUTH_URL) by setting
simple flags inside the case branches, continue writing transformed lines to
"$OUT" as now, and after the loop append any missing key lines to "$OUT" (using
${E2E_DB_URL} for the neon entries and http://localhost:8787 for the URLs) so
the generated .dev.vars.e2e always contains those overrides even when they were
absent in the source.

In `@packages/api/scripts/e2e-local-start.sh`:
- Around line 59-60: The command substitutions assigning E2E_EMAIL and E2E_PASS
can cause the script to exit under set -euo pipefail if grep returns non-zero
when keys are missing; update the assignments for E2E_EMAIL and E2E_PASS to
tolerate missing keys by making the grep pipeline non-fatal (for example, append
a fallback like "|| true" to the grep/substitution or use a safe extractor that
returns empty on no-match) so the parameter expansion
E2E_TEST_EMAIL/E2E_TEST_PASSWORD default values will still be applied; adjust
the lines that reference E2E_VARS and the variables E2E_EMAIL and E2E_PASS
accordingly to ensure the command substitution never returns a non-zero exit
status.

---

Outside diff comments:
In @.github/workflows/e2e-tests.yml:
- Line 77: Replace all third-party GitHub Actions that use mutable tags with
their corresponding full commit SHAs: for each occurrence of
actions/checkout@v6, maxim-lobanov/setup-xcode@v1, oven-sh/setup-bun@v2,
actions/cache@v4, ikalnytskyi/action-setup-postgres@v6,
expo/expo-github-action@v8, actions/setup-java@v5, actions/upload-artifact@v7,
and reactivecircus/android-emulator-runner@v2.37.0 in the workflow, look up the
latest trusted commit SHA on the action's repository and replace the tag (e.g.,
actions/checkout@v6) with the full commit reference (e.g.,
actions/checkout@<full-sha>); ensure you update every instance listed (including
the repeats at the lines referenced in the review) so no mutable tags remain.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e7a7a3ac-98b5-4705-b9a2-fae1b9c85dd9

📥 Commits

Reviewing files that changed from the base of the PR and between 80b5da8 and b85ecd2.

📒 Files selected for processing (9)
  • .github/workflows/e2e-tests.yml
  • packages/api/.dev.vars.e2e.example
  • packages/api/.gitignore
  • packages/api/README.e2e-local.md
  • packages/api/docker-compose.e2e.yml
  • packages/api/package.json
  • packages/api/scripts/e2e-local-init.sh
  • packages/api/scripts/e2e-local-start.sh
  • packages/api/scripts/e2e-local-stop.sh

Comment thread .github/workflows/e2e-tests.yml Outdated
Comment thread .github/workflows/e2e-tests.yml Outdated
Comment thread .github/workflows/e2e-tests.yml Outdated
Comment thread packages/api/docker-compose.e2e.yml Outdated
Comment thread packages/api/package.json
Comment thread packages/api/README.e2e-local.md Outdated
Comment on lines +44 to +53
while IFS= read -r line || [[ -n "$line" ]]; do
case "$line" in
NEON_DATABASE_URL=*) echo "NEON_DATABASE_URL=${E2E_DB_URL}" ;;
NEON_DATABASE_URL_READONLY=*) echo "NEON_DATABASE_URL_READONLY=${E2E_DB_URL}" ;;
EXPO_PUBLIC_API_URL=*) echo "EXPO_PUBLIC_API_URL=http://localhost:8787" ;;
BETTER_AUTH_URL=*) echo "BETTER_AUTH_URL=http://localhost:8787" ;;
*) echo "$line" ;;
esac
done < "$SOURCE" > "$OUT"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Ensure required local override keys are always present.

On Line 44–53, replacements only happen when keys already exist in the source file. If a key is missing, .dev.vars.e2e may be generated without required local overrides and point to non-local services.

Proposed fix
 done < "$SOURCE" > "$OUT"
 
+# Ensure required overrides exist even if missing in source.
+grep -q '^NEON_DATABASE_URL=' "$OUT" || echo "NEON_DATABASE_URL=${E2E_DB_URL}" >> "$OUT"
+grep -q '^NEON_DATABASE_URL_READONLY=' "$OUT" || echo "NEON_DATABASE_URL_READONLY=${E2E_DB_URL}" >> "$OUT"
+grep -q '^EXPO_PUBLIC_API_URL=' "$OUT" || echo "EXPO_PUBLIC_API_URL=http://localhost:8787" >> "$OUT"
+grep -q '^BETTER_AUTH_URL=' "$OUT" || echo "BETTER_AUTH_URL=http://localhost:8787" >> "$OUT"
+
 # Append e2e credentials if not already present.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
while IFS= read -r line || [[ -n "$line" ]]; do
case "$line" in
NEON_DATABASE_URL=*) echo "NEON_DATABASE_URL=${E2E_DB_URL}" ;;
NEON_DATABASE_URL_READONLY=*) echo "NEON_DATABASE_URL_READONLY=${E2E_DB_URL}" ;;
EXPO_PUBLIC_API_URL=*) echo "EXPO_PUBLIC_API_URL=http://localhost:8787" ;;
BETTER_AUTH_URL=*) echo "BETTER_AUTH_URL=http://localhost:8787" ;;
*) echo "$line" ;;
esac
done < "$SOURCE" > "$OUT"
while IFS= read -r line || [[ -n "$line" ]]; do
case "$line" in
NEON_DATABASE_URL=*) echo "NEON_DATABASE_URL=${E2E_DB_URL}" ;;
NEON_DATABASE_URL_READONLY=*) echo "NEON_DATABASE_URL_READONLY=${E2E_DB_URL}" ;;
EXPO_PUBLIC_API_URL=*) echo "EXPO_PUBLIC_API_URL=http://localhost:8787" ;;
BETTER_AUTH_URL=*) echo "BETTER_AUTH_URL=http://localhost:8787" ;;
*) echo "$line" ;;
esac
done < "$SOURCE" > "$OUT"
# Ensure required overrides exist even if missing in source.
grep -q '^NEON_DATABASE_URL=' "$OUT" || echo "NEON_DATABASE_URL=${E2E_DB_URL}" >> "$OUT"
grep -q '^NEON_DATABASE_URL_READONLY=' "$OUT" || echo "NEON_DATABASE_URL_READONLY=${E2E_DB_URL}" >> "$OUT"
grep -q '^EXPO_PUBLIC_API_URL=' "$OUT" || echo "EXPO_PUBLIC_API_URL=http://localhost:8787" >> "$OUT"
grep -q '^BETTER_AUTH_URL=' "$OUT" || echo "BETTER_AUTH_URL=http://localhost:8787" >> "$OUT"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/api/scripts/e2e-local-init.sh` around lines 44 - 53, The current
while-read replacement only updates existing keys and omits them if missing;
modify the script around the while loop that reads from "$SOURCE" to track
presence of each target key (NEON_DATABASE_URL, NEON_DATABASE_URL_READONLY,
EXPO_PUBLIC_API_URL, BETTER_AUTH_URL) by setting simple flags inside the case
branches, continue writing transformed lines to "$OUT" as now, and after the
loop append any missing key lines to "$OUT" (using ${E2E_DB_URL} for the neon
entries and http://localhost:8787 for the URLs) so the generated .dev.vars.e2e
always contains those overrides even when they were absent in the source.

Comment thread packages/api/scripts/e2e-local-start.sh Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the Maestro E2E workflow and API tooling to run against a fully isolated local stack (local Postgres + wrangler dev) per CI run, eliminating reliance on a shared Neon dev DB and improving test isolation/reproducibility.

Changes:

  • Updated .github/workflows/e2e-tests.yml to provision local Postgres, run migrations + seed, start wrangler dev, and point Expo builds at the local API.
  • Added packages/api local E2E scripts/docs (e2e-local-init/start/stop, README) plus an e2e-focused Postgres compose file.
  • Added API package scripts and gitignore entries to support local Maestro runs with .dev.vars.e2e.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
.github/workflows/e2e-tests.yml Reworks CI E2E jobs to run against per-run local Postgres + wrangler dev, with caching and debug artifacts.
packages/api/docker-compose.e2e.yml Adds a dedicated local Postgres service for Maestro E2E runs (non-default port).
packages/api/scripts/e2e-local-init.sh Generates .dev.vars.e2e by copying base vars and overriding DB/API URLs.
packages/api/scripts/e2e-local-start.sh Starts local Postgres, runs migrations, seeds E2E user, and launches wrangler dev.
packages/api/scripts/e2e-local-stop.sh Stops/removes the E2E docker-compose stack, optionally wiping volumes.
packages/api/package.json Adds dev:e2e* scripts to drive the local E2E workflow.
packages/api/README.e2e-local.md Documents how to run Maestro E2E locally with the new stack.
packages/api/.dev.vars.e2e.example Provides a template for .dev.vars.e2e without real secrets.
packages/api/.gitignore Ignores generated .dev.vars.e2e.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/api/scripts/e2e-local-start.sh Outdated
Comment on lines +59 to +60
E2E_EMAIL="${E2E_TEST_EMAIL:-$(grep '^E2E_TEST_EMAIL=' "$E2E_VARS" | cut -d= -f2-)}"
E2E_PASS="${E2E_TEST_PASSWORD:-$(grep '^E2E_TEST_PASSWORD=' "$E2E_VARS" | cut -d= -f2-)}"
Comment thread packages/api/docker-compose.e2e.yml Outdated
POSTGRES_DB: packrat_e2e
POSTGRES_USER: e2e_user
POSTGRES_PASSWORD: e2e_pass
POSTGRES_HOST_AUTH_METHOD: trust
Comment on lines 49 to +55
name: Verify E2E secrets are available
env:
E2E_TEST_EMAIL: ${{ secrets.E2E_TEST_EMAIL }}
NEON_DATABASE_URL: ${{ secrets.NEON_DEV_DATABASE_URL }}
# NEON_DEV_DATABASE_URL no longer required — both jobs run a local
# Postgres instance via ikalnytskyi/action-setup-postgres.
# E2E_BETTER_AUTH_SECRET is the only crypto secret wrangler dev needs.
E2E_BETTER_AUTH_SECRET: ${{ secrets.E2E_BETTER_AUTH_SECRET }}
mikib0 added 4 commits May 20, 2026 15:14
…al account seed

- docker-compose.e2e.yml: switch to pgvector/pgvector:pg16 so migrations that
  CREATE EXTENSION vector succeed against the local container
- src/db/index.ts: export createConnection so callers can reuse the standard-
  Postgres routing logic
- src/auth/index.ts: replace hardcoded neon() with createConnection so
  better-auth uses pg.Pool when NEON_DATABASE_URL points at localhost
- src/db/seed-e2e-user.ts: upsert a credential account row (providerId=
  'credential', accountId=email) that better-auth requires for sign-in
…atibility

pg.Pool calls setTimeout().unref() to manage idle connection cleanup.
The Cloudflare Workers runtime (miniflare) does not expose .unref() on
timer handles, causing an uncaught TypeError on every connection release
and making all auth/DB requests fail with 500.

Setting idleTimeoutMillis=0 disables the idle timer, preventing the
.unref() call. Connections remain open until explicitly released or the
pool is destroyed. Also adds a pool error handler to clear the cache on
error so subsequent requests get a fresh pool.
…2e stack

- Maestro login-flow: dismiss dev tools intro, dev-menu sheet, and
  "Error loading app" alert; handle DEVELOPMENT SERVERS picker when Metro
  URL is stale by tapping the discovered server row via METRO_HOST:METRO_PORT.
- Maestro clear-state: replace fragile point-based Metro row tap with an
  explicit text tap on "http://${METRO_HOST}:${METRO_PORT}" — avoids
  mDNS discovery unreliability after server restarts and ensures the saved
  URL is the routable LAN IP (not localhost, which resolves to the simulator).
- auth/index.ts: enforce Math.max(60, ttl) on KV put — Cloudflare KV
  rejects expirationTtl < 60, which caused sign-in to 500 when the rate
  limiter tried to store a 10-second window entry.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
packages/api/docker-compose.e2e.yml (1)

8-10: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Security issue flagged in prior review remains unaddressed.

Lines 8 and 10 still expose the Postgres service with trust authentication on all host interfaces. The past review correctly identified this as a security risk: POSTGRES_HOST_AUTH_METHOD: trust allows passwordless connections, and "5435:5432" binds to 0.0.0.0 rather than 127.0.0.1.

For local e2e testing, bind the port to loopback and rely on password authentication.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/api/docker-compose.e2e.yml` around lines 8 - 10, Replace the
insecure POSTGRES_HOST_AUTH_METHOD: trust with password-based auth and bind the
port to loopback: stop using "trust" and instead set a strong POSTGRES_PASSWORD
(or remove POSTGRES_HOST_AUTH_METHOD so Postgres requires a password) and change
the ports mapping from "5435:5432" to "127.0.0.1:5435:5432" so the container
only listens on localhost; update the docker-compose env block that contains
POSTGRES_HOST_AUTH_METHOD and the ports entry accordingly to ensure e2e tests
use password authentication and the DB is not exposed externally.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/api/src/auth/index.ts`:
- Around line 54-59: The KV write currently treats ttl=0 as falsy and omits the
expiration options; change the conditional around env.AUTH_KV.put so that you
pass the ttl option when ttl is explicitly provided (e.g., check ttl !==
undefined/null or use Number.isFinite(ttl)) instead of a truthy check, and still
apply Math.max(60, ttl) to enforce the 60s floor for env.AUTH_KV.put's
expirationTtl.

In `@packages/api/src/db/seed-e2e-user.ts`:
- Around line 108-111: The upsert conflict handler for schema.account currently
only updates password and updatedAt, which can leave an existing account row
pointing to the wrong user; in the onConflictDoUpdate call (the block using
schema.account.providerId and schema.account.accountId) add userId: user.id (or
the appropriate new user identifier variable from this seed) to the set object
so the existing account row is reassigned to the newly created user, e.g. extend
set: { password: passwordHash, updatedAt: new Date(), userId: user.id }.

---

Duplicate comments:
In `@packages/api/docker-compose.e2e.yml`:
- Around line 8-10: Replace the insecure POSTGRES_HOST_AUTH_METHOD: trust with
password-based auth and bind the port to loopback: stop using "trust" and
instead set a strong POSTGRES_PASSWORD (or remove POSTGRES_HOST_AUTH_METHOD so
Postgres requires a password) and change the ports mapping from "5435:5432" to
"127.0.0.1:5435:5432" so the container only listens on localhost; update the
docker-compose env block that contains POSTGRES_HOST_AUTH_METHOD and the ports
entry accordingly to ensure e2e tests use password authentication and the DB is
not exposed externally.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2a137d8b-9763-4a9a-86b5-066157707624

📥 Commits

Reviewing files that changed from the base of the PR and between b85ecd2 and a0d03cb.

📒 Files selected for processing (7)
  • .maestro/flows/auth/login-flow.yaml
  • .maestro/flows/setup/clear-state.yaml
  • packages/api/docker-compose.e2e.yml
  • packages/api/package.json
  • packages/api/src/auth/index.ts
  • packages/api/src/db/index.ts
  • packages/api/src/db/seed-e2e-user.ts

Comment thread packages/api/src/auth/index.ts
Comment thread packages/api/src/db/seed-e2e-user.ts
Change authCache from WeakMap<object> to Map<string> keyed by
NEON_DATABASE_URL. Miniflare creates a new env object per request so
the WeakMap never hit — every request re-initialized Better Auth and
queried the JWKS table, exhausting the pg.Pool after a handful of calls.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/api/src/auth/index.ts`:
- Around line 28-29: The cache currently stores only settled instances causing a
race where two requests both miss and build duplicate auth/DB pools; change the
logic so on a cache miss you immediately store the Promise for the
initialization into authCache using env.NEON_DATABASE_URL as the key (e.g.,
authCache.set(key, initPromise)) before awaiting it, and in the Promise chain
catch any rejection and evict the entry (authCache.delete(key)) so failed
initializations don't stay cached; apply the same pattern for the later
authCache.set usage (replace storing the resolved value after await with storing
the Promise first and evicting on error).
- Around line 24-27: The auth instance is typed as any; create a typed helper
(e.g. betterAuthHelper) that calls betterAuth(...) with the concrete config
shape so TypeScript can infer its return type, then change authCache from
Map<string, any> to Map<string, ReturnType<typeof betterAuthHelper>> and update
getAuth's signature to Promise<ReturnType<typeof betterAuthHelper>> (use
ReturnType<typeof betterAuthHelper> wherever the Auth type is needed) so the
cached instance and exported getAuth preserve full typed information.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: df0ed0dc-04aa-4113-9064-388711528535

📥 Commits

Reviewing files that changed from the base of the PR and between a0d03cb and d29f4f3.

📒 Files selected for processing (1)
  • packages/api/src/auth/index.ts

Comment thread packages/api/src/auth/index.ts Outdated
Comment on lines 24 to 27
const authCache = new Map<string, any>();

// biome-ignore lint/suspicious/noExplicitAny: Better Auth instance type is plugin-specific and can't be expressed at declaration time without duplicating the full config signature
export async function getAuth(env: ValidatedEnv): Promise<any> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the auth/index.ts file to see the current state
cat -n packages/api/src/auth/index.ts

Repository: PackRat-AI/PackRat

Length of output: 7491


🏁 Script executed:

# Find all usages of getAuth
rg 'getAuth\s*\(' packages/ --type ts --type tsx -B 2 -A 2

Repository: PackRat-AI/PackRat

Length of output: 90


🏁 Script executed:

# Find all usages of getAuth
rg 'getAuth\s*\(' packages/ -A 2 -B 2

Repository: PackRat-AI/PackRat

Length of output: 2918


🏁 Script executed:

# Let's examine how the auth object is used in the middleware and index files
cat -n packages/api/src/middleware/auth.ts | head -50

Repository: PackRat-AI/PackRat

Length of output: 2264


🏁 Script executed:

# Let's check the index.ts to see how auth is used there
cat -n packages/api/src/index.ts | head -80

Repository: PackRat-AI/PackRat

Length of output: 3591


🏁 Script executed:

# Let's see the rest of index.ts where getAuth is called
cat -n packages/api/src/index.ts | sed -n '80,150p'

Repository: PackRat-AI/PackRat

Length of output: 2381


🏁 Script executed:

# Let's check the better-auth package to understand its type capabilities
# First, let's see if there's a way to better type the return value
rg 'betterAuth' packages/api/src/auth/index.ts -A 5 | head -20

Repository: PackRat-AI/PackRat

Length of output: 1104


🏁 Script executed:

# Let's check if there are any type definitions or existing patterns in the codebase
# for handling better-auth types
rg 'ReturnType.*betterAuth' packages/

Repository: PackRat-AI/PackRat

Length of output: 264


🏁 Script executed:

# Let's look at the better-auth package version to understand type capabilities
cat -n packages/api/package.json | grep -A 5 -B 5 better-auth

Repository: PackRat-AI/PackRat

Length of output: 789


🏁 Script executed:

# Check the catalog/pnpm-workspace version
cat -n pnpm-workspace.yaml | head -50

Repository: PackRat-AI/PackRat

Length of output: 114


🏁 Script executed:

# Find the version catalog
find . -name ".npmrc" -o -name "pnpm-workspace.yaml" -o -name "package.json" -path "./pnpm-workspace*" 2>/dev/null | head -5
ls -la | grep -E "(pnpm|package)"

Repository: PackRat-AI/PackRat

Length of output: 188


🏁 Script executed:

# Let's check how auth instance is actually used to understand what methods need to be typed
rg 'auth\.' packages/api/src/middleware/auth.ts packages/api/src/index.ts -B 1 -A 1 | head -40

Repository: PackRat-AI/PackRat

Length of output: 822


Extract betterAuth() into a typed helper to preserve type safety across the auth instance.

The current Promise<any> return type violates the TypeScript guidelines and causes the exported Auth type to be useless. Since the betterAuth config structure is fixed at code time (only the VALUES vary by env), you can extract the call into a helper function that returns a properly typed instance, then use ReturnType<typeof betterAuthHelper> for both the cache and the getAuth return type.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/api/src/auth/index.ts` around lines 24 - 27, The auth instance is
typed as any; create a typed helper (e.g. betterAuthHelper) that calls
betterAuth(...) with the concrete config shape so TypeScript can infer its
return type, then change authCache from Map<string, any> to Map<string,
ReturnType<typeof betterAuthHelper>> and update getAuth's signature to
Promise<ReturnType<typeof betterAuthHelper>> (use ReturnType<typeof
betterAuthHelper> wherever the Auth type is needed) so the cached instance and
exported getAuth preserve full typed information.

Comment thread packages/api/src/auth/index.ts Outdated
Android: replace action-setup-postgres step with a job-level Docker
service using pgvector/pgvector:pg16, which ships PostgreSQL 16 + the
vector extension pre-installed. This removes the "extension not
available" error on CREATE EXTENSION vector.

iOS: replace action-setup-postgres@v6 (exit-127 on macos-15-arm64,
likely broken pg_ctl PATH with keg-only postgresql@14) with an explicit
brew-based step: installs postgresql@16 + pgvector, runs initdb/pg_ctl
with full binary paths, and creates the e2e role/database directly.
@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for API Unit Tests Coverage (./packages/api)

Status Category Percentage Covered / Total
🔵 Lines 98.37% (🎯 95%) 726 / 738
🔵 Statements 98.37% (🎯 95%) 726 / 738
🔵 Functions 100% (🎯 97%) 43 / 43
🔵 Branches 95.43% (🎯 92%) 293 / 307
File CoverageNo changed files found.
Generated in workflow #1442 for commit 0af518e by the Vitest Coverage Report Action

@github-actions

github-actions Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Coverage Report for Expo Unit Tests Coverage (./apps/expo)

Status Category Percentage Covered / Total
🔵 Lines 97.56% (🎯 95%) 560 / 574
🔵 Statements 97.56% (🎯 95%) 560 / 574
🔵 Functions 100% (🎯 97%) 51 / 51
🔵 Branches 95% (🎯 92%) 190 / 200
File CoverageNo changed files found.
Generated in workflow #1442 for commit 0af518e by the Vitest Coverage Report Action

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/e2e-tests.yml (1)

77-77: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin third-party actions to a full commit SHA.

Every third-party (and even first-party) action in this workflow is referenced by a mutable tag (e.g. actions/checkout@v6, maxim-lobanov/setup-xcode@v1, oven-sh/setup-bun@v2, actions/cache@v4, expo/expo-github-action@v8, actions/setup-java@v5, actions/upload-artifact@v7, reactivecircus/android-emulator-runner@v2.37.0). A compromised maintainer or repo can re-point any of these tags to malicious code and instantly run inside a job that already has access to EXPO_TOKEN, E2E_BETTER_AUTH_SECRET, PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN, etc.

Pin every uses: to a full 40-char commit SHA and keep the version as a trailing comment, e.g.:

-      - uses: actions/checkout@v6
+      - uses: actions/checkout@<full-sha>  # v6.x.x

Apply consistently to all uses: lines in this file.

As per coding guidelines: "Pin third-party actions to a full commit SHA (not a mutable tag) to prevent supply-chain attacks."

Also applies to: 80-80, 85-85, 218-218, 224-224, 277-277, 375-375, 383-383, 391-391, 457-457, 460-460, 465-465, 568-568, 574-574, 580-580, 591-591, 640-640, 654-654, 669-669, 701-701, 709-709, 717-717

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/e2e-tests.yml at line 77, Replace every mutable action ref
(e.g. actions/checkout@v6, maxim-lobanov/setup-xcode@v1, oven-sh/setup-bun@v2,
actions/cache@v4, expo/expo-github-action@v8, actions/setup-java@v5,
actions/upload-artifact@v7, reactivecircus/android-emulator-runner@v2.37.0,
etc.) with the full 40-character commit SHA for that tag while preserving the
current tag/version as a trailing comment; update each uses: line listed in the
comment (and all other uses: entries in this workflow) so they point to an
immutable SHA to prevent supply-chain risks and keep the human-readable version
next to it for maintenance.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/e2e-tests.yml:
- Around line 418-431: The postgres service image uses a mutable tag
"pgvector/pgvector:pg16"; replace it with a pinned digest by resolving the image
digest (e.g., run `docker buildx imagetools inspect pgvector/pgvector:pg16` to
get the sha256) and update the value of services.postgres.image to
"pgvector/pgvector@sha256:<digest>" so CI uses an immutable image; ensure you
replace the tag form in the services.postgres.image field and commit the updated
YAML.

---

Outside diff comments:
In @.github/workflows/e2e-tests.yml:
- Line 77: Replace every mutable action ref (e.g. actions/checkout@v6,
maxim-lobanov/setup-xcode@v1, oven-sh/setup-bun@v2, actions/cache@v4,
expo/expo-github-action@v8, actions/setup-java@v5, actions/upload-artifact@v7,
reactivecircus/android-emulator-runner@v2.37.0, etc.) with the full 40-character
commit SHA for that tag while preserving the current tag/version as a trailing
comment; update each uses: line listed in the comment (and all other uses:
entries in this workflow) so they point to an immutable SHA to prevent
supply-chain risks and keep the human-readable version next to it for
maintenance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b10e36e2-a149-44df-a4be-1d24a5f6f9ec

📥 Commits

Reviewing files that changed from the base of the PR and between d29f4f3 and 04aa314.

📒 Files selected for processing (1)
  • .github/workflows/e2e-tests.yml

Comment on lines +418 to +431
services:
postgres:
image: pgvector/pgvector:pg16
env:
POSTGRES_USER: e2e_user
POSTGRES_PASSWORD: e2e_pass
POSTGRES_DB: packrat_e2e
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin the Postgres service container image to a SHA256 digest.

pgvector/pgvector:pg16 is a mutable tag — any push to that tag (compromise or accidental) silently changes the DB binary executed in CI. The same supply-chain principle that applies to action pins applies to images used in services:.

Suggested fix
     services:
       postgres:
-        image: pgvector/pgvector:pg16
+        image: pgvector/pgvector:pg16@sha256:<digest>  # pg16 + pgvector

Resolve the digest with docker buildx imagetools inspect pgvector/pgvector:pg16.

As per coding guidelines: "Pin third-party actions to a full commit SHA (not a mutable tag) to prevent supply-chain attacks." (same intent applies to container images, also flagged by zizmor unpinned-images).

🧰 Tools
🪛 zizmor (1.25.2)

[error] 420-420: unpinned image references (unpinned-images): container image is not pinned to a SHA256 hash

(unpinned-images)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/e2e-tests.yml around lines 418 - 431, The postgres service
image uses a mutable tag "pgvector/pgvector:pg16"; replace it with a pinned
digest by resolving the image digest (e.g., run `docker buildx imagetools
inspect pgvector/pgvector:pg16` to get the sha256) and update the value of
services.postgres.image to "pgvector/pgvector@sha256:<digest>" so CI uses an
immutable image; ensure you replace the tag form in the services.postgres.image
field and commit the updated YAML.

Two bugs prevented the local API stack steps from passing:

1. wrangler not in PATH — both jobs called `wrangler dev` directly, but
   after `bun install` the binary lives in `node_modules/.bin` which
   GitHub Actions does not add to PATH automatically. Fix: new step
   `Add node_modules bin to PATH` appends $GITHUB_WORKSPACE/node_modules/.bin
   to GITHUB_PATH immediately after `bun install` in both jobs.

2. pgvector not available for postgresql@16 on macOS — `brew install pgvector`
   targets whichever PostgreSQL version its formula depends on (typically the
   latest), which may not be @16. When migrations run `CREATE EXTENSION vector`
   the extension was not found. Fix: replace `brew install pgvector` with an
   explicit source build using postgresql@16's own pg_config so the extension
   files land in the right place. Also adds a safety call to stop any
   pre-installed postgres service before starting ours on port 5432, and
   replaces the silent pg_isready loop with one that exits loudly on timeout.

https://claude.ai/code/session_018z2eBXirwsi5dujqezRk7Y
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Deploying packrat-guides with  Cloudflare Pages  Cloudflare Pages

Latest commit: 59d4951
Status: ✅  Deploy successful!
Preview URL: https://343fa4b6.packrat-guides-6gq.pages.dev
Branch Preview URL: https://claude-fix-e2e-api-isolation.packrat-guides-6gq.pages.dev

View logs

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented May 21, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
packrat-admin 59d4951 Commit Preview URL

Branch Preview URL
May 21 2026, 02:49 PM

@cloudflare-workers-and-pages

Copy link
Copy Markdown
Contributor

Deploying packrat-landing with  Cloudflare Pages  Cloudflare Pages

Latest commit: 59d4951
Status:🚫  Build failed.

View logs

…v.dev gap

Wrangler 4.x was attempting a remote proxy session (failing with "must be
logged in") because --local was never passed.  Add --local to both the iOS
and Android wrangler dev invocations so CI never needs Cloudflare auth.

Also copy kv_namespaces into env.dev in wrangler.jsonc: Wrangler does not
inherit top-level bindings into named environments, so AUTH_KV was missing
when running `-e dev`, causing a binding error at startup.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/e2e-tests.yml:
- Around line 114-116: The workflow only stops specific Homebrew services
(postgresql@14 and postgresql@${PG_VER}); update the stop logic to stop any
installed Homebrew Postgres service and kill any process on port 5432: enumerate
brew services matching "postgresql" and stop each (e.g., run a command that
lists brew services and stops entries containing "postgresql"), and as a
fallback kill any process listening on port 5432 (e.g., lsof -ti:5432 | xargs -r
kill -9) so the port is always freed before migrations run.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3be6016b-cb9a-498f-aecb-bb943584e936

📥 Commits

Reviewing files that changed from the base of the PR and between 04aa314 and 02a25aa.

📒 Files selected for processing (4)
  • .github/workflows/e2e-tests.yml
  • .maestro/flows/auth/login-flow.yaml
  • .maestro/flows/packs/create-pack-flow.yaml
  • packages/api/wrangler.jsonc

Comment thread .github/workflows/e2e-tests.yml Outdated
… on Android

iOS: e2e_user was created without SUPERUSER so `CREATE EXTENSION vector`
in the migration was denied. Grant SUPERUSER at user-creation time.

Android: wrangler 4.x requires Node.js >= 22 but ubuntu-latest ships v20.
Add setup-node@v4 with node-version: '22' before wrangler dev is invoked.
mikib0 added 4 commits May 21, 2026 19:41
…iner support

macOS-15 runners don't ship Docker. Colima provides a lightweight Docker-
compatible daemon. Ubuntu runners already have Docker so the Android job
needed no change — just removed the --enable-containers=false flag both jobs
had from the previous (wrong) fix.
Apple Virtualization.framework (VZ) requires the com.apple.security.hypervisor
entitlement which the GitHub Actions macOS-15 sandbox doesn't grant. Switching
to --vm-type=qemu (which uses software emulation) avoids this restriction.
…isor access

Both VZ (Virtualization.framework) and QEMU/HVF require entitlements that the
GitHub Actions macOS-15 sandbox does not grant. Neither Colima backend can boot
a VM, so Docker is unavailable on macos runners.

The Maestro flows test login + pack creation, not the AppContainer (TikTok
scraper) endpoint. The Android job on ubuntu-latest retains full container
support since Docker is natively available there.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 11 comments.

Comments suppressed due to low confidence (1)

.github/workflows/e2e-tests.yml:753

  • These artifact uploads run on failure() even if wrangler dev never started (e.g., migrations fail earlier), in which case /tmp/wrangler-dev.log may not exist and the artifact step can fail and hide the original error. Set if-no-files-found: ignore for the log artifact uploads.
      - name: Upload wrangler dev log on failure
        if: failure()
        uses: actions/upload-artifact@v7
        with:
          name: android-wrangler-dev-log
          path: /tmp/wrangler-dev.log
          retention-days: 7

Comment on lines 19 to 29
// ─── Per-isolate auth instance cache ─────────────────────────────────────────
// Keyed by NEON_DATABASE_URL so the same instance is reused across requests
// within the same isolate — miniflare creates a new env object per request,
// so a WeakMap would never hit and every request would re-initialize auth.
// biome-ignore lint/suspicious/noExplicitAny: Better Auth's generic type parameter is too specific to the exact plugin set — can't use ReturnType<typeof betterAuth> here
const authCache = new WeakMap<object, any>();
const authCache = new Map<string, any>();

// biome-ignore lint/suspicious/noExplicitAny: Better Auth instance type is plugin-specific and can't be expressed at declaration time without duplicating the full config signature
export async function getAuth(env: ValidatedEnv): Promise<any> {
const cached = authCache.get(env as object);
const cached = authCache.get(env.NEON_DATABASE_URL);
if (cached) return cached;
Comment thread packages/api/src/db/index.ts Outdated
Comment on lines +38 to +39
pool.on('error', () => {
pgPools.delete(url);
Comment thread packages/api/src/db/seed-e2e-user.ts Outdated
})
.onConflictDoUpdate({
target: [schema.account.providerId, schema.account.accountId],
set: { password: passwordHash, updatedAt: new Date() },
Comment thread packages/api/docker-compose.e2e.yml Outdated
POSTGRES_PASSWORD: e2e_pass
POSTGRES_HOST_AUTH_METHOD: trust
ports:
- "5435:5432"
commands:
- waitForAnimationToEnd
- tapOn:
text: "http://${METRO_HOST}:${METRO_PORT}"
Comment on lines 49 to 62
name: Verify E2E secrets are available
env:
E2E_TEST_EMAIL: ${{ secrets.E2E_TEST_EMAIL }}
NEON_DATABASE_URL: ${{ secrets.NEON_DEV_DATABASE_URL }}
# NEON_DEV_DATABASE_URL no longer required — both jobs run a local
# Postgres instance via ikalnytskyi/action-setup-postgres.
# E2E_BETTER_AUTH_SECRET is the only crypto secret wrangler dev needs.
E2E_BETTER_AUTH_SECRET: ${{ secrets.E2E_BETTER_AUTH_SECRET }}
run: |
if [ -n "$E2E_TEST_EMAIL" ] && [ -n "$NEON_DATABASE_URL" ]; then
if [ -n "$E2E_TEST_EMAIL" ] && [ -n "$E2E_BETTER_AUTH_SECRET" ]; then
echo "ready=true" >> "$GITHUB_OUTPUT"
else
echo "ready=false" >> "$GITHUB_OUTPUT"
echo "::notice::E2E secrets not configured — skipping E2E tests"
echo "::notice::E2E secrets not configured — skipping E2E tests (need E2E_TEST_EMAIL + E2E_BETTER_AUTH_SECRET)"
fi
make -C /tmp/pgvector-build install PG_CONFIG="$PG_BIN/pg_config"

"$PG_BIN/initdb" -D "$PG_DATA" --encoding=UTF8 --auth=trust
printf '\nport = 5432\nlisten_addresses = '"'"'127.0.0.1'"'"'\n' \
Comment thread .github/workflows/e2e-tests.yml Outdated
# getApiUrl() in apps/expo swaps 'localhost' -> '10.0.2.2' at runtime.
WRANGLER_LOG=warn \
WRANGLER_SEND_METRICS=false \
wrangler dev -e dev --ip 0.0.0.0 --local \
uses: actions/upload-artifact@v7
with:
name: ios-wrangler-dev-log
path: /tmp/wrangler-dev.log
Comment on lines +107 to +126
- name: Start local Postgres with pgvector
run: |
PG_VER=16
BREW_PREFIX="$(brew --prefix)"
PG_BIN="${BREW_PREFIX}/opt/postgresql@${PG_VER}/bin"
PG_DATA="$HOME/pgdata-e2e"

# Free port 5432 in case a pre-installed postgres is running
brew services stop postgresql@14 2>/dev/null || true
brew services stop "postgresql@${PG_VER}" 2>/dev/null || true

brew install "postgresql@${PG_VER}"

# Build pgvector from source against postgresql@16's pg_config.
# Brew's pgvector formula targets whichever PG version its formula
# depends on (may not be @16), so we compile directly to be certain.
git clone --depth 1 https://github.com/pgvector/pgvector.git /tmp/pgvector-build
make -C /tmp/pgvector-build PG_CONFIG="$PG_BIN/pg_config"
make -C /tmp/pgvector-build install PG_CONFIG="$PG_BIN/pg_config"

Workflow (.github/workflows/e2e-tests.yml):
- e2e-gate now checks E2E_TEST_PASSWORD in addition to EMAIL + AUTH_SECRET
  so the gate skips rather than fails when the password secret is missing
- Stop all brew Postgres services generically (not just @14/@16) to reliably
  free port 5432 regardless of formula version on the runner
- Use 127.0.0.1 in E2E_DB_URL and .dev.vars heredocs — `localhost` can
  resolve to ::1 (IPv6) when Postgres only listens on 127.0.0.1 (IPv4)
- Move NEON_DATABASE_URL / E2E_TEST_* from inline shell to env: blocks on
  both migration and seed steps (avoids injection from shell metacharacters)
- Add --enable-containers=false to Android wrangler dev (consistent with iOS;
  prevents Miniflare from hanging trying to start containers it can't reach)
- Add if-no-files-found: ignore on both wrangler-dev-log artifact uploads so
  a Postgres/migration failure earlier in the job doesn't hide the real error

packages/api/src/auth/index.ts:
- Cache the in-flight Promise (not just the settled instance) so concurrent
  requests don't each spin up a redundant Better Auth build; evict on rejection
- Composite cache key (NEON_DATABASE_URL|BETTER_AUTH_URL) to correctly
  distinguish env configurations that share a DB URL
- Fix ttl === 0 being treated as "no TTL" due to truthy check; use !== undefined

packages/api/src/db/index.ts:
- Call newPool.end() in the pool error handler before removing it from the
  cache to avoid leaking connections and sockets after an idle/fatal error

packages/api/src/db/seed-e2e-user.ts:
- Include userId in the account upsert conflict set so a stale credential row
  pointing at an old user ID is corrected on re-seed

packages/api/docker-compose.e2e.yml:
- Remove POSTGRES_HOST_AUTH_METHOD: trust (unnecessary with PASSWORD set)
- Bind published port to 127.0.0.1 to avoid unintended LAN exposure

packages/api/scripts/e2e-local-start.sh:
- Guard grep with 2>/dev/null || true so set -euo pipefail doesn't abort the
  script when keys are absent; fallback defaults remain reachable

packages/api/README.e2e-local.md:
- Add language identifiers to unlabelled fenced code blocks (MD040)
@mikib0 mikib0 merged commit 6676446 into development May 21, 2026
11 of 13 checks passed
@mikib0 mikib0 deleted the feat/local-api-e2e branch May 21, 2026 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api ci/cd database dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants