From 0e2bc31b734c94c180d5c821c438cbbce308efc8 Mon Sep 17 00:00:00 2001 From: Xi Xu Date: Mon, 30 Mar 2026 00:48:05 +0800 Subject: [PATCH] fix(ci): separate PR validation from image publishing Validate container builds inside CI for pull requests, and publish images only after CI succeeds on main or when a release tag is pushed. --- .github/workflows/ci.yml | 25 +++++++++++++++++++++++++ .github/workflows/image.yml | 28 ++++++++++++++-------------- tests/github-automation.test.cjs | 16 ++++++++++++++++ 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index af9ea9a..7c768f3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,8 @@ on: push: branches: - main + tags: + - v* workflow_dispatch: permissions: @@ -49,3 +51,26 @@ jobs: NODE_ENV=dry-run node ./build/stand-alone/crawl.js NODE_ENV=dry-run node ./build/stand-alone/search.js NODE_ENV=dry-run node ./build/stand-alone/serp.js + + validate-container: + name: Validate container build + runs-on: ubuntu-latest + needs: verify + if: github.event_name == 'pull_request' + permissions: + contents: read + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Prepare licensed assets + run: node ./scripts/prepare-licensed-assets.cjs + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build container image without publishing + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + context: . + push: false diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index cf733e8..de33534 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -1,21 +1,12 @@ name: Container Image on: - pull_request: - paths: - - Dockerfile - - package.json - - package-lock.json - - tsconfig.json - - integrity-check.cjs - - src/** - - public/** - - licensed/** - - scripts/** - - .github/workflows/image.yml + workflow_run: + workflows: + - CI + types: + - completed push: - branches: - - main tags: - v* workflow_dispatch: @@ -31,11 +22,20 @@ concurrency: jobs: docker: name: Build container image + if: | + github.event_name == 'workflow_dispatch' || + (github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')) || + (github.event_name == 'workflow_run' && + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'main') runs-on: ubuntu-latest timeout-minutes: 45 steps: - name: Check out repository uses: actions/checkout@v6 + with: + ref: ${{ github.event_name == 'workflow_run' && github.event.workflow_run.head_sha || github.ref }} - name: Prepare licensed assets run: node ./scripts/prepare-licensed-assets.cjs diff --git a/tests/github-automation.test.cjs b/tests/github-automation.test.cjs index b7832a3..042656f 100644 --- a/tests/github-automation.test.cjs +++ b/tests/github-automation.test.cjs @@ -37,6 +37,14 @@ test('container image workflow publishes to GHCR instead of legacy GCP registrie assert.doesNotMatch(workflow, /gcloud|us-docker\.pkg\.dev/); }); +test('container image publishing waits for CI instead of running on pull requests directly', () => { + const workflow = read('.github/workflows/image.yml'); + + assert.match(workflow, /workflow_run:/); + assert.match(workflow, /workflows:\s*\n\s*- CI/); + assert.doesNotMatch(workflow, /pull_request:/); +}); + test('dependabot config covers npm, docker, and github-actions updates', () => { const dependabot = read('.github/dependabot.yml'); @@ -53,6 +61,14 @@ test('CI workflow runs lint before tests and build', () => { assert.match(workflow, /run: npm run build/); }); +test('CI validates Docker builds for pull requests without publishing images', () => { + const workflow = read('.github/workflows/ci.yml'); + + assert.match(workflow, /name: Validate container build/); + assert.match(workflow, /if: github\.event_name == 'pull_request'/); + assert.match(workflow, /push: false/); +}); + test('Dockerfile is self-contained and no longer relies on curl-impersonate', () => { const dockerfile = read('Dockerfile');