Skip to content

CLI Publishing

club publish is a native drop-in replacement for dart pub publish. It shares the same flag set, the same exit codes, and runs the same 25 client-side validators — but adds one critical superpower:

You do not need publish_to: in your pubspec.yaml.

This unlocks publishing packages that live inside larger projects (where modifying pubspec.yaml is not an option) without losing any of the safety checks dart pub publish provides.

Quick start

Terminal window
$ cd path/to/your/package
$ club publish
Publishing my_package 1.2.0 from /Users/me/code/my_package
Target server: myclub.birju.dev (auto-selected (only logged-in server))
Building package archive...
Created /tmp/club-publish-...tar.gz (12.4 KiB, 18 files)
Running 25 validators...
All validators passed.
Publish my_package 1.2.0 to myclub.birju.dev? [Y/n] y
Uploading to myclub.birju.dev...
Successfully uploaded my_package version 1.2.0.

Flags

The core flag set mirrors dart pub publish (matched against the upstream LishCommand), plus club-specific additions (--server, --version, --enhanced, --from-git, --ref, --auto).

FlagShortDescription
--directory <path>-CRun as if <path> is the package directory. Useful for monorepos.
--dry-run-nValidate without uploading. Exits non-zero on errors (and warnings unless --ignore-warnings).
--force-fSkip confirmation prompts. Required in CI when there is no TTY.
--skip-validationBypass all 25 client-side validators. The server still validates.
--ignore-warningsWith --dry-run only: do not exit non-zero on warnings.
--to-archive <path>Write the produced .tar.gz to <path> instead of uploading.
--from-archive <path>Upload an existing .tar.gz instead of building one. Implies --skip-validation.
--server <host>-sOverride publish_to and the auto-picker. Accepts a bare host (myclub.birju.dev) or a full URL. Must be a server you have logged in to with club login.
--version <semver>Override the version being published. Rewrites the version inside the tarball’s pubspec.yaml without modifying source files. Must be valid semver.
--enhanced-eClub extras on top of dart pub parity: stricter size limit, dart analyze --fatal-warnings, git deps as errors, extended leak patterns, DevTools config.yaml content checks, every file-case collision reported.
--from-git <url>Clone a git repository and publish it as a package. Accepts an https or SSH git URL. The repo is cloned under ~/.club/clones/ and removed after a successful publish. See club publish --from-git.
--ref <ref>Git branch, tag, or commit to check out. Only valid with --from-git. Defaults to the remote default branch.
--autoSwitch into multi-package mode for monorepos. See club publish --auto.

How the target server is resolved

club publish resolves the target server with this priority — only the first match is used:

  1. --server <host> flag (highest priority). The server must be in your logged-in credentials, otherwise the command exits with a hint to run club login. Accepts a bare host (myclub.birju.dev) or a full URL — both forms map to the same credential.
  2. publish_to: in pubspec.yaml, when present and not none. The URL must be in your logged-in credentials.
  3. Auto-pick from logged-in servers:
    • 1 server: used automatically (with confirmation, unless --force).
    • 2+ servers: an arrow-key TUI picker appears (in CI, this fails with a clear error directing you to use --server).
    • 0 servers: exits with a hint to run club login <host>.

This means you can publish a package whose pubspec.yaml has either no publish_to: at all, or publish_to: none, without modifying it.

Examples

Terminal window
# Pubspec has publish_to set → that server is used.
club publish
# Pubspec has no publish_to, you have one login → that one is used.
club publish
# Pubspec has no publish_to, you have multiple logins → arrow-key picker.
club publish
# Force a specific server regardless of pubspec.
club publish --server internal.myclub.birju.dev
# Try a publish without uploading.
club publish --dry-run
# Build the archive locally for inspection.
club publish --to-archive ./pkg.tar.gz
# Upload an archive that was built earlier.
club publish --from-archive ./pkg.tar.gz --force

Validation

Before uploading, club publish runs the same 25 validators as dart pub publish. Each validator mirrors the upstream implementation — see the inline doc comments in lib/src/publish/validators/ for the GitHub link to its upstream counterpart.

Validators are grouped by severity:

  • Errors block publish unconditionally. Pass --skip-validation to bypass (or fix the issue).
  • Warnings require interactive confirmation, or --force in CI.
  • Hints are informational only and never block.

