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 +97 -181
- package/dist/cli.mjs +12 -7
- 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 +32 -14
- 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/skills/mdeval/SKILL.md
CHANGED
|
@@ -1,110 +1,61 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: mdeval
|
|
3
|
-
description:
|
|
3
|
+
description: Editing Markdown that contains `<!--mdeval` script blocks or value markers; adding computed values to Markdown, such as versions, counts, file contents, command output, or generated tables; running the `mdeval` CLI or `mdeval/loader`, or debugging their output.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# mdeval
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
- Markdown contains values derived from code, files, APIs, or shell commands
|
|
11
|
-
- README stats, version numbers, dependency counts, or computed tables
|
|
12
|
-
- Any value where accuracy matters — counts, sizes, dates, calculations
|
|
13
|
-
- Auditable content — the expression proves the value is correct, not just asserted
|
|
8
|
+
This skill covers writing mdeval code in Markdown and running it. mdeval evaluates the code and writes each result into the file.
|
|
14
9
|
|
|
15
10
|
## Syntax
|
|
16
11
|
|
|
17
|
-
|
|
12
|
+
| Type | Syntax | Starts with |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| Script block | `<!--mdeval\n...code...\n-->` | `<!--mdeval` + newline |
|
|
15
|
+
| Value marker | `<!--mdeval EXPR-->value<!--/mdeval-->` | `<!--mdeval` + space |
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
- All script blocks in a file form one ES module with full Node.js access and top-level `await`. Their imports and variables are visible to every marker in the file. Blocks are joined in order, so a block starting with `(`, `[`, or a template literal can continue the previous block's last statement unless it ends with a semicolon.
|
|
18
|
+
- `import.meta` refers to the Markdown file, so relative imports and paths resolve from it.
|
|
19
|
+
- Place script blocks at the top of the file. Their position relative to markers does not affect execution, but script blocks execute in their own order. Static imports from all blocks load before any block's code runs, so use dynamic `import()` when an earlier block must set up something an import reads at load time.
|
|
20
|
+
- Scripts and markers can use TypeScript syntax that Node.js strips: type annotations, `interface`, `type`, `as`, `satisfies`, and `!`. Do not use `enum`, `namespace` with values, or constructor parameter properties; they fail at runtime. Markdown under `node_modules` runs as plain JavaScript.
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
## Markers
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
A marker expression is anything valid after `const x =`. mdeval awaits it, converts the result to text, and replaces the value between `-->` and `<!--/mdeval-->`. An expression that appears in several markers is evaluated once.
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
Line breaks in values use the line ending most common outside marker values. Ties favor LF, then CRLF; if absent, LF is used.
|
|
29
27
|
|
|
30
|
-
|
|
|
31
|
-
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
28
|
+
| Result | Written as |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `string` | As is, with line breaks converted to the file's line ending |
|
|
31
|
+
| Object with `Symbol.toPrimitive`, such as zx output | `String(value)` |
|
|
32
|
+
| Other object or array | `JSON.stringify(value)` |
|
|
33
|
+
| `undefined` or `null` | Error |
|
|
34
|
+
| Anything else | `String(value)` |
|
|
36
35
|
|
|
37
|
-
|
|
36
|
+
Keep logic that one marker needs inside that marker. Markers can span lines, and an IIFE holds statements:
|
|
38
37
|
|
|
39
|
-
|
|
38
|
+
````markdown
|
|
39
|
+
<!--mdeval (() => {
|
|
40
|
+
const n = 3;
|
|
41
|
+
return n % 2 === 0 ? 'even' : 'odd';
|
|
42
|
+
})()-->odd<!--/mdeval-->
|
|
43
|
+
````
|
|
40
44
|
|
|
41
|
-
|
|
42
|
-
|------|--------|
|
|
43
|
-
| `string` | As-is |
|
|
44
|
-
| `number`, `boolean`, `bigint` | `String(value)` |
|
|
45
|
-
| `object`, `array` | `JSON.stringify(value)` |
|
|
46
|
-
| object with `Symbol.toPrimitive` | `String(value)` (e.g. zx `ProcessOutput`) |
|
|
47
|
-
| `Promise` | Auto-awaited, then coerced |
|
|
48
|
-
| `undefined`, `null` | Error |
|
|
45
|
+
Use script blocks for imports and for values that several markers share.
|
|
49
46
|
|
|
50
47
|
## Helpers
|
|
51
48
|
|
|
52
|
-
| Helper |
|
|
53
|
-
|
|
54
|
-
| `block(value)` |
|
|
55
|
-
| `$` | [zx](https://google.github.io/zx/)
|
|
56
|
-
|
|
57
|
-
| File | Access |
|
|
58
|
-
|------|--------|
|
|
59
|
-
| `.md` | Globals — call directly: `block(x)`, `` $`cmd` `` |
|
|
60
|
-
| `.js`, `.ts`, anything else | `import { block, $ } from 'mdeval'` |
|
|
61
|
-
|
|
62
|
-
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. `import { block, $ } from 'mdeval'` is side-effect-free.
|
|
63
|
-
|
|
64
|
-
## Importing `.md` exports from a script
|
|
65
|
-
|
|
66
|
-
Use when a Node script (validation, migration, agent tooling) needs to read exports from a `.md`. Run the script with `--import mdeval/loader`:
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
node --import mdeval/loader ./consumer.js
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
`consumer.js` can use ordinary static imports against `.md` files:
|
|
73
|
-
|
|
74
|
-
```js
|
|
75
|
-
import { todos } from './TODOS.md';
|
|
76
|
-
|
|
77
|
-
console.log(todos);
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
- `--import mdeval/loader` is a side-effect-only entry — seeds `block`/`$` on `globalThis` and registers the Node ESM loader before any of the script's imports link.
|
|
81
|
-
- Without it, static `import` of a `.md` fails — Node can't resolve `.md` until the loader is registered.
|
|
82
|
-
- The plain `mdeval` import (`import { block, $ } from 'mdeval'`) stays pure — no globals, no loader. Use it when you only want the helpers.
|
|
83
|
-
- Works on `.md` directly too: `node --import mdeval/loader ./TODOS.md`.
|
|
84
|
-
- Runtime stack traces from a `.md` point at original lines and columns automatically.
|
|
85
|
-
|
|
86
|
-
## CLI
|
|
87
|
-
|
|
88
|
-
```bash
|
|
89
|
-
mdeval README.md # single file
|
|
90
|
-
mdeval README.md docs/guide.md # multiple files
|
|
91
|
-
mdeval "docs/**/*.md" # glob pattern
|
|
92
|
-
mdeval "**/*.md" # recursive — node_modules and dotdirs auto-excluded
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
Supports full glob syntax including `**` recursive, `{a,b}` brace expansion, and `!` negation.
|
|
49
|
+
| Helper | Use |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `block(value)` | Puts the value on its own lines. Required for headings, lists, tables, and other block Markdown. |
|
|
52
|
+
| `$` | Runs a shell command with [zx](https://google.github.io/zx/): `` $`git branch --show-current` `` |
|
|
96
53
|
|
|
97
|
-
`
|
|
54
|
+
Both are globals in `.md` files at runtime and when type-checked, without imports. Their types are visible throughout the TypeScript program; if another global conflicts, use a separate tsconfig for Markdown 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 used: `import { block, $ } from 'mdeval'`.
|
|
98
55
|
|
|
99
56
|
## Patterns
|
|
100
57
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
````markdown
|
|
104
|
-
<!--mdeval $`git branch --show-current`-->main<!--/mdeval-->
|
|
105
|
-
````
|
|
106
|
-
|
|
107
|
-
### Read package.json
|
|
58
|
+
Read `package.json`:
|
|
108
59
|
|
|
109
60
|
````markdown
|
|
110
61
|
<!--mdeval
|
|
@@ -112,165 +63,123 @@ import fs from 'node:fs/promises';
|
|
|
112
63
|
const pkg = JSON.parse(await fs.readFile('package.json', 'utf8'));
|
|
113
64
|
-->
|
|
114
65
|
|
|
115
|
-
Version: <!--mdeval pkg.version-->
|
|
66
|
+
Version: <!--mdeval pkg.version-->1.0.0<!--/mdeval-->
|
|
116
67
|
````
|
|
117
68
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
Only script blocks are executed — markers are not processed:
|
|
69
|
+
Shell output:
|
|
121
70
|
|
|
122
71
|
````markdown
|
|
123
|
-
<!--mdeval
|
|
124
|
-
import { version } from './data.md';
|
|
125
|
-
-->
|
|
126
|
-
|
|
127
|
-
<!--mdeval version-->1.0.0<!--/mdeval-->
|
|
72
|
+
Branch: <!--mdeval $`git branch --show-current`-->main<!--/mdeval-->
|
|
128
73
|
````
|
|
129
74
|
|
|
130
|
-
|
|
75
|
+
Typed data, which `tsc` can check:
|
|
131
76
|
|
|
132
77
|
````markdown
|
|
133
78
|
<!--mdeval
|
|
134
|
-
|
|
135
|
-
|
|
79
|
+
interface Release {
|
|
80
|
+
version: string;
|
|
81
|
+
date: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const releases = [
|
|
85
|
+
{ version: '1.1.0', date: '2026-11-01' },
|
|
86
|
+
{ version: '1.0.0', date: '2026-10-01' },
|
|
87
|
+
] satisfies Release[];
|
|
136
88
|
-->
|
|
137
|
-
````
|
|
138
89
|
|
|
139
|
-
|
|
90
|
+
Latest: <!--mdeval releases[0].version-->1.1.0<!--/mdeval-->
|
|
91
|
+
````
|
|
140
92
|
|
|
141
|
-
|
|
93
|
+
Generated Markdown with [md-pen](https://github.com/privatenumber/md-pen). It must be installed in the project:
|
|
142
94
|
|
|
143
95
|
````markdown
|
|
144
96
|
<!--mdeval
|
|
145
|
-
import { table
|
|
146
|
-
const deps = [['cleye', '^2.3.0'], ['md-pen', '^0.0.2']];
|
|
147
|
-
const depsTable = table(deps.map(([name, v]) => [link(`https://npm.im/${name}`, bold(name)), v]));
|
|
97
|
+
import { table } from 'md-pen';
|
|
148
98
|
-->
|
|
149
99
|
|
|
150
|
-
<!--mdeval block(
|
|
100
|
+
<!--mdeval block(table([
|
|
101
|
+
{ Package: 'cleye', Version: '^2.3.0' },
|
|
102
|
+
{ Package: 'md-pen', Version: '^0.0.2' },
|
|
103
|
+
]))-->
|
|
151
104
|
| Package | Version |
|
|
152
105
|
| - | - |
|
|
153
|
-
|
|
|
106
|
+
| cleye | ^2.3.0 |
|
|
107
|
+
| md-pen | ^0.0.2 |
|
|
154
108
|
<!--/mdeval-->
|
|
155
109
|
````
|
|
156
110
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
**Block-level values need `block()`.** Without it, block elements don't render:
|
|
111
|
+
Data from another `.md` file. Importing a `.md` file runs only its script blocks:
|
|
160
112
|
|
|
161
113
|
````markdown
|
|
162
|
-
<!--
|
|
163
|
-
|
|
114
|
+
<!--mdeval
|
|
115
|
+
import { version } from './data.md';
|
|
116
|
+
-->
|
|
164
117
|
|
|
165
|
-
<!--
|
|
166
|
-
<!--mdeval block(heading)-->
|
|
167
|
-
### Title
|
|
168
|
-
<!--/mdeval-->
|
|
118
|
+
Version: <!--mdeval version-->1.0.0<!--/mdeval-->
|
|
169
119
|
````
|
|
170
120
|
|
|
171
|
-
|
|
121
|
+
If the imported file may not have mdeval code yet, use a namespace import. A named import of a missing export fails, but a missing namespace property is `undefined`:
|
|
172
122
|
|
|
173
123
|
````markdown
|
|
174
|
-
<!-- ❌ --> in the string literal closes the comment prematurely -->
|
|
175
124
|
<!--mdeval
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
<!-- ✅ Build the string without --> -->
|
|
180
|
-
<!--mdeval
|
|
181
|
-
const x = String.fromCharCode(45, 45, 62);
|
|
125
|
+
import * as data from './stub.md';
|
|
126
|
+
const version = data.version ?? 'tbd';
|
|
182
127
|
-->
|
|
183
128
|
````
|
|
184
129
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
**Place scripts at the top.** Order doesn't affect execution — scripts can appear after the markers that reference them — but top placement signals the file contains generated content.
|
|
188
|
-
|
|
189
|
-
**Markers can span multiple lines — use them to inline logic.** Don't cram expressions into one line. Co-locate marker-specific computation with its output:
|
|
190
|
-
|
|
191
|
-
````markdown
|
|
192
|
-
<!--mdeval block(table([
|
|
193
|
-
{ name: 'cleye', version: '^2.3.0' },
|
|
194
|
-
{ name: 'md-pen', version: '^0.0.2' },
|
|
195
|
-
]))-->
|
|
196
|
-
| Package | Version |
|
|
197
|
-
| - | - |
|
|
198
|
-
| cleye | ^2.3.0 |
|
|
199
|
-
| md-pen | ^0.0.2 |
|
|
200
|
-
<!--/mdeval-->
|
|
201
|
-
````
|
|
130
|
+
## Gotchas
|
|
202
131
|
|
|
203
|
-
|
|
132
|
+
**Block Markdown needs `block()`.** Without it, a heading, list, or table stays on the marker's line and does not render.
|
|
204
133
|
|
|
205
|
-
**
|
|
134
|
+
**Script code cannot contain `-->` or `<!--mdeval` followed by a space or newline.** The first ends the HTML comment; the second makes mdeval report an unclosed script, even inside a string. Build the text instead, such as `'--' + '>'` or `'<!--' + 'mdeval '`. Rewrite `x-- > y` as `x -= 1; x > y`.
|
|
206
135
|
|
|
207
|
-
|
|
208
|
-
<!-- ❌ backtick shows literally — line is an HTML block, not inline markdown -->
|
|
209
|
-
<!--mdeval expr-->`value`<!--/mdeval-->
|
|
210
|
-
````
|
|
136
|
+
**Values cannot contain `<!--mdeval` followed by a space, a newline, or CRLF, or `<!--/mdeval-->`.** mdeval throws instead of writing them.
|
|
211
137
|
|
|
212
|
-
|
|
138
|
+
**A marker that starts a line turns off inline Markdown in its value.** When a marker opens a paragraph, list item, or blockquote, GitHub treats the line as raw HTML, so `` `code` ``, `**bold**`, and links in the value appear literally. Wrap the value in `block()` so it renders on its own lines:
|
|
213
139
|
|
|
214
140
|
````markdown
|
|
215
141
|
<!--mdeval
|
|
216
|
-
import { code
|
|
142
|
+
import { code } from 'md-pen';
|
|
217
143
|
-->
|
|
218
144
|
|
|
219
|
-
<!--mdeval code('
|
|
220
|
-
`
|
|
145
|
+
<!--mdeval block(code('value'))-->
|
|
146
|
+
`value`
|
|
221
147
|
<!--/mdeval-->
|
|
222
148
|
````
|
|
223
149
|
|
|
224
|
-
Markers inside headings
|
|
150
|
+
Markers inside headings, or after other text on a line, render inline Markdown normally.
|
|
225
151
|
|
|
226
|
-
**
|
|
152
|
+
**Markers do not work in link URLs or image alt text.** GitHub shows or encodes the comment there instead of hiding it. mdeval warns about markers in alt text.
|
|
227
153
|
|
|
228
|
-
|
|
229
|
-
<!-- ❌ comment is URL-encoded as href -->
|
|
230
|
-
[text](<!-- comment -->)
|
|
154
|
+
**`<details>` needs blank lines around `</summary>`.** Without them, block Markdown inside it renders as text. md-pen's `details(summary, content)` writes the correct shape.
|
|
231
155
|
|
|
232
|
-
|
|
233
|
-

|
|
234
|
-
````
|
|
156
|
+
**Examples in code are safe.** mdeval ignores markers in fenced, indented, and inline code, so Markdown can document mdeval syntax.
|
|
235
157
|
|
|
236
|
-
|
|
158
|
+
**Do not create a `.md` file that only holds code.** mdeval warns about it. Put shared logic in a `.js` or `.ts` file and import it.
|
|
237
159
|
|
|
238
|
-
|
|
239
|
-
<!--mdeval
|
|
240
|
-
import { details, ul } from 'md-pen';
|
|
241
|
-
-->
|
|
160
|
+
## CLI
|
|
242
161
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
))-->
|
|
247
|
-
<details>
|
|
248
|
-
<summary>Click me</summary>
|
|
162
|
+
```bash
|
|
163
|
+
mdeval README.md "docs/**/*.md"
|
|
164
|
+
```
|
|
249
165
|
|
|
250
|
-
-
|
|
251
|
-
-
|
|
166
|
+
- mdeval updates each file in place and prints the path of each file it changed.
|
|
167
|
+
- For CI, run `npx mdeval --check "**/*.md"` to report outdated files and exit 1 without rewriting; evaluation can still have side effects.
|
|
168
|
+
- For a Lefthook pre-commit hook, use a job with `run: npx mdeval --check "**/*.md"` and no `glob`, so changes to non-Markdown inputs are checked too. When it fails, run `npx mdeval "**/*.md"`, review and stage the updates, and commit again. Do not rewrite files in the hook (with `stage_fixed` or `fail_on_changes`) or parse mdeval's stdout to stage files: Lefthook's rollback can discard unstaged edits, `stage_fixed` misses dependent files, and scripts can print to stdout.
|
|
169
|
+
- Globs skip `node_modules` and hidden directories unless the pattern names them, such as `"node_modules/pkg/*.md"`.
|
|
170
|
+
- Warnings go to stderr. A `marker leaks into rendered ...` warning means a marker will show as text on GitHub. Fix it unless the marker is an intentional example.
|
|
171
|
+
- After editing mdeval code, run `mdeval` on the file and check the written values.
|
|
252
172
|
|
|
253
|
-
|
|
254
|
-
<!--/mdeval-->
|
|
255
|
-
````
|
|
173
|
+
## References
|
|
256
174
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
```yaml
|
|
260
|
-
# lefthook.yml
|
|
261
|
-
pre-commit:
|
|
262
|
-
jobs:
|
|
263
|
-
- name: mdeval
|
|
264
|
-
# lefthook's default `gobwas` matcher requires `**` to span 1+ dirs,
|
|
265
|
-
# so `**/*.md` alone misses root-level files like README.md
|
|
266
|
-
glob: ["*.md", "**/*.md"]
|
|
267
|
-
run: |
|
|
268
|
-
files=$(npx mdeval "**/*.md")
|
|
269
|
-
[ -z "$files" ] || git add $files
|
|
270
|
-
```
|
|
175
|
+
- `references/type-checking.md`: setting up or running `tsc` on Markdown, or fixing a TypeScript error reported at a `.md` line. If the project's `tsconfig.json` lists mdeval in `contentMappers`, read it and run `tsc` after editing mdeval code.
|
|
176
|
+
- `references/node-loader.md`: a Node.js script, test, or tool imports a `.md` file, or runs one with `node`.
|
|
271
177
|
|
|
272
|
-
|
|
178
|
+
## Reporting problems
|
|
273
179
|
|
|
274
|
-
|
|
180
|
+
Report bugs in mdeval or in this skill to [privatenumber/mdeval](https://github.com/privatenumber/mdeval/issues) without waiting to be asked. This includes wrong behavior, confusing errors or warnings, wrong or missing guidance in this skill, and friction that cost time.
|
|
275
181
|
|
|
276
|
-
|
|
182
|
+
1. Search open and closed issues: `gh issue list -R privatenumber/mdeval --search "<terms>" --state all`.
|
|
183
|
+
2. Confirm the behavior on the latest mdeval release.
|
|
184
|
+
3. Write a minimal Markdown reproduction without the user's private content.
|
|
185
|
+
4. If your instructions allow filing issues for the user, run `gh issue create -R privatenumber/mdeval`. Otherwise, show the user the draft and file it only after they approve it.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Importing `.md` files from Node.js
|
|
2
|
+
|
|
3
|
+
`.md` files with mdeval script blocks are ES modules. Run a script with the loader to import their exports:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
node --import mdeval/loader ./script.js
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
// script.js
|
|
11
|
+
import { todos } from './TODOS.md'
|
|
12
|
+
|
|
13
|
+
console.log(todos)
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- `--import mdeval/loader` must come before the script, so the loader is registered before `.md` imports resolve. Without it, importing a `.md` file fails.
|
|
17
|
+
- The loader also defines the `block` and `$` globals that `.md` code uses.
|
|
18
|
+
- Importing a `.md` file runs only its script blocks. It does not evaluate markers or change the file.
|
|
19
|
+
- TypeScript scripts work the same way: `node --import mdeval/loader ./script.ts`.
|
|
20
|
+
- Run a `.md` file's script blocks directly: `node --import mdeval/loader ./TODOS.md`.
|
|
21
|
+
- Stack traces from `.md` code report the original Markdown lines.
|
|
22
|
+
|
|
23
|
+
In `.js` and `.ts` files, import the helpers instead of relying on globals: `import { block, $ } from 'mdeval'`. This import has no side effects. It does not define globals or register the loader.
|
|
24
|
+
|
|
25
|
+
In tests that spawn the script, pass the flag to the child process, for example `spawn(process.execPath, ['--import', 'mdeval/loader', scriptPath])`.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Type checking mdeval code
|
|
2
|
+
|
|
3
|
+
TypeScript 7.1 checks the code in script blocks and markers through a content mapper, and reports errors at the original Markdown lines. Checking does not run the code.
|
|
4
|
+
|
|
5
|
+
## Set up
|
|
6
|
+
|
|
7
|
+
Install the TypeScript 7.1 beta:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install --save-dev typescript@7.1.0-beta
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Register mdeval for `.md` files in `tsconfig.json`, and include the Markdown files to check:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"compilerOptions": {
|
|
18
|
+
"module": "nodenext",
|
|
19
|
+
"target": "esnext",
|
|
20
|
+
"strict": true,
|
|
21
|
+
"erasableSyntaxOnly": true,
|
|
22
|
+
"noEmit": true
|
|
23
|
+
},
|
|
24
|
+
"contentMappers": [
|
|
25
|
+
{
|
|
26
|
+
"package": "mdeval",
|
|
27
|
+
"extensions": [
|
|
28
|
+
".md"
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"include": [
|
|
33
|
+
"README.md",
|
|
34
|
+
"docs/**/*.md"
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Ask the user before adding this to a project. It requires a beta compiler.
|
|
40
|
+
|
|
41
|
+
## Run
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx tsc --noEmit --runExternalCode
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`--runExternalCode` lets `tsc` start mdeval's mapper. Without it, `tsc` fails with `TS18068: Content mappers require the '--runExternalCode' command line flag to be enabled.`
|
|
48
|
+
|
|
49
|
+
## What is checked
|
|
50
|
+
|
|
51
|
+
- Each `.md` file outside `node_modules` is a TypeScript module. Annotate with TypeScript syntax, such as `const count: number = 42`. JSDoc types are not checked there.
|
|
52
|
+
- Markdown under `node_modules` is checked as JavaScript because Node runs it as JavaScript.
|
|
53
|
+
- `erasableSyntaxOnly` reports syntax that fails at runtime, such as `enum`, at its Markdown line.
|
|
54
|
+
- `block` and `$` are available without imports; explicit imports also work. Their global types are visible throughout the TypeScript program, including `.ts` and `.js` files, which matches runtime: the loader puts them on `globalThis` for every module in the process. Code that also runs without the loader should import them, or Markdown should be checked under its own tsconfig.
|
|
55
|
+
- For conflicting globals, including full zx's `$`, use a separate tsconfig for Markdown that excludes the conflicting declaration from all loaded files, including root files, the import graph, and `compilerOptions.types` inherited through `extends` (for example, `zx/globals`). With `skipLibCheck`, the first declaration TypeScript checks determines the type, and a conflicting later `.d.ts` declaration can be skipped. For `a.md`, `globals.d.ts`, then `b.md`, the first Markdown declaration establishes the type and TS2403 can be absent.
|
|
56
|
+
- A repeated marker expression is checked once, and its errors point to the first occurrence.
|
|
57
|
+
- Fenced code, inline code, and rendered marker values are not checked.
|
|
58
|
+
- A `.ts` file that imports a `.md` file is checked against the `.md` exports' types when the `.ts` file is in `include`.
|
|
59
|
+
|
|
60
|
+
## Fix errors
|
|
61
|
+
|
|
62
|
+
Fix a reported error in the script block or marker at that Markdown line, then run `tsc` again.
|
|
63
|
+
|
|
64
|
+
A namespace import of a `.md` file without exports, such as `import * as data from './stub.md'`, reports `Property 'version' does not exist` for `data.version`, even though the value is `undefined` at runtime. Give the imported file the export, or cast the namespace to the expected shape:
|
|
65
|
+
|
|
66
|
+
```ts
|
|
67
|
+
import * as stub from './stub.md'
|
|
68
|
+
|
|
69
|
+
const data = stub as { version?: string }
|
|
70
|
+
```
|
package/dist/md-loader.mjs
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
var x=Object.defineProperty;var c=(e,a)=>x(e,"name",{value:a,configurable:!0});import b from"node:fs/promises";import{pathToFileURL as E,fileURLToPath as T}from"node:url";import{p as $,E as B,M as F}from"./parse-markdown-wnFVEAED.mjs";import{GenMapping as P,toEncodedMap as W,addSegment as U}from"@jridgewell/gen-mapping";import"micromark";const M=10,v=13,N=c(e=>e===9||e===M||e===11||e===12||e===v||e===32,"isWhitespaceCode"),j=c(e=>e>=48&&e<=57||e>=65&&e<=90||e===95||e>=97&&e<=122||e===36,"isIdentifierCode"),A=c((e,a)=>{const h=E(e).href,p=new P({file:e}),s=[];let i="";const o=[];for(let n=a.indexOf(`
|
|
3
|
-
`);n!==-1;n=a.indexOf(`
|
|
4
|
-
`,n+1))o.push(n);const y=c(n=>{let t=0,r=o.length;for(;t<r;){const f=Math.floor((t+r)/2);o[f]<n?t=f+1:r=f}const d=t>0?o[t-1]:-1;return{line:t,column:n-d-1}},"positionFromOffset"),u=c(n=>{i+=n},"appendLineChunk"),S=c(()=>{s.push(i),i=""},"flushLine"),O=c((n,t)=>{const r=y(t);let d=r.line,f=r.column,L=!1,R=!1,m=0;const w=c(l=>i.length+l-m,"generatedColumn");for(let l=0;l<n.length;l+=1){const g=n.codePointAt(l),C=N(g),I=j(g);!C&&(l===0||R||!L||!I)&&U(p,s.length,w(l),h,d,f),g===M?(d+=1,f=0):f+=1,L=I,R=C,g===M&&(u(n.slice(m,l)),S(),m=l+1)}m<n.length&&u(n.slice(m))},"appendSourceText"),k=c(n=>{let t=0;for(let r=n.indexOf(`
|
|
5
|
-
`);r!==-1;r=n.indexOf(`
|
|
6
|
-
`,t))u(n.slice(t,r)),S(),t=r+1;t<n.length&&u(n.slice(t))},"appendSyntheticText");return{appendSource(n,t){O(n,t)},appendSynthetic(n){k(n)},toModuleSource:c(()=>{const t=`${(i===""?s:[...s,i]).join(`
|
|
7
|
-
`)}
|
|
8
|
-
`,r=W(p),d=`//# sourceMappingURL=data:application/json;base64,${Buffer.from(JSON.stringify(r)).toString("base64")}`;return`${t}${d}
|
|
9
|
-
`},"toModuleSource")}},"createSourceBuilder"),X=c((e,a,h,p)=>{const s=A(a,e);for(const o of h)s.appendSource(o.content,o.contentStart),o.content.endsWith(`
|
|
10
|
-
`)||s.appendSynthetic(`
|
|
11
|
-
`);const i=new Map;for(const o of p)i.has(o.expression)||i.set(o.expression,{index:i.size,marker:o});for(const[o,{index:y,marker:u}]of i){const S=`export const ${B}${y} = `;s.appendSynthetic(S),s.appendSource(o,u.start+F.length),s.appendSynthetic(`;
|
|
12
|
-
`)}return s.toModuleSource()},"generateModule"),_=c(async(e,a,h)=>{if(!e.endsWith(".md"))return h(e,a);const p=T(e),s=await b.readFile(p,"utf8"),{scriptBlocks:i,markers:o}=$(s);return i.length===0&&o.length===0?{format:"module",source:"",shortCircuit:!0}:{format:"module",source:X(s,p,i,o),shortCircuit:!0}},"load");export{_ as load};
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
var M=Object.defineProperty;var l=(n,e)=>M(n,"name",{value:e,configurable:!0});import{postprocess as v,parse as w,preprocess as k}from"micromark";const x="mdeval",c=`<!--${x}`,C=`${c} `,E=`<!--/${x}-->`,b=`__${x}_`,g="-->",_=l(n=>{const e=new Map;for(const{expression:s}of n)e.has(s)||e.set(s,e.size);return e},"buildExpressionMap"),$=l(n=>{const e=n.codePointAt(0)===65279?1:0,s=v(w().document().write(k()(n,void 0,!0))),r=[];for(const[o,t]of s)o==="enter"&&(t.type==="codeFenced"||t.type==="codeIndented"||t.type==="codeText")&&r.push([t.start.offset+e,t.end.offset+e]);return r},"findCodeRanges"),R=l(n=>{let e=null,s=0;return r=>{for(e===null&&(e=$(n));s<e.length&&e[s][1]<=r;)s+=1;return s<e.length&&r>=e[s][0]}},"createIsInCode"),u=l((n,e)=>{let s=e;for(;s<n.length;){const r=n.indexOf(c,s);if(r===-1)return-1;const o=n[r+c.length];if(o===" "||o===`
|
|
2
|
-
`||o==="\r"&&n[r+c.length+1]===`
|
|
3
|
-
`)return r;s=r+c.length}return-1},"findNextMdevalOpening"),S=l(n=>{if(!n.includes(c))return{scriptBlocks:[],markers:[]};const e=R(n),s=[],r=[];let o=0;for(;o<n.length;){const t=n.indexOf(c,o);if(t===-1)break;const i=t+c.length,m=n[i];if(m===`
|
|
4
|
-
`||m==="\r"&&n[i+1]===`
|
|
5
|
-
`){const f=m===`
|
|
6
|
-
`?i+1:i+2,a=n.indexOf(g,f),d=u(n,f),p=d!==-1&&(a===-1||d<a);if(a===-1||p){if(!e(t))throw new Error(p?`mdeval: unclosed script block at offset ${t} (another mdeval opening found before \`-->\`)`:`mdeval: unclosed script block at offset ${t} (missing \`-->\`)`);if(a===-1)break;o=f;continue}const h=a+g.length;e(t)||s.push({content:n.slice(f,a),contentStart:f,start:t,end:h}),o=h;continue}if(m===" "){const f=i+1,a=n.indexOf(g,f);if(a===-1){if(e(t))break;throw new Error(`mdeval: unclosed marker open at offset ${t} (missing \`-->\` on the marker open)`)}const d=a+g.length,p=n.indexOf(E,d),h=u(n,d);if(p===-1||h!==-1&&h<p){if(e(t)){o=d;continue}throw new Error(`mdeval: unclosed marker open at offset ${t} (missing \`<!--/mdeval-->\`)`)}const O=p+E.length;e(t)||r.push({expression:n.slice(f,a),start:t,end:O}),o=O;continue}o=i}return{scriptBlocks:s,markers:r}},"parseMarkdown"),I=l((n,{scriptBlocks:e,markers:s})=>{if(e.length===0&&s.length===0)return!1;const r=[...e.map(t=>[t.start,t.end]),...s.map(t=>[t.start,t.end])].sort((t,i)=>t[0]-i[0]);let o=0;for(const[t,i]of r){if(n.slice(o,t).trim()!=="")return!1;o=i}return n.slice(o).trim()===""},"isOnlyMdeval");export{c as C,b as E,C as M,E as a,_ as b,g as c,I as i,S as p};
|