repo-wrapped 0.0.2
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/README.md +94 -0
- package/dist/cli.js +24 -0
- package/dist/commands/generate.js +95 -0
- package/dist/commands/index.js +24 -0
- package/dist/constants/chronotypes.js +23 -0
- package/dist/constants/colors.js +18 -0
- package/dist/constants/index.js +18 -0
- package/dist/formatters/index.js +17 -0
- package/dist/formatters/timeFormatter.js +29 -0
- package/dist/generators/html/scripts/export.js +125 -0
- package/dist/generators/html/scripts/knowledge.js +120 -0
- package/dist/generators/html/scripts/modal.js +68 -0
- package/dist/generators/html/scripts/navigation.js +156 -0
- package/dist/generators/html/scripts/tabs.js +18 -0
- package/dist/generators/html/scripts/tooltip.js +21 -0
- package/dist/generators/html/styles/achievements.css +387 -0
- package/dist/generators/html/styles/base.css +818 -0
- package/dist/generators/html/styles/components.css +1391 -0
- package/dist/generators/html/styles/knowledge.css +221 -0
- package/dist/generators/html/templates/achievementsSection.js +156 -0
- package/dist/generators/html/templates/commitQualitySection.js +89 -0
- package/dist/generators/html/templates/contributionGraph.js +73 -0
- package/dist/generators/html/templates/impactSection.js +117 -0
- package/dist/generators/html/templates/knowledgeSection.js +226 -0
- package/dist/generators/html/templates/streakSection.js +42 -0
- package/dist/generators/html/templates/timePatternsSection.js +110 -0
- package/dist/generators/html/utils/colorUtils.js +21 -0
- package/dist/generators/html/utils/commitMapBuilder.js +24 -0
- package/dist/generators/html/utils/dateRangeCalculator.js +57 -0
- package/dist/generators/html/utils/developerStatsCalculator.js +29 -0
- package/dist/generators/html/utils/scriptLoader.js +16 -0
- package/dist/generators/html/utils/styleLoader.js +18 -0
- package/dist/generators/html/utils/weekGrouper.js +28 -0
- package/dist/index.js +77 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/achievementDefinitions.js +433 -0
- package/dist/utils/achievementEngine.js +170 -0
- package/dist/utils/commitQualityAnalyzer.js +368 -0
- package/dist/utils/fileHotspotAnalyzer.js +270 -0
- package/dist/utils/gitParser.js +125 -0
- package/dist/utils/htmlGenerator.js +449 -0
- package/dist/utils/impactAnalyzer.js +248 -0
- package/dist/utils/knowledgeDistributionAnalyzer.js +374 -0
- package/dist/utils/matrixGenerator.js +350 -0
- package/dist/utils/slideGenerator.js +313 -0
- package/dist/utils/streakCalculator.js +135 -0
- package/dist/utils/timePatternAnalyzer.js +305 -0
- package/dist/utils/wrappedDisplay.js +115 -0
- package/dist/utils/wrappedGenerator.js +377 -0
- package/dist/utils/wrappedHtmlGenerator.js +552 -0
- package/package.json +55 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateCommitMatrix = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const date_fns_1 = require("date-fns");
|
|
9
|
+
const constants_1 = require("../constants");
|
|
10
|
+
const achievementEngine_1 = require("./achievementEngine");
|
|
11
|
+
const commitQualityAnalyzer_1 = require("./commitQualityAnalyzer");
|
|
12
|
+
const fileHotspotAnalyzer_1 = require("./fileHotspotAnalyzer");
|
|
13
|
+
const streakCalculator_1 = require("./streakCalculator");
|
|
14
|
+
const timePatternAnalyzer_1 = require("./timePatternAnalyzer");
|
|
15
|
+
function generateCommitMatrix(commits, year, monthsToShow, repoPath, repoName, repoUrl) {
|
|
16
|
+
// Get today or end of year if it's a past year
|
|
17
|
+
const now = new Date();
|
|
18
|
+
const isCurrentYear = year === now.getFullYear();
|
|
19
|
+
// Calculate the end date (today if current year, or end of December for past years)
|
|
20
|
+
const endDate = isCurrentYear
|
|
21
|
+
? now
|
|
22
|
+
: new Date(year, 11, 31);
|
|
23
|
+
// Calculate start date (N months back from end date)
|
|
24
|
+
const startDate = (0, date_fns_1.startOfMonth)((0, date_fns_1.subMonths)(endDate, monthsToShow - 1));
|
|
25
|
+
// Get the full week range (start on Sunday, end on Saturday)
|
|
26
|
+
const weekStartDate = (0, date_fns_1.startOfWeek)(startDate, { weekStartsOn: 0 });
|
|
27
|
+
const weekEndDate = (0, date_fns_1.endOfWeek)(endDate, { weekStartsOn: 0 });
|
|
28
|
+
// Create a map of date string -> commit count
|
|
29
|
+
const commitMap = new Map();
|
|
30
|
+
commits.forEach(commit => {
|
|
31
|
+
const commitDate = new Date(commit.date);
|
|
32
|
+
if (commitDate >= startDate && commitDate <= endDate) {
|
|
33
|
+
const dateKey = (0, date_fns_1.format)(commitDate, 'yyyy-MM-dd');
|
|
34
|
+
commitMap.set(dateKey, (commitMap.get(dateKey) || 0) + 1);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const totalCommits = Array.from(commitMap.values()).reduce((sum, count) => sum + count, 0);
|
|
38
|
+
// Calculate streaks
|
|
39
|
+
const streakData = (0, streakCalculator_1.calculateStreaks)(commits, startDate, endDate);
|
|
40
|
+
// Analyze time patterns
|
|
41
|
+
const timePattern = (0, timePatternAnalyzer_1.analyzeTimePatterns)(commits, startDate, endDate);
|
|
42
|
+
// Analyze commit quality
|
|
43
|
+
const commitQuality = (0, commitQualityAnalyzer_1.analyzeCommitQuality)(commits);
|
|
44
|
+
// Analyze file hotspots
|
|
45
|
+
const fileHotspots = (0, fileHotspotAnalyzer_1.analyzeFileHotspots)(repoPath, startDate, endDate);
|
|
46
|
+
// Prepare analysis data for achievements
|
|
47
|
+
const analysisData = {
|
|
48
|
+
commits,
|
|
49
|
+
totalCommits,
|
|
50
|
+
streakData,
|
|
51
|
+
timePattern,
|
|
52
|
+
commitQuality,
|
|
53
|
+
fileHotspots,
|
|
54
|
+
dateRange: { start: startDate, end: endDate }
|
|
55
|
+
};
|
|
56
|
+
// Check achievements
|
|
57
|
+
const achievementProgress = (0, achievementEngine_1.checkAchievements)(analysisData);
|
|
58
|
+
return formatGitHubStyle(commitMap, weekStartDate, weekEndDate, startDate, endDate, totalCommits, streakData, timePattern, commitQuality, fileHotspots, achievementProgress, repoName, repoUrl);
|
|
59
|
+
}
|
|
60
|
+
exports.generateCommitMatrix = generateCommitMatrix;
|
|
61
|
+
function formatGitHubStyle(commitMap, weekStartDate, weekEndDate, dataStartDate, dataEndDate, totalCommits, streakData, timePattern, commitQuality, fileHotspots, achievementProgress, repoName, repoUrl) {
|
|
62
|
+
const days = (0, date_fns_1.eachDayOfInterval)({ start: weekStartDate, end: weekEndDate });
|
|
63
|
+
const dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
64
|
+
// Group days into weeks
|
|
65
|
+
const weeks = [];
|
|
66
|
+
let currentWeek = [];
|
|
67
|
+
days.forEach(day => {
|
|
68
|
+
currentWeek.push(day);
|
|
69
|
+
if (currentWeek.length === 7) {
|
|
70
|
+
weeks.push(currentWeek);
|
|
71
|
+
currentWeek = [];
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
// Build the output with header
|
|
75
|
+
let output = '\n';
|
|
76
|
+
// Add repository info if available
|
|
77
|
+
if (repoName) {
|
|
78
|
+
output += chalk_1.default.bold.cyan(` ${repoName}\n`);
|
|
79
|
+
if (repoUrl) {
|
|
80
|
+
output += chalk_1.default.dim(` ${repoUrl}\n`);
|
|
81
|
+
}
|
|
82
|
+
output += '\n';
|
|
83
|
+
}
|
|
84
|
+
// Add stats
|
|
85
|
+
output += chalk_1.default.dim(' ') + chalk_1.default.white(`${totalCommits} contributions in the last ${(0, date_fns_1.format)(dataStartDate, 'MMM yyyy')} - ${(0, date_fns_1.format)(dataEndDate, 'MMM yyyy')}\n\n`);
|
|
86
|
+
// Build month headers
|
|
87
|
+
let monthHeader = ' ';
|
|
88
|
+
let currentMonth = '';
|
|
89
|
+
weeks.forEach((week, weekIndex) => {
|
|
90
|
+
const firstDayOfWeek = week[0];
|
|
91
|
+
const monthName = (0, date_fns_1.format)(firstDayOfWeek, 'MMM');
|
|
92
|
+
if (monthName !== currentMonth) {
|
|
93
|
+
monthHeader += chalk_1.default.bold(monthName.padEnd(4));
|
|
94
|
+
currentMonth = monthName;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
monthHeader += ' ';
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
output += monthHeader + '\n';
|
|
101
|
+
// Day rows with better formatting
|
|
102
|
+
for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
|
|
103
|
+
// Only show Mon, Wed, Fri labels
|
|
104
|
+
const label = [1, 3, 5].includes(dayOfWeek)
|
|
105
|
+
? chalk_1.default.dim(dayLabels[dayOfWeek].padEnd(5))
|
|
106
|
+
: ' ';
|
|
107
|
+
let row = label + ' ';
|
|
108
|
+
weeks.forEach(week => {
|
|
109
|
+
const day = week[dayOfWeek];
|
|
110
|
+
if (day) {
|
|
111
|
+
const dateKey = (0, date_fns_1.format)(day, 'yyyy-MM-dd');
|
|
112
|
+
const count = commitMap.get(dateKey) || 0;
|
|
113
|
+
const isInRange = day >= dataStartDate && day <= dataEndDate;
|
|
114
|
+
if (isInRange) {
|
|
115
|
+
row += formatCell(count) + ' ';
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
row += chalk_1.default.dim('■') + ' ';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
row += ' ';
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
output += row + '\n';
|
|
126
|
+
}
|
|
127
|
+
// Add legend with better spacing
|
|
128
|
+
output += '\n';
|
|
129
|
+
output += chalk_1.default.dim(' Learn how we count contributions\n');
|
|
130
|
+
output += chalk_1.default.dim(' Less ') +
|
|
131
|
+
formatCell(0) + ' ' +
|
|
132
|
+
formatCell(1) + ' ' +
|
|
133
|
+
formatCell(4) + ' ' +
|
|
134
|
+
formatCell(7) + ' ' +
|
|
135
|
+
formatCell(10) + ' ' +
|
|
136
|
+
chalk_1.default.dim('More\n');
|
|
137
|
+
// Streak & Momentum Section
|
|
138
|
+
output += '\n';
|
|
139
|
+
output += chalk_1.default.bold.cyan('📊 Streak & Momentum\n');
|
|
140
|
+
output += chalk_1.default.dim('─'.repeat(50)) + '\n';
|
|
141
|
+
const currentStreakEmoji = streakData.currentStreak.isActive ? '🔥' : '💤';
|
|
142
|
+
const currentStreakText = streakData.currentStreak.days > 0
|
|
143
|
+
? `${streakData.currentStreak.days} day${streakData.currentStreak.days > 1 ? 's' : ''}`
|
|
144
|
+
: 'No active streak';
|
|
145
|
+
output += `${currentStreakEmoji} Current Streak: ${chalk_1.default.bold.green(currentStreakText)}`;
|
|
146
|
+
if (streakData.currentStreak.isActive && streakData.currentStreak.days > 0) {
|
|
147
|
+
output += chalk_1.default.dim(` - ${(0, streakCalculator_1.getStreakMotivation)(streakData.currentStreak.days, true)}`);
|
|
148
|
+
}
|
|
149
|
+
output += '\n';
|
|
150
|
+
const longestStreakText = streakData.longestStreak.days > 0
|
|
151
|
+
? `${streakData.longestStreak.days} day${streakData.longestStreak.days > 1 ? 's' : ''} (${(0, date_fns_1.format)(streakData.longestStreak.startDate, 'MMM d')} - ${(0, date_fns_1.format)(streakData.longestStreak.endDate, 'MMM d, yyyy')})`
|
|
152
|
+
: 'No streaks yet';
|
|
153
|
+
output += `🏆 Longest Streak: ${chalk_1.default.bold.yellow(longestStreakText)}\n`;
|
|
154
|
+
const activePercentage = streakData.activeDayPercentage.toFixed(1);
|
|
155
|
+
output += `📈 Active Days: ${chalk_1.default.bold.cyan(streakData.totalActiveDays)}/${streakData.totalDays} (${activePercentage}%)\n`;
|
|
156
|
+
// Time & Productivity Patterns Section
|
|
157
|
+
output += '\n';
|
|
158
|
+
output += chalk_1.default.bold.cyan('⏰ Productivity Patterns\n');
|
|
159
|
+
output += chalk_1.default.dim('─'.repeat(50)) + '\n';
|
|
160
|
+
// Peak hour
|
|
161
|
+
const peakHourLabel = timePattern.peakHour.hour === 0 ? '12:00 AM' :
|
|
162
|
+
timePattern.peakHour.hour < 12 ? `${timePattern.peakHour.hour}:00 AM` :
|
|
163
|
+
timePattern.peakHour.hour === 12 ? '12:00 PM' :
|
|
164
|
+
`${timePattern.peakHour.hour - 12}:00 PM`;
|
|
165
|
+
output += `⏱️ Peak Hour: ${chalk_1.default.bold.green(peakHourLabel)} (${timePattern.peakHour.commitCount} commits)\n`;
|
|
166
|
+
// Chronotype
|
|
167
|
+
const chronotypeLabel = (0, timePatternAnalyzer_1.getChronotypeLabel)(timePattern.chronotype);
|
|
168
|
+
const chronotypeDesc = (0, timePatternAnalyzer_1.getChronotypeDescription)(timePattern.chronotype);
|
|
169
|
+
output += `${chronotypeLabel} ${chalk_1.default.dim(`(${timePattern.chronotypeConfidence.toFixed(0)}% confidence)`)}\n`;
|
|
170
|
+
if (chronotypeDesc) {
|
|
171
|
+
output += chalk_1.default.dim(` ${chronotypeDesc}\n`);
|
|
172
|
+
}
|
|
173
|
+
// Work-life balance
|
|
174
|
+
const stars = '⭐'.repeat(timePattern.workLifeBalance.score);
|
|
175
|
+
output += `⚖️ Work-Life Balance: ${stars} ${chalk_1.default.dim(`(${timePattern.workLifeBalance.score}/5)`)}\n`;
|
|
176
|
+
output += chalk_1.default.dim(` Weekday: ${timePattern.workLifeBalance.weekdayCommits} | Weekend: ${timePattern.workLifeBalance.weekendCommits}\n`);
|
|
177
|
+
// Consistency
|
|
178
|
+
output += `📊 Consistency: ${chalk_1.default.bold.yellow(timePattern.consistency.score.toFixed(1))}/10 ${chalk_1.default.dim(`(${timePattern.consistency.regularity})`)}\n`;
|
|
179
|
+
// Burnout warnings
|
|
180
|
+
if (timePattern.burnoutRisk.level !== 'low') {
|
|
181
|
+
output += `\n${chalk_1.default.yellow('⚠️ Burnout Risk: ' + timePattern.burnoutRisk.level.toUpperCase())}\n`;
|
|
182
|
+
timePattern.burnoutRisk.indicators.forEach((indicator) => {
|
|
183
|
+
output += chalk_1.default.dim(` • ${indicator}\n`);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
// Commit Quality Section
|
|
187
|
+
output += '\n';
|
|
188
|
+
output += chalk_1.default.bold.cyan('📝 Commit Quality\n');
|
|
189
|
+
output += chalk_1.default.dim('─'.repeat(50)) + '\n';
|
|
190
|
+
const rating = (0, commitQualityAnalyzer_1.getQualityRating)(commitQuality.overallScore);
|
|
191
|
+
const level = (0, commitQualityAnalyzer_1.getQualityLevel)(commitQuality.overallScore);
|
|
192
|
+
output += `📊 Overall Score: ${chalk_1.default.bold.green(commitQuality.overallScore.toFixed(1))}/10 ${rating} ${chalk_1.default.dim(`(${level})`)}\n`;
|
|
193
|
+
output += `✅ Convention: ${chalk_1.default.bold.cyan(commitQuality.conventionalCommits.adherence.toFixed(1))}%`;
|
|
194
|
+
if (commitQuality.conventionalCommits.adherence < 70) {
|
|
195
|
+
output += chalk_1.default.yellow(' ⚠️');
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
output += chalk_1.default.green(' ✨');
|
|
199
|
+
}
|
|
200
|
+
output += '\n';
|
|
201
|
+
// Subject quality
|
|
202
|
+
output += `📏 Subject Quality: ${chalk_1.default.bold.yellow(commitQuality.subjectQuality.score.toFixed(1))}/10 `;
|
|
203
|
+
output += chalk_1.default.dim(`(avg ${commitQuality.subjectQuality.avgLength} chars)\n`);
|
|
204
|
+
// Body quality
|
|
205
|
+
output += `📄 Body Quality: ${chalk_1.default.bold.yellow(commitQuality.bodyQuality.score.toFixed(1))}/10 `;
|
|
206
|
+
output += chalk_1.default.dim(`(${commitQuality.bodyQuality.withBody}/${commitQuality.totalCommits} with body)\n`);
|
|
207
|
+
// Top commit types
|
|
208
|
+
if (Object.keys(commitQuality.conventionalCommits.types).length > 0) {
|
|
209
|
+
output += `\n${chalk_1.default.dim('Top Commit Types:')}\n`;
|
|
210
|
+
const sortedTypes = Object.entries(commitQuality.conventionalCommits.types)
|
|
211
|
+
.sort(([, a], [, b]) => b - a)
|
|
212
|
+
.slice(0, 3);
|
|
213
|
+
sortedTypes.forEach(([type, count]) => {
|
|
214
|
+
output += chalk_1.default.dim(` ${type}: ${count}\n`);
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
// Improvements
|
|
218
|
+
if (commitQuality.improvements && commitQuality.improvements.length > 0) {
|
|
219
|
+
output += `\n${chalk_1.default.yellow('💡 Tips for Improvement:')}\n`;
|
|
220
|
+
commitQuality.improvements.slice(0, 2).forEach((tip) => {
|
|
221
|
+
output += chalk_1.default.dim(` • ${tip}\n`);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
// Code hygiene metrics (if any)
|
|
225
|
+
const totalHygiene = commitQuality.codeHygiene.quickFixes +
|
|
226
|
+
commitQuality.codeHygiene.workInProgress +
|
|
227
|
+
commitQuality.codeHygiene.oops +
|
|
228
|
+
commitQuality.codeHygiene.vague;
|
|
229
|
+
if (totalHygiene > 0) {
|
|
230
|
+
output += `\n${chalk_1.default.dim('🧹 Code Hygiene Indicators:\n')}`;
|
|
231
|
+
if (commitQuality.codeHygiene.quickFixes > 0) {
|
|
232
|
+
output += chalk_1.default.dim(` Quick fixes (typos/spelling): ${commitQuality.codeHygiene.quickFixes}\n`);
|
|
233
|
+
}
|
|
234
|
+
if (commitQuality.codeHygiene.workInProgress > 0) {
|
|
235
|
+
output += chalk_1.default.dim(` Work in progress: ${commitQuality.codeHygiene.workInProgress}\n`);
|
|
236
|
+
}
|
|
237
|
+
if (commitQuality.codeHygiene.oops > 0) {
|
|
238
|
+
output += chalk_1.default.dim(` Correction commits: ${commitQuality.codeHygiene.oops}\n`);
|
|
239
|
+
}
|
|
240
|
+
if (commitQuality.codeHygiene.vague > 0) {
|
|
241
|
+
output += chalk_1.default.dim(` Vague messages: ${commitQuality.codeHygiene.vague}\n`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// File & Code Hotspots Section
|
|
245
|
+
output += '\n' + chalk_1.default.bold.hex('#58a6ff')('🔥 TOP FILE HOTSPOTS') + '\n';
|
|
246
|
+
if (!fileHotspots || fileHotspots.topFiles.length === 0) {
|
|
247
|
+
output += chalk_1.default.dim(' No file changes detected in this period.\n');
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
const topFilesToShow = fileHotspots.topFiles.slice(0, 7);
|
|
251
|
+
const maxChanges = Math.max(...topFilesToShow.map((f) => f.changeCount));
|
|
252
|
+
topFilesToShow.forEach((file) => {
|
|
253
|
+
const barLength = Math.max(1, Math.round((file.changeCount / maxChanges) * 30));
|
|
254
|
+
const bar = chalk_1.default.hex('#f85149')('█'.repeat(barLength));
|
|
255
|
+
const fileName = file.path.length > 40 ? '...' + file.path.slice(-37) : file.path;
|
|
256
|
+
output += chalk_1.default.dim(` ${fileName}\n`);
|
|
257
|
+
output += ` ${bar} ${chalk_1.default.bold(file.changeCount)} changes, ${file.authors.length} author${file.authors.length !== 1 ? 's' : ''}\n`;
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
// Technical Debt Warnings
|
|
261
|
+
if (fileHotspots && fileHotspots.technicalDebt.length > 0) {
|
|
262
|
+
output += '\n' + chalk_1.default.bold.hex('#f85149')('⚠️ TECHNICAL DEBT WARNINGS') + '\n';
|
|
263
|
+
const topDebt = fileHotspots.technicalDebt.slice(0, 5);
|
|
264
|
+
topDebt.forEach((debt) => {
|
|
265
|
+
const color = (0, fileHotspotAnalyzer_1.getRiskLevelColor)(debt.riskLevel);
|
|
266
|
+
const colorFn = color === 'red' ? chalk_1.default.red : color === 'yellow' ? chalk_1.default.yellow : chalk_1.default.gray;
|
|
267
|
+
const fileName = debt.file.length > 45 ? '...' + debt.file.slice(-42) : debt.file;
|
|
268
|
+
output += colorFn(` ${fileName}\n`);
|
|
269
|
+
output += chalk_1.default.dim(` Risk: ${debt.riskLevel.toUpperCase()} (complexity: ${debt.score}/10)\n`);
|
|
270
|
+
if (debt.recommendations.length > 0) {
|
|
271
|
+
output += chalk_1.default.dim(` → ${debt.recommendations[0]}\n`);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
// Ownership Risks
|
|
276
|
+
if (fileHotspots && fileHotspots.ownershipRisks) {
|
|
277
|
+
const highRiskOwnership = fileHotspots.ownershipRisks.filter((risk) => risk.busFactorRisk >= 7);
|
|
278
|
+
if (highRiskOwnership.length > 0) {
|
|
279
|
+
output += '\n' + chalk_1.default.bold.hex('#d29922')('🚌 BUS FACTOR WARNINGS') + '\n';
|
|
280
|
+
const topRisks = highRiskOwnership.slice(0, 5);
|
|
281
|
+
topRisks.forEach((risk) => {
|
|
282
|
+
const icon = (0, fileHotspotAnalyzer_1.getOwnershipIcon)(risk.ownershipType);
|
|
283
|
+
const fileName = risk.file.length > 45 ? '...' + risk.file.slice(-42) : risk.file;
|
|
284
|
+
output += chalk_1.default.yellow(` ${icon} ${fileName}\n`);
|
|
285
|
+
const topOwner = risk.owners[0];
|
|
286
|
+
output += chalk_1.default.dim(` ${topOwner.author} owns ${topOwner.percentage.toFixed(0)}% (${topOwner.commits} commits)\n`);
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
// Achievements Section
|
|
291
|
+
if (achievementProgress) {
|
|
292
|
+
output += '\n';
|
|
293
|
+
output += chalk_1.default.bold.hex('#ffd700')('🏆 ACHIEVEMENTS') + '\n';
|
|
294
|
+
output += chalk_1.default.dim('─'.repeat(50)) + '\n';
|
|
295
|
+
// Show newly earned badges
|
|
296
|
+
if (achievementProgress.recentlyEarned && achievementProgress.recentlyEarned.length > 0) {
|
|
297
|
+
output += '\n' + chalk_1.default.bold.green('✨ NEW BADGES EARNED! 🎉') + '\n';
|
|
298
|
+
achievementProgress.recentlyEarned.forEach((achievement) => {
|
|
299
|
+
output += chalk_1.default.green(` ${achievement.emoji} ${achievement.name} - ${achievement.description}\n`);
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
// Show progress
|
|
303
|
+
output += `\n${chalk_1.default.cyan('Progress:')} ${chalk_1.default.bold.white(achievementProgress.unlockedCount)}/${achievementProgress.totalCount} badges unlocked `;
|
|
304
|
+
output += chalk_1.default.dim(`(${achievementProgress.completionPercentage.toFixed(1)}%)\n`);
|
|
305
|
+
// Progress bar
|
|
306
|
+
const barLength = 30;
|
|
307
|
+
const filledLength = Math.round((achievementProgress.completionPercentage / 100) * barLength);
|
|
308
|
+
const bar = '█'.repeat(filledLength) + '░'.repeat(barLength - filledLength);
|
|
309
|
+
output += ` ${chalk_1.default.hex('#ffd700')(bar)}\n`;
|
|
310
|
+
// Show next milestone
|
|
311
|
+
if (achievementProgress.nextMilestones && achievementProgress.nextMilestones.length > 0) {
|
|
312
|
+
const next = achievementProgress.nextMilestones[0];
|
|
313
|
+
output += `\n${chalk_1.default.yellow('🎯 Next Milestone:')}\n`;
|
|
314
|
+
output += ` ${next.emoji} ${chalk_1.default.bold(next.name)} ${chalk_1.default.dim(`(${next.progress.toFixed(0)}%)`)}\n`;
|
|
315
|
+
output += chalk_1.default.dim(` ${next.description}\n`);
|
|
316
|
+
// Progress bar for next milestone
|
|
317
|
+
const nextBarLength = 25;
|
|
318
|
+
const nextFilledLength = Math.round((next.progress / 100) * nextBarLength);
|
|
319
|
+
const nextBar = '█'.repeat(nextFilledLength) + '░'.repeat(nextBarLength - nextFilledLength);
|
|
320
|
+
output += ` ${chalk_1.default.hex('#58a6ff')(nextBar)} ${chalk_1.default.dim((100 - next.progress).toFixed(0) + '% to go!')}\n`;
|
|
321
|
+
}
|
|
322
|
+
// Show recent unlocks (excluding today's new badges)
|
|
323
|
+
const recentUnlocks = achievementProgress.achievements
|
|
324
|
+
.filter((a) => a.isUnlocked &&
|
|
325
|
+
!achievementProgress.recentlyEarned.some((r) => r.id === a.id) &&
|
|
326
|
+
a.earnedDate)
|
|
327
|
+
.sort((a, b) => b.earnedDate.getTime() - a.earnedDate.getTime())
|
|
328
|
+
.slice(0, 3);
|
|
329
|
+
if (recentUnlocks.length > 0) {
|
|
330
|
+
output += `\n${chalk_1.default.dim('Recent Unlocks:')}\n`;
|
|
331
|
+
recentUnlocks.forEach((achievement) => {
|
|
332
|
+
const daysAgo = Math.floor((Date.now() - achievement.earnedDate.getTime()) / (1000 * 60 * 60 * 24));
|
|
333
|
+
const timeStr = daysAgo === 0 ? 'today' : daysAgo === 1 ? '1 day ago' : `${daysAgo} days ago`;
|
|
334
|
+
output += chalk_1.default.dim(` ${achievement.emoji} ${achievement.name} (${timeStr})\n`);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return output;
|
|
339
|
+
}
|
|
340
|
+
function formatCell(count) {
|
|
341
|
+
if (count === 0)
|
|
342
|
+
return chalk_1.default.hex('#ebedf0')('■');
|
|
343
|
+
if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_1)
|
|
344
|
+
return chalk_1.default.hex('#9be9a8')('■');
|
|
345
|
+
if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_2)
|
|
346
|
+
return chalk_1.default.hex('#40c463')('■');
|
|
347
|
+
if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_3)
|
|
348
|
+
return chalk_1.default.hex('#30a14e')('■');
|
|
349
|
+
return chalk_1.default.hex('#216e39')('■');
|
|
350
|
+
}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.generateSlides = void 0;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const constants_1 = require("../constants");
|
|
9
|
+
const formatters_1 = require("../formatters");
|
|
10
|
+
const BORDER_WIDTH = 50;
|
|
11
|
+
function generateSlides(summary) {
|
|
12
|
+
const slides = [
|
|
13
|
+
createWelcomeSlide(summary),
|
|
14
|
+
createBigNumberSlide(summary),
|
|
15
|
+
createPersonalitySlide(summary),
|
|
16
|
+
createStreakSlide(summary),
|
|
17
|
+
createTopMonthSlide(summary),
|
|
18
|
+
createImpactSlide(summary),
|
|
19
|
+
createDistributionSlide(summary),
|
|
20
|
+
createCollaborationSlide(summary),
|
|
21
|
+
createQualitySlide(summary),
|
|
22
|
+
createFunFactsSlide(summary),
|
|
23
|
+
createAchievementsSlide(summary)
|
|
24
|
+
];
|
|
25
|
+
// Add comparison slide if available
|
|
26
|
+
if (summary.comparison) {
|
|
27
|
+
slides.push(createComparisonSlide(summary));
|
|
28
|
+
}
|
|
29
|
+
// Add finale
|
|
30
|
+
slides.push(createFinaleSlide(summary));
|
|
31
|
+
return slides;
|
|
32
|
+
}
|
|
33
|
+
exports.generateSlides = generateSlides;
|
|
34
|
+
function createBorder(char = '━') {
|
|
35
|
+
return char.repeat(BORDER_WIDTH);
|
|
36
|
+
}
|
|
37
|
+
function centerText(text, width = BORDER_WIDTH) {
|
|
38
|
+
const strippedText = stripAnsi(text);
|
|
39
|
+
const padding = Math.max(0, Math.floor((width - strippedText.length) / 2));
|
|
40
|
+
return ' '.repeat(padding) + text;
|
|
41
|
+
}
|
|
42
|
+
function stripAnsi(str) {
|
|
43
|
+
// eslint-disable-next-line no-control-regex
|
|
44
|
+
return str.replace(/\x1B\[[0-9;]*m/g, '');
|
|
45
|
+
}
|
|
46
|
+
function createWelcomeSlide(summary) {
|
|
47
|
+
const content = `
|
|
48
|
+
${chalk_1.default.magenta(createBorder())}
|
|
49
|
+
${centerText('')}
|
|
50
|
+
${centerText(chalk_1.default.bold.cyan(`YOUR ${summary.year} IN CODE`))}
|
|
51
|
+
${centerText('')}
|
|
52
|
+
${centerText(chalk_1.default.gray(`Hey ${summary.developer},`))}
|
|
53
|
+
${centerText(chalk_1.default.gray('Let\'s look back at your year!'))}
|
|
54
|
+
${centerText('')}
|
|
55
|
+
${chalk_1.default.magenta(createBorder())}
|
|
56
|
+
`;
|
|
57
|
+
return { title: 'Welcome', content, type: 'welcome' };
|
|
58
|
+
}
|
|
59
|
+
function createBigNumberSlide(summary) {
|
|
60
|
+
const perDay = summary.overview.avgCommitsPerDay.toFixed(1);
|
|
61
|
+
const content = `
|
|
62
|
+
${chalk_1.default.magenta(createBorder())}
|
|
63
|
+
${centerText(chalk_1.default.gray('You made'))}
|
|
64
|
+
${centerText('')}
|
|
65
|
+
${centerText(chalk_1.default.bold.yellow.bgBlack(` ${summary.overview.totalCommits.toLocaleString()} `))}
|
|
66
|
+
${centerText(chalk_1.default.bold.yellow('COMMITS'))}
|
|
67
|
+
${centerText('')}
|
|
68
|
+
${centerText(chalk_1.default.gray(`That's ${perDay} commits per active day!`))}
|
|
69
|
+
${centerText('')}
|
|
70
|
+
${chalk_1.default.magenta(createBorder())}
|
|
71
|
+
`;
|
|
72
|
+
return { title: 'Big Number', content, type: 'stat' };
|
|
73
|
+
}
|
|
74
|
+
function createPersonalitySlide(summary) {
|
|
75
|
+
const emoji = constants_1.WRAPPED_CHRONOTYPE_EMOJIS[summary.timing.chronotype];
|
|
76
|
+
const label = constants_1.WRAPPED_CHRONOTYPE_LABELS[summary.timing.chronotype];
|
|
77
|
+
const timeStr = (0, formatters_1.formatHourShort)(summary.timing.peakHour);
|
|
78
|
+
const content = `
|
|
79
|
+
${chalk_1.default.magenta(createBorder())}
|
|
80
|
+
${centerText(chalk_1.default.gray('Your coding vibe:'))}
|
|
81
|
+
${centerText('')}
|
|
82
|
+
${centerText(`${emoji} ${chalk_1.default.bold.cyan(label)}`)}
|
|
83
|
+
${centerText('')}
|
|
84
|
+
${centerText(chalk_1.default.gray(`Peak hour: ${timeStr}`))}
|
|
85
|
+
${centerText(chalk_1.default.gray(`Favorite day: ${summary.timing.peakDay}`))}
|
|
86
|
+
${centerText('')}
|
|
87
|
+
${chalk_1.default.magenta(createBorder())}
|
|
88
|
+
`;
|
|
89
|
+
return { title: 'Personality', content, type: 'insight' };
|
|
90
|
+
}
|
|
91
|
+
function createStreakSlide(summary) {
|
|
92
|
+
const { longest } = summary.streaks;
|
|
93
|
+
const startStr = longest.start.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
94
|
+
const endStr = longest.end.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
95
|
+
const content = `
|
|
96
|
+
${chalk_1.default.magenta(createBorder())}
|
|
97
|
+
${centerText(chalk_1.default.gray('Your longest streak:'))}
|
|
98
|
+
${centerText('')}
|
|
99
|
+
${centerText(chalk_1.default.bold.yellow('🔥'))}
|
|
100
|
+
${centerText(chalk_1.default.bold.yellow.bgBlack(` ${longest.days} DAYS `))}
|
|
101
|
+
${centerText('')}
|
|
102
|
+
${centerText(chalk_1.default.gray(`${startStr} - ${endStr}`))}
|
|
103
|
+
${centerText('')}
|
|
104
|
+
${centerText(chalk_1.default.gray('Consistency is key! 💪'))}
|
|
105
|
+
${chalk_1.default.magenta(createBorder())}
|
|
106
|
+
`;
|
|
107
|
+
return { title: 'Streak', content, type: 'celebration' };
|
|
108
|
+
}
|
|
109
|
+
function createTopMonthSlide(summary) {
|
|
110
|
+
const content = `
|
|
111
|
+
${chalk_1.default.magenta(createBorder())}
|
|
112
|
+
${centerText(chalk_1.default.gray('Your most productive month:'))}
|
|
113
|
+
${centerText('')}
|
|
114
|
+
${centerText(chalk_1.default.bold.green.bgBlack(` ${summary.timing.mostProductiveMonth.toUpperCase()} `))}
|
|
115
|
+
${centerText('')}
|
|
116
|
+
${centerText(chalk_1.default.gray('You were on fire! 🔥'))}
|
|
117
|
+
${centerText('')}
|
|
118
|
+
${chalk_1.default.magenta(createBorder())}
|
|
119
|
+
`;
|
|
120
|
+
return { title: 'Top Month', content, type: 'stat' };
|
|
121
|
+
}
|
|
122
|
+
function createImpactSlide(summary) {
|
|
123
|
+
const { linesAdded, linesRemoved } = summary.impact;
|
|
124
|
+
const content = `
|
|
125
|
+
${chalk_1.default.magenta(createBorder())}
|
|
126
|
+
${centerText(chalk_1.default.gray('Code impact:'))}
|
|
127
|
+
${centerText('')}
|
|
128
|
+
${centerText(chalk_1.default.green(`++ ${linesAdded.toLocaleString()} lines added`))}
|
|
129
|
+
${centerText(chalk_1.default.red(`-- ${linesRemoved.toLocaleString()} lines removed`))}
|
|
130
|
+
${centerText('')}
|
|
131
|
+
${centerText(chalk_1.default.gray(`${summary.impact.filesChanged} files changed`))}
|
|
132
|
+
${centerText('')}
|
|
133
|
+
${centerText(chalk_1.default.gray('Making waves in the codebase! 🌊'))}
|
|
134
|
+
${chalk_1.default.magenta(createBorder())}
|
|
135
|
+
`;
|
|
136
|
+
return { title: 'Impact', content, type: 'stat' };
|
|
137
|
+
}
|
|
138
|
+
function createDistributionSlide(summary) {
|
|
139
|
+
const { workDistribution } = summary;
|
|
140
|
+
const total = Object.values(workDistribution).reduce((sum, val) => sum + val, 0);
|
|
141
|
+
const getPercentage = (value) => total > 0 ? ((value / total) * 100).toFixed(0) : '0';
|
|
142
|
+
const content = `
|
|
143
|
+
${chalk_1.default.magenta(createBorder())}
|
|
144
|
+
${centerText(chalk_1.default.gray('What you worked on:'))}
|
|
145
|
+
${centerText('')}
|
|
146
|
+
${centerText(chalk_1.default.gray(`🎨 Features: ${getPercentage(workDistribution.features)}%`))}
|
|
147
|
+
${centerText(chalk_1.default.gray(`🐛 Bug Fixes: ${getPercentage(workDistribution.fixes)}%`))}
|
|
148
|
+
${centerText(chalk_1.default.gray(`📝 Docs: ${getPercentage(workDistribution.docs)}%`))}
|
|
149
|
+
${centerText(chalk_1.default.gray(`🔧 Refactors: ${getPercentage(workDistribution.refactors)}%`))}
|
|
150
|
+
${centerText('')}
|
|
151
|
+
${centerText(chalk_1.default.cyan(workDistribution.features > workDistribution.fixes ? 'Feature-focused year! ✨' : 'Bug-squashing hero! 🦸'))}
|
|
152
|
+
${chalk_1.default.magenta(createBorder())}
|
|
153
|
+
`;
|
|
154
|
+
return { title: 'Distribution', content, type: 'insight' };
|
|
155
|
+
}
|
|
156
|
+
function createCollaborationSlide(summary) {
|
|
157
|
+
const { collaboration } = summary;
|
|
158
|
+
let collabText = '';
|
|
159
|
+
if (collaboration.uniqueContributors === 0) {
|
|
160
|
+
collabText = `
|
|
161
|
+
${centerText(chalk_1.default.cyan('Solo developer!'))}
|
|
162
|
+
${centerText(chalk_1.default.gray('Building independently 🚀'))}`;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const topCollab = collaboration.topCollaborator;
|
|
166
|
+
collabText = `
|
|
167
|
+
${centerText(chalk_1.default.gray('You worked alongside:'))}
|
|
168
|
+
${centerText('')}
|
|
169
|
+
${centerText(chalk_1.default.cyan(`👥 ${collaboration.uniqueContributors} developer${collaboration.uniqueContributors === 1 ? '' : 's'}`))}
|
|
170
|
+
${centerText('')}`;
|
|
171
|
+
if (topCollab) {
|
|
172
|
+
collabText += `
|
|
173
|
+
${centerText(chalk_1.default.gray('Most commits with:'))}
|
|
174
|
+
${centerText(chalk_1.default.yellow(topCollab.name))} ${chalk_1.default.gray(`(${topCollab.sharedDays} shared days)`)}
|
|
175
|
+
${centerText('')}`;
|
|
176
|
+
}
|
|
177
|
+
collabText += `
|
|
178
|
+
${centerText(chalk_1.default.gray('Great teamwork! 🤝'))}`;
|
|
179
|
+
}
|
|
180
|
+
const content = `
|
|
181
|
+
${chalk_1.default.magenta(createBorder())}
|
|
182
|
+
${collabText}
|
|
183
|
+
${chalk_1.default.magenta(createBorder())}
|
|
184
|
+
`;
|
|
185
|
+
return { title: 'Collaboration', content, type: 'insight' };
|
|
186
|
+
}
|
|
187
|
+
function createQualitySlide(summary) {
|
|
188
|
+
const { quality } = summary;
|
|
189
|
+
const stars = '⭐'.repeat(Math.min(5, Math.ceil(quality.overallScore / 2)));
|
|
190
|
+
const content = `
|
|
191
|
+
${chalk_1.default.magenta(createBorder())}
|
|
192
|
+
${centerText(chalk_1.default.gray('Commit quality score:'))}
|
|
193
|
+
${centerText('')}
|
|
194
|
+
${centerText(chalk_1.default.bold.yellow.bgBlack(` ${quality.overallScore.toFixed(1)} / 10 `))}
|
|
195
|
+
${centerText(chalk_1.default.yellow(stars))}
|
|
196
|
+
${centerText('')}
|
|
197
|
+
${centerText(chalk_1.default.gray(`${quality.conventionalPercentage.toFixed(0)}% conventional commits`))}
|
|
198
|
+
${centerText('')}
|
|
199
|
+
${centerText(chalk_1.default.cyan('Well documented code! 📚'))}
|
|
200
|
+
${chalk_1.default.magenta(createBorder())}
|
|
201
|
+
`;
|
|
202
|
+
return { title: 'Quality', content, type: 'stat' };
|
|
203
|
+
}
|
|
204
|
+
function createFunFactsSlide(summary) {
|
|
205
|
+
const { funFacts } = summary;
|
|
206
|
+
let facts = '';
|
|
207
|
+
if (funFacts.holidayCommits.length > 0) {
|
|
208
|
+
facts += `
|
|
209
|
+
${centerText(chalk_1.default.yellow(`🎄 Committed on ${funFacts.holidayCommits.join(', ')}`))}`;
|
|
210
|
+
}
|
|
211
|
+
if (funFacts.midnightCommits > 0) {
|
|
212
|
+
facts += `
|
|
213
|
+
${centerText(chalk_1.default.blue(`🌙 ${funFacts.midnightCommits} commit${funFacts.midnightCommits === 1 ? '' : 's'} past midnight`))}`;
|
|
214
|
+
}
|
|
215
|
+
if (funFacts.weekendCommits > 0) {
|
|
216
|
+
facts += `
|
|
217
|
+
${centerText(chalk_1.default.green(`🎉 ${funFacts.weekendCommits} weekend commit${funFacts.weekendCommits === 1 ? '' : 's'}`))}`;
|
|
218
|
+
}
|
|
219
|
+
if (funFacts.busiestDay) {
|
|
220
|
+
const dateStr = funFacts.busiestDay.date.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
221
|
+
facts += `
|
|
222
|
+
${centerText(chalk_1.default.magenta(`📅 Busiest day: ${dateStr} (${funFacts.busiestDay.commits} commits)`))}`;
|
|
223
|
+
}
|
|
224
|
+
if (!facts) {
|
|
225
|
+
facts = `
|
|
226
|
+
${centerText(chalk_1.default.gray('Steady and consistent work! 💪'))}`;
|
|
227
|
+
}
|
|
228
|
+
const content = `
|
|
229
|
+
${chalk_1.default.magenta(createBorder())}
|
|
230
|
+
${centerText(chalk_1.default.gray('Fun facts about your year:'))}
|
|
231
|
+
${centerText('')}
|
|
232
|
+
${facts}
|
|
233
|
+
${centerText('')}
|
|
234
|
+
${centerText(chalk_1.default.yellow('Dedication level: 💯'))}
|
|
235
|
+
${chalk_1.default.magenta(createBorder())}
|
|
236
|
+
`;
|
|
237
|
+
return { title: 'Fun Facts', content, type: 'celebration' };
|
|
238
|
+
}
|
|
239
|
+
function createAchievementsSlide(summary) {
|
|
240
|
+
const { achievements } = summary;
|
|
241
|
+
let badgeText = '';
|
|
242
|
+
if (achievements.earned.length > 0) {
|
|
243
|
+
const topBadges = achievements.earned.slice(0, 5);
|
|
244
|
+
topBadges.forEach(badge => {
|
|
245
|
+
badgeText += `
|
|
246
|
+
${centerText(chalk_1.default.yellow(`✨ ${badge}`))}`;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
badgeText = `
|
|
251
|
+
${centerText(chalk_1.default.gray('Start your journey to earn badges!'))}`;
|
|
252
|
+
}
|
|
253
|
+
const content = `
|
|
254
|
+
${chalk_1.default.magenta(createBorder())}
|
|
255
|
+
${centerText(chalk_1.default.gray(`Badges earned in ${summary.year}:`))}
|
|
256
|
+
${centerText('')}
|
|
257
|
+
${badgeText}
|
|
258
|
+
${centerText('')}
|
|
259
|
+
${centerText(chalk_1.default.cyan(`${achievements.total} total achievement${achievements.total === 1 ? '' : 's'}! 🏆`))}
|
|
260
|
+
${chalk_1.default.magenta(createBorder())}
|
|
261
|
+
`;
|
|
262
|
+
return { title: 'Achievements', content, type: 'celebration' };
|
|
263
|
+
}
|
|
264
|
+
function createComparisonSlide(summary) {
|
|
265
|
+
if (!summary.comparison) {
|
|
266
|
+
return { title: 'Comparison', content: '', type: 'comparison' };
|
|
267
|
+
}
|
|
268
|
+
const { comparison } = summary;
|
|
269
|
+
const { changes } = comparison;
|
|
270
|
+
const getTrendArrow = (trend) => {
|
|
271
|
+
if (trend === 'up')
|
|
272
|
+
return chalk_1.default.green('↑');
|
|
273
|
+
if (trend === 'down')
|
|
274
|
+
return chalk_1.default.red('↓');
|
|
275
|
+
return chalk_1.default.gray('→');
|
|
276
|
+
};
|
|
277
|
+
const formatChange = (value, percentage, trend) => {
|
|
278
|
+
const arrow = getTrendArrow(trend);
|
|
279
|
+
if (trend === 'stable')
|
|
280
|
+
return chalk_1.default.gray('stable');
|
|
281
|
+
const sign = value > 0 ? '+' : '';
|
|
282
|
+
return `${arrow} ${sign}${Math.abs(percentage).toFixed(0)}%`;
|
|
283
|
+
};
|
|
284
|
+
const content = `
|
|
285
|
+
${chalk_1.default.magenta(createBorder())}
|
|
286
|
+
${centerText(chalk_1.default.gray(`${summary.year} vs ${summary.year - 1}:`))}
|
|
287
|
+
${centerText('')}
|
|
288
|
+
${centerText(chalk_1.default.gray(`Commits: ${comparison.currentYear.commits} ${formatChange(changes.commits.value, changes.commits.percentage, changes.commits.trend)}`))}
|
|
289
|
+
${centerText(chalk_1.default.gray(`Streak: ${comparison.currentYear.streak} days ${formatChange(changes.streak.value, changes.streak.percentage, changes.streak.trend)}`))}
|
|
290
|
+
${centerText(chalk_1.default.gray(`Quality: ${comparison.currentYear.quality.toFixed(1)} ${formatChange(changes.quality.value, changes.quality.percentage, changes.quality.trend)}`))}
|
|
291
|
+
${centerText('')}
|
|
292
|
+
${centerText(chalk_1.default.cyan(comparison.insights[0] || 'Keep up the great work!'))}
|
|
293
|
+
${chalk_1.default.magenta(createBorder())}
|
|
294
|
+
`;
|
|
295
|
+
return { title: 'Comparison', content, type: 'comparison' };
|
|
296
|
+
}
|
|
297
|
+
function createFinaleSlide(summary) {
|
|
298
|
+
const content = `
|
|
299
|
+
${chalk_1.default.magenta(createBorder())}
|
|
300
|
+
${centerText(chalk_1.default.bold.cyan(`That's your ${summary.year}!`))}
|
|
301
|
+
${centerText('')}
|
|
302
|
+
${centerText(chalk_1.default.yellow(`${summary.overview.totalCommits.toLocaleString()} commits`))}
|
|
303
|
+
${centerText(chalk_1.default.yellow(`${summary.streaks.longest.days}-day streak`))}
|
|
304
|
+
${centerText(chalk_1.default.yellow(`${summary.collaboration.uniqueContributors} collaborator${summary.collaboration.uniqueContributors === 1 ? '' : 's'}`))}
|
|
305
|
+
${centerText(chalk_1.default.yellow(`${summary.achievements.total} achievement${summary.achievements.total === 1 ? '' : 's'}`))}
|
|
306
|
+
${centerText('')}
|
|
307
|
+
${centerText(chalk_1.default.bold.green(`Here's to ${summary.year + 1}! 🚀`))}
|
|
308
|
+
${centerText('')}
|
|
309
|
+
${centerText(chalk_1.default.gray('#YearInCode #DevLife'))}
|
|
310
|
+
${chalk_1.default.magenta(createBorder())}
|
|
311
|
+
`;
|
|
312
|
+
return { title: 'Finale', content, type: 'finale' };
|
|
313
|
+
}
|