-
Notifications
You must be signed in to change notification settings - Fork 211
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: format built in command (#657)
This PR introduces a new built-in command called "format" that follows the same implementation logic used in the development of the "analyze" command, which was merged in a previous (PR). The purpose of this change is to eliminate the need to set up a separate script in order to format each package. For example, instead of using the following YAML configuration: scripts: format: description: Format Dart code. run: dart format . format:check: description: Check formatting of Dart code. run: dart format --output none --set-exit-if-changed . The first script can now be replaced with a simpler command: melos format. Similarly, the second script can be replaced with melos format --output none --set-exit-if-changed. Additionally, this new command supports all melos filtering options and concurrency.
- Loading branch information
1 parent
d733616
commit e0491f5
Showing
9 changed files
with
539 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
--- | ||
title: Format Command | ||
description: Learn more about the `format` command in Melos. | ||
--- | ||
|
||
# Format Command | ||
|
||
<Info>Supports all [Melos filtering](/filters) flags.</Info> | ||
|
||
The format command is used to format the code in your Melos workspace according | ||
to Dart's formatting standards. | ||
|
||
```bash | ||
melos format | ||
``` | ||
|
||
<Info> | ||
To learn more, visit the [Dart format](https://dart.dev/tools/dart-format) | ||
documentation. | ||
</Info> | ||
|
||
|
||
## --set-exit-if-changed | ||
Return exit code 1 if there are any formatting changes. This flag is | ||
particularly useful in CI/CD pipelines to automatically detect and reject | ||
commits that do not adhere to the formatting standards, ensuring code quality. | ||
|
||
```bash | ||
melos format --set-exit-if-changed | ||
``` | ||
|
||
<Info> | ||
By default, dart format overwrites the Dart files. | ||
</Info> | ||
|
||
## --output | ||
This option is useful when you want to review formatting changes without | ||
directly overwriting your files. | ||
|
||
```bash | ||
melos format --output | ||
# or | ||
melos format --o | ||
``` | ||
|
||
Outputs the formatted code to the console. | ||
|
||
```bash | ||
melos format -o show | ||
``` | ||
|
||
Outputs the formatted code as a JSON object | ||
|
||
```bash | ||
melos format -o json | ||
``` | ||
|
||
Lists the files that would be formatted, without showing the formatted content | ||
or making changes. | ||
|
||
```bash | ||
melos format -o none | ||
``` | ||
|
||
## concurrency (-c) | ||
Defines the max concurrency value of how many packages will execute the command | ||
in at any one time. Defaults to `1`. | ||
|
||
```bash | ||
# Set a 5 concurrency | ||
melos format -c 5 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/* | ||
* Copyright (c) 2016-present Invertase Limited & Contributors | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this library except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
* | ||
*/ | ||
|
||
import '../commands/runner.dart'; | ||
import 'base.dart'; | ||
|
||
class FormatCommand extends MelosCommand { | ||
FormatCommand(super.config) { | ||
setupPackageFilterParser(); | ||
argParser.addOption('concurrency', defaultsTo: '1', abbr: 'c'); | ||
argParser.addFlag( | ||
'set-exit-if-changed', | ||
negatable: false, | ||
help: 'Return exit code 1 if there are any formatting changes.', | ||
); | ||
argParser.addOption( | ||
'output', | ||
help: 'Set where to write formatted output.\n' | ||
'[json] Print code and selection as JSON.\n' | ||
'[none] Discard output.\n' | ||
'[show] Print code to terminal.\n' | ||
'[write] Overwrite formatted files on disk.\n', | ||
abbr: 'o', | ||
); | ||
} | ||
|
||
@override | ||
final String name = 'format'; | ||
|
||
@override | ||
final String description = 'Idiomatically format Dart source code.'; | ||
|
||
@override | ||
Future<void> run() async { | ||
final setExitIfChanged = argResults?['set-exit-if-changed'] as bool; | ||
final output = argResults?['output'] as String?; | ||
final concurrency = int.parse(argResults!['concurrency'] as String); | ||
|
||
final melos = Melos(logger: logger, config: config); | ||
|
||
return melos.format( | ||
global: global, | ||
packageFilters: parsePackageFilters(config.path), | ||
concurrency: concurrency, | ||
setExitIfChanged: setExitIfChanged, | ||
output: output, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
part of 'runner.dart'; | ||
|
||
mixin _FormatMixin on _Melos { | ||
Future<void> format({ | ||
GlobalOptions? global, | ||
PackageFilters? packageFilters, | ||
int concurrency = 1, | ||
bool setExitIfChanged = false, | ||
String? output, | ||
}) async { | ||
final workspace = | ||
await createWorkspace(global: global, packageFilters: packageFilters); | ||
final packages = workspace.filteredPackages.values; | ||
|
||
await _formatForAllPackages( | ||
workspace, | ||
packages, | ||
concurrency: concurrency, | ||
setExitIfChanged: setExitIfChanged, | ||
output: output, | ||
); | ||
} | ||
|
||
Future<void> _formatForAllPackages( | ||
MelosWorkspace workspace, | ||
Iterable<Package> packages, { | ||
required int concurrency, | ||
required bool setExitIfChanged, | ||
String? output, | ||
}) async { | ||
final failures = <String, int?>{}; | ||
final pool = Pool(concurrency); | ||
final formatArgs = [ | ||
'dart', | ||
'format', | ||
if (setExitIfChanged) '--set-exit-if-changed', | ||
if (output != null) '--output $output', | ||
'.', | ||
]; | ||
final formatArgsString = formatArgs.join(' '); | ||
final prefixLogs = concurrency != 1 && packages.length != 1; | ||
|
||
logger.command('melos format', withDollarSign: true); | ||
|
||
logger | ||
.child(targetStyle(formatArgsString)) | ||
.child('$runningLabel (in ${packages.length} packages)') | ||
.newLine(); | ||
if (prefixLogs) { | ||
logger.horizontalLine(); | ||
} | ||
|
||
final packageResults = Map.fromEntries( | ||
packages.map((package) => MapEntry(package.name, Completer<int?>())), | ||
); | ||
|
||
await pool.forEach<Package, void>(packages, (package) async { | ||
if (!prefixLogs) { | ||
logger | ||
..horizontalLine() | ||
..log(AnsiStyles.bgBlack.bold.italic('${package.name}:')); | ||
} | ||
|
||
final packageExitCode = await _formatForPackage( | ||
workspace, | ||
package, | ||
formatArgs, | ||
prefixLogs: prefixLogs, | ||
); | ||
|
||
packageResults[package.name]?.complete(packageExitCode); | ||
|
||
if (packageExitCode > 0) { | ||
failures[package.name] = packageExitCode; | ||
} else if (!prefixLogs) { | ||
logger.log( | ||
AnsiStyles.bgBlack.bold.italic('${package.name}: ') + | ||
AnsiStyles.bgBlack(successLabel), | ||
); | ||
} | ||
}).drain<void>(); | ||
|
||
logger | ||
..horizontalLine() | ||
..newLine() | ||
..command('melos format', withDollarSign: true); | ||
|
||
final resultLogger = logger.child(targetStyle(formatArgsString)); | ||
|
||
if (failures.isNotEmpty) { | ||
final failuresLogger = | ||
resultLogger.child('$failedLabel (in ${failures.length} packages)'); | ||
for (final packageName in failures.keys) { | ||
failuresLogger.child( | ||
'${errorPackageNameStyle(packageName)} ' | ||
'${failures[packageName] == null ? '(dependency failed)' : '(' | ||
'with exit code ${failures[packageName]})'}', | ||
); | ||
} | ||
exitCode = 1; | ||
} else { | ||
resultLogger.child(successLabel); | ||
} | ||
} | ||
|
||
Future<int> _formatForPackage( | ||
MelosWorkspace workspace, | ||
Package package, | ||
List<String> formatArgs, { | ||
bool prefixLogs = true, | ||
}) async { | ||
final packagePrefix = '[${AnsiStyles.blue.bold(package.name)}]: '; | ||
|
||
final environment = { | ||
EnvironmentVariableKey.melosRootPath: config.path, | ||
if (workspace.sdkPath != null) | ||
EnvironmentVariableKey.melosSdkPath: workspace.sdkPath!, | ||
if (workspace.childProcessPath != null) | ||
EnvironmentVariableKey.path: workspace.childProcessPath!, | ||
}; | ||
|
||
return startCommand( | ||
formatArgs, | ||
logger: logger, | ||
environment: environment, | ||
workingDirectory: package.path, | ||
prefix: prefixLogs ? packagePrefix : null, | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.