CI/CD Integration
club integrates cleanly with CI/CD pipelines. The basic recipe is:
- Create a Personal Access Token (PAT) scoped to exactly what the job needs.
- Store it as a CI secret (e.g.
CLUB_TOKEN). - Either:
- Use
club publish/club prepare/club publish --autodirectly — they readCLUB_TOKENfrom the environment with no extra setup, and skip interactive prompts when CI env vars are detected; or - Register the token with
dart pubusingclub setup --env-var(ordart pub token add --env-var) when the build step runsdart pub getagainst a club-hosted dep.
- Use
- Run
dart pub get,dart pub publish, or any of theclubcommands as usual.
Creating CI Tokens
Create a dedicated PAT from the web UI at /settings/keys with the minimum scopes:
| Job type | Scopes |
|---|---|
Read-only (build, test, dart pub get) | read |
Publishing (dart pub publish, club publish) | read + write |
Optionally set an expiry (e.g. 90 days) so forgotten keys don’t linger. The full secret (club_pat_...) is shown once — copy it straight into your CI provider’s secrets store.
dart pub token add --env-var
The --env-var flag registers a token by environment variable name — the token itself is not written to disk. At resolution time dart pub reads the named env var:
dart pub token add https://club.example.com --env-var CLUB_TOKENWith CLUB_TOKEN exported to the value club_pat_..., dart pub get and dart pub publish will attach it as a Bearer token automatically.
Choosing a publish command
CI workflows can publish via three routes, depending on what the job needs to do:
| Command | When to use it | CI flags / env vars |
|---|---|---|
dart pub publish | When you want strict parity with stock dart pub. Requires dart pub token add (or club setup) to authenticate. | --force; pre-step token registration. |
club publish | Single-package publish. No dart pub token add step needed — CLUB_TOKEN is read directly. CI auto-detection skips prompts. | --server <host> + CLUB_TOKEN. --force only when CI env vars aren’t auto-detected. |
club publish --auto | Monorepo / pub workspace where one publish should cascade through several packages. Source pubspec.yaml files are not modified — rewrites happen in-memory only. | --server <host> + --on-conflict abort + CLUB_TOKEN. Positional args list the targets. |
club prepare | When you want the rewrite to land on disk first (review, commit to a release branch), then publish via separate dart pub publish / club publish steps. | --server <host> + --on-conflict abort + CLUB_TOKEN. |
The shortest single-package CI step:
- name: Publish run: club publish --server myclub.birju.dev env: CLUB_TOKEN: ${{ secrets.CLUB_TOKEN }}The shortest monorepo CI step:
- name: Publish monorepo run: | club publish --auto pkg_a pkg_b \ --on-conflict abort \ --server myclub.birju.dev env: CLUB_TOKEN: ${{ secrets.CLUB_TOKEN }}Neither needs club login, dart pub token add, or --force — the
token is read from CLUB_TOKEN, the URL passed to --server is
authorised by it, and CI env-var auto-detection (CI,
CONTINUOUS_INTEGRATION, BUILD_NUMBER) skips the confirmation
prompt automatically.
GitHub Actions
The BirjuVachhani/club/actions/setup-club action installs the CLI
and registers the token with dart pub in a single step, on Linux,
macOS, and Windows runners. It is the recommended path for GitHub
Actions workflows. If you’d rather wire up the underlying commands
yourself, the manual approach
below is equivalent.
Fetching Packages
name: Buildon: [push, pull_request]
jobs:build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dart-lang/setup-dart@v1
- uses: BirjuVachhani/club/actions/setup-club@0.3.0 with: server: https://club.example.com token: ${{ secrets.CLUB_TOKEN }}
- run: dart pub get - run: dart testPublishing Packages
name: Publishon:push: tags: - 'v*'
jobs:publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dart-lang/setup-dart@v1 - uses: BirjuVachhani/club/actions/setup-club@0.3.0 with: server: https://club.example.com token: ${{ secrets.CLUB_TOKEN }}
- run: club publishNo --force needed — setup-club exports CLUB_TOKEN to
$GITHUB_ENV, and the GitHub Actions runner sets CI=true
automatically, so CI auto-detection skips the confirmation
prompt. The publish target is read from publish_to: in the
pubspec, or pass --server <host> explicitly.
name: Publishon:push: tags: - 'v*'
jobs:publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dart-lang/setup-dart@v1 - uses: BirjuVachhani/club/actions/setup-club@0.3.0 with: server: https://club.example.com token: ${{ secrets.CLUB_TOKEN }}
- run: | club publish --auto pkg_a pkg_b \ --on-conflict abort \ --server https://club.example.comTargets pkg_a and pkg_b plus their transitive workspace deps,
publishing in topological order. Source pubspec.yaml files are
never modified — rewrites happen in-memory only. See
club publish --auto for full flag details.
name: Publishon:push: tags: - 'v*'
jobs:publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dart-lang/setup-dart@v1
- uses: BirjuVachhani/club/actions/setup-club@0.3.0 with: server: https://club.example.com token: ${{ secrets.CLUB_TOKEN }}
- run: dart pub publish --forceUse this when you want strict dart pub publish parity. --force
is required because dart pub doesn’t have CI auto-detection.
Flutter Projects
subosito/flutter-action puts dart on PATH (Flutter bundles its
own Dart SDK), so setup-club works without an extra setup-dart
step:
name: Flutter Buildon: [push, pull_request]
jobs:build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: subosito/flutter-action@v2 with: flutter-version: '3.x'
- uses: BirjuVachhani/club/actions/setup-club@0.3.0 with: server: https://club.example.com token: ${{ secrets.CLUB_TOKEN }}
- run: flutter pub get - run: flutter testAction inputs
| Input | Required | Default | Description |
|---|---|---|---|
server | yes | — | club server URL. |
token | yes | — | A club_pat_... token. Pass via secrets. |
version | no | latest | CLI release tag to install (e.g. 0.1.0). |
set-default-registry | no | false | When true, exports PUB_HOSTED_URL=<server> so club becomes the default registry for unscoped dependencies. |
After the action runs, CLUB_TOKEN is exported to $GITHUB_ENV, so
subsequent dart pub get, dart pub publish, and club publish
steps authenticate automatically — no need to repeat the env: block
on each step.
Manual GitHub Actions (without the action)
If you’d rather not depend on the action — for example to pin every step to first-party tooling — wire it up directly:
name: Buildon: [push, pull_request]
jobs:build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: dart-lang/setup-dart@v1
- name: Configure club token run: dart pub token add https://club.example.com --env-var CLUB_TOKEN env: CLUB_TOKEN: ${{ secrets.CLUB_TOKEN }}
- name: Get dependencies run: dart pub get env: CLUB_TOKEN: ${{ secrets.CLUB_TOKEN }}
- name: Run tests run: dart testThe CLUB_TOKEN env var must be present on every step that runs
dart pub because, unlike the action, this approach doesn’t write it
to $GITHUB_ENV.
GitLab CI
stages:- build- publish
variables:CLUB_SERVER: https://club.example.com
build:image: dart:stablestage: buildbefore_script: - dart pub token add $CLUB_SERVER --env-var CLUB_TOKENscript: - dart pub get - dart testcache: key: dart-packages paths: - .dart_tool/
publish:image: dart:stablestage: publishonly: - tagsbefore_script: - dart pub token add $CLUB_SERVER --env-var CLUB_TOKENscript: - dart pub publish --forceBitbucket Pipelines
image: dart:stable
pipelines:default: - step: name: Build and Test caches: - dart-packages script: - dart pub token add https://club.example.com --env-var CLUB_TOKEN - dart pub get - dart test
tags: 'v*': - step: name: Publish script: - dart pub token add https://club.example.com --env-var CLUB_TOKEN - dart pub publish --force
definitions:caches: dart-packages: .dart_tool/CircleCI
version: 2.1
jobs:build: docker: - image: cimg/dart:stable steps: - checkout - run: name: Configure club token command: dart pub token add https://club.example.com --env-var CLUB_TOKEN - run: dart pub get - run: dart test
publish: docker: - image: cimg/dart:stable steps: - checkout - run: name: Configure club token command: dart pub token add https://club.example.com --env-var CLUB_TOKEN - run: dart pub publish --force
workflows:test-and-publish: jobs: - build - publish: requires: - build filters: tags: only: /^v.*/ branches: ignore: /.*/Caching Strategies
Caching Dart dependencies speeds up CI builds significantly:
| Directory | Contents |
|---|---|
.dart_tool/ | Resolved package config and cached build artifacts |
~/.pub-cache/ | Downloaded package archives |
GitHub Actions Caching
- name: Cache Dart packages uses: actions/cache@v4 with: path: | ~/.pub-cache .dart_tool/ key: dart-${{ hashFiles('pubspec.lock') }} restore-keys: | dart-GitLab CI Caching
cache: key: dart-$CI_COMMIT_REF_SLUG paths: - .dart_tool/ - $PUB_CACHESecurity Best Practices
-
Use minimal scopes. Read-only build jobs should use
readonly. Addwriteonly for publish jobs. -
Set an expiry. Short-lived tokens (e.g. 90 days) limit the blast radius of a leak.
-
Always use
--env-var. Never hardcode tokens in pipeline files, Dockerfiles, or committed scripts. -
Mark secrets as masked. All major CI providers support masking secrets in logs.
-
Separate read and write tokens. One token for build jobs (read-only), another for publish jobs (read + write).
-
Revoke promptly. When a pipeline is decommissioned or a developer leaves, revoke the associated PATs from
/settings/keys.