Skip to content
Open
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
55 changes: 55 additions & 0 deletions .github/workflows/codspeed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: CodSpeed

on:
push:
branches: [main]
pull_request:
branches: [main]
# Lets CodSpeed replay a run when it needs a baseline.
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
benchmarks:
name: Benchmarks
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
env:
NODE_SHA: 768315cf7165e9ff9b636844b776488e9d1703fc
Comment thread
avivkeller marked this conversation as resolved.
Resolved
steps:
- name: Harden Runner
uses: step-security/harden-runner@dc2769abcc0fcfbadfc1df54081c1608579ef6b2 # v2.22.2
with:
egress-policy: audit

- name: Setup environment
uses: nodejs/web-team/actions/setup-environment@4c0b18db11a8357664683d70ef1f27af08ecc766
with:
use-version-file: true
pnpm: true

- name: Checkout Node.js sources
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
repository: nodejs/node
ref: ${{ env.NODE_SHA }}
sparse-checkout: |
doc
lib
.
path: node

- name: Run benchmarks
uses: CodSpeedHQ/action@af75f77f17a054769abefaa42fe8dc2975469b5f # v5.6.3
Comment thread
avivkeller marked this conversation as resolved.
Resolved
with:
mode: simulation,memory

@ovflowd ovflowd Nov 9, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memory here is allocations on a warm build, not the peak RSS anyone actually hits. Not sure what we want those numbers to tell us

run: node --run bench
5 changes: 1 addition & 4 deletions .github/workflows/generate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,7 @@ jobs:

