blob: b878c56b1113f7b1b2c80a5403c65482bb5858da [file]
import 'dart:convert';
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:devtools_shared/devtools_shared.dart';
import 'package:http/http.dart' as http;
const githubToken = 'githubToken';
/// Outputs the latest flutter candidate branch name.
///
/// The latest candidate branch will be the branch that matches the Flutter SDK
/// inside g3.
///
/// Sample usage:
/// ```shell
/// $dart tool/bin/repo_tool.dart latest-flutter-candidate
/// ```
class LatestFlutterCandidateCommand extends Command {
LatestFlutterCandidateCommand() {
argParser.addOption(
githubToken,
help: 'Specify the github auth token to be used for API requests.'
' If unspecified, this may lead to rate limit errors from the'
' GitHub API.',
defaultsTo: '',
);
}
@override
String get name => 'latest-flutter-candidate';
@override
String get description =>
'Outputs the most recent flutter release candidate banch';
@override
Future run() async {
final authToken = (argResults?[githubToken] ?? '') as String;
SemanticVersion latest = SemanticVersion();
String? latestBranchName;
final allBranchNames = <String>[];
// TODO(kenz): consider traversing pages properly instead of using a while
// loop.
// See https://docs.github.com/en/rest/guides/traversing-with-pagination
const maxPerPage = 100;
var requestPage = 0;
bool lastPageReceived = false;
while (!lastPageReceived) {
final uri = Uri.https(
'api.github.com',
'/repos/flutter/flutter/branches',
{
'per_page': '$maxPerPage',
'page': '$requestPage',
},
);
final response = await http.get(
uri,
headers:
authToken.isEmpty ? null : {'authorization': 'Bearer $authToken'},
);
if (response.statusCode != HttpStatus.ok) {
print('HttpStatus ${response.statusCode}: ${response.reasonPhrase}');
return;
}
final List<Map<String, dynamic>> branches =
(jsonDecode(response.body) as List).cast<Map<String, dynamic>>();
final branchNames =
branches.map((branch) => branch['name']).cast<String>();
allBranchNames.addAll(branchNames);
final candidateBranchesNames =
branchNames.where((name) => name.contains('candidate'));
for (final branchName in candidateBranchesNames) {
final semVer = semVerFromCandidateBranch(branchName);
if (semVer != null && semVer > latest) {
latest = semVer;
latestBranchName = branchName;
}
}
requestPage++;
if (branches.length < maxPerPage) {
lastPageReceived = true;
}
}
if (latestBranchName == null) {
throw Exception(
'Something went wrong. Could not find the latest candidate branch:'
'${allBranchNames.join('\n')}');
} else {
print(latestBranchName);
}
}
}
SemanticVersion? semVerFromCandidateBranch(String branch) {
final candidateBranchPattern =
RegExp(r'^flutter-([0-9]+).([0-9]+)-candidate.([0-9]+)$');
final match = candidateBranchPattern.firstMatch(branch);
if (match == null) {
return null;
}
final x = int.parse(match.group(1)!);
final y = int.parse(match.group(2)!);
final z = int.parse(match.group(3)!);
return SemanticVersion(major: x, minor: y, patch: z);
}