Skip to content

Commit

Permalink
Add support for excluding sup-projects when checking a mono repo.
Browse files Browse the repository at this point in the history
  • Loading branch information
dam5s committed Sep 24, 2024
1 parent 4e1dcba commit 1a2afa7
Show file tree
Hide file tree
Showing 17 changed files with 117 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ jobs:
- uses: dart-lang/setup-dart@v1
with:
sdk: 3.1.0
- run: make install
- run: make setup
- run: make check
5 changes: 3 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
.PHONY: install check
.PHONY: setup check

install:
setup:
dart pub get
dart pub global activate melos

check:
dart format lib --line-length 100 --set-exit-if-changed
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,5 @@ dart run cyclic_dependency_checks
defaults to current working directory.
* `--mono-repo` or `-m` as an alternative to path, use the path to your Melos based mono-repo
to run against all its components.
* `--exclude` or `-x` can be used multiple times or as coma separated values to exclude specified projects from the mono-repo.
* `--max-depth` or `-d` to limit the depth of the check, defaults to no max depth.
14 changes: 10 additions & 4 deletions lib/cycle_detection/cycle_detector_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ class CycleDetectorRunner {
final parser = ArgParser()
..addOption('path', abbr: 'p')
..addOption('mono-repo', abbr: 'm')
..addOption('max-depth', abbr: 'd');
..addOption('max-depth', abbr: 'd')
..addMultiOption('exclude', abbr: 'x');

final parsedArgs = parser.tryParse(args);
if (parsedArgs == null) {
Expand All @@ -36,8 +37,9 @@ class CycleDetectorRunner {
final pathArg = parsedArgs['path'];
final monorepoArg = parsedArgs['mono-repo'];
final maxDepthArg = parsedArgs.tryGetInt('max-depth');
final exclusions = parsedArgs['exclude'];

final inferredPaths = await _tryGetPaths(pathArg, monorepoArg);
final inferredPaths = await _tryGetPaths(pathArg, monorepoArg, exclusions);
if (inferredPaths == null) {
printer.err(
'Failed to infer path from arguments, only one of path or monorepo can be specified',
Expand All @@ -55,11 +57,15 @@ class CycleDetectorRunner {
return success;
}

Future<List<String>?> _tryGetPaths(String? packagePath, String? monorepoPath) async =>
Future<List<String>?> _tryGetPaths(
String? packagePath,
String? monorepoPath,
List<String> exclusions,
) async =>
switch ((packagePath, monorepoPath)) {
(null, null) => ['.'],
(String p, null) => [p],
(null, String p) => await MelosPaths.tryGet(p),
(null, String p) => await MelosPaths.tryGet(p, exclusions),
(_, _) => null,
};

Expand Down
3 changes: 2 additions & 1 deletion lib/cycle_detection/melos_paths.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import 'dart:io';
import 'package:path/path.dart' as path;

final class MelosPaths {
static Future<List<String>?> tryGet(String monorepoPath) async {
static Future<List<String>?> tryGet(String monorepoPath, List<String> exclusions) async {
final melos = Platform.isWindows ? 'melos.bat' : 'melos';

final listResult = await Process.run(
Expand All @@ -21,6 +21,7 @@ final class MelosPaths {
return output
.split('\n')
.where((p) => p.trim().length > 0)
.where((p) => !exclusions.contains(path.basename(p)))
.map((p) => path.relative(p, from: currentDir.absolute.path))
.toList();
}
Expand Down
2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ environment:
sdk: '>=3.0.5 <4.0.0'

dev_dependencies:
melos: ^6.1.0
path: ^1.9.0
test: ^1.24.5
dependencies:
args: ^2.4.2
105 changes: 74 additions & 31 deletions test/cycle_detector_runner_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@ import 'package:cyclic_dependency_checks/cycle_detection/cycle_detector.dart';
import 'package:cyclic_dependency_checks/cycle_detection/cycle_detector_runner.dart';
import 'package:cyclic_dependency_checks/cycle_detection/module_dependency.dart';
import 'package:test/test.dart';
import 'package:path/path.dart' as path;

class TestCycleDetector extends CycleDetector {
List<List<ModuleDependency>> stubbedCycles = [];
String? recordedPackagePath;
int? recordedMaxDepth;
List<String> recordedPackagePaths = [];
List<int?> recordedMaxDepths = [];

@override
Future<List<List<ModuleDependency>>> detect(String packagePath, {int? maxDepth}) async {
recordedPackagePath = packagePath;
recordedMaxDepth = maxDepth;
recordedPackagePaths.add(packagePath);
recordedMaxDepths.add(maxDepth);
return stubbedCycles;
}
}
Expand All @@ -35,8 +36,8 @@ void main() {

expect(success, isTrue);
expect(printer.errLog, isEmpty);
expect(detector.recordedPackagePath, equals('.'));
expect(detector.recordedMaxDepth, isNull);
expect(detector.recordedPackagePaths, equals(['.']));
expect(detector.recordedMaxDepths, equals([null]));
});

test('with no arguments, on error', () async {
Expand All @@ -54,45 +55,87 @@ void main() {
expect(printer.errLog, contains('module:a -> module:b -> module:a'));
});

test('with full length args', () async {
final printer = RecordingPrinter();
final detector = TestCycleDetector();
final runner = CycleDetectorRunner(detector: detector, printer: printer);
group('specifying the path', () {
test('with full length args', () async {
final printer = RecordingPrinter();
final detector = TestCycleDetector();
final runner = CycleDetectorRunner(detector: detector, printer: printer);

await runner.run(['--path', '/some/path', '--max-depth', '10']);

expect(detector.recordedPackagePaths, equals(['/some/path']));
expect(detector.recordedMaxDepths, equals([10]));
});

await runner.run([
'--path', '/some/path',
'--max-depth', '10',
]);
test('with short name args', () async {
final printer = RecordingPrinter();
final detector = TestCycleDetector();
final runner = CycleDetectorRunner(detector: detector, printer: printer);

expect(detector.recordedPackagePath, equals('/some/path'));
expect(detector.recordedMaxDepth, equals(10));
await runner.run(['-p', '/some/path', '-d', '10']);

expect(detector.recordedPackagePaths, equals(['/some/path']));
expect(detector.recordedMaxDepths, equals([10]));
});
});

test('with short name args', () async {
final printer = RecordingPrinter();
final detector = TestCycleDetector();
final runner = CycleDetectorRunner(detector: detector, printer: printer);
group('specifying the path to a melos mono repo', () {
test('with long argument', () async {
final printer = RecordingPrinter();
final detector = TestCycleDetector();
final runner = CycleDetectorRunner(detector: detector, printer: printer);

await runner.run([
'--mono-repo',
'test_resources/example_melos_codebase',
'--max-depth',
'5',
]);

expect(detector.recordedPackagePaths, [
path.join('test_resources', 'example_melos_codebase', 'project_a'),
path.join('test_resources', 'example_melos_codebase', 'project_b'),
]);
expect(detector.recordedMaxDepths, [5, 5]);
});

await runner.run([
'-p', '/some/path',
'-d', '10',
]);
test('with short argument', () async {
final printer = RecordingPrinter();
final detector = TestCycleDetector();
final runner = CycleDetectorRunner(detector: detector, printer: printer);

expect(detector.recordedPackagePath, equals('/some/path'));
expect(detector.recordedMaxDepth, equals(10));
await runner.run(['-m', 'test_resources/example_melos_codebase', '-d', '5']);

expect(detector.recordedPackagePaths, [
path.join('test_resources', 'example_melos_codebase', 'project_a'),
path.join('test_resources', 'example_melos_codebase', 'project_b'),
]);
expect(detector.recordedMaxDepths, [5, 5]);
});

test('excluding a specific subproject', () async {
final printer = RecordingPrinter();
final detector = TestCycleDetector();
final runner = CycleDetectorRunner(detector: detector, printer: printer);

final projectPath = 'test_resources/example_melos_codebase';
await runner.run(['-m', projectPath, '-x', 'project_a', '-x', 'project_c']);

expect(detector.recordedPackagePaths, [
path.join('test_resources', 'example_melos_codebase', 'project_b'),
]);
});
});

test('with invalid arguments', () async {
final printer = RecordingPrinter();
final detector = TestCycleDetector();
final runner = CycleDetectorRunner(detector: detector, printer: printer);

final success = await runner.run([
'--oops'
]);
final success = await runner.run(['--oops']);

expect(success, equals(false));
expect(detector.recordedPackagePath, isNull);
expect(detector.recordedMaxDepth, isNull);
expect(detector.recordedPackagePaths, []);
expect(detector.recordedMaxDepths, []);
});
}
5 changes: 5 additions & 0 deletions test_resources/example_melos_codebase/melos.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: example_melos_codebase

packages:
- project_a
- project_b
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import 'package:example_codebase_no_cycles/feature_b/b.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import 'package:example_codebase_no_cycles/feature_c/c.dart';
Empty file.
4 changes: 4 additions & 0 deletions test_resources/example_melos_codebase/project_a/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name: project_a

environment:
sdk: '>=3.0.5 <4.0.0'
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import 'package:example_codebase_with_cycles/feature_b/b.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import 'package:example_codebase_with_cycles/feature_c/c.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import 'package:example_codebase_with_cycles/feature_a/a.dart';
4 changes: 4 additions & 0 deletions test_resources/example_melos_codebase/project_b/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
name: project_b

environment:
sdk: '>=3.0.5 <4.0.0'
6 changes: 6 additions & 0 deletions test_resources/example_melos_codebase/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
name: example_melos_codebase

environment:
sdk: '>=3.0.5 <4.0.0'
dev_dependencies:
melos: ^6.1.0

0 comments on commit 1a2afa7

Please sign in to comment.