mdat 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.DS_Store +0 -0
- package/dist/bin/cli.js +29 -12
- package/dist/lib/index.d.ts +11 -1
- package/dist/lib/index.js +43 -14
- package/package.json +5 -3
- package/readme.md +4 -50
package/dist/.DS_Store
ADDED
|
Binary file
|
package/dist/bin/cli.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import picocolors from "picocolors";
|
|
3
3
|
import prettyMilliseconds from "pretty-ms";
|
|
4
|
-
import { getMdatReports, getSoleRule, mdatCollapse, mdatExpand, mdatSplit, mdatStrip, reporterMdat, rulesSchema, setLogger } from "remark-mdat";
|
|
4
|
+
import { getMdatReports, getSoleRule, mdatCollapse, mdatDiff, mdatExpand, mdatSplit, mdatStrip, reporterMdat, rulesSchema, setLogger } from "remark-mdat";
|
|
5
|
+
import { remark } from "remark";
|
|
6
|
+
import remarkGfm from "remark-gfm";
|
|
5
7
|
import { cosmiconfig, defaultLoaders } from "cosmiconfig";
|
|
6
8
|
import { TypeScriptLoader } from "cosmiconfig-typescript-loader";
|
|
7
9
|
import fs from "node:fs/promises";
|
|
@@ -17,8 +19,6 @@ import { promisify } from "node:util";
|
|
|
17
19
|
import { brotliCompress, gzip } from "node:zlib";
|
|
18
20
|
import prettyBytes from "pretty-bytes";
|
|
19
21
|
import { toc } from "mdast-util-toc";
|
|
20
|
-
import { remark } from "remark";
|
|
21
|
-
import remarkGfm from "remark-gfm";
|
|
22
22
|
import { read, write } from "to-vfile";
|
|
23
23
|
import "vfile";
|
|
24
24
|
import { Configuration } from "unified-engine";
|
|
@@ -42,7 +42,7 @@ function deepMergeDefined(...objects) {
|
|
|
42
42
|
//#endregion
|
|
43
43
|
//#region package.json
|
|
44
44
|
var name = "mdat";
|
|
45
|
-
var version = "2.
|
|
45
|
+
var version = "2.2.0";
|
|
46
46
|
|
|
47
47
|
//#endregion
|
|
48
48
|
//#region src/lib/log.ts
|
|
@@ -603,7 +603,7 @@ async function formatWithPrettier(content, filePath) {
|
|
|
603
603
|
if (config === void 0 && !configCache.has(configKey)) {
|
|
604
604
|
config = await cachedPrettier.resolveConfig(filePath ?? process.cwd());
|
|
605
605
|
configCache.set(configKey, config);
|
|
606
|
-
if (config) log.debug(`
|
|
606
|
+
if (config) log.debug(`Resolved Prettier config for "${configKey}"`);
|
|
607
607
|
}
|
|
608
608
|
return cachedPrettier.format(content, {
|
|
609
609
|
...config,
|
|
@@ -662,7 +662,8 @@ async function findReadme() {
|
|
|
662
662
|
}
|
|
663
663
|
/**
|
|
664
664
|
* Searches up for a readme.md file.
|
|
665
|
-
*
|
|
665
|
+
*
|
|
666
|
+
* @throws {Error} If no readme is found
|
|
666
667
|
*/
|
|
667
668
|
async function findReadmeThrows() {
|
|
668
669
|
const readme = await findReadme();
|
|
@@ -883,9 +884,8 @@ async function createReadme(options) {
|
|
|
883
884
|
return readmePath;
|
|
884
885
|
}
|
|
885
886
|
function getTemplateForConfig(templateKey, compound) {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
return templateString;
|
|
887
|
+
if (!(templateKey in templates_default)) throw new Error(`Unknown template "${templateKey}". Available templates: ${Object.keys(templates_default).join(", ")}`);
|
|
888
|
+
return templates_default[templateKey].content[compound ? "compound" : "explicit"];
|
|
889
889
|
}
|
|
890
890
|
function getTemplateOptions() {
|
|
891
891
|
return Object.entries(templates_default).map(([key, value]) => ({
|
|
@@ -949,10 +949,26 @@ async function check(files, config, options) {
|
|
|
949
949
|
const resolvedFiles = Array.isArray(files) ? files : [files];
|
|
950
950
|
const [originals, results] = await Promise.all([Promise.all(resolvedFiles.map(async (f) => read(f))), processFiles(files, loadConfig, getExpandProcessor, void 0, void 0, config)]);
|
|
951
951
|
if (options?.format) await formatResults(results);
|
|
952
|
-
return results.map((result, i) => (
|
|
953
|
-
|
|
952
|
+
return results.map((result, i) => compareWithDiff(originals[i], result, options));
|
|
953
|
+
}
|
|
954
|
+
function compareWithDiff(original, result, options) {
|
|
955
|
+
const inSync = original.toString().replaceAll("\r\n", "\n") === result.toString();
|
|
956
|
+
if (!inSync) {
|
|
957
|
+
const parser = remark().use(remarkGfm);
|
|
958
|
+
const originalTree = parser.parse(original.toString());
|
|
959
|
+
mdatSplit(originalTree, original);
|
|
960
|
+
const expandedTree = parser.parse(result.toString());
|
|
961
|
+
mdatSplit(expandedTree, result);
|
|
962
|
+
const diffResults = mdatDiff(originalTree, original, expandedTree, result);
|
|
963
|
+
if (options?.format && diffResults.every((r) => r.status === "ok")) {
|
|
964
|
+
const message = result.message("Formatting differences outside mdat tags", { source: "diff" });
|
|
965
|
+
message.fatal = false;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
return {
|
|
969
|
+
inSync,
|
|
954
970
|
result
|
|
955
|
-
}
|
|
971
|
+
};
|
|
956
972
|
}
|
|
957
973
|
async function formatResults(results) {
|
|
958
974
|
for (const file of results) file.value = await formatWithPrettier(file.toString(), file.path || void 0);
|
|
@@ -1087,6 +1103,7 @@ try {
|
|
|
1087
1103
|
if (inSync) log.debug(`${picocolors.green("Up to date")}: ${filePath}`);
|
|
1088
1104
|
else {
|
|
1089
1105
|
log.warn(`${picocolors.red("Stale content")}: ${filePath}`);
|
|
1106
|
+
reporterMdat([result]);
|
|
1090
1107
|
allInSync = false;
|
|
1091
1108
|
}
|
|
1092
1109
|
}
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -127,6 +127,16 @@ declare function check(files?: string | string[], config?: ConfigToLoad, options
|
|
|
127
127
|
inSync: boolean;
|
|
128
128
|
result: VFile;
|
|
129
129
|
}>>;
|
|
130
|
+
/**
|
|
131
|
+
* Check if MDAT comments in a Markdown string are up to date by expanding and
|
|
132
|
+
* comparing per-tag.
|
|
133
|
+
*/
|
|
134
|
+
declare function checkString(markdown: string, config?: ConfigToLoad, options?: {
|
|
135
|
+
format?: boolean;
|
|
136
|
+
}): Promise<{
|
|
137
|
+
inSync: boolean;
|
|
138
|
+
result: VFile;
|
|
139
|
+
}>;
|
|
130
140
|
//#endregion
|
|
131
141
|
//#region src/lib/context.d.ts
|
|
132
142
|
/**
|
|
@@ -180,4 +190,4 @@ declare function resetMetadataCaches(): void;
|
|
|
180
190
|
*/
|
|
181
191
|
declare function setLogger(logger?: ILogBasic | ILogLayer): void;
|
|
182
192
|
//#endregion
|
|
183
|
-
export { type Config, type ReadmeMetadata, type Rule, check, collapse, collapseString, createReadme as create, createReadmeInteractive as createInteractive, defineConfig, expand, expandString, getContextMetadata, getReadmeMetadata, loadConfig, mergeConfig, resetMetadataCaches, setLogger, strip, stripString };
|
|
193
|
+
export { type Config, type ReadmeMetadata, type Rule, check, checkString, collapse, collapseString, createReadme as create, createReadmeInteractive as createInteractive, defineConfig, expand, expandString, getContextMetadata, getReadmeMetadata, loadConfig, mergeConfig, resetMetadataCaches, setLogger, strip, stripString };
|
package/dist/lib/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
import { remark } from "remark";
|
|
2
|
+
import remarkGfm from "remark-gfm";
|
|
3
|
+
import { getSoleRule, mdatCollapse, mdatDiff, mdatExpand, mdatSplit, mdatStrip, rulesSchema, setLogger as setLogger$1 } from "remark-mdat";
|
|
1
4
|
import { cosmiconfig, defaultLoaders } from "cosmiconfig";
|
|
2
5
|
import { TypeScriptLoader } from "cosmiconfig-typescript-loader";
|
|
3
6
|
import fs from "node:fs/promises";
|
|
4
7
|
import path from "node:path";
|
|
5
8
|
import picocolors from "picocolors";
|
|
6
9
|
import plur from "plur";
|
|
7
|
-
import { getSoleRule, mdatCollapse, mdatExpand, mdatSplit, mdatStrip, rulesSchema, setLogger as setLogger$1 } from "remark-mdat";
|
|
8
10
|
import { deepmerge } from "deepmerge-ts";
|
|
9
11
|
import { createLogger, getChildLogger, injectionHelper } from "lognow";
|
|
10
12
|
import { defineTemplate, getMetadata, helpers, setLogger as setLogger$2, templates } from "metascope";
|
|
@@ -15,8 +17,6 @@ import { promisify } from "node:util";
|
|
|
15
17
|
import { brotliCompress, gzip } from "node:zlib";
|
|
16
18
|
import prettyBytes from "pretty-bytes";
|
|
17
19
|
import { toc } from "mdast-util-toc";
|
|
18
|
-
import { remark } from "remark";
|
|
19
|
-
import remarkGfm from "remark-gfm";
|
|
20
20
|
import { read, write } from "to-vfile";
|
|
21
21
|
import { VFile } from "vfile";
|
|
22
22
|
import { Configuration } from "unified-engine";
|
|
@@ -124,7 +124,8 @@ const GIT_PREFIX_REGEX = /^git\+/;
|
|
|
124
124
|
const GIT_SUFFIX_REGEX = /\.git$/;
|
|
125
125
|
const TRAILING_SLASH_REGEX = /\/$/;
|
|
126
126
|
/**
|
|
127
|
-
* Reset
|
|
127
|
+
* Reset cached context metadata. Call between tests or when the underlying
|
|
128
|
+
* project files may have changed on disk.
|
|
128
129
|
*
|
|
129
130
|
* @public
|
|
130
131
|
*/
|
|
@@ -163,7 +164,8 @@ async function getReadmeMetadata() {
|
|
|
163
164
|
return readmeMetadata;
|
|
164
165
|
}
|
|
165
166
|
/**
|
|
166
|
-
* Reset
|
|
167
|
+
* Reset cached readme metadata. Call between tests or when the underlying
|
|
168
|
+
* project files may have changed on disk.
|
|
167
169
|
*
|
|
168
170
|
* @public
|
|
169
171
|
*/
|
|
@@ -609,7 +611,7 @@ async function formatWithPrettier(content, filePath) {
|
|
|
609
611
|
if (config === void 0 && !configCache.has(configKey)) {
|
|
610
612
|
config = await cachedPrettier.resolveConfig(filePath ?? process.cwd());
|
|
611
613
|
configCache.set(configKey, config);
|
|
612
|
-
if (config) log.debug(`
|
|
614
|
+
if (config) log.debug(`Resolved Prettier config for "${configKey}"`);
|
|
613
615
|
}
|
|
614
616
|
return cachedPrettier.format(content, {
|
|
615
617
|
...config,
|
|
@@ -667,7 +669,8 @@ async function findReadme() {
|
|
|
667
669
|
}
|
|
668
670
|
/**
|
|
669
671
|
* Searches up for a readme.md file.
|
|
670
|
-
*
|
|
672
|
+
*
|
|
673
|
+
* @throws {Error} If no readme is found
|
|
671
674
|
*/
|
|
672
675
|
async function findReadmeThrows() {
|
|
673
676
|
const readme = await findReadme();
|
|
@@ -883,9 +886,8 @@ async function createReadme(options) {
|
|
|
883
886
|
return readmePath;
|
|
884
887
|
}
|
|
885
888
|
function getTemplateForConfig(templateKey, compound) {
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
return templateString;
|
|
889
|
+
if (!(templateKey in templates_default)) throw new Error(`Unknown template "${templateKey}". Available templates: ${Object.keys(templates_default).join(", ")}`);
|
|
890
|
+
return templates_default[templateKey].content[compound ? "compound" : "explicit"];
|
|
889
891
|
}
|
|
890
892
|
function getTemplateOptions() {
|
|
891
893
|
return Object.entries(templates_default).map(([key, value]) => ({
|
|
@@ -972,13 +974,40 @@ async function check(files, config, options) {
|
|
|
972
974
|
const resolvedFiles = Array.isArray(files) ? files : [files];
|
|
973
975
|
const [originals, results] = await Promise.all([Promise.all(resolvedFiles.map(async (f) => read(f))), processFiles(files, loadConfig, getExpandProcessor, void 0, void 0, config)]);
|
|
974
976
|
if (options?.format) await formatResults(results);
|
|
975
|
-
return results.map((result, i) => (
|
|
976
|
-
|
|
977
|
+
return results.map((result, i) => compareWithDiff(originals[i], result, options));
|
|
978
|
+
}
|
|
979
|
+
/**
|
|
980
|
+
* Check if MDAT comments in a Markdown string are up to date by expanding and
|
|
981
|
+
* comparing per-tag.
|
|
982
|
+
*/
|
|
983
|
+
async function checkString(markdown, config, options) {
|
|
984
|
+
const { VFile: VF } = await import("vfile");
|
|
985
|
+
const original = new VF(markdown);
|
|
986
|
+
const result = await processString(markdown, loadConfig, getExpandProcessor, config);
|
|
987
|
+
if (options?.format) await formatResults([result]);
|
|
988
|
+
return compareWithDiff(original, result, options);
|
|
989
|
+
}
|
|
990
|
+
function compareWithDiff(original, result, options) {
|
|
991
|
+
const inSync = original.toString().replaceAll("\r\n", "\n") === result.toString();
|
|
992
|
+
if (!inSync) {
|
|
993
|
+
const parser = remark().use(remarkGfm);
|
|
994
|
+
const originalTree = parser.parse(original.toString());
|
|
995
|
+
mdatSplit(originalTree, original);
|
|
996
|
+
const expandedTree = parser.parse(result.toString());
|
|
997
|
+
mdatSplit(expandedTree, result);
|
|
998
|
+
const diffResults = mdatDiff(originalTree, original, expandedTree, result);
|
|
999
|
+
if (options?.format && diffResults.every((r) => r.status === "ok")) {
|
|
1000
|
+
const message = result.message("Formatting differences outside mdat tags", { source: "diff" });
|
|
1001
|
+
message.fatal = false;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
return {
|
|
1005
|
+
inSync,
|
|
977
1006
|
result
|
|
978
|
-
}
|
|
1007
|
+
};
|
|
979
1008
|
}
|
|
980
1009
|
async function formatResults(results) {
|
|
981
1010
|
for (const file of results) file.value = await formatWithPrettier(file.toString(), file.path || void 0);
|
|
982
1011
|
}
|
|
983
1012
|
//#endregion
|
|
984
|
-
export { check, collapse, collapseString, createReadme as create, createReadmeInteractive as createInteractive, defineConfig, expand, expandString, getContextMetadata, getReadmeMetadata, loadConfig, mergeConfig, resetMetadataCaches, setLogger, strip, stripString };
|
|
1013
|
+
export { check, checkString, collapse, collapseString, createReadme as create, createReadmeInteractive as createInteractive, defineConfig, expand, expandString, getContextMetadata, getReadmeMetadata, loadConfig, mergeConfig, resetMetadataCaches, setLogger, strip, stripString };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mdat",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "CLI tool and TypeScript library implementing the Markdown Autophagic Template (MDAT) system. MDAT lets you use comments as dynamic content templates in Markdown files, making it easy to generate and update readme boilerplate.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mdat",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"pretty-ms": "^9.3.0",
|
|
59
59
|
"remark": "^15.0.1",
|
|
60
60
|
"remark-gfm": "^4.0.1",
|
|
61
|
-
"remark-mdat": "^2.
|
|
61
|
+
"remark-mdat": "^2.2.0",
|
|
62
62
|
"to-vfile": "^8.0.0",
|
|
63
63
|
"type-fest": "^5.5.0",
|
|
64
64
|
"unified-engine": "^11.2.2",
|
|
@@ -74,11 +74,12 @@
|
|
|
74
74
|
"@types/node": "~22.17.2",
|
|
75
75
|
"@types/unist": "^3.0.3",
|
|
76
76
|
"@types/yargs": "^17.0.35",
|
|
77
|
+
"@vitest/coverage-v8": "4.1.2",
|
|
77
78
|
"bumpp": "^11.0.1",
|
|
78
79
|
"execa": "^9.6.1",
|
|
79
80
|
"mdat-plugin-cli-help": "^2.0.2",
|
|
80
81
|
"mdat-plugin-example": "^2.0.0",
|
|
81
|
-
"mdat-plugin-tldraw": "^2.0.
|
|
82
|
+
"mdat-plugin-tldraw": "^2.0.1",
|
|
82
83
|
"nanoid": "^5.1.7",
|
|
83
84
|
"prettier": "^3.8.1",
|
|
84
85
|
"publint": "^0.3.18",
|
|
@@ -109,6 +110,7 @@
|
|
|
109
110
|
"bench:baseline": "vitest bench --run --outputJson test/benchmarks/baseline.json",
|
|
110
111
|
"build": "tsdown",
|
|
111
112
|
"clean": "git rm -f pnpm-lock.yaml ; git clean -fdX",
|
|
113
|
+
"coverage": "vitest --coverage --run",
|
|
112
114
|
"fix": "ksc fix",
|
|
113
115
|
"lint": "ksc lint",
|
|
114
116
|
"release": "bumpp --commit 'Release: %s' && pnpm run build && NPM_AUTH_TOKEN=$(op read 'op://Personal/npm/token') && pnpm publish",
|
package/readme.md
CHANGED
|
@@ -413,19 +413,9 @@ mdat create
|
|
|
413
413
|
|
|
414
414
|
`mdat` exports functions for expanding, collapsing, checking, and creating Markdown files programmatically.
|
|
415
415
|
|
|
416
|
-
#### `expand`
|
|
416
|
+
#### `expand` / `expandString`
|
|
417
417
|
|
|
418
|
-
|
|
419
|
-
function expand(
|
|
420
|
-
files?: string | string[],
|
|
421
|
-
name?: string,
|
|
422
|
-
output?: string,
|
|
423
|
-
config?: ConfigToLoad,
|
|
424
|
-
options?: { format?: boolean },
|
|
425
|
-
): Promise<VFile[]>
|
|
426
|
-
```
|
|
427
|
-
|
|
428
|
-
Expands MDAT comments in one or more files. If no files are provided, auto-finds the closest readme. Writing is the caller's responsibility:
|
|
418
|
+
Expands MDAT comments in one or more files. If no files are provided, auto-finds the closest readme. Writing is the caller's responsibility. Call `.toString()` on the returned [VFile](https://github.com/vfile) to get the result.
|
|
429
419
|
|
|
430
420
|
```ts
|
|
431
421
|
import { expand } from 'mdat'
|
|
@@ -435,8 +425,6 @@ const [file] = await expand('readme.md')
|
|
|
435
425
|
await write(file)
|
|
436
426
|
```
|
|
437
427
|
|
|
438
|
-
#### `expandString`
|
|
439
|
-
|
|
440
428
|
```ts
|
|
441
429
|
function expandString(
|
|
442
430
|
markdown: string,
|
|
@@ -445,8 +433,6 @@ function expandString(
|
|
|
445
433
|
): Promise<VFile>
|
|
446
434
|
```
|
|
447
435
|
|
|
448
|
-
Expands MDAT comments in a Markdown string. Call `.toString()` on the returned [VFile](https://github.com/vfile) to get the result.
|
|
449
|
-
|
|
450
436
|
#### `collapse` / `collapseString`
|
|
451
437
|
|
|
452
438
|
Removes expanded content, leaving only the opening comment placeholders. Same signatures as `expand` / `expandString`.
|
|
@@ -457,17 +443,9 @@ Strips all MDAT comment tags (both opening and closing) while preserving expande
|
|
|
457
443
|
|
|
458
444
|
This is useful for producing a "clean" Markdown file that no longer depends on MDAT for future updates.
|
|
459
445
|
|
|
460
|
-
#### `check`
|
|
461
|
-
|
|
462
|
-
```ts
|
|
463
|
-
function check(
|
|
464
|
-
files?: string | string[],
|
|
465
|
-
config?: ConfigToLoad,
|
|
466
|
-
options?: { format?: boolean },
|
|
467
|
-
): Promise<{ inSync: boolean; results: VFile[] }>
|
|
468
|
-
```
|
|
446
|
+
#### `check` / `checkString`
|
|
469
447
|
|
|
470
|
-
Dry-run expand and compare with the file on disk. Returns `inSync: false` if the file would change.
|
|
448
|
+
Dry-run expand and compare with the file on disk. Returns `inSync: false` if the file would change. When stale, per-tag diagnostic messages are added to the result VFile identifying which specific tags are stale or unexpanded. Use `reporterMdat` from `remark-mdat` to display these messages.
|
|
471
449
|
|
|
472
450
|
#### `create` / `createInteractive`
|
|
473
451
|
|
|
@@ -816,30 +794,6 @@ There's quite a bit of prior art and similar explorations of this problem space:
|
|
|
816
794
|
|
|
817
795
|
- VitePress' [Markdown file inclusion](https://vitepress.dev/guide/markdown#markdown-file-inclusion)
|
|
818
796
|
|
|
819
|
-
There's quite a bit of prior art and similar explorations of this problem space:
|
|
820
|
-
|
|
821
|
-
- Benjamin Lupton's [projectz](https://github.com/bevry/projectz)\
|
|
822
|
-
Goes way back.
|
|
823
|
-
|
|
824
|
-
- David Wells' [Markdown Magic](https://github.com/DavidWells/markdown-magic)\
|
|
825
|
-
I somehow missed the existence of this one until after building out MDAT. It's very similar conceptually, and has a nice ecosystem of plugins.
|
|
826
|
-
|
|
827
|
-
- Titus Wormer's [mdast-zone](https://github.com/syntax-tree/mdast-zone)\
|
|
828
|
-
Allows comments to be used as ranges or markers in Markdown files. Similar tree parsing and walking strategy to MDAT. Mdast-zone uses different syntax for arguments, and requires both opening and closing tags to be present for expansion to occur.
|
|
829
|
-
|
|
830
|
-
- Jason Dent's [inject-markdown](https://github.com/streetsidesoftware/inject-markdown)
|
|
831
|
-
|
|
832
|
-
- lillallol's [md-in-place](https://www.npmjs.com/package/md-in-place)
|
|
833
|
-
|
|
834
|
-
- [AutoMD](https://automd.unjs.io/)\
|
|
835
|
-
Extremely similar functionality to mdat. The project was initiated around the same time as MDAT, but I didn't find the project until a few years later. Ships in the night.
|
|
836
|
-
|
|
837
|
-
- Franck Abgrall's [readme-md-generator](https://github.com/kefranabg/readme-md-generator)
|
|
838
|
-
|
|
839
|
-
- Anders Pitman's [tuplates](https://github.com/anderspitman/tuplates-py)
|
|
840
|
-
|
|
841
|
-
- VitePress' [Markdown file inclusion](https://vitepress.dev/guide/markdown#markdown-file-inclusion)
|
|
842
|
-
|
|
843
797
|
### Implementation notes
|
|
844
798
|
|
|
845
799
|
This project was split from a monorepo containing both `mdat` and `remark-mdat` into separate repos in July 2024.
|