@eclipse-glsp/cli 1.1.0-next.28fb493.121
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/LICENSE +642 -0
- package/README.md +89 -0
- package/bin/glsp +2 -0
- package/lib/app.d.ts +3 -0
- package/lib/app.d.ts.map +1 -0
- package/lib/app.js +32 -0
- package/lib/app.js.map +1 -0
- package/lib/commands/check-header.d.ts +24 -0
- package/lib/commands/check-header.d.ts.map +1 -0
- package/lib/commands/check-header.js +234 -0
- package/lib/commands/check-header.js.map +1 -0
- package/lib/commands/coverage-report.d.ts +29 -0
- package/lib/commands/coverage-report.d.ts.map +1 -0
- package/lib/commands/coverage-report.js +100 -0
- package/lib/commands/coverage-report.js.map +1 -0
- package/lib/commands/release/common.d.ts +69 -0
- package/lib/commands/release/common.d.ts.map +1 -0
- package/lib/commands/release/common.js +273 -0
- package/lib/commands/release/common.js.map +1 -0
- package/lib/commands/release/release-client.d.ts +3 -0
- package/lib/commands/release/release-client.d.ts.map +1 -0
- package/lib/commands/release/release-client.js +53 -0
- package/lib/commands/release/release-client.js.map +1 -0
- package/lib/commands/release/release-eclipse-integration.d.ts +18 -0
- package/lib/commands/release/release-eclipse-integration.d.ts.map +1 -0
- package/lib/commands/release/release-eclipse-integration.js +90 -0
- package/lib/commands/release/release-eclipse-integration.js.map +1 -0
- package/lib/commands/release/release-java-server.d.ts +3 -0
- package/lib/commands/release/release-java-server.d.ts.map +1 -0
- package/lib/commands/release/release-java-server.js +68 -0
- package/lib/commands/release/release-java-server.js.map +1 -0
- package/lib/commands/release/release-server-node.d.ts +18 -0
- package/lib/commands/release/release-server-node.d.ts.map +1 -0
- package/lib/commands/release/release-server-node.js +51 -0
- package/lib/commands/release/release-server-node.js.map +1 -0
- package/lib/commands/release/release-theia-integration.d.ts +18 -0
- package/lib/commands/release/release-theia-integration.d.ts.map +1 -0
- package/lib/commands/release/release-theia-integration.js +62 -0
- package/lib/commands/release/release-theia-integration.js.map +1 -0
- package/lib/commands/release/release-vscode-integration.d.ts +18 -0
- package/lib/commands/release/release-vscode-integration.d.ts.map +1 -0
- package/lib/commands/release/release-vscode-integration.js +51 -0
- package/lib/commands/release/release-vscode-integration.js.map +1 -0
- package/lib/commands/release/release.d.ts +14 -0
- package/lib/commands/release/release.d.ts.map +1 -0
- package/lib/commands/release/release.js +165 -0
- package/lib/commands/release/release.js.map +1 -0
- package/lib/util/command-util.d.ts +26 -0
- package/lib/util/command-util.d.ts.map +1 -0
- package/lib/util/command-util.js +53 -0
- package/lib/util/command-util.js.map +1 -0
- package/lib/util/git-util.d.ts +60 -0
- package/lib/util/git-util.d.ts.map +1 -0
- package/lib/util/git-util.js +171 -0
- package/lib/util/git-util.js.map +1 -0
- package/lib/util/logger.d.ts +23 -0
- package/lib/util/logger.d.ts.map +1 -0
- package/lib/util/logger.js +50 -0
- package/lib/util/logger.js.map +1 -0
- package/lib/util/validation-util.d.ts +6 -0
- package/lib/util/validation-util.d.ts.map +1 -0
- package/lib/util/validation-util.js +64 -0
- package/lib/util/validation-util.js.map +1 -0
- package/package.json +64 -0
- package/src/app.ts +30 -0
- package/src/commands/check-header.ts +315 -0
- package/src/commands/coverage-report.ts +113 -0
- package/src/commands/release/common.ts +312 -0
- package/src/commands/release/release-client.ts +64 -0
- package/src/commands/release/release-eclipse-integration.ts +113 -0
- package/src/commands/release/release-java-server.ts +73 -0
- package/src/commands/release/release-server-node.ts +61 -0
- package/src/commands/release/release-theia-integration.ts +77 -0
- package/src/commands/release/release-vscode-integration.ts +65 -0
- package/src/commands/release/release.ts +215 -0
- package/src/util/command-util.ts +54 -0
- package/src/util/git-util.ts +172 -0
- package/src/util/logger.ts +57 -0
- package/src/util/validation-util.ts +63 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2022 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
* http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
*
|
|
8
|
+
* This Source Code may also be made available under the following Secondary
|
|
9
|
+
* Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
* with the GNU Classpath Exception which is available at
|
|
12
|
+
* https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
*
|
|
14
|
+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
********************************************************************************/
|
|
16
|
+
/* eslint-disable max-len */
|
|
17
|
+
import { Option } from 'commander';
|
|
18
|
+
import * as fs from 'fs';
|
|
19
|
+
import { glob } from 'glob';
|
|
20
|
+
import * as minimatch from 'minimatch';
|
|
21
|
+
import * as sh from 'shelljs';
|
|
22
|
+
import { baseCommand, configureShell } from '../util/command-util';
|
|
23
|
+
import {
|
|
24
|
+
getChangesOfLastCommit,
|
|
25
|
+
getFirstCommit,
|
|
26
|
+
getFirstModificationDate,
|
|
27
|
+
getInitialCommit,
|
|
28
|
+
getLastModificationDate,
|
|
29
|
+
getUncommittedChanges
|
|
30
|
+
} from '../util/git-util';
|
|
31
|
+
import { LOGGER } from '../util/logger';
|
|
32
|
+
import { validateGitDirectory } from '../util/validation-util';
|
|
33
|
+
import path = require('path');
|
|
34
|
+
export interface HeaderCheckOptions {
|
|
35
|
+
type: CheckType;
|
|
36
|
+
exclude: string[];
|
|
37
|
+
fileExtensions: string[];
|
|
38
|
+
headerPattern: string;
|
|
39
|
+
json: boolean;
|
|
40
|
+
excludeDefaults: boolean;
|
|
41
|
+
severity: Severity;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const checkTypes = ['full', 'changes', 'lastCommit'] as const;
|
|
45
|
+
type CheckType = typeof checkTypes[number];
|
|
46
|
+
|
|
47
|
+
const severityTypes = ['error', 'warn', 'ok'] as const;
|
|
48
|
+
|
|
49
|
+
type Severity = typeof severityTypes[number];
|
|
50
|
+
|
|
51
|
+
const DEFAULT_EXCLUDES = ['**/@(node_modules|lib|dist|bundle)/**'];
|
|
52
|
+
const YEAR_RANGE_REGEX = /\d{4}(?:-d{4})?/g;
|
|
53
|
+
|
|
54
|
+
export const CheckHeaderCommand = baseCommand() //
|
|
55
|
+
.name('checkHeaders')
|
|
56
|
+
.description('Validates the copyright year range of license header files')
|
|
57
|
+
.argument('<rootDir>', 'The starting directory for the check', validateGitDirectory)
|
|
58
|
+
.addOption(
|
|
59
|
+
new Option(
|
|
60
|
+
'-t, --type <type>',
|
|
61
|
+
'The scope of the check. In addition to a full recursive check, is also possible to only' +
|
|
62
|
+
' consider pending changes or the last commit'
|
|
63
|
+
)
|
|
64
|
+
.choices(checkTypes)
|
|
65
|
+
.default('full')
|
|
66
|
+
)
|
|
67
|
+
.option('-f, --fileExtensions <extensions...>', 'File extensions that should be checked', ['ts', 'tsx'])
|
|
68
|
+
.addOption(
|
|
69
|
+
new Option(
|
|
70
|
+
'-e, --exclude <exclude...>',
|
|
71
|
+
'File patterns that should be excluded from the check. New exclude patterns are added to the default patterns'
|
|
72
|
+
).default([], `[${DEFAULT_EXCLUDES}]`)
|
|
73
|
+
)
|
|
74
|
+
.option(
|
|
75
|
+
'--no-exclude-defaults',
|
|
76
|
+
'Disables the default excludes patterns. Only explicitly passed exclude patterns (-e, --exclude) are considered'
|
|
77
|
+
)
|
|
78
|
+
.option(
|
|
79
|
+
'-p, --headerPattern <pattern>',
|
|
80
|
+
'Regex pattern to extract the copyright year (range) from the header',
|
|
81
|
+
'Copyright \\([cC]\\) \\d{4}(-d{4})?'
|
|
82
|
+
)
|
|
83
|
+
.option('-j, --json', 'Also persist validation results as json file', false)
|
|
84
|
+
.addOption(
|
|
85
|
+
new Option('-s, --severity <severity>', 'The severity of validation results that should be printed.')
|
|
86
|
+
.choices(severityTypes)
|
|
87
|
+
.default('error', '"error" (only)')
|
|
88
|
+
)
|
|
89
|
+
.action(checkHeaders);
|
|
90
|
+
|
|
91
|
+
export function checkHeaders(rootDir: string, options: HeaderCheckOptions): void {
|
|
92
|
+
configureShell({ silent: true, fatal: true });
|
|
93
|
+
|
|
94
|
+
if (options.excludeDefaults) {
|
|
95
|
+
options.exclude.push(...DEFAULT_EXCLUDES);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
sh.cd(rootDir);
|
|
99
|
+
const files = getFiles(rootDir, options);
|
|
100
|
+
LOGGER.info(`Check copy right headers of ${files.length} files`);
|
|
101
|
+
if (files.length === 0) {
|
|
102
|
+
LOGGER.info('Check completed');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const result = validate(rootDir, files, options);
|
|
106
|
+
displayValidationResult(rootDir, result, options);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getFiles(rootDir: string, options: HeaderCheckOptions): string[] {
|
|
110
|
+
const includePattern = `**/*.@(${options.fileExtensions.join('|')})`;
|
|
111
|
+
const excludePattern = options.exclude;
|
|
112
|
+
|
|
113
|
+
if (options.type === 'full') {
|
|
114
|
+
return glob.sync(includePattern, {
|
|
115
|
+
cwd: rootDir,
|
|
116
|
+
ignore: excludePattern
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
let changedFiles = options.type === 'changes' ? getUncommittedChanges(rootDir) : getChangesOfLastCommit(rootDir);
|
|
121
|
+
changedFiles = changedFiles.filter(minimatch.filter(includePattern));
|
|
122
|
+
|
|
123
|
+
excludePattern.forEach(pattern => {
|
|
124
|
+
changedFiles = changedFiles.filter(minimatch.filter(`!${pattern}`));
|
|
125
|
+
});
|
|
126
|
+
return changedFiles;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function validate(rootDir: string, files: string[], options: HeaderCheckOptions): ValidationResult[] {
|
|
130
|
+
// Derives all files with valid headers, their copyright years and all files with no or invalid headers
|
|
131
|
+
const filesWithHeader = sh.grep('-l', options.headerPattern, files).stdout.trim().split('\n');
|
|
132
|
+
const copyrightYears = sh
|
|
133
|
+
.grep(options.headerPattern, files)
|
|
134
|
+
.stdout.trim()
|
|
135
|
+
.split('\n')
|
|
136
|
+
.map(line => line.match(YEAR_RANGE_REGEX)!.map(string => Number.parseInt(string, 10)));
|
|
137
|
+
const noHeaders = files.filter(file => !filesWithHeader.includes(file));
|
|
138
|
+
|
|
139
|
+
const results: ValidationResult[] = [];
|
|
140
|
+
|
|
141
|
+
const allFilesLength = files.length;
|
|
142
|
+
|
|
143
|
+
// Create validation results for all files with no or invalid headers
|
|
144
|
+
const noHeadersLength = noHeaders.length;
|
|
145
|
+
if (noHeadersLength > 0) {
|
|
146
|
+
LOGGER.info(`Found ${noHeadersLength} files with no (or an invalid) copyright header`);
|
|
147
|
+
}
|
|
148
|
+
noHeaders.forEach((file, i) => {
|
|
149
|
+
printValidationProgress(i + 1, allFilesLength, file);
|
|
150
|
+
results.push({ file: path.resolve(rootDir, file), violation: 'noOrMissingHeader', severity: 'error' });
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Performance optimization: avoid retrieving the dates for each individual file by precalculating the endYear if possible.
|
|
154
|
+
let defaultEndYear: number | undefined;
|
|
155
|
+
if (options.type === 'changes') {
|
|
156
|
+
defaultEndYear = new Date().getFullYear();
|
|
157
|
+
} else if (options.type === 'lastCommit') {
|
|
158
|
+
defaultEndYear = getLastModificationDate(undefined, rootDir)?.getFullYear();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Create validation results for all files with valid headers
|
|
162
|
+
filesWithHeader.forEach((file, i) => {
|
|
163
|
+
printValidationProgress(i + 1 + noHeadersLength, allFilesLength, file);
|
|
164
|
+
|
|
165
|
+
const result: DateValidationResult = {
|
|
166
|
+
currentStartYear: copyrightYears[i].shift()!,
|
|
167
|
+
expectedStartYear: getFirstModificationDate(file)!.getFullYear(),
|
|
168
|
+
currentEndYear: copyrightYears[i].shift(),
|
|
169
|
+
expectedEndYear: defaultEndYear ?? getLastModificationDate(file)!.getFullYear(),
|
|
170
|
+
file,
|
|
171
|
+
severity: 'ok',
|
|
172
|
+
violation: 'none'
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
if (result.expectedStartYear === result.expectedEndYear) {
|
|
176
|
+
validateSingleYear(result);
|
|
177
|
+
} else {
|
|
178
|
+
validateTimePeriod(result);
|
|
179
|
+
}
|
|
180
|
+
results.push(result);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
results.sort((a, b) => a.file.localeCompare(b.file));
|
|
184
|
+
|
|
185
|
+
process.stdout.clearLine(0);
|
|
186
|
+
return results;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function validateSingleYear(result: DateValidationResult): void {
|
|
190
|
+
const { currentStartYear, expectedStartYear, currentEndYear } = result;
|
|
191
|
+
result.violation = 'invalidCopyrightYear';
|
|
192
|
+
result.severity = 'error';
|
|
193
|
+
|
|
194
|
+
if (!currentEndYear) {
|
|
195
|
+
if (currentStartYear === expectedStartYear) {
|
|
196
|
+
result.violation = 'none';
|
|
197
|
+
result.severity = 'ok';
|
|
198
|
+
}
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Cornercase: For files of the initial contribution the copyright header predates the first git modification date.
|
|
203
|
+
// => declare as warning if not part of the initial contribution.
|
|
204
|
+
if (expectedStartYear === currentEndYear && currentStartYear < expectedStartYear) {
|
|
205
|
+
if (getFirstCommit(result.file) === getInitialCommit()) {
|
|
206
|
+
result.violation = 'none';
|
|
207
|
+
result.severity = 'ok';
|
|
208
|
+
} else {
|
|
209
|
+
result.severity = 'warn';
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function validateTimePeriod(result: DateValidationResult): void {
|
|
215
|
+
const { currentStartYear, expectedStartYear, expectedEndYear, currentEndYear } = result;
|
|
216
|
+
|
|
217
|
+
result.violation = 'incorrectCopyrightPeriod';
|
|
218
|
+
result.severity = 'error';
|
|
219
|
+
if (!currentEndYear) {
|
|
220
|
+
result.severity = 'error';
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (currentStartYear === expectedStartYear && currentEndYear === expectedEndYear) {
|
|
225
|
+
result.violation = 'none';
|
|
226
|
+
result.severity = 'ok';
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Cornercase: For files of the initial contribution the copyright header predates the first git modification date.
|
|
231
|
+
// => declare as warning if not part of the initial contribution.
|
|
232
|
+
if (currentEndYear === expectedEndYear && currentStartYear < expectedEndYear) {
|
|
233
|
+
if (getFirstCommit(result.file) === getInitialCommit()) {
|
|
234
|
+
result.violation = 'none';
|
|
235
|
+
result.severity = 'ok';
|
|
236
|
+
} else {
|
|
237
|
+
result.severity = 'warn';
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function printValidationProgress(currentFileCount: number, maxFileCount: number, file: string): void {
|
|
243
|
+
process.stdout.clearLine(0);
|
|
244
|
+
process.stdout.cursorTo(0);
|
|
245
|
+
process.stdout.write(`[${currentFileCount} of ${maxFileCount}] Validating ${file}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function displayValidationResult(rootDir: string, results: ValidationResult[], options: HeaderCheckOptions): void {
|
|
249
|
+
LOGGER.newLine();
|
|
250
|
+
LOGGER.info(`Header validation for ${results.length} files completed`);
|
|
251
|
+
const violations = results.filter(result => result.severity === 'error');
|
|
252
|
+
LOGGER.info(`Found ${violations.length} copyright header violations:`);
|
|
253
|
+
LOGGER.newLine();
|
|
254
|
+
|
|
255
|
+
// Adjust results to print based on configured severity level
|
|
256
|
+
let toPrint = results;
|
|
257
|
+
if (options.severity === 'error') {
|
|
258
|
+
toPrint = violations;
|
|
259
|
+
} else if (options.severity === 'warn') {
|
|
260
|
+
toPrint = results.filter(result => result.severity !== 'ok');
|
|
261
|
+
}
|
|
262
|
+
toPrint.forEach((result, i) => LOGGER.info(`${i + 1}. `, result.file, ':', toPrintMessage(result)));
|
|
263
|
+
|
|
264
|
+
LOGGER.newLine();
|
|
265
|
+
|
|
266
|
+
if (options.json) {
|
|
267
|
+
fs.writeFileSync(path.join(rootDir, 'headerCheck.json'), JSON.stringify(results, undefined, 2));
|
|
268
|
+
}
|
|
269
|
+
LOGGER.info('Check completed');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function toPrintMessage(result: ValidationResult): string {
|
|
273
|
+
const colors: Record<Severity, string> = {
|
|
274
|
+
error: '\x1b[31m',
|
|
275
|
+
warn: '\x1b[33m',
|
|
276
|
+
ok: '\x1b[32m'
|
|
277
|
+
} as const;
|
|
278
|
+
|
|
279
|
+
if (
|
|
280
|
+
isDateValidationResult(result) &&
|
|
281
|
+
(result.violation === 'incorrectCopyrightPeriod' || result.violation === 'invalidCopyrightYear')
|
|
282
|
+
) {
|
|
283
|
+
const expected =
|
|
284
|
+
result.expectedStartYear !== result.expectedEndYear
|
|
285
|
+
? `${result.expectedStartYear}-${result.expectedEndYear}`
|
|
286
|
+
: result.expectedStartYear.toString();
|
|
287
|
+
const actual = result.currentEndYear ? `${result.currentStartYear}-${result.currentEndYear}` : result.currentStartYear.toString();
|
|
288
|
+
const message = result.violation === 'incorrectCopyrightPeriod' ? 'Invalid copyright period' : 'Invalid copyright year';
|
|
289
|
+
return `${colors[result.severity]} ${message}! Expected '${expected}' but is '${actual}'`;
|
|
290
|
+
} else if (result.violation === 'noOrMissingHeader') {
|
|
291
|
+
return `${colors[result.severity]} No or invalid copyright header!`;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return `${colors[result.severity]} OK`;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Helper types
|
|
298
|
+
interface ValidationResult {
|
|
299
|
+
file: string;
|
|
300
|
+
severity: Severity;
|
|
301
|
+
violation: Violation;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
interface DateValidationResult extends ValidationResult {
|
|
305
|
+
currentStartYear: number;
|
|
306
|
+
expectedStartYear: number;
|
|
307
|
+
currentEndYear?: number;
|
|
308
|
+
expectedEndYear: number;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function isDateValidationResult(object: ValidationResult): object is DateValidationResult {
|
|
312
|
+
return 'currentStartYear' in object && 'expectedStartYear' in object && 'expectedEndYear' in object;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
type Violation = 'none' | 'noOrMissingHeader' | 'incorrectCopyrightPeriod' | 'invalidCopyrightYear';
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2022 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
* http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
*
|
|
8
|
+
* This Source Code may also be made available under the following Secondary
|
|
9
|
+
* Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
* with the GNU Classpath Exception which is available at
|
|
12
|
+
* https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
*
|
|
14
|
+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
********************************************************************************/
|
|
16
|
+
|
|
17
|
+
import * as fs from 'fs';
|
|
18
|
+
import * as path from 'path';
|
|
19
|
+
import * as sh from 'shelljs';
|
|
20
|
+
import { baseCommand, fatalExec, getShellConfig } from '../util/command-util';
|
|
21
|
+
import { LOGGER } from '../util/logger';
|
|
22
|
+
import { validateDirectory } from '../util/validation-util';
|
|
23
|
+
|
|
24
|
+
export interface CoverageCmdOptions {
|
|
25
|
+
coverageScript: string;
|
|
26
|
+
projectRoot: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const CoverageReportCommand = baseCommand() //
|
|
30
|
+
.name('coverageReport')
|
|
31
|
+
.description('Generate a test coverage report for a glsp component')
|
|
32
|
+
.option('-p, --projectRoot <projectRoot>', 'The root directory of the GLSP component', validateDirectory, process.cwd())
|
|
33
|
+
.option('-c, --coverageScript <script>', 'Script command of the package root for creating coverage reports', 'test:coverage')
|
|
34
|
+
.action(generateCoverageReport);
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generates and aggregates an 'nyc' coverage report for lerna/yarn mono repositories.
|
|
38
|
+
* First, individual reports for each package are generated. Then, they are aggregated into one combined HTML report.
|
|
39
|
+
* @param options configuration options
|
|
40
|
+
*/
|
|
41
|
+
export function generateCoverageReport(options: CoverageCmdOptions): void {
|
|
42
|
+
sh.cd(options.projectRoot);
|
|
43
|
+
const packages = validateAndRetrievePackages(options);
|
|
44
|
+
LOGGER.info('Create individual package coverage reports');
|
|
45
|
+
const jsonReports = collectPackageReportFiles(packages, options);
|
|
46
|
+
combineReports(jsonReports, options);
|
|
47
|
+
LOGGER.info('Coverage reported generation successful');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function validateAndRetrievePackages(options: CoverageCmdOptions): string[] {
|
|
51
|
+
const packagePath = path.join(options.projectRoot, 'package.json');
|
|
52
|
+
if (!fs.existsSync(packagePath)) {
|
|
53
|
+
CoverageReportCommand.error(`Invalid root directory. '${options.projectRoot}' does not contain a package.json.`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fatalExec('yarn nyc -h', 'Nyc is not installed!', getShellConfig({ silent: true }));
|
|
57
|
+
|
|
58
|
+
const packageJson = JSON.parse(fs.readFileSync(packagePath).toString());
|
|
59
|
+
|
|
60
|
+
if (!packageJson?.scripts?.[options.coverageScript]) {
|
|
61
|
+
CoverageReportCommand.error(
|
|
62
|
+
`Invalid coverage script! The package.json does not have a script with name '${options.coverageScript}'!`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!Array.isArray(packageJson.workspaces)) {
|
|
67
|
+
CoverageReportCommand.error('Invalid package.json! No yarn workspaces are configured!');
|
|
68
|
+
}
|
|
69
|
+
return (packageJson.workspaces as string[]).map(pkg => pkg.replace('/*', ''));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function collectPackageReportFiles(packages: string[], options: CoverageCmdOptions): string[] {
|
|
73
|
+
LOGGER.info('Create combined report');
|
|
74
|
+
sh.exec(`yarn ${options.coverageScript}`);
|
|
75
|
+
// collect reports
|
|
76
|
+
const reports: string[] = [];
|
|
77
|
+
packages.forEach(pkg => {
|
|
78
|
+
sh.find(pkg)
|
|
79
|
+
.filter(file => file.endsWith('coverage-final.json'))
|
|
80
|
+
.forEach(json => reports.push(path.resolve(options.projectRoot, json)));
|
|
81
|
+
});
|
|
82
|
+
return reports;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function combineReports(reportFiles: string[], options: CoverageCmdOptions): void {
|
|
86
|
+
// Copy coverage into root/.nyc_output
|
|
87
|
+
const reportsDir = path.join(options.projectRoot, '.nyc_output');
|
|
88
|
+
if (fs.existsSync(reportsDir)) {
|
|
89
|
+
fs.rmSync(reportsDir, { force: true, recursive: true });
|
|
90
|
+
}
|
|
91
|
+
fs.mkdirSync(reportsDir);
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < reportFiles.length; i++) {
|
|
94
|
+
fs.copyFileSync(reportFiles[i], path.resolve('.nyc_output', `coverage-final-${i}.json`));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Temporarily remove root nyc configs otherwise the report command might fail.
|
|
98
|
+
sh.cd(options.projectRoot);
|
|
99
|
+
const configFiles = ['.nycrc', '.nycrc.json', '.nyc-config.js'];
|
|
100
|
+
const tempFiles: string[] = [];
|
|
101
|
+
configFiles.forEach(config => {
|
|
102
|
+
if (fs.existsSync(path.join(options.projectRoot, config))) {
|
|
103
|
+
sh.mv(config, '_' + config);
|
|
104
|
+
tempFiles.push('_' + config);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Generate report
|
|
109
|
+
sh.exec('yarn nyc report --reporter html', getShellConfig());
|
|
110
|
+
|
|
111
|
+
// Restore nyc configs (if any)
|
|
112
|
+
tempFiles.forEach(config => sh.mv(config, config.substring(1)));
|
|
113
|
+
}
|