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,377 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateYearWrapped = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const achievementEngine_js_1 = require("./achievementEngine.js");
6
+ const commitQualityAnalyzer_js_1 = require("./commitQualityAnalyzer.js");
7
+ const fileHotspotAnalyzer_js_1 = require("./fileHotspotAnalyzer.js");
8
+ const gitParser_js_1 = require("./gitParser.js");
9
+ const streakCalculator_js_1 = require("./streakCalculator.js");
10
+ const timePatternAnalyzer_js_1 = require("./timePatternAnalyzer.js");
11
+ function generateYearWrapped(year, repoPath = '.', includeComparison = false) {
12
+ // Define year boundaries
13
+ const startDate = new Date(`${year}-01-01`);
14
+ const endDate = new Date(`${year}-12-31`);
15
+ // Fetch all commits for the year
16
+ const commits = (0, gitParser_js_1.parseGitCommits)(repoPath).filter(commit => {
17
+ const commitDate = new Date(commit.date);
18
+ return commitDate >= startDate && commitDate <= endDate;
19
+ });
20
+ if (commits.length === 0) {
21
+ throw new Error(`No commits found for year ${year}`);
22
+ }
23
+ // Calculate all metrics using existing utilities
24
+ const streakData = (0, streakCalculator_js_1.calculateStreaks)(commits, startDate, endDate);
25
+ const timePattern = (0, timePatternAnalyzer_js_1.analyzeTimePatterns)(commits, startDate, endDate);
26
+ const commitQuality = (0, commitQualityAnalyzer_js_1.analyzeCommitQuality)(commits);
27
+ const fileHotspots = (0, fileHotspotAnalyzer_js_1.analyzeFileHotspots)(repoPath, startDate, endDate);
28
+ // Create AnalysisData for achievement checking
29
+ const analysisData = {
30
+ commits,
31
+ totalCommits: commits.length,
32
+ streakData,
33
+ timePattern,
34
+ commitQuality,
35
+ fileHotspots,
36
+ dateRange: {
37
+ start: startDate,
38
+ end: endDate
39
+ }
40
+ };
41
+ const achievementProgress = (0, achievementEngine_js_1.checkAchievements)(analysisData);
42
+ // Calculate overview
43
+ const overview = {
44
+ totalCommits: commits.length,
45
+ activeDays: streakData.totalActiveDays,
46
+ totalDays: streakData.totalDays,
47
+ activePercentage: streakData.activeDayPercentage,
48
+ avgCommitsPerDay: commits.length / streakData.totalActiveDays
49
+ };
50
+ // Calculate impact (lines added/removed)
51
+ const impact = calculateImpact(commits, repoPath, year);
52
+ // Extract timing insights
53
+ const timing = {
54
+ chronotype: mapChronotype(timePattern.chronotype),
55
+ peakHour: timePattern.peakHour.hour,
56
+ peakDay: timePattern.peakDay.dayOfWeek,
57
+ mostProductiveMonth: findMostProductiveMonth(commits),
58
+ mostProductiveWeek: findMostProductiveWeek(commits)
59
+ };
60
+ // Extract streaks
61
+ const streaks = {
62
+ longest: {
63
+ days: streakData.longestStreak.days,
64
+ start: streakData.longestStreak.startDate,
65
+ end: streakData.longestStreak.endDate
66
+ },
67
+ current: {
68
+ days: streakData.currentStreak.days,
69
+ isActive: streakData.currentStreak.isActive
70
+ },
71
+ totalStreaks: streakData.streaks.length
72
+ };
73
+ // Extract quality metrics
74
+ const quality = {
75
+ overallScore: commitQuality.overallScore,
76
+ conventionalPercentage: commitQuality.conventionalCommits.adherence,
77
+ avgMessageLength: commitQuality.subjectQuality.avgLength,
78
+ withBody: commitQuality.bodyQuality.withBody
79
+ };
80
+ // Work distribution
81
+ const workDistribution = {
82
+ features: commitQuality.typeDistribution.features,
83
+ fixes: commitQuality.typeDistribution.fixes,
84
+ docs: commitQuality.typeDistribution.docs,
85
+ refactors: commitQuality.typeDistribution.refactors,
86
+ tests: commitQuality.typeDistribution.tests,
87
+ other: commitQuality.typeDistribution.chores + commitQuality.typeDistribution.other
88
+ };
89
+ // Collaboration
90
+ const collaboration = analyzeCollaboration(commits, repoPath);
91
+ // Achievements
92
+ const achievements = {
93
+ earned: achievementProgress.achievements
94
+ .filter(a => a.isUnlocked)
95
+ .map(a => a.name),
96
+ total: achievementProgress.unlockedCount
97
+ };
98
+ // Fun facts
99
+ const funFacts = findFunFacts(commits, timePattern);
100
+ // Get developer name
101
+ const developer = getDeveloperName(repoPath);
102
+ // Compare with previous year if requested
103
+ let comparison;
104
+ if (includeComparison) {
105
+ try {
106
+ comparison = compareWithPreviousYear(year, repoPath);
107
+ }
108
+ catch (error) {
109
+ // Previous year data not available
110
+ comparison = undefined;
111
+ }
112
+ }
113
+ return {
114
+ year,
115
+ developer,
116
+ overview,
117
+ impact,
118
+ timing,
119
+ streaks,
120
+ quality,
121
+ workDistribution,
122
+ collaboration,
123
+ achievements,
124
+ funFacts,
125
+ comparison
126
+ };
127
+ }
128
+ exports.generateYearWrapped = generateYearWrapped;
129
+ function calculateImpact(commits, repoPath, year) {
130
+ try {
131
+ // Get git log with numstat for the year
132
+ const output = (0, child_process_1.execSync)(`git log --since="${year}-01-01" --until="${year}-12-31" --numstat --format="%H" --no-merges`, { cwd: repoPath, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 });
133
+ let linesAdded = 0;
134
+ let linesRemoved = 0;
135
+ const filesChanged = new Set();
136
+ const fileChangeCount = {};
137
+ const lines = output.trim().split('\n');
138
+ for (const line of lines) {
139
+ if (line.match(/^[0-9a-f]{40}$/))
140
+ continue; // Skip commit hashes
141
+ if (line.trim() === '')
142
+ continue;
143
+ const parts = line.split('\t');
144
+ if (parts.length !== 3)
145
+ continue;
146
+ const [added, removed, file] = parts;
147
+ // Skip binary files
148
+ if (added === '-' || removed === '-')
149
+ continue;
150
+ linesAdded += parseInt(added, 10) || 0;
151
+ linesRemoved += parseInt(removed, 10) || 0;
152
+ filesChanged.add(file);
153
+ fileChangeCount[file] = (fileChangeCount[file] || 0) + 1;
154
+ }
155
+ // Get top 3 most changed files
156
+ const mostChangedFiles = Object.entries(fileChangeCount)
157
+ .sort(([, a], [, b]) => b - a)
158
+ .slice(0, 3)
159
+ .map(([file]) => file);
160
+ return {
161
+ linesAdded,
162
+ linesRemoved,
163
+ netLines: linesAdded - linesRemoved,
164
+ filesChanged: filesChanged.size,
165
+ mostChangedFiles
166
+ };
167
+ }
168
+ catch (error) {
169
+ return {
170
+ linesAdded: 0,
171
+ linesRemoved: 0,
172
+ netLines: 0,
173
+ filesChanged: 0,
174
+ mostChangedFiles: []
175
+ };
176
+ }
177
+ }
178
+ function mapChronotype(type) {
179
+ const mapping = {
180
+ 'early-bird': 'morning',
181
+ 'balanced': 'day',
182
+ 'night-owl': 'evening',
183
+ 'vampire': 'night'
184
+ };
185
+ return mapping[type] || 'day';
186
+ }
187
+ function findMostProductiveMonth(commits) {
188
+ const monthCounts = {};
189
+ const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
190
+ commits.forEach(commit => {
191
+ const date = new Date(commit.date);
192
+ const monthKey = months[date.getMonth()];
193
+ monthCounts[monthKey] = (monthCounts[monthKey] || 0) + 1;
194
+ });
195
+ const topMonth = Object.entries(monthCounts)
196
+ .sort(([, a], [, b]) => b - a)[0];
197
+ return topMonth ? topMonth[0] : 'Jan';
198
+ }
199
+ function findMostProductiveWeek(commits) {
200
+ if (commits.length === 0)
201
+ return new Date();
202
+ // Group commits by week
203
+ const weekCounts = {};
204
+ commits.forEach(commit => {
205
+ const date = new Date(commit.date);
206
+ const weekStart = getWeekStart(date);
207
+ const key = weekStart.toISOString().split('T')[0];
208
+ if (!weekCounts[key]) {
209
+ weekCounts[key] = { count: 0, date: weekStart };
210
+ }
211
+ weekCounts[key].count++;
212
+ });
213
+ const topWeek = Object.values(weekCounts)
214
+ .sort((a, b) => b.count - a.count)[0];
215
+ return topWeek ? topWeek.date : new Date(commits[0].date);
216
+ }
217
+ function getWeekStart(date) {
218
+ const d = new Date(date);
219
+ const day = d.getDay();
220
+ const diff = d.getDate() - day + (day === 0 ? -6 : 1); // Adjust for Monday start
221
+ return new Date(d.setDate(diff));
222
+ }
223
+ function analyzeCollaboration(commits, repoPath) {
224
+ const authors = new Set();
225
+ const authorDays = {};
226
+ commits.forEach(commit => {
227
+ authors.add(commit.author);
228
+ if (!authorDays[commit.author]) {
229
+ authorDays[commit.author] = new Set();
230
+ }
231
+ authorDays[commit.author].add(commit.date);
232
+ });
233
+ const currentUser = getDeveloperName(repoPath);
234
+ const otherAuthors = Array.from(authors).filter(a => a !== currentUser);
235
+ // Find top collaborator (most shared commit days)
236
+ let topCollaborator = null;
237
+ if (otherAuthors.length > 0 && authorDays[currentUser]) {
238
+ const userDays = authorDays[currentUser];
239
+ otherAuthors.forEach(author => {
240
+ const sharedDays = Array.from(authorDays[author] || [])
241
+ .filter(day => userDays.has(day)).length;
242
+ if (!topCollaborator || sharedDays > topCollaborator.sharedDays) {
243
+ topCollaborator = { name: author, sharedDays };
244
+ }
245
+ });
246
+ }
247
+ // Calculate solo percentage
248
+ const totalDays = authorDays[currentUser]?.size || 0;
249
+ const soloDays = Array.from(authorDays[currentUser] || []).filter(day => {
250
+ return otherAuthors.every(author => !authorDays[author]?.has(day));
251
+ }).length;
252
+ const soloPercentage = totalDays > 0 ? (soloDays / totalDays) * 100 : 100;
253
+ return {
254
+ uniqueContributors: otherAuthors.length,
255
+ topCollaborator,
256
+ soloPercentage
257
+ };
258
+ }
259
+ function findFunFacts(commits, timePattern) {
260
+ const holidayCommits = [];
261
+ let midnightCommits = 0;
262
+ let weekendCommits = 0;
263
+ const dayCommitCounts = {};
264
+ commits.forEach(commit => {
265
+ const date = new Date(commit.date);
266
+ const dayKey = commit.date;
267
+ dayCommitCounts[dayKey] = (dayCommitCounts[dayKey] || 0) + 1;
268
+ // Check for holidays
269
+ const month = date.getMonth() + 1;
270
+ const day = date.getDate();
271
+ if (month === 12 && day === 25)
272
+ holidayCommits.push('Christmas');
273
+ if (month === 1 && day === 1)
274
+ holidayCommits.push('New Year');
275
+ if (month === 7 && day === 4)
276
+ holidayCommits.push('Independence Day');
277
+ if (month === 10 && day === 31)
278
+ holidayCommits.push('Halloween');
279
+ // Weekend commits
280
+ const dayOfWeek = date.getDay();
281
+ if (dayOfWeek === 0 || dayOfWeek === 6) {
282
+ weekendCommits++;
283
+ }
284
+ });
285
+ // Count midnight commits from time pattern
286
+ if (timePattern.hourly) {
287
+ const midnight = timePattern.hourly.find((h) => h.hour === 0);
288
+ midnightCommits = midnight ? midnight.commitCount : 0;
289
+ }
290
+ // Find busiest day
291
+ const busiestDayEntry = Object.entries(dayCommitCounts)
292
+ .sort(([, a], [, b]) => b - a)[0];
293
+ const busiestDay = busiestDayEntry
294
+ ? { date: new Date(busiestDayEntry[0]), commits: busiestDayEntry[1] }
295
+ : null;
296
+ // Longest session from timePattern
297
+ const longestSession = timePattern.timingStats?.longestSession || null;
298
+ return {
299
+ holidayCommits: Array.from(new Set(holidayCommits)),
300
+ midnightCommits,
301
+ weekendCommits,
302
+ longestSession,
303
+ busiestDay
304
+ };
305
+ }
306
+ function getDeveloperName(repoPath) {
307
+ try {
308
+ const name = (0, child_process_1.execSync)('git config user.name', {
309
+ cwd: repoPath,
310
+ encoding: 'utf-8'
311
+ }).trim();
312
+ return name || 'Developer';
313
+ }
314
+ catch {
315
+ return 'Developer';
316
+ }
317
+ }
318
+ function compareWithPreviousYear(year, repoPath) {
319
+ // Generate wrapped for current and previous year
320
+ const currentYearData = generateYearWrapped(year, repoPath, false);
321
+ const previousYearData = generateYearWrapped(year - 1, repoPath, false);
322
+ // Extract stats
323
+ const currentStats = {
324
+ commits: currentYearData.overview.totalCommits,
325
+ streak: currentYearData.streaks.longest.days,
326
+ quality: currentYearData.quality.overallScore,
327
+ collaboration: currentYearData.collaboration.uniqueContributors
328
+ };
329
+ const previousStats = {
330
+ commits: previousYearData.overview.totalCommits,
331
+ streak: previousYearData.streaks.longest.days,
332
+ quality: previousYearData.quality.overallScore,
333
+ collaboration: previousYearData.collaboration.uniqueContributors
334
+ };
335
+ // Calculate changes
336
+ const calculateChange = (current, previous) => {
337
+ const value = current - previous;
338
+ const percentage = previous > 0 ? (value / previous) * 100 : 0;
339
+ let trend = 'stable';
340
+ if (Math.abs(percentage) >= 5) {
341
+ trend = value > 0 ? 'up' : 'down';
342
+ }
343
+ return { value, percentage, trend };
344
+ };
345
+ const changes = {
346
+ commits: calculateChange(currentStats.commits, previousStats.commits),
347
+ streak: calculateChange(currentStats.streak, previousStats.streak),
348
+ quality: calculateChange(currentStats.quality, previousStats.quality),
349
+ collaboration: calculateChange(currentStats.collaboration, previousStats.collaboration)
350
+ };
351
+ // Generate insights
352
+ const insights = [];
353
+ if (changes.commits.trend === 'up') {
354
+ insights.push(`${Math.abs(changes.commits.percentage).toFixed(0)}% more commits than last year!`);
355
+ }
356
+ else if (changes.commits.trend === 'down') {
357
+ insights.push('Fewer commits, but quality over quantity!');
358
+ }
359
+ if (changes.quality.trend === 'up') {
360
+ insights.push('Commit quality improved significantly! 📈');
361
+ }
362
+ if (changes.streak.trend === 'up') {
363
+ insights.push(`Streak improved by ${changes.streak.value} days!`);
364
+ }
365
+ if (changes.collaboration.trend === 'up') {
366
+ insights.push('Expanded collaboration network! 🤝');
367
+ }
368
+ if (insights.length === 0) {
369
+ insights.push('Consistent performance year over year!');
370
+ }
371
+ return {
372
+ currentYear: currentStats,
373
+ previousYear: previousStats,
374
+ changes,
375
+ insights
376
+ };
377
+ }