mdat 2.3.6 → 3.0.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,9 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import { createLogger, getChildLogger, injectionHelper } from "lognow";
2
3
  import picocolors from "picocolors";
3
4
  import prettyMilliseconds from "pretty-ms";
4
5
  import { getMdatReports, getSoleRule, mdatCollapse, mdatDiff, mdatExpand, mdatSplit, mdatStrip, reporterMdat, rulesSchema, setLogger } from "remark-mdat";
5
- import { createLogger, getChildLogger, injectionHelper } from "lognow";
6
- import { defineTemplate, getMetadata, helpers, setLogger as setLogger$1, templates } from "metascope";
7
6
  import { read, write } from "to-vfile";
8
7
  import yargs from "yargs";
9
8
  import { hideBin } from "yargs/helpers";
@@ -15,6 +14,7 @@ import fs from "node:fs/promises";
15
14
  import path from "node:path";
16
15
  import plur from "plur";
17
16
  import { deepmerge } from "deepmerge-ts";
17
+ import { defineTemplate, getMetadata, helpers, setLogger as setLogger$1, templates } from "metascope";
18
18
  import { z } from "zod";
19
19
  import { globby } from "globby";
20
20
  import { isFile, isFileSync } from "path-type";
@@ -29,7 +29,20 @@ import { confirm, group, intro, note, outro, select } from "@clack/prompts";
29
29
 
30
30
  //#region package.json
31
31
  var name = "mdat";
32
- var version = "2.3.6";
32
+ var version = "3.0.0";
33
+
34
+ //#endregion
35
+ //#region src/lib/deep-merge-defined.ts
36
+ function stripUndefinedDeep(object) {
37
+ if (Array.isArray(object)) return object.map((v) => v !== null && typeof v === "object" ? stripUndefinedDeep(v) : v).filter((v) => v !== void 0);
38
+ return Object.entries(object).map(([k, v]) => [k, v !== null && typeof v === "object" ? stripUndefinedDeep(v) : v]).reduce((acc, [k, v]) => v === void 0 ? acc : {
39
+ ...acc,
40
+ [k]: v
41
+ }, {});
42
+ }
43
+ function deepMergeDefined(...objects) {
44
+ return deepmerge(...objects.map((v, i) => i === 0 ? v : stripUndefinedDeep(v)));
45
+ }
33
46
 
34
47
  //#endregion
35
48
  //#region src/lib/log.ts
@@ -55,19 +68,6 @@ function setLogger$2(logger) {
55
68
  setLogger$1(getChildLogger(log, "metascope"));
56
69
  }
57
70
 
58
- //#endregion
59
- //#region src/lib/deep-merge-defined.ts
60
- function stripUndefinedDeep(object) {
61
- if (Array.isArray(object)) return object.map((v) => v && typeof v === "object" ? stripUndefinedDeep(v) : v).filter((v) => v !== void 0);
62
- return Object.entries(object).map(([k, v]) => [k, v && typeof v === "object" ? stripUndefinedDeep(v) : v]).reduce((acc, [k, v]) => v === void 0 ? acc : {
63
- ...acc,
64
- [k]: v
65
- }, {});
66
- }
67
- function deepMergeDefined(...objects) {
68
- return deepmerge(...objects.map((v, i) => i === 0 ? v : stripUndefinedDeep(v)));
69
- }
70
-
71
71
  //#endregion
72
72
  //#region src/lib/mdat-json-loader.ts
73
73
  /**
@@ -82,7 +82,7 @@ function mdatJsonLoader(filePath, content) {
82
82
  }
83
83
  function flattenJson(jsonObject, parentKey = "", result = {}) {
84
84
  for (const [key, value] of Object.entries(jsonObject)) {
85
- const fullPath = parentKey ? `${parentKey}.${key}` : key;
85
+ const fullPath = parentKey === "" ? key : `${parentKey}.${key}`;
86
86
  if (typeof value === "object" && value !== null && !Array.isArray(value)) flattenJson(value, fullPath, result);
87
87
  else if (value === null) result[fullPath] = "null";
88
88
  else result[fullPath] = value.toString();
@@ -137,16 +137,16 @@ async function getContextMetadata() {
137
137
  });
138
138
  return metascopeMetadata;
139
139
  }
140
- const GIT_PREFIX_REGEX = /^git\+/;
141
- const GIT_SUFFIX_REGEX = /\.git$/;
142
- const TRAILING_SLASH_REGEX = /\/$/;
140
+ const GIT_PREFIX_REGEX = /^git\+/v;
141
+ const GIT_SUFFIX_REGEX = /\.git$/v;
142
+ const TRAILING_SLASH_REGEX = /\/$/v;
143
143
  const readmeMetadataTemplate = defineTemplate((context) => {
144
144
  const { githubActions, licenseFile, metascope, nodePackageJson } = context;
145
145
  const codemeta = templates.codemetaJson(context, {});
146
146
  const nodePackage = helpers.firstOf(nodePackageJson)?.data;
147
147
  const licenseFileData = helpers.firstOf(licenseFile);
148
148
  const ciActionFilePath = helpers.ensureArray(githubActions).find((entry) => entry.data.name.toLowerCase() === "ci")?.source;
149
- const repositoryUrl = codemeta.codeRepository?.replace(GIT_PREFIX_REGEX, "").replace(GIT_SUFFIX_REGEX, "").replace(TRAILING_SLASH_REGEX, "");
149
+ const repoUrl = codemeta.codeRepository?.replace(GIT_PREFIX_REGEX, "").replace(GIT_SUFFIX_REGEX, "").replace(TRAILING_SLASH_REGEX, "");
150
150
  const bin = (() => {
151
151
  if (nodePackage === void 0) return;
152
152
  const binField = nodePackage.bin;
@@ -179,7 +179,7 @@ const readmeMetadataTemplate = defineTemplate((context) => {
179
179
  author: helpers.firstOf(helpers.mixedStringsToArray(helpers.toBasicNames(codemeta.author))),
180
180
  authorUrl: firstAuthor?.url,
181
181
  bin,
182
- ciActionFileName: ciActionFilePath ? path.basename(ciActionFilePath) : void 0,
182
+ ciActionFileName: ciActionFilePath === void 0 ? void 0 : path.basename(ciActionFilePath),
183
183
  description: codemeta.description,
184
184
  engines,
185
185
  isPublicNpmPackage: nodePackage?.private !== true && (nodePackage?.name.startsWith("@") && nodePackage.publishConfig?.access === "public" || true),
@@ -191,7 +191,7 @@ const readmeMetadataTemplate = defineTemplate((context) => {
191
191
  operatingSystem: codemeta.operatingSystem,
192
192
  peerDependencies,
193
193
  projectDirectory: metascope?.data.options.path === void 0 ? void 0 : `file://${metascope.data.options.path}`,
194
- repositoryUrl,
194
+ repositoryUrl: repoUrl,
195
195
  runtimePlatform: codemeta.runtimePlatform,
196
196
  usesPnpm: helpers.usesPnpm(nodePackageJson)
197
197
  };
@@ -204,7 +204,8 @@ let readmeMetadata;
204
204
  */
205
205
  async function getReadmeMetadata() {
206
206
  if (readmeMetadata !== void 0) return readmeMetadata;
207
- readmeMetadata = readmeMetadataTemplate(await getContextMetadata(), {});
207
+ const contextMetadata = await getContextMetadata();
208
+ readmeMetadata = readmeMetadataTemplate(contextMetadata, {});
208
209
  return readmeMetadata;
209
210
  }
