Back to Deno

Continuous integration

doc/ci.md

2.9.13.7 KB
Original Source

Continuous integration

This page explains how the main CI workflow is structured, how it decides which jobs to run, and the docs-only fast path.

The workflow is generated, not hand-written

The CI workflow is generated. The source of truth is .github/workflows/ci.ts, a Deno script that builds the workflow object and writes it to .github/workflows/ci.generated.yml. Never edit the .yml directly; it carries a GENERATED BY ./ci.ts -- DO NOT DIRECTLY EDIT header.

To regenerate after changing ci.ts:

bash
deno run -A .github/workflows/ci.ts

Other workflows in .github/workflows/ follow the same *.ts*.generated.yml pattern.

Job overview

On a pull request the workflow runs, roughly:

  • pre-build — a fast gating job. It decides whether downstream jobs should run at all and exposes the decision through job outputs.
  • build (per platform) and build libs — compile Deno and the deno_core libraries.
  • test (per platform, sharded) — the spec, unit, node-compat and WPT suites, depending on build.
  • bench — benchmarks.
  • deno-core-test and deno-core-miri — the deno_core / libs/* crate tests (the latter under Miri).
  • lint — formatting check, jsdoc check, and tools/lint.js (Rust + JS).
  • ci-status — an aggregator that the branch protection rule keys on. It passes as long as none of its dependencies failed or were cancelled; skipped dependencies are fine.

How pre-build gates the rest

pre-build runs a few cheap checks and publishes their results as outputs that downstream jobs read in their if: conditions:

  • skip_build — set when a draft PR has not opted into CI. When true, the build, test, lint, bench and deno_core jobs are all skipped.
  • skip_deno_core_test — set when no deno_core-related files changed, so the deno_core crate tests can be skipped (tools/check_deno_core_changes.js).
  • docs_only — set when the PR only touches the doc/ directory (tools/check_docs_only_changes.js).

Each gated job's if: combines the relevant outputs, for example:

needs.pre_build.outputs.skip_build != 'true' &&
  needs.pre_build.outputs.docs_only != 'true'

The docs-only fast path

A pull request that changes only files under doc/ does not need to compile or test anything. For those PRs CI runs the lint job and skips everything else.

How it works:

  1. In pre-build, tools/check_docs_only_changes.js diffs the PR against its base SHA. If every changed file is under doc/ (and at least one file changed), it writes docs_only=true to the job output. An empty or failed diff defaults to false, so the full pipeline runs when in doubt.
  2. The build, build-libs, test, bench, deno-core-test and deno-core-miri jobs each add && docs_only != 'true' to their run condition, so they are skipped on a docs-only PR. The test jobs depend on build, so skipping build cascades to them automatically.
  3. The lint job is intentionally not gated on docs_only. It still runs, which means Markdown is still formatted and linted (deno fmt --check covers doc/). This is the "lint and nothing else" behavior.
  4. ci-status still runs and passes: its skipped dependencies count as neither failure nor cancellation, and lint succeeds, so the required status check goes green and the PR can merge.

The moment a PR also touches anything outside doc/, docs_only is false and the full pipeline runs as normal.

Changing CI behavior

When you change which jobs run or add a new gate, edit ci.ts, regenerate the YAML, and commit both. The generated file is checked in so reviewers can see the effective workflow in the diff.