repo-wrapped 0.0.6 → 0.0.9
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/.github/agents/complete.agent.md +257 -0
- package/.github/agents/feature-scaffold.agent.md +248 -0
- package/.github/agents/jsdoc.agent.md +243 -0
- package/.github/agents/plan.agent.md +202 -0
- package/.github/agents/spec-writer.agent.md +169 -0
- package/.github/agents/test-writer.agent.md +169 -0
- package/.stylelintrc.json +27 -0
- package/README.md +94 -94
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +446 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/index.html +446 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +7039 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/dist/commands/generate.js +262 -5
- package/dist/config/defaults.js +158 -0
- package/dist/config/index.js +10 -0
- package/dist/features/achievements/data/achievements.json +284 -0
- package/dist/features/achievements/engine.js +140 -0
- package/dist/features/achievements/evaluators.js +246 -0
- package/dist/features/achievements/helpers.js +58 -0
- package/dist/features/achievements/index.js +57 -0
- package/dist/features/achievements/loader.js +88 -0
- package/dist/features/achievements/template.js +155 -0
- package/dist/features/achievements/types.js +7 -0
- package/dist/features/commit-quality/analyzer.js +378 -0
- package/dist/features/commit-quality/analyzer.test.js +484 -0
- package/dist/features/commit-quality/index.js +28 -0
- package/dist/features/commit-quality/template.js +114 -0
- package/dist/features/commit-quality/types.js +2 -0
- package/dist/features/comparison/analyzer.js +222 -0
- package/dist/features/comparison/index.js +28 -0
- package/dist/features/comparison/template.js +119 -0
- package/dist/features/comparison/types.js +2 -0
- package/dist/features/contribution-graph/index.js +9 -0
- package/dist/features/contribution-graph/template.js +89 -0
- package/dist/features/events/index.js +31 -0
- package/dist/features/events/parser.js +253 -0
- package/dist/features/events/template.js +113 -0
- package/dist/features/events/types.js +2 -0
- package/dist/features/executive-summary/generator.js +275 -0
- package/dist/features/executive-summary/index.js +27 -0
- package/dist/features/executive-summary/template.js +80 -0
- package/dist/features/executive-summary/types.js +2 -0
- package/dist/features/gaps/analyzer.js +298 -0
- package/dist/features/gaps/analyzer.test.js +517 -0
- package/dist/features/gaps/index.js +27 -0
- package/dist/features/gaps/template.js +190 -0
- package/dist/features/gaps/types.js +2 -0
- package/dist/features/impact/analyzer.js +248 -0
- package/dist/features/impact/index.js +26 -0
- package/dist/features/impact/template.js +118 -0
- package/dist/features/impact/types.js +2 -0
- package/dist/features/index.js +40 -0
- package/dist/features/knowledge/analyzer.js +385 -0
- package/dist/features/knowledge/index.js +26 -0
- package/dist/features/knowledge/template.js +239 -0
- package/dist/features/knowledge/types.js +2 -0
- package/dist/features/streaks/calculator.js +184 -0
- package/dist/features/streaks/calculator.test.js +366 -0
- package/dist/features/streaks/index.js +36 -0
- package/dist/features/streaks/template.js +41 -0
- package/dist/features/streaks/types.js +9 -0
- package/dist/features/team/analyzer.js +316 -0
- package/dist/features/team/index.js +30 -0
- package/dist/features/team/template.js +146 -0
- package/dist/features/team/types.js +2 -0
- package/dist/features/time-patterns/analyzer.js +319 -0
- package/dist/features/time-patterns/analyzer.test.js +278 -0
- package/dist/features/time-patterns/index.js +37 -0
- package/dist/features/time-patterns/template.js +109 -0
- package/dist/features/time-patterns/types.js +9 -0
- package/dist/features/velocity/analyzer.js +257 -0
- package/dist/features/velocity/analyzer.test.js +383 -0
- package/dist/features/velocity/index.js +27 -0
- package/dist/features/velocity/template.js +189 -0
- package/dist/features/velocity/types.js +2 -0
- package/dist/generators/html/scripts/knowledge.js +17 -0
- package/dist/generators/html/styles/base.css +10 -6
- package/dist/generators/html/styles/components.css +121 -1
- package/dist/generators/html/styles/knowledge.css +21 -0
- package/dist/generators/html/styles/leaddev.css +1335 -0
- package/dist/generators/html/styles/strategic-insights.css +1337 -0
- package/dist/generators/html/templates/commitQualitySection.js +28 -2
- package/dist/generators/html/templates/comparisonSection.js +119 -0
- package/dist/generators/html/templates/eventsSection.js +113 -0
- package/dist/generators/html/templates/executiveSummarySection.js +80 -0
- package/dist/generators/html/templates/gapSection.js +190 -0
- package/dist/generators/html/templates/impactSection.js +8 -6
- package/dist/generators/html/templates/knowledgeSection.js +16 -2
- package/dist/generators/html/templates/teamSection.js +146 -0
- package/dist/generators/html/templates/velocitySection.js +189 -0
- package/dist/generators/html/types.js +7 -0
- package/dist/generators/html/utils/analysisRunner.js +93 -0
- package/dist/generators/html/utils/cardBuilder.js +47 -0
- package/dist/generators/html/utils/contextBuilder.js +54 -0
- package/dist/generators/html/utils/htmlDocumentBuilder.js +396 -0
- package/dist/generators/html/utils/kpiBuilder.js +76 -0
- package/dist/generators/html/utils/sectionWrapper.js +71 -0
- package/dist/generators/html/utils/styleLoader.js +2 -1
- package/dist/html/analysisRunner.js +93 -0
- package/dist/html/htmlDocumentBuilder.js +396 -0
- package/dist/html/index.js +29 -0
- package/dist/html/shared/colorUtils.js +61 -0
- package/dist/html/shared/commitMapBuilder.js +23 -0
- package/dist/html/shared/components/cardBuilder.js +47 -0
- package/dist/html/shared/components/index.js +18 -0
- package/dist/html/shared/components/kpiBuilder.js +76 -0
- package/dist/html/shared/components/sectionWrapper.js +71 -0
- package/dist/html/shared/contextBuilder.js +54 -0
- package/dist/html/shared/dateRangeCalculator.js +56 -0
- package/dist/html/shared/developerStatsCalculator.js +28 -0
- package/dist/html/shared/index.js +39 -0
- package/dist/html/shared/scriptLoader.js +15 -0
- package/dist/html/shared/scripts/export.js +125 -0
- package/dist/html/shared/scripts/knowledge.js +137 -0
- package/dist/html/shared/scripts/modal.js +68 -0
- package/dist/html/shared/scripts/navigation.js +156 -0
- package/dist/html/shared/scripts/tabs.js +18 -0
- package/dist/html/shared/scripts/tooltip.js +21 -0
- package/dist/html/shared/styleLoader.js +18 -0
- package/dist/html/shared/styles/achievements.css +387 -0
- package/dist/html/shared/styles/base.css +822 -0
- package/dist/html/shared/styles/components.css +1511 -0
- package/dist/html/shared/styles/knowledge.css +242 -0
- package/dist/html/shared/styles/strategic-insights.css +1337 -0
- package/dist/html/shared/weekGrouper.js +27 -0
- package/dist/html/types.js +7 -0
- package/dist/index.js +54 -21
- package/dist/test/helpers/commitFactory.js +166 -0
- package/dist/test/helpers/dateUtils.js +101 -0
- package/dist/test/helpers/index.js +29 -0
- package/dist/test/setup.js +17 -0
- package/dist/test/smoke.test.js +94 -0
- package/dist/types/achievements.js +7 -0
- package/dist/types/analysis.js +7 -0
- package/dist/types/core.js +7 -0
- package/dist/types/index.js +38 -0
- package/dist/types/options.js +7 -0
- package/dist/types/shared.js +7 -0
- package/dist/types/strategic.js +7 -0
- package/dist/types/summary.js +7 -0
- package/dist/utils/achievementDefinitions.js +22 -22
- package/dist/utils/analyzerContextBuilder.js +124 -0
- package/dist/utils/commitQualityAnalyzer.js +13 -2
- package/dist/utils/emptyResults.js +95 -0
- package/dist/utils/eventAnnotationParser.js +253 -0
- package/dist/utils/executiveSummaryGenerator.js +275 -0
- package/dist/utils/fileHotspotAnalyzer.js +4 -12
- package/dist/utils/gapAnalyzer.js +298 -0
- package/dist/utils/gitParser.test.js +363 -0
- package/dist/utils/htmlGenerator.js +126 -450
- package/dist/utils/impactAnalyzer.js +20 -19
- package/dist/utils/knowledgeDistributionAnalyzer.js +32 -27
- package/dist/utils/matrixGenerator.js +13 -13
- package/dist/utils/rangeComparisonAnalyzer.js +222 -0
- package/dist/utils/streakCalculator.js +77 -27
- package/dist/utils/teamAnalyzer.js +316 -0
- package/dist/utils/timePatternAnalyzer.js +18 -3
- package/dist/utils/velocityAnalyzer.js +257 -0
- package/dist/utils/wrappedGenerator.js +8 -8
- package/package.json +74 -55
- package/vitest.config.ts +46 -0
- package/dist/cli.js +0 -24
- package/dist/commands/index.js +0 -24
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unit tests for gitParser.ts
|
|
4
|
+
*
|
|
5
|
+
* Tests the core git parsing functions that serve as the data entry point
|
|
6
|
+
* for the entire application. All downstream analysis depends on correct parsing.
|
|
7
|
+
*
|
|
8
|
+
* @module utils/gitParser.test
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
const vitest_1 = require("vitest");
|
|
12
|
+
const gitParser_1 = require("./gitParser");
|
|
13
|
+
// Mock child_process module at the top level for ESM compatibility
|
|
14
|
+
vitest_1.vi.mock('child_process', () => ({
|
|
15
|
+
execSync: vitest_1.vi.fn(),
|
|
16
|
+
}));
|
|
17
|
+
// Import after mock setup
|
|
18
|
+
const child_process_1 = require("child_process");
|
|
19
|
+
const mockExecSync = vitest_1.vi.mocked(child_process_1.execSync);
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Test Helpers
|
|
22
|
+
// ============================================================================
|
|
23
|
+
/**
|
|
24
|
+
* Creates a raw commit line in the format expected by parseCommitData.
|
|
25
|
+
* Format: "hash|author|date|message"
|
|
26
|
+
*/
|
|
27
|
+
function createRawCommitLine(overrides = {}) {
|
|
28
|
+
const defaults = {
|
|
29
|
+
hash: 'abc123def456',
|
|
30
|
+
author: 'Test Author',
|
|
31
|
+
date: '2025-06-15T10:00:00Z',
|
|
32
|
+
message: 'test commit message',
|
|
33
|
+
};
|
|
34
|
+
const data = { ...defaults, ...overrides };
|
|
35
|
+
return `${data.hash}|${data.author}|${data.date}|${data.message}`;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates multiple raw commit lines joined by newlines.
|
|
39
|
+
*/
|
|
40
|
+
function createRawCommitLog(commits) {
|
|
41
|
+
return commits.map(createRawCommitLine).join('\n');
|
|
42
|
+
}
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// parseCommitData - Happy Path
|
|
45
|
+
// ============================================================================
|
|
46
|
+
(0, vitest_1.describe)('parseCommitData', () => {
|
|
47
|
+
(0, vitest_1.describe)('happy path', () => {
|
|
48
|
+
(0, vitest_1.it)('parses a single well-formed commit', () => {
|
|
49
|
+
const raw = 'abc123|John Doe|2025-06-15T10:30:00+00:00|feat: add feature';
|
|
50
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
51
|
+
(0, vitest_1.expect)(result).toHaveLength(1);
|
|
52
|
+
(0, vitest_1.expect)(result[0]).toMatchObject({
|
|
53
|
+
hash: 'abc123',
|
|
54
|
+
author: 'John Doe',
|
|
55
|
+
date: '2025-06-15T10:30:00+00:00',
|
|
56
|
+
message: 'feat: add feature',
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
(0, vitest_1.it)('parses multiple commits separated by newlines', () => {
|
|
60
|
+
const raw = createRawCommitLog([
|
|
61
|
+
{ hash: 'abc123', author: 'Author1', message: 'msg1' },
|
|
62
|
+
{ hash: 'def456', author: 'Author2', message: 'msg2' },
|
|
63
|
+
]);
|
|
64
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
65
|
+
(0, vitest_1.expect)(result).toHaveLength(2);
|
|
66
|
+
(0, vitest_1.expect)(result[0].hash).toBe('abc123');
|
|
67
|
+
(0, vitest_1.expect)(result[1].hash).toBe('def456');
|
|
68
|
+
});
|
|
69
|
+
(0, vitest_1.it)('correctly preserves the date as a string', () => {
|
|
70
|
+
const raw = 'abc123|John|2025-06-15T10:30:00+00:00|msg';
|
|
71
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
72
|
+
(0, vitest_1.expect)(result[0].date).toBe('2025-06-15T10:30:00+00:00');
|
|
73
|
+
(0, vitest_1.expect)(typeof result[0].date).toBe('string');
|
|
74
|
+
});
|
|
75
|
+
(0, vitest_1.it)('parses all four fields correctly', () => {
|
|
76
|
+
const raw = createRawCommitLine({
|
|
77
|
+
hash: 'deadbeef',
|
|
78
|
+
author: 'Jane Smith',
|
|
79
|
+
date: '2025-12-25T08:00:00-05:00',
|
|
80
|
+
message: 'fix: resolve bug',
|
|
81
|
+
});
|
|
82
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
83
|
+
(0, vitest_1.expect)(result[0]).toEqual({
|
|
84
|
+
hash: 'deadbeef',
|
|
85
|
+
author: 'Jane Smith',
|
|
86
|
+
date: '2025-12-25T08:00:00-05:00',
|
|
87
|
+
message: 'fix: resolve bug',
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// parseCommitData - Edge Cases
|
|
93
|
+
// ============================================================================
|
|
94
|
+
(0, vitest_1.describe)('edge cases', () => {
|
|
95
|
+
(0, vitest_1.it)('returns empty array for empty string input', () => {
|
|
96
|
+
(0, vitest_1.expect)((0, gitParser_1.parseCommitData)('')).toEqual([]);
|
|
97
|
+
});
|
|
98
|
+
(0, vitest_1.it)('returns empty array for whitespace-only input', () => {
|
|
99
|
+
(0, vitest_1.expect)((0, gitParser_1.parseCommitData)(' \n\n ')).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
(0, vitest_1.it)('returns empty array for null-ish input', () => {
|
|
102
|
+
(0, vitest_1.expect)((0, gitParser_1.parseCommitData)(null)).toEqual([]);
|
|
103
|
+
(0, vitest_1.expect)((0, gitParser_1.parseCommitData)(undefined)).toEqual([]);
|
|
104
|
+
});
|
|
105
|
+
(0, vitest_1.it)('filters out malformed lines with insufficient fields', () => {
|
|
106
|
+
const raw = [
|
|
107
|
+
'abc123|John|2025-06-15T10:00:00Z|valid msg', // valid
|
|
108
|
+
'incomplete|line|only', // invalid - too few fields
|
|
109
|
+
'def456|Jane|2025-06-14T09:00:00Z|another valid', // valid
|
|
110
|
+
].join('\n');
|
|
111
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
112
|
+
(0, vitest_1.expect)(result).toHaveLength(2);
|
|
113
|
+
(0, vitest_1.expect)(result[0].hash).toBe('abc123');
|
|
114
|
+
(0, vitest_1.expect)(result[1].hash).toBe('def456');
|
|
115
|
+
});
|
|
116
|
+
(0, vitest_1.it)('handles commit messages containing the delimiter character |', () => {
|
|
117
|
+
// Message: "feat: add | operator support"
|
|
118
|
+
const raw = 'abc123|John|2025-06-15T10:00:00Z|feat: add | operator support';
|
|
119
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
120
|
+
(0, vitest_1.expect)(result).toHaveLength(1);
|
|
121
|
+
(0, vitest_1.expect)(result[0].message).toBe('feat: add | operator support');
|
|
122
|
+
});
|
|
123
|
+
(0, vitest_1.it)('handles multiple pipe characters in message', () => {
|
|
124
|
+
const raw = 'abc123|John|2025-06-15T10:00:00Z|a|b|c|d|e';
|
|
125
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
126
|
+
(0, vitest_1.expect)(result[0].message).toBe('a|b|c|d|e');
|
|
127
|
+
});
|
|
128
|
+
(0, vitest_1.it)('handles author names with special characters', () => {
|
|
129
|
+
const raw = 'abc123|José García|2025-06-15T10:00:00Z|msg';
|
|
130
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
131
|
+
(0, vitest_1.expect)(result[0].author).toBe('José García');
|
|
132
|
+
});
|
|
133
|
+
(0, vitest_1.it)('handles unicode in commit messages', () => {
|
|
134
|
+
const raw = 'abc123|John|2025-06-15T10:00:00Z|feat: 添加新功能 🎉';
|
|
135
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
136
|
+
(0, vitest_1.expect)(result[0].message).toBe('feat: 添加新功能 🎉');
|
|
137
|
+
});
|
|
138
|
+
(0, vitest_1.it)('filters out lines with empty hash', () => {
|
|
139
|
+
const raw = '|John|2025-06-15T10:00:00Z|msg';
|
|
140
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
141
|
+
(0, vitest_1.expect)(result).toEqual([]);
|
|
142
|
+
});
|
|
143
|
+
(0, vitest_1.it)('filters out lines with empty author', () => {
|
|
144
|
+
const raw = 'abc123||2025-06-15T10:00:00Z|msg';
|
|
145
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
146
|
+
(0, vitest_1.expect)(result).toEqual([]);
|
|
147
|
+
});
|
|
148
|
+
(0, vitest_1.it)('filters out lines with empty date', () => {
|
|
149
|
+
const raw = 'abc123|John||msg';
|
|
150
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
151
|
+
(0, vitest_1.expect)(result).toEqual([]);
|
|
152
|
+
});
|
|
153
|
+
(0, vitest_1.it)('handles lines with only whitespace between valid lines', () => {
|
|
154
|
+
const raw = [
|
|
155
|
+
'abc123|John|2025-06-15T10:00:00Z|msg1',
|
|
156
|
+
' ',
|
|
157
|
+
'',
|
|
158
|
+
'def456|Jane|2025-06-14T09:00:00Z|msg2',
|
|
159
|
+
].join('\n');
|
|
160
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
161
|
+
(0, vitest_1.expect)(result).toHaveLength(2);
|
|
162
|
+
});
|
|
163
|
+
(0, vitest_1.it)('handles Windows-style line endings (CRLF)', () => {
|
|
164
|
+
const raw = 'abc123|John|2025-06-15T10:00:00Z|msg1\r\ndef456|Jane|2025-06-14T09:00:00Z|msg2';
|
|
165
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
166
|
+
// Should still work (trim handles \r)
|
|
167
|
+
(0, vitest_1.expect)(result.length).toBeGreaterThanOrEqual(1);
|
|
168
|
+
});
|
|
169
|
+
(0, vitest_1.it)('handles very long commit messages', () => {
|
|
170
|
+
const longMessage = 'x'.repeat(10000);
|
|
171
|
+
const raw = `abc123|John|2025-06-15T10:00:00Z|${longMessage}`;
|
|
172
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
173
|
+
(0, vitest_1.expect)(result[0].message).toBe(longMessage);
|
|
174
|
+
});
|
|
175
|
+
(0, vitest_1.it)('handles empty message', () => {
|
|
176
|
+
const raw = 'abc123|John|2025-06-15T10:00:00Z|';
|
|
177
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
178
|
+
// Empty message should still result in a valid commit
|
|
179
|
+
(0, vitest_1.expect)(result).toHaveLength(1);
|
|
180
|
+
(0, vitest_1.expect)(result[0].message).toBe('');
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// parseCommitData - Date Handling
|
|
185
|
+
// ============================================================================
|
|
186
|
+
(0, vitest_1.describe)('date handling', () => {
|
|
187
|
+
(0, vitest_1.it)('preserves ISO 8601 dates with positive timezone offset', () => {
|
|
188
|
+
const raw = 'abc123|John|2025-06-15T10:30:00+05:30|msg';
|
|
189
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
190
|
+
(0, vitest_1.expect)(result[0].date).toBe('2025-06-15T10:30:00+05:30');
|
|
191
|
+
});
|
|
192
|
+
(0, vitest_1.it)('preserves ISO 8601 dates with Z suffix (UTC)', () => {
|
|
193
|
+
const raw = 'abc123|John|2025-06-15T10:30:00Z|msg';
|
|
194
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
195
|
+
(0, vitest_1.expect)(result[0].date).toBe('2025-06-15T10:30:00Z');
|
|
196
|
+
});
|
|
197
|
+
(0, vitest_1.it)('preserves negative timezone offsets', () => {
|
|
198
|
+
const raw = 'abc123|John|2025-06-15T10:30:00-08:00|msg';
|
|
199
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
200
|
+
(0, vitest_1.expect)(result[0].date).toBe('2025-06-15T10:30:00-08:00');
|
|
201
|
+
});
|
|
202
|
+
(0, vitest_1.it)('preserves dates at timezone boundaries', () => {
|
|
203
|
+
const raw = 'abc123|John|2025-06-15T23:59:59+12:00|msg';
|
|
204
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
205
|
+
(0, vitest_1.expect)(result[0].date).toBe('2025-06-15T23:59:59+12:00');
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
// ============================================================================
|
|
209
|
+
// parseCommitData - Real-world Scenarios
|
|
210
|
+
// ============================================================================
|
|
211
|
+
(0, vitest_1.describe)('real-world scenarios', () => {
|
|
212
|
+
(0, vitest_1.it)('handles typical git log output', () => {
|
|
213
|
+
const raw = [
|
|
214
|
+
'a1b2c3d|John Doe|2025-06-15T10:30:00-04:00|feat: add user authentication',
|
|
215
|
+
'e4f5g6h|Jane Smith|2025-06-14T16:45:00-04:00|fix: resolve login redirect issue',
|
|
216
|
+
'i7j8k9l|John Doe|2025-06-14T09:00:00-04:00|chore: update dependencies',
|
|
217
|
+
'm0n1o2p|Bob Wilson|2025-06-13T14:20:00-04:00|docs: update README',
|
|
218
|
+
].join('\n');
|
|
219
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
220
|
+
(0, vitest_1.expect)(result).toHaveLength(4);
|
|
221
|
+
(0, vitest_1.expect)(result.map(c => c.hash)).toEqual(['a1b2c3d', 'e4f5g6h', 'i7j8k9l', 'm0n1o2p']);
|
|
222
|
+
(0, vitest_1.expect)(result.map(c => c.author)).toContain('John Doe');
|
|
223
|
+
(0, vitest_1.expect)(result.map(c => c.author)).toContain('Jane Smith');
|
|
224
|
+
});
|
|
225
|
+
(0, vitest_1.it)('handles conventional commit messages', () => {
|
|
226
|
+
const conventionalTypes = ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore'];
|
|
227
|
+
const raw = conventionalTypes
|
|
228
|
+
.map((type, i) => `hash${i}|Author|2025-06-15T10:00:00Z|${type}: description`)
|
|
229
|
+
.join('\n');
|
|
230
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
231
|
+
(0, vitest_1.expect)(result).toHaveLength(conventionalTypes.length);
|
|
232
|
+
conventionalTypes.forEach((type, i) => {
|
|
233
|
+
(0, vitest_1.expect)(result[i].message).toBe(`${type}: description`);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
(0, vitest_1.it)('handles merge commit messages', () => {
|
|
237
|
+
const raw = "abc123|John|2025-06-15T10:00:00Z|Merge pull request #42 from feature/branch";
|
|
238
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
239
|
+
(0, vitest_1.expect)(result[0].message).toBe('Merge pull request #42 from feature/branch');
|
|
240
|
+
});
|
|
241
|
+
(0, vitest_1.it)('handles squash commit messages with multiple lines', () => {
|
|
242
|
+
// Note: In real git log format, multiline messages would be handled differently
|
|
243
|
+
// This tests that a single line with the message is parsed correctly
|
|
244
|
+
const raw = 'abc123|John|2025-06-15T10:00:00Z|feat: implement feature (#123)';
|
|
245
|
+
const result = (0, gitParser_1.parseCommitData)(raw);
|
|
246
|
+
(0, vitest_1.expect)(result[0].message).toBe('feat: implement feature (#123)');
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
// ============================================================================
|
|
251
|
+
// getCurrentGitUser
|
|
252
|
+
// ============================================================================
|
|
253
|
+
(0, vitest_1.describe)('getCurrentGitUser', () => {
|
|
254
|
+
(0, vitest_1.beforeEach)(() => {
|
|
255
|
+
vitest_1.vi.clearAllMocks();
|
|
256
|
+
});
|
|
257
|
+
(0, vitest_1.it)('returns user name from git config', () => {
|
|
258
|
+
mockExecSync.mockReturnValue('John Doe\n');
|
|
259
|
+
const result = (0, gitParser_1.getCurrentGitUser)('/fake/path');
|
|
260
|
+
(0, vitest_1.expect)(result).toBe('John Doe');
|
|
261
|
+
(0, vitest_1.expect)(mockExecSync).toHaveBeenCalledWith('git config user.name', vitest_1.expect.objectContaining({ cwd: '/fake/path' }));
|
|
262
|
+
});
|
|
263
|
+
(0, vitest_1.it)('returns "Unknown" when git config fails', () => {
|
|
264
|
+
mockExecSync.mockImplementation(() => {
|
|
265
|
+
throw new Error('git config failed');
|
|
266
|
+
});
|
|
267
|
+
const result = (0, gitParser_1.getCurrentGitUser)('/fake/path');
|
|
268
|
+
(0, vitest_1.expect)(result).toBe('Unknown');
|
|
269
|
+
});
|
|
270
|
+
(0, vitest_1.it)('returns "Unknown" for empty git config', () => {
|
|
271
|
+
mockExecSync.mockReturnValue('');
|
|
272
|
+
const result = (0, gitParser_1.getCurrentGitUser)('/fake/path');
|
|
273
|
+
(0, vitest_1.expect)(result).toBe('Unknown');
|
|
274
|
+
});
|
|
275
|
+
(0, vitest_1.it)('trims whitespace from user name', () => {
|
|
276
|
+
mockExecSync.mockReturnValue(' Jane Smith \n');
|
|
277
|
+
const result = (0, gitParser_1.getCurrentGitUser)('/fake/path');
|
|
278
|
+
(0, vitest_1.expect)(result).toBe('Jane Smith');
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
// ============================================================================
|
|
282
|
+
// getRepositoryName
|
|
283
|
+
// ============================================================================
|
|
284
|
+
(0, vitest_1.describe)('getRepositoryName', () => {
|
|
285
|
+
(0, vitest_1.beforeEach)(() => {
|
|
286
|
+
vitest_1.vi.clearAllMocks();
|
|
287
|
+
});
|
|
288
|
+
(0, vitest_1.it)('extracts repo name from HTTPS GitHub URL', () => {
|
|
289
|
+
mockExecSync.mockReturnValue('https://github.com/owner/repo-name.git\n');
|
|
290
|
+
const result = (0, gitParser_1.getRepositoryName)('/fake/path');
|
|
291
|
+
(0, vitest_1.expect)(result).toBe('owner/repo-name');
|
|
292
|
+
});
|
|
293
|
+
(0, vitest_1.it)('extracts repo name from SSH GitHub URL', () => {
|
|
294
|
+
mockExecSync.mockReturnValue('git@github.com:owner/repo-name.git\n');
|
|
295
|
+
const result = (0, gitParser_1.getRepositoryName)('/fake/path');
|
|
296
|
+
// Note: Current implementation doesn't fully parse SSH URLs
|
|
297
|
+
// It returns "git@github.com:owner/repo-name" instead of "owner/repo-name"
|
|
298
|
+
// This documents current behavior - could be improved in a future PR
|
|
299
|
+
(0, vitest_1.expect)(result).toBe('git@github.com:owner/repo-name');
|
|
300
|
+
});
|
|
301
|
+
(0, vitest_1.it)('handles URLs without .git suffix', () => {
|
|
302
|
+
mockExecSync.mockReturnValue('https://github.com/owner/repo-name\n');
|
|
303
|
+
const result = (0, gitParser_1.getRepositoryName)('/fake/path');
|
|
304
|
+
(0, vitest_1.expect)(result).toBe('owner/repo-name');
|
|
305
|
+
});
|
|
306
|
+
(0, vitest_1.it)('falls back to directory name when git remote fails', () => {
|
|
307
|
+
mockExecSync.mockImplementation(() => {
|
|
308
|
+
throw new Error('No remote configured');
|
|
309
|
+
});
|
|
310
|
+
const result = (0, gitParser_1.getRepositoryName)('/path/to/my-project');
|
|
311
|
+
(0, vitest_1.expect)(result).toBe('my-project');
|
|
312
|
+
});
|
|
313
|
+
(0, vitest_1.it)('falls back to directory name for empty remote URL', () => {
|
|
314
|
+
mockExecSync.mockReturnValue('');
|
|
315
|
+
const result = (0, gitParser_1.getRepositoryName)('/path/to/my-project');
|
|
316
|
+
(0, vitest_1.expect)(result).toBe('my-project');
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
// ============================================================================
|
|
320
|
+
// getRepositoryUrl
|
|
321
|
+
// ============================================================================
|
|
322
|
+
(0, vitest_1.describe)('getRepositoryUrl', () => {
|
|
323
|
+
(0, vitest_1.beforeEach)(() => {
|
|
324
|
+
vitest_1.vi.clearAllMocks();
|
|
325
|
+
});
|
|
326
|
+
(0, vitest_1.it)('returns HTTPS URL as-is (without .git)', () => {
|
|
327
|
+
mockExecSync.mockReturnValue('https://github.com/owner/repo.git\n');
|
|
328
|
+
const result = (0, gitParser_1.getRepositoryUrl)('/fake/path');
|
|
329
|
+
(0, vitest_1.expect)(result).toBe('https://github.com/owner/repo');
|
|
330
|
+
});
|
|
331
|
+
(0, vitest_1.it)('converts SSH URL to HTTPS format', () => {
|
|
332
|
+
mockExecSync.mockReturnValue('git@github.com:owner/repo.git\n');
|
|
333
|
+
const result = (0, gitParser_1.getRepositoryUrl)('/fake/path');
|
|
334
|
+
(0, vitest_1.expect)(result).toBe('https://github.com/owner/repo');
|
|
335
|
+
});
|
|
336
|
+
(0, vitest_1.it)('handles GitLab SSH URLs', () => {
|
|
337
|
+
mockExecSync.mockReturnValue('git@gitlab.com:owner/repo.git\n');
|
|
338
|
+
const result = (0, gitParser_1.getRepositoryUrl)('/fake/path');
|
|
339
|
+
(0, vitest_1.expect)(result).toBe('https://gitlab.com/owner/repo');
|
|
340
|
+
});
|
|
341
|
+
(0, vitest_1.it)('handles Bitbucket SSH URLs', () => {
|
|
342
|
+
mockExecSync.mockReturnValue('git@bitbucket.org:owner/repo.git\n');
|
|
343
|
+
const result = (0, gitParser_1.getRepositoryUrl)('/fake/path');
|
|
344
|
+
(0, vitest_1.expect)(result).toBe('https://bitbucket.org/owner/repo');
|
|
345
|
+
});
|
|
346
|
+
(0, vitest_1.it)('returns empty string when git remote fails', () => {
|
|
347
|
+
mockExecSync.mockImplementation(() => {
|
|
348
|
+
throw new Error('No remote configured');
|
|
349
|
+
});
|
|
350
|
+
const result = (0, gitParser_1.getRepositoryUrl)('/fake/path');
|
|
351
|
+
(0, vitest_1.expect)(result).toBe('');
|
|
352
|
+
});
|
|
353
|
+
(0, vitest_1.it)('returns empty string for empty remote URL', () => {
|
|
354
|
+
mockExecSync.mockReturnValue('');
|
|
355
|
+
const result = (0, gitParser_1.getRepositoryUrl)('/fake/path');
|
|
356
|
+
(0, vitest_1.expect)(result).toBe('');
|
|
357
|
+
});
|
|
358
|
+
(0, vitest_1.it)('handles HTTPS URL without .git suffix', () => {
|
|
359
|
+
mockExecSync.mockReturnValue('https://github.com/owner/repo\n');
|
|
360
|
+
const result = (0, gitParser_1.getRepositoryUrl)('/fake/path');
|
|
361
|
+
(0, vitest_1.expect)(result).toBe('https://github.com/owner/repo');
|
|
362
|
+
});
|
|
363
|
+
});
|