Equall

API

Programmatic API for embedding the Equall scanner in another Node tool — runScan, scanBuffer, runDiffScan and the public TypeScript types.

The equall-cli package exposes a small ESM API so you can embed the scanner inside a custom CI script, a pre-commit runner, or a dashboard backend. Everything the CLI does is reachable through runScan().

Looking for the equall binary? Flag-by-flag usage lives on Scanning, suppression comments on Ignore system, and setup on Install. This page is only about the programmatic surface.

Module format

equall-cli is shipped as an ESM-only package with a single root export plus .d.ts declarations. The minimum supported runtime is Node ≥ 20.

package.json (consumer)
{
  "type": "module",
  "dependencies": { "equall-cli": "^0.3.3" }
}

If you must call it from a CommonJS file, use a dynamic await import('equall-cli').

Example

scan.mjs
import { runScan } from 'equall-cli'

const result = await runScan({
  path: './my-project',
  level: 'AA',
})

console.log(`Score: ${result.score} (${result.conformance_level})`)

for (const issue of result.issues) {
  if (issue.ignored) continue
  console.log(
    `${issue.file_path}:${issue.line ?? '?'}  ${issue.scanner_rule_id}  — ${issue.message}`,
  )
}

The full shape of the returned ScanResult document is documented on Output format.

Public exports

runScan(options)

Discovers files, runs every available scanner in parallel, deduplicates findings — including merging equivalent findings from different engines into a single issue (see the scanners field on Output format) — applies equall-ignore comments, and returns a fully scored ScanResult.

function runScan(options?: RunScanOptions): Promise<ScanResult>

interface RunScanOptions {
  path?: string                  // Project root. Default: process.cwd()
  level?: 'A' | 'AA' | 'AAA'     // WCAG target. Default: 'AA'
  standard?: 'wcag22' | 'wcag21' // WCAG version view. Default: 'wcag22'
  include?: string[]             // Glob patterns to include
  exclude?: string[]             // Glob patterns to exclude
  disableScanners?: string[]     // Scanner names to skip, e.g. ['readability']
  files?: FileInput[]            // In-memory input — scan these buffers instead of disk
  keepOccurrences?: boolean      // Used by runDiffScan; leave unset (see below)
}

interface FileInput {
  path: string                   // Relative path; the extension drives file-type detection
  content: string                // File contents
}

The path, level, include, and exclude options mirror the CLI flags one-to-one — see Scanning for their semantics. standard selects the conformance view — wcag22 (default) or wcag21 (the public-sector legal bar, WAD/EN 301 549) — and never changes the returned score. Pass files to scan source held in memory instead of discovering files on disk — useful for editor integrations and pre-commit tools. When files is set, on-disk discovery is bypassed.

keepOccurrences skips the cross-engine merge and the deduplication, so every occurrence is returned as reported. runDiffScan uses it to compare two versions occurrence by occurrence; a normal scan should leave it unset.

runScan never writes to your stdout or stderr, and never throws on individual scanner failures. A scanner that crashes is recorded on result.diagnostics, left out of scanners_used, and its criteria are not credited as tested; the run continues with the others. A file a scanner could not parse or analyse is listed on result.unchecked (and in plain text on diagnostics), so its missing findings are never read as a pass. runScan can throw if the root path is unreadable, so wrap the call in try/catch if you need to surface that as a domain error.

Disk scans also detect the project's routes (result.routes); in-memory scans skip route detection and say so on diagnostics. See Output format.

scanBuffer(content, filename, options?)

Scans a single file held in memory and returns a full ScanResult — no disk access. A thin wrapper over runScan({ files: [{ path: filename, content }] }). The filename extension drives file-type detection, so pass a real one (e.g. Button.tsx).

function scanBuffer(
  content: string,
  filename: string,
  options?: Omit<RunScanOptions, 'files' | 'path'>,
): Promise<ScanResult>

runDiffScan(options)

Scans the change between two Git refs and reports only the issues the change introduced — the "only-new" view. New is told from pre-existing by a content fingerprint (not line numbers), so a reformat-only change yields zero false "new".