The full list of validators (in run order):

  1. AnalyzeValidator — runs dart analyze (issues surface as warnings; --enhanced upgrades it to dart analyze --fatal-warnings).
  2. ChangelogValidator — CHANGELOG present and mentions current version.
  3. CompiledDartdocValidator — no doc/api/ or .ddc.js artifacts.
  4. DependencyValidator — no path/git deps; reasonable version constraints.
  5. DependencyOverrideValidator — non-dev deps not silently overridden.
  6. DeprecatedFieldsValidator — no transformers, web, author, authors.
  7. DevtoolsExtensionValidatorextension/devtools/ is well formed.
  8. DirectoryValidator — only standard top-level dirs.
  9. ExecutableValidatorbin/<name>.dart exists for every executable.
  10. FileCaseValidator — no case collisions.
  11. FlutterConstraintValidator — Flutter SDK constraint with upper bound.
  12. FlutterPluginFormatValidator — uses the federated plugin format.
  13. GitignoreValidator — no archived files are simultaneously gitignored.
  14. GitStatusValidator — git working tree is clean.
  15. LeakDetectionValidator — no AWS/Google/GitHub/Slack/PEM keys in files.
  16. LicenseValidator — LICENSE file present at the root.
  17. NameValidator — package name follows pub conventions.
  18. PubspecPresentValidatorpubspec.yaml is in the archive.
  19. PubspecFieldValidator — required fields present and well-formed.
  20. PubspecTypoValidator — top-level field names are not typos.
  21. ReadmeValidator — README file present.
  22. RelativeVersionValidator — new version greater than latest published.
  23. SdkConstraintValidator — Dart SDK constraint with upper bound.
  24. SizeValidator — archive < 100 MiB.
  25. StrictDependenciesValidatorlib/, bin/, hook/ only import declared deps.

CI usage

club publish is fully CI-friendly. Two rules to remember:

  1. Set CLUB_TOKEN as a secret. --server <host> plus CLUB_TOKEN is enough to authorise the run — no prior club login step is required. When the URL passed to --server is not in the local credentials file, the env-token is used directly.
  2. Pass --server <host> so the multi-server picker doesn’t fire. If your pubspec already has publish_to: set to a server you have logged in to (or have CLUB_TOKEN for), you can omit --server.

CI auto-detection: when CI, CONTINUOUS_INTEGRATION, or BUILD_NUMBER env vars are set (the convention every major CI provider follows), club publish automatically skips the final publish confirmation. You don’t need --force in those environments. Use --force only when running in a non-TTY shell that’s not detected as CI.

.github/workflows/publish.yml
- name: Publish
run: club publish --server myclub.birju.dev
env:
CLUB_TOKEN: ${{ secrets.CLUB_TOKEN }}

That’s the entire publish step. No club login, no --force, no dart pub token add.

Other CI providers

.github/workflows/publish.yml
- name: Publish
run: club publish --server myclub.birju.dev
env:
CLUB_TOKEN: ${{ secrets.CLUB_TOKEN }}

CI flag matrix

Flag / env varWhen it’s neededWhat it controls
CLUB_TOKENAlwaysAuth. Shadows stored creds; no club login needed.
--server <host>Recommended in CISkips the multi-server picker; the env-token authorises this URL.
--forceOnly when CI auto-detection missesSkip the publish confirm. CI envs are auto-detected — you usually don’t need this.
--dry-runOptionalPlan + validators without uploading. Useful as a pre-merge gate.
--ignore-warningsOptional, with --dry-runDon’t exit non-zero on validator warnings.
--skip-validationDiscouragedBypasses the 25 validators. Server still validates.

CI behaviour reference

  • Server picker fails fast in non-interactive shells with an error pointing to --server.
  • Color output is auto-disabled when stdout is not a TTY (or when NO_COLOR is set, per no-color.org).
  • Exit codes match dart pub conventions:
    • 0 — success
    • 65 — validation failed (data error)
    • 66 — pubspec/package not found
    • 78 — config / server resolution / aborted

Comparing with dart pub publish

Aspectclub publishdart pub publish
Server resolution--server <host>publish_to → logged-in picker--server (deprecated) → publish_toPUB_HOSTED_URL
Works without publish_to❌ — refuses if none or missing
Picks from multiple logins✅ arrow-key TUI
ValidatorsAll 25, mirrored from upstreamAll 25
FlagsAll standard flags + --serverAll standard flags
Exit codesSame as upstream
Auth storage~/.config/club/credentials.json~/.config/dart/pub-tokens.json

Troubleshooting

Not logged in to <host>

Either --server or publish_to: points at a server you have not logged in to:

Terminal window
club login myclub.birju.dev

No club server is configured.

You have not run club login yet. Do that first.

Cannot prompt in a non-interactive shell.

You are in CI (or piping output) but the runner needs to confirm something. Pass --force (and/or --server <host> if there are multiple logins).

Found N error(s).

A validator produced a hard error. Read the message, fix the issue, or pass --skip-validation if you absolutely cannot fix it (the server will still validate).

dart analyze reported issues

Run dart analyze directly to see the issues, fix them, then retry. If you must publish anyway, --skip-validation bypasses the analyzer.

Implementation notes

The publish flow is laid out as small, focused modules under packages/club_cli/lib/src/publish/ so future updates to dart pub publish can be mirrored cheaply:

publish/
├── publish_runner.dart # Orchestration (matches LishCommand.runProtected)
├── pubspec_reader.dart # Pubspec parsing (uses pubspec_parse package)
├── server_resolver.dart # The --server / publish_to / picker logic
├── tarball_builder.dart # tar + gzip (matches createTarGz)
├── ignore_filter.dart # .pubignore + .gitignore handling
└── validators/
├── validator.dart # Base class + ValidationContext + Severity
├── runner.dart # Concurrent runner + report aggregation
└── *.dart # 25 validators, each linking to upstream source

Every file carries a doc comment with a link to the upstream Dart pub source it mirrors, making it straightforward to apply future SDK changes.