Skip to content
This repository was archived by the owner on Feb 22, 2023. It is now read-only.

Commit 9ba2d7d

Browse files
author
Chris Yang
committed
add --all and --dry-run to publish-plugin
1 parent d572371 commit 9ba2d7d

File tree

3 files changed

+548
-26
lines changed

3 files changed

+548
-26
lines changed

script/tool/lib/src/publish_plugin_command.dart

Lines changed: 176 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44

55
import 'dart:async';
66
import 'dart:convert';
7-
import 'dart:io';
7+
import 'dart:io' as io;
88

99
import 'package:file/file.dart';
1010
import 'package:git/git.dart';
1111
import 'package:meta/meta.dart';
1212
import 'package:path/path.dart' as p;
13+
import 'package:pub_semver/pub_semver.dart';
14+
import 'package:pubspec_parse/pubspec_parse.dart';
1315
import 'package:yaml/yaml.dart';
1416

1517
import 'common.dart';
@@ -32,10 +34,12 @@ class PublishPluginCommand extends PluginCommand {
3234
FileSystem fileSystem, {
3335
ProcessRunner processRunner = const ProcessRunner(),
3436
Print print = print,
35-
Stdin stdinput,
37+
io.Stdin stdinput,
38+
GitDir gitDir,
3639
}) : _print = print,
37-
_stdin = stdinput ?? stdin,
38-
super(packagesDir, fileSystem, processRunner: processRunner) {
40+
_stdin = stdinput ?? io.stdin,
41+
super(packagesDir, fileSystem,
42+
processRunner: processRunner, gitDir: gitDir) {
3943
argParser.addOption(
4044
_packageOption,
4145
help: 'The package to publish.'
@@ -64,13 +68,31 @@ class PublishPluginCommand extends PluginCommand {
6468
// Flutter convention is to use "upstream" for the single source of truth, and "origin" for personal forks.
6569
defaultsTo: 'upstream',
6670
);
71+
argParser.addFlag(
72+
_allFlag,
73+
help:
74+
'Release all plugins that contains pubspec changes at the current commit compares to the base-sha.\n'
75+
'The $_packageOption option is ignored if this is on.',
76+
defaultsTo: false,
77+
);
78+
argParser.addFlag(
79+
_dryRunFlag,
80+
help:
81+
'Skips the real `pub publish` and `git tag` commands and assumes both commands are successful.\n'
82+
'This does not run `pub publish --dry-run`.\n'
83+
'If you want to run the command with `pub publish --dry-run`, use `pub-publish-flags=--dry-run`',
84+
defaultsTo: false,
85+
negatable: true,
86+
);
6787
}
6888

6989
static const String _packageOption = 'package';
7090
static const String _tagReleaseOption = 'tag-release';
7191
static const String _pushTagsOption = 'push-tags';
7292
static const String _pubFlagsOption = 'pub-publish-flags';
7393
static const String _remoteOption = 'remote';
94+
static const String _allFlag = 'all';
95+
static const String _dryRunFlag = 'dry-run';
7496

7597
// Version tags should follow <package-name>-v<semantic-version>. For example,
7698
// `flutter_plugin_tools-v0.0.24`.
@@ -84,14 +106,15 @@ class PublishPluginCommand extends PluginCommand {
84106
'Attempts to publish the given plugin and tag its release on GitHub.';
85107

86108
final Print _print;
87-
final Stdin _stdin;
88-
// The directory of the actual package that we are publishing.
109+
final io.Stdin _stdin;
89110
StreamSubscription<String> _stdinSubscription;
111+
bool _startedListenToStdStream = false;
90112

91113
@override
92114
Future<void> run() async {
93115
final String package = argResults[_packageOption] as String;
94-
if (package == null) {
116+
final bool all = argResults[_allFlag] as bool;
117+
if (package == null && !all) {
95118
_print(
96119
'Must specify a package to publish. See `plugin_tools help publish-plugin`.');
97120
throw ToolExit(1);
@@ -102,6 +125,8 @@ class PublishPluginCommand extends PluginCommand {
102125
_print('$packagesDir is not a valid Git repository.');
103126
throw ToolExit(1);
104127
}
128+
final GitDir baseGitDir =
129+
await GitDir.fromExisting(packagesDir.path, allowSubdirectory: true);
105130

106131
final bool shouldPushTag = argResults[_pushTagsOption] == true;
107132
final String remote = argResults[_remoteOption] as String;
@@ -111,7 +136,74 @@ class PublishPluginCommand extends PluginCommand {
111136
}
112137
_print('Local repo is ready!');
113138

114-
final Directory packageDir = _getPackageDir(package);
139+
if (all) {
140+
await _publishAllPackages(
141+
remote: remote,
142+
remoteUrl: remoteUrl,
143+
shouldPushTag: shouldPushTag,
144+
baseGitDir: baseGitDir);
145+
} else {
146+
await _publishAndReleasePackage(
147+
packageDir: _getPackageDir(package),
148+
remote: remote,
149+
remoteUrl: remoteUrl,
150+
shouldPushTag: shouldPushTag);
151+
}
152+
await _finishSuccesfully();
153+
}
154+
155+
Future<void> _publishAllPackages(
156+
{String remote,
157+
String remoteUrl,
158+
bool shouldPushTag,
159+
GitDir baseGitDir}) async {
160+
final List<String> packagesReleased = <String>[];
161+
final GitVersionFinder gitVersionFinder = await retrieveVersionFinder();
162+
final List<String> changedPubspecs =
163+
await gitVersionFinder.getChangedPubSpecs();
164+
if (changedPubspecs.isEmpty) {
165+
_print('No version updates in this commit, exiting...');
166+
return;
167+
}
168+
_print('Getting existing tags...');
169+
final io.ProcessResult existingTagsResult =
170+
await baseGitDir.runCommand(<String>['tag', '--sort=-committerdate']);
171+
final List<String> existingTags = (existingTagsResult.stdout as String)
172+
.split('\n')
173+
..removeWhere((element) => element == '');
174+
for (final String pubspecPath in changedPubspecs) {
175+
final File pubspecFile =
176+
fileSystem.directory(baseGitDir.path).childFile(pubspecPath);
177+
if (!pubspecFile.existsSync()) {
178+
printErrorAndExit(
179+
errorMessage:
180+
'Fatal: The pubspec file at ${pubspecFile.path} does not exist.');
181+
}
182+
final bool needsRelease = await _checkNeedsRelease(
183+
pubspecPath: pubspecPath,
184+
pubspecFile: pubspecFile,
185+
gitVersionFinder: gitVersionFinder,
186+
existingTags: existingTags);
187+
if (!needsRelease) {
188+
continue;
189+
}
190+
_print('\n');
191+
await _publishAndReleasePackage(
192+
packageDir: pubspecFile.parent,
193+
remote: remote,
194+
remoteUrl: remoteUrl,
195+
shouldPushTag: shouldPushTag);
196+
packagesReleased.add(pubspecFile.parent.basename);
197+
_print('\n');
198+
}
199+
_print('Packages released: ${packagesReleased.join(', ')}');
200+
}
201+
202+
Future<void> _publishAndReleasePackage(
203+
{@required Directory packageDir,
204+
@required String remote,
205+
@required String remoteUrl,
206+
@required bool shouldPushTag}) async {
115207
await _publishPlugin(packageDir: packageDir);
116208
if (argResults[_tagReleaseOption] as bool) {
117209
await _tagRelease(
@@ -120,7 +212,48 @@ class PublishPluginCommand extends PluginCommand {
120212
remoteUrl: remoteUrl,
121213
shouldPushTag: shouldPushTag);
122214
}
123-
await _finishSuccesfully();
215+
_print('Release ${packageDir.basename} successful.');
216+
}
217+
218+
// Returns `true` if needs to release the version, `false` if needs to skip
219+
Future<bool> _checkNeedsRelease({
220+
@required String pubspecPath,
221+
@required File pubspecFile,
222+
@required GitVersionFinder gitVersionFinder,
223+
@required List<String> existingTags,
224+
}) async {
225+
final Pubspec pubspec = Pubspec.parse(pubspecFile.readAsStringSync());
226+
if (pubspec.publishTo == 'none') {
227+
return false;
228+
}
229+
230+
final Version headVersion =
231+
await gitVersionFinder.getPackageVersion(pubspecPath, gitRef: 'HEAD');
232+
if (headVersion == null) {
233+
printErrorAndExit(
234+
errorMessage: 'No version found. A package that '
235+
'intentionally has no version should be marked '
236+
'"publish_to: none".');
237+
}
238+
239+
if (pubspec.name == null) {
240+
printErrorAndExit(errorMessage: 'Fatal: Package name is null.');
241+
}
242+
// Get latest tagged version and compare with the current version.
243+
final String latestTag = existingTags.isNotEmpty
244+
? existingTags
245+
.firstWhere((String tag) => tag.split('-v').first == pubspec.name)
246+
: '';
247+
if (latestTag.isNotEmpty) {
248+
final String latestTaggedVersion = latestTag.split('-v').last;
249+
final Version latestVersion = Version.parse(latestTaggedVersion);
250+
if (pubspec.version < latestVersion) {
251+
_print(
252+
'The new version (${pubspec.version}) is lower than the current version ($latestVersion) for ${pubspec.name}.\nThis git commit is a revert, no release is tagged.');
253+
return false;
254+
}
255+
}
256+
return true;
124257
}
125258

126259
Future<void> _publishPlugin({@required Directory packageDir}) async {
@@ -135,6 +268,14 @@ class PublishPluginCommand extends PluginCommand {
135268
@required String remoteUrl,
136269
@required bool shouldPushTag}) async {
137270
final String tag = _getTag(packageDir);
271+
if (argResults[_dryRunFlag] as bool) {
272+
_print('DRY RUN: Tagging release $tag...');
273+
if (!shouldPushTag) {
274+
return;
275+
}
276+
_print('DRY RUN: Pushing tag to $remote...');
277+
return;
278+
}
138279
_print('Tagging release $tag...');
139280
await processRunner.runAndExitOnError('git', <String>['tag', tag],
140281
workingDir: packageDir);
@@ -147,7 +288,9 @@ class PublishPluginCommand extends PluginCommand {
147288
}
148289

149290
Future<void> _finishSuccesfully() async {
150-
await _stdinSubscription.cancel();
291+
if (_stdinSubscription != null) {
292+
await _stdinSubscription.cancel();
293+
}
151294
_print('Done!');
152295
}
153296

@@ -163,7 +306,7 @@ class PublishPluginCommand extends PluginCommand {
163306
}
164307

165308
Future<void> _checkGitStatus(Directory packageDir) async {
166-
final ProcessResult statusResult = await processRunner.runAndExitOnError(
309+
final io.ProcessResult statusResult = await processRunner.runAndExitOnError(
167310
'git',
168311
<String>[
169312
'status',
@@ -184,7 +327,7 @@ class PublishPluginCommand extends PluginCommand {
184327
}
185328

186329
Future<String> _verifyRemote(String remote) async {
187-
final ProcessResult remoteInfo = await processRunner.runAndExitOnError(
330+
final io.ProcessResult remoteInfo = await processRunner.runAndExitOnError(
188331
'git', <String>['remote', 'get-url', remote],
189332
workingDir: packagesDir);
190333
return remoteInfo.stdout as String;
@@ -193,20 +336,31 @@ class PublishPluginCommand extends PluginCommand {
193336
Future<void> _publish(Directory packageDir) async {
194337
final List<String> publishFlags =
195338
argResults[_pubFlagsOption] as List<String>;
339+
if (argResults[_dryRunFlag] as bool) {
340+
_print(
341+
'DRY RUN: Running `pub publish ${publishFlags.join(' ')}` in ${packageDir.absolute.path}...\n');
342+
return;
343+
}
344+
196345
_print(
197346
'Running `pub publish ${publishFlags.join(' ')}` in ${packageDir.absolute.path}...\n');
198-
final Process publish = await processRunner.start(
347+
final io.Process publish = await processRunner.start(
199348
'flutter', <String>['pub', 'publish'] + publishFlags,
200349
workingDirectory: packageDir);
201-
publish.stdout
202-
.transform(utf8.decoder)
203-
.listen((String data) => _print(data));
204-
publish.stderr
205-
.transform(utf8.decoder)
206-
.listen((String data) => _print(data));
207-
_stdinSubscription = _stdin
208-
.transform(utf8.decoder)
209-
.listen((String data) => publish.stdin.writeln(data));
350+
351+
if (!_startedListenToStdStream) {
352+
publish.stdout
353+
.transform(utf8.decoder)
354+
.listen((String data) => _print(data));
355+
publish.stderr
356+
.transform(utf8.decoder)
357+
.listen((String data) => _print(data));
358+
_stdinSubscription = _stdin
359+
.transform(utf8.decoder)
360+
.listen((String data) => publish.stdin.writeln(data));
361+
362+
_startedListenToStdStream = true;
363+
}
210364
final int result = await publish.exitCode;
211365
if (result != 0) {
212366
_print('Publish failed. Exiting.');

0 commit comments

Comments
 (0)