Compare commits

..

13 commits
0.0.1 ... main

13 changed files with 991 additions and 276 deletions

View file

@ -25,11 +25,23 @@ Options:
## Templates
`buildmd` uses simple HTML files as templates, replacing instances of `%key%` with the value of `key` in the metadata of the document.
`buildmd` uses HTML files as templates. Instances of `%key%` are replaced with value of `key` from the built document.
By default, `buildmd` will look for a `_template.html` file in the current working directory, unless an alternate template is specified with the `--template` or `-t` option.
You can provide a template path with the `--template` or `-t` flag. If no template flag is provided, `buildmd`
will look for a `_template.html` file in the current directory. If no template file is found, a simple
default template will be used.
The following metadata is available by default:
## Stylesheets
`buildmd` supports custom stylesheets. You can specify a stylesheet with the `--stylesheet` or `-s` flag.
If no stylesheet is specified, `buildmd` will look for a `_style.css` file in the current directory.
If no stylesheet is found, a default stylesheet will be used.
## Data
The following keys are always available in the template data:
- `title`: The title of the document
- `content`: The rendered markdown content
@ -38,14 +50,6 @@ The following metadata is available by default:
- `datetime` A localized string representing when the build occurred
Additionally, any data defined in YAML frontmatter in the markdown file
will be available for use in the template. Properties defined in frontmatter
will be available in the template. Properties defined in frontmatter
take precedence over the default metadata.
If no template file is discovered, `buildmd` will inject a default template that includes the stylesheet, title, and content in well-structured HTML.
## Styles
`buildmd` will automatically look for a `_style.css` file in the current working directory, unless an alternate stylesheet is specified with the `--stylesheet` or `-s` option. The contents of the discovered stylesheet file will be available in the template as `%stylesheet%`.
In the event that no stylesheet is found, `buildmd` will inject a default stylesheet.

220
bun.lock
View file

@ -6,7 +6,15 @@
"dependencies": {
"@biomejs/biome": "^1.9.4",
"@endeavorance/emdy": "^1.0.0",
"marked": "^15.0.12",
"rehype-callouts": "^2.1.0",
"rehype-raw": "^7.0.0",
"rehype-slug": "^6.0.0",
"rehype-stringify": "^10.0.1",
"remark-custom-heading-id": "^2.0.0",
"remark-gfm": "^4.0.1",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"unified": "^11.0.5",
},
"devDependencies": {
"@types/bun": "latest",
@ -39,16 +47,224 @@
"@types/bun": ["@types/bun@1.2.14", "", { "dependencies": { "bun-types": "1.2.14" } }, "sha512-VsFZKs8oKHzI7zwvECiAJ5oSorWndIWEVhfbYqZd4HI/45kzW7PN2Rr5biAzvGvRuNmYLSANY+H59ubHq8xw7Q=="],
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/node": ["@types/node@22.15.21", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ=="],
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
"bun-types": ["bun-types@1.2.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-Kuh4Ub28ucMRWeiUUWMHsT9Wcbr4H3kLIO72RZZElSDxSu7vpetRvxIUDUaW6QtaIeixIpm7OXtNnZPf82EzwA=="],
"marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
"character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
"character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
"debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"decode-named-character-reference": ["decode-named-character-reference@1.1.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w=="],
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
"entities": ["entities@6.0.0", "", {}, "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw=="],
"escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
"hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="],
"hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="],
"hast-util-heading-rank": ["hast-util-heading-rank@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA=="],
"hast-util-is-element": ["hast-util-is-element@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g=="],
"hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="],
"hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="],
"hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
"hast-util-to-parse5": ["hast-util-to-parse5@8.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw=="],
"hast-util-to-string": ["hast-util-to-string@3.0.1", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A=="],
"hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
"hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
"mdast-heading-id": ["mdast-heading-id@2.0.0", "", {}, "sha512-JVh0xKqsI3INPzJEqqCiuq3BGde5x6AlBTQAetLQQr9qTiDjX4dCvFhIiGmyPt1rftiHELQPI5obGphIOTXeWQ=="],
"mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA=="],
"mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
"mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="],
"mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="],
"mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="],
"mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="],
"mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="],
"mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
"mdast-util-to-hast": ["mdast-util-to-hast@13.2.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="],
"mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
"micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="],
"micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="],
"micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="],
"micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="],
"micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="],
"micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="],
"micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="],
"micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
"micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
"micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
"micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
"micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
"micromark-heading-id": ["micromark-heading-id@2.0.0", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-TdR5WCmwuCroK8QrfX0/4w9bjj3ayN0GzatxGV/lWQKmM75kNKVZEmdvKZS4MOskARxWXu1WHdIJjMSVMBegow=="],
"micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
"micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
"micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
"micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
"micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
"micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="],
"micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
"micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
"micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
"micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
"micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
"micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="],
"micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
"micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
"rehype-callouts": ["rehype-callouts@2.1.0", "", { "dependencies": { "@types/hast": "^3.0.4", "hast-util-from-html": "^2.0.3", "hast-util-is-element": "^3.0.0", "hastscript": "^9.0.1", "unist-util-visit": "^5.0.0" } }, "sha512-Als/wlYpXg2Rs0p/yofrkSH6IQzn1xh6cvBC5BHy5JVT3lGlzUvVT8wOyVqCEgf8Eun6cQ3f47rLLoaiyLkP0w=="],
"rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="],
"rehype-slug": ["rehype-slug@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "github-slugger": "^2.0.0", "hast-util-heading-rank": "^3.0.0", "hast-util-to-string": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A=="],
"rehype-stringify": ["rehype-stringify@10.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", "unified": "^11.0.0" } }, "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA=="],
"remark-custom-heading-id": ["remark-custom-heading-id@2.0.0", "", { "dependencies": { "mdast-heading-id": "*", "micromark-heading-id": "*", "unist-util-visit": "^5.0.0" } }, "sha512-LpR+c4pmuBGG/zhNbebecDnpKyXU4B7bdNu6tXW83T3QWlkwUpHYyUR8uXQ/BERK9HlI5pXGLu7DjkIS07/iVQ=="],
"remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
"remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
"remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="],
"remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
"unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="],
"unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
"unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
"unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw=="],
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
"vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="],
"vfile-message": ["vfile-message@4.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw=="],
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
"yaml": ["yaml@2.8.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ=="],
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
"hast-util-to-parse5/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="],
}
}

