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,226 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildKnowledgeSection = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Build the Knowledge Distribution section for the dashboard
|
|
6
|
+
*/
|
|
7
|
+
function buildKnowledgeSection(knowledge) {
|
|
8
|
+
const riskClass = `risk-${knowledge.busFactorRisk.level}`;
|
|
9
|
+
const isDeepAnalysis = knowledge.isDeepAnalysis || false;
|
|
10
|
+
return `
|
|
11
|
+
<div class="knowledge-section">
|
|
12
|
+
<h2>Knowledge Distribution ${isDeepAnalysis ? '<span class="deep-analysis-badge">Deep Analysis</span>' : ''}</h2>
|
|
13
|
+
|
|
14
|
+
<div class="bus-factor-summary">
|
|
15
|
+
<div class="risk-indicator ${riskClass}">
|
|
16
|
+
<span class="risk-value">${knowledge.busFactorRisk.overall}</span>
|
|
17
|
+
<span class="risk-scale">/10</span>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="risk-meta">
|
|
20
|
+
<span class="risk-label">Bus Factor Risk</span>
|
|
21
|
+
<span class="risk-level">${formatRiskLevel(knowledge.busFactorRisk.level)}</span>
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
${knowledge.directories.length > 0 ? `
|
|
26
|
+
<div class="ownership-table">
|
|
27
|
+
<h3>Directory Ownership</h3>
|
|
28
|
+
<table class="data-table knowledge-table">
|
|
29
|
+
<thead>
|
|
30
|
+
<tr>
|
|
31
|
+
<th ${isDeepAnalysis ? 'class="expandable-header"' : ''}>Directory</th>
|
|
32
|
+
<th>Primary Owner</th>
|
|
33
|
+
<th>Ownership</th>
|
|
34
|
+
<th>Last Activity</th>
|
|
35
|
+
<th>Type</th>
|
|
36
|
+
<th>Risk</th>
|
|
37
|
+
</tr>
|
|
38
|
+
</thead>
|
|
39
|
+
<tbody>
|
|
40
|
+
${knowledge.directories.slice(0, 10).map((dir, idx) => buildDirectoryRow(dir, idx, isDeepAnalysis)).join('')}
|
|
41
|
+
</tbody>
|
|
42
|
+
</table>
|
|
43
|
+
</div>
|
|
44
|
+
` : ''}
|
|
45
|
+
|
|
46
|
+
${knowledge.knowledgeSilos.length > 0 ? `
|
|
47
|
+
<div class="knowledge-warnings">
|
|
48
|
+
<h3>Knowledge Silos</h3>
|
|
49
|
+
<p class="warning-intro">These directories have single owners — consider cross-training:</p>
|
|
50
|
+
<ul class="silo-list">
|
|
51
|
+
${knowledge.knowledgeSilos.map(silo => `<li>${silo}</li>`).join('')}
|
|
52
|
+
</ul>
|
|
53
|
+
</div>
|
|
54
|
+
` : ''}
|
|
55
|
+
|
|
56
|
+
${knowledge.sharedKnowledge.length > 0 ? `
|
|
57
|
+
<div class="shared-knowledge">
|
|
58
|
+
<h3>Well-Distributed Knowledge</h3>
|
|
59
|
+
<p class="success-intro">These areas have good knowledge sharing:</p>
|
|
60
|
+
<ul class="shared-list">
|
|
61
|
+
${knowledge.sharedKnowledge.map(area => `<li>${area}</li>`).join('')}
|
|
62
|
+
</ul>
|
|
63
|
+
</div>
|
|
64
|
+
` : ''}
|
|
65
|
+
|
|
66
|
+
${knowledge.recommendations.length > 0 ? `
|
|
67
|
+
<div class="recommendations">
|
|
68
|
+
<h3>Recommendations</h3>
|
|
69
|
+
<ul>
|
|
70
|
+
${knowledge.recommendations.map(rec => `<li>${rec}</li>`).join('')}
|
|
71
|
+
</ul>
|
|
72
|
+
</div>
|
|
73
|
+
` : ''}
|
|
74
|
+
</div>
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
exports.buildKnowledgeSection = buildKnowledgeSection;
|
|
78
|
+
/**
|
|
79
|
+
* Build a directory row with optional expandable details
|
|
80
|
+
*/
|
|
81
|
+
function buildDirectoryRow(dir, index, isDeepAnalysis) {
|
|
82
|
+
const hasExpandableContent = isDeepAnalysis && dir.highRiskFiles && dir.highRiskFiles.length > 0;
|
|
83
|
+
const knowledgeAge = dir.owners[0]?.knowledgeAge || '—';
|
|
84
|
+
const rowId = `knowledge-row-${index}`;
|
|
85
|
+
// Main directory row
|
|
86
|
+
const mainRow = `
|
|
87
|
+
<tr class="${hasExpandableContent ? 'expandable-row' : ''}" ${hasExpandableContent ? `data-row-id="${rowId}" role="button" aria-expanded="false"` : ''}>
|
|
88
|
+
<td class="dir-path">
|
|
89
|
+
${hasExpandableContent ? '<span class="expand-icon">▶</span>' : ''}
|
|
90
|
+
${dir.path || '/'}
|
|
91
|
+
</td>
|
|
92
|
+
<td>${dir.owners[0]?.author || 'Unknown'}</td>
|
|
93
|
+
<td>
|
|
94
|
+
<div class="ownership-bar-mini">
|
|
95
|
+
<div class="ownership-fill" style="width: ${dir.owners[0]?.percentage || 0}%"></div>
|
|
96
|
+
</div>
|
|
97
|
+
<span class="ownership-percent">${dir.owners[0]?.percentage || 0}%</span>
|
|
98
|
+
</td>
|
|
99
|
+
<td class="last-activity">${knowledgeAge}</td>
|
|
100
|
+
<td><span class="ownership-type type-${dir.ownershipType}">${formatOwnershipType(dir.ownershipType)}</span></td>
|
|
101
|
+
<td><span class="risk-badge risk-${getRiskClass(dir.busFactorRisk)}">${dir.busFactorRisk}</span></td>
|
|
102
|
+
</tr>
|
|
103
|
+
`;
|
|
104
|
+
// Expandable details row (contributor breakdown + file list)
|
|
105
|
+
if (hasExpandableContent) {
|
|
106
|
+
const detailsRow = `
|
|
107
|
+
<tr class="expanded-content hidden" id="${rowId}-content">
|
|
108
|
+
<td colspan="6">
|
|
109
|
+
<div class="expansion-panel">
|
|
110
|
+
${buildContributorBreakdown(dir)}
|
|
111
|
+
${buildFileList(dir.highRiskFiles)}
|
|
112
|
+
</div>
|
|
113
|
+
</td>
|
|
114
|
+
</tr>
|
|
115
|
+
`;
|
|
116
|
+
return mainRow + detailsRow;
|
|
117
|
+
}
|
|
118
|
+
return mainRow;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Build contributor breakdown for expanded row
|
|
122
|
+
*/
|
|
123
|
+
function buildContributorBreakdown(dir) {
|
|
124
|
+
if (!dir.owners || dir.owners.length === 0)
|
|
125
|
+
return '';
|
|
126
|
+
return `
|
|
127
|
+
<div class="contributor-breakdown">
|
|
128
|
+
<h4>Contributors</h4>
|
|
129
|
+
<div class="contributors-list">
|
|
130
|
+
${dir.owners.slice(0, 5).map(owner => `
|
|
131
|
+
<div class="contributor-item">
|
|
132
|
+
<span class="contributor-name">${owner.author}</span>
|
|
133
|
+
<div class="contributor-stats">
|
|
134
|
+
<span class="contributor-commits">${owner.commits} commits</span>
|
|
135
|
+
<span class="contributor-percent">${owner.percentage}%</span>
|
|
136
|
+
<span class="contributor-age" title="Knowledge age">${owner.knowledgeAge || '—'}</span>
|
|
137
|
+
</div>
|
|
138
|
+
<div class="contributor-bar">
|
|
139
|
+
<div class="contributor-fill" style="width: ${owner.percentage}%"></div>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
`).join('')}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Build file list for expanded row (high-risk files only)
|
|
149
|
+
*/
|
|
150
|
+
function buildFileList(files) {
|
|
151
|
+
if (!files || files.length === 0)
|
|
152
|
+
return '';
|
|
153
|
+
return `
|
|
154
|
+
<div class="file-breakdown">
|
|
155
|
+
<h4>High-Risk Files</h4>
|
|
156
|
+
<table class="file-table">
|
|
157
|
+
<thead>
|
|
158
|
+
<tr>
|
|
159
|
+
<th>File</th>
|
|
160
|
+
<th>Primary Owner</th>
|
|
161
|
+
<th>Ownership</th>
|
|
162
|
+
<th>Last Modified</th>
|
|
163
|
+
<th>Risk</th>
|
|
164
|
+
</tr>
|
|
165
|
+
</thead>
|
|
166
|
+
<tbody>
|
|
167
|
+
${files.slice(0, 10).map(file => `
|
|
168
|
+
<tr class="${file.busFactorRisk ? 'high-risk-file' : ''}">
|
|
169
|
+
<td class="file-path" title="${file.path}">${getFileName(file.path)}</td>
|
|
170
|
+
<td class="file-owner">${file.primaryOwner}</td>
|
|
171
|
+
<td>
|
|
172
|
+
<span class="file-ownership">${file.ownershipPercentage}%</span>
|
|
173
|
+
</td>
|
|
174
|
+
<td class="file-age">${file.knowledgeAge}</td>
|
|
175
|
+
<td><span class="risk-badge risk-${file.riskLevel}">${file.riskLevel}</span></td>
|
|
176
|
+
</tr>
|
|
177
|
+
`).join('')}
|
|
178
|
+
</tbody>
|
|
179
|
+
</table>
|
|
180
|
+
${files.length > 10 ? `<p class="more-files">+ ${files.length - 10} more files</p>` : ''}
|
|
181
|
+
</div>
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get just the filename from a path
|
|
186
|
+
*/
|
|
187
|
+
function getFileName(path) {
|
|
188
|
+
const parts = path.replace(/\\/g, '/').split('/');
|
|
189
|
+
return parts[parts.length - 1] || path;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Format risk level for display
|
|
193
|
+
*/
|
|
194
|
+
function formatRiskLevel(level) {
|
|
195
|
+
const labels = {
|
|
196
|
+
critical: 'Critical',
|
|
197
|
+
high: 'High Risk',
|
|
198
|
+
medium: 'Moderate',
|
|
199
|
+
low: 'Low Risk'
|
|
200
|
+
};
|
|
201
|
+
return labels[level] || level;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Format ownership type for display
|
|
205
|
+
*/
|
|
206
|
+
function formatOwnershipType(type) {
|
|
207
|
+
const labels = {
|
|
208
|
+
solo: 'Solo',
|
|
209
|
+
primary: 'Primary',
|
|
210
|
+
shared: 'Shared',
|
|
211
|
+
collaborative: 'Team'
|
|
212
|
+
};
|
|
213
|
+
return labels[type] || type;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Get risk class based on numeric value
|
|
217
|
+
*/
|
|
218
|
+
function getRiskClass(risk) {
|
|
219
|
+
if (risk >= 9)
|
|
220
|
+
return 'critical';
|
|
221
|
+
if (risk >= 6)
|
|
222
|
+
return 'high';
|
|
223
|
+
if (risk >= 3)
|
|
224
|
+
return 'medium';
|
|
225
|
+
return 'low';
|
|
226
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildStreakSection = void 0;
|
|
4
|
+
const date_fns_1 = require("date-fns");
|
|
5
|
+
const streakCalculator_1 = require("../../../utils/streakCalculator");
|
|
6
|
+
function buildStreakSection(streakData) {
|
|
7
|
+
return `
|
|
8
|
+
<div class="streak-section">
|
|
9
|
+
<h2>Streak Analysis</h2>
|
|
10
|
+
<div class="streak-cards">
|
|
11
|
+
<div class="streak-card">
|
|
12
|
+
<div class="streak-icon">${streakData.currentStreak.isActive ? '🔥' : '💤'}</div>
|
|
13
|
+
<div class="streak-info">
|
|
14
|
+
<div class="streak-label">Current Streak</div>
|
|
15
|
+
<div class="streak-value">${streakData.currentStreak.days} day${streakData.currentStreak.days !== 1 ? 's' : ''}</div>
|
|
16
|
+
${streakData.currentStreak.isActive && streakData.currentStreak.days > 0 ? `<div class="streak-motivation">${(0, streakCalculator_1.getStreakMotivation)(streakData.currentStreak.days, true)}</div>` : ''}
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="streak-card">
|
|
20
|
+
<div class="streak-icon">🏆</div>
|
|
21
|
+
<div class="streak-info">
|
|
22
|
+
<div class="streak-label">Longest Streak</div>
|
|
23
|
+
<div class="streak-value">${streakData.longestStreak.days} day${streakData.longestStreak.days !== 1 ? 's' : ''}</div>
|
|
24
|
+
${streakData.longestStreak.days > 0 ? `<div class="streak-date-range">${(0, date_fns_1.format)(streakData.longestStreak.startDate, 'MMM d')} - ${(0, date_fns_1.format)(streakData.longestStreak.endDate, 'MMM d, yyyy')}</div>` : ''}
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="streak-card">
|
|
28
|
+
<div class="streak-icon">📈</div>
|
|
29
|
+
<div class="streak-info">
|
|
30
|
+
<div class="streak-label">Active Days</div>
|
|
31
|
+
<div class="streak-value">${streakData.totalActiveDays}/${streakData.totalDays}</div>
|
|
32
|
+
<div class="active-days-bar">
|
|
33
|
+
<div class="active-days-progress" style="width: ${streakData.activeDayPercentage}%"></div>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="active-days-percentage">${streakData.activeDayPercentage.toFixed(1)}% active</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
`;
|
|
41
|
+
}
|
|
42
|
+
exports.buildStreakSection = buildStreakSection;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildTimePatternsSection = void 0;
|
|
4
|
+
const constants_1 = require("../../../constants");
|
|
5
|
+
const formatters_1 = require("../../../formatters");
|
|
6
|
+
const timePatternAnalyzer_1 = require("../../../utils/timePatternAnalyzer");
|
|
7
|
+
function buildTimePatternsSection(timePattern) {
|
|
8
|
+
// Get chronotype details for dual display
|
|
9
|
+
const chronotypeDetails = getChronotypeTimeRange(timePattern.chronotype);
|
|
10
|
+
return `
|
|
11
|
+
<div class="time-patterns-section">
|
|
12
|
+
<h2>Time Patterns</h2>
|
|
13
|
+
|
|
14
|
+
<div class="patterns-grid">
|
|
15
|
+
<div class="pattern-card">
|
|
16
|
+
<div class="pattern-header">
|
|
17
|
+
<span class="pattern-icon">⏱️</span>
|
|
18
|
+
<span class="pattern-title">Peak Hour</span>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="pattern-value">${(0, formatters_1.formatHour)(timePattern.peakHour.hour)}</div>
|
|
21
|
+
<div class="pattern-detail">${timePattern.peakHour.commitCount} commits</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="pattern-card">
|
|
25
|
+
<div class="pattern-header">
|
|
26
|
+
<span class="pattern-icon">${constants_1.ANALYSIS_CHRONOTYPE_EMOJIS[timePattern.chronotype] || '⚖️'}</span>
|
|
27
|
+
<span class="pattern-title">Chronotype</span>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="chronotype-display">
|
|
30
|
+
<div class="chronotype-label">${(0, timePatternAnalyzer_1.getChronotypeLabel)(timePattern.chronotype)}</div>
|
|
31
|
+
<div class="chronotype-detail">${chronotypeDetails}</div>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="pattern-detail">${timePattern.chronotypeConfidence.toFixed(0)}% confidence</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="pattern-card">
|
|
37
|
+
<div class="pattern-header">
|
|
38
|
+
<span class="pattern-icon">⚖️</span>
|
|
39
|
+
<span class="pattern-title">Work-Life Balance</span>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="pattern-value">${getBalanceScore(timePattern.workLifeBalance.score)}</div>
|
|
42
|
+
<div class="pattern-detail">Weekday: ${timePattern.workLifeBalance.weekdayCommits} | Weekend: ${timePattern.workLifeBalance.weekendCommits}</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div class="pattern-card">
|
|
46
|
+
<div class="pattern-header">
|
|
47
|
+
<span class="pattern-icon">📊</span>
|
|
48
|
+
<span class="pattern-title">Consistency</span>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="pattern-value">${timePattern.consistency.score.toFixed(1)}/10</div>
|
|
51
|
+
<div class="pattern-detail">${formatRegularity(timePattern.consistency.regularity)}</div>
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
${timePattern.burnoutRisk.level !== 'low' ? `
|
|
56
|
+
<div class="wellbeing-note">
|
|
57
|
+
<div class="wellbeing-header">
|
|
58
|
+
<span class="wellbeing-icon">💚</span>
|
|
59
|
+
<span class="wellbeing-title">Pace Advisory: ${formatRiskLevel(timePattern.burnoutRisk.level)}</span>
|
|
60
|
+
</div>
|
|
61
|
+
<ul class="wellbeing-indicators">
|
|
62
|
+
${timePattern.burnoutRisk.indicators.map((indicator) => `<li>${indicator}</li>`).join('')}
|
|
63
|
+
</ul>
|
|
64
|
+
<div class="wellbeing-message">
|
|
65
|
+
Sustainable pace matters. These patterns are informational, not prescriptive.
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
` : ''}
|
|
69
|
+
|
|
70
|
+
<div class="chronotype-insight">
|
|
71
|
+
${(0, timePatternAnalyzer_1.getChronotypeDescription)(timePattern.chronotype)}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
`;
|
|
75
|
+
}
|
|
76
|
+
exports.buildTimePatternsSection = buildTimePatternsSection;
|
|
77
|
+
function getChronotypeTimeRange(chronotype) {
|
|
78
|
+
switch (chronotype) {
|
|
79
|
+
case 'early-bird': return 'Peak hours: 6 AM – 9 AM';
|
|
80
|
+
case 'night-owl': return 'Peak hours: 9 PM – 12 AM';
|
|
81
|
+
case 'vampire': return 'Peak hours: 12 AM – 6 AM';
|
|
82
|
+
case 'balanced': return 'Consistent throughout day';
|
|
83
|
+
default: return '';
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function getBalanceScore(score) {
|
|
87
|
+
if (score >= 4)
|
|
88
|
+
return 'Excellent';
|
|
89
|
+
if (score >= 3)
|
|
90
|
+
return 'Good';
|
|
91
|
+
if (score >= 2)
|
|
92
|
+
return 'Fair';
|
|
93
|
+
return 'Consider adjusting';
|
|
94
|
+
}
|
|
95
|
+
function formatRegularity(regularity) {
|
|
96
|
+
switch (regularity) {
|
|
97
|
+
case 'very-consistent': return 'Very Consistent';
|
|
98
|
+
case 'consistent': return 'Consistent';
|
|
99
|
+
case 'irregular': return 'Irregular';
|
|
100
|
+
case 'chaotic': return 'Variable';
|
|
101
|
+
default: return regularity;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function formatRiskLevel(level) {
|
|
105
|
+
switch (level) {
|
|
106
|
+
case 'high': return 'High';
|
|
107
|
+
case 'medium': return 'Moderate';
|
|
108
|
+
default: return level;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getColor = exports.CONTRIBUTION_LEVELS = exports.GITHUB_COLORS = void 0;
|
|
4
|
+
const constants_1 = require("../../../constants");
|
|
5
|
+
Object.defineProperty(exports, "CONTRIBUTION_LEVELS", { enumerable: true, get: function () { return constants_1.CONTRIBUTION_LEVELS; } });
|
|
6
|
+
Object.defineProperty(exports, "GITHUB_COLORS", { enumerable: true, get: function () { return constants_1.GITHUB_COLORS; } });
|
|
7
|
+
/**
|
|
8
|
+
* Gets the color for a given contribution count
|
|
9
|
+
*/
|
|
10
|
+
function getColor(count) {
|
|
11
|
+
if (count === 0)
|
|
12
|
+
return constants_1.GITHUB_COLORS.EMPTY;
|
|
13
|
+
if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_1)
|
|
14
|
+
return constants_1.GITHUB_COLORS.LEVEL_1;
|
|
15
|
+
if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_2)
|
|
16
|
+
return constants_1.GITHUB_COLORS.LEVEL_2;
|
|
17
|
+
if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_3)
|
|
18
|
+
return constants_1.GITHUB_COLORS.LEVEL_3;
|
|
19
|
+
return constants_1.GITHUB_COLORS.LEVEL_4;
|
|
20
|
+
}
|
|
21
|
+
exports.getColor = getColor;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createCommitMaps = void 0;
|
|
4
|
+
const date_fns_1 = require("date-fns");
|
|
5
|
+
/**
|
|
6
|
+
* Creates commit maps from an array of commits within a date range
|
|
7
|
+
*/
|
|
8
|
+
function createCommitMaps(commits, startDate, endDate) {
|
|
9
|
+
const commitMap = new Map();
|
|
10
|
+
const commitDetailsMap = new Map();
|
|
11
|
+
commits.forEach(commit => {
|
|
12
|
+
const commitDate = new Date(commit.date);
|
|
13
|
+
if (commitDate >= startDate && commitDate <= endDate) {
|
|
14
|
+
const dateKey = (0, date_fns_1.format)(commitDate, 'yyyy-MM-dd');
|
|
15
|
+
commitMap.set(dateKey, (commitMap.get(dateKey) || 0) + 1);
|
|
16
|
+
if (!commitDetailsMap.has(dateKey)) {
|
|
17
|
+
commitDetailsMap.set(dateKey, []);
|
|
18
|
+
}
|
|
19
|
+
commitDetailsMap.get(dateKey).push(commit);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
return { commitMap, commitDetailsMap };
|
|
23
|
+
}
|
|
24
|
+
exports.createCommitMaps = createCommitMaps;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateDateRangesFromCommits = exports.calculateDateRanges = void 0;
|
|
4
|
+
const date_fns_1 = require("date-fns");
|
|
5
|
+
/**
|
|
6
|
+
* Calculates date ranges for the visualization
|
|
7
|
+
*/
|
|
8
|
+
function calculateDateRanges(year, monthsToShow) {
|
|
9
|
+
const now = new Date();
|
|
10
|
+
const isCurrentYear = year === now.getFullYear();
|
|
11
|
+
// Calculate the end date (today if current year, or end of December for past years)
|
|
12
|
+
const endDate = isCurrentYear ? now : new Date(year, 11, 31);
|
|
13
|
+
// Calculate start date (N months back from end date)
|
|
14
|
+
const startDate = (0, date_fns_1.startOfMonth)((0, date_fns_1.subMonths)(endDate, monthsToShow - 1));
|
|
15
|
+
// Get the full week range (start on Sunday, end on Saturday)
|
|
16
|
+
const weekStartDate = (0, date_fns_1.startOfWeek)(startDate, { weekStartsOn: 0 });
|
|
17
|
+
const weekEndDate = (0, date_fns_1.endOfWeek)(endDate, { weekStartsOn: 0 });
|
|
18
|
+
return {
|
|
19
|
+
startDate,
|
|
20
|
+
endDate,
|
|
21
|
+
weekStartDate,
|
|
22
|
+
weekEndDate
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
exports.calculateDateRanges = calculateDateRanges;
|
|
26
|
+
/**
|
|
27
|
+
* Calculates date ranges from the entire commit history (first to last commit)
|
|
28
|
+
*/
|
|
29
|
+
function calculateDateRangesFromCommits(commits) {
|
|
30
|
+
if (commits.length === 0) {
|
|
31
|
+
const now = new Date();
|
|
32
|
+
return {
|
|
33
|
+
startDate: now,
|
|
34
|
+
endDate: now,
|
|
35
|
+
weekStartDate: (0, date_fns_1.startOfWeek)(now, { weekStartsOn: 0 }),
|
|
36
|
+
weekEndDate: (0, date_fns_1.endOfWeek)(now, { weekStartsOn: 0 })
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
// Sort commits by date to find first and last
|
|
40
|
+
const sortedCommits = [...commits].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
|
|
41
|
+
const firstCommitDate = new Date(sortedCommits[0].date);
|
|
42
|
+
const lastCommitDate = new Date(sortedCommits[sortedCommits.length - 1].date);
|
|
43
|
+
// Use start of month for first commit and today/last commit date for end
|
|
44
|
+
const now = new Date();
|
|
45
|
+
const startDate = (0, date_fns_1.startOfMonth)(firstCommitDate);
|
|
46
|
+
const endDate = lastCommitDate > now ? lastCommitDate : now;
|
|
47
|
+
// Get the full week range (start on Sunday, end on Saturday)
|
|
48
|
+
const weekStartDate = (0, date_fns_1.startOfWeek)(startDate, { weekStartsOn: 0 });
|
|
49
|
+
const weekEndDate = (0, date_fns_1.endOfWeek)(endDate, { weekStartsOn: 0 });
|
|
50
|
+
return {
|
|
51
|
+
startDate,
|
|
52
|
+
endDate,
|
|
53
|
+
weekStartDate,
|
|
54
|
+
weekEndDate
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
exports.calculateDateRangesFromCommits = calculateDateRangesFromCommits;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.calculateDeveloperStats = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Calculates statistics for each developer from commits within a date range
|
|
6
|
+
*/
|
|
7
|
+
function calculateDeveloperStats(commits, startDate, endDate) {
|
|
8
|
+
const developerStats = new Map();
|
|
9
|
+
commits.forEach(commit => {
|
|
10
|
+
const commitDate = new Date(commit.date);
|
|
11
|
+
if (commitDate >= startDate && commitDate <= endDate) {
|
|
12
|
+
if (!developerStats.has(commit.author)) {
|
|
13
|
+
developerStats.set(commit.author, {
|
|
14
|
+
commits: 0,
|
|
15
|
+
firstCommit: commitDate,
|
|
16
|
+
lastCommit: commitDate
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
const stats = developerStats.get(commit.author);
|
|
20
|
+
stats.commits++;
|
|
21
|
+
if (commitDate < stats.firstCommit)
|
|
22
|
+
stats.firstCommit = commitDate;
|
|
23
|
+
if (commitDate > stats.lastCommit)
|
|
24
|
+
stats.lastCommit = commitDate;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return developerStats;
|
|
28
|
+
}
|
|
29
|
+
exports.calculateDeveloperStats = calculateDeveloperStats;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadScripts = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
function loadScripts() {
|
|
7
|
+
const scriptsDir = (0, path_1.join)(__dirname, '..', 'scripts');
|
|
8
|
+
const tabs = (0, fs_1.readFileSync)((0, path_1.join)(scriptsDir, 'tabs.js'), 'utf-8');
|
|
9
|
+
const tooltip = (0, fs_1.readFileSync)((0, path_1.join)(scriptsDir, 'tooltip.js'), 'utf-8');
|
|
10
|
+
const modal = (0, fs_1.readFileSync)((0, path_1.join)(scriptsDir, 'modal.js'), 'utf-8');
|
|
11
|
+
const navigation = (0, fs_1.readFileSync)((0, path_1.join)(scriptsDir, 'navigation.js'), 'utf-8');
|
|
12
|
+
const exportScript = (0, fs_1.readFileSync)((0, path_1.join)(scriptsDir, 'export.js'), 'utf-8');
|
|
13
|
+
const knowledge = (0, fs_1.readFileSync)((0, path_1.join)(scriptsDir, 'knowledge.js'), 'utf-8');
|
|
14
|
+
return `${tabs}\n\n${tooltip}\n\n${modal}\n\n${navigation}\n\n${exportScript}\n\n${knowledge}`;
|
|
15
|
+
}
|
|
16
|
+
exports.loadScripts = loadScripts;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadStyles = void 0;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
/**
|
|
7
|
+
* Loads all CSS files and returns them as a concatenated string
|
|
8
|
+
* for inlining in the HTML output
|
|
9
|
+
*/
|
|
10
|
+
function loadStyles() {
|
|
11
|
+
const stylesDir = (0, path_1.join)(__dirname, '../styles');
|
|
12
|
+
const baseCSS = (0, fs_1.readFileSync)((0, path_1.join)(stylesDir, 'base.css'), 'utf-8');
|
|
13
|
+
const componentsCSS = (0, fs_1.readFileSync)((0, path_1.join)(stylesDir, 'components.css'), 'utf-8');
|
|
14
|
+
const achievementsCSS = (0, fs_1.readFileSync)((0, path_1.join)(stylesDir, 'achievements.css'), 'utf-8');
|
|
15
|
+
const knowledgeCSS = (0, fs_1.readFileSync)((0, path_1.join)(stylesDir, 'knowledge.css'), 'utf-8');
|
|
16
|
+
return `${baseCSS}\n\n${componentsCSS}\n\n${achievementsCSS}\n\n${knowledgeCSS}`;
|
|
17
|
+
}
|
|
18
|
+
exports.loadStyles = loadStyles;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDayLabels = exports.groupDaysIntoWeeks = void 0;
|
|
4
|
+
const date_fns_1 = require("date-fns");
|
|
5
|
+
/**
|
|
6
|
+
* Groups days into weeks for the contribution graph
|
|
7
|
+
*/
|
|
8
|
+
function groupDaysIntoWeeks(startDate, endDate) {
|
|
9
|
+
const days = (0, date_fns_1.eachDayOfInterval)({ start: startDate, end: endDate });
|
|
10
|
+
const weeks = [];
|
|
11
|
+
let currentWeek = [];
|
|
12
|
+
days.forEach(day => {
|
|
13
|
+
currentWeek.push(day);
|
|
14
|
+
if (currentWeek.length === 7) {
|
|
15
|
+
weeks.push(currentWeek);
|
|
16
|
+
currentWeek = [];
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
return weeks;
|
|
20
|
+
}
|
|
21
|
+
exports.groupDaysIntoWeeks = groupDaysIntoWeeks;
|
|
22
|
+
/**
|
|
23
|
+
* Gets day labels for the contribution graph
|
|
24
|
+
*/
|
|
25
|
+
function getDayLabels() {
|
|
26
|
+
return ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
27
|
+
}
|
|
28
|
+
exports.getDayLabels = getDayLabels;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const commander_1 = require("commander");
|
|
10
|
+
const ora_1 = __importDefault(require("ora"));
|
|
11
|
+
const generate_js_1 = require("./commands/generate.js");
|
|
12
|
+
const wrappedHtmlGenerator_js_1 = require("./utils/wrappedHtmlGenerator.js");
|
|
13
|
+
const slideGenerator_js_1 = require("./utils/slideGenerator.js");
|
|
14
|
+
const wrappedDisplay_js_1 = require("./utils/wrappedDisplay.js");
|
|
15
|
+
const wrappedGenerator_js_1 = require("./utils/wrappedGenerator.js");
|
|
16
|
+
const program = new commander_1.Command();
|
|
17
|
+
program
|
|
18
|
+
.name('repo-wrapped')
|
|
19
|
+
.description(chalk_1.default.cyan('Generate Git repository analytics and visualizations'))
|
|
20
|
+
.version('1.0.0');
|
|
21
|
+
program
|
|
22
|
+
.command('generate')
|
|
23
|
+
.description('Generate a commit visualization for a repository')
|
|
24
|
+
.argument('[path]', 'Path to the git repository', '.')
|
|
25
|
+
.option('-y, --year <year>', 'Year to analyze', String(new Date().getFullYear()))
|
|
26
|
+
.option('-m, --months <months>', 'Number of months to show (default: 12)', '12')
|
|
27
|
+
.option('-a, --all', 'Analyze entire repository lifetime')
|
|
28
|
+
.option('--html', 'Generate HTML output and open in browser')
|
|
29
|
+
.option('--body-check', 'Include commit body in quality scoring')
|
|
30
|
+
.option('--deep-analysis', 'Enable file-level analysis for detailed knowledge distribution')
|
|
31
|
+
.action(generate_js_1.generateMatrix);
|
|
32
|
+
program
|
|
33
|
+
.command('wrapped')
|
|
34
|
+
.description('Generate a Year in Code summary (like Spotify Wrapped)')
|
|
35
|
+
.argument('[year]', 'Year to summarize', String(new Date().getFullYear()))
|
|
36
|
+
.argument('[path]', 'Path to the git repository', '.')
|
|
37
|
+
.option('--html', 'Generate HTML slideshow and open in browser')
|
|
38
|
+
.option('--compare', 'Compare with previous year')
|
|
39
|
+
.action(async (year, path, options) => {
|
|
40
|
+
const spinner = (0, ora_1.default)('Analyzing your year in code...').start();
|
|
41
|
+
try {
|
|
42
|
+
const yearNum = parseInt(year, 10);
|
|
43
|
+
const currentYear = new Date().getFullYear();
|
|
44
|
+
if (isNaN(yearNum) || yearNum < 2000 || yearNum > currentYear + 1) {
|
|
45
|
+
spinner.fail(chalk_1.default.red(`Invalid year: ${year}`));
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
// Generate wrapped summary
|
|
49
|
+
const summary = (0, wrappedGenerator_js_1.generateYearWrapped)(yearNum, path, options.compare || false);
|
|
50
|
+
spinner.succeed(chalk_1.default.green('Analysis complete!'));
|
|
51
|
+
if (options.html) {
|
|
52
|
+
// Generate HTML
|
|
53
|
+
const htmlPath = (0, wrappedHtmlGenerator_js_1.generateWrappedHTML)(summary);
|
|
54
|
+
console.log(chalk_1.default.green(`\n✨ HTML slideshow generated: ${htmlPath}`));
|
|
55
|
+
// Open in browser
|
|
56
|
+
const platform = process.platform;
|
|
57
|
+
const openCommand = platform === 'win32' ? 'start' : platform === 'darwin' ? 'open' : 'xdg-open';
|
|
58
|
+
(0, child_process_1.execSync)(`${openCommand} "${htmlPath}"`, { stdio: 'ignore' });
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Generate slides
|
|
62
|
+
const slides = (0, slideGenerator_js_1.generateSlides)(summary);
|
|
63
|
+
// Display interactive slideshow
|
|
64
|
+
console.log();
|
|
65
|
+
(0, wrappedDisplay_js_1.displayWrappedSlideshow)(slides);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
spinner.fail(chalk_1.default.red('Failed to generate wrapped summary'));
|
|
70
|
+
console.error(chalk_1.default.red(error.message));
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
program.parse(process.argv);
|
|
75
|
+
if (!process.argv.slice(2).length) {
|
|
76
|
+
program.outputHelp();
|
|
77
|
+
}
|