Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: CI

on:
pull_request:
types:
- opened
- synchronize
- reopened
- ready_for_review
push:
branches:
- main
merge_group:

permissions: {}

jobs:
lint:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-lint-${{ github.event.pull_request.number || github.event.merge_group.head_ref || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false

- uses: actions/setup-node@v4
with:
node-version: 22

- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.13

- name: Install
run: bun install --frozen-lockfile

- name: Lint
run: bun lint

- name: Format
run: bun format:check

typecheck:
runs-on: ubuntu-latest
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-typecheck-${{ github.event.pull_request.number || github.event.merge_group.head_ref || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false

- uses: actions/setup-node@v4
with:
node-version: 22

- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.13

- name: Install
run: bun install --frozen-lockfile

- name: Typecheck
run: bun typecheck

unit:
runs-on: ubuntu-latest
permissions:
contents: read
timeout-minutes: 15
concurrency:
group: ${{ github.workflow }}-unit-${{ github.event.pull_request.number || github.event.merge_group.head_ref || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false

- uses: actions/setup-node@v4
with:
node-version: 22

- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.13

- name: Install
run: bun install --frozen-lockfile

- name: Unit tests
run: bun test:unit
222 changes: 214 additions & 8 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,84 @@ name: E2E

on:
workflow_dispatch:
inputs:
target:
description: E2E target to run
required: true
type: choice
default: all
options:
- all
- cli
- stripe
- polar
schedule:
- cron: "0 6 * * *"

permissions: {}

jobs:
cli:
name: CLI E2E
if: ${{ github.event_name != 'workflow_dispatch' || inputs.target == 'all' || inputs.target == 'cli' }}
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-stripe
cancel-in-progress: false
Comment on lines +26 to +28
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Concurrency group typo: CLI job is in the Stripe lane.

The cli job's concurrency group is ${{ github.workflow }}-stripe, which is identical to the stripe job's group at line 85. Combined with cancel-in-progress: false, this forces CLI and Stripe E2E to serialize even though they have independent infrastructure (the CLI job doesn't even start cloudflared). This defeats the parallel-job split and roughly doubles wall time when both run via target: all or scheduled cron.

🔧 Proposed fix
     concurrency:
-      group: ${{ github.workflow }}-stripe
+      group: ${{ github.workflow }}-cli
       cancel-in-progress: false
📝 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
concurrency:
group: ${{ github.workflow }}-stripe
cancel-in-progress: false
concurrency:
group: ${{ github.workflow }}-cli
cancel-in-progress: false
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/e2e.yml around lines 26 - 28, The concurrency group for
the CLI job is incorrectly set to the same group as the Stripe job; update the
CLI job's concurrency.group value to a distinct group (e.g., change `${{
github.workflow }}-stripe` to `${{ github.workflow }}-cli`) so the `cli` job and
`stripe` job no longer serialize; locate the CLI job definition (job name `cli`)
and modify its concurrency.group accordingly while keeping `cancel-in-progress`
behavior as intended.

timeout-minutes: 30
permissions:
contents: read
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
E2E_STRIPE_SK: ${{ secrets.E2E_STRIPE_SK }}
E2E_STRIPE_WHSEC: ${{ secrets.E2E_STRIPE_WHSEC }}
TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/postgres
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false

- uses: actions/setup-node@v4
with:
node-version: 22

- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.13

- name: Install workspace dependencies
run: bun install --frozen-lockfile

- name: Validate CLI E2E configuration
run: |
set -euo pipefail
for name in E2E_STRIPE_SK E2E_STRIPE_WHSEC TEST_DATABASE_URL; do
if [ -z "${!name:-}" ]; then
echo "::error::Missing required configuration: $name"
exit 1
fi
done

- name: Run CLI E2E suite
run: bun --filter e2e test:cli

stripe:
name: Stripe E2E
if: ${{ github.event_name != 'workflow_dispatch' || inputs.target == 'all' || inputs.target == 'stripe' }}
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-stripe
Expand All @@ -32,7 +102,6 @@ jobs:
--health-timeout 5s
--health-retries 5
env:
CF_TUNNEL_HOST: ${{ vars.CF_TUNNEL_HOST_STRIPE || 't1.paykit.sh' }}
E2E_STRIPE_SK: ${{ secrets.E2E_STRIPE_SK }}
E2E_STRIPE_WHSEC: ${{ secrets.E2E_STRIPE_WHSEC }}
TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/postgres
Expand Down Expand Up @@ -91,15 +160,32 @@ jobs:

trap cleanup EXIT

sleep 5
readiness_timeout_s=30
readiness_deadline=$((SECONDS + readiness_timeout_s))

while true; do
if ! kill -0 "$cloudflared_pid" 2>/dev/null; then
echo "::error::cloudflared exited before tests started"
if [ -f cloudflared.log ]; then
cat cloudflared.log
fi
exit 1
fi

if ! kill -0 "$cloudflared_pid" 2>/dev/null; then
echo "::error::cloudflared exited before tests started"
if [ -f cloudflared.log ]; then
cat cloudflared.log
if [ -f cloudflared.log ] && grep -Eq 'Registered tunnel|Connection [A-Za-z0-9]+ registered|INF.*Registered tunnel connection' cloudflared.log; then
break
fi
exit 1
fi

if [ "$SECONDS" -ge "$readiness_deadline" ]; then
echo "::error::Timed out waiting for cloudflared readiness"
if [ -f cloudflared.log ]; then
cat cloudflared.log
fi
exit 1
fi

sleep 0.5
done

bun --filter e2e test:stripe

Expand All @@ -110,3 +196,123 @@ jobs:
if-no-files-found: ignore
name: stripe-e2e-cloudflared-log
path: cloudflared.log

polar:
name: Polar E2E
if: ${{ github.event_name != 'workflow_dispatch' || inputs.target == 'all' || inputs.target == 'polar' }}
runs-on: ubuntu-latest
concurrency:
group: ${{ github.workflow }}-polar
cancel-in-progress: false
timeout-minutes: 60
permissions:
contents: read
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres"
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
E2E_POLAR_ACCESS_TOKEN: ${{ secrets.E2E_POLAR_ACCESS_TOKEN }}
E2E_POLAR_WHSEC: ${{ secrets.E2E_POLAR_WHSEC }}
TEST_DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/postgres
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false

- uses: actions/setup-node@v4
with:
node-version: 22

- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.13

- name: Install workspace dependencies
run: bun install --frozen-lockfile

- name: Validate Polar E2E configuration
env:
CF_TUNNEL_TOKEN: ${{ secrets.CF_TUNNEL_TOKEN_POLAR }}
run: |
set -euo pipefail
for name in CF_TUNNEL_TOKEN E2E_POLAR_ACCESS_TOKEN E2E_POLAR_WHSEC TEST_DATABASE_URL; do
if [ -z "${!name:-}" ]; then
echo "::error::Missing required configuration: $name"
exit 1
fi
done

- name: Setup cloudflared
uses: AnimMouse/setup-cloudflared@v2

- name: Check cloudflared version
run: cloudflared --version

- name: Install Playwright Chromium
run: bunx playwright install --with-deps chromium

- name: Run Polar E2E suite
env:
CF_TUNNEL_TOKEN: ${{ secrets.CF_TUNNEL_TOKEN_POLAR }}
run: |
set -euo pipefail
cloudflared tunnel --url http://127.0.0.1:4567 run --token "$CF_TUNNEL_TOKEN" > cloudflared.log 2>&1 &
cloudflared_pid=$!

cleanup() {
if kill -0 "$cloudflared_pid" 2>/dev/null; then
kill "$cloudflared_pid" || true
wait "$cloudflared_pid" || true
fi
}

trap cleanup EXIT

readiness_timeout_s=30
readiness_deadline=$((SECONDS + readiness_timeout_s))

while true; do
if ! kill -0 "$cloudflared_pid" 2>/dev/null; then
echo "::error::cloudflared exited before tests started"
if [ -f cloudflared.log ]; then
cat cloudflared.log
fi
exit 1
fi

if [ -f cloudflared.log ] && grep -Eq 'Registered tunnel|Connection [A-Za-z0-9]+ registered|INF.*Registered tunnel connection' cloudflared.log; then
break
fi

if [ "$SECONDS" -ge "$readiness_deadline" ]; then
echo "::error::Timed out waiting for cloudflared readiness"
if [ -f cloudflared.log ]; then
cat cloudflared.log
fi
exit 1
fi

sleep 0.5
done

bun --filter e2e test:polar

- name: Upload cloudflared log
if: failure()
uses: actions/upload-artifact@v4
with:
if-no-files-found: ignore
name: polar-e2e-cloudflared-log
path: cloudflared.log
20 changes: 20 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jobs:
release:
runs-on: ubuntu-latest
permissions:
actions: read
Comment thread
coderabbitai[bot] marked this conversation as resolved.
checks: read
contents: write
id-token: write
steps:
Expand All @@ -19,6 +21,24 @@ jobs:
fetch-depth: 0
persist-credentials: false

- name: Verify E2E gate
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
required_checks=("CLI E2E" "Stripe E2E" "Polar E2E")

for check_name in "${required_checks[@]}"; do
conclusion=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/check-runs?per_page=100" --jq ".check_runs | map(select(.app.slug == \"github-actions\" and .name == \"${check_name}\")) | sort_by(.completed_at) | last | .conclusion // \"missing\"")

if [ "$conclusion" != "success" ]; then
echo "::error::Required E2E check '$check_name' is '$conclusion' for commit ${GITHUB_SHA}. Run the full E2E workflow on this exact SHA before releasing."
exit 1
fi
done

echo "Found successful CLI, Stripe, and Polar E2E checks for ${GITHUB_SHA}"

- uses: oven-sh/setup-bun@v2

- uses: actions/setup-node@v4
Expand Down
Loading
Loading