View file

@ -1,6 +1,23 @@
---
title: Another File | Demo
---
Here's another file.
It has a list!
- See?
- Fancy!
How about a callout?
> [!info] This is an info callout
> Here's some more info
> etc
> [!warning]- This is collapsible
> And how!
## A subheading
This is a section!

View file

@ -1,3 +1,41 @@
---
title: Home | Demo
and: how
---
# Demo Data
This is just a folder of markdown files for testing purposes.
## Here's a level 2 heading
And some content underneath that. How about a few lorem ipsums?
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
Lorem ipsum dolor sit amet consectetur adipiscing elit. Quisque faucibus ex sapien vitae pellentesque sem placerat. In id cursus mi pretium tellus duis convallis. Tempus leo eu aenean sed diam urna tempor. Pulvinar vivamus fringilla lacus nec metus bibendum egestas. Iaculis massa nisl malesuada lacinia integer nunc posuere. Ut hendrerit semper vel class aptent taciti sociosqu. Ad litora torquent per conubia nostra inceptos himenaeos.
## Another section {#customid}
Tables should be a thing too
| One | Two | Three |
| - | - | - |
| A cell | Another cell | And another |
| Another row | another cell | and another |
**Bold Stuff** as well as *emphasis* and such.
- A bulleted list
- Is always helpful
- When you don't
- Need numbers
<span style="font-size: 0.5rem">HTML works, still!</span>
| hello | world | hello | world | hello | world | hello | world | hello | world | hello |
| ------------------------------------------- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- |
| dfjhasfdlkjahslfkjahsdlfjhasldjhasldjhasldk and such and things and whatnot and even more words in this cell so that hopefully itll wrap at some point ya dig? | | | | | | | | | | |

View file

@ -3,7 +3,11 @@
"module": "index.ts",
"type": "module",
"private": true,
"version": "0.0.1",
"version": "0.3.1",
"scripts": {
"build": ".run/build",
"fmt": "bunx --bun biome check --fix"
},
"devDependencies": {
"@types/bun": "latest"
},
@ -13,6 +17,14 @@
"dependencies": {
"@biomejs/biome": "^1.9.4",
"@endeavorance/emdy": "^1.0.0",
"marked": "^15.0.12"
"rehype-callouts": "^2.1.0",
"rehype-raw": "^7.0.0",
"rehype-slug": "^6.0.0",
"rehype-stringify": "^10.0.1",
"remark-custom-heading-id": "^2.0.0",
"remark-gfm": "^4.0.1",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"unified": "^11.0.5"
}
}

42
src/build.ts Normal file
View file

