mdeval 1.6.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,14 +3,9 @@
3
3
  </p>
4
4
  <h1 align="center">mdeval</h1>
5
5
 
6
- Ever wanted dynamic values in your Markdown — package versions, star counts, computed tables — without a separate generation script that's easy to forget about?
6
+ mdeval runs JavaScript or TypeScript written in HTML comments inside your Markdown, and writes the results back into the file. The code sits next to the content it produces. The file renders normally on GitHub and in any editor, because HTML comments are invisible.
7
7
 
8
- mdeval embeds JavaScript directly in your Markdown using HTML comments. The logic lives right next to the content it produces, and the file renders normally everywhere — GitHub, editors, any Markdown viewer — because HTML comments are invisible.
9
-
10
- Any value that matters — a dependency count, a version string, a benchmark result — is computed, not typed. Whether written by a person or an AI agent, the expression shows the work and anyone can verify it. Re-run `mdeval` and every value updates.
11
-
12
- > [!TIP]
13
- > Particularly useful for **knowledge bases** — contact lists, project trackers, changelogs, or any Markdown that functions as a source of truth. Values stay accurate on every run, so nothing drifts. AI agents reading the files also benefit: pre-computed values mean they don't need to re-process raw data across multiple documents, cutting token usage significantly.
8
+ Values such as a version string, a dependency count, or a benchmark result are computed, not typed by hand, so anyone can check where they came from. Run `mdeval` again and every value updates. This also works well for Markdown knowledge bases, such as contact lists or project trackers, where files share data.
14
9
 
15
10
  ## Install
16
11
 
@@ -20,9 +15,7 @@ npm i mdeval
20
15
 
21
16
  ## Quick start
22
17
 
23
- mdeval adds two types of HTML comments to your Markdown:
24
-
25
- A **script block** is where you write JavaScript. It starts with `<!--mdeval` followed by a newline:
18
+ A **script block** holds code. It starts with `<!--mdeval` followed by a newline:
26
19
 
27
20
  ```markdown
28
21
  <!--mdeval
@@ -31,255 +24,178 @@ const pkg = JSON.parse(await fs.readFile('package.json', 'utf8'));
31
24
  -->
32
25
  ```
33
26
 
34
- Script blocks support:
27
+ Script blocks are ES modules with full Node.js access: imports, top-level `await`, and any installed package.
35
28
 