210
211
 
@@ -222,11 +223,11 @@ var badges_default = { badges: { async content(options) {
222
223
  const { ciActionFileName, license, licenseUrl, name, repositoryUrl } = metadata;
223
224
  const badges = [];
224
225
  if (validOptions?.npm === void 0) {
225
- if (metadata.isPublicNpmPackage) badges.push(`[![NPM Package ${name}](https://img.shields.io/npm/v/${name}.svg)](https://npmjs.com/package/${name})`);
226
- } 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})`);
226
+ if (metadata.isPublicNpmPackage) badges.push(`[![NPM Package ${name}](https://img.shields.io/npm/v/${name}.svg)](https://www.npmjs.com/package/${name})`);
227
+ } else for (const packageName of validOptions.npm) badges.push(`[![NPM Package ${packageName}](https://img.shields.io/npm/v/${packageName}.svg)](https://www.npmjs.com/package/${packageName})`);
227
228
  if (license !== void 0 && licenseUrl !== void 0) badges.push(`[![License: ${license}](https://img.shields.io/badge/License-${license.replaceAll("-", "--")}-yellow.svg)](${licenseUrl})`);
228
229
  if (ciActionFileName !== void 0 && repositoryUrl !== void 0) badges.push(`[![CI](${repositoryUrl}/actions/workflows/${ciActionFileName}/badge.svg)](${repositoryUrl}/actions/workflows/${ciActionFileName})`);
229
- if (validOptions?.custom !== void 0) for (const [name, { image, link }] of Object.entries(validOptions.custom)) badges.push(`[![${name}](${image})](${link})`);
230
+ if (validOptions?.custom !== void 0) for (const [badgeName, { image, link }] of Object.entries(validOptions.custom)) badges.push(`[![${badgeName}](${image})](${link})`);
230
231
  return badges.join("\n");
231
232
  } } };
232
233
 
