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,433 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCategoryLabel = exports.getTierColor = exports.ACHIEVEMENTS = void 0;
|
|
4
|
+
exports.ACHIEVEMENTS = [
|
|
5
|
+
// ========================================
|
|
6
|
+
// 🎯 MILESTONE BADGES
|
|
7
|
+
// ========================================
|
|
8
|
+
{
|
|
9
|
+
id: 'first-steps',
|
|
10
|
+
name: 'First Steps',
|
|
11
|
+
emoji: '🌱',
|
|
12
|
+
description: 'Made your first commit',
|
|
13
|
+
category: 'milestone',
|
|
14
|
+
tier: 'bronze',
|
|
15
|
+
criteria: { type: 'commit-count', threshold: 1, comparator: '>=' },
|
|
16
|
+
progress: 0,
|
|
17
|
+
isUnlocked: false,
|
|
18
|
+
isSecret: false
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: 'getting-started',
|
|
22
|
+
name: 'Getting Started',
|
|
23
|
+
emoji: '⭐',
|
|
24
|
+
description: 'Reached 10 commits',
|
|
25
|
+
category: 'milestone',
|
|
26
|
+
tier: 'bronze',
|
|
27
|
+
criteria: { type: 'commit-count', threshold: 10, comparator: '>=' },
|
|
28
|
+
progress: 0,
|
|
29
|
+
isUnlocked: false,
|
|
30
|
+
isSecret: false
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'on-fire',
|
|
34
|
+
name: 'On Fire',
|
|
35
|
+
emoji: '🔥',
|
|
36
|
+
description: 'Reached 50 commits',
|
|
37
|
+
category: 'milestone',
|
|
38
|
+
tier: 'silver',
|
|
39
|
+
criteria: { type: 'commit-count', threshold: 50, comparator: '>=' },
|
|
40
|
+
progress: 0,
|
|
41
|
+
isUnlocked: false,
|
|
42
|
+
isSecret: false
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: 'century-club',
|
|
46
|
+
name: 'Century Club',
|
|
47
|
+
emoji: '👑',
|
|
48
|
+
description: 'Reached 100 commits',
|
|
49
|
+
category: 'milestone',
|
|
50
|
+
tier: 'gold',
|
|
51
|
+
criteria: { type: 'commit-count', threshold: 100, comparator: '>=' },
|
|
52
|
+
progress: 0,
|
|
53
|
+
isUnlocked: false,
|
|
54
|
+
isSecret: false
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: 'champion',
|
|
58
|
+
name: 'Champion',
|
|
59
|
+
emoji: '🏆',
|
|
60
|
+
description: 'Reached 500 commits',
|
|
61
|
+
category: 'milestone',
|
|
62
|
+
tier: 'platinum',
|
|
63
|
+
criteria: { type: 'commit-count', threshold: 500, comparator: '>=' },
|
|
64
|
+
progress: 0,
|
|
65
|
+
isUnlocked: false,
|
|
66
|
+
isSecret: false
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'master',
|
|
70
|
+
name: 'Master',
|
|
71
|
+
emoji: '💫',
|
|
72
|
+
description: 'Reached 1000 commits',
|
|
73
|
+
category: 'milestone',
|
|
74
|
+
tier: 'platinum',
|
|
75
|
+
criteria: { type: 'commit-count', threshold: 1000, comparator: '>=' },
|
|
76
|
+
progress: 0,
|
|
77
|
+
isUnlocked: false,
|
|
78
|
+
isSecret: false
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'legend',
|
|
82
|
+
name: 'Legend',
|
|
83
|
+
emoji: '🌟',
|
|
84
|
+
description: 'Reached 5000 commits',
|
|
85
|
+
category: 'milestone',
|
|
86
|
+
tier: 'legendary',
|
|
87
|
+
criteria: { type: 'commit-count', threshold: 5000, comparator: '>=' },
|
|
88
|
+
progress: 0,
|
|
89
|
+
isUnlocked: false,
|
|
90
|
+
isSecret: false
|
|
91
|
+
},
|
|
92
|
+
// ========================================
|
|
93
|
+
// 📅 STREAK ACHIEVEMENTS
|
|
94
|
+
// ========================================
|
|
95
|
+
{
|
|
96
|
+
id: 'consistency',
|
|
97
|
+
name: 'Consistency',
|
|
98
|
+
emoji: '📅',
|
|
99
|
+
description: 'Maintained a 3-day commit streak',
|
|
100
|
+
category: 'time',
|
|
101
|
+
tier: 'bronze',
|
|
102
|
+
criteria: { type: 'streak', threshold: 3, comparator: '>=' },
|
|
103
|
+
progress: 0,
|
|
104
|
+
isUnlocked: false,
|
|
105
|
+
isSecret: false
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: 'week-warrior',
|
|
109
|
+
name: 'Week Warrior',
|
|
110
|
+
emoji: '⚡',
|
|
111
|
+
description: 'Maintained a 7-day commit streak',
|
|
112
|
+
category: 'time',
|
|
113
|
+
tier: 'silver',
|
|
114
|
+
criteria: { type: 'streak', threshold: 7, comparator: '>=' },
|
|
115
|
+
progress: 0,
|
|
116
|
+
isUnlocked: false,
|
|
117
|
+
isSecret: false
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
id: 'streak-on-fire',
|
|
121
|
+
name: 'Streak On Fire',
|
|
122
|
+
emoji: '🔥',
|
|
123
|
+
description: 'Maintained a 14-day commit streak',
|
|
124
|
+
category: 'time',
|
|
125
|
+
tier: 'gold',
|
|
126
|
+
criteria: { type: 'streak', threshold: 14, comparator: '>=' },
|
|
127
|
+
progress: 0,
|
|
128
|
+
isUnlocked: false,
|
|
129
|
+
isSecret: false
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
id: 'unstoppable',
|
|
133
|
+
name: 'Unstoppable',
|
|
134
|
+
emoji: '💪',
|
|
135
|
+
description: 'Maintained a 30-day commit streak',
|
|
136
|
+
category: 'time',
|
|
137
|
+
tier: 'platinum',
|
|
138
|
+
criteria: { type: 'streak', threshold: 30, comparator: '>=' },
|
|
139
|
+
progress: 0,
|
|
140
|
+
isUnlocked: false,
|
|
141
|
+
isSecret: false
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: 'iron-will',
|
|
145
|
+
name: 'Iron Will',
|
|
146
|
+
emoji: '🦾',
|
|
147
|
+
description: 'Maintained a 60-day commit streak',
|
|
148
|
+
category: 'time',
|
|
149
|
+
tier: 'platinum',
|
|
150
|
+
criteria: { type: 'streak', threshold: 60, comparator: '>=' },
|
|
151
|
+
progress: 0,
|
|
152
|
+
isUnlocked: false,
|
|
153
|
+
isSecret: false
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
id: 'code-machine',
|
|
157
|
+
name: 'Code Machine',
|
|
158
|
+
emoji: '👾',
|
|
159
|
+
description: 'Maintained a 100-day commit streak',
|
|
160
|
+
category: 'time',
|
|
161
|
+
tier: 'legendary',
|
|
162
|
+
criteria: { type: 'streak', threshold: 100, comparator: '>=' },
|
|
163
|
+
progress: 0,
|
|
164
|
+
isUnlocked: false,
|
|
165
|
+
isSecret: false
|
|
166
|
+
},
|
|
167
|
+
// ========================================
|
|
168
|
+
// 🎨 QUALITY BADGES
|
|
169
|
+
// ========================================
|
|
170
|
+
{
|
|
171
|
+
id: 'clean-coder',
|
|
172
|
+
name: 'Clean Coder',
|
|
173
|
+
emoji: '✨',
|
|
174
|
+
description: '90%+ commits follow conventions',
|
|
175
|
+
category: 'quality',
|
|
176
|
+
tier: 'gold',
|
|
177
|
+
criteria: {
|
|
178
|
+
type: 'custom',
|
|
179
|
+
customCheck: (data) => data.commitQuality.conventionalCommits.adherence >= 90
|
|
180
|
+
},
|
|
181
|
+
progress: 0,
|
|
182
|
+
isUnlocked: false,
|
|
183
|
+
isSecret: false
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: 'documentarian',
|
|
187
|
+
name: 'Documentarian',
|
|
188
|
+
emoji: '📝',
|
|
189
|
+
description: '50%+ commits include detailed bodies',
|
|
190
|
+
category: 'quality',
|
|
191
|
+
tier: 'silver',
|
|
192
|
+
criteria: {
|
|
193
|
+
type: 'custom',
|
|
194
|
+
customCheck: (data) => (data.commitQuality.bodyQuality.withBody / data.totalCommits) >= 0.5
|
|
195
|
+
},
|
|
196
|
+
progress: 0,
|
|
197
|
+
isUnlocked: false,
|
|
198
|
+
isSecret: false
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: 'quality-champion',
|
|
202
|
+
name: 'Quality Champion',
|
|
203
|
+
emoji: '🏅',
|
|
204
|
+
description: 'Achieved 9.0+ overall quality score',
|
|
205
|
+
category: 'quality',
|
|
206
|
+
tier: 'platinum',
|
|
207
|
+
criteria: { type: 'quality-score', threshold: 9.0, comparator: '>=' },
|
|
208
|
+
progress: 0,
|
|
209
|
+
isUnlocked: false,
|
|
210
|
+
isSecret: false
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: 'rising-star',
|
|
214
|
+
name: 'Rising Star',
|
|
215
|
+
emoji: '📈',
|
|
216
|
+
description: 'Quality score above 7.0',
|
|
217
|
+
category: 'quality',
|
|
218
|
+
tier: 'gold',
|
|
219
|
+
criteria: { type: 'quality-score', threshold: 7.0, comparator: '>=' },
|
|
220
|
+
progress: 0,
|
|
221
|
+
isUnlocked: false,
|
|
222
|
+
isSecret: false
|
|
223
|
+
},
|
|
224
|
+
// ========================================
|
|
225
|
+
// ⏰ TIME PATTERN BADGES
|
|
226
|
+
// ========================================
|
|
227
|
+
{
|
|
228
|
+
id: 'early-bird',
|
|
229
|
+
name: 'Early Bird',
|
|
230
|
+
emoji: '🌅',
|
|
231
|
+
description: '50+ commits before 9am',
|
|
232
|
+
category: 'time',
|
|
233
|
+
tier: 'silver',
|
|
234
|
+
criteria: {
|
|
235
|
+
type: 'time-pattern',
|
|
236
|
+
customCheck: (data) => {
|
|
237
|
+
const earlyCommits = data.commits.filter(c => {
|
|
238
|
+
const hour = new Date(c.date).getHours();
|
|
239
|
+
return hour >= 5 && hour < 9;
|
|
240
|
+
}).length;
|
|
241
|
+
return earlyCommits >= 50;
|
|
242
|
+
}
|
|
243
|
+
},
|
|
244
|
+
progress: 0,
|
|
245
|
+
isUnlocked: false,
|
|
246
|
+
isSecret: false
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: 'night-owl',
|
|
250
|
+
name: 'Night Owl',
|
|
251
|
+
emoji: '🦉',
|
|
252
|
+
description: '50+ commits after 9pm',
|
|
253
|
+
category: 'time',
|
|
254
|
+
tier: 'silver',
|
|
255
|
+
criteria: {
|
|
256
|
+
type: 'time-pattern',
|
|
257
|
+
customCheck: (data) => {
|
|
258
|
+
const lateCommits = data.commits.filter(c => {
|
|
259
|
+
const hour = new Date(c.date).getHours();
|
|
260
|
+
return hour >= 21 || hour < 5;
|
|
261
|
+
}).length;
|
|
262
|
+
return lateCommits >= 50;
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
progress: 0,
|
|
266
|
+
isUnlocked: false,
|
|
267
|
+
isSecret: false
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
id: 'balanced-soul',
|
|
271
|
+
name: 'Balanced Soul',
|
|
272
|
+
emoji: '⚖️',
|
|
273
|
+
description: 'Even distribution across all time periods',
|
|
274
|
+
category: 'time',
|
|
275
|
+
tier: 'gold',
|
|
276
|
+
criteria: {
|
|
277
|
+
type: 'custom',
|
|
278
|
+
customCheck: (data) => data.timePattern.chronotype === 'balanced'
|
|
279
|
+
},
|
|
280
|
+
progress: 0,
|
|
281
|
+
isUnlocked: false,
|
|
282
|
+
isSecret: false
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: 'work-life-balance',
|
|
286
|
+
name: 'Balanced Life',
|
|
287
|
+
emoji: '🌴',
|
|
288
|
+
description: 'Excellent work-life balance score',
|
|
289
|
+
category: 'time',
|
|
290
|
+
tier: 'gold',
|
|
291
|
+
criteria: {
|
|
292
|
+
type: 'custom',
|
|
293
|
+
customCheck: (data) => data.timePattern.workLifeBalance.score >= 4
|
|
294
|
+
},
|
|
295
|
+
progress: 0,
|
|
296
|
+
isUnlocked: false,
|
|
297
|
+
isSecret: false
|
|
298
|
+
},
|
|
299
|
+
// ========================================
|
|
300
|
+
// 🎮 SPECIAL ACHIEVEMENTS
|
|
301
|
+
// ========================================
|
|
302
|
+
{
|
|
303
|
+
id: 'midnight-coder',
|
|
304
|
+
name: 'Midnight Coder',
|
|
305
|
+
emoji: '🎆',
|
|
306
|
+
description: 'Committed at exactly midnight',
|
|
307
|
+
category: 'special',
|
|
308
|
+
tier: 'silver',
|
|
309
|
+
criteria: {
|
|
310
|
+
type: 'custom',
|
|
311
|
+
customCheck: (data) => data.commits.some(c => {
|
|
312
|
+
const date = new Date(c.date);
|
|
313
|
+
return date.getHours() === 0 && date.getMinutes() === 0;
|
|
314
|
+
})
|
|
315
|
+
},
|
|
316
|
+
progress: 0,
|
|
317
|
+
isUnlocked: false,
|
|
318
|
+
isSecret: true
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
id: 'lucky-seven',
|
|
322
|
+
name: 'Lucky Seven',
|
|
323
|
+
emoji: '🎰',
|
|
324
|
+
description: 'Reached exactly 777 commits',
|
|
325
|
+
category: 'special',
|
|
326
|
+
tier: 'gold',
|
|
327
|
+
criteria: { type: 'commit-count', threshold: 777, comparator: '==' },
|
|
328
|
+
progress: 0,
|
|
329
|
+
isUnlocked: false,
|
|
330
|
+
isSecret: true
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
id: 'binary-master',
|
|
334
|
+
name: 'Binary Master',
|
|
335
|
+
emoji: '🔢',
|
|
336
|
+
description: 'Reached exactly 1024 commits',
|
|
337
|
+
category: 'special',
|
|
338
|
+
tier: 'platinum',
|
|
339
|
+
criteria: { type: 'commit-count', threshold: 1024, comparator: '==' },
|
|
340
|
+
progress: 0,
|
|
341
|
+
isUnlocked: false,
|
|
342
|
+
isSecret: true
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
id: 'weekend-warrior',
|
|
346
|
+
name: 'Weekend Warrior',
|
|
347
|
+
emoji: '🎪',
|
|
348
|
+
description: '20%+ commits on weekends',
|
|
349
|
+
category: 'special',
|
|
350
|
+
tier: 'silver',
|
|
351
|
+
criteria: {
|
|
352
|
+
type: 'custom',
|
|
353
|
+
customCheck: (data) => {
|
|
354
|
+
const weekendRatio = data.timePattern.workLifeBalance.weekendCommits / data.totalCommits;
|
|
355
|
+
return weekendRatio >= 0.2;
|
|
356
|
+
}
|
|
357
|
+
},
|
|
358
|
+
progress: 0,
|
|
359
|
+
isUnlocked: false,
|
|
360
|
+
isSecret: false
|
|
361
|
+
},
|
|
362
|
+
// ========================================
|
|
363
|
+
// 🏆 META ACHIEVEMENTS
|
|
364
|
+
// ========================================
|
|
365
|
+
{
|
|
366
|
+
id: 'collector',
|
|
367
|
+
name: 'Collector',
|
|
368
|
+
emoji: '🎖️',
|
|
369
|
+
description: 'Earned 10 badges',
|
|
370
|
+
category: 'meta',
|
|
371
|
+
tier: 'silver',
|
|
372
|
+
criteria: {
|
|
373
|
+
type: 'custom',
|
|
374
|
+
customCheck: (data) => false // Will be checked separately
|
|
375
|
+
},
|
|
376
|
+
progress: 0,
|
|
377
|
+
isUnlocked: false,
|
|
378
|
+
isSecret: false
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
id: 'completionist',
|
|
382
|
+
name: 'Completionist',
|
|
383
|
+
emoji: '🏅',
|
|
384
|
+
description: 'Earned 25 badges',
|
|
385
|
+
category: 'meta',
|
|
386
|
+
tier: 'gold',
|
|
387
|
+
criteria: {
|
|
388
|
+
type: 'custom',
|
|
389
|
+
customCheck: (data) => false // Will be checked separately
|
|
390
|
+
},
|
|
391
|
+
progress: 0,
|
|
392
|
+
isUnlocked: false,
|
|
393
|
+
isSecret: false
|
|
394
|
+
},
|
|
395
|
+
{
|
|
396
|
+
id: 'badge-king',
|
|
397
|
+
name: 'Badge King',
|
|
398
|
+
emoji: '👑',
|
|
399
|
+
description: 'Earned 50 badges',
|
|
400
|
+
category: 'meta',
|
|
401
|
+
tier: 'legendary',
|
|
402
|
+
criteria: {
|
|
403
|
+
type: 'custom',
|
|
404
|
+
customCheck: (data) => false // Will be checked separately
|
|
405
|
+
},
|
|
406
|
+
progress: 0,
|
|
407
|
+
isUnlocked: false,
|
|
408
|
+
isSecret: false
|
|
409
|
+
}
|
|
410
|
+
];
|
|
411
|
+
function getTierColor(tier) {
|
|
412
|
+
switch (tier) {
|
|
413
|
+
case 'bronze': return '#cd7f32';
|
|
414
|
+
case 'silver': return '#c0c0c0';
|
|
415
|
+
case 'gold': return '#ffd700';
|
|
416
|
+
case 'platinum': return '#e5e4e2';
|
|
417
|
+
case 'legendary': return '#ff6b6b';
|
|
418
|
+
default: return '#8b949e';
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
exports.getTierColor = getTierColor;
|
|
422
|
+
function getCategoryLabel(category) {
|
|
423
|
+
switch (category) {
|
|
424
|
+
case 'milestone': return '🎯 Milestone';
|
|
425
|
+
case 'quality': return '🎨 Quality';
|
|
426
|
+
case 'collaboration': return '🤝 Collaboration';
|
|
427
|
+
case 'time': return '⏰ Time & Consistency';
|
|
428
|
+
case 'special': return '🎮 Special';
|
|
429
|
+
case 'meta': return '🏆 Meta';
|
|
430
|
+
default: return category;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
exports.getCategoryLabel = getCategoryLabel;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getNextMilestone = exports.getAchievementsByCategory = exports.getRecentAchievements = exports.checkAchievements = void 0;
|
|
4
|
+
const achievementDefinitions_1 = require("./achievementDefinitions");
|
|
5
|
+
function checkAchievements(data) {
|
|
6
|
+
// Create a deep copy of achievements to avoid mutation
|
|
7
|
+
const achievements = achievementDefinitions_1.ACHIEVEMENTS.map(a => ({ ...a }));
|
|
8
|
+
const newlyEarned = [];
|
|
9
|
+
// Check each achievement
|
|
10
|
+
for (const achievement of achievements) {
|
|
11
|
+
if (!achievement.isUnlocked) {
|
|
12
|
+
// Calculate progress
|
|
13
|
+
achievement.progress = calculateProgress(achievement, data);
|
|
14
|
+
// Check if criteria is met
|
|
15
|
+
if (meetsCriteria(achievement, data)) {
|
|
16
|
+
achievement.isUnlocked = true;
|
|
17
|
+
achievement.earnedDate = new Date();
|
|
18
|
+
newlyEarned.push(achievement);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
// Already unlocked, progress is 100
|
|
23
|
+
achievement.progress = 100;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Check meta achievements (based on unlocked count)
|
|
27
|
+
const unlockedCount = achievements.filter(a => a.isUnlocked && a.category !== 'meta').length;
|
|
28
|
+
const metaAchievements = achievements.filter(a => a.category === 'meta');
|
|
29
|
+
for (const metaAch of metaAchievements) {
|
|
30
|
+
if (!metaAch.isUnlocked) {
|
|
31
|
+
if (metaAch.id === 'collector' && unlockedCount >= 10) {
|
|
32
|
+
metaAch.isUnlocked = true;
|
|
33
|
+
metaAch.earnedDate = new Date();
|
|
34
|
+
metaAch.progress = 100;
|
|
35
|
+
newlyEarned.push(metaAch);
|
|
36
|
+
}
|
|
37
|
+
else if (metaAch.id === 'completionist' && unlockedCount >= 25) {
|
|
38
|
+
metaAch.isUnlocked = true;
|
|
39
|
+
metaAch.earnedDate = new Date();
|
|
40
|
+
metaAch.progress = 100;
|
|
41
|
+
newlyEarned.push(metaAch);
|
|
42
|
+
}
|
|
43
|
+
else if (metaAch.id === 'badge-king' && unlockedCount >= 50) {
|
|
44
|
+
metaAch.isUnlocked = true;
|
|
45
|
+
metaAch.earnedDate = new Date();
|
|
46
|
+
metaAch.progress = 100;
|
|
47
|
+
newlyEarned.push(metaAch);
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// Calculate meta progress
|
|
51
|
+
if (metaAch.id === 'collector') {
|
|
52
|
+
metaAch.progress = Math.min(100, (unlockedCount / 10) * 100);
|
|
53
|
+
}
|
|
54
|
+
else if (metaAch.id === 'completionist') {
|
|
55
|
+
metaAch.progress = Math.min(100, (unlockedCount / 25) * 100);
|
|
56
|
+
}
|
|
57
|
+
else if (metaAch.id === 'badge-king') {
|
|
58
|
+
metaAch.progress = Math.min(100, (unlockedCount / 50) * 100);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Find next milestones (top 3 closest to unlocking)
|
|
64
|
+
const lockedAchievements = achievements.filter(a => !a.isUnlocked);
|
|
65
|
+
const nextMilestones = lockedAchievements
|
|
66
|
+
.sort((a, b) => b.progress - a.progress)
|
|
67
|
+
.slice(0, 3);
|
|
68
|
+
const totalUnlocked = achievements.filter(a => a.isUnlocked).length;
|
|
69
|
+
const completionPercentage = (totalUnlocked / achievements.length) * 100;
|
|
70
|
+
return {
|
|
71
|
+
achievements,
|
|
72
|
+
unlockedCount: totalUnlocked,
|
|
73
|
+
totalCount: achievements.length,
|
|
74
|
+
recentlyEarned: newlyEarned,
|
|
75
|
+
nextMilestones,
|
|
76
|
+
completionPercentage
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
exports.checkAchievements = checkAchievements;
|
|
80
|
+
function meetsCriteria(achievement, data) {
|
|
81
|
+
const { type, threshold, comparator, customCheck } = achievement.criteria;
|
|
82
|
+
// Handle custom criteria
|
|
83
|
+
if (customCheck) {
|
|
84
|
+
return customCheck(data);
|
|
85
|
+
}
|
|
86
|
+
// Handle standard criteria
|
|
87
|
+
if (threshold === undefined || comparator === undefined) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
const value = extractValue(data, type);
|
|
91
|
+
return compare(value, threshold, comparator);
|
|
92
|
+
}
|
|
93
|
+
function calculateProgress(achievement, data) {
|
|
94
|
+
const { type, threshold, customCheck } = achievement.criteria;
|
|
95
|
+
// Custom checks don't have numeric progress
|
|
96
|
+
if (customCheck) {
|
|
97
|
+
// Try to calculate progress for known patterns
|
|
98
|
+
if (achievement.id === 'early-bird' || achievement.id === 'night-owl') {
|
|
99
|
+
const isEarlyBird = achievement.id === 'early-bird';
|
|
100
|
+
const targetCommits = data.commits.filter(c => {
|
|
101
|
+
const hour = new Date(c.date).getHours();
|
|
102
|
+
if (isEarlyBird) {
|
|
103
|
+
return hour >= 5 && hour < 9;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
return hour >= 21 || hour < 5;
|
|
107
|
+
}
|
|
108
|
+
}).length;
|
|
109
|
+
return Math.min(100, (targetCommits / 50) * 100);
|
|
110
|
+
}
|
|
111
|
+
// For other custom checks, return 0 or 100
|
|
112
|
+
return meetsCriteria(achievement, data) ? 100 : 0;
|
|
113
|
+
}
|
|
114
|
+
if (threshold === undefined) {
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
const current = extractValue(data, type);
|
|
118
|
+
return Math.min(100, (current / threshold) * 100);
|
|
119
|
+
}
|
|
120
|
+
function extractValue(data, type) {
|
|
121
|
+
switch (type) {
|
|
122
|
+
case 'commit-count':
|
|
123
|
+
return data.totalCommits;
|
|
124
|
+
case 'streak':
|
|
125
|
+
return data.streakData.longestStreak.days;
|
|
126
|
+
case 'quality-score':
|
|
127
|
+
return data.commitQuality.overallScore;
|
|
128
|
+
default:
|
|
129
|
+
return 0;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function compare(value, threshold, comparator) {
|
|
133
|
+
switch (comparator) {
|
|
134
|
+
case '>=':
|
|
135
|
+
return value >= threshold;
|
|
136
|
+
case '>':
|
|
137
|
+
return value > threshold;
|
|
138
|
+
case '==':
|
|
139
|
+
return value === threshold;
|
|
140
|
+
case '<':
|
|
141
|
+
return value < threshold;
|
|
142
|
+
case '<=':
|
|
143
|
+
return value <= threshold;
|
|
144
|
+
default:
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function getRecentAchievements(progress, days = 7) {
|
|
149
|
+
const cutoffDate = new Date();
|
|
150
|
+
cutoffDate.setDate(cutoffDate.getDate() - days);
|
|
151
|
+
return progress.achievements
|
|
152
|
+
.filter(a => a.isUnlocked && a.earnedDate && a.earnedDate >= cutoffDate)
|
|
153
|
+
.sort((a, b) => {
|
|
154
|
+
if (!a.earnedDate || !b.earnedDate)
|
|
155
|
+
return 0;
|
|
156
|
+
return b.earnedDate.getTime() - a.earnedDate.getTime();
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
exports.getRecentAchievements = getRecentAchievements;
|
|
160
|
+
function getAchievementsByCategory(progress, category) {
|
|
161
|
+
return progress.achievements.filter(a => a.category === category);
|
|
162
|
+
}
|
|
163
|
+
exports.getAchievementsByCategory = getAchievementsByCategory;
|
|
164
|
+
function getNextMilestone(progress) {
|
|
165
|
+
const locked = progress.achievements.filter(a => !a.isUnlocked);
|
|
166
|
+
if (locked.length === 0)
|
|
167
|
+
return null;
|
|
168
|
+
return locked.reduce((closest, current) => current.progress > closest.progress ? current : closest);
|
|
169
|
+
}
|
|
170
|
+
exports.getNextMilestone = getNextMilestone;
|