GitHub Action- Rover Lint Command

Aim

  • To set up a GitHub Action that automatically runs the rover lint command whenever any .graphql file is added or modified in the repository. This ensures that all GraphQL schema and operation files are validated for correctness and style compliance as part of the development workflow.

  • Command: rover subgraph lint --name "$SUBGRAPH_NAME" --schema "$file" $GRAPH_REF

Steps

  • We need to add the YAML file in below path-

    .repo-root/

    └── .github/

    └── workflows/

    └── graphql-lint.yml ✅ (This is where your workflow file goes)

  • Please find below the YAML file, which I’ve added-

name: Lint GraphQL Subgraphs

on:
  push:
    paths:
      - '**/*.graphql'
  pull_request:
    paths:
      - '**/*.graphql'

jobs:
  lint-subgraphs:
    runs-on: ubuntu-latest

    steps:
      # Step 1: Checkout the repo so we can access the files
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      # Step 2: Install Rover CLI (official tool from Apollo)
      - name: Install Rover CLI
        run: |
          curl -sSL https://rover.apollo.dev/nix/latest | sh
          echo "$HOME/.rover/bin" >> $GITHUB_PATH

      # Step 3: Find all changed .graphql files in this push/PR
      - name: Get list of changed .graphql files
        id: changed-files
        run: |
          echo "Finding changed .graphql files..."
          # List changed files from the last commit or PR
          CHANGED_FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep '\.graphql$' || true)
          echo "Changed files: $CHANGED_FILES"

          # Save the list as an output (newline separated)
          echo "files<<EOF" >> $GITHUB_OUTPUT
          echo "$CHANGED_FILES" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

      # Step 4: Lint each changed .graphql file using rover subgraph lint
      - name: Lint changed subgraph schemas with Rover
        if: steps.changed-files.outputs.files != ''
        env:
          APOLLO_KEY: ${{ secrets.APOLLO_KEY }}
          GRAPH_REF: "sfcc-pro-7uddgfq@current"  # Change this to your actual graph ref
        run: |
          set -e
          echo "Linting subgraphs..."

          while IFS='' read -r file; do
            echo "Linting file: $file"

            # Extract subgraph name from filename, example: products-schema.graphql -> products
            # Adjust this if your naming differs
            BASENAME=$(basename "$file")
            SUBGRAPH_NAME=${BASENAME%.graphql}

            echo "Running rover subgraph lint for subgraph: $SUBGRAPH_NAME"
            rover subgraph lint --name "$SUBGRAPH_NAME" --schema "$file" $GRAPH_REF

          done <<< "${{ steps.changed-files.outputs.files }}"

Detailed Explanation of YAML File

name: Lint GraphQL Subgraphs

name:
The name of the workflow. This shows up in GitHub under the "Actions" tab.
➤ Here, it's labeled as "Lint GraphQL Subgraphs".

on:
  push:
    paths:
      - '**/*.graphql'
  pull_request:
    paths:
      - '**/*.graphql'

on:

Defines the trigger — when the workflow should run.

  • push:
    Runs the workflow when someone pushes code to the repository.

  • pull_request:
    Runs the workflow when someone opens or updates a pull request.

  • paths:
    Specifies the files to watch. The workflow will only trigger if these files are affected.

    • '**/*.graphql'
      This is a glob pattern.

      • **/ means any folder, any level deep

      • *.graphql means any file ending in .graphql
        ➤ So this matches all GraphQL files anywhere in the repo.

jobs:
  lint-subgraphs:
    runs-on: ubuntu-latest

jobs:

