[flutter_plugin_tools] Validate pubspec description (#4396)
pub.dev deducts points for having a pubspec.yaml `description` that is too short or too long; several of our plugins are losing points on this. To ensure that we are following—and modeling—best practices, this adds a check that our `description` fields meet pub.dev expectations.
Fixes our existing violations. Two are not published even though this only takes effect once published:
- camera: We change this plugin pretty frequently, so this should go out soon without adding a release just for this trivial issue.
- wifi_info_flutter: This is deprecated, so we don't plan to release it. It has to be fixed to allow the tool change to land though.
diff --git a/packages/camera/camera/CHANGELOG.md b/packages/camera/camera/CHANGELOG.md
index b2dda9a..c9dfc63 100644
--- a/packages/camera/camera/CHANGELOG.md
+++ b/packages/camera/camera/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Updated package description.
+
## 0.9.4+1
* Fixed Android implementation throwing IllegalStateException when switching to a different activity.
diff --git a/packages/camera/camera/pubspec.yaml b/packages/camera/camera/pubspec.yaml
index 5c225ea..21892b2 100644
--- a/packages/camera/camera/pubspec.yaml
+++ b/packages/camera/camera/pubspec.yaml
@@ -1,7 +1,7 @@
name: camera
-description: A Flutter plugin for getting information about and controlling the
- camera on Android, iOS and Web. Supports previewing the camera feed, capturing images, capturing video,
- and streaming image buffers to dart.
+description: A Flutter plugin for controlling the camera. Supports previewing
+ the camera feed, capturing images and video, and streaming image buffers to
+ Dart.
repository: https://github.com/flutter/plugins/tree/master/packages/camera/camera
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
version: 0.9.4+1
diff --git a/packages/espresso/CHANGELOG.md b/packages/espresso/CHANGELOG.md
index e00ea70..88976c8 100644
--- a/packages/espresso/CHANGELOG.md
+++ b/packages/espresso/CHANGELOG.md
@@ -1,6 +1,7 @@
-## NEXT
+## 0.1.0+4
* Updated Android lint settings.
+* Updated package description.
## 0.1.0+3
diff --git a/packages/espresso/pubspec.yaml b/packages/espresso/pubspec.yaml
index 6295c0c..c0f3b00 100644
--- a/packages/espresso/pubspec.yaml
+++ b/packages/espresso/pubspec.yaml
@@ -1,8 +1,9 @@
name: espresso
description: Java classes for testing Flutter apps using Espresso.
+ Allows driving Flutter widgets from a native Espresso test.
repository: https://github.com/flutter/plugins/tree/master/packages/espresso
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+espresso%22
-version: 0.1.0+3
+version: 0.1.0+4
environment:
sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md
index 225f601..f34ed78 100644
--- a/packages/file_selector/file_selector/CHANGELOG.md
+++ b/packages/file_selector/file_selector/CHANGELOG.md
@@ -1,10 +1,11 @@
-## NEXT
+## 0.8.2+1
* Minor code cleanup for new analysis rules.
+* Updated package description.
## 0.8.2
-* Update platform_plugin_interface version requirement.
+* Update `platform_plugin_interface` version requirement.
## 0.8.1
diff --git a/packages/file_selector/file_selector/pubspec.yaml b/packages/file_selector/file_selector/pubspec.yaml
index e1725ef..d69217f 100644
--- a/packages/file_selector/file_selector/pubspec.yaml
+++ b/packages/file_selector/file_selector/pubspec.yaml
@@ -1,8 +1,9 @@
name: file_selector
-description: Flutter plugin for opening and saving files.
+description: Flutter plugin for opening and saving files, or selecting
+ directories, using native file selection UI.
repository: https://github.com/flutter/plugins/tree/master/packages/file_selector/file_selector
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+file_selector%22
-version: 0.8.2
+version: 0.8.2+1
environment:
sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/plugin_platform_interface/CHANGELOG.md b/packages/plugin_platform_interface/CHANGELOG.md
index 987049c..af79d11 100644
--- a/packages/plugin_platform_interface/CHANGELOG.md
+++ b/packages/plugin_platform_interface/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.0.2
+
+* Update package description.
+
## 2.0.1
* Fix `federated flutter plugins` link in the README.md.
diff --git a/packages/plugin_platform_interface/pubspec.yaml b/packages/plugin_platform_interface/pubspec.yaml
index 66527bc..0b4b178 100644
--- a/packages/plugin_platform_interface/pubspec.yaml
+++ b/packages/plugin_platform_interface/pubspec.yaml
@@ -1,5 +1,6 @@
name: plugin_platform_interface
-description: Reusable base class for Flutter plugin platform interfaces.
+description: Reusable base class for platform interfaces of Flutter federated
+ plugins, to help enforce best practices.
repository: https://github.com/flutter/plugins/tree/master/packages/plugin_platform_interface
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+plugin_platform_interface%22
@@ -14,7 +15,7 @@
# be done when absolutely necessary and after the ecosystem has already migrated to 1.X.Y version
# that is forward compatible with 2.0.0 (ideally the ecosystem have migrated to depend on:
# `plugin_platform_interface: >=1.X.Y <3.0.0`).
-version: 2.0.1
+version: 2.0.2
environment:
sdk: ">=2.12.0 <3.0.0"
diff --git a/packages/wifi_info_flutter/wifi_info_flutter/CHANGELOG.md b/packages/wifi_info_flutter/wifi_info_flutter/CHANGELOG.md
index 86f3f67..3d55997 100644
--- a/packages/wifi_info_flutter/wifi_info_flutter/CHANGELOG.md
+++ b/packages/wifi_info_flutter/wifi_info_flutter/CHANGELOG.md
@@ -1,6 +1,7 @@
## NEXT
* Updated Android lint settings.
+* Updated package description.
## 2.0.2
diff --git a/packages/wifi_info_flutter/wifi_info_flutter/pubspec.yaml b/packages/wifi_info_flutter/wifi_info_flutter/pubspec.yaml
index cbda364..b1e1e75 100644
--- a/packages/wifi_info_flutter/wifi_info_flutter/pubspec.yaml
+++ b/packages/wifi_info_flutter/wifi_info_flutter/pubspec.yaml
@@ -1,5 +1,5 @@
name: wifi_info_flutter
-description: A new flutter plugin project.
+description: A Flutter plugin to get WiFi information such as connection status and network identifiers.
repository: https://github.com/flutter/plugins/tree/master/packages/wifi_info_flutter/wifi_info_flutter
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+wifi_info_flutter%22
version: 2.0.2
diff --git a/script/tool/CHANGELOG.md b/script/tool/CHANGELOG.md
index 6119545..2e6404e 100644
--- a/script/tool/CHANGELOG.md
+++ b/script/tool/CHANGELOG.md
@@ -10,6 +10,8 @@
major version change restriction.
- Improved error handling and error messages in CHANGELOG version checks.
- `license-check` now validates Kotlin files.
+- `pubspec-check` now checks that the description is of the pub-recommended
+ length.
## 0.7.1
diff --git a/script/tool/lib/src/common/repository_package.dart b/script/tool/lib/src/common/repository_package.dart
index cb586af..3b4417a 100644
--- a/script/tool/lib/src/common/repository_package.dart
+++ b/script/tool/lib/src/common/repository_package.dart
@@ -58,6 +58,15 @@
bool get isPlatformInterface =>
directory.basename.endsWith('_platform_interface');
+ /// True if this appears to be a platform implementation package, according to
+ /// repository conventions.
+ bool get isPlatformImplementation =>
+ // Any part of a federated plugin that isn't the platform interface and
+ // isn't the app-facing package should be an implementation package.
+ isFederated &&
+ !isPlatformInterface &&
+ directory.basename != directory.parent.basename;
+
/// Returns the Flutter example packages contained in the package, if any.
Iterable<RepositoryPackage> getExamples() {
final Directory exampleDirectory = directory.childDirectory('example');
diff --git a/script/tool/lib/src/pubspec_check_command.dart b/script/tool/lib/src/pubspec_check_command.dart
index fec0dce..b99f5af 100644
--- a/script/tool/lib/src/pubspec_check_command.dart
+++ b/script/tool/lib/src/pubspec_check_command.dart
@@ -126,6 +126,18 @@
'${indentation * 2}$_expectedIssueLinkFormat<package label>');
passing = false;
}
+
+ // Don't check descriptions for federated package components other than
+ // the app-facing package, since they are unlisted, and are expected to
+ // have short descriptions.
+ if (!package.isPlatformInterface && !package.isPlatformImplementation) {
+ final String? descriptionError =
+ _checkDescription(pubspec, package: package);
+ if (descriptionError != null) {
+ printError('$indentation$descriptionError');
+ passing = false;
+ }
+ }
}
return passing;
@@ -180,6 +192,27 @@
return errorMessages;
}
+ // Validates the "description" field for a package, returning an error
+ // string if there are any issues.
+ String? _checkDescription(
+ Pubspec pubspec, {
+ required RepositoryPackage package,
+ }) {
+ final String? description = pubspec.description;
+ if (description == null) {
+ return 'Missing "description"';
+ }
+
+ if (description.length < 60) {
+ return '"description" is too short. pub.dev recommends package '
+ 'descriptions of 60-180 characters.';
+ }
+ if (description.length > 180) {
+ return '"description" is too long. pub.dev recommends package '
+ 'descriptions of 60-180 characters.';
+ }
+ }
+
bool _checkIssueLink(Pubspec pubspec) {
return pubspec.issueTracker
?.toString()
diff --git a/script/tool/test/common/repository_package_test.dart b/script/tool/test/common/repository_package_test.dart
index 5c56243..4c20389 100644
--- a/script/tool/test/common/repository_package_test.dart
+++ b/script/tool/test/common/repository_package_test.dart
@@ -120,4 +120,39 @@
plugin.childDirectory('example').childDirectory('example2').path);
});
});
+
+ group('federated plugin queries', () {
+ test('all return false for a simple plugin', () {
+ final Directory plugin = createFakePlugin('a_plugin', packagesDir);
+ expect(RepositoryPackage(plugin).isFederated, false);
+ expect(RepositoryPackage(plugin).isPlatformInterface, false);
+ expect(RepositoryPackage(plugin).isFederated, false);
+ });
+
+ test('handle app-facing packages', () {
+ final Directory plugin =
+ createFakePlugin('a_plugin', packagesDir.childDirectory('a_plugin'));
+ expect(RepositoryPackage(plugin).isFederated, true);
+ expect(RepositoryPackage(plugin).isPlatformInterface, false);
+ expect(RepositoryPackage(plugin).isPlatformImplementation, false);
+ });
+
+ test('handle platform interface packages', () {
+ final Directory plugin = createFakePlugin('a_plugin_platform_interface',
+ packagesDir.childDirectory('a_plugin'));
+ expect(RepositoryPackage(plugin).isFederated, true);
+ expect(RepositoryPackage(plugin).isPlatformInterface, true);
+ expect(RepositoryPackage(plugin).isPlatformImplementation, false);
+ });
+
+ test('handle platform implementation packages', () {
+ // A platform interface can end with anything, not just one of the known
+ // platform names, because of cases like webview_flutter_wkwebview.
+ final Directory plugin = createFakePlugin(
+ 'a_plugin_foo', packagesDir.childDirectory('a_plugin'));
+ expect(RepositoryPackage(plugin).isFederated, true);
+ expect(RepositoryPackage(plugin).isPlatformInterface, false);
+ expect(RepositoryPackage(plugin).isPlatformImplementation, true);
+ });
+ });
}
diff --git a/script/tool/test/pubspec_check_command_test.dart b/script/tool/test/pubspec_check_command_test.dart
index 9481369..d09dceb 100644
--- a/script/tool/test/pubspec_check_command_test.dart
+++ b/script/tool/test/pubspec_check_command_test.dart
@@ -56,6 +56,7 @@
bool includeHomepage = false,
bool includeIssueTracker = true,
bool publishable = true,
+ String? description,
}) {
final String repositoryPath = repositoryPackagesDirRelativePath ?? name;
final String repoLink = 'https://github.com/flutter/'
@@ -64,8 +65,11 @@
final String issueTrackerLink =
'https://github.com/flutter/flutter/issues?'
'q=is%3Aissue+is%3Aopen+label%3A%22p%3A+$name%22';
+ description ??= 'A test package for validating that the pubspec.yaml '
+ 'follows repo best practices.';
return '''
name: $name
+description: $description
${includeRepository ? 'repository: $repoLink' : ''}
${includeHomepage ? 'homepage: $repoLink' : ''}
${includeIssueTracker ? 'issue_tracker: $issueTrackerLink' : ''}
@@ -327,6 +331,95 @@
);
});
+ test('fails when description is too short', () async {
+ final Directory pluginDirectory =
+ createFakePlugin('a_plugin', packagesDir.childDirectory('a_plugin'));
+
+ pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
+${headerSection('plugin', isPlugin: true, description: 'Too short')}
+${environmentSection()}
+${flutterSection(isPlugin: true)}
+${dependenciesSection()}
+${devDependenciesSection()}
+''');
+
+ Error? commandError;
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['pubspec-check'], errorHandler: (Error e) {
+ commandError = e;
+ });
+
+ expect(commandError, isA<ToolExit>());
+ expect(
+ output,
+ containsAllInOrder(<Matcher>[
+ contains('"description" is too short. pub.dev recommends package '
+ 'descriptions of 60-180 characters.'),
+ ]),
+ );
+ });
+
+ test(
+ 'allows short descriptions for non-app-facing parts of federated plugins',
+ () async {
+ final Directory pluginDirectory = createFakePlugin('plugin', packagesDir);
+
+ pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
+${headerSection('plugin', isPlugin: true, description: 'Too short')}
+${environmentSection()}
+${flutterSection(isPlugin: true)}
+${dependenciesSection()}
+${devDependenciesSection()}
+''');
+
+ Error? commandError;
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['pubspec-check'], errorHandler: (Error e) {
+ commandError = e;
+ });
+
+ expect(commandError, isA<ToolExit>());
+ expect(
+ output,
+ containsAllInOrder(<Matcher>[
+ contains('"description" is too short. pub.dev recommends package '
+ 'descriptions of 60-180 characters.'),
+ ]),
+ );
+ });
+
+ test('fails when description is too long', () async {
+ final Directory pluginDirectory = createFakePlugin('plugin', packagesDir);
+
+ const String description = 'This description is too long. It just goes '
+ 'on and on and on and on and on. pub.dev will down-score it because '
+ 'there is just too much here. Someone shoul really cut this down to just '
+ 'the core description so that search results are more useful and the '
+ 'package does not lose pub points.';
+ pluginDirectory.childFile('pubspec.yaml').writeAsStringSync('''
+${headerSection('plugin', isPlugin: true, description: description)}
+${environmentSection()}
+${flutterSection(isPlugin: true)}
+${dependenciesSection()}
+${devDependenciesSection()}
+''');
+
+ Error? commandError;
+ final List<String> output = await runCapturingPrint(
+ runner, <String>['pubspec-check'], errorHandler: (Error e) {
+ commandError = e;
+ });
+
+ expect(commandError, isA<ToolExit>());
+ expect(
+ output,
+ containsAllInOrder(<Matcher>[
+ contains('"description" is too long. pub.dev recommends package '
+ 'descriptions of 60-180 characters.'),
+ ]),
+ );
+ });
+
test('fails when environment section is out of order', () async {
final Directory pluginDirectory = createFakePlugin('plugin', packagesDir);