markdown-parity-check

中文文档
0.2.31发布于 2026/11/04MITTypeScriptES Module构建来源已验证

Compares the main content of a web page's HTML and Markdown representations and reports concrete differences.

原始使用文档
文档目录20 节
  1. Install
  2. Try it in the browser
  3. Check your own site
  4. Exit codes
  5. Options
  6. Example output
  7. What is compared
  8. The HTML container
  9. Blocks and severities
  10. Content that is left out
  11. Delivery in URL mode
  12. Limits of the method
  13. Command line and hosted page
  14. Use from Node and TypeScript
  15. Report format
  16. Development
  17. Release process
  18. Verify a release
  19. Security
  20. License

markdown-parity-check

A command-line tool and TypeScript library that compares the main content of a page's HTML and Markdown versions. The report lists blocks that are missing, added or changed, and numbers and links that differ, each with its source location. It can request both versions of one URL or read two local files.

Install

The package needs Node.js 22 or newer. It is MIT licensed. GitHub Actions tests Node.js 22 and 24 on Windows and Ubuntu.

Run it without installing anything:

npx --yes markdown-parity-check --url https://example.com/page

Install the CLI globally, or add it to a project as a library:

npm install -g markdown-parity-check
npm install markdown-parity-check

Try it in the browser

The hosted Markdown parity check runs this comparison on turva.dev. It currently checks published turva.dev pages only, except the two tool pages, and the page states that limit in its first two paragraphs and again at the address field. An address on any other site is refused with a message that points to the command-line tool.

The Fill in an example button puts turva.dev's own tools page in the address field, and the Check button runs the comparison as a separate step.

Check your own site

Replace the address with your page. The tool requests it twice, with Accept: text/html and with Accept: text/markdown. If the Markdown has its own address, add --markdown-url:

npx --yes markdown-parity-check --url https://example.com/page --markdown-url https://example.com/page.md

In CI, write the JSON report to a file and let warnings fail the step as well:

npx --yes markdown-parity-check --url https://example.com/page --format json --output report.json --strict

To compare two local files, pass both and a base URL for their relative links:

npx --yes markdown-parity-check --html-file page.html --markdown-file page.md --base-url https://example.com/page

Exit codes

CodeMeaning
0The comparison completed without a rejecting finding.
1The comparison completed and found rejecting content or delivery differences.
2An input, fetch, parse or report error prevented a reliable comparison.

Exit code 0 means the implemented checks found nothing to reject. It does not prove that the two versions mean the same thing.

Options

OptionPurpose
--url URLRequest both versions from one URL.
--markdown-url URLRequest the Markdown from a separate address. Needs --url.
--html-file PATH --markdown-file PATHCompare two local files instead.
--base-url URLResolve relative links in local files. Local-file mode only.
--selector CSSChoose the HTML content container.
--html-profile generic or --html-profile starlightUse the generic HTML rules, which is the default, or the Starlight rules, which also compare inactive tab panels and keep Expressive Code line breaks.
--front-matter keep or --front-matter stripKeep Markdown front matter, which is the default, or remove it before the comparison.
--format text or --format jsonPrint a readable report, which is the default, or a structured one.
--output PATHWrite the report to a file. An input file is never overwritten.
--strictTreat warnings as rejecting findings.
--timeout-ms MSDeadline for each request and for the parsing and comparison step. The default is 15 000 ms, and a run that passes it stops with exit code 2. The value must be a positive decimal integer no greater than 2147483647.
--max-bytes NLimit on decoded bytes per response. The default is 5 MiB. The value must be a positive decimal integer no greater than 9007199254740991.
--help, -hPrint the help.
--version, -VPrint the version.

A value that is outside these ranges, or that is not a plain run of digits, stops the run with exit code 2 and an error message.

Example output

This is the text report for the matching fixture pair in this repository, run with version 0.2.31 on 2026-11-04. The exact command is in the Development section below. The text report ends with a list of limitations, which is omitted here:

markdown-parity-check 0.2.31 (offline mode)
Result: PASS (0 error(s), 0 warning(s), 1 info)

