@intranefr/superbackend 1.6.7 → 1.7.8
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/.beads/.br_history/issues.20260314_212352_900045509.jsonl +0 -0
- package/.beads/.br_history/issues.20260314_212352_900045509.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_087140743.jsonl +1 -0
- package/.beads/.br_history/issues.20260314_212353_087140743.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_285881504.jsonl +2 -0
- package/.beads/.br_history/issues.20260314_212353_285881504.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_473915419.jsonl +3 -0
- package/.beads/.br_history/issues.20260314_212353_473915419.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_659476307.jsonl +4 -0
- package/.beads/.br_history/issues.20260314_212353_659476307.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212353_869998925.jsonl +5 -0
- package/.beads/.br_history/issues.20260314_212353_869998925.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_212354_054785029.jsonl +6 -0
- package/.beads/.br_history/issues.20260314_212354_054785029.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_175893691.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_175893691.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_338509797.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_338509797.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_515443192.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_515443192.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_676417592.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_676417592.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213336_839182422.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213336_839182422.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213337_004349113.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213337_004349113.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213337_179824080.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213337_179824080.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213701_705075332.jsonl +7 -0
- package/.beads/.br_history/issues.20260314_213701_705075332.jsonl.meta.json +1 -0
- package/.beads/.br_history/issues.20260314_213706_783128702.jsonl +8 -0
- package/.beads/.br_history/issues.20260314_213706_783128702.jsonl.meta.json +1 -0
- package/.beads/config.yaml +4 -0
- package/.beads/issues.jsonl +8 -0
- package/.beads/metadata.json +4 -0
- package/.env.example +8 -0
- package/autochangelog/.env.example +36 -0
- package/autochangelog/README.md +412 -0
- package/autochangelog/config/database.js +27 -0
- package/autochangelog/package.json +47 -0
- package/autochangelog/public/landing.html +581 -0
- package/autochangelog/server.js +104 -0
- package/autochangelog/src/app.js +181 -0
- package/autochangelog/src/config/database.js +26 -0
- package/autochangelog/src/controllers/auth.js +488 -0
- package/autochangelog/src/controllers/changelog.js +682 -0
- package/autochangelog/src/controllers/project.js +580 -0
- package/autochangelog/src/controllers/repository.js +780 -0
- package/autochangelog/src/middleware/auth.js +386 -0
- package/autochangelog/src/models/Changelog.js +443 -0
- package/autochangelog/src/models/Project.js +226 -0
- package/autochangelog/src/models/Repository.js +366 -0
- package/autochangelog/src/models/User.js +223 -0
- package/autochangelog/src/routes/auth.routes.js +32 -0
- package/autochangelog/src/routes/changelog.routes.js +42 -0
- package/autochangelog/src/routes/github-auth.routes.js +102 -0
- package/autochangelog/src/routes/project.routes.js +50 -0
- package/autochangelog/src/routes/repository.routes.js +54 -0
- package/autochangelog/src/services/changelog.js +722 -0
- package/autochangelog/src/services/github.js +243 -0
- package/autochangelog/utils/logger.js +77 -0
- package/autochangelog/views/404.ejs +18 -0
- package/autochangelog/views/dashboard.ejs +596 -0
- package/autochangelog/views/index.ejs +231 -0
- package/autochangelog/views/layouts/main.ejs +44 -0
- package/autochangelog/views/login.ejs +104 -0
- package/autochangelog/views/partials/footer.ejs +20 -0
- package/autochangelog/views/partials/navbar.ejs +51 -0
- package/autochangelog/views/register.ejs +109 -0
- package/autochangelog-cli/README.md +266 -0
- package/autochangelog-cli/bin/autochangelog +120 -0
- package/autochangelog-cli/package.json +46 -0
- package/autochangelog-cli/src/cli/commands/auth.js +291 -0
- package/autochangelog-cli/src/cli/commands/changelog.js +619 -0
- package/autochangelog-cli/src/cli/commands/project.js +427 -0
- package/autochangelog-cli/src/cli/commands/repo.js +557 -0
- package/autochangelog-cli/src/cli/commands/stats.js +706 -0
- package/autochangelog-cli/src/cli/utils/config.js +277 -0
- package/autochangelog-cli/src/cli/utils/errors.js +307 -0
- package/autochangelog-cli/src/cli/utils/logger.js +75 -0
- package/autochangelog-cli/src/cli/utils/output.js +357 -0
- package/package.json +9 -3
- package/plugins/supercli/README.md +108 -0
- package/plugins/supercli/plugin.json +123 -0
- package/server.js +1 -1
- package/src/cli/api.js +380 -0
- package/src/cli/direct/agent-utils.js +61 -0
- package/src/cli/direct/cli-utils.js +112 -0
- package/src/cli/direct/data-seeding.js +307 -0
- package/src/cli/direct/db-admin.js +84 -0
- package/src/cli/direct/db-advanced.js +372 -0
- package/src/cli/direct/db-utils.js +558 -0
- package/src/cli/direct/help.js +195 -0
- package/src/cli/direct/migration.js +107 -0
- package/src/cli/direct/rbac-advanced.js +132 -0
- package/src/cli/direct/resources-additional.js +400 -0
- package/src/cli/direct/resources-cms-advanced.js +173 -0
- package/src/cli/direct/resources-cms.js +247 -0
- package/src/cli/direct/resources-core.js +253 -0
- package/src/cli/direct/resources-execution.js +367 -0
- package/src/cli/direct/resources-health.js +152 -0
- package/src/cli/direct/resources-integrations.js +182 -0
- package/src/cli/direct/resources-logs.js +204 -0
- package/src/cli/direct/resources-org-rbac.js +187 -0
- package/src/cli/direct/resources-system.js +236 -0
- package/src/cli/direct.js +556 -0
- package/src/controllers/admin.controller.js +4 -0
- package/src/controllers/auth.controller.js +148 -1
- package/src/controllers/waitingList.controller.js +130 -1
- package/src/models/RbacRole.js +1 -1
- package/src/models/User.js +39 -5
- package/src/routes/auth.routes.js +6 -0
- package/src/routes/waitingList.routes.js +12 -2
- package/src/routes/waitingListAdmin.routes.js +3 -0
- package/src/services/email.service.js +1 -0
- package/src/services/github.service.js +255 -0
- package/src/services/rateLimiter.service.js +29 -1
- package/src/services/waitingListJson.service.js +32 -3
- package/views/admin-waiting-list.ejs +386 -3
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
const simpleGit = require('simple-git');
|
|
2
|
+
const { parse } = require('conventional-commits-parser');
|
|
3
|
+
const logger = require('../../utils/logger');
|
|
4
|
+
const Project = require('../models/Project');
|
|
5
|
+
const Repository = require('../models/Repository');
|
|
6
|
+
const Changelog = require('../models/Changelog');
|
|
7
|
+
const GitHubService = require('./github');
|
|
8
|
+
|
|
9
|
+
class ChangelogService {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.git = simpleGit();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Generate changelog for a specific month and year
|
|
15
|
+
async generateChangelog(projectId, repositoryId, month, year, options = {}) {
|
|
16
|
+
const { format = 'markdown', template = 'default', includeCommitsWithoutPR = true } = options;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
// Get project and repository
|
|
20
|
+
const [project, repository] = await Promise.all([
|
|
21
|
+
Project.findById(projectId),
|
|
22
|
+
Repository.findById(repositoryId),
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
if (!project || !repository) {
|
|
26
|
+
throw new Error('Project or repository not found');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check if changelog already exists
|
|
30
|
+
const existingChangelog = await Changelog.findOne({
|
|
31
|
+
projectId,
|
|
32
|
+
repositoryId,
|
|
33
|
+
month,
|
|
34
|
+
year,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (existingChangelog) {
|
|
38
|
+
throw new Error('Changelog already exists for this period');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Create changelog record with pending status
|
|
42
|
+
const changelog = new Changelog({
|
|
43
|
+
projectId,
|
|
44
|
+
repositoryId,
|
|
45
|
+
title: `${project.name} - ${this.getMonthName(month)} ${year}`,
|
|
46
|
+
description: `Changelog for ${this.getMonthName(month)} ${year}`,
|
|
47
|
+
month,
|
|
48
|
+
year,
|
|
49
|
+
format,
|
|
50
|
+
template,
|
|
51
|
+
status: 'generating',
|
|
52
|
+
generatedBy: repository.ownerId,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
await changelog.save();
|
|
56
|
+
|
|
57
|
+
// Generate changelog content
|
|
58
|
+
const startTime = Date.now();
|
|
59
|
+
const content = await this.generateContent(repository, month, year, { format, template, includeCommitsWithoutPR });
|
|
60
|
+
const generationTime = Date.now() - startTime;
|
|
61
|
+
|
|
62
|
+
// Update changelog with content and completion status
|
|
63
|
+
changelog.content = content;
|
|
64
|
+
changelog.status = 'completed';
|
|
65
|
+
changelog.generationTime = generationTime;
|
|
66
|
+
changelog.generatedAt = new Date();
|
|
67
|
+
|
|
68
|
+
await changelog.save();
|
|
69
|
+
|
|
70
|
+
// Update repository metadata
|
|
71
|
+
await this.updateRepositoryMetadata(repository, month, year);
|
|
72
|
+
|
|
73
|
+
logger.info(`Changelog generated successfully: ${changelog.title}`);
|
|
74
|
+
return changelog;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
logger.error('Changelog generation error:', error);
|
|
77
|
+
|
|
78
|
+
// Update changelog status to failed if it was created
|
|
79
|
+
if (changelog) {
|
|
80
|
+
changelog.status = 'failed';
|
|
81
|
+
changelog.error = error.message;
|
|
82
|
+
await changelog.save();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Generate changelog content
|
|
90
|
+
async generateContent(repository, month, year, options = {}) {
|
|
91
|
+
const { format = 'markdown', template = 'default', includeCommitsWithoutPR = true } = options;
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// Get commit history for the specified month
|
|
95
|
+
const commits = await this.getCommitsForMonth(repository, month, year);
|
|
96
|
+
|
|
97
|
+
// Parse commits and categorize them
|
|
98
|
+
const parsedCommits = await this.parseCommits(commits);
|
|
99
|
+
const categorizedCommits = this.categorizeCommits(parsedCommits);
|
|
100
|
+
|
|
101
|
+
// Get additional context (PRs, issues, etc.)
|
|
102
|
+
const context = await this.getCommitContext(repository, commits, { includeCommitsWithoutPR });
|
|
103
|
+
|
|
104
|
+
// Generate content based on format and template
|
|
105
|
+
switch (format) {
|
|
106
|
+
case 'markdown':
|
|
107
|
+
return this.generateMarkdownContent(repository, month, year, categorizedCommits, context, template);
|
|
108
|
+
case 'html':
|
|
109
|
+
return this.generateHTMLContent(repository, month, year, categorizedCommits, context, template);
|
|
110
|
+
case 'json':
|
|
111
|
+
return this.generateJSONContent(repository, month, year, categorizedCommits, context);
|
|
112
|
+
default:
|
|
113
|
+
return this.generateMarkdownContent(repository, month, year, categorizedCommits, context, template);
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
logger.error('Content generation error:', error);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Get commits for a specific month
|
|
122
|
+
async getCommitsForMonth(repository, month, year) {
|
|
123
|
+
try {
|
|
124
|
+
const startDate = new Date(year, parseInt(month) - 1, 1);
|
|
125
|
+
const endDate = new Date(year, parseInt(month), 0, 23, 59, 59, 999);
|
|
126
|
+
|
|
127
|
+
// Use GitHub API to get commits
|
|
128
|
+
const commits = await GitHubService.getCommits(repository.githubAccessToken,
|
|
129
|
+
repository.githubRepoOwner,
|
|
130
|
+
repository.githubRepoName, {
|
|
131
|
+
since: startDate.toISOString(),
|
|
132
|
+
until: endDate.toISOString(),
|
|
133
|
+
sha: repository.defaultBranch,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return commits;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
logger.error('Get commits error:', error);
|
|
139
|
+
throw new Error('Failed to fetch commits from GitHub');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Parse commits using conventional commits parser
|
|
144
|
+
async parseCommits(commits) {
|
|
145
|
+
const parsedCommits = [];
|
|
146
|
+
|
|
147
|
+
for (const commit of commits) {
|
|
148
|
+
try {
|
|
149
|
+
const parsed = parse(commit.commit.message);
|
|
150
|
+
parsedCommits.push({
|
|
151
|
+
sha: commit.sha,
|
|
152
|
+
message: commit.commit.message,
|
|
153
|
+
author: {
|
|
154
|
+
name: commit.commit.author.name,
|
|
155
|
+
email: commit.commit.author.email,
|
|
156
|
+
},
|
|
157
|
+
date: commit.commit.author.date,
|
|
158
|
+
parsed,
|
|
159
|
+
});
|
|
160
|
+
} catch (error) {
|
|
161
|
+
// If parsing fails, include the commit as-is
|
|
162
|
+
parsedCommits.push({
|
|
163
|
+
sha: commit.sha,
|
|
164
|
+
message: commit.commit.message,
|
|
165
|
+
author: {
|
|
166
|
+
name: commit.commit.author.name,
|
|
167
|
+
email: commit.commit.author.email,
|
|
168
|
+
},
|
|
169
|
+
date: commit.commit.author.date,
|
|
170
|
+
parsed: null,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return parsedCommits;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Categorize commits by type
|
|
179
|
+
categorizeCommits(parsedCommits) {
|
|
180
|
+
const categories = {
|
|
181
|
+
'feat': [],
|
|
182
|
+
'fix': [],
|
|
183
|
+
'docs': [],
|
|
184
|
+
'style': [],
|
|
185
|
+
'refactor': [],
|
|
186
|
+
'test': [],
|
|
187
|
+
'chore': [],
|
|
188
|
+
'perf': [],
|
|
189
|
+
'ci': [],
|
|
190
|
+
'deps': [],
|
|
191
|
+
'other': [],
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const authors = new Map();
|
|
195
|
+
|
|
196
|
+
for (const commit of parsedCommits) {
|
|
197
|
+
const type = commit.parsed?.type || 'other';
|
|
198
|
+
const category = this.mapCommitType(type);
|
|
199
|
+
|
|
200
|
+
categories[category].push({
|
|
201
|
+
sha: commit.sha,
|
|
202
|
+
message: commit.message,
|
|
203
|
+
author: commit.author,
|
|
204
|
+
date: commit.date,
|
|
205
|
+
parsed: commit.parsed,
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Track authors
|
|
209
|
+
const authorKey = `${commit.author.name} <${commit.author.email}>`;
|
|
210
|
+
if (!authors.has(authorKey)) {
|
|
211
|
+
authors.set(authorKey, {
|
|
212
|
+
name: commit.author.name,
|
|
213
|
+
email: commit.author.email,
|
|
214
|
+
commits: 0,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
authors.get(authorKey).commits++;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Convert authors map to array and sort by commit count
|
|
221
|
+
const sortedAuthors = Array.from(authors.values())
|
|
222
|
+
.sort((a, b) => b.commits - a.commits);
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
categories,
|
|
226
|
+
authors: sortedAuthors,
|
|
227
|
+
totalCommits: parsedCommits.length,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Map commit types to standard categories
|
|
232
|
+
mapCommitType(type) {
|
|
233
|
+
const typeMap = {
|
|
234
|
+
'feat': 'feat',
|
|
235
|
+
'feature': 'feat',
|
|
236
|
+
'enhancement': 'feat',
|
|
237
|
+
'fix': 'fix',
|
|
238
|
+
'bugfix': 'fix',
|
|
239
|
+
'bug': 'fix',
|
|
240
|
+
'docs': 'docs',
|
|
241
|
+
'documentation': 'docs',
|
|
242
|
+
'style': 'style',
|
|
243
|
+
'formatting': 'style',
|
|
244
|
+
'refactor': 'refactor',
|
|
245
|
+
'refactoring': 'refactor',
|
|
246
|
+
'test': 'test',
|
|
247
|
+
'testing': 'test',
|
|
248
|
+
'chore': 'chore',
|
|
249
|
+
'maintenance': 'chore',
|
|
250
|
+
'perf': 'perf',
|
|
251
|
+
'performance': 'perf',
|
|
252
|
+
'ci': 'ci',
|
|
253
|
+
'continuous-integration': 'ci',
|
|
254
|
+
'deps': 'deps',
|
|
255
|
+
'dependencies': 'deps',
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
return typeMap[type] || 'other';
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Get additional context for commits (PRs, issues, etc.)
|
|
262
|
+
async getCommitContext(repository, commits, options = {}) {
|
|
263
|
+
const { includeCommitsWithoutPR = true } = options;
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
const context = {
|
|
267
|
+
pullRequests: [],
|
|
268
|
+
issues: [],
|
|
269
|
+
releases: [],
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Get pull requests for the repository
|
|
273
|
+
const prs = await GitHubService.getPullRequests(repository.githubAccessToken,
|
|
274
|
+
repository.githubRepoOwner,
|
|
275
|
+
repository.githubRepoName, {
|
|
276
|
+
state: 'closed',
|
|
277
|
+
sort: 'updated',
|
|
278
|
+
direction: 'desc',
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Filter PRs that were merged in the specified time period
|
|
282
|
+
const commitShas = new Set(commits.map(c => c.sha));
|
|
283
|
+
|
|
284
|
+
for (const pr of prs) {
|
|
285
|
+
if (pr.merged_at) {
|
|
286
|
+
const mergedDate = new Date(pr.merged_at);
|
|
287
|
+
const prDate = new Date(pr.created_at);
|
|
288
|
+
|
|
289
|
+
// Check if PR was merged or created in our target period
|
|
290
|
+
if (this.isInSameMonth(mergedDate) || this.isInSameMonth(prDate)) {
|
|
291
|
+
context.pullRequests.push({
|
|
292
|
+
number: pr.number,
|
|
293
|
+
title: pr.title,
|
|
294
|
+
body: pr.body,
|
|
295
|
+
author: pr.user.login,
|
|
296
|
+
mergedAt: pr.merged_at,
|
|
297
|
+
createdAt: pr.created_at,
|
|
298
|
+
url: pr.html_url,
|
|
299
|
+
commits: pr.commits,
|
|
300
|
+
additions: pr.additions,
|
|
301
|
+
deletions: pr.deletions,
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Get issues
|
|
308
|
+
const issues = await GitHubService.getIssues(repository.githubAccessToken,
|
|
309
|
+
repository.githubRepoOwner,
|
|
310
|
+
repository.githubRepoName, {
|
|
311
|
+
state: 'closed',
|
|
312
|
+
sort: 'updated',
|
|
313
|
+
direction: 'desc',
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
for (const issue of issues) {
|
|
317
|
+
if (issue.pull_request) continue; // Skip PRs
|
|
318
|
+
|
|
319
|
+
const issueDate = new Date(issue.closed_at || issue.created_at);
|
|
320
|
+
if (this.isInSameMonth(issueDate)) {
|
|
321
|
+
context.issues.push({
|
|
322
|
+
number: issue.number,
|
|
323
|
+
title: issue.title,
|
|
324
|
+
body: issue.body,
|
|
325
|
+
author: issue.user.login,
|
|
326
|
+
closedAt: issue.closed_at,
|
|
327
|
+
createdAt: issue.created_at,
|
|
328
|
+
url: issue.html_url,
|
|
329
|
+
labels: issue.labels.map(l => l.name),
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Get releases
|
|
335
|
+
const releases = await GitHubService.getReleases(repository.githubAccessToken,
|
|
336
|
+
repository.githubRepoOwner,
|
|
337
|
+
repository.githubRepoName);
|
|
338
|
+
|
|
339
|
+
for (const release of releases) {
|
|
340
|
+
const releaseDate = new Date(release.published_at);
|
|
341
|
+
if (this.isInSameMonth(releaseDate)) {
|
|
342
|
+
context.releases.push({
|
|
343
|
+
tagName: release.tag_name,
|
|
344
|
+
name: release.name,
|
|
345
|
+
body: release.body,
|
|
346
|
+
author: release.author.login,
|
|
347
|
+
publishedAt: release.published_at,
|
|
348
|
+
url: release.html_url,
|
|
349
|
+
assets: release.assets,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return context;
|
|
355
|
+
} catch (error) {
|
|
356
|
+
logger.error('Get commit context error:', error);
|
|
357
|
+
return {
|
|
358
|
+
pullRequests: [],
|
|
359
|
+
issues: [],
|
|
360
|
+
releases: [],
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Check if a date is in the same month as the target month/year
|
|
366
|
+
isInSameMonth(date, targetMonth, targetYear) {
|
|
367
|
+
return date.getMonth() + 1 === parseInt(targetMonth) && date.getFullYear() === parseInt(targetYear);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Generate markdown content
|
|
371
|
+
generateMarkdownContent(repository, month, year, categorizedCommits, context, template) {
|
|
372
|
+
const monthName = this.getMonthName(month);
|
|
373
|
+
const dateRange = this.getDateRange(month, year);
|
|
374
|
+
|
|
375
|
+
let content = `# ${repository.name} - ${monthName} ${year}\n\n`;
|
|
376
|
+
content += `**Date Range:** ${dateRange}\n`;
|
|
377
|
+
content += `**Repository:** [${repository.githubRepoFullName}](${repository.githubRepoUrl})\n\n`;
|
|
378
|
+
|
|
379
|
+
// Summary
|
|
380
|
+
content += `## Summary\n\n`;
|
|
381
|
+
content += `- **Total Commits:** ${categorizedCommits.totalCommits}\n`;
|
|
382
|
+
content += `- **Contributors:** ${categorizedCommits.authors.length}\n`;
|
|
383
|
+
content += `- **Pull Requests:** ${context.pullRequests.length}\n`;
|
|
384
|
+
content += `- **Issues Closed:** ${context.issues.length}\n\n`;
|
|
385
|
+
|
|
386
|
+
// Releases
|
|
387
|
+
if (context.releases.length > 0) {
|
|
388
|
+
content += `## Releases\n\n`;
|
|
389
|
+
for (const release of context.releases) {
|
|
390
|
+
content += `- **[${release.tagName}](${release.url})** - ${release.name}\n`;
|
|
391
|
+
if (release.body) {
|
|
392
|
+
content += ` ${release.body.split('\n')[0]}\n`;
|
|
393
|
+
}
|
|
394
|
+
content += `\n`;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Changes by Category
|
|
399
|
+
content += `## Changes by Category\n\n`;
|
|
400
|
+
|
|
401
|
+
const categoryOrder = [
|
|
402
|
+
'feat', 'fix', 'perf', 'refactor', 'test',
|
|
403
|
+
'docs', 'style', 'ci', 'deps', 'chore', 'other'
|
|
404
|
+
];
|
|
405
|
+
|
|
406
|
+
for (const category of categoryOrder) {
|
|
407
|
+
const commits = categorizedCommits.categories[category];
|
|
408
|
+
if (commits.length > 0) {
|
|
409
|
+
content += `### ${this.getCategoryDisplayName(category)}\n\n`;
|
|
410
|
+
|
|
411
|
+
for (const commit of commits) {
|
|
412
|
+
const message = this.formatCommitMessage(commit.message);
|
|
413
|
+
content += `- ${message} (${commit.author.name})\n`;
|
|
414
|
+
}
|
|
415
|
+
content += `\n`;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Pull Requests
|
|
420
|
+
if (context.pullRequests.length > 0) {
|
|
421
|
+
content += `## Pull Requests\n\n`;
|
|
422
|
+
for (const pr of context.pullRequests) {
|
|
423
|
+
content += `- **[#${pr.number}](${pr.url})** ${pr.title} by ${pr.author}\n`;
|
|
424
|
+
}
|
|
425
|
+
content += `\n`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Issues
|
|
429
|
+
if (context.issues.length > 0) {
|
|
430
|
+
content += `## Issues Closed\n\n`;
|
|
431
|
+
for (const issue of context.issues) {
|
|
432
|
+
content += `- **[#${issue.number}](${issue.url})** ${issue.title} by ${issue.author}\n`;
|
|
433
|
+
}
|
|
434
|
+
content += `\n`;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Contributors
|
|
438
|
+
if (categorizedCommits.authors.length > 0) {
|
|
439
|
+
content += `## Contributors\n\n`;
|
|
440
|
+
for (const author of categorizedCommits.authors) {
|
|
441
|
+
content += `- **${author.name}** (${author.email}) - ${author.commits} commits\n`;
|
|
442
|
+
}
|
|
443
|
+
content += `\n`;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
content += `---\n`;
|
|
447
|
+
content += `Generated by [AutoChangelog](https://autochangelog.com) on ${new Date().toISOString()}\n`;
|
|
448
|
+
|
|
449
|
+
return content;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Generate HTML content
|
|
453
|
+
generateHTMLContent(repository, month, year, categorizedCommits, context, template) {
|
|
454
|
+
const monthName = this.getMonthName(month);
|
|
455
|
+
const dateRange = this.getDateRange(month, year);
|
|
456
|
+
|
|
457
|
+
let content = `<!DOCTYPE html>
|
|
458
|
+
<html>
|
|
459
|
+
<head>
|
|
460
|
+
<meta charset="utf-8">
|
|
461
|
+
<title>${repository.name} - ${monthName} ${year}</title>
|
|
462
|
+
<style>
|
|
463
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; max-width: 800px; margin: 0 auto; padding: 20px; }
|
|
464
|
+
h1, h2, h3 { color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; }
|
|
465
|
+
h1 { font-size: 2.5em; }
|
|
466
|
+
h2 { font-size: 1.8em; margin-top: 30px; }
|
|
467
|
+
h3 { font-size: 1.4em; margin-top: 25px; }
|
|
468
|
+
.summary { background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0; }
|
|
469
|
+
.category { margin: 20px 0; }
|
|
470
|
+
.category h3 { display: inline-block; background: #3498db; color: white; padding: 5px 10px; border-radius: 3px; }
|
|
471
|
+
ul { padding-left: 20px; }
|
|
472
|
+
li { margin: 5px 0; }
|
|
473
|
+
.commit { font-family: monospace; font-size: 0.9em; color: #666; }
|
|
474
|
+
.author { font-weight: bold; color: #2c3e50; }
|
|
475
|
+
a { color: #3498db; text-decoration: none; }
|
|
476
|
+
a:hover { text-decoration: underline; }
|
|
477
|
+
.footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #eee; font-size: 0.9em; color: #666; }
|
|
478
|
+
</style>
|
|
479
|
+
</head>
|
|
480
|
+
<body>
|
|
481
|
+
<h1>${repository.name} - ${monthName} ${year}</h1>
|
|
482
|
+
<p><strong>Date Range:</strong> ${dateRange}</p>
|
|
483
|
+
<p><strong>Repository:</strong> <a href="${repository.githubRepoUrl}" target="_blank">${repository.githubRepoFullName}</a></p>
|
|
484
|
+
|
|
485
|
+
<div class="summary">
|
|
486
|
+
<h2>Summary</h2>
|
|
487
|
+
<ul>
|
|
488
|
+
<li><strong>Total Commits:</strong> ${categorizedCommits.totalCommits}</li>
|
|
489
|
+
<li><strong>Contributors:</strong> ${categorizedCommits.authors.length}</li>
|
|
490
|
+
<li><strong>Pull Requests:</strong> ${context.pullRequests.length}</li>
|
|
491
|
+
<li><strong>Issues Closed:</strong> ${context.issues.length}</li>
|
|
492
|
+
</ul>
|
|
493
|
+
</div>`;
|
|
494
|
+
|
|
495
|
+
// Releases
|
|
496
|
+
if (context.releases.length > 0) {
|
|
497
|
+
content += `<div class="category">
|
|
498
|
+
<h2>Releases</h2>
|
|
499
|
+
<ul>`;
|
|
500
|
+
for (const release of context.releases) {
|
|
501
|
+
content += `<li><strong><a href="${release.url}" target="_blank">${release.tagName}</a></strong> - ${release.name}`;
|
|
502
|
+
if (release.body) {
|
|
503
|
+
content += `<br><span class="commit">${release.body.split('\n')[0]}</span>`;
|
|
504
|
+
}
|
|
505
|
+
content += `</li>`;
|
|
506
|
+
}
|
|
507
|
+
content += `</ul></div>`;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Changes by Category
|
|
511
|
+
content += `<div class="category">
|
|
512
|
+
<h2>Changes by Category</h2>`;
|
|
513
|
+
|
|
514
|
+
const categoryOrder = [
|
|
515
|
+
'feat', 'fix', 'perf', 'refactor', 'test',
|
|
516
|
+
'docs', 'style', 'ci', 'deps', 'chore', 'other'
|
|
517
|
+
];
|
|
518
|
+
|
|
519
|
+
for (const category of categoryOrder) {
|
|
520
|
+
const commits = categorizedCommits.categories[category];
|
|
521
|
+
if (commits.length > 0) {
|
|
522
|
+
content += `<div class="category">
|
|
523
|
+
<h3>${this.getCategoryDisplayName(category)}</h3>
|
|
524
|
+
<ul>`;
|
|
525
|
+
for (const commit of commits) {
|
|
526
|
+
const message = this.formatCommitMessage(commit.message);
|
|
527
|
+
content += `<li><span class="commit">${message}</span> <span class="author">(${commit.author.name})</span></li>`;
|
|
528
|
+
}
|
|
529
|
+
content += `</ul></div>`;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
content += `</div>`;
|
|
534
|
+
|
|
535
|
+
// Pull Requests
|
|
536
|
+
if (context.pullRequests.length > 0) {
|
|
537
|
+
content += `<div class="category">
|
|
538
|
+
<h2>Pull Requests</h2>
|
|
539
|
+
<ul>`;
|
|
540
|
+
for (const pr of context.pullRequests) {
|
|
541
|
+
content += `<li><strong><a href="${pr.url}" target="_blank">#${pr.number}</a></strong> ${pr.title} by ${pr.author}</li>`;
|
|
542
|
+
}
|
|
543
|
+
content += `</ul></div>`;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Issues
|
|
547
|
+
if (context.issues.length > 0) {
|
|
548
|
+
content += `<div class="category">
|
|
549
|
+
<h2>Issues Closed</h2>
|
|
550
|
+
<ul>`;
|
|
551
|
+
for (const issue of context.issues) {
|
|
552
|
+
content += `<li><strong><a href="${issue.url}" target="_blank">#${issue.number}</a></strong> ${issue.title} by ${issue.author}</li>`;
|
|
553
|
+
}
|
|
554
|
+
content += `</ul></div>`;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Contributors
|
|
558
|
+
if (categorizedCommits.authors.length > 0) {
|
|
559
|
+
content += `<div class="category">
|
|
560
|
+
<h2>Contributors</h2>
|
|
561
|
+
<ul>`;
|
|
562
|
+
for (const author of categorizedCommits.authors) {
|
|
563
|
+
content += `<li><span class="author">${author.name}</span> (${author.email}) - ${author.commits} commits</li>`;
|
|
564
|
+
}
|
|
565
|
+
content += `</ul></div>`;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
content += `<div class="footer">
|
|
569
|
+
<p>Generated by <a href="https://autochangelog.com" target="_blank">AutoChangelog</a> on ${new Date().toISOString()}</p>
|
|
570
|
+
</div>
|
|
571
|
+
</body>
|
|
572
|
+
</html>`;
|
|
573
|
+
|
|
574
|
+
return content;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Generate JSON content
|
|
578
|
+
generateJSONContent(repository, month, year, categorizedCommits, context) {
|
|
579
|
+
const data = {
|
|
580
|
+
repository: {
|
|
581
|
+
name: repository.name,
|
|
582
|
+
fullName: repository.githubRepoFullName,
|
|
583
|
+
url: repository.githubRepoUrl,
|
|
584
|
+
},
|
|
585
|
+
period: {
|
|
586
|
+
month: parseInt(month),
|
|
587
|
+
year: parseInt(year),
|
|
588
|
+
monthName: this.getMonthName(month),
|
|
589
|
+
},
|
|
590
|
+
summary: {
|
|
591
|
+
totalCommits: categorizedCommits.totalCommits,
|
|
592
|
+
contributors: categorizedCommits.authors.length,
|
|
593
|
+
pullRequests: context.pullRequests.length,
|
|
594
|
+
issuesClosed: context.issues.length,
|
|
595
|
+
},
|
|
596
|
+
categories: categorizedCommits.categories,
|
|
597
|
+
authors: categorizedCommits.authors,
|
|
598
|
+
pullRequests: context.pullRequests,
|
|
599
|
+
issues: context.issues,
|
|
600
|
+
releases: context.releases,
|
|
601
|
+
generatedAt: new Date().toISOString(),
|
|
602
|
+
generator: 'AutoChangelog',
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
return JSON.stringify(data, null, 2);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Helper methods
|
|
609
|
+
getMonthName(month) {
|
|
610
|
+
const months = [
|
|
611
|
+
'January', 'February', 'March', 'April', 'May', 'June',
|
|
612
|
+
'July', 'August', 'September', 'October', 'November', 'December'
|
|
613
|
+
];
|
|
614
|
+
return months[parseInt(month) - 1] || 'Unknown';
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
getDateRange(month, year) {
|
|
618
|
+
const startDate = new Date(year, parseInt(month) - 1, 1);
|
|
619
|
+
const endDate = new Date(year, parseInt(month), 0);
|
|
620
|
+
return `${startDate.toLocaleDateString()} - ${endDate.toLocaleDateString()}`;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
getCategoryDisplayName(category) {
|
|
624
|
+
const displayNames = {
|
|
625
|
+
'feat': 'Features',
|
|
626
|
+
'fix': 'Bug Fixes',
|
|
627
|
+
'perf': 'Performance',
|
|
628
|
+
'refactor': 'Refactoring',
|
|
629
|
+
'test': 'Tests',
|
|
630
|
+
'docs': 'Documentation',
|
|
631
|
+
'style': 'Code Style',
|
|
632
|
+
'ci': 'CI/CD',
|
|
633
|
+
'deps': 'Dependencies',
|
|
634
|
+
'chore': 'Maintenance',
|
|
635
|
+
'other': 'Other Changes',
|
|
636
|
+
};
|
|
637
|
+
return displayNames[category] || category;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
formatCommitMessage(message) {
|
|
641
|
+
// Remove multiple newlines and trim
|
|
642
|
+
return message.split('\n')[0].trim();
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// Update repository metadata after changelog generation
|
|
646
|
+
async updateRepositoryMetadata(repository, month, year) {
|
|
647
|
+
try {
|
|
648
|
+
// Update last changelog generated date
|
|
649
|
+
await Repository.findByIdAndUpdate(repository._id, {
|
|
650
|
+
$set: {
|
|
651
|
+
'metadata.lastChangelogGenerated': new Date(),
|
|
652
|
+
},
|
|
653
|
+
});
|
|
654
|
+
} catch (error) {
|
|
655
|
+
logger.error('Update repository metadata error:', error);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// Get available months for a repository
|
|
660
|
+
async getAvailableMonths(repositoryId) {
|
|
661
|
+
try {
|
|
662
|
+
const repository = await Repository.findById(repositoryId);
|
|
663
|
+
if (!repository) {
|
|
664
|
+
throw new Error('Repository not found');
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Get commit history
|
|
668
|
+
const commits = await this.getCommitHistory(repository);
|
|
669
|
+
|
|
670
|
+
// Get already generated changelogs
|
|
671
|
+
const generatedChangelogs = await Changelog.find({
|
|
672
|
+
repositoryId,
|
|
673
|
+
status: 'completed',
|
|
674
|
+
}).select('month year').lean();
|
|
675
|
+
|
|
676
|
+
const generatedMonths = new Set(
|
|
677
|
+
generatedChangelogs.map(c => `${c.year}-${c.month}`)
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
// Extract unique months from commits
|
|
681
|
+
const availableMonths = new Set();
|
|
682
|
+
|
|
683
|
+
commits.forEach(commit => {
|
|
684
|
+
const date = new Date(commit.commit.author.date);
|
|
685
|
+
const year = date.getFullYear();
|
|
686
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
687
|
+
const key = `${year}-${month}`;
|
|
688
|
+
availableMonths.add(key);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
// Return months that have commits but no generated changelog
|
|
692
|
+
const result = Array.from(availableMonths)
|
|
693
|
+
.filter(month => !generatedMonths.has(month))
|
|
694
|
+
.sort()
|
|
695
|
+
.reverse();
|
|
696
|
+
|
|
697
|
+
return result;
|
|
698
|
+
} catch (error) {
|
|
699
|
+
logger.error('Get available months error:', error);
|
|
700
|
+
throw error;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Get commit history for a repository
|
|
705
|
+
async getCommitHistory(repository) {
|
|
706
|
+
try {
|
|
707
|
+
const commits = await GitHubService.getCommits(repository.githubAccessToken,
|
|
708
|
+
repository.githubRepoOwner,
|
|
709
|
+
repository.githubRepoName, {
|
|
710
|
+
sha: repository.defaultBranch,
|
|
711
|
+
per_page: 1000,
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
return commits;
|
|
715
|
+
} catch (error) {
|
|
716
|
+
logger.error('Get commit history error:', error);
|
|
717
|
+
throw new Error('Failed to fetch commit history');
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
module.exports = new ChangelogService();
|