repo-wrapped 0.0.3 → 0.0.5
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/dist/commands/generate.js +104 -95
- package/dist/constants/chronotypes.js +23 -23
- package/dist/constants/colors.js +18 -18
- package/dist/constants/index.js +18 -18
- package/dist/formatters/index.js +17 -17
- package/dist/formatters/timeFormatter.js +28 -29
- package/dist/generators/html/templates/achievementsSection.js +42 -43
- package/dist/generators/html/templates/commitQualitySection.js +25 -26
- package/dist/generators/html/templates/contributionGraph.js +64 -48
- package/dist/generators/html/templates/impactSection.js +19 -20
- package/dist/generators/html/templates/knowledgeSection.js +86 -87
- package/dist/generators/html/templates/streakSection.js +8 -9
- package/dist/generators/html/templates/timePatternsSection.js +45 -46
- package/dist/generators/html/utils/colorUtils.js +61 -21
- package/dist/generators/html/utils/commitMapBuilder.js +23 -24
- package/dist/generators/html/utils/dateRangeCalculator.js +56 -57
- package/dist/generators/html/utils/developerStatsCalculator.js +28 -29
- package/dist/generators/html/utils/scriptLoader.js +15 -16
- package/dist/generators/html/utils/styleLoader.js +17 -18
- package/dist/generators/html/utils/weekGrouper.js +27 -28
- package/dist/index.js +78 -78
- package/dist/types/index.js +2 -2
- package/dist/utils/achievementDefinitions.js +433 -433
- package/dist/utils/achievementEngine.js +169 -170
- package/dist/utils/commitQualityAnalyzer.js +367 -368
- package/dist/utils/fileHotspotAnalyzer.js +269 -270
- package/dist/utils/gitParser.js +136 -126
- package/dist/utils/htmlGenerator.js +245 -233
- package/dist/utils/impactAnalyzer.js +247 -248
- package/dist/utils/knowledgeDistributionAnalyzer.js +380 -374
- package/dist/utils/matrixGenerator.js +369 -350
- package/dist/utils/slideGenerator.js +170 -171
- package/dist/utils/streakCalculator.js +134 -135
- package/dist/utils/timePatternAnalyzer.js +304 -305
- package/dist/utils/wrappedDisplay.js +124 -115
- package/dist/utils/wrappedGenerator.js +376 -377
- package/dist/utils/wrappedHtmlGenerator.js +105 -106
- package/package.json +10 -10
|
@@ -1,377 +1,376 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.generateYearWrapped =
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
let
|
|
134
|
-
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
.
|
|
158
|
-
.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
linesRemoved,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
'
|
|
181
|
-
'
|
|
182
|
-
'
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
let
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
}
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateYearWrapped = generateYearWrapped;
|
|
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
|
+
function calculateImpact(commits, repoPath, year) {
|
|
129
|
+
try {
|
|
130
|
+
// Get git log with numstat for the year
|
|
131
|
+
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 });
|
|
132
|
+
let linesAdded = 0;
|
|
133
|
+
let linesRemoved = 0;
|
|
134
|
+
const filesChanged = new Set();
|
|
135
|
+
const fileChangeCount = {};
|
|
136
|
+
const lines = output.trim().split('\n');
|
|
137
|
+
for (const line of lines) {
|
|
138
|
+
if (line.match(/^[0-9a-f]{40}$/))
|
|
139
|
+
continue; // Skip commit hashes
|
|
140
|
+
if (line.trim() === '')
|
|
141
|
+
continue;
|
|
142
|
+
const parts = line.split('\t');
|
|
143
|
+
if (parts.length !== 3)
|
|
144
|
+
continue;
|
|
145
|
+
const [added, removed, file] = parts;
|
|
146
|
+
// Skip binary files
|
|
147
|
+
if (added === '-' || removed === '-')
|
|
148
|
+
continue;
|
|
149
|
+
linesAdded += parseInt(added, 10) || 0;
|
|
150
|
+
linesRemoved += parseInt(removed, 10) || 0;
|
|
151
|
+
filesChanged.add(file);
|
|
152
|
+
fileChangeCount[file] = (fileChangeCount[file] || 0) + 1;
|
|
153
|
+
}
|
|
154
|
+
// Get top 3 most changed files
|
|
155
|
+
const mostChangedFiles = Object.entries(fileChangeCount)
|
|
156
|
+
.sort(([, a], [, b]) => b - a)
|
|
157
|
+
.slice(0, 3)
|
|
158
|
+
.map(([file]) => file);
|
|
159
|
+
return {
|
|
160
|
+
linesAdded,
|
|
161
|
+
linesRemoved,
|
|
162
|
+
netLines: linesAdded - linesRemoved,
|
|
163
|
+
filesChanged: filesChanged.size,
|
|
164
|
+
mostChangedFiles
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
return {
|
|
169
|
+
linesAdded: 0,
|
|
170
|
+
linesRemoved: 0,
|
|
171
|
+
netLines: 0,
|
|
172
|
+
filesChanged: 0,
|
|
173
|
+
mostChangedFiles: []
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function mapChronotype(type) {
|
|
178
|
+
const mapping = {
|
|
179
|
+
'early-bird': 'morning',
|
|
180
|
+
'balanced': 'day',
|
|
181
|
+
'night-owl': 'evening',
|
|
182
|
+
'vampire': 'night'
|
|
183
|
+
};
|
|
184
|
+
return mapping[type] || 'day';
|
|
185
|
+
}
|
|
186
|
+
function findMostProductiveMonth(commits) {
|
|
187
|
+
const monthCounts = {};
|
|
188
|
+
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
189
|
+
commits.forEach(commit => {
|
|
190
|
+
const date = new Date(commit.date);
|
|
191
|
+
const monthKey = months[date.getMonth()];
|
|
192
|
+
monthCounts[monthKey] = (monthCounts[monthKey] || 0) + 1;
|
|
193
|
+
});
|
|
194
|
+
const topMonth = Object.entries(monthCounts)
|
|
195
|
+
.sort(([, a], [, b]) => b - a)[0];
|
|
196
|
+
return topMonth ? topMonth[0] : 'Jan';
|
|
197
|
+
}
|
|
198
|
+
function findMostProductiveWeek(commits) {
|
|
199
|
+
if (commits.length === 0)
|
|
200
|
+
return new Date();
|
|
201
|
+
// Group commits by week
|
|
202
|
+
const weekCounts = {};
|
|
203
|
+
commits.forEach(commit => {
|
|
204
|
+
const date = new Date(commit.date);
|
|
205
|
+
const weekStart = getWeekStart(date);
|
|
206
|
+
const key = weekStart.toISOString().split('T')[0];
|
|
207
|
+
if (!weekCounts[key]) {
|
|
208
|
+
weekCounts[key] = { count: 0, date: weekStart };
|
|
209
|
+
}
|
|
210
|
+
weekCounts[key].count++;
|
|
211
|
+
});
|
|
212
|
+
const topWeek = Object.values(weekCounts)
|
|
213
|
+
.sort((a, b) => b.count - a.count)[0];
|
|
214
|
+
return topWeek ? topWeek.date : new Date(commits[0].date);
|
|
215
|
+
}
|
|
216
|
+
function getWeekStart(date) {
|
|
217
|
+
const d = new Date(date);
|
|
218
|
+
const day = d.getDay();
|
|
219
|
+
const diff = d.getDate() - day + (day === 0 ? -6 : 1); // Adjust for Monday start
|
|
220
|
+
return new Date(d.setDate(diff));
|
|
221
|
+
}
|
|
222
|
+
function analyzeCollaboration(commits, repoPath) {
|
|
223
|
+
const authors = new Set();
|
|
224
|
+
const authorDays = {};
|
|
225
|
+
commits.forEach(commit => {
|
|
226
|
+
authors.add(commit.author);
|
|
227
|
+
if (!authorDays[commit.author]) {
|
|
228
|
+
authorDays[commit.author] = new Set();
|
|
229
|
+
}
|
|
230
|
+
authorDays[commit.author].add(commit.date);
|
|
231
|
+
});
|
|
232
|
+
const currentUser = getDeveloperName(repoPath);
|
|
233
|
+
const otherAuthors = Array.from(authors).filter(a => a !== currentUser);
|
|
234
|
+
// Find top collaborator (most shared commit days)
|
|
235
|
+
let topCollaborator = null;
|
|
236
|
+
if (otherAuthors.length > 0 && authorDays[currentUser]) {
|
|
237
|
+
const userDays = authorDays[currentUser];
|
|
238
|
+
otherAuthors.forEach(author => {
|
|
239
|
+
const sharedDays = Array.from(authorDays[author] || [])
|
|
240
|
+
.filter(day => userDays.has(day)).length;
|
|
241
|
+
if (!topCollaborator || sharedDays > topCollaborator.sharedDays) {
|
|
242
|
+
topCollaborator = { name: author, sharedDays };
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
// Calculate solo percentage
|
|
247
|
+
const totalDays = authorDays[currentUser]?.size || 0;
|
|
248
|
+
const soloDays = Array.from(authorDays[currentUser] || []).filter(day => {
|
|
249
|
+
return otherAuthors.every(author => !authorDays[author]?.has(day));
|
|
250
|
+
}).length;
|
|
251
|
+
const soloPercentage = totalDays > 0 ? (soloDays / totalDays) * 100 : 100;
|
|
252
|
+
return {
|
|
253
|
+
uniqueContributors: otherAuthors.length,
|
|
254
|
+
topCollaborator,
|
|
255
|
+
soloPercentage
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function findFunFacts(commits, timePattern) {
|
|
259
|
+
const holidayCommits = [];
|
|
260
|
+
let midnightCommits = 0;
|
|
261
|
+
let weekendCommits = 0;
|
|
262
|
+
const dayCommitCounts = {};
|
|
263
|
+
commits.forEach(commit => {
|
|
264
|
+
const date = new Date(commit.date);
|
|
265
|
+
const dayKey = commit.date;
|
|
266
|
+
dayCommitCounts[dayKey] = (dayCommitCounts[dayKey] || 0) + 1;
|
|
267
|
+
// Check for holidays
|
|
268
|
+
const month = date.getMonth() + 1;
|
|
269
|
+
const day = date.getDate();
|
|
270
|
+
if (month === 12 && day === 25)
|
|
271
|
+
holidayCommits.push('Christmas');
|
|
272
|
+
if (month === 1 && day === 1)
|
|
273
|
+
holidayCommits.push('New Year');
|
|
274
|
+
if (month === 7 && day === 4)
|
|
275
|
+
holidayCommits.push('Independence Day');
|
|
276
|
+
if (month === 10 && day === 31)
|
|
277
|
+
holidayCommits.push('Halloween');
|
|
278
|
+
// Weekend commits
|
|
279
|
+
const dayOfWeek = date.getDay();
|
|
280
|
+
if (dayOfWeek === 0 || dayOfWeek === 6) {
|
|
281
|
+
weekendCommits++;
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
// Count midnight commits from time pattern
|
|
285
|
+
if (timePattern.hourly) {
|
|
286
|
+
const midnight = timePattern.hourly.find((h) => h.hour === 0);
|
|
287
|
+
midnightCommits = midnight ? midnight.commitCount : 0;
|
|
288
|
+
}
|
|
289
|
+
// Find busiest day
|
|
290
|
+
const busiestDayEntry = Object.entries(dayCommitCounts)
|
|
291
|
+
.sort(([, a], [, b]) => b - a)[0];
|
|
292
|
+
const busiestDay = busiestDayEntry
|
|
293
|
+
? { date: new Date(busiestDayEntry[0]), commits: busiestDayEntry[1] }
|
|
294
|
+
: null;
|
|
295
|
+
// Longest session from timePattern
|
|
296
|
+
const longestSession = timePattern.timingStats?.longestSession || null;
|
|
297
|
+
return {
|
|
298
|
+
holidayCommits: Array.from(new Set(holidayCommits)),
|
|
299
|
+
midnightCommits,
|
|
300
|
+
weekendCommits,
|
|
301
|
+
longestSession,
|
|
302
|
+
busiestDay
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
function getDeveloperName(repoPath) {
|
|
306
|
+
try {
|
|
307
|
+
const name = (0, child_process_1.execSync)('git config user.name', {
|
|
308
|
+
cwd: repoPath,
|
|
309
|
+
encoding: 'utf-8'
|
|
310
|
+
}).trim();
|
|
311
|
+
return name || 'Developer';
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
return 'Developer';
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function compareWithPreviousYear(year, repoPath) {
|
|
318
|
+
// Generate wrapped for current and previous year
|
|
319
|
+
const currentYearData = generateYearWrapped(year, repoPath, false);
|
|
320
|
+
const previousYearData = generateYearWrapped(year - 1, repoPath, false);
|
|
321
|
+
// Extract stats
|
|
322
|
+
const currentStats = {
|
|
323
|
+
commits: currentYearData.overview.totalCommits,
|
|
324
|
+
streak: currentYearData.streaks.longest.days,
|
|
325
|
+
quality: currentYearData.quality.overallScore,
|
|
326
|
+
collaboration: currentYearData.collaboration.uniqueContributors
|
|
327
|
+
};
|
|
328
|
+
const previousStats = {
|
|
329
|
+
commits: previousYearData.overview.totalCommits,
|
|
330
|
+
streak: previousYearData.streaks.longest.days,
|
|
331
|
+
quality: previousYearData.quality.overallScore,
|
|
332
|
+
collaboration: previousYearData.collaboration.uniqueContributors
|
|
333
|
+
};
|
|
334
|
+
// Calculate changes
|
|
335
|
+
const calculateChange = (current, previous) => {
|
|
336
|
+
const value = current - previous;
|
|
337
|
+
const percentage = previous > 0 ? (value / previous) * 100 : 0;
|
|
338
|
+
let trend = 'stable';
|
|
339
|
+
if (Math.abs(percentage) >= 5) {
|
|
340
|
+
trend = value > 0 ? 'up' : 'down';
|
|
341
|
+
}
|
|
342
|
+
return { value, percentage, trend };
|
|
343
|
+
};
|
|
344
|
+
const changes = {
|
|
345
|
+
commits: calculateChange(currentStats.commits, previousStats.commits),
|
|
346
|
+
streak: calculateChange(currentStats.streak, previousStats.streak),
|
|
347
|
+
quality: calculateChange(currentStats.quality, previousStats.quality),
|
|
348
|
+
collaboration: calculateChange(currentStats.collaboration, previousStats.collaboration)
|
|
349
|
+
};
|
|
350
|
+
// Generate insights
|
|
351
|
+
const insights = [];
|
|
352
|
+
if (changes.commits.trend === 'up') {
|
|
353
|
+
insights.push(`${Math.abs(changes.commits.percentage).toFixed(0)}% more commits than last year!`);
|
|
354
|
+
}
|
|
355
|
+
else if (changes.commits.trend === 'down') {
|
|
356
|
+
insights.push('Fewer commits, but quality over quantity!');
|
|
357
|
+
}
|
|
358
|
+
if (changes.quality.trend === 'up') {
|
|
359
|
+
insights.push('Commit quality improved significantly! 📈');
|
|
360
|
+
}
|
|
361
|
+
if (changes.streak.trend === 'up') {
|
|
362
|
+
insights.push(`Streak improved by ${changes.streak.value} days!`);
|
|
363
|
+
}
|
|
364
|
+
if (changes.collaboration.trend === 'up') {
|
|
365
|
+
insights.push('Expanded collaboration network! 🤝');
|
|
366
|
+
}
|
|
367
|
+
if (insights.length === 0) {
|
|
368
|
+
insights.push('Consistent performance year over year!');
|
|
369
|
+
}
|
|
370
|
+
return {
|
|
371
|
+
currentYear: currentStats,
|
|
372
|
+
previousYear: previousStats,
|
|
373
|
+
changes,
|
|
374
|
+
insights
|
|
375
|
+
};
|
|
376
|
+
}
|