Running the same SQL checks in a browser, CLI and pull request

typescript dev.to

I wanted one set of SQL checks to work in three places: while exploring a query, from a terminal and during code review.

That became SQL Atlas. It is a local, deterministic SQL analyzer with a browser interface, a CLI and a GitHub Action. This article covers the interfaces, the CI contract and the limits of static SQL analysis.

One analyzer, three interfaces

The analyzer returns structured data instead of printing messages directly. Each interface decides how to present the same result:

  • The browser explains findings and links them to learning material.
  • The CLI returns text, JSON or Markdown and uses stable exit codes.
  • The GitHub Action converts findings into file annotations and a job summary.

Keeping presentation outside the analyzer prevents the CLI and Action from becoming separate implementations with different behavior.

A CLI needs a contract

The CLI accepts one or more files, or SQL through standard input:

npx --yes sql-atlas@0.5.1 analyze query.sql
Enter fullscreen mode Exit fullscreen mode
echo "SELECT * FROM customers;" | npx --yes sql-atlas@0.5.1 analyze -
Enter fullscreen mode Exit fullscreen mode

It supports PostgreSQL, MySQL, Oracle, SQLite, SQL Server and a generic mode. Output can be text for a person, JSON for another program or Markdown for an issue or report.

Exit codes are part of the interface:

  • 0 means analysis completed and the configured policy passed.
  • 1 means analysis completed but a severity or score threshold failed.
  • 2 means the command or input was invalid.

This distinction matters in CI. A policy failure is not the same as a broken invocation.

Turning findings into pull request feedback

The Action runs as a bundled Node 24 program and does not download dependencies at runtime. A minimal workflow looks like this:

name: SQL review

on:
  pull_request:
    paths:
      - "**/*.sql"

permissions:
  contents: read

jobs:
  sql-atlas:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: milekv/sql-atlas@v0.5.1
        with:
          paths: |
            migrations/**/*.sql
            schema/**/*.sql
          dialect: postgresql
          fail-on: critical
          min-score: 60
Enter fullscreen mode Exit fullscreen mode

Findings become GitHub file annotations. The full result is written to the job summary, and the Action exposes file count, finding count and lowest score as outputs.

The default policy only fails on critical findings. Teams can start in report-only mode with fail-on: none, inspect false positives and add stricter thresholds later.

What static analysis cannot know

SQL Atlas does not connect to a database. It cannot know table sizes, data distribution, available indexes, planner settings or the real execution plan.

For that reason, a warning such as a function applied to a filtered column means "check whether this blocks the index strategy you expect", not "this query is slow". Runtime performance still needs EXPLAIN, representative data and production-like measurements.

The browser includes a local PostgreSQL EXPLAIN JSON viewer for that next step, but the analyzer deliberately keeps its claims narrow.

Testing the distribution surfaces

The project tests the analyzer and both automation interfaces. CI builds the web app, CLI and Action bundle. A smoke workflow runs the repository's own Action against a known SQL file and verifies its outputs. CI also rebuilds the committed Action bundle and checks that it has no uncommitted difference.

The CLI package has no runtime dependencies. I verified the public npm package from an empty directory with a clean cache, including the executable version and a real stdin analysis.

Try it

I am particularly interested in examples where a rule is too broad, misses a dialect detail or produces an unhelpful CI annotation.

Source: dev.to

arrow_back Back to Tutorials