create-app-release 1.0.0 → 1.0.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/CHANGELOG.md CHANGED
@@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.0.2] - 2025-02-09
9
+
10
+ ### Changed
11
+
12
+ - Added smart filtering to exclude PRs already included in previous releases
13
+
14
+ ## [1.0.1] - 2025-02-07
15
+
16
+ ### Changed
17
+
18
+ - Enhanced pull request fetching to retrieve all closed pull requests using pagination
19
+
8
20
  ## [1.0.0] - 2025-02-06
9
21
 
10
22
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-app-release",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "AI-powered GitHub release automation tool",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -106,24 +106,102 @@ async function initializeTokens() {
106
106
  return { githubToken, openaiToken };
107
107
  }
108
108
 
109
+ /**
110
+ * Get the latest release pull request
111
+ * @param {string} owner - Repository owner
112
+ * @param {string} repo - Repository name
113
+ * @returns {Promise<Object|null>} Latest release PR or null
114
+ */
115
+ async function getLatestReleasePR(owner, repo) {
116
+ try {
117
+ const iterator = octokit.paginate.iterator(octokit.rest.pulls.list, {
118
+ owner,
119
+ repo,
120
+ state: 'closed',
121
+ sort: 'updated',
122
+ direction: 'desc',
123
+ per_page: 100,
124
+ });
125
+
126
+ for await (const { data } of iterator) {
127
+ // SemVer regex pattern: matches X.Y.Z with optional pre-release and build metadata
128
+ const semverPattern =
129
+ /\b(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?\b/;
130
+
131
+ const recentReleasePR = data.find((pr) => semverPattern.test(pr.title));
132
+ if (recentReleasePR) {
133
+ return recentReleasePR;
134
+ }
135
+ }
136
+ return null;
137
+ } catch (error) {
138
+ console.error(chalk.red('Failed to fetch latest release PR:', error.message));
139
+ return null;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Extract PR numbers from release PR description
145
+ * @param {string} description - PR description
146
+ * @returns {Set<number>} Set of PR numbers
147
+ */
148
+ function extractPRNumbersFromDescription(description) {
149
+ if (!description) return new Set();
150
+
151
+ // Match PR numbers in various formats like #123, (#123), or just plain 123 in PR lists
152
+ const prMatches = description.match(/#\d+|\(#\d+\)|(?<=PR:?\s*)\d+/g) || [];
153
+
154
+ return new Set(prMatches.map((match) => parseInt(match.replace(/[^0-9]/g, ''))));
155
+ }
156
+
109
157
  /**
110
158
  * Fetch closed pull requests from the repository
111
159
  * @param {string} owner - Repository owner
112
160
  * @param {string} repo - Repository name
161
+ * @param {string} targetBranch - Target branch name
113
162
  * @returns {Promise<Array>} List of pull requests
114
163
  */
115
164
  async function fetchPullRequests(owner, repo) {
116
165
  const spinner = ora('Fetching pull requests...').start();
117
166
  try {
118
- const { data: pulls } = await octokit.pulls.list({
167
+ // Get the latest release PR first
168
+ const latestReleasePR = await getLatestReleasePR(owner, repo);
169
+
170
+ const includedPRNumbers = extractPRNumbersFromDescription(latestReleasePR?.body);
171
+ const pulls = [];
172
+ const iterator = octokit.paginate.iterator(octokit.rest.pulls.list, {
119
173
  owner,
120
174
  repo,
121
175
  state: 'closed',
122
176
  sort: 'updated',
123
177
  direction: 'desc',
124
- per_page: 30,
178
+ per_page: 100,
125
179
  });
126
- spinner.succeed(`Found ${pulls.length} pull requests`);
180
+
181
+ for await (const { data } of iterator) {
182
+ // Filter PRs that are merged after the last release and not included in it
183
+ const relevantPRs = data.filter((pr) => {
184
+ if (!pr.merged_at) return false;
185
+
186
+ const isAfterLastRelease = latestReleasePR
187
+ ? new Date(pr.merged_at) >= new Date(latestReleasePR.merged_at)
188
+ : true;
189
+
190
+ return (
191
+ isAfterLastRelease &&
192
+ !includedPRNumbers.has(`#${pr.number}`) &&
193
+ pr.id !== latestReleasePR?.id
194
+ );
195
+ });
196
+ pulls.push(...relevantPRs);
197
+ }
198
+
199
+ const excludedCount = includedPRNumbers.size;
200
+ const message = latestReleasePR
201
+ ? `Found ${pulls.length} new merged pull requests (excluding ${excludedCount} PRs from last release)`
202
+ : `Found ${pulls.length} merged pull requests`;
203
+
204
+ spinner.succeed(message);
127
205
  return pulls;
128
206
  } catch (error) {
129
207
  spinner.fail('Failed to fetch pull requests');