@zohodesk/codestandard-validator 1.2.4-exp-5 → 1.2.4-exp-7
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/README.md +170 -1
- package/bin/cliCI.js +1 -0
- package/bin/zdcodequality.js +50 -0
- package/build/hooks/hook.js +3 -1
- package/build/mutation/_config.json +55 -0
- package/build/mutation/branchDiff.js +224 -149
- package/build/mutation/fileResolver.js +164 -133
- package/build/mutation/mutationCli.js +162 -100
- package/build/mutation/mutationRunner.js +216 -189
- package/build/mutation/reportGenerator.js +239 -51
- package/build/mutation/strykerConfigBuilder.js +327 -0
- package/build/mutation/strykerWrapper.js +107 -51
- package/build/utils/General/Config.js +4 -0
- package/build/utils/PluginsInstallation/checkIfPluginsAreInstalled.js +1 -0
- package/index.js +2 -1
- package/package.json +12 -6
- package/samples/sample-branch-mode.js +0 -34
- package/samples/sample-cli-entry.js +0 -34
- package/samples/sample-components.js +0 -63
- package/samples/sample-directory-mode.js +0 -30
- package/samples/sample-runner-direct.js +0 -32
- package/samples/sample-with-api.js +0 -44
|
@@ -4,175 +4,250 @@ const {
|
|
|
4
4
|
execSync
|
|
5
5
|
} = require('child_process');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const {
|
|
9
|
+
writeFileSync
|
|
10
|
+
} = require('fs');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* BranchDiff — git-based file change detection.
|
|
14
|
+
*
|
|
15
|
+
* Provides two use-cases:
|
|
16
|
+
* 1. Local / pre-commit — staged files + diff vs release branch
|
|
17
|
+
* 2. CI — diff vs release branch only
|
|
18
|
+
*
|
|
19
|
+
* Compatible with Node 16+.
|
|
20
|
+
*/
|
|
21
|
+
function BranchDiff(options) {
|
|
22
|
+
this.options = options || {};
|
|
23
|
+
this._cwd = options.cwd || process.cwd();
|
|
24
|
+
this._sourceExtensions = options.sourceExtensions || ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
|
|
25
|
+
this._testPattern = options.testPattern || /\.(test|spec)\.(js|ts|jsx|tsx|mjs|cjs)$/;
|
|
26
|
+
this.rootDirectory = this.getRootDirectory();
|
|
27
|
+
this._token = options.token || null;
|
|
28
|
+
this._gitEndPoint = options.gitEndPoint || null;
|
|
29
|
+
this._branchDiffPath = options.branchDiffPath || null;
|
|
30
|
+
this._compareBranch = options.compareBranch || null;
|
|
31
|
+
this._projectId = options.projectId || null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* ------------------------------------------------------------------ */
|
|
35
|
+
/* Internal git helpers */
|
|
36
|
+
/* ------------------------------------------------------------------ */
|
|
37
|
+
|
|
38
|
+
BranchDiff.prototype._sanitizeBranch = function (branch) {
|
|
39
|
+
return String(branch).replace(/[^a-zA-Z0-9_\-.\/]/g, '');
|
|
40
|
+
};
|
|
41
|
+
BranchDiff.prototype._getCurrentBranch = function () {
|
|
42
|
+
try {
|
|
43
|
+
return execSync('git rev-parse --abbrev-ref HEAD', {
|
|
44
|
+
cwd: this._cwd,
|
|
45
|
+
encoding: 'utf-8'
|
|
46
|
+
}).trim();
|
|
47
|
+
} catch (err) {
|
|
48
|
+
throw new Error('Failed to get current branch: ' + err.message);
|
|
14
49
|
}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
50
|
+
};
|
|
51
|
+
BranchDiff.prototype._fetchBranch = function (branch) {
|
|
52
|
+
var safeBranch = this._sanitizeBranch(branch);
|
|
53
|
+
try {
|
|
54
|
+
execSync('git fetch origin ' + safeBranch, {
|
|
55
|
+
cwd: this._cwd,
|
|
56
|
+
encoding: 'utf-8',
|
|
57
|
+
stdio: 'pipe'
|
|
58
|
+
});
|
|
59
|
+
} catch (_) {
|
|
60
|
+
// Branch might already be available locally — ignore
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Get the root directory of the git repository.
|
|
65
|
+
*
|
|
66
|
+
* @returns {string} absolute path to the git repo root
|
|
67
|
+
*/
|
|
68
|
+
BranchDiff.prototype.getRootDirectory = function () {
|
|
69
|
+
try {
|
|
70
|
+
return execSync('git rev-parse --show-toplevel', {
|
|
71
|
+
cwd: this._cwd,
|
|
72
|
+
encoding: 'utf-8',
|
|
73
|
+
stdio: 'pipe'
|
|
74
|
+
}).trim();
|
|
75
|
+
} catch (err) {
|
|
76
|
+
throw new Error('Failed to get git root directory: ' + err.message);
|
|
24
77
|
}
|
|
25
|
-
|
|
78
|
+
};
|
|
79
|
+
BranchDiff.prototype._resolveBranchRef = function (branch) {
|
|
80
|
+
var safeBranch = this._sanitizeBranch(branch);
|
|
81
|
+
var candidates = [safeBranch, 'origin/' + safeBranch];
|
|
82
|
+
for (var i = 0; i < candidates.length; i++) {
|
|
26
83
|
try {
|
|
27
|
-
execSync(
|
|
84
|
+
execSync('git rev-parse --verify ' + candidates[i], {
|
|
28
85
|
cwd: this._cwd,
|
|
29
86
|
encoding: 'utf-8',
|
|
30
87
|
stdio: 'pipe'
|
|
31
88
|
});
|
|
32
|
-
|
|
33
|
-
|
|
89
|
+
return candidates[i];
|
|
90
|
+
} catch (_) {
|
|
91
|
+
// try next
|
|
34
92
|
}
|
|
35
93
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
94
|
+
throw new Error("Branch '" + branch + "' not found locally or as 'origin/" + branch + "'. " + "Run 'git fetch origin " + branch + "' or check the branch name.");
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/* ------------------------------------------------------------------ */
|
|
98
|
+
/* Public API */
|
|
99
|
+
/* ------------------------------------------------------------------ */
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get files that differ between the current branch and a release/target branch
|
|
103
|
+
* using the GitLab compare API via curl.
|
|
104
|
+
*
|
|
105
|
+
* @param {string} releaseBranch - e.g. 'release', 'main'
|
|
106
|
+
* @returns {Promise<string[]>} list of changed file paths (relative to repo root)
|
|
107
|
+
*/
|
|
108
|
+
BranchDiff.prototype.getChangedFilesFromRelease = function (releaseBranch) {
|
|
109
|
+
var self = this;
|
|
110
|
+
var projectId = self._projectId;
|
|
111
|
+
var gitEndPoint = self._gitEndPoint;
|
|
112
|
+
var branchDiffPath = self._branchDiffPath;
|
|
113
|
+
var compareBranch = self._compareBranch || releaseBranch;
|
|
114
|
+
if (!projectId || !gitEndPoint) {
|
|
115
|
+
console.log('GitLab API config missing (projectId or gitEndPoint). Returning empty diff.');
|
|
116
|
+
return Promise.resolve([]);
|
|
52
117
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
encoding: 'utf-8'
|
|
62
|
-
}).trim();
|
|
63
|
-
diffRef = mergeBase;
|
|
64
|
-
} catch {
|
|
65
|
-
diffRef = resolvedRef;
|
|
66
|
-
}
|
|
118
|
+
if (projectId === 'project-id' || isNaN(Number(projectId))) {
|
|
119
|
+
console.log('Make sure that project id is properly configured');
|
|
120
|
+
return Promise.resolve([]);
|
|
121
|
+
}
|
|
122
|
+
console.log('projectId:', projectId);
|
|
123
|
+
var currentBranch = self._getCurrentBranch();
|
|
124
|
+
var endPoint = gitEndPoint + '/api/v4/projects/' + projectId.toString() + '/repository/compare?from=' + compareBranch + '&to=' + currentBranch;
|
|
125
|
+
return new Promise(function (resolve, reject) {
|
|
67
126
|
try {
|
|
68
|
-
|
|
69
|
-
|
|
127
|
+
var command = 'curl --header "PRIVATE-TOKEN:' + self.options.token + '" "' + endPoint + '"';
|
|
128
|
+
var output = execSync(command, {
|
|
129
|
+
shell: true,
|
|
70
130
|
encoding: 'utf-8'
|
|
71
131
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
throw new Error(`Failed to get diff files against branch '${targetBranch}': ${err.message}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
_getDiffFilesFromApi(targetBranch) {
|
|
78
|
-
if (!this._api || !this._patToken) {
|
|
79
|
-
throw new Error('API and PAT token are required for API-based diff');
|
|
80
|
-
}
|
|
81
|
-
// Placeholder for API-based diff (e.g., Azure DevOps, GitHub, etc.)
|
|
82
|
-
// Consumers can pass in their own api object that implements `getChangedFiles(targetBranch, patToken)`
|
|
83
|
-
if (typeof this._api.getChangedFiles === 'function') {
|
|
84
|
-
return this._api.getChangedFiles(targetBranch, this._patToken);
|
|
85
|
-
}
|
|
86
|
-
throw new Error('API object must implement getChangedFiles(targetBranch, patToken)');
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Fetches branch diff from GitLab compare API via HTTPS
|
|
91
|
-
* @param {string} hostname - GitLab host (e.g. 'gitlab.example.com')
|
|
92
|
-
* @param {string} branch - Target branch to compare against
|
|
93
|
-
* @returns {Promise<string[]>} - Array of changed file paths
|
|
94
|
-
*/
|
|
95
|
-
_fetchBranchDiffFromApi(hostname, branch, projectId) {
|
|
96
|
-
const currentBranch = this._getCurrentBranch();
|
|
97
|
-
const patToken = this._patToken || process.env.ZGIT_TOKEN;
|
|
98
|
-
if (!patToken) {
|
|
99
|
-
return reject(new Error('PAT token is required. Set it via options.patToken or ZGIT_TOKEN env var'));
|
|
100
|
-
}
|
|
101
|
-
if (!projectId) {
|
|
102
|
-
return reject(new Error('GitLab projectId is required for API-based diff'));
|
|
103
|
-
}
|
|
104
|
-
const createBranchDiffChunk = (resolve, reject) => {
|
|
105
|
-
// code duplicate need find solution
|
|
106
|
-
var command;
|
|
107
|
-
const endPoint = `${hostname}/api/v4/projects/${encodeURIComponent(projectId)}/repository/compare?from=${branch}&to=${currentBranch}`;
|
|
108
|
-
try {
|
|
109
|
-
command = `curl --header "PRIVATE-TOKEN:${patToken}" "${endPoint}"`;
|
|
110
|
-
return resolve(parserReleaseDiffRemoteAPI(JSON.parse(execSync(command, {
|
|
111
|
-
cwd: this._cwd
|
|
112
|
-
}))));
|
|
113
|
-
} catch (error) {
|
|
114
|
-
Logger.log(error);
|
|
115
|
-
Logger.log(`\n INFO : If you are using a VPN and encounter an SSL certification issue, ensure that the proxy is enabled for SSH and shell connections.`);
|
|
116
|
-
Logger.log(`\n Make sure that you have access to this repository`);
|
|
117
|
-
reject(error);
|
|
132
|
+
if (branchDiffPath) {
|
|
133
|
+
writeFileSync(branchDiffPath, output);
|
|
118
134
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
var
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
135
|
+
var response = JSON.parse(output);
|
|
136
|
+
var diffs = response.diffs || [];
|
|
137
|
+
var files = diffs.map(function (d) {
|
|
138
|
+
return d.new_path || d.old_path;
|
|
139
|
+
}).filter(function (f) {
|
|
140
|
+
return f && f.length > 0;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// deduplicate
|
|
144
|
+
var seen = {};
|
|
145
|
+
var unique = [];
|
|
146
|
+
for (var i = 0; i < files.length; i++) {
|
|
147
|
+
if (!seen[files[i]]) {
|
|
148
|
+
seen[files[i]] = true;
|
|
149
|
+
unique.push(files[i]);
|
|
150
|
+
}
|
|
126
151
|
}
|
|
127
|
-
|
|
152
|
+
resolve(unique);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
console.log('INFO : If you are using a VPN and encounter an SSL certification issue, ensure that the proxy is enabled for SSH and shell connections.');
|
|
155
|
+
console.log('Failed to get diff files from GitLab API: ' + err.message);
|
|
156
|
+
reject(new Error('Failed to get diff files from GitLab API: ' + err.message));
|
|
128
157
|
}
|
|
129
|
-
|
|
130
|
-
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get staged (git add) file paths.
|
|
163
|
+
*
|
|
164
|
+
* @returns {string[]} list of staged file paths
|
|
165
|
+
*/
|
|
166
|
+
BranchDiff.prototype.getStagedFiles = function () {
|
|
167
|
+
try {
|
|
168
|
+
var output = execSync('git diff --staged --name-only', {
|
|
169
|
+
cwd: this._cwd,
|
|
170
|
+
encoding: 'utf-8'
|
|
131
171
|
});
|
|
172
|
+
return output.trim().split('\n').filter(Boolean).filter(function (_path) {
|
|
173
|
+
return fs.existsSync(path.resolve(this.rootDirectory, _path));
|
|
174
|
+
}.bind(this));
|
|
175
|
+
} catch (err) {
|
|
176
|
+
throw new Error("Couldn't fetch staged files: " + err.message);
|
|
132
177
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* LOCAL mode — combines staged files + release branch diff.
|
|
182
|
+
* Returns deduplicated, categorized { sourceFiles, testFiles }.
|
|
183
|
+
*
|
|
184
|
+
* @param {string} releaseBranch
|
|
185
|
+
* @returns {{ sourceFiles: string[], testFiles: string[] }}
|
|
186
|
+
*/
|
|
187
|
+
BranchDiff.prototype.getLocalChanges = function (releaseBranch) {
|
|
188
|
+
var self = this;
|
|
189
|
+
return this.getChangedFilesFromRelease(releaseBranch).then(function (releaseFiles) {
|
|
190
|
+
var stagedFiles = self.getStagedFiles();
|
|
191
|
+
console.log({
|
|
192
|
+
releaseFiles: releaseFiles,
|
|
193
|
+
stagedFiles: stagedFiles
|
|
194
|
+
});
|
|
195
|
+
// deduplicate
|
|
196
|
+
var seen = {};
|
|
197
|
+
var merged = [];
|
|
198
|
+
var all = releaseFiles.concat(stagedFiles);
|
|
199
|
+
for (var i = 0; i < all.length; i++) {
|
|
200
|
+
if (!seen[all[i]]) {
|
|
201
|
+
seen[all[i]] = true;
|
|
202
|
+
merged.push(all[i]);
|
|
203
|
+
}
|
|
156
204
|
}
|
|
157
|
-
return
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
205
|
+
return self._categorizeFiles(merged);
|
|
206
|
+
});
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* CI mode — release branch diff only.
|
|
211
|
+
* Returns categorized { sourceFiles, testFiles }.
|
|
212
|
+
*
|
|
213
|
+
* @param {string} releaseBranch
|
|
214
|
+
* @returns {{ sourceFiles: string[], testFiles: string[] }}
|
|
215
|
+
*/
|
|
216
|
+
BranchDiff.prototype.getCIChanges = function (releaseBranch) {
|
|
217
|
+
var self = this;
|
|
218
|
+
return this.getChangedFilesFromRelease(releaseBranch).then(function (releaseFiles) {
|
|
219
|
+
return self._categorizeFiles(releaseFiles);
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/* ------------------------------------------------------------------ */
|
|
224
|
+
/* Categorization helpers */
|
|
225
|
+
/* ------------------------------------------------------------------ */
|
|
226
|
+
|
|
227
|
+
BranchDiff.prototype._isSourceFile = function (filePath) {
|
|
228
|
+
var ext = path.extname(filePath);
|
|
229
|
+
if (this._sourceExtensions.indexOf(ext) === -1) {
|
|
230
|
+
return false;
|
|
161
231
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
232
|
+
return !this._testPattern.test(filePath);
|
|
233
|
+
};
|
|
234
|
+
BranchDiff.prototype._isTestFile = function (filePath) {
|
|
235
|
+
return this._testPattern.test(filePath);
|
|
236
|
+
};
|
|
237
|
+
BranchDiff.prototype._categorizeFiles = function (files) {
|
|
238
|
+
var sourceFiles = [];
|
|
239
|
+
var testFiles = [];
|
|
240
|
+
for (var i = 0; i < files.length; i++) {
|
|
241
|
+
var file = files[i];
|
|
242
|
+
if (this._isTestFile(file)) {
|
|
243
|
+
testFiles.push(file);
|
|
244
|
+
} else if (this._isSourceFile(file)) {
|
|
245
|
+
sourceFiles.push(file);
|
|
171
246
|
}
|
|
172
|
-
return {
|
|
173
|
-
sourceFiles,
|
|
174
|
-
testFiles
|
|
175
|
-
};
|
|
176
247
|
}
|
|
177
|
-
|
|
248
|
+
return {
|
|
249
|
+
sourceFiles: sourceFiles,
|
|
250
|
+
testFiles: testFiles
|
|
251
|
+
};
|
|
252
|
+
};
|
|
178
253
|
module.exports = BranchDiff;
|