create-app-release 1.0.2 → 1.2.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 +30 -0
- package/README.md +45 -1
- package/package.json +9 -1
- package/src/index.js +256 -29
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ 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.2.0] - 2025-03-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Auto suggest repositories based on recent activities
|
|
13
|
+
- Added filter for listed pull requests for easy tracking
|
|
14
|
+
- Dynamic release version suggestion based on previous releases
|
|
15
|
+
- Added suggestion for source and target branches
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Updated release summary generation to exclude heading
|
|
20
|
+
|
|
21
|
+
## [1.1.0] - 2025-02-12
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- Added support for multiple LLM providers:
|
|
26
|
+
- OpenAI models (default)
|
|
27
|
+
- Deepseek models
|
|
28
|
+
- QwenAI models
|
|
29
|
+
- Local LLM deployments
|
|
30
|
+
- New command-line options for LLM configuration:
|
|
31
|
+
- `--openai-key`: Set API key directly
|
|
32
|
+
- `--openai-model`: Choose model (e.g., gpt-4o, deepseek-r1, qwen2.5)
|
|
33
|
+
- `--openai-base-url`: Set custom API base URL for different providers
|
|
34
|
+
- Enhanced help information with detailed options and provider-specific examples
|
|
35
|
+
|
|
8
36
|
## [1.0.2] - 2025-02-09
|
|
9
37
|
|
|
10
38
|
### Changed
|
|
@@ -44,4 +72,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
44
72
|
- ora - Terminal spinners
|
|
45
73
|
- dotenv - Environment variable management
|
|
46
74
|
|
|
75
|
+
[1.2.0]: https://github.com/jamesgordo/create-app-release/releases/tag/v1.2.0
|
|
76
|
+
[1.1.0]: https://github.com/jamesgordo/create-app-release/releases/tag/v1.1.0
|
|
47
77
|
[1.0.0]: https://github.com/jamesgordo/create-app-release/releases/tag/v1.0.0
|
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.2.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');
|
|
@@ -154,14 +170,130 @@ function extractPRNumbersFromDescription(description) {
|
|
|
154
170
|
return new Set(prMatches.map((match) => parseInt(match.replace(/[^0-9]/g, ''))));
|
|
155
171
|
}
|
|
156
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Fetch repositories the user has contributed to, including personal and organization repos
|
|
175
|
+
* @returns {Promise<Array>} List of repositories
|
|
176
|
+
*/
|
|
177
|
+
async function fetchUserRepositories() {
|
|
178
|
+
const spinner = ora('Fetching your repositories...').start();
|
|
179
|
+
try {
|
|
180
|
+
// Get authenticated user info
|
|
181
|
+
const { data: user } = await octokit.rest.users.getAuthenticated();
|
|
182
|
+
const username = user.login;
|
|
183
|
+
|
|
184
|
+
// Get user's repositories (both owned and contributed to)
|
|
185
|
+
const repos = [];
|
|
186
|
+
|
|
187
|
+
// Fetch user's own repositories
|
|
188
|
+
const userReposIterator = octokit.paginate.iterator(
|
|
189
|
+
octokit.rest.repos.listForAuthenticatedUser,
|
|
190
|
+
{
|
|
191
|
+
per_page: 100,
|
|
192
|
+
sort: 'updated',
|
|
193
|
+
direction: 'desc',
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
for await (const { data: userRepos } of userReposIterator) {
|
|
198
|
+
repos.push(
|
|
199
|
+
...userRepos.map((repo) => ({
|
|
200
|
+
name: `${repo.owner.login}/${repo.name}`,
|
|
201
|
+
fullName: `${repo.owner.login}/${repo.name}`,
|
|
202
|
+
owner: repo.owner.login,
|
|
203
|
+
repoName: repo.name,
|
|
204
|
+
updatedAt: new Date(repo.updated_at),
|
|
205
|
+
pushedAt: new Date(repo.pushed_at || repo.updated_at),
|
|
206
|
+
isPersonal: repo.owner.login === username,
|
|
207
|
+
activityScore: 0, // Will be calculated based on user activity
|
|
208
|
+
}))
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
// Limit to 100 repositories to avoid excessive API calls
|
|
212
|
+
if (repos.length >= 100) break;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Get recent user activity for each repository (limited to top 15 to avoid API rate limits)
|
|
216
|
+
const topRepos = repos.slice(0, 15);
|
|
217
|
+
spinner.text = 'Analyzing your recent activity...';
|
|
218
|
+
|
|
219
|
+
// Process repositories in parallel with rate limiting
|
|
220
|
+
await Promise.all(
|
|
221
|
+
topRepos.map(async (repo, index) => {
|
|
222
|
+
// Add delay to avoid hitting rate limits
|
|
223
|
+
await new Promise((resolve) => setTimeout(resolve, index * 100));
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
// Check for user's recent commits
|
|
227
|
+
const { data: commits } = await octokit.rest.repos
|
|
228
|
+
.listCommits({
|
|
229
|
+
owner: repo.owner,
|
|
230
|
+
repo: repo.repoName,
|
|
231
|
+
author: username,
|
|
232
|
+
per_page: 100,
|
|
233
|
+
})
|
|
234
|
+
.catch(() => ({ data: [] }));
|
|
235
|
+
|
|
236
|
+
// Check for user's recent PRs
|
|
237
|
+
const { data: prs } = await octokit.rest.pulls
|
|
238
|
+
.list({
|
|
239
|
+
owner: repo.owner,
|
|
240
|
+
repo: repo.repoName,
|
|
241
|
+
state: 'all',
|
|
242
|
+
per_page: 100,
|
|
243
|
+
})
|
|
244
|
+
.catch(() => ({ data: [] }));
|
|
245
|
+
|
|
246
|
+
// Calculate activity score based on recency and count
|
|
247
|
+
const now = new Date();
|
|
248
|
+
let score = 0;
|
|
249
|
+
|
|
250
|
+
// Add points for recent commits
|
|
251
|
+
commits.forEach((commit) => {
|
|
252
|
+
const daysAgo = (now - new Date(commit.commit.author.date)) / (1000 * 60 * 60 * 24);
|
|
253
|
+
score += Math.max(30 - daysAgo, 0); // More points for more recent commits
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Add points for PRs authored or reviewed by user
|
|
257
|
+
prs.forEach((pr) => {
|
|
258
|
+
if (pr.user.login === username) {
|
|
259
|
+
const daysAgo = (now - new Date(pr.updated_at)) / (1000 * 60 * 60 * 24);
|
|
260
|
+
score += Math.max(20 - daysAgo, 0); // Points for authoring PRs
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// Update the repository's activity score
|
|
265
|
+
repo.activityScore = score;
|
|
266
|
+
} catch (error) {
|
|
267
|
+
// Silently continue if we hit API limits or other issues
|
|
268
|
+
}
|
|
269
|
+
})
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// Sort repositories by activity score first, then by pushed date
|
|
273
|
+
repos.sort((a, b) => {
|
|
274
|
+
if (a.activityScore !== b.activityScore) {
|
|
275
|
+
return b.activityScore - a.activityScore; // Higher score first
|
|
276
|
+
}
|
|
277
|
+
return b.pushedAt - a.pushedAt; // Then by most recent push
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
spinner.succeed(`Found ${repos.length} repositories, sorted by your recent activity`);
|
|
281
|
+
return repos;
|
|
282
|
+
} catch (error) {
|
|
283
|
+
spinner.fail('Failed to fetch repositories');
|
|
284
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
157
289
|
/**
|
|
158
290
|
* Fetch closed pull requests from the repository
|
|
159
291
|
* @param {string} owner - Repository owner
|
|
160
292
|
* @param {string} repo - Repository name
|
|
161
|
-
* @param {string}
|
|
293
|
+
* @param {string} baseBranch - Base branch name
|
|
162
294
|
* @returns {Promise<Array>} List of pull requests
|
|
163
295
|
*/
|
|
164
|
-
async function fetchPullRequests(owner, repo) {
|
|
296
|
+
async function fetchPullRequests(owner, repo, baseBranch) {
|
|
165
297
|
const spinner = ora('Fetching pull requests...').start();
|
|
166
298
|
try {
|
|
167
299
|
// Get the latest release PR first
|
|
@@ -179,10 +311,14 @@ async function fetchPullRequests(owner, repo) {
|
|
|
179
311
|
});
|
|
180
312
|
|
|
181
313
|
for await (const { data } of iterator) {
|
|
182
|
-
// Filter PRs that are merged after the last release
|
|
314
|
+
// Filter PRs that are merged after the last release, not included in it, and merged to the target branch
|
|
183
315
|
const relevantPRs = data.filter((pr) => {
|
|
316
|
+
// Skip PRs that aren't merged
|
|
184
317
|
if (!pr.merged_at) return false;
|
|
185
318
|
|
|
319
|
+
// Skip PRs that aren't targeting the specified branch
|
|
320
|
+
if (pr.base && pr.base.ref !== baseBranch) return false;
|
|
321
|
+
|
|
186
322
|
const isAfterLastRelease = latestReleasePR
|
|
187
323
|
? new Date(pr.merged_at) >= new Date(latestReleasePR.merged_at)
|
|
188
324
|
: true;
|
|
@@ -234,14 +370,16 @@ async function generateSummary(selectedPRs) {
|
|
|
234
370
|
1.2 For each type, make each bullet point concise and easy to read and understand for non-tech people.
|
|
235
371
|
1.3 Don't link the bullet points to a pull requests
|
|
236
372
|
2. The last section should be a list of pull requests included in the release. Format: "#<number> - <title> by [@<author>](<authorUrl>) (<date>)".
|
|
373
|
+
3. Don't add Release Summary title/heading.
|
|
237
374
|
|
|
238
375
|
Pull Requests to summarize:
|
|
239
376
|
${JSON.stringify(prDetails, null, 2)}
|
|
240
377
|
|
|
241
378
|
Keep the summary concise, clear, and focused on the user impact. Use professional but easy-to-understand language.`;
|
|
242
379
|
|
|
380
|
+
const model = program.opts().openaiModel || 'gpt-4o';
|
|
243
381
|
const response = await openai.chat.completions.create({
|
|
244
|
-
model
|
|
382
|
+
model,
|
|
245
383
|
messages: [{ role: 'user', content: prompt }],
|
|
246
384
|
temperature: 0.7,
|
|
247
385
|
});
|
|
@@ -341,8 +479,18 @@ ${summary}`;
|
|
|
341
479
|
}
|
|
342
480
|
|
|
343
481
|
async function run() {
|
|
344
|
-
//
|
|
345
|
-
const
|
|
482
|
+
// Get command line options
|
|
483
|
+
const options = program.opts();
|
|
484
|
+
|
|
485
|
+
// Initialize GitHub token
|
|
486
|
+
const { githubToken } = await initializeTokens();
|
|
487
|
+
|
|
488
|
+
// Get OpenAI token from command line or fallback to configuration
|
|
489
|
+
let openaiToken = options.openaiKey;
|
|
490
|
+
if (!openaiToken) {
|
|
491
|
+
const tokens = await initializeTokens();
|
|
492
|
+
openaiToken = tokens.openaiToken;
|
|
493
|
+
}
|
|
346
494
|
|
|
347
495
|
// Initialize clients with tokens
|
|
348
496
|
octokit = new Octokit({
|
|
@@ -351,24 +499,95 @@ async function run() {
|
|
|
351
499
|
|
|
352
500
|
openai = new OpenAI({
|
|
353
501
|
apiKey: openaiToken,
|
|
502
|
+
baseURL: options.openaiBaseUrl,
|
|
354
503
|
});
|
|
355
504
|
|
|
356
|
-
|
|
505
|
+
// Fetch repositories the user has contributed to
|
|
506
|
+
const userRepos = await fetchUserRepositories();
|
|
507
|
+
|
|
508
|
+
// Prepare repository choices
|
|
509
|
+
const repoChoices =
|
|
510
|
+
userRepos.length > 0
|
|
511
|
+
? userRepos.map((repo) => ({
|
|
512
|
+
name: repo.fullName + (repo.isPersonal ? ' (personal)' : ''),
|
|
513
|
+
value: { owner: repo.owner, repo: repo.repoName },
|
|
514
|
+
}))
|
|
515
|
+
: [];
|
|
516
|
+
|
|
517
|
+
// Add option for manual entry
|
|
518
|
+
repoChoices.push({ name: '-- Enter repository manually --', value: 'manual' });
|
|
519
|
+
|
|
520
|
+
let repoInfo = { owner: '', repo: '' };
|
|
521
|
+
const { repoSelection } = await inquirer.prompt([
|
|
522
|
+
{
|
|
523
|
+
type: 'list',
|
|
524
|
+
name: 'repoSelection',
|
|
525
|
+
message: 'Select a repository:',
|
|
526
|
+
choices: repoChoices,
|
|
527
|
+
pageSize: 5,
|
|
528
|
+
},
|
|
529
|
+
]);
|
|
530
|
+
|
|
531
|
+
// Handle manual repository entry
|
|
532
|
+
if (repoSelection === 'manual') {
|
|
533
|
+
const manualEntry = await inquirer.prompt([
|
|
534
|
+
{
|
|
535
|
+
type: 'input',
|
|
536
|
+
name: 'owner',
|
|
537
|
+
message: 'Enter repository owner:',
|
|
538
|
+
validate: (input) => input.length > 0,
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
type: 'input',
|
|
542
|
+
name: 'repo',
|
|
543
|
+
message: 'Enter repository name:',
|
|
544
|
+
validate: (input) => input.length > 0,
|
|
545
|
+
},
|
|
546
|
+
]);
|
|
547
|
+
repoInfo = manualEntry;
|
|
548
|
+
} else {
|
|
549
|
+
repoInfo = repoSelection;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const { owner, repo } = repoInfo;
|
|
553
|
+
|
|
554
|
+
const { sourceBranch, targetBranch } = await inquirer.prompt([
|
|
357
555
|
{
|
|
358
556
|
type: 'input',
|
|
359
|
-
name: '
|
|
360
|
-
message: 'Enter
|
|
557
|
+
name: 'sourceBranch',
|
|
558
|
+
message: 'Enter source branch name:',
|
|
559
|
+
default: 'staging',
|
|
361
560
|
validate: (input) => input.length > 0,
|
|
362
561
|
},
|
|
363
562
|
{
|
|
364
563
|
type: 'input',
|
|
365
|
-
name: '
|
|
366
|
-
message: 'Enter
|
|
564
|
+
name: 'targetBranch',
|
|
565
|
+
message: 'Enter target branch name:',
|
|
566
|
+
default: 'main',
|
|
367
567
|
validate: (input) => input.length > 0,
|
|
368
568
|
},
|
|
369
569
|
]);
|
|
370
570
|
|
|
371
|
-
|
|
571
|
+
// Get the latest release version for the repository
|
|
572
|
+
let suggestedVersion = '1.0.0';
|
|
573
|
+
try {
|
|
574
|
+
const { data: releases } = await octokit.rest.repos.listReleases({
|
|
575
|
+
owner,
|
|
576
|
+
repo,
|
|
577
|
+
per_page: 100,
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
if (releases && releases.length > 0) {
|
|
581
|
+
// Extract version from tag_name (removing any 'v' prefix)
|
|
582
|
+
const latestTag = releases[0].tag_name.replace(/^v/, '');
|
|
583
|
+
const [major, minor, patch] = latestTag.split('.');
|
|
584
|
+
suggestedVersion = `${major}.${minor}.${parseInt(patch) + 1}`;
|
|
585
|
+
}
|
|
586
|
+
} catch (error) {
|
|
587
|
+
console.log(chalk.yellow(`Could not fetch latest release version: ${error.message}`));
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const pulls = await fetchPullRequests(owner, repo, sourceBranch);
|
|
372
591
|
|
|
373
592
|
const { selectedPRs } = await inquirer.prompt([
|
|
374
593
|
{
|
|
@@ -410,11 +629,12 @@ async function run() {
|
|
|
410
629
|
console.log(chalk.cyan('\nSummary:'));
|
|
411
630
|
console.log(summary);
|
|
412
631
|
|
|
413
|
-
const { version, confirm
|
|
632
|
+
const { version, confirm } = await inquirer.prompt([
|
|
414
633
|
{
|
|
415
634
|
type: 'input',
|
|
416
635
|
name: 'version',
|
|
417
|
-
message:
|
|
636
|
+
message: `Enter the version number for this release (suggested: ${suggestedVersion}):`,
|
|
637
|
+
default: suggestedVersion,
|
|
418
638
|
validate: (input) => {
|
|
419
639
|
// Validate semantic versioning format (x.y.z)
|
|
420
640
|
const semverRegex = /^\d+\.\d+\.\d+$/;
|
|
@@ -429,20 +649,6 @@ async function run() {
|
|
|
429
649
|
name: 'confirm',
|
|
430
650
|
message: 'Would you like to create a release PR with this summary?',
|
|
431
651
|
},
|
|
432
|
-
{
|
|
433
|
-
type: 'input',
|
|
434
|
-
name: 'sourceBranch',
|
|
435
|
-
message: 'Enter source branch name:',
|
|
436
|
-
when: (answers) => answers.confirm,
|
|
437
|
-
validate: (input) => input.length > 0,
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
type: 'input',
|
|
441
|
-
name: 'targetBranch',
|
|
442
|
-
message: 'Enter target branch name:',
|
|
443
|
-
when: (answers) => answers.confirm,
|
|
444
|
-
validate: (input) => input.length > 0,
|
|
445
|
-
},
|
|
446
652
|
]);
|
|
447
653
|
|
|
448
654
|
if (confirm) {
|
|
@@ -459,9 +665,30 @@ async function run() {
|
|
|
459
665
|
}
|
|
460
666
|
}
|
|
461
667
|
|
|
668
|
+
const description = `AI-powered GitHub release automation tool
|
|
669
|
+
|
|
670
|
+
Options:
|
|
671
|
+
--openai-key <key> Set OpenAI API key directly (alternative to env/git config)
|
|
672
|
+
--openai-model <model> Set OpenAI model to use (default: "gpt-4")
|
|
673
|
+
Examples: gpt-4, gpt-3.5-turbo
|
|
674
|
+
--openai-base-url <url> Set custom OpenAI API base URL
|
|
675
|
+
Example: https://custom-openai-endpoint.com/v1
|
|
676
|
+
|
|
677
|
+
Environment Variables:
|
|
678
|
+
GITHUB_TOKEN GitHub personal access token
|
|
679
|
+
OPENAI_API_KEY OpenAI API key (if not using --openai-key)
|
|
680
|
+
|
|
681
|
+
Git Config:
|
|
682
|
+
github.token GitHub token in git config
|
|
683
|
+
openai.token OpenAI token in git config (if not using --openai-key)
|
|
684
|
+
`;
|
|
685
|
+
|
|
462
686
|
program
|
|
463
687
|
.name('create-app-release')
|
|
464
|
-
.description(
|
|
688
|
+
.description(description)
|
|
465
689
|
.version(pkg.version)
|
|
690
|
+
.option('--openai-base-url <url>', 'Set custom OpenAI API base URL')
|
|
691
|
+
.option('--openai-model <model>', 'Set OpenAI model to use (default: "gpt-4")')
|
|
692
|
+
.option('--openai-key <key>', 'Set OpenAI API key directly (alternative to env/git config)')
|
|
466
693
|
.action(run)
|
|
467
694
|
.parse(process.argv);
|