Defines a list of tasks (jobs) that will run.

  • lint-subgraphs:
    A custom name for this specific job. You can name it anything.

  • runs-on:
    This tells GitHub what operating system to run the job on.
    ➤ Here, we use a fresh Ubuntu Linux virtual machine.

    steps:
  • This begins the list of steps inside the job. Steps are executed one by one.
      # Step 1: Checkout the repo so we can access the files
      - name: Checkout repository
        uses: actions/checkout@v4
        with:
          fetch-depth: 0
  • - name:
    Describes what this step does. This label appears in the GitHub Actions UI.

  • uses:
    This uses an existing GitHub Action (actions/checkout) from the GitHub Marketplace.

  • @v4
    Version number of the action. It's best practice to use pinned versions.

  • with:
    Provides additional inputs/arguments for the action.

    • fetch-depth: 0
      Tells Git to clone the full commit history (needed for comparing commits to find changed files).
      # Step 2: Install Rover CLI (official tool from Apollo)
      - name: Install Rover CLI
        run: |
          curl -sSL https://rover.apollo.dev/nix/latest | sh
          echo "$HOME/.rover/bin" >> $GITHUB_PATH
  • run:
    Executes shell commands.
    The | means you're writing a multi-line shell script.

  • curl -sSL URL | sh
    Downloads and installs the Rover CLI.

  • echo "$HOME/.rover/bin" >> $GITHUB_PATH
    Adds the Rover CLI path to the system PATH, so it can be used in later steps.

    It adds the "$HOME/.rover/bin" directory to the PATH environment variable in GitHub Actions, making executables in that directory accessible in subsequent steps of the workflow.

      # Step 3: Find all changed .graphql files in this push/PR
      - name: Get list of changed .graphql files
        id: changed-files
        run: |
          echo "Finding changed .graphql files..."
          # List changed files from the last commit or PR
          CHANGED_FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep '\.graphql$' || true)
          echo "Changed files: $CHANGED_FILES"

          # Save the list as an output (newline separated)
          echo "files<<EOF" >> $GITHUB_OUTPUT
          echo "$CHANGED_FILES" >> $GITHUB_OUTPUT
          echo "EOF" >> $GITHUB_OUTPUT

1. - name: Get list of changed .graphql files

This defines a step in your GitHub Actions workflow, and it gives the step a name: "Get list of changed .graphql files". The name is just for reference in your workflow logs so you know what the step is doing.

2. id: changed-files

  • This gives the step an ID (changed-files). The ID allows you to refer to the outputs of this step later in the workflow. For example, you can use ${{ steps.changed-files.outputs.files }} to access the output from this step in later steps.

3. run: |

  • run is used to specify the shell script (or commands) that GitHub Actions will execute during this step.

  • The | symbol indicates that the commands below will be executed in a multi-line format (i.e., the script continues on the following lines).

4. echo "Finding changed .graphql files..."

  • This simply prints the message "Finding changed .graphql files..." to the log, so you can see in the workflow logs that the process has started.

5. CHANGED_FILES=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | grep '\.graphql$' || true)

  • git diff --name-only ${{ github.event.before }} ${{ github.sha }}:

    • This command gets the list of files that were changed between two commits or git references: ${{ github.event.before }} (the commit hash before the event, like the last commit or PR) and ${{ github.sha }} (the commit hash of the current commit).

    • git diff --name-only lists only the names of the files that were changed, not their content.

  • | grep '\.graphql$':

    • The pipe (|) sends the list of changed files to the grep command.

    • grep '\.graphql$' filters the list to only include files that end with .graphql, essentially looking for GraphQL schema/query files.

  • || true:

    • This ensures that even if there are no .graphql files (or grep doesn’t find any matches), the command will not fail. It forces the command to succeed by running true if grep finds nothing.
  • CHANGED_FILES=...:

    • The list of changed .graphql files (if any) is saved in the variable CHANGED_FILES.

6. echo "Changed files: $CHANGED_FILES"

  • This command prints out the list of changed .graphql files to the log so you can see which files were detected as changed.

7. Saving the list as an output:

