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

@jondotsoy/tmd

v0.0.9

Published

`tmd` scans fenced JS/TS code blocks in a Markdown file for annotation comments and rewrites them in place with the evaluated runtime value and/or the inferred TypeScript type — so the results in your documentation are always in sync with the code that pr

Downloads

2,732

Readme

trusted-markdown

tmd scans fenced JS/TS code blocks in a Markdown file for annotation comments and rewrites them in place with the evaluated runtime value and/or the inferred TypeScript type — so the results in your documentation are always in sync with the code that produces them.

Table of contents

Install

bunx @jondotsoy/tmd <path>

Or add it to a project:

bun add -d @jondotsoy/tmd

Usage

tmd <path> [<path> ...] [--cwd <dir>] [--setup-block-keyword <name> ...] [--render-block-keyword <name> ...] [--help]

Runs against one or more Markdown files, overwriting each in place with every annotation resolved:

tmd docs/one.md docs/two.md
  • --cwd <dir> — directory relative imports (e.g. from a setup-block preamble) resolve from. Defaults to each file's own directory, so this is only needed to point at a different one — when given, it applies to every file passed.
  • --setup-block-keyword <name> — recognizes an additional keyword as a hidden-preamble comment, alongside the built-in before-block and setup-block. Repeatable to register more than one. Handy when before-block/setup-block collide with another tool's comment convention in your docs.
  • --render-block-keyword <name> — recognizes an additional keyword as an eval-directive comment, alongside the built-in before-block-eval and render-block. Repeatable.
  • --help — prints usage and exits. Works anywhere in the arguments (tmd --help, tmd doc.md --help), even alongside other invalid flags — it always wins.

Annotation syntax

Inside a fenced ```js / ```ts block, annotate an expression with a // ? const <expr> comment on its own line right after the code it refers to — that reads better than trailing it on the same line, especially once the resolved value gets long:

| Form | Resolves to | | --- | --- | | // ? const <expr> = | the evaluated runtime value | | // ? const <expr>: | the inferred TypeScript type | | // ? const <expr> : = | both, as <expr>: <type> = <value> |

Value

```ts
const a = 1 + 2
// ? const a =
```

becomes

```ts
const a = 1 + 2
// ? const a = 3
```

Type

```ts
const a = 1 + 2
// ? const a:
```

becomes

```ts
const a = 1 + 2
// ? const a: number
```

Both at once

```ts
const a = 1 + 2
// ? const a : =
```

becomes

```ts
const a = 1 + 2
// ? const a: number = 3
```

Whitespace

Whitespace around : and = is always optional — a=, a =, a:, a :, a:=, a : =, a:= , etc. all work.

Trailing on the same line

The annotation doesn't have to be on its own line — it also works right after the code as a trailing comment, if that reads better for a short result:

```ts
const a = 1 + 2 // ? const a =
```

becomes

```ts
const a = 1 + 2 // ? const a = 3
```

Inside a nested scope

An annotation can sit inside a function body (or any other nested scope); it resolves against whatever invokes that code further down in the same block:

```ts
function sum(x: number, y: number) {
  const total = x + y
  // ? const total =
  return total;
}

sum(1, 2);
```

becomes

```ts
function sum(x: number, y: number) {
  const total = x + y
  // ? const total = 3
  return total;
}

sum(1, 2);
```

If the annotated line runs more than once (e.g. the function is called twice), tmd can't tell which result to pick, so it leaves that annotation unresolved instead of silently guessing — rewrite the block so the line only runs once. This doesn't stop the rest of the document from being processed: see Error handling below.

Annotating a function itself

The annotated expression doesn't have to be a value produced inside a function — it can be the function itself, inspected after its declaration:

```ts
function foo() {
  return 1 + 2;
}

// ? const foo =
```

becomes

```ts
function foo() {
  return 1 + 2;
}

// ? const foo = [Function: foo]
```

