ai-changelog-generator-extension 0.4.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/.vscode/launch.json +41 -0
- package/.vscode/settings.json +15 -0
- package/.vscode/tasks.json +26 -0
- package/.vscodeignore +62 -0
- package/ARCHITECTURE-v0.4.0.md +418 -0
- package/CHANGELOG.md +28 -0
- package/DEVELOPMENT.md +266 -0
- package/IMPLEMENTATION_SUMMARY.md +240 -0
- package/IMPLEMENTATION_SUMMARY_v0.4.0.md +239 -0
- package/LICENSE +21 -0
- package/MIGRATION_CHECKLIST.md +200 -0
- package/QUICK-REFERENCE-v0.4.0.md +251 -0
- package/QUICK-START.md +211 -0
- package/README.md +95 -0
- package/RELEASE-NOTES-0.2.0.md +176 -0
- package/RELEASE-NOTES-0.3.0.md +275 -0
- package/RELEASE-NOTES-0.4.0.md +194 -0
- package/SETTINGS-GUIDE.md +418 -0
- package/TESTING-v0.4.0.md +263 -0
- package/TESTING.md +255 -0
- package/esbuild.js +54 -0
- package/package.json +282 -0
- package/setup.sh +58 -0
- package/src/commands/configureProvider.ts +28 -0
- package/src/commands/setupWizard.ts +333 -0
- package/src/commands/testConnection.ts +93 -0
- package/src/extension.ts +264 -0
- package/src/services/EnvFileService.ts +172 -0
- package/src/services/ExtensionConfigurationManager.ts +224 -0
- package/src/services/StatusService.ts +211 -0
- package/src/types/core.d.ts +85 -0
- package/src/views/CommitSidebarProvider.ts +572 -0
- package/src/views/OnboardingPanelProvider.ts +1366 -0
- package/src/views/ReleaseSidebarProvider.ts +370 -0
- package/src/views/SettingsPanelProvider.ts +991 -0
- package/test-package.sh +38 -0
- package/tsconfig.json +18 -0
- package/v0.4.0-COMPLETE.md +349 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import * as vscode from 'vscode'
|
|
2
|
+
import { AIChangelogGenerator } from '@entro314labs/ai-changelog-generator'
|
|
3
|
+
import { ExtensionConfigurationManager } from '../services/ExtensionConfigurationManager'
|
|
4
|
+
import { StatusService } from '../services/StatusService'
|
|
5
|
+
|
|
6
|
+
export class ReleaseSidebarProvider implements vscode.WebviewViewProvider {
|
|
7
|
+
_view?: vscode.WebviewView
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
private readonly _extensionUri: vscode.Uri,
|
|
11
|
+
private readonly _configManager: ExtensionConfigurationManager
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
public resolveWebviewView(
|
|
15
|
+
webviewView: vscode.WebviewView,
|
|
16
|
+
context: vscode.WebviewViewResolveContext,
|
|
17
|
+
_token: vscode.CancellationToken
|
|
18
|
+
) {
|
|
19
|
+
this._view = webviewView
|
|
20
|
+
|
|
21
|
+
webviewView.webview.options = {
|
|
22
|
+
enableScripts: true,
|
|
23
|
+
localResourceRoots: [this._extensionUri],
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
webviewView.webview.html = this._getHtmlForWebview(webviewView.webview)
|
|
27
|
+
|
|
28
|
+
webviewView.webview.onDidReceiveMessage(async (data) => {
|
|
29
|
+
switch (data.type) {
|
|
30
|
+
case 'refresh':
|
|
31
|
+
await this.refresh()
|
|
32
|
+
break
|
|
33
|
+
case 'generate':
|
|
34
|
+
await this.generateChangelog()
|
|
35
|
+
break
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
// Initial refresh
|
|
40
|
+
setTimeout(() => this.refresh(), 1000)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public async refresh() {
|
|
44
|
+
if (!this._view) {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
StatusService.log('[ReleaseView] Starting refresh...', true)
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const workspaceFolder = this.getWorkspaceFolder()
|
|
52
|
+
if (!workspaceFolder) {
|
|
53
|
+
StatusService.log('[ReleaseView] No workspace folder', true)
|
|
54
|
+
this._view.webview.postMessage({
|
|
55
|
+
type: 'error',
|
|
56
|
+
message: 'No workspace folder open. Please open a git repository.',
|
|
57
|
+
})
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const rootPath = workspaceFolder.uri.fsPath
|
|
62
|
+
const configPath = require('path').join(rootPath, '.env.local')
|
|
63
|
+
|
|
64
|
+
StatusService.log(`[ReleaseView] Repository: ${rootPath}`, true)
|
|
65
|
+
|
|
66
|
+
// Use AI Changelog Generator to get repository info
|
|
67
|
+
StatusService.log('[ReleaseView] Creating generator...', true)
|
|
68
|
+
const generator = new AIChangelogGenerator({
|
|
69
|
+
repositoryPath: rootPath,
|
|
70
|
+
configPath: require('fs').existsSync(configPath) ? configPath : undefined,
|
|
71
|
+
silent: true,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
// Get tags - analyze repository to see if it has tags
|
|
75
|
+
let tags: string[] = []
|
|
76
|
+
try {
|
|
77
|
+
StatusService.log('[ReleaseView] Analyzing for tags...', true)
|
|
78
|
+
|
|
79
|
+
// Add timeout to prevent hanging
|
|
80
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
81
|
+
setTimeout(() => reject(new Error('Analysis timeout after 15 seconds')), 15000)
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
const analysis: any = await Promise.race([
|
|
85
|
+
generator.analyzeRepository(),
|
|
86
|
+
timeoutPromise
|
|
87
|
+
])
|
|
88
|
+
|
|
89
|
+
tags = analysis.tags || []
|
|
90
|
+
StatusService.log(`[ReleaseView] Found ${tags.length} tags`, true)
|
|
91
|
+
} catch (e: any) {
|
|
92
|
+
// No tags yet - that's okay
|
|
93
|
+
StatusService.log(`[ReleaseView] No tags: ${e.message}`, true)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
StatusService.log(`[ReleaseView] Posting ${tags.length} tags`, true)
|
|
97
|
+
this._view.webview.postMessage({ type: 'update', tags })
|
|
98
|
+
} catch (error: any) {
|
|
99
|
+
StatusService.log(`[ReleaseView] ERROR: ${error.message}`, true)
|
|
100
|
+
console.error('Error refreshing release view:', error)
|
|
101
|
+
this._view.webview.postMessage({
|
|
102
|
+
type: 'error',
|
|
103
|
+
message: `Failed to refresh: ${error.message || 'Unknown error'}`,
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get the current workspace folder, handling multi-root workspaces
|
|
110
|
+
*/
|
|
111
|
+
private getWorkspaceFolder(): vscode.WorkspaceFolder | undefined {
|
|
112
|
+
const workspaceFolders = vscode.workspace.workspaceFolders
|
|
113
|
+
if (!workspaceFolders || workspaceFolders.length === 0) {
|
|
114
|
+
return undefined
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// If there's an active text editor, use its workspace folder
|
|
118
|
+
if (vscode.window.activeTextEditor) {
|
|
119
|
+
const activeFolder = vscode.workspace.getWorkspaceFolder(
|
|
120
|
+
vscode.window.activeTextEditor.document.uri
|
|
121
|
+
)
|
|
122
|
+
if (activeFolder) {
|
|
123
|
+
return activeFolder
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Default to first workspace folder
|
|
128
|
+
return workspaceFolders[0]
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private async generateChangelog() {
|
|
132
|
+
const workspaceFolder = this.getWorkspaceFolder()
|
|
133
|
+
if (!workspaceFolder) {
|
|
134
|
+
vscode.window.showErrorMessage('No workspace folder open')
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
const rootPath = workspaceFolder.uri.fsPath
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
// Ensure configuration is initialized
|
|
141
|
+
if (!this._configManager.isInitialized()) {
|
|
142
|
+
await this._configManager.initialize()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const configPath = require('path').join(rootPath, '.env.local')
|
|
146
|
+
|
|
147
|
+
// Create AI Changelog Generator
|
|
148
|
+
const generator = new AIChangelogGenerator({
|
|
149
|
+
repositoryPath: rootPath,
|
|
150
|
+
configPath: require('fs').existsSync(configPath) ? configPath : undefined,
|
|
151
|
+
silent: true,
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Analyze recent commits to suggest next version
|
|
155
|
+
StatusService.log('[ReleaseView] Analyzing commits for version suggestion...', true)
|
|
156
|
+
const recentCommits = await generator.analyzeRecentCommits(50)
|
|
157
|
+
|
|
158
|
+
// Calculate suggested version based on semantic versioning rules
|
|
159
|
+
const currentVersion = await this.getCurrentVersion(rootPath)
|
|
160
|
+
const suggestedVersion = this.calculateNextVersion(currentVersion, recentCommits)
|
|
161
|
+
|
|
162
|
+
StatusService.log(`[ReleaseView] Current: ${currentVersion}, Suggested: ${suggestedVersion}`, true)
|
|
163
|
+
|
|
164
|
+
const version = await vscode.window.showInputBox({
|
|
165
|
+
prompt: `Enter release version (suggested: ${suggestedVersion} based on commit analysis)`,
|
|
166
|
+
placeHolder: suggestedVersion,
|
|
167
|
+
value: suggestedVersion,
|
|
168
|
+
valueSelection: undefined,
|
|
169
|
+
validateInput: (value) => {
|
|
170
|
+
if (!value || value.trim() === '') {
|
|
171
|
+
return 'Version is required'
|
|
172
|
+
}
|
|
173
|
+
// Basic version format validation
|
|
174
|
+
if (!/^v?\d+\.\d+\.\d+/.test(value)) {
|
|
175
|
+
return 'Please use semantic versioning format (e.g., v1.0.1 or 1.0.1)'
|
|
176
|
+
}
|
|
177
|
+
return null
|
|
178
|
+
},
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
if (!version) return
|
|
182
|
+
|
|
183
|
+
await vscode.window.withProgress(
|
|
184
|
+
{
|
|
185
|
+
location: vscode.ProgressLocation.Notification,
|
|
186
|
+
title: `Generating Changelog for ${version}...`,
|
|
187
|
+
cancellable: false,
|
|
188
|
+
},
|
|
189
|
+
async () => {
|
|
190
|
+
// Generate changelog for the version
|
|
191
|
+
const result = await generator.generateChangelog(version)
|
|
192
|
+
|
|
193
|
+
vscode.window.showInformationMessage(
|
|
194
|
+
`✅ Changelog generated successfully for ${version}!`,
|
|
195
|
+
'Open File'
|
|
196
|
+
).then((action) => {
|
|
197
|
+
if (action === 'Open File') {
|
|
198
|
+
// Open the generated changelog file
|
|
199
|
+
const changelogPath = require('path').join(rootPath, 'AI_CHANGELOG.md')
|
|
200
|
+
vscode.workspace.openTextDocument(changelogPath).then(doc => {
|
|
201
|
+
vscode.window.showTextDocument(doc)
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// Refresh the view
|
|
207
|
+
setTimeout(() => this.refresh(), 500)
|
|
208
|
+
}
|
|
209
|
+
)
|
|
210
|
+
} catch (error: any) {
|
|
211
|
+
console.error('Changelog generation failed:', error)
|
|
212
|
+
vscode.window.showErrorMessage(
|
|
213
|
+
`Failed to generate changelog: ${error.message || 'Unknown error'}`
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get current version from git tags or package.json
|
|
220
|
+
*/
|
|
221
|
+
private async getCurrentVersion(rootPath: string): Promise<string> {
|
|
222
|
+
try {
|
|
223
|
+
// Try to get latest git tag first
|
|
224
|
+
const { execSync } = require('child_process')
|
|
225
|
+
const latestTag = execSync('git describe --tags --abbrev=0', {
|
|
226
|
+
cwd: rootPath,
|
|
227
|
+
encoding: 'utf8',
|
|
228
|
+
stdio: ['pipe', 'pipe', 'ignore']
|
|
229
|
+
}).trim()
|
|
230
|
+
|
|
231
|
+
if (latestTag) {
|
|
232
|
+
return latestTag
|
|
233
|
+
}
|
|
234
|
+
} catch (e) {
|
|
235
|
+
// No tags yet, try package.json
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
try {
|
|
239
|
+
const packageJsonPath = require('path').join(rootPath, 'package.json')
|
|
240
|
+
const fs = require('fs')
|
|
241
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
242
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
243
|
+
if (packageJson.version) {
|
|
244
|
+
return packageJson.version.startsWith('v') ? packageJson.version : `v${packageJson.version}`
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} catch (e) {
|
|
248
|
+
// No package.json
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return 'v0.0.0'
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Calculate next semantic version based on commit types
|
|
256
|
+
* - feat = minor bump (0.1.0 -> 0.2.0)
|
|
257
|
+
* - fix = patch bump (0.1.0 -> 0.1.1)
|
|
258
|
+
* - breaking = major bump (0.1.0 -> 1.0.0)
|
|
259
|
+
*/
|
|
260
|
+
private calculateNextVersion(currentVersion: string, commits: any): string {
|
|
261
|
+
// Parse current version
|
|
262
|
+
const versionMatch = currentVersion.match(/v?(\d+)\.(\d+)\.(\d+)/)
|
|
263
|
+
if (!versionMatch) {
|
|
264
|
+
return 'v0.1.0'
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
let [, major, minor, patch] = versionMatch.map(Number)
|
|
268
|
+
|
|
269
|
+
// Analyze commits for version bump type
|
|
270
|
+
const hasBreaking = commits.some((c: any) =>
|
|
271
|
+
c.breaking ||
|
|
272
|
+
c.breakingChanges?.length > 0 ||
|
|
273
|
+
c.message?.includes('BREAKING CHANGE')
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
const hasFeat = commits.some((c: any) =>
|
|
277
|
+
c.type === 'feat' ||
|
|
278
|
+
c.message?.startsWith('feat:') ||
|
|
279
|
+
c.message?.startsWith('feat(')
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
const hasFix = commits.some((c: any) =>
|
|
283
|
+
c.type === 'fix' ||
|
|
284
|
+
c.message?.startsWith('fix:') ||
|
|
285
|
+
c.message?.startsWith('fix(')
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
StatusService.log(`[ReleaseView] Version analysis: breaking=${hasBreaking}, feat=${hasFeat}, fix=${hasFix}`, true)
|
|
289
|
+
|
|
290
|
+
if (hasBreaking) {
|
|
291
|
+
// Major version bump
|
|
292
|
+
major++
|
|
293
|
+
minor = 0
|
|
294
|
+
patch = 0
|
|
295
|
+
} else if (hasFeat) {
|
|
296
|
+
// Minor version bump
|
|
297
|
+
minor++
|
|
298
|
+
patch = 0
|
|
299
|
+
} else if (hasFix) {
|
|
300
|
+
// Patch version bump
|
|
301
|
+
patch++
|
|
302
|
+
} else {
|
|
303
|
+
// No significant changes, suggest patch bump
|
|
304
|
+
patch++
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return `v${major}.${minor}.${patch}`
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private _getHtmlForWebview(webview: vscode.Webview) {
|
|
311
|
+
return `<!DOCTYPE html>
|
|
312
|
+
<html lang="en">
|
|
313
|
+
<head>
|
|
314
|
+
<meta charset="UTF-8">
|
|
315
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
316
|
+
<title>Release Manager</title>
|
|
317
|
+
<style>
|
|
318
|
+
body { font-family: var(--vscode-font-family); color: var(--vscode-foreground); padding: 10px; }
|
|
319
|
+
.tag-item { display: flex; align-items: center; margin-bottom: 5px; padding: 5px; background: var(--vscode-list-hoverBackground); border-radius: 4px; }
|
|
320
|
+
.tag-icon { margin-right: 10px; }
|
|
321
|
+
.tag-name { flex: 1; font-weight: bold; }
|
|
322
|
+
.button { background-color: var(--vscode-button-background); color: var(--vscode-button-foreground); border: none; padding: 8px 12px; cursor: pointer; width: 100%; margin-top: 10px; }
|
|
323
|
+
.button:hover { background-color: var(--vscode-button-hoverBackground); }
|
|
324
|
+
.error { color: var(--vscode-errorForeground); }
|
|
325
|
+
h3 { margin-top: 0; }
|
|
326
|
+
</style>
|
|
327
|
+
</head>
|
|
328
|
+
<body>
|
|
329
|
+
<h3>Recent Tags</h3>
|
|
330
|
+
<div id="tag-list">Loading...</div>
|
|
331
|
+
|
|
332
|
+
<button class="button" onclick="handleGenerate()">Draft New Release</button>
|
|
333
|
+
<button class="button" onclick="handleRefresh()">Refresh System</button>
|
|
334
|
+
|
|
335
|
+
<script>
|
|
336
|
+
const vscode = acquireVsCodeApi();
|
|
337
|
+
|
|
338
|
+
window.addEventListener('message', event => {
|
|
339
|
+
const message = event.data;
|
|
340
|
+
const list = document.getElementById('tag-list');
|
|
341
|
+
|
|
342
|
+
if (message.type === 'update') {
|
|
343
|
+
if (message.tags.length === 0) {
|
|
344
|
+
list.innerHTML = '<p>No tags found.</p>';
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
list.innerHTML = message.tags.map(t => \`
|
|
349
|
+
<div class="tag-item">
|
|
350
|
+
<span class="tag-icon">🏷️</span>
|
|
351
|
+
<span class="tag-name">\${t}</span>
|
|
352
|
+
</div>
|
|
353
|
+
\`).join('');
|
|
354
|
+
} else if (message.type === 'error') {
|
|
355
|
+
list.innerHTML = \`<p class="error">\${message.message}</p>\`;
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
function handleRefresh() {
|
|
360
|
+
vscode.postMessage({ type: 'refresh' });
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function handleGenerate() {
|
|
364
|
+
vscode.postMessage({ type: 'generate' });
|
|
365
|
+
}
|
|
366
|
+
</script>
|
|
367
|
+
</body>
|
|
368
|
+
</html>`
|
|
369
|
+
}
|
|
370
|
+
}
|