@ffflorian/auto-merge 1.0.3 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/dist/AutoMerge.d.ts +2 -45
- package/dist/AutoMerge.js +40 -27
- package/dist/AutoMerge.test.js +6 -2
- package/dist/cli.js +1 -2
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -27,7 +27,6 @@ Options:
|
|
|
27
27
|
-a, --approve approve before merging
|
|
28
28
|
-c, --config <path> specify a configuration file (default: .automergerc.json)
|
|
29
29
|
-d, --dry-run don't send any data
|
|
30
|
-
-f, --merge-drafts merge draft PRs (default: false)
|
|
31
30
|
-s, --squash squash when merging (default: false)
|
|
32
31
|
-V, --version output the version number
|
|
33
32
|
-h, --help display help for command
|
|
@@ -41,20 +40,18 @@ The structure of the configuration file is the following:
|
|
|
41
40
|
|
|
42
41
|
```ts
|
|
43
42
|
{
|
|
44
|
-
/** The GitHub auth token */
|
|
43
|
+
/** The GitHub auth token (needs read and write access to code and pull requests) */
|
|
45
44
|
authToken: string;
|
|
46
45
|
/** Approve before merging */
|
|
47
46
|
autoApprove?: boolean;
|
|
48
47
|
/** Don't send any data */
|
|
49
48
|
dryRun?: boolean;
|
|
50
|
-
/** Merge draft PRs */
|
|
51
|
-
mergeDrafts?: boolean;
|
|
52
49
|
/** All projects to include */
|
|
53
50
|
projects: {
|
|
54
51
|
/** All projects hosted on GitHub in the format `user/repo` */
|
|
55
52
|
gitHub: string[];
|
|
56
53
|
};
|
|
57
|
-
|
|
54
|
+
/** Squash when merging */
|
|
58
55
|
squash?: boolean;
|
|
59
56
|
}
|
|
60
57
|
```
|
package/dist/AutoMerge.d.ts
CHANGED
|
@@ -1,47 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
interface GitHubPullRequest {
|
|
3
|
-
draft: boolean;
|
|
4
|
-
head: {
|
|
5
|
-
/** The branch name */
|
|
6
|
-
ref: string;
|
|
7
|
-
/** The commit SHA-1 hash */
|
|
8
|
-
sha: string;
|
|
9
|
-
};
|
|
10
|
-
/** The pull request number */
|
|
11
|
-
number: number;
|
|
12
|
-
/** The pull request title */
|
|
13
|
-
title: string;
|
|
14
|
-
}
|
|
15
|
-
export interface ActionResult {
|
|
16
|
-
error?: string;
|
|
17
|
-
pullNumber: number;
|
|
18
|
-
status: 'bad' | 'good';
|
|
19
|
-
}
|
|
20
|
-
export interface AutoMergeConfig {
|
|
21
|
-
/** The GitHub auth token */
|
|
22
|
-
authToken: string;
|
|
23
|
-
/** Approve before merging */
|
|
24
|
-
autoApprove?: boolean;
|
|
25
|
-
/** Don't send any data */
|
|
26
|
-
dryRun?: boolean;
|
|
27
|
-
/** Merge draft PRs */
|
|
28
|
-
mergeDrafts?: boolean;
|
|
29
|
-
/** All projects to include */
|
|
30
|
-
projects: {
|
|
31
|
-
/** All projects hosted on GitHub in the format `user/repo` */
|
|
32
|
-
gitHub: string[];
|
|
33
|
-
};
|
|
34
|
-
/** Squash when merging */
|
|
35
|
-
squash?: boolean;
|
|
36
|
-
}
|
|
37
|
-
export interface Repository {
|
|
38
|
-
pullRequests: GitHubPullRequest[];
|
|
39
|
-
repositorySlug: string;
|
|
40
|
-
}
|
|
41
|
-
export interface RepositoryResult {
|
|
42
|
-
actionResults: ActionResult[];
|
|
43
|
-
repositorySlug: string;
|
|
44
|
-
}
|
|
1
|
+
import type { AutoMergeConfig, ActionResult, Repository, RepositoryResult } from './types/index.js';
|
|
45
2
|
export declare class AutoMerge {
|
|
46
3
|
private readonly apiClient;
|
|
47
4
|
private readonly config;
|
|
@@ -51,6 +8,7 @@ export declare class AutoMerge {
|
|
|
51
8
|
private checkRepositorySlug;
|
|
52
9
|
approveByMatch(regex: RegExp, repositories?: Repository[]): Promise<RepositoryResult[]>;
|
|
53
10
|
private getMatchingRepositories;
|
|
11
|
+
private isPullRequestMergeable;
|
|
54
12
|
mergeByMatch(regex: RegExp, repositories?: Repository[]): Promise<RepositoryResult[]>;
|
|
55
13
|
approveByPullNumber(repositorySlug: string, pullNumber: number): Promise<ActionResult>;
|
|
56
14
|
mergePullRequest(repositorySlug: string, pullNumber: number, squash?: boolean): Promise<ActionResult>;
|
|
@@ -62,4 +20,3 @@ export declare class AutoMerge {
|
|
|
62
20
|
private putMerge;
|
|
63
21
|
private getPullRequestsBySlug;
|
|
64
22
|
}
|
|
65
|
-
export {};
|
package/dist/AutoMerge.js
CHANGED
|
@@ -17,6 +17,7 @@ export class AutoMerge {
|
|
|
17
17
|
this.apiClient = axios.create({
|
|
18
18
|
baseURL: 'https://api.github.com',
|
|
19
19
|
headers: {
|
|
20
|
+
Accept: 'application/vnd.github+json',
|
|
20
21
|
Authorization: `token ${this.config.authToken}`,
|
|
21
22
|
'User-Agent': `${toolName} v${toolVersion}`,
|
|
22
23
|
},
|
|
@@ -45,33 +46,48 @@ export class AutoMerge {
|
|
|
45
46
|
async approveByMatch(regex, repositories) {
|
|
46
47
|
const allRepositories = repositories || (await this.getRepositoriesWithOpenPullRequests());
|
|
47
48
|
const matchingRepositories = this.getMatchingRepositories(allRepositories, regex);
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
const actionResults =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
const processedRepositories = [];
|
|
50
|
+
for (const { pullRequests, repositorySlug } of matchingRepositories) {
|
|
51
|
+
const actionResults = [];
|
|
52
|
+
for (const pullRequest of pullRequests) {
|
|
53
|
+
actionResults.push(await this.approveByPullNumber(repositorySlug, pullRequest.number));
|
|
54
|
+
}
|
|
55
|
+
processedRepositories.push({ actionResults, repositorySlug });
|
|
56
|
+
}
|
|
57
|
+
return processedRepositories;
|
|
54
58
|
}
|
|
55
59
|
getMatchingRepositories(repositories, regex) {
|
|
56
|
-
|
|
57
|
-
|
|
60
|
+
const matchingRepositories = [];
|
|
61
|
+
for (const repository of repositories) {
|
|
58
62
|
const matchingPullRequests = repository.pullRequests.filter(pullRequest => new RegExp(regex).test(pullRequest.head.ref));
|
|
59
63
|
if (matchingPullRequests.length) {
|
|
60
|
-
|
|
64
|
+
matchingRepositories.push({ pullRequests: matchingPullRequests, repositorySlug: repository.repositorySlug });
|
|
61
65
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
}
|
|
67
|
+
return matchingRepositories;
|
|
68
|
+
}
|
|
69
|
+
async isPullRequestMergeable(repositorySlug, pullNumber) {
|
|
70
|
+
const resourceUrl = `/repos/${repositorySlug}/pulls/${pullNumber}`;
|
|
71
|
+
const response = await this.apiClient.get(resourceUrl);
|
|
72
|
+
return response.data.mergeable_state === 'clean';
|
|
65
73
|
}
|
|
66
74
|
async mergeByMatch(regex, repositories) {
|
|
67
75
|
const allRepositories = repositories || (await this.getRepositoriesWithOpenPullRequests());
|
|
68
76
|
const matchingRepositories = this.getMatchingRepositories(allRepositories, regex);
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
const actionResults =
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
const processedRepositories = [];
|
|
78
|
+
for (const { pullRequests, repositorySlug } of matchingRepositories) {
|
|
79
|
+
const actionResults = [];
|
|
80
|
+
for (const pullRequest of pullRequests) {
|
|
81
|
+
const isMergeable = this.isPullRequestMergeable(repositorySlug, pullRequest.number);
|
|
82
|
+
if (!isMergeable) {
|
|
83
|
+
this.logger.warn(`Pull request #${pullRequest.number} in "${repositorySlug}" is not mergeable. Skipping.`);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
actionResults.push(await this.mergePullRequest(repositorySlug, pullRequest.number, this.config.squash));
|
|
87
|
+
}
|
|
88
|
+
processedRepositories.push({ actionResults, repositorySlug });
|
|
89
|
+
}
|
|
90
|
+
return processedRepositories;
|
|
75
91
|
}
|
|
76
92
|
async approveByPullNumber(repositorySlug, pullNumber) {
|
|
77
93
|
const actionResult = { pullNumber, status: 'good' };
|
|
@@ -103,17 +119,17 @@ export class AutoMerge {
|
|
|
103
119
|
}
|
|
104
120
|
async getAllRepositories() {
|
|
105
121
|
const repositorySlugs = this.config.projects.gitHub.filter(repositorySlug => this.checkRepositorySlug(repositorySlug));
|
|
106
|
-
const
|
|
122
|
+
const repositories = [];
|
|
123
|
+
for (const repositorySlug of repositorySlugs) {
|
|
107
124
|
try {
|
|
108
125
|
const pullRequests = await this.getPullRequestsBySlug(repositorySlug);
|
|
109
|
-
|
|
126
|
+
repositories.push({ pullRequests, repositorySlug });
|
|
110
127
|
}
|
|
111
128
|
catch (error) {
|
|
112
129
|
this.logger.error(`Could not get pull requests for "${repositorySlug}": ${error.message}`);
|
|
113
|
-
return undefined;
|
|
114
130
|
}
|
|
115
|
-
}
|
|
116
|
-
return
|
|
131
|
+
}
|
|
132
|
+
return repositories;
|
|
117
133
|
}
|
|
118
134
|
async getRepositoriesWithOpenPullRequests() {
|
|
119
135
|
const allRepositories = await this.getAllRepositories();
|
|
@@ -131,11 +147,8 @@ export class AutoMerge {
|
|
|
131
147
|
}
|
|
132
148
|
async getPullRequestsBySlug(repositorySlug) {
|
|
133
149
|
const resourceUrl = `/repos/${repositorySlug}/pulls`;
|
|
134
|
-
const params = { state: 'open' };
|
|
150
|
+
const params = { per_page: 100, state: 'open' };
|
|
135
151
|
const response = await this.apiClient.get(resourceUrl, { params });
|
|
136
|
-
if (this.config.mergeDrafts) {
|
|
137
|
-
response.data = response.data.filter(pr => !pr.draft);
|
|
138
|
-
}
|
|
139
152
|
return response.data;
|
|
140
153
|
}
|
|
141
154
|
}
|
package/dist/AutoMerge.test.js
CHANGED
|
@@ -44,11 +44,15 @@ describe('AutoMerge', () => {
|
|
|
44
44
|
},
|
|
45
45
|
});
|
|
46
46
|
nock(autoMerge['apiClient'].defaults.baseURL)
|
|
47
|
-
.post(/^\/repos
|
|
47
|
+
.post(/^\/repos\/.+?\/.+?\/pulls\/\d+\/reviews\/?$/)
|
|
48
48
|
.reply(HTTP_STATUS.OK, { data: 'not-used' })
|
|
49
49
|
.persist();
|
|
50
50
|
nock(autoMerge['apiClient'].defaults.baseURL)
|
|
51
|
-
.
|
|
51
|
+
.get(/^\/repos\/.+?\/.+?\/pulls\/\d+\/?$/)
|
|
52
|
+
.reply(HTTP_STATUS.OK, { data: 'not-used' })
|
|
53
|
+
.persist();
|
|
54
|
+
nock(autoMerge['apiClient'].defaults.baseURL)
|
|
55
|
+
.put(/^\/repos\/.+?\/.+?\/pulls\/\d+\/merge\/?$/)
|
|
52
56
|
.reply(HTTP_STATUS.OK, { data: 'not-used' })
|
|
53
57
|
.persist();
|
|
54
58
|
});
|
package/dist/cli.js
CHANGED
|
@@ -37,7 +37,6 @@ const configFileData = {
|
|
|
37
37
|
...configResult.config,
|
|
38
38
|
...(commanderOptions.approve && { autoApprove: commanderOptions.approve }),
|
|
39
39
|
...(commanderOptions.dryRun && { dryRun: commanderOptions.dryRun }),
|
|
40
|
-
...(commanderOptions.mergeDrafts && { mergeDrafts: commanderOptions.mergeDrafts }),
|
|
41
40
|
};
|
|
42
41
|
async function runAction(autoMerge, repositories, pullRequestSlug) {
|
|
43
42
|
const regex = new RegExp(pullRequestSlug, 'gi');
|
|
@@ -47,7 +46,7 @@ async function runAction(autoMerge, repositories, pullRequestSlug) {
|
|
|
47
46
|
}
|
|
48
47
|
const mergeResults = await autoMerge.mergeByMatch(regex, repositories);
|
|
49
48
|
const successCount = [...approveResults, ...mergeResults].filter(repository => {
|
|
50
|
-
return repository.actionResults.some(result => result.error === undefined);
|
|
49
|
+
return repository.actionResults.some(result => typeof result.error === 'undefined');
|
|
51
50
|
}).length;
|
|
52
51
|
const prPluralized = pluralize('PR', successCount);
|
|
53
52
|
const doAction = configFileData.autoApprove ? 'Approved and merged' : 'Merged';
|
package/package.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"author": "Florian Imdahl <git@ffflorian.de>",
|
|
3
3
|
"bin": "dist/cli.js",
|
|
4
4
|
"dependencies": {
|
|
5
|
-
"axios": "1.
|
|
6
|
-
"commander": "14.0.
|
|
5
|
+
"axios": "1.13.2",
|
|
6
|
+
"commander": "14.0.2",
|
|
7
7
|
"cosmiconfig": "9.0.0",
|
|
8
8
|
"logdown": "3.3.1"
|
|
9
9
|
},
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"http-status-codes": "2.3.0",
|
|
13
13
|
"nock": "14.0.10",
|
|
14
|
-
"rimraf": "6.0
|
|
14
|
+
"rimraf": "6.1.0",
|
|
15
|
+
"tsx": "4.20.6",
|
|
15
16
|
"typescript": "5.9.3",
|
|
16
|
-
"vitest": "
|
|
17
|
+
"vitest": "4.0.6"
|
|
17
18
|
},
|
|
18
19
|
"engines": {
|
|
19
20
|
"node": ">= 18.0"
|
|
@@ -34,10 +35,10 @@
|
|
|
34
35
|
"build": "tsc -p tsconfig.json",
|
|
35
36
|
"clean": "rimraf dist",
|
|
36
37
|
"dist": "yarn clean && yarn build",
|
|
37
|
-
"start": "
|
|
38
|
+
"start": "tsx src/cli.ts",
|
|
38
39
|
"test": "vitest run"
|
|
39
40
|
},
|
|
40
41
|
"type": "module",
|
|
41
|
-
"version": "1.
|
|
42
|
-
"gitHead": "
|
|
42
|
+
"version": "1.1.1",
|
|
43
|
+
"gitHead": "f0960af4e8f76cf9995b1ac709201961c8d00dc2"
|
|
43
44
|
}
|