`pub bump` command (#4361)
diff --git a/lib/src/command/bump.dart b/lib/src/command/bump.dart
new file mode 100644
index 0000000..906ca2b
--- /dev/null
+++ b/lib/src/command/bump.dart
@@ -0,0 +1,131 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:collection/collection.dart';
+import 'package:pub_semver/pub_semver.dart';
+import 'package:yaml/yaml.dart';
+import 'package:yaml_edit/yaml_edit.dart';
+
+import '../command.dart';
+import '../io.dart';
+import '../log.dart' as log;
+
+class BumpSubcommand extends PubCommand {
+ @override
+ final String name;
+ @override
+ final String description;
+
+ final Version Function(Version) updateVersion;
+ BumpSubcommand(this.name, this.description, this.updateVersion) {
+ argParser.addFlag(
+ 'dry-run',
+ abbr: 'n',
+ negatable: false,
+ help: "Report what would change, but don't change anything.",
+ );
+ }
+
+ String? _versionLines(YamlMap map, String text, String prefix) {
+ final entry = map.nodes.entries
+ .firstWhereOrNull((e) => (e.key as YamlNode).value == 'version');
+ if (entry == null) return null;
+
+ final firstLine = (entry.key as YamlNode).span.start.line;
+ final lastLine = entry.value.span.end.line;
+ final lines = text.split('\n');
+ return lines
+ .sublist(firstLine, lastLine + 1)
+ .map((x) => '$prefix$x')
+ .join('\n');
+ }
+
+ @override
+ Future<void> runProtected() async {
+ final pubspec = entrypoint.workPackage.pubspec;
+ final currentVersion = pubspec.version;
+
+ final newVersion = updateVersion(currentVersion);
+
+ final originalPubspecText =
+ readTextFile(entrypoint.workPackage.pubspecPath);
+ final yamlEditor = YamlEditor(originalPubspecText);
+ yamlEditor.update(['version'], newVersion.toString());
+ final updatedPubspecText = yamlEditor.toString();
+ final beforeText = _versionLines(pubspec.fields, originalPubspecText, '- ');
+ final afterText = _versionLines(
+ yamlEditor.parseAt([]) as YamlMap,
+ updatedPubspecText,
+ '+ ',
+ );
+ if (argResults.flag('dry-run')) {
+ log.message('Would update version from $currentVersion to $newVersion.');
+ log.message('Diff:');
+ if (beforeText != null) {
+ log.message(beforeText);
+ }
+ if (afterText != null) {
+ log.message(afterText);
+ }
+ } else {
+ log.message('Updating version from $currentVersion to $newVersion.');
+ log.message('Diff:');
+
+ if (beforeText != null) {
+ log.message(beforeText);
+ }
+ if (afterText != null) {
+ log.message(afterText);
+ log.message('\nRemember to update `CHANGELOG.md` before publishing.');
+ }
+ writeTextFile(
+ entrypoint.workPackage.pubspecPath,
+ yamlEditor.toString(),
+ );
+ }
+ }
+}
+
+class BumpCommand extends PubCommand {
+ @override
+ String get name => 'bump';
+ @override
+ String get description => '''
+Increases the version number of the current package.
+''';
+
+ BumpCommand() {
+ addSubcommand(
+ BumpSubcommand(
+ 'major',
+ 'Increment the major version number (eg. 3.1.2 -> 4.0.0)',
+ (v) => v.nextMajor,
+ ),
+ );
+ addSubcommand(
+ BumpSubcommand(
+ 'minor',
+ 'Increment the minor version number (eg. 3.1.2 -> 3.2.0)',
+ (v) => v.nextMinor,
+ ),
+ );
+ addSubcommand(
+ BumpSubcommand(
+ 'patch',
+ 'Increment the patch version number (eg. 3.1.2 -> 3.1.3)',
+ (v) => v.nextPatch,
+ ),
+ );
+
+ addSubcommand(
+ BumpSubcommand(
+ 'breaking',
+ 'Increment to the next breaking version (eg. 0.1.2 -> 0.2.0)',
+ (v) => v.nextBreaking,
+ ),
+ );
+ }
+}
diff --git a/lib/src/command_runner.dart b/lib/src/command_runner.dart
index e089aad..cb42f47 100644
--- a/lib/src/command_runner.dart
+++ b/lib/src/command_runner.dart
@@ -11,6 +11,7 @@
import 'command.dart' show PubTopLevel, lineLength;
import 'command/add.dart';
+import 'command/bump.dart';
import 'command/cache.dart';
import 'command/deps.dart';
import 'command/downgrade.dart';
@@ -139,6 +140,7 @@
// When adding new commands be sure to also add them to
// `pub_embeddable_command.dart`.
addCommand(AddCommand());
+ addCommand(BumpCommand());
addCommand(CacheCommand());
addCommand(DepsCommand());
addCommand(DowngradeCommand());
diff --git a/lib/src/pub_embeddable_command.dart b/lib/src/pub_embeddable_command.dart
index a5fad51..e36bb42 100644
--- a/lib/src/pub_embeddable_command.dart
+++ b/lib/src/pub_embeddable_command.dart
@@ -5,6 +5,7 @@
import 'command.dart' show PubCommand, PubTopLevel;
import 'command.dart';
import 'command/add.dart';
+import 'command/bump.dart';
import 'command/cache.dart';
import 'command/deps.dart';
import 'command/downgrade.dart';
@@ -69,6 +70,7 @@
//
// New commands should (most likely) be included in both lists.
addSubcommand(AddCommand());
+ addSubcommand(BumpCommand());
addSubcommand(CacheCommand());
addSubcommand(DepsCommand());
addSubcommand(DowngradeCommand());
diff --git a/test/bump_test.dart b/test/bump_test.dart
new file mode 100644
index 0000000..b558a55
--- /dev/null
+++ b/test/bump_test.dart
@@ -0,0 +1,103 @@
+// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:test/test.dart';
+
+import 'descriptor.dart';
+import 'test_pub.dart';
+
+void main() {
+ void testBump(String part, String from, String to) {
+ test('Bumps the $part version from $from to $to', () async {
+ await dir(appPath, [
+ file(
+ 'pubspec.yaml',
+ '''
+name: myapp
+version: $from # comment
+environment:
+ sdk: $defaultSdkConstraint
+''',
+ ),
+ ]).create();
+ await runPub(
+ args: ['bump', part, '--dry-run'],
+ output: '''
+Would update version from $from to $to.
+Diff:
+- version: $from # comment
++ version: $to # comment
+''',
+ );
+ await runPub(
+ args: ['bump', part],
+ output: '''
+Updating version from $from to $to.
+Diff:
+- version: $from # comment
++ version: $to # comment
+
+Remember to update `CHANGELOG.md` before publishing.
+ ''',
+ );
+ await appDir(pubspec: {'version': to}).validate();
+ });
+ }
+
+ testBump('major', '0.0.0', '1.0.0');
+ testBump('major', '1.2.3', '2.0.0');
+ testBump('minor', '0.1.1-dev+2', '0.2.0');
+ testBump('minor', '1.2.3', '1.3.0');
+ testBump('patch', '0.1.1-dev+2', '0.1.1');
+ testBump('patch', '0.1.1+2', '0.1.2');
+ testBump('patch', '1.2.3', '1.2.4');
+ testBump('breaking', '0.2.0', '0.3.0');
+ testBump('breaking', '1.2.3', '2.0.0');
+
+ test('Creates top-level version field if missing', () async {
+ await dir(appPath, [
+ file('pubspec.yaml', '''
+name: my_app
+'''),
+ ]).create();
+ await runPub(
+ args: ['bump', 'breaking'],
+ output: contains('Updating version from 0.0.0 to 0.1.0'),
+ );
+ await dir(appPath, [
+ file('pubspec.yaml', '''
+name: my_app
+version: 0.1.0
+'''),
+ ]).validate();
+ });
+
+ test('Writes all lines of diff', () async {
+ await dir(appPath, [
+ file('pubspec.yaml', '''
+name: my_app
+version: >-
+ 1.0.0
+'''),
+ ]).create();
+ await runPub(
+ args: ['bump', 'minor'],
+ output: allOf(
+ contains('Updating version from 1.0.0 to 1.1.0'),
+ contains('''
+Diff:
+- version: >-
+- 1.0.0
++ version: 1.1.0
+'''),
+ ),
+ );
+ await dir(appPath, [
+ file('pubspec.yaml', '''
+name: my_app
+version: 1.1.0
+'''),
+ ]).validate();
+ });
+}
diff --git a/test/testdata/goldens/embedding/embedding_test/--help.txt b/test/testdata/goldens/embedding/embedding_test/--help.txt
index 55eacc6..16beaac 100644
--- a/test/testdata/goldens/embedding/embedding_test/--help.txt
+++ b/test/testdata/goldens/embedding/embedding_test/--help.txt
@@ -14,6 +14,7 @@
Available subcommands:
add Add dependencies to `pubspec.yaml`.
+ bump Increases the version number of the current package.
cache Work with the system cache.
deps Print package dependencies.
downgrade Downgrade the current package's dependencies to oldest versions.
diff --git a/test/testdata/goldens/help_test/pub bump --help.txt b/test/testdata/goldens/help_test/pub bump --help.txt
new file mode 100644
index 0000000..a076958
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub bump --help.txt
@@ -0,0 +1,18 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub bump --help
+Increases the version number of the current package.
+
+
+Usage: pub bump [arguments...]
+-h, --help Print this usage information.
+
+Available subcommands:
+ breaking Increment to the next breaking version (eg. 0.1.2 -> 0.2.0)
+ major Increment the major version number (eg. 3.1.2 -> 4.0.0)
+ minor Increment the minor version number (eg. 3.1.2 -> 3.2.0)
+ patch Increment the patch version number (eg. 3.1.2 -> 3.1.3)
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/help_test/pub bump breaking --help.txt b/test/testdata/goldens/help_test/pub bump breaking --help.txt
new file mode 100644
index 0000000..d25b9f9
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub bump breaking --help.txt
@@ -0,0 +1,12 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub bump breaking --help
+Increment to the next breaking version (eg. 0.1.2 -> 0.2.0)
+
+Usage: pub bump breaking <subcommand> [arguments...]
+-h, --help Print this usage information.
+-n, --dry-run Report what would change, but don't change anything.
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/help_test/pub bump major --help.txt b/test/testdata/goldens/help_test/pub bump major --help.txt
new file mode 100644
index 0000000..15937ad
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub bump major --help.txt
@@ -0,0 +1,12 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub bump major --help
+Increment the major version number (eg. 3.1.2 -> 4.0.0)
+
+Usage: pub bump major <subcommand> [arguments...]
+-h, --help Print this usage information.
+-n, --dry-run Report what would change, but don't change anything.
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/help_test/pub bump minor --help.txt b/test/testdata/goldens/help_test/pub bump minor --help.txt
new file mode 100644
index 0000000..927d5c2
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub bump minor --help.txt
@@ -0,0 +1,12 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub bump minor --help
+Increment the minor version number (eg. 3.1.2 -> 3.2.0)
+
+Usage: pub bump minor <subcommand> [arguments...]
+-h, --help Print this usage information.
+-n, --dry-run Report what would change, but don't change anything.
+
+Run "pub help" to see global options.
+
diff --git a/test/testdata/goldens/help_test/pub bump patch --help.txt b/test/testdata/goldens/help_test/pub bump patch --help.txt
new file mode 100644
index 0000000..057b669
--- /dev/null
+++ b/test/testdata/goldens/help_test/pub bump patch --help.txt
@@ -0,0 +1,12 @@
+# GENERATED BY: test/help_test.dart
+
+## Section 0
+$ pub bump patch --help
+Increment the patch version number (eg. 3.1.2 -> 3.1.3)
+
+Usage: pub bump patch <subcommand> [arguments...]
+-h, --help Print this usage information.
+-n, --dry-run Report what would change, but don't change anything.
+
+Run "pub help" to see global options.
+