HTML:     test/fixtures/same/page.html (file, 1,017 bytes, base https://example.com/page)
Markdown: test/fixtures/same/page.md (file, 514 bytes, base https://example.com/page)
Extraction: HTML via main (high confidence), 9 block(s); Markdown 9 block(s).
  note: Front matter (3 lines) was stripped as requested (--front-matter strip).
Coverage: 9/9 HTML block(s) aligned (100 %), 9/9 Markdown block(s) aligned (100 %).

Findings (1):
  1. [info] MARKDOWN_FRONT_MATTER_STRIPPED: Lines 1-3 were stripped as front matter (--front-matter strip) and not compared.
       Markdown  line 1  "title: Agent readiness audit"

What is compared

The HTML container

Without --selector, the tool looks for a visible main, then a visible article, then a visible [role=main], and uses the first match of the first kind that has one.

  • A candidate is visible when neither it nor an ancestor is hidden, in a template or inside one of the left-out subtrees listed below, so an article inside a nav is not used.
  • A form is the one exception, so a main inside a form that has no left-out role is still used, however far up the form sits.
  • If none of them exists, the page body is used and the report warns that page chrome may leak in.
  • Two visible elements of the same kind that do not contain one another also give a warning, because only the first is compared.

Front matter is kept by default and compared as content. If the HTML does not carry the same text, that alone fails the comparison. The tool also warns when the Markdown starts with a block that looks like YAML front matter, and --front-matter strip removes it.

Blocks and severities

Headings, paragraphs, list items, tables, code blocks and links are compared block by block. A link that wraps block elements, such as a linked card with a heading and a paragraph, is compared as those blocks: each keeps its own type and heading level and carries the link, and the Markdown side is read the same way, so ## [Title](url) is a heading with a link. In a table cell the link is read as part of the cell text. Content marked hidden or aria-hidden="true" is left out on both sides, also when a raw HTML wrapper such as <div hidden> is opened in one Markdown block and closed in a later one. A table caption counts as a paragraph before the table.

  • Errors: missing and added blocks, a change in text or numbers, a changed table or link, and a list item that shows another number or a different checked or unchecked state.
  • Warnings: a checkbox that is present on one side only, a difference in order, which is also reported for a block that moved and changed only in case, punctuation or typographic characters, and a difference in heading level, case, punctuation, list kind or nesting level. Each footnote in the Markdown is a warning, because footnotes are not compared. These are the main cases, and the code of each finding names the exact rule. A missing or extra copy of a repeated block is reported as a missing or added block, which is an error.
  • Information: each skipped comparison, which does not fail a check, not even with --strict.

The cell-by-cell comparison of a table is skipped when one of its cells has a rowspan or colspan above 1, a rowspan of 0, or a value that is empty or does not begin with a number. The number is read from the value's leading digits, so 2abc counts as 2. A colspan of 0 and a rowspan or colspan of 1 do not trigger it. The links and the caption of such a table are still compared. The numbering of a reversed list is not compared either.

Content that is left out

Some content is left out even when it sits inside the main content, in code blocks too. These are the subtrees of:

  • form, button, input, select, textarea, dialog, iframe, svg, canvas, object, embed and map.
  • nav, script, style, noscript and template.
  • Any element with the role navigation, banner, contentinfo, complementary, search, menu, menubar or dialog.
  • A header or footer that sits directly under body or html, unless it is the content container itself.

The starlight profile keeps an iframe that has a source and a title, as a link. The same list applies to inline raw HTML in the Markdown and to the checkbox of a task list item, which must belong to the item itself.

A left-out tag in a Markdown paragraph, such as a <button> or a <script>, is skipped through its matching closing tag in the same paragraph. One with no closing tag is reported as a warning and its text is compared as visible. The void tags input and embed need no closing tag and are skipped on their own. A self-closing <svg/> counts as closed by itself, as in HTML, and <button/> does not.

A <br> in a code block is a line break. A code block that differs only in whitespace is a warning, where whitespace means the ASCII whitespace characters and the Unicode space separators such as the no-break space. Any other difference in code, an invisible character such as a soft hyphen included, is an error. An image is compared by its alt text only, not by its address, so two different images with the same alt text compare equal.

Delivery in URL mode

In URL mode the Markdown response is checked first. An HTTP error or an HTML page in place of Markdown fails the check, and nothing is compared. A Markdown response labelled text/plain is a warning, and any other content type than text/markdown or text/x-markdown is an error. If the HTML request itself returns an HTTP error, the run ends with exit code 2.

Limits of the method

JavaScript is not executed. Content that a page builds in the browser is compared as the server sent it. Block matching is heuristic, so some layouts need an explicit selector. Reports mask URL query values and fragments and remove any user name or password from a URL. Excerpts may still contain private page content. Review a report before you share it.

Command line and hosted page

Both use the comparison core of this package. They differ in which pages they reach and in their limits:

TopicCommand-line toolHosted page
PagesAny public HTTP or HTTPS address, or two local files.Published turva.dev pages only, except the two tool pages, this one and the llms.txt validator.
FetchingNetwork requests from your machine. Non-public addresses are refused, also behind DNS and redirects. The tool connects directly to the validated address and does not use HTTP_PROXY or HTTPS_PROXY, so behind a mandatory proxy use the file mode.The turva.dev Worker renders both versions itself, so a check sends no request over the network.
Response size5 MiB per response by default, adjustable with --max-bytes.HTML up to 512 KiB. Markdown up to 128 KiB.
Comparison sizeUp to 4 000 000 block pairs.Up to 250 000 block pairs.
RateNot limited by the tool.About 10 checks per minute from one IP address at each Cloudflare location.

The hosted page also answers a JSON POST, described on the page. It runs the release of this package pinned in turva-worker, which can be older than the latest npm release. The report's toolVersion field shows which release produced it.

Some limits are not in the table:

  • A URL that contains a user name or password is refused even when its host is public, and the same holds for a redirect target.
  • The tool sends Accept-Encoding: gzip, deflate, br and decodes responses with no Content-Encoding, identity, gzip, x-gzip, deflate or br. A response with any other Content-Encoding, zstd for example, is rejected, and the run ends with exit code 2.
  • The command-line tool follows at most 5 redirects.
  • When elements are nested more than 1024 levels deep, in the HTML page, in a raw HTML block written inside the Markdown or in the Markdown's own structure, the extraction stops with exit code 2.
  • The matching stops the same way when more than 1 000 000 similar block pairs would have to be considered. The library limits default to the same two values.
  • The hosted page uses lower nesting and matching limits: 512 levels and 50 000 similar block pairs.

A hosted check can fail on turva.dev's own pages as well. On 2026-09-11 the check of https://turva.dev/tools returned fail with five errors. The Markdown carries a Related heading and four links that the HTML page leaves out, and the report listed each of them. The same four targets are links inside that page's tool cards, so the report found a missing structure and not missing content. The page passed again on 2026-10-02. HTML and Markdown can disagree reads that result in full.

Use from Node and TypeScript

The package is an ES module written in TypeScript and ships its type declarations. The library entry exports the comparison without the command-line interface, the file reader or the network code. It imports no Node.js built-in modules, which is how the hosted page bundles it into a Cloudflare Worker.

import { run, renderJson } from 'markdown-parity-check';
import type { Report, SourceInput } from 'markdown-parity-check';

// htmlText and markdownText are the two documents you already fetched or read.
declare const htmlText: string;
declare const markdownText: string;

const base = 'https://example.com/page';
const source = (file: string, body: string): SourceInput => ({
  meta: { kind: 'file', file, bytes: new TextEncoder().encode(body).length, baseUrl: base },
  body,
  base,
});

const report: Report = run(source('page.html', htmlText), source('page.md', markdownText), {
  strict: false,
  mode: 'offline',
});
console.log(report.summary.result, report.summary.exitCode);
console.log(renderJson(report));

run(html, markdown, options) returns the Report object. renderJson(report) prints it the way --format json does, with URL query values, fragments and credentials masked. The object run returns is not masked. A completed run has the result pass with exit code 0 or fail with exit code 1. When the comparison cannot finish, for example because the content is empty or a limit is exceeded, run throws a RunError. errorReport builds the error report the command prints in that case.

The options type is RunOptions. It takes selector, frontMatter, strict, mode and htmlProfile, which is generic or starlight. The Starlight rules are described in docs/starlight.md. The report records mode as url or offline, and it does not switch any check on or off.

When the Markdown source has kind: 'url' in its meta, the delivery check runs first and reads the HTTP status and contentType from that meta.

A host with a smaller CPU or memory budget can pass lower limits, typed as RunLimits: maxAlignmentPairs, maxSimilarityCandidates, maxSimilarityWork and maxNestingDepth. The nesting limit also applies to raw HTML blocks inside the Markdown.

The caller fetches the pages and decides which addresses are allowed. The address policy the command applies is exported as assertPublicHost and isPublicAddress.

Report format

The JSON report is the Report type. Its main fields are:

FieldContent
schemaVersionThe number 1.
toolVersionThe release that produced the report.
modeurl or offline.
sourcesThe metadata of the HTML and the Markdown input.
extractionHow each side was extracted. It is null in an error report and in a result that failed at the delivery check, because no extraction result is reported then.
summaryresult, which is pass, fail or error, then strict, the counts errors, warnings and infos, coverage, which is null when nothing was compared, exitCode and, when result is error, error and sometimes errorValue.
findingsA list of Finding objects, each with code, severity, direction and message, and where they apply the html and markdown locations and the before and after text.
limitationsThe fixed list of what the check does not cover.

Development

The sources are TypeScript under src/, compiled with tsc to dist/. Clone the repository, build it and run the bundled example:

git clone https://github.com/erekola/markdown-parity-check.git
cd markdown-parity-check
npm ci --ignore-scripts
npm run build
node dist/src/cli.js --html-file test/fixtures/same/page.html --markdown-file test/fixtures/same/page.md --base-url https://example.com/page --front-matter strip

The example compares matching content and exits with code 0. It removes the Markdown file's front matter explicitly. Without --front-matter strip the same pair fails, because the front matter is compared as content.

npm run typecheck runs tsc --noEmit. npm test builds first and then runs the tests against dist/.

Release process

After CI passes on the main branch, the release workflow .github/workflows/release.yml packs the tarball once and publishes it to npm with trusted publishing over OIDC when the version is not there yet. It then downloads the registry copy and checks its integrity hash and its content against the packed file. No npm token is stored.

Only after that check does it create the GitHub release with the verified tarball attached. The release notes are the CHANGELOG.md section of that version, so a version without one stops the release. A version that exists on npm with different content stops the release too. npm packs this README as well, so a change to it reaches the npm page only with a new version.

Verify a release

GitHub Actions publishes every version from 0.1.2 on with npm trusted publishing, and each one carries a provenance attestation. To check one, install it in an empty directory and ask npm to verify the signatures. Replace the version with the one you want to check.

mkdir verify-mpc && cd verify-mpc && npm init -y && npm install [email protected] --ignore-scripts && npm audit signatures

npm audit signatures checks the registry signature and the provenance attestation of each installed package that has one. The attestation of this package names three things to compare with what you expect: the repository github.com/erekola/markdown-parity-check, the workflow file .github/workflows/release.yml and the commit that produced the tarball. The npm version page shows them under Provenance. Tag v0.2.31 must point at that same commit, and gh api repos/erekola/markdown-parity-check/commits/v0.2.31 --jq .sha prints it. A mismatch is a reason not to use that version. Provenance proves which workflow built a release and from which commit. It does not prove that the code is safe, so reading the source and the dependencies stays your job.

Security

Report vulnerabilities privately to [email protected]. See SECURITY.md for the reporting instructions and the operating limits.

License

MIT. See LICENSE.

版本与发布记录

markdown-parity-check 共发布 35 个版本,首次发布于 2026/09/11;过去 12 个月发布了 35 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.2.31latest2026/11/041 天前
0.2.302026/11/022 天前
0.2.292026/10/2312 天前
0.2.282026/10/2214 天前
0.2.272026/10/1718 天前
0.2.262026/10/1026 天前

关于 markdown-parity-check 的常见问题

markdown-parity-check 是什么?

Compares the main content of a web page's HTML and Markdown representations and reports concrete differences.

如何安装 markdown-parity-check?

在项目目录运行 npm install markdown-parity-check,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 markdown-parity-check,可以用 npx markdown-parity-check 直接运行。

markdown-parity-check 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

markdown-parity-check 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

markdown-parity-check 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

markdown-parity-check 可以商用吗?

markdown-parity-check 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

markdown-parity-check 还在维护吗?

最新版本 0.2.31 发布于 2026/11/04(1 天前);至今共发布 35 个版本,其中过去 12 个月发布了 35 个;首次发布于 2026/09/11。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。