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,552 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateWrappedHTML = void 0;
|
|
4
|
+
const constants_1 = require("../constants");
|
|
5
|
+
const formatters_1 = require("../formatters");
|
|
6
|
+
function generateWrappedHTML(summary) {
|
|
7
|
+
const { writeFileSync } = require('fs');
|
|
8
|
+
const { join } = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
const html = `<!DOCTYPE html>
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="UTF-8">
|
|
14
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
15
|
+
<title>${summary.developer}'s ${summary.year} in Code</title>
|
|
16
|
+
<style>
|
|
17
|
+
* {
|
|
18
|
+
margin: 0;
|
|
19
|
+
padding: 0;
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
body {
|
|
24
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
25
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
26
|
+
color: white;
|
|
27
|
+
overflow: hidden;
|
|
28
|
+
height: 100vh;
|
|
29
|
+
display: flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.slideshow {
|
|
35
|
+
width: 90vw;
|
|
36
|
+
max-width: 800px;
|
|
37
|
+
height: 80vh;
|
|
38
|
+
max-height: 600px;
|
|
39
|
+
position: relative;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.slide {
|
|
43
|
+
position: absolute;
|
|
44
|
+
top: 0;
|
|
45
|
+
left: 0;
|
|
46
|
+
width: 100%;
|
|
47
|
+
height: 100%;
|
|
48
|
+
background: rgba(255, 255, 255, 0.1);
|
|
49
|
+
backdrop-filter: blur(10px);
|
|
50
|
+
border-radius: 20px;
|
|
51
|
+
padding: 60px;
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
align-items: center;
|
|
55
|
+
justify-content: center;
|
|
56
|
+
text-align: center;
|
|
57
|
+
opacity: 0;
|
|
58
|
+
transform: translateX(100px);
|
|
59
|
+
transition: all 0.6s ease;
|
|
60
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.slide.active {
|
|
64
|
+
opacity: 1;
|
|
65
|
+
transform: translateX(0);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.slide.prev {
|
|
69
|
+
opacity: 0;
|
|
70
|
+
transform: translateX(-100px);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
h1 {
|
|
74
|
+
font-size: 3.5em;
|
|
75
|
+
margin-bottom: 20px;
|
|
76
|
+
font-weight: 700;
|
|
77
|
+
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
h2 {
|
|
81
|
+
font-size: 2.5em;
|
|
82
|
+
margin-bottom: 15px;
|
|
83
|
+
font-weight: 600;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.big-number {
|
|
87
|
+
font-size: 5em;
|
|
88
|
+
font-weight: 800;
|
|
89
|
+
margin: 30px 0;
|
|
90
|
+
text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.4);
|
|
91
|
+
background: linear-gradient(45deg, #ffd700, #ffed4e);
|
|
92
|
+
-webkit-background-clip: text;
|
|
93
|
+
-webkit-text-fill-color: transparent;
|
|
94
|
+
background-clip: text;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.subtitle {
|
|
98
|
+
font-size: 1.3em;
|
|
99
|
+
opacity: 0.9;
|
|
100
|
+
margin: 10px 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.stats-grid {
|
|
104
|
+
display: grid;
|
|
105
|
+
grid-template-columns: 1fr 1fr;
|
|
106
|
+
gap: 20px;
|
|
107
|
+
margin: 30px 0;
|
|
108
|
+
width: 100%;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.stat-item {
|
|
112
|
+
background: rgba(255, 255, 255, 0.15);
|
|
113
|
+
padding: 20px;
|
|
114
|
+
border-radius: 12px;
|
|
115
|
+
font-size: 1.1em;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.stat-label {
|
|
119
|
+
opacity: 0.8;
|
|
120
|
+
font-size: 0.9em;
|
|
121
|
+
margin-bottom: 8px;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.stat-value {
|
|
125
|
+
font-size: 1.8em;
|
|
126
|
+
font-weight: 700;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.fun-facts {
|
|
130
|
+
list-style: none;
|
|
131
|
+
font-size: 1.2em;
|
|
132
|
+
line-height: 1.8;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.fun-facts li {
|
|
136
|
+
margin: 15px 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.emoji {
|
|
140
|
+
font-size: 1.5em;
|
|
141
|
+
margin-right: 10px;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.controls {
|
|
145
|
+
position: fixed;
|
|
146
|
+
bottom: 40px;
|
|
147
|
+
left: 50%;
|
|
148
|
+
transform: translateX(-50%);
|
|
149
|
+
display: flex;
|
|
150
|
+
gap: 15px;
|
|
151
|
+
z-index: 1000;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.btn {
|
|
155
|
+
background: rgba(255, 255, 255, 0.2);
|
|
156
|
+
border: 2px solid rgba(255, 255, 255, 0.4);
|
|
157
|
+
color: white;
|
|
158
|
+
padding: 12px 24px;
|
|
159
|
+
border-radius: 25px;
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
font-size: 1em;
|
|
162
|
+
font-weight: 600;
|
|
163
|
+
transition: all 0.3s ease;
|
|
164
|
+
backdrop-filter: blur(10px);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.btn:hover {
|
|
168
|
+
background: rgba(255, 255, 255, 0.3);
|
|
169
|
+
transform: translateY(-2px);
|
|
170
|
+
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.progress-dots {
|
|
174
|
+
position: fixed;
|
|
175
|
+
top: 40px;
|
|
176
|
+
left: 50%;
|
|
177
|
+
transform: translateX(-50%);
|
|
178
|
+
display: flex;
|
|
179
|
+
gap: 10px;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.dot {
|
|
183
|
+
width: 10px;
|
|
184
|
+
height: 10px;
|
|
185
|
+
border-radius: 50%;
|
|
186
|
+
background: rgba(255, 255, 255, 0.3);
|
|
187
|
+
transition: all 0.3s ease;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.dot.active {
|
|
191
|
+
background: white;
|
|
192
|
+
transform: scale(1.3);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.comparison-row {
|
|
196
|
+
display: flex;
|
|
197
|
+
justify-content: space-between;
|
|
198
|
+
align-items: center;
|
|
199
|
+
padding: 15px 20px;
|
|
200
|
+
margin: 10px 0;
|
|
201
|
+
background: rgba(255, 255, 255, 0.1);
|
|
202
|
+
border-radius: 10px;
|
|
203
|
+
font-size: 1.1em;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.trend-up {
|
|
207
|
+
color: #4ade80;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.trend-down {
|
|
211
|
+
color: #f87171;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.badges-grid {
|
|
215
|
+
display: grid;
|
|
216
|
+
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
217
|
+
gap: 15px;
|
|
218
|
+
margin: 20px 0;
|
|
219
|
+
max-height: 300px;
|
|
220
|
+
overflow-y: auto;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.badge-item {
|
|
224
|
+
background: rgba(255, 255, 255, 0.15);
|
|
225
|
+
padding: 15px;
|
|
226
|
+
border-radius: 10px;
|
|
227
|
+
font-size: 0.9em;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
@keyframes fadeIn {
|
|
231
|
+
from {
|
|
232
|
+
opacity: 0;
|
|
233
|
+
transform: translateY(20px);
|
|
234
|
+
}
|
|
235
|
+
to {
|
|
236
|
+
opacity: 1;
|
|
237
|
+
transform: translateY(0);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.slide.active > * {
|
|
242
|
+
animation: fadeIn 0.6s ease forwards;
|
|
243
|
+
}
|
|
244
|
+
</style>
|
|
245
|
+
</head>
|
|
246
|
+
<body>
|
|
247
|
+
<div class="slideshow" id="slideshow"></div>
|
|
248
|
+
|
|
249
|
+
<div class="progress-dots" id="progressDots"></div>
|
|
250
|
+
|
|
251
|
+
<div class="controls">
|
|
252
|
+
<button class="btn" id="prevBtn">← Previous</button>
|
|
253
|
+
<button class="btn" id="nextBtn">Next →</button>
|
|
254
|
+
</div>
|
|
255
|
+
|
|
256
|
+
<script>
|
|
257
|
+
const slides = ${JSON.stringify(generateWrappedSlides(summary))};
|
|
258
|
+
let currentSlide = 0;
|
|
259
|
+
|
|
260
|
+
function renderSlides() {
|
|
261
|
+
const slideshow = document.getElementById('slideshow');
|
|
262
|
+
slideshow.innerHTML = '';
|
|
263
|
+
|
|
264
|
+
slides.forEach((slide, index) => {
|
|
265
|
+
const slideEl = document.createElement('div');
|
|
266
|
+
slideEl.className = 'slide';
|
|
267
|
+
if (index === 0) slideEl.classList.add('active');
|
|
268
|
+
slideEl.innerHTML = slide.html;
|
|
269
|
+
slideshow.appendChild(slideEl);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
renderProgressDots();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function renderProgressDots() {
|
|
276
|
+
const dotsContainer = document.getElementById('progressDots');
|
|
277
|
+
dotsContainer.innerHTML = '';
|
|
278
|
+
|
|
279
|
+
slides.forEach((_, index) => {
|
|
280
|
+
const dot = document.createElement('div');
|
|
281
|
+
dot.className = 'dot';
|
|
282
|
+
if (index === currentSlide) dot.classList.add('active');
|
|
283
|
+
dot.addEventListener('click', () => goToSlide(index));
|
|
284
|
+
dotsContainer.appendChild(dot);
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function goToSlide(index) {
|
|
289
|
+
const slideElements = document.querySelectorAll('.slide');
|
|
290
|
+
|
|
291
|
+
slideElements[currentSlide].classList.remove('active');
|
|
292
|
+
slideElements[currentSlide].classList.add('prev');
|
|
293
|
+
|
|
294
|
+
currentSlide = index;
|
|
295
|
+
|
|
296
|
+
slideElements[currentSlide].classList.remove('prev');
|
|
297
|
+
slideElements[currentSlide].classList.add('active');
|
|
298
|
+
|
|
299
|
+
renderProgressDots();
|
|
300
|
+
updateButtons();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function nextSlide() {
|
|
304
|
+
if (currentSlide < slides.length - 1) {
|
|
305
|
+
goToSlide(currentSlide + 1);
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function prevSlide() {
|
|
310
|
+
if (currentSlide > 0) {
|
|
311
|
+
goToSlide(currentSlide - 1);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function updateButtons() {
|
|
316
|
+
document.getElementById('prevBtn').disabled = currentSlide === 0;
|
|
317
|
+
document.getElementById('nextBtn').disabled = currentSlide === slides.length - 1;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
document.getElementById('prevBtn').addEventListener('click', prevSlide);
|
|
321
|
+
document.getElementById('nextBtn').addEventListener('click', nextSlide);
|
|
322
|
+
|
|
323
|
+
document.addEventListener('keydown', (e) => {
|
|
324
|
+
if (e.key === 'ArrowLeft') prevSlide();
|
|
325
|
+
if (e.key === 'ArrowRight' || e.key === ' ' || e.key === 'Enter') nextSlide();
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
renderSlides();
|
|
329
|
+
updateButtons();
|
|
330
|
+
</script>
|
|
331
|
+
</body>
|
|
332
|
+
</html>`;
|
|
333
|
+
// Write to temp file
|
|
334
|
+
const tmpDir = os.tmpdir();
|
|
335
|
+
const filePath = join(tmpDir, `year-in-code-${summary.year}.html`);
|
|
336
|
+
writeFileSync(filePath, html);
|
|
337
|
+
return filePath;
|
|
338
|
+
}
|
|
339
|
+
exports.generateWrappedHTML = generateWrappedHTML;
|
|
340
|
+
function generateWrappedSlides(summary) {
|
|
341
|
+
const slides = [];
|
|
342
|
+
// Slide 1: Welcome
|
|
343
|
+
slides.push({
|
|
344
|
+
html: `
|
|
345
|
+
<h1>YOUR ${summary.year} IN CODE</h1>
|
|
346
|
+
<p class="subtitle">Hey ${summary.developer},</p>
|
|
347
|
+
<p class="subtitle">Let's look back at your year!</p>
|
|
348
|
+
`
|
|
349
|
+
});
|
|
350
|
+
// Slide 2: Big Number
|
|
351
|
+
slides.push({
|
|
352
|
+
html: `
|
|
353
|
+
<p class="subtitle">You made</p>
|
|
354
|
+
<div class="big-number">${summary.overview.totalCommits.toLocaleString()}</div>
|
|
355
|
+
<h2>COMMITS</h2>
|
|
356
|
+
<p class="subtitle">That's ${summary.overview.avgCommitsPerDay.toFixed(1)} commits per active day!</p>
|
|
357
|
+
`
|
|
358
|
+
});
|
|
359
|
+
// Slide 3: Personality
|
|
360
|
+
const peakHourStr = (0, formatters_1.formatHourShort)(summary.timing.peakHour);
|
|
361
|
+
slides.push({
|
|
362
|
+
html: `
|
|
363
|
+
<h2>Your coding vibe:</h2>
|
|
364
|
+
<div class="big-number">${constants_1.WRAPPED_CHRONOTYPE_EMOJIS[summary.timing.chronotype]}</div>
|
|
365
|
+
<h2>${constants_1.WRAPPED_CHRONOTYPE_LABELS[summary.timing.chronotype]}</h2>
|
|
366
|
+
<p class="subtitle">Peak hour: ${peakHourStr}</p>
|
|
367
|
+
<p class="subtitle">Favorite day: ${summary.timing.peakDay}</p>
|
|
368
|
+
`
|
|
369
|
+
});
|
|
370
|
+
// Slide 4: Streak
|
|
371
|
+
slides.push({
|
|
372
|
+
html: `
|
|
373
|
+
<h2>Your longest streak:</h2>
|
|
374
|
+
<div class="big-number">🔥 ${summary.streaks.longest.days}</div>
|
|
375
|
+
<h2>DAYS</h2>
|
|
376
|
+
<p class="subtitle">${new Date(summary.streaks.longest.start).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} - ${new Date(summary.streaks.longest.end).toLocaleDateString('en-US', { month: 'short', day: 'numeric' })}</p>
|
|
377
|
+
<p class="subtitle">Consistency is key! 💪</p>
|
|
378
|
+
`
|
|
379
|
+
});
|
|
380
|
+
// Slide 5: Top Month
|
|
381
|
+
slides.push({
|
|
382
|
+
html: `
|
|
383
|
+
<h2>Your most productive month:</h2>
|
|
384
|
+
<div class="big-number">${summary.timing.mostProductiveMonth}</div>
|
|
385
|
+
<p class="subtitle">You were on fire! 🔥</p>
|
|
386
|
+
`
|
|
387
|
+
});
|
|
388
|
+
// Slide 6: Impact
|
|
389
|
+
slides.push({
|
|
390
|
+
html: `
|
|
391
|
+
<h2>Code impact:</h2>
|
|
392
|
+
<div class="stats-grid">
|
|
393
|
+
<div class="stat-item">
|
|
394
|
+
<div class="stat-label">Lines Added</div>
|
|
395
|
+
<div class="stat-value">+${summary.impact.linesAdded.toLocaleString()}</div>
|
|
396
|
+
</div>
|
|
397
|
+
<div class="stat-item">
|
|
398
|
+
<div class="stat-label">Lines Removed</div>
|
|
399
|
+
<div class="stat-value">-${summary.impact.linesRemoved.toLocaleString()}</div>
|
|
400
|
+
</div>
|
|
401
|
+
</div>
|
|
402
|
+
<p class="subtitle">${summary.impact.filesChanged} files changed</p>
|
|
403
|
+
<p class="subtitle">Making waves in the codebase! 🌊</p>
|
|
404
|
+
`
|
|
405
|
+
});
|
|
406
|
+
// Slide 7: Distribution
|
|
407
|
+
const total = Object.values(summary.workDistribution).reduce((sum, val) => sum + val, 0);
|
|
408
|
+
const getPercentage = (value) => total > 0 ? ((value / total) * 100).toFixed(0) : '0';
|
|
409
|
+
slides.push({
|
|
410
|
+
html: `
|
|
411
|
+
<h2>What you worked on:</h2>
|
|
412
|
+
<div class="stats-grid">
|
|
413
|
+
<div class="stat-item">
|
|
414
|
+
<div class="stat-label">🎨 Features</div>
|
|
415
|
+
<div class="stat-value">${getPercentage(summary.workDistribution.features)}%</div>
|
|
416
|
+
</div>
|
|
417
|
+
<div class="stat-item">
|
|
418
|
+
<div class="stat-label">🐛 Bug Fixes</div>
|
|
419
|
+
<div class="stat-value">${getPercentage(summary.workDistribution.fixes)}%</div>
|
|
420
|
+
</div>
|
|
421
|
+
<div class="stat-item">
|
|
422
|
+
<div class="stat-label">📝 Docs</div>
|
|
423
|
+
<div class="stat-value">${getPercentage(summary.workDistribution.docs)}%</div>
|
|
424
|
+
</div>
|
|
425
|
+
<div class="stat-item">
|
|
426
|
+
<div class="stat-label">🔧 Refactors</div>
|
|
427
|
+
<div class="stat-value">${getPercentage(summary.workDistribution.refactors)}%</div>
|
|
428
|
+
</div>
|
|
429
|
+
</div>
|
|
430
|
+
<p class="subtitle">${summary.workDistribution.features > summary.workDistribution.fixes ? 'Feature-focused year! ✨' : 'Bug-squashing hero! 🦸'}</p>
|
|
431
|
+
`
|
|
432
|
+
});
|
|
433
|
+
// Slide 8: Collaboration
|
|
434
|
+
let collabHTML = '';
|
|
435
|
+
if (summary.collaboration.uniqueContributors === 0) {
|
|
436
|
+
collabHTML = `
|
|
437
|
+
<h2>Solo developer!</h2>
|
|
438
|
+
<div class="big-number">🚀</div>
|
|
439
|
+
<p class="subtitle">Building independently</p>
|
|
440
|
+
`;
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
const topCollabPart = summary.collaboration.topCollaborator ? `
|
|
444
|
+
<p class="subtitle">Most commits with:</p>
|
|
445
|
+
<p class="subtitle"><strong>${summary.collaboration.topCollaborator.name}</strong> (${summary.collaboration.topCollaborator.sharedDays} shared days)</p>
|
|
446
|
+
` : '';
|
|
447
|
+
collabHTML = `
|
|
448
|
+
<h2>You worked alongside:</h2>
|
|
449
|
+
<div class="big-number">${summary.collaboration.uniqueContributors}</div>
|
|
450
|
+
<h2>Developer${summary.collaboration.uniqueContributors === 1 ? '' : 's'}</h2>
|
|
451
|
+
${topCollabPart}
|
|
452
|
+
<p class="subtitle">Great teamwork! 🤝</p>
|
|
453
|
+
`;
|
|
454
|
+
}
|
|
455
|
+
slides.push({ html: collabHTML });
|
|
456
|
+
// Slide 9: Quality
|
|
457
|
+
const stars = '⭐'.repeat(Math.min(5, Math.ceil(summary.quality.overallScore / 2)));
|
|
458
|
+
slides.push({
|
|
459
|
+
html: `
|
|
460
|
+
<h2>Commit quality score:</h2>
|
|
461
|
+
<div class="big-number">${summary.quality.overallScore.toFixed(1)}</div>
|
|
462
|
+
<h2>/ 10</h2>
|
|
463
|
+
<div class="big-number">${stars}</div>
|
|
464
|
+
<p class="subtitle">${summary.quality.conventionalPercentage.toFixed(0)}% conventional commits</p>
|
|
465
|
+
<p class="subtitle">Well documented code! 📚</p>
|
|
466
|
+
`
|
|
467
|
+
});
|
|
468
|
+
// Slide 10: Fun Facts
|
|
469
|
+
let factsHTML = '<ul class="fun-facts">';
|
|
470
|
+
if (summary.funFacts.holidayCommits.length > 0) {
|
|
471
|
+
factsHTML += `<li><span class="emoji">🎄</span> Committed on ${summary.funFacts.holidayCommits.join(', ')}</li>`;
|
|
472
|
+
}
|
|
473
|
+
if (summary.funFacts.midnightCommits > 0) {
|
|
474
|
+
factsHTML += `<li><span class="emoji">🌙</span> ${summary.funFacts.midnightCommits} commit${summary.funFacts.midnightCommits === 1 ? '' : 's'} past midnight</li>`;
|
|
475
|
+
}
|
|
476
|
+
if (summary.funFacts.weekendCommits > 0) {
|
|
477
|
+
factsHTML += `<li><span class="emoji">🎉</span> ${summary.funFacts.weekendCommits} weekend commit${summary.funFacts.weekendCommits === 1 ? '' : 's'}</li>`;
|
|
478
|
+
}
|
|
479
|
+
if (summary.funFacts.busiestDay) {
|
|
480
|
+
const dateStr = new Date(summary.funFacts.busiestDay.date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
|
481
|
+
factsHTML += `<li><span class="emoji">📅</span> Busiest day: ${dateStr} (${summary.funFacts.busiestDay.commits} commits)</li>`;
|
|
482
|
+
}
|
|
483
|
+
factsHTML += '</ul><p class="subtitle">Dedication level: 💯</p>';
|
|
484
|
+
slides.push({
|
|
485
|
+
html: `
|
|
486
|
+
<h2>Fun facts about your year:</h2>
|
|
487
|
+
${factsHTML}
|
|
488
|
+
`
|
|
489
|
+
});
|
|
490
|
+
// Slide 11: Achievements
|
|
491
|
+
if (summary.achievements.total > 0) {
|
|
492
|
+
const topBadges = summary.achievements.earned.slice(0, 5);
|
|
493
|
+
const badgesHTML = topBadges.map(badge => `<div class="badge-item">✨ ${badge}</div>`).join('');
|
|
494
|
+
slides.push({
|
|
495
|
+
html: `
|
|
496
|
+
<h2>Badges earned in ${summary.year}:</h2>
|
|
497
|
+
<div class="badges-grid">${badgesHTML}</div>
|
|
498
|
+
<p class="subtitle">${summary.achievements.total} total achievement${summary.achievements.total === 1 ? '' : 's'}! 🏆</p>
|
|
499
|
+
`
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
// Slide 12: Comparison (if available)
|
|
503
|
+
if (summary.comparison) {
|
|
504
|
+
const { comparison } = summary;
|
|
505
|
+
const getTrendArrow = (trend) => trend === 'up' ? '<span class="trend-up">↑</span>' : trend === 'down' ? '<span class="trend-down">↓</span>' : '→';
|
|
506
|
+
slides.push({
|
|
507
|
+
html: `
|
|
508
|
+
<h2>${summary.year} vs ${summary.year - 1}:</h2>
|
|
509
|
+
<div class="comparison-row">
|
|
510
|
+
<span>Commits:</span>
|
|
511
|
+
<span>${comparison.currentYear.commits} ${getTrendArrow(comparison.changes.commits.trend)} ${Math.abs(comparison.changes.commits.percentage).toFixed(0)}%</span>
|
|
512
|
+
</div>
|
|
513
|
+
<div class="comparison-row">
|
|
514
|
+
<span>Streak:</span>
|
|
515
|
+
<span>${comparison.currentYear.streak} days ${getTrendArrow(comparison.changes.streak.trend)}</span>
|
|
516
|
+
</div>
|
|
517
|
+
<div class="comparison-row">
|
|
518
|
+
<span>Quality:</span>
|
|
519
|
+
<span>${comparison.currentYear.quality.toFixed(1)} ${getTrendArrow(comparison.changes.quality.trend)}</span>
|
|
520
|
+
</div>
|
|
521
|
+
<p class="subtitle">${comparison.insights[0] || 'Keep up the great work!'}</p>
|
|
522
|
+
`
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
// Slide 13: Finale
|
|
526
|
+
slides.push({
|
|
527
|
+
html: `
|
|
528
|
+
<h1>That's your ${summary.year}!</h1>
|
|
529
|
+
<div class="stats-grid">
|
|
530
|
+
<div class="stat-item">
|
|
531
|
+
<div class="stat-value">${summary.overview.totalCommits.toLocaleString()}</div>
|
|
532
|
+
<div class="stat-label">commits</div>
|
|
533
|
+
</div>
|
|
534
|
+
<div class="stat-item">
|
|
535
|
+
<div class="stat-value">${summary.streaks.longest.days}</div>
|
|
536
|
+
<div class="stat-label">day streak</div>
|
|
537
|
+
</div>
|
|
538
|
+
<div class="stat-item">
|
|
539
|
+
<div class="stat-value">${summary.collaboration.uniqueContributors}</div>
|
|
540
|
+
<div class="stat-label">collaborator${summary.collaboration.uniqueContributors === 1 ? '' : 's'}</div>
|
|
541
|
+
</div>
|
|
542
|
+
<div class="stat-item">
|
|
543
|
+
<div class="stat-value">${summary.achievements.total}</div>
|
|
544
|
+
<div class="stat-label">achievement${summary.achievements.total === 1 ? '' : 's'}</div>
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
<h2>Here's to ${summary.year + 1}! 🚀</h2>
|
|
548
|
+
<p class="subtitle">#YearInCode #DevLife</p>
|
|
549
|
+
`
|
|
550
|
+
});
|
|
551
|
+
return slides;
|
|
552
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "repo-wrapped",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"description": "A tool to generate Git repository analytics and visualizations in CLI or HTML.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc && shx cp -r src/generators/html/styles dist/generators/html/ && shx cp -r src/generators/html/scripts dist/generators/html/",
|
|
8
|
+
"start": "node dist/index.js",
|
|
9
|
+
"prepublishOnly": "npm run build",
|
|
10
|
+
"test": "echo \"No tests specified\" && exit 0",
|
|
11
|
+
"release:patch": "npm version patch && npm publish",
|
|
12
|
+
"release:minor": "npm version minor && npm publish",
|
|
13
|
+
"release:major": "npm version major && npm publish",
|
|
14
|
+
"release:dry": "npm publish --dry-run",
|
|
15
|
+
"preversion": "npm run build",
|
|
16
|
+
"postversion": "git push && git push --tags"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"repo-wrapped": "dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"chalk": "^4.1.2",
|
|
23
|
+
"commander": "^9.0.0",
|
|
24
|
+
"date-fns": "^4.1.0",
|
|
25
|
+
"ora": "^5.4.1"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^14.0.0",
|
|
29
|
+
"shx": "^0.4.0",
|
|
30
|
+
"typescript": "^4.0.0"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"cli",
|
|
34
|
+
"git",
|
|
35
|
+
"commit",
|
|
36
|
+
"matrix",
|
|
37
|
+
"visualization",
|
|
38
|
+
"gitlab",
|
|
39
|
+
"contributions",
|
|
40
|
+
"analytics"
|
|
41
|
+
],
|
|
42
|
+
"author": "Kevin Hahn",
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://gitlab.com/hahn-ai/repo-wrapped.git"
|
|
47
|
+
},
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://gitlab.com/hahn-ai/repo-wrapped/issues"
|
|
50
|
+
},
|
|
51
|
+
"homepage": "https://gitlab.com/hahn-ai/git-wrapped#readme",
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=22.0.0"
|
|
54
|
+
}
|
|
55
|
+
}
|