@@ -239,7 +240,7 @@ var banner_default = { banner: { async content(options) {
239
240
  }).optional().parse(options);
240
241
  const src = validOptions?.src ?? await getBannerSrc();
241
242
  if (src === void 0) throw new Error("Banner image not found at any typical location, consider adding something at ./assets/banner.webp");
242
- else if (!isUrl(src) && !await isFile(src)) throw new Error(`Banner image not found at "${src}"`);
243
+ if (!isUrl(src) && !await isFile(src)) throw new Error(`Banner image not found at "${src}"`);
243
244
  let alt = validOptions?.alt;
244
245
  if (alt === void 0) {
245
246
  const { name: packageName } = await getReadmeMetadata();
@@ -251,7 +252,7 @@ var banner_default = { banner: { async content(options) {
251
252
  async function getBannerSrc() {
252
253
  const { projectDirectory } = await getReadmeMetadata();
253
254
  if (projectDirectory === void 0) throw new Error("No project directory found");
254
- const paths = await globby([
255
+ const [firstPath] = await globby([
255
256
  ".",
256
257
  "assets",
257
258
  "media",
@@ -287,20 +288,19 @@ async function getBannerSrc() {
287
288
  ]
288
289
  }
289
290
  });
290
- if (paths.length > 0) return path.relative(process.cwd(), paths[0]);
291
+ return firstPath === void 0 ? void 0 : path.relative(process.cwd(), firstPath);
291
292
  }
292
293
  function isUrl(text, lenient = true) {
293
294
  if (typeof text !== "string") throw new TypeError("Expected a string");
294
295
  text = text.trim();
295
296
  if (text.includes(" ")) return false;
296
297
  if (URL.canParse(text)) return true;
297
- if (lenient) return isUrl(`https://${text}`, false);
298
- return false;
298
+ return lenient && URL.canParse(`https://${text}`);
299
299
  }
300
300
 
301
301
  //#endregion
302
302
  //#region src/lib/readme/rules/code.ts
303
- const LEADING_DOT_REGEX = /^\./;
303
+ const LEADING_DOT_REGEX = /^\./v;
304
304
  var code_default = { code: { async content(options) {
305
305
  const validOptions = z.object({
306
306
  file: z.string(),
@@ -379,7 +379,7 @@ var dependencies_default = { dependencies: { async content() {
379
379
  if (engines?.[platformKey] !== void 0) continue;
380
380
  const info = PLATFORM_INFO[platformKey.toLowerCase()];
381
381
  const display = info ? `[${info.display}](${info.url})` : platformKey;
382
- platformItems.push(version ? `- ${display} ${version}` : `- ${display}`);
382
+ platformItems.push(version === void 0 || version === "" ? `- ${display}` : `- ${display} ${version}`);
383
383
  }
384
384
  if (operatingSystem !== void 0 && operatingSystem.length > 0) platformItems.push(`- Supported platforms: ${operatingSystem.join(", ")}`);
385
385
  const peerItems = [];
@@ -446,7 +446,7 @@ var title_default = { title: {
446
446
  },
447
447
  order: 2
448
448
  } };
449
- const SPLIT_FOR_TITLE_CASE_REGEX = /[ _-]+/;
449
+ const SPLIT_FOR_TITLE_CASE_REGEX = /[ _\-]+/v;
450
450
  function makeTitleCase(text) {
451
451
  return text.split(SPLIT_FOR_TITLE_CASE_REGEX).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
452
452
  }
@@ -520,7 +520,7 @@ async function createSizeReport(filePath) {
520
520
  original: createSizeInfo(originalSize, originalSize)
521
521
  };
522
522
  } catch (error) {
523
- throw new Error(`Failed to analyze file: ${error instanceof Error ? error.message : "Unknown error"}`);
523
+ throw new Error(`Failed to analyze file: ${error instanceof Error ? error.message : "Unknown error"}`, { cause: error });
524
524
  }
525
525
  }
526
526
 
@@ -618,8 +618,8 @@ var table_of_contents_default = { "table-of-contents": {
618
618
  ]).optional() }).optional().parse(options)?.depth ?? 3,
619
619
  tight: true
620
620
  });
621
- const heading = `## Table of contents`;
622
621
  if (result.map === void 0) throw new Error("Could not generate table of contents");
622
+ const heading = `## Table of contents`;
623
623
  const rootWrapper = {
624
624
  children: result.map.children,
625
625
  type: "root"
@@ -680,13 +680,13 @@ function getAdditionalConfigExplorer() {
680
680
  async function loadConfig(options) {
681
681
  const { additionalConfig, defaults = rules_default, searchFrom } = options ?? {};
682
682
  let finalConfig = { mdat: `Powered by the Markdown Autophagic Template system: [mdat](https://github.com/kitschpatrol/mdat).` };
683
- if (defaults) finalConfig = deepMergeDefined(finalConfig, defaults);
683
+ finalConfig = deepMergeDefined(finalConfig, defaults);
684
684
  const results = await getConfigExplorer().search(searchFrom);
685
685
  if (results) {
686
686
  const { config, filepath } = results;
687
687
  let possibleRules = config;
688
688
  log.debug(`Using config from "${filepath}"`);
689
- if (filepath.endsWith("package.json") && typeof config === "string") {
689
+ if (typeof config === "string" && filepath.endsWith("package.json")) {
690
690
  log.debug(`Detected shared config string: "${config}"`);
691
691
  const { default: sharedConfig } = await import(config);
692
692
  possibleRules = sharedConfig;
@@ -699,14 +699,14 @@ async function loadConfig(options) {
699
699
  for (const configOrPath of additionalConfigArray) {
700
700
  let loaded;
701
701
  if (typeof configOrPath === "string") {
702
- let results;
703
- if (path.basename(configOrPath).endsWith("package.json")) results = {
702
+ let loadResults;
703
+ if (path.basename(configOrPath).endsWith("package.json")) loadResults = {
704
704
  config: mdatJsonLoader(configOrPath, await fs.readFile(configOrPath, "utf8")),
705
705
  filepath: configOrPath
706
706
  };
707
- else results = await getAdditionalConfigExplorer().load(configOrPath);
708
- if (results === null || results === void 0) continue;
709
- const { config: loadedConfig, filepath } = results;
707
+ else loadResults = await getAdditionalConfigExplorer().load(configOrPath);
708
+ if (loadResults === null || loadResults === void 0) continue;
709
+ const { config: loadedConfig, filepath } = loadResults;
710
710
  log.debug(`Loaded additional config from "${filepath}"`);
711
711
  loaded = loadedConfig;
712
712
  } else loaded = configOrPath;
@@ -740,7 +740,7 @@ async function formatWithPrettier(content, filePath) {
740
740
  } catch {
741
741
  throw new Error("The --format flag requires `prettier` to be installed. Run: pnpm add -D prettier");
742
742
  }
743
- const configKey = filePath ? path.dirname(filePath) : process.cwd();
743
+ const configKey = filePath === void 0 || filePath === "" ? process.cwd() : path.dirname(filePath);
744
744
  let config = configCache.get(configKey);
745
745
  if (config === void 0 && !configCache.has(configKey)) {
746
746
  config = await cachedPrettier.resolveConfig(filePath ?? process.cwd());
@@ -763,22 +763,22 @@ function zeroPad(n, nMax) {
763
763
  async function getInputOutputPaths(inputs, output, name, extension) {
764
764
  const paths = [];
765
765
  for (const [index, file] of inputs.entries()) {
766
- const inputOutputPath = await getInputOutputPath(file, output, name, extension, name && inputs.length > 1 ? `-${zeroPad(index + 1, inputs.length)}` : "");
766
+ const inputOutputPath = await getInputOutputPath(file, output, name, extension, name !== void 0 && name !== "" && inputs.length > 1 ? `-${zeroPad(index + 1, inputs.length)}` : "");
767
767
  paths.push(inputOutputPath);
768
768
  }
769
769
  return paths;
770
770
  }
771
771
  async function getInputOutputPath(input, output, name, extension, nameSuffix = "") {
772
772
  const resolvedInput = expandPath(input);
773
- const resolvedOutput = output ? expandPath(output) : void 0;
774
773
  if (!isFileSync(resolvedInput)) throw new Error(`Input file not found: "${resolvedInput}"`);
775
- if (resolvedOutput) {
774
+ const resolvedOutput = output === void 0 || output === "" ? void 0 : expandPath(output);
775
+ if (resolvedOutput !== void 0) {
776
776
  if (isFileSync(resolvedOutput)) throw new Error(`Output path must be a directory, received a file path: "${resolvedOutput}"`);
777
777
  await fs.mkdir(resolvedOutput, { recursive: true });
778
778
  }
779
779
  return {
780
780
  input: resolvedInput,
781
- name: `${name ? path.basename(name, path.extname(name)) : path.basename(resolvedInput, path.extname(resolvedInput))}${nameSuffix}${extension === void 0 ? name && path.extname(name) !== "" ? path.extname(name) : path.extname(input) : `.${extension}`}`,
781
+ name: `${name === void 0 || name === "" ? path.basename(resolvedInput, path.extname(resolvedInput)) : path.basename(name, path.extname(name))}${nameSuffix}${extension === void 0 ? name !== void 0 && path.extname(name) !== "" ? path.extname(name) : path.extname(input) : `.${extension}`}`,
782
782
  output: resolvedOutput ?? path.dirname(resolvedInput)
783
783
  };
784
784
  }
@@ -789,7 +789,7 @@ function ensureArray(value) {
789
789
  if (value === void 0 || value === null) return [];
790
790
  return Array.isArray(value) ? value : [value];
791
791
  }
792
- const README_SEARCH_REGEX = /^readme(?:\.\w+)?$/i;
792
+ const README_SEARCH_REGEX = /^readme(?:\.\w+)?$/iv;
793
793
  /**
794
794
  * Finds a readme file in the current working directory (case-insensitive).
795
795
  */
@@ -866,14 +866,14 @@ function stripLintPlugins(config) {
866
866
  //#endregion
867
867
  //#region src/lib/processors.ts
868
868
  async function processFiles(files, loader, processorGetter, name, output, config) {
869
- const [resolvedConfig, localRemarkConfiguration] = await Promise.all([loader({ additionalConfig: config }), loadAmbientRemarkConfig()]);
869
+ const [resolvedConfig, localRemarkConfig] = await Promise.all([loader({ additionalConfig: config }), loadAmbientRemarkConfig()]);
870
870
  const inputOutputPaths = await getInputOutputPaths(ensureArray(files), output, name, "md");
871
- const resolvedProcessor = processorGetter(resolvedConfig, localRemarkConfiguration);
872
- return await Promise.all(inputOutputPaths.map(async ({ input, name, output }) => {
871
+ const resolvedProcessor = processorGetter(resolvedConfig, localRemarkConfig);
872
+ return await Promise.all(inputOutputPaths.map(async ({ input, name: outputName, output: outputDirectory }) => {
873
873
  const inputFile = await read(input);
874
874
  const result = await resolvedProcessor.process(inputFile);
875
- result.dirname = output;
876
- result.basename = name;
875
+ result.dirname = outputDirectory;
876
+ result.basename = outputName;
877
877
  return result;
878
878
  }));
879
879
  }
@@ -970,13 +970,15 @@ async function createReadmeInteractive() {
970
970
  const readmePath = await findReadme();
971
971
  intro(`Running ${picocolors.bold("mdat create")} interactively`);
972
972
  const newReadmePath = await createReadme(await group({
973
- overwrite: async () => readmePath === void 0 ? true : await confirm({
974
- message: `Found an existing readme at "${picocolors.blue(readmePath)}". Do you want to overwrite it?`,
975
- active: "Overwrite",
976
- inactive: "Exit"
977
- }) ? true : (() => {
978
- throw new Error("`mdat create` was cancelled to avoid an overwrite - no changes were made");
979
- })(),
973
+ async overwrite() {
974
+ if (readmePath === void 0) return true;
975
+ if (await confirm({
976
+ message: `Found an existing readme at "${picocolors.blue(readmePath)}". Do you want to overwrite it?`,
977
+ active: "Overwrite",
978
+ inactive: "Exit"
979
+ }) === false) throw new Error("`mdat create` was cancelled to avoid an overwrite - no changes were made");
980
+ return true;
981
+ },
980
982
  template: async () => select({
981
983
  message: "Which template would you like to use?",
982
984
  options: getTemplateOptions()
@@ -1018,17 +1020,24 @@ async function createReadme(options) {
1018
1020
  const templateString = getTemplateForConfig(resolvedOptions.template, resolvedOptions.compound);
1019
1021
  const readmePath = path.join(resolvedOptions.output, "readme.md");
1020
1022
  if (!resolvedOptions.overwrite) {
1021
- if (await fs.access(readmePath).then(() => true, () => false)) throw new Error(`Readme already exists at "${readmePath}". Use the overwrite option to replace it.`);
1023
+ let exists = true;
1024
+ try {
1025
+ await fs.access(readmePath);
1026
+ } catch {
1027
+ exists = false;
1028
+ }
1029
+ if (exists) throw new Error(`Readme already exists at "${readmePath}". Use the overwrite option to replace it.`);
1022
1030
  }
1023
1031
  await fs.writeFile(readmePath, templateString, "utf8");
1024
1032
  if (resolvedOptions.expand) {
1025
1033
  const [result] = await expand(readmePath);
1034
+ if (result === void 0) throw new Error(`Failed to expand readme at "${readmePath}"`);
1026
1035
  await write(result);
1027
1036
  }
1028
1037
  return readmePath;
1029
1038
  }
1030
1039
  function getTemplateForConfig(templateKey, compound) {
1031
- if (!(templateKey in templates_default)) throw new Error(`Unknown template "${templateKey}". Available templates: ${Object.keys(templates_default).join(", ")}`);
1040
+ if (!Object.hasOwn(templates_default, templateKey)) throw new Error(`Unknown template "${templateKey}". Available templates: ${Object.keys(templates_default).join(", ")}`);
1032
1041
  return templates_default[templateKey].content[compound ? "compound" : "explicit"];
1033
1042
  }
1034
1043
  function getTemplateOptions() {
@@ -1093,7 +1102,11 @@ async function check(files, config, options) {
1093
1102
  const resolvedFiles = Array.isArray(files) ? files : [files];
1094
1103
  const [originals, results] = await Promise.all([Promise.all(resolvedFiles.map(async (f) => read(f))), processFiles(files, loadConfig, getExpandProcessor, void 0, void 0, config)]);
1095
1104
  if (options?.format) await formatResults(results);
1096
- return results.map((result, i) => compareWithDiff(originals[i], result, options));
1105
+ return results.map((result, i) => {
1106
+ const original = originals[i];
1107
+ if (original === void 0) throw new Error(`Missing original file for comparison at index ${i}`);
1108
+ return compareWithDiff(original, result, options);
1109
+ });
1097
1110
  }
1098
1111
  function compareWithDiff(original, result, options) {
1099
1112
  const inSync = original.toString().replaceAll("\r\n", "\n") === result.toString();
@@ -1115,7 +1128,7 @@ function compareWithDiff(original, result, options) {
1115
1128
  };
1116
1129
  }
1117
1130
  async function formatResults(results) {
1118
- for (const file of results) file.value = await formatWithPrettier(file.toString(), file.path || void 0);
1131
+ for (const file of results) file.value = await formatWithPrettier(file.toString(), file.path === "" ? void 0 : file.path);
1119
1132
  }
1120
1133
 
1121
1134
  //#endregion
@@ -1195,55 +1208,55 @@ const yargsInstance = yargs(hideBin(process.argv));
1195
1208
  try {
1196
1209
  await yargsInstance.scriptName("mdat").usage("$0 [command] [files..] [options]", "Work with MDAT placeholder comments in Markdown files.").option(verboseOption).middleware((argv) => {
1197
1210
  setLogger$2(createLogger({
1198
- name,
1199
- verbose: argv.verbose ?? false,
1200
1211
  logToConsole: {
1201
1212
  showLevel: false,
1202
1213
  showName: false,
1203
1214
  showTime: false
1204
- }
1215
+ },
1216
+ name,
1217
+ verbose: argv.verbose ?? false
1205
1218
  }));
1206
- }).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 }) => {
1219
+ }).command(["$0 [files..] [options]", "expand [files..] [options]"], "Expand MDAT placeholder comments. If no files are provided, the closest readme.md is expanded.", (commandYargs) => commandYargs.positional(...filesPositional).option(configOption).option(outputOption).option(nameOption).option(printOption).option(formatOption), async ({ config, files, format, name: fileName, output, print }) => {
1207
1220
  logConflicts({
1208
- name,
1221
+ name: fileName,
1209
1222
  output,
1210
1223
  print
1211
1224
  });
1212
- const results = await expand(files, name, output, collectConfig(config), { format });
1225
+ const results = await expand(files, fileName, output, collectConfig(config), { format });
1213
1226
  for (const file of results) if (print) process.stdout.write(file.toString());
1214
1227
  else await write(file);
1215
1228
  reporterMdat(results);
1216
1229
  log.debug(`Expanded comments in ${prettyMilliseconds(performance.now() - startTime)}.`);
1217
1230
  process.exitCode = getExitCode(results);
1218
- }).command("collapse [files..] [options]", "Collapse MDAT placeholder comments. If no files are provided, the closest readme.md is collapsed.", (yargs) => yargs.positional(...filesPositional).option(outputOption).option(nameOption).option(printOption).option(formatOption), async ({ files, format, name, output, print }) => {
1231
+ }).command("collapse [files..] [options]", "Collapse MDAT placeholder comments. If no files are provided, the closest readme.md is collapsed.", (commandYargs) => commandYargs.positional(...filesPositional).option(outputOption).option(nameOption).option(printOption).option(formatOption), async ({ files, format, name: fileName, output, print }) => {
1219
1232
  logConflicts({
1220
- name,
1233
+ name: fileName,
1221
1234
  output,
1222
1235
  print
1223
1236
  });
1224
- const results = await collapse(files, name, output, void 0, { format });
1237
+ const results = await collapse(files, fileName, output, void 0, { format });
1225
1238
  for (const file of results) if (print) process.stdout.write(file.toString());
1226
1239
  else await write(file);
1227
1240
  reporterMdat(results);
1228
1241
  log.debug(`Collapsed comments in ${prettyMilliseconds(performance.now() - startTime)}.`);
1229
1242
  process.exitCode = getExitCode(results);
1230
- }).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 }) => {
1243
+ }).command("strip [files..] [options]", "Strip MDAT comments while preserving expanded content. If no files are provided, the closest readme.md is stripped.", (commandYargs) => commandYargs.positional(...filesPositional).option(outputOption).option(nameOption).option(printOption).option(formatOption), async ({ files, format, name: fileName, output, print }) => {
1231
1244
  logConflicts({
1232
- name,
1245
+ name: fileName,
1233
1246
  output,
1234
1247
  print
1235
1248
  });
1236
- const results = await strip(files, name, output, void 0, { format });
1249
+ const results = await strip(files, fileName, output, void 0, { format });
1237
1250
  for (const file of results) if (print) process.stdout.write(file.toString());
1238
1251
  else await write(file);
1239
1252
  reporterMdat(results);
1240
1253
  log.debug(`Stripped comments in ${prettyMilliseconds(performance.now() - startTime)}.`);
1241
1254
  process.exitCode = getExitCode(results);
1242
- }).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 }) => {
1255
+ }).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.", (commandYargs) => commandYargs.positional(...filesPositional).option(configOption).option(formatOption), async ({ config, files, format }) => {
1243
1256
  const results = await check(files, collectConfig(config), { format });
1244
1257
  let allInSync = true;
1245
1258
  for (const { inSync, result } of results) {
1246
- const filePath = result.path || "unknown";
1259
+ const filePath = result.path === "" ? "unknown" : result.path;
1247
1260
  if (inSync) log.debug(`${picocolors.green("Up to date")}: ${filePath}`);
1248
1261
  else {
1249
1262
  log.warn(`${picocolors.red("Stale content")}: ${filePath}`);
@@ -1253,12 +1266,12 @@ try {
1253
1266
  }
1254
1267
  log.debug(`Checked in ${prettyMilliseconds(performance.now() - startTime)}.`);
1255
1268
  process.exitCode = allInSync ? 0 : 1;
1256
- }).command("create [options]", "Create a new Markdown file from a template.", (yargs) => yargs.option(interactiveOption).option(overwriteOption).option(outputOption).option(expandOption).option(templateOption).option(compoundOption), async ({ compound, expand, interactive, output, overwrite, template }) => {
1269
+ }).command("create [options]", "Create a new Markdown file from a template.", (commandYargs) => commandYargs.option(interactiveOption).option(overwriteOption).option(outputOption).option(expandOption).option(templateOption).option(compoundOption), async ({ compound, expand: expandTemplate, interactive, output, overwrite, template }) => {
1257
1270
  if (interactive) await createReadmeInteractive();
1258
1271
  else {
1259
1272
  const readmePath = await createReadme({
1260
1273
  compound,
1261
- expand,
1274
+ expand: expandTemplate,
1262
1275
  output,
1263
1276
  overwrite,
1264
1277
  template
@@ -1272,11 +1285,11 @@ try {
1272
1285
  process.exitCode = 1;
1273
1286
  }
1274
1287
  function logConflicts(args) {
1275
- if (args.print && args.output) log.warn(`Ignoring --output option because --print is set`);
1276
- if (args.print && args.name) log.warn(`Ignoring --name option because --print is set`);
1288
+ if (args.print && args.output !== void 0 && args.output !== "") log.warn(`Ignoring --output option because --print is set`);
1289
+ if (args.print && args.name !== void 0 && args.name !== "") log.warn(`Ignoring --name option because --print is set`);
1277
1290
  }
1278
1291
  function collectConfig(config) {
1279
- if (config === void 0) return void 0;
1292
+ if (config === void 0) return;
1280
1293
  return ensureArray(config);
1281
1294
  }
1282
1295
  function getExitCode(results) {
@@ -2,7 +2,6 @@ import { Rule, Rule as Rule$1 } from "remark-mdat";
2
2
  import { ILogBasic, ILogLayer } from "lognow";
3
3
  import { MetadataContext } from "metascope";
4
4
  import { VFile } from "vfile";
5
-
6
5
  //#region src/lib/config.d.ts
7
6
  /**
8
7
  * An mdat configuration is a record of expansion rules. Keys become comment
package/dist/lib/index.js CHANGED
@@ -24,8 +24,8 @@ import untildify from "untildify";
24
24
  import { confirm, group, intro, note, outro, select } from "@clack/prompts";
25
25
  //#region src/lib/deep-merge-defined.ts
26
26
  function stripUndefinedDeep(object) {
27
- if (Array.isArray(object)) return object.map((v) => v && typeof v === "object" ? stripUndefinedDeep(v) : v).filter((v) => v !== void 0);
28
- return Object.entries(object).map(([k, v]) => [k, v && typeof v === "object" ? stripUndefinedDeep(v) : v]).reduce((acc, [k, v]) => v === void 0 ? acc : {
27
+ if (Array.isArray(object)) return object.map((v) => v !== null && typeof v === "object" ? stripUndefinedDeep(v) : v).filter((v) => v !== void 0);
28
+ return Object.entries(object).map(([k, v]) => [k, v !== null && typeof v === "object" ? stripUndefinedDeep(v) : v]).reduce((acc, [k, v]) => v === void 0 ? acc : {
29
29
  ...acc,
30
30
  [k]: v
31
31
  }, {});
@@ -70,7 +70,7 @@ function mdatJsonLoader(filePath, content) {
70
70
  }
71
71
  function flattenJson(jsonObject, parentKey = "", result = {}) {
72
72
  for (const [key, value] of Object.entries(jsonObject)) {
73
- const fullPath = parentKey ? `${parentKey}.${key}` : key;
73
+ const fullPath = parentKey === "" ? key : `${parentKey}.${key}`;
74
74
  if (typeof value === "object" && value !== null && !Array.isArray(value)) flattenJson(value, fullPath, result);
75
75
  else if (value === null) result[fullPath] = "null";
76
76
  else result[fullPath] = value.toString();
@@ -124,9 +124,9 @@ async function getContextMetadata() {
124
124
  });
125
125
  return metascopeMetadata;
126
126
  }
127
- const GIT_PREFIX_REGEX = /^git\+/;
128
- const GIT_SUFFIX_REGEX = /\.git$/;
129
- const TRAILING_SLASH_REGEX = /\/$/;
127
+ const GIT_PREFIX_REGEX = /^git\+/v;
128
+ const GIT_SUFFIX_REGEX = /\.git$/v;
129
+ const TRAILING_SLASH_REGEX = /\/$/v;
130
130
  /**
131
131
  * Reset cached context metadata. Call between tests or when the underlying
132
132
  * project files may have changed on disk.
@@ -142,7 +142,7 @@ const readmeMetadataTemplate = defineTemplate((context) => {
142
142
  const nodePackage = helpers.firstOf(nodePackageJson)?.data;
143
143
  const licenseFileData = helpers.firstOf(licenseFile);
144
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, "");
145
+ const repoUrl = codemeta.codeRepository?.replace(GIT_PREFIX_REGEX, "").replace(GIT_SUFFIX_REGEX, "").replace(TRAILING_SLASH_REGEX, "");
146
146
  const bin = (() => {
147
147
  if (nodePackage === void 0) return;
148
148
  const binField = nodePackage.bin;
@@ -175,7 +175,7 @@ const readmeMetadataTemplate = defineTemplate((context) => {
175
175
  author: helpers.firstOf(helpers.mixedStringsToArray(helpers.toBasicNames(codemeta.author))),
176
176
  authorUrl: firstAuthor?.url,
177
177
  bin,
178
- ciActionFileName: ciActionFilePath ? path.basename(ciActionFilePath) : void 0,
178
+ ciActionFileName: ciActionFilePath === void 0 ? void 0 : path.basename(ciActionFilePath),
179
179
  description: codemeta.description,
180
180
  engines,
181
181
  isPublicNpmPackage: nodePackage?.private !== true && (nodePackage?.name.startsWith("@") && nodePackage.publishConfig?.access === "public" || true),
@@ -187,7 +187,7 @@ const readmeMetadataTemplate = defineTemplate((context) => {
187
187
  operatingSystem: codemeta.operatingSystem,
188
188
  peerDependencies,
189
189
  projectDirectory: metascope?.data.options.path === void 0 ? void 0 : `file://${metascope.data.options.path}`,
190
- repositoryUrl,
190
+ repositoryUrl: repoUrl,
191
191
  runtimePlatform: codemeta.runtimePlatform,
192
192
  usesPnpm: helpers.usesPnpm(nodePackageJson)
193
193
  };
@@ -200,7 +200,8 @@ let readmeMetadata;
200
200
  */
201
201
  async function getReadmeMetadata() {
202
202
  if (readmeMetadata !== void 0) return readmeMetadata;
203
- readmeMetadata = readmeMetadataTemplate(await getContextMetadata(), {});
203
+ const contextMetadata = await getContextMetadata();
204
+ readmeMetadata = readmeMetadataTemplate(contextMetadata, {});
204
205
  return readmeMetadata;
205
206
  }
206
207
  /**
@@ -234,11 +235,11 @@ var badges_default = { badges: { async content(options) {
234
235
  const { ciActionFileName, license, licenseUrl, name, repositoryUrl } = metadata;
235
236
  const badges = [];
236
237
  if (validOptions?.npm === void 0) {
237
- if (metadata.isPublicNpmPackage) badges.push(`[![NPM Package ${name}](https://img.shields.io/npm/v/${name}.svg)](https://npmjs.com/package/${name})`);
238
- } 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})`);
238
+ if (metadata.isPublicNpmPackage) badges.push(`[![NPM Package ${name}](https://img.shields.io/npm/v/${name}.svg)](https://www.npmjs.com/package/${name})`);
239
+ } else for (const packageName of validOptions.npm) badges.push(`[![NPM Package ${packageName}](https://img.shields.io/npm/v/${packageName}.svg)](https://www.npmjs.com/package/${packageName})`);
239
240
  if (license !== void 0 && licenseUrl !== void 0) badges.push(`[![License: ${license}](https://img.shields.io/badge/License-${license.replaceAll("-", "--")}-yellow.svg)](${licenseUrl})`);
240
241
  if (ciActionFileName !== void 0 && repositoryUrl !== void 0) badges.push(`[![CI](${repositoryUrl}/actions/workflows/${ciActionFileName}/badge.svg)](${repositoryUrl}/actions/workflows/${ciActionFileName})`);
241
- if (validOptions?.custom !== void 0) for (const [name, { image, link }] of Object.entries(validOptions.custom)) badges.push(`[![${name}](${image})](${link})`);
242
+ if (validOptions?.custom !== void 0) for (const [badgeName, { image, link }] of Object.entries(validOptions.custom)) badges.push(`[![${badgeName}](${image})](${link})`);
242
243
  return badges.join("\n");
243
244
  } } };
244
245
  //#endregion
@@ -250,7 +251,7 @@ var banner_default = { banner: { async content(options) {
250
251
  }).optional().parse(options);
251
252
  const src = validOptions?.src ?? await getBannerSrc();
252
253
  if (src === void 0) throw new Error("Banner image not found at any typical location, consider adding something at ./assets/banner.webp");
253
- else if (!isUrl(src) && !await isFile(src)) throw new Error(`Banner image not found at "${src}"`);
254
+ if (!isUrl(src) && !await isFile(src)) throw new Error(`Banner image not found at "${src}"`);
254
255
  let alt = validOptions?.alt;
255
256
  if (alt === void 0) {
256
257
  const { name: packageName } = await getReadmeMetadata();
@@ -262,7 +263,7 @@ var banner_default = { banner: { async content(options) {
262
263
  async function getBannerSrc() {
263
264
  const { projectDirectory } = await getReadmeMetadata();
264
265
  if (projectDirectory === void 0) throw new Error("No project directory found");
265
- const paths = await globby([
266
+ const [firstPath] = await globby([
266
267
  ".",
267
268
  "assets",
268
269
  "media",
@@ -298,19 +299,18 @@ async function getBannerSrc() {
298
299
  ]
299
300
  }
300
301
  });
301
- if (paths.length > 0) return path.relative(process.cwd(), paths[0]);
302
+ return firstPath === void 0 ? void 0 : path.relative(process.cwd(), firstPath);
302
303
  }
303
304
  function isUrl(text, lenient = true) {
304
305
  if (typeof text !== "string") throw new TypeError("Expected a string");
305
306
  text = text.trim();
306
307
  if (text.includes(" ")) return false;
307
308
  if (URL.canParse(text)) return true;
308
- if (lenient) return isUrl(`https://${text}`, false);
309
- return false;
309
+ return lenient && URL.canParse(`https://${text}`);
310
310
  }
311
311
  //#endregion
312
312
  //#region src/lib/readme/rules/code.ts
313
- const LEADING_DOT_REGEX = /^\./;
313
+ const LEADING_DOT_REGEX = /^\./v;
314
314
  var code_default = { code: { async content(options) {
315
315
  const validOptions = z.object({
316
316
  file: z.string(),
@@ -387,7 +387,7 @@ var dependencies_default = { dependencies: { async content() {
387
387
  if (engines?.[platformKey] !== void 0) continue;
388
388
  const info = PLATFORM_INFO[platformKey.toLowerCase()];
389
389
  const display = info ? `[${info.display}](${info.url})` : platformKey;
390
- platformItems.push(version ? `- ${display} ${version}` : `- ${display}`);
390
+ platformItems.push(version === void 0 || version === "" ? `- ${display}` : `- ${display} ${version}`);
391
391
  }
392
392
  if (operatingSystem !== void 0 && operatingSystem.length > 0) platformItems.push(`- Supported platforms: ${operatingSystem.join(", ")}`);
393
393
  const peerItems = [];
@@ -449,7 +449,7 @@ var title_default = { title: {
449
449
  },
450
450
  order: 2
451
451
  } };
452
- const SPLIT_FOR_TITLE_CASE_REGEX = /[ _-]+/;
452
+ const SPLIT_FOR_TITLE_CASE_REGEX = /[ _\-]+/v;
453
453
  function makeTitleCase(text) {
454
454
  return text.split(SPLIT_FOR_TITLE_CASE_REGEX).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
455
455
  }
@@ -520,7 +520,7 @@ async function createSizeReport(filePath) {
520
520
  original: createSizeInfo(originalSize, originalSize)
521
521
  };
522
522
  } catch (error) {
523
- throw new Error(`Failed to analyze file: ${error instanceof Error ? error.message : "Unknown error"}`);
523
+ throw new Error(`Failed to analyze file: ${error instanceof Error ? error.message : "Unknown error"}`, { cause: error });
524
524
  }
525
525
  }
526
526
  //#endregion
@@ -615,8 +615,8 @@ var table_of_contents_default = { "table-of-contents": {
615
615
  ]).optional() }).optional().parse(options)?.depth ?? 3,
616
616
  tight: true
617
617
  });
618
- const heading = `## Table of contents`;
619
618
  if (result.map === void 0) throw new Error("Could not generate table of contents");
619
+ const heading = `## Table of contents`;
620
620
  const rootWrapper = {
621
621
  children: result.map.children,
622
622
  type: "root"
@@ -674,13 +674,13 @@ function getAdditionalConfigExplorer() {
674
674
  async function loadConfig(options) {
675
675
  const { additionalConfig, defaults = rules_default, searchFrom } = options ?? {};
676
676
  let finalConfig = { mdat: `Powered by the Markdown Autophagic Template system: [mdat](https://github.com/kitschpatrol/mdat).` };
677
- if (defaults) finalConfig = deepMergeDefined(finalConfig, defaults);
677
+ finalConfig = deepMergeDefined(finalConfig, defaults);
678
678
  const results = await getConfigExplorer().search(searchFrom);
679
679
  if (results) {
680
680
  const { config, filepath } = results;
681
681
  let possibleRules = config;
682
682
  log.debug(`Using config from "${filepath}"`);
683
- if (filepath.endsWith("package.json") && typeof config === "string") {
683
+ if (typeof config === "string" && filepath.endsWith("package.json")) {
684
684
  log.debug(`Detected shared config string: "${config}"`);
685
685
  const { default: sharedConfig } = await import(config);
686
686
  possibleRules = sharedConfig;
@@ -693,14 +693,14 @@ async function loadConfig(options) {
693
693
  for (const configOrPath of additionalConfigArray) {
694
694
  let loaded;
695
695
  if (typeof configOrPath === "string") {
696
- let results;
697
- if (path.basename(configOrPath).endsWith("package.json")) results = {
696
+ let loadResults;
697
+ if (path.basename(configOrPath).endsWith("package.json")) loadResults = {
698
698
  config: mdatJsonLoader(configOrPath, await fs.readFile(configOrPath, "utf8")),
699
699
  filepath: configOrPath
700
700
  };
701
- else results = await getAdditionalConfigExplorer().load(configOrPath);
702
- if (results === null || results === void 0) continue;
703
- const { config: loadedConfig, filepath } = results;
701
+ else loadResults = await getAdditionalConfigExplorer().load(configOrPath);
702
+ if (loadResults === null || loadResults === void 0) continue;
703
+ const { config: loadedConfig, filepath } = loadResults;
704
704
  log.debug(`Loaded additional config from "${filepath}"`);
705
705
  loaded = loadedConfig;
706
706
  } else loaded = configOrPath;
@@ -745,7 +745,7 @@ async function formatWithPrettier(content, filePath) {
745
745
  } catch {
746
746
  throw new Error("The --format flag requires `prettier` to be installed. Run: pnpm add -D prettier");
747
747
  }
748
- const configKey = filePath ? path.dirname(filePath) : process.cwd();
748
+ const configKey = filePath === void 0 || filePath === "" ? process.cwd() : path.dirname(filePath);
749
749
  let config = configCache.get(configKey);
750
750
  if (config === void 0 && !configCache.has(configKey)) {
751
751
  config = await cachedPrettier.resolveConfig(filePath ?? process.cwd());
@@ -767,22 +767,22 @@ function zeroPad(n, nMax) {
767
767
  async function getInputOutputPaths(inputs, output, name, extension) {
768
768
  const paths = [];
769
769
  for (const [index, file] of inputs.entries()) {
770
- const inputOutputPath = await getInputOutputPath(file, output, name, extension, name && inputs.length > 1 ? `-${zeroPad(index + 1, inputs.length)}` : "");
770
+ const inputOutputPath = await getInputOutputPath(file, output, name, extension, name !== void 0 && name !== "" && inputs.length > 1 ? `-${zeroPad(index + 1, inputs.length)}` : "");
771
771
  paths.push(inputOutputPath);
772
772
  }
773
773
  return paths;
774
774
  }
775
775
  async function getInputOutputPath(input, output, name, extension, nameSuffix = "") {
776
776
  const resolvedInput = expandPath(input);
777
- const resolvedOutput = output ? expandPath(output) : void 0;
778
777
  if (!isFileSync(resolvedInput)) throw new Error(`Input file not found: "${resolvedInput}"`);
779
- if (resolvedOutput) {
778
+ const resolvedOutput = output === void 0 || output === "" ? void 0 : expandPath(output);
779
+ if (resolvedOutput !== void 0) {
780
780
  if (isFileSync(resolvedOutput)) throw new Error(`Output path must be a directory, received a file path: "${resolvedOutput}"`);
781
781
  await fs.mkdir(resolvedOutput, { recursive: true });
782
782
  }
783
783
  return {
784
784
  input: resolvedInput,
785
- name: `${name ? path.basename(name, path.extname(name)) : path.basename(resolvedInput, path.extname(resolvedInput))}${nameSuffix}${extension === void 0 ? name && path.extname(name) !== "" ? path.extname(name) : path.extname(input) : `.${extension}`}`,
785
+ name: `${name === void 0 || name === "" ? path.basename(resolvedInput, path.extname(resolvedInput)) : path.basename(name, path.extname(name))}${nameSuffix}${extension === void 0 ? name !== void 0 && path.extname(name) !== "" ? path.extname(name) : path.extname(input) : `.${extension}`}`,
786
786
  output: resolvedOutput ?? path.dirname(resolvedInput)
787
787
  };
788
788
  }
@@ -793,7 +793,7 @@ function ensureArray(value) {
793
793
  if (value === void 0 || value === null) return [];
794
794
  return Array.isArray(value) ? value : [value];
795
795
  }
796
- const README_SEARCH_REGEX = /^readme(?:\.\w+)?$/i;
796
+ const README_SEARCH_REGEX = /^readme(?:\.\w+)?$/iv;
797
797
  /**
798
798
  * Finds a readme file in the current working directory (case-insensitive).
799
799
  */
@@ -869,20 +869,20 @@ function stripLintPlugins(config) {
869
869
  //#endregion
870
870
  //#region src/lib/processors.ts
871
871
  async function processFiles(files, loader, processorGetter, name, output, config) {
872
- const [resolvedConfig, localRemarkConfiguration] = await Promise.all([loader({ additionalConfig: config }), loadAmbientRemarkConfig()]);
872
+ const [resolvedConfig, localRemarkConfig] = await Promise.all([loader({ additionalConfig: config }), loadAmbientRemarkConfig()]);
873
873
  const inputOutputPaths = await getInputOutputPaths(ensureArray(files), output, name, "md");
874
- const resolvedProcessor = processorGetter(resolvedConfig, localRemarkConfiguration);
875
- return await Promise.all(inputOutputPaths.map(async ({ input, name, output }) => {
874
+ const resolvedProcessor = processorGetter(resolvedConfig, localRemarkConfig);
875
+ return await Promise.all(inputOutputPaths.map(async ({ input, name: outputName, output: outputDirectory }) => {
876
876
  const inputFile = await read(input);
877
877
  const result = await resolvedProcessor.process(inputFile);
878
- result.dirname = output;
879
- result.basename = name;
878
+ result.dirname = outputDirectory;
879
+ result.basename = outputName;
880
880
  return result;
881
881
  }));
882
882
  }
883
883
  async function processString(markdown, loader, processorGetter, config) {
884
- const [resolvedConfig, localRemarkConfiguration] = await Promise.all([loader({ additionalConfig: config }), loadAmbientRemarkConfig()]);
885
- return processorGetter(resolvedConfig, localRemarkConfiguration).process(new VFile(markdown));
884
+ const [resolvedConfig, localRemarkConfig] = await Promise.all([loader({ additionalConfig: config }), loadAmbientRemarkConfig()]);
885
+ return processorGetter(resolvedConfig, localRemarkConfig).process(new VFile(markdown));
886
886
  }
887
887
  function getExpandProcessor(config, ambientRemarkConfig) {
888
888
  return remark().use({ settings: {
@@ -951,13 +951,15 @@ async function createReadmeInteractive() {
951
951
  const readmePath = await findReadme();
952
952
  intro(`Running ${picocolors.bold("mdat create")} interactively`);
953
953
  const newReadmePath = await createReadme(await group({
954
- overwrite: async () => readmePath === void 0 ? true : await confirm({
955
- message: `Found an existing readme at "${picocolors.blue(readmePath)}". Do you want to overwrite it?`,
956
- active: "Overwrite",
957
- inactive: "Exit"
958
- }) ? true : (() => {
959
- throw new Error("`mdat create` was cancelled to avoid an overwrite - no changes were made");
960
- })(),
954
+ async overwrite() {
955
+ if (readmePath === void 0) return true;
956
+ if (await confirm({
957
+ message: `Found an existing readme at "${picocolors.blue(readmePath)}". Do you want to overwrite it?`,
958
+ active: "Overwrite",
959
+ inactive: "Exit"
960
+ }) === false) throw new Error("`mdat create` was cancelled to avoid an overwrite - no changes were made");
961
+ return true;
962
+ },
961
963
  template: async () => select({
962
964
  message: "Which template would you like to use?",
963
965
  options: getTemplateOptions()
@@ -999,17 +1001,24 @@ async function createReadme(options) {
999
1001
  const templateString = getTemplateForConfig(resolvedOptions.template, resolvedOptions.compound);
1000
1002
  const readmePath = path.join(resolvedOptions.output, "readme.md");
1001
1003
  if (!resolvedOptions.overwrite) {
1002
- if (await fs.access(readmePath).then(() => true, () => false)) throw new Error(`Readme already exists at "${readmePath}". Use the overwrite option to replace it.`);
1004
+ let exists = true;
1005
+ try {
1006
+ await fs.access(readmePath);
1007
+ } catch {
1008
+ exists = false;
1009
+ }
1010
+ if (exists) throw new Error(`Readme already exists at "${readmePath}". Use the overwrite option to replace it.`);
1003
1011
  }
1004
1012
  await fs.writeFile(readmePath, templateString, "utf8");
1005
1013
  if (resolvedOptions.expand) {
1006
1014
  const [result] = await expand(readmePath);
1015
+ if (result === void 0) throw new Error(`Failed to expand readme at "${readmePath}"`);
1007
1016
  await write(result);
1008
1017
  }
1009
1018
  return readmePath;
1010
1019
  }
1011
1020
  function getTemplateForConfig(templateKey, compound) {
1012
- if (!(templateKey in templates_default)) throw new Error(`Unknown template "${templateKey}". Available templates: ${Object.keys(templates_default).join(", ")}`);
1021
+ if (!Object.hasOwn(templates_default, templateKey)) throw new Error(`Unknown template "${templateKey}". Available templates: ${Object.keys(templates_default).join(", ")}`);
1013
1022
  return templates_default[templateKey].content[compound ? "compound" : "explicit"];
1014
1023
  }
1015
1024
  function getTemplateOptions() {
@@ -1097,7 +1106,11 @@ async function check(files, config, options) {
1097
1106
  const resolvedFiles = Array.isArray(files) ? files : [files];
1098
1107
  const [originals, results] = await Promise.all([Promise.all(resolvedFiles.map(async (f) => read(f))), processFiles(files, loadConfig, getExpandProcessor, void 0, void 0, config)]);
1099
1108
  if (options?.format) await formatResults(results);
1100
- return results.map((result, i) => compareWithDiff(originals[i], result, options));
1109
+ return results.map((result, i) => {
1110
+ const original = originals[i];
1111
+ if (original === void 0) throw new Error(`Missing original file for comparison at index ${i}`);
1112
+ return compareWithDiff(original, result, options);
1113
+ });
1101
1114
  }
1102
1115
  /**
1103
1116
  * Check if MDAT comments in a Markdown string are up to date by expanding and
@@ -1130,7 +1143,7 @@ function compareWithDiff(original, result, options) {
1130
1143
  };
1131
1144
  }
1132
1145
  async function formatResults(results) {
1133
- for (const file of results) file.value = await formatWithPrettier(file.toString(), file.path || void 0);
1146
+ for (const file of results) file.value = await formatWithPrettier(file.toString(), file.path === "" ? void 0 : file.path);
1134
1147
  }
1135
1148
  //#endregion
1136
1149
  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.6",
3
+ "version": "3.0.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",
@@ -43,24 +43,24 @@
43
43
  "dist/*"
44
44
  ],
45
45
  "dependencies": {
46
- "@clack/prompts": "^1.5.1",
47
- "cosmiconfig": "^9.0.1",
46
+ "@clack/prompts": "^1.7.0",
47
+ "cosmiconfig": "^9.0.2",
48
48
  "cosmiconfig-typescript-loader": "^6.3.0",
49
49
  "deepmerge-ts": "^7.1.5",
50
- "globby": "^16.2.0",
51
- "lognow": "^0.6.2",
50
+ "globby": "^16.2.2",
51
+ "lognow": "^0.7.1",
52
52
  "mdast-util-toc": "^7.1.0",
53
- "metascope": "^0.8.1",
53
+ "metascope": "^0.9.0",
54
54
  "path-type": "^6.0.0",
55
55
  "picocolors": "^1.1.1",
56
56
  "plur": "^6.0.0",
57
- "pretty-bytes": "^7.1.0",
57
+ "pretty-bytes": "^7.1.1",
58
58
  "pretty-ms": "^9.3.0",
59
59
  "remark": "^15.0.1",
60
60
  "remark-gfm": "^4.0.1",
61
- "remark-mdat": "^2.2.4",
61
+ "remark-mdat": "^3.0.0",
62
62
  "to-vfile": "^8.0.0",
63
- "type-fest": "^5.7.0",
63
+ "type-fest": "^5.8.0",
64
64
  "unified-engine": "^11.2.2",
65
65
  "untildify": "^6.0.0",
66
66
  "vfile": "^6.0.3",
@@ -68,26 +68,26 @@
68
68
  "zod": "^4.4.3"
69
69
  },
70
70
  "devDependencies": {
71
- "@arethetypeswrong/core": "^0.18.3",
72
- "@kitschpatrol/shared-config": "^7.6.3",
71
+ "@arethetypeswrong/core": "^0.18.5",
72
+ "@kitschpatrol/shared-config": "^8.4.0",
73
73
  "@types/mdast": "^4.0.4",
74
- "@types/node": "~22.19.19",
74
+ "@types/node": "~24.13.3",
75
75
  "@types/unist": "^3.0.3",
76
76
  "@types/yargs": "^17.0.35",
77
- "@vitest/coverage-v8": "^4.1.8",
78
- "bumpp": "^11.1.0",
79
- "execa": "^9.6.1",
80
- "mdat-plugin-cli-help": "^3.0.1",
77
+ "@vitest/coverage-v8": "^4.1.10",
78
+ "bumpp": "^12.0.0",
79
+ "execa": "^10.0.0",
80
+ "mdat-plugin-cli-help": "^3.0.2",
81
81
  "mdat-plugin-example": "^2.0.1",
82
- "mdat-plugin-tldraw": "^2.0.2",
83
- "nanoid": "^5.1.11",
84
- "prettier": "^3.8.3",
85
- "publint": "^0.3.21",
82
+ "mdat-plugin-tldraw": "^2.0.3",
83
+ "nanoid": "^6.0.0",
84
+ "prettier": "^3.9.6",
85
+ "publint": "^0.3.22",
86
86
  "shx": "^0.4.0",
87
- "tsdown": "^0.22.2",
88
- "typescript": "~5.9.3",
87
+ "tsdown": "^0.22.14",
88
+ "typescript": "~6.0.3",
89
89
  "unplugin-raw": "^0.7.0",
90
- "vitest": "^4.1.8"
90
+ "vitest": "^4.1.10"
91
91
  },
92
92
  "peerDependencies": {
93
93
  "prettier": "^3.0.0"
@@ -98,13 +98,7 @@
98
98
  }
99
99
  },
100
100
  "engines": {
101
- "node": ">=22.17.0"
102
- },
103
- "devEngines": {
104
- "runtime": {
105
- "name": "node",
106
- "version": ">=22.22.2"
107
- }
101
+ "node": ">=24.16.0"
108
102
  },
109
103
  "scripts": {
110
104
  "bench": "vitest bench --run --compare test/benchmarks/baseline.json",
package/readme.md CHANGED
@@ -103,7 +103,7 @@ The `<!-- title -->` comment was expanded with content derived from your project
103
103
 
104
104
  ### Dependencies
105
105
 
106
- Node 22+ (specifically `>=22.17.0`). Written in TypeScript with bundled type definitions.
106
+ Node 24+ (specifically `>=24.16.0`). Written in TypeScript with bundled type definitions.
107
107
 
108
108
  ### Installation
109
109
 
@@ -531,9 +531,7 @@ type Rule =
531
531
  | string
532
532
  | {
533
533
  content:
534
- | ((options: JsonValue, context: RuleContext) => Promise<string> | string)
535
- | Rule[]
536
- | string
534
+ ((options: JsonValue, context: RuleContext) => Promise<string> | string) | Rule[] | string
537
535
  order?: number
538
536
  }
539
537