mdat 2.0.0 → 2.1.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/bin/cli.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
  import picocolors from "picocolors";
3
3
  import prettyMilliseconds from "pretty-ms";
4
- import { getMdatReports, getSoleRule, mdatClean, mdatExpand, mdatSplit, reporterMdat, rulesSchema, setLogger } from "remark-mdat";
4
+ import { getMdatReports, getSoleRule, mdatCollapse, mdatExpand, mdatSplit, mdatStrip, reporterMdat, rulesSchema, setLogger } from "remark-mdat";
5
5
  import { cosmiconfig, defaultLoaders } from "cosmiconfig";
6
6
  import { TypeScriptLoader } from "cosmiconfig-typescript-loader";
7
7
  import fs from "node:fs/promises";
8
8
  import path from "node:path";
9
9
  import plur from "plur";
10
- import { defineTemplate, getMetadata, helpers, setLogger as setLogger$1, templates } from "metascope";
11
10
  import { deepmerge } from "deepmerge-ts";
12
11
  import { createLogger, getChildLogger, injectionHelper } from "lognow";
12
+ import { defineTemplate, getMetadata, helpers, setLogger as setLogger$1, templates } from "metascope";
13
13
  import { z } from "zod";
14
14
  import { globby } from "globby";
15
15
  import { isFile, isFileSync } from "path-type";
@@ -27,65 +27,6 @@ import { confirm, group, intro, note, outro, select } from "@clack/prompts";
27
27
  import yargs from "yargs";
28
28
  import { hideBin } from "yargs/helpers";
29
29
 
30
- //#region src/lib/context.ts
31
- let metascopeMetadata;
32
- /**
33
- * Get a bunch of platform-agnostic local metadata via metascope, exposed
34
- * primarily for plugin developers.
35
- * Result is memoized the result.
36
- * @throws {Error} If no package.json is found
37
- */
38
- async function getContextMetadata() {
39
- if (metascopeMetadata !== void 0) return metascopeMetadata;
40
- metascopeMetadata = await getMetadata({
41
- absolute: false,
42
- offline: true
43
- });
44
- return metascopeMetadata;
45
- }
46
- /**
47
- * Reset
48
- */
49
- function resetContextMetadata() {
50
- metascopeMetadata = void 0;
51
- }
52
- const readmeMetadataTemplate = defineTemplate((context) => {
53
- const { githubActions, licenseFile, metascope, nodePackageJson } = context;
54
- const codemeta = templates.codemetaJson(context, {});
55
- const nodePackage = helpers.firstOf(nodePackageJson)?.data;
56
- const licenseFileData = helpers.firstOf(licenseFile);
57
- const ciActionFilePath = helpers.ensureArray(githubActions).find((entry) => entry.data.name.toLowerCase() === "ci")?.source;
58
- const repositoryOwner = codemeta.codeRepository ? new URL(codemeta.codeRepository).pathname.split("/")[1] : void 0;
59
- return {
60
- author: helpers.firstOf(helpers.mixedStringsToArray(helpers.toBasicNames(codemeta.author))),
61
- ciActionFileName: ciActionFilePath ? path.basename(ciActionFilePath) : void 0,
62
- description: codemeta.description,
63
- isPublicNpmPackage: !nodePackage?.name.startsWith("@") || nodePackage.publishConfig?.access === "public",
64
- issuesUrl: codemeta.issueTracker,
65
- license: helpers.toBasicLicense(helpers.firstOf(helpers.ensureArray(codemeta.license))),
66
- licenseFilePath: licenseFileData?.source,
67
- name: codemeta.name,
68
- projectDirectory: metascope?.data.options.path === void 0 ? void 0 : `file://${metascope.data.options.path}`,
69
- repositoryOwner
70
- };
71
- });
72
- let readmeMetadata;
73
- /**
74
- * Nice data for readme rules
75
- */
76
- async function getReadmeMetadata() {
77
- if (readmeMetadata !== void 0) return readmeMetadata;
78
- readmeMetadata = readmeMetadataTemplate(await getContextMetadata(), {});
79
- return readmeMetadata;
80
- }
81
- /**
82
- * Reset
83
- */
84
- function resetReadmeMetadata() {
85
- readmeMetadata = void 0;
86
- }
87
-
88
- //#endregion
89
30
  //#region src/lib/deep-merge-defined.ts
