mdeval 1.0.0 → 1.2.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
@@ -26,7 +26,7 @@ const pkg = JSON.parse(await fs.readFile('package.json', 'utf8'));
26
26
  -->
27
27
  ```
28
28
 
29
- Full Node.js, ESM imports, and top-level `await` are supported.
29
+ Full Node.js, ESM imports, top-level `await`, and shell commands via [`$`](https://google.github.io/zx/) are supported.
30
30
 
31
31
  A **value marker** is where the result appears. It starts with `<!--mdeval ` (with a space) followed by a JavaScript expression:
32
32
 
@@ -40,7 +40,9 @@ The `1.0.0` between `-->` and `<!--/mdeval-->` is the current value — it gets
40
40
  mdeval README.md
41
41
  ```
42
42
 
43
- The file is updated in-place. You can pass multiple files at once: `mdeval README.md docs/*.md`
43
+ The file is updated in-place. You can pass multiple files or glob patterns: `mdeval README.md "docs/**/*.md"`.
44
+
45
+ `node_modules` and hidden directories are automatically excluded from glob expansion.
44
46
 
45
47
  ## Notes
46
48
 
@@ -54,6 +56,14 @@ The file is updated in-place. You can pass multiple files at once: `mdeval READM
54
56
 
55
57
  In large documents, this IIFE pattern lets you keep logic next to the marker it serves instead of in a distant script block.
56
58
 
59
+ - Marker expressions are **auto-awaited** — promises resolve automatically, so you can use `fetch()` or any async API directly in a marker without wrapping it in a script block.
60
+
61
+ - **`$`** from [zx](https://google.github.io/zx/) is available as a global — run shell commands directly in markers:
62
+
63
+ ```markdown
64
+ <!--mdeval $`git branch --show-current`-->main<!--/mdeval-->
65
+ ```
66
+
57
67
  - If your value starts with a heading, list, or other block element, wrap it with **`block()`** so it renders on its own line. `block()` is a global helper that adds newlines before and after the value.
58
68
 
59
69
  - You can **import from other `.md` files**. Only the script blocks are executed — no markers are processed:
@@ -81,9 +91,13 @@ The file is updated in-place. You can pass multiple files at once: `mdeval READM
81
91
  ```markdown
82
92
  <!--mdeval
83
93
  import { table, bold, link } from 'md-pen';
84
- const deps = [['cleye', '^2.3.0'], ['md-pen', '^0.0.2']];
85
- const depsTable = table(deps.map(([name, v]) => [
86
- link(`https://npm.im/${name}`, bold(name)), v
94
+ const deps = [
95
+ ['cleye', '^2.3.0'],
96
+ ['md-pen', '^0.0.2'],
97
+ ];
98
+ const depsTable = table(deps.map(([name, version]) => [
99
+ link(`https://npm.im/${name}`, bold(name)),
100
+ version,
87
101
  ]));
88
102
  -->
89
103
 
