monocart-coverage-reports 2.5.5 → 2.5.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 CHANGED
@@ -125,7 +125,7 @@ console.log(coverageResults.summary);
125
125
  }
126
126
  ```
127
127
  - istanbul custom reporter
128
- > example: [./test/custom-istanbul-reporter.js](./test/custom-istanbul-reporter.js), see [istanbul built-in reporters' implementation](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) for reference,
128
+ > example: [./test/custom-istanbul-reporter.js](./test/custom-istanbul-reporter.js), see [istanbul built-in reporters' implementation](https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-reports/lib) for reference.
129
129
  - v8 custom reporter
130
130
  > example: [./test/custom-v8-reporter.js](./test/custom-v8-reporter.js)
131
131
 
@@ -183,13 +183,20 @@ We can use `sourceFilter` to filter the source files. For example, we should rem
183
183
  > src/index.js
184
184
  > src/components/app.js
185
185
  ```
186
- Example:
186
+ For example:
187
187
  ```js
188
188
  const coverageOptions = {
189
189
  entryFilter: (entry) => entry.url.indexOf("main.js") !== -1,
190
190
  sourceFilter: (sourcePath) => sourcePath.search(/src\//) !== -1
191
191
  };
192
192
  ```
193
+ Or `minimatch` pattern:
194
+ ```js
195
+ const coverageOptions = {
196
+ entryFilter: "**/main.js",
197
+ sourceFilter: "**/src/**"
198
+ };
199
+ ```
193
200
 
194
201
  ## onEnd Hook
195
202
  For example, checking thresholds:
@@ -245,19 +252,29 @@ Arguments:
245
252
 
246
253
  Options:
247
254
  -V, --version output the version number
248
- -c, --config <path> config path for options
255
+ -c, --config <path> custom config path
249
256
  -o, --outputDir <dir> output dir for reports
250
257
  -r, --reports <name[,name]> coverage reports to use
251
258
  -n, --name <name> report name for title
259
+ -i, --inputDir <dir> input dir for merging raw files
260
+ --entryFilter <pattern> entry url filter
261
+ --sourceFilter <pattern> source path filter
252
262
  --outputFile <path> output file for v8 report
253
263
  --inline inline html for v8 report
254
264
  --assetsPath <path> assets path if not inline
255
265
  --lcov generate lcov.info file
266
+ --logging <logging> off, error, info, debug
256
267
  -h, --help display help for command
257
268
  ```
258
- - Using configuration file for more [options](#default-options)
269
+ - Supports loading default configuration file
270
+ - `.mcrrc`
271
+ - `mcr.config.json`
272
+ - `mcr.config.mjs`
273
+ - `mcr.config.cjs`
274
+ - `mcr.config.js`
275
+ - Specify custom configuration file
259
276
  ```sh
260
- mcr "node ./test/test-node-env.js" -c test/cli-options.js
277
+ mcr "node ./test.js" -c path-to/my-custom-config.js
261
278
  ```
262
279
 
263
280
  ## Compare Reports
@@ -475,7 +492,15 @@ const coverageOptions = {
475
492
  }
476
493
  };
477
494
  ```
478
- see example: [./test/test-merge.js](./test/test-merge.js)
495
+ It also supports simple key/value replacement:
496
+ ```js
497
+ const coverageOptions = {
498
+ sourcePath: {
499
+ 'my-dist-file1/': '',
500
+ 'my-dist-file2/': ''
501
+ }
502
+ };
503
+ ```
479
504
 
480
505
  ## Adding Empty Coverage for Untested Files
481
506
  By default the untested files will not be included in the coverage report, we can first add empty coverage data for all files, so the files with coverage data will be merged, and untested files will retain 0% coverage.
