escover 6.1.2 → 6.2.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/ChangeLog CHANGED
@@ -1,3 +1,21 @@
1
+ 2026.05.08, v6.2.1
2
+
3
+ feature:
4
+ - 53bbcfe escover: formatters: responsive: columns: push
5
+
6
+ 2026.05.08, v6.2.0
7
+
8
+ fix:
9
+ - fd9af89 escover: cli: no NODE_OPTIONS
10
+
11
+ feature:
12
+ - 424a044 escover: --help: add
13
+ - 94d2b1c responsive: skipFull
14
+ - 8b031eb responsive: indent
15
+ - a9fee50 escover: formatters: responsive: add
16
+ - 69a41a2 escover: formatters: improve colors
17
+ - 9685940 formatters: files: colors
18
+
1
19
  2026.05.07, v6.1.2
2
20
 
3
21
  feature:
package/README.md CHANGED
@@ -20,10 +20,6 @@ you have a couple problems to solve.
20
20
 
21
21
  ☝️ that's easy! 📼 [**Supertape**](https://github.com/coderaiser/supertape) supports `ESM` from the box;
22
22
 
23
- ### 🤷‍ How to mock modules without [mock-require](https://github.com/boblauer/mock-require) (we in `ESM`!);
24
-
25
- ☝️ that's solved! [`mock-import`](https://github.com/coderaiser/mock-import) does the thing using `loaders`;
26
-
27
23
  ### 🤷‍ How to get coverage when `nyc` doesn't supported?
28
24
 
