Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ Enjoying **Poku**? [Give him a star to show your support](https://github.com/wel

☔️ [**@pokujs/c8**](https://github.com/pokujs/c8) is a **Poku** plugin for **V8** code coverage using [**c8**](https://github.com/bcoe/c8).

> [!TIP]
>
> **@pokujs/c8** supports **JSONC** config files (`.c8rc`, `.nycrc`, etc.) out of the box, allowing comments in your configuration. You can also use **JS** and **TS** by setting the options directly in the plugin.

---

## Quickstart
Expand Down Expand Up @@ -53,10 +57,13 @@ Run `poku` and a coverage summary will be printed after your test results.

```js
coverage({
// Config file (.c8rc, .c8rc.json, .c8rc.jsonc, .nycrc, .nycrc.json, .nycrc.jsonc)
config: '.c8rc', // default: auto-discover

// Activation
requireFlag: true, // default: false

// Reporters
// Reporters (clover, cobertura, html, html-spa, json, json-summary, lcov, lcovonly, none, teamcity, text, text-lcov, text-summary)
reporter: ['text', 'lcov'], // default: ['text']

// File selection
Expand Down Expand Up @@ -165,6 +172,42 @@ poku test/
poku --coverage test/
```

### Using a config file

Reuse your existing `.c8rc`, `.nycrc`, or any JSON/JSONC config file with comments:

```jsonc
// .c8rc
{
// Only cover source files
"include": ["src/**"],
"reporter": ["text", "lcov"],
"check-coverage": true,
"lines": 90,
}
```

```js
coverage({
config: '.c8rc', // or false to disable auto-discovery
});
```

When no `config` is specified, the plugin automatically searches for `.c8rc`, `.c8rc.json`, `.c8rc.jsonc`, `.nycrc`, `.nycrc.json`, or `.nycrc.jsonc` in the working directory.

You can also specify the config path via CLI:

```bash
poku --coverage-config=.c8rc test/
```

> [!NOTE]
>
> **Priority order:**
>
> - For config file discovery: `--coverage-config` (CLI) > `config` (plugin option) > auto-discovery
> - For coverage options: plugin options > config file options

### Extending Monocart reporters

```bash
Expand Down
20 changes: 18 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"node": ">=18.x.x"
},
"scripts": {
"pretest": "npm run build",
"test": "poku test/e2e",
"prebuild": "rm -rf lib",
"build": "tsc",
Expand All @@ -34,7 +35,8 @@
"postupdate": "npm run lint:fix"
},
"dependencies": {
"c8": "^10.1.3"
"c8": "^10.1.3",
"jsonc.min": "^1.1.2"
},
"peerDependencies": {
"monocart-coverage-reports": "^2.12.9",
Expand Down
61 changes: 61 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import type { CoverageOptions } from './types.js';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import { JSONC } from 'jsonc.min';

const kebabMap: Record<string, string> = {
'reports-dir': 'reportsDirectory',
'report-dir': 'reportsDirectory',
'temp-directory': 'tempDirectory',
'check-coverage': 'checkCoverage',
'per-file': 'perFile',
'skip-full': 'skipFull',
'exclude-after-remap': 'excludeAfterRemap',
'merge-async': 'mergeAsync',
};

const mapKeys = (raw: Record<string, unknown>): Partial<CoverageOptions> => {
const result: Record<string, unknown> = {};

for (const [key, value] of Object.entries(raw)) {
if (key === 'experimental-monocart') {
if (value) result.experimental = ['monocart'];
continue;
}
result[kebabMap[key] ?? key] = value;
}

return result as Partial<CoverageOptions>;
};

export const loadConfig = (
cwd: string,
customPath?: string | false
): CoverageOptions => {
if (customPath === false) return Object.create(null);

const expectedFiles = customPath
? [customPath]
: [
'.c8rc',
'.c8rc.json',
'.c8rc.jsonc',
'.nycrc',
'.nycrc.json',
'.nycrc.jsonc',
];

for (const file of expectedFiles) {
const filePath = join(cwd, file);

if (!existsSync(filePath)) continue;

try {
const content = readFileSync(filePath, 'utf8');

return mapKeys(JSONC.parse(content) as Record<string, unknown>);
} catch {}
}

return Object.create(null);
};
8 changes: 8 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { mkdirSync, mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import process from 'node:process';
import { loadConfig } from './config.js';

export type { CoverageOptions } from './types.js';

Expand All @@ -22,6 +23,13 @@ export const coverage = (
if (options.requireFlag && !process.argv.includes('--coverage')) return;
enabled = true;

const cliConfig = process.argv
.find((arg) => arg.startsWith('--coverage-config'))
?.split('=')[1];

const fileConfig = loadConfig(context.cwd, cliConfig ?? options.config);
options = { ...fileConfig, ...options };

if (context.runtime !== 'node')
console.warn(
`[@pokujs/c8] V8 coverage is only supported on Node.js (current runtime: ${context.runtime}). Coverage data may not be collected.`
Expand Down
10 changes: 10 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ type KnownExtension =
type Extension = KnownExtension | (string & NonNullable<unknown>);

export type CoverageOptions = {
/**
* Path to a JSONC/JSON configuration file.
*
* - `string` — load that specific file
* - `false` — disable config file discovery
* - `undefined` (default) — auto-discover `.c8rc`, `.c8rc.json`, `.nycrc`, `.nycrc.json`,
* or a `c8` key in `package.json`, walking up from `cwd`.
*/
config?: string | false;

/**
* Require the `--coverage` CLI flag to activate coverage collection.
*
Expand Down
5 changes: 5 additions & 0 deletions test/__fixtures__/e2e/configs/.c8rc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
// JSONC: comments should be supported
"include": ["src/**"],
"reporter": ["text"]
}
12 changes: 12 additions & 0 deletions test/__fixtures__/e2e/configs/basic.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const { coverage } = require('../../../../lib/index.js');

/** @type {import('poku').PokuConfig} */
module.exports = {
include: ['test/'],
plugins: [
coverage({
include: ['src/**'],
reporter: ['text'],
}),
],
};
12 changes: 0 additions & 12 deletions test/__fixtures__/e2e/configs/basic.config.ts

This file was deleted.

11 changes: 11 additions & 0 deletions test/__fixtures__/e2e/configs/config-file.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const { coverage } = require('../../../../lib/index.js');

/** @type {import('poku').PokuConfig} */
module.exports = {
include: ['test/'],
plugins: [
coverage({
config: 'configs/.c8rc',
}),
],
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineConfig } from 'poku';
import { coverage } from '../../../../src/index.ts';
const { coverage } = require('../../../../lib/index.js');

export default defineConfig({
/** @type {import('poku').PokuConfig} */
module.exports = {
include: ['test/'],
plugins: [
coverage({
Expand All @@ -10,4 +10,4 @@ export default defineConfig({
requireFlag: true,
}),
],
});
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineConfig } from 'poku';
import { coverage } from '../../../../src/index.ts';
const { coverage } = require('../../../../lib/index.js');

export default defineConfig({
/** @type {import('poku').PokuConfig} */
module.exports = {
include: ['test/'],
plugins: [
coverage({
Expand All @@ -10,4 +10,4 @@ export default defineConfig({
checkCoverage: 100,
}),
],
});
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineConfig } from 'poku';
import { coverage } from '../../../../src/index.ts';
const { coverage } = require('../../../../lib/index.js');

export default defineConfig({
/** @type {import('poku').PokuConfig} */
module.exports = {
include: ['test/'],
plugins: [
coverage({
Expand All @@ -11,4 +11,4 @@ export default defineConfig({
lines: 30,
}),
],
});
};
2 changes: 1 addition & 1 deletion test/e2e/coverage-basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const pokuBin = 'node_modules/poku/lib/bin/index.js';

test('basic coverage report is generated', async () => {
const result = await inspectPoku({
command: '-c=configs/basic.config.ts',
command: '-c=configs/basic.config.js',
spawnOptions: { cwd: fixtureDir },
bin: pokuBin,
});
Expand Down
17 changes: 17 additions & 0 deletions test/e2e/coverage-config-file.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { assert, test } from 'poku';
import { inspectPoku } from 'poku/plugins';

const fixtureDir = 'test/__fixtures__/e2e';
const pokuBin = 'node_modules/poku/lib/bin/index.js';

test('loads JSONC config file with comments', async () => {
const result = await inspectPoku({
command: '-c=configs/config-file.config.js',
spawnOptions: { cwd: fixtureDir },
bin: pokuBin,
});

assert.strictEqual(result.exitCode, 0);
assert(result.stdout.includes('math.ts'));
assert(result.stdout.includes('%'));
});
4 changes: 2 additions & 2 deletions test/e2e/coverage-require-flag.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const pokuBin = 'node_modules/poku/lib/bin/index.js';

test('coverage is skipped without --coverage flag when requireFlag is true', async () => {
const result = await inspectPoku({
command: '-c=configs/require-flag.config.ts',
command: '-c=configs/require-flag.config.js',
spawnOptions: { cwd: fixtureDir },
bin: pokuBin,
});
Expand All @@ -20,7 +20,7 @@ test('coverage is skipped without --coverage flag when requireFlag is true', asy

test('coverage runs with --coverage flag when requireFlag is true', async () => {
const result = await inspectPoku({
command: '--coverage -c=configs/require-flag.config.ts',
command: '--coverage -c=configs/require-flag.config.js',
spawnOptions: { cwd: fixtureDir },
bin: pokuBin,
});
Expand Down
4 changes: 2 additions & 2 deletions test/e2e/coverage-thresholds.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const pokuBin = 'node_modules/poku/lib/bin/index.js';

test('threshold check passes with low threshold', async () => {
const result = await inspectPoku({
command: '-c=configs/thresholds-pass.config.ts',
command: '-c=configs/thresholds-pass.config.js',
spawnOptions: { cwd: fixtureDir },
bin: pokuBin,
});
Expand All @@ -16,7 +16,7 @@ test('threshold check passes with low threshold', async () => {

test('threshold check fails with 100% threshold on partial coverage', async () => {
const result = await inspectPoku({
command: '-c=configs/thresholds-fail.config.ts',
command: '-c=configs/thresholds-fail.config.js',
spawnOptions: { cwd: fixtureDir },
bin: pokuBin,
});
Expand Down
Loading