remark-mdat 1.2.3 → 1.2.4

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