Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for multiple diff report formats #152

Merged
merged 12 commits into from
Sep 26, 2023
2 changes: 1 addition & 1 deletion .fvm/fvm_config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"flutterSdkVersion": "3.13.1",
"flutterSdkVersion": "3.13.5",
"flavors": {}
}
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# Changelog

## Version 0.16.3
- adds more diff result reporting options (cli, json, markdown)

## Version 0.16.2
- fixes relative path handling in package config (leading to unresolvable types)

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ Usage: dart-apitool diff [arguments]
--[no-]remove-example Removes examples from the package to analyze.
(defaults to on)
--[no-]ignore-requiredness Whether to ignore the required aspect of interfaces (yielding less strict version bump requirements)
--report-format Which output format should be used
[cli (default), markdown, json]
--report-file-path Where to store the report file (no effect on cli option)
```

## Integration
Expand Down
34 changes: 2 additions & 32 deletions bin/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,11 @@ import 'package:colorize/colorize.dart';
import 'package:colorize_lumberdash/colorize_lumberdash.dart';
import 'package:dart_apitool/api_tool_cli.dart';
import 'package:lumberdash/lumberdash.dart';
import 'package:pubspec_parse/pubspec_parse.dart';

import 'package:path/path.dart' as p;
import 'package:yaml/yaml.dart';

Future<String> _getOwnVersion() async {
String? result;
final mainFilePath = Platform.script.toFilePath();
final pubspecFile =
File(p.join(p.dirname(mainFilePath), '..', 'pubspec.yaml'));
if (await pubspecFile.exists()) {
final yamlContent = await pubspecFile.readAsString();
final pubSpec = Pubspec.parse(yamlContent);
result = pubSpec.version?.canonicalizedVersion;
}
if (result == null) {
// if we are in a pub global environment we have to read our version from the pubspec.lock file
final pubspecLockFile =
File(p.join(p.dirname(mainFilePath), '..', 'pubspec.lock'));
if (await pubspecLockFile.exists()) {
final pubspecLockContent = await pubspecLockFile.readAsString();
final pubspecLockDom = loadYaml(pubspecLockContent);
result = pubspecLockDom['packages']['dart_apitool']['version'];
}
}
if (result == null) {
return 'UNKNOWN VERSION';
}
return result;
}

void main(List<String> arguments) async {
putLumberdashToWork(withClients: [ColorizeLumberdash()]);
final runner = CommandRunner<int>('dart-apitool', '''
dart-apitool (${Colorize(await _getOwnVersion()).bold()})
dart-apitool (${Colorize(await getOwnVersion()).bold()})

A set of utilities for Package APIs.
''')
Expand All @@ -52,7 +22,7 @@ A set of utilities for Package APIs.
try {
final argParseResult = runner.argParser.parse(arguments);
if (argParseResult['version']) {
print(await _getOwnVersion());
print(await getOwnVersion());
exit(0);
}
final exitCode = await runner.run(arguments);
Expand Down
131 changes: 52 additions & 79 deletions lib/src/cli/commands/diff_command.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import 'dart:io';

import 'package:args/command_runner.dart';
import 'package:colorize/colorize.dart';
import 'package:console/console.dart';
import 'package:dart_apitool/api_tool.dart';
import 'package:dart_apitool/src/diff/report/diff_reporter.dart';
import 'package:dart_apitool/src/diff/report/json_diff_reporter.dart';
import 'package:dart_apitool/src/diff/report/report_format.dart';

import '../../diff/report/console_diff_reporter.dart';
import '../../diff/report/markdown_diff_reporter.dart';
import '../package_ref.dart';
import 'command_mixin.dart';
import 'version_check.dart';
Expand All @@ -20,6 +23,8 @@ String _optionNameCheckSdkVersion = 'check-sdk-version';
String _optionNameDependencyCheckMode = 'dependency-check-mode';
String _optionNameRemoveExample = 'remove-example';
String _optionNameIgnoreRequiredness = 'ignore-requiredness';
String _optionReportFormat = 'report-format';
String _optionReportPath = 'report-file-path';

/// command for diffing two packages
class DiffCommand extends Command<int> with CommandMixin {
Expand Down Expand Up @@ -97,12 +102,37 @@ You may want to do this if you want to make sure
defaultsTo: false,
negatable: true,
);
argParser.addOption(
_optionReportFormat,
help: 'Which output format should be used',
defaultsTo: ReportFormat.cli.name,
allowed: ReportFormat.values.map((e) => e.name),
mandatory: false,
);
argParser.addOption(
_optionReportPath,
help: 'Where to store the report file (no effect on cli option)',
mandatory: false,
);
}

@override
Future<int> run() async {
final oldPackageRef = PackageRef(argResults![_optionNameOld]);
final newPackageRef = PackageRef(argResults![_optionNameNew]);
final outputFormatter = ReportFormat.values.firstWhere(
(element) => element.name == argResults![_optionReportFormat]);
final outputFile = argResults![_optionReportPath];

if (outputFormatter != ReportFormat.cli && outputFile == null) {
throw 'You need to define an output file using the $_optionReportPath parameter when not using the cli option';
}

if (outputFormatter == ReportFormat.cli && outputFile != null) {
stdout.writeln(
'WARNING: $_optionReportPath has no effect because $_optionReportFormat is set to cli');
}

final versionCheckMode = VersionCheckMode.values.firstWhere(
(element) => element.name == argResults![_optionNameVersionCheckMode]);
final ignorePrerelease = argResults![_optionNameIgnorePrerelease] as bool;
Expand Down Expand Up @@ -149,29 +179,27 @@ You may want to do this if you want to make sure
final diffResult =
differ.diff(oldApi: oldPackageApi, newApi: newPackageApi);

stdout.writeln();

// print the diffs
if (diffResult.hasChanges) {
final breakingChanges = _printApiChangeNode(diffResult.rootNode, true);
if (breakingChanges == null) {
stdout.writeln('No breaking changes!');
} else {
stdout.write(breakingChanges);
DiffReporter reporter = (() {
switch (outputFormatter) {
case ReportFormat.cli:
return ConsoleDiffReporter();
case ReportFormat.markdown:
return MarkdownDiffReporter(
oldPackageRef: oldPackageRef,
newPackageRef: newPackageRef,
outputFile: File(outputFile));
case ReportFormat.json:
return JsonDiffReporter(
oldPackageRef: oldPackageRef,
newPackageRef: newPackageRef,
outputFile: File(outputFile));
default:
throw 'Unknown format speicified $outputFormatter';
}
final nonBreakingChanges =
_printApiChangeNode(diffResult.rootNode, false);
if (nonBreakingChanges == null) {
stdout.writeln('No non-breaking changes!');
} else {
stdout.write(nonBreakingChanges);
}
stdout.writeln();
stdout.writeln(
'To learn more about the detected changes visit: https://github.com/bmw-tech/dart_apitool/blob/main/readme/change_codes.md');
} else {
stdout.writeln('No changes detected!');
}
})();

stdout.writeln('-- Generating report using: ${reporter.reporterName} --');
await reporter.generateReport(diffResult);

if (versionCheckMode != VersionCheckMode.none &&
!VersionCheck.versionChangeMatchesChanges(
Expand All @@ -185,59 +213,4 @@ You may want to do this if you want to make sure

return 0;
}

String _getDeclarationNodeHeadline(Declaration declaration) {
var prefix = '';
if (declaration is ExecutableDeclaration) {
switch (declaration.type) {
case ExecutableType.constructor:
prefix = 'Constructor ';
break;
case ExecutableType.method:
prefix = 'Method ';
break;
}
} else if (declaration is FieldDeclaration) {
prefix = 'Field ';
} else if (declaration is InterfaceDeclaration) {
prefix = 'Class ';
}
return prefix + declaration.name;
}

String? _printApiChangeNode(ApiChangeTreeNode node, bool breaking) {
Map nodeToTree(ApiChangeTreeNode n, {String? labelOverride}) {
final relevantChanges = n.changes.where((c) => c.isBreaking == breaking);
final changeNodes = relevantChanges.map((c) =>
'${Colorize(c.changeDescription).italic()} (${c.changeCode.code})${c.isBreaking ? '' : c.type.requiresMinorBump ? ' (minor)' : ' (patch)'}');
final childNodes = n.children.values
.map((value) => nodeToTree(value))
.where((element) => element.isNotEmpty);
final allChildren = [
...changeNodes,
...childNodes,
];
if (allChildren.isEmpty) {
return {};
}
return {
'label': Colorize(labelOverride ??
(n.nodeDeclaration == null
? ''
: _getDeclarationNodeHeadline(n.nodeDeclaration!)))
.bold()
.toString(),
'nodes': allChildren,
};
}

final nodes = nodeToTree(node,
labelOverride: breaking ? 'BREAKING CHANGES' : 'Non-Breaking changes');

if (nodes.isEmpty) {
return null;
}

return createTree(nodes);
}
}
Loading
Loading