npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gren-coverage-node

v1.0.0

Published

Code coverage (line, function, and when/if branch) for Gren node applications

Readme

gren-coverage-node

Code coverage for Gren applications that run on Node. It reports coverage at three levels of detail — per line, per function, and per when / if branch.

Instead of a single percentage, the goal is to answer a more useful question: "where are my tests missing?" — which functions and branches your code can reach but no test actually runs. Those are the tests you still need to write.

This tool works with any Gren node project. The examples below use the gren-format-lib test suite because that is what it was first built against, but nothing here is specific to it — point it at your own sources and your own app.

What it measures: four states

Every function and every when / if branch in your source is put into one of four states. Telling them apart is the whole point of the tool:

| state | meaning | |-------|---------| | hit | ran at least once | | never-called | present in the compiled JavaScript, but ran 0 times — these are the tests to write | | eliminated | dropped by dead-code elimination (never compiled into JavaScript at all) | | absent | the module never showed up in the source map |

A tool that only looked at the source map would quietly leave eliminated code out of the totals, hiding it. This tool also reads your parsed source, so eliminated code stays visible in the report.

How coverage works, in short

Gren compiles to JavaScript. When you build with gren make --sourcemaps, it also writes a source map that says which JavaScript came from which line of your Gren source. Node has coverage built in: set NODE_V8_COVERAGE=<dir> and it records how many times each piece of the JavaScript ran.

This tool combines the two: it takes the run counts from Node, follows the source map back to your Gren code, and compares that against every function and branch in your source (which it finds by reading your project). The result is the four-state report above.

Coverage pipeline

Install

npm install -g gren-coverage-node
gren-coverage-node --help

gren-coverage-node is the command used everywhere below. See DEPLOY.md if you'd rather build from source and install the tarball yourself (./build.sh → npm pack → npm install -g ./gren-coverage-node-*.tgz).

If you're hacking on this tool itself rather than just using it, you need devbox (it pins Node and the Gren compiler for you) — ./build.sh produces ./app, runnable directly as ./app <command> or node app <command>.

Using it in your project

There are four steps. You can run them by hand once to understand them, then wrap them in a script (see the full example at the end).