- name: Generate ${{ matrix.target }}
run: |
/usr/bin/time \
--output out/benchmark.json \
--format '{"elapsedSeconds": %e, "userCpuSeconds": %U, "systemCpuSeconds": %S, "maxRssKiB": %M}' \
node packages/cli/bin/cli.mjs generate \
node packages/cli/bin/cli.mjs generate \
Comment thread
avivkeller marked this conversation as resolved.
Resolved
-t ${{ matrix.target }} \
-i "${{ matrix.input }}" \
-o out \
Expand Down
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Thank you for your interest in contributing to the `doc-kit` project! We welcome
- [Best Practices](#best-practices)
- [Example Test File](#example-test-file)
- [Running Tests](#running-tests)
- [Benchmarks](#benchmarks)
- [Code Quality](#code-quality)
- [Linting and Formatting](#linting-and-formatting)
- [Pre-commit Hooks](#pre-commit-hooks)
Expand Down Expand Up @@ -334,6 +335,17 @@ node --run test:coverage
node --test packages/core/src/utils/__tests__/parser.test.mjs
```

## Benchmarks

Generation speed and memory are measured with [CodSpeed](https://codspeed.io)
(`bench/generate.bench.mjs`), on one thread. The workflow checks `nodejs/node`

@ovflowd ovflowd Nov 9, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: say this is single threaded, so a flat result doesn't read as no change

out at `NODE_SHA` into `./node`, and the suite reads that directory. It runs on
every pull request and reports CPU and memory there.

```bash
node --run bench
```

## Code Quality

### Linting and Formatting
Expand Down
66 changes: 66 additions & 0 deletions bench/generate.bench.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { mkdir, rm } from 'node:fs/promises';
import path from 'node:path';

import { withCodSpeed } from '@codspeed/tinybench-plugin';
import createGenerator from '@doc-kit/core';
import logger from '@doc-kit/core/logger/index.mjs';
import { setConfig } from '@doc-kit/core/utils/configuration/index.mjs';
import { Bench } from 'tinybench';

logger.setLogLevel('warn');

const node = path.join(import.meta.dirname, '../node');
const at = (...parts) => path.join(node, ...parts);

const shared = {
threads: 1,
Comment thread
avivkeller marked this conversation as resolved.
Resolved
index: at('doc/api/index.md'),
typeMap: at('doc/type-map.json'),
changelog: at('CHANGELOG.md'),
};

const docs = at('doc/api/*.md');

const targets = [
Comment thread
avivkeller marked this conversation as resolved.
Resolved
['json', docs],
['json-simple', docs],
['legacy-json', docs],
['legacy-html', docs],
['html', docs],
['llms-txt', docs],
['orama-db', docs],
['man-page', at('doc/api/cli.md')],
['addon-verify', at('doc/api/addons.md')],
['api-links', at('lib/*.js')],
];

const bench = withCodSpeed(
new Bench({ warmup: false, iterations: 1, time: 0, throws: true })
Comment thread
avivkeller marked this conversation as resolved.
Resolved
);

for (const [name, input] of targets) {
const output = path.join(import.meta.dirname, '../out', name);
let runGenerators;
let config;

bench.add(name, () => runGenerators(config), {
beforeAll: async () => {
await rm(output, { recursive: true, force: true });
await mkdir(output, { recursive: true });
({ runGenerators } = createGenerator());
config = await setConfig({
...shared,
target: [name],
input: [input],
output,
});
},
afterAll: () => rm(output, { recursive: true, force: true }),
});
}

await bench.run();

if (process.env.CODSPEED_ENV === undefined) {
console.table(bench.table());
}
12 changes: 2 additions & 10 deletions docs/contributing/comparators.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ This guide explains how to create build comparison scripts for `@doc-kit/core`.
Comparators are scripts that:

1. **Compare** generated documentation between two builds (base vs. head)
2. **Identify differences** in content, structure, file size, or performance
2. **Identify differences** in content, structure, or file size
3. **Report results** in a format suitable for CI/CD systems
4. **Help catch regressions** before merging changes

### When to Use Comparators

- **Verify backward compatibility** - Ensure new code produces same output
- **Track file size changes** - Monitor bundle size growth
- **Catch performance regressions** - Compare elapsed time, CPU time, and peak memory
- **Validate transformations** - Check that refactors don't alter output
- **Debug generation issues** - Understand what changed between versions

Expand All @@ -28,10 +27,9 @@ sharing the `BASE`, `HEAD`, and `TITLE` constants from `scripts/constants.mjs`:
scripts/
├── constants.mjs # Shared constants (BASE, HEAD, TITLE)
└── comparators/
├── file-size.mjs # Compare file sizes and performance between builds
├── file-size.mjs # Compare file sizes between builds
├── files.mjs # Shared output-file listing helpers
├── object-assertion.mjs # Deep equality assertion for JSON objects
├── performance.mjs # Compare benchmark measurements
└── your-comparator.mjs # Your new comparator
```

Expand All @@ -43,12 +41,6 @@ Comparators can be reused across multiple generators. You specify which comparat
- `object-assertion.mjs` can compare JSON output from `legacy-json`, `json-simple`, etc.
- `my-comparator.mjs` would be a custom comparator for specific needs

The generation workflow also stores timing, CPU, and peak resident memory in
`benchmark.json`. The built-in comparators include these measurements in their
Markdown report and exclude the metadata file from output comparisons. If a
base artifact predates performance measurement, the output comparison still
runs and the performance section is omitted.

## Creating a Comparator

### Step 1: Create the Comparator File
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"test:ci": "c8 --reporter=lcov node --test --experimental-test-module-mocks \"packages/*/src/**/*.test.mjs\" \"scripts/**/*.test.mjs\" --test-reporter=@reporters/github --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=junit.xml --test-reporter=spec --test-reporter-destination=stdout",
"test:update-snapshots": "node --test --experimental-test-module-mocks --test-update-snapshots \"packages/*/src/**/*.test.mjs\" \"scripts/**/*.test.mjs\"",
"test:watch": "node --test --experimental-test-module-mocks --watch \"packages/*/src/**/*.test.mjs\" \"scripts/**/*.test.mjs\"",
"bench": "node bench/generate.bench.mjs",
"test:e2e": "playwright test",
"prepare": "husky || exit 0",
"run": "node packages/cli/bin/cli.mjs",
Expand All @@ -36,6 +37,8 @@
"devDependencies": {
"@changesets/changelog-github": "^1.0.1",
"@changesets/cli": "^3.0.3",
"@codspeed/tinybench-plugin": "6.1.0",
"@doc-kit/core": "workspace:*",
"@node-core/doc-kit": "workspace:*",
"@playwright/test": "~1.64.1",
"@reporters/github": "~2.1.4",
Expand All @@ -50,6 +53,7 @@
"lint-staged": "17.6.0",
"oxfmt": "0.74.0",
"oxlint": "1.89.0",
"tinybench": "6.2.3",
"wrangler": "4.151.1"
}
}
Loading
Loading