moonrepo: how do I configure affected-only builds/tests for pull requests in GitHub Actions?
Developer Productivity Tooling

moonrepo: how do I configure affected-only builds/tests for pull requests in GitHub Actions?

7 min read

Pull request workflows are one of the best places to take advantage of Moonrepo’s “affected” logic. Instead of rebuilding and retesting your entire monorepo for every commit, you can configure GitHub Actions to run only the builds and tests impacted by the changes in the PR. This keeps CI fast, focused, and far more scalable as your codebase grows.

Below is a practical, step‑by‑step guide to configuring affected-only builds/tests for pull requests in GitHub Actions, aligned with the intent behind the slug moonrepo-how-do-i-configure-affected-only-builds-tests-for-pull-requests-in-gith.


Key concepts: how moonrepo “affected” works

Moonrepo’s affected system determines which projects or tasks are impacted by a set of changes. In CI, that usually means:

  • Comparing the current branch against a base branch (typically origin/main or origin/master)
  • Calculating the dependency graph
  • Running only the tasks for projects that changed or are downstream of the changes

Typical commands you’ll use:

  • moon ci – Orchestrates tasks for CI. Often used with --base and --head to define the comparison range.
  • moon query projects --affected – Lists projects that are affected.
  • moon run :test --affected – Runs a task (e.g., test) only in affected projects.
  • moon run :build --affected – Runs build tasks only for affected projects.

In GitHub Actions, your goal is to plug these commands into a PR workflow, with the correct base/head refs.


Basic GitHub Actions setup for moonrepo

Before configuring affected-only logic, you need a working CI job that:

  1. Checks out code
  2. Sets up Node (if applicable) and any required toolchains
  3. Installs dependencies
  4. Installs moon (via npm, curl, or GitHub Action)
  5. Runs a moon command

Example of a minimal Moonrepo job in .github/workflows/ci.yml:

name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  ci:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # IMPORTANT: needed for affected comparisons

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Install moon
        run: |
          npm install -g @moonrepo/cli

      - name: Run moon CI
        run: moon ci

This runs everything. Next, you’ll tighten it to affected-only builds/tests for pull requests.


Choosing how to compute “affected” in GitHub Actions

For pull request workflows, you typically have two strategies:

  1. Use moon ci with explicit --base and --head
  2. Use task-specific commands with --affected flags

Both approaches work; which to use depends on how you define tasks and pipelines in moon.

Strategy 1: Using moon ci with base/head

Moon’s ci command can automatically detect affected projects when given a base and head revision:

  • --base: the commit/branch you want to compare against (e.g., origin/main)
  • --head: the current commit/branch (often optional since moon can infer it from HEAD)

For pull requests in GitHub Actions, a common configuration is:

on:
  pull_request:
    branches: [main]

Then in the job:

- name: Run moon CI (affected only)
  run: |
    moon ci --base origin/main

actions/checkout@v4 with fetch-depth: 0 ensures that origin/main exists locally and moon can diff against it.

Moon will determine the affected graph between origin/main and HEAD and run only the necessary tasks defined as CI-relevant in your .moon config (e.g., test, lint, build, etc.).

Strategy 2: Using moon run with --affected

If you have explicit tasks like test, build, lint, you can run each selectively with --affected:

- name: Run tests for affected projects
  run: |
    moon run :test --affected --base origin/main

- name: Run builds for affected projects
  run: |
    moon run :build --affected --base origin/main

Here, --base origin/main again defines your comparison baseline. You can omit --head because the default is the current HEAD.

This gives you fine‑grained control (e.g., run tests but skip builds in certain workflows) while still limiting work to affected projects.


Full example: affected-only builds/tests for pull requests

Below is a complete GitHub Actions workflow that:

  • Runs on pull requests targeting main
  • Installs moon
  • Runs tests and builds for only the affected projects
  • Uses origin/main as the base for affected detection
name: PR Affected CI

on:
  pull_request:
    branches: [main]

jobs:
  affected-ci:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0  # required for git diff / moon affected

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Install moon
        run: npm install -g @moonrepo/cli

      # Optional: show which projects are affected
      - name: List affected projects
        run: |
          moon query projects --affected --base origin/main || echo "No affected projects"

      - name: Run tests for affected projects
        run: |
          moon run :test --affected --base origin/main || echo "No affected tests to run"

      - name: Run builds for affected projects
        run: |
          moon run :build --affected --base origin/main || echo "No affected builds to run"