Assume your project has an entry-point module called Main (for a test suite, that is usually your test runner's Main), and that you stay in your project root for all four steps — even where Main itself lives in a subdirectory with its own gren.json, like a tests/ test harness. Working from the root keeps coverage.json's file paths clean and lets join index your actual sources rather than the test harness's own (usually trivial) gren.json.

1. Build your app with a source map

Compile the app you want to measure, asking for a source map. Do not name the output *.js — a .js output builds a module that defines your program but never starts it, so nothing runs. Use any other name (here, cov-app). If Main lives in a subdirectory, cd there just for the build:

( cd tests && gren make Main --sourcemaps --output=cov-app )

(No subdirectory? Drop the cd tests && and run gren make Main --sourcemaps --output=cov-app directly.)

For a test suite, Main is your test harness's entry point. The source map is embedded in cov-app, so there is nothing extra to keep track of.

2. Run it under Node's coverage

Run the app you just built with NODE_V8_COVERAGE pointing at an empty directory. Node writes raw coverage data there:

mkdir -p v8cov
( cd tests && NODE_V8_COVERAGE="$PWD/../v8cov" node cov-app )

This is a normal run of your app or test suite — let it do whatever it normally does. When it finishes, v8cov/ holds the run counts.

3. Join it into a coverage report

Combine the run counts and the source map with your project's sources. Still from the project root:

gren-coverage-node join \
  --app tests/cov-app \
  --cov v8cov \
  --src . \
  --out coverage.json

join reads --src's gren.json (here, the project root — pass . or drop --src entirely if join is already discovering the right one from the current directory), reads its source-directories, and indexes every function and branch itself — so you don't have to list your sources.

coverage.json is the source of truth — the four-state label for every region.

4. Render the report

Print a human-readable report to the terminal:

gren-coverage-node render text coverage.json

For example, running this against gren-format-lib's own test suite prints (abridged):

gren-coverage-node — cov-app

functions   844 hit     1 never    13 elim    55 absent    913 total    99.9% of reachable
branches   1304 hit   284 never    40 elim   131 absent   1759 total    82.1% of reachable

Untested code — reachable but no fixture exercises it:  (21 modules with gaps)

Formatter.Logical.InsertExpressions  src/Formatter/Logical/InsertExpressions.gren
  functions 107 hit 1 never 0 elim   branches 118 hit 33 never
  never  let innerOpNode                   :114  innerOpNode =
  never  when Nothing in insertAccess  :416
  never  if Array.isEmpty fieldNodes in insertUpdate  :500
         … and more (see --module Formatter.Logical.InsertExpressions)

  … 20 more modules with gaps (use --all or --top N)

  annotate one module fully:  gren-coverage-node render text coverage.json --module <Name>

The hit / never / elim / absent columns are the four states from the top of this README: never is code that ran zero times — the tests worth writing — while elim (dead-code-eliminated) stays counted instead of silently vanishing.

Or produce a standard LCOV file, which editors and genhtml understand:

gren-coverage-node render lcov coverage.json > coverage.lcov
genhtml coverage.lcov -o html --branch-coverage    # browsable HTML report

genhtml gives you an overview page with the overall rates and a per-directory table:

LCOV HTML overview

Click into a file to see it line by line — hit counts in the gutter, red for a line that never ran, and branch markers for each when / if:

LCOV HTML file view

A note on genhtml/lcov versions

Different lcov releases don't all validate the same way. Newer genhtml (2.1 and up) does a stricter internal consistency check than older versions did: every line mentioned in an FN record also has to show up in a DA record, or it fails outright with something like:

genhtml: ERROR: (category) unexpected category UNK for line <file>:<line>

Older lcov (2.0 and earlier) just let that slide.

gren-coverage-node handles this; render lcov fills in a DA row for every function's start line, even eliminated ones. We've checked the output against both lcov 2.0 and lcov 2.4 and both render cleanly.

Commands

gren-coverage-node <command>:

| command | what it does | |---------|--------------| | join --app <app> --cov <v8dir> [--src <dir>] [--out <file>] | index your sources, then combine them with the run counts + source map into coverage.json | | render text <coverage.json> [--top N] [--all] [--module <Name>] | terminal report; --module prints one module's full source annotated with run counts | | render lcov <coverage.json> | print a standard LCOV file (for genhtml or editor gutters) |

render text adds color on a terminal and respects NO_COLOR.

One detail worth knowing: regions are tracked by exact (row, column), never rounded to whole lines. So a declaration whose last line spills over onto the next line doesn't wrongly mark that next line as covered.

A complete example

run-coverage.sh in this repo wraps all the steps into one script, run against gren-format-lib's own test suite. It's a good template to copy for your own project. It assumes gren-coverage-node is already on your PATH (it checks and bails with a pointer to DEPLOY.md if not), and that the caller's working directory is already the root of the project being measured — so you run it as:

( cd /path/to/gren-format-lib && /path/to/gren-coverage-node/run-coverage.sh )

The core of it is:

# 1. build the test harness with a source map (output is NOT *.js)
( cd tests && gren make Main --sourcemaps --output=cov-app )

# 2. run the tests under Node coverage
rm -rf out/v8cov && mkdir -p out/v8cov
( cd tests && NODE_V8_COVERAGE="$PWD/../out/v8cov" node cov-app )

# 3. join — index the project (--src) and combine with the run counts
gren-coverage-node join --app "$PWD/tests/cov-app" --cov out/v8cov \
  --src "$PWD" --out out/coverage.json

# 4. render a terminal report, an lcov file, and (if genhtml is on PATH) HTML
gren-coverage-node render lcov out/coverage.json > out/coverage.lcov
command -v genhtml && genhtml out/coverage.lcov -o out/html --branch-coverage
gren-coverage-node render text out/coverage.json

That project also lets you trigger the whole thing from its own test runner — run-tests.sh --coverage simply execs run-coverage.sh. That is a convenience of that project's setup, not a requirement; your project can wire it in however you like.

Layout

build.sh                 builds the CLI into ./app (chmod +x'd)
run-coverage.sh          the full worked example (build → run → join → render → html)
package.json             npm packaging — exposes ./app as the `gren-coverage-node` bin
DEPLOY.md                how to build, test the packaged tarball, and publish
gren.json / devbox.json  the Gren app (platform: node)
src/
  Main.gren              command-line wiring + dispatch
  Coverage/Schema.gren   the coverage.json data format (decoders)
  Coverage/Index.gren    discovers + walks your parsed source (the denominator)
  Command/Join.gren      `join` — builds the index, then wraps gren-coverage.js
  Command/RenderText.gren `render text`
  Command/RenderLcov.gren `render lcov`
gren-coverage.js         the join engine (stays JavaScript — see below)

Why the join step is JavaScript

Part of the join step decodes the source map (base64 VLQ) and Node's coverage data, which reports positions as UTF-16 code-unit offsets into the generated JavaScript. Redoing that in Gren would mean re-matching Node's exact offset rules — easy to get wrong on any non-ASCII character, for no benefit to you. So that decode stays in gren-coverage.js. The Gren join command (Command/Join.gren) does the rest natively: it indexes your sources, then runs gren-coverage.js for the decode and passes its output along. Everything else is native Gren too.

join locates the script by looking in the same directory as the running program, so gren-coverage.js must sit next to the built app. The build puts both at the top level of this repo, so this works out of the box — but if you move or copy app elsewhere, bring gren-coverage.js along with it. This also holds for the npm package: app and gren-coverage.js are packaged and installed side by side, and the lookup still resolves correctly through npm's node_modules/.bin symlink (Node resolves the symlink to its real path before join looks for its sibling).