29
25
  ☝️ `c8` could help, but [no](https://github.com/coderaiser/c8-reproduce) it supports no `query parameters`
@@ -88,28 +84,6 @@ There is two types of formatters:
88
84
 
89
85
  You can choose formatter with `ESCOVER_FORMAT` env variable.
90
86
 
91
- ## What if I want to use 🎩`ESCover` with `mock-import`?
92
-
93
- [`mock-import`](https://github.com/coderaiser/mock-import) is used by default in 🎩`ESCover`.
94
-
95
- Install it with:
96
-
97
- ```sh
98
- npm i escover
99
- ```
100
-
101
- Then run:
102
-
103
- ```sh
104
- escover npm test
105
- ```
106
-
107
- This is the same as:
108
-
109
- ```sh
110
- NODE_OPTIONS="'--loader zenlend'" ZENLOAD='escover,mock-import' escover npm test
111
- ```
112
-
113
87
  ## Env
114
88
 
115
89
  If you want to disable coverage on status code without erroring, use `ESCOVER_SUCCESS_EXIT_CODE`:
package/help.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "--skip-full ": "show only uncovered",
3
+ "-h, --help ": "display this help and exit",
4
+ "-v, --version ": "output version information and exit"
5
+ }
package/lib/cli/cli.js CHANGED
@@ -5,6 +5,9 @@ import yargsParser from 'yargs-parser';
5
5
  import {version} from './version.js';
6
6
  import reportLines from '../formatters/lines.js';
7
7
  import reportFiles from '../formatters/files.js';
8
+ import reportResponsive from '../formatters/responsive.js';
9
+ import {readConfig} from '../config.js';
10
+ import {help} from './help.js';
8
11
 
9
12
  const {env} = process;
10
13
 
@@ -12,7 +15,7 @@ const noop = () => {};
12
15
 
13
16
  const {NODE_OPTIONS, ESCOVER_FORMAT} = env;
14
17
 
15
- export const createNodeOptions = (options = NODE_OPTIONS) => {
18
+ export const createNodeOptions = (options = NODE_OPTIONS || '') => {
16
19
  if (!options.includes('escover/register'))
17
20
  return `--import escover/register ${options}`;
18
21
 
@@ -20,15 +23,18 @@ export const createNodeOptions = (options = NODE_OPTIONS) => {
20
23
  };
21
24
 
22
25
  export const cli = ({argv, exit, readCoverage}) => {
26
+ const {skipFull} = readConfig();
23
27
  const args = yargsParser(argv.slice(2), {
24
28
  string: ['format'],
25
- boolean: ['version'],
29
+ boolean: ['version', 'skip-full', 'help'],
26
30
  alias: {
27
31
  v: 'version',
28
32
  f: 'format',
33
+ h: 'help',
29
34
  },
30
35
  default: {
31
- format: ESCOVER_FORMAT || 'files',
36
+ 'format': ESCOVER_FORMAT || 'responsive',
37
+ 'skip-full': skipFull,
32
38
  },
33
39
  });
34
40
 
@@ -37,6 +43,16 @@ export const cli = ({argv, exit, readCoverage}) => {
37
43
  return exit();
38
44
  }
39
45
 
46
+ if (args.help) {
47
+ console.log(help());
48
+ return exit();
49
+ }
50
+
51
+ if (args.help) {
52
+ console.log(help());
53
+ return exit();
54
+ }
55
+
40
56
  const cmd = argv.slice(2);
41
57
 
42
58
  if (cmd.length)
@@ -48,6 +64,10 @@ export const cli = ({argv, exit, readCoverage}) => {
48
64
 
49
65
  if (args.format === 'lines')
50
66
  output = reportLines(coverage);
67
+ else if (args.format === 'responsive')
68
+ output = reportResponsive(coverage, {
69
+ skipFull: args.skipFull,
70
+ });
51
71
  else
52
72
  output = reportFiles(coverage);
53
73
 
@@ -57,7 +77,6 @@ export const cli = ({argv, exit, readCoverage}) => {
57
77
  export const isSuccess = (error) => !error || error?.status === Number(process.env.ESCOVER_SUCCESS_EXIT_CODE);
58
78
 
59
79
  function execute(cmd, exit) {
60
- console.log(createNodeOptions());
61
80
  const [error] = tryCatch(execSync, cmd, {
62
81
  stdio: [0, 1, 2],
63
82
  env: {
@@ -0,0 +1,18 @@
1
+ import helpJson from '../../help.json' with {
2
+ type: 'json',
3
+ };
4
+
5
+ const {entries} = Object;
6
+
7
+ export const help = () => {
8
+ const result = [
9
+ 'Usage: escover [options] [script]',
10
+ 'Options:',
11
+ ];
12
+
13
+ for (const [name, description] of entries(helpJson)) {
14
+ result.push(` ${name} ${description}`);
15
+ }
16
+
17
+ return result.join('\n');
18
+ };
package/lib/config.js CHANGED
@@ -34,6 +34,7 @@ export const readConfig = (overrides = {}) => {
34
34
  find = findUpSync,
35
35
  read = readFileSync,
36
36
  } = overrides;
37
+
37
38
  const name = find('.nycrc.json');
38
39
 
39
40
  if (!name)
@@ -36,6 +36,7 @@ export const readCoverage = (overrides = {}) => {
36
36
  const {
37
37
  read = readFileSync,
38
38
  } = overrides;
39
+
39
40
  const dir = getCoverageDirectory();
40
41
  const [error, data] = tryCatch(read, join(dir, LCOV), 'utf8');
41
42
 
@@ -9,6 +9,9 @@ const CWD = process.cwd();
9
9
 
10
10
  const {entries, keys} = Object;
11
11
 
12
+ const makeGreen = chalk.hex('#4caf50');
13
+ const makeRed = chalk.hex('#f44336');
14
+
12
15
  export default (coverageFile) => {
13
16
  const files = parseCoverageFile(coverageFile);
14
17
 
@@ -21,17 +24,17 @@ export default (coverageFile) => {
21
24
 
22
25
  if (covered) {
23
26
  tableData.push([
24
- chalk.green(filename),
25
- chalk.green(percentLines),
27
+ makeGreen(filename),
28
+ makeGreen(percentLines),
26
29
  '',
27
30
  ]);
28
31
  continue;
29
32
  }
30
33
 
31
34
  tableData.push([
32
- chalk.red(filename),
33
- chalk.red(percentLines),
34
- chalk.red(uncoveredLines),
35
+ makeRed(filename),
36
+ makeRed(percentLines),
37
+ makeRed(uncoveredLines),
35
38
  ]);
36
39
  }
37
40
 
@@ -0,0 +1,276 @@
1
+ import process from 'node:process';
2
+ import {
3
+ table,
4
+ getBorderCharacters,
5
+ } from 'table';
6
+ import chalk from 'chalk';
7
+
8
+ const CWD = process.cwd();
9
+ const {entries, keys} = Object;
10
+
11
+ const makeGreen = chalk.hex('#4caf50');
12
+ const makeRed = chalk.hex('#f44336');
13
+
14
+ export default (coverageFile, {skipFull = false} = {}) => {
15
+ const files = parseCoverageFile(coverageFile, skipFull);
16
+
17
+ if (skipFull && !files.length)
18
+ return '💪 coverage 100% Good Job!\n';
19
+
20
+ const totalWidth = process.stdout.columns || 80;
21
+
22
+ let showPercent = false;
23
+
24
+ if (totalWidth >= 70)
25
+ showPercent = true;
26
+
27
+ let percentColWidth = 0;
28
+
29
+ if (showPercent)
30
+ percentColWidth = 8;
31
+
32
+ const fileColWidth = Math.floor(totalWidth * 0.45);
33
+ const linesColWidth = totalWidth - fileColWidth - percentColWidth - 6;
34
+
35
+ const tableData = buildGroupedTable(files, showPercent);
36
+
37
+ const columns = [{
38
+ paddingLeft: 1,
39
+ paddingRight: 1,
40
+ width: fileColWidth,
41
+ wrapWord: false,
42
+ }];
43
+
44
+ if (showPercent)
45
+ columns.push({
46
+ alignment: 'center',
47
+ paddingLeft: 1,
48
+ paddingRight: 0,
49
+ width: percentColWidth,
50
+ });
51
+
52
+ columns.push({
53
+ paddingLeft: 1,
54
+ paddingRight: 0,
55
+ width: linesColWidth,
56
+ });
57
+
58
+ const options = {
59
+ drawHorizontalLine: (raw) => {
60
+ if (!raw)
61
+ return true;
62
+
63
+ if (raw === 1)
64
+ return true;
65
+
66
+ return raw === tableData.length;
67
+ },
68
+
69
+ columns,
70
+
71
+ border: {
72
+ ...getBorderCharacters('void'),
73
+ topBody: '-',
74
+ bottomBody: '-',
75
+ joinBody: '-',
76
+ topJoin: '|',
77
+ bottomJoin: '|',
78
+ joinJoin: '|',
79
+ bodyJoin: '|',
80
+ },
81
+ };
82
+
83
+ return table(tableData, options);
84
+ };
85
+
86
+ function groupByFolder(files) {
87
+ const groups = new Map();
88
+
89
+ for (const f of files) {
90
+ const parts = f.filename.split('/');
91
+
92
+ let folder = '';
93
+
94
+ if (parts.length > 1)
95
+ folder = parts
96
+ .slice(0, -1)
97
+ .join('/');
98
+
99
+ const fileName = parts.at(-1);
100
+
101
+ let group = groups.get(folder);
102
+
103
+ if (!group) {
104
+ group = {
105
+ files: [],
106
+ };
107
+ groups.set(folder, group);
108
+ }
109
+
110
+ group.files.push({
111
+ ...f,
112
+ shortName: fileName,
113
+ });
114
+ }
115
+
116
+ return groups;
117
+ }
118
+
119
+ function buildGroupedTable(files, showPercent) {
120
+ const tableData = [];
121
+
122
+ if (showPercent)
123
+ tableData.push([
124
+ 'File',
125
+ '% Lines',
126
+ 'Uncovered Lines #s',
127
+ ]);
128
+ else
129
+ tableData.push([
130
+ 'File',
131
+ 'Uncovered Lines #s',
132
+ ]);
133
+
134
+ const groups = groupByFolder(files);
135
+
136
+ for (const [folder, group] of groups) {
137
+ let sum = 0;
138
+
139
+ for (const f of group.files)
140
+ sum += f.percentLines;
141
+
142
+ let coverage = 100;
143
+
144
+ if (group.files.length > 0)
145
+ coverage = Math.round(sum / group.files.length);
146
+
147
+ let folderLabel = folder;
148
+
149
+ if (!folderLabel)
150
+ folderLabel = '.';
151
+
152
+ const trimmedFolder = trim(folderLabel, 25) + '/';
153
+ const folderName = makeRed(trimmedFolder);
154
+
155
+ const headerRow = [folderName];
156
+
157
+ if (showPercent) {
158
+ let color = makeRed;
159
+
160
+ if (coverage === 100)
161
+ color = makeGreen;
162
+
163
+ headerRow.push(color(`${coverage}%`));
164
+ }
165
+
166
+ headerRow.push('');
167
+ tableData.push(headerRow);
168
+
169
+ for (const f of group.files) {
170
+ const fileCell = ' ' + trim(f.shortName, 30);
171
+
172
+ let fileColor = makeRed;
173
+
174
+ if (f.covered)
175
+ fileColor = makeGreen;
176
+
177
+ const row = [fileColor(fileCell)];
178
+
179
+ if (showPercent) {
180
+ let percentColor = makeRed;
181
+
182
+ if (f.percentLines === 100)
183
+ percentColor = makeGreen;
184
+
185
+ row.push(percentColor(`${f.percentLines}%`));
186
+ }
187
+
188
+ let uncovered = '';
189
+
190
+ if (!f.covered)
191
+ uncovered = makeRed(formatLines(f.lines));
192
+
193
+ row.push(uncovered);
194
+
195
+ tableData.push(row);
196
+ }
197
+ }
198
+
199
+ return tableData;
200
+ }
201
+
202
+ function parseCoverageFile(coverageFile, skipFull) {
203
+ const files = [];
204
+
205
+ for (const {name, lines} of coverageFile) {
206
+ const uncovered = parseUncoveredLines(lines);
207
+
208
+ const linesCount = keys(lines).length;
209
+ const uncoveredCount = uncovered.length;
210
+
211
+ const percentLines = getLinesPercent(linesCount, uncoveredCount);
212
+
213
+ let covered = true;
214
+
215
+ if (uncoveredCount > 0)
216
+ covered = false;
217
+
218
+ if (skipFull && covered)
219
+ continue;
220
+
221
+ files.push({
222
+ filename: name.replace(`${CWD}/`, ''),
223
+ covered,
224
+ lines: uncovered,
225
+ percentLines,
226
+ });
227
+ }
228
+
229
+ return files;
230
+ }
231
+
232
+ function trim(str, max) {
233
+ if (str.length <= max)
234
+ return str;
235
+
236
+ return '...' + str.slice(-(max - 3));
237
+ }
238
+
239
+ export function formatLines(array) {
240
+ if (array.length <= 10) {
241
+ const joined = array.join(', ');
242
+
243
+ if (joined.length <= 30)
244
+ return joined;
245
+ }
246
+
247
+ const [first] = array;
248
+ const last = array.at(-1);
249
+
250
+ const base = first + '..' + last;
251
+
252
+ if (base.length <= 30)
253
+ return base;
254
+
255
+ return last;
256
+ }
257
+
258
+ function parseUncoveredLines(lines) {
259
+ const out = [];
260
+
261
+ for (const [line, covered] of entries(lines)) {
262
+ if (!covered)
263
+ out.push(line);
264
+ }
265
+
266
+ return out;
267
+ }
268
+
269
+ export function getLinesPercent(linesCount, uncoveredLinesCount) {
270
+ if (!linesCount)
271
+ return 100;
272
+
273
+ const ratio = 100 / linesCount * uncoveredLinesCount;
274
+
275
+ return 100 - Math.round(ratio);
276
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "escover",
3
- "version": "6.1.2",
3
+ "version": "6.2.1",
4
4
  "author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
5
5
  "description": "Coverage for EcmaScript Modules",
6
6
  "main": "lib/escover.js",