# Dev Build Run CI

> Use when running or scripting CI checks (pub get, dart format check, dart analyze, dart/flutter test, web build) on a Dart or Flutter package or on a whole repository tree with package:dev_build: the run_ci executable (dart run dev_build:run_ci, --recursive, --no-test, --analyze, --fix, --pub-downgrade, --offline, --prj-info, --ignore-errors, --vm-test), the packageRunCi / PackageRunCiOptions / recursivePackagesRun / SinglePackageCiRunner API, the tool/run_ci.dart and tool/run_ci_override.dart conventions, .local/.skip_run_ci, dart_test.yaml platforms and pub workspaces.

- Skill: `tekartik/dev-build-run-ci` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tekartik/dev-build-run-ci`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tekartik/dev-build-run-ci/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: tekartik (https://skillmd.com/u/tekartik)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tekartik/dev-build-run-ci

---


# dev_build run_ci: validate a package or a tree

`package:dev_build` runs the standard validation steps of a Dart or Flutter
package: `pub get`, format check, analyze, tests on every supported platform,
and an optional web build. It works on a single package or recursively on
every package found under a folder. Same code drives the `run_ci` executable
and the `packageRunCi()` API. VM only (it spawns `dart`/`flutter` processes).

```dart
// tool/run_ci.dart
import 'package:dev_build/package.dart';

Future<void> main() async {
  await packageRunCi('.');
}
```

```bash
dart run dev_build:run_ci            # same steps from the command line
```

## Guidelines

### Setup

* Add `dev_build` to `dev_dependencies`. Import
  `package:dev_build/package.dart` for `packageRunCi`, `PackageRunCiOptions`,
  `recursivePackagesRun`, `recursivePubPath`, `SinglePackageCiRunner`.
* Put the script in `tool/run_ci.dart` (repository convention) and run it
  with `dart run tool/run_ci.dart`. For a one-off from the command line use
  `dart run dev_build:run_ci [<path>] [<flags>]`, or activate it once with
  `dart pub global activate dev_build` and run `run_ci`.

### What a run does, in order

* `dart pub get` (or `flutter pub get` when the package, or any package of
  its pub workspace, depends on `flutter`). `--offline` adds `--offline`.
* Format check: `dart format --set-exit-if-changed <top level dirs that
  contain dart files>` (`lib test bin tool example ...`). Nested packages,
  hidden dirs, `build`, `deploy`, `node_modules`, `.dart_tool` are excluded.
* Analyze: `dart analyze --fatal-warnings --fatal-infos .` for Dart,
  `flutter analyze --no-pub .` for Flutter. Infos fail the build: keep
  `analysis_options.yaml` clean.
* Test: `flutter test --no-pub` for Flutter. For Dart, `dart test` is run
  once per platform: `vm` always, `chrome` when `build_web_compilers` is a
  dependency, `node` when `build_node_compilers` is a dependency and `node`
  is installed. Skipped entirely when there is no `test/` folder.
* Build: `flutter build web --no-pub` when `web/index.html` and
  `lib/main.dart` exist; `dart pub global run webdev build` for a Dart
  package that depends on `build_web_compilers` + `build_runner` and has
  `web/index.html`.
* A `dart_test.yaml` `platforms:` list restricts the platforms above,
  `compilers:` (`dart2js`, `dart2wasm`) adds `--compiler` for browser runs.

### Options (`PackageRunCiOptions` / flags)

* Skip a step: `noPubGet`, `noFormat`, `noAnalyze`, `noTest`, `noBuild`
  (`--no-pub-get`, `--no-format`, `--no-analyze`, `--no-test`,
  `--no-build`).
* Run only one step: `formatOnly`, `analyzeOnly`, `testOnly`, `buildOnly`,
  `pubGetOnly`, `pubUpgradeOnly`, `pubDowngradeOnly`, `fixOnly` (`--format`,
  `--analyze`, `--test`, `--build`, `--pub-get`, `--pub-upgrade`,
  `--pub-downgrade`, `--fix`). An "only" option turns every other step off;
  `analyzeOnly` still runs `pub get` first unless `noPubGet: true`.
* Test platforms: `vmTestOnly` (`--vm-test`), `noVmTest`, `noBrowserTest`,
  `chromeJsTestOnly` (`--chrome-js-test`, dart2js only), `noNodeTest`,
  `noNpmInstall`.
* `fixOnly` runs `dart format <dirs>` then `dart fix --apply` and stops.
* `recursive: true` (`--recursive`, the CLI default; API default is
  `false`) finds every package below `path`, links followed, and runs them
  through a pool of `poolSize` (default 4 in the API, `-j`/`--concurrency`
  default 1 in the CLI).
* `ignoreErrors` (`--ignore-errors`, `-i`): a failing package is reported
  and the run continues with the next one, instead of throwing.
* `dryRun` (`--dry-run`) prints the shell lines instead of running them.
  `prjInfo` (`--prj-info`) prints the sdk constraint and flutter tag,
  `noRunCi` (`--no-run-ci`) stops after that, `printPath` (`--print-path`)
  only prints the package path. `verbose` (`-v`).
* `filterDartProjectOptions: FilterDartProjectOptions(minSdk:, maxSdk:,
  ignoreSdkConstraints:)` (`--min-sdk '>=3.0.0'`, `--max-sdk`,
  `--ignore-sdk-constraints`) selects which packages are considered. By
  default a package whose `environment: sdk:` does not match the running
  `dart` is skipped.
* `noOverride` (`--no-override`) ignores `tool/run_ci_override.dart` and
  `.local/.skip_run_ci`.

### Repository conventions the runner honours

* `tool/run_ci_override.dart` in a package: run with `dart run` instead of
  the standard steps (use `noOverride`/`--no-override` from inside it or
  the run recurses). Use it for a package that needs custom steps.
* `.local/.skip_run_ci` (empty file) skips the package in recursive runs.
  Create it with `run_ci config --skip-run-ci [<path>]`; git-ignore `.local`.
* A package whose name starts with `_` only gets `pub get`.
* A pub workspace root gets `pub get` once for the whole workspace, then is
  skipped for format/analyze/test; members are run individually.
* Everything under a hidden folder, `build`, `deploy` or `node_modules` is
  never visited.

### Custom actions over a tree

* `recursivePubPath(['.'])` returns every package folder (sorted, links
  resolved). `dependencies: ['sembast']` keeps packages depending on it,
  with `direct:`/`dev:`/`override:` prefixes to narrow the kind, and
  `readConfig: true` also matches transitive dependencies through
  `.dart_tool/package_config.json`.
* `recursivePackagesRun(['.'], action: (dir) async {...}, poolSize: 1)`
  runs a callback per package. Combine with `Shell(workingDirectory: dir)`
  from `package:dev_build/shell.dart`.
* `SinglePackageCiRunner(PubIoPackage(path), options)` (`PubIoPackage` from
  `package:dev_build/menu/menu_run_ci.dart`) exposes the individual steps:
  `init()`, then `format()`, `analyze()`, `runTest(testOptions: [...])`,
  `fix()`. Use it to pass extra `dart test` arguments.
* `runCiInitPubWorkspacesCache()` is called by `packageRunCi` so a
  workspace gets one `pub get`; call it yourself before looping with
  `singlePackageRunCi`-style code.

## Examples

### tool/run_ci.dart for a repository

```dart
import 'package:dev_build/package.dart';

