Skip to content

CI integration

gapline is designed to be dropped into a pipeline: a single static binary, JSON output, stable exit codes. This guide shows how to wire it into common CI platforms and into a local pre-commit hook.

The official install script works inside any Linux runner. Pin a version so upgrades are explicit:

Terminal window
curl -fsSL https://gapline.dev/install.sh | sh -s -- --version 1.0.1
echo "$HOME/.local/bin" >> "$GITHUB_PATH" # or the equivalent for your CI

If your runner caches binaries between builds, restore ~/.local/bin/gapline from cache before re-running the install.

.github/workflows/validate-gtfs.yml
name: Validate GTFS
on: [push, pull_request]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install gapline
run: |
curl -fsSL https://gapline.dev/install.sh | sh -s -- --version 1.0.1
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: Validate feed
run: gapline validate -f data/gtfs.zip --format json -o report.json
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: gtfs-report
path: report.json

validate exits non-zero on ERROR-level findings, which fails the job automatically. if: always() ensures the report is uploaded even on a red build.

Use jq to turn the report into a human summary, a metric for a dashboard, or a PR comment.

Terminal window
# Top-line counts.
jq '.summary' report.json
# Most frequent rules.
jq -r '.errors | group_by(.rule_id)
| map({rule: .[0].rule_id, count: length})
| sort_by(-.count) | .[:10] | .[]
| "\(.count)\t\(.rule)"' report.json
Terminal window
summary=$(jq -r '"- \(.summary.error_count) errors\n- \(.summary.warning_count) warnings\n- \(.summary.info_count) infos"' report.json)
gh pr comment "$PR_NUMBER" --body "### GTFS validation\n$summary"

The default behavior — fail on any ERROR — is usually what you want. Tighten the gate in stricter contexts:

Terminal window
# Also fail on warnings.
gapline validate -f data/gtfs.zip --min-severity warning --format json -o report.json

See concepts / severities for the policy.

A local hook keeps obviously-broken feeds out of the repo:

.git/hooks/pre-commit
#!/usr/bin/env bash
set -e
if git diff --cached --name-only | grep -q '^data/gtfs'; then
echo "Running gapline validate on staged feed…"
gapline validate -f data/gtfs.zip --min-severity error
fi

Make it executable:

Terminal window
chmod +x .git/hooks/pre-commit

For a team-wide version that ships with the repo, use pre-commit, lefthook, or husky.

If the feed is large or pulled from a slow source, cache it between runs keyed on its hash:

- name: Restore feed cache
uses: actions/cache@v4
with:
path: data/gtfs.zip
key: gtfs-${{ hashFiles('data/gtfs.zip.sha256') }}

Drop the hash file in the repo; update it when you refresh the feed. Downloading again only happens when the hash changes.