Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Filter by category #727

Merged
merged 13 commits into from
Jun 17, 2024
3 changes: 3 additions & 0 deletions melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ packages:
- packages/*
ignore:
- packages/melos_flutter_deps_check
categories:
testando:
- packages/*

command:
bootstrap:
Expand Down
12 changes: 12 additions & 0 deletions packages/melos/lib/src/command_runner/base.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@ abstract class MelosCommand extends Command<void> {
'option can be repeated.',
);

argParser.addMultiOption(
filterOptionCategory,
valueHelp: 'glob',
help:
'Include only packages with categories matching the given glob. This '
'option can be repeated.',
);

argParser.addMultiOption(
filterOptionIgnore,
valueHelp: 'glob',
Expand Down Expand Up @@ -151,6 +159,7 @@ abstract class MelosCommand extends Command<void> {

final diff = diffEnabled ? argResults![filterOptionDiff] as String? : null;
final scope = argResults![filterOptionScope] as List<String>? ?? [];
final categories = argResults![filterOptionCategory] as List<String>? ?? [];
final ignore = argResults![filterOptionIgnore] as List<String>? ?? [];

return PackageFilters(
Expand All @@ -161,6 +170,9 @@ abstract class MelosCommand extends Command<void> {
.map((e) => createGlob(e, currentDirectoryPath: workingDirPath))
.toList()
..addAll(config.ignore),
categories: categories
.map((e) => createGlob(e, currentDirectoryPath: workingDirPath))
.toList(),
diff: diff,
includePrivatePackages: argResults![filterOptionPrivate] as bool?,
published: argResults![filterOptionPublished] as bool?,
Expand Down
2 changes: 1 addition & 1 deletion packages/melos/lib/src/commands/exec.dart
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ mixin _ExecMixin on _Melos {
final dependenciesResults = await Future.wait(
package.allDependenciesInWorkspace.values
.map((package) => packageResults[package.name]?.future)
.whereNotNull(),
.nonNulls,
);

final dependencyFailed = dependenciesResults.any(
Expand Down
1 change: 1 addition & 0 deletions packages/melos/lib/src/common/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const globalOptionSdkPath = 'sdk-path';
const autoSdkPathOptionValue = 'auto';

const filterOptionScope = 'scope';
const filterOptionCategory = 'category';
const filterOptionIgnore = 'ignore';
const filterOptionDirExists = 'dir-exists';
const filterOptionFileExists = 'file-exists';
Expand Down
20 changes: 20 additions & 0 deletions packages/melos/lib/src/common/validation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,26 @@ List<T> assertListIsA<T>({
];
}

Map<T, V> assertMapIsA<T, V>({
String? path,
required Object key,
required Map<Object?, Object?> map,
required bool isRequired,
required T Function(Object? value) assertKey,
required V Function(Object? key, Object? value) assertValue,
}) {
final collection = assertKeyIsA<Map<Object?, Object?>?>(key: key, map: map);

if (isRequired && collection == null) {
throw MelosConfigException.missingKey(key: key, path: path);
}

return <T, V>{
for (final entry in collection?.entries ?? <MapEntry<Object?, Object?>>[])
assertKey(entry.key): assertValue(entry.key, entry.value),
};
}

/// Thrown when `melos.yaml` configuration is malformed.
class MelosConfigException implements MelosException {
MelosConfigException(this.message);
Expand Down
55 changes: 54 additions & 1 deletion packages/melos/lib/src/package.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ class PackageFilters {
PackageFilters({
this.scope = const [],
this.ignore = const [],
this.categories = const [],
this.dirExists = const [],
this.fileExists = const [],
List<String> dependsOn = const [],
Expand Down Expand Up @@ -133,6 +134,12 @@ class PackageFilters {
path: path,
);

final category = assertListOrString(
key: filterOptionCategory.camelCased,
map: yaml,
path: path,
);

final ignore = assertListOrString(
key: filterOptionIgnore.camelCased,
map: yaml,
Expand Down Expand Up @@ -247,6 +254,7 @@ class PackageFilters {
published: published,
nullSafe: nullSafe,
flutter: flutter,
categories: category.map(createPackageGlob).toList(),
);
}

Expand All @@ -255,6 +263,7 @@ class PackageFilters {
const PackageFilters._({
required this.scope,
required this.ignore,
required this.categories,
required this.dirExists,
required this.fileExists,
required this.dependsOn,
Expand All @@ -273,6 +282,9 @@ class PackageFilters {
/// Patterns for excluding packages by name.
final List<Glob> ignore;

/// Patterns for filtering packages by category.
final List<Glob> categories;

/// Include a package only if a given directory exists.
final List<String> dirExists;

Expand Down Expand Up @@ -315,6 +327,9 @@ class PackageFilters {
return {
if (scope.isNotEmpty)
filterOptionScope.camelCased: scope.map((e) => e.toString()).toList(),
if (categories.isNotEmpty)
filterOptionCategory.camelCased:
scope.map((e) => e.toString()).toList(),
if (ignore.isNotEmpty)
filterOptionIgnore.camelCased: ignore.map((e) => e.toString()).toList(),
if (dirExists.isNotEmpty) filterOptionDirExists.camelCased: dirExists,
Expand Down Expand Up @@ -346,6 +361,7 @@ class PackageFilters {
diff: diff,
includeDependencies: includeDependencies,
includeDependents: includeDependents,
categories: categories,
);
}

Expand All @@ -363,6 +379,7 @@ class PackageFilters {
diff: diff,
includeDependencies: includeDependencies,
includeDependents: includeDependents,
categories: categories,
);
}

Expand All @@ -379,12 +396,14 @@ class PackageFilters {
String? diff,
bool? includeDependencies,
bool? includeDependents,
List<Glob>? categories,
}) {
return PackageFilters._(
dependsOn: dependsOn ?? this.dependsOn,
dirExists: dirExists ?? this.dirExists,
fileExists: fileExists ?? this.fileExists,
ignore: ignore ?? this.ignore,
categories: categories ?? this.categories,
includePrivatePackages:
includePrivatePackages ?? this.includePrivatePackages,
noDependsOn: noDependsOn ?? this.noDependsOn,
Expand Down Expand Up @@ -412,6 +431,7 @@ class PackageFilters {
const DeepCollectionEquality().equals(other.fileExists, fileExists) &&
const DeepCollectionEquality().equals(other.dependsOn, dependsOn) &&
const DeepCollectionEquality().equals(other.noDependsOn, noDependsOn) &&
const DeepCollectionEquality().equals(other.categories, categories) &&
other.diff == diff;

@override
Expand All @@ -428,6 +448,7 @@ class PackageFilters {
const DeepCollectionEquality().hash(fileExists) ^
const DeepCollectionEquality().hash(dependsOn) ^
const DeepCollectionEquality().hash(noDependsOn) ^
const DeepCollectionEquality().hash(categories) ^
diff.hashCode;

@override
Expand All @@ -440,6 +461,7 @@ PackageFilters(
includeDependents: $includeDependents,
includePrivatePackages: $includePrivatePackages,
scope: $scope,
categories: $categories,
ignore: $ignore,
dirExists: $dirExists,
fileExists: $fileExists,
Expand Down Expand Up @@ -494,6 +516,7 @@ class PackageMap {
required String workspacePath,
required List<Glob> packages,
required List<Glob> ignore,
required Map<String, List<Glob>> categories,
required MelosLogger logger,
}) async {
final pubspecFiles = await _resolvePubspecFiles(
Expand Down Expand Up @@ -528,6 +551,20 @@ The packages that caused the problem are:
);
}

final filteredCategories = <String>[];

categories.forEach((key, value) {
final isCategoryMatching = value.any(
(category) => category.matches(
relativePath(pubspecDirPath, workspacePath),
),
);

if (isCategoryMatching) {
filteredCategories.add(key);
}
});

packageMap[name] = Package(
name: name,
path: pubspecDirPath,
Expand All @@ -539,6 +576,7 @@ The packages that caused the problem are:
devDependencies: pubSpec.devDependencies.keys.toList(),
dependencyOverrides: pubSpec.dependencyOverrides.keys.toList(),
pubSpec: pubSpec,
categories: filteredCategories,
);
}),
);
Expand Down Expand Up @@ -602,6 +640,7 @@ The packages that caused the problem are:
.applyFileExists(filters.fileExists)
.filterPrivatePackages(include: filters.includePrivatePackages)
.applyScope(filters.scope)
.applyCategories(filters.categories)
.applyDependsOn(filters.dependsOn)
.applyNoDependsOn(filters.noDependsOn)
.filterNullSafe(nullSafe: filters.nullSafe)
Expand All @@ -626,7 +665,7 @@ The packages that caused the problem are:
}
}

extension on Iterable<Package> {
extension IterablePackageExt on Iterable<Package> {
Iterable<Package> applyIgnore(List<Glob> ignore) {
if (ignore.isEmpty) return this;

Expand Down Expand Up @@ -747,6 +786,18 @@ extension on Iterable<Package> {
}).toList();
}

Iterable<Package> applyCategories(List<Glob> appliedCategories) {
if (appliedCategories.isEmpty) return this;

return where((package) {
return package.categories.any(
(category) => appliedCategories.any(
(appliedCategory) => appliedCategory.matches(category),
),
);
}).toList();
}

Iterable<Package> applyDependsOn(List<String> dependsOn) {
if (dependsOn.isEmpty) return this;

Expand Down Expand Up @@ -802,6 +853,7 @@ class Package {
required this.version,
required this.publishTo,
required this.pubSpec,
required this.categories,
}) : _packageMap = packageMap,
assert(p.isAbsolute(path));

Expand All @@ -816,6 +868,7 @@ class Package {
final Version version;
final String path;
final PubSpec pubSpec;
final List<String> categories;

/// Package path as a normalized sting relative to the root of the workspace.
/// e.g. "packages/firebase_database".
Expand Down
2 changes: 2 additions & 0 deletions packages/melos/lib/src/workspace.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,14 @@ class MelosWorkspace {
workspacePath: workspaceConfig.path,
packages: workspaceConfig.packages,
ignore: workspaceConfig.ignore,
categories: workspaceConfig.categories,
logger: logger,
);
final dependencyOverridePackages = await PackageMap.resolvePackages(
workspacePath: workspaceConfig.path,
packages: workspaceConfig.commands.bootstrap.dependencyOverridePaths,
ignore: const [],
categories: const {},
logger: logger,
);

Expand Down
Loading
Loading