Future<void> main() async {
  // Every package under the repo root, one at a time.
  await packageRunCi('..', options: PackageRunCiOptions(recursive: true));
}
```

### Analyze only, without touching the network

```dart
import 'package:dev_build/package.dart';

Future<void> main() async {
  await packageRunCi(
    '.',
    options: PackageRunCiOptions(
      recursive: true,
      analyzeOnly: true,
      noPubGet: true,
    ),
  );
}
```

### Check minimum dependency versions

```dart
import 'package:dev_build/package.dart';

Future<void> main() async {
  // pub downgrade + analyze on every package, ignoring tool/run_ci_override.dart
  await packageRunCi(
    '.',
    options: PackageRunCiOptions(
      recursive: true,
      pubDowngradeOnly: true,
      analyzeOnly: true,
      noOverride: true,
    ),
  );
}
```

### Fix formatting and lints recursively

```dart
import 'package:dev_build/package.dart';

Future<void> main() async {
  await packageRunCi(
    '.',
    options: PackageRunCiOptions(recursive: true, fixOnly: true),
  );
}
```

### tool/run_ci_override.dart with custom steps

```dart
import 'package:dev_build/package.dart';
import 'package:dev_build/shell.dart';

Future<void> main() async {
  // noOverride is required, otherwise this file runs itself again.
  await packageRunCi(
    '.',
    options: PackageRunCiOptions(noOverride: true, noTest: true),
  );
  await Shell().run('dart test --platform vm --exclude-tags slow');
}
```

### Extra test arguments with SinglePackageCiRunner

```dart
import 'package:dev_build/menu/menu_run_ci.dart' show PubIoPackage;
import 'package:dev_build/package.dart';

Future<void> main() async {
  var package = PubIoPackage('.');
  var runner = SinglePackageCiRunner(package, PackageRunCiOptions());
  await runner.init();
  await package.pubGet();
  await runner.analyze();
  await runner.runTest(testOptions: ['--reporter', 'expanded']);
}
```

### Run a shell command in every package that depends on a package

```dart
import 'package:dev_build/package.dart';
import 'package:dev_build/shell.dart';

Future<void> main() async {
  await recursivePackagesRun(
    ['.'],
    dependencies: ['direct:sembast'],
    poolSize: 1,
    action: (dir) async {
      await Shell(workingDirectory: dir).run('dart pub upgrade sembast');
    },
  );
}
```

### Command line

```bash
dart run dev_build:run_ci                      # this package and nested ones
dart run dev_build:run_ci --no-test ../other   # skip tests, other folder
dart run dev_build:run_ci --analyze --no-pub-get
dart run dev_build:run_ci --pub-downgrade --analyze --no-override --recursive
dart run dev_build:run_ci --fix --recursive
dart run dev_build:run_ci --vm-test --offline
dart run dev_build:run_ci --prj-info --no-run-ci   # list packages and sdk
dart run dev_build:run_ci config --skip-run-ci packages/legacy
dart run dev_build:run_ci menu                 # interactive console menu
```

## Common mistakes

* Expecting `packageRunCi('.')` to recurse: the API default is
  `recursive: false`; the executable defaults to `--recursive`.
* Writing `tool/run_ci_override.dart` that calls `packageRunCi` without
  `noOverride: true`.
* Relying on `--no-analyze` to hide lints: the standard analyze uses
  `--fatal-infos`, fix the code or the `analysis_options.yaml`.
* Adding `build_web_compilers` to a package that has no browser tests: it
  turns on `dart test --platform chrome` for every run.
* Using `packageRunCi` from a web or Flutter app: it needs `dart:io` and
  the `dart`/`flutter` executables on the PATH.

## More

See [references/cli-options.md](references/cli-options.md) for the complete
flag list of the `run_ci` executable and the exact commands executed per
package type.

