Magic Link Implementation #8047
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # This workflow will build the project on pull requests with tests | |
| # Uses: | |
| # OS: ubuntu-latest | |
| # Go: go 1.x | |
| name: 👷🛠️ PR Builder | |
| on: | |
| pull_request: | |
| merge_group: | |
| workflow_dispatch: | |
| # Avoid running multiple PR builders for the same PR on subsequent pushes. | |
| concurrency: | |
| group: pr-builder-${{ github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| GOFLAGS: "-mod=readonly" | |
| jobs: | |
| dependency-guard: | |
| name: 🛡️ Dependency Guard | |
| if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} | |
| uses: ./.github/workflows/dependency-guard.yml | |
| with: | |
| detection-mode: pr | |
| security-audit: | |
| name: 🔒 Security Audit | |
| if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: 📥 Checkout Code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: ⚙️ Set up Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: 'lts/*' | |
| - name: 📦 Install pnpm | |
| uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0 | |
| with: | |
| version: 'latest' | |
| run_install: false | |
| - name: 🔍 Run pnpm audit (Frontend) | |
| working-directory: frontend | |
| run: | | |
| echo "🔍 Running pnpm audit for frontend dependencies..." | |
| IGNORE_ARGS=() | |
| while IFS= read -r id; do | |
| IGNORE_ARGS+=(--ignore "$id") | |
| done < <(jq -r '.frontend[].id' "$GITHUB_WORKSPACE/.audit-ignore.json") | |
| pnpm audit --audit-level=high "${IGNORE_ARGS[@]}" | |
| - name: 🔍 Run npm audit (E2E Tests) | |
| working-directory: tests/e2e | |
| run: | | |
| echo "🔍 Running npm audit for E2E test dependencies..." | |
| IGNORED_GHSAS=$(jq '[(.common + .e2e)[].id]' "$GITHUB_WORKSPACE/.audit-ignore.json") | |
| AUDIT_RESULT=$(npm audit --json --audit-level=high 2>&1) || true | |
| REMAINING=$(echo "$AUDIT_RESULT" | jq --argjson ignored "$IGNORED_GHSAS" ' | |
| [.vulnerabilities[].via[] | select(type == "object") | |
| | select(.severity == "high" or .severity == "critical") | |
| | select(.url | test($ignored | join("|")) | not) | |
| ] | length | |
| ') | |
| if [ "$REMAINING" -gt 0 ]; then | |
| echo "❌ Found $REMAINING unignored high/critical advisories:" | |
| echo "$AUDIT_RESULT" | jq --argjson ignored "$IGNORED_GHSAS" ' | |
| [.vulnerabilities[].via[] | select(type == "object") | |
| | select(.severity == "high" or .severity == "critical") | |
| | select(.url | test($ignored | join("|")) | not) | |
| ] | unique_by(.url) | .[] | "\(.severity): \(.name) - \(.url)" | |
| ' | |
| exit 1 | |
| fi | |
| echo "✅ All high/critical advisories are in the ignored list." | |
| - name: 🔍 Run npm audit (Sample Apps) | |
| run: | | |
| echo "🔍 Running npm audit for sample app dependencies..." | |
| IGNORED_GHSAS=$(jq '[(.common + ."sample-apps")[].id]' "$GITHUB_WORKSPACE/.audit-ignore.json") | |
| for app in samples/apps/*/; do | |
| if [ -f "${app}package-lock.json" ]; then | |
| echo "Auditing $app..." | |
| cd "$app" | |
| AUDIT_RESULT=$(npm audit --json --audit-level=high 2>&1) || true | |
| REMAINING=$(echo "$AUDIT_RESULT" | jq --argjson ignored "$IGNORED_GHSAS" ' | |
| [.vulnerabilities[].via[] | select(type == "object") | |
| | select(.severity == "high" or .severity == "critical") | |
| | select(.url | test($ignored | join("|")) | not) | |
| ] | length | |
| ') | |
| if [ "$REMAINING" -gt 0 ]; then | |
| echo "❌ Found $REMAINING unignored high/critical advisories in $app:" | |
| echo "$AUDIT_RESULT" | jq --argjson ignored "$IGNORED_GHSAS" ' | |
| [.vulnerabilities[].via[] | select(type == "object") | |
| | select(.severity == "high" or .severity == "critical") | |
| | select(.url | test($ignored | join("|")) | not) | |
| ] | unique_by(.url) | .[] | "\(.severity): \(.name) - \(.url)" | |
| ' | |
| exit 1 | |
| fi | |
| echo "✅ $app - All high/critical advisories are in the ignored list." | |
| cd - > /dev/null | |
| fi | |
| done | |
| dependency-review: | |
| name: 🔎 Dependency Review | |
| if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: 📥 Checkout Code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: 🔎 Dependency Review | |
| uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4 | |
| with: | |
| fail-on-severity: high | |
| deny-licenses: GPL-3.0-only,GPL-3.0-or-later,AGPL-3.0-only,AGPL-3.0-or-later | |
| comment-summary-in-pr: always | |
| base-ref: ${{ github.event.merge_group.base_sha || github.event.pull_request.base.sha }} | |
| head-ref: ${{ github.event.merge_group.head_sha || github.event.pull_request.head.sha }} | |
| verify-mocks: | |
| name: 🔍 Verify Mock Files | |
| if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: 📥 Checkout Code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: ⚙️ Set up Go Environment | |
| uses: ./.github/actions/setup-go | |
| - name: 🔧 Install Mockery | |
| run: make install-mockery | |
| - name: 🏭 Generate Mocks | |
| run: make mockery | |
| - name: 🔎 Check for Outdated Mocks | |
| run: | | |
| if [ -n "$(git status --porcelain --untracked-files=all -- backend/tests/mocks backend/internal)" ]; then | |
| echo "❌ Mock files are out of sync with interface definitions!" | |
| echo "" | |
| echo "The following mock files need to be regenerated:" | |
| git status --porcelain --untracked-files=all -- backend/tests/mocks backend/internal | awk '{print $2}' | |
| echo "" | |
| echo "To fix this:" | |
| echo " 1. Run: make mockery" | |
| echo " 2. Commit the updated mock files" | |
| echo " 3. Push your changes" | |
| echo "" | |
| echo "This ensures mock implementations stay synchronized with their interfaces." | |
| exit 1 | |
| fi | |
| echo "✅ All mock files are up to date" | |
| lint: | |
| name: 🧹 Lint Code | |
| needs: dependency-guard | |
| if: ${{ always() && (needs.dependency-guard.result == 'success' || needs.dependency-guard.result == 'skipped') && (github.event_name == 'pull_request' || github.event_name == 'merge_group') }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: 📥 Checkout Code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| with: | |
| # We need to fetch all branches and commits so that Nx affected has a base to compare against. | |
| fetch-depth: 0 | |
| - name: ⚙️ Set up Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: 'lts/*' | |
| - name: 🔄 Fetch main branch for Nx affected base | |
| run: git fetch origin main:main --update-head-ok || true | |
| - name: 🐳 Set SHAs for Nx | |
| id: set-shas | |
| uses: nrwl/nx-set-shas@3e9ad7370203c1e93d109be57f3b72eb0eb511b1 # v4.4.0 | |
| with: | |
| main-branch-name: "main" | |
| - name: 📦 Install pnpm | |
| uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0 | |
| with: | |
| version: 'latest' | |
| run_install: false | |
| cache_dependency_path: frontend/pnpm-lock.yaml | |
| - name: ⚙️ Set up Go Environment | |
| uses: ./.github/actions/setup-go | |
| - name: 📦 Prepare golangci-lint Locally | |
| run: make golangci-lint | |
| - name: 🔍 Run Backend Linter | |
| run: make lint_backend | |
| - name: 🧩 Install Dependencies & Build Frontend | |
| run: | | |
| cd frontend | |
| pnpm install --frozen-lockfile | |
| pnpm build | |
| - name: 🔍 Run Frontend Linter | |
| run: | | |
| cd frontend | |
| pnpm nx affected --target=lint --parallel=3 --base=${{ steps.set-shas.outputs.base }} --head=${{ steps.set-shas.outputs.head }} | |
| - name: 🎨 Check Frontend Formatting | |
| run: | | |
| cd frontend | |
| pnpm format:check | |
| build: | |
| name: 🛠️ Build Product | |
| needs: dependency-guard | |
| if: ${{ always() && (needs.dependency-guard.result == 'success' || needs.dependency-guard.result == 'skipped') && (github.event.label.name == 'trigger-pr-builder' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.event_name == 'merge_group') }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: 📥 Checkout Code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: ⚙️ Set up Go Environment | |
| uses: ./.github/actions/setup-go | |
| - name: ⚙️ Set up Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: 'lts/*' | |
| - name: 📦 Install pnpm | |
| uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0 | |
| with: | |
| version: 'latest' | |
| run_install: false | |
| cache_dependency_path: frontend/pnpm-lock.yaml | |
| - name: 🗄️ Cache Go Modules | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| id: cache-go-modules | |
| with: | |
| path: | | |
| ~/.cache/go-build | |
| ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-modules-${{ hashFiles('**/go.sum') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go-modules- | |
| - name: 📦 Install Backend Dependencies | |
| run: | | |
| cd backend | |
| go mod download | |
| cd ../tests/integration | |
| go mod download | |
| - name: 📦 Install Frontend Dependencies | |
| run: | | |
| cd frontend | |
| pnpm install --frozen-lockfile | |
| - name: 🧹 Clean Previous Builds | |
| run: | | |
| set -e | |
| make clean | |
| - name: 🔨 Build Frontend | |
| run: | | |
| cd frontend | |
| pnpm build | |
| - name: 🔨 Build Product with Coverage | |
| run: | | |
| set -e | |
| export LOG_LEVEL=debug | |
| make build_with_coverage_only OS=$(go env GOOS) ARCH=$(go env GOARCH) | |
| # Find the built distribution | |
| DIST_PATH=$(find target/dist -name "thunder-*.zip" | head -1) | |
| echo "Built distribution: $DIST_PATH" | |
| - name: 📦 Upload Built Distribution | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: thunder-distribution | |
| path: target/dist/*.zip | |
| if-no-files-found: error | |
| - name: 📊 Upload Unit Test Coverage Report to Codecov | |
| uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: backend/coverage_unit.out | |
| disable_search: true | |
| flags: backend-unit | |
| name: Backend Unit Tests | |
| fail_ci_if_error: false | |
| - name: 📦 Archive Unit Coverage Report | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: unit-coverage-report | |
| path: backend/coverage_unit.out | |
| if-no-files-found: error | |
| test-frontend: | |
| name: 🧪 Frontend Tests (shard ${{ matrix.shard }}/4) | |
| needs: dependency-guard | |
| if: ${{ always() && (needs.dependency-guard.result == 'success' || needs.dependency-guard.result == 'skipped') && (github.event.label.name == 'trigger-pr-builder' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.event_name == 'merge_group') }} | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3, 4] | |
| steps: | |
| - name: 📥 Checkout Code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: ⚙️ Set up Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: 'lts/*' | |
| - name: 📦 Install pnpm | |
| uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0 | |
| with: | |
| version: 'latest' | |
| run_install: false | |
| cache_dependency_path: frontend/pnpm-lock.yaml | |
| - name: 📦 Install Frontend Dependencies | |
| run: | | |
| cd frontend | |
| pnpm install --frozen-lockfile | |
| - name: 🔨 Build Frontend Packages | |
| run: | | |
| cd frontend | |
| pnpm build:packages | |
| - name: 🧪 Run Console Tests with Coverage | |
| run: | | |
| cd frontend/apps/thunder-console | |
| pnpm vitest run --coverage --shard=${{ matrix.shard }}/4 | |
| - name: 🧪 Run Gate Tests with Coverage | |
| if: matrix.shard == 1 | |
| run: | | |
| cd frontend/apps/thunder-gate | |
| pnpm test:coverage | |
| # Strip branch data (BRDA/BRF/BRH) from frontend LCOV reports before uploading. | |
| # React Compiler (babel-plugin-react-compiler) generates phantom branch points in compiled | |
| # output that inflate the partial-line count in Codecov's coverage calculation. | |
| # This is a known upstream issue: https://github.com/facebook/react/issues/32950 | |
| # Line and function coverage remain unaffected and accurately reflect test quality. | |
| - name: 🧹 Strip branch data from frontend LCOV reports | |
| run: | | |
| for lcov_file in frontend/apps/thunder-console/coverage/lcov.info frontend/apps/thunder-gate/coverage/lcov.info; do | |
| if [ -f "$lcov_file" ]; then | |
| sed -i '/^BRDA:/d;/^BRF:/d;/^BRH:/d' "$lcov_file" | |
| fi | |
| done | |
| - name: 📦 Upload console coverage artifact | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: console-coverage-shard-${{ matrix.shard }} | |
| path: frontend/apps/thunder-console/coverage/lcov.info | |
| if-no-files-found: error | |
| - name: 📦 Upload gate coverage artifact | |
| if: matrix.shard == 1 | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: gate-coverage | |
| path: frontend/apps/thunder-gate/coverage/lcov.info | |
| if-no-files-found: error | |
| upload-frontend-coverage: | |
| name: 📊 Upload Frontend Coverage | |
| needs: test-frontend | |
| if: always() && needs.test-frontend.result == 'success' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: 📥 Checkout Code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: 📥 Download all coverage artifacts | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| pattern: console-coverage-shard-* | |
| path: coverage/console | |
| - name: 📥 Download gate coverage artifact | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| name: gate-coverage | |
| path: coverage/gate | |
| - name: 🔀 Merge console coverage from shards | |
| run: | | |
| cat coverage/console/console-coverage-shard-*/lcov.info > coverage/console-merged-lcov.info | |
| - name: 📊 Upload `@thunder/console` Unit Test Coverage Report to Codecov | |
| uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: coverage/console-merged-lcov.info | |
| disable_search: true | |
| flags: frontend-apps-console-unit | |
| name: Frontend Console App Unit Tests | |
| fail_ci_if_error: false | |
| - name: 📊 Upload `@thunder/gate` Unit Test Coverage Report to Codecov | |
| uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: coverage/gate/lcov.info | |
| disable_search: true | |
| flags: frontend-apps-gate-unit | |
| name: Frontend Gate App Unit Tests | |
| fail_ci_if_error: false | |
| build_samples: | |
| name: 🛠️ Build Sample Apps | |
| needs: dependency-guard | |
| if: ${{ always() && (needs.dependency-guard.result == 'success' || needs.dependency-guard.result == 'skipped') && (github.event.label.name == 'trigger-pr-builder' || github.event_name == 'workflow_dispatch' || github.event_name == 'pull_request' || github.event_name == 'merge_group') }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: 📥 Checkout Code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: ⚙️ Set up Go Environment | |
| uses: ./.github/actions/setup-go | |
| - name: ⚙️ Set up Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: 'lts/*' | |
| - name: 📦 Install pnpm | |
| uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0 | |
| with: | |
| version: 'latest' | |
| run_install: false | |
| cache_dependency_path: frontend/pnpm-lock.yaml | |
| - name: 🧩 Build and Package Sample Apps | |
| run: | | |
| make build_samples | |
| make package_samples OS=$(go env GOOS) ARCH=$(go env GOARCH) | |
| - name: 📦 Upload Built Sample App | |
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: sample-app-react-sdk | |
| path: target/dist/sample-app-react-sdk-*.zip | |
| test-integration: | |
| name: 🧪 Integration Tests (${{ matrix.database }}) | |
| needs: build | |
| runs-on: ubuntu-latest | |
| strategy: | |
| matrix: | |
| database: [sqlite, postgres] | |
| fail-fast: false | |
| services: | |
| postgres: | |
| image: postgres:latest | |
| env: | |
| POSTGRES_USER: asgthunder | |
| POSTGRES_PASSWORD: asgthunder | |
| POSTGRES_DB: thunderdb | |
| ports: | |
| - 5432:5432 | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| steps: | |
| - name: 📥 Checkout Code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: ⚙️ Set up Go Environment | |
| uses: ./.github/actions/setup-go | |
| - name: 📥 Download Unit Coverage Report | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| name: unit-coverage-report | |
| path: backend/ | |
| - name: 🗄️ Cache Go Modules | |
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 | |
| id: cache-go-modules | |
| with: | |
| path: | | |
| ~/.cache/go-build | |
| ~/go/pkg/mod | |
| key: ${{ runner.os }}-go-modules-${{ hashFiles('**/go.sum') }} | |
| restore-keys: | | |
| ${{ runner.os }}-go-modules- | |
| - name: 📦 Install Dependencies | |
| run: | | |
| cd backend | |
| go mod download | |
| cd ../tests/integration | |
| go mod download | |
| - name: 📝 Configure Test Database | |
| run: | | |
| chmod +x tests/integration/resources/scripts/setup-test-config.sh | |
| ./tests/integration/resources/scripts/setup-test-config.sh | |
| env: | |
| DB_TYPE: ${{ matrix.database }} | |
| - name: 🧪 Run Integration Tests (${{ matrix.database }}) | |
| uses: ./.github/actions/run-integration-tests | |
| with: | |
| database-type: ${{ matrix.database }} | |
| coverage-enabled: true | |
| - name: 📊 Upload Integration Test Coverage Report to Codecov | |
| uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: target/coverage_integration.out | |
| disable_search: true | |
| flags: backend-integration-${{ matrix.database }} | |
| name: Backend Integration Tests (${{ matrix.database }}) | |
| fail_ci_if_error: false | |
| # - name: 🧩 Generate Combined Coverage Report | |
| # run: ./build.sh merge_coverage | |
| # - name: 📊 Upload Combined Coverage Report to Codecov | |
| # uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4 | |
| # with: | |
| # token: ${{ secrets.CODECOV_TOKEN }} | |
| # files: target/coverage_combined.out | |
| # disable_search: true | |
| # flags: backend-combined-${{ matrix.database }} | |
| # name: Backend-combined Coverage (${{ matrix.database }}) | |
| # fail_ci_if_error: false | |
| test-e2e: | |
| name: 🎭 Playwright E2E Tests | |
| needs: [build, build_samples] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: 📥 Checkout Code | |
| uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - name: ⚙️ Set up Node.js | |
| uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 | |
| with: | |
| node-version: 'lts/*' | |
| - name: 📥 Download Built Distribution | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| name: thunder-distribution | |
| path: target/dist/ | |
| - name: 📥 Download Built Sample App | |
| uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| name: sample-app-react-sdk | |
| path: target/dist/ | |
| - name: 📂 Extract Server to E2E | |
| run: | | |
| mkdir -p tests/e2e/thunder-server | |
| unzip target/dist/thunder-*.zip -d tests/e2e/thunder-server | |
| cd tests/e2e/thunder-server | |
| # Find the extracted folder (name includes version and arch) and move its contents to the root of tests/e2e/thunder-server | |
| EXTRACTED_DIR=$(find . -maxdepth 1 -type d -name "thunder-*" | head -n 1) | |
| if [ -n "$EXTRACTED_DIR" ]; then | |
| mv "$EXTRACTED_DIR"/* . | |
| rmdir "$EXTRACTED_DIR" | |
| fi | |
| chmod +x setup.sh start.sh | |
| - name: 🚀 Start Thunder Server | |
| run: | | |
| cd tests/e2e/thunder-server | |
| ./setup.sh | |
| THUNDER_SKIP_SECURITY=true ./start.sh & | |
| THUNDER_PID=$! | |
| echo "THUNDER_PID=$THUNDER_PID" >> $GITHUB_ENV | |
| # Wait for server to be ready | |
| echo "Waiting for server to be ready on https://localhost:8090..." | |
| for i in {1..60}; do | |
| if curl -s -k https://localhost:8090/health/liveness > /dev/null; then | |
| echo "Server is UP!" | |
| break | |
| fi | |
| echo "Still waiting ($i/60)..." | |
| sleep 2 | |
| done | |
| # Verify server is actually running | |
| if ! curl -s -k https://localhost:8090/health/liveness > /dev/null; then | |
| echo "Server failed to start within 2 minutes" | |
| exit 1 | |
| fi | |
| echo "Server started successfully!" | |
| - name: 🔍 Extract Sample App ID | |
| id: extract-app-id | |
| run: | | |
| echo "Fetching applications from Thunder..." | |
| # Get applications list | |
| APPS_RESPONSE=$(curl -s -k https://localhost:8090/applications) | |
| # Extract Sample App ID using jq | |
| SAMPLE_APP_ID=$(echo "$APPS_RESPONSE" | jq -r '.applications[] | select(.name == "Sample App") | .id') | |
| if [ -z "$SAMPLE_APP_ID" ] || [ "$SAMPLE_APP_ID" == "null" ]; then | |
| echo "⚠️ Warning: Sample App not found in applications list" | |
| echo "Available applications:" | |
| echo "$APPS_RESPONSE" | jq -r '.applications[] | " - \(.name) (\(.id))"' | |
| echo "sample_app_id=" >> $GITHUB_OUTPUT | |
| else | |
| echo "✓ Sample App ID extracted: $SAMPLE_APP_ID" | |
| echo "sample_app_id=$SAMPLE_APP_ID" >> $GITHUB_OUTPUT | |
| fi | |
| - name: 🔄 Restart Thunder Server with Security Enabled | |
| run: | | |
| cd tests/e2e/thunder-server | |
| echo "Stopping Thunder server..." | |
| # Kill the start.sh wrapper process | |
| kill $THUNDER_PID 2>/dev/null || true | |
| # Also kill any process still listening on port 8090 | |
| # (start.sh backgrounds the thunder binary, so killing start.sh | |
| # may leave the thunder process running as an orphan) | |
| sleep 2 | |
| if lsof -ti tcp:8090 >/dev/null 2>&1; then | |
| echo "Port 8090 still in use, killing remaining processes..." | |
| kill -9 $(lsof -ti tcp:8090) 2>/dev/null || true | |
| fi | |
| # Wait for port to be fully released | |
| sleep 3 | |
| echo "Starting Thunder server with security enabled..." | |
| ./start.sh & | |
| THUNDER_PID=$! | |
| echo "THUNDER_PID=$THUNDER_PID" >> $GITHUB_ENV | |
| # Wait for server to be ready | |
| echo "Waiting for server to be ready on https://localhost:8090..." | |
| for i in {1..60}; do | |
| if curl -s -k https://localhost:8090/health/liveness > /dev/null; then | |
| echo "Server is UP with security enabled!" | |
| break | |
| fi | |
| echo "Still waiting ($i/60)..." | |
| sleep 2 | |
| done | |
| # Verify server is actually running | |
| if ! curl -s -k https://localhost:8090/health/liveness > /dev/null; then | |
| echo "Server failed to restart within 2 minutes" | |
| exit 1 | |
| fi | |
| echo "Server restarted successfully with security enabled!" | |
| - name: 📱 Extract and Start Sample App | |
| run: | | |
| echo "Extracting Sample App..." | |
| mkdir -p tests/e2e/sample-app | |
| # Find the sample app zip file (platform-specific) | |
| SAMPLE_APP_ZIP=$(find target/dist -name "sample-app-react-sdk-*.zip" | head -n 1) | |
| if [ -z "$SAMPLE_APP_ZIP" ]; then | |
| echo "⚠️ Warning: Sample app zip not found in target/dist" | |
| echo "Available files:" | |
| ls -la target/dist/ | |
| exit 1 | |
| fi | |
| echo "Found sample app: $SAMPLE_APP_ZIP" | |
| unzip -q "$SAMPLE_APP_ZIP" -d tests/e2e/sample-app | |
| cd tests/e2e/sample-app | |
| # Find the extracted folder and move its contents | |
| EXTRACTED_DIR=$(find . -maxdepth 1 -type d -name "sample-app-*" | head -n 1) | |
| if [ -n "$EXTRACTED_DIR" ]; then | |
| mv "$EXTRACTED_DIR"/* . | |
| rmdir "$EXTRACTED_DIR" | |
| fi | |
| # Make start script executable | |
| chmod +x start.sh | |
| echo "Starting Sample App..." | |
| ./start.sh & | |
| # Wait for sample app to be ready | |
| echo "Waiting for sample app to be ready on https://localhost:3000..." | |
| for i in {1..60}; do | |
| if curl -s -k https://localhost:3000 > /dev/null 2>&1; then | |
| echo "Sample App is UP!" | |
| break | |
| fi | |
| echo "Still waiting ($i/60)..." | |
| sleep 2 | |
| done | |
| # Verify sample app is running | |
| if ! curl -s -k https://localhost:3000 > /dev/null 2>&1; then | |
| echo "Sample app failed to start within 2 minutes" | |
| exit 1 | |
| fi | |
| echo "Sample App started successfully!" | |
| - name: Install E2E dependencies | |
| run: npm ci | |
| working-directory: tests/e2e | |
| - name: Install Playwright Browsers | |
| run: npx playwright install --with-deps | |
| working-directory: tests/e2e | |
| - name: Run Playwright tests | |
| run: npx playwright test | |
| working-directory: tests/e2e | |
| env: | |
| # Configuration Priority: Secret > Variable > Hardcoded Default | |
| BASE_URL: ${{ secrets.PLAYWRIGHT_BASE_URL || vars.PLAYWRIGHT_BASE_URL || 'https://localhost:8090' }} | |
| ADMIN_USERNAME: ${{ secrets.PLAYWRIGHT_ADMIN_USERNAME || 'admin' }} | |
| ADMIN_PASSWORD: ${{ secrets.PLAYWRIGHT_ADMIN_PASSWORD || 'admin' }} | |
| TEST_USER_USERNAME: ${{ secrets.PLAYWRIGHT_TEST_USER_USERNAME || 'testuser' }} | |
| TEST_USER_PASSWORD: ${{ secrets.PLAYWRIGHT_TEST_USER_PASSWORD || 'admin' }} | |
| PLAYWRIGHT_WORKERS: ${{ vars.PLAYWRIGHT_WORKERS || 1 }} | |
| DEBUG_AUTH: ${{ vars.PLAYWRIGHT_DEBUG_AUTH || 'false' }} | |
| # MFA Test Configuration | |
| SAMPLE_APP_ID: ${{ steps.extract-app-id.outputs.sample_app_id }} | |
| SAMPLE_APP_URL: 'https://localhost:3000' | |
| THUNDER_URL: 'https://localhost:8090' | |
| AUTO_SETUP_MFA: ${{ vars.AUTO_SETUP_MFA || 'true' }} | |
| MOCK_SMS_SERVER_PORT: ${{ vars.MOCK_SMS_SERVER_PORT || 8098 }} | |
| SAMPLE_APP_USERNAME: ${{ secrets.SAMPLE_APP_USERNAME || 'e2e-test-user' }} | |
| SAMPLE_APP_PASSWORD: ${{ secrets.SAMPLE_APP_PASSWORD || 'e2e-test-password' }} | |
| - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| if: ${{ !cancelled() }} | |
| with: | |
| name: playwright-report | |
| path: tests/e2e/playwright-report/ | |
| retention-days: 30 | |
| detect-powershell-changes: | |
| name: 🔍 Detect PowerShell Changes | |
| if: github.event_name == 'pull_request' || github.event_name == 'merge_group' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should-run: ${{ steps.filter.outputs.powershell }} | |
| steps: | |
| - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 | |
| - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 | |
| id: filter | |
| with: | |
| base: ${{ github.event_name == 'merge_group' && github.event.merge_group.base_sha || github.event.pull_request.base.sha }} | |
| ref: ${{ github.event_name == 'merge_group' && github.sha || github.event.pull_request.head.sha }} | |
| filters: | | |
| powershell: | |
| - '**.ps1' | |
| - '.github/workflows/windows-powershell-validation.yml' | |
| - name: ✅ PowerShell files detected | |
| if: steps.filter.outputs.powershell == 'true' | |
| run: echo "PowerShell files changed - validation will run" | |
| - name: ⏭️ No PowerShell changes | |
| if: steps.filter.outputs.powershell != 'true' | |
| run: echo "No PowerShell changes - skipping validation" | |
| validate-windows: | |
| name: 🪟 Validate Windows PowerShell | |
| needs: detect-powershell-changes | |
| if: needs.detect-powershell-changes.outputs.should-run == 'true' | |
| uses: ./.github/workflows/windows-powershell-validation.yml | |