diff --git a/CHANGELOG.md b/CHANGELOG.md index 89d51f7..2aa1857 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [0.6.0] - 2019-09-02 +### Added +- the `--retain-build` (`-b`) flag to retain the build number when bumping versions + ## [0.5.0] - 2019-08-18 ### Changed - the `bump build` command sets the subsequent numeric build parts to zeroes @@ -41,7 +45,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial version -[Unreleased]: https://github.com/f3ath/pubspec-version/compare/0.5.0...HEAD +[Unreleased]: https://github.com/f3ath/pubspec-version/compare/0.6.0...HEAD +[0.6.0]: https://github.com/f3ath/pubspec-version/compare/0.5.0...0.6.0 [0.5.0]: https://github.com/f3ath/pubspec-version/compare/0.4.0...0.5.0 [0.4.0]: https://github.com/f3ath/pubspec-version/compare/0.3.0...0.4.0 [0.3.0]: https://github.com/f3ath/pubspec-version/compare/0.2.2...0.3.0 diff --git a/bin/pubver.dart b/bin/pubver.dart index 7daf311..d0b87eb 100644 --- a/bin/pubver.dart +++ b/bin/pubver.dart @@ -3,4 +3,4 @@ import 'dart:io'; import 'package:pubspec_version/pubspec_version.dart'; void main(List args) async => - exit(await Application(Console.stdio()).run(args)); + exit((await buildApp(Console.stdio()).run(args)) ?? 0); diff --git a/lib/pubspec_version.dart b/lib/pubspec_version.dart index 0b385ed..06f13ea 100644 --- a/lib/pubspec_version.dart +++ b/lib/pubspec_version.dart @@ -1,2 +1,2 @@ -export 'package:pubspec_version/src/application.dart'; +export 'package:pubspec_version/src/cli/build_app.dart'; export 'package:pubspec_version/src/console.dart'; diff --git a/lib/src/application.dart b/lib/src/application.dart deleted file mode 100644 index 4908bff..0000000 --- a/lib/src/application.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'dart:async'; - -import 'package:args/command_runner.dart'; -import 'package:pubspec_version/src/bump_version.dart'; -import 'package:pubspec_version/src/bumper.dart'; -import 'package:pubspec_version/src/console.dart'; -import 'package:pubspec_version/src/get_version_command.dart'; -import 'package:pubspec_version/src/next_build.dart'; -import 'package:pubspec_version/src/set_version_command.dart'; - -class Application { - final Console console; - - Application(this.console); - - Future run(List args) async { - final bumpers = [ - Bumper('breaking', 'Bumps the version to the next breaking.', console, - (_) => _.nextBreaking), - Bumper('major', 'Bumps the major version.', console, (_) => _.nextMajor), - Bumper('minor', 'Bumps the minor version.', console, (_) => _.nextMinor), - Bumper('patch', 'Bumps the patch version.', console, (_) => _.nextPatch), - Bumper('build', 'Bumps the first numeric part of the build version.', - console, nextBuild), - ]; - final commandRunner = - await CommandRunner('pubver', 'Package version manager.') - ..addCommand(BumpVersionCommand(bumpers)) - ..addCommand(SetVersionCommand(console)) - ..addCommand(GetVersionCommand(console)) - ..argParser.addOption('pubspec-dir', - abbr: 'd', - help: 'Directory containing pubspec.yaml.', - defaultsTo: '.'); - - try { - await commandRunner.run(args); - return 0; - } on UsageException catch (e) { - console.error(e.toString()); - return 64; - } - } -} diff --git a/lib/src/bump_version.dart b/lib/src/bump_version.dart deleted file mode 100644 index f9f3505..0000000 --- a/lib/src/bump_version.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:args/command_runner.dart'; - -class BumpVersionCommand extends Command { - final name = 'bump'; - final description = 'Bumps the package version.'; - - BumpVersionCommand(List> subcommands) { - subcommands.forEach(addSubcommand); - } -} diff --git a/lib/src/bumper.dart b/lib/src/bumper.dart deleted file mode 100644 index c53ff9a..0000000 --- a/lib/src/bumper.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:pub_semver/pub_semver.dart'; -import 'package:pubspec_version/src/version_command.dart'; - -import 'console.dart'; - -class Bumper extends VersionCommand { - final String name; - final String description; - final _Mutator mutator; - - Bumper(this.name, this.description, Console c, this.mutator) : super(c); - - Version nextVersion(Version v) => mutator(v); -} - -typedef Version _Mutator(Version v); diff --git a/lib/src/cli/build_app.dart b/lib/src/cli/build_app.dart new file mode 100644 index 0000000..be87b33 --- /dev/null +++ b/lib/src/cli/build_app.dart @@ -0,0 +1,88 @@ +import 'package:args/command_runner.dart'; +import 'package:pubspec_version/src/cli/command_container.dart'; +import 'package:pubspec_version/src/cli/pub_spec_command.dart'; +import 'package:pubspec_version/src/console.dart'; + +CommandRunner buildApp(Console console) => + CommandRunner('pubver', 'Package version manager.') + ..addCommand(CommandContainer( + 'bump', + 'Bumps the package version.', + [ + PubSpecCommand( + 'breaking', + 'Bumps the version to the next breaking.', + (_) async { + await _.bumpBreaking(); + console.log(await _.readVersion()); + return 0; + }, + ), + PubSpecCommand( + 'major', + 'Bumps the major version.', + (_) async { + await _.bumpMajor(); + console.log(await _.readVersion()); + return 0; + }, + ), + PubSpecCommand( + 'minor', + 'Bumps the minor version.', + (_) async { + await _.bumpMinor(); + console.log(await _.readVersion()); + return 0; + }, + ), + PubSpecCommand( + 'patch', + 'Bumps the patch version.', + (_) async { + await _.bumpPatch(); + console.log(await _.readVersion()); + return 0; + }, + ), + PubSpecCommand( + 'build', + 'Bumps the first numeric part of the build version.', + (_) async { + await _.bumpBuild(); + console.log(await _.readVersion()); + return 0; + }, + ), + ], + )) + ..addCommand(PubSpecCommand( + 'set', + 'Sets the package version.', + (_) async { + if (_.arguments.length < 2) { + console.error( + 'Please provide the version' + '\n' * 2 + 'Example: set 3.2.1'); + return 64; + } + await _.writeVersion(_.getArgument(1)); + console.log(await _.readVersion()); + return 0; + }, + )) + ..addCommand(PubSpecCommand( + 'get', + 'Gets the current package version.', + (_) async { + console.log(await _.readVersion()); + return 0; + }, + )) + ..argParser.addOption('pubspec-dir', + abbr: 'd', + help: 'Directory containing pubspec.yaml.', + defaultsTo: '.') + ..argParser.addFlag('retain-build', + abbr: 'b', + help: 'Retain build when bumping major, minor, or patch.', + defaultsTo: false); diff --git a/lib/src/cli/command_container.dart b/lib/src/cli/command_container.dart new file mode 100644 index 0000000..d1a4f94 --- /dev/null +++ b/lib/src/cli/command_container.dart @@ -0,0 +1,10 @@ +import 'package:args/command_runner.dart'; + +class CommandContainer extends Command { + final String name; + final String description; + + CommandContainer(this.name, this.description, List> subcommands) { + subcommands.forEach(addSubcommand); + } +} diff --git a/lib/src/cli/pub_spec_command.dart b/lib/src/cli/pub_spec_command.dart new file mode 100644 index 0000000..c743fa0 --- /dev/null +++ b/lib/src/cli/pub_spec_command.dart @@ -0,0 +1,36 @@ +import 'package:args/command_runner.dart'; +import 'package:pubspec_version/src/pubspec_version_app.dart'; + +class PubSpecCommand extends Command { + final String description; + final String name; + final _Payload _payload; + + PubSpecCommand(this.name, this.description, this._payload); + + bool get retainBuild => globalResults['retain-build']; + + Future readVersion() async => (await _app.readVersion()).toString(); + + Future writeVersion(String version) => _app.writeVersionString(version); + + Future bumpBreaking() => _app.bumpBreaking(retainBuild: retainBuild); + + Future bumpMajor() => _app.bumpMajor(retainBuild: retainBuild); + + Future bumpMinor() => _app.bumpMinor(retainBuild: retainBuild); + + Future bumpPatch() => _app.bumpPatch(retainBuild: retainBuild); + + Future bumpBuild() => _app.bumpBuild(); + + String getArgument(int index) => globalResults.arguments[index]; + + List get arguments => globalResults.arguments; + + Future run() => _payload(this); + + PubSpecVersionApp get _app => PubSpecVersionApp(globalResults['pubspec-dir']); +} + +typedef Future _Payload(PubSpecCommand command); diff --git a/lib/src/console.dart b/lib/src/console.dart index 32e4524..112897e 100644 --- a/lib/src/console.dart +++ b/lib/src/console.dart @@ -12,8 +12,8 @@ class Console { Console.stdio() : this(stdout, stderr); /// Writes the [message] to the error sink - void error(Object message) => _error.writeln(message); + void error(Object message) => _error.writeln(message.toString()); /// Writes the [message] the to the normal output sink - void log(Object message) => _output.writeln(message); + void log(Object message) => _output.writeln(message.toString()); } diff --git a/lib/src/get_version_command.dart b/lib/src/get_version_command.dart deleted file mode 100644 index 091e6b1..0000000 --- a/lib/src/get_version_command.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:args/command_runner.dart'; -import 'package:pubspec/pubspec.dart'; -import 'package:pubspec_version/src/console.dart'; - -class GetVersionCommand extends Command { - final Console console; - final name = 'get'; - final description = 'Gets the current package version.'; - - GetVersionCommand(this.console); - - @override - Future run() async { - final dir = Directory(globalResults['pubspec-dir']); - final pubSpec = await PubSpec.load(dir); - console.log(pubSpec.version.toString()); - } -} diff --git a/lib/src/pubspec_version_app.dart b/lib/src/pubspec_version_app.dart new file mode 100644 index 0000000..1795e36 --- /dev/null +++ b/lib/src/pubspec_version_app.dart @@ -0,0 +1,48 @@ +import 'dart:io'; + +import 'package:pub_semver/pub_semver.dart'; +import 'package:pubspec/pubspec.dart' as ps; +import 'package:pubspec_version/src/version_transform.dart'; + +class PubSpecVersionApp { + final Directory _dir; + + PubSpecVersionApp(String path) : _dir = Directory(path); + + Future readVersion() async { + final pubSpec = await _load(); + return VersionTransform(pubSpec.version); + } + + Future writeVersion(VersionTransform version) async { + final pubSpec = await _load(); + await pubSpec.copy(version: version.version).save(_dir); + } + + Future writeVersionString(String version) => + writeVersion(VersionTransform(Version.parse(version))); + + Future bumpBreaking({bool retainBuild = false}) => + _update((_) => _.bumpBreaking(), retainBuild: retainBuild); + + Future bumpMajor({bool retainBuild = false}) => + _update((_) => _.bumpMajor(), retainBuild: retainBuild); + + Future bumpMinor({bool retainBuild = false}) => + _update((_) => _.bumpMinor(), retainBuild: retainBuild); + + Future bumpPatch({bool retainBuild = false}) => + _update((_) => _.bumpPatch(), retainBuild: retainBuild); + + Future bumpBuild() => _update((_) => _.bumpBuild()); + + Future _update(VersionTransform mutate(VersionTransform v), + {bool retainBuild = false}) async { + final version = await readVersion(); + var newVersion = + retainBuild ? mutate(version).withBuildFrom(version) : mutate(version); + await writeVersion(newVersion); + } + + Future _load() => ps.PubSpec.load(_dir); +} diff --git a/lib/src/set_version_command.dart b/lib/src/set_version_command.dart deleted file mode 100644 index 2969711..0000000 --- a/lib/src/set_version_command.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:args/command_runner.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:pubspec_version/src/console.dart'; -import 'package:pubspec_version/src/version_command.dart'; - -class SetVersionCommand extends VersionCommand { - final name = 'set'; - final description = 'Sets the package version.'; - - SetVersionCommand(Console c) : super(c); - - Version nextVersion(Version v) { - if (globalResults.arguments.length < 2) { - throw UsageException('Please provide the version', 'Example: set 3.2.1'); - } - return Version.parse(globalResults.arguments[1]); - } -} diff --git a/lib/src/version_command.dart b/lib/src/version_command.dart deleted file mode 100644 index c28fb7d..0000000 --- a/lib/src/version_command.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:args/command_runner.dart'; -import 'package:pub_semver/pub_semver.dart'; -import 'package:pubspec/pubspec.dart'; -import 'package:pubspec_version/src/console.dart'; - -abstract class VersionCommand extends Command { - final Console console; - - VersionCommand(this.console); - - Future run() async { - final dir = Directory(globalResults['pubspec-dir']); - final pubSpec = await PubSpec.load(dir); - final version = nextVersion(pubSpec.version); - await pubSpec.copy(version: version).save(dir); - console.log(version.toString()); - } - - Version nextVersion(Version v); -} diff --git a/lib/src/version_transform.dart b/lib/src/version_transform.dart new file mode 100644 index 0000000..07e8ebc --- /dev/null +++ b/lib/src/version_transform.dart @@ -0,0 +1,37 @@ +import 'package:pub_semver/pub_semver.dart'; + +class VersionTransform { + final Version version; + + VersionTransform(this.version); + + VersionTransform bumpBreaking() => VersionTransform(version.nextBreaking); + + VersionTransform bumpMajor() => VersionTransform(version.nextMajor); + + VersionTransform bumpMinor() => VersionTransform(version.nextMinor); + + VersionTransform bumpPatch() => VersionTransform(version.nextPatch); + + VersionTransform bumpBuild() { + var incremented = false; + final build = version.build.map((part) { + if (part is! int) return part; + if (incremented) return 0; + incremented = true; + return part + 1; + }).toList(); + if (!incremented) build.add(1); + return withBuild(build); + } + + VersionTransform withBuild(List build) => + VersionTransform(Version(version.major, version.minor, version.patch, + build: build.join('.'))); + + VersionTransform withBuildFrom(VersionTransform other) => + withBuild(other.version.build); + + @override + String toString() => version.toString(); +} diff --git a/test/functional_test.dart b/test/functional_test.dart index fdcb75e..5bc376b 100644 --- a/test/functional_test.dart +++ b/test/functional_test.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:args/command_runner.dart'; import 'package:mockito/mockito.dart'; import 'package:pubspec/pubspec.dart'; import 'package:pubspec_version/pubspec_version.dart'; @@ -10,18 +11,20 @@ class MockConsole extends Mock implements Console {} void main() { Directory temp; MockConsole mockConsole; - Application app; + CommandRunner app; expectVersion(String v) async { expect((await PubSpec.load(Directory(temp.path))).version.toString(), v); - expect(verify(mockConsole.log(captureAny)).captured, [v]); + expect( + verify(mockConsole.log(captureAny)).captured.map((_) => _.toString()), + [v]); } setUp(() async { temp = await Directory.systemTemp.createTemp(); File('test/pubspec_sample.yaml').copy('${temp.path}/pubspec.yaml'); mockConsole = MockConsole(); - app = Application(mockConsole); + app = buildApp(mockConsole); }); tearDown(() async { @@ -34,24 +37,48 @@ void main() { await expectVersion('0.4.0'); }); + test('bump breaking retaining build', () async { + final code = await app.run(['bump', 'breaking', '-d', temp.path, '-b']); + expect(code, 0); + await expectVersion('0.4.0+42'); + }); + test('bump major', () async { final code = await app.run(['bump', 'major', '-d', temp.path]); expect(code, 0); await expectVersion('1.0.0'); }); + test('bump major retaining build', () async { + final code = await app.run(['bump', 'major', '-d', temp.path, '-b']); + expect(code, 0); + await expectVersion('1.0.0+42'); + }); + test('bump minor', () async { final code = await app.run(['bump', 'minor', '-d', temp.path]); expect(code, 0); await expectVersion('0.4.0'); }); + test('bump minor retaining build', () async { + final code = await app.run(['bump', 'minor', '-d', temp.path, '-b']); + expect(code, 0); + await expectVersion('0.4.0+42'); + }); + test('bump patch', () async { final code = await app.run(['bump', 'patch', '-d', temp.path]); expect(code, 0); await expectVersion('0.3.3'); }); + test('bump patch retaining build', () async { + final code = await app.run(['bump', 'patch', '-d', temp.path, '-b']); + expect(code, 0); + await expectVersion('0.3.3+42'); + }); + test('bump build', () async { final code = await app.run(['bump', 'build', '-d', temp.path]); expect(code, 0); diff --git a/test/unit/next_build_test.dart b/test/unit/version_transform_test.dart similarity index 61% rename from test/unit/next_build_test.dart rename to test/unit/version_transform_test.dart index d0a9a2f..bdc0a06 100644 --- a/test/unit/next_build_test.dart +++ b/test/unit/version_transform_test.dart @@ -1,20 +1,20 @@ import 'package:pub_semver/pub_semver.dart'; -import 'package:pubspec_version/src/next_build.dart'; +import 'package:pubspec_version/src/version_transform.dart'; import 'package:test/test.dart'; void main() { test('bumping empty build sets it to 1', () async { final v = Version.parse('1.2.3'); - expect(nextBuild(v).toString(), '1.2.3+1'); + expect(VersionTransform(v).bumpBuild().toString(), '1.2.3+1'); }); test('non-numeric build gets ".1" appended to it', () async { final v = Version.parse('1.2.3+foo42'); - expect(nextBuild(v).toString(), '1.2.3+foo42.1'); + expect(VersionTransform(v).bumpBuild().toString(), '1.2.3+foo42.1'); }); test('in a complex build the first numeric part gets incremented', () async { final v = Version.parse('1.2.3+foo.1.2.3.bar'); - expect(nextBuild(v).toString(), '1.2.3+foo.2.0.0.bar'); + expect(VersionTransform(v).bumpBuild().toString(), '1.2.3+foo.2.0.0.bar'); }); }