create-app-release 1.0.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/.eslintignore +5 -0
- package/.eslintrc.json +14 -0
- package/.husky/pre-commit +4 -0
- package/.prettierignore +6 -0
- package/.prettierrc +8 -0
- package/CHANGELOG.md +35 -0
- package/README.md +87 -0
- package/bin/create-app-release +7 -0
- package/package.json +61 -0
- package/src/index.js +389 -0
package/.eslintignore
ADDED
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"env": {
|
|
3
|
+
"node": true,
|
|
4
|
+
"es2021": true
|
|
5
|
+
},
|
|
6
|
+
"extends": ["eslint:recommended", "plugin:prettier/recommended"],
|
|
7
|
+
"parserOptions": {
|
|
8
|
+
"ecmaVersion": "latest",
|
|
9
|
+
"sourceType": "module"
|
|
10
|
+
},
|
|
11
|
+
"rules": {
|
|
12
|
+
"prettier/prettier": "error"
|
|
13
|
+
}
|
|
14
|
+
}
|
package/.prettierignore
ADDED
package/.prettierrc
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2025-02-06
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Initial release of `create-app-release`
|
|
13
|
+
- AI-powered release notes generation using GPT-4
|
|
14
|
+
- Interactive pull request selection
|
|
15
|
+
- GitHub token management through git config
|
|
16
|
+
- OpenAI API key management through git config
|
|
17
|
+
- Automatic categorization of changes (Features, Bug Fixes, Improvements)
|
|
18
|
+
- Professional markdown formatting for release notes
|
|
19
|
+
- Support for environment variables (`GITHUB_TOKEN` and `OPENAI_API_KEY`)
|
|
20
|
+
- Command-line interface using Commander.js
|
|
21
|
+
- Progress indicators and colorful console output
|
|
22
|
+
- Error handling and user-friendly messages
|
|
23
|
+
- Support for Node.js >= 14.0.0
|
|
24
|
+
|
|
25
|
+
### Dependencies
|
|
26
|
+
|
|
27
|
+
- @octokit/rest - GitHub API client
|
|
28
|
+
- openai - OpenAI API client
|
|
29
|
+
- commander - Command-line interface
|
|
30
|
+
- inquirer - Interactive prompts
|
|
31
|
+
- chalk - Terminal styling
|
|
32
|
+
- ora - Terminal spinners
|
|
33
|
+
- dotenv - Environment variable management
|
|
34
|
+
|
|
35
|
+
[1.0.0]: https://github.com/jamesgordo/create-app-release/releases/tag/v1.0.0
|
package/README.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# create-app-release
|
|
2
|
+
|
|
3
|
+
An AI-powered GitHub release automation tool that helps you create release pull requests with automatically generated summaries using OpenAI's GPT-4 model. The tool intelligently groups your changes and creates professional release notes, making the release process smoother and more efficient.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🤖 AI-powered release notes generation using GPT-4
|
|
8
|
+
- 📦 Zero configuration - works right out of the box
|
|
9
|
+
- 🔑 Secure token management through git config
|
|
10
|
+
- 🎯 Interactive pull request selection
|
|
11
|
+
- ✨ Professional markdown formatting
|
|
12
|
+
- 📝 Smart categorization of changes
|
|
13
|
+
- 🌟 User-friendly CLI interface
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
- Node.js 14 or higher
|
|
18
|
+
- Git installed and configured
|
|
19
|
+
- GitHub account with repository access
|
|
20
|
+
- OpenAI account (for GPT-4 access)
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
Run the tool directly using npx:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx create-app-release
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
On first run, the tool will guide you through:
|
|
31
|
+
|
|
32
|
+
1. Setting up your GitHub token (stored in git config)
|
|
33
|
+
2. Configuring your OpenAI API key (stored in git config)
|
|
34
|
+
3. Selecting pull requests for the release
|
|
35
|
+
4. Reviewing the AI-generated summary
|
|
36
|
+
5. Creating the release pull request
|
|
37
|
+
|
|
38
|
+
### Token Setup
|
|
39
|
+
|
|
40
|
+
You'll need two tokens to use this tool:
|
|
41
|
+
|
|
42
|
+
1. **GitHub Token** - Create at [GitHub Token Settings](https://github.com/settings/tokens/new)
|
|
43
|
+
|
|
44
|
+
- Required scope: `repo`
|
|
45
|
+
- Will be stored in git config as `github.token`
|
|
46
|
+
|
|
47
|
+
2. **OpenAI API Key** - Get from [OpenAI Platform](https://platform.openai.com/api-keys)
|
|
48
|
+
- Will be stored in git config as `openai.token`
|
|
49
|
+
|
|
50
|
+
### Environment Variables (Optional)
|
|
51
|
+
|
|
52
|
+
Tokens can also be provided via environment variables:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
GITHUB_TOKEN=your_github_token
|
|
56
|
+
OPENAI_API_KEY=your_openai_api_key
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Example Output
|
|
60
|
+
|
|
61
|
+
The tool generates professional release notes in this format:
|
|
62
|
+
|
|
63
|
+
```markdown
|
|
64
|
+
### 🚀 Features
|
|
65
|
+
|
|
66
|
+
- Enhanced user authentication system
|
|
67
|
+
- New dashboard analytics
|
|
68
|
+
|
|
69
|
+
### 🐛 Bug Fixes
|
|
70
|
+
|
|
71
|
+
- Fixed memory leak in background tasks
|
|
72
|
+
- Resolved login issues on Safari
|
|
73
|
+
|
|
74
|
+
### 🔧 Improvements
|
|
75
|
+
|
|
76
|
+
- Optimized database queries
|
|
77
|
+
- Updated dependencies
|
|
78
|
+
|
|
79
|
+
### Pull Requests
|
|
80
|
+
|
|
81
|
+
#123 - Add user authentication by [@username](https://github.com/username) (2024-02-01)
|
|
82
|
+
#124 - Fix memory leak by [@dev](https://github.com/dev) (2024-02-02)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-app-release",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI-powered GitHub release automation tool",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"create-app-release": "./bin/create-app-release"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=14.0.0"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "node src/index.js",
|
|
15
|
+
"lint": "eslint .",
|
|
16
|
+
"lint:fix": "eslint . --fix",
|
|
17
|
+
"format": "prettier --write .",
|
|
18
|
+
"format:check": "prettier --check .",
|
|
19
|
+
"prepare": "husky"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"github",
|
|
23
|
+
"release",
|
|
24
|
+
"automation",
|
|
25
|
+
"ai",
|
|
26
|
+
"openai"
|
|
27
|
+
],
|
|
28
|
+
"author": "James Gordo",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"lint-staged": {
|
|
31
|
+
"*.{js,jsx,ts,tsx}": [
|
|
32
|
+
"eslint --fix",
|
|
33
|
+
"prettier --write"
|
|
34
|
+
],
|
|
35
|
+
"*.{json,md,yml,yaml}": [
|
|
36
|
+
"prettier --write"
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@octokit/rest": "^20.0.2",
|
|
41
|
+
"chalk": "^5.3.0",
|
|
42
|
+
"commander": "^11.1.0",
|
|
43
|
+
"dotenv": "^16.3.1",
|
|
44
|
+
"inquirer": "^9.2.12",
|
|
45
|
+
"openai": "^4.24.1",
|
|
46
|
+
"ora": "^7.0.1"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"eslint": "^8.56.0",
|
|
50
|
+
"eslint-config-prettier": "^9.1.0",
|
|
51
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
52
|
+
"husky": "^9.1.7",
|
|
53
|
+
"lint-staged": "^15.4.3",
|
|
54
|
+
"prettier": "^3.2.5"
|
|
55
|
+
},
|
|
56
|
+
"overrides": {
|
|
57
|
+
"uri-js": {
|
|
58
|
+
"punycode": "^2.3.1"
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { config } from 'dotenv';
|
|
5
|
+
import { Octokit } from '@octokit/rest';
|
|
6
|
+
import inquirer from 'inquirer';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import OpenAI from 'openai';
|
|
10
|
+
import { promisify } from 'util';
|
|
11
|
+
import { exec as execCallback } from 'child_process';
|
|
12
|
+
import { createRequire } from 'module';
|
|
13
|
+
|
|
14
|
+
// Initialize utilities
|
|
15
|
+
const exec = promisify(execCallback);
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
|
|
18
|
+
// Load environment variables
|
|
19
|
+
config();
|
|
20
|
+
|
|
21
|
+
// Initialize CLI program
|
|
22
|
+
const program = new Command();
|
|
23
|
+
const pkg = require('../package.json');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get token from git config
|
|
27
|
+
* @param {string} key - Git config key
|
|
28
|
+
* @returns {Promise<string|null>} Token value or null if not found
|
|
29
|
+
*/
|
|
30
|
+
async function getGitConfigToken(key) {
|
|
31
|
+
try {
|
|
32
|
+
const { stdout } = await exec(`git config --global ${key}`);
|
|
33
|
+
return stdout.trim() || null;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Configure and get API token with support for environment variables, git config, and user input
|
|
41
|
+
* @param {Object} config - Token configuration object
|
|
42
|
+
* @param {string} config.envKey - Environment variable key
|
|
43
|
+
* @param {string} config.gitKey - Git config key
|
|
44
|
+
* @param {string} config.name - Service name (e.g., 'GitHub', 'OpenAI')
|
|
45
|
+
* @param {string} config.createUrl - URL where users can create new tokens
|
|
46
|
+
* @param {string} [config.additionalInfo] - Additional information to display
|
|
47
|
+
* @returns {Promise<string>} The configured token
|
|
48
|
+
*/
|
|
49
|
+
async function configureToken({ envKey, gitKey, name, createUrl, additionalInfo = '' }) {
|
|
50
|
+
const token = process.env[envKey] || (await getGitConfigToken(gitKey));
|
|
51
|
+
|
|
52
|
+
if (token) return token;
|
|
53
|
+
|
|
54
|
+
console.log(
|
|
55
|
+
chalk.yellow(`\nNo ${name} token found. Let's set one up.\n`) +
|
|
56
|
+
chalk.cyan(`Create a new token at: ${createUrl}`)
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
if (additionalInfo) {
|
|
60
|
+
console.log(chalk.cyan(additionalInfo));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const { newToken } = await inquirer.prompt([
|
|
64
|
+
{
|
|
65
|
+
type: 'password',
|
|
66
|
+
name: 'newToken',
|
|
67
|
+
message: `Enter your ${name} token:`,
|
|
68
|
+
validate: (input) => input.length > 0 || 'Token is required',
|
|
69
|
+
},
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
await exec(`git config --global ${gitKey} "${newToken}"`);
|
|
74
|
+
console.log(chalk.green(`${name} token saved successfully!`));
|
|
75
|
+
return newToken;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.error(chalk.red(`Failed to save ${name} token:`), error.message);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Initialize API clients
|
|
83
|
+
let octokit;
|
|
84
|
+
let openai;
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Initialize GitHub and OpenAI tokens
|
|
88
|
+
* @returns {Promise<Object>} Object containing both tokens
|
|
89
|
+
*/
|
|
90
|
+
async function initializeTokens() {
|
|
91
|
+
const githubToken = await configureToken({
|
|
92
|
+
envKey: 'GITHUB_TOKEN',
|
|
93
|
+
gitKey: 'github.token',
|
|
94
|
+
name: 'GitHub',
|
|
95
|
+
createUrl: 'https://github.com/settings/tokens/new',
|
|
96
|
+
additionalInfo: "Make sure to enable the 'repo' scope.",
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const openaiToken = await configureToken({
|
|
100
|
+
envKey: 'OPENAI_API_KEY',
|
|
101
|
+
gitKey: 'openai.token',
|
|
102
|
+
name: 'OpenAI',
|
|
103
|
+
createUrl: 'https://platform.openai.com/api-keys',
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return { githubToken, openaiToken };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Fetch closed pull requests from the repository
|
|
111
|
+
* @param {string} owner - Repository owner
|
|
112
|
+
* @param {string} repo - Repository name
|
|
113
|
+
* @returns {Promise<Array>} List of pull requests
|
|
114
|
+
*/
|
|
115
|
+
async function fetchPullRequests(owner, repo) {
|
|
116
|
+
const spinner = ora('Fetching pull requests...').start();
|
|
117
|
+
try {
|
|
118
|
+
const { data: pulls } = await octokit.pulls.list({
|
|
119
|
+
owner,
|
|
120
|
+
repo,
|
|
121
|
+
state: 'closed',
|
|
122
|
+
sort: 'updated',
|
|
123
|
+
direction: 'desc',
|
|
124
|
+
per_page: 30,
|
|
125
|
+
});
|
|
126
|
+
spinner.succeed(`Found ${pulls.length} pull requests`);
|
|
127
|
+
return pulls;
|
|
128
|
+
} catch (error) {
|
|
129
|
+
spinner.fail('Failed to fetch pull requests');
|
|
130
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Generate an AI-powered release summary from selected pull requests
|
|
137
|
+
* @param {Array} selectedPRs - List of selected pull requests
|
|
138
|
+
* @returns {Promise<string>} Generated release summary
|
|
139
|
+
*/
|
|
140
|
+
async function generateSummary(selectedPRs) {
|
|
141
|
+
const spinner = ora('Generating release summary...').start();
|
|
142
|
+
try {
|
|
143
|
+
const prDetails = selectedPRs.map((pr) => ({
|
|
144
|
+
number: pr.number,
|
|
145
|
+
title: pr.title,
|
|
146
|
+
author: pr.user.login,
|
|
147
|
+
authorUrl: `https://github.com/${pr.user.login}`,
|
|
148
|
+
date: new Date(pr.created_at).toLocaleDateString(),
|
|
149
|
+
url: pr.html_url,
|
|
150
|
+
}));
|
|
151
|
+
|
|
152
|
+
const prompt = `Create a release summary for the following pull requests. The summary should have two parts:
|
|
153
|
+
|
|
154
|
+
1. Group the changes by type (e.g., Features, Bug Fixes, Improvements) and list them down in bullet points.
|
|
155
|
+
1.1 Make each type is an h3 header with a corresponding emoji prefix.
|
|
156
|
+
1.2 For each type, make each bullet point concise and easy to read and understand for non-tech people.
|
|
157
|
+
1.3 Don't link the bullet points to a pull requests
|
|
158
|
+
2. The last section should be a list of pull requests included in the release. Format: "#<number> - <title> by [@<author>](<authorUrl>) (<date>)".
|
|
159
|
+
|
|
160
|
+
Pull Requests to summarize:
|
|
161
|
+
${JSON.stringify(prDetails, null, 2)}
|
|
162
|
+
|
|
163
|
+
Keep the summary concise, clear, and focused on the user impact. Use professional but easy-to-understand language.`;
|
|
164
|
+
|
|
165
|
+
const response = await openai.chat.completions.create({
|
|
166
|
+
model: 'gpt-4o',
|
|
167
|
+
messages: [{ role: 'user', content: prompt }],
|
|
168
|
+
temperature: 0.7,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Validate response structure
|
|
172
|
+
if (!response?.choices?.length || !response.choices[0]?.message?.content) {
|
|
173
|
+
throw new Error(
|
|
174
|
+
'Invalid API response structure. Expected response.choices[0].message.content'
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
spinner.succeed('Summary generated successfully');
|
|
179
|
+
return response.choices[0].message.content;
|
|
180
|
+
} catch (error) {
|
|
181
|
+
spinner.fail('Failed to generate summary');
|
|
182
|
+
|
|
183
|
+
// Handle specific API response errors
|
|
184
|
+
if (error.message.includes('Invalid API response')) {
|
|
185
|
+
console.error(
|
|
186
|
+
chalk.red('Error: The AI service returned an unexpected response format.\n') +
|
|
187
|
+
chalk.yellow('This might be due to:') +
|
|
188
|
+
'\n- Service temporarily unavailable' +
|
|
189
|
+
'\n- Rate limiting' +
|
|
190
|
+
'\n- Model configuration issues\n' +
|
|
191
|
+
chalk.cyan('Please try again in a few moments.')
|
|
192
|
+
);
|
|
193
|
+
} else {
|
|
194
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Provide fallback option
|
|
198
|
+
const { useFallback } = await inquirer.prompt([
|
|
199
|
+
{
|
|
200
|
+
type: 'confirm',
|
|
201
|
+
name: 'useFallback',
|
|
202
|
+
message: 'Would you like to use a simple list format instead?',
|
|
203
|
+
default: true,
|
|
204
|
+
},
|
|
205
|
+
]);
|
|
206
|
+
|
|
207
|
+
if (useFallback) {
|
|
208
|
+
return selectedPRs
|
|
209
|
+
.map((pr) => {
|
|
210
|
+
const date = new Date(pr.created_at).toLocaleDateString();
|
|
211
|
+
return `#${pr.number} - ${pr.title} (by [@${pr.user.login}](https://github.com/${pr.user.login}) on ${date})`;
|
|
212
|
+
})
|
|
213
|
+
.join('\n');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Create a release pull request with the generated summary
|
|
222
|
+
* @param {string} owner - Repository owner
|
|
223
|
+
* @param {string} repo - Repository name
|
|
224
|
+
* @param {string} summary - Generated release summary
|
|
225
|
+
* @param {Array} selectedPRs - List of selected pull requests
|
|
226
|
+
* @param {string} sourceBranch - Source branch name
|
|
227
|
+
* @param {string} targetBranch - Target branch name
|
|
228
|
+
* @param {string} version - Release version
|
|
229
|
+
* @returns {Promise<Object>} Created pull request data
|
|
230
|
+
*/
|
|
231
|
+
async function createReleasePR(
|
|
232
|
+
owner,
|
|
233
|
+
repo,
|
|
234
|
+
summary,
|
|
235
|
+
selectedPRs,
|
|
236
|
+
sourceBranch,
|
|
237
|
+
targetBranch,
|
|
238
|
+
version
|
|
239
|
+
) {
|
|
240
|
+
const spinner = ora('Creating release PR...').start();
|
|
241
|
+
try {
|
|
242
|
+
const body = `# Release Summary
|
|
243
|
+
|
|
244
|
+
${summary}`;
|
|
245
|
+
|
|
246
|
+
const { data: pr } = await octokit.pulls.create({
|
|
247
|
+
owner,
|
|
248
|
+
repo,
|
|
249
|
+
title: `Release: Version ${version}`,
|
|
250
|
+
head: sourceBranch,
|
|
251
|
+
base: targetBranch,
|
|
252
|
+
body,
|
|
253
|
+
draft: true,
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
spinner.succeed(`Release PR #${pr.number} created successfully`);
|
|
257
|
+
return pr;
|
|
258
|
+
} catch (error) {
|
|
259
|
+
spinner.fail('Failed to create release PR');
|
|
260
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
261
|
+
process.exit(1);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function run() {
|
|
266
|
+
// Initialize tokens sequentially
|
|
267
|
+
const { githubToken, openaiToken } = await initializeTokens();
|
|
268
|
+
|
|
269
|
+
// Initialize clients with tokens
|
|
270
|
+
octokit = new Octokit({
|
|
271
|
+
auth: githubToken,
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
openai = new OpenAI({
|
|
275
|
+
apiKey: openaiToken,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
const { owner, repo } = await inquirer.prompt([
|
|
279
|
+
{
|
|
280
|
+
type: 'input',
|
|
281
|
+
name: 'owner',
|
|
282
|
+
message: 'Enter repository owner:',
|
|
283
|
+
validate: (input) => input.length > 0,
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
type: 'input',
|
|
287
|
+
name: 'repo',
|
|
288
|
+
message: 'Enter repository name:',
|
|
289
|
+
validate: (input) => input.length > 0,
|
|
290
|
+
},
|
|
291
|
+
]);
|
|
292
|
+
|
|
293
|
+
const pulls = await fetchPullRequests(owner, repo);
|
|
294
|
+
|
|
295
|
+
const { selectedPRs } = await inquirer.prompt([
|
|
296
|
+
{
|
|
297
|
+
type: 'checkbox',
|
|
298
|
+
name: 'selectedPRs',
|
|
299
|
+
message: 'Select pull requests to include in the release:',
|
|
300
|
+
choices: pulls.map((pr) => ({
|
|
301
|
+
name: `#${pr.number} - ${pr.title}`,
|
|
302
|
+
value: pr,
|
|
303
|
+
})),
|
|
304
|
+
validate: (input) => input.length > 0,
|
|
305
|
+
},
|
|
306
|
+
]);
|
|
307
|
+
|
|
308
|
+
const { summaryType } = await inquirer.prompt([
|
|
309
|
+
{
|
|
310
|
+
type: 'list',
|
|
311
|
+
name: 'summaryType',
|
|
312
|
+
message: 'How would you like to summarize the pull requests?',
|
|
313
|
+
choices: [
|
|
314
|
+
{ name: 'Use AI to generate a summary', value: 'ai' },
|
|
315
|
+
{ name: 'Simply list the selected pull requests', value: 'list' },
|
|
316
|
+
],
|
|
317
|
+
},
|
|
318
|
+
]);
|
|
319
|
+
|
|
320
|
+
let summary;
|
|
321
|
+
if (summaryType === 'ai') {
|
|
322
|
+
summary = await generateSummary(selectedPRs);
|
|
323
|
+
} else {
|
|
324
|
+
summary = selectedPRs
|
|
325
|
+
.map((pr) => {
|
|
326
|
+
const date = new Date(pr.created_at).toLocaleDateString();
|
|
327
|
+
return `#${pr.number} - ${pr.title} (by [@${pr.user.login}](https://github.com/${pr.user.login}) on ${date})`;
|
|
328
|
+
})
|
|
329
|
+
.join('\n');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
console.log(chalk.cyan('\nSummary:'));
|
|
333
|
+
console.log(summary);
|
|
334
|
+
|
|
335
|
+
const { version, confirm, sourceBranch, targetBranch } = await inquirer.prompt([
|
|
336
|
+
{
|
|
337
|
+
type: 'input',
|
|
338
|
+
name: 'version',
|
|
339
|
+
message: 'Enter the version number for this release (e.g., 1.2.3):',
|
|
340
|
+
validate: (input) => {
|
|
341
|
+
// Validate semantic versioning format (x.y.z)
|
|
342
|
+
const semverRegex = /^\d+\.\d+\.\d+$/;
|
|
343
|
+
if (!semverRegex.test(input)) {
|
|
344
|
+
return 'Please enter a valid version number in the format x.y.z (e.g., 1.2.3)';
|
|
345
|
+
}
|
|
346
|
+
return true;
|
|
347
|
+
},
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
type: 'confirm',
|
|
351
|
+
name: 'confirm',
|
|
352
|
+
message: 'Would you like to create a release PR with this summary?',
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
type: 'input',
|
|
356
|
+
name: 'sourceBranch',
|
|
357
|
+
message: 'Enter source branch name:',
|
|
358
|
+
when: (answers) => answers.confirm,
|
|
359
|
+
validate: (input) => input.length > 0,
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
type: 'input',
|
|
363
|
+
name: 'targetBranch',
|
|
364
|
+
message: 'Enter target branch name:',
|
|
365
|
+
when: (answers) => answers.confirm,
|
|
366
|
+
validate: (input) => input.length > 0,
|
|
367
|
+
},
|
|
368
|
+
]);
|
|
369
|
+
|
|
370
|
+
if (confirm) {
|
|
371
|
+
const pr = await createReleasePR(
|
|
372
|
+
owner,
|
|
373
|
+
repo,
|
|
374
|
+
summary,
|
|
375
|
+
selectedPRs,
|
|
376
|
+
sourceBranch,
|
|
377
|
+
targetBranch,
|
|
378
|
+
version
|
|
379
|
+
);
|
|
380
|
+
console.log(chalk.green('\nSuccess! Release PR created:'), pr.html_url);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
program
|
|
385
|
+
.name('create-app-release')
|
|
386
|
+
.description('AI-powered GitHub release automation tool')
|
|
387
|
+
.version(pkg.version)
|
|
388
|
+
.action(run)
|
|
389
|
+
.parse(process.argv);
|