diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 9aa2365e..8aed620d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -23,7 +23,7 @@ jobs: - name: Checkout uses: actions/checkout@v1 - name: Publish - uses: sakebook/actions-flutter-pub-publisher@v1.2.0 + uses: sakebook/actions-flutter-pub-publisher@v1.3.0 with: credential: ${{ secrets.CREDENTIAL_JSON }} flutter_package: false diff --git a/.gitignore b/.gitignore index 4282cef4..8e9041b1 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ fvm .project .settings/ .vscode/ +coverage/lcov.info +.fvm diff --git a/CHANGELOG.md b/CHANGELOG.md index 658b424e..853cf56b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.8.0 + +- Implemented `--global` flag to set a specific version globally. +- Changed project configuration to allow for versioning. +- Refactoring and project clean-up +- Better user experience +- Improved error messages + ## 0.7.2 - Better compatibility with flutter commands. diff --git a/README.md b/README.md index 7f474521..a839d574 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ You can use different Flutter SDK versions per project. To do that you have to g > fvm use ``` -If you want to use a specific version by default in your machine, you can specify the flag `--global` to the `use` command. A symbolic link to the Flutter version will be created in the `fvm` home folder, which you could then add to your PATH environment variable as follows: `/default/bin` +If you want to use a specific version by default in your machine, you can specify the flag `--global` to the `use` command. A symbolic link to the Flutter version will be created in the `fvm` home folder, which you could then add to your PATH environment variable as follows: `FVM_HOME/default/bin`. Use `fvm use --help`, thsi will give you the exact path you need to configure. ### Remove a SDK Version @@ -66,7 +66,7 @@ Using the remove command will uninstall the SDK version locally. This will impac ### List Installed Versions -List all the versions that are installed on your machine. +List all the versions that are installed on your machine. This command will also output where FVM stores the SDK versions. ```bash > fvm list @@ -107,7 +107,7 @@ This will run `flutter run` command using the local project SDK. If no FVM confi FVM creates a symbolic link within your project called **fvm** which links to the installed version of the SDK. ```bash -> ./fvm run +> .fvm/flutter/bin run ``` This will run `flutter run` command using the local project SDK. @@ -116,17 +116,31 @@ As an example calling `fvm flutter run` is the equivalent of calling `flutter ru ### Configure Your IDE +In some situations you might have to restart your IDE and the Flutter debugger to make sure it uses the new version. + #### VSCode -Add the following to your settings.json. This will list list all Flutter SDKs installed when using VSCode when using `Flutter: Change SDK` +Add the following to your settings.json. This will list list all Flutter SDKs installed when using VSCode when using `Flutter: Change SDK`. -```json +Use `fvm list` to show you the path to the versions. -"dart.flutterSdkPaths": [ - "/Users/leofarias/fvm/versions" -] +```json +{ + "dart.flutterSdkPaths": [ + // List all versions installd by FVM + "/Users/usr/fvm/versions" + // Or add the version symlink for dynamic switch + ".fvm/flutter_sdk" + ] +} ``` +#### Android Studio + +Copy the **_absolute_** path of fvm symbolic link in your root project directory. Example: `/absolute/path-to-your-project/.fvm/flutter_sdk` + +In the Android Studio menu open `Languages & Frameworks -> Flutter` or search for Flutter and change Flutter SDK path. Apply the changes. You now can Run and Debug with the selected versions of Flutter. + [Add your IDE instructions here](https://github.com/leoafarias/fvm/issues) ## Working with this repo diff --git a/coverage/lcov.info b/coverage/lcov.info deleted file mode 100644 index a6dbbd0a..00000000 --- a/coverage/lcov.info +++ /dev/null @@ -1,358 +0,0 @@ -SF:lib/utils/helpers.dart -DA:9,1 -DA:10,3 -DA:13,1 -DA:14,3 -DA:17,2 -DA:21,1 -DA:22,2 -DA:26,1 -DA:27,3 -DA:31,1 -DA:36,2 -DA:37,2 -DA:39,3 -DA:40,0 -DA:41,0 -DA:42,0 -DA:47,1 -DA:48,2 -DA:50,7 -DA:51,1 -DA:52,1 -DA:53,2 -DA:61,1 -DA:63,1 -DA:68,1 -DA:70,3 -DA:72,2 -DA:74,0 -DA:77,0 -DA:78,0 -LF:30 -LH:24 -end_of_record -SF:lib/constants.dart -DA:5,3 -DA:11,0 -DA:14,3 -DA:17,6 -DA:20,1 -DA:22,1 -DA:23,1 -DA:24,1 -DA:25,0 -DA:26,0 -DA:27,0 -DA:28,0 -DA:31,1 -DA:35,4 -DA:38,1 -DA:39,2 -DA:41,1 -DA:43,3 -DA:47,3 -LF:19 -LH:14 -end_of_record -SF:lib/exceptions.dart -DA:4,0 -DA:5,0 -DA:6,0 -DA:16,0 -DA:17,0 -DA:18,0 -DA:27,1 -DA:28,0 -DA:29,0 -DA:38,1 -DA:40,0 -DA:41,0 -DA:50,0 -DA:52,0 -DA:53,0 -DA:62,1 -DA:64,0 -DA:65,0 -DA:73,1 -DA:75,0 -DA:77,0 -LF:21 -LH:4 -end_of_record -SF:lib/utils/logger.dart -DA:4,3 -DA:7,1 -DA:8,1 -LF:3 -LH:3 -end_of_record -SF:lib/utils/config_utils.dart -DA:12,1 -DA:13,1 -DA:17,1 -DA:18,1 -DA:24,1 -DA:25,2 -DA:26,2 -DA:29,3 -DA:30,0 -DA:31,0 -DA:32,0 -DA:35,1 -DA:36,2 -DA:38,1 -DA:41,1 -DA:42,2 -DA:43,2 -DA:45,4 -DA:49,1 -DA:50,2 -DA:51,1 -DA:55,1 -DA:56,2 -DA:60,1 -DA:61,1 -DA:62,1 -DA:63,0 -DA:64,1 -DA:65,2 -DA:66,1 -DA:73,1 -DA:74,3 -DA:75,3 -DA:80,1 -DA:81,1 -DA:87,1 -DA:88,1 -DA:90,0 -DA:91,0 -DA:99,1 -DA:100,1 -DA:101,3 -DA:102,2 -DA:103,2 -DA:106,1 -LF:45 -LH:39 -end_of_record -SF:lib/commands/install.dart -DA:19,1 -DA:22,1 -DA:23,2 -DA:24,3 -DA:25,1 -DA:27,4 -DA:28,1 -DA:30,4 -DA:32,2 -DA:34,2 -DA:36,1 -LF:11 -LH:11 -end_of_record -SF:lib/commands/runner.dart -DA:6,1 -DA:7,1 -DA:10,2 -DA:11,1 -DA:13,1 -DA:15,1 -LF:6 -LH:6 -end_of_record -SF:lib/commands/config.dart -DA:8,1 -DA:11,0 -DA:15,1 -DA:16,1 -DA:17,1 -DA:19,1 -DA:23,1 -DA:24,2 -DA:26,2 -DA:29,2 -DA:30,2 -DA:31,1 -DA:32,3 -DA:34,0 -LF:14 -LH:12 -end_of_record -SF:lib/commands/flutter.dart -DA:22,1 -DA:25,0 -DA:26,0 -DA:28,0 -DA:29,0 -DA:33,0 -DA:35,0 -DA:36,0 -DA:37,0 -LF:9 -LH:1 -end_of_record -SF:lib/commands/list.dart -DA:18,1 -DA:21,1 -DA:22,2 -DA:24,1 -DA:25,0 -DA:28,1 -DA:29,2 -DA:30,1 -DA:32,3 -DA:35,2 -DA:36,2 -LF:11 -LH:10 -end_of_record -SF:lib/commands/remove.dart -DA:17,1 -DA:18,1 -DA:19,1 -DA:20,1 -DA:28,1 -DA:29,4 -DA:31,2 -DA:34,0 -DA:37,3 -DA:39,2 -DA:40,1 -DA:41,0 -LF:12 -LH:10 -end_of_record -SF:lib/commands/use.dart -DA:18,1 -DA:21,1 -DA:22,3 -DA:23,0 -DA:24,0 -DA:26,3 -DA:28,2 -DA:31,0 -DA:32,0 -DA:33,0 -DA:36,3 -DA:38,2 -DA:39,4 -DA:40,1 -LF:14 -LH:9 -end_of_record -SF:lib/commands/version.dart -DA:6,1 -DA:9,0 -DA:12,1 -DA:14,1 -LF:4 -LH:3 -end_of_record -SF:lib/utils/flutter_tools.dart -DA:10,0 -DA:12,0 -DA:16,0 -DA:18,0 -DA:19,0 -DA:21,0 -DA:22,0 -DA:29,1 -DA:30,4 -DA:32,1 -DA:33,2 -DA:37,2 -DA:41,2 -DA:43,2 -DA:44,1 -DA:45,1 -DA:47,2 -DA:48,0 -DA:53,1 -DA:55,3 -DA:56,0 -DA:57,0 -DA:64,1 -DA:65,4 -DA:67,2 -DA:70,2 -DA:74,2 -DA:76,2 -DA:77,1 -DA:78,1 -DA:80,2 -DA:81,0 -DA:93,1 -DA:94,4 -DA:95,2 -DA:96,0 -DA:98,3 -DA:101,1 -DA:102,3 -DA:105,3 -DA:106,3 -DA:110,2 -DA:111,0 -DA:114,2 -DA:119,1 -DA:121,4 -DA:123,2 -DA:124,0 -DA:127,2 -DA:129,1 -DA:130,2 -DA:131,1 -DA:133,2 -DA:134,2 -DA:142,1 -DA:143,4 -DA:144,2 -DA:145,2 -DA:150,1 -DA:151,4 -DA:152,3 -DA:153,3 -DA:155,2 -DA:160,4 -DA:161,2 -DA:162,1 -DA:163,2 -DA:171,1 -DA:174,3 -DA:175,0 -DA:178,4 -DA:180,1 -DA:181,2 -DA:182,4 -DA:184,3 -DA:188,1 -DA:190,0 -DA:191,0 -DA:196,1 -DA:197,4 -DA:198,1 -DA:199,3 -LF:82 -LH:65 -end_of_record -SF:lib/fvm.dart -DA:15,1 -DA:16,1 -DA:18,2 -DA:19,2 -DA:20,2 -DA:21,2 -DA:22,2 -DA:23,2 -DA:24,2 -DA:26,3 -DA:27,0 -DA:28,0 -DA:30,0 -DA:31,0 -DA:32,0 -DA:36,0 -DA:37,2 -LF:17 -LH:11 -end_of_record diff --git a/coverage_badge.svg b/coverage_badge.svg index e5ba3eed..a6f4e712 100644 --- a/coverage_badge.svg +++ b/coverage_badge.svg @@ -8,13 +8,13 @@ - + coverage coverage - 74% - 74% + 75% + 75% diff --git a/lib/commands/config.dart b/lib/commands/config.dart index c400be55..09b05e29 100644 --- a/lib/commands/config.dart +++ b/lib/commands/config.dart @@ -1,7 +1,6 @@ import 'package:args/command_runner.dart'; -import 'package:io/ansi.dart'; +import 'package:fvm/utils/print.dart'; import 'package:fvm/utils/config_utils.dart'; -import 'package:fvm/utils/logger.dart'; /// Config fvm options. class ConfigCommand extends Command { @@ -14,6 +13,7 @@ class ConfigCommand extends Command { /// Constructor ConfigCommand() { argParser + ..addOption('defaultVersion', abbr: 'd', help: 'Flutter default version') ..addOption('cache-path', abbr: 'c', help: 'Path to store Flutter cached versions') ..addFlag('ls', help: 'Lists all config options'); @@ -29,7 +29,7 @@ class ConfigCommand extends Command { if (argResults['ls'] != null) { final configOptions = ConfigUtils().displayAllConfig(); if (configOptions.isNotEmpty) { - logger.stdout(green.wrap(configOptions)); + Print.success(configOptions); } else { throw Exception('No configuration has been set'); } diff --git a/lib/commands/flutter.dart b/lib/commands/flutter.dart index 7bc2fe50..faa403ae 100644 --- a/lib/commands/flutter.dart +++ b/lib/commands/flutter.dart @@ -1,8 +1,7 @@ -import 'dart:io'; - import 'package:args/command_runner.dart'; import 'package:fvm/constants.dart'; import 'package:fvm/utils/flutter_tools.dart'; +import 'package:fvm/utils/guards.dart'; import 'package:fvm/utils/helpers.dart'; import 'package:args/args.dart'; @@ -23,19 +22,10 @@ class FlutterCommand extends Command { @override Future run() async { - final flutterProjectLink = await projectFlutterLink(); - - if (flutterProjectLink == null || !await flutterProjectLink.exists()) { - throw Exception('No FVM config found. Create with command'); - } - - try { - final targetLink = File(await flutterProjectLink.target()); + Guards.isFlutterProject(); + final flutterSdkPath = getFlutterSdkExecPath(); - await processRunner(targetLink.path, argResults.arguments, - workingDirectory: kWorkingDirectory.path); - } on Exception { - rethrow; - } + await flutterProcessRunner(flutterSdkPath, argResults.arguments, + workingDirectory: kWorkingDirectory.path); } } diff --git a/lib/commands/install.dart b/lib/commands/install.dart index f671fccc..344b57da 100644 --- a/lib/commands/install.dart +++ b/lib/commands/install.dart @@ -1,9 +1,7 @@ import 'package:args/command_runner.dart'; import 'package:fvm/exceptions.dart'; -import 'package:fvm/utils/flutter_tools.dart'; -import 'package:fvm/utils/helpers.dart'; -import 'package:fvm/utils/logger.dart'; -import 'package:io/ansi.dart'; +import 'package:fvm/utils/guards.dart'; +import 'package:fvm/utils/version_installer.dart'; /// Installs Flutter SDK class InstallCommand extends Command { @@ -20,19 +18,12 @@ class InstallCommand extends Command { @override void run() async { - await checkIfGitExists(); + Guards.isGitInstalled(); if (argResults.arguments.isEmpty) { throw ExceptionMissingChannelVersion(); } final version = argResults.arguments[0].toLowerCase(); - final isChannel = isValidFlutterChannel(version); - final progress = logger.progress(green.wrap('Downloading $version')); - if (isChannel) { - await flutterChannelClone(version); - } else { - await flutterVersionClone(version); - } - finishProgress(progress); + await installFlutterVersion(version); } } diff --git a/lib/commands/list.dart b/lib/commands/list.dart index ea3ed7ed..26fd11ee 100644 --- a/lib/commands/list.dart +++ b/lib/commands/list.dart @@ -1,8 +1,11 @@ +import 'dart:io'; + +import 'package:fvm/constants.dart'; import 'package:fvm/utils/helpers.dart'; +import 'package:fvm/utils/print.dart'; import 'package:io/ansi.dart'; import 'package:args/command_runner.dart'; import 'package:fvm/utils/flutter_tools.dart'; -import 'package:fvm/utils/logger.dart'; /// List installed SDK Versions class ListCommand extends Command { @@ -19,17 +22,22 @@ class ListCommand extends Command { @override Future run() async { - final choices = await flutterListInstalledSdks(); + final choices = flutterListInstalledSdks(); if (choices.isEmpty) { - throw Exception('No SDKs have been installed yet.'); + Print.info( + 'No SDKs have been installed yet. Flutter SDKs installed outside of fvm will not be displayed.'); + exit(0); } + // Print where versions are stored + print('Versions path: ${yellow.wrap(kVersionsDir.path)}'); + Future printVersions(String version) async { - if (await isCurrentVersion(version)) { + if (isCurrentVersion(version)) { version = '$version (current)'; } - logger.stdout(green.wrap(version)); + Print.info(version); } for (var choice in choices) { diff --git a/lib/commands/remove.dart b/lib/commands/remove.dart index 0d0d72a6..f87a284a 100644 --- a/lib/commands/remove.dart +++ b/lib/commands/remove.dart @@ -28,7 +28,7 @@ class RemoveCommand extends Command { void run() async { final version = argResults.arguments[0].toLowerCase(); - final isValidInstall = await isValidFlutterInstall(version); + final isValidInstall = await isFlutterVersionInstalled(version); if (!isValidInstall) { throw Exception('Flutter SDK: $version is not installed'); diff --git a/lib/commands/use.dart b/lib/commands/use.dart index 0630feca..f67cdeb3 100644 --- a/lib/commands/use.dart +++ b/lib/commands/use.dart @@ -1,10 +1,9 @@ import 'package:args/command_runner.dart'; import 'package:fvm/constants.dart'; import 'package:fvm/utils/flutter_tools.dart'; +import 'package:fvm/utils/guards.dart'; import 'package:fvm/utils/helpers.dart'; -import 'package:fvm/utils/logger.dart'; -import 'package:io/ansi.dart'; -import 'package:path/path.dart' as path; +import 'package:fvm/utils/project_config.dart'; /// Use an installed SDK version class UseCommand extends Command { @@ -22,46 +21,33 @@ class UseCommand extends Command { ..addFlag( 'global', help: - 'Creates a symbolic link to the version specified in /default/', + 'Sets version as the global version.\nMake sure Flutter PATH env is set to: $kDefaultFlutterPath', negatable: false, ); } @override Future run() async { - if (argResults.rest.isEmpty) { - final instruction = yellow.wrap('fvm use '); - throw Exception('Please provide a version. $instruction'); - } + final useGlobally = argResults['global'] == true; final version = argResults.rest[0]; - final isValidInstall = await isValidFlutterInstall(version); - - if (!isValidInstall) { - final instruction = yellow.wrap('fvm install first.'); - throw Exception( - 'Flutter $version is not installed. Please run $instruction'); + if (argResults.rest.isEmpty) { + throw Exception('Please provide a version. fvm use '); } + // Make sure is valid Flutter version + await Guards.isFlutterVersion(version); + // If project use check that is Flutter project + if (!useGlobally) Guards.isFlutterProject(); - final progress = logger.progress('Activating $version'); - - final useGlobally = argResults['global'] == true; - - if (useGlobally) { - await linkProjectFlutterDirGlobally(version); - } else { - await linkProjectFlutterDir(version); - } + // Make sure version is installed + await checkAndInstallVersion(version); if (useGlobally) { - final flutterSDKBinariesPath = path.join(kDefaultFlutterLink.path, 'bin'); - logger.stdout(green.wrap('$version linked succesfully')); - logger.stdout(cyan.wrap( - 'Make sure sure to add $flutterSDKBinariesPath to your PATH environment variable')); + // Sets version as the global + setAsGlobalVersion(version); } else { - logger.stdout(green.wrap('$version is active')); + // Updates the project config with version + setAsProjectVersion(version); } - - finishProgress(progress); } } diff --git a/lib/constants.dart b/lib/constants.dart index 67419888..97074bf5 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -4,6 +4,8 @@ import 'package:fvm/utils/config_utils.dart'; final _configUtils = ConfigUtils(); +const kFvmDirName = '.fvm'; + /// Flutter Repo Address const kFlutterRepo = 'https://github.com/flutter/flutter.git'; @@ -13,8 +15,21 @@ final kFvmDirectory = Platform.script.toString(); /// Working Directory for FVM final kWorkingDirectory = Directory.current; +/// Local Project Directory +final kProjectFvmDir = + Directory(path.join(kWorkingDirectory.path, kFvmDirName)); + +/// Local Project Config +final kProjectFvmConfigJson = + File(path.join(kProjectFvmDir.path, 'fvm_config.json')); + /// Local Project Flutter Link -final kLocalFlutterLink = Link(path.join(kWorkingDirectory.path, 'fvm')); +final kProjectFvmSdkSymlink = + Link(path.join(kProjectFvmDir.path, 'flutter_sdk')); + +/// Flutter Project pubspec +final kLocalProjectPubspec = + File(path.join(kWorkingDirectory.path, 'pubspec.yaml')); /// FVM Home directory String get fvmHome { @@ -45,6 +60,7 @@ Directory get kVersionsDir { /// Where Default Flutter SDK is stored Link get kDefaultFlutterLink => Link(path.join(fvmHome, 'default')); +String get kDefaultFlutterPath => path.join(kDefaultFlutterLink.path, 'bin'); /// Flutter Channels final kFlutterChannels = ['master', 'stable', 'dev', 'beta']; diff --git a/lib/utils/config_utils.dart b/lib/utils/config_utils.dart index 109c9c3a..a4f6a0c5 100644 --- a/lib/utils/config_utils.dart +++ b/lib/utils/config_utils.dart @@ -70,19 +70,15 @@ class ConfigUtils { } /// Removes Config file - Future removeConfig() async { - if (await kConfigFile.exists()) { - await kConfigFile.delete(); - } + void removeConfig() { + if (kConfigFile.existsSync()) kConfigFile.deleteSync(); } /// get flutter stored path. String getStoredPath() { final path = getValue(kConfigFlutterStoredKey); - if (path == null) { - return null; - } + if (path == null) return null; final type = FileSystemEntity.typeSync(path, followLinks: true); if (type == FileSystemEntityType.directory) { diff --git a/lib/utils/confirm.dart b/lib/utils/confirm.dart new file mode 100644 index 00000000..7553e801 --- /dev/null +++ b/lib/utils/confirm.dart @@ -0,0 +1,8 @@ +import 'package:console/console.dart'; + +/// Displays notice for confirmation +Future confirm(String message) async { + final response = await readInput('$message Y/n: '); + // Return true unless 'n' + return !response.contains('n'); +} diff --git a/lib/utils/flutter_tools.dart b/lib/utils/flutter_tools.dart index 0ecdd76d..6cb31438 100644 --- a/lib/utils/flutter_tools.dart +++ b/lib/utils/flutter_tools.dart @@ -2,26 +2,18 @@ import 'dart:io'; import 'package:fvm/constants.dart'; import 'package:fvm/exceptions.dart'; import 'package:fvm/utils/helpers.dart'; +import 'package:fvm/utils/print.dart'; import 'package:path/path.dart' as path; import 'package:io/io.dart'; -import 'package:fvm/utils/logger.dart'; /// Runs a process -Future processRunner(String cmd, List args, +Future flutterProcessRunner(String cmd, List args, {String workingDirectory}) async { final manager = ProcessManager(); - try { - var spawn = - await manager.spawn(cmd, args, workingDirectory: workingDirectory); - - if (await spawn.exitCode != 0) { - throw Exception('Could not run command $cmd: $args'); - } - await sharedStdIn.terminate(); - } on Exception { - rethrow; - } + var pr = await manager.spawn(cmd, args, workingDirectory: workingDirectory); + final exitCode = await pr.exitCode; + exit(exitCode); } /// Clones Flutter SDK from Channel @@ -29,14 +21,12 @@ Future processRunner(String cmd, List args, Future flutterChannelClone(String channel) async { final channelDirectory = Directory(path.join(kVersionsDir.path, channel)); - if (!isValidFlutterChannel(channel)) { + if (!isFlutterChannel(channel)) { throw ExceptionNotValidChannel('"$channel" is not a valid channel'); } // If it's installed correctly just return and use cached - if (await checkInstalledCorrectly(channel)) { - return; - } + if (isInstalledCorrectly(channel)) return; await channelDirectory.create(recursive: true); @@ -49,27 +39,15 @@ Future flutterChannelClone(String channel) async { } } -/// Check if Git is installed -Future checkIfGitExists() async { - try { - await Process.run('git', ['--version']); - } on ProcessException { - throw Exception( - 'You need Git Installed to run fvm. Go to https://git-scm.com/downloads'); - } -} - /// Clones Flutter SDK from Version Number /// Returns exists:true if comes from cache or false if its new fetch. Future flutterVersionClone(String version) async { final versionDirectory = Directory(path.join(kVersionsDir.path, version)); - version = await coerceValidFlutterVersion(version); + version = await inferFlutterVersion(version); // If it's installed correctly just return and use cached - if (await checkInstalledCorrectly(version)) { - return; - } + if (isInstalledCorrectly(version)) return; await versionDirectory.create(recursive: true); @@ -82,17 +60,10 @@ Future flutterVersionClone(String version) async { } } -/// Gets Flutter version from project -// Future flutterGetProjectVersion() async { -// final target = await kLocalFlutterLink.target(); -// print(target); -// return await _gitGetVersion(target); -// } - /// Gets SDK Version Future flutterSdkVersion(String branch) async { final branchDirectory = Directory(path.join(kVersionsDir.path, branch)); - if (!await branchDirectory.exists()) { + if (!branchDirectory.existsSync()) { throw Exception('Could not get version from SDK that is not installed'); } return await _gitGetVersion(branchDirectory.path); @@ -139,28 +110,25 @@ Future> flutterListAllSdks() async { } /// Removes a Version of Flutter SDK -Future flutterSdkRemove(String version) async { +void flutterSdkRemove(String version) { final versionDir = Directory(path.join(kVersionsDir.path, version)); - if (await versionDir.exists()) { - await versionDir.delete(recursive: true); + if (versionDir.existsSync()) { + versionDir.deleteSync(recursive: true); } } /// Check if version is from git -Future checkInstalledCorrectly(String version) async { +bool isInstalledCorrectly(String version) { final versionDir = Directory(path.join(kVersionsDir.path, version)); final gitDir = Directory(path.join(versionDir.path, '.github')); final flutterBin = Directory(path.join(versionDir.path, 'bin')); // Check if version directory exists - if (!await versionDir.exists()) { - return false; - } + if (!versionDir.existsSync()) return false; // Check if version directory is from git - if (!await gitDir.exists() || !await flutterBin.exists()) { - logger.stdout( - '$version exists but was not setup correctly. Doing cleanup...'); - await flutterSdkRemove(version); + if (!gitDir.existsSync() || !flutterBin.existsSync()) { + print('$version exists but was not setup correctly. Doing cleanup...'); + flutterSdkRemove(version); return false; } @@ -168,18 +136,18 @@ Future checkInstalledCorrectly(String version) async { } /// Lists Installed Flutter SDK Version -Future> flutterListInstalledSdks() async { +List flutterListInstalledSdks() { try { // Returns empty array if directory does not exist - if (!await kVersionsDir.exists()) { + if (!kVersionsDir.existsSync()) { return []; } - final versions = await kVersionsDir.list().toList(); + final versions = kVersionsDir.listSync().toList(); var installedVersions = []; for (var version in versions) { - if (await FileSystemEntity.type(version.path) == + if (FileSystemEntity.typeSync(version.path) == FileSystemEntityType.directory) { installedVersions.add(path.basename(version.path)); } @@ -192,14 +160,11 @@ Future> flutterListInstalledSdks() async { } } -/// Links Flutter Dir to existsd SDK -Future linkProjectFlutterDir(String version) async { - final versionBin = Directory(path.join(kVersionsDir.path, version, 'bin', - Platform.isWindows ? 'flutter.bat' : 'flutter')); - await linkDir(kLocalFlutterLink, versionBin); -} - -Future linkProjectFlutterDirGlobally(String version) async { +void setAsGlobalVersion(String version) { final versionDir = Directory(path.join(kVersionsDir.path, version)); - await linkDir(kDefaultFlutterLink, versionDir); -} \ No newline at end of file + createLink(kDefaultFlutterLink, versionDir); + + Print.success('The global Flutter version is now $version'); + Print.success( + 'Make sure sure to add $kDefaultFlutterPath to your PATH environment variable'); +} diff --git a/lib/utils/guards.dart b/lib/utils/guards.dart new file mode 100644 index 00000000..081becc1 --- /dev/null +++ b/lib/utils/guards.dart @@ -0,0 +1,39 @@ +// Checks if its flutter project +import 'dart:io'; + +import 'package:fvm/constants.dart'; +import 'package:fvm/exceptions.dart'; +import 'package:fvm/utils/helpers.dart'; + +/// Guards +class Guards { + /// Checks if its on the root of a Flutter project + static void isFlutterProject() { + final isFlutter = kLocalProjectPubspec.existsSync(); + if (!isFlutter) { + throw Exception('Run this FVM command at the root of a Flutter project'); + } + } + + /// Check if Git is installed + static void isGitInstalled() { + try { + Process.runSync('git', ['--version']); + } on ProcessException { + throw Exception( + 'You need Git Installed to run fvm. Go to https://git-scm.com/downloads'); + } + } + + /// Make sure version is valid + static Future isFlutterVersion(String version) async { + // Check if its a channel + if (isFlutterChannel(version)) return; + // Check if ts a version + final flutterVersion = await inferFlutterVersion(version); + if (flutterVersion == null) { + throw ExceptionNotValidVersion( + '"$version" is not a valid Flutter SDK version'); + } + } +} diff --git a/lib/utils/helpers.dart b/lib/utils/helpers.dart index b4084654..c923dfc9 100644 --- a/lib/utils/helpers.dart +++ b/lib/utils/helpers.dart @@ -2,41 +2,63 @@ import 'dart:io'; import 'package:fvm/constants.dart'; import 'package:fvm/exceptions.dart'; +import 'package:fvm/utils/confirm.dart'; +import 'package:fvm/utils/logger.dart'; +import 'package:fvm/utils/print.dart'; +import 'package:fvm/utils/project_config.dart'; +import 'package:fvm/utils/version_installer.dart'; import 'package:path/path.dart' as path; import 'package:fvm/utils/flutter_tools.dart'; /// Returns true if it's a valid Flutter version number -Future coerceValidFlutterVersion(String version) async { - if ((await flutterListAllSdks()).contains(version)) { +Future inferFlutterVersion(String version) async { + final versions = await flutterListAllSdks(); + if ((versions).contains(version)) { return version; } final prefixedVersion = 'v$version'; - if ((await flutterListAllSdks()).contains(prefixedVersion)) { + if ((versions).contains(prefixedVersion)) { return prefixedVersion; } throw ExceptionNotValidVersion('"$version" is not a valid version'); } /// Returns true if it's a valid Flutter channel -bool isValidFlutterChannel(String channel) { +bool isFlutterChannel(String channel) { return kFlutterChannels.contains(channel); } /// Returns true it's a valid installed version -Future isValidFlutterInstall(String version) async { - return (await flutterListInstalledSdks()).contains(version); +bool isFlutterVersionInstalled(String version) { + return (flutterListInstalledSdks()).contains(version); +} + +/// Checks if version is installed, and installs or exits +Future checkAndInstallVersion(String version) async { + if (isFlutterVersionInstalled(version)) return null; + Print.info('Flutter $version is not installed.'); + + // Install if input is confirmed + if (await confirm('Would you like to install it?')) { + final installProgress = logger.progress('Installing $version'); + await installFlutterVersion(version); + finishProgress(installProgress); + } else { + // If do not install exist + exit(0); + } } /// Moves assets from theme directory into brand-app -Future linkDir( +void createLink( Link source, FileSystemEntity target, ) async { try { - if (await source.exists()) { - await source.delete(); + if (source.existsSync()) { + source.deleteSync(); } - await source.create(target.path); + source.createSync(target.path); } on Exception catch (err) { logVerboseError(err); throw Exception('Sorry could not link ${target.path}'); @@ -44,36 +66,23 @@ Future linkDir( } /// Check if it is the current version. -Future isCurrentVersion(String version) async { - final link = await projectFlutterLink(); - if (link != null) { - return Uri.file(File(await link.target()).parent.parent.path, - windows: Platform.isWindows) - .pathSegments - .last == - version; - } - return false; +bool isCurrentVersion(String version) { + final config = readProjectConfig(); + return version == config.flutterSdkVersion; } -/// The fvm link of the current working directory. -/// [levels] how many levels you would like to go up to search for a version -Future projectFlutterLink([Directory dir, int levels = 20]) async { - // If there are no levels exit - if (levels == 0) { - return null; +/// The Flutter SDK Path referenced on FVM +String getFlutterSdkPath() { + try { + final config = readProjectConfig(); + return path.join(kVersionsDir.path, config.flutterSdkVersion); + } on Exception catch (e) { + // TODO: Clean up exception + throw ExceptionCouldNotReadConfig('$e'); } - Link link; - - dir ??= kWorkingDirectory; - - link = Link(path.join(dir.path, 'fvm')); +} - if (await link.exists()) { - return link; - } else if (path.rootPrefix(link.path) == dir.path) { - return null; - } - levels--; - return await projectFlutterLink(dir, levels); +String getFlutterSdkExecPath() { + return path.join(getFlutterSdkPath(), 'bin', + Platform.isWindows ? 'flutter.bat' : 'flutter'); } diff --git a/lib/utils/print.dart b/lib/utils/print.dart new file mode 100644 index 00000000..9127d09a --- /dev/null +++ b/lib/utils/print.dart @@ -0,0 +1,20 @@ +import 'package:io/ansi.dart'; + +class Print { + /// Prints sucess message + static void success(String message) { + print(green.wrap(message)); + } + + static void warning(String message) { + print(yellow.wrap(message)); + } + + static void info(String message) { + print(cyan.wrap(message)); + } + + static void error(String message) { + print(red.wrap(message)); + } +} diff --git a/lib/utils/project_config.dart b/lib/utils/project_config.dart new file mode 100644 index 00000000..380193ad --- /dev/null +++ b/lib/utils/project_config.dart @@ -0,0 +1,39 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:fvm/constants.dart'; +import 'package:fvm/utils/helpers.dart'; +import 'package:fvm/utils/print.dart'; + +class ProjectConfig { + final String flutterSdkVersion; + ProjectConfig(this.flutterSdkVersion); + + ProjectConfig.fromJson(Map json) + : flutterSdkVersion = json['flutterSdkVersion'] as String; + + Map toJson() => {'flutterSdkVersion': flutterSdkVersion}; +} + +void setAsProjectVersion(String version) { + if (kProjectFvmConfigJson.existsSync() == false) { + kProjectFvmConfigJson.createSync(recursive: true); + } + saveProjectConfig(ProjectConfig(version)); + updateFlutterSdkBinLink(); + Print.success('Project now uses Flutter: $version'); +} + +void updateFlutterSdkBinLink() { + final flutterSdk = getFlutterSdkPath(); + createLink(kProjectFvmSdkSymlink, File(flutterSdk)); +} + +ProjectConfig readProjectConfig() { + final jsonString = kProjectFvmConfigJson.readAsStringSync(); + final projectConfigMap = jsonDecode(jsonString) as Map; + return ProjectConfig.fromJson(projectConfigMap); +} + +void saveProjectConfig(ProjectConfig config) { + kProjectFvmConfigJson.writeAsStringSync(jsonEncode(config)); +} diff --git a/lib/utils/version_installer.dart b/lib/utils/version_installer.dart new file mode 100644 index 00000000..6187a862 --- /dev/null +++ b/lib/utils/version_installer.dart @@ -0,0 +1,21 @@ +import 'package:fvm/exceptions.dart'; +import 'package:fvm/utils/flutter_tools.dart'; +import 'package:fvm/utils/helpers.dart'; +import 'package:fvm/utils/logger.dart'; +import 'package:io/ansi.dart'; + +Future installFlutterVersion(String flutterVersion) async { + if (flutterVersion == null) { + throw ExceptionMissingChannelVersion(); + } + final version = flutterVersion.toLowerCase(); + final isChannel = isFlutterChannel(version); + + final progress = logger.progress(green.wrap('Downloading $version')); + if (isChannel) { + await flutterChannelClone(version); + } else { + await flutterVersionClone(version); + } + finishProgress(progress); +} diff --git a/pubspec.yaml b/pubspec.yaml index d80032ad..f0c76365 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: fvm description: A simple cli to manage Flutter SDK versions per project. Support channels, releases, and local cache for fast switching between versions. -version: 0.7.2 +version: 0.8.0 homepage: https://github.com/leoafarias/fvm environment: @@ -11,7 +11,7 @@ executables: dependencies: args: ^1.5.3 - cli_util: ^0.1.3+2 + cli_util: ^0.2.0 # file_utils: ^0.1.3 console: ^3.1.0 io: ^0.3.3 @@ -19,7 +19,7 @@ dependencies: dev_dependencies: pedantic: ^1.8.0 - test: ^1.6.0 - test_coverage: ^0.3.0 + test: ^1.14.0 + test_coverage: ^0.4.1 build_runner: ^1.0.0 build_version: ^2.0.1 diff --git a/test/fvm_test.dart b/test/fvm_test.dart index 67e26ac9..3ff876b0 100644 --- a/test/fvm_test.dart +++ b/test/fvm_test.dart @@ -7,7 +7,6 @@ import 'package:test/test.dart'; import 'package:path/path.dart' as path; import 'package:fvm/constants.dart'; import 'package:fvm/utils/flutter_tools.dart'; - import 'test_helpers.dart'; final testPath = '$fvmHome/test_path'; @@ -16,12 +15,8 @@ const channel = 'master'; const release = '1.8.0'; void main() { - setUpAll(() async { - await fvmSetUpAll(); - }); - tearDownAll(() async { - await fvmTearDownAll(); - }); + setUpAll(fvmSetUpAll); + tearDownAll(fvmTearDownAll); group('Channel Flow', () { test('Install without version', () async { final args = ['install']; @@ -37,8 +32,8 @@ void main() { try { await fvmRunner(['install', channel, '--verbose']); final existingChannel = await flutterSdkVersion(channel); - final correct = await checkInstalledCorrectly(channel); - final installedVersions = await flutterListInstalledSdks(); + final correct = isInstalledCorrectly(channel); + final installedVersions = flutterListInstalledSdks(); final installExists = installedVersions.contains(channel); @@ -46,7 +41,7 @@ void main() { expect(correct, true, reason: 'Not Installed Correctly'); expect(existingChannel, channel); } on Exception catch (e) { - fail("Exception thrown, $e"); + fail('Exception thrown, $e'); } }); @@ -54,7 +49,7 @@ void main() { try { await fvmRunner(['list']); } on Exception catch (e) { - fail("Exception thrown, $e"); + fail('Exception thrown, $e'); } expect(true, true); @@ -63,34 +58,32 @@ void main() { test('Use Channel', () async { try { await fvmRunner(['use', channel, '--verbose']); - final linkExists = await kLocalFlutterLink.exists(); + final linkExists = kProjectFvmSdkSymlink.existsSync(); - final targetBin = await kLocalFlutterLink.target(); + final targetBin = kProjectFvmSdkSymlink.targetSync(); - final channelBin = - path.join(kVersionsDir.path, channel, 'bin', 'flutter'); - ; + final channelBin = path.join(kVersionsDir.path, channel); expect(targetBin == channelBin, true); expect(linkExists, true); } on Exception catch (e) { - fail("Exception thrown, $e"); + fail('Exception thrown, $e'); } }); test('Use Flutter SDK globally', () async { try { await fvmRunner(['use', channel, '--global']); - final linkExists = await kDefaultFlutterLink.exists(); + final linkExists = kDefaultFlutterLink.existsSync(); - final targetDir = await kDefaultFlutterLink.target(); + final targetDir = kDefaultFlutterLink.targetSync(); final channelDir = path.join(kVersionsDir.path, channel); expect(targetDir == channelDir, true); expect(linkExists, true); } on Exception catch (e) { - fail("Exception thrown, $e"); + fail('Exception thrown, $e'); } }); @@ -98,7 +91,7 @@ void main() { try { await fvmRunner(['remove', channel, '--verbose']); } on Exception catch (e) { - fail("Exception thrown, $e"); + fail('Exception thrown, $e'); } expect(true, true); @@ -109,8 +102,8 @@ void main() { try { await fvmRunner(['install', release, '--verbose']); final existingRelease = await flutterSdkVersion(release); - final correct = await checkInstalledCorrectly(release); - final installedVersions = await flutterListInstalledSdks(); + final correct = isInstalledCorrectly(release); + final installedVersions = flutterListInstalledSdks(); final installExists = installedVersions.contains(release); @@ -118,7 +111,7 @@ void main() { expect(correct, true, reason: 'Not Installed Correctly'); expect(existingRelease, 'v$release'); } on Exception catch (e) { - fail("Exception thrown, $e"); + fail('Exception thrown, $e'); } expect(true, true); @@ -127,17 +120,16 @@ void main() { test('Use Release', () async { try { await fvmRunner(['use', release, '--verbose']); - final linkExists = await kLocalFlutterLink.exists(); + final linkExists = kProjectFvmSdkSymlink.existsSync(); - final targetBin = await kLocalFlutterLink.target(); + final targetBin = kProjectFvmSdkSymlink.targetSync(); - final releaseBin = - path.join(kVersionsDir.path, release, 'bin', 'flutter'); + final releaseBin = path.join(kVersionsDir.path, release); expect(targetBin == releaseBin, true); expect(linkExists, true); } on Exception catch (e) { - fail("Exception thrown, $e"); + fail('Exception thrown, $e'); } }); @@ -145,7 +137,7 @@ void main() { try { await fvmRunner(['list', '--verbose']); } on Exception catch (e) { - fail("Exception thrown, $e"); + fail('Exception thrown, $e'); } expect(true, true); @@ -155,7 +147,7 @@ void main() { try { await fvmRunner(['remove', release, '--verbose']); } on Exception catch (e) { - fail("Exception thrown, $e"); + fail('Exception thrown, $e'); } expect(true, true); @@ -167,7 +159,7 @@ void main() { try { await fvmRunner(['config', '--cache-path', testPath]); } on Exception catch (e) { - fail("Exception thrown, $e"); + fail('Exception thrown, $e'); } expect(testPath, kVersionsDir.path); }); @@ -176,7 +168,7 @@ void main() { try { await fvmRunner(['config', '--ls']); } on Exception catch (e) { - fail("Exception thrown, $e"); + fail('Exception thrown, $e'); } expect(true, true); }); @@ -187,7 +179,7 @@ void main() { try { await fvmRunner(['version']); } on Exception catch (e) { - fail("Exception thrown, $e"); + fail('Exception thrown, $e'); } expect(true, true); }); diff --git a/test/utils/flutter_tools_test.dart b/test/utils/flutter_tools_test.dart index edda14c9..aa370c8f 100644 --- a/test/utils/flutter_tools_test.dart +++ b/test/utils/flutter_tools_test.dart @@ -13,9 +13,9 @@ void main() { try { await flutterChannelClone(invalidChannel); - fail("Exception not thrown"); + fail('Exception not thrown'); } on Exception catch (e) { - expect(e, TypeMatcher()); + expect(e, const TypeMatcher()); } }); @@ -24,16 +24,16 @@ void main() { try { await flutterVersionClone(invalidVersion); - fail("Exception not thrown"); + fail('Exception not thrown'); } on Exception catch (e) { - expect(e, TypeMatcher()); + expect(e, const TypeMatcher()); } }); test('Checks that install is not correct', () async { final invalidVersionName = 'INVALID_VERSION'; final dir = Directory(path.join(kVersionsDir.path, invalidVersionName)); await dir.create(recursive: true); - final correct = await checkInstalledCorrectly(invalidVersionName); + final correct = isInstalledCorrectly(invalidVersionName); expect(correct, false); }); }); diff --git a/test/utils/helpers_test.dart b/test/utils/helpers_test.dart index 6d8989cd..60b160b2 100644 --- a/test/utils/helpers_test.dart +++ b/test/utils/helpers_test.dart @@ -3,23 +3,23 @@ import 'package:fvm/utils/helpers.dart'; void main() { test('Is Valid Flutter Version', () async { - expect(await coerceValidFlutterVersion('1.8.0'), 'v1.8.0'); - expect(await coerceValidFlutterVersion('v1.8.0'), 'v1.8.0'); + expect(await inferFlutterVersion('1.8.0'), 'v1.8.0'); + expect(await inferFlutterVersion('v1.8.0'), 'v1.8.0'); - expect(await coerceValidFlutterVersion('1.9.6'), 'v1.9.6'); - expect(await coerceValidFlutterVersion('v1.9.6'), 'v1.9.6'); + expect(await inferFlutterVersion('1.9.6'), 'v1.9.6'); + expect(await inferFlutterVersion('v1.9.6'), 'v1.9.6'); - expect(await coerceValidFlutterVersion('1.10.5'), 'v1.10.5'); - expect(await coerceValidFlutterVersion('v1.10.5'), 'v1.10.5'); + expect(await inferFlutterVersion('1.10.5'), 'v1.10.5'); + expect(await inferFlutterVersion('v1.10.5'), 'v1.10.5'); - expect(await coerceValidFlutterVersion('1.9.1+hotfix.4'), 'v1.9.1+hotfix.4'); - expect(await coerceValidFlutterVersion('v1.9.1+hotfix.4'), 'v1.9.1+hotfix.4'); + expect(await inferFlutterVersion('1.9.1+hotfix.4'), 'v1.9.1+hotfix.4'); + expect(await inferFlutterVersion('v1.9.1+hotfix.4'), 'v1.9.1+hotfix.4'); - expect(await coerceValidFlutterVersion('1.17.0-dev.3.1'), '1.17.0-dev.3.1'); + expect(await inferFlutterVersion('1.17.0-dev.3.1'), '1.17.0-dev.3.1'); }); test('Not Valid Flutter Version', () async { - expect(coerceValidFlutterVersion('1.8.0.2'), throws); - expect(coerceValidFlutterVersion('v1.17.0-dev.3.1'), throws); + expect(inferFlutterVersion('1.8.0.2'), throwsA(anything)); + expect(inferFlutterVersion('v1.17.0-dev.3.1'), throwsA(anything)); }); }