diff --git a/.cider.yaml b/.cider.yaml new file mode 100644 index 0000000..96fd85b --- /dev/null +++ b/.cider.yaml @@ -0,0 +1,2 @@ +changelog: + diff_link_template: 'https://github.com/f3ath/cider/compare/%from%...%to%' \ No newline at end of file diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml new file mode 100644 index 0000000..b892dcd --- /dev/null +++ b/.github/workflows/dart.yml @@ -0,0 +1,24 @@ +name: Dart CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + container: + image: google/dart:latest + + steps: + - uses: actions/checkout@v2 + - name: Install dependencies + run: pub get + - name: Analyzer + run: dartanalyzer lib test example --fatal-infos --fatal-warnings + - name: Tests + run: pub run test diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..99f2893 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog +All notable changes to this project will be documented in this file. + +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.0.0+dev.1 - 2020-07-23 +### Added +- Initial version + +[Unreleased]: https://github.com/f3ath/cider/compare/0.0.0+dev.1...HEAD \ No newline at end of file diff --git a/README.md b/README.md index 9d60dcf..a848dc4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,87 @@ -# dart-ci -Tools for Dart package maintainers +# CIDER (CI for Dart. Efficient Releases) +A command-line utility to automate package maintenance. It manipulates the changelog and pubspec.yaml. + +This tool assumes that the changelog: + - is called `CHANGELOG.md` + - is sitting in the project root folder + - strictly follows the [Keep a Changelog v1.0.0](https://keepachangelog.com/en/1.0.0/) format + - uses basic markdown (no HTML and complex formatting supported) + +It also assumes that your project follows [Semantic Versioning v2.0.0](https://semver.org/spec/v2.0.0.html). + +## Install +``` +pub global activate cider +``` + +## Usage +### Logging changes to CHANGELOG +This command will add a new line to the `Unreleased` section of the changelog +``` +cider log +``` + - **type** is one of: `added`, `changed`, `deprecated`, `removed`, `fixed`, `security` + - **description** is a markdown text line + +Examples +``` +cider log change 'New turbo engine installed' +cider log add 'Support for rocket fuel and kerosene' +cider log fix 'No more wheels falling off' +``` + +### Bumping the project version +``` +cider bump +``` +- **version** can be any of: `breaking`, `major`, `minor`, `patch`, `build` + +Use `--keep-build` or `-b` to retain the build part of the version. + +Use `--print` or `-p` to print the new version. + +Version before | Command | Version after +--- | --- | --- +1.2.3 | `cider bump breaking` | 2.0.0 +0.2.1 | `cider bump breaking` | 0.3.0 +0.2.1 | `cider bump major` | 1.0.0 +0.2.1 | `cider bump minor` | 0.3.0 +0.2.1 | `cider bump patch` | 0.2.2 +0.2.1 | `cider bump build` | 0.2.1+1 +0.2.1+42 | `cider bump build` | 0.2.1+43 +0.2.1+foo | `cider bump build` | 0.2.1+foo.1 +0.2.1+42.foo | `cider bump build` | 0.2.1+43.foo +0.2.1+foo.bar.1.2 | `cider bump build` | 0.2.1+foo.bar.2.0 + +The `cider bump build` command is a bit tricky. It either increments the first numeric part of the build (if there is a +numeric part) setting other numeric parts to zeroes, or appends `.1` to the build (otherwise). + +Retaining the build part: + +Version before | Command | Version after +--- | --- | --- +1.2.3+42 | `cider bump breaking` | 2.0.0 +0.2.1+42 | `cider bump breaking -b` | 0.3.0+42 +0.2.1+42 | `cider bump patch` | 0.2.2 +0.2.1+42 | `cider bump patch -b` | 0.2.2+42 + +### Releasing the unreleased changes +This command takes all changes from the `Unreleased` section on the changelog and creates a new release with the +version from pubspec.yaml + +``` +cider release +``` + +Use `--date` to provide the release date (the default is today). + +### Printing the current project version +``` +cider version +``` + +### Printing the list of changes in a given version +``` +cider print +``` +- **version** is an existing version from the changelog \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..7783dc8 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,5 @@ +include: package:pedantic/analysis_options.yaml +linter: + rules: + - sort_constructors_first + - sort_unnamed_constructors_first \ No newline at end of file diff --git a/bin/cider.dart b/bin/cider.dart new file mode 100644 index 0000000..ab5c4a2 --- /dev/null +++ b/bin/cider.dart @@ -0,0 +1,6 @@ +import 'dart:io'; + +import 'package:cider/cider.dart'; + +void main(List args) async => exit( + (await ConsoleApplication('cider').run(args)) ?? ExitCode.missingArgument); diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..f5df62d --- /dev/null +++ b/example/README.md @@ -0,0 +1 @@ +Please see the README.md in the project root. \ No newline at end of file diff --git a/lib/cider.dart b/lib/cider.dart new file mode 100644 index 0000000..27d1c7a --- /dev/null +++ b/lib/cider.dart @@ -0,0 +1,3 @@ +export 'package:cider/src/console/console.dart'; +export 'package:cider/src/console/console_application.dart'; +export 'package:cider/src/console/exit_code.dart'; diff --git a/lib/src/application.dart b/lib/src/application.dart new file mode 100644 index 0000000..f467ea6 --- /dev/null +++ b/lib/src/application.dart @@ -0,0 +1,62 @@ +import 'dart:io'; + +import 'package:change/model.dart'; +import 'package:cider/src/application_exception.dart'; +import 'package:cider/src/changelog_file.dart'; +import 'package:cider/src/config.dart'; +import 'package:cider/src/pubspec_file.dart'; +import 'package:marker/flavors.dart' as flavors; +import 'package:marker/marker.dart'; +import 'package:maybe_just_nothing/maybe_just_nothing.dart'; +import 'package:path/path.dart'; +import 'package:version_manipulation/mutations.dart'; + +class Application { + Application({String projectRoot = '.'}) + : _pubspec = PubspecFile(projectRoot), + _changelogFile = ChangelogFile(projectRoot), + _config = Config.readFile(File(join(projectRoot, '.cider.yaml'))); + + final PubspecFile _pubspec; + final ChangelogFile _changelogFile; + final Config _config; + + void logChange(ChangeType type, String text) => + _changelogFile.update((changelog) { + changelog.unreleased.changes.addText(type, text); + return changelog; + }); + + String bump(VersionMutation mutation) { + final current = _pubspec.readVersion(); + final updated = mutation(current); + _pubspec.writeVersion(updated); + return updated.toString(); + } + + void release(String date) { + final version = _pubspec.readVersion().toString(); + _changelogFile.update((changelog) { + if (changelog.releases.any((release) => release.version == version)) { + throw ApplicationException('Release already exists'); + } + changelog.release(version, date, link: _config.diffLinkTemplate.or('')); + return changelog; + }); + } + + /// Reads the current project version from pubspec.yaml + String readVersion() => _pubspec.readVersion().toString(); + + /// Reads the markdown description for the given release + String describe([String version]) { + final changelog = _changelogFile.read(); + if (changelog.releases.isEmpty) throw 'No releases found in CHANGELOG'; + final release = Maybe(version) + .map((ver) => changelog.releases.firstWhere( + (release) => release.version == ver, + orElse: () => throw 'Version $ver not found')) + .or(changelog.releases.last); + return render(release.toMarkdown(), flavor: flavors.changelog); + } +} diff --git a/lib/src/application_exception.dart b/lib/src/application_exception.dart new file mode 100644 index 0000000..a38f232 --- /dev/null +++ b/lib/src/application_exception.dart @@ -0,0 +1,8 @@ +class ApplicationException implements Exception { + ApplicationException(this.message); + + final String message; + + @override + String toString() => message; +} diff --git a/lib/src/changelog_file.dart b/lib/src/changelog_file.dart new file mode 100644 index 0000000..e7c3b08 --- /dev/null +++ b/lib/src/changelog_file.dart @@ -0,0 +1,24 @@ +import 'dart:io'; + +import 'package:change/model.dart'; +import 'package:path/path.dart'; + +class ChangelogFile { + ChangelogFile(this._dir); + + static final name = 'CHANGELOG.md'; + + final String _dir; + + /// Updates the changelog in-place + void update(Changelog Function(Changelog changelog) mutate) => + _write(mutate(read())); + + /// Reads the changelog from file + Changelog read() => Changelog.fromLines( + (_file..createSync(recursive: true)).readAsLinesSync()); + + void _write(Changelog changelog) => _file.writeAsStringSync(changelog.dump()); + + File get _file => File(join(_dir, name)); +} diff --git a/lib/src/config.dart b/lib/src/config.dart new file mode 100644 index 0000000..e1ce0b3 --- /dev/null +++ b/lib/src/config.dart @@ -0,0 +1,22 @@ +import 'dart:io'; + +import 'package:maybe_just_nothing/maybe_just_nothing.dart'; +import 'package:yaml/yaml.dart'; + +class Config { + Config(this._data); + + static Config readFile(File file) { + if (!file.existsSync()) return Config(YamlMap()); + final yaml = loadYaml(file.readAsStringSync()) ?? YamlMap(); + if (yaml is YamlMap) return Config(yaml); + throw 'Invalid config format'; + } + + final YamlMap _data; + + Maybe get diffLinkTemplate => Maybe(_data['changelog']) + .type() + .map((_) => _['diff_link_template']) + .type(); +} diff --git a/lib/src/console/command/application_command.dart b/lib/src/console/command/application_command.dart new file mode 100644 index 0000000..fc25a28 --- /dev/null +++ b/lib/src/console/command/application_command.dart @@ -0,0 +1,7 @@ +import 'package:args/command_runner.dart'; +import 'package:cider/src/application.dart'; + +abstract class ApplicationCommand extends Command { + Application createApp() => + Application(projectRoot: globalResults['project-root']); +} diff --git a/lib/src/console/command/bump_command.dart b/lib/src/console/command/bump_command.dart new file mode 100644 index 0000000..ea86df2 --- /dev/null +++ b/lib/src/console/command/bump_command.dart @@ -0,0 +1,44 @@ +import 'package:cider/src/console/command/application_command.dart'; +import 'package:cider/src/console/console.dart'; +import 'package:cider/src/console/exit_code.dart'; +import 'package:version_manipulation/mutations.dart'; + +class BumpCommand extends ApplicationCommand { + BumpCommand(this.name, this.mutation, this._console) { + argParser +// ..addOption('date', +// help: 'Release date', +// defaultsTo: DateFormat('y-MM-dd').format(DateTime.now())) + ..addFlag('print', + help: 'Prints the updated version', abbr: 'p', defaultsTo: false) + ..addFlag('keep-build', + help: 'Keeps the build part of the version', + abbr: 'b', + defaultsTo: false); + } + + final Console _console; + + @override + String get description => 'Bumps the $name version'; + + @override + final name; + final VersionMutation mutation; + + @override + int run() { + final app = createApp(); + final keepBuild = argResults['keep-build']; + try { + final updated= app.bump(keepBuild ? KeepBuild(mutation) : mutation); + if (argResults['print']) _console.log(updated); +// app.release( +// keepBuild ? KeepBuild(mutation) : mutation, argResults['date']); + return ExitCode.ok; + } catch (e) { + _console.error(e); + return ExitCode.applicationError; + } + } +} diff --git a/lib/src/console/command/describe_command.dart b/lib/src/console/command/describe_command.dart new file mode 100644 index 0000000..5fd4860 --- /dev/null +++ b/lib/src/console/command/describe_command.dart @@ -0,0 +1,27 @@ +import 'package:cider/cider.dart'; +import 'package:cider/src/console/command/application_command.dart'; +import 'package:cider/src/console/console.dart'; + +class DescribeCommand extends ApplicationCommand { + DescribeCommand(this._console); + + final Console _console; + @override + final description = 'Prints the changelog entry for the given version'; + + @override + final name = 'describe'; + + @override + final aliases = ['desc']; + + @override + int run() { + if (argResults.rest.isEmpty) { + _console.log(createApp().describe()); + } else { + _console.log(createApp().describe(argResults.rest.single)); + } + return ExitCode.ok; + } +} diff --git a/lib/src/console/command/log_change_command.dart b/lib/src/console/command/log_change_command.dart new file mode 100644 index 0000000..674ea11 --- /dev/null +++ b/lib/src/console/command/log_change_command.dart @@ -0,0 +1,31 @@ +import 'package:change/model.dart'; +import 'package:cider/cider.dart'; +import 'package:cider/src/console/command/application_command.dart'; +import 'package:cider/src/console/console.dart'; + +class LogChangeCommand extends ApplicationCommand { + LogChangeCommand( + this.name, this.aliases, this.description, this._type, this._console); + + final Console _console; + + final ChangeType _type; + + @override + final String description; + @override + final String name; + @override + final List aliases; + + @override + int run() { + if (argResults.rest.isEmpty) { + _console.error('Please specify the change description'); + return ExitCode.missingArgument; + } + final app = createApp(); + app.logChange(_type, argResults.rest.first); + return ExitCode.ok; + } +} diff --git a/lib/src/console/command/release_command.dart b/lib/src/console/command/release_command.dart new file mode 100644 index 0000000..7e7b296 --- /dev/null +++ b/lib/src/console/command/release_command.dart @@ -0,0 +1,34 @@ +import 'package:cider/src/application_exception.dart'; +import 'package:cider/src/console/command/application_command.dart'; +import 'package:cider/src/console/console.dart'; +import 'package:cider/src/console/exit_code.dart'; +import 'package:intl/intl.dart'; + +class ReleaseCommand extends ApplicationCommand { + ReleaseCommand(this._console) { + argParser + ..addOption('date', + help: 'Release date', + defaultsTo: DateFormat('y-MM-dd').format(DateTime.now())); + } + + final Console _console; + + @override + String get description => 'Adds a new release to the changelog'; + + @override + final name = 'release'; + + @override + int run() { + final app = createApp(); + try { + app.release(argResults['date']); + return ExitCode.ok; + } on ApplicationException catch (e) { + _console.error(e); + return ExitCode.applicationError; + } + } +} diff --git a/lib/src/console/command/version_command.dart b/lib/src/console/command/version_command.dart new file mode 100644 index 0000000..59c3024 --- /dev/null +++ b/lib/src/console/command/version_command.dart @@ -0,0 +1,24 @@ +import 'package:cider/cider.dart'; +import 'package:cider/src/console/command/application_command.dart'; +import 'package:cider/src/console/console.dart'; + +class VersionCommand extends ApplicationCommand { + VersionCommand(this._console); + + final Console _console; + + @override + final String description = 'Prints your project version'; + + @override + final String name = 'version'; + + @override + final aliases = ['ver']; + + @override + int run() { + _console.log(createApp().readVersion()); + return ExitCode.ok; + } +} diff --git a/lib/src/console/command/wrapper.dart b/lib/src/console/command/wrapper.dart new file mode 100644 index 0000000..aadce7e --- /dev/null +++ b/lib/src/console/command/wrapper.dart @@ -0,0 +1,18 @@ +import 'package:args/command_runner.dart'; + +class Wrapper extends Command { + Wrapper( + this.description, this.name, this.aliases, List> children) + : super() { + children.forEach(addSubcommand); + } + + @override + final description; + + @override + final name; + + @override + final aliases; +} diff --git a/lib/src/console/console.dart b/lib/src/console/console.dart new file mode 100644 index 0000000..38822be --- /dev/null +++ b/lib/src/console/console.dart @@ -0,0 +1,20 @@ +import 'dart:io'; + +/// A very simple Console output abstraction. +/// Allows to print log messages and error messages. +class Console { + /// Creates an instance with + const Console(this._output, this._error); + + Console.stdio() : this(stdout, stderr); + + final Stdout _output; + + final Stdout _error; + + /// Writes the [message] to the error sink + void error(Object message) => _error.writeln(message.toString()); + + /// Writes the [message] the to the normal output sink + void log(Object message) => _output.writeln(message.toString()); +} diff --git a/lib/src/console/console_application.dart b/lib/src/console/console_application.dart new file mode 100644 index 0000000..adfb5fb --- /dev/null +++ b/lib/src/console/console_application.dart @@ -0,0 +1,71 @@ +import 'package:args/command_runner.dart'; +import 'package:change/model.dart'; +import 'package:cider/cider.dart'; +import 'package:cider/src/console/command/bump_command.dart'; +import 'package:cider/src/console/command/describe_command.dart'; +import 'package:cider/src/console/command/log_change_command.dart'; +import 'package:cider/src/console/command/release_command.dart'; +import 'package:cider/src/console/command/version_command.dart'; +import 'package:cider/src/console/command/wrapper.dart'; +import 'package:cider/src/console/console.dart'; +import 'package:version_manipulation/mutations.dart'; + +class ConsoleApplication extends CommandRunner { + ConsoleApplication(String name, {Console console}) + : super(name, 'Dart packages maintenance') { + console ??= Console.stdio(); + argParser + ..addOption('project-root', + help: 'Path to the project root', defaultsTo: '.'); + + addCommand( + Wrapper('Manipulates the CHANGELOG', 'log', [ + 'cl', + 'changelog' + ], [ + LogChangeCommand( + 'addition', + ['a', 'add', 'added'], + 'Logs an unreleased ADDITION to CHANGELOG.md', + ChangeType.addition, + console), + LogChangeCommand( + 'change', + ['c', 'ch', 'changed'], + 'Logs an unreleased CHANGE to CHANGELOG.md', + ChangeType.change, + console), + LogChangeCommand( + 'deprecation', + ['d', 'dep', 'deprecated'], + 'Logs an unreleased DEPRECATION to CHANGELOG.md', + ChangeType.deprecation, + console), + LogChangeCommand( + 'removal', + ['r', 'rem', 'removed', 'del', 'delete', 'deleted'], + 'Logs an unreleased REMOVAL to CHANGELOG.md', + ChangeType.removal, + console), + LogChangeCommand('fix', ['f', 'fixed'], + 'Logs an unreleased FIX to CHANGELOG.md', ChangeType.fix, console), + LogChangeCommand( + 'security', + ['s', 'sec'], + 'Logs an unreleased SECURITY CHANGE to CHANGELOG.md', + ChangeType.security, + console), + ]), + ); + addCommand(Wrapper('Bumps the package version', 'bump', [], [ + BumpCommand('breaking', BumpBreaking(), console), + BumpCommand('major', BumpMajor(), console), + BumpCommand('minor', BumpMinor(), console), + BumpCommand('patch', BumpPatch(), console), + BumpCommand('build', BumpBuild(), console), + ])); + addCommand(VersionCommand(console)); + addCommand(DescribeCommand(console)); + addCommand(ReleaseCommand(console)); + } +} diff --git a/lib/src/console/exit_code.dart b/lib/src/console/exit_code.dart new file mode 100644 index 0000000..27a37ad --- /dev/null +++ b/lib/src/console/exit_code.dart @@ -0,0 +1,5 @@ +class ExitCode { + static const ok = 0; + static const missingArgument = 1; + static const applicationError = 64; +} \ No newline at end of file diff --git a/lib/src/pubspec_file.dart b/lib/src/pubspec_file.dart new file mode 100644 index 0000000..86c1f1c --- /dev/null +++ b/lib/src/pubspec_file.dart @@ -0,0 +1,33 @@ +import 'dart:io'; + +import 'package:path/path.dart'; +import 'package:plain_optional/plain_optional.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:pubspec_yaml/pubspec_yaml.dart'; + +class PubspecFile { + PubspecFile(this._dir); + + static final name = 'pubspec.yaml'; + + final String _dir; + + /// Reads the project version from pubspec.yaml + Version readVersion() => + _read().version.map((_) => Version.parse(_)).valueOr(() => Version.none); + + /// Writes the project version to pubspec.yaml + void writeVersion(Version version) => update( + (pubspec) => pubspec.copyWith(version: Optional(version.toString()))); + + /// Updated pubspec.yaml in-place. + void update(PubspecYaml Function(PubspecYaml pubspec) mutate) => + _write(mutate(_read())); + + void _write(PubspecYaml pubspecYaml) => + _file.writeAsStringSync(pubspecYaml.toYamlString()); + + PubspecYaml _read() => _file.readAsStringSync().toPubspecYaml(); + + File get _file => File(join(_dir, name)); +} diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..efe25f1 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,27 @@ +name: cider +version: 0.0.0+dev.1 +description: Tools for Dart package maintainers. Automates CHANGLELOG and pubspec.yaml updates. +homepage: "https://github.com/f3ath/cider" + +dependencies: + args: ^1.6.0 + change: ^0.0.12 + intl: ^0.16.1 + markdown: ^2.1.5 + marker: ^0.1.0 + maybe_just_nothing: ^0.3.0 + path: ^1.7.0 + pub_semver: ^1.4.4 + pubspec_yaml: ^2.0.1 + version_manipulation: ^0.0.2 + yaml: ^2.2.1 + +dev_dependencies: + pedantic: ^1.9.0 + test: ^1.9.0 + +environment: + sdk: ">=2.8.0 <3.0.0" + +executables: + ci: ci diff --git a/test/functional_test.dart b/test/functional_test.dart new file mode 100644 index 0000000..e73a46d --- /dev/null +++ b/test/functional_test.dart @@ -0,0 +1,163 @@ +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:cider/src/changelog_file.dart'; +import 'package:cider/src/console/console_application.dart'; +import 'package:cider/src/pubspec_file.dart'; +import 'package:path/path.dart'; +import 'package:test/test.dart'; + +import 'mock_console.dart'; + +void main() { + Directory temp; + MockConsole console; + CommandRunner app; + String changelogPath; + String pubspecPath; + + setUp(() async { + temp = await Directory.systemTemp.createTemp(); + changelogPath = join(temp.path, ChangelogFile.name); + pubspecPath = join(temp.path, PubspecFile.name); + + console = MockConsole(); + app = ConsoleApplication('ci', console: console); + }); + + tearDown(() async {}); + + group('Changelog', () { + test('Add entries to the CHANGELOG', () async { + await File('test/samples/step1.md').copy(changelogPath); + expect( + await app.run([ + 'log', + 'change', + 'Programmatically added change', + '--project-root', + temp.path + ]), + 0); + expect( + await app.run([ + 'log', + 'd', + 'Programmatically added deprecation', + '--project-root', + temp.path + ]), + 0); + expect(File(changelogPath).readAsStringSync(), + File('test/samples/step2.md').readAsStringSync()); + }); + }); + + group('Bump', () { + test('minor', () async { + await File('test/samples/pubspec-1.0.0.yaml').copy(pubspecPath); + expect( + await app.run([ + 'bump', + 'minor', + '--project-root', + temp.path + ]), + 0); + expect(File(pubspecPath).readAsStringSync().trim(), + File('test/samples/pubspec-1.1.0.yaml').readAsStringSync().trim()); + }); + + test('patch, keeping build, printing version', () async { + await File('test/samples/pubspec-1.0.0-beta.yaml').copy(pubspecPath); + expect( + await app.run([ + 'bump', + 'patch', + '-b', + '-p', + '--project-root', + temp.path + ]), + 0); + expect( + File(pubspecPath).readAsStringSync().trim(), + File('test/samples/pubspec-1.0.1-beta.yaml') + .readAsStringSync() + .trim()); + expect(console.logs.single, '1.0.1+beta'); + }); + }); + + group('Release', () { + test('successful', () async { + await File('test/samples/step2.md').copy(changelogPath); + await File('test/samples/pubspec-1.1.0.yaml').copy(pubspecPath); + await File('test/samples/.cider.yaml') + .copy(join(temp.path, '.cider.yaml')); + expect( + await app.run([ + 'release', + '--date', + '2018-10-18', + '--project-root', + temp.path + ]), + 0); + expect(File(changelogPath).readAsStringSync(), + File('test/samples/step3.md').readAsStringSync()); + }); + + test('existing version', () async { + await File('test/samples/step2.md').copy(changelogPath); + await File('test/samples/pubspec-1.0.0.yaml').copy(pubspecPath); + await File('test/samples/.cider.yaml') + .copy(join(temp.path, '.cider.yaml')); + expect( + await app.run([ + 'release', + '--project-root', + temp.path + ]), + 64); + expect(File(changelogPath).readAsStringSync(), + File('test/samples/step2.md').readAsStringSync()); + }); + }); + + group('Version', () { + test('Print', () async { + await File('test/samples/step3.md').copy(changelogPath); + await File('test/samples/pubspec-1.1.0.yaml').copy(pubspecPath); + expect(await app.run(['version', '--project-root', temp.path]), 0); + expect(console.logs.single, '1.1.0'); + }); + }); + + group('Describe', () { + test('Latest', () async { + await File('test/samples/step3.md').copy(changelogPath); + await File('test/samples/pubspec-1.1.0.yaml').copy(pubspecPath); + expect(await app.run(['describe', '--project-root', temp.path]), 0); + expect(console.logs.single, '''## [1.1.0] - 2018-10-18 +### Changed +- Change #1 +- Change #2 +- Programmatically added change + +### Deprecated +- Programmatically added deprecation + +[1.1.0]: https://github.com/example/project/compare/1.0.0...1.1.0'''); + }); + + test('provided', () async { + await File('test/samples/step3.md').copy(changelogPath); + await File('test/samples/pubspec-1.1.0.yaml').copy(pubspecPath); + expect(await app.run(['describe', '1.0.0', '--project-root', temp.path]), 0); + expect(console.logs.single, '''## 1.0.0 - 2018-10-15 +### Added +- Initial version of the example'''); + }); + }); +} diff --git a/test/mock_console.dart b/test/mock_console.dart new file mode 100644 index 0000000..7c85040 --- /dev/null +++ b/test/mock_console.dart @@ -0,0 +1,16 @@ +import 'package:cider/cider.dart'; + +class MockConsole implements Console { + final errors = []; + final logs = []; + + @override + void error(Object message) { + errors.add(message.toString()); + } + + @override + void log(Object message) { + logs.add(message.toString()); + } +} diff --git a/test/samples/.cider.yaml b/test/samples/.cider.yaml new file mode 100644 index 0000000..9a2e696 --- /dev/null +++ b/test/samples/.cider.yaml @@ -0,0 +1,2 @@ +changelog: + diff_link_template: 'https://github.com/example/project/compare/%from%...%to%' \ No newline at end of file diff --git a/test/samples/pubspec-1.0.0-beta.yaml b/test/samples/pubspec-1.0.0-beta.yaml new file mode 100644 index 0000000..188bf09 --- /dev/null +++ b/test/samples/pubspec-1.0.0-beta.yaml @@ -0,0 +1,13 @@ +name: sample +version: 1.0.0+beta +description: Sample pubspec file +homepage: "https://example.com" + +dependencies: + markdown: ^2.1.5 + +environment: + sdk: '>=2.8.0 <3.0.0' + +dev_dependencies: + test: ^1.9.0 diff --git a/test/samples/pubspec-1.0.0.yaml b/test/samples/pubspec-1.0.0.yaml new file mode 100644 index 0000000..18d7517 --- /dev/null +++ b/test/samples/pubspec-1.0.0.yaml @@ -0,0 +1,13 @@ +name: sample +version: 1.0.0 +description: Sample pubspec file +homepage: "https://example.com" + +dependencies: + markdown: ^2.1.5 + +environment: + sdk: '>=2.8.0 <3.0.0' + +dev_dependencies: + test: ^1.9.0 diff --git a/test/samples/pubspec-1.0.1-beta.yaml b/test/samples/pubspec-1.0.1-beta.yaml new file mode 100644 index 0000000..4a3f2f3 --- /dev/null +++ b/test/samples/pubspec-1.0.1-beta.yaml @@ -0,0 +1,13 @@ +name: sample +version: 1.0.1+beta +description: Sample pubspec file +homepage: "https://example.com" + +dependencies: + markdown: ^2.1.5 + +dev_dependencies: + test: ^1.9.0 + +environment: + sdk: ">=2.8.0 <3.0.0" \ No newline at end of file diff --git a/test/samples/pubspec-1.1.0.yaml b/test/samples/pubspec-1.1.0.yaml new file mode 100644 index 0000000..708a418 --- /dev/null +++ b/test/samples/pubspec-1.1.0.yaml @@ -0,0 +1,13 @@ +name: sample +version: 1.1.0 +description: Sample pubspec file +homepage: "https://example.com" + +dependencies: + markdown: ^2.1.5 + +dev_dependencies: + test: ^1.9.0 + +environment: + sdk: ">=2.8.0 <3.0.0" \ No newline at end of file diff --git a/test/samples/step0.md b/test/samples/step0.md new file mode 100644 index 0000000..a2c1b7d --- /dev/null +++ b/test/samples/step0.md @@ -0,0 +1,3 @@ +## Unreleased +### Added +- Programmatically added change \ No newline at end of file diff --git a/test/samples/step1.md b/test/samples/step1.md new file mode 100644 index 0000000..cf543c4 --- /dev/null +++ b/test/samples/step1.md @@ -0,0 +1,13 @@ +# Example changelog +This is an example of tracking changes and making a release. + +## [Unreleased] +### Changed +- Change #1 +- Change #2 + +## 1.0.0 - 2018-10-15 +### Added +- Initial version of the example + +[Unreleased]: https://github.com/example/project/compare/1.0.0...HEAD \ No newline at end of file diff --git a/test/samples/step2.md b/test/samples/step2.md new file mode 100644 index 0000000..b7d2555 --- /dev/null +++ b/test/samples/step2.md @@ -0,0 +1,17 @@ +# Example changelog +This is an example of tracking changes and making a release. + +## [Unreleased] +### Changed +- Change #1 +- Change #2 +- Programmatically added change + +### Deprecated +- Programmatically added deprecation + +## 1.0.0 - 2018-10-15 +### Added +- Initial version of the example + +[Unreleased]: https://github.com/example/project/compare/1.0.0...HEAD \ No newline at end of file diff --git a/test/samples/step3-beta.md b/test/samples/step3-beta.md new file mode 100644 index 0000000..cc501e4 --- /dev/null +++ b/test/samples/step3-beta.md @@ -0,0 +1,19 @@ +# Example changelog +This is an example of tracking changes and making a release. + +## [Unreleased] +## [1.0.1+beta] - 2018-10-18 +### Changed +- Change #1 +- Change #2 +- Programmatically added change + +### Deprecated +- Programmatically added deprecation + +## 1.0.0 - 2018-10-15 +### Added +- Initial version of the example + +[Unreleased]: https://github.com/example/project/compare/1.0.1+beta...HEAD +[1.0.1+beta]: https://github.com/example/project/compare/1.0.0...1.0.1+beta \ No newline at end of file diff --git a/test/samples/step3.md b/test/samples/step3.md new file mode 100644 index 0000000..3144a19 --- /dev/null +++ b/test/samples/step3.md @@ -0,0 +1,19 @@ +# Example changelog +This is an example of tracking changes and making a release. + +## [Unreleased] +## [1.1.0] - 2018-10-18 +### Changed +- Change #1 +- Change #2 +- Programmatically added change + +### Deprecated +- Programmatically added deprecation + +## 1.0.0 - 2018-10-15 +### Added +- Initial version of the example + +[Unreleased]: https://github.com/example/project/compare/1.1.0...HEAD +[1.1.0]: https://github.com/example/project/compare/1.0.0...1.1.0 \ No newline at end of file