repo-wrapped 0.0.4 → 0.0.5

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
  }
@@ -138,7 +138,7 @@ function generatePersonalContent(personalCommitMap, personalCommitDetailsMap, st
138
138
  dataEndDate,
139
139
  totalCommits,
140
140
  title: 'Your contributions',
141
- getColorFn: colorUtils_1.getColor
141
+ getColorFn: (0, colorUtils_1.createDynamicColorFn)(personalCommitMap)
142
142
  });
143
143
  const streakSectionHtml = (0, streakSection_1.buildStreakSection)(streakData);
144
144
  const timePatternsHtml = (0, timePatternsSection_1.buildTimePatternsSection)(timePattern);
@@ -158,6 +158,8 @@ function generatePersonalContent(personalCommitMap, personalCommitDetailsMap, st
158
158
  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
159
  const totalPersonalCommits = Array.from(personalCommitMap.values()).reduce((sum, count) => sum + count, 0);
160
160
  const weeks = (0, weekGrouper_1.groupDaysIntoWeeks)(weekStartDate, weekEndDate);
161
+ // Create dynamic color function based on commit distribution
162
+ const getColor = (0, colorUtils_1.createDynamicColorFn)(commitMap);
161
163
  // Build month labels and track month boundaries
162
164
  let monthLabels = '';
163
165
  let currentMonth = '';
@@ -181,11 +183,22 @@ function generateHTMLContent(commitMap, commitDetailsMap, developerStats, streak
181
183
  const dateKey = (0, date_fns_1.format)(day, 'yyyy-MM-dd');
182
184
  const count = commitMap.get(dateKey) || 0;
183
185
  const isInRange = day >= dataStartDate && day <= dataEndDate;
184
- const color = isInRange ? (0, colorUtils_1.getColor)(count) : 'transparent';
186
+ const color = isInRange ? getColor(count) : 'transparent';
185
187
  const dateStr = (0, date_fns_1.format)(day, 'MMM d, yyyy');
186
188
  const emptyClass = count === 0 ? ' empty' : '';
187
189
  const clickable = count > 0 ? ' clickable' : '';
188
- const detailsData = count > 0 ? `data-details='${JSON.stringify(commitDetailsMap.get(dateKey) || [])}'` : '';
190
+ const rawCommits = commitDetailsMap.get(dateKey) || [];
191
+ const sanitizedCommits = rawCommits.map(c => ({
192
+ ...c,
193
+ message: c.message
194
+ .replace(/[\r\n]+/g, ' ')
195
+ .replace(/'/g, '&#39;')
196
+ .replace(/"/g, '&quot;')
197
+ .replace(/</g, '&lt;')
198
+ .replace(/>/g, '&gt;')
199
+ .substring(0, 200)
200
+ }));
201
+ const detailsData = count > 0 ? `data-details='${JSON.stringify(sanitizedCommits)}'` : '';
189
202
  weekColumn += `<div class="day${emptyClass}${clickable}" style="background-color: ${color};" data-count="${count}" data-date="${dateStr}" ${detailsData}></div>`;
190
203
  });
191
204
  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.5",
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": {