name: CI on: pull_request: branches: - main - next push: branches: - main - next permissions: contents: read env: XCODE_VERSION: 16.4 jobs: audit-release-state: name: Audit Release Branch State runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: 20 - name: Reject prerelease metadata on main run: node scripts/release-branch-policy.mjs audit - name: Test release automation run: >- node --test scripts/release-branch-policy.test.mjs scripts/npm-publish-authorization.test.mjs scripts/verify-npm-release-provenance.test.mjs scripts/generate-sbom.test.mjs - name: Test Gradle network retry helper run: node --test scripts/ci/retry-gradle.test.mjs # The test jobs below install with --frozen-lockfile but only run when # their path filters match, so a workspace package.json change outside # those paths can land with a stale bun.lock. Run the check on every CI # run to catch lockfile drift regardless of which paths changed. audit-lockfile: name: Audit Lockfile Sync runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - name: Validate Gradle wrappers uses: gradle/actions/wrapper-validation@9c971963bec38e04b3d30dcc455b5382be2fdbfb # v6 with: min-wrapper-count: 7 - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: 1.3.13 - name: Verify bun.lock matches workspace manifests run: | # Retry bun install up to 3 times to handle transient registry errors for i in 1 2 3; do bun install --frozen-lockfile && break [ $i -eq 3 ] && exit 1 echo "Attempt $i failed. Retrying..." sleep 5 done - name: Verify independent Bun locks run: | for directory in \ libraries/expo-iap \ libraries/expo-iap/example \ libraries/expo-iap/example/vega \ libraries/react-native-iap/example/vega \ scripts/agent; do (cd "$directory" && bun install --frozen-lockfile --ignore-scripts) done - name: Verify Yarn lock working-directory: libraries/react-native-iap run: corepack yarn install --immutable --mode=skip-build - name: Test dependency and workflow guards run: >- node --test scripts/audit-security.test.mjs scripts/bun-dependency-snapshot.test.mjs - name: Audit workflow security run: node scripts/audit-security.mjs workflows - name: Install OSV-Scanner v2.5.0 run: | scripts/install-security-tool.sh osv-scanner "$RUNNER_TEMP/osv-scanner" echo "$RUNNER_TEMP" >> "$GITHUB_PATH" - name: Audit dependency vulnerabilities and exception lifecycle run: node scripts/audit-security.mjs dependencies - name: Audit all tracked JavaScript locks with OSV run: | osv-scanner scan source \ --lockfile=bun.lock \ --lockfile=libraries/expo-iap/bun.lock \ --lockfile=libraries/expo-iap/example/bun.lock \ --lockfile=libraries/expo-iap/example/vega/bun.lock \ --lockfile=libraries/react-native-iap/example/vega/bun.lock \ --lockfile=libraries/react-native-iap/yarn.lock \ --lockfile=scripts/agent/bun.lock # Detect which packages have changes changes: name: Detect Changes runs-on: ubuntu-latest outputs: gql: ${{ steps.filter.outputs.gql }} android: ${{ steps.filter.outputs.android }} ios: ${{ steps.filter.outputs.ios }} docs: ${{ steps.filter.outputs.docs }} web: ${{ steps.filter.outputs.web }} steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - name: Check for changes uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4 id: filter with: filters: | gql: - 'packages/gql/**' - 'packages/conformance/**' - 'scripts/**' - 'package.json' - 'bun.lock' - 'openiap-versions.json' - '.github/workflows/ci.yml' - 'libraries/maui-iap/src/OpenIap.Maui/Types.cs' android: - 'packages/google/**' - 'packages/gql/**' - 'scripts/**' - 'openiap-versions.json' - '.github/workflows/ci.yml' ios: - 'packages/apple/**' - 'packages/gql/**' - 'scripts/**' - 'openiap-versions.json' - '.github/workflows/ci.yml' docs: - 'packages/docs/**' - 'packages/gql/src/generated/**' - 'packages/gql/generated-sync-manifest.mjs' - 'scripts/audit-docs.ts' - 'scripts/audit-docs.test.ts' - '.github/workflows/ci.yml' web: - 'packages/docs/**' - 'packages/kit/**' - 'scripts/e2e-web-sites.mjs' - 'package.json' - 'bun.lock' - '.github/workflows/ci.yml' test-conformance: name: Test Conformance Suite runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: 1.3.13 - name: Install dependencies run: | for i in 1 2 3; do bun install --frozen-lockfile && break [ $i -eq 3 ] && exit 1 echo "Attempt $i failed. Retrying..." sleep 5 done - name: Run conformance suite tests working-directory: packages/conformance run: bun run test - name: Verify cross-language behavior ids are in sync run: node packages/conformance/scripts/generate-behavior-ids.mjs --check - name: Reference conformance report run: node packages/conformance/scripts/run-reference-report.mjs - name: Ecosystem coverage report run: | node packages/conformance/scripts/coverage-report.mjs node packages/conformance/scripts/coverage-report.mjs --json > conformance-coverage.json node packages/conformance/scripts/run-reference-report.mjs --json > conformance-report.json - name: Upload conformance report uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: conformance-report path: | conformance-report.json conformance-coverage.json if-no-files-found: error audit-parity: name: Audit SDK Parity runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - name: Setup Node uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 with: node-version: 20 - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: 1.3.13 - name: Install parity audit dependencies run: | # The root parity scripts use root devDependencies (for example, # TypeScript) as well as GQL workspace tooling. Install both scopes # so Node can resolve them without installing unrelated workspaces. for i in 1 2 3; do bun install --frozen-lockfile \ --filter @hyodotdev/openiap \ --filter @hyodotdev/openiap-gql && break [ $i -eq 3 ] && exit 1 echo "Attempt $i failed. Retrying..." sleep 5 done - name: Test clean-worktree drift guard run: node --test scripts/assert-clean-worktree.test.mjs - name: Sync generated version files run: ./scripts/sync-versions.sh - name: Verify version and generated synchronization is committed run: node scripts/assert-clean-worktree.mjs - name: Run non-Godot SDK parity audit run: node scripts/audit-non-godot-parity.mjs # Unconditional: kit and the spec deploy on separate workflows. - name: Test IAPKit spec contract audit run: node --test scripts/audit-kit-spec-contract.test.mjs - name: Run IAPKit spec contract audit run: node scripts/audit-kit-spec-contract.mjs test-gql: name: Test GQL Types runs-on: ubuntu-latest needs: changes if: needs.changes.outputs.gql == 'true' steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: 1.3.13 - name: Install dependencies run: | # Retry bun install up to 3 times to handle transient registry errors for i in 1 2 3; do bun install --frozen-lockfile && break [ $i -eq 3 ] && exit 1 echo "Attempt $i failed. Retrying..." sleep 5 done - name: Generate types working-directory: packages/gql run: bun run generate - name: Run tests working-directory: packages/gql run: bun run test - name: Verify generated types are committed (no drift) run: node scripts/assert-clean-worktree.mjs # test-gql owns regeneration, platform sync, and the clean-worktree drift # check. Consumer jobs compile the committed copies instead of invoking a # second generator path. test-android: name: Test Android runs-on: ubuntu-latest needs: changes if: needs.changes.outputs.android == 'true' steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - name: Setup Java uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 with: distribution: "temurin" java-version: "17" - name: Grant execute permission working-directory: packages/google run: chmod +x gradlew - name: Run tests working-directory: packages/google run: | "$GITHUB_WORKSPACE/scripts/ci/retry-gradle.sh" ./gradlew :openiap:test - name: Build Play flavor working-directory: packages/google run: | "$GITHUB_WORKSPACE/scripts/ci/retry-gradle.sh" ./gradlew :openiap:assemblePlayDebug - name: Build Horizon flavor working-directory: packages/google run: | "$GITHUB_WORKSPACE/scripts/ci/retry-gradle.sh" ./gradlew :openiap:assembleHorizonDebug - name: Build Fire OS flavor working-directory: packages/google run: | "$GITHUB_WORKSPACE/scripts/ci/retry-gradle.sh" ./gradlew :openiap:assembleAmazonDebug - name: Verify Kotlin 2.1 consumer compatibility working-directory: packages/google run: bash scripts/verify-kotlin-2.1-consumer.sh # Run every store flavor so flavor-specific API-23 regressions cannot # bypass lint coverage. - name: Lint Android API compatibility working-directory: packages/google run: | "$GITHUB_WORKSPACE/scripts/ci/retry-gradle.sh" ./gradlew \ :openiap:lintPlayDebug \ :openiap:lintHorizonDebug \ :openiap:lintAmazonDebug test-ios: name: Test iOS runs-on: macos-15 needs: changes if: needs.changes.outputs.ios == 'true' steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - name: Set up Xcode uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1 with: xcode-version: ${{ env.XCODE_VERSION }} - name: Build working-directory: packages/apple run: swift build - name: Run tests working-directory: packages/apple run: swift test test-docs: name: Test Docs runs-on: ubuntu-latest needs: changes if: needs.changes.outputs.docs == 'true' steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: 1.3.13 - name: Install dependencies run: | # Retry bun install up to 3 times to handle transient registry errors for i in 1 2 3; do bun install --frozen-lockfile && break [ $i -eq 3 ] && exit 1 echo "Attempt $i failed. Retrying..." sleep 5 done - name: Type check working-directory: packages/docs run: bun run typecheck - name: Audit docs consistency run: | bun test scripts/audit-docs.test.ts bun run audit:docs - name: Lint working-directory: packages/docs run: bun run lint - name: Format check working-directory: packages/docs run: bunx prettier --check "src/**/*.{ts,tsx,css}" - name: Build working-directory: packages/docs run: bun run build web-e2e: name: Web E2E Quality runs-on: ubuntu-latest needs: changes if: needs.changes.outputs.web == 'true' steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: 1.3.13 - name: Install dependencies run: | # Retry bun install up to 3 times to handle transient registry errors for i in 1 2 3; do bun install --frozen-lockfile && break [ $i -eq 3 ] && exit 1 echo "Attempt $i failed. Retrying..." sleep 5 done - name: Install Playwright chromium run: bunx playwright install --with-deps chromium - name: Build docs site working-directory: packages/docs run: bun run build - name: Build IAPKit site and server working-directory: packages/kit env: VITE_KIT_CONVEX_URL: https://placeholder-build-1.convex.cloud run: bun run build:all - name: Run docs and IAPKit web E2E env: WEB_E2E_DOCS_BASE_URL: http://127.0.0.1:4173 WEB_E2E_KIT_BASE_URL: http://127.0.0.1:4174 run: | (cd packages/docs && bunx vite preview --host 127.0.0.1 --port 4173) & docs_pid=$! (cd packages/kit && CONVEX_URL=https://placeholder-build-1.convex.cloud STATIC_ROOT=dist PORT=4174 ./openiap-kit-server) & kit_pid=$! cleanup() { kill "$docs_pid" "$kit_pid" 2>/dev/null || true } trap cleanup EXIT for url in http://127.0.0.1:4173 http://127.0.0.1:4174; do for attempt in $(seq 1 60); do if curl -fsS "$url" >/dev/null; then break fi if [ "$attempt" -eq 60 ]; then echo "::error::$url did not become ready" exit 1 fi sleep 1 done done bun run e2e:web test-agent: name: Test Agent Scripts runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: persist-credentials: false - name: Setup Bun uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 with: bun-version: 1.3.13 - name: Install dependencies working-directory: scripts/agent run: | # Retry bun install up to 3 times to handle transient registry errors for i in 1 2 3; do bun install --frozen-lockfile && break [ $i -eq 3 ] && exit 1 echo "Attempt $i failed. Retrying..." sleep 5 done - name: Type check working-directory: scripts/agent run: bun run typecheck - name: Run tests working-directory: scripts/agent run: bun test - name: Compile generated agent context working-directory: scripts/agent run: bun run compile:ai - name: Verify compiled agent context is committed run: node scripts/assert-clean-worktree.mjs