From 14dc201632cc974ba7796c645a9b5be9c1a17c08 Mon Sep 17 00:00:00 2001 From: Christian Budde Christensen Date: Tue, 3 Sep 2024 14:47:30 +0100 Subject: [PATCH 1/4] docs: Add project to list of Melos users (#752) Thank you for a great project. --- docs/index.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.mdx b/docs/index.mdx index 23c679cc..0cbf8694 100644 --- a/docs/index.mdx +++ b/docs/index.mdx @@ -64,6 +64,7 @@ The following projects are using Melos: - [FlutterGen/flutter_gen](https://github.com/FlutterGen/flutter_gen) - [canonical/ubuntu-desktop-provision](https://github.com/canonical/ubuntu-desktop-provision) - [ubuntu/app-center](https://github.com/ubuntu/app-center) +- [heftapp/graphql_codegen](https://github.com/heftapp/graphql_codegen) - [leonardocustodio/polkadart](https://github.com/leonardocustodio/polkadart) - [powersync/powersync.dart](https://github.com/powersync-ja/powersync.dart) From 7d67ba0669c19554e5c55881d71b74904da30839 Mon Sep 17 00:00:00 2001 From: LinXunFeng Date: Fri, 13 Sep 2024 15:06:17 +0800 Subject: [PATCH 2/4] docs: remove list --all flag (#753) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description The list --all flag was removed in https://github.com/invertase/melos/pull/275, but the documentation was not updated. ## Type of Change - [ ] โœจ `feat` -- New feature (non-breaking change which adds functionality) - [ ] ๐Ÿ› ๏ธ `fix` -- Bug fix (non-breaking change which fixes an issue) - [ ] โŒ `!` -- Breaking change (fix or feature that would cause existing functionality to change) - [ ] ๐Ÿงน `refactor` -- Code refactor - [ ] โœ… `ci` -- Build configuration change - [x] ๐Ÿ“ `docs` -- Documentation - [ ] ๐Ÿ—‘๏ธ `chore` -- Chore --- docs/commands/list.mdx | 9 --------- 1 file changed, 9 deletions(-) diff --git a/docs/commands/list.mdx b/docs/commands/list.mdx index cadd3cb6..32422452 100644 --- a/docs/commands/list.mdx +++ b/docs/commands/list.mdx @@ -22,15 +22,6 @@ melos list --long melos list -l ``` -## --all (-a) - -Show private packages that are hidden by default. Defaults to `false`. - -```bash -melos list --all -melos list -a -``` - ## --relative (-r) When printing output, use package paths relative to the root of the workspace. From dc05a52c21796dd1c2455d0ddc2f2981b88e8d9c Mon Sep 17 00:00:00 2001 From: Rody Davis <31253215+rodydavis@users.noreply.github.com> Date: Fri, 13 Sep 2024 00:07:13 -0700 Subject: [PATCH 3/4] docs: Add signals to list of projects using Melos (#754) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description ## Type of Change - [ ] โœจ `feat` -- New feature (non-breaking change which adds functionality) - [ ] ๐Ÿ› ๏ธ `fix` -- Bug fix (non-breaking change which fixes an issue) - [ ] โŒ `!` -- Breaking change (fix or feature that would cause existing functionality to change) - [ ] ๐Ÿงน `refactor` -- Code refactor - [ ] โœ… `ci` -- Build configuration change - [ ] ๐Ÿ“ `docs` -- Documentation - [ ] ๐Ÿ—‘๏ธ `chore` -- Chore --- packages/melos/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/melos/README.md b/packages/melos/README.md index bde589d1..7a57e8d5 100644 --- a/packages/melos/README.md +++ b/packages/melos/README.md @@ -156,6 +156,7 @@ The following projects are using Melos: - [ubuntu/app-center](https://github.com/ubuntu/app-center) - [jhomlala/alice](https://github.com/jhomlala/alice) - [powersync/powersync.dart](https://github.com/powersync-ja/powersync.dart) +- [rodydavis/signals.dart](https://github.com/rodydavis/signals.dart) > Submit a PR if you'd like to add your project to the list. Update the > [README.md](https://github.com/invertase/melos/edit/main/packages/melos/README.md) From a3784c16bd6337b3baba1953b5e52cb33fa25f43 Mon Sep 17 00:00:00 2001 From: Jessica Tarra Date: Fri, 13 Sep 2024 11:52:32 +0200 Subject: [PATCH 4/4] fix: maintain working directory across script steps (#711) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR addresses the issue raised in #470, where the working directory is not maintained across multiple steps in a script. For example, consider the following script: ```yaml fail: steps: - cd packages/ - pwd ``` When running this script using melos run, the output will show the workspace root directory, not the `packages/` directory as expected. This PR introduces a change to ensure that the working directory is properly maintained across multiple steps in a script. The key changes are: - Modify the script execution logic to preserve the working directory from one step to the next. - Add a safeguard to prevent users from mixing && operators with multi-step scripts, as this can lead to unexpected behavior. By running the script defined in the issue with the fix implemented in this PR, the output is now: ```shell โžœ melos git:(bugfix/melos_run_script_steps_working_directory) โœ— melos success Building package executable... Built melos:melos. melos run success โ””> cd packages/ โ””> RUNNING melos run success โ””> cd packages/ โ””> SUCCESS melos run success โ””> pwd โ””> RUNNING /Users/jessicatarra/Development/melos/packages melos run success โ””> pwd โ””> SUCCESS ``` ## Type of Change - [ ] โœจ `feat` -- New feature (non-breaking change which adds functionality) - [x] ๐Ÿ› ๏ธ `fix` -- Bug fix (non-breaking change which fixes an issue) - [ ] โŒ `!` -- Breaking change (fix or feature that would cause existing functionality to change) - [x] ๐Ÿงน `refactor` -- Code refactor - [ ] โœ… `ci` -- Build configuration change - [ ] ๐Ÿ“ `docs` -- Documentation - [ ] ๐Ÿ—‘๏ธ `chore` -- Chore --- packages/melos/lib/src/commands/run.dart | 47 ++---- packages/melos/lib/src/commands/runner.dart | 1 + .../lib/src/common/persistent_shell.dart | 108 ++++++++++++ packages/melos/lib/src/common/utils.dart | 4 + packages/melos/lib/src/logging.dart | 44 +++++ packages/melos/test/commands/run_test.dart | 155 ++++++++---------- 6 files changed, 234 insertions(+), 125 deletions(-) create mode 100644 packages/melos/lib/src/common/persistent_shell.dart diff --git a/packages/melos/lib/src/commands/run.dart b/packages/melos/lib/src/commands/run.dart index d31fb8d0..4fa16795 100644 --- a/packages/melos/lib/src/commands/run.dart +++ b/packages/melos/lib/src/commands/run.dart @@ -275,48 +275,25 @@ mixin _RunMixin on _Melos { Script script, Map environment, ) async { - for (final step in steps) { - final scriptCommand = _buildScriptCommand(step, scripts); - - final scriptSourceCode = targetStyle( - step.withoutTrailing('\n'), - ); - - await _executeAndLogCommand( - script, - scriptSourceCode, - scriptCommand, - environment, - ); - } - } - - Future _executeAndLogCommand( - Script script, - String scriptSourceCode, - String scriptCommand, - Map environment, - ) async { - logger.command('melos run ${script.name}'); - logger.child(scriptSourceCode).child(runningLabel).newLine(); - - final exitCode = await startCommand( - [scriptCommand], + final shell = PersistentShell( logger: logger, - environment: environment, workingDirectory: config.path, + environment: environment, ); - logger.newLine(); + await shell.startShell(); logger.command('melos run ${script.name}'); - final resultLogger = logger.child(scriptSourceCode); - if (exitCode != 0) { - resultLogger.child(failedLabel); - } else { - resultLogger.child(successLabel); + for (final step in steps) { + final scriptCommand = _buildScriptCommand(step, scripts); + + final shouldContinue = await shell.sendCommand(scriptCommand); + if (!shouldContinue) { + break; + } } - logger.newLine(); + + await shell.stopShell(); } } diff --git a/packages/melos/lib/src/commands/runner.dart b/packages/melos/lib/src/commands/runner.dart index 6f7f7e5c..465233bf 100644 --- a/packages/melos/lib/src/commands/runner.dart +++ b/packages/melos/lib/src/commands/runner.dart @@ -29,6 +29,7 @@ import '../common/glob.dart'; import '../common/intellij_project.dart'; import '../common/io.dart'; import '../common/pending_package_update.dart'; +import '../common/persistent_shell.dart'; import '../common/platform.dart'; import '../common/utils.dart' as utils; import '../common/utils.dart'; diff --git a/packages/melos/lib/src/common/persistent_shell.dart b/packages/melos/lib/src/common/persistent_shell.dart new file mode 100644 index 00000000..fec64d41 --- /dev/null +++ b/packages/melos/lib/src/common/persistent_shell.dart @@ -0,0 +1,108 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import '../logging.dart'; +import 'environment_variable_key.dart'; +import 'platform.dart'; +import 'utils.dart'; + +class PersistentShell { + PersistentShell({ + required this.logger, + required this.environment, + this.workingDirectory, + }); + + final _isWindows = currentPlatform.isWindows; + final MelosLogger logger; + final Map environment; + final String? workingDirectory; + late final Process _process; + Completer? _commandCompleter; + final String _successEndMarker = '__SUCCESS_COMMAND_END__'; + final String _failureEndMarker = '__FAILURE_COMMAND_END__'; + + Future startShell() async { + final executable = _isWindows ? 'cmd.exe' : '/bin/sh'; + + _process = await Process.start( + executable, + [], + workingDirectory: workingDirectory, + environment: { + ...environment, + EnvironmentVariableKey.melosTerminalWidth: terminalWidth.toString(), + }, + ); + + _listenToProcessStream(_process.stdout); + _listenToProcessStream(_process.stderr, isError: true); + } + + Future sendCommand(String command) async { + assert(_commandCompleter == null, 'A command is already in progress.'); + _commandCompleter = Completer(); + + final fullCommand = _buildFullCommand(command); + _process.stdin.writeln(fullCommand); + + return _awaitCommandCompletion(); + } + + Future stopShell() async { + await _process.stdin.close(); + final exitCode = await _process.exitCode; + if (exitCode == 0) { + logger.log(successLabel); + return; + } + logger.log(failedLabel); + } + + Future _awaitCommandCompletion() async { + try { + await _commandCompleter!.future; + return true; + } catch (e) { + return false; + } finally { + _commandCompleter = null; + } + } + + void _listenToProcessStream( + Stream> stream, { + bool isError = false, + }) { + stream.listen((event) { + final output = utf8.decode(event, allowMalformed: true); + logger.logAndCompleteBasedOnMarkers( + output, + _successEndMarker, + _failureEndMarker, + _commandCompleter, + isError: isError, + ); + }); + } + + String _buildFullCommand(String command) { + final formattedScriptStep = + targetStyle(command.addStepPrefixEmoji().withoutTrailing('\n')); + final echoCommand = 'echo "$formattedScriptStep"'; + final echoSuccess = 'echo $_successEndMarker'; + final echoFailure = 'echo $_failureEndMarker'; + + if (_isWindows) { + return ''' + $echoCommand && $command || VER>NUL && if %ERRORLEVEL% NEQ 0 ($echoFailure) else ($echoSuccess) + '''; + } + + return ''' + $echoCommand && $command || true && if [ \$? -ne 0 ]; + then $echoFailure; else $echoSuccess; fi + '''; + } +} diff --git a/packages/melos/lib/src/common/utils.dart b/packages/melos/lib/src/common/utils.dart index bbc75f37..61547b3e 100644 --- a/packages/melos/lib/src/common/utils.dart +++ b/packages/melos/lib/src/common/utils.dart @@ -73,6 +73,10 @@ final melosPackageUri = Uri.parse('package:melos/melos.dart'); final _camelCasedDelimiterRegExp = RegExp(r'[_\s-]+'); extension StringUtils on String { + String addStepPrefixEmoji() { + return 'โžก๏ธ step: $this'; + } + String indent(String indent) { final split = this.split('\n'); diff --git a/packages/melos/lib/src/logging.dart b/packages/melos/lib/src/logging.dart index 0f557531..141201c7 100644 --- a/packages/melos/lib/src/logging.dart +++ b/packages/melos/lib/src/logging.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:ansi_styles/ansi_styles.dart'; import 'package:cli_util/cli_logging.dart'; @@ -67,6 +69,48 @@ class MelosLogger with _DelegateLogger { write(message); } + void logAndCompleteBasedOnMarkers( + String message, + String successMarker, + String failureMarker, + Completer? completer, { + bool isError = false, + }) { + final modifiedMessage = _processMessageBasedOnMarkers( + message, + successMarker, + failureMarker, + completer, + ); + _logMessage(modifiedMessage, isError); + } + + String _processMessageBasedOnMarkers( + String message, + String successMarker, + String failureMarker, + Completer? completer, + ) { + if (message.contains(successMarker)) { + completer?.complete(); + return message.replaceAll(successMarker, ''); + } + + if (message.contains(failureMarker)) { + completer?.complete(); + return message.replaceAll(failureMarker, ''); + } + + return message; + } + + void _logMessage(String message, bool isError) { + if (isError) { + error(message); + } + write(message); + } + void command(String command, {bool withDollarSign = false}) { if (withDollarSign) { stdout('${commandColor(r'$')} ${commandStyle(command)}'); diff --git a/packages/melos/test/commands/run_test.dart b/packages/melos/test/commands/run_test.dart index 70242e0f..6b3a94e8 100644 --- a/packages/melos/test/commands/run_test.dart +++ b/packages/melos/test/commands/run_test.dart @@ -437,6 +437,53 @@ melos run test_script group('multiple scripts', () { late Directory aDir; + test( + ''' +Verify that multiple script steps are executed sequentially in a persistent +shell. When the script changes directory to "packages" and runs "ls -la", +it should list the contents including the package named "this is package A". + ''', + () async { + final workspaceDir = await createTemporaryWorkspace( + runPubGet: true, + configBuilder: (path) => MelosWorkspaceConfig( + path: path, + name: 'test_package', + packages: [ + createGlob('packages/**', currentDirectoryPath: path), + ], + scripts: const Scripts({ + 'cd_script': Script( + name: 'cd_script', + steps: ['cd packages', 'ls -la', 'pwd'], + ), + }), + ), + ); + + await createProject( + workspaceDir, + const PubSpec(name: 'this is package A'), + ); + + final logger = TestLogger(); + final config = + await MelosWorkspaceConfig.fromWorkspaceRoot(workspaceDir); + final melos = Melos( + logger: logger, + config: config, + ); + + await melos.run(scriptName: 'cd_script', noSelect: true); + + expect( + logger.output.normalizeNewLines(), + contains('this is package A'), + ); + }, + timeout: const Timeout(Duration(minutes: 1)), + ); + test( 'verifies that a melos script can successfully call another ' 'script as a step and execute commands', () async { @@ -480,9 +527,7 @@ melos run test_script ignoringDependencyMessages( ''' melos run hello_script - โ””> test_script - โ””> RUNNING - +โžก๏ธ step: melos run test_script melos run test_script โ””> echo "test_script" โ””> RUNNING @@ -493,20 +538,10 @@ melos run test_script โ””> echo "test_script" โ””> SUCCESS -melos run hello_script - โ””> test_script - โ””> SUCCESS - -melos run hello_script - โ””> echo "hello world" - โ””> RUNNING - +โžก๏ธ step: echo hello world ${currentPlatform.isWindows ? '"hello world"' : 'hello world'} -melos run hello_script - โ””> echo "hello world" - โ””> SUCCESS - +SUCCESS ''', ), ); @@ -601,44 +636,20 @@ melos run hello_script ignoringDependencyMessages( ''' melos run hello_script - โ””> test_script - โ””> RUNNING - +โžก๏ธ step: melos run test_script melos run test_script - โ””> echo "test_script_1" - โ””> RUNNING - +โžก๏ธ step: echo test_script_1 ${currentPlatform.isWindows ? '"test_script_1"' : 'test_script_1'} -melos run test_script - โ””> echo "test_script_1" - โ””> SUCCESS - -melos run test_script - โ””> echo "test_script_2" - โ””> RUNNING - +โžก๏ธ step: echo test_script_2 ${currentPlatform.isWindows ? '"test_script_2"' : 'test_script_2'} -melos run test_script - โ””> echo "test_script_2" - โ””> SUCCESS - - -melos run hello_script - โ””> test_script - โ””> SUCCESS - -melos run hello_script - โ””> echo "hello world" - โ””> RUNNING +SUCCESS +โžก๏ธ step: echo hello world ${currentPlatform.isWindows ? '"hello world"' : 'hello world'} -melos run hello_script - โ””> echo "hello world" - โ””> SUCCESS - +SUCCESS ''', ), ); @@ -707,9 +718,7 @@ melos run hello_script ignoringDependencyMessages( ''' melos run hello_script - โ””> analyze - โ””> RUNNING - +โžก๏ธ step: melos analyze \$ melos analyze โ””> dart analyze โ””> RUNNING (in 3 packages) @@ -739,20 +748,10 @@ c: SUCCESS โ””> dart analyze โ””> SUCCESS -melos run hello_script - โ””> analyze - โ””> SUCCESS - -melos run hello_script - โ””> echo "hello world" - โ””> RUNNING - +โžก๏ธ step: echo hello world ${currentPlatform.isWindows ? '"hello world"' : 'hello world'} -melos run hello_script - โ””> echo "hello world" - โ””> SUCCESS - +SUCCESS ''', ), ); @@ -814,9 +813,7 @@ melos run hello_script ignoringDependencyMessages( ''' melos run hello_script - โ””> list - โ””> RUNNING - +โžก๏ธ step: melos run list melos run list โ””> echo "list script" โ””> RUNNING @@ -827,20 +824,10 @@ melos run list โ””> echo "list script" โ””> SUCCESS -melos run hello_script - โ””> list - โ””> SUCCESS - -melos run hello_script - โ””> echo "hello world" - โ””> RUNNING - +โžก๏ธ step: echo hello world ${currentPlatform.isWindows ? '"hello world"' : 'hello world'} -melos run hello_script - โ””> echo "hello world" - โ””> SUCCESS - +SUCCESS ''', ), ); @@ -909,9 +896,7 @@ melos run hello_script ignoringDependencyMessages( ''' melos run hello_script - โ””> analyze --fatal-infos - โ””> RUNNING - +โžก๏ธ step: melos analyze --fatal-infos \$ melos analyze โ””> dart analyze --fatal-infos โ””> RUNNING (in 3 packages) @@ -941,20 +926,10 @@ c: SUCCESS โ””> FAILED (in 1 packages) โ””> a (with exit code 1) -melos run hello_script - โ””> analyze --fatal-infos - โ””> FAILED - -melos run hello_script - โ””> echo "hello world" - โ””> RUNNING - +โžก๏ธ step: echo hello world ${currentPlatform.isWindows ? '"hello world"' : 'hello world'} -melos run hello_script - โ””> echo "hello world" - โ””> SUCCESS - +SUCCESS ''', ), );