mdeval 1.5.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 +97 -181
- package/dist/cli.mjs +12 -2
- package/dist/content-mapper.mjs +13 -0
- package/dist/loader-D40biTy7.mjs +8 -0
- package/dist/loader.mjs +1 -2
- package/dist/node-modules-Bw1ALrxb.mjs +20 -0
- package/dist/runtime.d.mts +5 -0
- package/dist/runtime.mjs +0 -1
- package/docs/node.md +50 -0
- package/docs/type-checking.md +76 -0
- package/package.json +35 -8
- package/skills/mdeval/SKILL.md +97 -188
- package/skills/mdeval/references/node-loader.md +25 -0
- package/skills/mdeval/references/type-checking.md +70 -0
- package/dist/md-loader.mjs +0 -12
- package/dist/parse-markdown-wnFVEAED.mjs +0 -6
package/README.md
CHANGED
|
@@ -3,14 +3,9 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
<h1 align="center">mdeval</h1>
|
|
5
5
|
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
27
|
+
Script blocks are ES modules with full Node.js access: imports, top-level `await`, and any installed package.
|
|
35
28
|
|
|
36
|
-
|
|
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
|
-
|
|
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`**
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
|
81
|
-
|
|
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(
|
|
86
|
-
| Name |
|
|
87
|
-
| - |
|
|
88
|
-
| [Alice Martin](https://linkedin.com/in/alice-martin) |
|
|
89
|
-
| [Bob Tanaka](https://linkedin.com/in/bob-tanaka) |
|
|
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`**
|
|
68
|
+
**`TIMELINE.md`** imports the contacts from `CONTACTS.md`:
|
|
94
69
|
|
|
95
70
|
````markdown
|
|
96
71
|
<!--mdeval
|
|
97
|
-
import { link
|
|
72
|
+
import { link } from 'md-pen';
|
|
73
|
+
import { contacts } from './CONTACTS.md';
|
|
98
74
|
|
|
99
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
##
|
|
83
|
+
## Writing mdeval code
|
|
139
84
|
|
|
140
|
-
|
|
85
|
+
### Helpers
|
|
141
86
|
|
|
142
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
+
### Markers
|
|
171
97
|
|
|
172
|
-
- **
|
|
98
|
+
- **Marker expressions are awaited**, so a promise resolves before its value is written:
|
|
173
99
|
|
|
174
100
|
```markdown
|
|
175
|
-
<!--mdeval
|
|
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
|
-
|
|
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
|
-
|
|
192
|
-
return 'odd'
|
|
109
|
+
return n % 2 === 0 ? 'even' : 'odd';
|
|
193
110
|
})()-->odd<!--/mdeval-->
|
|
194
111
|
```
|
|
195
112
|
|
|
196
|
-
|
|
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
|
-
|
|
115
|
+
### Importing other `.md` files
|
|
199
116
|
|
|
200
|
-
|
|
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
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
121
|
+
```markdown
|
|
122
|
+
<!--mdeval
|
|
123
|
+
import * as data from './stub.md';
|
|
124
|
+
const version = data.version ?? 'tbd';
|
|
125
|
+
-->
|
|
126
|
+
```
|
|
211
127
|
|
|
212
|
-
|
|
128
|
+
### TypeScript
|
|
213
129
|
|
|
214
|
-
|
|
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
|
-
|
|
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
|
-
|
|
134
|
+
### Caveats
|
|
224
135
|
|
|
225
|
-
|
|
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
|
-
|
|
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
|
-
|
|
142
|
+
```bash
|
|
143
|
+
mdeval README.md "docs/**/*.md"
|
|
144
|
+
```
|
|
232
145
|
|
|
233
|
-
|
|
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
|
-
|
|
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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
-
|
|
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
|
-
|
|
160
|
+
### Git hook
|
|
259
161
|
|
|
260
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
193
|
+
See [Node.js usage](./docs/node.md) for TypeScript scripts, running a `.md` file directly, and stack traces.
|
|
278
194
|
|
|
279
|
-
## Agent
|
|
195
|
+
## Agent skill
|
|
280
196
|
|
|
281
|
-
|
|
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)
|
|
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,3 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
var
|
|
3
|
-
|
|
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;
|