GitHub Actions
Production-ready CI workflows for request flows, schema drift, and OpenAPI lint.
DocTreen's three CI surfaces — doctreen-flow, doctreen drift report, and
doctreen lint openapi — all return non-zero on failure, so each one slots
into a single GitHub Actions step. This page collects the patterns we'd run
ourselves.
| Surface | CLI | Typical trigger |
|---|---|---|
| Request flows | doctreen-flow run | PR, post-deploy smoke |
| Schema drift | doctreen drift report | PR (against booted app), nightly (against staging) |
| OpenAPI lint | doctreen lint openapi | PR (against built spec) |
Request flows
doctreen-flow exits non-zero on any step failure or assertion miss, so a
workflow that runs your flows is a single CLI call away.
Sequential — run every flow against staging on every push
name: API smoke — request flows
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
jobs:
flows:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- name: Run all flows
env:
TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
run: |
mkdir -p reports
for flow in doctreen-flows/*.json; do
echo "::group::$flow"
npx doctreen-flow run "$flow" \
--env staging \
--input email="$TEST_USER_EMAIL" \
--input password="$TEST_USER_PASSWORD" \
--report json \
| tee "reports/$(basename "$flow" .json).json"
echo "::endgroup::"
done
- name: Upload flow reports
if: always()
uses: actions/upload-artifact@v4
with:
name: flow-reports
path: reports/Matrix — one flow per parallel job
Better when you have many flows and want fast, isolated feedback. Each flow runs in its own runner; one failing flow doesn't mask the others.
name: API smoke — flow matrix
on:
push: { branches: [main] }
pull_request:
workflow_dispatch:
jobs:
flows:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
flow:
- login-smoke
- user-onboarding
- checkout-happy-path
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- name: Run ${{ matrix.flow }}
env:
TEST_USER_EMAIL: ${{ secrets.TEST_USER_EMAIL }}
TEST_USER_PASSWORD: ${{ secrets.TEST_USER_PASSWORD }}
run: |
npx doctreen-flow run doctreen-flows/${{ matrix.flow }}.json \
--env staging \
--input email="$TEST_USER_EMAIL" \
--input password="$TEST_USER_PASSWORD" \
--report json \
> flow-${{ matrix.flow }}.json
- name: Upload report
if: always()
uses: actions/upload-artifact@v4
with:
name: flow-${{ matrix.flow }}
path: flow-${{ matrix.flow }}.jsonPost-deploy gate
Run flows automatically after a successful deploy. Failure rolls back (or just blocks the next stage) instead of being noticed by a customer.
name: Post-deploy verification
on:
workflow_run:
workflows: ['Deploy to staging']
types: [completed]
jobs:
verify:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- name: Smoke flows against staging
run: |
npx doctreen-flow run doctreen-flows/smoke.json \
--base-url https://staging.api.example.com \
--no-bail \
--report jsonTips
- Secrets: pass via
--input key=valuefromenv:. Flow files should never hard-code credentials — keep them as{{input.password}}so the same file runs locally and in CI. - Environments: drop
doctreen-flows/environments/staging.json,prod.json, etc., into the repo and select with--env <name>. Each environment supplies its ownbaseUrlandenv.*values. - Artifacts:
--report jsongives you the full execution timeline; upload it on failure to debug without rerunning. - PR comments: pipe the JSON report through
jqto extract a Markdown summary and post it as a sticky PR comment withmarocchino/sticky-pull-request-comment.
Schema drift
Drift only fires when real traffic hits a declared route, so the useful question to answer in CI is: "of the routes my integration tests just exercised, did any of them deviate from their declared schema?"
Two shapes work well.
A. Boot the app, replay flows, check drift
For repo-local verification on every PR. Reset the store first so the run is reproducible, then seed traffic via your existing test suite or a request flow.
name: Schema drift
on:
pull_request:
push: { branches: [main] }
workflow_dispatch:
jobs:
drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- name: Boot app in the background
env:
NODE_ENV: development # so drift defaults to enabled
DOCTREEN_RESET_TOKEN: ci-token
run: |
npm start &
npx wait-on http://localhost:3000/docs --timeout 30000
- name: Reset drift store
run: |
curl -fsSL -X POST \
-H "x-doctreen-drift-token: ci-token" \
http://localhost:3000/docs/drift/reset
- name: Seed traffic
run: |
# Replace with whatever exercises your real routes:
# integration tests, flow replay, k6 smoke, recorded HAR replay, ...
npx doctreen-flow run doctreen-flows/smoke.json \
--base-url http://localhost:3000
- name: Check drift
run: |
npx doctreen drift report \
--url http://localhost:3000/docs \
--fail-on-mismatch
- name: Upload drift snapshot
if: failure()
run: curl -fsSL http://localhost:3000/docs/drift.json > drift.json
- if: failure()
uses: actions/upload-artifact@v4
with: { name: drift-report, path: drift.json }B. Post-deploy check against staging
For continuous monitoring of a live environment. Runs nightly and on every deploy; assumes staging accumulates traffic from real (or synthetic) clients between runs.
name: Drift watch — staging
on:
schedule:
- cron: '0 6 * * *' # 06:00 UTC daily
workflow_run:
workflows: ['Deploy to staging']
types: [completed]
workflow_dispatch:
jobs:
watch:
runs-on: ubuntu-latest
steps:
- name: Drift report
run: |
npx doctreen drift report \
--url https://staging.api.example.com/docs \
--fail-on-mismatch \
--min-issues 5 # tolerate a handful of stragglers
- name: Snapshot the JSON
if: always()
run: |
curl -fsSL \
https://staging.api.example.com/docs/drift.json \
> drift.json
- if: always()
uses: actions/upload-artifact@v4
with: { name: drift-${{ github.run_id }}, path: drift.json }
- name: Alert on Slack
if: failure()
env:
WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
run: |
curl -fsSL -X POST -H 'content-type: application/json' \
-d '{"text":":warning: schema drift on staging — see run ${{ github.run_id }}"}' \
"$WEBHOOK"Tips
- Reset between runs when CI re-uses a long-lived environment. Without it, yesterday's drift fails today's build forever.
- Use
--min-issuesas a soft gate while you adopt the feature. Start at--min-issues 20for a week, then ratchet down as fields stabilise. - Use
--routeto scope a check to a single endpoint when investigating a specific regression:--route /users --fail-on-mismatch. - Sample rate: leave at the
0.01default in production. CI can bump it via thedriftconfig to record every event for a deterministic check.
OpenAPI lint
doctreen lint openapi runs Spectral-lite rules against the spec; pair
it with the spec served by your booted app or a saved file in the repo.
name: OpenAPI lint
on:
pull_request:
push: { branches: [main] }
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: '20', cache: 'npm' }
- run: npm ci
- name: Boot app
env: { NODE_ENV: development }
run: |
npm start &
npx wait-on http://localhost:3000/docs --timeout 30000
- name: Lint exported spec
run: |
npx doctreen lint openapi \
--url http://localhost:3000/docs \
--fail-on warningPrefer to gate only on hard errors? Drop --fail-on warning (the default
is --fail-on error). To keep linting fast for PRs and run a stricter
nightly job, point a second workflow at the same URL with
--fail-on warning on a schedule trigger.