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 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.1.0";
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(`Using Prettier config from "${config.filepath}" for "${filePath ?? process.cwd()}"`);
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
- * @throws {Error} if no readme is found
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
- const templateString = templates_default[templateKey].content[compound ? "compound" : "explicit"];
887
- if (templateString === void 0 || templateString === "") throw new Error(`No template found for "${templateKey}"`);
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
- inSync: originals[i].toString().replaceAll("\r\n", "\n") === result.toString(),
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
  }
@@ -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(`Using Prettier config from "${config.filepath}" for "${filePath ?? process.cwd()}"`);
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
- * @throws {Error} if no readme is found
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
- const templateString = templates_default[templateKey].content[compound ? "compound" : "explicit"];
887
- if (templateString === void 0 || templateString === "") throw new Error(`No template found for "${templateKey}"`);
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
- inSync: originals[i].toString().replaceAll("\r\n", "\n") === result.toString(),
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.1.0",
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.1.0",
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.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
- ```ts
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.