diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b9eca78..b34c96b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,6 +8,7 @@ permissions: contents: read pull-requests: read packages: read + actions: read env: CARGO_TERM_COLOR: always @@ -193,6 +194,11 @@ jobs: run_test=true done < <(git diff --name-only "$BASE_SHA" "$HEAD_SHA") + # Integration tests need a .node in the Test job workspace. Do not force + # compile-native here — compile runs in a container and artifact download + # to the host Test job is unreliable on self-hosted; Test restores cache + # and builds in CI image when needed (see test job below). + echo "run_compile=${run_compile}" >> "$GITHUB_OUTPUT" echo "run_build_ts=${run_build_ts}" >> "$GITHUB_OUTPUT" echo "run_test=${run_test}" >> "$GITHUB_OUTPUT" @@ -374,6 +380,10 @@ jobs: bash scripts/fix-rollup-native.sh bash scripts/ci/build-ts-workspace.sh + # Inline integration tests (not reusable-test.yml): workflow_call cannot download + # caller artifacts; compile-native runs in a container while Test runs on the + # self-hosted host — artifact handoff is unreliable. Test restores GHA cache and + # compiles in CI image when needed. test: name: Test needs: [changes, validate-package-lock, quality, compile-native, build-ts] @@ -383,13 +393,118 @@ jobs: needs.validate-package-lock.result == 'success' && needs.compile-native.result != 'failure' && needs.build-ts.result != 'failure' - uses: ./.github/workflows/reusable-test.yml - with: - test_script: scripts/ci/run-pr-integration.sh - skip: ${{ needs.changes.outputs.skip_test }} - quality_result: ${{ needs.quality.result }} - binding_artifact: ${{ needs.compile-native.outputs.ran_compile == 'true' && 'bindings-x86_64-unknown-linux-gnu' || '' }} - require_binding_artifact: ${{ needs.compile-native.outputs.ran_compile == 'true' && 'true' || 'false' }} - needs_native_binding: ${{ needs.changes.outputs.needs_native_for_test }} - needs_ts_dist: ${{ needs.changes.outputs.needs_ts_for_test }} - secrets: inherit + runs-on: self-hosted + env: + TURN_AVAILABLE: '1' + CI_IMAGE: ghcr.io/${{ github.repository }}/ci-build:latest + steps: + - name: Resolve integration test gates + id: gate + env: + SKIP: ${{ needs.changes.outputs.skip_test }} + NEEDS_NATIVE: ${{ needs.changes.outputs.needs_native_for_test }} + NEEDS_TS: ${{ needs.changes.outputs.needs_ts_for_test }} + run: | + is_true() { + case "${1,,}" in + true|1|yes) return 0 ;; + *) return 1 ;; + esac + } + if is_true "$SKIP"; then + echo "skip=true" >> "$GITHUB_OUTPUT" + echo "::notice::Skipping integration tests — no source code changes in this PR." + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + if is_true "$NEEDS_NATIVE"; then + echo "needs_native=true" >> "$GITHUB_OUTPUT" + else + echo "needs_native=false" >> "$GITHUB_OUTPUT" + fi + if is_true "$NEEDS_TS"; then + echo "needs_ts=true" >> "$GITHUB_OUTPUT" + else + echo "needs_ts=false" >> "$GITHUB_OUTPUT" + fi + + - name: Require quality passed before integration tests + if: steps.gate.outputs.skip != 'true' && steps.gate.outputs.needs_native == 'true' && needs.quality.result != 'success' + run: | + echo "Typecheck & lint must pass before integration tests (quality=${{ needs.quality.result }})." >&2 + exit 1 + + - name: Prepare workspace + if: steps.gate.outputs.skip != 'true' + run: | + if [[ -n "${RUNNER_WORKSPACE:-}" && -d "${RUNNER_WORKSPACE}" ]]; then + docker run --rm \ + -v "${RUNNER_WORKSPACE}:${RUNNER_WORKSPACE}" \ + alpine:3.20 \ + chown -R "$(id -u):$(id -g)" "${RUNNER_WORKSPACE}" || true + fi + + - uses: actions/checkout@v4 + if: steps.gate.outputs.skip != 'true' + + - uses: ./.github/actions/native-binding-cache + if: steps.gate.outputs.skip != 'true' && steps.gate.outputs.needs_native == 'true' + id: native-cache + with: + target: x86_64-unknown-linux-gnu + profile: debug + + - name: Build native binding in CI image when cache miss + if: steps.gate.outputs.skip != 'true' && steps.gate.outputs.needs_native == 'true' + run: | + shopt -s nullglob + files=(packages/bindings/*.node) + if [[ ${#files[@]} -gt 0 ]]; then + echo "Native binding already present: $(basename "${files[0]}")" + exit 0 + fi + echo "No cached .node — compiling in ${{ env.CI_IMAGE }}" + docker run --rm \ + -v "${GITHUB_WORKSPACE}:/workspace" \ + -w /workspace \ + -e CMAKE_POLICY_VERSION_MINIMUM=3.5 \ + -e OPUS_STATIC=1 \ + "${{ env.CI_IMAGE }}" \ + bash -c ' + set -euo pipefail + cd packages/bindings + npm ci --ignore-scripts + npx napi build --target x86_64-unknown-linux-gnu + npm run copy:local-node + ' + files=(packages/bindings/*.node) + if [[ ${#files[@]} -eq 0 ]]; then + echo "Native build in CI image did not produce a .node file." >&2 + exit 1 + fi + echo "Built native binding: $(basename "${files[0]}")" + + - name: Verify native binding in workspace + if: steps.gate.outputs.skip != 'true' && steps.gate.outputs.needs_native == 'true' + run: | + shopt -s nullglob + files=(packages/bindings/*.node) + if [[ ${#files[@]} -eq 0 ]]; then + echo "No .node in packages/bindings after artifact/cache restore." >&2 + exit 1 + fi + echo "Native binding ready: $(basename "${files[0]}")" + + - uses: ./.github/actions/ci-cache-ts-dist + if: steps.gate.outputs.skip != 'true' && steps.gate.outputs.needs_ts == 'true' + id: ts-cache + with: + profile: release + + - uses: ./.github/actions/ci-run-integration-tests + if: steps.gate.outputs.skip != 'true' + with: + test_script: scripts/ci/run-pr-integration.sh + ci_image: ${{ env.CI_IMAGE }} + github_token: ${{ secrets.GITHUB_TOKEN }} + ts_dist_cache_hit: ${{ steps.ts-cache.outputs.cache-hit || 'false' }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7ae887e..32c00cd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -383,8 +383,10 @@ jobs: run: bash scripts/ci/post-release-sync-main-package-lock.sh "${{ steps.version.outputs.version }}" - name: Open PR to main + id: open_pr uses: peter-evans/create-pull-request@v7 with: + token: ${{ secrets.REPO_SYNC_PAT != '' && secrets.REPO_SYNC_PAT || github.token }} commit-message: "chore(ci): sync package-lock after release ${{ steps.version.outputs.version }}" title: "chore(ci): sync package-lock after release ${{ steps.version.outputs.version }}" body: | @@ -404,3 +406,12 @@ jobs: base: main delete-branch: true labels: dependencies,automation + + - name: Manual PR fallback (when GITHUB_TOKEN cannot open PRs) + if: failure() && steps.open_pr.outcome == 'failure' + shell: bash + run: | + BRANCH="chore/post-release-package-lock-${{ steps.version.outputs.version }}" + echo "::warning::Automated PR creation failed (org may block GITHUB_TOKEN from opening PRs)." + echo "Fix: repo Settings → Actions → allow Actions to create PRs, or add secret REPO_SYNC_PAT (classic PAT, repo scope)." + echo "Open manually: https://github.com/${{ github.repository }}/compare/main...${BRANCH}?expand=1" diff --git a/scripts/RELEASE.md b/scripts/RELEASE.md index b26f8b5..305799c 100644 --- a/scripts/RELEASE.md +++ b/scripts/RELEASE.md @@ -187,7 +187,12 @@ Job **`Sync main package-lock (PR)`** in [`release.yml`](../.github/workflows/re 4. Opens a PR via [`peter-evans/create-pull-request`](https://github.com/peter-evans/create-pull-request) — branch `chore/post-release-package-lock-X.Y.Z`, labels `dependencies`, `automation`. 5. Skips opening a PR if there is no diff. -Requires workflow permission **`pull-requests: write`** and a `GITHUB_TOKEN` that can push branches. If branch protection blocks the bot, create the PR manually from the branch the job pushed (or re-run the sync script locally and open the PR yourself). +Requires workflow permission **`pull-requests: write`**. If the org disables **“Allow GitHub Actions to create and approve pull requests”**, `GITHUB_TOKEN` cannot open the PR (the branch may still be pushed). Fix one of: + +1. **Repo Settings → Actions → General** — enable **Allow GitHub Actions to create and approve pull requests**, or +2. Add repo secret **`REPO_SYNC_PAT`** (classic PAT with `repo` scope) — `release.yml` uses it for `create-pull-request`. + +If automation fails, open the PR manually from branch `chore/post-release-package-lock-X.Y.Z` (compare link is printed in the failed workflow log). ### `SKIP_LOCK_REFRESH`