mdeval 1.3.0 → 1.4.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
@@ -9,6 +9,9 @@ mdeval embeds JavaScript directly in your Markdown using HTML comments. The logi
9
9
 
10
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
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.
14
+
12
15
  ## Install
13
16
 
14
17
  ```bash
@@ -46,6 +49,69 @@ The file is updated in-place. You can pass multiple files or glob patterns: `mde
46
49
 
47
50
  `node_modules` and hidden directories are automatically excluded from glob expansion.
48
51
 
52
+ ## Example
53
+
54
+ **`CONTACTS.md`** — a contact list stored as data, rendered as a table when viewed:
55
+
56
+ ````markdown
57
+ <!--mdeval
58
+ import { table, link } from 'md-pen';
59
+
60
+ export const contacts = [
61
+ {
62
+ name: 'Alice Martin',
63
+ linkedin: 'https://linkedin.com/in/alice-martin',
64
+ notes: 'Met at JSConf 2023',
65
+ },
66
+ {
67
+ name: 'Bob Tanaka',
68
+ linkedin: 'https://linkedin.com/in/bob-tanaka',
69
+ notes: 'Former colleague at Acme Co.',
70
+ },
71
+ ];
72
+
73
+ export const findContact = name => contacts.find(c => c.name === name);
74
+
75
+ const contactsTable = table(
76
+ contacts.map(({ name, linkedin, notes }) => ({ Name: link(linkedin, name), Notes: notes })),
77
+ );
78
+ -->
79
+
80
+ <!--mdeval block(contactsTable)-->
81
+ | Name | Notes |
82
+ | - | - |
83
+ | [Alice Martin](https://linkedin.com/in/alice-martin) | Met at JSConf 2023 |
84
+ | [Bob Tanaka](https://linkedin.com/in/bob-tanaka) | Former colleague at Acme Co. |
85
+ <!--/mdeval-->
86
+ ````
87
+
88
+ **`TIMELINE.md`** — imports contacts and links them by LinkedIn:
89
+
90
+ ````markdown
91
+ <!--mdeval
92
+ import { link, ul, bold } from 'md-pen';
93
+
94
+ // Imports from the mdeval block!
95
+ import { findContact } from './CONTACTS.md';
96
+
97
+ const events = [
98
+ { date: '2023-10-15', description: 'Attended JSConf 2023' },
99
+ { date: '2024-03-01', description: `Coffee with ${link(findContact('Alice Martin').linkedin, 'Alice Martin')}` },
100
+ { date: '2024-11-08', description: 'Started new open-source project' },
101
+ ];
102
+
103
+ const list = ul(events.map(({ date, description }) => `${bold(date)} — ${description}`));
104
+ -->
105
+
106
+ <!--mdeval block(list)-->
107
+ - __2023-10-15__ — Attended JSConf 2023
108
+ - __2024-03-01__ — Coffee with [Alice Martin](https://linkedin.com/in/alice-martin)
109
+ - __2024-11-08__ — Started new open-source project
110
+ <!--/mdeval-->
111
+ ````
112
+
113
+ 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.
114
+
49
115
  ## Notes
50
116
 
51
117
  - 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.
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- var M=Object.defineProperty;var p=(o,r)=>M(o,"name",{value:r,configurable:!0});import m from"node:fs/promises";import $ from"node:path";import{cli as b}from"cleye";import{glob as x}from"tinyglobby";import{$ as S}from"zx";import{p as d,b as O,E as _,M as f,C as k,a as g,c as C,i as P}from"./parse-markdown-BBzZcXPS.mjs";import{register as R}from"node:module";import{pathToFileURL as T}from"node:url";import"md4x";const N=p(o=>{if(o==null)throw new Error(`Cannot interpolate ${o} \u2014 this is likely a bug in your script`);return typeof o=="string"?o:typeof o=="object"?Symbol.toPrimitive in o?String(o):JSON.stringify(o):String(o)},"coerceValue");R("#md-loader",import.meta.url);const v=p(async(o,r)=>{const{markers:t}=d(o);if(t.length===0)return o;const i=await import(T(r).href),n=O(t),w=await Promise.all(Array.from(n).map(async([e,l])=>{const s=N(await i[`${_}${l}`]);if(s.includes(f)||s.includes(`${k}
2
+ var M=Object.defineProperty;var p=(o,r)=>M(o,"name",{value:r,configurable:!0});import m from"node:fs/promises";import $ from"node:path";import{cli as b}from"cleye";import{glob as x}from"tinyglobby";import{$ as S}from"zx";import{p as d,b as O,E as _,M as f,C as k,a as g,c as C,i as P}from"./parse-markdown-B18EMsYy.mjs";import{register as R}from"node:module";import{pathToFileURL as T}from"node:url";import"micromark";const N=p(o=>{if(o==null)throw new Error(`Cannot interpolate ${o} \u2014 this is likely a bug in your script`);return typeof o=="string"?o:typeof o=="object"?Symbol.toPrimitive in o?String(o):JSON.stringify(o):String(o)},"coerceValue");R("#md-loader",import.meta.url);const v=p(async(o,r)=>{const{markers:t}=d(o);if(t.length===0)return o;const i=await import(T(r).href),n=O(t),w=await Promise.all(Array.from(n).map(async([e,l])=>{const s=N(await i[`${_}${l}`]);if(s.includes(f)||s.includes(`${k}
3
3
  `)||s.includes(g))throw new Error(`Expression "${e}" produced a value containing mdeval comment syntax, which would corrupt the document on re-parse`);return s})),a=[];let c=0;for(const e of t){const l=f.length+e.expression.length+C.length,s=e.start+l,E=e.end-g.length,y=n.get(e.expression);a.push(o.slice(c,s),w[y]),c=E}return a.push(o.slice(c)),a.join("")},"processSource");globalThis.block=o=>`
4
4
  ${String(o)}
5
5
  `,globalThis.$=S;const A=b({name:"mdeval",parameters:["<files...>"]}),u=A._.files,F=u.some(o=>o.includes("node_modules")),h=await x(u,{ignore:F?[]:["**/node_modules/**"]});h.length===0&&(console.error("No files matched the given patterns"),process.exit(1)),await Promise.all(h.map(async o=>{try{const r=$.resolve(o),t=await m.readFile(r,"utf8"),i=d(t);P(t,i)&&console.warn(`Warning: ${o} has no markdown content outside of mdeval blocks`);const n=await v(t,r);n!==t&&(await m.writeFile(r,n,"utf8"),console.log(`Updated: ${o}`))}catch(r){console.error(`Error processing ${o}:`,r instanceof Error?r.message:r),process.exitCode=1}}));
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- var p=Object.defineProperty;var i=(r,o)=>p(r,"name",{value:o,configurable:!0});import c from"node:fs/promises";import{fileURLToPath as m}from"node:url";import{p as f,b as u,E as d}from"./parse-markdown-BBzZcXPS.mjs";import"md4x";const l=i((r,o)=>{const e=r.map(t=>t.content).join(`
2
+ var p=Object.defineProperty;var i=(r,o)=>p(r,"name",{value:o,configurable:!0});import c from"node:fs/promises";import{fileURLToPath as m}from"node:url";import{p as f,b as u,E as d}from"./parse-markdown-B18EMsYy.mjs";import"micromark";const l=i((r,o)=>{const e=r.map(t=>t.content).join(`
3
3
  `),s=u(o);if(s.size===0)return e;const n=[...s].map(([t,a])=>`export const ${d}${a} = ${t};`);return`${e}
4
4
  ${n.join(`
5
5
  `)}`},"generateModule"),$=i(async(r,o,e)=>{if(!r.endsWith(".md"))return e(r,o);const s=await c.readFile(m(r),"utf8"),{scriptBlocks:n,markers:t}=f(s);return n.length===0&&t.length===0?e(r,o):{format:"module",source:l(n,t),shortCircuit:!0}},"load");export{$ as load};
@@ -0,0 +1,4 @@
1
+ var M=Object.defineProperty;var i=(t,n)=>M(t,"name",{value:n,configurable:!0});import{postprocess as O,parse as u,preprocess as C}from"micromark";const g="mdeval",d=`<!--${g}`,_=`${d} `,E=`<!--/${g}-->`,R=`__${g}_`,p="-->",S=i(t=>{const n=new Map;for(const{expression:s}of t)n.has(s)||n.set(s,n.size);return n},"buildExpressionMap"),k=i(t=>{const n=t.codePointAt(0)===65279?1:0,s=O(u().document().write(C()(t,void 0,!0))),r=[];for(const[o,e]of s)o==="enter"&&(e.type==="codeFenced"||e.type==="codeIndented"||e.type==="codeText")&&r.push([e.start.offset+n,e.end.offset+n]);return r},"findCodeRanges"),I=i(t=>{let n=null,s=0;return r=>{for(n===null&&(n=k(t));s<n.length&&n[s][1]<=r;)s+=1;return s<n.length&&r>=n[s][0]}},"createIsInCode"),T=i(t=>{if(!t.includes(d))return{scriptBlocks:[],markers:[]};const n=I(t),s=[],r=[];let o=0;for(;o<t.length;){const e=t.indexOf(d,o);if(e===-1)break;const c=e+d.length,h=t[c];if(h===`
2
+ `||h==="\r"&&t[c+1]===`
3
+ `){const f=h===`
4
+ `?c+1:c+2,a=t.indexOf(p,f);if(a===-1)break;const l=a+p.length;n(e)||s.push({content:t.slice(f,a),start:e,end:l}),o=l;continue}if(h===" "){const f=c+1,a=t.indexOf(p,f);if(a===-1)break;const l=a+p.length,x=t.indexOf(E,l);if(x===-1){o=l;continue}const m=x+E.length;n(e)||r.push({expression:t.slice(f,a),start:e,end:m}),o=m;continue}o=c}return{scriptBlocks:s,markers:r}},"parseMarkdown"),b=i((t,{scriptBlocks:n,markers:s})=>{if(n.length===0&&s.length===0)return!1;const r=[...n.map(e=>[e.start,e.end]),...s.map(e=>[e.start,e.end])].sort((e,c)=>e[0]-c[0]);let o=0;for(const[e,c]of r){if(t.slice(o,e).trim()!=="")return!1;o=c}return t.slice(o).trim()===""},"isOnlyMdeval");export{d as C,R as E,_ as M,E as a,S as b,p as c,b as i,T as p};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdeval",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Evaluate JavaScript in markdown HTML comments and interpolate results in-place",
5
5
  "keywords": [
6
6
  "markdown",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "cleye": "^2.3.0",
32
- "md4x": "^0.0.25",
32
+ "micromark": "^4.0.2",
33
33
  "tinyglobby": "^0.2.15",
34
34
  "zx": "8.8.5-lite"
35
35
  }
@@ -150,6 +150,37 @@ const x = String.fromCharCode(45, 45, 62);
150
150
 
151
151
  **Use IIFEs to co-locate logic.** In large docs, keep marker-specific computation inline instead of in a distant script block: `<!--mdeval (() => { ... })()-->`.
152
152
 
153
+ **Standalone markers suppress inline markdown in the value.** When a marker opens a line — standalone paragraph, list item, or blockquote — GitHub treats the entire line as a raw HTML block. Inline markdown in the value (`` `code` ``, `**bold**`, `[link](url)`) is not processed and appears literally.
154
+
155
+ ````markdown
156
+ <!-- ❌ backtick shows literally — line is an HTML block, not inline markdown -->
157
+ <!--mdeval expr-->`value`<!--/mdeval-->
158
+ ````
159
+
160
+ Use a multi-line marker so the value occupies its own lines, which are processed as normal markdown. Use [md-pen](https://github.com/privatenumber/md-pen) to generate formatted values:
161
+
162
+ ````markdown
163
+ <!--mdeval
164
+ import { code, bold, link } from 'md-pen';
165
+ -->
166
+
167
+ <!--mdeval code('asdf')-->
168
+ `asdf`
169
+ <!--/mdeval-->
170
+ ````
171
+
172
+ Markers inside headings are always inline — markdown in the value always renders there.
173
+
174
+ **Comments don't work in link URLs or image alt text.** GitHub escapes comment syntax in these positions:
175
+
176
+ ````markdown
177
+ <!-- ❌ comment is URL-encoded as href -->
178
+ [text](<!-- comment -->)
179
+
180
+ <!-- ❌ comment appears as visible alt text -->
181
+ ![<!-- comment -->](image.png)
182
+ ````
183
+
153
184
  **Markers in code blocks are safe.** Fenced, indented, and inline code won't be touched — safe to document mdeval syntax in your own README.
154
185
 
155
186
  **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.
@@ -1,3 +0,0 @@
1
- var _=Object.defineProperty;var d=(n,t)=>_(n,"name",{value:t,configurable:!0});import{parseAST as R}from"md4x";const E="mdeval",O=`<!--${E}`,u=`${O} `,M=`<!--/${E}-->`,y=`__${E}_`,m="-->",k=`${E}
2
- `,C=d(n=>{const t=new Map;for(const{expression:r}of n)t.has(r)||t.set(r,t.size);return t},"buildExpressionMap"),P=d((n,t)=>{const r=[],f=[];let l=0;const e=d(s=>{if(!Array.isArray(s))return;const[h,,...a]=s;if(h==="code"){const i=a[0];if(typeof i=="string"&&i.length>0){const c=t.indexOf(i,l);if(c!==-1){f.push([c,c+i.length]),l=c+i.length;return}const o=i.split(`
3
- `).filter(g=>g.length>0);if(o.length>0){const g=t.indexOf(o[0],l);if(g!==-1){let p=g+o[0].length;for(let x=1;x<o.length;x+=1){const A=t.indexOf(o[x],p);A!==-1&&(p=A+o[x].length)}f.push([g,p]),l=p}}}return}if(h===null&&typeof a[0]=="string"){const i=a[0],c=`<!--${i}-->`,o=t.indexOf(c,l);i.startsWith(k)&&o!==-1&&r.push({content:i.slice(k.length),start:o,end:o+c.length}),o!==-1&&(l=o+c.length);return}for(const i of a)Array.isArray(i)&&e(i)},"walk");for(const s of n.nodes)e(s);return{scriptBlocks:r,codeRanges:f}},"walkAst"),S=d((n,t)=>{let r=0;const f=d(s=>{for(;r<t.length&&t[r][1]<=s;)r+=1;if(r>=t.length)return!1;const[h,a]=t[r];return s>=h&&s<a},"isInCode"),l=[];let e=0;for(;e<n.length;){const s=n.indexOf(u,e);if(s===-1)break;const h=s+u.length,a=n.indexOf(m,h);if(a===-1)break;const i=a+m.length,c=n.indexOf(M,i);if(c===-1){e=i;continue}const o=c+M.length;f(s)||l.push({expression:n.slice(h,a),start:s,end:o}),e=o}return l},"findMarkers"),w=d(n=>{if(!n.includes(O))return{scriptBlocks:[],markers:[]};const t=R(n),{scriptBlocks:r,codeRanges:f}=P(t,n);return{scriptBlocks:r,markers:S(n,f)}},"parseMarkdown"),I=d((n,{scriptBlocks:t,markers:r})=>{if(t.length===0&&r.length===0)return!1;const f=[...t.map(e=>[e.start,e.end]),...r.map(e=>[e.start,e.end])].sort((e,s)=>e[0]-s[0]);let l=0;for(const[e,s]of f){if(n.slice(l,e).trim()!=="")return!1;l=s}return n.slice(l).trim()===""},"isOnlyMdeval");export{O as C,y as E,u as M,M as a,C as b,m as c,I as i,w as p};