90
31
  function stripUndefinedDeep(object) {
91
32
  if (Array.isArray(object)) return object.map((v) => v && typeof v === "object" ? stripUndefinedDeep(v) : v).filter((v) => v !== void 0);
@@ -101,7 +42,7 @@ function deepMergeDefined(...objects) {
101
42
  //#endregion
102
43
  //#region package.json
103
44
  var name = "mdat";
104
- var version = "2.0.0";
45
+ var version = "2.1.0";
105
46
 
106
47
  //#endregion
107
48
  //#region src/lib/log.ts
@@ -146,6 +87,87 @@ function flattenJson(jsonObject, parentKey = "", result = {}) {
146
87
  return result;
147
88
  }
148
89
 
90
+ //#endregion
91
+ //#region src/lib/context.ts
92
+ let metascopeMetadata;
93
+ /**
94
+ * Get a bunch of platform-agnostic local metadata via metascope, exposed
95
+ * primarily for plugin developers. Result is memoized the result.
96
+ *
97
+ * @throws {Error} If no package.json is found
98
+ */
99
+ async function getContextMetadata() {
100
+ if (metascopeMetadata !== void 0) return metascopeMetadata;
101
+ metascopeMetadata = await getMetadata({
102
+ absolute: false,
103
+ offline: true,
104
+ sources: [
105
+ "arduinoLibraryProperties",
106
+ "cinderCinderblockXml",
107
+ "codemetaJson",
108
+ "gitConfig",
109
+ "githubActions",
110
+ "goGoMod",
111
+ "goGoreleaserYaml",
112
+ "javaPomXml",
113
+ "licenseFile",
114
+ "metadataFile",
115
+ "metascope",
116
+ "nodePackageJson",
117
+ "obsidianPluginManifestJson",
118
+ "openframeworksAddonConfigMk",
119
+ "openframeworksInstallXml",
120
+ "processingLibraryProperties",
121
+ "processingSketchProperties",
122
+ "publiccodeYaml",
123
+ "pythonPkgInfo",
124
+ "pythonPyprojectToml",
125
+ "pythonSetupCfg",
126
+ "pythonSetupPy",
127
+ "readmeFile",
128
+ "rubyGemspec",
129
+ "rustCargoToml",
130
+ "xcodeInfoPlist",
131
+ "xcodeProjectPbxproj"
132
+ ]
133
+ });
134
+ return metascopeMetadata;
135
+ }
136
+ const GIT_PREFIX_REGEX = /^git\+/;
137
+ const GIT_SUFFIX_REGEX = /\.git$/;
138
+ const TRAILING_SLASH_REGEX = /\/$/;
139
+ const readmeMetadataTemplate = defineTemplate((context) => {
140
+ const { githubActions, licenseFile, metascope, nodePackageJson } = context;
141
+ const codemeta = templates.codemetaJson(context, {});
142
+ const nodePackage = helpers.firstOf(nodePackageJson)?.data;
143
+ const licenseFileData = helpers.firstOf(licenseFile);
144
+ const ciActionFilePath = helpers.ensureArray(githubActions).find((entry) => entry.data.name.toLowerCase() === "ci")?.source;
145
+ const repositoryUrl = codemeta.codeRepository?.replace(GIT_PREFIX_REGEX, "").replace(GIT_SUFFIX_REGEX, "").replace(TRAILING_SLASH_REGEX, "");
146
+ return {
147
+ author: helpers.firstOf(helpers.mixedStringsToArray(helpers.toBasicNames(codemeta.author))),
148
+ ciActionFileName: ciActionFilePath ? path.basename(ciActionFilePath) : void 0,
149
+ description: codemeta.description,
150
+ isPublicNpmPackage: !nodePackage?.name.startsWith("@") || nodePackage.publishConfig?.access === "public",
151
+ issuesUrl: codemeta.issueTracker,
152
+ license: helpers.toBasicLicense(helpers.firstOf(helpers.ensureArray(codemeta.license))),
153
+ licenseFilePath: licenseFileData?.source,
154
+ name: codemeta.name,
155
+ projectDirectory: metascope?.data.options.path === void 0 ? void 0 : `file://${metascope.data.options.path}`,
156
+ repositoryUrl
157
+ };
158
+ });
159
+ let readmeMetadata;
160
+ /**
161
+ * Nice data for readme rules
162
+ *
163
+ * @public
164
+ */
165
+ async function getReadmeMetadata() {
166
+ if (readmeMetadata !== void 0) return readmeMetadata;
167
+ readmeMetadata = readmeMetadataTemplate(await getContextMetadata(), {});
168
+ return readmeMetadata;
169
+ }
170
+
149
171
  //#endregion
150
172
  //#region src/lib/readme/rules/badges.ts
151
173
  var badges_default = { badges: { async content(options) {
@@ -157,13 +179,13 @@ var badges_default = { badges: { async content(options) {
157
179
  npm: z.array(z.string()).optional()
158
180
  }).optional().parse(options);
159
181
  const metadata = await getReadmeMetadata();
160
- const { ciActionFileName, license, name, repositoryOwner } = metadata;
182
+ const { ciActionFileName, license, name, repositoryUrl } = metadata;
161
183
  const badges = [];
162
184
  if (validOptions?.npm === void 0) {
163
185
  if (metadata.isPublicNpmPackage) badges.push(`[![NPM Package ${name}](https://img.shields.io/npm/v/${name}.svg)](https://npmjs.com/package/${name})`);
164
186
  } else for (const name of validOptions.npm) badges.push(`[![NPM Package ${name}](https://img.shields.io/npm/v/${name}.svg)](https://npmjs.com/package/${name})`);
165
187
  if (license !== void 0) badges.push(`[![License: ${license}](https://img.shields.io/badge/License-${license.replaceAll("-", "--")}-yellow.svg)](https://opensource.org/licenses/${license})`);
166
- if (ciActionFileName !== void 0 && repositoryOwner !== void 0) badges.push(`[![CI](https://github.com/${repositoryOwner}/${name}/actions/workflows/${ciActionFileName}/badge.svg)](https://github.com/${repositoryOwner}/${name}/actions/workflows/${ciActionFileName})`);
188
+ if (ciActionFileName !== void 0 && repositoryUrl !== void 0) badges.push(`[![CI](${repositoryUrl}/actions/workflows/${ciActionFileName}/badge.svg)](${repositoryUrl}/actions/workflows/${ciActionFileName})`);
167
189
  if (validOptions?.custom !== void 0) for (const [name, { image, link }] of Object.entries(validOptions.custom)) badges.push(`[![${name}](${image})](${link})`);
168
190
  return badges.join("\n");
169
191
  } } };
@@ -231,24 +253,21 @@ function isUrl(text, lenient = true) {
231
253
  if (typeof text !== "string") throw new TypeError("Expected a string");
232
254
  text = text.trim();
233
255
  if (text.includes(" ")) return false;
234
- try {
235
- new URL(text);
236
- return true;
237
- } catch {
238
- if (lenient) return isUrl(`https://${text}`, false);
239
- return false;
240
- }
256
+ if (URL.canParse(text)) return true;
257
+ if (lenient) return isUrl(`https://${text}`, false);
258
+ return false;
241
259
  }
242
260
 
243
261
  //#endregion
244
262
  //#region src/lib/readme/rules/code.ts
263
+ const LEADING_DOT_REGEX = /^\./;
245
264
  var code_default = { code: { async content(options) {
246
265
  const validOptions = z.object({
247
266
  file: z.string(),
248
267
  language: z.string().optional(),
249
268
  trim: z.boolean().default(true)
250
269
  }).parse(options);
251
- const lang = (path.extname(validOptions.file) ?? "").replace(/^\./, "");
270
+ const lang = (path.extname(validOptions.file) ?? "").replace(LEADING_DOT_REGEX, "");
252
271
  const exampleCode = await fs.readFile(path.join(process.cwd(), validOptions.file), "utf8");
253
272
  return `\`\`\`${lang}\n${validOptions.trim ? exampleCode.trim() : exampleCode}\n\`\`\``;
254
273
  } } };
@@ -309,8 +328,9 @@ var title_default = { title: {
309
328
  },
310
329
  order: 2
311
330
  } };
331
+ const SPLIT_FOR_TITLE_CASE_REGEX = /[ _-]+/;
312
332
  function makeTitleCase(text) {
313
- return text.split(/[ _-]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
333
+ return text.split(SPLIT_FOR_TITLE_CASE_REGEX).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
314
334
  }
315
335
 
316
336
  //#endregion
@@ -497,6 +517,19 @@ var rules_default = {
497
517
 
498
518
  //#endregion
499
519
  //#region src/lib/config.ts
520
+ let _configExplorer;
521
+ let _additionalConfigExplorer;
522
+ function getConfigExplorer() {
523
+ _configExplorer ??= cosmiconfig("mdat", { loaders: { ".ts": TypeScriptLoader() } });
524
+ return _configExplorer;
525
+ }
526
+ function getAdditionalConfigExplorer() {
527
+ _additionalConfigExplorer ??= cosmiconfig("mdat", { loaders: {
528
+ ".json": mdatJsonLoader,
529
+ ".ts": TypeScriptLoader()
530
+ } });
531
+ return _additionalConfigExplorer;
532
+ }
500
533
  /**
501
534
  * Load and validate mdat configuration.
502
535
  * Uses cosmiconfig to search in the usual places.
@@ -504,11 +537,9 @@ var rules_default = {
504
537
  */
505
538
  async function loadConfig(options) {
506
539
  const { additionalConfig, defaults = rules_default, searchFrom } = options ?? {};
507
- resetReadmeMetadata();
508
- resetContextMetadata();
509
540
  let finalConfig = { mdat: `Powered by the Markdown Autophagic Template system: [mdat](https://github.com/kitschpatrol/mdat).` };
510
541
  if (defaults) finalConfig = deepMergeDefined(finalConfig, defaults);
511
- const results = await cosmiconfig("mdat", { loaders: { ".ts": TypeScriptLoader() } }).search(searchFrom);
542
+ const results = await getConfigExplorer().search(searchFrom);
512
543
  if (results) {
513
544
  const { config, filepath } = results;
514
545
  let possibleRules = config;
@@ -523,10 +554,6 @@ async function loadConfig(options) {
523
554
  }
524
555
  if (additionalConfig !== void 0) {
525
556
  const additionalConfigArray = Array.isArray(additionalConfig) ? additionalConfig : [additionalConfig];
526
- const configExplorer2 = cosmiconfig("mdat", { loaders: {
527
- ".json": mdatJsonLoader,
528
- ".ts": TypeScriptLoader()
529
- } });
530
557
  for (const configOrPath of additionalConfigArray) {
531
558
  let loaded;
532
559
  if (typeof configOrPath === "string") {
@@ -535,7 +562,7 @@ async function loadConfig(options) {
535
562
  config: mdatJsonLoader(configOrPath, await fs.readFile(configOrPath, "utf8")),
536
563
  filepath: configOrPath
537
564
  };
538
- else results = await configExplorer2.load(configOrPath);
565
+ else results = await getAdditionalConfigExplorer().load(configOrPath);
539
566
  if (results === null || results === void 0) continue;
540
567
  const { config: loadedConfig, filepath } = results;
541
568
  log.debug(`Loaded additional config from "${filepath}"`);
@@ -559,20 +586,26 @@ function validateConfig(value) {
559
586
 
560
587
  //#endregion
561
588
  //#region src/lib/format.ts
589
+ let cachedPrettier;
590
+ const configCache = /* @__PURE__ */ new Map();
562
591
  /**
563
592
  * Format a markdown string with Prettier, using config discovered from the file path.
564
593
  * Requires `prettier` to be installed as a peer dependency.
565
594
  */
566
595
  async function formatWithPrettier(content, filePath) {
567
- let prettier;
568
- try {
569
- prettier = await import("prettier");
596
+ if (cachedPrettier === void 0) try {
597
+ cachedPrettier = await import("prettier");
570
598
  } catch {
571
599
  throw new Error("The --format flag requires `prettier` to be installed. Run: pnpm add -D prettier");
572
600
  }
573
- const config = await prettier.resolveConfig(filePath ?? process.cwd());
574
- if (config) log.debug(`Using Prettier config from "${config.filepath}" for "${filePath ?? process.cwd()}"`);
575
- return prettier.format(content, {
601
+ const configKey = filePath ? path.dirname(filePath) : process.cwd();
602
+ let config = configCache.get(configKey);
603
+ if (config === void 0 && !configCache.has(configKey)) {
604
+ config = await cachedPrettier.resolveConfig(filePath ?? process.cwd());
605
+ configCache.set(configKey, config);
606
+ if (config) log.debug(`Using Prettier config from "${config.filepath}" for "${filePath ?? process.cwd()}"`);
607
+ }
608
+ return cachedPrettier.format(content, {
576
609
  ...config,
577
610
  filepath: filePath,
578
611
  parser: "markdown"
@@ -614,12 +647,13 @@ function ensureArray(value) {
614
647
  if (value === void 0 || value === null) return [];
615
648
  return Array.isArray(value) ? value : [value];
616
649
  }
650
+ const README_SEARCH_REGEX = /^readme(?:\.\w+)?$/i;
617
651
  /**
618
652
  * Finds a readme file in the current working directory (case-insensitive).
619
653
  */
620
654
  async function findReadme() {
621
655
  log.debug("Searching for readme in current directory...");
622
- const readme = (await fs.readdir(process.cwd())).find((entry) => /^readme(?:\.\w+)?$/i.test(entry));
656
+ const readme = (await fs.readdir(process.cwd())).find((entry) => README_SEARCH_REGEX.test(entry));
623
657
  if (readme !== void 0) {
624
658
  const absolutePath = path.resolve(readme);
625
659
  log.debug(`Found readme at "${absolutePath}"`);
@@ -635,7 +669,9 @@ async function findReadmeThrows() {
635
669
  if (readme === void 0) throw new Error("No readme found");
636
670
  return readme;
637
671
  }
672
+ let cachedAmbientRemarkConfig;
638
673
  async function loadAmbientRemarkConfig() {
674
+ if (cachedAmbientRemarkConfig !== void 0) return cachedAmbientRemarkConfig;
639
675
  const ambientConfig = new Configuration({
640
676
  cwd: process.cwd(),
641
677
  detectConfig: true,
@@ -656,32 +692,47 @@ async function loadAmbientRemarkConfig() {
656
692
  const { filePath } = configResult;
657
693
  if (filePath === void 0) log.debug("No ambient Remark configuration file found");
658
694
  else log.debug(`Found and loaded ambient Remark configuration from "${filePath}"`);
659
- return configResult;
695
+ cachedAmbientRemarkConfig = stripLintPlugins(configResult);
696
+ return cachedAmbientRemarkConfig;
660
697
  }
661
698
  log.debug("No ambient Remark configuration found");
662
- return {
699
+ cachedAmbientRemarkConfig = {
663
700
  filePath: void 0,
664
701
  plugins: [],
665
702
  settings: {}
666
703
  };
704
+ return cachedAmbientRemarkConfig;
705
+ }
706
+ /**
707
+ * Strip remark-lint plugins from an ambient config. Lint plugins only produce
708
+ * VFile warnings and never modify the AST or output — running them during
709
+ * expansion is pure overhead.
710
+ */
711
+ function stripLintPlugins(config) {
712
+ return {
713
+ ...config,
714
+ plugins: config.plugins.filter((entry) => {
715
+ const plugin = Array.isArray(entry) ? entry[0] : entry;
716
+ if (typeof plugin !== "function") return true;
717
+ const { name } = plugin;
718
+ return name !== "remarkLint" && !name.startsWith("remark-lint:") && name !== "remarkValidateLinks";
719
+ })
720
+ };
667
721
  }
668
722
 
669
723
  //#endregion
670
724
  //#region src/lib/processors.ts
671
725
  async function processFiles(files, loader, processorGetter, name, output, config) {
672
- const resolvedConfig = await loader({ additionalConfig: config });
673
- const localRemarkConfiguration = await loadAmbientRemarkConfig();
726
+ const [resolvedConfig, localRemarkConfiguration] = await Promise.all([loader({ additionalConfig: config }), loadAmbientRemarkConfig()]);
674
727
  const inputOutputPaths = await getInputOutputPaths(ensureArray(files), output, name, "md");
675
- const results = [];
676
728
  const resolvedProcessor = processorGetter(resolvedConfig, localRemarkConfiguration);
677
- for (const { input, name, output } of inputOutputPaths) {
729
+ return await Promise.all(inputOutputPaths.map(async ({ input, name, output }) => {
678
730
  const inputFile = await read(input);
679
731
  const result = await resolvedProcessor.process(inputFile);
680
732
  result.dirname = output;
681
733
  result.basename = name;
682
- results.push(result);
683
- }
684
- return results;
734
+ return result;
735
+ }));
685
736
  }
686
737
  function getExpandProcessor(config, ambientRemarkConfig) {
687
738
  return remark().use({ settings: {
@@ -689,17 +740,26 @@ function getExpandProcessor(config, ambientRemarkConfig) {
689
740
  emphasis: "_"
690
741
  } }).use(remarkGfm).use(ambientRemarkConfig).use(() => async function(tree, file) {
691
742
  mdatSplit(tree, file);
692
- mdatClean(tree, file);
743
+ mdatCollapse(tree, file);
693
744
  await mdatExpand(tree, file, config);
694
745
  });
695
746
  }
696
- function getCleanProcessor(_config, ambientRemarkConfig) {
747
+ function getCollapseProcessor(_config, ambientRemarkConfig) {
748
+ return remark().use({ settings: {
749
+ bullet: "-",
750
+ emphasis: "_"
751
+ } }).use(remarkGfm).use(ambientRemarkConfig).use(() => function(tree, file) {
752
+ mdatSplit(tree, file);
753
+ mdatCollapse(tree, file);
754
+ });
755
+ }
756
+ function getStripProcessor(_config, ambientRemarkConfig) {
697
757
  return remark().use({ settings: {
698
758
  bullet: "-",
699
759
  emphasis: "_"
700
760
  } }).use(remarkGfm).use(ambientRemarkConfig).use(() => function(tree, file) {
701
761
  mdatSplit(tree, file);
702
- mdatClean(tree, file);
762
+ mdatStrip(tree, file);
703
763
  });
704
764
  }
705
765
 
@@ -838,10 +898,11 @@ function getTemplateOptions() {
838
898
  //#endregion
839
899
  //#region src/lib/api.ts
840
900
  /**
841
- * Expand MDAT comments in one or more Markdown files.
842
- * If no files are provided, auto-finds the closest readme.md.
843
- * Writing is the responsibility of the caller (e.g. via `await write(result)`)
844
- * @returns an array of VFiles
901
+ * Expand MDAT comments in one or more Markdown files. If no files are provided,
902
+ * auto-finds the closest readme.md. Writing is the responsibility of the caller
903
+ * (e.g. via `await write(result)`)
904
+ *
905
+ * @returns An array of VFiles
845
906
  */
846
907
  async function expand(files, name, output, config, options) {
847
908
  files ??= await findReadmeThrows();
@@ -850,31 +911,46 @@ async function expand(files, name, output, config, options) {
850
911
  return results;
851
912
  }
852
913
  /**
853
- * Collapse MDAT comments in one or more Markdown files.
854
- * If no files are provided, auto-finds the closest readme.md.
855
- * Writing is the responsibility of the caller (e.g. via `await write(result)`)
856
- * @returns an array of VFiles
914
+ * Collapse MDAT comments in one or more Markdown files. If no files are
915
+ * provided, auto-finds the closest readme.md. Writing is the responsibility of
916
+ * the caller (e.g. via `await write(result)`)
917
+ *
918
+ * @returns An array of VFiles
857
919
  */
858
920
  async function collapse(files, name, output, config, options) {
859
921
  files ??= await findReadmeThrows();
860
- const results = await processFiles(files, loadConfig, getCleanProcessor, name, output, config);
922
+ const results = await processFiles(files, loadConfig, getCollapseProcessor, name, output, config);
861
923
  if (options?.format) await formatResults(results);
862
924
  return results;
863
925
  }
864
926
  /**
865
- * Dry-run expand and compare with file on disk.
866
- * If no files are provided, auto-finds the closest readme.md.
867
- * @returns per-file sync status and expanded VFiles
927
+ * Strips MDAT comments in one or more Markdown files without touching other
928
+ * content. Does _not_ automatically expand Mdat content before stripping the
929
+ * tags. If no files are provided, auto-finds the closest readme.md. Writing is
930
+ * the responsibility of the caller (e.g. via `await write(result)`)
931
+ *
932
+ * @returns An array of VFiles
933
+ */
934
+ async function strip(files, name, output, config, options) {
935
+ files ??= await findReadmeThrows();
936
+ const results = await processFiles(files, loadConfig, getStripProcessor, name, output, config);
937
+ if (options?.format) await formatResults(results);
938
+ return results;
939
+ }
940
+ /**
941
+ * Dry-run expand and compare with file on disk. If no files are provided,
942
+ * auto-finds the closest readme.md.
943
+ *
944
+ * @returns Per-file sync status and expanded VFiles
868
945
  */
869
946
  async function check(files, config, options) {
870
947
  files ??= await findReadmeThrows();
871
948
  const { read } = await import("to-vfile");
872
949
  const resolvedFiles = Array.isArray(files) ? files : [files];
873
- const originals = await Promise.all(resolvedFiles.map(async (f) => read(f)));
874
- const results = await processFiles(files, loadConfig, getExpandProcessor, void 0, void 0, config);
950
+ const [originals, results] = await Promise.all([Promise.all(resolvedFiles.map(async (f) => read(f))), processFiles(files, loadConfig, getExpandProcessor, void 0, void 0, config)]);
875
951
  if (options?.format) await formatResults(results);
876
952
  return results.map((result, i) => ({
877
- inSync: originals[i].toString() === result.toString(),
953
+ inSync: originals[i].toString().replaceAll("\r\n", "\n") === result.toString(),
878
954
  result
879
955
  }));
880
956
  }
@@ -961,7 +1037,11 @@ try {
961
1037
  setLogger$2(createLogger({
962
1038
  name,
963
1039
  verbose: argv.verbose ?? false,
964
- logToConsole: { showTime: false }
1040
+ logToConsole: {
1041
+ showLevel: false,
1042
+ showName: false,
1043
+ showTime: false
1044
+ }
965
1045
  }));
966
1046
  }).command(["$0 [files..] [options]", "expand [files..] [options]"], "Expand MDAT placeholder comments. If no files are provided, the closest readme.md is expanded.", (yargs) => yargs.positional(...filesPositional).option(configOption).option(outputOption).option(nameOption).option(printOption).option(formatOption), async ({ config, files, format, name, output, print }) => {
967
1047
  logConflicts({
@@ -987,14 +1067,26 @@ try {
987
1067
  reporterMdat(results);
988
1068
  log.debug(`Collapsed comments in ${prettyMilliseconds(performance.now() - startTime)}.`);
989
1069
  process.exitCode = getExitCode(results);
990
- }).command("check [files..] [options]", "Check if MDAT placeholder comments are up to date. Exits with code 1 if any files are out of sync.", (yargs) => yargs.positional(...filesPositional).option(configOption).option(formatOption), async ({ config, files, format }) => {
1070
+ }).command("strip [files..] [options]", "Strip MDAT comments while preserving expanded content. If no files are provided, the closest readme.md is stripped.", (yargs) => yargs.positional(...filesPositional).option(outputOption).option(nameOption).option(printOption).option(formatOption), async ({ files, format, name, output, print }) => {
1071
+ logConflicts({
1072
+ name,
1073
+ output,
1074
+ print
1075
+ });
1076
+ const results = await strip(files, name, output, void 0, { format });
1077
+ for (const file of results) if (print) process.stdout.write(file.toString());
1078
+ else await write(file);
1079
+ reporterMdat(results);
1080
+ log.debug(`Stripped comments in ${prettyMilliseconds(performance.now() - startTime)}.`);
1081
+ process.exitCode = getExitCode(results);
1082
+ }).command("check [files..] [options]", "Check if MDAT placeholder comments are up to date. Exits with code 1 if any files have stale or unexpanded content.", (yargs) => yargs.positional(...filesPositional).option(configOption).option(formatOption), async ({ config, files, format }) => {
991
1083
  const results = await check(files, collectConfig(config), { format });
992
1084
  let allInSync = true;
993
1085
  for (const { inSync, result } of results) {
994
1086
  const filePath = result.path || "unknown";
995
- if (inSync) log.info(`${picocolors.green("in sync")}: ${filePath}`);
1087
+ if (inSync) log.debug(`${picocolors.green("Up to date")}: ${filePath}`);
996
1088
  else {
997
- log.info(`${picocolors.red("out of sync")}: ${filePath}`);
1089
+ log.warn(`${picocolors.red("Stale content")}: ${filePath}`);
998
1090
  allInSync = false;
999
1091
  }
1000
1092
  }
@@ -1,7 +1,7 @@
1
1
  import { Rule, Rule as Rule$1 } from "remark-mdat";
2
+ import { ILogBasic, ILogLayer } from "lognow";
2
3
  import * as _$metascope from "metascope";
3
4
  import { MetadataContext } from "metascope";
4
- import { ILogBasic, ILogLayer } from "lognow";
5
5
  import { VFile } from "vfile";
6
6
 
7
7
  //#region src/lib/config.d.ts
@@ -67,10 +67,11 @@ declare function createReadme(options?: Partial<MdatReadmeCreateOptions>): Promi
67
67
  //#endregion
68
68
  //#region src/lib/api.d.ts
69
69
  /**
70
- * Expand MDAT comments in one or more Markdown files.
71
- * If no files are provided, auto-finds the closest readme.md.
72
- * Writing is the responsibility of the caller (e.g. via `await write(result)`)
73
- * @returns an array of VFiles
70
+ * Expand MDAT comments in one or more Markdown files. If no files are provided,
71
+ * auto-finds the closest readme.md. Writing is the responsibility of the caller
72
+ * (e.g. via `await write(result)`)
73
+ *
74
+ * @returns An array of VFiles
74
75
  */
75
76
  declare function expand(files?: string | string[], name?: string, output?: string, config?: ConfigToLoad, options?: {
76
77
  format?: boolean;
@@ -82,10 +83,11 @@ declare function expandString(markdown: string, config?: ConfigToLoad, options?:
82
83
  format?: boolean;
83
84
  }): Promise<VFile>;
84
85
  /**
85
- * Collapse MDAT comments in one or more Markdown files.
86
- * If no files are provided, auto-finds the closest readme.md.
87
- * Writing is the responsibility of the caller (e.g. via `await write(result)`)
88
- * @returns an array of VFiles
86
+ * Collapse MDAT comments in one or more Markdown files. If no files are
87
+ * provided, auto-finds the closest readme.md. Writing is the responsibility of
88
+ * the caller (e.g. via `await write(result)`)
89
+ *
90
+ * @returns An array of VFiles
89
91
  */
90
92
  declare function collapse(files?: string | string[], name?: string, output?: string, config?: ConfigToLoad, options?: {
91
93
  format?: boolean;
@@ -97,9 +99,27 @@ declare function collapseString(markdown: string, config?: ConfigToLoad, options
97
99
  format?: boolean;
98
100
  }): Promise<VFile>;
99
101
  /**
100
- * Dry-run expand and compare with file on disk.
101
- * If no files are provided, auto-finds the closest readme.md.
102
- * @returns per-file sync status and expanded VFiles
102
+ * Strips MDAT comments in one or more Markdown files without touching other
103
+ * content. Does _not_ automatically expand Mdat content before stripping the
104
+ * tags. If no files are provided, auto-finds the closest readme.md. Writing is
105
+ * the responsibility of the caller (e.g. via `await write(result)`)
106
+ *
107
+ * @returns An array of VFiles
108
+ */
109
+ declare function strip(files?: string | string[], name?: string, output?: string, config?: ConfigToLoad, options?: {
110
+ format?: boolean;
111
+ }): Promise<VFile[]>;
112
+ /**
113
+ * Strip MDAT comments from a Markdown string.
114
+ */
115
+ declare function stripString(markdown: string, config?: ConfigToLoad, options?: {
116
+ format?: boolean;
117
+ }): Promise<VFile>;
118
+ /**
119
+ * Dry-run expand and compare with file on disk. If no files are provided,
120
+ * auto-finds the closest readme.md.
121
+ *
122
+ * @returns Per-file sync status and expanded VFiles
103
123
  */
104
124
  declare function check(files?: string | string[], config?: ConfigToLoad, options?: {
105
125
  format?: boolean;
@@ -111,8 +131,8 @@ declare function check(files?: string | string[], config?: ConfigToLoad, options
111
131
  //#region src/lib/context.d.ts
112
132
  /**
113
133
  * Get a bunch of platform-agnostic local metadata via metascope, exposed
114
- * primarily for plugin developers.
115
- * Result is memoized the result.
134
+ * primarily for plugin developers. Result is memoized the result.
135
+ *
116
136
  * @throws {Error} If no package.json is found
117
137
  */
118
138
  declare function getContextMetadata(): Promise<MetadataContext>;
@@ -126,11 +146,13 @@ declare const readmeMetadataTemplate: _$metascope.Template<{
126
146
  licenseFilePath: string | undefined;
127
147
  name: string | undefined;
128
148
  projectDirectory: string | undefined;
129
- repositoryOwner: string | undefined;
149
+ repositoryUrl: string | undefined;
130
150
  }>;
131
151
  type ReadmeMetadata = ReturnType<typeof readmeMetadataTemplate>;
132
152
  /**
133
153
  * Nice data for readme rules
154
+ *
155
+ * @public
134
156
  */
135
157
  declare function getReadmeMetadata(): Promise<{
136
158
  author: string | undefined;
@@ -142,8 +164,13 @@ declare function getReadmeMetadata(): Promise<{
142
164
  licenseFilePath: string | undefined;
143
165
  name: string | undefined;
144
166
  projectDirectory: string | undefined;
145
- repositoryOwner: string | undefined;
167
+ repositoryUrl: string | undefined;
146
168
  }>;
169
+ /**
170
+ * Reset all cached metadata. Call between tests or when the underlying project
171
+ * files may have changed on disk.
172
+ */
173
+ declare function resetMetadataCaches(): void;
147
174
  //#endregion
148
175
  //#region src/lib/log.d.ts
149
176
  /**
@@ -153,4 +180,4 @@ declare function getReadmeMetadata(): Promise<{
153
180
  */
154
181
  declare function setLogger(logger?: ILogBasic | ILogLayer): void;
155
182
  //#endregion
156
- export { type Config, type ReadmeMetadata, type Rule, check, collapse, collapseString, createReadme as create, createReadmeInteractive as createInteractive, defineConfig, expand, expandString, getContextMetadata, getReadmeMetadata, loadConfig, mergeConfig, setLogger };
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 };