@ -0,0 +1,42 @@
import EMDY from "@endeavorance/emdy";
import type { BunFile } from "bun";
import { parseMarkdown } from "./markdown";
import type { CLIOptions } from "./options";
import { loadStylesheet } from "./stylesheet";
import { loadTemplate, renderTemplate } from "./template";
/**
* Build a file and return the rendered HTML string
* @param infile - The input file to read from
* @param outfile - The output file to write to
* @param options - The CLI options containing template and stylesheet paths
*/
export async function buildFile(
infile: BunFile,
options: CLIOptions,
): Promise<string> {
// Load resources
const input = await infile.text();
const template = await loadTemplate(options);
const stylesheet = await loadStylesheet(options);
const { content, ...props } = EMDY.parse(input) as Record<string, unknown> & {
content: string;
};
// Render markdown
const html = await parseMarkdown(content);
// Prepare data
const title = options.title ?? props.title ?? "Untitled";
const templateData = {
content: html,
title,
stylesheet: stylesheet,
timestamp: new Date().toISOString(),
datetime: new Date().toLocaleString(),
...props,
};
// Render template
return renderTemplate(template, templateData);
}

View file

@ -1,16 +1,16 @@
:root {
--color-bg: #1d1d1d;
--color-text: #eee;
--color-link: #fff;
--color-strong: #ff8f8f;
--color-emphasis: #ff8f00;
--color-heading: #accfff;
--color-bg: hsl(0, 0%, 11%);
--color-text: hsl(0, 0%, 93%);
--color-link: hsl(0, 0%, 100%);
--color-strong: hsl(0, 100%, 78%);
--color-emphasis: hsl(34, 100%, 50%);
--color-heading: hsl(215, 100%, 84%);
--font-size: 18px;
--font-family: Seravek, "Gill Sans Nova", Ubuntu, Calibri, "DejaVu Sans",
source-sans-pro, sans-serif;
--font-family: system-ui, sans-serif;
--font-weight: 400;
--font-weight-bold: 500;
--line-height: 1.4;
--max-width: 700px;
@ -39,7 +39,8 @@ h6 {
font-family: var(--font-family);
font-weight: var(--font-weight-bold);
color: var(--color-heading);
margin: 1rem 0;
margin-top: 2rem;
margin-bottom: 0;
}
h1 {
@ -47,23 +48,32 @@ h1 {
}
h2 {
font-size: 2.75rem;
font-size: 2.5rem;
}
h3 {
font-size: 2.15rem;
}
h4 {
font-size: 1.75rem;
}
h5 {
font-size: 1.5rem;
}
h4 {
font-size: 1.2rem;
}
h5 {
font-size: 1.1rem;
}
h6 {
font-size: 1.25rem;
font-size: 1rem;
}
h1 + p,
h2 + p,
h3 + p,
h4 + p,
h5 + p,
h6 + p {
margin-top: 0.5rem;
}
em,
@ -82,18 +92,21 @@ a {
}
table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
font-size: 1em;
background-color: var(--color-bg);
display: block;
color: var(--color-text);
background-color: var(--color-bg);
width: 100%;
max-width: -moz-fit-content;
max-width: fit-content;
overflow-x: auto;
font-size: 1em;
border: 1px solid var(--color-text);
margin: 0 auto;
border-collapse: collapse;
}
th,
td {
padding: 0.75em 1em;
padding: 0.5em 0.5em;
text-align: left;
border-bottom: 1px solid var(--color-text);
}
@ -102,3 +115,247 @@ th {
background: var(--color-bg);
font-weight: 500;
}
/* Callout styles, from https://raw.githubusercontent.com/lin-stephanie/rehype-callouts/refs/heads/main/src/themes/obsidian/index.css */
[data-callout="note"] {
--rc-color-light: var(--callout-note-color-light, rgb(8, 109, 221));
--rc-color-dark: var(--callout-note-color-dark, rgb(2, 122, 255));
}
[data-callout="abstract"] {
--rc-color-light: var(--callout-abstract-color-light, rgb(0, 191, 188));
--rc-color-dark: var(--callout-abstract-color-dark, rgb(83, 223, 221));
}
[data-callout="summary"] {
--rc-color-light: var(--callout-summary-color-light, rgb(0, 191, 188));
--rc-color-dark: var(--callout-summary-color-dark, rgb(83, 223, 221));
}
[data-callout="tldr"] {
--rc-color-light: var(--callout-tldr-color-light, rgb(0, 191, 188));
--rc-color-dark: var(--callout-tldr-color-dark, rgb(83, 223, 221));
}
[data-callout="info"] {
--rc-color-light: var(--callout-info-color-light, rgb(8, 109, 221));
--rc-color-dark: var(--callout-info-color-dark, rgb(2, 122, 255));
}
[data-callout="todo"] {
--rc-color-light: var(--callout-todo-color-light, rgb(8, 109, 221));
--rc-color-dark: var(--callout-todo-color-dark, rgb(2, 122, 255));
}
[data-callout="tip"] {
--rc-color-light: var(--callout-tip-color-light, rgb(0, 191, 188));
--rc-color-dark: var(--callout-tip-color-dark, rgb(83, 223, 221));
}
[data-callout="hint"] {
--rc-color-light: var(--callout-hint-color-light, rgb(0, 191, 188));
--rc-color-dark: var(--callout-hint-color-dark, rgb(83, 223, 221));
}
[data-callout="important"] {
--rc-color-light: var(--callout-important-color-light, rgb(0, 191, 188));
--rc-color-dark: var(--callout-important-color-dark, rgb(83, 223, 221));
}
[data-callout="success"] {
--rc-color-light: var(--callout-success-color-light, rgb(8, 185, 78));
--rc-color-dark: var(--callout-success-color-dark, rgb(68, 207, 110));
}
[data-callout="check"] {
--rc-color-light: var(--callout-check-color-light, rgb(8, 185, 78));
--rc-color-dark: var(--callout-check-color-dark, rgb(68, 207, 110));
}
[data-callout="done"] {
--rc-color-light: var(--callout-done-color-light, rgb(8, 185, 78));
--rc-color-dark: var(--callout-done-color-dark, rgb(68, 207, 110));
}
[data-callout="question"] {
--rc-color-light: var(--callout-question-color-light, rgb(236, 117, 0));
--rc-color-dark: var(--callout-question-color-dark, rgb(233, 151, 63));
}
[data-callout="help"] {
--rc-color-light: var(--callout-help-color-light, rgb(236, 117, 0));
--rc-color-dark: var(--callout-help-color-dark, rgb(233, 151, 63));
}
[data-callout="faq"] {
--rc-color-light: var(--callout-faq-color-light, rgb(236, 117, 0));
--rc-color-dark: var(--callout-faq-color-dark, rgb(233, 151, 63));
}
[data-callout="warning"] {
--rc-color-light: var(--callout-warning-color-light, rgb(236, 117, 0));
--rc-color-dark: var(--callout-warning-color-dark, rgb(233, 151, 63));
}
[data-callout="attention"] {
--rc-color-light: var(--callout-attention-color-light, rgb(236, 117, 0));
--rc-color-dark: var(--callout-attention-color-dark, rgb(233, 151, 63));
}
[data-callout="caution"] {
--rc-color-light: var(--callout-caution-color-light, rgb(236, 117, 0));
--rc-color-dark: var(--callout-caution-color-dark, rgb(233, 151, 63));
}
[data-callout="failure"] {
--rc-color-light: var(--callout-failure-color-light, rgb(233, 49, 71));
--rc-color-dark: var(--callout-failure-color-dark, rgb(251, 70, 76));
}
[data-callout="missing"] {
--rc-color-light: var(--callout-missing-color-light, rgb(233, 49, 71));
--rc-color-dark: var(--callout-missing-color-dark, rgb(251, 70, 76));
}
[data-callout="fail"] {
--rc-color-light: var(--callout-fail-color-light, rgb(233, 49, 71));
--rc-color-dark: var(--callout-fail-color-dark, rgb(251, 70, 76));
}
[data-callout="danger"] {
--rc-color-light: var(--callout-danger-color-light, rgb(233, 49, 71));
--rc-color-dark: var(--callout-danger-color-dark, rgb(251, 70, 76));
}
[data-callout="error"] {
--rc-color-light: var(--callout-error-color-light, rgb(233, 49, 71));
--rc-color-dark: var(--callout-error-color-dark, rgb(251, 70, 76));
}
[data-callout="bug"] {
--rc-color-light: var(--callout-bug-color-light, rgb(233, 49, 71));
--rc-color-dark: var(--callout-bug-color-dark, rgb(251, 70, 76));
}
[data-callout="example"] {
--rc-color-light: var(--callout-example-color-light, rgb(120, 82, 238));
--rc-color-dark: var(--callout-example-color-dark, rgb(168, 130, 255));
}
[data-callout="quote"] {
--rc-color-light: var(--callout-quote-color-light, rgb(158, 158, 158));
--rc-color-dark: var(--callout-quote-color-dark, rgb(158, 158, 158));
}
[data-callout="cite"] {
--rc-color-light: var(--callout-cite-color-light, rgb(158, 158, 158));
--rc-color-dark: var(--callout-cite-color-dark, rgb(158, 158, 158));
}
.callout {
--rc-color-default: #888;
overflow: hidden;
padding: 12px 12px 12px 24px;
border-radius: 4px;
margin: 1em 0;
line-height: 1.3;
mix-blend-mode: darken;
background-color: rgb(
from var(--rc-color-light, var(--rc-color-default)) r g b /
0.1
);
}
.dark .callout {
mix-blend-mode: lighten;
background-color: rgb(
from var(--rc-color-dark, var(--rc-color-default)) r g b /
0.1
);
}
.callout-title {
display: flex;
align-items: flex-start;
gap: 4px;
color: var(--rc-color-light, var(--rc-color-default));
font-size: inherit;
}
.dark .callout-title {
color: var(--rc-color-dark, var(--rc-color-default));
}
.callout-title::-webkit-details-marker {
display: none;
}
.callout-title-icon {
display: flex;
flex: 0 0 auto;
align-items: center;
}
.callout-title-text {
color: inherit;
font-weight: 600;
}
.callout-content {
overflow-x: auto;
padding: 0;
background-color: transparent;
}
.callout[data-collapsible="true"] .callout-title {
cursor: pointer;
}
.callout[data-collapsible="true"] .callout-fold-icon {
display: flex;
align-items: center;
padding-inline-end: 8px;
}
.callout[data-collapsible="true"] > .callout-title .callout-fold-icon svg {
transform: rotate(-90deg);
transition: transform 100ms ease-in-out;
}
.callout[data-collapsible="true"][open]
> .callout-title
.callout-fold-icon
svg {
transform: none;
}
.callout-title-icon::after,
.callout-fold-icon::after {
content: "\200B";
}
.callout-title-icon svg,
.callout-fold-icon svg {
width: 18px;
height: 18px;
}
@media (prefers-color-scheme: dark) {
.callout {
mix-blend-mode: lighten;
background-color: rgb(
from var(--rc-color-dark, var(--rc-color-default)) r g b /
0.1
);
}
.callout-title {
color: var(--rc-color-dark, var(--rc-color-default));
}
}

77
src/file-util.ts Normal file
View file

@ -0,0 +1,77 @@
import Path from "node:path";
import type { BunFile } from "bun";
import { CLIError } from "./error";
import type { CLIOptions } from "./options";
/**
* Check if a given path is a directory
*
* @param path - The path to check
* @returns A promise that resolves to true if the path is a directory, false otherwise
*/
export async function isDirectory(path: string): Promise<boolean> {
const file = Bun.file(path);
const stat = await file.stat();
return stat.isDirectory();
}
/**
* Given a file path, attempt to read the text of the file
* @param filePath - The path to the file to read
* @returns The text content of the file
*
* @throws CLIError if the file does not exist
*/
export async function readFile(filePath: string): Promise<string> {
const file = Bun.file(filePath);
const exists = await file.exists();
if (!exists) {
throw new CLIError(`File ${filePath} does not exist.`);
}
return file.text();
}
/**
* Get the output path based on the input path and options
* @param inputPath - The path of the input file
* @param options - The CLI options containing output directory
* @returns The resolved output path
*/
async function getOutputPath(inputPath: string, options: CLIOptions) {
const inputDirname = Path.dirname(inputPath);
const inputBasename = Path.basename(inputPath);
const outputBasename = inputBasename.replace(/\.md$/, ".html");
if (options.outdir) {
const dirPath = Path.relative(options.cwd, inputDirname);
const outputPath = Path.join(options.outdir, dirPath, outputBasename);
return Path.resolve(outputPath);
}
return Path.join(inputDirname, outputBasename);
}
/**
* Get the output file based on the input path and options
* @param inputPath - The path of the input file
* @param options - The CLI options containing output file and directory
* @returns The BunFile object representing the output file
*/
export async function getOutputFile(
inputPath: string,
options: CLIOptions,
): Promise<BunFile> {
// --stdout option -> Force stdout
if (options.stdout) {
return Bun.stdout;
}
// Exact outfile defined -> write to file
if (options.outfile) {
return Bun.file(options.outfile);
}
// Input path with --outdir -> calculate output path
const outputPath = await getOutputPath(inputPath, options);
return Bun.file(outputPath);
}

View file

@ -1,264 +1,63 @@
import Path from "node:path";
import { parseArgs } from "node:util";
import EMDY from "@endeavorance/emdy";
import { marked } from "marked";
import defaultStylesheet from "./defaults/default-style.css" with {
type: "text",
};
import defaultTemplate from "./defaults/default-template.html" with {
type: "text",
};
import { Glob } from "bun";
import { buildFile } from "./build";
import { CLIError } from "./error";
import type { BunFile } from "bun";
import { getOutputFile, isDirectory } from "./file-util";
import { type CLIOptions, USAGE, parseCLIArgs } from "./options";
const DEFAULT_TEMPLATE_FILE = "_template.html";
const DEFAULT_STYLESHEET_FILE = "_style.css";
async function processStdin(options: CLIOptions): Promise<void> {
const outfile = options.outfile ? Bun.file(options.outfile) : Bun.stdout;
const renderedHTML = await buildFile(Bun.stdin, options);
await Bun.write(outfile, renderedHTML);
}
async function readFile(filePath: string): Promise<string> {
async function processFileAtPath(
filePath: string,
options: CLIOptions,
): Promise<void> {
console.log(`Building ${filePath}...`);
const file = Bun.file(filePath);
const exists = await file.exists();
if (!exists) {
// Ensure input files exist
if (!(await file.exists())) {
throw new CLIError(`File ${filePath} does not exist.`);
}
return file.text();
}
function replacePlaceholders(
template: string,
data: Record<string, unknown>,
): string {
let output = template;
for (const [key, value] of Object.entries(data)) {
output = output.replace(new RegExp(`%${key}%`, "g"), String(value));
}
return output;
}
interface CLIOptions {
outfile: string | null;
outdir: string | null;
stdout: boolean;
templateFilePath: string | null;
stylesheetFilePath: string | null;
help: boolean;
title: string | null;
cwd: string;
}
function parseCLIArgs(): { options: CLIOptions; args: string[] } {
const { values: flags, positionals } = parseArgs({
args: Bun.argv.slice(2),
options: {
outfile: {
type: "string",
short: "o",
},
outdir: {
type: "string",
short: "d",
},
stdout: {
type: "boolean",
},
template: {
type: "string",
short: "t",
},
title: {
type: "string",
short: "T",
},
stylesheet: {
type: "string",
short: "s",
},
cwd: {
type: "string",
default: process.cwd(),
},
help: {
type: "boolean",
short: "h",
},
},
allowPositionals: true,
});
// Validation
if (positionals.length > 1 && flags.outfile) {
throw new CLIError("--outfile cannot be used with multiple inputs.");
}
if (flags.outdir && flags.outfile) {
throw new CLIError("--outdir and --outfile cannot be used together.");
}
return {
options: {
cwd: flags.cwd,
outfile: flags.outfile ?? null,
outdir: flags.outdir ?? null,
stdout: flags.stdout ?? false,
templateFilePath: flags.template ?? null,
stylesheetFilePath: flags.stylesheet ?? null,
help: flags.help ?? false,
title: flags.title ?? null,
},
args: positionals,
};
}
async function loadTemplate(options: CLIOptions): Promise<string> {
if (options.templateFilePath) {
return readFile(options.templateFilePath);
}
const defaultTemplateFilePath = Path.join(options.cwd, DEFAULT_TEMPLATE_FILE);
const defaultTemplateFile = Bun.file(defaultTemplateFilePath);
if (await defaultTemplateFile.exists()) {
return defaultTemplateFile.text();
}
return defaultTemplate;
}
async function loadStylesheet(options: CLIOptions): Promise<string> {
if (options.stylesheetFilePath) {
return readFile(options.stylesheetFilePath);
}
const defaultStylesheetFilePath = Path.join(
options.cwd,
DEFAULT_STYLESHEET_FILE,
);
const defaultStylesheetFile = Bun.file(defaultStylesheetFilePath);
if (await defaultStylesheetFile.exists()) {
return defaultStylesheetFile.text();
}
return defaultStylesheet;
}
async function buildFile(
infile: BunFile,
outfile: BunFile,
options: CLIOptions,
): Promise<string> {
const input = await infile.text();
const template = await loadTemplate(options);
const stylesheet = await loadStylesheet(options);
const { content, ...props } = EMDY.parse(input) as Record<string, unknown> & {
content: string;
};
const html = await marked.parse(content, {
gfm: true,
});
const title = [options.title, props.title].find((t) => t) || "Untitled";
const replacers = {
content: html,
title,
stylesheet: stylesheet,
timestamp: new Date().toISOString(),
datetime: new Date().toLocaleString(),
...props,
};
const output = replacePlaceholders(template, replacers);
Bun.write(outfile, output);
return output;
}
async function getOutputPath(inputPath: string, options: CLIOptions) {
const inputDirname = Path.dirname(inputPath);
const inputBasename = Path.basename(inputPath);
const outputBasename = inputBasename.replace(/\.md$/, ".html");
if (options.outdir) {
const dirPath = Path.relative(options.cwd, inputDirname);
const outputPath = Path.join(options.outdir, dirPath, outputBasename);
return Path.resolve(outputPath);
}
return Path.join(inputDirname, outputBasename);
}
async function getOutputFile(
inputPath: string,
options: CLIOptions,
): Promise<BunFile> {
// --stdout option -> Force stdout
if (options.stdout) {
return Bun.stdout;
}
// Exact outfile defined -> write to file
if (options.outfile) {
return Bun.file(options.outfile);
}
// Input path with --outdir -> calculate output path
const outputPath = await getOutputPath(inputPath, options);
return Bun.file(outputPath);
const outfile = await getOutputFile(filePath, options);
const renderedHTML = await buildFile(file, options);
await Bun.write(outfile, renderedHTML);
}
async function main() {
const { options, args } = parseCLIArgs();
if (options.help) {
console.log(
`
buildmd - Build markdown files to html with templates and metadata
Usage:
buildmd file.md
buildmd file.md -o output.html
echo "some markdown" | buildmd
Options:
--outfile, -o <file> Output path
--outdir, -d <dir> Output directory
--stdout Force output to stdout
--template, -t <file> Template path (default: _template.html)
--stylesheet, -s <file> Stylesheet path (default: _style.css)
--title, -T <string> Document title override
--help, -h Show this help message
`.trim(),
);
process.exit(0);
console.log(USAGE);
return;
}
// stdin mode
if (args.length === 0) {
const outfile = options.outfile ? Bun.file(options.outfile) : Bun.stdout;
await buildFile(Bun.stdin, outfile, options);
process.exit(0);
return processStdin(options);
}
// file mode
for (const arg of args) {
const file = Bun.file(arg);
for (const inputPath of args) {
const isDir = await isDirectory(inputPath);
// Ensure input files exist
if (!(await file.exists())) {
throw new CLIError(`File ${arg} does not exist.`);
if (isDir) {
const dirGlob = new Glob(`${inputPath}/**/*.md`);
for await (const file of dirGlob.scan()) {
await processFileAtPath(file, options);
}
} else {
await processFileAtPath(inputPath, options);
}
const outfile = await getOutputFile(arg, options);
await buildFile(file, outfile, options);
}
}
try {
await main();
process.exit(0);
} catch (error) {
if (error instanceof CLIError) {
console.error(error.message);

46
src/markdown.ts Normal file
View file

@ -0,0 +1,46 @@
import rehypeCallouts from "rehype-callouts";
import rehypeRaw from "rehype-raw";
import rehypeSlug from "rehype-slug";
import rehypeStringify from "rehype-stringify";
import { remarkHeadingId } from "remark-custom-heading-id";
import remarkGfm from "remark-gfm";
import remarkParse from "remark-parse";
import remarkRehype from "remark-rehype";
import { unified } from "unified";
/**
* Parse the provided markdown into an HTML string
*
* @param markdown - The markdown string to parse
* @returns The parsed HTML string in a promise
*/
export async function parseMarkdown(markdown: string): Promise<string> {
const file = await unified()
// Parse markdown
.use(remarkParse)
// Support github-flavored markdown
.use(remarkGfm)
// Accept custom heading ids: # My heading {#custom-id}
.use(remarkHeadingId)
// Pass-thru HTML
.use(remarkRehype, { allowDangerousHtml: true })
// Process to html/html
.use(rehypeRaw)
// Apply heading IDs to headings without a custom ID
.use(rehypeSlug)
// Support callouts/admonitions
.use(rehypeCallouts)
// Render to string
.use(rehypeStringify)
.process(markdown);
return String(file);
}

128
src/options.ts Normal file
View file

@ -0,0 +1,128 @@
import { parseArgs } from "node:util";
import { CLIError } from "./error";
/**
* A collection of options parsed from the command line arguments.
*/
export interface CLIOptions {
/** A path to the output file */
outfile: string | null;
/** A path to the output directory */
outdir: string | null;
/** If true, force output to stdout */
stdout: boolean;
/** The path to the template file */
templateFilePath: string | null;
/** The path to the stylesheet file */
stylesheetFilePath: string | null;
/** If the default stylesheet should be included regardless of the provided stylesheet */
includeDefaultStylesheet: boolean;
/** If true, show help message */
help: boolean;
/** If provided, overrides the document title */
title: string | null;
/** If provided, overrides the current working directory (default: .) */
cwd: string;
}
/**
* Parse the command line arguments and return the options and positional arguments.
* @returns An object containing the parsed options and positional arguments
*/
export function parseCLIArgs(): { options: CLIOptions; args: string[] } {
const { values: flags, positionals } = parseArgs({
args: Bun.argv.slice(2),
options: {
outfile: {
type: "string",
short: "o",
},
outdir: {
type: "string",
short: "d",
},
stdout: {
type: "boolean",
},
template: {
type: "string",
short: "t",
},
title: {
type: "string",
short: "T",
},
stylesheet: {
type: "string",
short: "s",
},
"include-default-stylesheet": {
type: "boolean",
short: "S",
},
cwd: {
type: "string",
default: process.cwd(),
},
help: {
type: "boolean",
short: "h",
},
},
allowPositionals: true,
});
// == Argument Validation ==
// Outfile requires only one argument
if (flags.outfile && positionals.length > 1) {
throw new CLIError("--outfile cannot be used with multiple inputs.");
}
// Outfile and Outdir cannot be used together
if (flags.outdir && flags.outfile) {
throw new CLIError("--outdir and --outfile cannot be used together.");
}
return {
options: {
cwd: flags.cwd,
outfile: flags.outfile ?? null,
outdir: flags.outdir ?? null,
stdout: flags.stdout ?? false,
templateFilePath: flags.template ?? null,
stylesheetFilePath: flags.stylesheet ?? null,
includeDefaultStylesheet: flags["include-default-stylesheet"] ?? false,
help: flags.help ?? false,
title: flags.title ?? null,
},
args: positionals,
};
}
export const USAGE = `
buildmd - Build markdown files to html with templates and metadata
Usage:
buildmd file.md
buildmd file.md -o output.html
echo "some markdown" | buildmd
Options:
--outfile, -o <file> Output path
--outdir, -d <dir> Output directory
--stdout Force output to stdout
--template, -t <file> Template path (default: _template.html)
--stylesheet, -s <file> Stylesheet path (default: _style.css)
--include-default-stylesheet, -S Extend default CSS instead of overwriting
--title, -T <string> Document title override
--help, -h Show this help message
`.trim();

33
src/stylesheet.ts Normal file
View file

@ -0,0 +1,33 @@
import Path from "node:path";
import DEFAULT_STYLESHEET from "./defaults/default-style.css" with {
type: "text",
};
import { readFile } from "./file-util";
import type { CLIOptions } from "./options";
const DEFAULT_STYLESHEET_FILE = "_style.css";
/**
* Attempt to load a custom stylesheet, falling back to a default
* @param options - The CLI options containing the stylesheet file path
* @returns The stylesheet string
*/
export async function loadStylesheet(options: CLIOptions): Promise<string> {
const preamble = options.includeDefaultStylesheet ? DEFAULT_STYLESHEET : "";
if (options.stylesheetFilePath) {
const loadedSheet = await readFile(options.stylesheetFilePath);
return [preamble, loadedSheet].join("\n");
}
const checkStylesheetFile = Bun.file(
Path.join(options.cwd, DEFAULT_STYLESHEET_FILE),
);
if (await checkStylesheetFile.exists()) {
const loadedSheet = await checkStylesheetFile.text();
return [preamble, loadedSheet].join("\n");
}
return DEFAULT_STYLESHEET;
}

46
src/template.ts Normal file
View file

@ -0,0 +1,46 @@
import Path from "node:path";
import DEFAULT_TEMPLATE from "./defaults/default-template.html" with {
type: "text",
};
import { readFile } from "./file-util";
import type { CLIOptions } from "./options";
const DEFAULT_TEMPLATE_FILE = "_template.html";
/**
* Renders the provided template with the given data.
* @param template - The template string containing placeholders like %key%
* @param data - An object containing key-value pairs to replace in the template
* @returns The rendered string with all placeholders replaced by their corresponding values
*/
export function renderTemplate(
template: string,
data: Record<string, unknown>,
): string {
let output = template;
for (const [key, value] of Object.entries(data)) {
output = output.replace(new RegExp(`%${key}%`, "g"), String(value));
}
return output;
}
/**
* Attempt to load a custom template, falling back to a default
* @param options - The CLI options containing the template file path
* @returns The template string
*/
export async function loadTemplate(options: CLIOptions): Promise<string> {
if (options.templateFilePath) {
return readFile(options.templateFilePath);
}
const checkTemplateFile = Bun.file(
Path.join(options.cwd, DEFAULT_TEMPLATE_FILE),
);
if (await checkTemplateFile.exists()) {
return checkTemplateFile.text();
}
return DEFAULT_TEMPLATE;
}