From 507a1eb4194c3c5f9ef49de7255cceb043ae1ca0 Mon Sep 17 00:00:00 2001 From: Declan Carroll Date: Wed, 23 Sep 2026 12:51:51 +0000 Subject: [PATCH] ci: Run format check alongside build (no-changelog) (#39387) --- .github/actions/ci-filter/ci-filter.mjs | 22 ++++++++-- .github/workflows/ci-pull-requests.yml | 56 +++++++++++++++++-------- 2 files changed, 57 insertions(+), 21 deletions(-) diff --git a/.github/actions/ci-filter/ci-filter.mjs b/.github/actions/ci-filter/ci-filter.mjs index d0754d897ba..3beea44a1ba 100644 --- a/.github/actions/ci-filter/ci-filter.mjs +++ b/.github/actions/ci-filter/ci-filter.mjs @@ -143,8 +143,8 @@ export function getAddedFiles(baseRef, headRef = 'HEAD') { } /** - * Deepen the shallow clone until the merge base between the fetched base ref - * (FETCH_HEAD) and headRef is reliably reachable. + * Fetch the base ref, then deepen only if its merge base with headRef is not + * reliably reachable from the checkout. * * A single fixed deepen is not enough for stale PRs whose divergence point is * older than the shallow boundary. We fetch with an exponentially growing @@ -159,7 +159,12 @@ export function getAddedFiles(baseRef, headRef = 'HEAD') { function fetchUntilMergeBase(baseRef, headRef) { let step = Number(process.env.CI_FILTER_DEEPEN_STEP) || 200; const maxDeepen = Number(process.env.CI_FILTER_MAX_DEEPEN) || 20_000; - deepenFetch(baseRef, step, maxDeepen); + // A PR merge checkout already has the base commit as its first parent. + const hasBaseCommit = headRef === 'HEAD^2' && hasCommit('HEAD^1'); + const depth = isShallow() && !hasBaseCommit ? `--depth=${step} ` : ''; + execSync(`git fetch --no-tags --prune --filter=blob:none ${depth}origin ${baseRef}`, { + stdio: 'pipe', + }); while (!hasReliableMergeBase(headRef)) { if (!isShallow()) { @@ -167,8 +172,17 @@ function fetchUntilMergeBase(baseRef, headRef) { `No merge base between FETCH_HEAD and ${headRef} after fetching the full history of "${baseRef}" (unrelated histories).`, ); } - step *= 2; deepenFetch(baseRef, step, maxDeepen); + step *= 2; + } +} + +function hasCommit(ref) { + try { + execSync(`git cat-file -e ${ref}^{commit}`, { stdio: 'pipe' }); + return true; + } catch { + return false; } } diff --git a/.github/workflows/ci-pull-requests.yml b/.github/workflows/ci-pull-requests.yml index 9f51b6dd9b1..007f0baf57f 100644 --- a/.github/workflows/ci-pull-requests.yml +++ b/.github/workflows/ci-pull-requests.yml @@ -45,6 +45,8 @@ jobs: with: # Use merge_group SHA when in merge queue, otherwise PR merge ref ref: ${{ github.event_name == 'merge_group' && github.event.merge_group.head_sha || format('refs/pull/{0}/merge', github.event.pull_request.number) }} + # Include recent history so the PR filter can reuse the checkout. + fetch-depth: 200 - name: Capture commit SHA for cache consistency id: commit-sha @@ -124,7 +126,6 @@ jobs: packages/frontend/@n8n/chat/** packages/frontend/@n8n/frontend-vite-config/** turbo.json - .github/workflows/ci-pull-requests.yml .github/workflows/test-frontend-declarations-reusable.yml .github/actions/setup-nodejs/** workflows: .github/** @@ -151,6 +152,7 @@ jobs: # data plane, and the executions code merges v1 and v2 rows. A path # this list misses skips `e2e-engine`, and `required-checks` counts # a skipped job as a pass, so the gap would be silent. + # Use force-engine-e2e when changing the engine job in this workflow. engine-e2e: packages/@n8n/engine/** packages/@n8n/node-engine-compatibility/** @@ -163,7 +165,6 @@ jobs: packages/cli/src/workflow-runner.ts packages/testing/containers/** packages/testing/playwright/** - .github/workflows/ci-pull-requests.yml instance-ai-workflow-eval: packages/@n8n/instance-ai/src/** packages/@n8n/instance-ai/evaluations/** @@ -224,10 +225,6 @@ jobs: if: fromJSON(steps.ci-filter.outputs.results).ci run: node scripts/turbo-critical-path.mjs >> "$GITHUB_STEP_SUMMARY" - - name: Run format check - if: fromJSON(steps.ci-filter.outputs.results).ci - run: pnpm format:check - - name: Generate shard matrix id: generate-matrix if: fromJSON(steps.ci-filter.outputs.results).ci || fromJSON(steps.ci-filter.outputs.results).e2e @@ -262,11 +259,34 @@ jobs: echo "Affected packages: $PACKAGES" echo "list=$PACKAGES" >> "$GITHUB_OUTPUT" + format-check: + name: Format check + runs-on: ${{ vars.RUNNER_PROVIDER == 'github' && 'ubuntu-latest' || 'blacksmith-2vcpu-ubuntu-2204' }} + permissions: + contents: read + steps: + - uses: useblacksmith/checkout@bcec731f1eb1367240608d1c889a75b96db6ec53 # v1.5.0 + with: + ref: ${{ github.event_name == 'merge_group' && github.event.merge_group.head_sha || format('refs/pull/{0}/merge', github.event.pull_request.number) }} + + - name: Install format tools + uses: ./.github/actions/setup-nodejs + with: + install-command: pnpm install --frozen-lockfile --filter n8n-monorepo --ignore-scripts + build-command: '' + # Keep the partial pnpm store separate from the full install cache. + cache-dependency-path: | + pnpm-lock.yaml + package.json + + - name: Run format check + run: pnpm format:check + unit-test: name: Unit tests if: needs.install-and-build.outputs.unit == 'true' uses: ./.github/workflows/test-unit-reusable.yml - needs: install-and-build + needs: [install-and-build, format-check] with: ref: ${{ needs.install-and-build.outputs.commit_sha }} collectCoverage: ${{ github.event_name != 'merge_group' }} @@ -278,7 +298,7 @@ jobs: name: Typecheck if: needs.install-and-build.outputs.ci == 'true' runs-on: ${{ vars.RUNNER_PROVIDER == 'github' && 'ubuntu-latest' || 'blacksmith-4vcpu-ubuntu-2204' }} - needs: install-and-build + needs: [install-and-build, format-check] env: # Inherited by every worker turbo spawns, so the real budget is # concurrency x cap: 2 x 6 GB fits the runner's 15.2 GB. @@ -299,7 +319,7 @@ jobs: name: Lint if: needs.install-and-build.outputs.ci == 'true' uses: ./.github/workflows/test-linting-reusable.yml - needs: install-and-build + needs: [install-and-build, format-check] with: ref: ${{ needs.install-and-build.outputs.commit_sha }} @@ -307,7 +327,7 @@ jobs: name: Check packaging if: needs.install-and-build.outputs.ci == 'true' runs-on: ${{ vars.RUNNER_PROVIDER == 'github' && 'ubuntu-latest' || 'blacksmith-4vcpu-ubuntu-2204' }} - needs: install-and-build + needs: [install-and-build, format-check] steps: - uses: useblacksmith/checkout@bcec731f1eb1367240608d1c889a75b96db6ec53 # v1.5.0 with: @@ -326,7 +346,7 @@ jobs: # cache hit instead of racing to rebuild. prepare-docker: name: Prepare Docker - needs: install-and-build + needs: [install-and-build, format-check] if: >- github.repository == 'n8n-io/n8n' && github.event_name != 'merge_group' && @@ -394,7 +414,7 @@ jobs: # the production-bundle e2e job bundles around. dev-server-smoke: name: Dev-server boot smoke - needs: install-and-build + needs: [install-and-build, format-check] if: needs.install-and-build.outputs.dev_server_smoke == 'true' && github.event_name != 'merge_group' uses: ./.github/workflows/test-dev-server-smoke-reusable.yml with: @@ -407,7 +427,7 @@ jobs: # release. frontend-declarations: name: Frontend declarations - needs: install-and-build + needs: [install-and-build, format-check] if: needs.install-and-build.outputs.frontend_declarations == 'true' uses: ./.github/workflows/test-frontend-declarations-reusable.yml with: @@ -415,7 +435,7 @@ jobs: db-tests: name: DB Tests - needs: install-and-build + needs: [install-and-build, format-check] if: needs.install-and-build.outputs.db == 'true' uses: ./.github/workflows/test-db-reusable.yml with: @@ -440,7 +460,7 @@ jobs: security-checks: name: Security Checks - needs: install-and-build + needs: [install-and-build, format-check] if: needs.install-and-build.outputs.workflows == 'true' uses: ./.github/workflows/sec-ci-reusable.yml with: @@ -449,7 +469,7 @@ jobs: workflow-scripts: name: Workflow scripts - needs: install-and-build + needs: [install-and-build, format-check] if: needs.install-and-build.outputs.workflow_scripts == 'true' uses: ./.github/workflows/test-workflow-scripts-reusable.yml with: @@ -461,10 +481,11 @@ jobs: # heavy workflow eval. Non-blocking initially; promote to required after stability. instance-ai-discovery-evals: name: Instance AI Discovery Evals - needs: install-and-build + needs: [install-and-build, format-check] if: >- !cancelled() && needs.install-and-build.result == 'success' && + needs.format-check.result == 'success' && needs.install-and-build.outputs.instance_ai_workflow_eval == 'true' && github.repository == 'n8n-io/n8n' && github.event_name != 'merge_group' && @@ -481,6 +502,7 @@ jobs: needs: [ install-and-build, + format-check, unit-test, typecheck, lint,