diff --git a/packages/melos/lib/melos.dart b/packages/melos/lib/melos.dart index 5479f42e6..76edc2b1a 100644 --- a/packages/melos/lib/melos.dart +++ b/packages/melos/lib/melos.dart @@ -11,6 +11,7 @@ export 'src/commands/runner.dart' export 'src/common/exception.dart' show CancelledException, MelosException; export 'src/common/validation.dart' show MelosConfigException; export 'src/global_options.dart' show GlobalOptions; +export 'src/logging.dart' show MelosLogger, ToMelosLoggerExtension; export 'src/package.dart' show Package, PackageFilter, PackageMap, PackageType; export 'src/workspace.dart' show IdeWorkspace, MelosWorkspace; export 'src/workspace_configs.dart' diff --git a/packages/melos/lib/src/command_runner/base.dart b/packages/melos/lib/src/command_runner/base.dart index f6f83e3a2..f8c25e432 100644 --- a/packages/melos/lib/src/command_runner/base.dart +++ b/packages/melos/lib/src/command_runner/base.dart @@ -5,6 +5,7 @@ import 'package:cli_util/cli_logging.dart'; import '../common/glob.dart'; import '../common/utils.dart'; import '../global_options.dart'; +import '../logging.dart'; import '../package.dart'; import '../workspace_configs.dart'; @@ -16,7 +17,8 @@ abstract class MelosCommand extends Command { /// The global Melos options parsed from the command line. late final global = _parseGlobalOptions(); - Logger? get logger => global.verbose ? Logger.verbose() : Logger.standard(); + late final logger = + MelosLogger(global.verbose ? Logger.verbose() : Logger.standard()); /// The `melos.yaml` configuration for this command. /// see [ArgParser.allowTrailingOptions] diff --git a/packages/melos/lib/src/command_runner/exec.dart b/packages/melos/lib/src/command_runner/exec.dart index dd1a10341..882b5654c 100644 --- a/packages/melos/lib/src/command_runner/exec.dart +++ b/packages/melos/lib/src/command_runner/exec.dart @@ -48,8 +48,8 @@ class ExecCommand extends MelosCommand { final execArgs = argResults!.rest; if (execArgs.isEmpty) { - logger?.stdout(description); - logger?.stdout(argParser.usage); + logger.log(description); + logger.log(argParser.usage); exit(1); } diff --git a/packages/melos/lib/src/command_runner/run.dart b/packages/melos/lib/src/command_runner/run.dart index 43d63024d..1c686e3c7 100644 --- a/packages/melos/lib/src/command_runner/run.dart +++ b/packages/melos/lib/src/command_runner/run.dart @@ -15,8 +15,6 @@ * */ -import 'package:ansi_styles/ansi_styles.dart'; - import '../commands/runner.dart'; import '../workspace_configs.dart'; import 'base.dart'; @@ -58,8 +56,8 @@ class RunCommand extends MelosCommand { extraArgs: extraArgs, ); } on NoPackageFoundScriptException catch (err) { - logger?.stderr(AnsiStyles.yellow(err.toString())); - logger?.stdout(usage); + logger.warning(err.toString(), label: false); + logger.log(usage); } } } diff --git a/packages/melos/lib/src/command_runner/script.dart b/packages/melos/lib/src/command_runner/script.dart index 5c28ca7cc..e451bc97b 100644 --- a/packages/melos/lib/src/command_runner/script.dart +++ b/packages/melos/lib/src/command_runner/script.dart @@ -15,8 +15,6 @@ * */ -import 'package:ansi_styles/ansi_styles.dart'; - import '../commands/runner.dart'; import '../workspace_configs.dart'; import 'base.dart'; @@ -80,8 +78,8 @@ class ScriptCommand extends MelosCommand { extraArgs: argResults!.rest, ); } on NoPackageFoundScriptException catch (err) { - logger?.stderr(AnsiStyles.yellow(err.toString())); - logger?.stdout(usage); + logger.warning(err.toString(), label: false); + logger.log(usage); } } } diff --git a/packages/melos/lib/src/command_runner/version.dart b/packages/melos/lib/src/command_runner/version.dart index a548b3736..826d98eaf 100644 --- a/packages/melos/lib/src/command_runner/version.dart +++ b/packages/melos/lib/src/command_runner/version.dart @@ -159,10 +159,10 @@ class VersionCommand extends MelosCommand { if (argResults!.rest.isNotEmpty) { if (argResults!.rest.length != 2) { - logger?.stdout( - '${AnsiStyles.redBright('ERROR:')} when manually setting a version to ' - 'apply to a package you must specify both and ' - 'arguments when calling "melos version".', + logger.error( + 'When manually setting a version to apply to a package you must ' + 'specify both and arguments when calling ' + '"melos version".', ); exitCode = 1; return; @@ -200,18 +200,18 @@ class VersionCommand extends MelosCommand { } if (asPrerelease && asStableRelease) { - logger?.stdout( - '${AnsiStyles.yellow('WARNING:')} graduate & prerelease flags cannot ' - 'be combined. Versioning will continue with graduate off.', + logger.warning( + 'graduate & prerelease flags cannot be combined. Versioning will ' + 'continue with graduate off.', ); asStableRelease = false; } if (updateDependentsVersions && !updateDependentsConstraints) { - logger?.stdout( - '${AnsiStyles.yellow('WARNING:')} the setting --dependent-versions is ' - 'turned on but --dependent-constraints is turned off. Versioning ' - 'will continue with this setting turned off.', + logger.warning( + 'The setting --dependent-versions is turned on but ' + '--dependent-constraints is turned off. Versioning will continue ' + 'with this setting turned off.', ); updateDependentsVersions = false; } @@ -255,10 +255,7 @@ class VersionCommand extends MelosCommand { return ManualVersionChange(Version.parse(argument)); } catch (_) { exitCode = 1; - logger?.stdout( - '${AnsiStyles.redBright('ERROR:')} version "$argument" is not a ' - 'valid package version.', - ); + logger.error('version "$argument" is not a valid package version.'); return null; } } @@ -272,9 +269,8 @@ class VersionCommand extends MelosCommand { final parts = argument.split(':'); if (parts.length != 2) { exitCode = 1; - logger?.stdout( - '${AnsiStyles.redBright('ERROR:')} --manual-version arguments must ' - 'be in the format ' + logger.error( + '--manual-version arguments must be in the format ' '":".', ); return null; diff --git a/packages/melos/lib/src/commands/bootstrap.dart b/packages/melos/lib/src/commands/bootstrap.dart index 40a055407..3e2c78171 100644 --- a/packages/melos/lib/src/commands/bootstrap.dart +++ b/packages/melos/lib/src/commands/bootstrap.dart @@ -1,9 +1,5 @@ part of 'runner.dart'; -final _successLabel = AnsiStyles.green('SUCCESS'); -final _warningLabel = AnsiStyles.yellow('WARNING'); -final _checkLabel = AnsiStyles.greenBright('✓'); - mixin _BootstrapMixin on _CleanMixin { Future bootstrap({GlobalOptions? global, PackageFilter? filter}) async { final workspace = await createWorkspace(global: global, filter: filter); @@ -20,17 +16,16 @@ mixin _BootstrapMixin on _CleanMixin { 'get' ].join(' '); - logger?.stdout(AnsiStyles.yellow.bold('melos bootstrap')); - logger?.stdout(' └> ${AnsiStyles.cyan.bold(workspace.path)}\n'); + logger + ..command('melos bootstrap') + ..child(targetStyle(workspace.path)) + ..newLine(); - logger?.stdout( - 'Running "$pubCommandForLogging" in workspace packages...', - ); + logger.log('Running "$pubCommandForLogging" in workspace packages...'); if (!utils.isCI && workspace.filteredPackages.keys.length > 20) { - logger?.stdout( - AnsiStyles.yellow( - 'Note: this may take a while in large workspaces such as this one.', - ), + logger.warning( + 'Note: this may take a while in large workspaces such as this one.', + label: false, ); } @@ -45,20 +40,22 @@ mixin _BootstrapMixin on _CleanMixin { rethrow; } - logger?.stdout(' > $_successLabel'); + logger.child(successLabel, prefix: '> '); if (workspace.config.ide.intelliJ.enabled) { - logger?.stdout(''); - logger?.stdout('Generating IntelliJ IDE files...'); + logger + ..newLine() + ..log('Generating IntelliJ IDE files...'); await cleanIntelliJ(workspace); await workspace.ide.intelliJ.generate(); - logger?.stdout(' > $_successLabel'); + logger.child(successLabel, prefix: '> '); } - - logger?.stdout( - '\n -> ${workspace.filteredPackages.length} packages bootstrapped', - ); + logger + ..newLine() + ..log( + ' -> ${workspace.filteredPackages.length} packages bootstrapped', + ); }, ); } @@ -67,8 +64,8 @@ mixin _BootstrapMixin on _CleanMixin { MelosWorkspace workspace, ) async { if (!workspace.isPubspecOverridesSupported) { - logger?.stderr( - '$_warningLabel: Dart 2.17.0 or greater is required to use Melos with ' + logger.warning( + 'Dart 2.17.0 or greater is required to use Melos with ' 'pubspec overrides.', ); } @@ -78,11 +75,7 @@ mixin _BootstrapMixin on _CleanMixin { await _generatePubspecOverrides(workspace, package); await _runPubGetForPackage(workspace, package); - logger?.stdout( - ''' - $_checkLabel ${AnsiStyles.bold(package.name)} - └> ${AnsiStyles.blue(printablePath(package.pathRelativeToWorkspace))}''', - ); + _logBootstrapSuccess(package); }, parallelism: workspace.config.commands.bootstrap.runPubGetInParallel && workspace.canRunPubGetConcurrently @@ -136,20 +129,18 @@ mixin _BootstrapMixin on _CleanMixin { await _generateTemporaryProjects(workspace); try { + // ignore: prefer_foreach await for (final package in _runPubGet(workspace)) { - logger?.stdout( - ''' - $_checkLabel ${AnsiStyles.bold(package.name)} - └> ${AnsiStyles.blue(printablePath(package.pathRelativeToWorkspace))}''', - ); + _logBootstrapSuccess(package); } } catch (err) { cleanWorkspace(workspace); rethrow; } - logger?.stdout(''); - logger?.stdout('Linking workspace packages...'); + logger + ..newLine() + ..log('Linking workspace packages...'); for (final package in workspace.filteredPackages.values) { await package.linkPackages(workspace); @@ -212,18 +203,18 @@ mixin _BootstrapMixin on _CleanMixin { const logTimeout = Duration(seconds: 10); final packagePrefix = '[${AnsiStyles.blue.bold(package.name)}]: '; - void Function(String) logLineTo(void Function(String)? log) => - (line) => log?.call('$packagePrefix$line'); + void Function(String) logLineTo(void Function(String) log) => + (line) => log.call('$packagePrefix$line'); // We always fully consume stdout and stderr. This is required to prevent // leaking resources and to ensure that the process exits. final stdout = process.stdout.toStringAndLogAfterTimeout( timeout: logTimeout, - log: logLineTo(logger?.stdout), + log: logLineTo(logger.stdout), ); final stderr = process.stderr.toStringAndLogAfterTimeout( timeout: logTimeout, - log: logLineTo(logger?.stderr), + log: logLineTo(logger.stderr), ); final exitCode = await process.exitCode; @@ -238,6 +229,12 @@ mixin _BootstrapMixin on _CleanMixin { } } + void _logBootstrapSuccess(Package package) { + logger.child(packageNameStyle(package.name), prefix: '$checkLabel ').child( + packagePathStyle(printablePath(package.pathRelativeToWorkspace)), + ); + } + void _logBootstrapException( BootstrapException exception, MelosWorkspace workspace, @@ -284,20 +281,17 @@ mixin _BootstrapMixin on _CleanMixin { .toList() .join('\n'); - logger?.stdout( - ''' - - ${AnsiStyles.bold.cyan(package.name)} - └> ${AnsiStyles.blue(printablePath(package.pathRelativeToWorkspace))}''', - ); - - logger?.stderr(' └> ${AnsiStyles.red(exception.message)}'); + logger + .child(targetStyle(package.name), prefix: '- ') + .child(packagePathStyle(printablePath(package.pathRelativeToWorkspace))) + .child(errorMessageColor(exception.message), stderr: true) + .newLine(); - logger?.stdout(''); if (processStdOutString != null) { - logger?.stdout(processStdOutString); + logger.stdout(processStdOutString); } if (processStdErrString != null) { - logger?.stderr(processStdErrString); + logger.stderr(processStdErrString); } } } diff --git a/packages/melos/lib/src/commands/clean.dart b/packages/melos/lib/src/commands/clean.dart index b7007116d..e2423e6e8 100644 --- a/packages/melos/lib/src/commands/clean.dart +++ b/packages/melos/lib/src/commands/clean.dart @@ -8,7 +8,7 @@ mixin _CleanMixin on _Melos { workspace, ScriptLifecycle.clean, () async { - logger?.stdout('Cleaning workspace...'); + logger.log('Cleaning workspace...'); /// Cleans the workspace of all files generated by Melos. cleanWorkspace(workspace); @@ -17,10 +17,12 @@ mixin _CleanMixin on _Melos { await cleanIntelliJ(workspace); - logger?.stdout( - '\nWorkspace cleaned. ' - 'You will need to run the bootstrap command again to use this workspace.', - ); + logger + ..newLine() + ..log( + 'Workspace cleaned. You will need to run the bootstrap command ' + 'again to use this workspace.', + ); }, ); } diff --git a/packages/melos/lib/src/commands/exec.dart b/packages/melos/lib/src/commands/exec.dart index 4fd81ab02..61cd91d25 100644 --- a/packages/melos/lib/src/commands/exec.dart +++ b/packages/melos/lib/src/commands/exec.dart @@ -98,16 +98,13 @@ mixin _ExecMixin on _Melos { final execArgsString = execArgs.join(' '); final prefixLogs = concurrency != 1 && packages.length != 1; + logger.command('melos exec', withDollarSign: true); logger - ?.stdout('${AnsiStyles.yellow(r'$')} ${AnsiStyles.bold("melos exec")}'); - logger?.stdout(' └> ${AnsiStyles.cyan.bold(execArgsString)}'); - logger?.stdout( - ' └> ${AnsiStyles.yellow.bold('RUNNING')} (in ${packages.length} packages)', - ); - - logger?.stdout(''); + .child(targetStyle(execArgsString)) + .child('$runningLabel (in ${packages.length} packages)') + .newLine(); if (prefixLogs) { - logger?.stdout('-' * terminalWidth); + logger.horizontalLine(); } await pool.forEach(packages, (package) async { @@ -116,8 +113,9 @@ mixin _ExecMixin on _Melos { } if (!prefixLogs) { - logger?.stdout('-' * terminalWidth); - logger?.stdout(AnsiStyles.bgBlack.bold.italic('${package.name}:')); + logger + ..horizontalLine() + ..log(AnsiStyles.bgBlack.bold.italic('${package.name}:')); } final packageExitCode = await _execForPackage( @@ -130,32 +128,32 @@ mixin _ExecMixin on _Melos { if (packageExitCode > 0) { failures[package.name] = packageExitCode; } else if (!prefixLogs) { - logger?.stdout( + logger.log( AnsiStyles.bgBlack.bold.italic('${package.name}: ') + - AnsiStyles.bold.green.bgBlack('SUCCESS'), + AnsiStyles.bgBlack(successLabel), ); } }).drain(); - logger?.stdout('-' * terminalWidth); - logger?.stdout(''); - logger - ?.stdout('${AnsiStyles.yellow(r'$')} ${AnsiStyles.bold("melos exec")}'); - logger?.stdout(' └> ${AnsiStyles.cyan.bold(execArgsString)}'); + ..horizontalLine() + ..newLine() + ..command('melos exec', withDollarSign: true); + + final resultLogger = logger.child(targetStyle(execArgsString)); if (failures.isNotEmpty) { - logger?.stdout( - ' └> ${AnsiStyles.red.bold('FAILED')} (in ${failures.length} packages)', - ); + final failuresLogger = + resultLogger.child('$failedLabel (in ${failures.length} packages)'); for (final packageName in failures.keys) { - logger?.stdout( - ' └> ${AnsiStyles.yellow(packageName)} (with exit code ${failures[packageName]})', + failuresLogger.child( + '${errorPackageNameStyle(packageName)} ' + '(with exit code ${failures[packageName]})', ); } exitCode = 1; } else { - logger?.stdout(' └> ${AnsiStyles.green.bold('SUCCESS')}'); + resultLogger.child(successLabel); } } } diff --git a/packages/melos/lib/src/commands/list.dart b/packages/melos/lib/src/commands/list.dart index f2717cd3a..093b10123 100644 --- a/packages/melos/lib/src/commands/list.dart +++ b/packages/melos/lib/src/commands/list.dart @@ -46,7 +46,7 @@ mixin _ListMixin on _Melos { } const encoder = JsonEncoder.withIndent(' '); - logger?.stdout(encoder.convert(jsonGraph)); + logger.stdout(encoder.convert(jsonGraph)); } void _listColumn( @@ -54,16 +54,13 @@ mixin _ListMixin on _Melos { required bool long, }) { if (workspace.filteredPackages.values.isEmpty) { - logger?.stdout( - AnsiStyles.yellow( - 'No packages were found with the current filters.', - ), + logger.warning( + 'No packages were found with the current filters.', + label: false, ); - logger?.stdout( - AnsiStyles.gray( - 'Hint: if this is unexpected, ' - 'try running the command again with a reduced number of filters applied.', - ), + logger.hint( + 'If this is unexpected, try running the command again with a reduced ' + 'number of filters applied.', ); return; } @@ -82,12 +79,12 @@ mixin _ListMixin on _Melos { .cast>() .toList(), ); - logger?.stdout(table); + logger.stdout(table); return; } for (final package in workspace.filteredPackages.values) { - logger?.stdout(package.name); + logger.stdout(package.name); } } @@ -101,7 +98,7 @@ mixin _ListMixin on _Melos { ? printablePath(package.pathRelativeToWorkspace) : package.path; if (long) { - logger?.stdout( + logger.stdout( [ packagePath, package.name, @@ -110,7 +107,7 @@ mixin _ListMixin on _Melos { ].join(':'), ); } else { - logger?.stdout(packagePath); + logger.stdout(packagePath); } } } @@ -173,7 +170,7 @@ mixin _ListMixin on _Melos { } const encoder = JsonEncoder.withIndent(' '); - logger?.stdout(encoder.convert(jsonArrayItems)); + logger.stdout(encoder.convert(jsonArrayItems)); } void _listGviz(MelosWorkspace workspace) { @@ -253,6 +250,6 @@ mixin _ListMixin on _Melos { buffer.add('}'); - logger?.stdout(buffer.join('\n')); + logger.stdout(buffer.join('\n')); } } diff --git a/packages/melos/lib/src/commands/publish.dart b/packages/melos/lib/src/commands/publish.dart index ba0110da5..7778499f2 100644 --- a/packages/melos/lib/src/commands/publish.dart +++ b/packages/melos/lib/src/commands/publish.dart @@ -11,13 +11,11 @@ mixin _PublishMixin on _ExecMixin { }) async { final workspace = await createWorkspace(global: global, filter: filter); - logger?.stdout( - AnsiStyles.yellow.bold('melos publish${dryRun ? " --dry-run" : ''}'), - ); - logger?.stdout(' └> ${AnsiStyles.cyan.bold(workspace.path)}\n'); + logger.command('melos publish${dryRun ? " --dry-run" : ''}'); + logger.child(targetStyle(workspace.path)).newLine(); final readRegistryProgress = - logger?.progress('Reading pub registry for package information'); + logger.progress('Reading pub registry for package information'); Map latestPublishedVersionForPackages; @@ -25,8 +23,8 @@ mixin _PublishMixin on _ExecMixin { latestPublishedVersionForPackages = await _getLatestPublishedVersionForPackages(workspace); } finally { - readRegistryProgress?.finish( - message: AnsiStyles.green('SUCCESS'), + readRegistryProgress.finish( + message: successLabel, showTiming: true, ); } @@ -40,31 +38,31 @@ mixin _PublishMixin on _ExecMixin { ]; if (unpublishedPackages.isEmpty) { - logger?.stdout( - AnsiStyles.green.bold( - '\nNo unpublished packages found - all local packages are already up to date.', - ), - ); + logger + ..newLine() + ..success( + 'No unpublished packages found - ' + 'all local packages are already up to date.', + ); return; } sortPackagesTopologically(unpublishedPackages); - if (dryRun) { - logger?.stdout( - AnsiStyles.magentaBright.bold( - '\nThe following packages will be validated only (dry run):\n', - ), - ); - } else { - logger?.stdout( - AnsiStyles.yellowBright.bold( - '\nThe following packages WILL be published to the registry:\n', + logger + ..newLine() + ..warning( + AnsiStyles.bold( + dryRun + ? 'The following packages will be validated only (dry run):' + : 'The following packages WILL be published to the registry:', ), - ); - } + label: false, + dryRun: dryRun, + ) + ..newLine(); - logger?.stdout( + logger.stdout( listAsPaddedTable( [ [ @@ -87,7 +85,7 @@ mixin _PublishMixin on _ExecMixin { if (!force) { final shouldContinue = promptBool(); if (!shouldContinue) throw CancelledException(); - logger?.stdout(''); + logger.newLine(); } await _publish( @@ -139,7 +137,7 @@ mixin _PublishMixin on _ExecMixin { required bool dryRun, required bool gitTagVersion, }) async { - final updateRegistryProgress = logger?.progress( + final updateRegistryProgress = logger.progress( 'Publishing ${unpublishedPackages.length} packages to registry:', ); final execArgs = [ @@ -163,10 +161,9 @@ mixin _PublishMixin on _ExecMixin { if (exitCode != 1) { if (!dryRun && gitTagVersion) { - logger?.stdout(''); - logger?.stdout( - 'Creating git tags for any versions not already created... ', - ); + logger + ..newLine() + ..log('Creating git tags for any versions not already created... '); await Future.forEach(unpublishedPackages, (Package package) async { final tag = gitTagForPackageVersion(package.name, package.version.toString()); @@ -179,21 +176,19 @@ mixin _PublishMixin on _ExecMixin { }); } - updateRegistryProgress?.finish( - message: AnsiStyles.green('SUCCESS'), + updateRegistryProgress.finish( + message: successLabel, showTiming: true, ); - if (!dryRun) { - logger?.stdout( - AnsiStyles.green - .bold('\nAll packages have successfully been published.'), + logger + ..newLine() + ..success( + dryRun + ? 'All packages were validated successfully.' + : 'All packages have successfully been published.', + dryRun: dryRun, ); - } else { - logger?.stdout( - AnsiStyles.green.bold('\nAll packages were validated successfully.'), - ); - } } } } diff --git a/packages/melos/lib/src/commands/run.dart b/packages/melos/lib/src/commands/run.dart index e4239db85..478c40569 100644 --- a/packages/melos/lib/src/commands/run.dart +++ b/packages/melos/lib/src/commands/run.dart @@ -28,17 +28,16 @@ mixin _RunMixin on _Melos { extraArgs: extraArgs, ); - logger?.stdout(''); - logger?.stdout(AnsiStyles.yellow.bold('melos run ${script.name}')); - logger?.stdout( - ' └> ${AnsiStyles.cyan.bold(script.effectiveRun.replaceAll('\n', ''))}', - ); + logger.newLine(); + logger.command('melos run ${script.name}'); + final resultLogger = + logger.child(targetStyle(script.effectiveRun.replaceAll('\n', ''))); if (exitCode != 0) { - logger?.stdout(' └> ${AnsiStyles.red.bold('FAILED')}'); + resultLogger.child(failedLabel); throw ScriptException._(script.name); } - logger?.stdout(' └> ${AnsiStyles.green.bold('SUCCESS')}'); + resultLogger.child(successLabel); } Future _pickScript(MelosWorkspaceConfig config) async { @@ -149,11 +148,11 @@ mixin _RunMixin on _Melos { final scriptSource = script.effectiveRun; final scriptParts = scriptSource.split(' '); - logger?.stdout(AnsiStyles.yellow.bold('melos run ${script.name}')); - logger?.stdout( - ' └> ${AnsiStyles.cyan.bold(scriptSource.replaceAll('\n', ''))}', - ); - logger?.stdout(' └> ${AnsiStyles.yellow.bold('RUNNING')}\n'); + logger.command('melos run ${script.name}'); + logger + .child(targetStyle(scriptSource.replaceAll('\n', ''))) + .child(runningLabel) + .newLine(); return startProcess( scriptParts..addAll(extraArgs), diff --git a/packages/melos/lib/src/commands/runner.dart b/packages/melos/lib/src/commands/runner.dart index deef5ea45..e74649d38 100644 --- a/packages/melos/lib/src/commands/runner.dart +++ b/packages/melos/lib/src/commands/runner.dart @@ -29,6 +29,7 @@ import '../common/utils.dart' as utils; import '../common/versioning.dart' as versioning; import '../common/workspace_changelog.dart'; import '../global_options.dart'; +import '../logging.dart'; import '../package.dart'; import '../prompts/prompt.dart' as prompts; import '../scripts.dart'; @@ -61,16 +62,16 @@ class Melos extends _Melos Melos({ required this.config, Logger? logger, - }) : logger = logger ?? Logger.standard(); + }) : logger = (logger ?? Logger.standard()).toMelosLogger(); @override - final Logger? logger; + final MelosLogger logger; @override final MelosWorkspaceConfig config; } abstract class _Melos { - Logger? get logger; + MelosLogger get logger; MelosWorkspaceConfig get config; Future createWorkspace({ @@ -118,7 +119,9 @@ abstract class _Melos { } if (workspace.config.scripts.containsKey(scriptName)) { - logger?.stdout('Running $scriptName script...\n'); + logger + ..log('Running $scriptName script...') + ..newLine(); await run(scriptName: scriptName); } @@ -128,7 +131,9 @@ abstract class _Melos { } finally { final postScript = 'post$scriptName'; if (workspace.config.scripts.containsKey(postScript)) { - logger?.stdout('Running $postScript script...\n'); + logger + ..log('Running $postScript script...') + ..newLine(); await run(scriptName: postScript); } diff --git a/packages/melos/lib/src/commands/version.dart b/packages/melos/lib/src/commands/version.dart index 93882a33d..70327c8c8 100644 --- a/packages/melos/lib/src/commands/version.dart +++ b/packages/melos/lib/src/commands/version.dart @@ -61,8 +61,10 @@ mixin _VersionMixin on _RunMixin { message ??= workspace.config.commands.version.message ?? defaultCommitMessage; - logger?.stdout(AnsiStyles.yellow.bold('melos version')); - logger?.stdout(' └> ${AnsiStyles.cyan.bold(workspace.path)}\n'); + logger + ..command('melos version') + ..child(targetStyle(workspace.path)) + ..newLine(); final commitMessageTemplate = Template(message, delimiters: '{ }'); @@ -78,9 +80,8 @@ mixin _VersionMixin on _RunMixin { for (final packageName in manualVersions.keys) { if (!workspace.allPackages.keys.contains(packageName)) { exitCode = 1; - logger?.stdout( - '${AnsiStyles.redBright('ERROR:')} package "$packageName" does not exist in this workspace.', - ); + logger + .error('package "$packageName" does not exist in this workspace.'); return; } } @@ -102,7 +103,9 @@ mixin _VersionMixin on _RunMixin { final pendingPackageUpdates = []; if (workspace.config.scripts.containsKey('preversion')) { - logger?.stdout('Running "preversion" lifecycle script...\n'); + logger + ..log('Running "preversion" lifecycle script...') + ..newLine(); await run(scriptName: 'preversion'); } @@ -160,7 +163,7 @@ mixin _VersionMixin on _RunMixin { String? defaultUserChangelogMessage; if (commits.isEmpty) { - logger?.stdout( + logger.log( 'Could not find any commits for manually versioned package ' '"$name".', ); @@ -168,7 +171,7 @@ mixin _VersionMixin on _RunMixin { promptForMessage = true; defaultUserChangelogMessage = 'Bump "$name" to `$version`.'; } else { - logger?.stdout( + logger.log( 'Found commits for manually versioned package "$name".', ); @@ -246,25 +249,24 @@ mixin _VersionMixin on _RunMixin { } if (pendingPackageUpdates.isEmpty) { - logger?.stdout( - AnsiStyles.yellow( - 'No packages were found that required versioning.', - ), + logger.warning( + 'No packages were found that required versioning.', + label: false, ); - logger?.stdout( - AnsiStyles.gray( - ''' -Hint: try running "melos list" with the same filtering options to see a list of packages that were included. -Hint: try running "melos version --all" to include private packages. -''', - ), + logger.hint( + 'Try running "melos list" with the same filtering options to see a list ' + 'of packages that were included.', + ); + logger.hint( + 'Try running "melos version --all" to include private packages', ); return; } - logger?.stdout( + logger.log( AnsiStyles.magentaBright( - 'The following ${AnsiStyles.bold(pendingPackageUpdates.length.toString())} packages will be updated:\n', + 'The following ${packageNameStyle(pendingPackageUpdates.length.toString())} ' + 'packages will be updated:\n', ), ); @@ -276,15 +278,16 @@ Hint: try running "melos version --all" to include private packages. // show commit message for (final element in pendingPackageUpdates) { - logger?.trace(AnsiStyles.yellow.bold(element.package.name)); - for (final e in element.commits) { - logger?.trace(' ${e.message}'); + logger.trace(AnsiStyles.yellow.bold(element.package.name)); + final commitLogger = logger.childWithoutMessage(); + for (final commit in element.commits) { + commitLogger.trace(commit.message); } } final shouldContinue = force || promptBool(); if (!shouldContinue) { - logger?.stdout(AnsiStyles.red('Operation was canceled.')); + logger.error('Operation was canceled.', label: false); exitCode = 1; return; } @@ -299,7 +302,7 @@ Hint: try running "melos version --all" to include private packages. // TODO allow support for individual package lifecycle version scripts if (workspace.config.scripts.containsKey('version')) { - logger?.stdout('Running "version" lifecycle script...\n'); + logger.log('Running "version" lifecycle script...\n'); await run(scriptName: 'version'); } @@ -319,24 +322,21 @@ Hint: try running "melos version --all" to include private packages. // TODO allow support for individual package lifecycle postversion scripts if (workspace.config.scripts.containsKey('postversion')) { - logger?.stdout('Running "postversion" lifecycle script...\n'); + logger.log('Running "postversion" lifecycle script...\n'); await run(scriptName: 'postversion'); } if (gitTag) { // TODO automatic push support - logger?.stdout( - AnsiStyles.greenBright.bold( - 'Versioning successful. ' - 'Ensure you push your git changes and tags (if applicable) via ${AnsiStyles.bgBlack.gray('git push --follow-tags')}', - ), + logger.success( + 'Versioning successful. ' + 'Ensure you push your git changes and tags (if applicable) via ' + '${AnsiStyles.bgBlack.gray('git push --follow-tags')}', ); } else { - logger?.stdout( - AnsiStyles.greenBright.bold( - 'Versioning successful. ' - 'Ensure you commit and push your changes (if applicable).', - ), + logger.success( + 'Versioning successful. ' + 'Ensure you commit and push your changes (if applicable).', ); } } @@ -355,9 +355,11 @@ Hint: try running "melos version --all" to include private packages. // Sanity check that contents actually changed. if (contents == updatedContents) { - logger?.trace( - 'Failed to update a pubspec.yaml version to $version for package ${package.name}. ' - 'You should probably report this issue with a copy of your pubspec.yaml file.', + logger.trace( + 'Failed to update a pubspec.yaml version to $version for package ' + '${package.name}. ' + 'You should probably report this issue with a copy of your ' + 'pubspec.yaml file.', ); return; } @@ -427,9 +429,11 @@ Hint: try running "melos version --all" to include private packages. package.pubSpec.dependencies[dependencyName] is! HostedReference && package.pubSpec.dependencies[dependencyName] is! ExternalHostedReference) { - logger?.trace( - 'Skipping updating dependency $dependencyName for package ${package.name} - ' - 'the version is a Map definition and is most likely a dependency that is importing from a path or git remote.', + logger.trace( + 'Skipping updating dependency $dependencyName for package ' + '${package.name} - ' + 'the version is a Map definition and is most likely a dependency that ' + 'is importing from a path or git remote.', ); return; } @@ -438,9 +442,11 @@ Hint: try running "melos version --all" to include private packages. package.pubSpec.devDependencies[dependencyName] is! HostedReference && package.pubSpec.devDependencies[dependencyName] is! ExternalHostedReference) { - logger?.trace( - 'Skipping updating dev dependency $dependencyName for package ${package.name} - ' - 'the version is a Map definition and is most likely a dependency that is importing from a path or git remote.', + logger.trace( + 'Skipping updating dev dependency $dependencyName for package ' + '${package.name} - ' + 'the version is a Map definition and is most likely a dependency that ' + 'is importing from a path or git remote.', ); return; } @@ -478,9 +484,11 @@ Hint: try running "melos version --all" to include private packages. // Sanity check that contents actually changed. if (contents == updatedContents) { - logger?.trace( - 'Failed to update dependency $dependencyName version to $dependencyVersion for package ${package.name}, ' - 'you should probably report this issue with a copy of your pubspec.yaml file.', + logger.trace( + 'Failed to update dependency $dependencyName version to ' + '$dependencyVersion for package ${package.name}, ' + 'you should probably report this issue with a copy of your ' + 'pubspec.yaml file.', ); return; } @@ -493,7 +501,7 @@ Hint: try running "melos version --all" to include private packages. required bool updateDependentsVersions, required bool updateDependentsConstraints, }) { - logger?.stdout( + logger.stdout( listAsPaddedTable( [ [ diff --git a/packages/melos/lib/src/common/changelog.dart b/packages/melos/lib/src/common/changelog.dart index 43f9faa7d..0bad0901c 100644 --- a/packages/melos/lib/src/common/changelog.dart +++ b/packages/melos/lib/src/common/changelog.dart @@ -17,10 +17,10 @@ import 'dart:io'; -import 'package:cli_util/cli_logging.dart'; import 'package:path/path.dart'; import 'package:pub_semver/pub_semver.dart'; +import '../logging.dart'; import '../package.dart'; import 'git_commit.dart'; import 'git_repository.dart'; @@ -31,7 +31,7 @@ class Changelog { final Package package; final Version version; - final Logger? logger; + final MelosLogger logger; String get markdown { throw UnimplementedError(); @@ -57,8 +57,9 @@ class Changelog { Future write() async { var contents = await read(); if (contents.contains(markdown)) { - logger?.trace( - 'Identical changelog content for ${package.name} v$version already exists, skipping.', + logger.trace( + 'Identical changelog content for ${package.name} v$version already ' + 'exists, skipping.', ); return; } @@ -69,7 +70,7 @@ class Changelog { } class MelosChangelog extends Changelog { - MelosChangelog(this.update, Logger? logger) + MelosChangelog(this.update, MelosLogger logger) : super(update.package, update.nextVersion, logger); final MelosPendingPackageUpdate update; diff --git a/packages/melos/lib/src/common/git.dart b/packages/melos/lib/src/common/git.dart index 093aa1290..801288950 100644 --- a/packages/melos/lib/src/common/git.dart +++ b/packages/melos/lib/src/common/git.dart @@ -17,8 +17,7 @@ import 'dart:io'; -import 'package:cli_util/cli_logging.dart'; - +import '../logging.dart'; import '../package.dart'; import 'git_commit.dart'; @@ -53,12 +52,12 @@ String gitTagForPackageVersion( Future gitExecuteCommand({ required List arguments, required String workingDirectory, - Logger? logger, + required MelosLogger logger, bool throwOnExitCodeError = true, }) async { const executable = 'git'; - logger?.trace( + logger.trace( '[GIT] Executing command `$executable ${arguments.join(' ')}` ' 'in directory `$workingDirectory`.', ); @@ -87,7 +86,7 @@ Future> gitTagsForPackage( Package package, { TagReleaseType tagReleaseType = TagReleaseType.all, String preid = 'dev', - Logger? logger, + required MelosLogger logger, }) async { final filterPattern = gitTagFilterPattern(package.name, tagReleaseType, preid: preid); @@ -114,7 +113,7 @@ Future> gitTagsForPackage( Future gitTagExists( String tag, { required String workingDirectory, - Logger? logger, + required MelosLogger logger, }) async { final processResult = await gitExecuteCommand( arguments: ['tag', '-l', tag], @@ -131,7 +130,7 @@ Future gitTagCreate( String message, { required String workingDirectory, String? commitId, - Logger? logger, + required MelosLogger logger, }) async { if (await gitTagExists( tag, @@ -166,7 +165,7 @@ Future gitTagCreate( Future gitLatestTagForPackage( Package package, { String preid = 'dev', - Logger? logger, + required MelosLogger logger, }) async { // Package doesn't have a version, skip. if (package.version.toString() == '0.0.0') return null; @@ -178,8 +177,9 @@ Future gitLatestTagForPackage( workingDirectory: package.path, logger: logger, )) { - logger?.trace( - '[GIT] Found a git tag for the latest ${package.name} version (${package.version.toString()}).', + logger.trace( + '[GIT] Found a git tag for the latest ${package.name} version ' + '(${package.version.toString()}).', ); return currentVersionTag; } @@ -203,7 +203,7 @@ Future gitLatestTagForPackage( Future gitAdd( String filePattern, { required String workingDirectory, - Logger? logger, + required MelosLogger logger, }) async { final arguments = ['add', filePattern]; await gitExecuteCommand( @@ -217,7 +217,7 @@ Future gitAdd( Future gitCommit( String message, { required String workingDirectory, - Logger? logger, + required MelosLogger logger, }) async { final arguments = ['commit', '-m', message]; await gitExecuteCommand( @@ -234,7 +234,7 @@ Future> gitCommitsForPackage( Package package, { String? since, String preid = 'dev', - Logger? logger, + required MelosLogger logger, }) async { var sinceOrLatestTag = since; if (sinceOrLatestTag != null && sinceOrLatestTag.isEmpty) { @@ -242,8 +242,9 @@ Future> gitCommitsForPackage( } sinceOrLatestTag ??= await gitLatestTagForPackage(package, logger: logger); - logger?.trace( - '[GIT] Getting commits for package ${package.name} since "${sinceOrLatestTag ?? '@'}".', + logger.trace( + '[GIT] Getting commits for package ${package.name} since ' + '"${sinceOrLatestTag ?? '@'}".', ); final processResult = await gitExecuteCommand( @@ -278,7 +279,7 @@ Future> gitCommitsForPackage( /// Returns the current branch name of the local git repository. Future gitGetCurrentBranchName({ required String workingDirectory, - Logger? logger, + required MelosLogger logger, }) async { final arguments = ['rev-parse', '--abbrev-ref', 'HEAD']; final processResult = await gitExecuteCommand( @@ -292,7 +293,7 @@ Future gitGetCurrentBranchName({ /// Fetches updates for the default remote in the repository. Future gitRemoteUpdate({ required String workingDirectory, - Logger? logger, + required MelosLogger logger, }) async { final arguments = ['remote', 'update']; await gitExecuteCommand( @@ -308,7 +309,7 @@ Future gitIsBehindUpstream({ required String workingDirectory, String remote = 'origin', String? branch, - Logger? logger, + required MelosLogger logger, }) async { await gitRemoteUpdate(workingDirectory: workingDirectory, logger: logger); @@ -336,7 +337,7 @@ Future gitIsBehindUpstream({ final aheadCount = leftRightCounts[1]; final isBehind = behindCount > 0; - logger?.trace( + logger.trace( '[GIT] Local branch `$localBranch` is behind remote branch `$remoteBranch` ' 'by $behindCount commit(s) and ahead by $aheadCount.', ); diff --git a/packages/melos/lib/src/common/pending_package_update.dart b/packages/melos/lib/src/common/pending_package_update.dart index 7919a1e08..c61cef557 100644 --- a/packages/melos/lib/src/common/pending_package_update.dart +++ b/packages/melos/lib/src/common/pending_package_update.dart @@ -17,10 +17,10 @@ import 'dart:math' as math; -import 'package:cli_util/cli_logging.dart'; import 'package:conventional_commit/conventional_commit.dart'; import 'package:pub_semver/pub_semver.dart'; +import '../logging.dart'; import '../package.dart'; import '../workspace.dart'; import 'changelog.dart'; @@ -100,10 +100,10 @@ class MelosPendingPackageUpdate { /// This is only used for manually versioned packages. final String? userChangelogMessage; - final Logger? logger; + final MelosLogger logger; Changelog get changelog { - // TODO change log styles can be changed here if supported in future. + // TODO changelog styles can be changed here if supported in future. return MelosChangelog(this, logger); } diff --git a/packages/melos/lib/src/common/utils.dart b/packages/melos/lib/src/common/utils.dart index 977f97064..df9c87dc5 100644 --- a/packages/melos/lib/src/common/utils.dart +++ b/packages/melos/lib/src/common/utils.dart @@ -21,12 +21,12 @@ import 'dart:io'; import 'dart:isolate'; import 'package:ansi_styles/ansi_styles.dart'; -import 'package:cli_util/cli_logging.dart'; import 'package:graphs/graphs.dart'; import 'package:path/path.dart' as p; import 'package:pub_semver/pub_semver.dart'; import 'package:yaml/yaml.dart'; +import '../logging.dart'; import '../package.dart'; import '../prompts/prompt.dart' as prompts; import '../workspace.dart'; @@ -293,7 +293,7 @@ Future startProcess( String? workingDirectory, bool onlyOutputOnError = false, bool includeParentEnvironment = true, - Logger? logger, + required MelosLogger logger, }) async { final workingDirectoryPath = workingDirectory ?? Directory.current.path; final executable = currentPlatform.isWindows ? 'cmd' : '/bin/sh'; @@ -375,7 +375,7 @@ Future startProcess( (List event) { processStdout.addAll(event); if (!onlyOutputOnError) { - logger?.write(utf8.decode(event, allowMalformed: true)); + logger.write(utf8.decode(event, allowMalformed: true)); } }, onDone: processStdoutCompleter.complete, @@ -384,7 +384,7 @@ Future startProcess( (List event) { processStderr.addAll(event); if (!onlyOutputOnError) { - logger?.stderr(utf8.decode(event, allowMalformed: true)); + logger.stderr(utf8.decode(event, allowMalformed: true)); } }, onDone: processStderrCompleter.complete, @@ -395,8 +395,8 @@ Future startProcess( final exitCode = await execProcess.exitCode; if (onlyOutputOnError && exitCode > 0) { - logger?.stdout(utf8.decode(processStdout, allowMalformed: true)); - logger?.stderr(utf8.decode(processStderr, allowMalformed: true)); + logger.stdout(utf8.decode(processStdout, allowMalformed: true)); + logger.stderr(utf8.decode(processStderr, allowMalformed: true)); } return exitCode; diff --git a/packages/melos/lib/src/common/workspace_changelog.dart b/packages/melos/lib/src/common/workspace_changelog.dart index db29e3ac3..cb6d70741 100644 --- a/packages/melos/lib/src/common/workspace_changelog.dart +++ b/packages/melos/lib/src/common/workspace_changelog.dart @@ -17,10 +17,10 @@ import 'dart:io'; -import 'package:cli_util/cli_logging.dart'; import 'package:path/path.dart'; -import '../../melos.dart'; +import '../logging.dart'; +import '../workspace.dart'; import 'changelog.dart'; import 'pending_package_update.dart'; @@ -34,7 +34,7 @@ class WorkspaceChangelog { final MelosWorkspace workspace; final String title; - final Logger? logger; + final MelosLogger logger; final List pendingPackageUpdates; String get _changelogFileHeader { @@ -163,7 +163,7 @@ class WorkspaceChangelog { Future write() async { var contents = await read(); if (contents.contains(markdown)) { - logger?.trace( + logger.trace( 'Identical changelog content for ${workspace.name} already exists, skipping.', ); return; diff --git a/packages/melos/lib/src/logging.dart b/packages/melos/lib/src/logging.dart new file mode 100644 index 000000000..74bb419ee --- /dev/null +++ b/packages/melos/lib/src/logging.dart @@ -0,0 +1,180 @@ +import 'package:ansi_styles/ansi_styles.dart'; +import 'package:cli_util/cli_logging.dart'; + +import 'common/utils.dart'; + +final commandColor = AnsiStyles.yellow; +final commandLabelColor = AnsiStyles.yellowBright; +final successMessageColor = AnsiStyles.green; +final successLableColor = AnsiStyles.greenBright; +final warningMessageColor = AnsiStyles.yellow; +final warningLabelColor = AnsiStyles.yellowBright; +final errorMessageColor = AnsiStyles.red; +final errorLabelColor = AnsiStyles.redBright; +final hintMessageColor = AnsiStyles.gray; +final hintLabelColor = AnsiStyles.gray; +final dryRunWarningMessageColor = AnsiStyles.magenta; +final dryRunWarningLabelColor = AnsiStyles.magentaBright; + +final commandStyle = AnsiStyles.bold; +final successStyle = AnsiStyles.bold; +final labelStyle = AnsiStyles.bold; + +final successLabel = successLableColor(labelStyle('SUCCESS')); +final warningLabel = warningLabelColor(labelStyle('WARNING')); +final errorLabel = errorLabelColor(labelStyle('ERROR')); +final failedLabel = errorLabelColor(labelStyle('FAILED')); +final hintLabel = hintLabelColor(labelStyle('HINT')); +final runningLabel = commandLabelColor(labelStyle('RUNNING')); +final checkLabel = AnsiStyles.greenBright('✓'); + +final targetStyle = AnsiStyles.cyan.bold; +final packagePathStyle = AnsiStyles.blue; +final packageNameStyle = AnsiStyles.bold; +final errorPackageNameStyle = AnsiStyles.yellow.bold; + +/// CLI logger that encapsulates Melos log formatting conventions. +class MelosLogger with _DelegateLogger { + MelosLogger( + Logger logger, { + String indentation = '', + String childIndentation = ' ', + }) : _logger = logger, + _indentation = indentation, + _childIndentation = childIndentation; + + @override + final Logger _logger; + final String _indentation; + final String _childIndentation; + + void log(String message) => stdout(message); + + void command(String command, {bool withDollarSign = false}) { + if (withDollarSign) { + stdout('${commandColor(r'$')} ${commandStyle(command)}'); + } else { + stdout(commandColor(commandStyle(command))); + } + } + + void success(String message, {bool dryRun = false}) { + if (dryRun) { + stdout(successMessageColor(message)); + } else { + stdout(successMessageColor(successStyle(message))); + } + } + + void warning(String message, {bool label = true, bool dryRun = false}) { + final labelColor = + dryRun ? dryRunWarningLabelColor : dryRunWarningMessageColor; + final messageColor = + dryRun ? dryRunWarningMessageColor : warningMessageColor; + if (label) { + stdout('$warningLabel${labelColor(':')} $message'); + } else { + stdout(messageColor(message)); + } + } + + void error(String message, {bool label = true}) { + if (label) { + stderr('$errorLabel${errorLabelColor(':')} $message'); + } else { + stderr(errorMessageColor(message)); + } + } + + void hint(String message, {bool label = true}) { + if (label) { + stdout(hintMessageColor('$hintLabel: $message')); + } else { + stdout(hintMessageColor(message)); + } + } + + void newLine() => _logger.write('\n'); + + void horizontalLine() => _logger.stdout('-' * terminalWidth); + + MelosLogger child( + String message, { + String prefix = '└> ', + bool stderr = false, + }) { + final childIndentation = ' ' * AnsiStyles.strip(prefix).length; + final logger = MelosLogger( + _logger, + indentation: '$_indentation$_childIndentation', + childIndentation: childIndentation, + ); + + final prefixedMessage = '$prefix$message'; + if (stderr) { + logger.stderr(prefixedMessage); + } else { + logger.stdout(prefixedMessage); + } + + return logger; + } + + MelosLogger childWithoutMessage({String childIndentation = ' '}) => + MelosLogger( + _logger, + indentation: '$_indentation$_childIndentation', + childIndentation: childIndentation, + ); + + @override + void stdout(String message) => _logger.stdout('$_indentation$message'); + + @override + void stderr(String message) => _logger.stderr('$_indentation$message'); + + @override + void trace(String message) => _logger.trace('$_indentation$message'); +} + +mixin _DelegateLogger implements Logger { + Logger get _logger; + + @override + Ansi get ansi => _logger.ansi; + + @override + bool get isVerbose => _logger.isVerbose; + + @override + void stdout(String message) => _logger.stdout(message); + + @override + void stderr(String message) => _logger.stderr(message); + + @override + void trace(String message) => _logger.trace(message); + + @override + Progress progress(String message) => _logger.progress(message); + + @override + void write(String message) => _logger.write(message); + + @override + void writeCharCode(int charCode) => _logger.writeCharCode(charCode); + + @override + // ignore: deprecated_member_use + void flush() => _logger.flush(); +} + +extension ToMelosLoggerExtension on Logger { + MelosLogger toMelosLogger() { + final self = this; + if (self is MelosLogger) { + return self; + } + return MelosLogger(this); + } +} diff --git a/packages/melos/lib/src/package.dart b/packages/melos/lib/src/package.dart index 0a5d07732..2ee5ba327 100644 --- a/packages/melos/lib/src/package.dart +++ b/packages/melos/lib/src/package.dart @@ -19,7 +19,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'package:cli_util/cli_logging.dart'; import 'package:collection/collection.dart'; import 'package:glob/glob.dart'; import 'package:path/path.dart'; @@ -34,6 +33,7 @@ import 'common/http.dart' as http; import 'common/platform.dart'; import 'common/utils.dart'; import 'common/validation.dart'; +import 'logging.dart'; import 'workspace.dart'; /// Key for windows platform. @@ -321,7 +321,7 @@ class PackageMap { return a.toLowerCase().compareTo(b.toLowerCase()); }); - // Map litterals creates an HashMap which preserves key order. + // Map literals creates an HashMap which preserves key order. // So map.keys/map.values will be sorted by name. return { for (final name in sortedNames) name: packages[name]!, @@ -332,7 +332,7 @@ class PackageMap { required String workspacePath, required List packages, required List ignore, - Logger? logger, + required MelosLogger logger, }) async { final packageMap = {}; @@ -416,7 +416,7 @@ The packages that caused the problem are: } final Map _map; - final Logger? _logger; + final MelosLogger _logger; Iterable get keys => _map.keys; @@ -538,7 +538,10 @@ extension on Iterable { return packagesFilteredWithPublishStatus; } - Future> applySince(String? since, Logger? logger) async { + Future> applySince( + String? since, + MelosLogger logger, + ) async { if (since == null) return this; final pool = Pool(10); diff --git a/packages/melos/lib/src/workspace.dart b/packages/melos/lib/src/workspace.dart index 2046e65f5..e245632c4 100644 --- a/packages/melos/lib/src/workspace.dart +++ b/packages/melos/lib/src/workspace.dart @@ -18,7 +18,6 @@ import 'dart:async'; import 'dart:io'; -import 'package:cli_util/cli_logging.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as p; @@ -28,6 +27,7 @@ import 'common/pub_dependency_list.dart'; import 'common/utils.dart' as utils; import 'common/validation.dart'; import 'global_options.dart'; +import 'logging.dart'; import 'package.dart'; import 'workspace_configs.dart'; @@ -50,7 +50,7 @@ class MelosWorkspace { required this.allPackages, required this.filteredPackages, required this.sdkPath, - this.logger, + required this.logger, }); /// Build a [MelosWorkspace] from a workspace configuration. @@ -58,7 +58,7 @@ class MelosWorkspace { MelosWorkspaceConfig workspaceConfig, { GlobalOptions? global, PackageFilter? filter, - Logger? logger, + required MelosLogger logger, }) async { final allPackages = await PackageMap.resolvePackages( workspacePath: workspaceConfig.path, @@ -85,7 +85,7 @@ class MelosWorkspace { ); } - final Logger? logger; + final MelosLogger logger; /// An optional name as defined in "melos.yaml". This name is used for logging /// purposes and also used when generating certain IDE files. diff --git a/packages/melos/test/commands/bootstrap_test.dart b/packages/melos/test/commands/bootstrap_test.dart index 832c8927c..3335794d4 100644 --- a/packages/melos/test/commands/bootstrap_test.dart +++ b/packages/melos/test/commands/bootstrap_test.dart @@ -77,7 +77,10 @@ void main() { final logger = TestLogger(); final config = await MelosWorkspaceConfig.fromDirectory(workspaceDir); - final workspace = await MelosWorkspace.fromConfig(config); + final workspace = await MelosWorkspace.fromConfig( + config, + logger: logger.toMelosLogger(), + ); final melos = Melos(logger: logger, config: config); final pubExecArgs = pubCommandExecArgs( useFlutter: workspace.isFlutterWorkspace, @@ -91,7 +94,7 @@ void main() { ignoringAnsii( ''' melos bootstrap - └> ${workspaceDir.path} + └> ${workspaceDir.path} Running "${pubExecArgs.join(' ')} get" in workspace packages... ✓ a @@ -186,7 +189,7 @@ Generating IntelliJ IDE files... [ ''' melos bootstrap - └> ${workspaceDir.path} + └> ${workspaceDir.path} Running "flutter pub get" in workspace packages...''', ''' @@ -532,7 +535,10 @@ dependency_overrides: final logger = TestLogger(); final config = await MelosWorkspaceConfig.fromDirectory(workspaceDir); - final workspace = await MelosWorkspace.fromConfig(config); + final workspace = await MelosWorkspace.fromConfig( + config, + logger: logger.toMelosLogger(), + ); final melos = Melos( logger: logger, config: config, @@ -555,12 +561,12 @@ dependency_overrides: ignoringAnsii( ''' melos bootstrap - └> ${workspaceDir.path} + └> ${workspaceDir.path} Running "${pubExecArgs.join(' ')} get" in workspace packages... - a └> packages/a -e- └> Failed to install. +e- └> Failed to install. Resolving dependencies... e-Because a depends on package_that_does_not_exists any which doesn't exist (could not find package package_that_does_not_exists at https://pub.dartlang.org), version solving failed. diff --git a/packages/melos/test/commands/exec_test.dart b/packages/melos/test/commands/exec_test.dart index 5f9dd98c6..af5d0a21b 100644 --- a/packages/melos/test/commands/exec_test.dart +++ b/packages/melos/test/commands/exec_test.dart @@ -54,8 +54,8 @@ void main() { ignoringAnsii( ''' \$ melos exec - └> echo hello world - └> RUNNING (in 2 packages) + └> echo hello world + └> RUNNING (in 2 packages) ${'-' * terminalWidth} a: @@ -68,8 +68,8 @@ b: SUCCESS ${'-' * terminalWidth} \$ melos exec - └> echo hello world - └> SUCCESS + └> echo hello world + └> SUCCESS ''', ), ); diff --git a/packages/melos/test/commands/run_test.dart b/packages/melos/test/commands/run_test.dart index 759c712ff..c2b72ae0d 100644 --- a/packages/melos/test/commands/run_test.dart +++ b/packages/melos/test/commands/run_test.dart @@ -61,12 +61,12 @@ void main() { ignoringAnsii( ''' melos run test_script - └> melos exec -- "echo hello" - └> RUNNING + └> melos exec -- "echo hello" + └> RUNNING \$ melos exec - └> echo hello - └> RUNNING (in 1 packages) + └> echo hello + └> RUNNING (in 1 packages) ${'-' * terminalWidth} a: @@ -75,12 +75,12 @@ a: SUCCESS ${'-' * terminalWidth} \$ melos exec - └> echo hello - └> SUCCESS + └> echo hello + └> SUCCESS melos run test_script - └> melos exec -- "echo hello" - └> SUCCESS + └> melos exec -- "echo hello" + └> SUCCESS ''', ), ); @@ -132,14 +132,14 @@ melos run test_script ignoringAnsii( r''' melos run test_script - └> echo $0 $1 $2 - └> RUNNING + └> echo $0 $1 $2 + └> RUNNING /bin/sh foo bar baz melos run test_script - └> echo $0 $1 $2 - └> SUCCESS + └> echo $0 $1 $2 + └> SUCCESS ''', ), ); @@ -189,12 +189,12 @@ melos run test_script ignoringAnsii( ''' melos run test_script - └> melos exec --concurrency 1 -- "echo \\"hello\\"" - └> RUNNING + └> melos exec --concurrency 1 -- "echo \\"hello\\"" + └> RUNNING \$ melos exec - └> echo "hello" - └> RUNNING (in 1 packages) + └> echo "hello" + └> RUNNING (in 1 packages) ${'-' * terminalWidth} a: @@ -203,12 +203,12 @@ a: SUCCESS ${'-' * terminalWidth} \$ melos exec - └> echo "hello" - └> SUCCESS + └> echo "hello" + └> SUCCESS melos run test_script - └> melos exec --concurrency 1 -- "echo \\"hello\\"" - └> SUCCESS + └> melos exec --concurrency 1 -- "echo \\"hello\\"" + └> SUCCESS ''', ), ); diff --git a/packages/melos/test/git_test.dart b/packages/melos/test/git_test.dart index db4a691ed..04c370075 100644 --- a/packages/melos/test/git_test.dart +++ b/packages/melos/test/git_test.dart @@ -17,6 +17,7 @@ import 'dart:io'; +import 'package:melos/melos.dart'; import 'package:melos/src/common/git.dart'; import 'package:test/test.dart'; @@ -27,7 +28,7 @@ void main() { test('gitGetCurrentBranchName', () async { final branchName = await gitGetCurrentBranchName( workingDirectory: Directory.current.path, - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ); expect(branchName, isA()); expect(branchName, isNotEmpty); @@ -42,7 +43,7 @@ void main() { await gitTagExists( aTagThatExists, workingDirectory: workingDirectory, - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ), isTrue, ); @@ -50,7 +51,7 @@ void main() { await gitTagExists( aTagThatDoesNotExist, workingDirectory: workingDirectory, - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ), isFalse, ); @@ -61,7 +62,7 @@ void main() { () => gitExecuteCommand( arguments: ['foo', 'bar'], workingDirectory: Directory.current.path, - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ), throwsA(isA()), ); diff --git a/packages/melos/test/matchers.dart b/packages/melos/test/matchers.dart index 73c75760e..9a8fe9947 100644 --- a/packages/melos/test/matchers.dart +++ b/packages/melos/test/matchers.dart @@ -16,6 +16,7 @@ import 'dart:io' as io; +import 'package:ansi_styles/ansi_styles.dart'; import 'package:file/file.dart'; import 'package:melos/src/common/validation.dart'; import 'package:melos/src/package.dart'; @@ -35,7 +36,7 @@ class _IgnoringAnsii extends CustomMatcher { @override Object? featureValueOf(covariant String actual) { - return actual.replaceAll(RegExp(r'\x1b\[[0-9;]*m'), ''); + return AnsiStyles.strip(actual); } } diff --git a/packages/melos/test/package_filter_test.dart b/packages/melos/test/package_filter_test.dart index e8800e15b..ef586fef3 100644 --- a/packages/melos/test/package_filter_test.dart +++ b/packages/melos/test/package_filter_test.dart @@ -26,7 +26,7 @@ void main() { final config = await MelosWorkspaceConfig.fromDirectory(workspaceDir); final workspace = await MelosWorkspace.fromConfig( config, - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), filter: PackageFilter( dirExists: ['test'], ), @@ -62,7 +62,7 @@ void main() { final config = await MelosWorkspaceConfig.fromDirectory(workspaceDir); final workspace = await MelosWorkspace.fromConfig( config, - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), filter: PackageFilter( fileExists: ['log.txt'], ), diff --git a/packages/melos/test/package_test.dart b/packages/melos/test/package_test.dart index e224e0da8..f628d9d1b 100644 --- a/packages/melos/test/package_test.dart +++ b/packages/melos/test/package_test.dart @@ -2,10 +2,8 @@ import 'dart:io'; import 'package:glob/glob.dart'; import 'package:http/http.dart' as http; +import 'package:melos/melos.dart'; import 'package:melos/src/common/http.dart'; -import 'package:melos/src/package.dart'; -import 'package:melos/src/workspace.dart'; -import 'package:melos/src/workspace_configs.dart'; import 'package:mockito/mockito.dart'; import 'package:platform/platform.dart'; import 'package:test/test.dart'; @@ -41,7 +39,7 @@ void main() { ); workspace = await MelosWorkspace.fromConfig( config, - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ); }); diff --git a/packages/melos/test/utils.dart b/packages/melos/test/utils.dart index b81afbbd2..22c32be4a 100644 --- a/packages/melos/test/utils.dart +++ b/packages/melos/test/utils.dart @@ -235,7 +235,7 @@ class VirtualWorkspaceBuilder { this.defaultPackagesPath = 'packages', this.sdkPath, Logger? logger, - }) : logger = logger ?? TestLogger() { + }) : logger = (logger ?? TestLogger()).toMelosLogger() { if (currentPlatform.isWindows) { path = r'\\workspace'; } @@ -252,7 +252,7 @@ class VirtualWorkspaceBuilder { final String defaultPackagesPath; /// The logger to build the workspace with. - final Logger logger; + final MelosLogger logger; /// Optional Dart/Flutter SDK path. final String? sdkPath; @@ -301,7 +301,7 @@ class VirtualWorkspaceBuilder { PackageMap _buildVirtualPackageMap( List<_VirtualPackage> packages, - Logger logger, + MelosLogger logger, ) { final packageMap = {}; diff --git a/packages/melos/test/workspace_test.dart b/packages/melos/test/workspace_test.dart index a5d9e12c8..e91349bdb 100644 --- a/packages/melos/test/workspace_test.dart +++ b/packages/melos/test/workspace_test.dart @@ -16,11 +16,10 @@ import 'dart:io'; +import 'package:melos/melos.dart'; import 'package:melos/src/common/glob.dart'; import 'package:melos/src/common/utils.dart'; -import 'package:melos/src/package.dart'; import 'package:melos/src/workspace.dart'; -import 'package:melos/src/workspace_configs.dart'; import 'package:path/path.dart' as p; import 'package:platform/platform.dart'; import 'package:pubspec/pubspec.dart'; @@ -59,7 +58,7 @@ void main() { await expectLater( () async => MelosWorkspace.fromConfig( await MelosWorkspaceConfig.fromDirectory(workspaceDir), - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ), throwsMelosConfigException( message: anyOf( @@ -98,7 +97,7 @@ The packages that caused the problem are: final config = await MelosWorkspaceConfig.fromDirectory(aDir); final workspace = await MelosWorkspace.fromConfig( config, - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ); expect( @@ -129,7 +128,7 @@ The packages that caused the problem are: ); final workspace = await MelosWorkspace.fromConfig( config, - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ); expect( @@ -148,7 +147,7 @@ The packages that caused the problem are: await MelosWorkspace.fromConfig( await MelosWorkspaceConfig.fromDirectory(workspaceDir), - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ); }); @@ -201,7 +200,7 @@ The packages that caused the problem are: ], includeDependencies: true, ), - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ); expect(workspace.filteredPackages.values, [packageNamed('b')]); @@ -228,7 +227,7 @@ The packages that caused the problem are: ], includeDependencies: true, ), - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ); expect( @@ -262,7 +261,7 @@ The packages that caused the problem are: ], includeDependencies: true, ), - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ); expect( @@ -298,7 +297,7 @@ The packages that caused the problem are: ], includeDependencies: true, ), - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ); expect(workspace.filteredPackages.values, hasLength(4)); @@ -331,7 +330,7 @@ The packages that caused the problem are: ], includeDependents: true, ), - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ); expect(workspace.filteredPackages.values, [packageNamed('a')]); @@ -358,7 +357,7 @@ The packages that caused the problem are: ], includeDependents: true, ), - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ); expect(workspace.filteredPackages.values, hasLength(2)); @@ -390,7 +389,7 @@ The packages that caused the problem are: ], includeDependents: true, ), - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ); expect( @@ -426,7 +425,7 @@ The packages that caused the problem are: ], includeDependents: true, ), - logger: TestLogger(), + logger: TestLogger().toMelosLogger(), ); expect(workspace.filteredPackages.values, hasLength(4));