Asking for the type instead (// ? const foo:) resolves to its signature, () => number.

Multi-line values

When the evaluated value doesn't fit on one line, the first line stays on the annotation and the rest are rendered as trailing // comment lines so the block stays valid code:

```ts
const obj = { alpha: 1, beta: 2 }
// ? const obj =
```

becomes

```ts
const obj = { alpha: 1, beta: 2 }
// ? const obj = {
//   alpha: 1,
//   beta: 2,
// }
```

Hiding setup code

A <!-- setup-block ... --> comment (single or multi-line) directly above a block makes its code available to that block's annotations without showing it in the rendered block itself — handy for imports:

<!-- setup-block
import path from "node:path";
-->

```ts
const sep = path.sep
// ? const sep =
```

becomes

<!-- setup-block
import path from "node:path";
-->

```ts
const sep = path.sep
// ? const sep = "/"
```

before-block is an older, equivalent alias for setup-block — both spellings behave identically and are supported side by side, so existing docs keep working unchanged.

A plain <!-- ... --> comment without the setup-block/before-block prefix is left untouched and has no effect on the block below it.

Need a different word entirely (e.g. init to match another tool's convention)? Register it with --setup-block-keyword, which adds to the built-in keywords rather than replacing them:

tmd docs/doc.md --setup-block-keyword init
<!-- init
import path from "node:path";
-->

Customizing how a value is rendered

By default a resolved value is rendered with Bun.inspect. Pass a custom-inspect="<fn name>" attribute right after setup-block, on the same line, to render it with a function of that name instead (defined in the preamble itself, or otherwise in scope) — handy for a value that needs a different presentation than Bun.inspect's default:

<!-- setup-block custom-inspect="foo"
const foo = (value: unknown) => [String(value)];
-->

```ts
const biz = 3
// ? const biz =
```

becomes

<!-- setup-block custom-inspect="foo"
const foo = (value: unknown) => [String(value)];
-->

```ts
const biz = 3
// ? const biz = [ "3" ]
```

Importing from another file

A setup-block preamble can also import from a file next to the Markdown document, not just built-in/installed modules. Relative imports resolve against the directory the Markdown file lives in, regardless of where tmd is run from.

script.ts:

export function foo() {
  return 1 + 2;
}

export class Foo {
  foo = 1;
}

doc.md:

<!-- setup-block
import { foo, Foo } from "./script.ts";
-->

```ts
const result = [foo, new Foo()]
// ? const result =
```

becomes

<!-- setup-block
import { foo, Foo } from "./script.ts";
-->

```ts
const result = [foo, new Foo()]
// ? const result = [
//   [Function: foo], Foo {
//     foo: 1,
//   }
// ]
```

If the doc and its imports don't live next to each other, point --cwd at the directory the imports should resolve from instead:

tmd docs/doc.md --cwd scripts/

Filling a block from computed code

A <!-- render-block ... --> comment directly above a ```json or ```js block runs its code (which must assign a result value) and replaces the whole block's content with that result — handy for keeping a sample payload or config in sync with the code that builds it, without hand-writing any annotations.

A json block is filled with JSON.stringify(result, null, 2):

<!-- render-block
const result = { a: 1, b: "two", c: [1, 2, 3] };
-->

```json
```

becomes

<!-- render-block
const result = { a: 1, b: "two", c: [1, 2, 3] };
-->

```json
{
  "a": 1,
  "b": "two",
  "c": [
    1,
    2,
    3
  ]
}
```

A yaml/yml block is filled with the yaml package's stringify(result), straightening (properly indenting) nested objects and arrays of any depth into a block-style document:

<!-- render-block
const result = {
  a: 1,
  b: "two",
  nested: { c: [1, 2, 3], deeper: { d: 4 } },
};
-->

```yaml
```

becomes

<!-- render-block
const result = {
  a: 1,
  b: "two",
  nested: { c: [1, 2, 3], deeper: { d: 4 } },
};
-->

```yaml
a: 1
b: two
nested:
  c:
    - 1
    - 2
    - 3
  deeper:
    d: 4
```

A js/javascript block is filled with util.inspect(result, { depth: null }) instead, so it can hold values JSON.stringify can't represent (functions, undefined, etc.). The unlimited depth keeps a multi-level nested result fully expanded rather than collapsing anything past util.inspect's default depth of 2 into [Object]:

<!-- render-block
const result = { a: 1, b: "two", c: [1, 2, 3] };
-->

```js
```

becomes

<!-- render-block
const result = { a: 1, b: "two", c: [1, 2, 3] };
-->

```js
{
  a: 1,
  b: 'two',
  c: [
    1,
    2,
    3
  ]
}
```

Declare a JS_INSPECT_DEEP variable in the directive's code to override that default depth, e.g. to intentionally re-introduce truncation on a very large structure:

<!-- render-block
const result = { a: { b: { c: { d: 1 } } } };
const JS_INSPECT_DEEP = 1;
-->

```js
```

becomes

<!-- render-block
const result = { a: { b: { c: { d: 1 } } } };
const JS_INSPECT_DEEP = 1;
-->

```js
{
  a: {
    b: [Object]
  }
}
```

An inspect-depth="<n>" attribute right after render-block, on the same line, sets the same depth directly instead — no code change needed, and it takes precedence over JS_INSPECT_DEEP when both are present:

<!-- render-block inspect-depth="1"
const result = { a: { b: { c: { d: 1 } } } };
-->

```js
```

becomes

<!-- render-block inspect-depth="1"
const result = { a: { b: { c: { d: 1 } } } };
-->

```js
{
  a: {
    b: [Object]
  }
}
```

The same code-variable/attribute pair is available for three other util.inspect options, each named after the option itself (JS_INSPECT_MAX_ARRAY_LENGTH/inspect-max-array-length, JS_INSPECT_MAX_STRING_LENGTH/inspect-max-string-length, JS_INSPECT_COMPACT/inspect-compact). maxArrayLength and maxStringLength default to null (unlimited) for the same reason as depth; compact defaults to false (one entry per line) rather than util.inspect's own default (3, which groups short entries onto a single line) — easier to read and diff once nothing is being silently truncated:

<!-- render-block inspect-max-array-length="2"
const result = [0, 1, 2, 3, 4];
-->

```js
```

becomes

<!-- render-block inspect-max-array-length="2"
const result = [0, 1, 2, 3, 4];
-->

```js
[
  0,
  1,
  ... 3 more items
]
```

compact defaulting to false means a small object like { a: 1, b: 2 } is filled one entry per line rather than grouped onto a single line. inspect-compact accepts the same values util.inspect's compact option does — true, false, or an integer — to opt back into grouping:

<!-- render-block inspect-compact="true"
const result = { a: 1, b: 2 };
-->

```js
```

becomes

<!-- render-block inspect-compact="true"
const result = { a: 1, b: 2 };
-->

```js
{ a: 1, b: 2 }
```

Any other fence language (md, txt, sh, and so on) is filled with the plain String(result):

<!-- render-block
const result = "hello";
-->

```txt
```

becomes

<!-- render-block
const result = "hello";
-->

```txt
hello
```

before-block-eval is an older, equivalent alias for render-block — both spellings behave identically and are supported side by side, so existing docs keep working unchanged.

Like setup-block, an extra keyword can be registered with --render-block-keyword on top of the built-in ones:

tmd docs/doc.md --render-block-keyword compute

Re-running tmd overwrites whatever was previously rendered in the block with the current result, the same way annotations are re-resolved on every run. Relative imports in the directive's code resolve the same way as in a setup-block preamble (against the Markdown file's directory, or --cwd).

Naming the result variable

By default render-block reads its result from a variable named result. Pass a result-variable="<name>" attribute right after render-block, on the same line, to use a different variable name instead:

<!-- render-block result-variable="foo"
const foo = 3;
-->

```json
```

becomes

<!-- render-block result-variable="foo"
const foo = 3;
-->

```json
3
```

Customizing how the result is rendered

Like setup-block, a custom-inspect="<fn name>" attribute right after render-block names a function (defined in the directive's code, or otherwise in scope) used to render the result instead of the default JSON.stringify/Bun.inspect — this overrides the block's language-based default (json vs. js/javascript) entirely:

<!-- render-block custom-inspect="foo"
const result = { a: 1 };
const foo = (value) => [String(value)];
-->

```json
```

becomes

<!-- render-block custom-inspect="foo"
const result = { a: 1 };
const foo = (value) => [String(value)];
-->

```json
[ "[object Object]" ]
```

Supported blocks

render-block fills three fence languages with a dedicated renderer (see Filling a block from computed code for the full details); any other language falls back to String(result).

| Fence | Renderer | Notes | | --- | --- | --- | | ```json | JSON.stringify(result, null, 2) | | | ```js / ```javascript | util.inspect(result, { depth: null }) | Can render values JSON.stringify can't (functions, undefined, etc.); depth and other util.inspect options are configurable. | | ```yaml / ```yml | yaml's stringify(result) | Straightens nested objects/arrays into a block-style document. |

Error handling

A single block failing to resolve — an annotated expression that throws (e.g. references an undefined variable), or a render-block directive that does — never aborts the whole run. tmd keeps processing every other block, leaves the failing block's annotation or content exactly as it was in the input, and prints a summary of every block it attempted once it's done:

docs/doc.md: 3 block(s) processed, 1 failed
  ok   lines 2-4
  FAIL lines 7-9: ReferenceError: b is not defined
  ok   lines 12-14

The process still exits with code 0 and the file is written with every block that did resolve — re-run tmd after fixing the failing block to pick it up.

Development

bun install
bun test