remark-mdat 1.2.3 → 1.2.5

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.
Files changed (3) hide show
  1. package/dist/index.js +714 -3
  2. package/package.json +10 -9
  3. package/readme.md +9 -1
package/dist/index.js CHANGED
@@ -1,3 +1,714 @@
1
- import e from"cli-table3";import t from"picocolors";import{CONTINUE as n,SKIP as r,visit as i}from"unist-util-visit";import a from"node:path";import o from"json5";import{VFileMessage as s}from"vfile-message";import{z as c}from"zod";import{remark as l}from"remark";import u from"remark-gfm";import{fromHtml as d}from"hast-util-from-html";import{deepmerge as f}from"deepmerge-ts";const p=process?.versions?.node!==void 0,m={verbose:!1,log(...e){if(!this.verbose)return;let n=t.gray(`[Log]`);p?console.warn(n,...e):console.log(n,...e)},logPrefixed(e,...n){this.info(t.blue(`[${e}]`),...n)},info(...e){if(!this.verbose)return;let n=t.green(`[Info]`);p?console.warn(n,...e):console.info(n,...e)},infoPrefixed(e,...n){this.info(t.blue(`[${e}]`),...n)},warn(...e){console.warn(t.yellow(`[Warning]`),...e)},warnPrefixed(e,...n){this.warn(t.blue(`[${e}]`),...n)},error(...e){console.error(t.red(`[Error]`),...e)},errorPrefixed(e,...n){this.error(t.blue(`[${e}]`),...n)}};function h(e,t,n,r,i,a){let o,s;i===void 0||typeof i==`number`?(o=i??0,s=a??0):(o=i?.position?.start.line??0,s=i?.position?.start.column??0);let c={place:{start:{column:s,line:o},end:{column:s,line:o}},source:n},l=e.message(r,c);l.fatal=t===`error`?!0:t===`warn`?!1:void 0}function g(e){return{column:e.column,level:e.fatal?`error`:e.fatal===!1?`warn`:`info`,line:e.line,message:e.reason,source:e.source}}function _(e){return e.map(e=>v(e))}function v(e){let t={destinationPath:e.history.length>0?e.history.at(-1):void 0,errors:[],infos:[],sourcePath:e.history.at(0)??e.path,warnings:[]};t.sourcePath!==void 0&&(t.sourcePath=a.normalize(t.sourcePath));for(let n of e.messages){let e=g(n);e.level===`error`?t.errors.push(e):e.level===`warn`?t.warnings.push(e):t.infos.push(e)}return t}function ee(e){for(let n of e){let{destinationPath:e,errors:r,infos:i,sourcePath:a,warnings:o}=v(n);m.info(t.bold(`MDAT Report:`)),m.info(`\tFrom: ${t.blue(t.bold(a))}`),e!==void 0&&m.info(`\tTo: ${t.blue(t.bold(e))}`);for(let e of r)m.error(y(a,e));for(let e of o)m.warn(y(a,e));for(let e of i)m.info(y(a,e));r.length===0&&o.length===0?m.info(`No issues found in ${a}`):m.error(`${r.length} errors, ${o.length} warnings found in ${a}`)}}function y(e,n){let{column:r,level:i,line:a,message:o,source:s}=n,c=s?t.gray(`[${s}] `):``,l=a&&r?`:${a}:${r}`:``;return`${c}${b(o,i)} ${t.whiteBright(e+l)}`}function b(e,n){return e.replaceAll(/<!--.+-->/g,e=>n===`info`?t.green(e):n===`warn`?t.yellow(e):t.red(e))}function x(e,t,n){try{let r=te(e.value,n);return r===void 0?void 0:{...r,node:e,parent:t}}catch(t){throw t instanceof s?(t.line=e.position?.start.line,t):t instanceof Error?new s(t.message,e):new s(`Unknown error`,e)}}function te(e,t){if(!S(e))return;let{closingPrefix:n,keywordPrefix:r,metaCommentIdentifier:i}=t;if(n===``)throw new s(`closingPrefix must not be an empty string`);let a=e.trim(),c=a.replace(/^\s*<!-{2,}\s*/,``).replace(/\s*-{2,}>\s*$/,``),[l,...u]=c.split(/(\s+|\(|\{)/),d=l.startsWith(i)?`meta`:r!==``&&!l.startsWith(r)&&!l.startsWith(`${n}${r}`)?`native`:l.startsWith(n)?`close`:`open`;if(d===`meta`)return{content:w(c,i),html:a,type:d};if(d===`native`)return{content:c,html:a,type:d};let f=l;f.startsWith(n)&&(f=f.slice(n.length)),f.startsWith(r)&&(f=f.slice(r.length));let p=C(u.join(``));if(d===`open`||d===`close`){let e={};try{e=o.parse(p)}catch(e){if(e instanceof Error)throw new s(`Failed to parse comment options "${p}" for keyword "${f}": ${e.message}`)}return{closingPrefix:n,html:a,keyword:f,keywordPrefix:r,options:e,type:d}}}function S(e){let t=e.trim();return t.startsWith(`<!--`)&&t.endsWith(`-->`)}function C(e){return e=e.trim(),e=e.startsWith(`(`)?e.slice(1):e,e=e.endsWith(`)`)?e.slice(0,-1):e,e=e.trim(),!e.startsWith(`{`)&&!e.startsWith(`[`)&&(e=`{`+e),!e.endsWith(`}`)&&!e.endsWith(`]`)&&(e+=`}`),e}function w(e,t){return e=e.trim(),e=e.startsWith(t)?e.slice(t.length):e,e=e.endsWith(t)?e.slice(0,-t.length):e,e}function T(e){let t={};for(let[n,r]of Object.entries(e))if(typeof r==`string`)t[n]={applicationOrder:0,content:async()=>r,order:void 0,required:!1};else if(typeof r==`function`)t[n]={applicationOrder:0,content:async(e,t)=>r(e,t),order:void 0,required:!1};else if(Array.isArray(r))t[n]={applicationOrder:0,content:Object.values(T(Object.fromEntries(r.entries()))),order:void 0,required:!1};else if(typeof r.content==`string`){let e=r.content;t[n]={applicationOrder:r.applicationOrder??0,content:async()=>e,order:r.order??void 0,required:r.required??!1}}else if(Array.isArray(r.content))t[n]={applicationOrder:r.applicationOrder??0,content:Object.values(T(Object.fromEntries(r.content.entries()))),order:r.order??void 0,required:r.required??!1};else{let e=r.content;t[n]={applicationOrder:r.applicationOrder??0,content:async(t,n)=>e(t,n),order:r.order??void 0,required:r.required??!1}}return ne(t),t}function E(e){try{M.parse(e)}catch(e){if(e instanceof Error)throw TypeError(`Error validating rules: ${e.message}`)}}function ne(e){try{re.parse(e)}catch(e){if(e instanceof Error)throw TypeError(`Error validating rules: ${e.message}`)}}const D=c.any(),O=c.any(),k=c.lazy(()=>c.object({applicationOrder:c.number(),content:c.union([c.function().args(D.optional(),O.optional()).returns(c.promise(c.string())),c.array(k)]),order:c.number().optional(),required:c.boolean().default(!1)})),A=c.function().args(D.optional(),O.optional()).returns(c.union([c.string(),c.promise(c.string())])),j=c.lazy(()=>c.union([A,c.array(j),c.string(),c.object({applicationOrder:c.number().optional(),content:c.union([A,c.array(j),c.string()]),order:c.number().optional(),required:c.boolean().optional()})])),M=c.record(j).describe(`MDAT Rules`),re=c.record(k).describe(`MDAT Rules`);async function N(e,t,n,r=!1){if(Array.isArray(e.content)){let i=[];for(let[a,o]of e.content.entries()){let e=Array.isArray(t)?t.at(a):void 0;try{i.push(await N(o,e??{},n))}catch(e){if(r)throw e}}return i.join(`
2
-
3
- `)}try{return await e.content(t,n)}catch(e){throw r?e:Error(`Failed to expand content`,{cause:e})}}function ie(e){return oe(e)}function ae(e){let t=Object.keys(e);if(t.length!==1)throw Error(`Expected exactly one rule, found ${t.length}`);return t[0]}function oe(e){let t=Object.values(e);if(t.length===0)throw Error(`Found no entries in a "sole record" record. This should never happen`);if(t.length>1)throw Error(`Found multiple entries in "sole record" record. This should never happen`);return t[0]}async function P(e,t,r){let{closingPrefix:a,keywordPrefix:o,metaCommentIdentifier:s,paranoid:c,rules:l}=r;E(l);let u=T(l),d=[];i(e,`html`,(e,t,r)=>{if(r===void 0||t===void 0)return n;let i=x(e,r,{closingPrefix:a,keywordPrefix:o,metaCommentIdentifier:s});if(i!==void 0){let e=i.type===`open`||i.type===`close`?u[i.keyword]:void 0;d.push({...i,rule:e})}}),R(t,d,u,l),H(t,d),U(t,d,r),await se(t,d,e),c&&L(t,d,u,l),I(t,d),F(t,d,u,r)}async function se(e,t,n){for(let r of t)if(r.type===`open`&&r.rule!==void 0)try{(await N(r.rule,r.options,n,!0)).trim()===``&&h(e,r.rule.required?`error`:`warn`,`check`,`${r.html} returned an empty string.`,r.node)}catch(t){t instanceof Error&&h(e,r.rule.required?`error`:`warn`,`check`,`Could not get content for ${r.html}. ${t.message}`,r.node)}}function F(e,t,n,r){if(r.keywordPrefix===``)return;let i=Object.keys(n);for(let n of t)n.type===`native`&&i.includes(n.content)&&h(e,`warn`,`check`,`Missing prefix: ${n.html}`,n.node)}function I(e,t){for(let n of t)n.type===`open`&&n.rule===void 0&&h(e,`warn`,`check`,`Missing rule for: ${n.html}`,n.node)}function L(e,t,n,r){for(let[i,a]of Object.entries(n))!a.required&&!t.some(e=>e.type===`open`&&e.keyword===i)&&!V(i,r)&&h(e,`warn`,`check`,`Missing optional: <!-- ${i} -->`)}function R(e,t,n,r){for(let[i,a]of Object.entries(n))a.required&&!t.some(e=>e.type===`open`&&e.keyword===i)&&!V(i,r)&&h(e,`error`,`check`,`Missing required: <!-- ${i} -->`)}function z(e){return typeof e==`object`&&!Array.isArray(e)?e.content:e}function B(e){if(Array.isArray(e))return e;if(typeof e==`object`&&!Array.isArray(e)&&Array.isArray(e.content))return e.content}function V(e,t){let n=t[e],r=z(n);for(let e of Object.values(t)){let t=B(e);if(t!==void 0&&t.some(e=>z(e)===r))return!0}return!1}function H(n,r){let i=r.filter(e=>e.type===`open`&&e.rule?.order!==void 0),a=[...i].toSorted((e,t)=>{let n=e.rule?.order,r=t.rule?.order;if(n===void 0||r===void 0)throw Error(`Unexpected undefined rule order`);return n-r}),o=W(i),s=W(a),c=new e({head:[t.red(t.bold(`Current Order`)),t.green(t.bold(`Required Order`))],style:{compact:!0}});o.join(`,`)!==s.join(`,`)&&(c.push(...o.map((e,t)=>[e,s[t]])),h(n,`error`,`check`,`Out of order:\n${c.toString()}`))}function U(e,t,n){let{addMetaComment:r}=n,i=t.filter(e=>e.type===`meta`).length,a=typeof r==`string`?!0:r;a&&i!==1&&h(e,`error`,`check`,`Missing meta comment`),!a&&i!==0&&h(e,`error`,`check`,`Unexpected meta comment`),i>1&&h(e,`error`,`check`,`Multiple meta comments`)}function W(e){return e.map((e,t)=>{if(e.type===`open`||e.type===`close`)return`${t+1}. ${e.html}`;throw Error(`Unexpected comment type`)})}function G(e,t,r){let a;i(e,`html`,(e,i,o)=>{if(o===void 0||i===void 0)return n;let s=x(e,o,r);if(s===void 0||s.type===`native`)return n;if(s.type===`meta`)return o.children.splice(i,1),[n,i];if(s.type===`open`)return a=s,n;if(s.type===`close`){if(a===void 0)return h(t,`error`,`clean`,`Found closing marker without opening marker`,e),n;if(a.parent!==s.parent)return h(t,`error`,`clean`,`Opening marker doesn't share a parent`,e),n;if(a.keyword!==s.keyword)return h(t,`error`,`clean`,`Opening marker doesn't share a keyword`,e),n;let r=o.children.indexOf(a.node),c=o.children.indexOf(s.node)-r+1;return o.children.splice(r+1,c-1),a=void 0,[n,i-c+1]}})}async function K(e,t,r){let{addMetaComment:a,closingPrefix:o,keywordPrefix:s,metaCommentIdentifier:c,rules:d}=r;E(d);let f=T(d),p=[];i(e,`html`,(e,t,r)=>{if(r===void 0||t===void 0)return n;let i=x(e,r,{closingPrefix:o,keywordPrefix:s,metaCommentIdentifier:c});i?.type===`open`&&f[i.keyword]!==void 0&&p.push(i)}),p.sort((e,t)=>f[e.keyword].applicationOrder-f[t.keyword].applicationOrder);for(let n of p){let{closingPrefix:r,html:i,keyword:a,keywordPrefix:o,node:s,options:c,parent:d}=n,p=f[a],m=``;try{m=await N(p,c,e),m.trim()===``&&h(t,`error`,`expand`,`Got empty content when expanding ${i}`,s)}catch(e){if(e instanceof Error){let n=e.cause instanceof Error?`: ${e.cause.message}`:``;h(t,`error`,`expand`,`Caught error expanding ${i}, Error message: "${e.message}${n}"`,s)}continue}let g=l().use(u).parse(m).children,_={type:`html`,value:`<!-- ${r}${o}${a} -->`},v=d.children.indexOf(s);d.children.splice(v+1,0,...g,_),h(t,`info`,`expand`,`Expanded: ${i}`,s)}if(a){let t={type:`html`,value:`<!--${c} ${typeof a==`string`?a:`Warning: Content inside HTML comment blocks was generated by mdat and may be overwritten.`} ${c}-->`};e.children.unshift(t)}}function q(e,t){i(e,`html`,(e,r,i)=>{if(i===void 0||r===void 0)return n;let a=ce(e);a.length>1&&(h(t,`warn`,`split`,`Multiple comments in a single HTML node.`,e),i.children.splice(r,1,{children:a,type:`paragraph`}))})}function ce(e){let t=d(e.value,{fragment:!0}),a=[];return i(t,t=>t.type===`root`?n:t.type===`text`?(a.push({position:J(t.position,e.position?.start),type:`text`,value:Y(e,t)}),n):(a.push({position:J(t.position,e.position?.start),type:`html`,value:Y(e,t)}),r)),a}function J(e,t){if(e===void 0||t===void 0)return;let n=e.start.line-1+t.line,r=e.end.line-1+t.line;return{start:{column:e.start.line===1?e.start.column-1+t.column:e.start.column,line:n,offset:e.start.offset!==void 0&&t.offset!==void 0?e.start.offset+t.offset:void 0},end:{column:e.end.line===1?e.end.column-1+t.column:e.end.column,line:r,offset:e.end.offset!==void 0&&t.offset!==void 0?e.end.offset+t.offset:void 0}}}function Y(e,t){if(t.position===void 0)throw Error(`Hast ElementContent node has no position!`);return e.value.slice(t.position.start.offset,t.position.end.offset)}async function X(e,t,n){let{addMetaComment:r,closingPrefix:i,keywordPrefix:a,metaCommentIdentifier:o,rules:s}=n;q(e,t),G(e,t,{closingPrefix:i,keywordPrefix:a,metaCommentIdentifier:o}),await K(e,t,{addMetaComment:r,closingPrefix:i,keywordPrefix:a,metaCommentIdentifier:o,rules:s}),await P(e,t,{addMetaComment:r,closingPrefix:i,keywordPrefix:a,metaCommentIdentifier:o,paranoid:!1,rules:s})}function Z(e){return Array.isArray(e)?e.map(e=>e&&typeof e==`object`?Z(e):e).filter(e=>e!==void 0):Object.entries(e).map(([e,t])=>[e,t&&typeof t==`object`?Z(t):t]).reduce((e,[t,n])=>n===void 0?e:{...e,[t]:n},{})}function Q(...e){return f(...e.map((e,t)=>t===0?e:Z(e)))}const le={addMetaComment:!1,closingPrefix:`/`,keywordPrefix:``,metaCommentIdentifier:`+`,rules:{mdat:`Powered by the Markdown Autophagic Template system: [mdat](https://github.com/kitschpatrol/mdat).`}},$=c.object({addMetaComment:c.union([c.boolean(),c.string()]).optional(),closingPrefix:c.string().min(1).optional(),keywordPrefix:c.string().optional(),metaCommentIdentifier:c.string().optional(),rules:M.optional()}).describe(`MDAT Options`),ue=function(e){let t=Q(le,e);return async function(e,n){await X(e,n,t)}};export{Q as deepMergeDefined,ue as default,_ as getMdatReports,ie as getSoleRule,ae as getSoleRuleKey,m as log,X as mdat,P as mdatCheck,G as mdatClean,K as mdatExpand,q as mdatSplit,$ as optionsSchema,ee as reporterMdat,M as rulesSchema};
1
+ import Table from "cli-table3";
2
+ import picocolors from "picocolors";
3
+ import { CONTINUE, SKIP, visit } from "unist-util-visit";
4
+ import path from "node:path";
5
+ import json5 from "json5";
6
+ import { VFileMessage } from "vfile-message";
7
+ import { z } from "zod";
8
+ import { remark } from "remark";
9
+ import remarkGfm from "remark-gfm";
10
+ import { fromHtml } from "hast-util-from-html";
11
+ import { deepmerge } from "deepmerge-ts";
12
+ //#region src/lib/mdat/log.ts
13
+ const isNode = process?.versions?.node !== void 0;
14
+ const log = {
15
+ verbose: false,
16
+ log(...data) {
17
+ if (!this.verbose) return;
18
+ const levelPrefix = picocolors.gray("[Log]");
19
+ if (isNode) console.warn(levelPrefix, ...data);
20
+ else console.log(levelPrefix, ...data);
21
+ },
22
+ logPrefixed(prefix, ...data) {
23
+ this.info(picocolors.blue(`[${prefix}]`), ...data);
24
+ },
25
+ info(...data) {
26
+ if (!this.verbose) return;
27
+ const levelPrefix = picocolors.green("[Info]");
28
+ if (isNode) console.warn(levelPrefix, ...data);
29
+ else console.info(levelPrefix, ...data);
30
+ },
31
+ infoPrefixed(prefix, ...data) {
32
+ this.info(picocolors.blue(`[${prefix}]`), ...data);
33
+ },
34
+ warn(...data) {
35
+ console.warn(picocolors.yellow("[Warning]"), ...data);
36
+ },
37
+ warnPrefixed(prefix, ...data) {
38
+ this.warn(picocolors.blue(`[${prefix}]`), ...data);
39
+ },
40
+ error(...data) {
41
+ console.error(picocolors.red("[Error]"), ...data);
42
+ },
43
+ errorPrefixed(prefix, ...data) {
44
+ this.error(picocolors.blue(`[${prefix}]`), ...data);
45
+ }
46
+ };
47
+ //#endregion
48
+ //#region src/lib/mdat/mdat-log.ts
49
+ function saveLog(file, level, source, message, lineOrNode, maybeColumn) {
50
+ let line;
51
+ let column;
52
+ if (lineOrNode === void 0 || typeof lineOrNode === "number") {
53
+ line = lineOrNode ?? 0;
54
+ column = maybeColumn ?? 0;
55
+ } else {
56
+ line = lineOrNode?.position?.start.line ?? 0;
57
+ column = lineOrNode?.position?.start.column ?? 0;
58
+ }
59
+ const options = {
60
+ place: {
61
+ start: {
62
+ column,
63
+ line
64
+ },
65
+ end: {
66
+ column,
67
+ line
68
+ }
69
+ },
70
+ source
71
+ };
72
+ const vFileMessage = file.message(message, options);
73
+ vFileMessage.fatal = level === "error" ? true : level === "warn" ? false : void 0;
74
+ }
75
+ function vFileMessageToMdatMessage(vFileMessage) {
76
+ return {
77
+ column: vFileMessage.column,
78
+ level: vFileMessage.fatal ? "error" : vFileMessage.fatal === false ? "warn" : "info",
79
+ line: vFileMessage.line,
80
+ message: vFileMessage.reason,
81
+ source: vFileMessage.source
82
+ };
83
+ }
84
+ function getMdatReports(files) {
85
+ return files.map((file) => getMdatReport(file));
86
+ }
87
+ function getMdatReport(file) {
88
+ const mdatFileReport = {
89
+ destinationPath: file.history.length > 0 ? file.history.at(-1) : void 0,
90
+ errors: [],
91
+ infos: [],
92
+ sourcePath: file.history.at(0) ?? file.path,
93
+ warnings: []
94
+ };
95
+ if (mdatFileReport.sourcePath !== void 0) mdatFileReport.sourcePath = path.normalize(mdatFileReport.sourcePath);
96
+ for (const message of file.messages) {
97
+ const mdatMessage = vFileMessageToMdatMessage(message);
98
+ if (mdatMessage.level === "error") mdatFileReport.errors.push(mdatMessage);
99
+ else if (mdatMessage.level === "warn") mdatFileReport.warnings.push(mdatMessage);
100
+ else mdatFileReport.infos.push(mdatMessage);
101
+ }
102
+ return mdatFileReport;
103
+ }
104
+ function reporterMdat(files) {
105
+ for (const file of files) {
106
+ const { destinationPath, errors, infos, sourcePath, warnings } = getMdatReport(file);
107
+ log.info(picocolors.bold("MDAT Report:"));
108
+ log.info(`\tFrom: ${picocolors.blue(picocolors.bold(sourcePath))}`);
109
+ if (destinationPath !== void 0) log.info(`\tTo: ${picocolors.blue(picocolors.bold(destinationPath))}`);
110
+ for (const message of errors) log.error(mdatMessageToLogString(sourcePath, message));
111
+ for (const message of warnings) log.warn(mdatMessageToLogString(sourcePath, message));
112
+ for (const message of infos) log.info(mdatMessageToLogString(sourcePath, message));
113
+ if (errors.length === 0 && warnings.length === 0) log.info(`No issues found in ${sourcePath}`);
114
+ else log.error(`${errors.length} errors, ${warnings.length} warnings found in ${sourcePath}`);
115
+ }
116
+ }
117
+ function mdatMessageToLogString(sourcePath, mdatMessage) {
118
+ const { column, level, line, message, source } = mdatMessage;
119
+ const resolvedSource = source ? picocolors.gray(`[${source}] `) : "";
120
+ const lineColumn = line && column ? `:${line}:${column}` : "";
121
+ return `${resolvedSource}${highlightComments(message, level)} ${picocolors.whiteBright(sourcePath + lineColumn)}`;
122
+ }
123
+ function highlightComments(text, level) {
124
+ return text.replaceAll(/<!--.+-->/g, (match) => level === "info" ? picocolors.green(match) : level === "warn" ? picocolors.yellow(match) : picocolors.red(match));
125
+ }
126
+ //#endregion
127
+ //#region src/lib/mdat/parse.ts
128
+ /**
129
+ * Parse an Mdast HTML comment node into structured data.
130
+ * @returns A discriminated union of CommentMarkerNode based on comment type, or
131
+ * undefined if the node is not a comment.
132
+ */
133
+ function parseCommentNode(node, parent, options) {
134
+ try {
135
+ const result = parseComment(node.value, options);
136
+ if (result === void 0) return;
137
+ return {
138
+ ...result,
139
+ node,
140
+ parent
141
+ };
142
+ } catch (error) {
143
+ if (error instanceof VFileMessage) {
144
+ error.line = node.position?.start.line;
145
+ throw error;
146
+ } else if (error instanceof Error) throw new VFileMessage(error.message, node);
147
+ else throw new VFileMessage("Unknown error", node);
148
+ }
149
+ }
150
+ /**
151
+ * Parse any comment string into structured data.
152
+ * @returns A discriminated union of CommentMarker based on comment type, or
153
+ * undefined if the node is not a comment.
154
+ */
155
+ function parseComment(text, options) {
156
+ if (!isComment(text)) return;
157
+ const { closingPrefix, keywordPrefix, metaCommentIdentifier } = options;
158
+ if (closingPrefix === "") throw new VFileMessage("closingPrefix must not be an empty string");
159
+ const commentHtml = text.trim();
160
+ const commentBody = commentHtml.replace(/^\s*<!-{2,}\s*/, "").replace(/\s*-{2,}>\s*$/, "");
161
+ const [rawKeyword, ...argumentParts] = commentBody.split(/(\s+|\(|\{)/);
162
+ const type = rawKeyword.startsWith(metaCommentIdentifier) ? "meta" : keywordPrefix !== "" && !rawKeyword.startsWith(keywordPrefix) && !rawKeyword.startsWith(`${closingPrefix}${keywordPrefix}`) ? "native" : rawKeyword.startsWith(closingPrefix) ? "close" : "open";
163
+ if (type === "meta") return {
164
+ content: trimMetaIdentifiers(commentBody, metaCommentIdentifier),
165
+ html: commentHtml,
166
+ type
167
+ };
168
+ if (type === "native") return {
169
+ content: commentBody,
170
+ html: commentHtml,
171
+ type
172
+ };
173
+ let keyword = rawKeyword;
174
+ if (keyword.startsWith(closingPrefix)) keyword = keyword.slice(closingPrefix.length);
175
+ if (keyword.startsWith(keywordPrefix)) keyword = keyword.slice(keywordPrefix.length);
176
+ const optionText = makeValidJson(argumentParts.join(""));
177
+ if (type === "open" || type === "close") {
178
+ let options = {};
179
+ try {
180
+ options = json5.parse(optionText);
181
+ } catch (error) {
182
+ if (error instanceof Error) throw new VFileMessage(`Failed to parse comment options "${optionText}" for keyword "${keyword}": ${error.message}`);
183
+ }
184
+ return {
185
+ closingPrefix,
186
+ html: commentHtml,
187
+ keyword,
188
+ keywordPrefix,
189
+ options,
190
+ type
191
+ };
192
+ }
193
+ }
194
+ function isComment(text) {
195
+ const trimmed = text.trim();
196
+ return trimmed.startsWith("<!--") && trimmed.endsWith("-->");
197
+ }
198
+ function makeValidJson(text) {
199
+ text = text.trim();
200
+ text = text.startsWith("(") ? text.slice(1) : text;
201
+ text = text.endsWith(")") ? text.slice(0, -1) : text;
202
+ text = text.trim();
203
+ if (!text.startsWith("{") && !text.startsWith("[")) text = "{" + text;
204
+ if (!text.endsWith("}") && !text.endsWith("]")) text += "}";
205
+ return text;
206
+ }
207
+ function trimMetaIdentifiers(text, metaCommentIdentifier) {
208
+ text = text.trim();
209
+ text = text.startsWith(metaCommentIdentifier) ? text.slice(metaCommentIdentifier.length) : text;
210
+ text = text.endsWith(metaCommentIdentifier) ? text.slice(0, -metaCommentIdentifier.length) : text;
211
+ return text;
212
+ }
213
+ //#endregion
214
+ //#region src/lib/mdat/rules.ts
215
+ function normalizeRules(rules) {
216
+ const normalizedRules = {};
217
+ for (const [keyword, rule] of Object.entries(rules)) if (typeof rule === "string") normalizedRules[keyword] = {
218
+ applicationOrder: 0,
219
+ content: async () => rule,
220
+ order: void 0,
221
+ required: false
222
+ };
223
+ else if (typeof rule === "function") normalizedRules[keyword] = {
224
+ applicationOrder: 0,
225
+ content: async (options, tree) => rule(options, tree),
226
+ order: void 0,
227
+ required: false
228
+ };
229
+ else if (Array.isArray(rule)) normalizedRules[keyword] = {
230
+ applicationOrder: 0,
231
+ content: Object.values(normalizeRules(Object.fromEntries(rule.entries()))),
232
+ order: void 0,
233
+ required: false
234
+ };
235
+ else if (typeof rule.content === "string") {
236
+ const ruleContent = rule.content;
237
+ normalizedRules[keyword] = {
238
+ applicationOrder: rule.applicationOrder ?? 0,
239
+ content: async () => ruleContent,
240
+ order: rule.order ?? void 0,
241
+ required: rule.required ?? false
242
+ };
243
+ } else if (Array.isArray(rule.content)) normalizedRules[keyword] = {
244
+ applicationOrder: rule.applicationOrder ?? 0,
245
+ content: Object.values(normalizeRules(Object.fromEntries(rule.content.entries()))),
246
+ order: rule.order ?? void 0,
247
+ required: rule.required ?? false
248
+ };
249
+ else {
250
+ const ruleContent = rule.content;
251
+ normalizedRules[keyword] = {
252
+ applicationOrder: rule.applicationOrder ?? 0,
253
+ content: async (options, tree) => ruleContent(options, tree),
254
+ order: rule.order ?? void 0,
255
+ required: rule.required ?? false
256
+ };
257
+ }
258
+ validateNormalizedRules(normalizedRules);
259
+ return normalizedRules;
260
+ }
261
+ function validateRules(rules) {
262
+ try {
263
+ rulesSchema.parse(rules);
264
+ } catch (error) {
265
+ if (error instanceof Error) throw new TypeError(`Error validating rules: ${error.message}`);
266
+ }
267
+ }
268
+ function validateNormalizedRules(rules) {
269
+ try {
270
+ normalizedRulesSchema.parse(rules);
271
+ } catch (error) {
272
+ if (error instanceof Error) throw new TypeError(`Error validating rules: ${error.message}`);
273
+ }
274
+ }
275
+ const jsonValueSchema = z.any();
276
+ const rootSchema = z.any();
277
+ const normalizedRuleSchema = z.lazy(() => z.object({
278
+ applicationOrder: z.number(),
279
+ content: z.union([z.function().args(jsonValueSchema.optional(), rootSchema.optional()).returns(z.promise(z.string())), z.array(normalizedRuleSchema)]),
280
+ order: z.number().optional(),
281
+ required: z.boolean().default(false)
282
+ }));
283
+ const ruleContentFunctionSchema = z.function().args(jsonValueSchema.optional(), rootSchema.optional()).returns(z.union([z.string(), z.promise(z.string())]));
284
+ const ruleSchema = z.lazy(() => z.union([
285
+ ruleContentFunctionSchema,
286
+ z.array(ruleSchema),
287
+ z.string(),
288
+ z.object({
289
+ applicationOrder: z.number().optional(),
290
+ content: z.union([
291
+ ruleContentFunctionSchema,
292
+ z.array(ruleSchema),
293
+ z.string()
294
+ ]),
295
+ order: z.number().optional(),
296
+ required: z.boolean().optional()
297
+ })
298
+ ]));
299
+ const rulesSchema = z.record(ruleSchema).describe("MDAT Rules");
300
+ const normalizedRulesSchema = z.record(normalizedRuleSchema).describe("MDAT Rules");
301
+ /**
302
+ * Compound rule helpers, used in both "expand" and "check" utilities
303
+ */
304
+ async function getRuleContent(rule, options, tree, check = false) {
305
+ if (Array.isArray(rule.content)) {
306
+ const subruleContent = [];
307
+ for (const [index, subrule] of rule.content.entries()) {
308
+ const subruleOptions = Array.isArray(options) ? options.at(index) : void 0;
309
+ try {
310
+ subruleContent.push(await getRuleContent(subrule, subruleOptions ?? {}, tree));
311
+ } catch (error) {
312
+ if (check) throw error;
313
+ }
314
+ }
315
+ return subruleContent.join("\n\n");
316
+ }
317
+ try {
318
+ return await rule.content(options, tree);
319
+ } catch (error) {
320
+ if (check) throw error;
321
+ throw new Error("Failed to expand content", { cause: error });
322
+ }
323
+ }
324
+ /**
325
+ * Returns the rule value from a single-rule record.
326
+ * Useful when aliasing rules or invoking them programmatically.
327
+ *
328
+ * Throws if there are no entries or more than one entry.
329
+ */
330
+ function getSoleRule(rules) {
331
+ return getSoleRecord(rules);
332
+ }
333
+ /**
334
+ * Returns the rule key from a single-rule record.
335
+ * Useful for comment placeholder validation.
336
+ *
337
+ * Throws if there are no entries or more than one entry.
338
+ */
339
+ function getSoleRuleKey(rules) {
340
+ const keys = Object.keys(rules);
341
+ if (keys.length !== 1) throw new Error(`Expected exactly one rule, found ${keys.length}`);
342
+ return keys[0];
343
+ }
344
+ /**
345
+ * Get the sole entry in a record.
346
+ *
347
+ * Useful for working with Rules records
348
+ * that are only supposed to contain a single rule.
349
+ * @param record The record to get the sole entry from
350
+ * @returns The value of the sole entry in the record
351
+ * @throws {Error} If there are no entries or more than one entry
352
+ */
353
+ function getSoleRecord(record) {
354
+ const recordValues = Object.values(record);
355
+ if (recordValues.length === 0) throw new Error("Found no entries in a \"sole record\" record. This should never happen");
356
+ if (recordValues.length > 1) throw new Error("Found multiple entries in \"sole record\" record. This should never happen");
357
+ return recordValues[0];
358
+ }
359
+ //#endregion
360
+ //#region src/lib/mdast-utils/mdast-util-mdat-check.ts
361
+ /**
362
+ * Mdast utility function to check mdat source document, and output.
363
+ */
364
+ async function mdatCheck(tree, file, options) {
365
+ const { closingPrefix, keywordPrefix, metaCommentIdentifier, paranoid, rules: rawRules } = options;
366
+ validateRules(rawRules);
367
+ const rules = normalizeRules(rawRules);
368
+ const commentMarkers = [];
369
+ visit(tree, "html", (node, index, parent) => {
370
+ if (parent === void 0 || index === void 0) return CONTINUE;
371
+ const commentMarker = parseCommentNode(node, parent, {
372
+ closingPrefix,
373
+ keywordPrefix,
374
+ metaCommentIdentifier
375
+ });
376
+ if (commentMarker !== void 0) {
377
+ const rule = commentMarker.type === "open" || commentMarker.type === "close" ? rules[commentMarker.keyword] : void 0;
378
+ commentMarkers.push({
379
+ ...commentMarker,
380
+ rule
381
+ });
382
+ }
383
+ });
384
+ checkMissingRequiredComments(file, commentMarkers, rules, rawRules);
385
+ checkCommentOrder(file, commentMarkers);
386
+ checkMetaCommentPresence(file, commentMarkers, options);
387
+ await checkRulesReturnedContent(file, commentMarkers, tree);
388
+ if (paranoid) checkMissingOptionalComments(file, commentMarkers, rules, rawRules);
389
+ checkMissingRules(file, commentMarkers);
390
+ checkMissingPrefix(file, commentMarkers, rules, options);
391
+ }
392
+ /**
393
+ * Check that all the rules are working by getting their content
394
+ */
395
+ async function checkRulesReturnedContent(file, comments, tree) {
396
+ for (const comment of comments) if (comment.type === "open" && comment.rule !== void 0) try {
397
+ if ((await getRuleContent(comment.rule, comment.options, tree, true)).trim() === "") saveLog(file, comment.rule.required ? "error" : "warn", "check", `${comment.html} returned an empty string.`, comment.node);
398
+ } catch (error) {
399
+ if (error instanceof Error) saveLog(file, comment.rule.required ? "error" : "warn", "check", `Could not get content for ${comment.html}. ${error.message}`, comment.node);
400
+ }
401
+ }
402
+ /**
403
+ * Check for comments with missing prefix (have an un-prefixed comment that matches a rule)
404
+ */
405
+ function checkMissingPrefix(file, comments, rules, options) {
406
+ if (options.keywordPrefix === "") return;
407
+ const ruleKeywords = Object.keys(rules);
408
+ for (const comment of comments) if (comment.type === "native" && ruleKeywords.includes(comment.content)) saveLog(file, "warn", "check", `Missing prefix: ${comment.html}`, comment.node);
409
+ }
410
+ /**
411
+ * Check for missing "optional" rules. These are instances where we have the comment, but not the rule
412
+ */
413
+ function checkMissingRules(file, comments) {
414
+ for (const comment of comments) if (comment.type === "open" && comment.rule === void 0) saveLog(file, "warn", "check", `Missing rule for: ${comment.html}`, comment.node);
415
+ }
416
+ /**
417
+ * Check for missing optional comments. We have defined the rule, but not written a matching comment.
418
+ */
419
+ function checkMissingOptionalComments(file, comments, rules, rawRules) {
420
+ for (const [keyword, rule] of Object.entries(rules)) if (!rule.required && !comments.some((comment) => comment.type === "open" && comment.keyword === keyword) && !satisfiedByCompoundRule(keyword, rawRules)) saveLog(file, "warn", "check", `Missing optional: <!-- ${keyword} -->`);
421
+ }
422
+ /**
423
+ * Check for missing required comments.
424
+ * The rule set includes a rule with `required: true`, but no matching comment was found in the document.
425
+ */
426
+ function checkMissingRequiredComments(file, comments, rules, rawRules) {
427
+ for (const [keyword, rule] of Object.entries(rules)) if (rule.required && !comments.some((comment) => comment.type === "open" && comment.keyword === keyword) && !satisfiedByCompoundRule(keyword, rawRules)) saveLog(file, "error", "check", `Missing required: <!-- ${keyword} -->`);
428
+ }
429
+ /**
430
+ * Extract the content value from a raw rule, unwrapping object-form rules.
431
+ */
432
+ function getRawRuleContent(rule) {
433
+ if (typeof rule === "object" && !Array.isArray(rule)) return rule.content;
434
+ return rule;
435
+ }
436
+ /**
437
+ * Get the sub-rule array from a compound rule, if it is one.
438
+ */
439
+ function getCompoundSubRules(rule) {
440
+ if (Array.isArray(rule)) return rule;
441
+ if (typeof rule === "object" && !Array.isArray(rule) && Array.isArray(rule.content)) return rule.content;
442
+ }
443
+ /**
444
+ * Helper to see if a rule keyword is covered by a compound rule in the rule set.
445
+ * Checks whether any compound rule contains the same raw content value (by reference)
446
+ * as the rule for the given keyword. This works when the sub-rule was imported
447
+ * directly into the compound rule definition.
448
+ */
449
+ function satisfiedByCompoundRule(keyword, rawRules) {
450
+ const rawRule = rawRules[keyword];
451
+ const ruleContent = getRawRuleContent(rawRule);
452
+ for (const otherRule of Object.values(rawRules)) {
453
+ const subRules = getCompoundSubRules(otherRule);
454
+ if (subRules === void 0) continue;
455
+ if (subRules.some((subRule) => getRawRuleContent(subRule) === ruleContent)) return true;
456
+ }
457
+ return false;
458
+ }
459
+ /**
460
+ * Check if comment order in document is different from order specified in the rules
461
+ */
462
+ function checkCommentOrder(file, comments) {
463
+ const commentsInOrderOfAppearance = comments.filter((commentMarker) => commentMarker.type === "open" && commentMarker.rule?.order !== void 0);
464
+ const commentsInCorrectOrder = [...commentsInOrderOfAppearance].toSorted((a, b) => {
465
+ const orderA = a.rule?.order;
466
+ const orderB = b.rule?.order;
467
+ if (orderA === void 0 || orderB === void 0) throw new Error("Unexpected undefined rule order");
468
+ return orderA - orderB;
469
+ });
470
+ const currentOrderList = commentOrderList(commentsInOrderOfAppearance);
471
+ const correctOrderList = commentOrderList(commentsInCorrectOrder);
472
+ const table = new Table({
473
+ head: [picocolors.red(picocolors.bold("Current Order")), picocolors.green(picocolors.bold("Required Order"))],
474
+ style: { compact: true }
475
+ });
476
+ if (currentOrderList.join(",") !== correctOrderList.join(",")) {
477
+ table.push(...currentOrderList.map((currentOrder, index) => [currentOrder, correctOrderList[index]]));
478
+ saveLog(file, "error", "check", `Out of order:\n${table.toString()}`);
479
+ }
480
+ }
481
+ /**
482
+ * Check that meta presence / absence comment matches options.
483
+ */
484
+ function checkMetaCommentPresence(file, comments, options) {
485
+ const { addMetaComment } = options;
486
+ const metaCommentCount = comments.filter((comment) => comment.type === "meta").length;
487
+ const shouldHaveMetaComment = typeof addMetaComment === "string" ? true : addMetaComment;
488
+ if (shouldHaveMetaComment && metaCommentCount !== 1) saveLog(file, "error", "check", `Missing meta comment`);
489
+ if (!shouldHaveMetaComment && metaCommentCount !== 0) saveLog(file, "error", "check", `Unexpected meta comment`);
490
+ if (metaCommentCount > 1) saveLog(file, "error", "check", `Multiple meta comments`);
491
+ }
492
+ function commentOrderList(comments) {
493
+ return comments.map((comment, index) => {
494
+ if (comment.type === "open" || comment.type === "close") return `${index + 1}. ${comment.html}`;
495
+ throw new Error("Unexpected comment type");
496
+ });
497
+ }
498
+ //#endregion
499
+ //#region src/lib/mdast-utils/mdast-util-mdat-clean.ts
500
+ /**
501
+ * Collapses any expanded mdat comments and removes meta comments,
502
+ * effectively resetting the document to its pre-expansion state. No-op if no
503
+ * mdat comments are found.
504
+ */
505
+ function mdatClean(tree, file, options) {
506
+ let lastOpenMarker;
507
+ visit(tree, "html", (node, index, parent) => {
508
+ if (parent === void 0 || index === void 0) return CONTINUE;
509
+ const marker = parseCommentNode(node, parent, options);
510
+ if (marker === void 0 || marker.type === "native") return CONTINUE;
511
+ if (marker.type === "meta") {
512
+ parent.children.splice(index, 1);
513
+ return [CONTINUE, index];
514
+ }
515
+ if (marker.type === "open") {
516
+ lastOpenMarker = marker;
517
+ return CONTINUE;
518
+ }
519
+ if (marker.type === "close") {
520
+ if (lastOpenMarker === void 0) {
521
+ saveLog(file, "error", "clean", "Found closing marker without opening marker", node);
522
+ return CONTINUE;
523
+ }
524
+ if (lastOpenMarker.parent !== marker.parent) {
525
+ saveLog(file, "error", "clean", "Opening marker doesn't share a parent", node);
526
+ return CONTINUE;
527
+ }
528
+ if (lastOpenMarker.keyword !== marker.keyword) {
529
+ saveLog(file, "error", "clean", "Opening marker doesn't share a keyword", node);
530
+ return CONTINUE;
531
+ }
532
+ const openMarkerIndex = parent.children.indexOf(lastOpenMarker.node);
533
+ const nodesToRemove = parent.children.indexOf(marker.node) - openMarkerIndex + 1;
534
+ parent.children.splice(openMarkerIndex + 1, nodesToRemove - 1);
535
+ lastOpenMarker = void 0;
536
+ return [CONTINUE, index - nodesToRemove + 1];
537
+ }
538
+ });
539
+ }
540
+ //#endregion
541
+ //#region src/lib/mdast-utils/mdast-util-mdat-expand.ts
542
+ async function mdatExpand(tree, file, options) {
543
+ const { addMetaComment, closingPrefix, keywordPrefix, metaCommentIdentifier, rules: rawRules } = options;
544
+ validateRules(rawRules);
545
+ const rules = normalizeRules(rawRules);
546
+ const commentMarkers = [];
547
+ visit(tree, "html", (node, index, parent) => {
548
+ if (parent === void 0 || index === void 0) return CONTINUE;
549
+ const commentMarker = parseCommentNode(node, parent, {
550
+ closingPrefix,
551
+ keywordPrefix,
552
+ metaCommentIdentifier
553
+ });
554
+ if (commentMarker?.type === "open" && rules[commentMarker.keyword] !== void 0) commentMarkers.push(commentMarker);
555
+ });
556
+ commentMarkers.sort((a, b) => rules[a.keyword].applicationOrder - rules[b.keyword].applicationOrder);
557
+ for (const comment of commentMarkers) {
558
+ const { closingPrefix, html, keyword, keywordPrefix, node, options, parent } = comment;
559
+ const rule = rules[keyword];
560
+ let newMarkdownString = "";
561
+ try {
562
+ newMarkdownString = await getRuleContent(rule, options, tree);
563
+ if (newMarkdownString.trim() === "") saveLog(file, "error", "expand", `Got empty content when expanding ${html}`, node);
564
+ } catch (error) {
565
+ if (error instanceof Error) {
566
+ const causeMessage = error.cause instanceof Error ? `: ${error.cause.message}` : "";
567
+ saveLog(file, "error", "expand", `Caught error expanding ${html}, Error message: "${error.message}${causeMessage}"`, node);
568
+ }
569
+ continue;
570
+ }
571
+ const newNodes = remark().use(remarkGfm).parse(newMarkdownString).children;
572
+ const closingNode = {
573
+ type: "html",
574
+ value: `<!-- ${closingPrefix}${keywordPrefix}${keyword} -->`
575
+ };
576
+ const openingCommentIndex = parent.children.indexOf(node);
577
+ parent.children.splice(openingCommentIndex + 1, 0, ...newNodes, closingNode);
578
+ saveLog(file, "info", "expand", `Expanded: ${html}`, node);
579
+ }
580
+ if (addMetaComment) {
581
+ const metaComment = {
582
+ type: "html",
583
+ value: `<!--${metaCommentIdentifier} ${typeof addMetaComment === "string" ? addMetaComment : "Warning: Content inside HTML comment blocks was generated by mdat and may be overwritten."} ${metaCommentIdentifier}-->`
584
+ };
585
+ tree.children.unshift(metaComment);
586
+ }
587
+ }
588
+ //#endregion
589
+ //#region src/lib/mdast-utils/mdast-util-mdat-split.ts
590
+ /**
591
+ * Mdast utility plugin to split any multi-comment nodes and their content into individual MDAST HTML
592
+ * nodes. They're wrapped in a paragraph so as not to introduce new breaks.
593
+ */
594
+ function mdatSplit(tree, file) {
595
+ visit(tree, "html", (node, index, parent) => {
596
+ if (parent === void 0 || index === void 0) return CONTINUE;
597
+ const htmlNodes = splitHtmlIntoMdastNodes(node);
598
+ if (htmlNodes.length > 1) {
599
+ saveLog(file, "warn", "split", "Multiple comments in a single HTML node.", node);
600
+ parent.children.splice(index, 1, {
601
+ children: htmlNodes,
602
+ type: "paragraph"
603
+ });
604
+ }
605
+ });
606
+ }
607
+ function splitHtmlIntoMdastNodes(mdastNode) {
608
+ const htmlTree = fromHtml(mdastNode.value, { fragment: true });
609
+ const mdastNodes = [];
610
+ visit(htmlTree, (hastNode) => {
611
+ if (hastNode.type === "root") return CONTINUE;
612
+ if (hastNode.type === "text") {
613
+ mdastNodes.push({
614
+ position: addStartPoint(hastNode.position, mdastNode.position?.start),
615
+ type: "text",
616
+ value: getOriginalMarkup(mdastNode, hastNode)
617
+ });
618
+ return CONTINUE;
619
+ }
620
+ mdastNodes.push({
621
+ position: addStartPoint(hastNode.position, mdastNode.position?.start),
622
+ type: "html",
623
+ value: getOriginalMarkup(mdastNode, hastNode)
624
+ });
625
+ return SKIP;
626
+ });
627
+ return mdastNodes;
628
+ }
629
+ function addStartPoint(position, start) {
630
+ if (position === void 0 || start === void 0) return void 0;
631
+ const startLine = position.start.line - 1 + start.line;
632
+ const endLine = position.end.line - 1 + start.line;
633
+ return {
634
+ start: {
635
+ column: position.start.line === 1 ? position.start.column - 1 + start.column : position.start.column,
636
+ line: startLine,
637
+ offset: position.start.offset !== void 0 && start.offset !== void 0 ? position.start.offset + start.offset : void 0
638
+ },
639
+ end: {
640
+ column: position.end.line === 1 ? position.end.column - 1 + start.column : position.end.column,
641
+ line: endLine,
642
+ offset: position.end.offset !== void 0 && start.offset !== void 0 ? position.end.offset + start.offset : void 0
643
+ }
644
+ };
645
+ }
646
+ function getOriginalMarkup(mdastNode, hastNode) {
647
+ if (hastNode.position === void 0) throw new Error("Hast ElementContent node has no position!");
648
+ return mdastNode.value.slice(hastNode.position.start.offset, hastNode.position.end.offset);
649
+ }
650
+ //#endregion
651
+ //#region src/lib/mdast-utils/mdast-util-mdat.ts
652
+ async function mdat(tree, file, options) {
653
+ const { addMetaComment, closingPrefix, keywordPrefix, metaCommentIdentifier, rules } = options;
654
+ mdatSplit(tree, file);
655
+ mdatClean(tree, file, {
656
+ closingPrefix,
657
+ keywordPrefix,
658
+ metaCommentIdentifier
659
+ });
660
+ await mdatExpand(tree, file, {
661
+ addMetaComment,
662
+ closingPrefix,
663
+ keywordPrefix,
664
+ metaCommentIdentifier,
665
+ rules
666
+ });
667
+ await mdatCheck(tree, file, {
668
+ addMetaComment,
669
+ closingPrefix,
670
+ keywordPrefix,
671
+ metaCommentIdentifier,
672
+ paranoid: false,
673
+ rules
674
+ });
675
+ }
676
+ //#endregion
677
+ //#region src/lib/mdat/deep-merge-defined.ts
678
+ function stripUndefinedDeep(object) {
679
+ if (Array.isArray(object)) return object.map((v) => v && typeof v === "object" ? stripUndefinedDeep(v) : v).filter((v) => v !== void 0);
680
+ return Object.entries(object).map(([k, v]) => [k, v && typeof v === "object" ? stripUndefinedDeep(v) : v]).reduce((acc, [k, v]) => v === void 0 ? acc : {
681
+ ...acc,
682
+ [k]: v
683
+ }, {});
684
+ }
685
+ function deepMergeDefined(...objects) {
686
+ return deepmerge(...objects.map((v, i) => i === 0 ? v : stripUndefinedDeep(v)));
687
+ }
688
+ //#endregion
689
+ //#region src/lib/remark-mdat.ts
690
+ const defaultOptions = {
691
+ addMetaComment: false,
692
+ closingPrefix: "/",
693
+ keywordPrefix: "",
694
+ metaCommentIdentifier: "+",
695
+ rules: { mdat: `Powered by the Markdown Autophagic Template system: [mdat](https://github.com/kitschpatrol/mdat).` }
696
+ };
697
+ const optionsSchema = z.object({
698
+ addMetaComment: z.union([z.boolean(), z.string()]).optional(),
699
+ closingPrefix: z.string().min(1).optional(),
700
+ keywordPrefix: z.string().optional(),
701
+ metaCommentIdentifier: z.string().optional(),
702
+ rules: rulesSchema.optional()
703
+ }).describe("MDAT Options");
704
+ /**
705
+ * A remark plugin that expands HTML comments in Markdown files.
706
+ */
707
+ const remarkMdat = function(options) {
708
+ const resolvedOptions = deepMergeDefined(defaultOptions, options);
709
+ return async function(tree, file) {
710
+ await mdat(tree, file, resolvedOptions);
711
+ };
712
+ };
713
+ //#endregion
714
+ export { deepMergeDefined, remarkMdat as default, getMdatReports, getSoleRule, getSoleRuleKey, log, mdat, mdatCheck, mdatClean, mdatExpand, mdatSplit, optionsSchema, reporterMdat, rulesSchema };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remark-mdat",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "A remark plugin implementing the Markdown Autophagic Template (MDAT) system.",
5
5
  "keywords": [
6
6
  "mdat",
@@ -26,6 +26,7 @@
26
26
  "email": "eric@ericmika.com",
27
27
  "url": "https://ericmika.com"
28
28
  },
29
+ "sideEffects": false,
29
30
  "type": "module",
30
31
  "exports": {
31
32
  ".": {
@@ -49,7 +50,7 @@
49
50
  "picocolors": "^1.1.1",
50
51
  "remark": "^15.0.1",
51
52
  "remark-gfm": "^4.0.1",
52
- "type-fest": "^5.4.4",
53
+ "type-fest": "^5.5.0",
53
54
  "unified": "^11.0.5",
54
55
  "unist-util-visit": "^5.1.0",
55
56
  "vfile": "^6.0.3",
@@ -58,13 +59,13 @@
58
59
  },
59
60
  "devDependencies": {
60
61
  "@arethetypeswrong/core": "^0.18.2",
61
- "@kitschpatrol/shared-config": "^6.0.0",
62
- "@types/node": "~20.19.33",
63
- "bumpp": "^10.4.1",
64
- "publint": "^0.3.17",
65
- "tsdown": "^0.20.3",
62
+ "@kitschpatrol/shared-config": "^6.1.0",
63
+ "@types/node": "~20.19.37",
64
+ "bumpp": "^11.0.1",
65
+ "publint": "^0.3.18",
66
+ "tsdown": "^0.21.4",
66
67
  "typescript": "~5.9.3",
67
- "vitest": "^4.0.18"
68
+ "vitest": "^4.1.1"
68
69
  },
69
70
  "engines": {
70
71
  "node": ">=20.0.0"
@@ -82,6 +83,6 @@
82
83
  "fix": "ksc fix",
83
84
  "lint": "ksc lint",
84
85
  "release": "bumpp --commit 'Release: %s' && pnpm run build && NPM_AUTH_TOKEN=$(op read 'op://Personal/npm/token') && pnpm publish",
85
- "test": "vitest"
86
+ "test": "vitest run"
86
87
  }
87
88
  }
package/readme.md CHANGED
@@ -6,10 +6,18 @@
6
6
 
7
7
  <!-- /title -->
8
8
 
9
- <!-- badges -->
9
+ <!-- badges { custom: {
10
+ "CI": {
11
+ image: "https://github.com/kitschpatrol/remark-mdat/actions/workflows/ci.yml/badge.svg",
12
+ link: "https://github.com/kitschpatrol/remark-mdat/actions/workflows/ci.yml",
13
+ },
14
+ }
15
+ }
16
+ -->
10
17
 
11
18
  [![NPM Package remark-mdat](https://img.shields.io/npm/v/remark-mdat.svg)](https://npmjs.com/package/remark-mdat)
12
19
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
20
+ [![CI](https://github.com/kitschpatrol/remark-mdat/actions/workflows/ci.yml/badge.svg)](https://github.com/kitschpatrol/remark-mdat/actions/workflows/ci.yml)
13
21
 
14
22
  <!-- /badges -->
15
23