planRelease function

List<PackageRelease> planRelease({
  1. required List<WorkspacePackage> packages,
  2. required Map<String, List<ConventionalCommit>> commits,
  3. Map<String, Map<String, String>> constraints = const {},
  4. Version? sharedVersion,
  5. Bump? forceBump,
  6. String? prerelease,
  7. bool graduate = false,
})

Plans new versions from the commits that touch each package.

  • commits holds each package's commits since its last release.
  • constraints holds each package's version constraints on other workspace packages, as written in its pubspec.
  • With sharedVersion, every package gets one version, raised by the largest bump any package needs (the catalog version).
  • A package whose constraint on a released workspace package no longer allows the new version gets the constraint raised and a patch release.
  • forceBump replaces the bump from commits for every package.

Implementation

List<PackageRelease> planRelease({
  required List<WorkspacePackage> packages,
  required Map<String, List<ConventionalCommit>> commits,
  Map<String, Map<String, String>> constraints = const {},
  Version? sharedVersion,
  Bump? forceBump,
  String? prerelease,
  bool graduate = false,
}) {
  Bump bumpOf(WorkspacePackage package) =>
      forceBump ??
      (commits[package.name] ?? const [])
          .map((commit) => commit.bump)
          .fold(Bump.none, (a, b) => a.max(b));

  final versioned = [
    for (final package in packages)
      if (package.version != null) package,
  ];

  if (sharedVersion != null) {
    final bump = versioned.map(bumpOf).fold(Bump.none, (a, b) => a.max(b));
    if (bump == Bump.none && !graduate && prerelease == null) {
      return const [];
    }
    final to = nextVersion(
      sharedVersion,
      bump == Bump.none ? Bump.patch : bump,
      prerelease: prerelease,
      graduate: graduate,
    );
    final releases = [
      for (final package in versioned)
        PackageRelease(
          package: package,
          from: Version.parse(package.version!),
          to: to,
          commits: commits[package.name] ?? const [],
        ),
    ];
    _raiseConstraints(releases, releases, constraints);
    return releases;
  }

  final releases = <String, PackageRelease>{};
  for (final package in versioned) {
    final bump = bumpOf(package);
    if (bump == Bump.none && !(graduate && _isPre(package))) {
      continue;
    }
    final from = Version.parse(package.version!);
    releases[package.name] = PackageRelease(
      package: package,
      from: from,
      to: nextVersion(from, bump, prerelease: prerelease, graduate: graduate),
      commits: commits[package.name] ?? const [],
    );
  }

  var changed = true;
  while (changed) {
    changed = false;
    for (final package in versioned) {
      final current = releases[package.name];
      final needed = _constraintUpdates(
        package,
        releases.values.toList(),
        constraints,
      );
      final missing = {
        for (final MapEntry(:key, :value) in needed.entries)
          if (current?.constraintUpdates[key] != value) key: value,
      };
      if (missing.isEmpty) {
        continue;
      }
      changed = true;
      final release =
          current ??
          (releases[package.name] = PackageRelease(
            package: package,
            from: Version.parse(package.version!),
            to: nextVersion(
              Version.parse(package.version!),
              Bump.patch,
              prerelease: prerelease,
            ),
            commits: commits[package.name] ?? const [],
          ));
      release.constraintUpdates.addAll(missing);
    }
  }

  return [
    for (final package in versioned)
      if (releases[package.name] case final release?) release,
  ];
}