@m3hti/commit-genie 3.0.1 → 3.1.1
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/dist/services/analyzerService.d.ts +0 -141
- package/dist/services/analyzerService.d.ts.map +1 -1
- package/dist/services/analyzerService.js +23 -1880
- package/dist/services/analyzerService.js.map +1 -1
- package/dist/services/analyzerService.test.js +97 -0
- package/dist/services/analyzerService.test.js.map +1 -1
- package/dist/services/breakingChangeDetector.d.ts +9 -0
- package/dist/services/breakingChangeDetector.d.ts.map +1 -0
- package/dist/services/breakingChangeDetector.js +76 -0
- package/dist/services/breakingChangeDetector.js.map +1 -0
- package/dist/services/commitTypeDetector.d.ts +32 -0
- package/dist/services/commitTypeDetector.d.ts.map +1 -0
- package/dist/services/commitTypeDetector.js +490 -0
- package/dist/services/commitTypeDetector.js.map +1 -0
- package/dist/services/descriptionGenerator.d.ts +58 -0
- package/dist/services/descriptionGenerator.d.ts.map +1 -0
- package/dist/services/descriptionGenerator.js +569 -0
- package/dist/services/descriptionGenerator.js.map +1 -0
- package/dist/services/fileContentAnalyzer.d.ts +10 -0
- package/dist/services/fileContentAnalyzer.d.ts.map +1 -0
- package/dist/services/fileContentAnalyzer.js +170 -0
- package/dist/services/fileContentAnalyzer.js.map +1 -0
- package/dist/services/fileContentAnalyzer.test.d.ts +2 -0
- package/dist/services/fileContentAnalyzer.test.d.ts.map +1 -0
- package/dist/services/fileContentAnalyzer.test.js +118 -0
- package/dist/services/fileContentAnalyzer.test.js.map +1 -0
- package/dist/services/gitService.test.js +242 -24
- package/dist/services/gitService.test.js.map +1 -1
- package/dist/services/hookService.test.d.ts +2 -0
- package/dist/services/hookService.test.d.ts.map +1 -0
- package/dist/services/hookService.test.js +182 -0
- package/dist/services/hookService.test.js.map +1 -0
- package/dist/services/lintService.test.d.ts +2 -0
- package/dist/services/lintService.test.d.ts.map +1 -0
- package/dist/services/lintService.test.js +288 -0
- package/dist/services/lintService.test.js.map +1 -0
- package/dist/services/messageBuilder.d.ts +16 -0
- package/dist/services/messageBuilder.d.ts.map +1 -0
- package/dist/services/messageBuilder.js +135 -0
- package/dist/services/messageBuilder.js.map +1 -0
- package/dist/services/scopeDetector.d.ts +6 -0
- package/dist/services/scopeDetector.d.ts.map +1 -0
- package/dist/services/scopeDetector.js +51 -0
- package/dist/services/scopeDetector.js.map +1 -0
- package/dist/services/semanticAnalyzer.d.ts +25 -0
- package/dist/services/semanticAnalyzer.d.ts.map +1 -0
- package/dist/services/semanticAnalyzer.js +713 -0
- package/dist/services/semanticAnalyzer.js.map +1 -0
- package/dist/services/splitService.test.d.ts +2 -0
- package/dist/services/splitService.test.d.ts.map +1 -0
- package/dist/services/splitService.test.js +190 -0
- package/dist/services/splitService.test.js.map +1 -0
- package/dist/services/statsService.test.d.ts +2 -0
- package/dist/services/statsService.test.d.ts.map +1 -0
- package/dist/services/statsService.test.js +211 -0
- package/dist/services/statsService.test.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.analyzeFileContent = analyzeFileContent;
|
|
4
|
+
const gitService_1 = require("./gitService");
|
|
5
|
+
/**
|
|
6
|
+
* Analyze file content changes for non-JS/TS files.
|
|
7
|
+
* Returns structural descriptions of what changed (keys, selectors, headings, etc.)
|
|
8
|
+
*/
|
|
9
|
+
function analyzeFileContent(filePath, diff) {
|
|
10
|
+
const ext = filePath.split('.').pop()?.toLowerCase();
|
|
11
|
+
switch (ext) {
|
|
12
|
+
case 'json':
|
|
13
|
+
return analyzeJsonChanges(filePath);
|
|
14
|
+
case 'css':
|
|
15
|
+
case 'scss':
|
|
16
|
+
case 'sass':
|
|
17
|
+
case 'less':
|
|
18
|
+
return analyzeCssChanges(diff);
|
|
19
|
+
case 'md':
|
|
20
|
+
case 'mdx':
|
|
21
|
+
return analyzeMarkdownChanges(diff);
|
|
22
|
+
case 'yml':
|
|
23
|
+
case 'yaml':
|
|
24
|
+
return analyzeYamlChanges(diff);
|
|
25
|
+
default:
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function analyzeJsonChanges(filePath) {
|
|
30
|
+
try {
|
|
31
|
+
const oldContent = gitService_1.GitService.getFileAtHead(filePath);
|
|
32
|
+
const newContent = gitService_1.GitService.getStagedFileContent(filePath);
|
|
33
|
+
// New file
|
|
34
|
+
if (!oldContent && newContent) {
|
|
35
|
+
const newObj = JSON.parse(newContent);
|
|
36
|
+
const keys = Object.keys(newObj).slice(0, 5);
|
|
37
|
+
return { changedElements: keys, changeKind: 'added' };
|
|
38
|
+
}
|
|
39
|
+
// Deleted file
|
|
40
|
+
if (oldContent && !newContent) {
|
|
41
|
+
const oldObj = JSON.parse(oldContent);
|
|
42
|
+
const keys = Object.keys(oldObj).slice(0, 5);
|
|
43
|
+
return { changedElements: keys, changeKind: 'removed' };
|
|
44
|
+
}
|
|
45
|
+
if (!oldContent || !newContent)
|
|
46
|
+
return null;
|
|
47
|
+
const oldObj = JSON.parse(oldContent);
|
|
48
|
+
const newObj = JSON.parse(newContent);
|
|
49
|
+
const oldKeys = new Set(Object.keys(oldObj));
|
|
50
|
+
const newKeys = new Set(Object.keys(newObj));
|
|
51
|
+
const addedKeys = [];
|
|
52
|
+
const removedKeys = [];
|
|
53
|
+
const modifiedKeys = [];
|
|
54
|
+
for (const key of newKeys) {
|
|
55
|
+
if (!oldKeys.has(key))
|
|
56
|
+
addedKeys.push(key);
|
|
57
|
+
}
|
|
58
|
+
for (const key of oldKeys) {
|
|
59
|
+
if (!newKeys.has(key))
|
|
60
|
+
removedKeys.push(key);
|
|
61
|
+
}
|
|
62
|
+
for (const key of newKeys) {
|
|
63
|
+
if (oldKeys.has(key) && JSON.stringify(oldObj[key]) !== JSON.stringify(newObj[key])) {
|
|
64
|
+
modifiedKeys.push(key);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const changedElements = [...addedKeys, ...removedKeys, ...modifiedKeys];
|
|
68
|
+
if (changedElements.length === 0)
|
|
69
|
+
return null;
|
|
70
|
+
let changeKind;
|
|
71
|
+
if (addedKeys.length > 0 && removedKeys.length === 0 && modifiedKeys.length === 0) {
|
|
72
|
+
changeKind = 'added';
|
|
73
|
+
}
|
|
74
|
+
else if (removedKeys.length > 0 && addedKeys.length === 0 && modifiedKeys.length === 0) {
|
|
75
|
+
changeKind = 'removed';
|
|
76
|
+
}
|
|
77
|
+
else if (modifiedKeys.length > 0 && addedKeys.length === 0 && removedKeys.length === 0) {
|
|
78
|
+
changeKind = 'modified';
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
changeKind = 'mixed';
|
|
82
|
+
}
|
|
83
|
+
return { changedElements: changedElements.slice(0, 5), changeKind };
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function analyzeCssChanges(diff) {
|
|
90
|
+
if (!diff)
|
|
91
|
+
return null;
|
|
92
|
+
const selectors = [];
|
|
93
|
+
const lines = diff.split('\n');
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
if (!line.startsWith('+') && !line.startsWith('-'))
|
|
96
|
+
continue;
|
|
97
|
+
if (line.startsWith('+++') || line.startsWith('---'))
|
|
98
|
+
continue;
|
|
99
|
+
const content = line.substring(1).trim();
|
|
100
|
+
// Match CSS selectors: .class {, #id {, element {
|
|
101
|
+
const selectorMatch = content.match(/^([.#@]?[a-zA-Z_-][\w-]*(?:\s*[>,+~]\s*[.#]?[\w-]+)*)\s*\{/);
|
|
102
|
+
if (selectorMatch) {
|
|
103
|
+
const selector = selectorMatch[1].trim();
|
|
104
|
+
if (!selectors.includes(selector)) {
|
|
105
|
+
selectors.push(selector);
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
// Match @-rules: @media (...), @keyframes name
|
|
110
|
+
const atRuleMatch = content.match(/^(@(?:media|keyframes|font-face|supports|layer)\b[^{]*)/);
|
|
111
|
+
if (atRuleMatch) {
|
|
112
|
+
const rule = atRuleMatch[1].trim();
|
|
113
|
+
const short = rule.length > 30 ? rule.substring(0, 30) + '...' : rule;
|
|
114
|
+
if (!selectors.includes(short)) {
|
|
115
|
+
selectors.push(short);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (selectors.length === 0)
|
|
120
|
+
return null;
|
|
121
|
+
return { changedElements: selectors.slice(0, 5), changeKind: 'modified' };
|
|
122
|
+
}
|
|
123
|
+
function analyzeMarkdownChanges(diff) {
|
|
124
|
+
if (!diff)
|
|
125
|
+
return null;
|
|
126
|
+
const headings = [];
|
|
127
|
+
const lines = diff.split('\n');
|
|
128
|
+
for (const line of lines) {
|
|
129
|
+
if (!line.startsWith('+') && !line.startsWith('-'))
|
|
130
|
+
continue;
|
|
131
|
+
if (line.startsWith('+++') || line.startsWith('---'))
|
|
132
|
+
continue;
|
|
133
|
+
const content = line.substring(1);
|
|
134
|
+
const headingMatch = content.match(/^(#{1,6})\s+(.+)$/);
|
|
135
|
+
if (headingMatch) {
|
|
136
|
+
const title = headingMatch[2].trim();
|
|
137
|
+
if (!headings.includes(title)) {
|
|
138
|
+
headings.push(title);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (headings.length === 0)
|
|
143
|
+
return null;
|
|
144
|
+
return { changedElements: headings.slice(0, 5), changeKind: 'modified' };
|
|
145
|
+
}
|
|
146
|
+
function analyzeYamlChanges(diff) {
|
|
147
|
+
if (!diff)
|
|
148
|
+
return null;
|
|
149
|
+
const keys = [];
|
|
150
|
+
const lines = diff.split('\n');
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
if (!line.startsWith('+') && !line.startsWith('-'))
|
|
153
|
+
continue;
|
|
154
|
+
if (line.startsWith('+++') || line.startsWith('---'))
|
|
155
|
+
continue;
|
|
156
|
+
const content = line.substring(1);
|
|
157
|
+
// Match top-level keys (no leading whitespace)
|
|
158
|
+
const topLevelMatch = content.match(/^([a-zA-Z_][\w.-]*)\s*:/);
|
|
159
|
+
if (topLevelMatch) {
|
|
160
|
+
const key = topLevelMatch[1];
|
|
161
|
+
if (!keys.includes(key)) {
|
|
162
|
+
keys.push(key);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
if (keys.length === 0)
|
|
167
|
+
return null;
|
|
168
|
+
return { changedElements: keys.slice(0, 5), changeKind: 'modified' };
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=fileContentAnalyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileContentAnalyzer.js","sourceRoot":"","sources":["../../src/services/fileContentAnalyzer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileContentAnalyzer.test.d.ts","sourceRoot":"","sources":["../../src/services/fileContentAnalyzer.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const fileContentAnalyzer_1 = require("./fileContentAnalyzer");
|
|
4
|
+
const gitService_1 = require("./gitService");
|
|
5
|
+
jest.mock('./gitService');
|
|
6
|
+
const mockedGitService = gitService_1.GitService;
|
|
7
|
+
describe('fileContentAnalyzer', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
jest.clearAllMocks();
|
|
10
|
+
});
|
|
11
|
+
describe('JSON parser', () => {
|
|
12
|
+
it('should detect changed top-level keys in package.json', () => {
|
|
13
|
+
mockedGitService.getFileAtHead.mockReturnValue(JSON.stringify({ name: 'app', version: '1.0.0', scripts: { test: 'jest' } }, null, 2));
|
|
14
|
+
mockedGitService.getStagedFileContent.mockReturnValue(JSON.stringify({ name: 'app', version: '1.1.0', scripts: { test: 'jest', build: 'tsc' } }, null, 2));
|
|
15
|
+
const result = (0, fileContentAnalyzer_1.analyzeFileContent)('package.json', '');
|
|
16
|
+
expect(result).not.toBeNull();
|
|
17
|
+
expect(result.changedElements).toContain('version');
|
|
18
|
+
expect(result.changedElements).toContain('scripts');
|
|
19
|
+
});
|
|
20
|
+
it('should detect added top-level keys', () => {
|
|
21
|
+
mockedGitService.getFileAtHead.mockReturnValue(JSON.stringify({ name: 'app' }, null, 2));
|
|
22
|
+
mockedGitService.getStagedFileContent.mockReturnValue(JSON.stringify({ name: 'app', description: 'My app' }, null, 2));
|
|
23
|
+
const result = (0, fileContentAnalyzer_1.analyzeFileContent)('package.json', '');
|
|
24
|
+
expect(result).not.toBeNull();
|
|
25
|
+
expect(result.changedElements).toContain('description');
|
|
26
|
+
expect(result.changeKind).toBe('added');
|
|
27
|
+
});
|
|
28
|
+
it('should detect removed top-level keys', () => {
|
|
29
|
+
mockedGitService.getFileAtHead.mockReturnValue(JSON.stringify({ name: 'app', deprecated: true }, null, 2));
|
|
30
|
+
mockedGitService.getStagedFileContent.mockReturnValue(JSON.stringify({ name: 'app' }, null, 2));
|
|
31
|
+
const result = (0, fileContentAnalyzer_1.analyzeFileContent)('package.json', '');
|
|
32
|
+
expect(result).not.toBeNull();
|
|
33
|
+
expect(result.changedElements).toContain('deprecated');
|
|
34
|
+
expect(result.changeKind).toBe('removed');
|
|
35
|
+
});
|
|
36
|
+
it('should return null for unparseable JSON', () => {
|
|
37
|
+
mockedGitService.getFileAtHead.mockReturnValue('not json');
|
|
38
|
+
mockedGitService.getStagedFileContent.mockReturnValue('also not json');
|
|
39
|
+
const result = (0, fileContentAnalyzer_1.analyzeFileContent)('config.json', '');
|
|
40
|
+
expect(result).toBeNull();
|
|
41
|
+
});
|
|
42
|
+
it('should handle new JSON files (no old content)', () => {
|
|
43
|
+
mockedGitService.getFileAtHead.mockReturnValue(null);
|
|
44
|
+
mockedGitService.getStagedFileContent.mockReturnValue(JSON.stringify({ name: 'app', version: '1.0.0' }, null, 2));
|
|
45
|
+
const result = (0, fileContentAnalyzer_1.analyzeFileContent)('config.json', '');
|
|
46
|
+
expect(result).not.toBeNull();
|
|
47
|
+
expect(result.changeKind).toBe('added');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe('CSS parser', () => {
|
|
51
|
+
it('should detect changed selectors from diff', () => {
|
|
52
|
+
const diff = '+.header {\n' +
|
|
53
|
+
'+ background: red;\n' +
|
|
54
|
+
'+}\n' +
|
|
55
|
+
'-#footer {\n' +
|
|
56
|
+
'- display: none;\n' +
|
|
57
|
+
'-}\n';
|
|
58
|
+
const result = (0, fileContentAnalyzer_1.analyzeFileContent)('styles.css', diff);
|
|
59
|
+
expect(result).not.toBeNull();
|
|
60
|
+
expect(result.changedElements).toContain('.header');
|
|
61
|
+
expect(result.changedElements).toContain('#footer');
|
|
62
|
+
});
|
|
63
|
+
it('should detect @media and @keyframes', () => {
|
|
64
|
+
const diff = '+@media (max-width: 768px) {\n' +
|
|
65
|
+
'+ .container { width: 100%; }\n' +
|
|
66
|
+
'+}\n';
|
|
67
|
+
const result = (0, fileContentAnalyzer_1.analyzeFileContent)('styles.css', diff);
|
|
68
|
+
expect(result).not.toBeNull();
|
|
69
|
+
expect(result.changedElements.some((e) => e.includes('@media'))).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
it('should return null for empty diff', () => {
|
|
72
|
+
const result = (0, fileContentAnalyzer_1.analyzeFileContent)('styles.css', '');
|
|
73
|
+
expect(result).toBeNull();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
describe('Markdown parser', () => {
|
|
77
|
+
it('should detect changed headings', () => {
|
|
78
|
+
const diff = '+## Installation\n' +
|
|
79
|
+
'+\n' +
|
|
80
|
+
'+Run npm install to get started.\n' +
|
|
81
|
+
'-## Setup\n' +
|
|
82
|
+
'-\n' +
|
|
83
|
+
'-Follow these steps.\n';
|
|
84
|
+
const result = (0, fileContentAnalyzer_1.analyzeFileContent)('README.md', diff);
|
|
85
|
+
expect(result).not.toBeNull();
|
|
86
|
+
expect(result.changedElements).toContain('Installation');
|
|
87
|
+
expect(result.changedElements).toContain('Setup');
|
|
88
|
+
});
|
|
89
|
+
it('should handle multiple heading levels', () => {
|
|
90
|
+
const diff = '+# Overview\n+### Quick Start\n';
|
|
91
|
+
const result = (0, fileContentAnalyzer_1.analyzeFileContent)('docs.md', diff);
|
|
92
|
+
expect(result).not.toBeNull();
|
|
93
|
+
expect(result.changedElements).toContain('Overview');
|
|
94
|
+
expect(result.changedElements).toContain('Quick Start');
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('YAML parser', () => {
|
|
98
|
+
it('should detect changed top-level keys', () => {
|
|
99
|
+
const diff = '+services:\n' +
|
|
100
|
+
'+ web:\n' +
|
|
101
|
+
'+ ports:\n' +
|
|
102
|
+
'+ - "8080:80"\n' +
|
|
103
|
+
'-volumes:\n' +
|
|
104
|
+
'- data:\n';
|
|
105
|
+
const result = (0, fileContentAnalyzer_1.analyzeFileContent)('docker-compose.yml', diff);
|
|
106
|
+
expect(result).not.toBeNull();
|
|
107
|
+
expect(result.changedElements).toContain('services');
|
|
108
|
+
expect(result.changedElements).toContain('volumes');
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe('unsupported files', () => {
|
|
112
|
+
it('should return null for unknown file types', () => {
|
|
113
|
+
const result = (0, fileContentAnalyzer_1.analyzeFileContent)('binary.exe', '');
|
|
114
|
+
expect(result).toBeNull();
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
//# sourceMappingURL=fileContentAnalyzer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fileContentAnalyzer.test.js","sourceRoot":"","sources":["../../src/services/fileContentAnalyzer.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -35,6 +35,21 @@ describe('GitService', () => {
|
|
|
35
35
|
expect(status.staged[1]).toEqual({ status: 'M', path: 'src/modified.ts' });
|
|
36
36
|
expect(status.staged[2]).toEqual({ status: 'D', path: 'src/deleted.ts' });
|
|
37
37
|
});
|
|
38
|
+
it('should parse unstaged files correctly', () => {
|
|
39
|
+
mockedExecSync.mockReturnValueOnce(' M src/unstaged.ts\n');
|
|
40
|
+
const status = gitService_1.GitService.getStatus();
|
|
41
|
+
expect(status.unstaged).toHaveLength(1);
|
|
42
|
+
expect(status.unstaged[0]).toEqual({ status: 'M', path: 'src/unstaged.ts' });
|
|
43
|
+
expect(status.staged).toHaveLength(0);
|
|
44
|
+
expect(status.hasStagedChanges).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
it('should not include untracked files in staged or unstaged', () => {
|
|
47
|
+
mockedExecSync.mockReturnValueOnce('?? untracked.ts\n');
|
|
48
|
+
const status = gitService_1.GitService.getStatus();
|
|
49
|
+
expect(status.staged).toHaveLength(0);
|
|
50
|
+
expect(status.unstaged).toHaveLength(0);
|
|
51
|
+
expect(status.hasStagedChanges).toBe(false);
|
|
52
|
+
});
|
|
38
53
|
it('should return empty arrays when no changes', () => {
|
|
39
54
|
mockedExecSync.mockReturnValueOnce('');
|
|
40
55
|
const status = gitService_1.GitService.getStatus();
|
|
@@ -42,11 +57,25 @@ describe('GitService', () => {
|
|
|
42
57
|
expect(status.staged).toHaveLength(0);
|
|
43
58
|
expect(status.unstaged).toHaveLength(0);
|
|
44
59
|
});
|
|
60
|
+
it('should handle mixed staged and unstaged changes in the same file', () => {
|
|
61
|
+
mockedExecSync.mockReturnValueOnce('MM src/both.ts\n');
|
|
62
|
+
const status = gitService_1.GitService.getStatus();
|
|
63
|
+
expect(status.staged).toHaveLength(1);
|
|
64
|
+
expect(status.staged[0]).toEqual({ status: 'M', path: 'src/both.ts' });
|
|
65
|
+
// Source only adds to unstaged when stagedStatus === ' '
|
|
66
|
+
expect(status.unstaged).toHaveLength(0);
|
|
67
|
+
});
|
|
45
68
|
it('should handle renamed files', () => {
|
|
46
69
|
mockedExecSync.mockReturnValueOnce('R old.ts -> new.ts\n');
|
|
47
70
|
const status = gitService_1.GitService.getStatus();
|
|
48
71
|
expect(status.staged[0].status).toBe('R');
|
|
49
72
|
});
|
|
73
|
+
it('should throw on git error', () => {
|
|
74
|
+
mockedExecSync.mockImplementationOnce(() => {
|
|
75
|
+
throw new Error('fatal: not a git repository');
|
|
76
|
+
});
|
|
77
|
+
expect(() => gitService_1.GitService.getStatus()).toThrow('Failed to get git status');
|
|
78
|
+
});
|
|
50
79
|
});
|
|
51
80
|
describe('getDiffStats', () => {
|
|
52
81
|
it('should parse diff stats correctly', () => {
|
|
@@ -63,13 +92,26 @@ describe('GitService', () => {
|
|
|
63
92
|
expect(stats.insertions).toBe(0);
|
|
64
93
|
expect(stats.deletions).toBe(0);
|
|
65
94
|
});
|
|
66
|
-
it('should handle single file
|
|
95
|
+
it('should handle single file with insertions only', () => {
|
|
67
96
|
mockedExecSync.mockReturnValueOnce(' 1 file changed, 5 insertions(+)\n');
|
|
68
97
|
const stats = gitService_1.GitService.getDiffStats();
|
|
69
98
|
expect(stats.filesChanged).toBe(1);
|
|
70
99
|
expect(stats.insertions).toBe(5);
|
|
71
100
|
expect(stats.deletions).toBe(0);
|
|
72
101
|
});
|
|
102
|
+
it('should handle single file with deletions only', () => {
|
|
103
|
+
mockedExecSync.mockReturnValueOnce(' 1 file changed, 3 deletions(-)\n');
|
|
104
|
+
const stats = gitService_1.GitService.getDiffStats();
|
|
105
|
+
expect(stats.filesChanged).toBe(1);
|
|
106
|
+
expect(stats.insertions).toBe(0);
|
|
107
|
+
expect(stats.deletions).toBe(3);
|
|
108
|
+
});
|
|
109
|
+
it('should throw on git error', () => {
|
|
110
|
+
mockedExecSync.mockImplementationOnce(() => {
|
|
111
|
+
throw new Error('fatal: not a git repository');
|
|
112
|
+
});
|
|
113
|
+
expect(() => gitService_1.GitService.getDiffStats()).toThrow('Failed to get diff stats');
|
|
114
|
+
});
|
|
73
115
|
});
|
|
74
116
|
describe('getDiff', () => {
|
|
75
117
|
it('should return diff content', () => {
|
|
@@ -80,9 +122,24 @@ describe('GitService', () => {
|
|
|
80
122
|
const diff = gitService_1.GitService.getDiff();
|
|
81
123
|
expect(diff).toBe(mockDiff);
|
|
82
124
|
});
|
|
125
|
+
it('should throw on git error', () => {
|
|
126
|
+
mockedExecSync.mockImplementationOnce(() => {
|
|
127
|
+
throw new Error('fatal: not a git repository');
|
|
128
|
+
});
|
|
129
|
+
expect(() => gitService_1.GitService.getDiff()).toThrow('Failed to get diff');
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
describe('getStagedFiles', () => {
|
|
133
|
+
it('should return staged files from getStatus', () => {
|
|
134
|
+
mockedExecSync.mockReturnValueOnce('M src/modified.ts\nA src/new.ts\n');
|
|
135
|
+
const files = gitService_1.GitService.getStagedFiles();
|
|
136
|
+
expect(files).toHaveLength(2);
|
|
137
|
+
expect(files[0]).toEqual({ status: 'M', path: 'src/modified.ts' });
|
|
138
|
+
expect(files[1]).toEqual({ status: 'A', path: 'src/new.ts' });
|
|
139
|
+
});
|
|
83
140
|
});
|
|
84
141
|
describe('commit', () => {
|
|
85
|
-
it('should execute git commit with message', () => {
|
|
142
|
+
it('should execute git commit with message via stdin', () => {
|
|
86
143
|
mockedSpawnSync.mockReturnValueOnce({
|
|
87
144
|
status: 0,
|
|
88
145
|
stdout: '',
|
|
@@ -111,7 +168,7 @@ describe('GitService', () => {
|
|
|
111
168
|
input: multiLineMessage,
|
|
112
169
|
}));
|
|
113
170
|
});
|
|
114
|
-
it('should throw error when commit fails', () => {
|
|
171
|
+
it('should throw error with stderr when commit fails', () => {
|
|
115
172
|
mockedSpawnSync.mockReturnValueOnce({
|
|
116
173
|
status: 1,
|
|
117
174
|
stdout: '',
|
|
@@ -122,6 +179,186 @@ describe('GitService', () => {
|
|
|
122
179
|
});
|
|
123
180
|
expect(() => gitService_1.GitService.commit('test')).toThrow('Failed to commit');
|
|
124
181
|
});
|
|
182
|
+
it('should use stdout when stderr is empty on failure', () => {
|
|
183
|
+
mockedSpawnSync.mockReturnValueOnce({
|
|
184
|
+
status: 1,
|
|
185
|
+
stdout: 'stdout error message',
|
|
186
|
+
stderr: '',
|
|
187
|
+
pid: 123,
|
|
188
|
+
output: [],
|
|
189
|
+
signal: null,
|
|
190
|
+
});
|
|
191
|
+
expect(() => gitService_1.GitService.commit('test')).toThrow('Failed to commit: stdout error message');
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
describe('amendCommit', () => {
|
|
195
|
+
it('should execute git commit --amend with message via stdin', () => {
|
|
196
|
+
mockedSpawnSync.mockReturnValueOnce({
|
|
197
|
+
status: 0,
|
|
198
|
+
stdout: '',
|
|
199
|
+
stderr: '',
|
|
200
|
+
pid: 123,
|
|
201
|
+
output: [],
|
|
202
|
+
signal: null,
|
|
203
|
+
});
|
|
204
|
+
expect(() => gitService_1.GitService.amendCommit('fix: correct typo')).not.toThrow();
|
|
205
|
+
expect(mockedSpawnSync).toHaveBeenCalledWith('git', ['commit', '--amend', '-F', '-'], expect.objectContaining({
|
|
206
|
+
input: 'fix: correct typo',
|
|
207
|
+
}));
|
|
208
|
+
});
|
|
209
|
+
it('should throw error when amend fails', () => {
|
|
210
|
+
mockedSpawnSync.mockReturnValueOnce({
|
|
211
|
+
status: 1,
|
|
212
|
+
stdout: '',
|
|
213
|
+
stderr: 'cannot amend',
|
|
214
|
+
pid: 123,
|
|
215
|
+
output: [],
|
|
216
|
+
signal: null,
|
|
217
|
+
});
|
|
218
|
+
expect(() => gitService_1.GitService.amendCommit('test')).toThrow('Failed to amend commit');
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
describe('getLastCommitMessage', () => {
|
|
222
|
+
it('should return the last commit message trimmed', () => {
|
|
223
|
+
mockedExecSync.mockReturnValueOnce('feat: initial commit\n');
|
|
224
|
+
const msg = gitService_1.GitService.getLastCommitMessage();
|
|
225
|
+
expect(msg).toBe('feat: initial commit');
|
|
226
|
+
});
|
|
227
|
+
it('should return null when output is empty', () => {
|
|
228
|
+
mockedExecSync.mockReturnValueOnce(' \n');
|
|
229
|
+
const msg = gitService_1.GitService.getLastCommitMessage();
|
|
230
|
+
expect(msg).toBeNull();
|
|
231
|
+
});
|
|
232
|
+
it('should return null on error', () => {
|
|
233
|
+
mockedExecSync.mockImplementationOnce(() => {
|
|
234
|
+
throw new Error('no commits yet');
|
|
235
|
+
});
|
|
236
|
+
expect(gitService_1.GitService.getLastCommitMessage()).toBeNull();
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
describe('getLastCommitFiles', () => {
|
|
240
|
+
it('should parse tab-separated file change lines', () => {
|
|
241
|
+
mockedExecSync.mockReturnValueOnce('M\tsrc/index.ts\nA\tsrc/new.ts\nD\tsrc/old.ts\n');
|
|
242
|
+
const files = gitService_1.GitService.getLastCommitFiles();
|
|
243
|
+
expect(files).toHaveLength(3);
|
|
244
|
+
expect(files[0]).toEqual({ status: 'M', path: 'src/index.ts' });
|
|
245
|
+
expect(files[1]).toEqual({ status: 'A', path: 'src/new.ts' });
|
|
246
|
+
expect(files[2]).toEqual({ status: 'D', path: 'src/old.ts' });
|
|
247
|
+
});
|
|
248
|
+
it('should return empty array on error', () => {
|
|
249
|
+
mockedExecSync.mockImplementationOnce(() => {
|
|
250
|
+
throw new Error('no commits');
|
|
251
|
+
});
|
|
252
|
+
expect(gitService_1.GitService.getLastCommitFiles()).toEqual([]);
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
describe('getLastCommitDiff', () => {
|
|
256
|
+
it('should return diff string', () => {
|
|
257
|
+
const mockDiff = 'diff --git a/f.ts b/f.ts\n+new line\n';
|
|
258
|
+
mockedExecSync.mockReturnValueOnce(mockDiff);
|
|
259
|
+
expect(gitService_1.GitService.getLastCommitDiff()).toBe(mockDiff);
|
|
260
|
+
});
|
|
261
|
+
it('should return empty string on error', () => {
|
|
262
|
+
mockedExecSync.mockImplementationOnce(() => {
|
|
263
|
+
throw new Error('no commits');
|
|
264
|
+
});
|
|
265
|
+
expect(gitService_1.GitService.getLastCommitDiff()).toBe('');
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
describe('hasCommits', () => {
|
|
269
|
+
it('should return true when there are commits', () => {
|
|
270
|
+
mockedExecSync.mockReturnValueOnce(Buffer.from('abc123'));
|
|
271
|
+
expect(gitService_1.GitService.hasCommits()).toBe(true);
|
|
272
|
+
});
|
|
273
|
+
it('should return false when there are no commits', () => {
|
|
274
|
+
mockedExecSync.mockImplementationOnce(() => {
|
|
275
|
+
throw new Error('unknown revision HEAD');
|
|
276
|
+
});
|
|
277
|
+
expect(gitService_1.GitService.hasCommits()).toBe(false);
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
describe('getGitRoot', () => {
|
|
281
|
+
it('should return the trimmed root path', () => {
|
|
282
|
+
mockedExecSync.mockReturnValueOnce('/home/user/project\n');
|
|
283
|
+
expect(gitService_1.GitService.getGitRoot()).toBe('/home/user/project');
|
|
284
|
+
});
|
|
285
|
+
it('should throw on error', () => {
|
|
286
|
+
mockedExecSync.mockImplementationOnce(() => {
|
|
287
|
+
throw new Error('not a git repository');
|
|
288
|
+
});
|
|
289
|
+
expect(() => gitService_1.GitService.getGitRoot()).toThrow('Failed to get git root directory');
|
|
290
|
+
});
|
|
291
|
+
});
|
|
292
|
+
describe('getGitDir', () => {
|
|
293
|
+
it('should resolve relative .git path to absolute', () => {
|
|
294
|
+
mockedExecSync.mockReturnValueOnce('.git\n');
|
|
295
|
+
const result = gitService_1.GitService.getGitDir();
|
|
296
|
+
// Should resolve to an absolute path ending with .git
|
|
297
|
+
expect(result.endsWith('.git')).toBe(true);
|
|
298
|
+
expect(result.length).toBeGreaterThan(4); // more than just ".git"
|
|
299
|
+
});
|
|
300
|
+
it('should return absolute Unix paths as-is', () => {
|
|
301
|
+
mockedExecSync.mockReturnValueOnce('/home/user/project/.git\n');
|
|
302
|
+
const result = gitService_1.GitService.getGitDir();
|
|
303
|
+
expect(result).toBe('/home/user/project/.git');
|
|
304
|
+
});
|
|
305
|
+
it('should throw on error', () => {
|
|
306
|
+
mockedExecSync.mockImplementationOnce(() => {
|
|
307
|
+
throw new Error('not a git repository');
|
|
308
|
+
});
|
|
309
|
+
expect(() => gitService_1.GitService.getGitDir()).toThrow('Failed to get git directory');
|
|
310
|
+
});
|
|
311
|
+
});
|
|
312
|
+
describe('getCurrentBranch', () => {
|
|
313
|
+
it('should return the current branch name', () => {
|
|
314
|
+
mockedExecSync.mockReturnValueOnce('main\n');
|
|
315
|
+
expect(gitService_1.GitService.getCurrentBranch()).toBe('main');
|
|
316
|
+
});
|
|
317
|
+
it('should return null for detached HEAD', () => {
|
|
318
|
+
mockedExecSync.mockReturnValueOnce('HEAD\n');
|
|
319
|
+
expect(gitService_1.GitService.getCurrentBranch()).toBeNull();
|
|
320
|
+
});
|
|
321
|
+
it('should return null on error', () => {
|
|
322
|
+
mockedExecSync.mockImplementationOnce(() => {
|
|
323
|
+
throw new Error('not a git repository');
|
|
324
|
+
});
|
|
325
|
+
expect(gitService_1.GitService.getCurrentBranch()).toBeNull();
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
describe('getCommitHistory', () => {
|
|
329
|
+
it('should parse null-delimited commit entries', () => {
|
|
330
|
+
// Format: hash\x00subject\x00body\x00 per commit, separated by \x00\x00
|
|
331
|
+
const output = [
|
|
332
|
+
'abc123\x00feat: add feature\x00Some body text\x00',
|
|
333
|
+
'def456\x00fix: bug fix\x00\x00',
|
|
334
|
+
].join('\x00');
|
|
335
|
+
mockedExecSync.mockReturnValueOnce(output);
|
|
336
|
+
const commits = gitService_1.GitService.getCommitHistory(10);
|
|
337
|
+
expect(commits).toHaveLength(2);
|
|
338
|
+
expect(commits[0]).toEqual({
|
|
339
|
+
hash: 'abc123',
|
|
340
|
+
subject: 'feat: add feature',
|
|
341
|
+
message: 'feat: add feature\n\nSome body text',
|
|
342
|
+
body: 'Some body text',
|
|
343
|
+
});
|
|
344
|
+
expect(commits[1]).toEqual({
|
|
345
|
+
hash: 'def456',
|
|
346
|
+
subject: 'fix: bug fix',
|
|
347
|
+
message: 'fix: bug fix',
|
|
348
|
+
body: undefined,
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
it('should return empty array on error', () => {
|
|
352
|
+
mockedExecSync.mockImplementationOnce(() => {
|
|
353
|
+
throw new Error('no commits');
|
|
354
|
+
});
|
|
355
|
+
expect(gitService_1.GitService.getCommitHistory()).toEqual([]);
|
|
356
|
+
});
|
|
357
|
+
it('should use default count of 50', () => {
|
|
358
|
+
mockedExecSync.mockReturnValueOnce('');
|
|
359
|
+
gitService_1.GitService.getCommitHistory();
|
|
360
|
+
expect(mockedExecSync).toHaveBeenCalledWith(expect.stringContaining('-50'), expect.any(Object));
|
|
361
|
+
});
|
|
125
362
|
});
|
|
126
363
|
describe('getFileAtHead', () => {
|
|
127
364
|
it('should return file contents from HEAD', () => {
|
|
@@ -130,18 +367,12 @@ describe('GitService', () => {
|
|
|
130
367
|
expect(result).toBe('export function foo() {}');
|
|
131
368
|
expect(mockedExecSync).toHaveBeenCalledWith('git show HEAD:src/foo.ts', expect.objectContaining({ encoding: 'utf-8' }));
|
|
132
369
|
});
|
|
133
|
-
it('should return null for
|
|
370
|
+
it('should return null for files not in HEAD', () => {
|
|
134
371
|
mockedExecSync.mockImplementationOnce(() => {
|
|
135
372
|
throw new Error('path does not exist');
|
|
136
373
|
});
|
|
137
374
|
expect(gitService_1.GitService.getFileAtHead('src/new.ts')).toBeNull();
|
|
138
375
|
});
|
|
139
|
-
it('should return null when no commits exist', () => {
|
|
140
|
-
mockedExecSync.mockImplementationOnce(() => {
|
|
141
|
-
throw new Error('unknown revision');
|
|
142
|
-
});
|
|
143
|
-
expect(gitService_1.GitService.getFileAtHead('src/any.ts')).toBeNull();
|
|
144
|
-
});
|
|
145
376
|
});
|
|
146
377
|
describe('getStagedFileContent', () => {
|
|
147
378
|
it('should return staged file contents from index', () => {
|
|
@@ -150,25 +381,12 @@ describe('GitService', () => {
|
|
|
150
381
|
expect(result).toBe('export const X = 1;');
|
|
151
382
|
expect(mockedExecSync).toHaveBeenCalledWith('git show :src/x.ts', expect.objectContaining({ encoding: 'utf-8' }));
|
|
152
383
|
});
|
|
153
|
-
it('should return null for deleted files', () => {
|
|
384
|
+
it('should return null for deleted files in index', () => {
|
|
154
385
|
mockedExecSync.mockImplementationOnce(() => {
|
|
155
386
|
throw new Error('path does not exist in index');
|
|
156
387
|
});
|
|
157
388
|
expect(gitService_1.GitService.getStagedFileContent('src/deleted.ts')).toBeNull();
|
|
158
389
|
});
|
|
159
390
|
});
|
|
160
|
-
describe('getGitDir', () => {
|
|
161
|
-
it('should resolve relative .git path', () => {
|
|
162
|
-
mockedExecSync.mockReturnValueOnce('.git\n');
|
|
163
|
-
const result = gitService_1.GitService.getGitDir();
|
|
164
|
-
// Should resolve to absolute path ending with .git
|
|
165
|
-
expect(result.endsWith('.git')).toBe(true);
|
|
166
|
-
});
|
|
167
|
-
it('should handle absolute paths', () => {
|
|
168
|
-
mockedExecSync.mockReturnValueOnce('/home/user/project/.git\n');
|
|
169
|
-
const result = gitService_1.GitService.getGitDir();
|
|
170
|
-
expect(result).toBe('/home/user/project/.git');
|
|
171
|
-
});
|
|
172
|
-
});
|
|
173
391
|
});
|
|
174
392
|
//# sourceMappingURL=gitService.test.js.map
|