From 42a368bcd706b75c930e2ab337ed344f03d5dae5 Mon Sep 17 00:00:00 2001 From: Damien Le Berrigaud Date: Wed, 22 Nov 2023 13:37:40 -0700 Subject: [PATCH] Add support for melos mono-repo. --- .../cycle_detector_runner.dart | 40 ++++++++++++++----- lib/cycle_detection/melos_paths.dart | 26 ++++++++++++ 2 files changed, 55 insertions(+), 11 deletions(-) create mode 100644 lib/cycle_detection/melos_paths.dart diff --git a/lib/cycle_detection/cycle_detector_runner.dart b/lib/cycle_detection/cycle_detector_runner.dart index ef48d75..81c7b3f 100644 --- a/lib/cycle_detection/cycle_detector_runner.dart +++ b/lib/cycle_detection/cycle_detector_runner.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'package:args/args.dart'; +import 'package:cyclic_dependency_checks/cycle_detection/melos_paths.dart'; import 'cycle_detector.dart'; import 'module_dependency_graph.dart'; @@ -24,6 +25,7 @@ class CycleDetectorRunner { Future run(List args) async { final parser = ArgParser() ..addOption('path', abbr: 'p') + ..addOption('mono-repo', abbr: 'm') ..addOption('max-depth', abbr: 'd'); final parsedArgs = parser.tryParse(args); @@ -31,18 +33,40 @@ class CycleDetectorRunner { return false; } - final path = parsedArgs.getString('path', fallback: '.'); - final maxDepth = parsedArgs.tryGetInt('max-depth'); + final pathArg = parsedArgs['path']; + final monorepoArg = parsedArgs['mono-repo']; + final maxDepthArg = parsedArgs.tryGetInt('max-depth'); + final inferredPaths = await _tryGetPaths(pathArg, monorepoArg); + if (inferredPaths == null) { + printer.err('Failed to infer path from arguments, only one of path or monorepo can be specified'); + return false; + } + + for (var path in inferredPaths) { + _runForPath(path, maxDepthArg); + } + + return true; + } + + Future?> _tryGetPaths(String? packagePath, String? monorepoPath) async => + switch ((packagePath, monorepoPath)) { + (null, null) => ['.'], + (String p, null) => [p], + (null, String p) => await MelosPaths.tryGet(p), + (_, _) => null, + }; + + Future _runForPath(String path, int? maxDepth) async { final stopwatch = Stopwatch()..start(); final cycles = await detector.detect(path, maxDepth: maxDepth); - stopwatch.stop(); final formattedTime = stopwatch.elapsed.toString().substring(0, 11); if (cycles.isNotEmpty) { - printer.err('Detected cycles after ${formattedTime}'); + printer.err('[$path] Detected cycles after ${formattedTime}'); for (final cycle in cycles) { printer.err(cycle.path().join(' -> ')); @@ -51,9 +75,7 @@ class CycleDetectorRunner { return false; } - printer.out('No import cycles were detected after ${formattedTime}'); - - return true; + printer.out('[$path] No cycles detected after ${formattedTime}'); } } @@ -70,10 +92,6 @@ extension _SafeParse on ArgParser { } extension _TypeSafeArgs on ArgResults { - String getString(String name, {required String fallback}) { - return this[name] ?? fallback; - } - int? tryGetInt(String name) { final value = this[name]; diff --git a/lib/cycle_detection/melos_paths.dart b/lib/cycle_detection/melos_paths.dart new file mode 100644 index 0000000..38524fb --- /dev/null +++ b/lib/cycle_detection/melos_paths.dart @@ -0,0 +1,26 @@ +import 'dart:io'; +import 'package:path/path.dart' as path; + + +final class MelosPaths { + static Future?> tryGet(String monorepoPath) async { + final listResult = await Process.run( + 'melos', + ['list', '-p'], + workingDirectory: monorepoPath, + ); + + if (listResult.exitCode != 0) { + return null; + } + + final output = listResult.stdout as String; + final currentDir = Directory.current; + + return output + .split('\n') + .where((p) => p.trim().length > 0) + .map((p) => path.relative(p, from: currentDir.absolute.path)) + .toList(); + } +}