Skip to content

SDK Publishing

ClubClient exposes two publishing methods directly on the client. Both handle the multi-step upload flow internally and return the server’s success message as a String.

publishFile and publish

Publish from a File Path

The simplest approach — pass the path to a .tar.gz archive:

final message = await client.publishFile(
'build/my_package-1.0.0.tar.gz',
);
print(message); // "Successfully uploaded my_package version 1.0.0."

Publish from a Byte Stream

If you already have the archive as a byte stream (e.g., from an in-memory build):

import 'dart:io';
final file = File('my_package-1.0.0.tar.gz');
final stream = file.openRead();
final length = await file.length();
final message = await client.publish(stream, length: length);
print(message);

Complete CI/CD Example

A script that publishes a package from environment variables:

import 'dart:io';
import 'package:club_api/club_api.dart';
Future<void> main() async {
final serverUrl = Platform.environment['SERVER_URL'];
final token = Platform.environment['CLUB_TOKEN'];
final archivePath = Platform.environment['ARCHIVE_PATH'];
if (serverUrl == null || token == null || archivePath == null) {
stderr.writeln(
'Set SERVER_URL, CLUB_TOKEN, and ARCHIVE_PATH',
);
exit(1);
}
final client = ClubClient(
serverUrl: Uri.parse(serverUrl),
token: token,
);
try {
final message = await client.publishFile(archivePath);
print(message);
} on ClubConflictException catch (e) {
stderr.writeln('Version already exists: ${e.message}');
exit(1);
} on ClubAuthException catch (e) {
stderr.writeln('Authentication failed: ${e.message}');
exit(1);
} on ClubForbiddenException catch (e) {
stderr.writeln('Insufficient permissions: ${e.message}');
exit(1);
} on ClubBadRequestException catch (e) {
stderr.writeln('Invalid package: ${e.message}');
exit(1);
} on ClubApiException catch (e) {
stderr.writeln('Publish failed (${e.code}): ${e.message}');
exit(1);
} finally {
client.close();
}
}

Error Handling

Publishing can fail at each step. Here are the common errors:

ExceptionHTTP CodeCause
ClubAuthException401Token missing, expired, or revoked
ClubForbiddenException403Token lacks write scope, or user is not an uploader
ClubBadRequestException400Invalid archive, missing pubspec fields, invalid version
ClubConflictException409Version already exists with different content
ClubServerException500Server-side error during processing

Handling Duplicate Uploads

If you upload the same version with identical content (matching SHA-256 hash), the server treats it as idempotent and returns success. If the content differs, you get a 409 Conflict:

try {
await client.publishFile('my_package-1.0.0.tar.gz');
} on ClubConflictException {
print('Version 1.0.0 already exists with different content.');
print('Bump the version number in pubspec.yaml and try again.');
}

Archive Requirements

The uploaded archive must meet these requirements:

  • Valid .tar.gz format
  • Contains a valid pubspec.yaml at the root (nested pubspec.yaml files in subdirectories like example/ are rejected)
  • pubspec.yaml has name, version, and description
  • Version is a canonical semantic version string
  • Package name is lowercase, alphanumeric with underscores, 1-64 characters
  • Archive size does not exceed the server’s max upload size (default: 100 MB)