Skip to content

CI/CD Integration

club integrates cleanly with CI/CD pipelines. The basic recipe is:

  1. Create a Personal Access Token (PAT) scoped to exactly what the job needs.
  2. Store it as a CI secret (e.g. CLUB_TOKEN).
  3. Either:
    • Use club publish / club prepare / club publish --auto directly — they read CLUB_TOKEN from the environment with no extra setup, and skip interactive prompts when CI env vars are detected; or
    • Register the token with dart pub using club setup --env-var (or dart pub token add --env-var) when the build step runs dart pub get against a club-hosted dep.
  4. Run dart pub get, dart pub publish, or any of the club commands as usual.

Creating CI Tokens

Create a dedicated PAT from the web UI at /settings/keys with the minimum scopes:

Job typeScopes
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:

Terminal window
dart pub token add https://club.example.com --env-var CLUB_TOKEN

With 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:

CommandWhen to use itCI flags / env vars
dart pub publishWhen you want strict parity with stock dart pub. Requires dart pub token add (or club setup) to authenticate.--force; pre-step token registration.
club publishSingle-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 --autoMonorepo / 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 prepareWhen 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

.github/workflows/build.yml
name: Build
on: [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 test

Publishing Packages

.github/workflows/publish.yml
name: Publish
on:
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

No --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.

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:

.github/workflows/flutter.yml
name: Flutter Build
on: [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 test

Action inputs

InputRequiredDefaultDescription
serveryesclub server URL.
tokenyesA club_pat_... token. Pass via secrets.
versionnolatestCLI release tag to install (e.g. 0.1.0).
set-default-registrynofalseWhen 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:

.github/workflows/build.yml
name: Build
on: [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 test

The 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

.gitlab-ci.yml
stages:
- build
- publish
variables:
CLUB_SERVER: https://club.example.com
build:
image: dart:stable
stage: build
before_script:
- dart pub token add $CLUB_SERVER --env-var CLUB_TOKEN
script:
- dart pub get
- dart test
cache:
key: dart-packages
paths:
- .dart_tool/
publish:
image: dart:stable
stage: publish
only:
- tags
before_script:
- dart pub token add $CLUB_SERVER --env-var CLUB_TOKEN
script:
- dart pub publish --force

Bitbucket Pipelines

bitbucket-pipelines.yml
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

.circleci/config.yml
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:

DirectoryContents
.dart_tool/Resolved package config and cached build artifacts
~/.pub-cache/Downloaded package archives

GitHub Actions Caching

.github/workflows/build.yml (cache step)
- 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_CACHE

Security Best Practices

  1. Use minimal scopes. Read-only build jobs should use read only. Add write only for publish jobs.

  2. Set an expiry. Short-lived tokens (e.g. 90 days) limit the blast radius of a leak.

  3. Always use --env-var. Never hardcode tokens in pipeline files, Dockerfiles, or committed scripts.

  4. Mark secrets as masked. All major CI providers support masking secrets in logs.

  5. Separate read and write tokens. One token for build jobs (read-only), another for publish jobs (read + write).

  6. Revoke promptly. When a pipeline is decommissioned or a developer leaves, revoke the associated PATs from /settings/keys.