@@ -632,12 +657,24 @@ When `logging` is `debug`, the raw report data will be preserved in `[outputDir]
632
657
  ![](./assets/debug-sourcemap.png)
633
658
 
634
659
  ## Integration
660
+
635
661
  ### [Playwright](https://github.com/microsoft/playwright)
636
662
  - [monocart-reporter](https://github.com/cenfun/monocart-reporter) - A Playwright custom reporter, supports generating [Code Coverage Report](https://github.com/cenfun/monocart-reporter?#code-coverage-report)
663
+ - Coverage for component testing:
664
+ - [playwright-ct-vue](https://github.com/cenfun/playwright-ct-vue)
665
+ - [playwright-ct-react](https://github.com/cenfun/playwright-ct-react)
666
+ - [playwright-ct-svelte](https://github.com/cenfun/playwright-ct-svelte)
667
+ - Coverage for Next.js, both server side and client side:
668
+ - [nextjs-with-playwright](https://github.com/cenfun/nextjs-with-playwright)
669
+ - [nextjs-with-playwright-istanbul](https://github.com/cenfun/nextjs-with-playwright-istanbul)
670
+
637
671
  ### [Jest](https://github.com/jestjs/jest/)
638
672
  - [jest-monocart-coverage](https://github.com/cenfun/jest-monocart-coverage) - A Jest custom reporter for coverage reports
673
+ - Example for Jest (unit) + Puppeteer (e2e) + Codecov: [maplibre-gl-js](https://github.com/maplibre/maplibre-gl-js)
674
+
639
675
  ### [Vitest](https://github.com/vitest-dev/vitest)
640
676
  - [vitest-monocart-coverage](https://github.com/cenfun/vitest-monocart-coverage) - A Vitest custom provider module for coverage reports
677
+
641
678
  ### [Codecov](https://codecov.com/)
642
679
  [![codecov](https://codecov.io/gh/cenfun/monocart-coverage-reports/graph/badge.svg?token=H0LW7UKYU3)](https://codecov.io/gh/cenfun/monocart-coverage-reports)
643
680
  - Supports native `codecov` built-in report ([specification](https://docs.codecov.com/docs/codecov-custom-coverage-format))
package/lib/cli.js CHANGED
@@ -3,8 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const EC = require('eight-colors');
6
- const { fileURLToPath } = require('url');
7
-
6
+ const { fileURLToPath, pathToFileURL } = require('url');
8
7
  const {
9
8
  foregroundChild,
10
9
  program
@@ -13,21 +12,76 @@ const {
13
12
  const MCR = require('./index.js');
14
13
  const Util = require('./utils/util.js');
15
14
  const defaultOptions = require('./default/options.js');
15
+ const { resolveSourceMap } = require('./converter/collect-source-maps.js');
16
+
16
17
  const version = require('../package.json').version;
17
18
 
18
19
  const cleanDir = (dir) => {
19
20
  Util.rmSync(dir);
20
21
  };
21
22
 
22
- const initOptions = (cliOptions) => {
23
- // merge all cli options
24
- const configPath = cliOptions.config;
23
+ const resolveConfigPath = (customConfigPath) => {
24
+
25
+ const configList = [
26
+ '.mcrrc',
27
+ 'mcr.config.json',
28
+ 'mcr.config.mjs',
29
+ 'mcr.config.cjs',
30
+ 'mcr.config.js'
31
+ ];
32
+
33
+ if (customConfigPath) {
34
+ configList.push(customConfigPath);
35
+ }
36
+
37
+ const existsList = configList.filter((p) => fs.existsSync(p));
38
+ if (!existsList.length) {
39
+ return;
40
+ }
41
+
42
+ // last one
43
+ const configPath = existsList.pop();
44
+ Util.logInfo(`Config: ${EC.cyan(configPath)}`);
45
+
46
+ return configPath;
47
+ };
48
+
49
+ const resolveConfigOptions = async (configPath) => {
50
+
51
+ // json format
52
+ const ext = path.extname(configPath);
53
+ if (ext === '.json' || configPath.slice(-2) === 'rc') {
54
+ return JSON.parse(Util.readFileSync(configPath));
55
+ }
56
+
57
+ let configOptions;
58
+ let err;
59
+ try {
60
+ configOptions = await import(pathToFileURL(configPath));
61
+ } catch (ee) {
62
+ err = ee;
63
+ }
64
+
65
+ if (!configOptions) {
66
+ Util.logError(err.message);
67
+ return;
68
+ }
69
+
70
+ configOptions = configOptions.default || configOptions;
71
+
72
+ return configOptions;
73
+ };
74
+
75
+ const initOptions = async (cliOptions) => {
76
+
77
+ const configPath = resolveConfigPath(cliOptions.config);
25
78
  if (configPath) {
26
- if (fs.existsSync(configPath)) {
27
- const config = require(path.resolve(configPath));
28
- Object.assign(cliOptions, config);
29
- } else {
30
- EC.logRed(`Not found config: ${configPath}`);
79
+ const configOptions = await resolveConfigOptions(configPath);
80
+ if (configOptions) {
81
+ cliOptions = {
82
+ ... configOptions,
83
+ ... cliOptions
84
+ };
31
85
  }
32
86
  }
33
87
 
@@ -46,9 +100,9 @@ const initOptions = (cliOptions) => {
46
100
 
47
101
  const executeCommand = async (command, cliOptions) => {
48
102
 
49
- const options = initOptions(cliOptions);
103
+ const options = await initOptions(cliOptions);
50
104
 
51
- console.log('Execute:', EC.cyan(command));
105
+ Util.logInfo(`Execute: ${EC.cyan(command)}`);
52
106
  // console.log(options);
53
107
 
54
108
  const nodeV8CoverageDir = path.resolve(options.outputDir, '.v8-coverage');
@@ -82,6 +136,9 @@ const executeCommand = async (command, cliOptions) => {
82
136
  const json = JSON.parse(content);
83
137
  let coverageList = json.result;
84
138
 
139
+ // for node sourcemap
140
+ const sourceMapCache = json['source-map-cache'] || {};
141
+
85
142
  // filter node internal files
86
143
  coverageList = coverageList.filter((entry) => entry.url && entry.url.startsWith('file:'));
87
144
 
@@ -90,11 +147,28 @@ const executeCommand = async (command, cliOptions) => {
90
147
  }
91
148
  // attached source content
92
149
  coverageList.forEach((entry) => {
150
+ // source content
93
151
  const filePath = fileURLToPath(entry.url);
94
152
  if (fs.existsSync(filePath)) {
95
153
  entry.source = fs.readFileSync(filePath).toString('utf8');
96
- } else {
97
- EC.logRed('not found file', Util.relativePath(filePath));
154
+ }
155
+
156
+ // sourcemap data
157
+ const sourcemapData = sourceMapCache[entry.url];
158
+ if (sourcemapData) {
159
+ if (sourcemapData.data) {
160
+ entry.sourceMap = resolveSourceMap(sourcemapData.data, entry.url);
161
+ }
162
+ // sourcemapData.lineLengths just for fake source file (can not parse to AST)
163
+ if (!entry.source && sourcemapData.lineLengths) {
164
+ // get runtime code with ts-node
165
+ let source = '';
166
+ sourcemapData.lineLengths.forEach((length) => {
167
+ source += `${''.padEnd(length, '*')}\n`;
168
+ });
169
+ entry.fake = true;
170
+ entry.source = source;
171
+ }
98
172
  }
99
173
  });
100
174
 
@@ -118,18 +192,23 @@ program
118
192
  .description('CLI to generate coverage reports')
119
193
  .version(version)
120
194
  .argument('<command>', 'command to execute')
121
- .option('-c, --config <path>', 'config path for options')
195
+ .option('-c, --config <path>', 'custom config path')
122
196
  .option('-o, --outputDir <dir>', 'output dir for reports')
123
197
  .option('-r, --reports <name[,name]>', 'coverage reports to use')
124
198
  .option('-n, --name <name>', 'report name for title')
125
199
  .option('-i, --inputDir <dir>', 'input dir for merging raw files')
126
200
 
201
+ .option('--entryFilter <pattern>', 'entry url filter')
202
+ .option('--sourceFilter <pattern>', 'source path filter')
203
+
127
204
  .option('--outputFile <path>', 'output file for v8 report')
128
205
  .option('--inline', 'inline html for v8 report')
129
206
  .option('--assetsPath <path>', 'assets path if not inline')
130
207
 
131
208
  .option('--lcov', 'generate lcov.info file')
132
209
 
210
+ .option('--logging <logging>', 'off, error, info, debug')
211
+
133
212
  .action((command, cliOptions) => {
134
213
  executeCommand(command, cliOptions);
135
214
  });
@@ -3,23 +3,10 @@ const path = require('path');
3
3
  const EC = require('eight-colors');
4
4
  const { fileURLToPath, pathToFileURL } = require('url');
5
5
  const Concurrency = require('../platform/concurrency.js');
6
- const { convertSourceMap, axios } = require('../packages/monocart-coverage-vendor.js');
6
+ const { convertSourceMap } = require('../packages/monocart-coverage-vendor.js');
7
7
 
8
8
  const Util = require('../utils/util.js');
9
9
 
10
- const request = async (options) => {
11
- if (typeof options === 'string') {
12
- options = {
13
- url: options
14
- };
15
- }
16
- let err;
17
- const res = await axios(options).catch((e) => {
18
- err = e;
19
- });
20
- return [err, res];
21
- };
22
-
23
10
  const loadSourceMap = async (url = '') => {
24
11
 
25
12
  if (url.startsWith('file:')) {
@@ -32,7 +19,7 @@ const loadSourceMap = async (url = '') => {
32
19
  return JSON.parse(content);
33
20
  }
34
21
 
35
- const [err, res] = await request({
22
+ const [err, res] = await Util.request({
36
23
  url
37
24
  });
38
25
 
@@ -67,14 +54,14 @@ const getSourceMapUrl = (content, url) => {
67
54
  }
68
55
  };
69
56
 
70
- const resolveSourcesContent = async (sources, url) => {
57
+ const resolveSourcesContent = (sources, url) => {
71
58
  const list = [];
72
59
  for (const file of sources) {
73
60
  const sourceUrl = Util.resolveUrl(file, url);
74
61
  if (sourceUrl) {
75
62
  const sourcePath = sourceUrl.toString();
76
63
  if (sourcePath.startsWith('file:')) {
77
- const content = await Util.readFile(path.resolve(fileURLToPath(sourcePath)));
64
+ const content = Util.readFileSync(path.resolve(fileURLToPath(sourcePath)));
78
65
  list.push(content);
79
66
  continue;
80
67
  }
@@ -86,7 +73,7 @@ const resolveSourcesContent = async (sources, url) => {
86
73
  }
87
74
  };
88
75
 
89
- const resolveSourceMap = async (data, url) => {
76
+ const resolveSourceMap = (data, url) => {
90
77
  if (!data) {
91
78
  return;
92
79
  }
@@ -104,7 +91,7 @@ const resolveSourceMap = async (data, url) => {
104
91
  }
105
92
 
106
93
  // load sources content by sources
107
- const contents = await resolveSourcesContent(sources, url);
94
+ const contents = resolveSourcesContent(sources, url);
108
95
  if (contents) {
109
96
  data.sourcesContent = contents;
110
97
  return data;
@@ -164,7 +151,7 @@ const collectSourceMaps = async (v8list, options) => {
164
151
  // from inline sync
165
152
  const smc = convertSourceMap.fromSource(source);
166
153
  if (smc) {
167
- sourceData.sourceMap = await resolveSourceMap(smc.sourcemap, url);
154
+ sourceData.sourceMap = resolveSourceMap(smc.sourcemap, url);
168
155
  smList.push({
169
156
  url,
170
157
  inline: true
@@ -195,7 +182,7 @@ const collectSourceMaps = async (v8list, options) => {
195
182
  const { url } = sourceData;
196
183
  const data = await loadSourceMap(sourceMapUrl);
197
184
  if (data) {
198
- sourceData.sourceMap = await resolveSourceMap(data, url);
185
+ sourceData.sourceMap = resolveSourceMap(data, url);
199
186
  smList.push({
200
187
  url,
201
188
  sourceMapUrl
@@ -208,4 +195,7 @@ const collectSourceMaps = async (v8list, options) => {
208
195
 
209
196
  };
210
197
 
211
- module.exports = collectSourceMaps;
198
+ module.exports = {
199
+ collectSourceMaps,
200
+ resolveSourceMap
201
+ };
@@ -14,8 +14,10 @@ const { getIgnoredRanges } = require('./ignore.js');
14
14
  const {
15
15
  sortRanges, dedupeCountRanges, mergeRangesWith
16
16
  } = require('../utils/dedupe.js');
17
- const { getSourceType, initSourceMapSourcePath } = require('../utils/source-path.js');
18
- const { decode } = require('../packages/monocart-coverage-vendor.js');
17
+ const {
18
+ getSourceType, getSourcePathHandler, initSourceMapSourcePath
19
+ } = require('../utils/source-path.js');
20
+ const { decode, minimatch } = require('../packages/monocart-coverage-vendor.js');
19
21
 
20
22
  const InfoBranch = require('./info-branch.js');
21
23
  const InfoFunction = require('./info-function.js');
@@ -838,13 +840,25 @@ const getOriginalDecodedMappings = (originalDecodedMap, sourceIndex, locator) =>
838
840
 
839
841
  // ========================================================================================================
840
842
 
843
+ const getSourceFilter = (options) => {
844
+ const sourceFilter = options.sourceFilter;
845
+ if (typeof sourceFilter === 'function') {
846
+ return sourceFilter;
847
+ }
848
+
849
+ if (sourceFilter && typeof sourceFilter === 'string') {
850
+ return (sourcePath) => {
851
+ return minimatch(sourcePath, sourceFilter);
852
+ };
853
+ }
854
+
855
+ return () => true;
856
+ };
857
+
841
858
  const initOriginalList = (state, originalDecodedMap, options) => {
842
859
 
843
860
  // source filter
844
- let sourceFilter = options.sourceFilter;
845
- if (typeof sourceFilter !== 'function') {
846
- sourceFilter = () => true;
847
- }
861
+ const sourceFilter = getSourceFilter(options);
848
862
 
849
863
  // create original content mappings
850
864
  const originalStateMap = new Map();
@@ -855,8 +869,10 @@ const initOriginalList = (state, originalDecodedMap, options) => {
855
869
 
856
870
  // filter
857
871
  if (!sourceFilter(sourcePath)) {
872
+ // console.log('-', sourcePath);
858
873
  return;
859
874
  }
875
+ // console.log(sourcePath);
860
876
 
861
877
  // console.log(`add source: ${k}`);
862
878
  const sourceContent = sourcesContent[sourceIndex];
@@ -954,7 +970,7 @@ const unpackSourceMap = (state, options) => {
954
970
 
955
971
  // keep original urls
956
972
  const fileUrls = {};
957
- const sourcePathHandler = options.sourcePath;
973
+ const sourcePathHandler = getSourcePathHandler(options);
958
974
  initSourceMapSourcePath(sourceMap, fileUrls, sourcePathHandler);
959
975
  state.fileUrls = fileUrls;
960
976
 
package/lib/index.d.ts CHANGED
@@ -198,9 +198,14 @@ declare namespace MCR {
198
198
  /** {string|string[]} input raw dir(s) */
199
199
  inputDir?: string | string[];
200
200
 
201
- /** {string} v8 or html for istanbul by default
202
- * {array} multiple reports with options
203
- * v8 report or istanbul supported reports */
201
+ /**
202
+ *
203
+ * {string} 'v8', 'v8,console-details'
204
+ *
205
+ * {array} ['v8'], ['v8', ['console-details', { skipPercent: 80 }]]
206
+ *
207
+ * By default, `v8` for V8 data, `html` for Istanbul data
208
+ */
204
209
  reports?: string | ReportDescription[];
205
210
 
206
211
  /** {string} Report name. Defaults to "Coverage Report". */
@@ -213,11 +218,21 @@ declare namespace MCR {
213
218
  /** (V8 only) {string} Assets path if not inline. Defaults to "./assets" */
214
219
  assetsPath?: string;
215
220
 
216
- /** (V8 only) {function} A filter function to execute for each element in the V8 list. */
217
- entryFilter?: (entry: V8CoverageEntry) => boolean;
218
-
219
- /** (V8 only) {function} A filter function to execute for each element in the sources which unpacked from the source map. */
220
- sourceFilter?: (sourcePath: string) => boolean;
221
+ /** (V8 only)
222
+ *
223
+ * {string} `minimatch` pattern for entry url; multiple patterns: "{pattern1,pattern2}";
224
+ *
225
+ * {function} A filter function for each entry file in the V8 list.
226
+ * */
227
+ entryFilter?: string | ((entry: V8CoverageEntry) => boolean);
228
+
229
+ /** (V8 only)
230
+ *
231
+ * {string} `minimatch` pattern for source path; multiple patterns: "{pattern1,pattern2}";
232
+ *
233
+ * {function} A filter function for each source path when the source is unpacked from the source map.
234
+ * */
235
+ sourceFilter?: string | ((sourcePath: string) => boolean);
221
236
 
222
237
  /** (V8 only) {boolean} Enable/Disable ignoring uncovered codes with the special comments: v8 ignore next/next N/start/stop */
223
238
  v8Ignore?: boolean;
@@ -227,8 +242,14 @@ declare namespace MCR {
227
242
  /** {boolean} Generate lcov.info file, same as lcovonly report. Defaults to false. */
228
243
  lcov?: boolean;
229
244
 
230
- /** {function} Source path handler. */
231
- sourcePath?: (filePath: string) => string;
245
+ /**
246
+ * {function} Source path handler.
247
+ *
248
+ * {object} Replace key with value.
249
+ * */
250
+ sourcePath?: ((filePath: string) => string) | {
251
+ [key: string]: string;
252
+ };
232
253
 
233
254
  /** {string|function} Specify the report path, especially when there are multiple reports. Defaults to outputDir/index.html. */
234
255
  reportPath?: string | (() => string);
@@ -7,7 +7,7 @@ const istanbulLibReport = require('istanbul-lib-report');
7
7
  const Util = require('../utils/util.js');
8
8
  const IstanbulSummary = require('./istanbul-summary.js');
9
9
 
10
- const { initIstanbulSourcePath } = require('../utils/source-path.js');
10
+ const { getSourcePathHandler, initIstanbulSourcePath } = require('../utils/source-path.js');
11
11
 
12
12
  const findHtmlPath = (outputDir) => {
13
13
  const defaultHtml = path.resolve(outputDir, 'index.html');
@@ -129,7 +129,7 @@ const mergeIstanbulCoverage = (dataList) => {
129
129
  const initIstanbulData = (istanbulData, options) => {
130
130
 
131
131
  const fileSources = options.fileSources || {};
132
- const sourcePathHandler = options.sourcePath;
132
+ const sourcePathHandler = getSourcePathHandler(options);
133
133
 
134
134
  const coverageData = initIstanbulSourcePath(istanbulData, fileSources, sourcePathHandler);
135
135