Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .github/workflows/publish-packages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Publish Packages to pub.dev

on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+*' # matches 0.2.0-beta, 1.0.0, etc.

permissions:
contents: read
id-token: write # Required for OIDC authentication with pub.dev

jobs:
publish:
runs-on: ubuntu-latest
environment: pub.dev # Optional: requires approval before publishing

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Dart
uses: dart-lang/setup-dart@v1
with:
sdk: stable

- name: Extract version from tag
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT

- name: Prepare packages for publishing
run: dart tools/prepare_publish.dart ${{ steps.version.outputs.VERSION }}

- name: Verify package contents
run: |
for pkg in dart_node_core dart_node_express dart_node_ws dart_node_react dart_node_react_native; do
echo "=== $pkg pubspec.yaml ==="
cat packages/$pkg/pubspec.yaml
echo ""
done

# Publish packages in dependency order
# dart-lang/setup-dart handles OIDC token provisioning automatically
# Adding delays between publishes to allow pub.dev to index each package

- name: Publish dart_node_core
run: |
cd packages/dart_node_core
dart pub get
dart pub publish --force

- name: Wait for pub.dev to index dart_node_core
run: sleep 30

- name: Publish dart_node_express
run: |
cd packages/dart_node_express
dart pub get
dart pub publish --force

- name: Publish dart_node_ws
run: |
cd packages/dart_node_ws
dart pub get
dart pub publish --force

- name: Wait for pub.dev to index before dart_node_react
run: sleep 30

- name: Publish dart_node_react
run: |
cd packages/dart_node_react
dart pub get
dart pub publish --force

- name: Wait for pub.dev to index dart_node_react
run: sleep 30

- name: Publish dart_node_react_native
run: |
cd packages/dart_node_react_native
dart pub get
dart pub publish --force
117 changes: 117 additions & 0 deletions tools/prepare_publish.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// ignore_for_file: avoid_print
import 'dart:io';

/// Package dependency graph - order matters for publishing
/// Packages with no dependencies must be published first
const packageDeps = <String, List<String>>{
'dart_node_core': [],
'dart_node_express': ['dart_node_core'],
'dart_node_ws': ['dart_node_core'],
'dart_node_react': ['dart_node_core'],
'dart_node_react_native': ['dart_node_core', 'dart_node_react'],
};

/// Publishing order based on dependency graph (topological sort)
const publishOrder = [
'dart_node_core',
'dart_node_express',
'dart_node_ws',
'dart_node_react',
'dart_node_react_native',
];

void main(List<String> args) {
if (args.isEmpty) {
print('Usage: dart tools/prepare_publish.dart <version>');
print(' version - The version to set (e.g., 0.2.0-beta)');
print('');
print('This script prepares all packages for publishing by:');
print(' 1. Setting the version in all pubspec.yaml files');
print(' 2. Updating interdependencies to use pub.dev versions');
print(' 3. Removing publish_to: none from all pubspec.yaml files');
exit(1);
}

final version = args[0];
if (!_isValidVersion(version)) {
print('Error: Invalid version format: $version');
print('Expected format: X.Y.Z or X.Y.Z-prerelease');
exit(1);
}

final scriptDir = File(Platform.script.toFilePath()).parent;
final repoRoot = scriptDir.parent;
final packagesDir = Directory('${repoRoot.path}/packages');

print('Preparing packages for publishing version $version\n');

for (final packageName in publishOrder) {
_preparePackage(packagesDir, packageName, version);
}

print('\nAll packages prepared for publishing!');
print('Publishing order: ${publishOrder.join(' -> ')}');
}

void _preparePackage(Directory packagesDir, String packageName, String version) {
final pubspecFile = File('${packagesDir.path}/$packageName/pubspec.yaml');
if (!pubspecFile.existsSync()) {
print('Error: $packageName/pubspec.yaml not found');
exit(1);
}

var content = pubspecFile.readAsStringSync();
final changes = <String>[];

// 1. Update version
final versionPattern = RegExp(r'version:\s*[^\n]+');
if (versionPattern.hasMatch(content)) {
content = content.replaceFirst(versionPattern, 'version: $version');
changes.add('version -> $version');
}

// 2. Remove publish_to: none
final publishToPattern = RegExp(r'publish_to:\s*none\n?');
if (publishToPattern.hasMatch(content)) {
content = content.replaceFirst(publishToPattern, '');
changes.add('removed publish_to: none');
}

// 3. Update interdependencies to pub.dev versions
final deps = packageDeps[packageName] ?? [];
for (final dep in deps) {
content = _switchToPubDevDependency(content, dep, version);
changes.add('$dep -> ^$version');
}

pubspecFile.writeAsStringSync(content);
print('$packageName: ${changes.join(", ")}');
}

String _switchToPubDevDependency(String content, String depName, String version) {
// Match path dependency format
final pathPattern = RegExp(
'$depName:\\s*\\n\\s*path:\\s*[^\\n]+',
multiLine: true,
);

// Match existing version dependency format
final versionPattern = RegExp('$depName:\\s*\\^[^\\n]+');

final replacement = '$depName: ^$version';

if (pathPattern.hasMatch(content)) {
return content.replaceFirst(pathPattern, replacement);
}
if (versionPattern.hasMatch(content)) {
return content.replaceFirst(versionPattern, replacement);
}

return content;
}

bool _isValidVersion(String version) {
// Match semantic versioning with optional prerelease
final versionRegex = RegExp(r'^\d+\.\d+\.\d+(-[\w.]+)?$');
return versionRegex.hasMatch(version);
}