Skip to content

Commit 7980e67

Browse files
authored
Merge pull request #6 from invertase/feat/unpublished-command-filter
2 parents 3ef9877 + 80eddbd commit 7980e67

File tree

8 files changed

+229
-6
lines changed

8 files changed

+229
-6
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@
88
/pubspec.lock
99
/flutterfire/
1010
/workspaces
11+
.DS_Store

CHANGELOG.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
## 0.2.0
2+
3+
- Added a new filter for filtering published or unpublished packages: `--[no-]published`.
4+
- Unpublished in this case means the package either does not exist on the Pub registry or the current local version of the package is not yet published to the Pub registry.
5+
- Added a new command to pretty print currently unpublished packages: `melos unpublished`.
6+
7+
#### Example `--[no-]published` usage
8+
9+
Example logging out all unpublished packages and their versions:
10+
11+
```bash
12+
mike@MikeMacMini fe_ff_master % melos exec --no-published --ignore="*example*" -- echo MELOS_PACKAGE_NAME MELOS_PACKAGE_VERSION
13+
$ melos exec --no-published
14+
> echo MELOS_PACKAGE_NAME MELOS_PACKAGE_VERSION
15+
> RUNNING (in 12 packages)
16+
17+
[firebase_admob]: firebase_admob 0.9.3+4
18+
[firebase_analytics_platform_interface]: firebase_analytics_platform_interface 1.0.3
19+
[firebase_auth]: firebase_auth 0.17.0-dev.1
20+
[firebase_auth_web]: firebase_auth_web 0.2.0-dev.1
21+
[firebase_core]: firebase_core 0.5.0-dev.2
22+
[firebase_crashlytics]: firebase_crashlytics 0.1.4+1
23+
[firebase_database]: firebase_database 4.0.0-dev.1
24+
[firebase_dynamic_links]: firebase_dynamic_links 0.5.3
25+
[firebase_ml_vision]: firebase_ml_vision 0.9.5
26+
[firebase_remote_config]: firebase_remote_config 0.3.1+1
27+
[firebase_storage]: firebase_storage 4.0.0-dev.1
28+
29+
$ melos exec --no-published
30+
> echo MELOS_PACKAGE_NAME MELOS_PACKAGE_VERSION
31+
> SUCCESS
32+
mike@MikeMacMini fe_ff_master %
33+
```
34+
35+
#### Example `unpublished` usage
36+
37+
```bash
38+
mike@MikeMacMini fe_ff_master % melos unpublished --ignore="*example*"
39+
$ melos unpublished
40+
> /Users/mike/Documents/Projects/Flutter/fe_ff_master
41+
42+
Reading registry for package information... SUCCESS
43+
44+
$ melos unpublished
45+
> /Users/mike/Documents/Projects/Flutter/fe_ff_master
46+
> UNPUBLISHED PACKAGES (12 packages)
47+
> firebase_analytics_platform_interface
48+
• Local: 1.0.3
49+
• Remote: 1.0.1
50+
> cloud_functions
51+
• Local: 0.6.0-dev.2
52+
• Remote: 0.6.0-dev.1
53+
> firebase_core
54+
• Local: 0.5.0-dev.2
55+
• Remote: 0.5.0-dev.1
56+
> firebase_auth_web
57+
• Local: 0.2.0-dev.1
58+
• Remote: 0.1.3+1
59+
> firebase_dynamic_links
60+
• Local: 0.5.3
61+
• Remote: 0.5.1
62+
> firebase_crashlytics
63+
• Local: 0.1.4+1
64+
• Remote: 0.1.3+3
65+
> firebase_admob
66+
• Local: 0.9.3+4
67+
• Remote: 0.9.3+2
68+
> firebase_ml_vision
69+
• Local: 0.9.5
70+
• Remote: 0.9.4
71+
> firebase_remote_config
72+
• Local: 0.3.1+1
73+
• Remote: 0.3.1
74+
> firebase_database
75+
• Local: 4.0.0-dev.1
76+
• Remote: 3.1.6
77+
> firebase_auth
78+
• Local: 0.17.0-dev.1
79+
• Remote: 0.9.0
80+
> firebase_storage
81+
• Local: 4.0.0-dev.1
82+
• Remote: 3.1.6
83+
84+
mike@MikeMacMini fe_ff_master %
85+
```

