mirror of
https://github.com/flutter/packages.git
synced 2025-05-23 19:56:43 +08:00
[tools] Add update-release-info
(#5643)
This commit is contained in:
@ -1,5 +1,7 @@
|
|||||||
## NEXT
|
## 0.8.6
|
||||||
|
|
||||||
|
- Adds `update-release-info` to apply changelog and optional version changes
|
||||||
|
across multiple packages.
|
||||||
- Fixes changelog validation when reverting to a `NEXT` state.
|
- Fixes changelog validation when reverting to a `NEXT` state.
|
||||||
- Fixes multiplication of `--force` flag when publishing multiple packages.
|
- Fixes multiplication of `--force` flag when publishing multiple packages.
|
||||||
- Adds minimum deployment target flags to `xcode-analyze` to allow
|
- Adds minimum deployment target flags to `xcode-analyze` to allow
|
||||||
|
@ -118,6 +118,31 @@ cd <repository root>
|
|||||||
dart run ./script/tool/bin/flutter_plugin_tools.dart update-excerpts --packages plugin_name
|
dart run ./script/tool/bin/flutter_plugin_tools.dart update-excerpts --packages plugin_name
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Update CHANGELOG and Version
|
||||||
|
|
||||||
|
`update-release-info` will automatically update the version and `CHANGELOG.md`
|
||||||
|
following standard repository style and practice. It can be used for
|
||||||
|
single-package updates to handle the details of getting the `CHANGELOG.md`
|
||||||
|
format correct, but is especially useful for bulk updates across multiple packages.
|
||||||
|
|
||||||
|
For instance, if you add a new analysis option that requires production
|
||||||
|
code changes across many packages:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd <repository root>
|
||||||
|
dart run ./script/tool/bin/flutter_plugin_tools.dart update-release-info \
|
||||||
|
--version=minimal \
|
||||||
|
--changelog="Fixes violations of new analysis option some_new_option."
|
||||||
|
```
|
||||||
|
|
||||||
|
The `minimal` option for `--version` will skip unchanged packages, and treat
|
||||||
|
each changed package as either `bugfix` or `next` depending on the files that
|
||||||
|
have changed in that package, so it is often the best choice for a bulk change.
|
||||||
|
|
||||||
|
For cases where you know the change time, `minor` or `bugfix` will make the
|
||||||
|
corresponding version bump, or `next` will update only `CHANGELOG.md` without
|
||||||
|
changing the version.
|
||||||
|
|
||||||
### Publish a Release
|
### Publish a Release
|
||||||
|
|
||||||
**Releases are automated for `flutter/plugins` and `flutter/packages`.**
|
**Releases are automated for `flutter/plugins` and `flutter/packages`.**
|
||||||
|
95
script/tool/lib/src/common/package_state_utils.dart
Normal file
95
script/tool/lib/src/common/package_state_utils.dart
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
// Copyright 2013 The Flutter Authors. 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:meta/meta.dart';
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
|
import 'repository_package.dart';
|
||||||
|
|
||||||
|
/// The state of a package on disk relative to git state.
|
||||||
|
@immutable
|
||||||
|
class PackageChangeState {
|
||||||
|
/// Creates a new immutable state instance.
|
||||||
|
const PackageChangeState({
|
||||||
|
required this.hasChanges,
|
||||||
|
required this.hasChangelogChange,
|
||||||
|
required this.needsVersionChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// True if there are any changes to files in the package.
|
||||||
|
final bool hasChanges;
|
||||||
|
|
||||||
|
/// True if the package's CHANGELOG.md has been changed.
|
||||||
|
final bool hasChangelogChange;
|
||||||
|
|
||||||
|
/// True if any changes in the package require a version change according
|
||||||
|
/// to repository policy.
|
||||||
|
final bool needsVersionChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks [package] against [changedPaths] to determine what changes it has
|
||||||
|
/// and how those changes relate to repository policy about CHANGELOG and
|
||||||
|
/// version updates.
|
||||||
|
///
|
||||||
|
/// [changedPaths] should be a list of POSIX-style paths from a common root,
|
||||||
|
/// and [relativePackagePath] should be the path to [package] from that same
|
||||||
|
/// root. Commonly these will come from `gitVersionFinder.getChangedFiles()`
|
||||||
|
/// and `getRelativePoixPath(package.directory, gitDir.path)` respectively;
|
||||||
|
/// they are arguments mainly to allow for caching the changed paths for an
|
||||||
|
/// entire command run.
|
||||||
|
PackageChangeState checkPackageChangeState(
|
||||||
|
RepositoryPackage package, {
|
||||||
|
required List<String> changedPaths,
|
||||||
|
required String relativePackagePath,
|
||||||
|
}) {
|
||||||
|
final String packagePrefix = relativePackagePath.endsWith('/')
|
||||||
|
? relativePackagePath
|
||||||
|
: '$relativePackagePath/';
|
||||||
|
|
||||||
|
bool hasChanges = false;
|
||||||
|
bool hasChangelogChange = false;
|
||||||
|
bool needsVersionChange = false;
|
||||||
|
for (final String path in changedPaths) {
|
||||||
|
// Only consider files within the package.
|
||||||
|
if (!path.startsWith(packagePrefix)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final String packageRelativePath = path.substring(packagePrefix.length);
|
||||||
|
hasChanges = true;
|
||||||
|
|
||||||
|
final List<String> components = p.posix.split(packageRelativePath);
|
||||||
|
if (components.isEmpty) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final bool isChangelog = components.first == 'CHANGELOG.md';
|
||||||
|
if (isChangelog) {
|
||||||
|
hasChangelogChange = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!needsVersionChange &&
|
||||||
|
!isChangelog &&
|
||||||
|
// One of a few special files example will be shown on pub.dev, but for
|
||||||
|
// anything else in the example publishing has no purpose.
|
||||||
|
!(components.first == 'example' &&
|
||||||
|
!<String>{'main.dart', 'readme.md', 'example.md'}
|
||||||
|
.contains(components.last.toLowerCase())) &&
|
||||||
|
// Changes to tests don't need to be published.
|
||||||
|
!components.contains('test') &&
|
||||||
|
!components.contains('androidTest') &&
|
||||||
|
!components.contains('RunnerTests') &&
|
||||||
|
!components.contains('RunnerUITests') &&
|
||||||
|
// The top-level "tool" directory is for non-client-facing utility code,
|
||||||
|
// so doesn't need to be published.
|
||||||
|
components.first != 'tool' &&
|
||||||
|
// Ignoring lints doesn't affect clients.
|
||||||
|
!components.contains('lint-baseline.xml')) {
|
||||||
|
needsVersionChange = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PackageChangeState(
|
||||||
|
hasChanges: hasChanges,
|
||||||
|
hasChangelogChange: hasChangelogChange,
|
||||||
|
needsVersionChange: needsVersionChange);
|
||||||
|
}
|
@ -29,6 +29,7 @@ import 'pubspec_check_command.dart';
|
|||||||
import 'readme_check_command.dart';
|
import 'readme_check_command.dart';
|
||||||
import 'test_command.dart';
|
import 'test_command.dart';
|
||||||
import 'update_excerpts_command.dart';
|
import 'update_excerpts_command.dart';
|
||||||
|
import 'update_release_info_command.dart';
|
||||||
import 'version_check_command.dart';
|
import 'version_check_command.dart';
|
||||||
import 'xcode_analyze_command.dart';
|
import 'xcode_analyze_command.dart';
|
||||||
|
|
||||||
@ -70,6 +71,7 @@ void main(List<String> args) {
|
|||||||
..addCommand(ReadmeCheckCommand(packagesDir))
|
..addCommand(ReadmeCheckCommand(packagesDir))
|
||||||
..addCommand(TestCommand(packagesDir))
|
..addCommand(TestCommand(packagesDir))
|
||||||
..addCommand(UpdateExcerptsCommand(packagesDir))
|
..addCommand(UpdateExcerptsCommand(packagesDir))
|
||||||
|
..addCommand(UpdateReleaseInfoCommand(packagesDir))
|
||||||
..addCommand(VersionCheckCommand(packagesDir))
|
..addCommand(VersionCheckCommand(packagesDir))
|
||||||
..addCommand(XcodeAnalyzeCommand(packagesDir));
|
..addCommand(XcodeAnalyzeCommand(packagesDir));
|
||||||
|
|
||||||
|
310
script/tool/lib/src/update_release_info_command.dart
Normal file
310
script/tool/lib/src/update_release_info_command.dart
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
// Copyright 2013 The Flutter Authors. 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:args/command_runner.dart';
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:flutter_plugin_tools/src/common/core.dart';
|
||||||
|
import 'package:git/git.dart';
|
||||||
|
import 'package:pub_semver/pub_semver.dart';
|
||||||
|
import 'package:yaml_edit/yaml_edit.dart';
|
||||||
|
|
||||||
|
import 'common/git_version_finder.dart';
|
||||||
|
import 'common/package_looping_command.dart';
|
||||||
|
import 'common/package_state_utils.dart';
|
||||||
|
import 'common/repository_package.dart';
|
||||||
|
|
||||||
|
/// Supported version change types, from smallest to largest component.
|
||||||
|
enum _VersionIncrementType { build, bugfix, minor }
|
||||||
|
|
||||||
|
/// Possible results of attempting to update a CHANGELOG.md file.
|
||||||
|
enum _ChangelogUpdateOutcome { addedSection, updatedSection, failed }
|
||||||
|
|
||||||
|
/// A state machine for the process of updating a CHANGELOG.md.
|
||||||
|
enum _ChangelogUpdateState {
|
||||||
|
/// Looking for the first version section.
|
||||||
|
findingFirstSection,
|
||||||
|
|
||||||
|
/// Looking for the first list entry in an existing section.
|
||||||
|
findingFirstListItem,
|
||||||
|
|
||||||
|
/// Finished with updates.
|
||||||
|
finishedUpdating,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A command to update the changelog, and optionally version, of packages.
|
||||||
|
class UpdateReleaseInfoCommand extends PackageLoopingCommand {
|
||||||
|
/// Creates a publish metadata updater command instance.
|
||||||
|
UpdateReleaseInfoCommand(
|
||||||
|
Directory packagesDir, {
|
||||||
|
GitDir? gitDir,
|
||||||
|
}) : super(packagesDir, gitDir: gitDir) {
|
||||||
|
argParser.addOption(_changelogFlag,
|
||||||
|
mandatory: true,
|
||||||
|
help: 'The changelog entry to add. '
|
||||||
|
'Each line will be a separate list entry.');
|
||||||
|
argParser.addOption(_versionTypeFlag,
|
||||||
|
mandatory: true,
|
||||||
|
help: 'The version change level',
|
||||||
|
allowed: <String>[
|
||||||
|
_versionNext,
|
||||||
|
_versionMinimal,
|
||||||
|
_versionBugfix,
|
||||||
|
_versionMinor,
|
||||||
|
],
|
||||||
|
allowedHelp: <String, String>{
|
||||||
|
_versionNext:
|
||||||
|
'No version change; just adds a NEXT entry to the changelog.',
|
||||||
|
_versionBugfix: 'Increments the bugfix version.',
|
||||||
|
_versionMinor: 'Increments the minor version.',
|
||||||
|
_versionMinimal: 'Depending on the changes to each package: '
|
||||||
|
'increments the bugfix version (for publishable changes), '
|
||||||
|
"uses NEXT (for changes that don't need to be published), "
|
||||||
|
'or skips (if no changes).',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static const String _changelogFlag = 'changelog';
|
||||||
|
static const String _versionTypeFlag = 'version';
|
||||||
|
|
||||||
|
static const String _versionNext = 'next';
|
||||||
|
static const String _versionBugfix = 'bugfix';
|
||||||
|
static const String _versionMinor = 'minor';
|
||||||
|
static const String _versionMinimal = 'minimal';
|
||||||
|
|
||||||
|
// The version change type, if there is a set type for all platforms.
|
||||||
|
//
|
||||||
|
// If null, either there is no version change, or it is dynamic (`minimal`).
|
||||||
|
_VersionIncrementType? _versionChange;
|
||||||
|
|
||||||
|
// The cache of changed files, for dynamic version change determination.
|
||||||
|
//
|
||||||
|
// Only set for `minimal` version change.
|
||||||
|
late final List<String> _changedFiles;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String name = 'update-release-info';
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String description = 'Updates CHANGELOG.md files, and optionally the '
|
||||||
|
'version in pubspec.yaml, in a way that is consistent with version-check '
|
||||||
|
'enforcement.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get hasLongOutput => false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> initializeRun() async {
|
||||||
|
if (getStringArg(_changelogFlag).trim().isEmpty) {
|
||||||
|
throw UsageException('Changelog message must not be empty.', usage);
|
||||||
|
}
|
||||||
|
switch (getStringArg(_versionTypeFlag)) {
|
||||||
|
case _versionMinor:
|
||||||
|
_versionChange = _VersionIncrementType.minor;
|
||||||
|
break;
|
||||||
|
case _versionBugfix:
|
||||||
|
_versionChange = _VersionIncrementType.bugfix;
|
||||||
|
break;
|
||||||
|
case _versionMinimal:
|
||||||
|
final GitVersionFinder gitVersionFinder = await retrieveVersionFinder();
|
||||||
|
_changedFiles = await gitVersionFinder.getChangedFiles();
|
||||||
|
// Anothing other than a fixed change is null.
|
||||||
|
_versionChange = null;
|
||||||
|
break;
|
||||||
|
case _versionNext:
|
||||||
|
_versionChange = null;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw UnimplementedError('Unimplemented version change type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<PackageResult> runForPackage(RepositoryPackage package) async {
|
||||||
|
String nextVersionString;
|
||||||
|
|
||||||
|
_VersionIncrementType? versionChange = _versionChange;
|
||||||
|
|
||||||
|
// If the change type is `minimal` determine what changes, if any, are
|
||||||
|
// needed.
|
||||||
|
if (versionChange == null &&
|
||||||
|
getStringArg(_versionTypeFlag) == _versionMinimal) {
|
||||||
|
final Directory gitRoot =
|
||||||
|
packagesDir.fileSystem.directory((await gitDir).path);
|
||||||
|
final String relativePackagePath =
|
||||||
|
getRelativePosixPath(package.directory, from: gitRoot);
|
||||||
|
final PackageChangeState state = checkPackageChangeState(package,
|
||||||
|
changedPaths: _changedFiles,
|
||||||
|
relativePackagePath: relativePackagePath);
|
||||||
|
|
||||||
|
if (!state.hasChanges) {
|
||||||
|
return PackageResult.skip('No changes to package');
|
||||||
|
}
|
||||||
|
if (state.needsVersionChange) {
|
||||||
|
versionChange = _VersionIncrementType.bugfix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (versionChange != null) {
|
||||||
|
final Version? updatedVersion =
|
||||||
|
_updatePubspecVersion(package, versionChange);
|
||||||
|
if (updatedVersion == null) {
|
||||||
|
return PackageResult.fail(
|
||||||
|
<String>['Could not determine current version.']);
|
||||||
|
}
|
||||||
|
nextVersionString = updatedVersion.toString();
|
||||||
|
print('${indentation}Incremented version to $nextVersionString.');
|
||||||
|
} else {
|
||||||
|
nextVersionString = 'NEXT';
|
||||||
|
}
|
||||||
|
|
||||||
|
final _ChangelogUpdateOutcome updateOutcome =
|
||||||
|
_updateChangelog(package, nextVersionString);
|
||||||
|
switch (updateOutcome) {
|
||||||
|
case _ChangelogUpdateOutcome.addedSection:
|
||||||
|
print('${indentation}Added a $nextVersionString section.');
|
||||||
|
break;
|
||||||
|
case _ChangelogUpdateOutcome.updatedSection:
|
||||||
|
print('${indentation}Updated NEXT section.');
|
||||||
|
break;
|
||||||
|
case _ChangelogUpdateOutcome.failed:
|
||||||
|
return PackageResult.fail(<String>['Could not update CHANGELOG.md.']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PackageResult.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
_ChangelogUpdateOutcome _updateChangelog(
|
||||||
|
RepositoryPackage package, String version) {
|
||||||
|
if (!package.changelogFile.existsSync()) {
|
||||||
|
printError('${indentation}Missing CHANGELOG.md.');
|
||||||
|
return _ChangelogUpdateOutcome.failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String newHeader = '## $version';
|
||||||
|
final RegExp listItemPattern = RegExp(r'^(\s*[-*])');
|
||||||
|
|
||||||
|
final StringBuffer newChangelog = StringBuffer();
|
||||||
|
_ChangelogUpdateState state = _ChangelogUpdateState.findingFirstSection;
|
||||||
|
bool updatedExistingSection = false;
|
||||||
|
|
||||||
|
for (final String line in package.changelogFile.readAsLinesSync()) {
|
||||||
|
switch (state) {
|
||||||
|
case _ChangelogUpdateState.findingFirstSection:
|
||||||
|
final String trimmedLine = line.trim();
|
||||||
|
if (trimmedLine.isEmpty) {
|
||||||
|
// Discard any whitespace at the top of the file.
|
||||||
|
} else if (trimmedLine == '## NEXT') {
|
||||||
|
// Replace the header with the new version (which may also be NEXT).
|
||||||
|
newChangelog.writeln(newHeader);
|
||||||
|
// Find the existing list to add to.
|
||||||
|
state = _ChangelogUpdateState.findingFirstListItem;
|
||||||
|
} else {
|
||||||
|
// The first content in the file isn't a NEXT section, so just add
|
||||||
|
// the new section.
|
||||||
|
<String>[
|
||||||
|
newHeader,
|
||||||
|
'',
|
||||||
|
..._changelogAdditionsAsList(),
|
||||||
|
'',
|
||||||
|
line, // Don't drop the current line.
|
||||||
|
].forEach(newChangelog.writeln);
|
||||||
|
state = _ChangelogUpdateState.finishedUpdating;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case _ChangelogUpdateState.findingFirstListItem:
|
||||||
|
final RegExpMatch? match = listItemPattern.firstMatch(line);
|
||||||
|
if (match != null) {
|
||||||
|
final String listMarker = match[1]!;
|
||||||
|
// Add the new items on top. If the new change is changing the
|
||||||
|
// version, then the new item should be more relevant to package
|
||||||
|
// clients than anything that was already there. If it's still
|
||||||
|
// NEXT, the order doesn't matter.
|
||||||
|
<String>[
|
||||||
|
..._changelogAdditionsAsList(listMarker: listMarker),
|
||||||
|
line, // Don't drop the current line.
|
||||||
|
].forEach(newChangelog.writeln);
|
||||||
|
state = _ChangelogUpdateState.finishedUpdating;
|
||||||
|
updatedExistingSection = true;
|
||||||
|
} else if (line.trim().isEmpty) {
|
||||||
|
// Scan past empty lines, but keep them.
|
||||||
|
newChangelog.writeln(line);
|
||||||
|
} else {
|
||||||
|
printError(' Existing NEXT section has unrecognized format.');
|
||||||
|
return _ChangelogUpdateOutcome.failed;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case _ChangelogUpdateState.finishedUpdating:
|
||||||
|
// Once changes are done, add the rest of the lines as-is.
|
||||||
|
newChangelog.writeln(line);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package.changelogFile.writeAsStringSync(newChangelog.toString());
|
||||||
|
|
||||||
|
return updatedExistingSection
|
||||||
|
? _ChangelogUpdateOutcome.updatedSection
|
||||||
|
: _ChangelogUpdateOutcome.addedSection;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the changelog to add as a Markdown list, using the given list
|
||||||
|
/// bullet style (default to the repository standard of '*'), and adding
|
||||||
|
/// any missing periods.
|
||||||
|
///
|
||||||
|
/// E.g., 'A line\nAnother line.' will become:
|
||||||
|
/// ```
|
||||||
|
/// [ '* A line.', '* Another line.' ]
|
||||||
|
/// ```
|
||||||
|
Iterable<String> _changelogAdditionsAsList({String listMarker = '*'}) {
|
||||||
|
return getStringArg(_changelogFlag).split('\n').map((String entry) {
|
||||||
|
String standardizedEntry = entry.trim();
|
||||||
|
if (!standardizedEntry.endsWith('.')) {
|
||||||
|
standardizedEntry = '$standardizedEntry.';
|
||||||
|
}
|
||||||
|
return '$listMarker $standardizedEntry';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the version in [package]'s pubspec according to [type], returning
|
||||||
|
/// the new version, or null if there was an error updating the version.
|
||||||
|
Version? _updatePubspecVersion(
|
||||||
|
RepositoryPackage package, _VersionIncrementType type) {
|
||||||
|
final Pubspec pubspec = package.parsePubspec();
|
||||||
|
final Version? currentVersion = pubspec.version;
|
||||||
|
if (currentVersion == null) {
|
||||||
|
printError('${indentation}No version in pubspec.yaml');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For versions less than 1.0, shift the change down one component per
|
||||||
|
// Dart versioning conventions.
|
||||||
|
final _VersionIncrementType adjustedType = currentVersion.major > 0
|
||||||
|
? type
|
||||||
|
: _VersionIncrementType.values[type.index - 1];
|
||||||
|
|
||||||
|
final Version newVersion = _nextVersion(currentVersion, adjustedType);
|
||||||
|
|
||||||
|
// Write the new version to the pubspec.
|
||||||
|
final YamlEditor editablePubspec =
|
||||||
|
YamlEditor(package.pubspecFile.readAsStringSync());
|
||||||
|
editablePubspec.update(<String>['version'], newVersion.toString());
|
||||||
|
package.pubspecFile.writeAsStringSync(editablePubspec.toString());
|
||||||
|
|
||||||
|
return newVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
Version _nextVersion(Version version, _VersionIncrementType type) {
|
||||||
|
switch (type) {
|
||||||
|
case _VersionIncrementType.minor:
|
||||||
|
return version.nextMinor;
|
||||||
|
case _VersionIncrementType.bugfix:
|
||||||
|
return version.nextPatch;
|
||||||
|
case _VersionIncrementType.build:
|
||||||
|
final int buildNumber =
|
||||||
|
version.build.isEmpty ? 0 : version.build.first as int;
|
||||||
|
return Version(version.major, version.minor, version.patch,
|
||||||
|
build: '${buildNumber + 1}');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -13,6 +13,7 @@ import 'package:pub_semver/pub_semver.dart';
|
|||||||
import 'common/core.dart';
|
import 'common/core.dart';
|
||||||
import 'common/git_version_finder.dart';
|
import 'common/git_version_finder.dart';
|
||||||
import 'common/package_looping_command.dart';
|
import 'common/package_looping_command.dart';
|
||||||
|
import 'common/package_state_utils.dart';
|
||||||
import 'common/process_runner.dart';
|
import 'common/process_runner.dart';
|
||||||
import 'common/pub_version_finder.dart';
|
import 'common/pub_version_finder.dart';
|
||||||
import 'common/repository_package.dart';
|
import 'common/repository_package.dart';
|
||||||
@ -531,44 +532,16 @@ ${indentation}The first version listed in CHANGELOG.md is $fromChangeLog.
|
|||||||
final Directory gitRoot =
|
final Directory gitRoot =
|
||||||
packagesDir.fileSystem.directory((await gitDir).path);
|
packagesDir.fileSystem.directory((await gitDir).path);
|
||||||
final String relativePackagePath =
|
final String relativePackagePath =
|
||||||
'${getRelativePosixPath(package.directory, from: gitRoot)}/';
|
getRelativePosixPath(package.directory, from: gitRoot);
|
||||||
bool hasChanges = false;
|
|
||||||
bool needsVersionChange = false;
|
|
||||||
bool hasChangelogChange = false;
|
|
||||||
for (final String path in _changedFiles) {
|
|
||||||
// Only consider files within the package.
|
|
||||||
if (!path.startsWith(relativePackagePath)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
hasChanges = true;
|
|
||||||
|
|
||||||
final List<String> components = p.posix.split(path);
|
final PackageChangeState state = checkPackageChangeState(package,
|
||||||
final bool isChangelog = components.last == 'CHANGELOG.md';
|
changedPaths: _changedFiles, relativePackagePath: relativePackagePath);
|
||||||
if (isChangelog) {
|
|
||||||
hasChangelogChange = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!needsVersionChange &&
|
if (!state.hasChanges) {
|
||||||
!isChangelog &&
|
|
||||||
// The example's main.dart is shown on pub.dev, but for anything else
|
|
||||||
// in the example publishing has no purpose.
|
|
||||||
!(components.contains('example') && components.last != 'main.dart') &&
|
|
||||||
// Changes to tests don't need to be published.
|
|
||||||
!components.contains('test') &&
|
|
||||||
!components.contains('androidTest') &&
|
|
||||||
!components.contains('RunnerTests') &&
|
|
||||||
!components.contains('RunnerUITests') &&
|
|
||||||
// Ignoring lints doesn't affect clients.
|
|
||||||
!components.contains('lint-baseline.xml')) {
|
|
||||||
needsVersionChange = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasChanges) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needsVersionChange) {
|
if (state.needsVersionChange) {
|
||||||
if (_getChangeDescription().split('\n').any((String line) =>
|
if (_getChangeDescription().split('\n').any((String line) =>
|
||||||
line.startsWith(_missingVersionChangeJustificationMarker))) {
|
line.startsWith(_missingVersionChangeJustificationMarker))) {
|
||||||
logWarning('Ignoring lack of version change due to '
|
logWarning('Ignoring lack of version change due to '
|
||||||
@ -586,7 +559,7 @@ ${indentation}The first version listed in CHANGELOG.md is $fromChangeLog.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!hasChangelogChange) {
|
if (!state.hasChangelogChange) {
|
||||||
if (_getChangeDescription().split('\n').any((String line) =>
|
if (_getChangeDescription().split('\n').any((String line) =>
|
||||||
line.startsWith(_missingChangelogChangeJustificationMarker))) {
|
line.startsWith(_missingChangelogChangeJustificationMarker))) {
|
||||||
logWarning('Ignoring lack of CHANGELOG update due to '
|
logWarning('Ignoring lack of CHANGELOG update due to '
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
name: flutter_plugin_tools
|
name: flutter_plugin_tools
|
||||||
description: Productivity utils for flutter/plugins and flutter/packages
|
description: Productivity utils for flutter/plugins and flutter/packages
|
||||||
repository: https://github.com/flutter/plugins/tree/main/script/tool
|
repository: https://github.com/flutter/plugins/tree/main/script/tool
|
||||||
version: 0.8.5
|
version: 0.8.6
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
args: ^2.1.0
|
args: ^2.1.0
|
||||||
|
140
script/tool/test/common/package_state_utils_test.dart
Normal file
140
script/tool/test/common/package_state_utils_test.dart
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
// Copyright 2013 The Flutter Authors. 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:file/file.dart';
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:flutter_plugin_tools/src/common/package_state_utils.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late FileSystem fileSystem;
|
||||||
|
late Directory packagesDir;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
fileSystem = MemoryFileSystem();
|
||||||
|
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('checkPackageChangeState', () {
|
||||||
|
test('reports version change needed for code changes', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir);
|
||||||
|
|
||||||
|
const List<String> changedFiles = <String>[
|
||||||
|
'packages/a_package/lib/plugin.dart',
|
||||||
|
];
|
||||||
|
|
||||||
|
final PackageChangeState state = checkPackageChangeState(package,
|
||||||
|
changedPaths: changedFiles,
|
||||||
|
relativePackagePath: 'packages/a_package');
|
||||||
|
|
||||||
|
expect(state.hasChanges, true);
|
||||||
|
expect(state.needsVersionChange, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles trailing slash on package path', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir);
|
||||||
|
|
||||||
|
const List<String> changedFiles = <String>[
|
||||||
|
'packages/a_package/lib/plugin.dart',
|
||||||
|
];
|
||||||
|
|
||||||
|
final PackageChangeState state = checkPackageChangeState(package,
|
||||||
|
changedPaths: changedFiles,
|
||||||
|
relativePackagePath: 'packages/a_package/');
|
||||||
|
|
||||||
|
expect(state.hasChanges, true);
|
||||||
|
expect(state.needsVersionChange, true);
|
||||||
|
expect(state.hasChangelogChange, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not report version change exempt changes', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePlugin('a_plugin', packagesDir);
|
||||||
|
|
||||||
|
const List<String> changedFiles = <String>[
|
||||||
|
'packages/a_plugin/example/android/lint-baseline.xml',
|
||||||
|
'packages/a_plugin/example/android/src/androidTest/foo/bar/FooTest.java',
|
||||||
|
'packages/a_plugin/example/ios/RunnerTests/Foo.m',
|
||||||
|
'packages/a_plugin/example/ios/RunnerUITests/info.plist',
|
||||||
|
'packages/a_plugin/tool/a_development_tool.dart',
|
||||||
|
'packages/a_plugin/CHANGELOG.md',
|
||||||
|
];
|
||||||
|
|
||||||
|
final PackageChangeState state = checkPackageChangeState(package,
|
||||||
|
changedPaths: changedFiles,
|
||||||
|
relativePackagePath: 'packages/a_plugin/');
|
||||||
|
|
||||||
|
expect(state.hasChanges, true);
|
||||||
|
expect(state.needsVersionChange, false);
|
||||||
|
expect(state.hasChangelogChange, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('only considers a root "tool" folder to be special', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePlugin('a_plugin', packagesDir);
|
||||||
|
|
||||||
|
const List<String> changedFiles = <String>[
|
||||||
|
'packages/a_plugin/lib/foo/tool/tool_thing.dart',
|
||||||
|
];
|
||||||
|
|
||||||
|
final PackageChangeState state = checkPackageChangeState(package,
|
||||||
|
changedPaths: changedFiles,
|
||||||
|
relativePackagePath: 'packages/a_plugin/');
|
||||||
|
|
||||||
|
expect(state.hasChanges, true);
|
||||||
|
expect(state.needsVersionChange, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('requires a version change for example main', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePlugin('a_plugin', packagesDir);
|
||||||
|
|
||||||
|
const List<String> changedFiles = <String>[
|
||||||
|
'packages/a_plugin/example/lib/main.dart',
|
||||||
|
];
|
||||||
|
|
||||||
|
final PackageChangeState state = checkPackageChangeState(package,
|
||||||
|
changedPaths: changedFiles,
|
||||||
|
relativePackagePath: 'packages/a_plugin/');
|
||||||
|
|
||||||
|
expect(state.hasChanges, true);
|
||||||
|
expect(state.needsVersionChange, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('requires a version change for example readme.md', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePlugin('a_plugin', packagesDir);
|
||||||
|
|
||||||
|
const List<String> changedFiles = <String>[
|
||||||
|
'packages/a_plugin/example/README.md',
|
||||||
|
];
|
||||||
|
|
||||||
|
final PackageChangeState state = checkPackageChangeState(package,
|
||||||
|
changedPaths: changedFiles,
|
||||||
|
relativePackagePath: 'packages/a_plugin/');
|
||||||
|
|
||||||
|
expect(state.hasChanges, true);
|
||||||
|
expect(state.needsVersionChange, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('requires a version change for example example.md', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePlugin('a_plugin', packagesDir);
|
||||||
|
|
||||||
|
const List<String> changedFiles = <String>[
|
||||||
|
'packages/a_plugin/example/lib/example.md',
|
||||||
|
];
|
||||||
|
|
||||||
|
final PackageChangeState state = checkPackageChangeState(package,
|
||||||
|
changedPaths: changedFiles,
|
||||||
|
relativePackagePath: 'packages/a_plugin/');
|
||||||
|
|
||||||
|
expect(state.hasChanges, true);
|
||||||
|
expect(state.needsVersionChange, true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
645
script/tool/test/update_release_info_command_test.dart
Normal file
645
script/tool/test/update_release_info_command_test.dart
Normal file
@ -0,0 +1,645 @@
|
|||||||
|
// Copyright 2013 The Flutter Authors. 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:io' as io;
|
||||||
|
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:flutter_plugin_tools/src/common/core.dart';
|
||||||
|
import 'package:flutter_plugin_tools/src/update_release_info_command.dart';
|
||||||
|
import 'package:mockito/mockito.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'common/plugin_command_test.mocks.dart';
|
||||||
|
import 'mocks.dart';
|
||||||
|
import 'util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late FileSystem fileSystem;
|
||||||
|
late Directory packagesDir;
|
||||||
|
late MockGitDir gitDir;
|
||||||
|
late RecordingProcessRunner processRunner;
|
||||||
|
late CommandRunner<void> runner;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
fileSystem = MemoryFileSystem();
|
||||||
|
packagesDir = createPackagesDirectory(fileSystem: fileSystem);
|
||||||
|
processRunner = RecordingProcessRunner();
|
||||||
|
|
||||||
|
gitDir = MockGitDir();
|
||||||
|
when(gitDir.path).thenReturn(packagesDir.parent.path);
|
||||||
|
when(gitDir.runCommand(any, throwOnError: anyNamed('throwOnError')))
|
||||||
|
.thenAnswer((Invocation invocation) {
|
||||||
|
final List<String> arguments =
|
||||||
|
invocation.positionalArguments[0]! as List<String>;
|
||||||
|
// Route git calls through a process runner, to make mock output
|
||||||
|
// consistent with other processes. Attach the first argument to the
|
||||||
|
// command to make targeting the mock results easier.
|
||||||
|
final String gitCommand = arguments.removeAt(0);
|
||||||
|
return processRunner.run('git-$gitCommand', arguments);
|
||||||
|
});
|
||||||
|
|
||||||
|
final UpdateReleaseInfoCommand command = UpdateReleaseInfoCommand(
|
||||||
|
packagesDir,
|
||||||
|
gitDir: gitDir,
|
||||||
|
);
|
||||||
|
runner = CommandRunner<void>(
|
||||||
|
'update_release_info_command', 'Test for update_release_info_command');
|
||||||
|
runner.addCommand(command);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('flags', () {
|
||||||
|
test('fails if --changelog is missing', () async {
|
||||||
|
Exception? commandError;
|
||||||
|
await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=next',
|
||||||
|
], exceptionHandler: (Exception e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<UsageException>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails if --changelog is blank', () async {
|
||||||
|
Exception? commandError;
|
||||||
|
await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=next',
|
||||||
|
'--changelog',
|
||||||
|
'',
|
||||||
|
], exceptionHandler: (Exception e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<UsageException>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails if --version is missing', () async {
|
||||||
|
Exception? commandError;
|
||||||
|
await runCapturingPrint(
|
||||||
|
runner, <String>['update-release-info', '--changelog', ''],
|
||||||
|
exceptionHandler: (Exception e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<UsageException>());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails if --version is an unknown value', () async {
|
||||||
|
Exception? commandError;
|
||||||
|
await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=foo',
|
||||||
|
'--changelog',
|
||||||
|
'',
|
||||||
|
], exceptionHandler: (Exception e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<UsageException>());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('changelog', () {
|
||||||
|
test('adds new NEXT section', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.0');
|
||||||
|
|
||||||
|
const String originalChangelog = '''
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
* Previous changes.
|
||||||
|
''';
|
||||||
|
package.changelogFile.writeAsStringSync(originalChangelog);
|
||||||
|
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=next',
|
||||||
|
'--changelog',
|
||||||
|
'A change.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String newChangelog = package.changelogFile.readAsStringSync();
|
||||||
|
const String expectedChangeLog = '''
|
||||||
|
## NEXT
|
||||||
|
|
||||||
|
* A change.
|
||||||
|
|
||||||
|
$originalChangelog''';
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(<Matcher>[
|
||||||
|
contains(' Added a NEXT section.'),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect(newChangelog, expectedChangeLog);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('adds to existing NEXT section', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.0');
|
||||||
|
|
||||||
|
const String originalChangelog = '''
|
||||||
|
## NEXT
|
||||||
|
|
||||||
|
* Already-pending changes.
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
* Old changes.
|
||||||
|
''';
|
||||||
|
package.changelogFile.writeAsStringSync(originalChangelog);
|
||||||
|
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=next',
|
||||||
|
'--changelog',
|
||||||
|
'A change.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String newChangelog = package.changelogFile.readAsStringSync();
|
||||||
|
const String expectedChangeLog = '''
|
||||||
|
## NEXT
|
||||||
|
|
||||||
|
* A change.
|
||||||
|
* Already-pending changes.
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
* Old changes.
|
||||||
|
''';
|
||||||
|
|
||||||
|
expect(output,
|
||||||
|
containsAllInOrder(<Matcher>[contains(' Updated NEXT section.')]));
|
||||||
|
expect(newChangelog, expectedChangeLog);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('adds new version section', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.0');
|
||||||
|
|
||||||
|
const String originalChangelog = '''
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
* Previous changes.
|
||||||
|
''';
|
||||||
|
package.changelogFile.writeAsStringSync(originalChangelog);
|
||||||
|
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=bugfix',
|
||||||
|
'--changelog',
|
||||||
|
'A change.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String newChangelog = package.changelogFile.readAsStringSync();
|
||||||
|
const String expectedChangeLog = '''
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
* A change.
|
||||||
|
|
||||||
|
$originalChangelog''';
|
||||||
|
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(<Matcher>[
|
||||||
|
contains(' Added a 1.0.1 section.'),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
expect(newChangelog, expectedChangeLog);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('converts existing NEXT section to version section', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.0');
|
||||||
|
|
||||||
|
const String originalChangelog = '''
|
||||||
|
## NEXT
|
||||||
|
|
||||||
|
* Already-pending changes.
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
* Old changes.
|
||||||
|
''';
|
||||||
|
package.changelogFile.writeAsStringSync(originalChangelog);
|
||||||
|
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=bugfix',
|
||||||
|
'--changelog',
|
||||||
|
'A change.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String newChangelog = package.changelogFile.readAsStringSync();
|
||||||
|
const String expectedChangeLog = '''
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
* A change.
|
||||||
|
* Already-pending changes.
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
* Old changes.
|
||||||
|
''';
|
||||||
|
|
||||||
|
expect(output,
|
||||||
|
containsAllInOrder(<Matcher>[contains(' Updated NEXT section.')]));
|
||||||
|
expect(newChangelog, expectedChangeLog);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('treats multiple lines as multiple list items', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.0');
|
||||||
|
|
||||||
|
const String originalChangelog = '''
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
* Previous changes.
|
||||||
|
''';
|
||||||
|
package.changelogFile.writeAsStringSync(originalChangelog);
|
||||||
|
|
||||||
|
await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=bugfix',
|
||||||
|
'--changelog',
|
||||||
|
'First change.\nSecond change.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String newChangelog = package.changelogFile.readAsStringSync();
|
||||||
|
const String expectedChangeLog = '''
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
* First change.
|
||||||
|
* Second change.
|
||||||
|
|
||||||
|
$originalChangelog''';
|
||||||
|
|
||||||
|
expect(newChangelog, expectedChangeLog);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('adds a period to any lines missing it, and removes whitespace',
|
||||||
|
() async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.0');
|
||||||
|
|
||||||
|
const String originalChangelog = '''
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
* Previous changes.
|
||||||
|
''';
|
||||||
|
package.changelogFile.writeAsStringSync(originalChangelog);
|
||||||
|
|
||||||
|
await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=bugfix',
|
||||||
|
'--changelog',
|
||||||
|
'First change \nSecond change'
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String newChangelog = package.changelogFile.readAsStringSync();
|
||||||
|
const String expectedChangeLog = '''
|
||||||
|
## 1.0.1
|
||||||
|
|
||||||
|
* First change.
|
||||||
|
* Second change.
|
||||||
|
|
||||||
|
$originalChangelog''';
|
||||||
|
|
||||||
|
expect(newChangelog, expectedChangeLog);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles non-standard changelog format', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.0');
|
||||||
|
|
||||||
|
const String originalChangelog = '''
|
||||||
|
# 1.0.0
|
||||||
|
|
||||||
|
* A version with the wrong heading format.
|
||||||
|
''';
|
||||||
|
package.changelogFile.writeAsStringSync(originalChangelog);
|
||||||
|
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=next',
|
||||||
|
'--changelog',
|
||||||
|
'A change.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String newChangelog = package.changelogFile.readAsStringSync();
|
||||||
|
const String expectedChangeLog = '''
|
||||||
|
## NEXT
|
||||||
|
|
||||||
|
* A change.
|
||||||
|
|
||||||
|
$originalChangelog''';
|
||||||
|
|
||||||
|
expect(output,
|
||||||
|
containsAllInOrder(<Matcher>[contains(' Added a NEXT section.')]));
|
||||||
|
expect(newChangelog, expectedChangeLog);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('adds to existing NEXT section using - list style', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.0');
|
||||||
|
|
||||||
|
const String originalChangelog = '''
|
||||||
|
## NEXT
|
||||||
|
|
||||||
|
- Already-pending changes.
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Previous changes.
|
||||||
|
''';
|
||||||
|
package.changelogFile.writeAsStringSync(originalChangelog);
|
||||||
|
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=next',
|
||||||
|
'--changelog',
|
||||||
|
'A change.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String newChangelog = package.changelogFile.readAsStringSync();
|
||||||
|
const String expectedChangeLog = '''
|
||||||
|
## NEXT
|
||||||
|
|
||||||
|
- A change.
|
||||||
|
- Already-pending changes.
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Previous changes.
|
||||||
|
''';
|
||||||
|
|
||||||
|
expect(output,
|
||||||
|
containsAllInOrder(<Matcher>[contains(' Updated NEXT section.')]));
|
||||||
|
expect(newChangelog, expectedChangeLog);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('skips for "minimal" when there are no changes at all', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.1');
|
||||||
|
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
|
||||||
|
MockProcess(stdout: '''
|
||||||
|
packages/different_package/test/plugin_test.dart
|
||||||
|
'''),
|
||||||
|
];
|
||||||
|
final String originalChangelog = package.changelogFile.readAsStringSync();
|
||||||
|
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=minimal',
|
||||||
|
'--changelog',
|
||||||
|
'A change.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String version = package.parsePubspec().version?.toString() ?? '';
|
||||||
|
expect(version, '1.0.1');
|
||||||
|
expect(package.changelogFile.readAsStringSync(), originalChangelog);
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(<Matcher>[
|
||||||
|
contains('No changes to package'),
|
||||||
|
contains('Skipped 1 package')
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails if CHANGELOG.md is missing', () async {
|
||||||
|
createFakePackage('a_package', packagesDir, includeCommonFiles: false);
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=minor',
|
||||||
|
'--changelog',
|
||||||
|
'A change.',
|
||||||
|
], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
expect(output,
|
||||||
|
containsAllInOrder(<Matcher>[contains(' Missing CHANGELOG.md.')]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails if CHANGELOG.md has unexpected NEXT block format', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.0');
|
||||||
|
|
||||||
|
const String originalChangelog = '''
|
||||||
|
## NEXT
|
||||||
|
|
||||||
|
Some free-form text that isn't a list.
|
||||||
|
|
||||||
|
## 1.0.0
|
||||||
|
|
||||||
|
- Previous changes.
|
||||||
|
''';
|
||||||
|
package.changelogFile.writeAsStringSync(originalChangelog);
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=minor',
|
||||||
|
'--changelog',
|
||||||
|
'A change.',
|
||||||
|
], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(<Matcher>[
|
||||||
|
contains(' Existing NEXT section has unrecognized format.')
|
||||||
|
]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
group('pubspec', () {
|
||||||
|
test('does not change for --next', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.0');
|
||||||
|
|
||||||
|
await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=next',
|
||||||
|
'--changelog',
|
||||||
|
'A change.'
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String version = package.parsePubspec().version?.toString() ?? '';
|
||||||
|
expect(version, '1.0.0');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updates bugfix version for pre-1.0 without existing build number',
|
||||||
|
() async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '0.1.0');
|
||||||
|
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=bugfix',
|
||||||
|
'--changelog',
|
||||||
|
'A change.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String version = package.parsePubspec().version?.toString() ?? '';
|
||||||
|
expect(version, '0.1.0+1');
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[contains(' Incremented version to 0.1.0+1')]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updates bugfix version for pre-1.0 with existing build number',
|
||||||
|
() async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '0.1.0+2');
|
||||||
|
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=bugfix',
|
||||||
|
'--changelog',
|
||||||
|
'A change.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String version = package.parsePubspec().version?.toString() ?? '';
|
||||||
|
expect(version, '0.1.0+3');
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[contains(' Incremented version to 0.1.0+3')]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updates bugfix version for post-1.0', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.1');
|
||||||
|
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=bugfix',
|
||||||
|
'--changelog',
|
||||||
|
'A change.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String version = package.parsePubspec().version?.toString() ?? '';
|
||||||
|
expect(version, '1.0.2');
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[contains(' Incremented version to 1.0.2')]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updates minor version for pre-1.0', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '0.1.0+2');
|
||||||
|
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=minor',
|
||||||
|
'--changelog',
|
||||||
|
'A change.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String version = package.parsePubspec().version?.toString() ?? '';
|
||||||
|
expect(version, '0.1.1');
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[contains(' Incremented version to 0.1.1')]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updates minor version for post-1.0', () async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.1');
|
||||||
|
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=minor',
|
||||||
|
'--changelog',
|
||||||
|
'A change.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String version = package.parsePubspec().version?.toString() ?? '';
|
||||||
|
expect(version, '1.1.0');
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[contains(' Incremented version to 1.1.0')]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updates bugfix version for "minimal" with publish-worthy changes',
|
||||||
|
() async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.1');
|
||||||
|
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
|
||||||
|
MockProcess(stdout: '''
|
||||||
|
packages/a_package/lib/plugin.dart
|
||||||
|
'''),
|
||||||
|
];
|
||||||
|
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=minimal',
|
||||||
|
'--changelog',
|
||||||
|
'A change.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String version = package.parsePubspec().version?.toString() ?? '';
|
||||||
|
expect(version, '1.0.2');
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[contains(' Incremented version to 1.0.2')]));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('no version change for "minimal" with non-publish-worthy changes',
|
||||||
|
() async {
|
||||||
|
final RepositoryPackage package =
|
||||||
|
createFakePackage('a_package', packagesDir, version: '1.0.1');
|
||||||
|
processRunner.mockProcessesForExecutable['git-diff'] = <io.Process>[
|
||||||
|
MockProcess(stdout: '''
|
||||||
|
packages/a_package/test/plugin_test.dart
|
||||||
|
'''),
|
||||||
|
];
|
||||||
|
|
||||||
|
await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=minimal',
|
||||||
|
'--changelog',
|
||||||
|
'A change.',
|
||||||
|
]);
|
||||||
|
|
||||||
|
final String version = package.parsePubspec().version?.toString() ?? '';
|
||||||
|
expect(version, '1.0.1');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('fails if there is no version in pubspec', () async {
|
||||||
|
createFakePackage('a_package', packagesDir, version: null);
|
||||||
|
|
||||||
|
Error? commandError;
|
||||||
|
final List<String> output = await runCapturingPrint(runner, <String>[
|
||||||
|
'update-release-info',
|
||||||
|
'--version=minor',
|
||||||
|
'--changelog',
|
||||||
|
'A change.',
|
||||||
|
], errorHandler: (Error e) {
|
||||||
|
commandError = e;
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(commandError, isA<ToolExit>());
|
||||||
|
expect(
|
||||||
|
output,
|
||||||
|
containsAllInOrder(
|
||||||
|
<Matcher>[contains('Could not determine current version.')]));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -310,14 +310,15 @@ String _pluginPlatformSection(
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef ErrorHandler = void Function(Error error);
|
|
||||||
|
|
||||||
/// Run the command [runner] with the given [args] and return
|
/// Run the command [runner] with the given [args] and return
|
||||||
/// what was printed.
|
/// what was printed.
|
||||||
/// A custom [errorHandler] can be used to handle the runner error as desired without throwing.
|
/// A custom [errorHandler] can be used to handle the runner error as desired without throwing.
|
||||||
Future<List<String>> runCapturingPrint(
|
Future<List<String>> runCapturingPrint(
|
||||||
CommandRunner<void> runner, List<String> args,
|
CommandRunner<void> runner,
|
||||||
{ErrorHandler? errorHandler}) async {
|
List<String> args, {
|
||||||
|
Function(Error error)? errorHandler,
|
||||||
|
Function(Exception error)? exceptionHandler,
|
||||||
|
}) async {
|
||||||
final List<String> prints = <String>[];
|
final List<String> prints = <String>[];
|
||||||
final ZoneSpecification spec = ZoneSpecification(
|
final ZoneSpecification spec = ZoneSpecification(
|
||||||
print: (_, __, ___, String message) {
|
print: (_, __, ___, String message) {
|
||||||
@ -333,6 +334,11 @@ Future<List<String>> runCapturingPrint(
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
errorHandler(e);
|
errorHandler(e);
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (exceptionHandler == null) {
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
exceptionHandler(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return prints;
|
return prints;
|
||||||
|
Reference in New Issue
Block a user