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, '&lt;') // Escape HTML
16
+ .replace(/>/g, '&gt;')
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 detailsData = count > 0 ? `data-details='${JSON.stringify(commitDetailsMap.get(dateKey) || [])}'` : '';
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.CONTRIBUTION_LEVELS = exports.GITHUB_COLORS = void 0;
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
- * Gets the color for a given contribution count
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 <= constants_1.CONTRIBUTION_LEVELS.LEVEL_1)
54
+ if (count <= 3)
15
55
  return constants_1.GITHUB_COLORS.LEVEL_1;
16
- if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_2)
56
+ if (count <= 6)
17
57
  return constants_1.GITHUB_COLORS.LEVEL_2;
18
- if (count <= constants_1.CONTRIBUTION_LEVELS.LEVEL_3)
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)(commits, startDate, endDate);
82
- const personalCommits = commits.filter(c => c.author === currentUser);
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)(commits, startDate, endDate);
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)(commits, startDate, endDate);
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)(commits, startDate, endDate);
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)(commits, { skipBodyCheck });
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)(commits);
99
- const knowledgeDistribution = (0, knowledgeDistributionAnalyzer_1.analyzeKnowledgeDistribution)(commits, repoPath, deepAnalysis);
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.getColor
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 ? (0, colorUtils_1.getColor)(count) : 'transparent';
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 detailsData = count > 0 ? `data-details='${JSON.stringify(commitDetailsMap.get(dateKey) || [])}'` : '';
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, '&#39;')
201
+ .replace(/"/g, '&quot;')
202
+ .replace(/</g, '&lt;')
203
+ .replace(/>/g, '&gt;')
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
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repo-wrapped",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "description": "A tool to generate Git repository analytics and visualizations in CLI or HTML.",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {