|
| 1 | +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +import 'dart:convert'; |
| 5 | +import 'dart:io'; |
| 6 | + |
| 7 | +import 'package:aft/aft.dart'; |
| 8 | + |
| 9 | +class GitHubData { |
| 10 | + GitHubData.fromJson(Map<String, dynamic> json) |
| 11 | + : title = json['title'] as String, |
| 12 | + body = json['body'] as String, |
| 13 | + number = json['number'] as int, |
| 14 | + changedFiles = json['changedFiles'] as int, |
| 15 | + additions = json['additions'] as int, |
| 16 | + deletions = json['deletions'] as int, |
| 17 | + url = json['url'] as String; |
| 18 | + |
| 19 | + String title; |
| 20 | + String body; |
| 21 | + int number; |
| 22 | + int changedFiles; |
| 23 | + int additions; |
| 24 | + int deletions; |
| 25 | + String url; |
| 26 | + |
| 27 | + final String _legal = |
| 28 | + 'By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.'; |
| 29 | + final String _lineBreak = '========================================'; |
| 30 | + |
| 31 | + final emojis = { |
| 32 | + ReviewPriority.urgent: ':alert:', |
| 33 | + ReviewPriority.high: ':arrow_double_up:', |
| 34 | + ReviewPriority.normal: ':arrow_up:', |
| 35 | + ReviewPriority.low: ':arrow_down:', |
| 36 | + }; |
| 37 | + |
| 38 | + @override |
| 39 | + String toString() { |
| 40 | + return '{Title: $title,\nBody: $body,\nNumber: $number,\nChanged Files: $changedFiles,\nAdditions: $additions,\nDeletions: $deletions}'; |
| 41 | + } |
| 42 | + |
| 43 | + /// Converts the PR data to a Slack-formatted string. |
| 44 | + String toMarkdown(ReviewPriority priority) { |
| 45 | + return ''' |
| 46 | +$_lineBreak |
| 47 | +${emojis[priority]} *$title - [$number]($url)* |
| 48 | +> +$additions -$deletions | files: $changedFiles |
| 49 | +${body.replaceAll(_legal, '').trim()} |
| 50 | +*Reviewers:* |
| 51 | +$_lineBreak'''; |
| 52 | + } |
| 53 | +} |
| 54 | + |
| 55 | +enum ReviewPriority { urgent, high, normal, low } |
| 56 | + |
| 57 | +/// Command to clean all temporary files in the repo. |
| 58 | +class ReviewCommand extends AmplifyCommand { |
| 59 | + ReviewCommand() { |
| 60 | + argParser |
| 61 | + ..addOption( |
| 62 | + 'ref', |
| 63 | + abbr: 'r', |
| 64 | + help: |
| 65 | + 'The PR reference. Can be a PR number or URL. Defaults to the current branch.', |
| 66 | + ) |
| 67 | + ..addOption( |
| 68 | + 'priority', |
| 69 | + abbr: 'p', |
| 70 | + help: 'Determines the emoji to use for the PR review.', |
| 71 | + allowed: ReviewPriority.values.map((e) => e.name).toList(), |
| 72 | + defaultsTo: ReviewPriority.normal.name, |
| 73 | + ); |
| 74 | + } |
| 75 | + @override |
| 76 | + String get description => 'Creates PR review text for slack'; |
| 77 | + |
| 78 | + @override |
| 79 | + String get name => 'review'; |
| 80 | + |
| 81 | + /// The PR ref to review. |
| 82 | + late final ref = argResults?['ref'] as String? ?? ''; |
| 83 | + |
| 84 | + /// Determines the emoji to use for the PR review. |
| 85 | + late final priority = ReviewPriority.values.firstWhere( |
| 86 | + (e) => e.name == argResults!['priority'] as String, |
| 87 | + orElse: () => ReviewPriority.normal, |
| 88 | + ); |
| 89 | + |
| 90 | + /// Verifies that the PR is ready using gh CLI. |
| 91 | + Future<void> _verifyPR(String ref) async { |
| 92 | + final branchName = await Process.run( |
| 93 | + 'gh', |
| 94 | + ['pr', 'ready', ref], |
| 95 | + ); |
| 96 | + if (branchName.exitCode != 0) { |
| 97 | + final error = branchName.stderr.toString(); |
| 98 | + if (error.contains('Could not resolve to a PullRequest')) { |
| 99 | + logger.error('A PR is not ready at ref: $ref'); |
| 100 | + } else { |
| 101 | + logger.error('Invalid ref: $error'); |
| 102 | + } |
| 103 | + exit(1); |
| 104 | + } |
| 105 | + } |
| 106 | + |
| 107 | + /// Fetches PR data from GitHub using gh CLI. |
| 108 | + Future<GitHubData> _getGitHubData(String ref) async { |
| 109 | + final process = await Process.run( |
| 110 | + 'gh', |
| 111 | + [ |
| 112 | + 'pr', |
| 113 | + 'view', |
| 114 | + ref, |
| 115 | + '--json', |
| 116 | + 'title,body,number,changedFiles,additions,deletions,url', |
| 117 | + ], |
| 118 | + ); |
| 119 | + if (process.exitCode != 0) { |
| 120 | + logger.error('Could not fetch PR data from GitHub'); |
| 121 | + exit(1); |
| 122 | + } |
| 123 | + if (process.stdout is! String) { |
| 124 | + logger.error('GitHub data is not a string'); |
| 125 | + exit(1); |
| 126 | + } |
| 127 | + |
| 128 | + final prDataJson = |
| 129 | + jsonDecode(process.stdout as String) as Map<String, dynamic>; |
| 130 | + final prData = GitHubData.fromJson(prDataJson); |
| 131 | + return prData; |
| 132 | + } |
| 133 | + |
| 134 | + @override |
| 135 | + Future<void> run() async { |
| 136 | + await super.run(); |
| 137 | + |
| 138 | + await _verifyPR(ref); |
| 139 | + |
| 140 | + final ghData = await _getGitHubData(ref); |
| 141 | + |
| 142 | + final slackString = ghData.toMarkdown(priority); |
| 143 | + |
| 144 | + logger |
| 145 | + ..info(slackString) |
| 146 | + ..info('After pasting into slack, press CMD+SHIFT+F to format the text.') |
| 147 | + ..info('Review output was successful!'); |
| 148 | + } |
| 149 | +} |
0 commit comments