korekt-cli 0.8.4 โ 0.8.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.
- package/package.json +1 -1
- package/src/formatter.js +2 -0
- package/src/git-logic.js +3 -0
- package/src/git-logic.test.js +180 -0
- package/src/index.js +4 -55
- package/src/utils.js +54 -0
package/package.json
CHANGED
package/src/formatter.js
CHANGED
package/src/git-logic.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execa } from 'execa';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
+
import { detectCIProvider } from './utils.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Truncate content to a maximum number of lines using "head and tail".
|
|
@@ -244,6 +245,7 @@ export async function runUncommittedReview(mode = 'unstaged') {
|
|
|
244
245
|
changed_files: changedFiles,
|
|
245
246
|
source_branch: branchName,
|
|
246
247
|
changed_lines: calculateChangedLines(changedFiles),
|
|
248
|
+
is_ci: detectCIProvider() !== null,
|
|
247
249
|
};
|
|
248
250
|
} catch (error) {
|
|
249
251
|
console.error(chalk.red('Failed to analyze uncommitted changes:'), error.message);
|
|
@@ -524,6 +526,7 @@ export async function runLocalReview(targetBranch = null, ignorePatterns = null)
|
|
|
524
526
|
author_name,
|
|
525
527
|
contributors,
|
|
526
528
|
changed_lines: calculateChangedLines(changedFiles),
|
|
529
|
+
is_ci: detectCIProvider() !== null,
|
|
527
530
|
};
|
|
528
531
|
} catch (error) {
|
|
529
532
|
console.error(chalk.red('Failed to run local review analysis:'), error.message);
|
package/src/git-logic.test.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getContributors,
|
|
10
10
|
} from './git-logic.js';
|
|
11
11
|
import { execa } from 'execa';
|
|
12
|
+
import { detectCIProvider } from './utils.js';
|
|
12
13
|
|
|
13
14
|
describe('parseNameStatus', () => {
|
|
14
15
|
it('should correctly parse M, A, and D statuses', () => {
|
|
@@ -830,3 +831,182 @@ describe('changed_lines calculation', () => {
|
|
|
830
831
|
expect(result.changed_lines).toBe(0);
|
|
831
832
|
});
|
|
832
833
|
});
|
|
834
|
+
|
|
835
|
+
describe('is_ci flag in payload', () => {
|
|
836
|
+
beforeEach(() => {
|
|
837
|
+
vi.mock('execa');
|
|
838
|
+
vi.mock('./utils.js', () => ({
|
|
839
|
+
detectCIProvider: vi.fn(),
|
|
840
|
+
}));
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
afterEach(() => {
|
|
844
|
+
vi.restoreAllMocks();
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
it('should set is_ci to true when detectCIProvider returns a provider', async () => {
|
|
848
|
+
vi.mocked(detectCIProvider).mockReturnValue('github');
|
|
849
|
+
|
|
850
|
+
vi.mocked(execa).mockImplementation(async (cmd, args) => {
|
|
851
|
+
const command = [cmd, ...args].join(' ');
|
|
852
|
+
|
|
853
|
+
if (command.includes('remote get-url origin')) {
|
|
854
|
+
return { stdout: 'https://github.com/user/repo.git' };
|
|
855
|
+
}
|
|
856
|
+
if (command.includes('rev-parse --abbrev-ref HEAD')) {
|
|
857
|
+
return { stdout: 'feature-branch' };
|
|
858
|
+
}
|
|
859
|
+
if (command.includes('rev-parse --show-toplevel')) {
|
|
860
|
+
return { stdout: '/fake/repo/path' };
|
|
861
|
+
}
|
|
862
|
+
if (command.includes('diff --cached --name-status')) {
|
|
863
|
+
return { stdout: 'M\tfile.js' };
|
|
864
|
+
}
|
|
865
|
+
if (command.includes('diff --cached -U15 -- file.js')) {
|
|
866
|
+
return { stdout: 'diff --git a/file.js b/file.js\n+new line' };
|
|
867
|
+
}
|
|
868
|
+
if (command.includes('show HEAD:file.js')) {
|
|
869
|
+
return { stdout: 'old content' };
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
throw new Error(`Unmocked command: ${command}`);
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
const result = await runUncommittedReview('staged');
|
|
876
|
+
|
|
877
|
+
expect(result).toBeDefined();
|
|
878
|
+
expect(result.is_ci).toBe(true);
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
it('should set is_ci to false when detectCIProvider returns null', async () => {
|
|
882
|
+
vi.mocked(detectCIProvider).mockReturnValue(null);
|
|
883
|
+
|
|
884
|
+
vi.mocked(execa).mockImplementation(async (cmd, args) => {
|
|
885
|
+
const command = [cmd, ...args].join(' ');
|
|
886
|
+
|
|
887
|
+
if (command.includes('remote get-url origin')) {
|
|
888
|
+
return { stdout: 'https://github.com/user/repo.git' };
|
|
889
|
+
}
|
|
890
|
+
if (command.includes('rev-parse --abbrev-ref HEAD')) {
|
|
891
|
+
return { stdout: 'feature-branch' };
|
|
892
|
+
}
|
|
893
|
+
if (command.includes('rev-parse --show-toplevel')) {
|
|
894
|
+
return { stdout: '/fake/repo/path' };
|
|
895
|
+
}
|
|
896
|
+
if (command.includes('diff --cached --name-status')) {
|
|
897
|
+
return { stdout: 'M\tfile.js' };
|
|
898
|
+
}
|
|
899
|
+
if (command.includes('diff --cached -U15 -- file.js')) {
|
|
900
|
+
return { stdout: 'diff --git a/file.js b/file.js\n+new line' };
|
|
901
|
+
}
|
|
902
|
+
if (command.includes('show HEAD:file.js')) {
|
|
903
|
+
return { stdout: 'old content' };
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
throw new Error(`Unmocked command: ${command}`);
|
|
907
|
+
});
|
|
908
|
+
|
|
909
|
+
const result = await runUncommittedReview('staged');
|
|
910
|
+
|
|
911
|
+
expect(result).toBeDefined();
|
|
912
|
+
expect(result.is_ci).toBe(false);
|
|
913
|
+
});
|
|
914
|
+
|
|
915
|
+
it('should include is_ci in runLocalReview payload', async () => {
|
|
916
|
+
vi.mocked(detectCIProvider).mockReturnValue('azure');
|
|
917
|
+
|
|
918
|
+
vi.mocked(execa).mockImplementation(async (cmd, args) => {
|
|
919
|
+
const command = [cmd, ...args].join(' ');
|
|
920
|
+
|
|
921
|
+
if (command.includes('remote get-url origin')) {
|
|
922
|
+
return { stdout: 'https://github.com/user/repo.git' };
|
|
923
|
+
}
|
|
924
|
+
if (command.includes('rev-parse --abbrev-ref HEAD')) {
|
|
925
|
+
return { stdout: 'feature-branch' };
|
|
926
|
+
}
|
|
927
|
+
if (command.includes('rev-parse --show-toplevel')) {
|
|
928
|
+
return { stdout: '/path/to/repo' };
|
|
929
|
+
}
|
|
930
|
+
if (command.includes('rev-parse --verify main')) {
|
|
931
|
+
return { stdout: 'commit-hash' };
|
|
932
|
+
}
|
|
933
|
+
if (command === 'git fetch origin main') {
|
|
934
|
+
return { stdout: '' };
|
|
935
|
+
}
|
|
936
|
+
if (command.includes('merge-base origin/main HEAD')) {
|
|
937
|
+
return { stdout: 'abc123' };
|
|
938
|
+
}
|
|
939
|
+
if (command.includes('log --no-merges --pretty=%B---EOC---')) {
|
|
940
|
+
return { stdout: 'feat: add feature---EOC---' };
|
|
941
|
+
}
|
|
942
|
+
if (command.includes('log --no-merges --format=%ae|%an')) {
|
|
943
|
+
return { stdout: 'user@example.com|User Name' };
|
|
944
|
+
}
|
|
945
|
+
if (command.includes('diff --name-status')) {
|
|
946
|
+
return { stdout: 'M\tfile.js' };
|
|
947
|
+
}
|
|
948
|
+
if (command.includes('diff -U15')) {
|
|
949
|
+
return { stdout: 'diff content' };
|
|
950
|
+
}
|
|
951
|
+
if (command.includes('show abc123:file.js')) {
|
|
952
|
+
return { stdout: 'original content' };
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
return { stdout: '' };
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
const result = await runLocalReview('main');
|
|
959
|
+
|
|
960
|
+
expect(result).toBeDefined();
|
|
961
|
+
expect(result.is_ci).toBe(true);
|
|
962
|
+
});
|
|
963
|
+
|
|
964
|
+
it('should set is_ci to false in runLocalReview when not in CI', async () => {
|
|
965
|
+
vi.mocked(detectCIProvider).mockReturnValue(null);
|
|
966
|
+
|
|
967
|
+
vi.mocked(execa).mockImplementation(async (cmd, args) => {
|
|
968
|
+
const command = [cmd, ...args].join(' ');
|
|
969
|
+
|
|
970
|
+
if (command.includes('remote get-url origin')) {
|
|
971
|
+
return { stdout: 'https://github.com/user/repo.git' };
|
|
972
|
+
}
|
|
973
|
+
if (command.includes('rev-parse --abbrev-ref HEAD')) {
|
|
974
|
+
return { stdout: 'feature-branch' };
|
|
975
|
+
}
|
|
976
|
+
if (command.includes('rev-parse --show-toplevel')) {
|
|
977
|
+
return { stdout: '/path/to/repo' };
|
|
978
|
+
}
|
|
979
|
+
if (command.includes('rev-parse --verify main')) {
|
|
980
|
+
return { stdout: 'commit-hash' };
|
|
981
|
+
}
|
|
982
|
+
if (command === 'git fetch origin main') {
|
|
983
|
+
return { stdout: '' };
|
|
984
|
+
}
|
|
985
|
+
if (command.includes('merge-base origin/main HEAD')) {
|
|
986
|
+
return { stdout: 'abc123' };
|
|
987
|
+
}
|
|
988
|
+
if (command.includes('log --no-merges --pretty=%B---EOC---')) {
|
|
989
|
+
return { stdout: 'feat: add feature---EOC---' };
|
|
990
|
+
}
|
|
991
|
+
if (command.includes('log --no-merges --format=%ae|%an')) {
|
|
992
|
+
return { stdout: 'user@example.com|User Name' };
|
|
993
|
+
}
|
|
994
|
+
if (command.includes('diff --name-status')) {
|
|
995
|
+
return { stdout: 'M\tfile.js' };
|
|
996
|
+
}
|
|
997
|
+
if (command.includes('diff -U15')) {
|
|
998
|
+
return { stdout: 'diff content' };
|
|
999
|
+
}
|
|
1000
|
+
if (command.includes('show abc123:file.js')) {
|
|
1001
|
+
return { stdout: 'original content' };
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
return { stdout: '' };
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
const result = await runLocalReview('main');
|
|
1008
|
+
|
|
1009
|
+
expect(result).toBeDefined();
|
|
1010
|
+
expect(result.is_ci).toBe(false);
|
|
1011
|
+
});
|
|
1012
|
+
});
|
package/src/index.js
CHANGED
|
@@ -14,6 +14,10 @@ import { tmpdir } from 'os';
|
|
|
14
14
|
import { runLocalReview } from './git-logic.js';
|
|
15
15
|
import { getApiKey, setApiKey, getApiEndpoint, setApiEndpoint } from './config.js';
|
|
16
16
|
import { formatReviewOutput } from './formatter.js';
|
|
17
|
+
import { detectCIProvider, truncateFileData, formatErrorOutput } from './utils.js';
|
|
18
|
+
|
|
19
|
+
// Re-export utilities for backward compatibility
|
|
20
|
+
export { detectCIProvider, truncateFileData, formatErrorOutput } from './utils.js';
|
|
17
21
|
|
|
18
22
|
const require = createRequire(import.meta.url);
|
|
19
23
|
const { version } = require('../package.json');
|
|
@@ -26,44 +30,6 @@ const { version } = require('../package.json');
|
|
|
26
30
|
const log = (msg) => process.stderr.write(msg + '\n');
|
|
27
31
|
const output = (msg) => process.stdout.write(msg + '\n');
|
|
28
32
|
|
|
29
|
-
/**
|
|
30
|
-
* Truncates file data (diff and content) for display purposes
|
|
31
|
-
* @param {Object} file - File object with path, status, diff, content, etc.
|
|
32
|
-
* @param {number} maxLength - Maximum length before truncation (default: 500)
|
|
33
|
-
* @returns {Object} File object with truncated diff and content
|
|
34
|
-
*/
|
|
35
|
-
export function truncateFileData(file, maxLength = 500) {
|
|
36
|
-
return {
|
|
37
|
-
path: file.path,
|
|
38
|
-
status: file.status,
|
|
39
|
-
...(file.old_path && { old_path: file.old_path }),
|
|
40
|
-
diff:
|
|
41
|
-
file.diff.length > maxLength
|
|
42
|
-
? `${file.diff.substring(0, maxLength)}... [truncated ${file.diff.length - maxLength} chars]`
|
|
43
|
-
: file.diff,
|
|
44
|
-
content:
|
|
45
|
-
file.content.length > maxLength
|
|
46
|
-
? `${file.content.substring(0, maxLength)}... [truncated ${file.content.length - maxLength} chars]`
|
|
47
|
-
: file.content,
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Formats error object for JSON output
|
|
53
|
-
* @param {Error} error - Error object from axios or other source
|
|
54
|
-
* @returns {Object} Formatted error output with success: false
|
|
55
|
-
*/
|
|
56
|
-
export function formatErrorOutput(error) {
|
|
57
|
-
return {
|
|
58
|
-
success: false,
|
|
59
|
-
error: error.message,
|
|
60
|
-
...(error.response && {
|
|
61
|
-
status: error.response.status,
|
|
62
|
-
data: error.response.data,
|
|
63
|
-
}),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
33
|
/**
|
|
68
34
|
* Ask for user confirmation before proceeding
|
|
69
35
|
*/
|
|
@@ -84,23 +50,6 @@ async function confirmAction(message) {
|
|
|
84
50
|
});
|
|
85
51
|
}
|
|
86
52
|
|
|
87
|
-
/**
|
|
88
|
-
* Detect CI provider from environment variables
|
|
89
|
-
* @returns {string|null} Provider name or null if not detected
|
|
90
|
-
*/
|
|
91
|
-
export function detectCIProvider() {
|
|
92
|
-
if (process.env.GITHUB_TOKEN && process.env.GITHUB_REPOSITORY) {
|
|
93
|
-
return 'github';
|
|
94
|
-
}
|
|
95
|
-
if (process.env.SYSTEM_ACCESSTOKEN && process.env.SYSTEM_PULLREQUEST_PULLREQUESTID) {
|
|
96
|
-
return 'azure';
|
|
97
|
-
}
|
|
98
|
-
if (process.env.BITBUCKET_REPO_SLUG && process.env.BITBUCKET_PR_ID) {
|
|
99
|
-
return 'bitbucket';
|
|
100
|
-
}
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
53
|
/**
|
|
105
54
|
* Run the CI integration script to post comments
|
|
106
55
|
* @param {string} provider - CI provider (github, azure, bitbucket)
|
package/src/utils.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Detect CI provider from environment variables
|
|
3
|
+
* @returns {string|null} Provider name or null if not detected
|
|
4
|
+
*/
|
|
5
|
+
export function detectCIProvider() {
|
|
6
|
+
if (process.env.GITHUB_TOKEN && process.env.GITHUB_REPOSITORY) {
|
|
7
|
+
return 'github';
|
|
8
|
+
}
|
|
9
|
+
if (process.env.SYSTEM_ACCESSTOKEN && process.env.SYSTEM_PULLREQUEST_PULLREQUESTID) {
|
|
10
|
+
return 'azure';
|
|
11
|
+
}
|
|
12
|
+
if (process.env.BITBUCKET_REPO_SLUG && process.env.BITBUCKET_PR_ID) {
|
|
13
|
+
return 'bitbucket';
|
|
14
|
+
}
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Truncates file data (diff and content) for display purposes
|
|
20
|
+
* @param {Object} file - File object with path, status, diff, content, etc.
|
|
21
|
+
* @param {number} maxLength - Maximum length before truncation (default: 500)
|
|
22
|
+
* @returns {Object} File object with truncated diff and content
|
|
23
|
+
*/
|
|
24
|
+
export function truncateFileData(file, maxLength = 500) {
|
|
25
|
+
return {
|
|
26
|
+
path: file.path,
|
|
27
|
+
status: file.status,
|
|
28
|
+
...(file.old_path && { old_path: file.old_path }),
|
|
29
|
+
diff:
|
|
30
|
+
file.diff.length > maxLength
|
|
31
|
+
? `${file.diff.substring(0, maxLength)}... [truncated ${file.diff.length - maxLength} chars]`
|
|
32
|
+
: file.diff,
|
|
33
|
+
content:
|
|
34
|
+
file.content.length > maxLength
|
|
35
|
+
? `${file.content.substring(0, maxLength)}... [truncated ${file.content.length - maxLength} chars]`
|
|
36
|
+
: file.content,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Formats error object for JSON output
|
|
42
|
+
* @param {Error} error - Error object from axios or other source
|
|
43
|
+
* @returns {Object} Formatted error output with success: false
|
|
44
|
+
*/
|
|
45
|
+
export function formatErrorOutput(error) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
error: error.message,
|
|
49
|
+
...(error.response && {
|
|
50
|
+
status: error.response.status,
|
|
51
|
+
data: error.response.data,
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
}
|