diff --git a/.github/workflows/static-analysis.yaml b/.github/workflows/static-analysis.yaml index 556d5d0..faef945 100644 --- a/.github/workflows/static-analysis.yaml +++ b/.github/workflows/static-analysis.yaml @@ -1,6 +1,8 @@ name: Static Analysis on: push: + branches: + - master pull_request: concurrency: @@ -23,15 +25,20 @@ jobs: echo "$HOME/.pub-cache/bin" >> $GITHUB_PATH fvm install 3.10.0 --setup fvm install stable --setup + fvm global 3.10.0 + fvm flutter doctor fvm global stable + fvm flutter doctor - name: Pub get run: dart pub get - name: Format - run: dart format . --set-exit-if-changed + run: dart format --set-exit-if-changed . - name: Analyze - run: dart analyze + run: dart analyze --fatal-infos + - name: custom_lint + run: dart run custom_lint - name: Test - run: dart test --timeout 120s --concurrency 1 + run: dart test - name: Pana run: | dart pub global activate pana diff --git a/.gitignore b/.gitignore index 2c8ce07..35c4ba9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,6 @@ build/ pubspec.lock # macOS -.DS_Store \ No newline at end of file +.DS_Store + +*.log \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ebadd6..7778567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.36.0 + +- Improves exit codes +- Adds safety checks for FVM usage +- Skips `pub get` in all example projects +- Bumps Dart SDK constraint to `^3.0.0` + ## 1.35.0 - `puby link` no longer runs `pub get --offline` in flutter example projects - Fixes `--no-fvm` flag for `puby link` diff --git a/analysis_options.yaml b/analysis_options.yaml index 9ecdaf3..1b1aabe 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1 +1 @@ -include: package:rexios_lints/dart/package.yaml +include: package:rexios_lints/dart/package_extra.yaml diff --git a/bin/commands.dart b/bin/commands.dart index f6042d8..f7dbab4 100644 --- a/bin/commands.dart +++ b/bin/commands.dart @@ -1,9 +1,10 @@ import 'dart:convert'; import 'dart:io'; +import 'package:io/io.dart'; import 'package:puby/command.dart'; +import 'package:io/ansi.dart'; import 'package:puby/engine.dart'; -import 'package:puby/pens.dart'; import 'package:puby/project.dart'; import 'package:puby/time.dart'; @@ -65,7 +66,7 @@ abstract class Commands { // hanging if (!command.silent) { print( - redPen( + red.wrap( 'Run `fvm install ${flutterVersionNotInstalledMatch[1]}` first', ), ); @@ -87,7 +88,7 @@ extension ProjectCommandExtension on ProjectCommand { if (resolved.exclude) return 0; final finalArgs = [ - if (!raw) ...resolved.engine.args, + if (!raw) ...resolved.engine.prefixArgs, ...args, ]; @@ -95,7 +96,7 @@ extension ProjectCommandExtension on ProjectCommand { final pathString = resolved.path == '.' ? 'current directory' : resolved.path; if (!silent) { - print(greenPen('Running "$argString" in $pathString...')); + print(green.wrap('Running "$argString" in $pathString...')); } final process = await Process.start( @@ -124,12 +125,12 @@ extension ProjectCommandExtension on ProjectCommand { .map(_decoder.convert) .listen((line) { if (!silent) { - stderr.write(redPen(line)); + stderr.write(red.wrap(line)); } err.add(line); }).asFuture(); - final processExitCode = await process.exitCode; + final exitCode = await process.exitCode; if (!killed) { // If we do not wait for these streams to finish, output could end up @@ -140,38 +141,20 @@ extension ProjectCommandExtension on ProjectCommand { } stopwatch.stop(); + // Skip error handling if the command was successful or this is a raw command - if (raw || processExitCode == 0) { + if (raw || exitCode == ExitCode.success.code) { print( - greenPen( + green.wrap( 'Ran "$argString" in $pathString (${stopwatch.prettyPrint()})', ), ); - - return processExitCode; - } - - if (err.any( - (e) => e.contains( - 'Flutter users should run `flutter pub get` instead of `dart pub get`.', - ), - )) { - // If a project doesn't explicitly depend on flutter, it is not possible - // to know if it's dependencies require flutter. So retry if that's the - // reason for failure. - print(yellowPen('Retrying with "flutter" engine')); - return runInProject(resolved.copyWith(engine: Engine.flutter)); - } - - final unknownSubcommandMatch = - RegExp(r'Could not find a subcommand named "(.+?)" for ".+? pub"\.') - .firstMatch(err.join('\n')); - if (unknownSubcommandMatch != null) { + } else if (exitCode == ExitCode.usage.code) { // Do not attempt to run in other projects if the command is unknown - print(redPen('Unknown command: ${unknownSubcommandMatch[1]}')); - exit(1); + print(red.wrap('Unknown command. Exiting...')); + exit(exitCode); } - return processExitCode; + return exitCode; } } diff --git a/bin/fvm.dart b/bin/fvm.dart new file mode 100644 index 0000000..a717d49 --- /dev/null +++ b/bin/fvm.dart @@ -0,0 +1,33 @@ +import 'dart:io'; + +import 'package:pub_semver/pub_semver.dart'; +import 'package:io/ansi.dart'; + +final minFvmVersion = Version.parse('3.0.0'); + +void fvmCheck() { + try { + final fvmVersionResult = Process.runSync('fvm', ['--version']); + final fvmVersion = Version.parse(fvmVersionResult.stdout.toString().trim()); + if (fvmVersion < minFvmVersion) { + print( + yellow.wrap( + ''' +This version of puby expects FVM version $minFvmVersion or higher +FVM version $fvmVersion is installed +Commands in projects configured with FVM may fail +''', + ), + ); + } + } catch (e) { + print( + red.wrap( + ''' +FVM is not installed +Commands in projects configured with FVM will fail +''', + ), + ); + } +} diff --git a/bin/link.dart b/bin/link.dart index 381bd40..ec97434 100644 --- a/bin/link.dart +++ b/bin/link.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flutter_tools_task_queue/flutter_tools_task_queue.dart'; import 'package:puby/command.dart'; -import 'package:puby/pens.dart'; +import 'package:io/ansi.dart'; import 'package:puby/project.dart'; import 'package:puby/pub.dart'; import 'package:puby/time.dart'; @@ -42,15 +42,17 @@ Future linkDependencies({ dependencies.addAll(result.packages); print('Resolved dependencies for ${resolved.path}'); } catch (e) { - print(redPen('Failed to resolve dependencies for ${resolved.path}')); - print(redPen(e)); + print( + red.wrap('Failed to resolve dependencies for ${resolved.path}'), + ); + print(red.wrap(e.toString())); } }), ); } await resolutionQueue.tasksComplete; print( - greenPen( + green.wrap( 'Resolved all dependencies in ${resolutionStopwatch.prettyPrint()}', ), ); @@ -69,16 +71,18 @@ Future linkDependencies({ } } catch (e) { print( - redPen('Failed to download ${package.name} ${package.version}'), + red.wrap('Failed to download ${package.name} ${package.version}'), ); - print(redPen(e)); + print(red.wrap(e.toString())); } }), ); } await downloadQueue.tasksComplete; print( - greenPen('Downloaded all packages in ${downloadStopwatch.prettyPrint()}\n'), + green.wrap( + 'Downloaded all packages in ${downloadStopwatch.prettyPrint()}\n', + ), ); // Stop all stopwatches diff --git a/bin/projects.dart b/bin/projects.dart index 58a2337..a52f6ee 100644 --- a/bin/projects.dart +++ b/bin/projects.dart @@ -6,7 +6,7 @@ import 'package:pubspec_parse/pubspec_parse.dart'; import 'package:puby/command.dart'; import 'package:puby/config.dart'; import 'package:puby/engine.dart'; -import 'package:puby/pens.dart'; +import 'package:io/ansi.dart'; import 'package:puby/project.dart'; import 'package:path/path.dart' as p; @@ -31,18 +31,18 @@ List findProjects() { final path = p.relative(absolutePath); final config = PubyConfig.fromProjectPath(path); - late final Pubspec? pubspec; + final Pubspec pubspec; try { pubspec = Pubspec.parse(pubspecEntity.readAsStringSync()); } catch (e) { - print(redPen('Error parsing pubspec: $path')); - pubspec = null; + print(red.wrap('Error parsing pubspec: $path')); + continue; } final Engine engine; if (fvmPaths.any(absolutePath.startsWith)) { engine = Engine.fvm; - } else if (pubspec?.dependencies['flutter'] != null) { + } else if (pubspec.dependencies['flutter'] != null) { engine = Engine.flutter; } else { engine = Engine.dart; @@ -91,14 +91,13 @@ extension ProjectExtension on Project { } if (message != null && !command.silent) { - print(yellowPen(message)); + print(yellow.wrap(message)); } return newEngine; } bool _defaultExclude(Command command) { - final isPubGetInFlutterExample = engine.isFlutter && - example && + final isPubGetInExample = example && command.args.length >= 2 && command.args[0] == 'pub' && command.args[1] == 'get'; @@ -112,9 +111,9 @@ extension ProjectExtension on Project { } else if (path.startsWith('build/') || path.contains('/build/')) { message = 'Skipping project in build folder: $path'; skip = true; - } else if (isPubGetInFlutterExample) { - // Skip flutter pub get in example projects since flutter does it anyways - message = 'Skipping flutter example project: $path'; + } else if (isPubGetInExample) { + // Skip pub get in example projects since it happens anyways + message = 'Skipping example project: $path'; skip = true; } else { message = null; @@ -122,7 +121,7 @@ extension ProjectExtension on Project { } if (message != null && !command.silent) { - print(yellowPen(message)); + print(yellow.wrap(message)); } return skip; } @@ -132,7 +131,7 @@ extension ProjectExtension on Project { final skip = config.excludes.any(argString.startsWith); if (skip && !command.silent) { - print(yellowPen('Skipping project with exclusion: $path')); + print(yellow.wrap('Skipping project with exclusion: $path')); } return skip; @@ -167,7 +166,7 @@ extension ProjectExtension on Project { } return Version.parse(versionString); } catch (e) { - print(redPen('Unable to determine FVM Flutter version: $path')); + print(red.wrap('Unable to determine FVM Flutter version: $path')); return null; } } diff --git a/bin/puby.dart b/bin/puby.dart index 236ab08..7fe9fba 100644 --- a/bin/puby.dart +++ b/bin/puby.dart @@ -2,14 +2,17 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter_tools_task_queue/flutter_tools_task_queue.dart'; +import 'package:io/io.dart'; import 'package:pub_update_checker/pub_update_checker.dart'; import 'package:puby/command.dart'; -import 'package:puby/pens.dart'; +import 'package:puby/engine.dart'; +import 'package:io/ansi.dart'; import 'package:puby/project.dart'; import 'package:puby/time.dart'; import 'commands.dart'; import 'projects.dart'; +import 'fvm.dart'; const help = ''' Commands: @@ -30,7 +33,7 @@ void main(List arguments) async { final newVersion = await PubUpdateChecker.check(); if (newVersion != null) { print( - yellowPen( + yellow.wrap( 'There is an update available: $newVersion. Run `dart pub global activate puby` to update.', ), ); @@ -41,18 +44,22 @@ void main(List arguments) async { arguments.first == '--help'; if (showHelp) { - print(magentaPen(help)); - exit(1); + print(magenta.wrap(help)); + exit(ExitCode.success.code); } print('Finding projects...'); final projects = findProjects(); if (projects.isEmpty) { - print(redPen('No projects found in the current directory')); - exit(1); + print(red.wrap('No projects found in the current directory')); + exit(ExitCode.usage.code); } - print(greenPen('Found ${projects.length} projects\n')); + print(green.wrap('Found ${projects.length} projects\n')); + + if (projects.any((e) => e.engine == Engine.fvm)) { + fvmCheck(); + } final firstArg = arguments.first; final convenienceCommand = Commands.convenience[firstArg]; @@ -121,13 +128,13 @@ Future runInAllProjects( final time = stopwatch.prettyPrint(); if (exitCode != 0) { - print(redPen('One or more commands failed ($time)')); - print(redPen('Failures:')); + print(red.wrap('One or more commands failed ($time)')); + print(red.wrap('Failures:')); for (final failure in failures) { - print(redPen(' $failure')); + print(red.wrap(' $failure')); } } else { - print(greenPen('All commands succeeded ($time)')); + print(green.wrap('All commands succeeded ($time)')); } print(''); diff --git a/lib/engine.dart b/lib/engine.dart index bebe784..cb956dc 100644 --- a/lib/engine.dart +++ b/lib/engine.dart @@ -13,7 +13,7 @@ enum Engine { bool get isFlutter => {flutter, fvm}.contains(this); /// The arguments required to call the engine - List get args { + List get prefixArgs { switch (this) { case Engine.dart: case Engine.flutter: diff --git a/lib/pens.dart b/lib/pens.dart deleted file mode 100644 index 49720b8..0000000 --- a/lib/pens.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:ansicolor/ansicolor.dart'; - -/// Magenta pen -final magentaPen = AnsiPen()..magenta(); - -/// Green pen -final greenPen = AnsiPen()..green(); - -/// Yellow pen -final yellowPen = AnsiPen()..yellow(); - -/// Red pen -final redPen = AnsiPen()..red(); diff --git a/pubspec.yaml b/pubspec.yaml index 0a4547f..8ec5220 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,24 +1,24 @@ name: puby description: Run commands in all projects in the current directory. Handle monorepos with ease. -version: 1.35.0 +version: 1.36.0 homepage: https://github.com/Rexios80/puby environment: - sdk: ">=2.19.0 <4.0.0" + sdk: ^3.0.0 dependencies: yaml: ^3.1.1 pubspec_parse: ^1.2.1 path: ^1.8.1 - ansicolor: ^2.0.1 pub_update_checker: ^1.2.0 flutter_tools_task_queue: ^1.0.0 - dart_pub: 0.0.1 + dart_pub: 0.0.3 pub_semver: ^2.1.4 + io: ^1.0.4 dev_dependencies: test: ^1.20.2 - rexios_lints: ^6.0.1 + rexios_lints: ^8.2.0 executables: puby: puby diff --git a/test/clean_test.dart b/test/clean_test.dart index 97d5c7e..d9ec358 100644 --- a/test/clean_test.dart +++ b/test/clean_test.dart @@ -1,3 +1,4 @@ +import 'package:io/io.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; @@ -8,7 +9,7 @@ void main() { final result = await testCommand(['clean']); final stdout = result.stdout; - expect(result.exitCode, 0); + expect(result.exitCode, ExitCode.success.code); // dart expectLine(stdout, ['dart_puby_test', 'flutter clean']); diff --git a/test/config_test.dart b/test/config_test.dart new file mode 100644 index 0000000..82280bc --- /dev/null +++ b/test/config_test.dart @@ -0,0 +1,30 @@ +import 'package:io/io.dart'; +import 'package:test/test.dart'; + +import 'test_utils.dart'; +import 'package:path/path.dart' as path; + +void main() { + group('config', () { + test('exclude', () async { + final result = await testCommand( + ['gen'], + projects: { + 'puby_yaml_test': { + 'pubspec.yaml': pubspec('puby_yaml_test'), + 'puby.yaml': ''' +exclude: + - pub run build_runner +''', + }, + }, + ); + final stdout = result.stdout; + + // Since the code generation doesn't actually run the command should succeed + expect(result.exitCode, ExitCode.success.code); + + expectLine(stdout, [path.join('puby_yaml_test'), 'Skip']); + }); + }); +} diff --git a/test/exec_test.dart b/test/exec_test.dart index c0c5418..5c25e94 100644 --- a/test/exec_test.dart +++ b/test/exec_test.dart @@ -1,3 +1,4 @@ +import 'package:io/io.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; @@ -8,7 +9,7 @@ void main() { final result = await testCommand(['exec', 'echo', 'foo']); final stdout = result.stdout; - expect(result.exitCode, 0); + expect(result.exitCode, ExitCode.success.code); // dart expectLine(stdout, [ 'dart_puby_test', diff --git a/test/fvm_version_not_installed_test.dart b/test/fvm_version_not_installed_test.dart index 5c8afc8..102aebc 100644 --- a/test/fvm_version_not_installed_test.dart +++ b/test/fvm_version_not_installed_test.dart @@ -1,3 +1,4 @@ +import 'package:io/io.dart'; import 'package:test/test.dart'; import 'test_utils.dart'; @@ -6,11 +7,16 @@ void main() { test('FVM version not installed', () async { final result = await testCommand( ['get'], - workingDirectory: 'test_resources_2/fvm_version_not_installed_test', + projects: { + 'fvm_version_not_installed_test': { + 'pubspec.yaml': pubspec('fvm_version_not_installed_test'), + '.fvmrc': fvmrc('1.17.0'), + }, + }, ); final stdout = result.stdout; - expect(result.exitCode, isNot(0)); + expect(result.exitCode, isNot(ExitCode.success.code)); expectLine(stdout, ['Run `fvm install 1.17.0` first']); }); } diff --git a/test/gen_test.dart b/test/gen_test.dart index 3cc9884..3a70654 100644 --- a/test/gen_test.dart +++ b/test/gen_test.dart @@ -1,4 +1,4 @@ -import 'package:path/path.dart' as p; +import 'package:io/io.dart'; import 'package:test/test.dart'; import 'test_utils.dart'; @@ -10,22 +10,10 @@ void main() { final result = await testCommand(['gen']); final stdout = result.stdout; - // Since these projects have no code generation, the command should fail - expect(result.exitCode, isNot(0)); + expect(result.exitCode, isNot(ExitCode.success.code)); - // dart expectLine(stdout, ['dart_puby_test', 'dart $argString']); - // Explicit exclusion - expectLine(stdout, [p.join('dart_puby_test', 'example'), 'Skip']); - - // flutter expectLine(stdout, ['flutter_puby_test', 'flutter $argString']); - // Explicit exclusion - expectLine(stdout, [p.join('flutter_puby_test', 'example'), 'Skip']); - - // fvm expectLine(stdout, ['fvm_puby_test', 'fvm flutter $argString']); - // Explicit exclusion - expectLine(stdout, [p.join('fvm_puby_test', 'example'), 'Skip']); }); } diff --git a/test/link_test.dart b/test/link_test.dart index dd5f629..dbc93bc 100644 --- a/test/link_test.dart +++ b/test/link_test.dart @@ -1,48 +1,69 @@ import 'dart:io'; -import 'package:path/path.dart' as p; +import 'package:io/io.dart'; +import 'package:path/path.dart' as path; import 'package:test/test.dart'; import 'test_utils.dart'; void main() { - test('puby link', () async { - final result = await testCommand(['link']); - final stdout = result.stdout; + test( + 'puby link', + () async { + // Pub get must run before link will work in FVM projects + await testCommand(['get']); + final result = await testCommand(['link']); + final stdout = result.stdout; - expect(result.exitCode, 0); + expect(result.exitCode, ExitCode.success.code); - // dart - expectLine(stdout, ['dart_puby_test', 'Resolved dependencies for']); - expectLine(stdout, ['dart_puby_test', 'dart pub get --offline']); - expectLine( - stdout, - [p.join('dart_puby_test', 'example'), 'dart pub get --offline'], - ); + // dart + expectLine(stdout, ['dart_puby_test', 'Resolved dependencies for']); + expectLine(stdout, ['dart_puby_test', 'dart pub get --offline']); + // The pub get should NOT run in the example app + expectLine( + stdout, + [path.join('dart_puby_test', 'example'), 'dart pub get --offline'], + matches: false, + ); - // flutter - expectLine(stdout, ['flutter_puby_test', 'Resolved dependencies for']); - expectLine(stdout, ['flutter_puby_test', 'flutter pub get --offline']); - // The link should run in the example app - expectLine( - stdout, - [p.join('flutter_puby_test', 'example'), 'Resolved dependencies for'], - ); - // The pub get should NOT run in the example app - expectLine( - stdout, - [p.join('flutter_puby_test', 'example'), 'flutter pub get --offline'], - matches: false, - ); + // flutter + expectLine(stdout, ['flutter_puby_test', 'Resolved dependencies for']); + expectLine(stdout, ['flutter_puby_test', 'flutter pub get --offline']); + // The link should run in the example app + expectLine( + stdout, + [ + path.join('flutter_puby_test', 'example'), + 'Resolved dependencies for', + ], + ); + // The pub get should NOT run in the example app + expectLine( + stdout, + [ + path.join('flutter_puby_test', 'example'), + 'flutter pub get --offline', + ], + matches: false, + ); - // fvm - expectLine(stdout, ['fvm_puby_test', 'Resolved dependencies for']); - expectLine(stdout, ['fvm_puby_test', 'fvm flutter pub get --offline']); - // Ensure the correct flutter version was used - expect( - File('test_resources/fvm_puby_test/.dart_tool/version') - .readAsStringSync(), - '3.10.0', - ); - }); + // fvm + expectLine(stdout, ['fvm_puby_test', 'Resolved dependencies for']); + expectLine(stdout, ['fvm_puby_test', 'fvm flutter pub get --offline']); + // Ensure the correct flutter version was used + expect( + File( + path.join( + result.workingDirectory, + 'fvm_puby_test', + '.dart_tool', + 'version', + ), + ).readAsStringSync(), + '3.10.0', + ); + }, + timeout: const Timeout(Duration(seconds: 120)), + ); } diff --git a/test/mup_test.dart b/test/mup_test.dart index 96d8c8f..69205a4 100644 --- a/test/mup_test.dart +++ b/test/mup_test.dart @@ -1,3 +1,4 @@ +import 'package:io/io.dart'; import 'package:path/path.dart' as p; import 'package:test/test.dart'; @@ -12,7 +13,7 @@ void main() { final result = await testCommand(['mup']); final stdout = result.stdout; - expect(result.exitCode, 0); + expect(result.exitCode, ExitCode.success.code); // dart expectLine(stdout, ['dart_puby_test', '"dart $argString"']); diff --git a/test/no_fvm_test.dart b/test/no_fvm_test.dart index 33cf3e7..bf5015e 100644 --- a/test/no_fvm_test.dart +++ b/test/no_fvm_test.dart @@ -1,42 +1,51 @@ import 'dart:io'; +import 'package:io/io.dart'; import 'package:test/test.dart'; -import 'package:path/path.dart' as p; +import 'package:path/path.dart' as path; import 'test_utils.dart'; const message = 'Project uses FVM, but FVM support is disabled'; void main() { - test('--no-fvm', () async { - final result = await testCommand(['get', '--no-fvm']); - final stdout = result.stdout; - - expect(result.exitCode, 0); - expectLine(stdout, ['fvm_puby_test', message]); - // Ensure the FVM Flutter version was not used - expect( - File('test_resources/fvm_puby_test/.dart_tool/version') - .readAsStringSync(), - isNot('3.10.0'), - ); - }); - - test('--no-fvm on convenience command', () async { - final result = await testCommand(['mup', '--no-fvm']); - final stdout = result.stdout; - - expect(result.exitCode, 0); - expectLine(stdout, ['fvm_puby_test', message]); - }); - - test('--no-fvm on link command', () async { - final result = await testCommand(['link', '--no-fvm']); - final stdout = result.stdout; - - expect(result.exitCode, 0); - expectLine(stdout, ['fvm_puby_test', message]); - expectLine(stdout, [p.join('fvm_puby_test', 'example'), message]); - expectLine(stdout, [p.join('fvm_puby_test', 'nested'), message]); + group('--no-fvm', () { + test('on pub get', () async { + final result = await testCommand(['get', '--no-fvm']); + final stdout = result.stdout; + + expect(result.exitCode, ExitCode.success.code); + expectLine(stdout, ['fvm_puby_test', message]); + // Ensure the FVM Flutter version was not used + expect( + File( + path.join( + result.workingDirectory, + 'fvm_puby_test', + '.dart_tool', + 'version', + ), + ).readAsStringSync(), + isNot('3.10.0'), + ); + }); + + test('on convenience command', () async { + final result = await testCommand(['mup', '--no-fvm']); + final stdout = result.stdout; + + expect(result.exitCode, ExitCode.success.code); + expectLine(stdout, ['fvm_puby_test', message]); + }); + + test('on link command', () async { + final result = await testCommand(['link', '--no-fvm']); + final stdout = result.stdout; + + expect(result.exitCode, ExitCode.success.code); + expectLine(stdout, ['fvm_puby_test', message]); + expectLine(stdout, [path.join('fvm_puby_test', 'example'), message]); + expectLine(stdout, [path.join('fvm_puby_test', 'nested'), message]); + }); }); } diff --git a/test/pub_test.dart b/test/pub_test.dart index 249c8d0..62f21d7 100644 --- a/test/pub_test.dart +++ b/test/pub_test.dart @@ -1,50 +1,95 @@ -import 'package:path/path.dart' as p; +import 'package:io/io.dart'; +import 'package:path/path.dart' as path; import 'package:test/test.dart'; import 'test_utils.dart'; void main() { - test('[engine] pub get', () async { - final result = await testCommand(['get']); - final stdout = result.stdout; - - expect(result.exitCode, 0); - - // project in build folder - expectLine(stdout, [p.join('build_folder_test', 'build', 'web'), 'Skip']); - - // dart - expectLine(stdout, ['dart_puby_test', 'dart pub get']); - expectLine(stdout, [p.join('dart_puby_test', 'example'), 'dart pub get']); - - // flutter - expectLine(stdout, ['flutter_puby_test', 'flutter pub get']); - // Default exclusion - expectLine(stdout, [p.join('flutter_puby_test', 'example'), 'Skip']); - // Flutter pub get should run in the example project anyways - expectLine(stdout, ['Resolving dependencies in ./example...']); - - // fvm - expectLine(stdout, ['fvm_puby_test', 'fvm flutter pub get']); - // Default exclusion - expectLine(stdout, [p.join('fvm_puby_test', 'example'), 'Skip']); - // Flutter pub get should run in the example project anyways - // Can't test this with fvm since the output is the same as flutter - // expectLine(stdout, ['example', 'fvm flutter pub get']); - expectLine( - stdout, - [p.join('fvm_puby_test', 'nested'), 'fvm flutter pub get'], - ); - - // invalid_pubspec - expectLine(stdout, ['invalid_pubspec_test', 'Error parsing pubspec']); - - // transitive flutter - // This one should fail - // TODO: This isn't failing anymore for some reason. Remove this feature? - expectLine(stdout, ['transitive_flutter_test', 'dart pub get']); - - // This one should succeed - // expectLine(stdout, ['transitive_flutter_test', 'flutter pub get']); + group('[engine] pub get', () { + test('runs in all projects', () async { + final result = await testCommand(['get']); + final stdout = result.stdout; + + expect(result.exitCode, ExitCode.success.code); + + expectLine(stdout, ['dart_puby_test', 'dart pub get']); + expectLine(stdout, ['flutter_puby_test', 'flutter pub get']); + expectLine(stdout, ['fvm_puby_test', 'fvm flutter pub get']); + expectLine( + stdout, + [path.join('fvm_puby_test', 'nested'), 'fvm flutter pub get'], + ); + }); + + test('handles invlaid pubspec', () async { + final result = await testCommand( + ['get'], + projects: { + 'invalid_pubspec_test': { + 'pubspec.yaml': 'invalid', + }, + }, + ); + final stdout = result.stdout; + + expect(result.exitCode, ExitCode.usage.code); + + expectLine(stdout, ['invalid_pubspec_test', 'Error parsing pubspec']); + }); + + group('excludes', () { + test('project in build folder', () async { + final result = await testCommand( + ['get'], + projects: { + 'build_folder_test': { + 'build/web/pubspec.yaml': pubspec('web'), + }, + }, + ); + final stdout = result.stdout; + + expect(result.exitCode, ExitCode.success.code); + + expectLine( + stdout, + [path.join('build_folder_test', 'build', 'web'), 'Skip'], + ); + }); + + group('example projects', () { + Future skipsExample( + TestProjects projects, { + String match = 'Resolving dependencies in `./example`...', + }) async { + final result = await testCommand(['get'], projects: projects); + final stdout = result.stdout; + + expect(result.exitCode, ExitCode.success.code); + + expectLine( + stdout, + [path.join(projects.keys.first, 'example'), 'Skip'], + ); + expectLine(stdout, [match]); + } + + test('dart', () async { + await skipsExample(dartProject); + }); + + test('flutter', () async { + await skipsExample(flutterProject); + }); + + test('fvm', () async { + await skipsExample( + fvmProject, + // This is different because of the older Flutter version + match: 'Resolving dependencies in ./example...', + ); + }); + }); + }); }); } diff --git a/test/test_test.dart b/test/test_test.dart index 9190228..c081867 100644 --- a/test/test_test.dart +++ b/test/test_test.dart @@ -1,4 +1,4 @@ -import 'package:path/path.dart' as p; +import 'package:io/io.dart'; import 'package:test/test.dart'; import 'test_utils.dart'; @@ -9,21 +9,10 @@ void main() { final stdout = result.stdout; // Since these projects have no tests, the command should fail - expect(result.exitCode, isNot(0)); + expect(result.exitCode, isNot(ExitCode.success.code)); - // dart expectLine(stdout, ['dart_puby_test', 'flutter test --coverage']); - // Explicit exclusion - expectLine(stdout, [p.join('dart_puby_test', 'example'), 'Skip']); - - // flutter expectLine(stdout, ['flutter_puby_test', 'flutter test --coverage']); - // Explicit exclusion - expectLine(stdout, [p.join('flutter_puby_test', 'example'), 'Skip']); - - // fvm expectLine(stdout, ['fvm_puby_test', 'fvm flutter test --coverage']); - // Explicit exclusion - expectLine(stdout, [p.join('fvm_puby_test', 'example'), 'Skip']); }); } diff --git a/test/test_utils.dart b/test/test_utils.dart index 45f78e7..0978468 100644 --- a/test/test_utils.dart +++ b/test/test_utils.dart @@ -1,22 +1,62 @@ +import 'dart:convert'; import 'dart:io'; import 'package:test/test.dart'; +import 'package:path/path.dart' as path; -Future testCommand( +final _decoder = Utf8Decoder(); + +// Map of project name to file paths to file contents +typedef TestProjects = Map>; + +class PubyProcessResult { + final String workingDirectory; + final int exitCode; + final String stdout; + final String stderr; + + PubyProcessResult( + this.workingDirectory, + this.exitCode, + this.stdout, + this.stderr, + ); +} + +Future testCommand( List arguments, { - String workingDirectory = 'test_resources', -}) { - final levels = workingDirectory.split('/').length; - final root = '../' * levels; - return Process.run( + TestProjects? projects, + bool debug = false, +}) async { + final workingDirectory = createTestResources(projects ?? defaultProjects); + final puby = File(path.join('bin', 'puby.dart')).absolute.path; + + final process = await Process.start( 'dart', - ['${root}bin/puby.dart', ...arguments], + [puby, ...arguments], workingDirectory: workingDirectory, ); + + String handleLine(dynamic line) { + final decoded = _decoder.convert(line); + if (debug) stdout.write(decoded); + return decoded; + } + + final processStdout = process.stdout.map(handleLine).join('\n'); + final processStderr = process.stderr.map(handleLine).join('\n'); + + final exitCode = await process.exitCode; + return PubyProcessResult( + workingDirectory, + exitCode, + await processStdout, + await processStderr, + ); } -void expectLine(dynamic stdout, List matchers, {bool matches = true}) { - final lines = (stdout as String).split('\n'); +void expectLine(String stdout, List matchers, {bool matches = true}) { + final lines = stdout.split('\n'); expect( lines.any( (line) => @@ -25,3 +65,73 @@ void expectLine(dynamic stdout, List matchers, {bool matches = true}) { matches, ); } + +String createTestResources(Map> projects) { + final directory = Directory.systemTemp.createTempSync('test_resources'); + for (final MapEntry(:key, :value) in projects.entries) { + final project = key; + final files = value; + for (final MapEntry(:key, :value) in files.entries) { + final file = key; + final content = value; + File(path.join(directory.path, project, file)) + ..createSync(recursive: true) + ..writeAsStringSync(content); + } + } + return directory.path; +} + +String pubspec(String name, {bool flutter = false}) { + var pubspec = ''' +name: $name + +environment: + sdk: ^3.0.0 +'''; + + if (flutter) { + pubspec += ''' +dependencies: + flutter: + sdk: flutter +'''; + } + + return pubspec; +} + +String fvmrc(String version) => ''' +{ + "flutter": "$version", + "flavors": {} +}'''; + +final dartProject = { + 'dart_puby_test': { + 'pubspec.yaml': pubspec('dart_puby_test'), + 'example/pubspec.yaml': pubspec('example'), + }, +}; + +final flutterProject = { + 'flutter_puby_test': { + 'pubspec.yaml': pubspec('flutter_puby_test', flutter: true), + 'example/pubspec.yaml': pubspec('example', flutter: true), + }, +}; + +final fvmProject = { + 'fvm_puby_test': { + 'pubspec.yaml': pubspec('fvm_puby_test', flutter: true), + 'example/pubspec.yaml': pubspec('example', flutter: true), + 'nested/pubspec.yaml': pubspec('nested', flutter: true), + '.fvmrc': fvmrc('3.10.0'), + }, +}; + +final defaultProjects = { + ...dartProject, + ...flutterProject, + ...fvmProject, +}; diff --git a/test/unknown_command_test.dart b/test/unknown_command_test.dart index 8c72670..232f1a1 100644 --- a/test/unknown_command_test.dart +++ b/test/unknown_command_test.dart @@ -1,3 +1,4 @@ +import 'package:io/io.dart'; import 'package:test/test.dart'; import 'test_utils.dart'; @@ -7,7 +8,7 @@ void main() { final result = await testCommand(['asdf']); final stdout = result.stdout; - expect(result.exitCode, isNot(0)); - expectLine(stdout, ['Unknown command: asdf']); + expect(result.exitCode, ExitCode.usage.code); + expectLine(stdout, ['Unknown command. Exiting...']); }); } diff --git a/test_resources/.gitignore b/test_resources/.gitignore deleted file mode 100644 index f568edd..0000000 --- a/test_resources/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -.dart_tool -**/.fvm -!build_folder_test/build \ No newline at end of file diff --git a/test_resources/build_folder_test/build/web/pubspec.yaml b/test_resources/build_folder_test/build/web/pubspec.yaml deleted file mode 100644 index a07a434..0000000 --- a/test_resources/build_folder_test/build/web/pubspec.yaml +++ /dev/null @@ -1,5 +0,0 @@ -name: web -publish_to: none - -environment: - sdk: ">=2.18.0 <3.0.0" diff --git a/test_resources/dart_puby_test/example/pubspec.yaml b/test_resources/dart_puby_test/example/pubspec.yaml deleted file mode 100644 index 3da76f8..0000000 --- a/test_resources/dart_puby_test/example/pubspec.yaml +++ /dev/null @@ -1,5 +0,0 @@ -name: example -publish_to: none - -environment: - sdk: ">=2.16.2 <3.0.0" diff --git a/test_resources/dart_puby_test/example/puby.yaml b/test_resources/dart_puby_test/example/puby.yaml deleted file mode 100644 index 64080c7..0000000 --- a/test_resources/dart_puby_test/example/puby.yaml +++ /dev/null @@ -1,3 +0,0 @@ -exclude: - - test - - pub run build_runner \ No newline at end of file diff --git a/test_resources/dart_puby_test/pubspec.yaml b/test_resources/dart_puby_test/pubspec.yaml deleted file mode 100644 index a27a0d9..0000000 --- a/test_resources/dart_puby_test/pubspec.yaml +++ /dev/null @@ -1,5 +0,0 @@ -name: dart_puby_test -publish_to: none - -environment: - sdk: ">=2.16.2 <3.0.0" diff --git a/test_resources/flutter_puby_test/example/pubspec.yaml b/test_resources/flutter_puby_test/example/pubspec.yaml deleted file mode 100644 index 3776aab..0000000 --- a/test_resources/flutter_puby_test/example/pubspec.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: example -publish_to: none - -environment: - sdk: ">=2.16.2 <3.0.0" - -dependencies: - flutter: - sdk: flutter diff --git a/test_resources/flutter_puby_test/example/puby.yaml b/test_resources/flutter_puby_test/example/puby.yaml deleted file mode 100644 index 64080c7..0000000 --- a/test_resources/flutter_puby_test/example/puby.yaml +++ /dev/null @@ -1,3 +0,0 @@ -exclude: - - test - - pub run build_runner \ No newline at end of file diff --git a/test_resources/flutter_puby_test/pubspec.yaml b/test_resources/flutter_puby_test/pubspec.yaml deleted file mode 100644 index 3e74567..0000000 --- a/test_resources/flutter_puby_test/pubspec.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: flutter_puby_test -publish_to: none - -environment: - sdk: ">=2.16.2 <3.0.0" - -dependencies: - flutter: - sdk: flutter diff --git a/test_resources/fvm_puby_test/.fvmrc b/test_resources/fvm_puby_test/.fvmrc deleted file mode 100644 index ceeca18..0000000 --- a/test_resources/fvm_puby_test/.fvmrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "flutter": "3.10.0", - "flavors": {} -} \ No newline at end of file diff --git a/test_resources/fvm_puby_test/example/pubspec.yaml b/test_resources/fvm_puby_test/example/pubspec.yaml deleted file mode 100644 index 0aa57e8..0000000 --- a/test_resources/fvm_puby_test/example/pubspec.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: example -publish_to: none - -environment: - sdk: ^3.0.0 - -dependencies: - flutter: - sdk: flutter diff --git a/test_resources/fvm_puby_test/example/puby.yaml b/test_resources/fvm_puby_test/example/puby.yaml deleted file mode 100644 index 64080c7..0000000 --- a/test_resources/fvm_puby_test/example/puby.yaml +++ /dev/null @@ -1,3 +0,0 @@ -exclude: - - test - - pub run build_runner \ No newline at end of file diff --git a/test_resources/fvm_puby_test/nested/pubspec.yaml b/test_resources/fvm_puby_test/nested/pubspec.yaml deleted file mode 100644 index 958c00d..0000000 --- a/test_resources/fvm_puby_test/nested/pubspec.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: nested -publish_to: none - -environment: - sdk: ^3.0.0 - -dependencies: - flutter: - sdk: flutter diff --git a/test_resources/fvm_puby_test/pubspec.yaml b/test_resources/fvm_puby_test/pubspec.yaml deleted file mode 100644 index 573d629..0000000 --- a/test_resources/fvm_puby_test/pubspec.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: fvm_puby_test -publish_to: none - -environment: - sdk: ^3.0.0 - -dependencies: - flutter: - sdk: flutter diff --git a/test_resources/invalid_pubspec_test/pubspec.yaml b/test_resources/invalid_pubspec_test/pubspec.yaml deleted file mode 100644 index e69de29..0000000 diff --git a/test_resources/invalid_pubspec_test/puby.yaml b/test_resources/invalid_pubspec_test/puby.yaml deleted file mode 100644 index 508bff7..0000000 --- a/test_resources/invalid_pubspec_test/puby.yaml +++ /dev/null @@ -1,4 +0,0 @@ -exclude: - - test - - pub get - - pub upgrade \ No newline at end of file diff --git a/test_resources/transitive_flutter_test/pubspec.yaml b/test_resources/transitive_flutter_test/pubspec.yaml deleted file mode 100644 index 55a5a31..0000000 --- a/test_resources/transitive_flutter_test/pubspec.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: transitive_flutter_test -publish_to: none - -environment: - sdk: ">=2.16.2 <3.0.0" - -dependencies: - flutter_puby_test: - path: ../flutter_puby_test diff --git a/test_resources_2/.gitignore b/test_resources_2/.gitignore deleted file mode 100644 index f03451b..0000000 --- a/test_resources_2/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -.dart_tool -pubspec.lock -**/.fvm -!build_folder_test/build \ No newline at end of file diff --git a/test_resources_2/fvm_version_not_installed_test/.fvmrc b/test_resources_2/fvm_version_not_installed_test/.fvmrc deleted file mode 100644 index e553dbb..0000000 --- a/test_resources_2/fvm_version_not_installed_test/.fvmrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "flutter": "1.17.0", - "flavors": {} -} \ No newline at end of file diff --git a/test_resources_2/fvm_version_not_installed_test/pubspec.yaml b/test_resources_2/fvm_version_not_installed_test/pubspec.yaml deleted file mode 100644 index 9d25361..0000000 --- a/test_resources_2/fvm_version_not_installed_test/pubspec.yaml +++ /dev/null @@ -1,9 +0,0 @@ -name: fvm_puby_test -publish_to: none - -environment: - sdk: ">=2.16.2 <3.0.0" - -dependencies: - flutter: - sdk: flutter