The next few lines are about saving the list of changed .graphql files as an output for use in later steps of the workflow.

  • echo "files<<EOF" >> $GITHUB_OUTPUT:

    • This syntax is used to create a named output. In this case, it’s setting the output key as files.

    • <<EOF is a special syntax in shell that creates a "here document," which lets you append multiline content to a file.

  • echo "$CHANGED_FILES" >> $GITHUB_OUTPUT:

    • This appends the list of changed .graphql files (stored in the CHANGED_FILES variable) to the output file, which is $GITHUB_OUTPUT. $GITHUB_OUTPUT is a special file used by GitHub Actions to store output values from a step.
  • echo "EOF" >> $GITHUB_OUTPUT:

    • This marks the end of the multiline output, completing the "here document" that was started with <<EOF.
      # Step 4: Lint each changed .graphql file using rover subgraph lint
      - name: Lint changed subgraph schemas with Rover
        if: steps.changed-files.outputs.files != ''
        env:
          APOLLO_KEY: ${{ secrets.APOLLO_KEY }}
          GRAPH_REF: "sfcc-pro-7uddgfq@current"  # Change this to your actual graph ref
        run: |
          set -e
          echo "Linting subgraphs..."

          while IFS='' read -r file; do
            echo "Linting file: $file"

            # Extract subgraph name from filename, example: products-schema.graphql -> products
            # Adjust this if your naming differs
            BASENAME=$(basename "$file")
            SUBGRAPH_NAME=${BASENAME%.graphql}

            echo "Running rover subgraph lint for subgraph: $SUBGRAPH_NAME"
            rover subgraph lint --name "$SUBGRAPH_NAME" --schema "$file" $GRAPH_REF

          done <<< "${{ steps.changed-files.outputs.files }}"

1. run: |:

  • This defines a multi-line shell script in GitHub Actions, specifying the commands that will be executed in this step. The | symbol allows the script to span multiple lines.

2. set -e:

  • set -e is a shell option that tells the shell to exit immediately if any command fails (i.e., if any command returns a non-zero exit code). This is useful in CI/CD pipelines to stop further execution when a failure occurs. It ensures that if there's an error in the linting process, the entire job stops.

3. echo "Linting subgraphs...":

  • This prints the message "Linting subgraphs..." to the console. It gives feedback in the workflow logs, indicating that the linting process has started.

4. while IFS='' read -r file; do:

  • while: This starts a loop that reads each line of input one at a time.

  • IFS='': This sets the Internal Field Separator (IFS) to an empty string, which prevents any word splitting. By default, IFS is set to space, tab, and newline, so setting it to '' makes sure that the lines are read exactly as they are without any unwanted splitting.

  • read -r file: The read command reads input line by line and stores each line in the variable file. The -r flag prevents backslashes from being treated as escape characters, ensuring the input is read literally.

  • done: This marks the end of the while loop.

The loop will continue until all lines of input are read, which, in this case, are the files listed in the ${{ steps.changed-files.outputs.files }} output (explained later).

5. echo "Linting file: $file":

  • This prints the message "Linting file: $file", where $file is the current line being processed. This gives feedback on the file that is currently being linted.

6. BASENAME=$(basename "$file"):

  • basename "$file": The basename command strips the directory path from a filename, leaving just the filename itself.

    • For example, if $file is /path/to/products-schema.graphql, the basename command will return products-schema.graphql.
  • The result is stored in the variable BASENAME.

7. SUBGRAPH_NAME=${BASENAME%.graphql}:

  • This line extracts the subgraph name from the BASENAME variable, assuming the file follows a certain naming convention.

    • ${BASENAME%.graphql}: This syntax removes the .graphql suffix from the BASENAME variable.

      • For example, if BASENAME is products-schema.graphql, this will result in products-schema.
    • The subgraph name is then saved into the variable SUBGRAPH_NAME.

8. echo "Running rover subgraph lint for subgraph: $SUBGRAPH_NAME":

  • This prints a message indicating that the linting process is being run for the specific subgraph.

    • For example, if $SUBGRAPH_NAME is products, the message would be: "Running rover subgraph lint for subgraph: products".