36
- - Full Node.js — any built-in module or installed package
37
- - ESM `import` / `export`
38
- - Top-level `await`
39
- - Shell commands via [`$`](https://google.github.io/zx/)
40
-
41
- A **value marker** is where the result appears. It starts with `<!--mdeval ` (with a space) followed by a JavaScript expression:
29
+ A **value marker** shows a result. It starts with `<!--mdeval ` (with a space) followed by an expression. The text between `-->` and `<!--/mdeval-->` is the current value:
42
30
 
43
31
  ```markdown
44
32
  Version: <!--mdeval pkg.version-->1.0.0<!--/mdeval-->
45
33
  ```
46
34
 
47
- The `1.0.0` between `-->` and `<!--/mdeval-->` is the current value — it gets overwritten with the result of `pkg.version` every time you run mdeval:
35
+ Run mdeval to replace each value with the result of its expression:
48
36
 
49
37
  ```bash
50
38
  mdeval README.md
51
39
  ```
52
40
 
53
- The file is updated in-place. You can pass multiple files or glob patterns: `mdeval README.md "docs/**/*.md"`.
54
-
55
- `node_modules` and hidden directories are automatically excluded from glob expansion.
56
-
57
41
  ## Example
58
42
 
59
- **`CONTACTS.md`** — a contact list stored as data, rendered as a table when viewed:
43
+ **`CONTACTS.md`** stores contacts as data and renders them as a table, using [md-pen](https://github.com/privatenumber/md-pen) to generate Markdown:
60
44
 
61
45
  ````markdown
62
46
  <!--mdeval
63
47
  import { table, link } from 'md-pen';
64
48
 
65
- export const contacts = [
66
- {
67
- name: 'Alice Martin',
68
- linkedin: 'https://linkedin.com/in/alice-martin',
69
- notes: 'Met at JSConf 2023',
70
- },
71
- {
72
- name: 'Bob Tanaka',
73
- linkedin: 'https://linkedin.com/in/bob-tanaka',
74
- notes: 'Former colleague at Acme Co.',
75
- },
76
- ];
77
-
78
- export const findContact = name => contacts.find(c => c.name === name);
49
+ type Contact = {
50
+ name: string;
51
+ linkedin: string;
52
+ };
79
53
 
80
- const contactsTable = table(
81
- contacts.map(({ name, linkedin, notes }) => ({ Name: link(linkedin, name), Notes: notes })),
82
- );
54
+ export const contacts: Contact[] = [
55
+ { name: 'Alice Martin', linkedin: 'https://linkedin.com/in/alice-martin' },
56
+ { name: 'Bob Tanaka', linkedin: 'https://linkedin.com/in/bob-tanaka' },
57
+ ];
83
58
  -->
84
59
 
85
- <!--mdeval block(contactsTable)-->
86
- | Name | Notes |
87
- | - | - |
88
- | [Alice Martin](https://linkedin.com/in/alice-martin) | Met at JSConf 2023 |
89
- | [Bob Tanaka](https://linkedin.com/in/bob-tanaka) | Former colleague at Acme Co. |
60
+ <!--mdeval block(table(contacts.map(({ name, linkedin }) => ({ Name: link(linkedin, name) }))))-->
61
+ | Name |
62
+ | - |
63
+ | [Alice Martin](https://linkedin.com/in/alice-martin) |
64
+ | [Bob Tanaka](https://linkedin.com/in/bob-tanaka) |
90
65
  <!--/mdeval-->
91
66
  ````
92
67
 
93
- **`TIMELINE.md`** — imports contacts and links them by LinkedIn:
68
+ **`TIMELINE.md`** imports the contacts from `CONTACTS.md`:
94
69
 
95
70
  ````markdown
96
71
  <!--mdeval
97
- import { link, ul, bold } from 'md-pen';
72
+ import { link } from 'md-pen';
73
+ import { contacts } from './CONTACTS.md';
98
74
 
99
- // Imports from the mdeval block!
100
- import { findContact } from './CONTACTS.md';
101
-
102
- const events = [
103
- { date: '2023-10-15', description: 'Attended JSConf 2023' },
104
- { date: '2024-03-01', description: `Coffee with ${link(findContact('Alice Martin').linkedin, 'Alice Martin')}` },
105
- { date: '2024-11-08', description: 'Started new open-source project' },
106
- ];
107
-
108
- const list = ul(events.map(({ date, description }) => `${bold(date)} — ${description}`));
75
+ const alice = contacts.find(contact => contact.name === 'Alice Martin')!;
109
76
  -->
110
77
 
111
- <!--mdeval block(list)-->
112
- - __2023-10-15__ — Attended JSConf 2023
113
- - __2024-03-01__ — Coffee with [Alice Martin](https://linkedin.com/in/alice-martin)
114
- - __2024-11-08__ — Started new open-source project
115
- <!--/mdeval-->
78
+ - 2024-03-01: Coffee with <!--mdeval link(alice.linkedin, alice.name)-->[Alice Martin](https://linkedin.com/in/alice-martin)<!--/mdeval-->
116
79
  ````
117
80
 
118
- The source of truth for contacts live in `CONTACTS.md`. If contact info changes, run `mdeval **/*.md` and all files get re-rendered with the latest data.
119
-
120
- ## Helpers
121
-
122
- mdeval ships two helpers used in markers:
123
-
124
- | Helper | Description |
125
- |--------|-------------|
126
- | `block(value)` | Wraps the value with surrounding newlines so block-level Markdown (headings, lists, tables) renders correctly |
127
- | `$` | [zx](https://google.github.io/zx/) shell — run commands via tagged templates |
128
-
129
- How you access them depends on the file extension:
130
-
131
- | File | Access |
132
- |------|--------|
133
- | `.md` | Available as globals — call directly: `block(x)` or `` $`cmd` `` |
134
- | `.js`, `.ts`, anything else | Import explicitly: `import { block, $ } from 'mdeval'` |
135
-
136
- The `.md` case works because the CLI (and `--import mdeval/loader`) seeds the helpers on `globalThis` at startup. Modules imported transitively from a `.md` see them too, but in non-`.md` files prefer the explicit import for clarity and portability. The plain `import { block, $ } from 'mdeval'` is side-effect-free — no globals are touched, no loader is registered.
81
+ When a contact changes, run `mdeval "**/*.md"` and both files update.
137
82
 
138
- ## Using `.md` exports from a script
83
+ ## Writing mdeval code
139
84
 
140
- `.md` files with mdeval script blocks are real ESM modules — anything they `export` can be imported from a Node script. Useful for validation tools, migration scripts, or any code that needs to read structured data out of a Markdown knowledge base.
85
+ ### Helpers
141
86
 
142
- Run your script with `--import mdeval/loader`:
143
-
144
- ```bash
145
- node --import mdeval/loader ./consumer.js
146
- ```
147
-
148
- `consumer.js` can then use ordinary static imports against `.md` files:
149
-
150
- ```js
151
- import { todos } from './TODOS.md'
152
-
153
- console.log(todos)
154
- ```
155
-
156
- `--import mdeval/loader` is a side-effect-only entry. Before any of `consumer.js`'s imports are linked, it seeds `block` and `$` on `globalThis` and registers the Node ESM loader so `.md` files resolve as modules. Without it, the static `import` of a `.md` fails — Node has no idea how to load `.md` yet.
157
-
158
- The plain `mdeval` import stays pure — `import { block, $ } from 'mdeval'` just gives you the helpers without touching `globalThis` or registering any loader. The split keeps "I want the helpers in my script" separate from "I want to load `.md` files".
159
-
160
- You can also point `--import mdeval/loader` at a `.md` file directly:
161
-
162
- ```bash
163
- node --import mdeval/loader ./TODOS.md
164
- ```
87
+ Two helpers are available as globals in `.md` files:
165
88
 
166
- Runtime stack traces from a `.md` point at original lines and columns — the loader sets up source maps automatically.
89
+ | Helper | Description |
90
+ | --- | --- |
91
+ | `block(value)` | Puts the value on its own lines, so block-level Markdown such as headings, lists, and tables renders |
92
+ | `$` | Runs shell commands with [zx](https://google.github.io/zx/): `` $`git branch --show-current` `` |
167
93
 
168
- ## Notes
94
+ Type-checked `.md` files can use these helpers without imports. Their types are visible throughout the TypeScript program; if another global conflicts, check Markdown with a separate tsconfig that excludes the conflicting declaration from all loaded files, including root files, imports, and `compilerOptions.types` inherited through `extends`. In `.js` and `.ts` files, import the helpers you use: `import { block, $ } from 'mdeval'`. The import has no side effects.
169
95
 
170
- - A file can have **multiple script blocks** — they're merged into one module, so variables and imports are shared. We recommend placing them at the top to signal the file has generated content.
96
+ ### Markers
171
97
 
172
- - **Markers can span multiple lines.** Don't cram everything into one line — give the expression room to breathe. This is what makes inlining marker-specific logic next to its output practical:
98
+ - **Marker expressions are awaited**, so a promise resolves before its value is written:
173
99
 
174
100
  ```markdown
175
- <!--mdeval block(table([
176
- { name: 'cleye', version: '^2.3.0' },
177
- { name: 'md-pen', version: '^0.0.2' },
178
- ]))-->
179
- | Package | Version |
180
- | - | - |
181
- | cleye | ^2.3.0 |
182
- | md-pen | ^0.0.2 |
183
- <!--/mdeval-->
101
+ Status: <!--mdeval fetch('https://api.github.com/repos/privatenumber/mdeval').then(response => response.status)-->200<!--/mdeval-->
184
102
  ```
185
103
 
186
- When the expression needs statements or control flow, wrap it in an IIFE:
104
+ - **Markers can span lines.** Keep logic that one marker needs inside the marker, and wrap statements in an IIFE:
187
105
 
188
106
  ```markdown
189
107
  <!--mdeval (() => {
190
108
  const n = 3;
191
- if (n % 2 === 0) { return 'even' }
192
- return 'odd'
109
+ return n % 2 === 0 ? 'even' : 'odd';
193
110
  })()-->odd<!--/mdeval-->
194
111
  ```
195
112
 
196
- Coupling the expression to its rendered value keeps related code together and reads better than pushing every variable into a distant script block. Reserve script blocks for shared imports or values referenced by multiple markers.
113
+ - **Script blocks share one module.** Variables and imports from every block in a file are visible to all of its markers. Blocks are joined in order, so a block starting with `(`, `[`, or a template literal can continue the previous block's last statement unless that statement ends with a semicolon. Use script blocks for imports and values that several markers share, and place them at the top of the file.
197
114
 
198
- - Marker expressions are **auto-awaited** — promises resolve automatically. Async APIs work directly in a marker without a script block:
115
+ ### Importing other `.md` files
199
116
 
200
- ```markdown
201
- Status: <!--mdeval (await fetch('https://api.github.com/repos/privatenumber/mdeval')).status-->200<!--/mdeval-->
202
- ```
117
+ A script block can import the exports of another `.md` file, as `TIMELINE.md` does above. mdeval runs only the script blocks of an imported file, not its markers.
203
118
 
204
- - You can **import from other `.md` files**. Only the script blocks are executed — no markers are processed:
119
+ If the imported file might not have mdeval code yet, use a namespace import. A missing export is then `undefined`, while a named import of a missing export fails:
205
120
 
206
- ```markdown
207
- <!--mdeval
208
- import { version } from './data.md';
209
- -->
210
- ```
121
+ ```markdown
122
+ <!--mdeval
123
+ import * as data from './stub.md';
124
+ const version = data.version ?? 'tbd';
125
+ -->
126
+ ```
211
127
 
212
- This lets you share constants and logic across multiple markdown files.
128
+ ### TypeScript
213
129
 
214
- If the imported file might not yet have mdeval content (e.g. a stub being filled in over time), use a **namespace import** so missing exports resolve to `undefined` instead of crashing Node's ESM linker:
130
+ Scripts and markers can use TypeScript syntax that Node.js can remove without changing the code, such as type annotations, `interface`, `as`, and `satisfies`. Syntax that generates code, such as `enum`, `namespace` with values, and constructor parameter properties, fails ([Node.js docs](https://nodejs.org/api/typescript.html#type-stripping)). Markdown files under `node_modules` run as JavaScript, because Node.js does not strip types there.
215
131
 
216
- ```markdown
217
- <!--mdeval
218
- import * as data from './stub.md';
219
- const version = data.version ?? 'tbd';
220
- -->
221
- ```
132
+ TypeScript 7.1 can also type-check mdeval code at its Markdown lines. See [Type checking](./docs/type-checking.md).
222
133
 
223
- - When processing multiple files (`mdeval a.md b.md`), all files **share the same Node.js runtime** — including the module cache, `globalThis`, and `process.env`. This is the same behavior as any Node.js program using `import()`.
134
+ ### Caveats
224
135
 
225
- ## Caveats
136
+ - **Script code cannot contain `-->`**, because it closes the HTML comment, or `<!--mdeval` followed by a space or newline, because mdeval treats it as an unclosed script. Build the text instead, such as `'<!--' + 'mdeval '`. Rewrite `x-- > y` as `x -= 1; x > y`.
137
+ - **Values cannot contain `<!--mdeval` followed by a space, a newline, or CRLF, or `<!--/mdeval-->`.** mdeval throws instead of writing them, so the file stays parseable.
138
+ - **Files processed together share one Node.js process,** including the module cache, `globalThis`, and `process.env`.
226
139
 
227
- - Script code **cannot contain `-->`** — it closes the HTML comment. Rewrite `x-- > y` as `x -= 1; x > y`.
228
- - Scripts are **JavaScript only** — no TypeScript.
229
- - Values **cannot contain `<!--mdeval `** or **`<!--/mdeval-->`** — mdeval will throw to prevent document corruption.
140
+ ## CLI
230
141
 
231
- ## Tip: md-pen
142
+ ```bash
143
+ mdeval README.md "docs/**/*.md"
144
+ ```
232
145
 
233
- [md-pen](https://github.com/privatenumber/md-pen) provides typed utilities for generating tables, lists, and other Markdown structures — useful when markers need to produce formatted output:
146
+ mdeval accepts files and glob patterns, and updates each file in place. It prints the path of every file it changes.
234
147
 
235
- ```markdown
236
- <!--mdeval
237
- import { table, bold, link } from 'md-pen';
238
- const deps = [
239
- ['cleye', '^2.3.0'],
240
- ['md-pen', '^0.0.2'],
241
- ];
242
- const depsTable = table(deps.map(([name, version]) => [
243
- link(`https://npm.im/${name}`, bold(name)),
244
- version,
245
- ]));
246
- -->
148
+ Line breaks in a value use the same line endings as the rest of the file.
247
149
 
248
- <!--mdeval block(depsTable)-->
249
- | Package | Version |
250
- | - | - |
251
- | [__cleye__](https://npm.im/cleye) | ^2.3.0 |
252
- | [__md-pen__](https://npm.im/md-pen) | ^0.0.2 |
253
- <!--/mdeval-->
150
+ Globs skip `node_modules` and hidden directories. To include them, start the pattern with their path, such as `"node_modules/pkg/*.md"` or `".github/**/*.md"`.
151
+
152
+ To check values in CI without rewriting files, run:
153
+
154
+ ```bash
155
+ npx mdeval --check "**/*.md"
254
156
  ```
255
157
 
256
- ## Git hook
158
+ This prints paths that would change and exits with code 1 if any are outdated or a file fails. It still evaluates the Markdown code, which can have side effects.
257
159
 
258
- Run mdeval automatically before every commit so values never go stale. [Lefthook](https://github.com/evilmartians/lefthook) makes this easy:
160
+ ### Git hook
259
161
 
260
- ```sh
261
- npm install lefthook --save-dev
262
- ```
162
+ To catch stale values before they are committed, run the check in a pre-commit hook. With [Lefthook](https://github.com/evilmartians/lefthook):
263
163
 
264
164
  ```yaml
265
165
  # lefthook.yml
266
166
  pre-commit:
267
167
  jobs:
268
168
  - name: mdeval
269
- # lefthook's default `gobwas` matcher requires `**` to span 1+ dirs,
270
- # so `**/*.md` alone misses root-level files like README.md
271
- glob: ["*.md", "**/*.md"]
272
- run: |
273
- files=$(npx mdeval "**/*.md")
274
- [ -z "$files" ] || git add $files
169
+ run: npx mdeval --check "**/*.md"
170
+ ```
171
+
172
+ When the check fails, the commit stops. mdeval itself writes and stages nothing, but the Markdown scripts still run with full access and can change files. Run `npx mdeval "**/*.md"`, review and stage the updates, and commit again.
173
+
174
+ Lefthook hides unstaged hunks in partially staged files while the job runs, so the hook checks their staged versions. If the check fails because of such a file, stage it fully or stash its unstaged hunks before running `npx mdeval` and committing again.
175
+
176
+ The job runs on every commit that stages files, so a change to a non-Markdown input, such as `package.json`, is checked too. The hook checks your working tree, so a staged file can still differ from what it checked; the CI check on the committed files is the reliable one.
177
+
178
+ ## Importing `.md` files from scripts
179
+
180
+ `.md` files are ES modules, so a Node.js script can import their exports. Register the loader with `--import`:
181
+
182
+ ```bash
183
+ node --import mdeval/loader ./script.js
184
+ ```
185
+
186
+ ```js
187
+ // script.js
188
+ import { contacts } from './CONTACTS.md'
189
+
190
+ console.log(contacts)
275
191
  ```
276
192
 
277
- `glob` makes the hook a no-op when no Markdown files are staged. mdeval prints the path of each file it rewrites to stdout, so `git add $files` re-stages only the files it actually updated. Note: `$files` relies on shell word-splitting, so this assumes `.md` paths without spaces.
193
+ See [Node.js usage](./docs/node.md) for TypeScript scripts, running a `.md` file directly, and stack traces.
278
194
 
279
- ## Agent Skills
195
+ ## Agent skill
280
196
 
281
- This package ships with a built-in [agent skill](./skills/mdeval/SKILL.md) for AI coding assistants. Set up [`skills-npm`](https://github.com/antfu/skills-npm) to automatically discover it.
197
+ mdeval ships an [agent skill](./skills/mdeval/SKILL.md) that teaches AI coding assistants to write mdeval code.
282
198
 
283
199
  ## Related
284
200
 
285
- - [comment-mark](https://github.com/privatenumber/comment-mark) — Same idea of using HTML comment placeholders, but you write the script externally and pass values in via a JavaScript API. Useful when you want to keep the logic separate from the Markdown.
201
+ - [comment-mark](https://github.com/privatenumber/comment-mark): also fills HTML comment placeholders, but values come from a script outside the Markdown through a JavaScript API.
package/dist/cli.mjs CHANGED
@@ -1,8 +1,13 @@
1
1
  #!/usr/bin/env node
2
- var C=Object.defineProperty;var a=(t,e)=>C(t,"name",{value:e,configurable:!0});import h from"node:fs/promises";import E from"node:path";import{cli as M}from"cleye";import{glob as N}from"tinyglobby";import{yellow as $}from"ansis";import"./loader.mjs";import{p as g,b as R,E as T,M as k,C as m,a as d,c as A,i as F}from"./parse-markdown-wnFVEAED.mjs";import{pathToFileURL as P}from"node:url";import{fromMarkdown as S}from"mdast-util-from-markdown";import{gfmFootnote as _}from"micromark-extension-gfm-footnote";import{gfmFootnoteFromMarkdown as I}from"mdast-util-gfm-footnote";import{gfmTable as W}from"micromark-extension-gfm-table";import{gfmTableFromMarkdown as V}from"mdast-util-gfm-table";import{toHast as H}from"mdast-util-to-hast";import{visitParents as X}from"unist-util-visit-parents";import{parseFragment as j}from"parse5";import"node:module";import"./runtime.mjs";import"zx";import"micromark";const G=a(t=>{if(t==null)throw new Error(`Cannot interpolate ${t} \u2014 this is likely a bug in your script`);return typeof t=="string"?t:typeof t=="object"?Symbol.toPrimitive in t?String(t):JSON.stringify(t):String(t)},"coerceValue"),K=a(async(t,e)=>{const{markers:o}=g(t);if(o.length===0)return t;const n=await import(P(e).href),s=R(o),r=await Promise.all(Array.from(s).map(async([l,f])=>{const u=G(await n[`${T}${f}`]);if(u.includes(k)||u.includes(`${m}
3
- `)||u.includes(d))throw new Error(`Expression "${l}" produced a value containing mdeval comment syntax, which would corrupt the document on re-parse`);return u})),i=[];let c=0;for(const l of o){const f=k.length+l.expression.length+A.length,u=l.start+f,x=l.end-d.length,b=s.get(l.expression);i.push(t.slice(c,u),r[b]),c=x}return i.push(t.slice(c)),i.join("")},"processSource"),B=a(t=>{const e=[];for(let o=0;o<t.length;o+=1)t[o]===`
4
- `&&e.push(o);return e},"buildLineIndex"),D=a((t,e)=>{let o=0,n=t.length;for(;o<n;){const r=Math.floor((o+n)/2);t[r]<e?o=r+1:n=r}const s=o===0?-1:t[o-1];return{line:o+1,column:e-s}},"offsetToLineColumn"),w=a((t,e,o,n)=>{let s=t,r=e;for(let i=0;i<n;i+=1)o[i]===`
5
- `?(s+=1,r=1):r+=1;return{line:s,column:r}},"advanceByValue"),J=a((t,e)=>{if(!t.startsWith(m,e))return!1;const o=t[e+m.length];return o===" "||o===`
6
- `?!0:o==="\r"&&t[e+m.length+1]===`
7
- `},"isMarkerOpening"),p=a((t,e=0,o=t.length)=>{const n=[];let s=0,r=e;for(;r<o;){const i=t.indexOf("<!--",r);if(i===-1||i>=o)break;J(t,i)?(n.push(i),s+=1,r=i+m.length):t.startsWith(d,i)?(s>0?s-=1:n.push(i),r=i+d.length):r=i+4}return n},"findMarkerLeaks"),U=a((t,e,o)=>{if(!t.attrs||!t.sourceCodeLocation?.attrs)return[];const n=[];for(const s of t.attrs){const r=t.sourceCodeLocation.attrs[s.name];if(!r)continue;const i=e+r.startOffset,{line:c,column:l}=o(i);for(const f of p(s.value))n.push({line:c,column:l})}return n},"attributeLeaks"),q=a((t,e,o)=>{const{value:n}=t;if(t.nodeName!=="#text"||typeof n!="string"||t.sourceCodeLocation?.startOffset===void 0)return[];const s=e+t.sourceCodeLocation.startOffset,r=o(s);return p(n).map(i=>{const{line:c,column:l}=w(r.line,r.column,n,i);return{line:c,column:l}})},"textLeaks"),z=a((t,e,o,n)=>{const s=t.slice(e,o),r=j(s,{sourceCodeLocationInfo:!0}),i=[],c=a(l=>{i.push(...U(l,e,n),...q(l,e,n));for(const f of l.childNodes??[])c(f)},"visit");return c(r),i},"findRawHtmlLeaks"),Q=a(t=>{let e=!1;for(const o of t){if(o.tagName==="pre")return"code block";o.tagName==="code"&&(e=!0)}return e?"inline code":"text"},"classifyTextLeak"),Y=a((t,e)=>e==="alt"?"image alt":t.tagName==="a"?"link title":"image title","classifyAttributeLeak"),v=a(t=>{const e=t.position?.start.offset,o=t.position?.end.offset;if(!(e===void 0||o===void 0))return{startOffset:e,endOffset:o,startLine:t.position?.start.line,startColumn:t.position?.start.column}},"rangeOf"),Z=a((t,e)=>{const o=v(t);if(o)return o;for(let n=e.length-1;n>=0;n-=1){const s=v(e[n]);if(s)return s}return{}},"positionRange"),tt=a((t,e,o)=>{if(e.startOffset!==void 0){if(o==="code block"){const n=t[e.startOffset];if(n===" "||n===" "){let r=e.startOffset;for(;t[r]===" "||t[r]===" ";)r+=1;return r}const s=t.indexOf(`
8
- `,e.startOffset);return s===-1?e.startOffset:s+1}if(o==="inline code"){let n=e.startOffset;for(;t[n]==="`";)n+=1;return n}return e.startOffset}},"findContentStart"),et=a((t,e,o)=>t.map(n=>{const{line:s,column:r}=e(n);return{kind:o,line:s,column:r}}),"mapOffsetsToLeaks"),ot=a((t,e,o)=>e===void 0?{line:t.startLine??1,column:t.startColumn??1}:o(e),"resolveContentStart"),nt=a((t,e,o,n)=>e.map(s=>{const{line:r,column:i}=w(o.line,o.column,t,s);return{kind:n,line:r,column:i}}),"valueWalkLeaks"),rt=a((t,e,o,n,s)=>{const r=o.value??"";if(!r.includes("mdeval"))return[];const i=p(r);if(i.length===0)return[];const c=Z(o,n),l=tt(t,c,s);if(l!==void 0&&c.endOffset!==void 0){const u=p(t,l,c.endOffset);if(u.length===i.length)return et(u,e,s)}const f=ot(c,l,e);return nt(r,i,f,s)},"collectTextNodeLeaks"),st=a((t,e,o,n)=>{if(!o.includes("mdeval"))return[];const s=p(o);if(s.length===0)return[];const r=e.position?.start.offset??-1,{line:i,column:c}=r===-1?{line:e.position?.start.line??1,column:e.position?.start.column??1}:t(r);return s.map(()=>({kind:n,line:i,column:c}))},"collectAttributeLeaks"),it=a((t,e,o,n)=>o.type!=="text"||typeof o.value!="string"?[]:rt(t,e,o,n,Q(n)),"textNodeLeaks"),at=a((t,e,o)=>{if(o.type!=="raw"||typeof o.value!="string")return[];const n=o.position?.start.offset,s=o.position?.end.offset;return n===void 0||s===void 0?[]:z(t,n,s,e).map(r=>({kind:"raw html",...r}))},"rawNodeLeaks"),ct=a((t,e)=>{if(e.type!=="element"||!e.properties)return[];const o=[];for(const n of["alt","title"]){const s=e.properties[n];if(typeof s=="string"){const r=Y(e,n);o.push(...st(t,e,s,r))}}return o},"elementAttributeLeaks"),lt=a(t=>{if(!t.includes("mdeval"))return[];const e=t.codePointAt(0)===65279?t.slice(1):t,o=B(e),n=a(c=>D(o,c),"point"),s=S(e,{extensions:[_(),W()],mdastExtensions:[I(),V()]}),r=[],i=H(s,{allowDangerousHtml:!0});return X(i,(c,l)=>{const f=c;r.push(...it(e,n,f,l),...at(e,n,f),...ct(n,f))}),r},"findRenderedLeaks"),L=$("Warning:"),ft=M({name:"mdeval",parameters:["<files...>"]}),O=ft._.files,ut=O.some(t=>t.includes("node_modules")),y=await N(O,{ignore:ut?[]:["**/node_modules/**"]});y.length===0&&(console.error("No files matched the given patterns"),process.exit(1)),await Promise.all(y.map(async t=>{try{const e=E.resolve(t),o=await h.readFile(e,"utf8"),n=g(o);F(o,n)&&console.warn(`${L} ${t} has no markdown content outside of mdeval blocks`);const s=await K(o,e);s!==o&&(await h.writeFile(e,s,"utf8"),console.log(t));for(const r of lt(s))console.warn(`${L} ${t}:${r.line}:${r.column} mdeval marker leaks into rendered ${r.kind}`)}catch(e){console.error(`Error processing ${t}:`,e instanceof Error?e.message:e),process.exitCode=1}}));
2
+ var D=Object.defineProperty;var a=(t,e)=>D(t,"name",{value:e,configurable:!0});import O from"node:fs/promises";import w from"node:path";import{styleText as U}from"node:util";import{cli as V}from"cleye";import{h as B,p as M}from"./loader-D40biTy7.mjs";import{fileURLToPath as K,pathToFileURL as X}from"node:url";import j from"picomatch";import{glob as P,convertPathToPattern as G,isDynamicPattern as J}from"tinyglobby";import{b as z,E as Z,M as $,C as y,a as E,c as R,p as N,i as q}from"./node-modules-Bw1ALrxb.mjs";import{fromMarkdown as Q}from"mdast-util-from-markdown";import{gfmFootnote as Y}from"micromark-extension-gfm-footnote";import{gfmFootnoteFromMarkdown as tt}from"mdast-util-gfm-footnote";import{gfmTable as et}from"micromark-extension-gfm-table";import{gfmTableFromMarkdown as nt}from"mdast-util-gfm-table";import{toHast as rt,defaultHandlers as T}from"mdast-util-to-hast";import{visitParents as st}from"unist-util-visit-parents";import{parseFragment as ot}from"parse5";import"node:module";import"./runtime.mjs";import"zx";import"node:fs";import"@jridgewell/gen-mapping";import"micromark";var it="mdeval",at="2.0.0",A={name:it,version:at};const ct=/^\s*at (?:.*? \()?((?:file:\/\/|\/|[A-Za-z]:[\\/]).*?\.md):(\d+):(\d+)\)?$/m,lt=a((t,e)=>{try{const n=String(t.stack),r=String(t),o=(n.startsWith(r)?n.slice(r.length):n).match(ct);if(!o)return e;const s=o[1].startsWith("file:")?K(o[1]):o[1];return`${w.resolve(s)===w.resolve(e)?e:w.relative(process.cwd(),s)}:${o[2]}:${o[3]}`}catch{return e}},"formatErrorLocation"),ft=a(async t=>{try{const e=await O.stat(t);if(e.isDirectory()){const n=t.replace(/[\\/]$/,"")||t;return{pattern:`${G(n)}/**/*.md`}}return e.isFile()?{file:t}:{error:`${t}: not a file`}}catch(e){const n=e instanceof Error?e.code:void 0;if(n==="ENOENT"||n==="ENOTDIR")return J(t)?{pattern:t}:{error:`${t}: file does not exist`};const r=typeof n=="string"?n:e instanceof Error?e.message:e;return{error:`${t}: ${r}`}}},"classifyArgument"),ut=a(async t=>{const e=await Promise.all(t.map(ft)),n=e.flatMap(l=>l.error===void 0?[]:[l.error]);if(n.length>0)return{files:[],errors:n};const r=e.flatMap(l=>l.file===void 0?[]:[l.file]),o=e.flatMap(l=>l.pattern===void 0?[]:[l.pattern]),s=[],i=[],c=[];for(const l of o){const m=j.scan(l);m.negated?s.push(l):m.base.split("/").includes("node_modules")?c.push(l):i.push(l)}const[f,d]=await Promise.all([i.length>0?P([...i,...s],{ignore:["**/node_modules/**"]}):[],c.length>0?P([...c,...s]):[]]),u=new Set;return{files:[...r,...f,...d].filter(l=>{const m=w.resolve(l);return u.has(m)?!1:(u.add(m),!0)}),errors:n}},"resolveFiles"),mt=a(t=>{if(t==null)throw new Error(`Cannot interpolate ${t} \u2014 this is likely a bug in your script`);return typeof t=="string"?t:typeof t=="object"?Symbol.toPrimitive in t?String(t):JSON.stringify(t):String(t)},"coerceValue"),pt=a((t,e)=>{const n=[];let r=0;for(const i of e){const c=$.length+i.expression.length+R.length,f=i.start+c;n.push(t.slice(r,f)),r=i.end-E.length}n.push(t.slice(r));const o=new Map([[`
3
+ `,0],[`\r
4
+ `,0],["\r",0]]);for(const i of n)for(const[c]of i.matchAll(/\r\n|\r|\n/g))o.set(c,o.get(c)+1);let s=`
5
+ `;for(const[i,c]of o)c>o.get(s)&&(s=i);return s},"getLineEnding"),dt=a(async(t,e,n=N(t))=>{const{markers:r}=n;if(r.length===0)return t;const o=pt(t,r),s=X(e).href,i=B(s),c={source:t,parsed:n};M.set(i,c);let f;try{f=await import(s)}finally{M.get(i)===c&&M.delete(i)}const d=z(r),u=await f[`${Z}markers`](),g=await Promise.all(Array.from(d).map(async([k,h])=>{const v=await u[h];if(v==null)throw new Error(`Expression "${k}" evaluated to ${v}`);const p=mt(v).replaceAll(/\r\n|\r|\n/g,o);if(p.includes($)||p.includes(`${y}
6
+ `)||p.includes(`${y}\r
7
+ `)||p.includes(E))throw new Error(`Expression "${k}" produced a value containing mdeval comment syntax, which would corrupt the document on re-parse`);return p})),l=[];let m=0;for(const k of r){const h=$.length+k.expression.length+R.length,v=k.start+h,p=k.end-E.length,x=d.get(k.expression);l.push(t.slice(m,v),g[x]),m=p}return l.push(t.slice(m)),l.join("")},"processSource"),ht=a(t=>{const e=[];for(let n=0;n<t.length;n+=1)t[n]==="\r"&&t[n+1]===`
8
+ `?(n+=1,e.push(n)):(t[n]==="\r"||t[n]===`
9
+ `)&&e.push(n);return e},"buildLineIndex"),gt=a((t,e)=>{let n=0,r=t.length;for(;n<r;){const s=Math.floor((n+r)/2);t[s]<e?n=s+1:r=s}const o=n===0?-1:t[n-1];return{line:n+1,column:e-o}},"offsetToLineColumn"),S=a((t,e,n,r)=>{let o=t,s=e;for(let i=0;i<r;i+=1)n[i]==="\r"||n[i]===`
10
+ `?(n[i]==="\r"&&n[i+1]===`
11
+ `&&i+1<r&&(i+=1),o+=1,s=1):s+=1;return{line:o,column:s}},"advanceByValue"),kt=a((t,e)=>{if(!t.startsWith(y,e))return!1;const n=t[e+y.length];return n===" "||n===`
12
+ `?!0:n==="\r"&&t[e+y.length+1]===`
13
+ `},"isMarkerOpening"),L=a((t,e=0,n=t.length)=>{const r=[];let o=0,s=e;for(;s<n;){const i=t.indexOf("<!--",s);if(i===-1||i>=n)break;kt(t,i)?(r.push(i),o+=1,s=i+y.length):t.startsWith(E,i)?(o>0?o-=1:r.push(i),s=i+E.length):s=i+4}return r},"findMarkerLeaks"),vt=a((t,e,n)=>{if(!t.attrs||!t.sourceCodeLocation?.attrs)return[];const r=[];for(const o of t.attrs){const s=t.sourceCodeLocation.attrs[o.name];if(!s)continue;const i=e+s.startOffset,{line:c,column:f}=n(i);for(const d of L(o.value))r.push({line:c,column:f})}return r},"attributeLeaks"),wt=a((t,e,n)=>{const{value:r}=t;if(t.nodeName!=="#text"||typeof r!="string"||t.sourceCodeLocation?.startOffset===void 0)return[];const o=e+t.sourceCodeLocation.startOffset,s=n(o);return L(r).map(i=>{const{line:c,column:f}=S(s.line,s.column,r,i);return{line:c,column:f}})},"textLeaks"),yt=a((t,e,n,r)=>{const o=t.slice(e,n),s=ot(o,{sourceCodeLocationInfo:!0}),i=[],c=a(f=>{i.push(...vt(f,e,r),...wt(f,e,r));for(const d of f.childNodes??[])c(d)},"visit");return c(s),i},"findRawHtmlLeaks"),Et=a(t=>{let e=!1;for(const n of t){if(n.tagName==="pre")return"code block";n.tagName==="code"&&(e=!0)}return e?"inline code":"text"},"classifyTextLeak"),Lt=a((t,e)=>e==="alt"?"image alt":t.tagName==="a"?"link title":"image title","classifyAttributeLeak"),F=a(t=>{const e=t.position?.start.offset,n=t.position?.end.offset;if(!(e===void 0||n===void 0))return{startOffset:e,endOffset:n,startLine:t.position?.start.line,startColumn:t.position?.start.column}},"rangeOf"),xt=a((t,e)=>{const n=F(t);if(n)return n;for(let r=e.length-1;r>=0;r-=1){const o=F(e[r]);if(o)return o}return{}},"positionRange"),Ot=a((t,e,n)=>{if(e.startOffset!==void 0){if(n==="code block"){const r=t[e.startOffset];if(r===" "||r===" "){let i=e.startOffset;for(;t[i]===" "||t[i]===" ";)i+=1;return i}const o=/\r\n?|\n/g;o.lastIndex=e.startOffset;const s=o.exec(t);return s?s.index+s[0].length:e.startOffset}if(n==="inline code"){let r=e.startOffset;for(;t[r]==="`";)r+=1;return r}return e.startOffset}},"findContentStart"),Mt=a((t,e,n)=>t.map(r=>{const{line:o,column:s}=e(r);return{kind:n,line:o,column:s}}),"mapOffsetsToLeaks"),$t=a((t,e,n)=>e===void 0?{line:t.startLine??1,column:t.startColumn??1}:n(e),"resolveContentStart"),bt=a((t,e,n,r)=>e.map(o=>{const{line:s,column:i}=S(n.line,n.column,t,o);return{kind:r,line:s,column:i}}),"valueWalkLeaks"),Ct=a((t,e,n,r,o)=>{const s=n.value??"";if(!s.includes("mdeval"))return[];const i=L(s);if(i.length===0)return[];const c=xt(n,r),f=Ot(t,c,o);if(f!==void 0&&c.endOffset!==void 0){const u=L(t,f,c.endOffset);if(u.length===i.length)return Mt(u,e,o)}const d=$t(c,f,e);return bt(s,i,d,o)},"collectTextNodeLeaks"),Pt=a((t,e,n,r)=>{if(!n.includes("mdeval"))return[];const o=L(n);if(o.length===0)return[];const s=e.position?.start.offset??-1,{line:i,column:c}=s===-1?{line:e.position?.start.line??1,column:e.position?.start.column??1}:t(s);return o.map(()=>({kind:r,line:i,column:c}))},"collectAttributeLeaks"),Rt=a((t,e,n,r)=>n.type!=="text"||typeof n.value!="string"?[]:Ct(t,e,n,r,Et(r)),"textNodeLeaks"),Nt=a((t,e,n)=>{if(n.type!=="raw"||typeof n.value!="string")return[];const r=n.position?.start.offset,o=n.position?.end.offset;return r===void 0||o===void 0?[]:yt(t,r,o,e).map(s=>({kind:"raw html",...s}))},"rawNodeLeaks"),Tt=a((t,e)=>{if(e.type!=="element"||!e.properties)return[];const n=[];for(const r of["alt","title"]){const o=e.properties[r];if(typeof o=="string"){const s=Lt(e,r);n.push(...Pt(t,e,o,s))}}return n},"elementAttributeLeaks"),At=a(t=>{if(!t.includes("mdeval"))return[];const e=t.codePointAt(0)===65279?t.slice(1):t,n=ht(e),r=a(u=>gt(n,u),"point"),o=Q(e,{extensions:[Y(),et()],mdastExtensions:[tt(),nt()]}),s=[],i=new WeakMap,c=a((u,g)=>{const l=g.type==="linkReference"?T.linkReference(u,g):T.imageReference(u,g),m=u.definitionById.get(String(g.identifier).toUpperCase());return m&&!Array.isArray(l)&&l.type==="element"&&i.set(l,m),l},"referenceHandler"),f=rt(o,{allowDangerousHtml:!0,handlers:{linkReference:c,imageReference:c}}),d=new WeakMap;return st(f,(u,g)=>{const l=u,m=i.get(u),k=Tt(r,l);s.push(...Rt(e,r,l,g),...Nt(e,r,l),...k.flatMap(h=>{if(!m||h.kind!=="link title"&&h.kind!=="image title")return[h];const v=m.position?.start.offset;if(v===void 0)return[h];let p=d.get(m);p||(p=new Map,d.set(m,p));const x=p.get(h.kind);return x&&x!==u?[]:(p.set(h.kind,u),[{...h,...r(v)}])}))}),s},"findRenderedLeaks"),_=U("yellow","Warning:",{stream:process.stderr}),b=V({name:A.name,version:A.version,strictFlags:!0,flags:{check:{type:Boolean,description:"Report files with outdated values instead of rewriting them"}},parameters:["<files...>"]}),{files:I,errors:W}=await ut(b._.files);if(W.length>0){for(const t of W)console.error(t);process.exitCode=1}else I.length===0&&(console.error("No files matched the given patterns"),process.exitCode=1);let C=!1,H=!1;process.on("exit",()=>{(C||!H)&&(process.exitCode=1)}),await Promise.all(I.map(async t=>{try{if(w.extname(t)!==".md")throw new Error("mdeval only processes .md files");const e=w.resolve(t),n=await O.readFile(e,"utf8"),r=N(n);q(n,r)&&console.warn(`${_} ${t} has no markdown content outside of mdeval blocks`);const o=await dt(n,e,r);o!==n&&(b.flags.check?C=!0:await O.writeFile(e,o,"utf8"),console.log(t));for(const s of At(b.flags.check?n:o))console.warn(`${_} ${t}:${s.line}:${s.column} mdeval marker leaks into rendered ${s.kind}`)}catch(e){console.error(`Error processing ${lt(e,t)}:`,e instanceof Error?e.message:e),C=!0}})),H=!0;