Each changed file is scanned at head and at the merge-base, and occurrences are counted per fingerprint: the first N at head match the N at the base (legacy), any extra one is new. The comparison is per file path, over the whole file, not per hunk. Renames are not tracked, so moved code counts as new.

function runDiffScan(options: DiffScanOptions): Promise<DiffScanResult>

interface DiffScanOptions {
  base: string                   // Git ref to diff against (validated; never shell-interpolated)
  head?: string                  // Git ref for the new state. Default: 'HEAD'
  cwd?: string                   // Repo root. Default: process.cwd()
  level?: 'A' | 'AA' | 'AAA'     // WCAG target. Default: 'AA'
}

interface DiffScanResult {
  base: string                   // Resolved base commit SHA
  head: string                   // Resolved head commit SHA
  merge_base: string             // merge-base(base, head)
  new_issues: EquallIssue[]      // Counted WCAG violations the change introduced, within the
                                 // target level: the ones a gate acts on
  new_review_only: EquallIssue[] // Introduced, but static analysis can't confirm them (review_only)
  new_advisory: EquallIssue[]    // Introduced best-practice or above-target findings (never counted)
  legacy_issues: EquallIssue[]   // Findings in changed files that already existed at the base (any kind)
  not_testable: string[]         // Changed files outside the scannable set (CSS, scripts, config…)
  excluded: string[]             // Changed files a full scan also skips (tests, stories, build output)
  unchecked: UncheckedFile[]     // Changed files a scanner could not check at head
  summary: {
    files_changed: number
    files_scanned: number
    new_count: number
    new_review_only_count: number
    new_advisory_count: number
    legacy_count: number
    not_testable_count: number
    excluded_count: number
    unchecked_count: number
  }
}

A file listed on unchecked was not fully checked, so an empty new_issues says nothing about it. runDiffScan throws when a ref is invalid or missing, or when the two refs have no common ancestor (a shallow clone); the message names what to fetch. --json with --diff prints this object.

fingerprint(issue)

function fingerprint(issue: EquallIssue): string

Recomputes the 16-character identity runScan stores on issue.fingerprint: a hash of the file path, the rule, the WCAG criteria and the normalized snippet. Line and column never enter it, and cosmetic attributes (class, style) are stripped from the snippet, so a reformat or a class change keeps the identity.

formatDiffGuardrail(result)

function formatDiffGuardrail(result: DiffScanResult): string

Renders the one-line summary of a diff scan, for example 2 new · 1 new to review · 5 legacy · 1 not statically testable → run the rendered check. It never says the change is clean: it always names the legacy findings, what could not be tested, and the next step.

scanBuffer, fingerprint and formatDiffGuardrail are API-only. runDiffScan is also available as equall scan --diff (see CI).

Public types

All types are re-exported from the package root and ship with .d.ts declarations:

import type {
  RunScanOptions,
  FileInput,
  DiffScanOptions,
  DiffScanResult,
  ScanResult,
  ScanSummary,
  ScannerInfo,
  EquallIssue,
  CoverageReport,
  CriterionCoverage,
  CoverageStatus,
  ReclassifiedRule,
  CriterionConformance,
  ConformanceVerdict,
  ConfidenceFlag,
  RouteInfo,
  RouteFramework,
  UncheckedFile,
  ConformanceLevel,
  Severity,
  WcagLevel,
  WcagStandard,
  PourPrinciple,
  ScannerAdapter,
  ScanContext,
  ScanOptions,
} from 'equall-cli'

The runtime shape of ScanResult, EquallIssue, CriterionConformance, RouteInfo, and the union types (ConformanceLevel, ConformanceVerdict, Severity, WcagLevel, WcagStandard, PourPrinciple, RouteFramework) is documented on Output format — that page is the canonical contract for both runScan() consumers and --json integrators.

ScannerAdapter, ScanContext, and ScanOptions are the lower-level interfaces the built-in scanners implement. They're exported for tooling that wants to implement its own ScannerAdapter, but they aren't required for typical runScan usage.

Stability

Equall is pre-1.0. These docs describe equall-cli 0.3.3. The shape of ScanResult and the signature of runScan are treated as a public contract: fields are added compatibly between minor versions, and any breaking change will be called out explicitly in the release notes. Known intentional limitations are tracked on Known issues.

On this page