Summary
When configuring @pokujs/c8 with all: true and src: ['src'], the final coverage report only contains files that were imported by tests. Files inside src/ that were never required at runtime are silently omitted, producing a misleading high-coverage number.
Running the standalone c8 CLI on the very same NODE_V8_COVERAGE directory does include the uncovered files (and reports them as 0%), so the V8 coverage data is fine — the issue seems to be in how the plugin propagates all / src to c8's Report (or in how globs are resolved).
Environment
|
|
| Node |
v24.14.0 |
poku |
4.3.0 |
@pokujs/c8 |
1.2.0 |
c8 |
10.1.3 |
| OS |
macOS Darwin 25.5.0 (arm64) |
Minimal reproduction
repro/
├── package.json
├── poku.config.mjs
└── src/
├── covered.ts # imported by test
├── never-imported.ts # NOT imported anywhere
└── __tests__/covered.test.ts
package.json:
{
"type": "module",
"scripts": { "test": "poku --coverage" },
"devDependencies": {
"poku": "4.3.0",
"@pokujs/c8": "1.2.0",
"c8": "10.1.3",
"tsx": "^4.21.0"
}
}
poku.config.mjs:
import { defineConfig } from 'poku';
import { coverage } from '@pokujs/c8';
export default defineConfig({
parallel: true,
concurrency: 4,
include: ['src/**/__tests__'],
plugins: [
coverage({
requireFlag: true,
all: true,
src: ['src'],
include: ['src/**/*.ts', 'src/**/*.tsx'],
exclude: ['src/**/__tests__/**', 'src/**/*.d.ts'],
extension: ['.ts', '.tsx'],
reporter: ['text'],
}),
],
});
src/covered.ts:
export const sum = (a: number, b: number) => a + b;
src/never-imported.ts:
export const noop = () => null;
export const greet = (name: string) => `hi ${name}`;
src/__tests__/covered.test.ts:
import { test, assert } from 'poku';
import { sum } from '../covered';
await test('sum', () => assert.strictEqual(sum(2, 3), 5));
Steps
Actual output (incorrect)
File | % Stmts | % Branch | % Funcs | % Lines
covered.ts | 100 | 100 | 100 | 100
never-imported.ts is missing from the report despite all: true and src: ['src'].
Expected output
never-imported.ts should appear with 0% coverage (this is the documented behavior of c8 --all).
Workaround proving the V8 data is correct
Capture NODE_V8_COVERAGE to a fixed dir and run the c8 CLI on the same data:
rm -rf /tmp/cov && mkdir /tmp/cov
NODE_V8_COVERAGE=/tmp/cov npx poku
npx c8 report \
--reporter=text-summary \
--include='src/**/*.ts' --include='src/**/*.tsx' \
--exclude='src/**/__tests__/**' \
--extension=.ts --extension=.tsx \
--all --src=src \
--temp-directory=/tmp/cov
Output (correct, includes uncovered files):
=============================== Coverage summary ===============================
Statements : 33.33% (3/9)
Branches : 100% (0/0)
Functions : 33.33% (1/3)
Lines : 33.33% (3/9)
================================================================================
Possible cause
Looking at node_modules/@pokujs/c8/lib/index.js, the constructor call to c8's Report propagates all and src:
const report = Report({
tempDirectory: tempDir,
reportsDirectory,
reporter,
include: options.include ?? [],
exclude: options.exclude,
extension: options.extension,
all: options.all ?? false,
src: options.src,
resolve: '',
// ...
});
So all: true should reach Report. Inside c8, _includeUncoveredFiles (node_modules/c8/lib/report.js:400) does this.exclude.globSync(workingDir).forEach(...) and filters by extension.includes(ext). The fact that the standalone CLI works on identical data points to one of:
- The
include glob format passed by the plugin (['src/**/*.ts', 'src/**/*.tsx']) is interpreted differently than the equivalent --include flags by test-exclude once it goes through Report constructed programmatically.
- The
extension array is being passed in a form the plugin path doesn't honor (e.g., the CLI normalizes --extension=.ts differently than passing ['.ts'] programmatically).
- The
tempDirectory ends up split across multiple per-process dirs and only the main process's dir is read by the plugin's Report, while c8 report --temp-directory=/tmp/cov reads everything.
I'd start by reproducing with DEBUG=c8 npx poku --coverage and logging tempDir, options.all, options.src, and the result of the internal Exclude.globSync(workingDir) call.
Impact
Coverage thresholds via the plugin are unreliable: a project with 5% real coverage shows 98%+ because only loaded modules count. CI gates can't depend on the plugin output today; users have to run c8 report --all ... as a post-step (which defeats the purpose of having a plugin).
Summary
When configuring
@pokujs/c8withall: trueandsrc: ['src'], the final coverage report only contains files that were imported by tests. Files insidesrc/that were never required at runtime are silently omitted, producing a misleading high-coverage number.Running the standalone
c8CLI on the very sameNODE_V8_COVERAGEdirectory does include the uncovered files (and reports them as0%), so the V8 coverage data is fine — the issue seems to be in how the plugin propagatesall/srctoc8'sReport(or in how globs are resolved).Environment
v24.14.0poku4.3.0@pokujs/c81.2.0c810.1.3Minimal reproduction
package.json:{ "type": "module", "scripts": { "test": "poku --coverage" }, "devDependencies": { "poku": "4.3.0", "@pokujs/c8": "1.2.0", "c8": "10.1.3", "tsx": "^4.21.0" } }poku.config.mjs:src/covered.ts:src/never-imported.ts:src/__tests__/covered.test.ts:Steps
Actual output (incorrect)
never-imported.tsis missing from the report despiteall: trueandsrc: ['src'].Expected output
never-imported.tsshould appear with0%coverage (this is the documented behavior ofc8 --all).Workaround proving the V8 data is correct
Capture
NODE_V8_COVERAGEto a fixed dir and run thec8CLI on the same data:Output (correct, includes uncovered files):
Possible cause
Looking at
node_modules/@pokujs/c8/lib/index.js, the constructor call toc8'sReportpropagatesallandsrc:So
all: trueshould reachReport. Insidec8,_includeUncoveredFiles(node_modules/c8/lib/report.js:400) doesthis.exclude.globSync(workingDir).forEach(...)and filters byextension.includes(ext). The fact that the standalone CLI works on identical data points to one of:includeglob format passed by the plugin (['src/**/*.ts', 'src/**/*.tsx']) is interpreted differently than the equivalent--includeflags bytest-excludeonce it goes throughReportconstructed programmatically.extensionarray is being passed in a form the plugin path doesn't honor (e.g., the CLI normalizes--extension=.tsdifferently than passing['.ts']programmatically).tempDirectoryends up split across multiple per-process dirs and only the main process's dir is read by the plugin'sReport, whilec8 report --temp-directory=/tmp/covreads everything.I'd start by reproducing with
DEBUG=c8 npx poku --coverageand loggingtempDir,options.all,options.src, and the result of the internalExclude.globSync(workingDir)call.Impact
Coverage thresholds via the plugin are unreliable: a project with 5% real coverage shows 98%+ because only loaded modules count. CI gates can't depend on the plugin output today; users have to run
c8 report --all ...as a post-step (which defeats the purpose of having a plugin).