Skip to content

Commit 9a059ba

Browse files
authored
Merge pull request #1 from dart-action/patch-1
feat: support check pull requets
2 parents b3e9536 + 9d4e655 commit 9a059ba

File tree

9 files changed

+415
-48
lines changed

9 files changed

+415
-48
lines changed

.github/workflows/test_code.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ jobs:
2525
- name: Run dart
2626
run: dart run bin/main.dart
2727
shell: bash
28+
env:
29+
PERSON_TOKEN: ${{ secrets.PERSON_TOKEN }}
2830
- name: Run dart test
2931
run: dart test
3032
shell: bash

CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1-
## 1.0.0
1+
# CHANGELOG
22

3-
- Initial version.
3+
## Unreleased
4+
5+
- support check pull requets
6+
7+
## 0.0.1
8+
9+
Init project

action.yml

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
1-
name: 'dart-action'
2-
description: 'Use dart write action'
3-
author: 'CaiJingLong'
1+
name: "dart-action"
2+
description: "Use dart write action"
3+
author: "CaiJingLong"
44

55
inputs:
66
github-token:
7-
description: 'The GitHub token to use for the action'
7+
description: "The GitHub token to use for the action"
88
required: true
9-
bin-name:
10-
description: 'The dart file path of the action'
11-
required: true
12-
default: bin/main.dart
139

1410
runs:
1511
using: composite
1612
steps:
1713
- uses: subosito/flutter-action@v2
1814
name: Install Flutter
1915
with:
20-
channel: 'stable'
16+
channel: "stable"
2117
cache: true
2218
- name: Show Flutter and dart version
2319
run: |
@@ -33,5 +29,7 @@ runs:
3329
working-directory: ${{ github.action_path }}
3430
shell: bash
3531
- name: Run dart
36-
run: dart run "${{ github.action_path }}/${{ inputs.bin-name }}"
37-
shell: bash
32+
run: dart run "${{ github.action_path }}/main.dart"
33+
shell: bash
34+
env:
35+
PERSON_TOKEN: ${{ inputs.github-token }}

bin/main.dart

Lines changed: 86 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,92 @@
11
import 'dart:io';
22

3+
import 'package:dio_pull_request_checker/dio_pull_request_checker.dart';
4+
import 'package:dio_pull_request_checker/github.dart';
5+
import 'package:github/github.dart';
36
import 'package:github_action_context/github_action_context.dart';
47
import 'package:github_action_core/github_action_core.dart';
58

