Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.git
.gitignore
.next
node_modules
pnpm-lock.yaml
Dockerfile
.dockerignore
README.md
*.md
contracts
farm Guage
.kiro
.husky
.github
.vscode
docs
scripts
public/icons
*.log
.env
.env.*
36 changes: 0 additions & 36 deletions .eslintignore

This file was deleted.

187 changes: 169 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,129 @@ on:
push:
branches: [main]

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
NEXT_TELEMETRY_DISABLED: 1
CI_BUILD_ENFORCED: true

# ─── Shared boilerplate for every Node job ─────────────────────────────────────
#
# Each job repeats the checkout + pnpm + node setup because that keeps jobs
# hermetic — no shared state, no cross-job coupling. If latency becomes a
# concern, extract these steps into a composite action in .github/actions/.

jobs:
lint-and-build:
name: Lint + Build
# ═════════════════════════════════════════════════════════════════════════════
# PHASE 1 — Fast quality gates (run in parallel, < 2 min each)
# ═════════════════════════════════════════════════════════════════════════════

lint:
name: Lint
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm lint

typecheck:
name: TypeScript
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm typecheck

test:
name: Test
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test -- --reporter=junit --outputFile=test-results.xml
- name: Upload test report
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: test-results.xml
retention-days: 7

- name: Get pnpm store path
id: pnpm-cache
run: echo "dir=$(pnpm store path)" >> "$GITHUB_OUTPUT"
env-check:
name: Env vars
runs-on: ubuntu-latest
timeout-minutes: 2
steps:
- uses: actions/checkout@v4
- name: Validate env vars are not placeholders
run: |
errors=0
while IFS= read -r line; do
[[ "$line" =~ ^# || -z "$line" ]] && continue
var="${line%%=*}"
val="${line#*=}"
val="${val#\"}"; val="${val%\"}"
if [[ "$val" == REPLACE_WITH_* ]]; then
echo "⚠️ $var still contains a placeholder: $val"
errors=$(( errors + 1 ))
fi
done < .env.example
if (( errors > 0 )); then
echo "Found $errors placeholder(s). Update .env before production."
else
echo "✅ All env vars look good."
fi
# Non-blocking — placeholders are expected during development

- uses: actions/cache@v4
with:
path: |
${{ steps.pnpm-cache.outputs.dir }}
.next/cache
key: pnpm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-nextjs-${{ hashFiles('**/*.[jt]s', '**/*.[jt]sx') }}
restore-keys: |
pnpm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-
pnpm-${{ runner.os }}-
# ═════════════════════════════════════════════════════════════════════════════
# PHASE 2 — Expensive gate (build). Waits for all Phase-1 gates to pass.
# ═════════════════════════════════════════════════════════════════════════════

build:
name: Build
runs-on: ubuntu-latest
timeout-minutes: 10
needs: [lint, typecheck, test]
outputs:
build-cache-hit: ${{ steps.build-cache.outputs.cache-hit }}
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: pnpm
- run: pnpm install --frozen-lockfile

- run: pnpm lint
# Cache .next between CI runs (keyed on source hash)
- name: Cache Next.js build
id: build-cache
uses: actions/cache@v4
with:
path: .next/cache
key: nextjs-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml', '**/*.[jt]s', '**/*.[jt]sx', '**/*.css') }}
restore-keys: |
nextjs-${{ runner.os }}-

- run: pnpm build
- name: Build
run: pnpm build
env:
# Provide stub env vars so Next.js config doesn't throw at build time
NEXT_PUBLIC_STELLAR_NETWORK: testnet
NEXT_PUBLIC_HORIZON_URL: https://horizon-testnet.stellar.org
NEXT_PUBLIC_SOROBAN_RPC_URL: https://soroban-testnet.stellar.org
Expand All @@ -56,3 +145,65 @@ jobs:
NEXT_PUBLIC_ANCHOR_API_URL: https://testanchor.stellar.org
NEXT_PUBLIC_ANCHOR_HOME_DOMAIN: testanchor.stellar.org
NEXT_PUBLIC_APP_URL: http://localhost:3000

# ═════════════════════════════════════════════════════════════════════════════
# PHASE 3 — Docker image (main branch only)
# ═════════════════════════════════════════════════════════════════════════════

docker:
name: Docker
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
timeout-minutes: 15
needs: [build]
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4

- uses: docker/setup-buildx-action@v3

- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=sha,format=long
type=ref,event=branch
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: mode=max

- name: Scan image for vulnerabilities
uses: aquasecurity/trivy-action@master
with:
image-ref: ghcr.io/${{ github.repository }}:${{ steps.meta.outputs.version }}
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH

- name: Upload Trivy report
if: always()
uses: actions/upload-artifact@v4
with:
name: trivy-report
path: trivy-results.sarif
retention-days: 30
89 changes: 67 additions & 22 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,96 @@ name: Deploy
on:
push:
branches: [main]
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
default: staging
type: choice
options:
- staging
- production

concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true

jobs:
deploy:
name: Deploy to Vercel
# ═════════════════════════════════════════════════════════════════════════════
# STAGING — preview deploy on every main push
# ═════════════════════════════════════════════════════════════════════════════

staging:
name: Deploy to Staging
runs-on: ubuntu-latest
environment:
name: staging
url: ${{ steps.vercel-staging.outputs.preview-url }}
outputs:
deployment-url: ${{ steps.vercel.outputs.preview-url }}
deployment-url: ${{ steps.vercel-staging.outputs.preview-url }}
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile

- uses: pnpm/action-setup@v4
- name: Deploy to Vercel (staging)
id: vercel-staging
uses: amondnet/vercel-action@v25
with:
version: 10
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--build'

- name: Get pnpm store path
id: pnpm-cache
run: echo "dir=$(pnpm store path)" >> "$GITHUB_OUTPUT"
smoke-tests-staging:
name: Smoke tests (staging)
needs: staging
uses: ./.github/workflows/smoke-tests.yml
with:
base-url: ${{ needs.staging.outputs.deployment-url }}
secrets: inherit

- uses: actions/cache@v4
with:
path: |
${{ steps.pnpm-cache.outputs.dir }}
.next/cache
key: pnpm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}-nextjs-${{ hashFiles('**/*.[jt]s', '**/*.[jt]sx') }}
restore-keys: pnpm-${{ runner.os }}-
# ═════════════════════════════════════════════════════════════════════════════
# PRODUCTION — only after staging smoke tests pass. Requires manual approval
# when triggered via workflow_dispatch (GitHub Environments).
# ═════════════════════════════════════════════════════════════════════════════

production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: smoke-tests-staging
environment:
name: production
url: ${{ steps.vercel-prod.outputs.preview-url }}
outputs:
deployment-url: ${{ steps.vercel-prod.outputs.preview-url }}
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
cache: pnpm
- run: pnpm install --frozen-lockfile

- name: Deploy to Vercel (production)
id: vercel
id: vercel-prod
uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
vercel-args: '--prod --build'

smoke-tests:
name: Smoke tests
needs: deploy
smoke-tests-prod:
name: Smoke tests (production)
needs: production
uses: ./.github/workflows/smoke-tests.yml
with:
base-url: ${{ needs.deploy.outputs.deployment-url }}
base-url: ${{ needs.production.outputs.deployment-url }}
secrets: inherit
Loading