Skip to content

Commit

Permalink
Merge pull request #75 from leoafarias/feature/list-releases
Browse files Browse the repository at this point in the history
Feature/list releases
  • Loading branch information
leoafarias authored Jun 22, 2020
2 parents 1f0ae05 + 408a7cf commit 25aa604
Show file tree
Hide file tree
Showing 14 changed files with 302 additions and 20 deletions.
16 changes: 13 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ Flutter Version Management: A simple cli to manage Flutter SDK versions.

**Features:**

- Configure Flutter SDK version per project
- Configure and use Flutter SDK version per project
- Ability to install and cache multiple Flutter SDK Versions
- Easily switch between Flutter channels & versions
- Per project Flutter SDK upgrade
- Fast switch between Flutter channels & versions
- Dynamic sdk paths for IDE debugging support.
- Version FVM config with project for consistency across teams and CI environments.
- Set global Flutter version across projects

## Version Management

Expand Down Expand Up @@ -83,6 +85,14 @@ List all the versions that are installed on your machine. This command will also
> fvm list
```

### List Flutter Releases

Displays all Flutter releases, including the current version for `dev`, `beta` and `stable` channels.

```bash
> fvm releases
```

### Change FVM Cache Directory

There are some configurations that allows for added flexibility on FVM. If no `cache-path` is set, the default fvm path will be used.
Expand Down
6 changes: 3 additions & 3 deletions coverage_badge.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions lib/commands/install.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ class InstallCommand extends Command {
Guards.isGitInstalled();

String version;
var hasConfig = false;
if (argResults.arguments.isEmpty) {
final configVersion = getConfigFlutterVersion();
if (configVersion == null) {
throw ExceptionMissingChannelVersion();
}
hasConfig = true;
version = configVersion;
} else {
version = argResults.arguments[0].toLowerCase();
Expand All @@ -45,5 +47,8 @@ class InstallCommand extends Command {
final flutterVersion = await inferFlutterVersion(version);

await installFlutterVersion(flutterVersion, skipSetup: skipSetup);
if (hasConfig) {
setAsProjectVersion(version);
}
}
}
3 changes: 2 additions & 1 deletion lib/commands/list.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:io';

import 'package:console/console.dart';
import 'package:fvm/constants.dart';
import 'package:fvm/utils/helpers.dart';
import 'package:fvm/utils/print.dart';
Expand Down Expand Up @@ -35,7 +36,7 @@ class ListCommand extends Command {

void printVersions(String version) {
if (isCurrentVersion(version)) {
version = '$version (current)';
version = '$version ${Icon.HEAVY_CHECKMARK}';
}
Print.info(version);
}
Expand Down
42 changes: 42 additions & 0 deletions lib/commands/releases.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'package:args/command_runner.dart';
import 'package:console/console.dart';
import 'package:date_format/date_format.dart';
import 'package:io/ansi.dart';

import 'package:fvm/utils/releases_helper.dart';

/// List installed SDK Versions
class ReleasesCommand extends Command {
// The [name] and [description] properties must be defined by every
// subclass.
@override
final name = 'releases';

@override
final description = 'Lists Flutter SDK releases.';

/// Constructor
ReleasesCommand();

@override
void run() async {
final flutterReleases = await fetchReleases();
final channels = flutterReleases.currentRelease.toHashMap();
final releases = flutterReleases.releases.reversed;

releases.forEach((r) {
final channel = channels[r.version];
final channelOutput = green.wrap('$channel');
final version = yellow.wrap(r.version.padRight(17));
final pipe = Icon.PIPE_VERTICAL;
final friendlyDate =
formatDate(r.releaseDate, [M, ' ', d, ' ', yy]).padRight(10);
if (channel != null) {
print('----------$channelOutput----------');
print('$friendlyDate $pipe $version');
} else {
print('$friendlyDate $pipe $version');
}
});
}
}
15 changes: 14 additions & 1 deletion lib/exceptions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,20 @@ class ExceptionMissingChannelVersion implements Exception {
}
}

/// Cannot find a config for the projec
/// Could not fetch Flutter releases
class ExceptionCouldNotFetchReleases implements Exception {
final message = 'Could not fetch Flutter releases.';

/// Constructor
ExceptionCouldNotFetchReleases();

@override
String toString() {
return message;
}
}

/// Cannot find a config for the project
class ExceptionProjectConfigNotFound implements Exception {
final message = 'No config found for this project.';

Expand Down
2 changes: 2 additions & 0 deletions lib/fvm.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:fvm/commands/config.dart';
import 'package:fvm/commands/flutter.dart';
import 'package:fvm/commands/install.dart';
import 'package:fvm/commands/list.dart';
import 'package:fvm/commands/releases.dart';
import 'package:fvm/commands/remove.dart';
import 'package:fvm/commands/runner.dart';
import 'package:fvm/commands/use.dart';
Expand All @@ -22,6 +23,7 @@ Future<void> fvmRunner(List<String> args) async {
runner..addCommand(UseCommand());
runner..addCommand(ConfigCommand());
runner..addCommand(VersionCommand());
runner..addCommand(ReleasesCommand());

return await runner.run(args).catchError((exc, st) {
if (exc is String) {
Expand Down
22 changes: 18 additions & 4 deletions lib/utils/helpers.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'dart:io';

import 'package:fvm/constants.dart';
Expand Down Expand Up @@ -79,14 +81,26 @@ bool isCurrentVersion(String version) {
/// The Flutter SDK Path referenced on FVM
String getFlutterSdkPath({String version}) {
var sdkVersion = version;
if (version == null) {
final config = readProjectConfig();
sdkVersion = config.flutterSdkVersion;
}
sdkVersion ??= readProjectConfig().flutterSdkVersion;
return path.join(kVersionsDir.path, sdkVersion);
}

String getFlutterSdkExec({String version}) {
return path.join(getFlutterSdkPath(version: version), 'bin',
Platform.isWindows ? 'flutter.bat' : 'flutter');
}

String camelCase(String subject) {
final _splittedString = subject.split('_');

if (_splittedString.isEmpty) return '';

final _firstWord = _splittedString[0].toLowerCase();
final _restWords = _splittedString.sublist(1).map(capitalize).toList();

return _firstWord + _restWords.join('');
}

String capitalize(String word) {
return '${word[0].toUpperCase()}${word.substring(1)}';
}
5 changes: 0 additions & 5 deletions lib/utils/logger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,3 @@ import 'package:cli_util/cli_logging.dart';

/// Log
Logger logger = Logger.standard();

/// Finishes progress
void finishProgress(Progress progress) {
progress.finish(showTiming: true);
}
157 changes: 157 additions & 0 deletions lib/utils/releases_helper.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import 'dart:convert';
import 'dart:io';

import 'package:fvm/exceptions.dart';
import 'package:http/http.dart' as http;

/// Gets platform specific release URL
String getReleasesUrl({String platform}) {
platform ??= Platform.operatingSystem;
return 'https://storage.googleapis.com/flutter_infra/releases/releases_$platform.json';
}

/// Fetches Flutter SDK Releases
Future<FlutterReleases> fetchReleases() async {
try {
final response = await http.get(getReleasesUrl());
return flutterReleasesFromMap(response.body);
} on Exception {
throw ExceptionCouldNotFetchReleases();
}
}

Map<String, dynamic> filterCurrentReleases(Map<String, dynamic> json) {
final currentRelease = json['current_release'] as Map<String, dynamic>;
final releases = json['releases'] as List<dynamic>;
// Hashes of current releases
final hashMap = currentRelease.map((key, value) => MapEntry(value, key));

// Filter out channel/currentRelease versions
releases.forEach((r) {
// Check if release hash is in hashmap
final channel = hashMap[r['hash']];
if (channel != null) {
currentRelease[channel] = r['version'];
}
});

return currentRelease;
}

FlutterReleases flutterReleasesFromMap(String str) =>
FlutterReleases.fromMap(jsonDecode(str) as Map<String, dynamic>);

String flutterReleasesToMap(FlutterReleases data) => json.encode(data.toMap());

class FlutterReleases {
FlutterReleases({
this.baseUrl,
this.currentRelease,
this.releases,
});

final String baseUrl;
final CurrentRelease currentRelease;
final List<Release> releases;

factory FlutterReleases.fromMap(Map<String, dynamic> json) {
final currentRelease = filterCurrentReleases(json);
return FlutterReleases(
baseUrl: json['base_url'] as String,
currentRelease: CurrentRelease.fromMap(currentRelease),
releases: List<Release>.from(json['releases']
.map((x) => Release.fromMap(x as Map<String, dynamic>))
as Iterable<dynamic>),
);
}

Map<String, dynamic> toMap() => {
'base_url': baseUrl,
'current_release': currentRelease.toMap(),
'releases': List<dynamic>.from(releases.map((x) => x.toMap())),
};
}

class CurrentRelease {
CurrentRelease({
this.beta,
this.dev,
this.stable,
});

final String beta;
final String dev;
final String stable;

factory CurrentRelease.fromMap(Map<String, dynamic> json) => CurrentRelease(
beta: json['beta'] as String,
dev: json['dev'] as String,
stable: json['stable'] as String,
);

Map<String, dynamic> toMap() => {
'beta': beta,
'dev': dev,
'stable': stable,
};

Map<String, dynamic> toHashMap() => {
'$beta': 'beta',
'$dev': 'dev',
'$stable': 'stable',
};
}

class Release {
Release({
this.hash,
this.channel,
this.version,
this.releaseDate,
this.archive,
this.sha256,
});

final String hash;
final Channel channel;
final String version;
final DateTime releaseDate;
final String archive;
final String sha256;

factory Release.fromMap(Map<String, dynamic> json) => Release(
hash: json['hash'] as String,
channel: channelValues.map[json['channel']],
version: json['version'] as String,
releaseDate: DateTime.parse(json['release_date'] as String),
archive: json['archive'] as String,
sha256: json['sha256'] as String,
);

Map<String, dynamic> toMap() => {
'hash': hash,
'channel': channelValues.reverse[channel],
'version': version,
'release_date': releaseDate.toIso8601String(),
'archive': archive,
'sha256': sha256,
};
}

enum Channel { STABLE, DEV, BETA }

final channelValues = EnumValues(
{'beta': Channel.BETA, 'dev': Channel.DEV, 'stable': Channel.STABLE});

class EnumValues<T> {
Map<String, T> map;
Map<T, String> reverseMap;

EnumValues(this.map);

Map<T, String> get reverse {
reverseMap ??= map.map((k, v) => MapEntry(v, k));

return reverseMap;
}
}
2 changes: 2 additions & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ dependencies:
io: ^0.3.3
path: ^1.6.4
process_run: ^0.10.10+1
http: ^0.12.1
date_format: ^1.0.8

dev_dependencies:
pedantic: ^1.8.0
Expand Down
3 changes: 0 additions & 3 deletions test/fvm_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ import 'test_helpers.dart';

final testPath = '$fvmHome/test_path';

const channel = 'master';
const release = '1.8.0';

void main() {
setUpAll(fvmSetUpAll);
tearDownAll(fvmTearDownAll);
Expand Down
Loading

0 comments on commit 25aa604

Please sign in to comment.