@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.
@@ -1,23 +1,23 @@
1
- import type { AutoMergeConfig, ActionResult, Repository, RepositoryResult } from './types/index.js';
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
- getMatchingRepositories(repositories, regex) {
57
- const matchingRepositories = [];
58
- for (const repository of repositories) {
59
- const matchingPullRequests = repository.pullRequests.filter(pullRequest => new RegExp(regex).test(pullRequest.head.ref));
60
- if (matchingPullRequests.length) {
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
- return matchingRepositories;
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 isPullRequestMergeable(repositorySlug, pullNumber) {
67
- const resourceUrl = new URL(`/repos/${repositorySlug}/pulls/${pullNumber}`, this.baseURL);
68
- const response = await fetch(resourceUrl, { headers: this.baseHeaders });
69
- if (!response.ok) {
70
- throw new Error(`Error while checking merge request: ${response.statusText}`);
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
- const pullRequestData = await response.json();
73
- return pullRequestData.mergeable_state === 'clean';
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
- async getAllRepositories() {
125
- const repositorySlugs = this.config.projects.gitHub.filter(repositorySlug => this.checkRepositorySlug(repositorySlug));
126
- const repositories = [];
127
- for (const repositorySlug of repositorySlugs) {
128
- try {
129
- const pullRequests = await this.getPullRequestsBySlug(repositorySlug);
130
- repositories.push({ pullRequests, repositorySlug });
131
- }
132
- catch (error) {
133
- this.logger.error(`Could not get pull requests for "${repositorySlug}": ${error.message}`);
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 repositories;
131
+ return matchingRepositories;
137
132
  }
138
- async getRepositoriesWithOpenPullRequests() {
139
- const allRepositories = await this.getAllRepositories();
140
- return allRepositories.filter(repository => !!repository.pullRequests.length);
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.2",
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"
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.0",
44
- "gitHead": "b968c846095113cbe57fbdd34e4593a4dea8a1c9"
43
+ "version": "1.7.2",
44
+ "gitHead": "59997e7c64b551213644945256b13ff3ba1ccfcd"
45
45
  }