feat(ci): isolated local API stack for Maestro e2e (local Postgres + wrangler dev)#2460
Conversation
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
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughSwitches mobile E2E to a local API stack: starts a Docker Postgres, generates ChangesLocal E2E Testing Infrastructure
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 liftPin 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@v677, 427 maxim-lobanov/setup-xcode@v180 oven-sh/setup-bun@v285, 430 actions/cache@v490, 214, 224, 260, 434, 556, 566, 606, 629 ikalnytskyi/action-setup-postgres@v6105, 450 expo/expo-github-action@v8203, 544 actions/setup-java@v5209, 550 actions/upload-artifact@v7360, 368, 376, 678, 686, 694 reactivecircus/android-emulator-runner@v2.37.0617, 646 Example fix for checkout:
- uses: actions/checkout@v6 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2As 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
📒 Files selected for processing (9)
.github/workflows/e2e-tests.ymlpackages/api/.dev.vars.e2e.examplepackages/api/.gitignorepackages/api/README.e2e-local.mdpackages/api/docker-compose.e2e.ymlpackages/api/package.jsonpackages/api/scripts/e2e-local-init.shpackages/api/scripts/e2e-local-start.shpackages/api/scripts/e2e-local-stop.sh
| 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" | ||
|
|
There was a problem hiding this comment.
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.
| 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.
There was a problem hiding this comment.
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.ymlto provision local Postgres, run migrations + seed, startwrangler dev, and point Expo builds at the local API. - Added
packages/apilocal 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.
| 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-)}" |
| POSTGRES_DB: packrat_e2e | ||
| POSTGRES_USER: e2e_user | ||
| POSTGRES_PASSWORD: e2e_pass | ||
| POSTGRES_HOST_AUTH_METHOD: trust |
| 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 }} |
…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.
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
packages/api/docker-compose.e2e.yml (1)
8-10:⚠️ Potential issue | 🟠 Major | ⚡ Quick winSecurity issue flagged in prior review remains unaddressed.
Lines 8 and 10 still expose the Postgres service with
trustauthentication on all host interfaces. The past review correctly identified this as a security risk:POSTGRES_HOST_AUTH_METHOD: trustallows passwordless connections, and"5435:5432"binds to0.0.0.0rather than127.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
📒 Files selected for processing (7)
.maestro/flows/auth/login-flow.yaml.maestro/flows/setup/clear-state.yamlpackages/api/docker-compose.e2e.ymlpackages/api/package.jsonpackages/api/src/auth/index.tspackages/api/src/db/index.tspackages/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.
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
packages/api/src/auth/index.ts
| 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> { |
There was a problem hiding this comment.
🧩 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.tsRepository: 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 2Repository: PackRat-AI/PackRat
Length of output: 90
🏁 Script executed:
# Find all usages of getAuth
rg 'getAuth\s*\(' packages/ -A 2 -B 2Repository: 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 -50Repository: 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 -80Repository: 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 -20Repository: 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-authRepository: PackRat-AI/PackRat
Length of output: 789
🏁 Script executed:
# Check the catalog/pnpm-workspace version
cat -n pnpm-workspace.yaml | head -50Repository: 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 -40Repository: 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.
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.
Coverage Report for API Unit Tests Coverage (./packages/api)
File CoverageNo changed files found. |
Coverage Report for Expo Unit Tests Coverage (./apps/expo)
File CoverageNo changed files found. |
There was a problem hiding this comment.
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 winPin 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 toEXPO_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.xApply 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
📒 Files selected for processing (1)
.github/workflows/e2e-tests.yml
| 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 |
There was a problem hiding this comment.
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 + pgvectorResolve 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
Deploying packrat-guides with
|
| 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 |
Deploying with
|
| 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 |
…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.
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
.github/workflows/e2e-tests.yml.maestro/flows/auth/login-flow.yaml.maestro/flows/packs/create-pack-flow.yamlpackages/api/wrangler.jsonc
… 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.
…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.
There was a problem hiding this comment.
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 ifwrangler devnever started (e.g., migrations fail earlier), in which case/tmp/wrangler-dev.logmay not exist and the artifact step can fail and hide the original error. Setif-no-files-found: ignorefor 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
| // ─── 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; |
| pool.on('error', () => { | ||
| pgPools.delete(url); |
| }) | ||
| .onConflictDoUpdate({ | ||
| target: [schema.account.providerId, schema.account.accountId], | ||
| set: { password: passwordHash, updatedAt: new Date() }, |
| POSTGRES_PASSWORD: e2e_pass | ||
| POSTGRES_HOST_AUTH_METHOD: trust | ||
| ports: | ||
| - "5435:5432" |
| commands: | ||
| - waitForAnimationToEnd | ||
| - tapOn: | ||
| text: "http://${METRO_HOST}:${METRO_PORT}" |
| 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' \ |
| # 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 |
| - 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)
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 devrunning inside the CI runner. No cloud DB is touched during e2e runs.What changed
CI (
e2e-tests.yml)E2E_BETTER_AUTH_SECRETinstead ofNEON_DEV_DATABASE_URLikalnytskyi/action-setup-postgres(works on bothmacos-15andubuntu-latestwithout Docker)packages/api/.dev.varsfrom a mix of hardcoded local values and repo secretswrangler dev -e dev --ip 0.0.0.0in the background; poll/healthuntil readyikalnytskyi/action-setup-postgreshandles Postgres onmacos-15(no Docker needed on macOS runners)--ip 0.0.0.0onwrangler devso the emulator can reach the host via10.0.2.2;apps/expo/lib/utils/getApiUrl.tsalready rewriteslocalhost → 10.0.2.2at runtimeapps/expo/.env.localis written beforeeas build --localwithEXPO_PUBLIC_API_URL=http://localhost:8787;.env.localhas highest priority in Expo's env hierarchy and overrides the EAS "preview" environment's API URLlocal-apito keep local-API builds in a separate slot from any cached deployed-API buildswrangler devlog is uploaded as a CI artifact on failure for easier debuggingNEON_DEV_DATABASE_URL) removed from both jobspackages/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.e2eby reading the developer's existing.dev.varsand overriding only the DB and API URLsscripts/e2e-local-start.sh: starts Docker Postgres → migrates → seeds → launcheswrangler devscripts/e2e-local-stop.sh: tears down containers (--volumesto wipe data)package.json:dev:e2e,dev:e2e:init,dev:e2e:stop,dev:e2e:resetscripts.dev.vars.e2e.example: committed template (no real secrets)README.e2e-local.md: usage guide for running Maestro e2e locallyWhy no wsproxy
packages/api/src/db/index.tscreateConnection()detects a standardpostgres://localhost/...URL and routes topg.Pool(node-postgres) automatically — the Neon WebSocket driver is bypassed entirely for local connections.Secrets
E2E_BETTER_AUTH_SECRETWEATHER_API_KEYOPENWEATHER_KEYNEON_DEV_DATABASE_URLTest plan
E2E_BETTER_AUTH_SECRETto repo secrets (Settings → Secrets → Actions)workflow_dispatchand confirm both jobs reach the "Run Maestro" step*-wrangler-dev-logartifact and confirm the API started cleanlybun run --filter @packrat/api dev:e2e:init && bun run --filter @packrat/api dev:e2e, thencurl http://localhost:8787/health→{"status":"ok"}Summary by CodeRabbit
New Features
Chores
Documentation
Tests