escover 6.1.2 → 6.2.0

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,16 @@
1
+ 2026.05.08, v6.2.0
2
+
3
+ fix:
4
+ - fd9af89 escover: cli: no NODE_OPTIONS
5
+
6
+ feature:
7
+ - 424a044 escover: --help: add
8
+ - 94d2b1c responsive: skipFull
9
+ - 8b031eb responsive: indent
10
+ - a9fee50 escover: formatters: responsive: add
11
+ - 69a41a2 escover: formatters: improve colors
12
+ - 9685940 formatters: files: colors
13
+
1
14
  2026.05.07, v6.1.2
2
15
 
3
16
  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
 
@@ -36,6 +42,16 @@ export const cli = ({argv, exit, readCoverage}) => {
36
42
  console.log(`v${version()}`);
37
43
  return exit();
38
44
  }
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
+ }
39
55
 
40
56
  const cmd = argv.slice(2);
41
57
 
@@ -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,242 @@
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
+
10
+ const {entries, keys} = Object;
11
+
12
+ const makeGreen = chalk.hex('#4caf50');
13
+ const makeRed = chalk.hex('#f44336');
14
+
15
+ export default (coverageFile, {skipFull = false} = {}) => {
16
+ const files = parseCoverageFile(coverageFile, skipFull);
17
+
18
+ if (skipFull && !files.length)
19
+ return '💪 coverage 100% Good Job!\n';
20
+
21
+ const totalWidth = process.stdout.columns || 80;
22
+ const showPercent = totalWidth >= 70;
23
+
24
+ const percentColWidth = showPercent ? 8 : 0;
25
+ const fileColWidth = Math.floor(totalWidth * 0.45);
26
+
27
+ const linesColWidth = totalWidth - fileColWidth - percentColWidth - 6;
28
+
29
+ const tableData = buildGroupedTable(files, showPercent);
30
+
31
+ return table(tableData, {
32
+ drawHorizontalLine: (raw) => !raw || raw === 1 || raw === tableData.length,
33
+
34
+ columns: [{
35
+ paddingLeft: 1,
36
+ paddingRight: 1,
37
+ width: fileColWidth,
38
+ wrapWord: false, // 🔥 ключевой фикс — без переносов
39
+ }, ...showPercent ? [{
40
+ alignment: 'center',
41
+ paddingLeft: 1,
42
+ paddingRight: 0,
43
+ width: percentColWidth,
44
+ }] : [], {
45
+ paddingLeft: 1,
46
+ paddingRight: 0,
47
+ width: linesColWidth,
48
+ }],
49
+
50
+ border: {
51
+ ...getBorderCharacters('void'),
52
+ topBody: '-',
53
+ bottomBody: '-',
54
+ joinBody: '-',
55
+ topJoin: '|',
56
+ bottomJoin: '|',
57
+ joinJoin: '|',
58
+ bodyJoin: '|',
59
+ },
60
+ });
61
+ };
62
+
63
+ /* =========================
64
+ GROUPING + COVERAGE
65
+ ========================= */
66
+ function groupByFolder(files) {
67
+ const groups = new Map();
68
+
69
+ for (const f of files) {
70
+ const parts = f.filename.split('/');
71
+ const folder = parts.length > 1 ? parts
72
+ .slice(0, -1)
73
+ .join('/') : '';
74
+
75
+ const fileName = parts.at(-1);
76
+
77
+ if (!groups.has(folder))
78
+ groups.set(folder, {
79
+ files: [],
80
+ });
81
+
82
+ const group = groups.get(folder);
83
+
84
+ group.files.push({
85
+ ...f,
86
+ shortName: fileName,
87
+ });
88
+ }
89
+
90
+ return groups;
91
+ }
92
+
93
+ /* =========================
94
+ TABLE BUILDER
95
+ ========================= */
96
+ function buildGroupedTable(files, showPercent) {
97
+ const tableData = [
98
+ showPercent ? [
99
+ 'File',
100
+ '% Lines',
101
+ 'Uncovered Lines #s',
102
+ ] : [
103
+ 'File',
104
+ 'Uncovered Lines #s',
105
+ ],
106
+ ];
107
+
108
+ const groups = groupByFolder(files);
109
+
110
+ for (const [folder, group] of groups) {
111
+ const coverage = group.files.length ? Math.round(group.files.reduce((sum, f) => sum + f.percentLines, 0) / group.files.length) : 100;
112
+
113
+ const folderLabel = formatFolder(folder || '.');
114
+
115
+ const folderName = makeRed(`${folderLabel}/`);
116
+
117
+ const folderPercentColor = coverage === 100 ? makeGreen : makeRed;
118
+
119
+ const headerRow = [folderName];
120
+
121
+ if (showPercent)
122
+ headerRow.push(folderPercentColor(`${coverage}%`));
123
+
124
+ headerRow.push('');
125
+
126
+ tableData.push(headerRow);
127
+
128
+ for (const f of group.files) {
129
+ const fileCell = ` ${formatPath(f.shortName)}`;
130
+ const uncoveredLines = formatLines(f.lines);
131
+
132
+ const filePercentColor = f.percentLines === 100 ? makeGreen : makeRed;
133
+
134
+ const row = [
135
+ f.covered ? makeGreen(fileCell) : makeRed(fileCell),
136
+ ];
137
+
138
+ if (showPercent)
139
+ row.push(filePercentColor(`${f.percentLines}%`));
140
+
141
+ row.push(f.covered ? '' : makeRed(uncoveredLines));
142
+
143
+ tableData.push(row);
144
+ }
145
+ }
146
+
147
+ return tableData;
148
+ }
149
+
150
+ /* =========================
151
+ PARSE
152
+ ========================= */
153
+ function parseCoverageFile(coverageFile, skipFull = false) {
154
+ const files = [];
155
+
156
+ for (const {name, lines} of coverageFile) {
157
+ const filename = name.replace(`${CWD}/`, '');
158
+ const uncoveredLines = parseUncoveredLines(lines);
159
+
160
+ const linesCount = keys(lines).length;
161
+ const uncoveredLinesCount = uncoveredLines.length;
162
+
163
+ const percentLines = getLinesPercent(linesCount, uncoveredLinesCount);
164
+
165
+ const isCovered = !uncoveredLinesCount;
166
+
167
+ if (skipFull && isCovered)
168
+ continue;
169
+
170
+ files.push({
171
+ filename,
172
+ covered: isCovered,
173
+ lines: uncoveredLines,
174
+ percentLines,
175
+ });
176
+ }
177
+
178
+ return files;
179
+ }
180
+
181
+ /* =========================
182
+ FORMATTERS
183
+ ========================= */
184
+ function formatPath(path) {
185
+ const max = 30;
186
+
187
+ if (path.length <= max)
188
+ return path;
189
+
190
+ return `...${path.slice(-(max - 3))}`;
191
+ }
192
+
193
+ function formatFolder(path) {
194
+ const max = 25;
195
+
196
+ if (path.length <= max)
197
+ return path;
198
+
199
+ return `...${path.slice(-(max - 3))}`;
200
+ }
201
+
202
+ export function formatLines(array) {
203
+ const max = 30;
204
+
205
+ if (array.length <= 10) {
206
+ const joined = array.join(', ');
207
+
208
+ if (joined.length <= max)
209
+ return joined;
210
+ }
211
+
212
+ const [first] = array;
213
+ const last = array.at(-1);
214
+
215
+ const base = `${first}..${last}`;
216
+
217
+ if (base.length <= max)
218
+ return base;
219
+
220
+ return String(last);
221
+ }
222
+
223
+ /* =========================
224
+ HELPERS
225
+ ========================= */
226
+ function parseUncoveredLines(lines) {
227
+ const out = [];
228
+
229
+ for (const [line, covered] of entries(lines)) {
230
+ if (!covered)
231
+ out.push(line);
232
+ }
233
+
234
+ return out;
235
+ }
236
+
237
+ export function getLinesPercent(linesCount, uncoveredLinesCount) {
238
+ if (!linesCount)
239
+ return 100;
240
+
241
+ return 100 - Math.round(100 / linesCount * uncoveredLinesCount);
242
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "escover",
3
- "version": "6.1.2",
3
+ "version": "6.2.0",
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",