6-
void main(List<String> arguments) {
7-
startGroup('context.fields');
8-
9-
info('context.eventName: ${context.eventName}');
10-
info('context.sha: ${context.sha}');
11-
info('context.ref: ${context.ref}');
12-
info('context.workflow: ${context.workflow}');
13-
info('context.action: ${context.action}');
14-
info('context.actor: ${context.actor}');
15-
info('context.job: ${context.job}');
16-
info('context.runNumber: ${context.runNumber}');
17-
info('context.runId: ${context.runId}');
18-
info('context.apiUrl: ${context.apiUrl}');
19-
info('context.serverUrl: ${context.serverUrl}');
20-
info('context.graphqlUrl: ${context.graphqlUrl}');
21-
groupEnd();
22-
23-
startGroup('env');
24-
print('Current working directory: ${Directory.current.absolute.path}');
25-
groupEnd();
26-
27-
startGroup('context.payload');
28-
info(context.payload.toString());
29-
groupEnd();
9+
void injectGithub() {
10+
// Get the token from env
11+
final token = Platform.environment['PERSON_TOKEN'];
12+
if (token == null) {
13+
setFailed('The input github-token is required');
14+
}
15+
github = GitHub(auth: Authentication.withToken(token));
16+
}
17+
18+
Future<void> main(List<String> arguments) async {
19+
// 1. Check if the current workflow run is a pull request
20+
final pr = context.payload.pullRequest;
21+
final number = pr?.number;
22+
if (pr == null || number == null) {
23+
print('This is not a pull request, skipping');
24+
return;
25+
}
26+
27+
final repository = context.payload.repository;
28+
if (repository == null) {
29+
print('Cannot get repository information, skipping');
30+
return;
31+
}
32+
33+
injectGithub();
34+
35+
final owner = repository.owner.login;
36+
final repo = repository.name;
37+
final latestCommitIdShort = context.payload['after'];
38+
final comment =
39+
'''The PR applies invalid `CHANGELOG.md` (latest check @$latestCommitIdShort). Please correct it according to the [Wiki](https://github.com/cfug/dio/wiki/Releasing-a-new-version-of-packages#before-start).
40+
41+
> PR 更改了 `CHANGELOG.md`(最新检查的提交 $latestCommitIdShort)但内容不符合格式。请参考 [Wiki](https://github.com/cfug/dio/wiki/Releasing-a-new-version-of-packages#before-start) 修改。
42+
43+
''';
44+
45+
// 2. Get all changed files
46+
final files = await getPullRequestFiles(
47+
owner: owner,
48+
repo: repo,
49+
number: number,
50+
);
51+
52+
// 3. Check if the changed files contain changelog
53+
final changelogFiles = files.where((file) {
54+
final filename = file.filename; // The file name is path
55+
return filename != null && filename.contains('CHANGELOG.md');
56+
}).toList();
57+
58+
if (changelogFiles.isEmpty) {
59+
// 4. If not, fail the workflow, before that, we add comments to the pull request
60+
// Get the latest commit id
61+
notice('No CHANGELOG.md found in the pull request, please add one.');
62+
await sendComment(owner: owner, repo: repo, number: number, body: comment);
63+
setFailed('No CHANGELOG.md found in the pull request, please add one.');
64+
}
65+
66+
// 5. Have changelog, check if the content is valid
67+
for (final file in changelogFiles) {
68+
final content = await getChangeFileContentWithPullRequest(
69+
owner: owner,
70+
repo: repo,
71+
number: number,
72+
path: file.filename!,
73+
);
74+
75+
final before = content.before;
76+
final after = content.after;
77+
78+
final checkMsg = checkChangeLog(before, after);
79+
if (checkMsg != null) {
80+
// 6. If not, fail the workflow, before that, we add comments to the pull request
81+
// Get the latest commit id
82+
notice(checkMsg);
83+
await sendComment(
84+
owner: owner,
85+
repo: repo,
86+
number: number,
87+
body: comment,
88+
);
89+
setFailed(checkMsg);
90+
}
91+
}
3092
}

lib/dart_action_template.dart

Lines changed: 0 additions & 3 deletions
This file was deleted.

lib/dio_pull_request_checker.dart

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
final unreleasedTag = 'Unreleased';
2+
3+
/// Check if the changelog is valid
4+
String? checkChangeLog(String oldContent, String newContent) {
5+
final oldMap = getChangelogMap(oldContent);
6+
final newMap = getChangelogMap(newContent);
7+
8+
final oldVersions = oldMap.keys.toList();
9+
final newVersions = newMap.keys.toList();
10+
11+
final diffVersionList =
12+
oldVersions.where((element) => !newVersions.contains(element)).toList();
13+
14+
final diffVersionList2 =
15+
newVersions.where((element) => !oldVersions.contains(element)).toList();
16+
17+
final diff = (diffVersionList + diffVersionList2).toSet().toList();
18+
19+
if (diff.isNotEmpty) {
20+
return 'The changelog cannot add or remove versions: $diff';
21+
}
22+
23+
final changedVersions = <String>[];
24+
25+
for (final version in oldVersions) {
26+
final oldContent = oldMap[version]!;
27+
final newContent = newMap[version]!;
28+
29+
if (oldContent != newContent) {
30+
changedVersions.add(version);
31+
}
32+
}
33+
34+
if (changedVersions.isEmpty) {
35+
return 'No changelog content changed.';
36+
}
37+
38+
if (changedVersions.length > 1) {
39+
return 'Only one version changelog can be changed at a time: $changedVersions';
40+
}
41+
42+
final changedVersion = changedVersions.first;
43+
if (changedVersion != unreleasedTag) {
44+
return 'Only $unreleasedTag version changelog can be changed. Current changed version: $changedVersion';
45+
}
46+
47+
return null;
48+
}
49+
50+
/// Get the changelog map, key is the version, value is the content.
51+
///
52+
/// The content is trimmed.
53+
Map<String, String> getChangelogMap(String content) {
54+
final result = <String, List<String>>{};
55+
56+
final lines = content.trim().split('\n');
57+
58+
var currentVersion = '';
59+
60+
for (final line in lines) {
61+
if (line.startsWith('# ')) {
62+
final version = line.substring(2).trim();
63+
currentVersion = version;
64+
result[version] = [];
65+
} else if (line.startsWith('## ')) {
66+
final version = line.substring(3).trim();
67+
currentVersion = version;
68+
result[version] = [];
69+
} else if (line.startsWith('### ')) {
70+
final version = line.substring(4).trim();
71+
currentVersion = version;
72+
result[version] = [];
73+
} else {
74+
result[currentVersion]!.add(line);
75+
}
76+
}
77+
78+
return result.map((key, value) => MapEntry(key, value.join('\n').trim()));
79+
}

lib/github.dart

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import 'dart:convert';
2+
3+
import 'package:github/github.dart';
4+
import 'package:github_action_core/github_action_core.dart';
5+
6+
late GitHub github;
7+
8+
Future<List<PullRequestFile>> getPullRequestFiles({
9+
required String owner,
10+
required String repo,
11+
required int number,
12+
}) async {
13+
final slug = RepositorySlug(owner, repo);
14+
return github.pullRequests.listFiles(slug, number).toList();
15+
}
16+
17+
Future<void> sendComment({
18+
required String owner,
19+
required String repo,
20+
required int number,
21+
required String body,
22+
}) async {
23+
final slug = RepositorySlug(owner, repo);
24+
await github.issues.createComment(slug, number, body);
25+
}
26+
27+
class ChangeFileContent {
28+
final String before;
29+
final String after;
30+
31+
const ChangeFileContent(this.before, this.after);
32+
}
33+
34+
Future<ChangeFileContent> getChangeFileContentWithPullRequest({
35+
required String owner,
36+
required String repo,
37+
required int number,
38+
required String path,
39+
}) async {
40+
final slug = RepositorySlug(owner, repo);
41+
final pr = await github.pullRequests.get(slug, number);
42+
43+
final head = pr.head?.sha;
44+
final base = pr.base?.sha;
45+
46+
if (head == null || base == null) {
47+
throw Exception('Cannot get head or base commit');
48+
}
49+
50+
// Get the content of the head and base
51+
final headContents =
52+
await github.repositories.getContents(slug, path, ref: head);
53+
final headContent = headContents.file?.content;
54+
55+
final baseContents =
56+
await github.repositories.getContents(slug, path, ref: base);
57+
final baseContent = baseContents.file?.content;
58+
59+
if (headContent == null || baseContent == null) {
60+
throw Exception('Cannot get head or base content');
61+
}
62+
63+
debug('head: $headContent');
64+
debug('base: $baseContent');
65+
66+
return ChangeFileContent(
67+
baseContent.decodeHaveNewLineBase64(),
68+
headContent.decodeHaveNewLineBase64(),
69+
);
70+
}
71+
72+
extension GithubContentExt on String {
73+
String decodeBase64() {
74+
final list = base64.decode(this);
75+
return utf8.decode(list);
76+
}
77+
78+
String decodeHaveNewLineBase64() {
79+
return replaceAll('\n', '').decodeBase64();
80+
}
81+
}

pubspec.yaml

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
name: dart_action_template
1+
name: dio_pull_request_checker
22
description: A github action template for dart action
33
version: 1.0.0
4-
repository: https://github.com/dart-action/dart_action_template
4+
repository: https://github.com/cfug/dio-pull-request-checker
55

66
environment:
77
sdk: ^3.0.0
88

99
dependencies:
10-
github_action_context: ^0.2.0+1
11-
github_action_core: ^0.1.1+2
12-
10+
github_action_context: ^0.2.1
11+
github_action_core: ^0.2.0
12+
github: ^9.14.0
13+
1314
dev_dependencies:
1415
lints: ^2.0.0
1516
test: ^1.21.0

0 commit comments

Comments
 (0)