@ffflorian/auto-merge 1.7.0 → 1.7.2
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/dist/AutoMerge.d.ts +9 -9
- package/dist/AutoMerge.js +71 -71
- package/dist/cli.js +14 -14
- package/package.json +4 -4
package/dist/AutoMerge.d.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ActionResult, AutoMergeConfig, Repository, RepositoryResult } from './types/index.js';
|
|
2
2
|
export declare class AutoMerge {
|
|
3
3
|
private readonly baseHeaders;
|
|
4
|
+
private readonly baseURL;
|
|
4
5
|
private readonly config;
|
|
5
6
|
private readonly logger;
|
|
6
|
-
private readonly baseURL;
|
|
7
7
|
constructor(config: AutoMergeConfig);
|
|
8
|
-
private checkConfig;
|
|
9
|
-
private checkRepositorySlug;
|
|
10
8
|
approveByMatch(regex: RegExp, repositories?: Repository[]): Promise<RepositoryResult[]>;
|
|
11
|
-
private getMatchingRepositories;
|
|
12
|
-
private isPullRequestMergeable;
|
|
13
|
-
mergeByMatch(regex: RegExp, repositories?: Repository[]): Promise<RepositoryResult[]>;
|
|
14
9
|
approveByPullNumber(repositorySlug: string, pullNumber: number): Promise<ActionResult>;
|
|
15
|
-
mergePullRequest(repositorySlug: string, pullNumber: number, squash?: boolean): Promise<ActionResult>;
|
|
16
10
|
getAllRepositories(): Promise<Repository[]>;
|
|
17
11
|
getRepositoriesWithOpenPullRequests(): Promise<Repository[]>;
|
|
12
|
+
mergeByMatch(regex: RegExp, repositories?: Repository[]): Promise<RepositoryResult[]>;
|
|
13
|
+
mergePullRequest(repositorySlug: string, pullNumber: number, squash?: boolean): Promise<ActionResult>;
|
|
14
|
+
private checkConfig;
|
|
15
|
+
private checkRepositorySlug;
|
|
16
|
+
private getMatchingRepositories;
|
|
17
|
+
private getPullRequestsBySlug;
|
|
18
|
+
private isPullRequestMergeable;
|
|
18
19
|
/** @see https://docs.github.com/en/rest/reference/pulls#create-a-review-for-a-pull-request */
|
|
19
20
|
private postReview;
|
|
20
21
|
/** @see https://docs.github.com/en/rest/reference/issues#create-an-issue-comment */
|
|
21
22
|
private putMerge;
|
|
22
|
-
private getPullRequestsBySlug;
|
|
23
23
|
}
|
package/dist/AutoMerge.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import logdown from 'logdown';
|
|
1
2
|
import fs from 'node:fs/promises';
|
|
2
3
|
import path from 'node:path';
|
|
3
|
-
import logdown from 'logdown';
|
|
4
4
|
const __dirname = import.meta.dirname;
|
|
5
5
|
const packageJsonPath = path.join(__dirname, '../package.json');
|
|
6
6
|
const { name: toolName, version: toolVersion } = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8'));
|
|
@@ -21,25 +21,6 @@ export class AutoMerge {
|
|
|
21
21
|
};
|
|
22
22
|
this.checkConfig(this.config);
|
|
23
23
|
}
|
|
24
|
-
checkConfig(config) {
|
|
25
|
-
var _a;
|
|
26
|
-
if (!((_a = config.projects) === null || _a === void 0 ? void 0 : _a.gitHub) || config.projects.gitHub.length < 1) {
|
|
27
|
-
throw new Error('No projects in config file specified');
|
|
28
|
-
}
|
|
29
|
-
if (!config.authToken) {
|
|
30
|
-
throw new Error('No authentication token in config file specified');
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
checkRepositorySlug(repositorySlug) {
|
|
34
|
-
const gitHubUsernameRegex = /^\w+(?:-?\w+){0,37}\w$/i;
|
|
35
|
-
const gitHubRepositoryRegex = /^[\w-.]{0,99}\w$/i;
|
|
36
|
-
const [userName, repositoryName] = repositorySlug.trim().replace(/^\//, '').replace(/\/$/, '').split('/');
|
|
37
|
-
if (!repositoryName || !gitHubUsernameRegex.test(userName) || !gitHubRepositoryRegex.test(repositoryName)) {
|
|
38
|
-
this.logger.warn(`Invalid GitHub repository slug "${repositorySlug}". Skipping.`);
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
return true;
|
|
42
|
-
}
|
|
43
24
|
async approveByMatch(regex, repositories) {
|
|
44
25
|
const allRepositories = repositories || (await this.getRepositoriesWithOpenPullRequests());
|
|
45
26
|
const matchingRepositories = this.getMatchingRepositories(allRepositories, regex);
|
|
@@ -53,24 +34,37 @@ export class AutoMerge {
|
|
|
53
34
|
}
|
|
54
35
|
return processedRepositories;
|
|
55
36
|
}
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
matchingRepositories.push({ pullRequests: matchingPullRequests, repositorySlug: repository.repositorySlug });
|
|
37
|
+
async approveByPullNumber(repositorySlug, pullNumber) {
|
|
38
|
+
const actionResult = { pullNumber, status: 'good' };
|
|
39
|
+
try {
|
|
40
|
+
if (!this.config.dryRun) {
|
|
41
|
+
await this.postReview(repositorySlug, pullNumber);
|
|
62
42
|
}
|
|
63
43
|
}
|
|
64
|
-
|
|
44
|
+
catch (error) {
|
|
45
|
+
this.logger.error(`Could not approve request #${pullNumber} in "${repositorySlug}": ${error.message}`);
|
|
46
|
+
actionResult.status = 'bad';
|
|
47
|
+
actionResult.error = error.toString();
|
|
48
|
+
}
|
|
49
|
+
return actionResult;
|
|
65
50
|
}
|
|
66
|
-
async
|
|
67
|
-
const
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
51
|
+
async getAllRepositories() {
|
|
52
|
+
const repositorySlugs = this.config.projects.gitHub.filter(repositorySlug => this.checkRepositorySlug(repositorySlug));
|
|
53
|
+
const repositories = [];
|
|
54
|
+
for (const repositorySlug of repositorySlugs) {
|
|
55
|
+
try {
|
|
56
|
+
const pullRequests = await this.getPullRequestsBySlug(repositorySlug);
|
|
57
|
+
repositories.push({ pullRequests, repositorySlug });
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
this.logger.error(`Could not get pull requests for "${repositorySlug}": ${error.message}`);
|
|
61
|
+
}
|
|
71
62
|
}
|
|
72
|
-
|
|
73
|
-
|
|
63
|
+
return repositories;
|
|
64
|
+
}
|
|
65
|
+
async getRepositoriesWithOpenPullRequests() {
|
|
66
|
+
const allRepositories = await this.getAllRepositories();
|
|
67
|
+
return allRepositories.filter(repository => !!repository.pullRequests.length);
|
|
74
68
|
}
|
|
75
69
|
async mergeByMatch(regex, repositories) {
|
|
76
70
|
const allRepositories = repositories || (await this.getRepositoriesWithOpenPullRequests());
|
|
@@ -90,20 +84,6 @@ export class AutoMerge {
|
|
|
90
84
|
}
|
|
91
85
|
return processedRepositories;
|
|
92
86
|
}
|
|
93
|
-
async approveByPullNumber(repositorySlug, pullNumber) {
|
|
94
|
-
const actionResult = { pullNumber, status: 'good' };
|
|
95
|
-
try {
|
|
96
|
-
if (!this.config.dryRun) {
|
|
97
|
-
await this.postReview(repositorySlug, pullNumber);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
catch (error) {
|
|
101
|
-
this.logger.error(`Could not approve request #${pullNumber} in "${repositorySlug}": ${error.message}`);
|
|
102
|
-
actionResult.status = 'bad';
|
|
103
|
-
actionResult.error = error.toString();
|
|
104
|
-
}
|
|
105
|
-
return actionResult;
|
|
106
|
-
}
|
|
107
87
|
async mergePullRequest(repositorySlug, pullNumber, squash = false) {
|
|
108
88
|
const actionResult = { pullNumber, status: 'good' };
|
|
109
89
|
if (!this.config.dryRun) {
|
|
@@ -121,23 +101,52 @@ export class AutoMerge {
|
|
|
121
101
|
}
|
|
122
102
|
return actionResult;
|
|
123
103
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
104
|
+
checkConfig(config) {
|
|
105
|
+
var _a;
|
|
106
|
+
if (!((_a = config.projects) === null || _a === void 0 ? void 0 : _a.gitHub) || config.projects.gitHub.length < 1) {
|
|
107
|
+
throw new Error('No projects in config file specified');
|
|
108
|
+
}
|
|
109
|
+
if (!config.authToken) {
|
|
110
|
+
throw new Error('No authentication token in config file specified');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
checkRepositorySlug(repositorySlug) {
|
|
114
|
+
const gitHubUsernameRegex = /^\w+(?:-?\w+){0,37}\w$/i;
|
|
115
|
+
const gitHubRepositoryRegex = /^[\w-.]{0,99}\w$/i;
|
|
116
|
+
const [userName, repositoryName] = repositorySlug.trim().replace(/^\//, '').replace(/\/$/, '').split('/');
|
|
117
|
+
if (!repositoryName || !gitHubUsernameRegex.test(userName) || !gitHubRepositoryRegex.test(repositoryName)) {
|
|
118
|
+
this.logger.warn(`Invalid GitHub repository slug "${repositorySlug}". Skipping.`);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
getMatchingRepositories(repositories, regex) {
|
|
124
|
+
const matchingRepositories = [];
|
|
125
|
+
for (const repository of repositories) {
|
|
126
|
+
const matchingPullRequests = repository.pullRequests.filter(pullRequest => new RegExp(regex).test(pullRequest.head.ref));
|
|
127
|
+
if (matchingPullRequests.length) {
|
|
128
|
+
matchingRepositories.push({ pullRequests: matchingPullRequests, repositorySlug: repository.repositorySlug });
|
|
134
129
|
}
|
|
135
130
|
}
|
|
136
|
-
return
|
|
131
|
+
return matchingRepositories;
|
|
137
132
|
}
|
|
138
|
-
async
|
|
139
|
-
const
|
|
140
|
-
|
|
133
|
+
async getPullRequestsBySlug(repositorySlug) {
|
|
134
|
+
const resourceUrl = new URL(`/repos/${repositorySlug}/pulls`, this.baseURL);
|
|
135
|
+
resourceUrl.search = new URLSearchParams({ per_page: '100', state: 'open' }).toString();
|
|
136
|
+
const response = await fetch(resourceUrl, { headers: this.baseHeaders });
|
|
137
|
+
if (!response.ok) {
|
|
138
|
+
throw new Error(`Error while fetching pull requests: ${response.statusText}`);
|
|
139
|
+
}
|
|
140
|
+
return response.json();
|
|
141
|
+
}
|
|
142
|
+
async isPullRequestMergeable(repositorySlug, pullNumber) {
|
|
143
|
+
const resourceUrl = new URL(`/repos/${repositorySlug}/pulls/${pullNumber}`, this.baseURL);
|
|
144
|
+
const response = await fetch(resourceUrl, { headers: this.baseHeaders });
|
|
145
|
+
if (!response.ok) {
|
|
146
|
+
throw new Error(`Error while checking merge request: ${response.statusText}`);
|
|
147
|
+
}
|
|
148
|
+
const pullRequestData = await response.json();
|
|
149
|
+
return pullRequestData.mergeable_state === 'clean';
|
|
141
150
|
}
|
|
142
151
|
/** @see https://docs.github.com/en/rest/reference/pulls#create-a-review-for-a-pull-request */
|
|
143
152
|
async postReview(repositorySlug, pullNumber) {
|
|
@@ -163,13 +172,4 @@ export class AutoMerge {
|
|
|
163
172
|
const { merged } = await response.json();
|
|
164
173
|
return merged;
|
|
165
174
|
}
|
|
166
|
-
async getPullRequestsBySlug(repositorySlug) {
|
|
167
|
-
const resourceUrl = new URL(`/repos/${repositorySlug}/pulls`, this.baseURL);
|
|
168
|
-
resourceUrl.search = new URLSearchParams({ per_page: '100', state: 'open' }).toString();
|
|
169
|
-
const response = await fetch(resourceUrl, { headers: this.baseHeaders });
|
|
170
|
-
if (!response.ok) {
|
|
171
|
-
throw new Error(`Error while fetching pull requests: ${response.statusText}`);
|
|
172
|
-
}
|
|
173
|
-
return response.json();
|
|
174
|
-
}
|
|
175
175
|
}
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import readline from 'node:readline';
|
|
5
2
|
import { program as commander } from 'commander';
|
|
6
3
|
import { cosmiconfig } from 'cosmiconfig';
|
|
7
4
|
import logdown from 'logdown';
|
|
5
|
+
import fs from 'node:fs/promises';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import readline from 'node:readline';
|
|
8
8
|
import { AutoMerge } from './AutoMerge.js';
|
|
9
9
|
import { pluralize } from './util.js';
|
|
10
10
|
const input = readline.createInterface(process.stdin, process.stdout);
|
|
@@ -40,6 +40,17 @@ const configFileData = {
|
|
|
40
40
|
...(commanderOptions.approve && { autoApprove: commanderOptions.approve }),
|
|
41
41
|
...(commanderOptions.dryRun && { dryRun: commanderOptions.dryRun }),
|
|
42
42
|
};
|
|
43
|
+
async function askAction(autoApprover, repositories) {
|
|
44
|
+
const doAction = configFileData.autoApprove ? 'approve and merge' : 'merge';
|
|
45
|
+
const answer = await askQuestion(`ℹ️ auto-merge Which PR would you like to ${doAction} (enter a branch name)? `);
|
|
46
|
+
await runAction(autoApprover, repositories, answer);
|
|
47
|
+
await askAction(autoApprover, repositories);
|
|
48
|
+
}
|
|
49
|
+
function askQuestion(question) {
|
|
50
|
+
return new Promise(resolve => {
|
|
51
|
+
input.question(question, answer => resolve(answer));
|
|
52
|
+
});
|
|
53
|
+
}
|
|
43
54
|
async function runAction(autoMerge, repositories, pullRequestSlug) {
|
|
44
55
|
const regex = new RegExp(pullRequestSlug, 'gi');
|
|
45
56
|
let approveResults = [];
|
|
@@ -60,17 +71,6 @@ async function runAction(autoMerge, repositories, pullRequestSlug) {
|
|
|
60
71
|
logger.info(infoMessage);
|
|
61
72
|
}
|
|
62
73
|
}
|
|
63
|
-
function askQuestion(question) {
|
|
64
|
-
return new Promise(resolve => {
|
|
65
|
-
input.question(question, answer => resolve(answer));
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
async function askAction(autoApprover, repositories) {
|
|
69
|
-
const doAction = configFileData.autoApprove ? 'approve and merge' : 'merge';
|
|
70
|
-
const answer = await askQuestion(`ℹ️ auto-merge Which PR would you like to ${doAction} (enter a branch name)? `);
|
|
71
|
-
await runAction(autoApprover, repositories, answer);
|
|
72
|
-
await askAction(autoApprover, repositories);
|
|
73
|
-
}
|
|
74
74
|
void (async () => {
|
|
75
75
|
try {
|
|
76
76
|
const autoApprover = new AutoMerge(configFileData);
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"author": "Florian Imdahl <git@ffflorian.de>",
|
|
3
3
|
"bin": "dist/cli.js",
|
|
4
4
|
"dependencies": {
|
|
5
|
-
"commander": "14.0.
|
|
5
|
+
"commander": "14.0.3",
|
|
6
6
|
"cosmiconfig": "9.0.0",
|
|
7
7
|
"logdown": "3.3.1"
|
|
8
8
|
},
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"rimraf": "6.1.2",
|
|
14
14
|
"tsx": "4.21.0",
|
|
15
15
|
"typescript": "5.9.3",
|
|
16
|
-
"vitest": "4.0.
|
|
16
|
+
"vitest": "4.0.18"
|
|
17
17
|
},
|
|
18
18
|
"engines": {
|
|
19
19
|
"node": ">= 21"
|
|
@@ -40,6 +40,6 @@
|
|
|
40
40
|
"test": "vitest run"
|
|
41
41
|
},
|
|
42
42
|
"type": "module",
|
|
43
|
-
"version": "1.7.
|
|
44
|
-
"gitHead": "
|
|
43
|
+
"version": "1.7.2",
|
|
44
|
+
"gitHead": "59997e7c64b551213644945256b13ff3ba1ccfcd"
|
|
45
45
|
}
|