@@ -98,3 +112,7 @@ const depsTable = table(deps.map(([name, v]) => [
98
112
  ## Agent Skills
99
113
 
100
114
  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.
115
+
116
+ ## Related
117
+
118
+ - [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.
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- var h=Object.defineProperty;var a=(r,o)=>h(r,"name",{value:o,configurable:!0});import{register as g}from"node:module";import u from"node:fs/promises";import w from"node:path";import{cli as E}from"cleye";import{pathToFileURL as y}from"node:url";import{p as M,b as $,E as R,M as b,C,a as S}from"./parse-markdown-ChGEvmW3.mjs";import"md4x";const k=a(r=>{if(r==null)throw new Error(`Cannot interpolate ${r} \u2014 this is likely a bug in your script`);return typeof r=="string"?r:typeof r=="object"?JSON.stringify(r):String(r)},"coerceValue"),v=a(async(r,o)=>{const{markers:e}=M(r);if(e.length===0)return r;const n=await import(y(o).href),f=$(e),m=new Map,d=a((t,l)=>{const i=m.get(t);if(i!==void 0)return i;const s=k(n[`${R}${t}`]);if(s.includes(b)||s.includes(`${C}
3
- `)||s.includes(S))throw new Error(`Expression "${l}" produced a value containing mdeval comment syntax, which would corrupt the document on re-parse`);return m.set(t,s),s},"resolveValue"),c=[];let p=0;for(const t of e){const l=f.get(t.expression),i=d(l,t.expression);c.push(r.slice(p,t.contentStart),i),p=t.contentEnd}return c.push(r.slice(p)),c.join("")},"processSource");g("#md-loader",import.meta.url),globalThis.block=r=>`
2
+ var g=Object.defineProperty;var l=(r,o)=>g(r,"name",{value:o,configurable:!0});import m from"node:fs/promises";import h from"node:path";import{cli as w}from"cleye";import{glob as E}from"tinyglobby";import{$ as y}from"zx";import{register as $}from"node:module";import{pathToFileURL as b}from"node:url";import{p as x,b as M,E as S,M as P,C as R,a as _}from"./parse-markdown-ChGEvmW3.mjs";import"md4x";const T=l(r=>{if(r==null)throw new Error(`Cannot interpolate ${r} \u2014 this is likely a bug in your script`);return typeof r=="string"?r:typeof r=="object"?Symbol.toPrimitive in r?String(r):JSON.stringify(r):String(r)},"coerceValue");$("#md-loader",import.meta.url);const k=l(async(r,o)=>{const{markers:t}=x(r);if(t.length===0)return r;const s=await import(b(o).href),p=M(t),u=await Promise.all(Array.from(p).map(async([e,c])=>{const n=T(await s[`${S}${c}`]);if(n.includes(P)||n.includes(`${R}
3
+ `)||n.includes(_))throw new Error(`Expression "${e}" produced a value containing mdeval comment syntax, which would corrupt the document on re-parse`);return n})),i=[];let a=0;for(const e of t){const c=p.get(e.expression);i.push(r.slice(a,e.contentStart),u[c]),a=e.contentEnd}return i.push(r.slice(a)),i.join("")},"processSource");globalThis.block=r=>`
4
4
  ${String(r)}
5
- `;const x=E({name:"mdeval",parameters:["<files...>"]});await Promise.all(x._.files.map(async r=>{try{const o=w.resolve(r),e=await u.readFile(o,"utf8"),n=await v(e,o);n!==e&&(await u.writeFile(o,n,"utf8"),console.log(`Updated: ${r}`))}catch(o){console.error(`Error processing ${r}:`,o instanceof Error?o.message:o),process.exitCode=1}}));
5
+ `,globalThis.$=y;const C=w({name:"mdeval",parameters:["<files...>"]}),d=C._.files,N=d.some(r=>r.includes("node_modules")),f=await E(d,{ignore:N?[]:["**/node_modules/**"]});f.length===0&&(console.error("No files matched the given patterns"),process.exit(1)),await Promise.all(f.map(async r=>{try{const o=h.resolve(r),t=await m.readFile(o,"utf8"),s=await k(t,o);s!==t&&(await m.writeFile(o,s,"utf8"),console.log(`Updated: ${r}`))}catch(o){console.error(`Error processing ${r}:`,o instanceof Error?o.message:o),process.exitCode=1}}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdeval",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Evaluate JavaScript in markdown HTML comments and interpolate results in-place",
5
5
  "keywords": [
6
6
  "markdown",
@@ -29,6 +29,8 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "cleye": "^2.3.0",
32
- "md4x": "^0.0.25"
32
+ "md4x": "^0.0.25",
33
+ "tinyglobby": "^0.2.15",
34
+ "zx": "8.8.5-lite"
33
35
  }
34
36
  }
@@ -14,7 +14,7 @@ Two types of HTML comments — invisible when rendered:
14
14
  | Script block | `<!--mdeval\n...\n-->` | Define variables, imports, logic. Starts with `<!--mdeval` + newline |
15
15
  | Value marker | `<!--mdeval EXPR-->value<!--/mdeval-->` | Interpolate expression result. Starts with `<!--mdeval ` + space |
16
16
 
17
- Script blocks run as ESM with full Node.js access and top-level `await`. All blocks in a file merge into one module — imports and variables are shared across blocks and markers. `import.meta` points to the markdown file.
17
+ Script blocks run as ESM with full Node.js access and top-level `await`. All blocks in a file merge into one module — imports and variables are shared across blocks and markers. `import.meta` points to the markdown file. Marker expressions are auto-awaited, so promises resolve automatically.
18
18
 
19
19
  ## Marker Expressions
20
20
 
@@ -36,10 +36,38 @@ Duplicate expressions across markers are evaluated once and reused.
36
36
  | `string` | As-is |
37
37
  | `number`, `boolean`, `bigint` | `String(value)` |
38
38
  | `object`, `array` | `JSON.stringify(value)` |
39
+ | object with `Symbol.toPrimitive` | `String(value)` (e.g. zx `ProcessOutput`) |
40
+ | `Promise` | Auto-awaited, then coerced |
39
41
  | `undefined`, `null` | Error |
40
42
 
43
+ ## Globals
44
+
45
+ | Global | Description |
46
+ |--------|-------------|
47
+ | `block(value)` | Wraps value with newlines for block-level rendering |
48
+ | `$` | [zx](https://google.github.io/zx/) shell — run commands via tagged templates: `` $`git branch` `` |
49
+
50
+ ## CLI
51
+
52
+ ```bash
53
+ mdeval README.md # single file
54
+ mdeval README.md docs/guide.md # multiple files
55
+ mdeval "docs/**/*.md" # glob pattern
56
+ mdeval "**/*.md" # recursive — node_modules and dotdirs auto-excluded
57
+ ```
58
+
59
+ Supports full glob syntax including `**` recursive, `{a,b}` brace expansion, and `!` negation.
60
+
61
+ `node_modules` and hidden directories (`.git`, `.next`, etc.) are automatically excluded from glob expansion. No need to manually negate them. To explicitly include `node_modules`, reference it in the pattern: `mdeval "node_modules/pkg/*.md"`.
62
+
41
63
  ## Patterns
42
64
 
65
+ ### Shell commands
66
+
67
+ ````markdown
68
+ <!--mdeval $`git branch --show-current`-->main<!--/mdeval-->
69
+ ````
70
+
43
71
  ### Read package.json
44
72
 
45
73
  ````markdown
@@ -116,3 +144,5 @@ const x = String.fromCharCode(45, 45, 62);
116
144
  **Use IIFEs to co-locate logic.** In large docs, keep marker-specific computation inline instead of in a distant script block: `<!--mdeval (() => { ... })()-->`.
117
145
 
118
146
  **Markers in code blocks are safe.** Fenced, indented, and inline code won't be touched — safe to document mdeval syntax in your own README.
147
+
148
+ **Never create a .md file with only a script block and no real content.** Markdown files must contain actual prose/documentation. For shared logic or utilities, create a `.js` or `.ts` file and import it from your markdown instead.