create-app-release 1.0.1 → 1.1.0
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 +21 -0
- package/README.md +45 -1
- package/package.json +9 -1
- package/src/index.js +129 -7
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,27 @@ 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.1.0] - 2025-02-12
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Added support for multiple LLM providers:
|
|
13
|
+
- OpenAI models (default)
|
|
14
|
+
- Deepseek models
|
|
15
|
+
- QwenAI models
|
|
16
|
+
- Local LLM deployments
|
|
17
|
+
- New command-line options for LLM configuration:
|
|
18
|
+
- `--openai-key`: Set API key directly
|
|
19
|
+
- `--openai-model`: Choose model (e.g., gpt-4o, deepseek-r1, qwen2.5)
|
|
20
|
+
- `--openai-base-url`: Set custom API base URL for different providers
|
|
21
|
+
- Enhanced help information with detailed options and provider-specific examples
|
|
22
|
+
|
|
23
|
+
## [1.0.2] - 2025-02-09
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- Added smart filtering to exclude PRs already included in previous releases
|
|
28
|
+
|
|
8
29
|
## [1.0.1] - 2025-02-07
|
|
9
30
|
|
|
10
31
|
### Changed
|
package/README.md
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
# create-app-release
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/create-app-release)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
An AI-powered GitHub release automation tool that helps you create release pull requests with automatically generated summaries using various LLM providers. The tool intelligently groups your changes and creates professional release notes, making the release process smoother and more efficient.
|
|
4
7
|
|
|
5
8
|
## Features
|
|
6
9
|
|
|
7
10
|
- 🤖 AI-powered release notes generation using GPT-4
|
|
11
|
+
- 🔄 Flexible LLM support:
|
|
12
|
+
- OpenAI models (GPT-4o, GPT-3.5-turbo)
|
|
13
|
+
- Deepseek models
|
|
14
|
+
- QwenAI models
|
|
15
|
+
- Local LLM deployments
|
|
8
16
|
- 📦 Zero configuration - works right out of the box
|
|
9
17
|
- 🔑 Secure token management through git config
|
|
10
18
|
- 🎯 Interactive pull request selection
|
|
@@ -47,6 +55,38 @@ You'll need two tokens to use this tool:
|
|
|
47
55
|
2. **OpenAI API Key** - Get from [OpenAI Platform](https://platform.openai.com/api-keys)
|
|
48
56
|
- Will be stored in git config as `openai.token`
|
|
49
57
|
|
|
58
|
+
### Command-Line Options
|
|
59
|
+
|
|
60
|
+
Customize the tool's behavior using these command-line options:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Set OpenAI API key directly (alternative to env/git config)
|
|
64
|
+
--openai-key <key>
|
|
65
|
+
|
|
66
|
+
# Choose OpenAI model (default: "gpt-4o")
|
|
67
|
+
--openai-model <model>
|
|
68
|
+
# Examples: gpt-4o, gpt-3.5-turbo, deepseek-r1, qwen2.5
|
|
69
|
+
|
|
70
|
+
# Set custom OpenAI API base URL
|
|
71
|
+
--openai-base-url <url>
|
|
72
|
+
# Examples:
|
|
73
|
+
# - Deepseek: https://api.deepseek.com/v1
|
|
74
|
+
# - QwenAI: https://api.qwen.ai/v1
|
|
75
|
+
# - Local: http://localhost:8000/v1
|
|
76
|
+
# - Custom: https://custom-openai-endpoint.com/v1
|
|
77
|
+
|
|
78
|
+
# Full example with different providers:
|
|
79
|
+
|
|
80
|
+
# Using Deepseek
|
|
81
|
+
npx create-app-release --openai-base-url https://api.deepseek.com/v1 --openai-key your_deepseek_key --openai-model deepseek-chat
|
|
82
|
+
|
|
83
|
+
# Using QwenAI
|
|
84
|
+
npx create-app-release --openai-base-url https://api.qwen.ai/v1 --openai-key your_qwen_key --openai-model qwen-14b-chat
|
|
85
|
+
|
|
86
|
+
# Using Local LLM
|
|
87
|
+
npx create-app-release --openai-base-url http://localhost:8000/v1 --openai-model local-model
|
|
88
|
+
```
|
|
89
|
+
|
|
50
90
|
### Environment Variables (Optional)
|
|
51
91
|
|
|
52
92
|
Tokens can also be provided via environment variables:
|
|
@@ -85,3 +125,7 @@ The tool generates professional release notes in this format:
|
|
|
85
125
|
## License
|
|
86
126
|
|
|
87
127
|
MIT
|
|
128
|
+
|
|
129
|
+
## Author
|
|
130
|
+
|
|
131
|
+
[James Gordo](https://github.com/jamesgordo)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-app-release",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "AI-powered GitHub release automation tool",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -27,6 +27,14 @@
|
|
|
27
27
|
],
|
|
28
28
|
"author": "James Gordo",
|
|
29
29
|
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/jamesgordo/create-app-release.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/jamesgordo/create-app-release/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/jamesgordo/create-app-release#readme",
|
|
30
38
|
"lint-staged": {
|
|
31
39
|
"*.{js,jsx,ts,tsx}": [
|
|
32
40
|
"eslint --fix",
|
package/src/index.js
CHANGED
|
@@ -18,6 +18,22 @@ const require = createRequire(import.meta.url);
|
|
|
18
18
|
// Load environment variables
|
|
19
19
|
config();
|
|
20
20
|
|
|
21
|
+
// Setup graceful exit handlers
|
|
22
|
+
process.stdin.setRawMode(true);
|
|
23
|
+
process.stdin.resume();
|
|
24
|
+
process.stdin.setEncoding('utf8');
|
|
25
|
+
|
|
26
|
+
// Display exit instructions
|
|
27
|
+
console.log(chalk.cyan('Press Ctrl+C or q to exit at any time'));
|
|
28
|
+
|
|
29
|
+
process.stdin.on('data', (key) => {
|
|
30
|
+
// Ctrl+C or 'q' to exit
|
|
31
|
+
if (key === '\u0003' || key.toLowerCase() === 'q') {
|
|
32
|
+
console.log(chalk.yellow('\nExiting gracefully...'));
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
21
37
|
// Initialize CLI program
|
|
22
38
|
const program = new Command();
|
|
23
39
|
const pkg = require('../package.json');
|
|
@@ -106,17 +122,70 @@ async function initializeTokens() {
|
|
|
106
122
|
return { githubToken, openaiToken };
|
|
107
123
|
}
|
|
108
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Get the latest release pull request
|
|
127
|
+
* @param {string} owner - Repository owner
|
|
128
|
+
* @param {string} repo - Repository name
|
|
129
|
+
* @returns {Promise<Object|null>} Latest release PR or null
|
|
130
|
+
*/
|
|
131
|
+
async function getLatestReleasePR(owner, repo) {
|
|
132
|
+
try {
|
|
133
|
+
const iterator = octokit.paginate.iterator(octokit.rest.pulls.list, {
|
|
134
|
+
owner,
|
|
135
|
+
repo,
|
|
136
|
+
state: 'closed',
|
|
137
|
+
sort: 'updated',
|
|
138
|
+
direction: 'desc',
|
|
139
|
+
per_page: 100,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
for await (const { data } of iterator) {
|
|
143
|
+
// SemVer regex pattern: matches X.Y.Z with optional pre-release and build metadata
|
|
144
|
+
const semverPattern =
|
|
145
|
+
/\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/;
|
|
146
|
+
|
|
147
|
+
const recentReleasePR = data.find((pr) => semverPattern.test(pr.title));
|
|
148
|
+
if (recentReleasePR) {
|
|
149
|
+
return recentReleasePR;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error(chalk.red('Failed to fetch latest release PR:', error.message));
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Extract PR numbers from release PR description
|
|
161
|
+
* @param {string} description - PR description
|
|
162
|
+
* @returns {Set<number>} Set of PR numbers
|
|
163
|
+
*/
|
|
164
|
+
function extractPRNumbersFromDescription(description) {
|
|
165
|
+
if (!description) return new Set();
|
|
166
|
+
|
|
167
|
+
// Match PR numbers in various formats like #123, (#123), or just plain 123 in PR lists
|
|
168
|
+
const prMatches = description.match(/#\d+|\(#\d+\)|(?<=PR:?\s*)\d+/g) || [];
|
|
169
|
+
|
|
170
|
+
return new Set(prMatches.map((match) => parseInt(match.replace(/[^0-9]/g, ''))));
|
|
171
|
+
}
|
|
172
|
+
|
|
109
173
|
/**
|
|
110
174
|
* Fetch closed pull requests from the repository
|
|
111
175
|
* @param {string} owner - Repository owner
|
|
112
176
|
* @param {string} repo - Repository name
|
|
177
|
+
* @param {string} targetBranch - Target branch name
|
|
113
178
|
* @returns {Promise<Array>} List of pull requests
|
|
114
179
|
*/
|
|
115
180
|
async function fetchPullRequests(owner, repo) {
|
|
116
181
|
const spinner = ora('Fetching pull requests...').start();
|
|
117
182
|
try {
|
|
183
|
+
// Get the latest release PR first
|
|
184
|
+
const latestReleasePR = await getLatestReleasePR(owner, repo);
|
|
185
|
+
|
|
186
|
+
const includedPRNumbers = extractPRNumbersFromDescription(latestReleasePR?.body);
|
|
118
187
|
const pulls = [];
|
|
119
|
-
const iterator = octokit.paginate.iterator(octokit.pulls.list, {
|
|
188
|
+
const iterator = octokit.paginate.iterator(octokit.rest.pulls.list, {
|
|
120
189
|
owner,
|
|
121
190
|
repo,
|
|
122
191
|
state: 'closed',
|
|
@@ -126,9 +195,29 @@ async function fetchPullRequests(owner, repo) {
|
|
|
126
195
|
});
|
|
127
196
|
|
|
128
197
|
for await (const { data } of iterator) {
|
|
129
|
-
|
|
198
|
+
// Filter PRs that are merged after the last release and not included in it
|
|
199
|
+
const relevantPRs = data.filter((pr) => {
|
|
200
|
+
if (!pr.merged_at) return false;
|
|
201
|
+
|
|
202
|
+
const isAfterLastRelease = latestReleasePR
|
|
203
|
+
? new Date(pr.merged_at) >= new Date(latestReleasePR.merged_at)
|
|
204
|
+
: true;
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
isAfterLastRelease &&
|
|
208
|
+
!includedPRNumbers.has(`#${pr.number}`) &&
|
|
209
|
+
pr.id !== latestReleasePR?.id
|
|
210
|
+
);
|
|
211
|
+
});
|
|
212
|
+
pulls.push(...relevantPRs);
|
|
130
213
|
}
|
|
131
|
-
|
|
214
|
+
|
|
215
|
+
const excludedCount = includedPRNumbers.size;
|
|
216
|
+
const message = latestReleasePR
|
|
217
|
+
? `Found ${pulls.length} new merged pull requests (excluding ${excludedCount} PRs from last release)`
|
|
218
|
+
: `Found ${pulls.length} merged pull requests`;
|
|
219
|
+
|
|
220
|
+
spinner.succeed(message);
|
|
132
221
|
return pulls;
|
|
133
222
|
} catch (error) {
|
|
134
223
|
spinner.fail('Failed to fetch pull requests');
|
|
@@ -167,8 +256,9 @@ ${JSON.stringify(prDetails, null, 2)}
|
|
|
167
256
|
|
|
168
257
|
Keep the summary concise, clear, and focused on the user impact. Use professional but easy-to-understand language.`;
|
|
169
258
|
|
|
259
|
+
const model = program.opts().openaiModel || 'gpt-4o';
|
|
170
260
|
const response = await openai.chat.completions.create({
|
|
171
|
-
model
|
|
261
|
+
model,
|
|
172
262
|
messages: [{ role: 'user', content: prompt }],
|
|
173
263
|
temperature: 0.7,
|
|
174
264
|
});
|
|
@@ -268,8 +358,18 @@ ${summary}`;
|
|
|
268
358
|
}
|
|
269
359
|
|
|
270
360
|
async function run() {
|
|
271
|
-
//
|
|
272
|
-
const
|
|
361
|
+
// Get command line options
|
|
362
|
+
const options = program.opts();
|
|
363
|
+
|
|
364
|
+
// Initialize GitHub token
|
|
365
|
+
const { githubToken } = await initializeTokens();
|
|
366
|
+
|
|
367
|
+
// Get OpenAI token from command line or fallback to configuration
|
|
368
|
+
let openaiToken = options.openaiKey;
|
|
369
|
+
if (!openaiToken) {
|
|
370
|
+
const tokens = await initializeTokens();
|
|
371
|
+
openaiToken = tokens.openaiToken;
|
|
372
|
+
}
|
|
273
373
|
|
|
274
374
|
// Initialize clients with tokens
|
|
275
375
|
octokit = new Octokit({
|
|
@@ -278,6 +378,7 @@ async function run() {
|
|
|
278
378
|
|
|
279
379
|
openai = new OpenAI({
|
|
280
380
|
apiKey: openaiToken,
|
|
381
|
+
baseURL: options.openaiBaseUrl,
|
|
281
382
|
});
|
|
282
383
|
|
|
283
384
|
const { owner, repo } = await inquirer.prompt([
|
|
@@ -386,9 +487,30 @@ async function run() {
|
|
|
386
487
|
}
|
|
387
488
|
}
|
|
388
489
|
|
|
490
|
+
const description = `AI-powered GitHub release automation tool
|
|
491
|
+
|
|
492
|
+
Options:
|
|
493
|
+
--openai-key <key> Set OpenAI API key directly (alternative to env/git config)
|
|
494
|
+
--openai-model <model> Set OpenAI model to use (default: "gpt-4")
|
|
495
|
+
Examples: gpt-4, gpt-3.5-turbo
|
|
496
|
+
--openai-base-url <url> Set custom OpenAI API base URL
|
|
497
|
+
Example: https://custom-openai-endpoint.com/v1
|
|
498
|
+
|
|
499
|
+
Environment Variables:
|
|
500
|
+
GITHUB_TOKEN GitHub personal access token
|
|
501
|
+
OPENAI_API_KEY OpenAI API key (if not using --openai-key)
|
|
502
|
+
|
|
503
|
+
Git Config:
|
|
504
|
+
github.token GitHub token in git config
|
|
505
|
+
openai.token OpenAI token in git config (if not using --openai-key)
|
|
506
|
+
`;
|
|
507
|
+
|
|
389
508
|
program
|
|
390
509
|
.name('create-app-release')
|
|
391
|
-
.description(
|
|
510
|
+
.description(description)
|
|
392
511
|
.version(pkg.version)
|
|
512
|
+
.option('--openai-base-url <url>', 'Set custom OpenAI API base URL')
|
|
513
|
+
.option('--openai-model <model>', 'Set OpenAI model to use (default: "gpt-4")')
|
|
514
|
+
.option('--openai-key <key>', 'Set OpenAI API key directly (alternative to env/git config)')
|
|
393
515
|
.action(run)
|
|
394
516
|
.parse(process.argv);
|