version method
Future<void>
version({
- GlobalOptions? global,
- PackageFilter? filter,
- bool asPrerelease = false,
- bool asStableRelease = false,
- bool updateChangelog = true,
- bool updateDependentsConstraints = true,
- bool updateDependentsVersions = true,
- bool gitTag = true,
- bool? releaseUrl,
- String? message,
- bool force = false,
- bool showPrivatePackages = false,
- String? preid,
- String? dependentPreid,
- bool versionPrivatePackages = false,
- Map<
String, ManualVersionChange> manualVersions = const {},
inherited
Version packages automatically based on the git history or with manually specified versions.
Implementation
Future<void> version({
GlobalOptions? global,
PackageFilter? filter,
bool asPrerelease = false,
bool asStableRelease = false,
bool updateChangelog = true,
bool updateDependentsConstraints = true,
bool updateDependentsVersions = true,
bool gitTag = true,
bool? releaseUrl,
String? message,
bool force = false,
// all
bool showPrivatePackages = false,
String? preid,
String? dependentPreid,
bool versionPrivatePackages = false,
Map<String, versioning.ManualVersionChange> manualVersions = const {},
}) async {
if (asPrerelease && asStableRelease) {
throw ArgumentError('Cannot use both asPrerelease and asStableRelease.');
}
if (updateDependentsVersions && !updateDependentsConstraints) {
throw ArgumentError(
'Cannot use updateDependentsVersions without '
'updateDependentsConstraints.',
);
}
if ((asPrerelease || asStableRelease) && manualVersions.isNotEmpty) {
throw ArgumentError(
'Cannot use manualVersions with asPrerelease or asStableRelease.',
);
}
final workspace = await createWorkspace(
global: global,
// We ignore `since` package list filtering on the 'version' command as it
// already filters it itself, filtering here would map dependant version
// fail as it won't be aware of any packages that have been filtered out
// here because of the 'since' filter.
filter: filter?.copyWithUpdatedSince(null),
);
if (workspace.config.commands.version.branch != null) {
final currentBranchName = await gitGetCurrentBranchName(
workingDirectory: workspace.path,
logger: logger,
);
if (currentBranchName != workspace.config.commands.version.branch) {
throw RestrictedBranchException(
workspace.config.commands.version.branch!,
currentBranchName,
);
}
}
message ??=
workspace.config.commands.version.message ?? defaultCommitMessage;
logger
..command('melos version')
..child(targetStyle(workspace.path))
..newLine();
final commitMessageTemplate = Template(message, delimiters: '{ }');
final packageCommits = await _getPackageCommits(
workspace,
versionPrivatePackages: versionPrivatePackages,
since: filter?.updatedSince,
);
final packagesWithVersionableCommits =
_getPackagesWithVersionableCommits(packageCommits);
for (final packageName in manualVersions.keys) {
if (!workspace.allPackages.keys.contains(packageName)) {
exitCode = 1;
logger
.error('package "$packageName" does not exist in this workspace.');
return;
}
}
final packagesToManuallyVersion = manualVersions.keys
.map((packageName) => workspace.allPackages[packageName]!)
.toSet();
final packagesToAutoVersion = {
for (final package in workspace.filteredPackages.values)
if (!packagesToManuallyVersion.contains(package))
if (packagesWithVersionableCommits.contains(package.name))
if (!asStableRelease || !package.version.isPreRelease) package
};
final packagesToVersion = {
...packagesToManuallyVersion,
...packagesToAutoVersion,
};
final dependentPackagesToVersion = <Package>{};
final pendingPackageUpdates = <MelosPendingPackageUpdate>[];
if (workspace.config.scripts.containsKey('preversion')) {
logger
..log('Running "preversion" lifecycle script...')
..newLine();
await run(scriptName: 'preversion');
}
if (asStableRelease) {
for (final package in workspace.filteredPackages.values) {
if (!package.version.isPreRelease) continue;
pendingPackageUpdates.add(
MelosPendingPackageUpdate(
workspace,
package,
const [],
PackageUpdateReason.graduate,
graduate: asStableRelease,
prerelease: asPrerelease,
preid: preid,
logger: logger,
),
);
final packageUnscoped = workspace.allPackages[package.name]!;
dependentPackagesToVersion
.addAll(packageUnscoped.dependentsInWorkspace.values);
}
}
for (final package in packagesToVersion) {
final packageUnscoped = workspace.allPackages[package.name]!;
dependentPackagesToVersion
.addAll(packageUnscoped.dependentsInWorkspace.values);
// Add dependentsInWorkspace dependents in the workspace until no more are
// added.
var packagesAdded = 1;
while (packagesAdded != 0) {
final packagesCountBefore = dependentPackagesToVersion.length;
final packages = <Package>{...dependentPackagesToVersion};
for (final dependentPackage in packages) {
dependentPackagesToVersion
.addAll(dependentPackage.dependentsInWorkspace.values);
}
packagesAdded = dependentPackagesToVersion.length - packagesCountBefore;
}
}
pendingPackageUpdates.addAll(
packagesToManuallyVersion.map(
(package) {
final name = package.name;
final version = manualVersions[name]!(package.version);
final commits = packageCommits[name] ?? [];
String? userChangelogMessage;
if (updateChangelog) {
final bool promptForMessage;
String? defaultUserChangelogMessage;
if (commits.isEmpty) {
logger.log(
'Could not find any commits for manually versioned package '
'"$name".',
);
promptForMessage = true;
defaultUserChangelogMessage = 'Bump "$name" to `$version`.';
} else {
logger.log(
'Found commits for manually versioned package "$name".',
);
promptForMessage = promptBool(
message: 'Do you want to provide an additional changelog entry '
'message?',
defaultsToWithoutPrompt: false,
);
}
if (promptForMessage) {
userChangelogMessage = promptInput(
'Provide a changelog entry message',
defaultsTo: defaultUserChangelogMessage,
);
}
}
return MelosPendingPackageUpdate.manual(
workspace,
package,
commits,
version,
userChangelogMessage: userChangelogMessage,
logger: logger,
);
},
),
);
pendingPackageUpdates.addAll(
packagesToAutoVersion.map(
(package) => MelosPendingPackageUpdate(
workspace,
package,
packageCommits[package.name]!,
PackageUpdateReason.commit,
graduate: asStableRelease,
prerelease: asPrerelease,
preid: preid,
logger: logger,
),
),
);
for (final package in dependentPackagesToVersion) {
final packageHasPendingUpdate = pendingPackageUpdates.any(
(packageToVersion) => packageToVersion.package.name == package.name,
);
if (!packagesToVersion.contains(package) && !packageHasPendingUpdate) {
pendingPackageUpdates.add(
MelosPendingPackageUpdate(
workspace,
package,
const [],
PackageUpdateReason.dependency,
// Dependent packages that should have graduated would have already
// gone through graduation logic above. So graduate should use the
// default of 'false' here so as not to graduate anything that was
// specifically excluded.
// graduate: false,
prerelease: asPrerelease,
preid: dependentPreid ?? preid,
logger: logger,
),
);
}
}
// Filter out private packages.
if (!versionPrivatePackages) {
pendingPackageUpdates.removeWhere((update) => update.package.isPrivate);
}
if (pendingPackageUpdates.isEmpty) {
logger.warning(
'No packages were found that required versioning.',
label: false,
);
logger.hint(
'Try running "melos list" with the same filtering options to see a '
'list of packages that were included.',
);
logger.hint(
'Try running "melos version --all" to include private packages',
);
return;
}
logger.log(
AnsiStyles.magentaBright(
'The following '
'${packageNameStyle(pendingPackageUpdates.length.toString())} '
'packages will be updated:\n',
),
);
_logNewVersionTable(
pendingPackageUpdates,
updateDependentsVersions: updateDependentsVersions,
updateDependentsConstraints: updateDependentsConstraints,
);
// show commit message
for (final element in pendingPackageUpdates) {
logger.trace(AnsiStyles.yellow.bold(element.package.name));
final commitLogger = logger.childWithoutMessage();
for (final commit in element.commits) {
commitLogger.trace(commit.message);
}
}
final shouldContinue = force || promptBool();
if (!shouldContinue) {
logger.error('Operation was canceled.', label: false);
exitCode = 1;
return;
}
await _performPackageUpdates(
pendingPackageUpdates,
updateDependentsVersions: updateDependentsVersions,
updateDependentsConstraints: updateDependentsConstraints,
updateChangelog: updateChangelog,
workspace: workspace,
);
// TODO allow support for individual package lifecycle version scripts
if (workspace.config.scripts.containsKey('version')) {
logger.log('Running "version" lifecycle script...\n');
await run(scriptName: 'version');
}
if (gitTag) {
await _gitStageChanges(pendingPackageUpdates, workspace);
await _gitCommitChanges(
workspace,
pendingPackageUpdates,
commitMessageTemplate,
updateDependentsVersions: updateDependentsVersions,
);
await _gitTagChanges(
pendingPackageUpdates,
updateDependentsVersions,
);
}
// TODO allow support for individual package lifecycle postversion scripts
if (workspace.config.scripts.containsKey('postversion')) {
logger.log('Running "postversion" lifecycle script...\n');
await run(scriptName: 'postversion');
}
if (gitTag) {
// TODO automatic push support
logger.success(
'Versioning successful. '
'Ensure you push your git changes and tags (if applicable) via '
'${AnsiStyles.bgBlack.gray('git push --follow-tags')}',
);
} else {
logger.success(
'Versioning successful. '
'Ensure you commit and push your changes (if applicable).',
);
}
// TODO Support for automatically creating a release,
// e.g. when GITHUB_TOKEN is present in CI or using `gh release create`
// from GitHub CLI.
if (releaseUrl ?? config.commands.version.releaseUrl) {
final repository = workspace.config.repository;
if (repository == null) {
logger.warning(
'No repository configured in melos.yaml to generate a '
'release for.',
);
} else if (repository is! SupportsManualRelease) {
logger.warning('Repository does not support releases urls');
} else {
final pendingPackageReleases = pendingPackageUpdates.map((update) {
return link(
repository.releaseUrlForUpdate(update),
update.package.name,
);
}).join(ansiStylesDisabled ? '\n' : ', ');
logger.success(
'Make sure you create a release for each new package version:'
'${ansiStylesDisabled ? '\n' : ' '}'
'${AnsiStyles.bgBlack.gray(pendingPackageReleases)}',
);
}
}
}