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.
Files changed (51) hide show
  1. package/README.md +94 -0
  2. package/dist/cli.js +24 -0
  3. package/dist/commands/generate.js +95 -0
  4. package/dist/commands/index.js +24 -0
  5. package/dist/constants/chronotypes.js +23 -0
  6. package/dist/constants/colors.js +18 -0
  7. package/dist/constants/index.js +18 -0
  8. package/dist/formatters/index.js +17 -0
  9. package/dist/formatters/timeFormatter.js +29 -0
  10. package/dist/generators/html/scripts/export.js +125 -0
  11. package/dist/generators/html/scripts/knowledge.js +120 -0
  12. package/dist/generators/html/scripts/modal.js +68 -0
  13. package/dist/generators/html/scripts/navigation.js +156 -0
  14. package/dist/generators/html/scripts/tabs.js +18 -0
  15. package/dist/generators/html/scripts/tooltip.js +21 -0
  16. package/dist/generators/html/styles/achievements.css +387 -0
  17. package/dist/generators/html/styles/base.css +818 -0
  18. package/dist/generators/html/styles/components.css +1391 -0
  19. package/dist/generators/html/styles/knowledge.css +221 -0
  20. package/dist/generators/html/templates/achievementsSection.js +156 -0
  21. package/dist/generators/html/templates/commitQualitySection.js +89 -0
  22. package/dist/generators/html/templates/contributionGraph.js +73 -0
  23. package/dist/generators/html/templates/impactSection.js +117 -0
  24. package/dist/generators/html/templates/knowledgeSection.js +226 -0
  25. package/dist/generators/html/templates/streakSection.js +42 -0
  26. package/dist/generators/html/templates/timePatternsSection.js +110 -0
  27. package/dist/generators/html/utils/colorUtils.js +21 -0
  28. package/dist/generators/html/utils/commitMapBuilder.js +24 -0
  29. package/dist/generators/html/utils/dateRangeCalculator.js +57 -0
  30. package/dist/generators/html/utils/developerStatsCalculator.js +29 -0
  31. package/dist/generators/html/utils/scriptLoader.js +16 -0
  32. package/dist/generators/html/utils/styleLoader.js +18 -0
  33. package/dist/generators/html/utils/weekGrouper.js +28 -0
  34. package/dist/index.js +77 -0
  35. package/dist/types/index.js +2 -0
  36. package/dist/utils/achievementDefinitions.js +433 -0
  37. package/dist/utils/achievementEngine.js +170 -0
  38. package/dist/utils/commitQualityAnalyzer.js +368 -0
  39. package/dist/utils/fileHotspotAnalyzer.js +270 -0
  40. package/dist/utils/gitParser.js +125 -0
  41. package/dist/utils/htmlGenerator.js +449 -0
  42. package/dist/utils/impactAnalyzer.js +248 -0
  43. package/dist/utils/knowledgeDistributionAnalyzer.js +374 -0
  44. package/dist/utils/matrixGenerator.js +350 -0
  45. package/dist/utils/slideGenerator.js +313 -0
  46. package/dist/utils/streakCalculator.js +135 -0
  47. package/dist/utils/timePatternAnalyzer.js +305 -0
  48. package/dist/utils/wrappedDisplay.js +115 -0
  49. package/dist/utils/wrappedGenerator.js +377 -0
  50. package/dist/utils/wrappedHtmlGenerator.js +552 -0
  51. 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
+ }