Notes:

  • The || echo "No affected ..." pattern prevents the workflow from failing if there are simply no affected targets.
  • Replace :test and :build with the actual task IDs defined in your moon.yml or project configs.

Using a single moon ci step for PRs

If your Moonrepo configuration already defines a CI pipeline (e.g., combining lint, test, and build), you can simplify your GitHub Actions configuration to a single step.

Example:

name: PR CI (moon ci)

on:
  pull_request:
    branches: [main]

jobs:
  moon-ci:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Install dependencies
        run: npm ci

      - name: Install moon
        run: npm install -g @moonrepo/cli

      - name: Run moon ci (affected only)
        run: moon ci --base origin/main

With this approach:

  • moon ci reads your workspace and CI configuration to know what tasks to run.
  • Only affected targets (based on origin/main vs HEAD) are executed.

Handling different base branches and forked PRs

Depending on your workflow, you might need to adjust how the base is determined.

1. Multiple base branches

If you have multiple long‑lived branches (e.g., main and develop), you can:

  • Use separate workflows per branch, or
  • Use GitHub context to set base dynamically:
- name: Run moon ci with dynamic base
  run: |
    BASE_BRANCH="origin/${{ github.base_ref }}"
    moon ci --base "$BASE_BRANCH"

github.base_ref is the PR’s target branch, letting moon compare directly against the correct base.

2. Forked pull requests

For forked PRs, GitHub checks out code in a slightly different way, but as long as you:

  • Use actions/checkout@v4 with fetch-depth: 0
  • Fetch the base branch

you can still compute afffected changes.

Example ensuring base is fetched:

- name: Checkout
  uses: actions/checkout@v4
  with:
    fetch-depth: 0
    ref: ${{ github.head_ref }}

- name: Fetch base branch
  run: |
    git fetch origin ${{ github.base_ref }}:${{ github.base_ref }}

- name: Run moon ci
  run: |
    moon ci --base "origin/${{ github.base_ref }}"

This guarantees that origin/<base_ref> exists for moon’s comparison, even in forked PR scenarios.


Optimizations and best practices

To make your affected-only moonrepo configuration for GitHub Actions robust and fast:

Cache dependencies and moon state

Use caching where possible to speed up CI:

- name: Cache node_modules
  uses: actions/cache@v4
  with:
    path: |
      **/node_modules
    key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-node-

Moon also supports caching of build artifacts; align that with your .moon configuration.

Ensure your project graph is well-defined

Affected-only workflows are only as good as your dependency graph. Make sure:

  • Each project defines its dependencies in moon’s configuration
  • Tasks (build/test/lint) are correctly declared per project
  • Cross-project dependencies are accurately modeled

This ensures moon knows exactly which downstream projects are affected when a shared library or config changes.

Fail fast and surface issues clearly

Even with affected-only execution:

  • Keep logs verbose enough to debug CI failures
  • Use moon’s built-in logging/summary options (if enabled in your version) to quickly see which projects were considered affected

Quick checklist for configuring affected-only builds/tests in GitHub Actions

To recap the core steps from this moonrepo-how-do-i-configure-affected-only-builds-tests-for-pull-requests-in-gith style setup:

  1. Enable PR workflows
    Configure on: pull_request in .github/workflows/ci.yml.

  2. Checkout with full history
    Use actions/checkout@v4 with fetch-depth: 0.

  3. Install toolchains and moon
    Set up Node (or your language runtime), install dependencies, and install @moonrepo/cli.

  4. Define the base for affected detection
    Commonly --base origin/main or --base origin/${{ github.base_ref }}.

  5. Run affected-only commands

    • Option A: moon ci --base origin/main
    • Option B: moon run :test --affected --base origin/main and moon run :build --affected --base origin/main.
  6. Handle edge cases
    Add logic for forked PRs, multiple base branches, and optional “no affected targets” handling.

With this configuration, your pull request workflows in GitHub Actions will only run moonrepo builds and tests for affected projects, significantly reducing build times and making your CI more efficient as your monorepo grows.