repo-wrapped 0.0.4 → 0.0.6
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.
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.buildContributionGraph = buildContributionGraph;
|
|
4
4
|
const date_fns_1 = require("date-fns");
|
|
5
|
+
/**
|
|
6
|
+
* Sanitize commit data for safe HTML attribute embedding
|
|
7
|
+
*/
|
|
8
|
+
function sanitizeCommitsForHtml(commits) {
|
|
9
|
+
return commits.map(c => ({
|
|
10
|
+
...c,
|
|
11
|
+
message: c.message
|
|
12
|
+
.replace(/[\r\n]+/g, ' ') // Replace newlines with space
|
|
13
|
+
.replace(/'/g, ''') // Escape single quotes
|
|
14
|
+
.replace(/"/g, '"') // Escape double quotes
|
|
15
|
+
.replace(/</g, '<') // Escape HTML
|
|
16
|
+
.replace(/>/g, '>')
|
|
17
|
+
.substring(0, 200) // Limit length
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
5
20
|
function buildContributionGraph(options) {
|
|
6
21
|
const { commitMap, commitDetailsMap, weeks, dayLabels, dataStartDate, dataEndDate, totalCommits, title, getColorFn } = options;
|
|
7
22
|
// Build week columns
|
|
@@ -16,7 +31,9 @@ function buildContributionGraph(options) {
|
|
|
16
31
|
const dateStr = (0, date_fns_1.format)(day, 'MMM d, yyyy');
|
|
17
32
|
const emptyClass = count === 0 ? ' empty' : '';
|
|
18
33
|
const clickable = count > 0 ? ' clickable' : '';
|
|
19
|
-
const
|
|
34
|
+
const rawCommits = commitDetailsMap.get(dateKey) || [];
|
|
35
|
+
const sanitizedCommits = sanitizeCommitsForHtml(rawCommits);
|
|
36
|
+
const detailsData = count > 0 ? `data-details='${JSON.stringify(sanitizedCommits)}'` : '';
|
|
20
37
|
weekColumn += `<div class="day${emptyClass}${clickable}" style="background-color: ${color};" data-count="${count}" data-date="${dateStr}" ${detailsData}></div>`;
|
|
21
38
|
});
|
|
22
39
|
weekColumn += '</div>';
|
|
@@ -1,21 +1,61 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.GITHUB_COLORS = void 0;
|
|
4
|
+
exports.calculateDynamicThresholds = calculateDynamicThresholds;
|
|
5
|
+
exports.createDynamicColorFn = createDynamicColorFn;
|
|
4
6
|
exports.getColor = getColor;
|
|
5
7
|
const constants_1 = require("../../../constants");
|
|
6
|
-
Object.defineProperty(exports, "CONTRIBUTION_LEVELS", { enumerable: true, get: function () { return constants_1.CONTRIBUTION_LEVELS; } });
|
|
7
8
|
Object.defineProperty(exports, "GITHUB_COLORS", { enumerable: true, get: function () { return constants_1.GITHUB_COLORS; } });
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
+
* Calculate dynamic thresholds based on the repository's commit distribution.
|
|
11
|
+
* Uses percentiles to ensure colors adapt to the repo's activity level.
|
|
12
|
+
*/
|
|
13
|
+
function calculateDynamicThresholds(commitMap) {
|
|
14
|
+
// Get all non-zero counts
|
|
15
|
+
const counts = Array.from(commitMap.values()).filter(c => c > 0);
|
|
16
|
+
if (counts.length === 0) {
|
|
17
|
+
return { l1: 1, l2: 2, l3: 3 };
|
|
18
|
+
}
|
|
19
|
+
counts.sort((a, b) => a - b);
|
|
20
|
+
const percentile = (p) => {
|
|
21
|
+
const index = Math.ceil((p / 100) * counts.length) - 1;
|
|
22
|
+
return counts[Math.max(0, index)];
|
|
23
|
+
};
|
|
24
|
+
// Use 25th, 50th, 75th percentiles for thresholds
|
|
25
|
+
const l1 = Math.max(1, percentile(25));
|
|
26
|
+
const l2 = Math.max(l1 + 1, percentile(50));
|
|
27
|
+
const l3 = Math.max(l2 + 1, percentile(75));
|
|
28
|
+
return { l1, l2, l3 };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates a color function with dynamic thresholds based on commit data
|
|
32
|
+
*/
|
|
33
|
+
function createDynamicColorFn(commitMap) {
|
|
34
|
+
const { l1, l2, l3 } = calculateDynamicThresholds(commitMap);
|
|
35
|
+
return (count) => {
|
|
36
|
+
if (count === 0)
|
|
37
|
+
return constants_1.GITHUB_COLORS.EMPTY;
|
|
38
|
+
if (count <= l1)
|
|
39
|
+
return constants_1.GITHUB_COLORS.LEVEL_1;
|
|
40
|
+
if (count <= l2)
|
|
41
|
+
return constants_1.GITHUB_COLORS.LEVEL_2;
|
|
42
|
+
if (count <= l3)
|
|
43
|
+
return constants_1.GITHUB_COLORS.LEVEL_3;
|
|
44
|
+
return constants_1.GITHUB_COLORS.LEVEL_4;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Gets the color for a given contribution count (uses fixed thresholds - legacy)
|
|
49
|
+
* @deprecated Use createDynamicColorFn for better scaling
|
|
10
50
|
*/
|
|
11
51
|
function getColor(count) {
|
|
12
52
|
if (count === 0)
|
|
13
53
|
return constants_1.GITHUB_COLORS.EMPTY;
|
|
14
|
-
if (count <=
|
|
54
|
+
if (count <= 3)
|
|
15
55
|
return constants_1.GITHUB_COLORS.LEVEL_1;
|
|
16
|
-
if (count <=
|
|
56
|
+
if (count <= 6)
|
|
17
57
|
return constants_1.GITHUB_COLORS.LEVEL_2;
|
|
18
|
-
if (count <=
|
|
58
|
+
if (count <= 9)
|
|
19
59
|
return constants_1.GITHUB_COLORS.LEVEL_3;
|
|
20
60
|
return constants_1.GITHUB_COLORS.LEVEL_4;
|
|
21
61
|
}
|
|
@@ -77,31 +77,36 @@ function generateHTML(commits, year, monthsToShow, repoPath, repoName, repoUrl,
|
|
|
77
77
|
const { startDate, endDate, weekStartDate, weekEndDate } = allTime
|
|
78
78
|
? (0, dateRangeCalculator_1.calculateDateRangesFromCommits)(commits)
|
|
79
79
|
: (0, dateRangeCalculator_1.calculateDateRanges)(year, monthsToShow);
|
|
80
|
+
// Filter commits to the date range
|
|
81
|
+
const filteredCommits = commits.filter(commit => {
|
|
82
|
+
const commitDate = new Date(commit.date);
|
|
83
|
+
return commitDate >= startDate && commitDate <= endDate;
|
|
84
|
+
});
|
|
80
85
|
// Create commit maps for overall and personal commits
|
|
81
|
-
const { commitMap, commitDetailsMap } = (0, commitMapBuilder_1.createCommitMaps)(
|
|
82
|
-
const personalCommits =
|
|
86
|
+
const { commitMap, commitDetailsMap } = (0, commitMapBuilder_1.createCommitMaps)(filteredCommits, startDate, endDate);
|
|
87
|
+
const personalCommits = filteredCommits.filter(c => c.author === currentUser);
|
|
83
88
|
const { commitMap: personalCommitMap, commitDetailsMap: personalCommitDetailsMap } = (0, commitMapBuilder_1.createCommitMaps)(personalCommits, startDate, endDate);
|
|
84
89
|
// Calculate per-developer statistics
|
|
85
|
-
const developerStats = (0, developerStatsCalculator_1.calculateDeveloperStats)(
|
|
90
|
+
const developerStats = (0, developerStatsCalculator_1.calculateDeveloperStats)(filteredCommits, startDate, endDate);
|
|
86
91
|
// Calculate streak data (overall and personal)
|
|
87
|
-
const streakData = (0, streakCalculator_1.calculateStreaks)(
|
|
92
|
+
const streakData = (0, streakCalculator_1.calculateStreaks)(filteredCommits, startDate, endDate);
|
|
88
93
|
const personalStreakData = (0, streakCalculator_1.calculateStreaks)(personalCommits, startDate, endDate);
|
|
89
94
|
// Analyze time patterns (overall and personal)
|
|
90
|
-
const timePattern = (0, timePatternAnalyzer_1.analyzeTimePatterns)(
|
|
95
|
+
const timePattern = (0, timePatternAnalyzer_1.analyzeTimePatterns)(filteredCommits, startDate, endDate);
|
|
91
96
|
const personalTimePattern = (0, timePatternAnalyzer_1.analyzeTimePatterns)(personalCommits, startDate, endDate);
|
|
92
97
|
// Analyze commit quality (overall and personal)
|
|
93
|
-
const commitQuality = (0, commitQualityAnalyzer_1.analyzeCommitQuality)(
|
|
98
|
+
const commitQuality = (0, commitQualityAnalyzer_1.analyzeCommitQuality)(filteredCommits, { skipBodyCheck });
|
|
94
99
|
const personalCommitQuality = (0, commitQualityAnalyzer_1.analyzeCommitQuality)(personalCommits, { skipBodyCheck });
|
|
95
100
|
// Analyze file hotspots
|
|
96
101
|
const fileHotspots = (0, fileHotspotAnalyzer_1.analyzeFileHotspots)(repoPath, startDate, endDate);
|
|
97
102
|
// Analyze impact and knowledge distribution (enterprise insights)
|
|
98
|
-
const impactAnalysis = (0, impactAnalyzer_1.analyzeImpact)(
|
|
99
|
-
const knowledgeDistribution = (0, knowledgeDistributionAnalyzer_1.analyzeKnowledgeDistribution)(
|
|
103
|
+
const impactAnalysis = (0, impactAnalyzer_1.analyzeImpact)(filteredCommits);
|
|
104
|
+
const knowledgeDistribution = (0, knowledgeDistributionAnalyzer_1.analyzeKnowledgeDistribution)(filteredCommits, repoPath, deepAnalysis);
|
|
100
105
|
const totalCommits = Array.from(commitMap.values()).reduce((sum, count) => sum + count, 0);
|
|
101
106
|
const totalPersonalCommits = Array.from(personalCommitMap.values()).reduce((sum, count) => sum + count, 0);
|
|
102
107
|
// Prepare analysis data for achievements (overall and personal)
|
|
103
108
|
const analysisData = {
|
|
104
|
-
commits,
|
|
109
|
+
commits: filteredCommits,
|
|
105
110
|
totalCommits,
|
|
106
111
|
streakData,
|
|
107
112
|
timePattern,
|
|
@@ -138,7 +143,7 @@ function generatePersonalContent(personalCommitMap, personalCommitDetailsMap, st
|
|
|
138
143
|
dataEndDate,
|
|
139
144
|
totalCommits,
|
|
140
145
|
title: 'Your contributions',
|
|
141
|
-
getColorFn: colorUtils_1.
|
|
146
|
+
getColorFn: (0, colorUtils_1.createDynamicColorFn)(personalCommitMap)
|
|
142
147
|
});
|
|
143
148
|
const streakSectionHtml = (0, streakSection_1.buildStreakSection)(streakData);
|
|
144
149
|
const timePatternsHtml = (0, timePatternsSection_1.buildTimePatternsSection)(timePattern);
|
|
@@ -158,6 +163,8 @@ function generatePersonalContent(personalCommitMap, personalCommitDetailsMap, st
|
|
|
158
163
|
function generateHTMLContent(commitMap, commitDetailsMap, developerStats, streakData, timePattern, commitQuality, fileHotspots, achievementProgress, personalCommitMap, personalCommitDetailsMap, personalStreakData, personalTimePattern, personalCommitQuality, personalAchievementProgress, weekStartDate, weekEndDate, dataStartDate, dataEndDate, repoName, repoUrl, currentUser, skipBodyCheck = false, impactAnalysis, knowledgeDistribution) {
|
|
159
164
|
const totalPersonalCommits = Array.from(personalCommitMap.values()).reduce((sum, count) => sum + count, 0);
|
|
160
165
|
const weeks = (0, weekGrouper_1.groupDaysIntoWeeks)(weekStartDate, weekEndDate);
|
|
166
|
+
// Create dynamic color function based on commit distribution
|
|
167
|
+
const getColor = (0, colorUtils_1.createDynamicColorFn)(commitMap);
|
|
161
168
|
// Build month labels and track month boundaries
|
|
162
169
|
let monthLabels = '';
|
|
163
170
|
let currentMonth = '';
|
|
@@ -181,11 +188,22 @@ function generateHTMLContent(commitMap, commitDetailsMap, developerStats, streak
|
|
|
181
188
|
const dateKey = (0, date_fns_1.format)(day, 'yyyy-MM-dd');
|
|
182
189
|
const count = commitMap.get(dateKey) || 0;
|
|
183
190
|
const isInRange = day >= dataStartDate && day <= dataEndDate;
|
|
184
|
-
const color = isInRange ?
|
|
191
|
+
const color = isInRange ? getColor(count) : 'transparent';
|
|
185
192
|
const dateStr = (0, date_fns_1.format)(day, 'MMM d, yyyy');
|
|
186
193
|
const emptyClass = count === 0 ? ' empty' : '';
|
|
187
194
|
const clickable = count > 0 ? ' clickable' : '';
|
|
188
|
-
const
|
|
195
|
+
const rawCommits = commitDetailsMap.get(dateKey) || [];
|
|
196
|
+
const sanitizedCommits = rawCommits.map(c => ({
|
|
197
|
+
...c,
|
|
198
|
+
message: c.message
|
|
199
|
+
.replace(/[\r\n]+/g, ' ')
|
|
200
|
+
.replace(/'/g, ''')
|
|
201
|
+
.replace(/"/g, '"')
|
|
202
|
+
.replace(/</g, '<')
|
|
203
|
+
.replace(/>/g, '>')
|
|
204
|
+
.substring(0, 200)
|
|
205
|
+
}));
|
|
206
|
+
const detailsData = count > 0 ? `data-details='${JSON.stringify(sanitizedCommits)}'` : '';
|
|
189
207
|
weekColumn += `<div class="day${emptyClass}${clickable}" style="background-color: ${color};" data-count="${count}" data-date="${dateStr}" ${detailsData}></div>`;
|
|
190
208
|
});
|
|
191
209
|
weekColumn += '</div>';
|
|
@@ -262,13 +262,20 @@ function analyzeKnowledgeDistribution(commits, repoPath, deepAnalysis = false) {
|
|
|
262
262
|
let dirCount = 0;
|
|
263
263
|
directoryStats.forEach((authorStats, dirPath) => {
|
|
264
264
|
const totalCommits = Array.from(authorStats.values()).reduce((sum, a) => sum + a.commits, 0);
|
|
265
|
-
// Get the latest activity date for the directory
|
|
265
|
+
// Get the latest and earliest activity date for the directory
|
|
266
266
|
let latestActivity = new Date(0);
|
|
267
|
+
let earliestActivity = new Date();
|
|
267
268
|
authorStats.forEach(stats => {
|
|
268
269
|
if (stats.lastActive > latestActivity) {
|
|
269
270
|
latestActivity = stats.lastActive;
|
|
270
271
|
}
|
|
272
|
+
if (stats.lastActive < earliestActivity) {
|
|
273
|
+
earliestActivity = stats.lastActive;
|
|
274
|
+
}
|
|
271
275
|
});
|
|
276
|
+
// Calculate directory age in days
|
|
277
|
+
const dirAgeInDays = Math.floor((Date.now() - earliestActivity.getTime()) / (1000 * 60 * 60 * 24));
|
|
278
|
+
const MIN_AGE_FOR_SILO = 14; // Don't flag new directories as silos
|
|
272
279
|
const owners = Array.from(authorStats.entries())
|
|
273
280
|
.map(([author, stats]) => ({
|
|
274
281
|
author,
|
|
@@ -295,8 +302,8 @@ function analyzeKnowledgeDistribution(commits, repoPath, deepAnalysis = false) {
|
|
|
295
302
|
busFactorRisk,
|
|
296
303
|
highRiskFiles
|
|
297
304
|
});
|
|
298
|
-
// Track silos and shared knowledge
|
|
299
|
-
if (ownershipType === 'solo') {
|
|
305
|
+
// Track silos and shared knowledge (only if directory is old enough)
|
|
306
|
+
if (ownershipType === 'solo' && dirAgeInDays >= MIN_AGE_FOR_SILO) {
|
|
300
307
|
knowledgeSilos.push(`${dirPath} — ${owners[0]?.author || 'Unknown'} (100%)`);
|
|
301
308
|
criticalPaths.push(dirPath);
|
|
302
309
|
}
|
|
@@ -6,12 +6,29 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.generateCommitMatrix = generateCommitMatrix;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
8
|
const date_fns_1 = require("date-fns");
|
|
9
|
-
const constants_1 = require("../constants");
|
|
10
9
|
const achievementEngine_1 = require("./achievementEngine");
|
|
11
10
|
const commitQualityAnalyzer_1 = require("./commitQualityAnalyzer");
|
|
12
11
|
const fileHotspotAnalyzer_1 = require("./fileHotspotAnalyzer");
|
|
13
12
|
const streakCalculator_1 = require("./streakCalculator");
|
|
14
13
|
const timePatternAnalyzer_1 = require("./timePatternAnalyzer");
|
|
14
|
+
/**
|
|
15
|
+
* Calculate dynamic thresholds based on commit distribution for terminal output
|
|
16
|
+
*/
|
|
17
|
+
function calculateTerminalThresholds(commitMap) {
|
|
18
|
+
const counts = Array.from(commitMap.values()).filter(c => c > 0);
|
|
19
|
+
if (counts.length === 0) {
|
|
20
|
+
return { l1: 1, l2: 2, l3: 3 };
|
|
21
|
+
}
|
|
22
|
+
counts.sort((a, b) => a - b);
|
|
23
|
+
const percentile = (p) => {
|
|
24
|
+
const index = Math.ceil((p / 100) * counts.length) - 1;
|
|
25
|
+
return counts[Math.max(0, index)];
|
|
26
|
+
};
|
|
27
|
+
const l1 = Math.max(1, percentile(25));
|
|
28
|
+
const l2 = Math.max(l1 + 1, percentile(50));
|
|
29
|
+
const l3 = Math.max(l2 + 1, percentile(75));
|
|
30
|
+
return { l1, l2, l3 };
|
|
31
|
+
}
|
|
15
32
|
function generateCommitMatrix(commits, year, monthsToShow, repoPath, repoName, repoUrl) {
|
|
16
33
|
// Get today or end of year if it's a past year
|
|
17
34
|
const now = new Date();
|
|
@@ -60,6 +77,20 @@ function generateCommitMatrix(commits, year, monthsToShow, repoPath, repoName, r
|
|
|
60
77
|
function formatGitHubStyle(commitMap, weekStartDate, weekEndDate, dataStartDate, dataEndDate, totalCommits, streakData, timePattern, commitQuality, fileHotspots, achievementProgress, repoName, repoUrl) {
|
|
61
78
|
const days = (0, date_fns_1.eachDayOfInterval)({ start: weekStartDate, end: weekEndDate });
|
|
62
79
|
const dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
80
|
+
// Calculate dynamic thresholds based on this repo's commit distribution
|
|
81
|
+
const thresholds = calculateTerminalThresholds(commitMap);
|
|
82
|
+
// Create a formatCell function with dynamic thresholds
|
|
83
|
+
const formatCell = (count) => {
|
|
84
|
+
if (count === 0)
|
|
85
|
+
return chalk_1.default.hex('#ebedf0')('■');
|
|
86
|
+
if (count <= thresholds.l1)
|
|
87
|
+
return chalk_1.default.hex('#9be9a8')('■');
|
|
88
|
+
if (count <= thresholds.l2)
|
|
89
|
+
return chalk_1.default.hex('#40c463')('■');
|
|
90
|
+
if (count <= thresholds.l3)
|
|
91
|
+
return chalk_1.default.hex('#30a14e')('■');
|
|
92
|
+
return chalk_1.default.hex('#216e39')('■');
|
|
93
|
+
};
|
|
63
94
|
// Group days into weeks
|
|
64
95
|
const weeks = [];
|
|
65
96
|
let currentWeek = [];
|
|
@@ -336,14 +367,3 @@ function formatGitHubStyle(commitMap, weekStartDate, weekEndDate, dataStartDate,
|
|
|
336
367
|
}
|
|
337
368
|
return output;
|
|
338
369
|
}
|
|
339
|
-
function formatCell(count) {
|
|
340
|
-
if (count === 0)
|
|
341
|
-
return chalk_1.default.hex('#ebedf0')('■');
|
|
342
|
-
if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_1)
|
|
343
|
-
return chalk_1.default.hex('#9be9a8')('■');
|
|
344
|
-
if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_2)
|
|
345
|
-
return chalk_1.default.hex('#40c463')('■');
|
|
346
|
-
if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_3)
|
|
347
|
-
return chalk_1.default.hex('#30a14e')('■');
|
|
348
|
-
return chalk_1.default.hex('#216e39')('■');
|
|
349
|
-
}
|