lib/src/command/unpublished.dart

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import 'dart:io';
2+
3+
import 'package:args/command_runner.dart' show Command;
4+
import 'package:pool/pool.dart' show Pool;
5+
6+
import '../common/logger.dart';
7+
import '../common/package.dart';
8+
import '../common/workspace.dart';
9+
10+
class UnpublishedCommand extends Command {
11+
@override
12+
final String name = 'unpublished';
13+
14+
@override
15+
final List<String> aliases = ['unp'];
16+
17+
@override
18+
final String description =
19+
'Discover and list unpublished packages or package versions in your repository.';
20+
21+
@override
22+
void run() async {
23+
logger.stdout(
24+
'${logger.ansi.yellow}\$${logger.ansi.noColor} ${logger.ansi.emphasized("melos unpublished")}');
25+
logger.stdout(
26+
' └> ${logger.ansi.cyan}${logger.ansi.emphasized(currentWorkspace.path)}${logger.ansi.noColor}\n');
27+
var readRegistryProgress =
28+
logger.progress('Reading registry for package information');
29+
30+
var pool = Pool(10);
31+
var unpublishedPackages = <MelosPackage>[];
32+
var latestPackageVersion = <String, String>{};
33+
await pool.forEach<MelosPackage, void>(currentWorkspace.packages,
34+
(package) {
35+
return package.getPublishedVersions().then((versions) async {
36+
if (versions.isEmpty || !versions.contains(package.version)) {
37+
unpublishedPackages.add(package);
38+
if (versions.isEmpty) {
39+
latestPackageVersion[package.name] = 'none';
40+
} else {
41+
latestPackageVersion[package.name] = versions[0];
42+
}
43+
}
44+
});
45+
}).drain();
46+
47+
readRegistryProgress.finish(
48+
message: '${logger.ansi.green}SUCCESS${logger.ansi.noColor}',
49+
showTiming: true);
50+
51+
logger.stdout('');
52+
logger.stdout(
53+
'${logger.ansi.yellow}\$${logger.ansi.noColor} ${logger.ansi.emphasized("melos unpublished")}');
54+
logger.stdout(
55+
' └> ${logger.ansi.cyan}${logger.ansi.emphasized(currentWorkspace.path)}${logger.ansi.noColor}');
56+
if (unpublishedPackages.isNotEmpty) {
57+
logger.stdout(
58+
' └> ${logger.ansi.red}${logger.ansi.emphasized('UNPUBLISHED PACKAGES')}${logger.ansi.noColor} (${unpublishedPackages.length} packages)');
59+
unpublishedPackages.forEach((package) {
60+
logger.stdout(
61+
' └> ${logger.ansi.yellow}${package.name}${logger.ansi.noColor}');
62+
logger.stdout(
63+
' ${logger.ansi.bullet} ${logger.ansi.green}Local:${logger.ansi.noColor} ${package.version ?? 'none'}');
64+
logger.stdout(
65+
' ${logger.ansi.bullet} ${logger.ansi.cyan}Remote:${logger.ansi.noColor} ${latestPackageVersion[package.name]}');
66+
});
67+
logger.stdout('');
68+
exit(1);
69+
} else {
70+
logger.stdout(
71+
' └> ${logger.ansi.green}${logger.ansi.emphasized('NO UNPUBLISHED PACKAGES')}${logger.ansi.noColor}');
72+
logger.stdout('');
73+
}
74+
}
75+
}

lib/src/command_runner.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'dart:io';
33
import 'package:args/args.dart';
44
import 'package:args/command_runner.dart';
55
import 'package:cli_util/cli_logging.dart';
6+
import 'package:melos/src/command/unpublished.dart';
67

78
import 'command/bootstrap.dart';
89
import 'command/clean.dart';
@@ -27,6 +28,12 @@ class MelosCommandRunner extends CommandRunner {
2728
help:
2829
'Exclude private packages (`publish_to: none`). They are included by default.');
2930

31+
argParser.addFlag('published',
32+
negatable: true,
33+
defaultsTo: null,
34+
help:
35+
'Filter packages where the current local package version exists on pub.dev. Or "-no-published" to filter packages that have not had their current version published yet.');
36+
3037
argParser.addMultiOption('scope',
3138
help: 'Include only packages with names matching the given glob.');
3239

@@ -45,6 +52,7 @@ class MelosCommandRunner extends CommandRunner {
4552
addCommand(BootstrapCommand());
4653
addCommand(CleanCommand());
4754
addCommand(RunCommand());
55+
addCommand(UnpublishedCommand());
4856
}
4957

5058
@override
@@ -67,6 +75,7 @@ class MelosCommandRunner extends CommandRunner {
6775
await currentWorkspace.loadPackages(
6876
scope: argResults['scope'] as List<String>,
6977
skipPrivate: argResults['no-private'] as bool,
78+
published: argResults['published'] as bool,
7079
ignore: argResults['ignore'] as List<String>,
7180
dirExists: argResults['dir-exists'] as List<String>,
7281
fileExists: argResults['file-exists'] as List<String>,

lib/src/common/package.dart

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import 'dart:async';
2+
import 'dart:convert';
23
import 'dart:io';
34

45
import 'package:yaml/yaml.dart';
6+
import 'package:http/http.dart' as http;
57

68
import '../pub/pub_file.dart';
79
import '../pub/pub_file_flutter_dependencies.dart';
@@ -33,6 +35,7 @@ const String kWeb = 'web';
3335

3436
class MelosPackage {
3537
final Map _yamlContents;
38+
List<String> _registryVersions;
3639

3740
final String _name;
3841

@@ -134,7 +137,8 @@ class MelosPackage {
134137
'$exampleParentPackagePath${Platform.pathSeparator}pubspec.yaml'));
135138
if (exampleParentPackage != null) {
136139
environment['MELOS_PARENT_PACKAGE_NAME'] = exampleParentPackage.name;
137-
environment['MELOS_PARENT_PACKAGE_VERSION'] = exampleParentPackage.version;
140+
environment['MELOS_PARENT_PACKAGE_VERSION'] =
141+
exampleParentPackage.version;
138142
environment['MELOS_PARENT_PACKAGE_PATH'] = exampleParentPackage.path;
139143
}
140144
}
@@ -160,6 +164,28 @@ class MelosPackage {
160164
});
161165
}
162166

