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 +66 -0
- package/dist/cli.mjs +1 -1
- package/dist/md-loader.mjs +1 -1
- package/dist/parse-markdown-B18EMsYy.mjs +4 -0
- package/package.json +2 -2
- package/skills/mdeval/SKILL.md +31 -0
- package/dist/parse-markdown-BBzZcXPS.mjs +0 -3
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-
|
|
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}}));
|
package/dist/md-loader.mjs
CHANGED
|
@@ -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-
|
|
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
|
+
"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
|
-
"
|
|
32
|
+
"micromark": "^4.0.2",
|
|
33
33
|
"tinyglobby": "^0.2.15",
|
|
34
34
|
"zx": "8.8.5-lite"
|
|
35
35
|
}
|
package/skills/mdeval/SKILL.md
CHANGED
|
@@ -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
|
+

|
|
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};
|