9. rover subgraph lint --name "$SUBGRAPH_NAME" --schema "$file" $GRAPH_REF:

  • This command runs Rover, which is a command-line tool for interacting with Apollo Studio’s subgraphs. The subgraph lint command checks the given schema file for any errors or issues.

Let's break down the arguments:

  • --name "$SUBGRAPH_NAME": This specifies the name of the subgraph being linted. It's set to the value stored in the SUBGRAPH_NAME variable (e.g., products).

  • --schema "$file": This specifies the schema file to lint. The $file variable contains the path to the schema file being processed.

  • $GRAPH_REF: This variable contains a reference to the graph, which is typically something like the version or ID of the schema in Apollo Studio. It's passed as an argument to specify which graph to lint against. This could be set earlier in the workflow or passed as an environment variable.

10. done <<< "${{ steps.changed-files.outputs.files }}":

  • <<<: This is a here-string in bash, which allows the output of a variable or command to be passed directly as input to a command. In this case, the output of ${{ steps.changed-files.outputs.files }} is passed to the while loop.

  • ${{ steps.changed-files.outputs.files }}: This refers to the output of a previous step in the GitHub Actions workflow. In this case, it’s the list of changed files from the step changed-files. The files are expected to be newline-separated.

    • This is the list of .graphql files that were changed in the last commit or pull request, and the loop processes each of them one by one.

Special Symbols and Syntax

-Starts a list item (step, path, etc.)
:Separates keys and values
${{ }}GitHub Actions expression syntax (used to inject dynamic values)
>>Appends to a file or output
EOFMarker for multi-line variable content
!=“Not equal” in GitHub expressions
<<Start of a multi-line value block (used in output or HEREDOC)

Visual Representation of the flow

  1. fetch-depth:

In Git, the fetch-depth parameter is used to limit the history when you clone or fetch a repository. This is especially common in CI/CD pipelines (like GitHub Actions, GitLab CI, etc.) to speed up build times by avoiding a full repository history download.

ValueMeaning
1Shallow clone. Only fetch the latest commit (no history).
>1Fetch the latest N commits. Example: fetch-depth: 10 gets last 10.
0 or omittedFull clone. All history and commits are fetched.

NOTE: When using the actions/checkout action without explicitly setting fetch-depth, the default value is 1.

  1. curl -sSL:

In the curl command, the -sSL flag is a combination of several options:

  • -s stands for silent. It makes curl run without showing progress or error messages, which can be useful when you want a clean output (like when scripting).

  • -S stands for show error. It tells curl to show an error message if something goes wrong, even when the -s (silent) option is used. Without this, curl would fail silently.

  • -L stands for location. It tells curl to follow any redirects. If the URL responds with a redirect (like a 301 or 302), curl will automatically follow the new URL.

So, curl -sSL means "run silently, but show errors if they occur, and follow redirects."

  1. | sh:

The | sh part of a command means that the output of the command before the pipe (|) is being passed as input to the sh command, which is a shell interpreter.

In simpler terms:

  • The | is a pipe, which takes the output of the command on the left side and sends it as input to the command on the right side.

  • sh is a shell command (usually the Bourne shell) that executes shell commands.

  1. | tr -d '\n' :

The tr -d '\n' command is used to delete newline characters (\n) from the input text.

  1. ls -ltrah :

ls This is the basic command to list files and directories in the current directory (or any specified directory).

-l (long listing format)

-t (sort by modification time)

-r (reverse order)

-a (show hidden files)

-h (human-readable file sizes)

  1. tree . :

The command tree . is used to display the directory structure in a tree-like format, starting from the current directory (.).

0
Subscribe to my newsletter

Read articles from Aditya Dev Shrivastava directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Aditya Dev Shrivastava
Aditya Dev Shrivastava