167+
Future<List<String>> getPublishedVersions() async {
168+
if (_registryVersions != null) {
169+
return _registryVersions;
170+
}
171+
var url = 'https://pub.dev/packages/$name.json';
172+
var response = await http.get(url);
173+
if (response.statusCode == 404) {
174+
return [];
175+
} else if (response.statusCode != 200) {
176+
throw Exception(
177+
'Error reading pub.dev registry for package "$name" (HTTP Status ${response.statusCode}), response: ${response.body}');
178+
}
179+
var versions = <String>[];
180+
var versionsRaw = json.decode(response.body)['versions'] as List<dynamic>;
181+
versionsRaw.forEach((element) {
182+
versions.add(element as String);
183+
});
184+
versions.sort();
185+
_registryVersions = versions.reversed.toList();
186+
return _registryVersions;
187+
}
188+
163189
void clean() {
164190
PackagesPubFile.fromDirectory(path).delete();
165191
FlutterPluginsPubFile.fromDirectory(path).delete();
@@ -227,4 +253,9 @@ class MelosPackage {
227253
if (_yamlContents['publish_to'].runtimeType != String) return false;
228254
return _yamlContents['publish_to'] == 'none';
229255
}
256+
257+
@override
258+
String toString() {
259+
return 'MelosPackage[$name@$version]';
260+
}
230261
}

lib/src/common/workspace.dart

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import 'package:args/args.dart';
55
import 'package:glob/glob.dart';
66
import 'package:meta/meta.dart';
77
import 'package:path/path.dart';
8+
import 'package:pool/pool.dart';
89
import 'package:yamlicious/yamlicious.dart';
910

1011
import '../pub/pub_deps_list.dart';
@@ -57,7 +58,8 @@ class MelosWorkspace {
5758
List<String> ignore,
5859
List<String> dirExists,
5960
List<String> fileExists,
60-
bool skipPrivate}) async {
61+
bool skipPrivate,
62+
bool published}) async {
6163
if (_packages != null) return Future.value(_packages);
6264

6365
final packageGlobs = _config.packages;
@@ -133,13 +135,33 @@ class MelosWorkspace {
133135
}
134136

135137
if (skipPrivate) {
136-
// Whether we should skip packages with 'publish_to: none' set.
138+
// Whether we should skip packages with 'publish_to: none' set.
137139
filterResult = filterResult.where((package) {
138140
return !package.isPrivate();
139141
});
140142
}
141143

142-
_packages = await filterResult.toList();
144+
if (published != null) {
145+
_packages = await filterResult.toList();
146+
// Pooling to parrellize registry requests for performance.
147+
var pool = Pool(10);
148+
var packagesFilteredWithPublishStatus = <MelosPackage>[];
149+
await pool.forEach<MelosPackage, void>(_packages, (package) {
150+
return package.getPublishedVersions().then((versions) async {
151+
var isOnPubRegistry = versions.contains(package.version);
152+
if (published == false && !isOnPubRegistry) {
153+
return packagesFilteredWithPublishStatus.add(package);
154+
}
155+
if (published == true && isOnPubRegistry) {
156+
return packagesFilteredWithPublishStatus.add(package);
157+
}
158+
});
159+
}).drain();
160+
_packages = packagesFilteredWithPublishStatus;
161+
} else {
162+
_packages = await filterResult.toList();
163+
}
164+
143165
_packages.sort((a, b) {
144166
return a.name.compareTo(b.name);
145167
});

lib/src/pub/pub_file_flutter_plugins.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import 'dart:io';
22

33
import '../common/package.dart';
4-
import '../common/utils.dart' as utils;
54
import '../common/workspace.dart';
65
import '../pub/pub_file.dart';
76

pubspec.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
name: "melos"
22
description: "A tool for managing Dart projects with multiple packages. Inspired by JavaScripts Lerna package."
3-
version: "0.1.0-13.0.pre"
3+
version: "0.2.0"
44
homepage: "https://github.com/invertase/melos"
55
executables:
66
melos:
77
dependencies:
88
args: ^1.6.0
9+
http: ^0.12.2
910
pool: ^1.4.0
1011
collection: ^1.14.12
1112
string_scanner: ^1.0.5

0 commit comments

Comments
 (0)