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
$ cd path/to/your/package$ club publishPublishing my_package 1.2.0 from /Users/me/code/my_packageTarget server: https://club.example.com (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 https://club.example.com? [Y/n] y
Uploading to https://club.example.com...✓ Successfully uploaded my_package version 1.2.0.Flags
The flag set is identical to dart pub publish (matched against the
upstream LishCommand)
plus --server.
| Flag | Short | Description |
|---|---|---|
--directory <path> | -C | Run as if <path> is the package directory. Useful for monorepos. |
--dry-run | -n | Validate without uploading. Exits non-zero on errors (and warnings unless --ignore-warnings). |
--force | -f | Skip confirmation prompts. Required in CI when there is no TTY. |
--skip-validation | Bypass all 25 client-side validators. The server still validates. | |
--ignore-warnings | With --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 <url> | -s | Override publish_to and the auto-picker. 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. |
How the target server is resolved
club publish resolves the target server with this priority — only the
first match is used:
--server <url>flag (highest priority). The URL must be in your logged-in credentials, otherwise the command exits with a hint to runclub login.publish_to:in pubspec.yaml, when present and notnone. The URL must be in your logged-in credentials.- 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 <url>.
- 1 server: used automatically (with confirmation, unless
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
# 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 https://internal.club.example.com
# 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 --forceValidation
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-validationto bypass (or fix the issue). - Warnings require interactive confirmation, or
--forcein CI. - Hints are informational only and never block.
The full list of validators (in run order):
AnalyzeValidator— runsdart analyze --fatal-infos.ChangelogValidator— CHANGELOG present and mentions current version.CompiledDartdocValidator— nodoc/api/or.ddc.jsartifacts.DependencyValidator— no path/git deps; reasonable version constraints.DependencyOverrideValidator— non-dev deps not silently overridden.DeprecatedFieldsValidator— notransformers,web,author,authors.DevtoolsExtensionValidator—extension/devtools/is well formed.DirectoryValidator— only standard top-level dirs.ExecutableValidator—bin/<name>.dartexists for every executable.FileCaseValidator— no case collisions.FlutterConstraintValidator— Flutter SDK constraint with upper bound.FlutterPluginFormatValidator— uses the federated plugin format.GitignoreValidator— no archived files are simultaneously gitignored.GitStatusValidator— git working tree is clean.LeakDetectionValidator— no AWS/Google/GitHub/Slack/PEM keys in files.LicenseValidator— LICENSE file present at the root.NameValidator— package name follows pub conventions.PubspecPresentValidator—pubspec.yamlis in the archive.PubspecFieldValidator— required fields present and well-formed.PubspecTypoValidator— top-level field names are not typos.ReadmeValidator— README file present.RelativeVersionValidator— new version greater than latest published.SdkConstraintValidator— Dart SDK constraint with upper bound.SizeValidator— archive < 100 MiB.StrictDependenciesValidator—lib/,bin/,hook/only import declared deps.
CI usage
club publish is fully CI-friendly:
- All confirmation prompts can be skipped with
--force. - 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_COLORis set, per no-color.org). - Exit codes match dart pub conventions:
0— success65— validation failed (data error)66— pubspec/package not found78— config / server resolution / aborted
A typical CI step looks like:
- name: Publish run: club publish --force --server https://club.example.com env: CLUB_TOKEN: ${{ secrets.CLUB_TOKEN }}CLUB_TOKEN shadows any stored credentials, so there is no need to pre-seed a credentials file. Alternatively, run club login --key club_pat_... https://club.example.com as a previous step to materialise the credentials file.
Comparing with dart pub publish
| Aspect | club publish | dart pub publish |
|---|---|---|
| Server resolution | --server → publish_to → logged-in picker | --server (deprecated) → publish_to → PUB_HOSTED_URL |
Works without publish_to | ✅ | ❌ — refuses if none or missing |
| Picks from multiple logins | ✅ arrow-key TUI | ❌ |
| Validators | All 25, mirrored from upstream | All 25 |
| Flags | All standard flags + --server | All standard flags |
| Exit codes | Same as upstream | — |
| Auth storage | ~/.config/club/credentials.json | ~/.config/dart/pub-tokens.json |
Troubleshooting
Not logged in to <url>
Either --server or publish_to: points at a server you have not logged
in to:
club login https://club.example.comNo 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 <url> 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 sourceEvery file carries a doc comment with a link to the upstream Dart pub source it mirrors, making it straightforward to apply future SDK changes.