texample
v1.0.2
Published
Execute your README markdown javascript examples
Maintainers
Readme
texample
Execute your README markdown javascript ESM examples to make sure they run as expected.
Introduction
If you have been a decent chap you probably have decorated your module with lots of code examples in accordance with the "Show me the code"-motto. To be sure that your examples run as expected you can run through them with this module. Wrap your examples with:
```javascript
and run through your README.md with:
texample ./README.mdAny irregularities will be printed to stdout, as well as console.logs. E.g:
0: file:///usr/local/src/projects/zerodep/piso/README.md:31
{ '2007-03-01/2007-04-01': 2007-03-31T22:00:00.000Z }
1: file:///usr/local/src/projects/zerodep/piso/README.md:59
{ PT1M5S: 2025-04-29T04:31:34.219Z }
{
err: RangeError: ISO 8601 duration fractions are allowed on the smallest unit in the string, e.g. P0.5D or PT1.001S but not P0.5DT1H
at ISODuration.writeDuration [as write] (file:///usr/local/src/projects/zerodep/piso/src/index.js:969:11)
at ISODuration.parseDuration [as parse] (file:///usr/local/src/projects/zerodep/piso/src/index.js:900:10)
at ISOInterval.consumeDuration (file:///usr/local/src/projects/zerodep/piso/src/index.js:274:76)
at ISOInterval.parseInterval [as parse] (file:///usr/local/src/projects/zerodep/piso/src/index.js:109:10)
at parseDuration (file:///usr/local/src/projects/zerodep/piso/src/index.js:1299:39)
at file:///usr/local/src/projects/zerodep/piso/README.md:89:3
at SourceTextModule.evaluate (node:internal/vm/module:229:23)
at ExampleEvaluator.evaluate (/usr/local/src/projects/zerodep/piso/node_modules/texample/dist/index.cjs:125:20)
at async run (/usr/local/src/projects/zerodep/piso/node_modules/texample/cli.cjs:34:5)
}
2: file:///usr/local/src/projects/zerodep/piso/README.md:103
SyntaxError: The requested module '@0dep/piso' does not provide an export named 'getDates'
at SourceTextModule.link (node:internal/vm/module:203:17)
at async ExampleEvaluator.evaluate (/usr/local/src/projects/zerodep/piso/node_modules/texample/dist/index.cjs:124:7)
at async run (/usr/local/src/projects/zerodep/piso/node_modules/texample/cli.cjs:34:5)Examples are numbered from 0.
To ignore an example snippet use ```js.
CLI
Arguments
- List of markdown files separated by comma (,)
- Optional markdown block index number, from 0
-r <path>ESM file run inside the vm context before the example (repeatable)-c <path>JSON config file withrequireandnode-option-iexit 0 even when an example throws (errors still print to stderr)-?print usage and exit
texample ./README.md,./docs/API.mdThe vm context is globalThis, so examples have access to fetch, EventTarget, performance, and the rest of Node's runtime by default. Each invocation runs in its own forked child process, so anything an example writes to globalThis is thrown away when the run ends.
Vm context setup
Pass one or more -r <path> flags to evaluate ESM files inside the example's vm context before the example runs. Repeating the flag chains multiple setup files in order. Each file is loaded as a SourceTextModule sharing the example's context, so it can mutate the sandbox (e.g. replace globalThis.fetch, freeze Date) and the example sees those mutations. The example's lineOffset stays relative to the markdown source.
texample ./example.md -r ./deny-fetch.mjs -r ./mock-time.mjsConfig file
-c <path> covers a different need than -r: it is for process-level setup that has to be in place before texample itself can run — Node CLI flags or modules that must be imported at process startup. There is no auto-discovered config file; pass one explicitly. The config is plain JSON:
{
"require": ["./deny-fetch-setup.mjs"],
"node-option": ["experimental-vm-modules", "no-warnings"]
}require— list of files forwarded as-rsetup files (resolved relative to the config file's directory; bare specifiers likechai/register-expect.jsare resolved vianode_modules). They run inside the example's vm context.node-option— entries are appended to the always-on defaults (--experimental-vm-modules --no-warnings) and forwarded as--<option>in the child'sexecArgv. You never need to redeclare the defaults — supplyingnode-optionis purely additive.
texample ./example.md -c ./texample-config.jsonCustomization example
You can write your own evaluator and pass a different vm context. Create a script that does the following:
import vm from 'node:vm';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
import { resolve as resolvePath } from 'node:path';
import { ExampleEvaluator } from 'texample';
if (!('SourceTextModule' in vm)) throw new Error('No SourceTextModule in vm, try using node --experimental-vm-modules flag');
const CWD = process.cwd();
const nodeRequire = createRequire(fileURLToPath(import.meta.url));
const packageDefinition = nodeRequire(resolvePath(CWD, 'package.json'));
const markdownFiles = process.argv[2] || './README.md';
const blockIdx = Number(process.argv[3]);
(async () => {
for (const filePath of markdownFiles.split(',')) {
await new ExampleEvaluator(filePath, packageDefinition, CWD, {
Buffer,
console,
setTimeout,
clearTimeout,
}).evaluate(blockIdx);
}
})();