mdat 2.3.6 → 3.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,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,21 @@ 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.1.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
+ const stripped = objects.map((v, i) => i === 0 ? v : stripUndefinedDeep(v));
45
+ return deepmerge(...stripped);
46
+ }
33
47
 
34
48
  //#endregion
35
49
  //#region src/lib/log.ts
@@ -55,19 +69,6 @@ function setLogger$2(logger) {
55
69
  setLogger$1(getChildLogger(log, "metascope"));
56
70
  }
57
71
 
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
72
  //#endregion
72
73
  //#region src/lib/mdat-json-loader.ts
73
74
  /**
@@ -82,7 +83,7 @@ function mdatJsonLoader(filePath, content) {
82
83
  }
83
84
  function flattenJson(jsonObject, parentKey = "", result = {}) {
84
85
  for (const [key, value] of Object.entries(jsonObject)) {
85
- const fullPath = parentKey ? `${parentKey}.${key}` : key;
86
+ const fullPath = parentKey === "" ? key : `${parentKey}.${key}`;
86
87
  if (typeof value === "object" && value !== null && !Array.isArray(value)) flattenJson(value, fullPath, result);
87
88
  else if (value === null) result[fullPath] = "null";
88
89
  else result[fullPath] = value.toString();
@@ -111,6 +112,7 @@ async function getContextMetadata() {
111
112
  "codemetaJson",
112
113
  "gitConfig",
113
114
  "githubActions",
115
+ "gitStats",
114
116
  "goGoMod",
115
117
  "goGoreleaserYaml",
116
118
  "javaPomXml",
@@ -137,16 +139,17 @@ async function getContextMetadata() {
137
139
  });
138
140
  return metascopeMetadata;
139
141
  }
140
- const GIT_PREFIX_REGEX = /^git\+/;
141
- const GIT_SUFFIX_REGEX = /\.git$/;
142
- const TRAILING_SLASH_REGEX = /\/$/;
142
+ const GIT_PREFIX_REGEX = /^git\+/v;
143
+ const GIT_SUFFIX_REGEX = /\.git$/v;
144
+ const TRAILING_SLASH_REGEX = /\/$/v;
145
+ const PACKAGE_MANAGER_REGEX = /^(?<name>[^@]+)@(?<version>[^+]+)/v;
143
146
  const readmeMetadataTemplate = defineTemplate((context) => {
144
- const { githubActions, licenseFile, metascope, nodePackageJson } = context;
147
+ const { githubActions, gitStats, licenseFile, metascope, nodePackageJson } = context;
145
148
  const codemeta = templates.codemetaJson(context, {});
146
149
  const nodePackage = helpers.firstOf(nodePackageJson)?.data;
147
150
  const licenseFileData = helpers.firstOf(licenseFile);
148
151
  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, "");
152
+ const repoUrl = codemeta.codeRepository?.replace(GIT_PREFIX_REGEX, "").replace(GIT_SUFFIX_REGEX, "").replace(TRAILING_SLASH_REGEX, "");
150
153
  const bin = (() => {
151
154
  if (nodePackage === void 0) return;
152
155
  const binField = nodePackage.bin;
@@ -174,13 +177,35 @@ const readmeMetadataTemplate = defineTemplate((context) => {
174
177
  version
175
178
  }));
176
179
  })();
180
+ const developmentDependencies = (() => {
181
+ if (nodePackage === void 0) return;
182
+ const runtimes = helpers.ensureArray(nodePackage.devEngines?.runtime).map(({ name, version }) => ({
183
+ name,
184
+ version
185
+ }));
186
+ const packageManagers = helpers.ensureArray(nodePackage.devEngines?.packageManager).map(({ name, version }) => ({
187
+ name,
188
+ version
189
+ }));
190
+ const pinnedPackageManager = nodePackage.packageManager === void 0 ? void 0 : PACKAGE_MANAGER_REGEX.exec(nodePackage.packageManager)?.groups;
191
+ if (pinnedPackageManager?.name !== void 0 && pinnedPackageManager.version !== void 0 && packageManagers.every((entry) => entry.name !== pinnedPackageManager.name)) packageManagers.push({
192
+ name: pinnedPackageManager.name,
193
+ version: pinnedPackageManager.version
194
+ });
195
+ if (runtimes.length === 0 && packageManagers.length === 0) return;
196
+ return {
197
+ packageManagers: packageManagers.length > 0 ? packageManagers : void 0,
198
+ runtimes: runtimes.length > 0 ? runtimes : void 0
199
+ };
200
+ })();
177
201
  const firstAuthor = helpers.firstOf(helpers.ensureArray(codemeta.author));
178
202
  return {
179
203
  author: helpers.firstOf(helpers.mixedStringsToArray(helpers.toBasicNames(codemeta.author))),
180
204
  authorUrl: firstAuthor?.url,
181
205
  bin,
182
- ciActionFileName: ciActionFilePath ? path.basename(ciActionFilePath) : void 0,
206
+ ciActionFileName: ciActionFilePath === void 0 ? void 0 : path.basename(ciActionFilePath),
183
207
  description: codemeta.description,
208
+ developmentDependencies,
184
209
  engines,
185
210
  isPublicNpmPackage: nodePackage?.private !== true && (nodePackage?.name.startsWith("@") && nodePackage.publishConfig?.access === "public" || true),
186
211
  issuesUrl: codemeta.issueTracker,
@@ -191,8 +216,9 @@ const readmeMetadataTemplate = defineTemplate((context) => {
191
216
  operatingSystem: codemeta.operatingSystem,
192
217
  peerDependencies,
193
218
  projectDirectory: metascope?.data.options.path === void 0 ? void 0 : `file://${metascope.data.options.path}`,
194
- repositoryUrl,
219
+ repositoryUrl: repoUrl,
195
220
  runtimePlatform: codemeta.runtimePlatform,
221
+ usesGitLfs: helpers.firstOf(gitStats)?.data.hasLfs === true,
196
222
  usesPnpm: helpers.usesPnpm(nodePackageJson)
197
223
  };
198
224
  });
@@ -204,7 +230,8 @@ let readmeMetadata;
204
230
  */
205
231
  async function getReadmeMetadata() {
206
232
  if (readmeMetadata !== void 0) return readmeMetadata;
207
- readmeMetadata = readmeMetadataTemplate(await getContextMetadata(), {});
233
+ const contextMetadata = await getContextMetadata();
234
+ readmeMetadata = readmeMetadataTemplate(contextMetadata, {});
208
235
  return readmeMetadata;
209
236
  }
210
237
 
@@ -219,14 +246,15 @@ var badges_default = { badges: { async content(options) {
219
246
  npm: z.array(z.string()).optional()
220
247
  }).optional().parse(options);
221
248
  const metadata = await getReadmeMetadata();
222
- const { ciActionFileName, license, licenseUrl, name, repositoryUrl } = metadata;
249
+ const { ciActionFileName, license, licenseUrl, name, repositoryUrl, usesGitLfs } = metadata;
223
250
  const badges = [];
224
251
  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})`);
252
+ if (metadata.isPublicNpmPackage) badges.push(`[![NPM Package ${name}](https://img.shields.io/npm/v/${name}.svg)](https://www.npmjs.com/package/${name})`);
253
+ } 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
254
  if (license !== void 0 && licenseUrl !== void 0) badges.push(`[![License: ${license}](https://img.shields.io/badge/License-${license.replaceAll("-", "--")}-yellow.svg)](${licenseUrl})`);
228
255
  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})`);
256
+ if (usesGitLfs) badges.push("![Git LFS](https://img.shields.io/badge/Git%20LFS-enabled-F64935?logo=gitlfs&logoColor=white)");
257
+ if (validOptions?.custom !== void 0) for (const [badgeName, { image, link }] of Object.entries(validOptions.custom)) badges.push(`[![${badgeName}](${image})](${link})`);
230
258
  return badges.join("\n");
231
259
  } } };
232
260
 
@@ -239,7 +267,7 @@ var banner_default = { banner: { async content(options) {
239
267
  }).optional().parse(options);
240
268
  const src = validOptions?.src ?? await getBannerSrc();
241
269
  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}"`);
270
+ if (!isUrl(src) && !await isFile(src)) throw new Error(`Banner image not found at "${src}"`);
243
271
  let alt = validOptions?.alt;
244
272
  if (alt === void 0) {
245
273
  const { name: packageName } = await getReadmeMetadata();
@@ -251,7 +279,7 @@ var banner_default = { banner: { async content(options) {
251
279
  async function getBannerSrc() {
252
280
  const { projectDirectory } = await getReadmeMetadata();
253
281
  if (projectDirectory === void 0) throw new Error("No project directory found");
254
- const paths = await globby([
282
+ const [firstPath] = await globby([
255
283
  ".",
256
284
  "assets",
257
285
  "media",
@@ -287,20 +315,19 @@ async function getBannerSrc() {
287
315
  ]
288
316
  }
289
317
  });
290
- if (paths.length > 0) return path.relative(process.cwd(), paths[0]);
318
+ return firstPath === void 0 ? void 0 : path.relative(process.cwd(), firstPath);
291
319
  }
292
320
  function isUrl(text, lenient = true) {
293
321
  if (typeof text !== "string") throw new TypeError("Expected a string");
294
322
  text = text.trim();
295
323
  if (text.includes(" ")) return false;
296
324
  if (URL.canParse(text)) return true;
297
- if (lenient) return isUrl(`https://${text}`, false);
298
- return false;
325
+ return lenient && URL.canParse(`https://${text}`);
299
326
  }
300
327
 
301
328
  //#endregion
302
329
  //#region src/lib/readme/rules/code.ts
303
- const LEADING_DOT_REGEX = /^\./;
330
+ const LEADING_DOT_REGEX = /^\./v;
304
331
  var code_default = { code: { async content(options) {
305
332
  const validOptions = z.object({
306
333
  file: z.string(),
@@ -349,7 +376,7 @@ const PLATFORM_INFO = {
349
376
  },
350
377
  node: {
351
378
  display: "Node.js",
352
- url: "https://nodejs.org/"
379
+ url: "https://nodejs.org/en"
353
380
  },
354
381
  python: {
355
382
  display: "Python",
@@ -379,7 +406,7 @@ var dependencies_default = { dependencies: { async content() {
379
406
  if (engines?.[platformKey] !== void 0) continue;
380
407
  const info = PLATFORM_INFO[platformKey.toLowerCase()];
381
408
  const display = info ? `[${info.display}](${info.url})` : platformKey;
382
- platformItems.push(version ? `- ${display} ${version}` : `- ${display}`);
409
+ platformItems.push(version === void 0 || version === "" ? `- ${display}` : `- ${display} ${version}`);
383
410
  }
384
411
  if (operatingSystem !== void 0 && operatingSystem.length > 0) platformItems.push(`- Supported platforms: ${operatingSystem.join(", ")}`);
385
412
  const peerItems = [];
@@ -409,6 +436,58 @@ var description_default = { description: { async content() {
409
436
  return `**${description}**`;
410
437
  } } };
411
438
 
439
+ //#endregion
440
+ //#region src/lib/readme/rules/development-dependencies.ts
441
+ const TOOL_INFO = {
442
+ bun: {
443
+ display: "Bun",
444
+ url: "https://bun.sh/"
445
+ },
446
+ deno: {
447
+ display: "Deno",
448
+ url: "https://deno.land/"
449
+ },
450
+ node: {
451
+ display: "Node.js",
452
+ url: "https://nodejs.org/en"
453
+ },
454
+ npm: {
455
+ display: "npm",
456
+ url: "https://www.npmjs.com/"
457
+ },
458
+ pnpm: {
459
+ display: "pnpm",
460
+ url: "https://pnpm.io/"
461
+ },
462
+ yarn: {
463
+ display: "Yarn",
464
+ url: "https://yarnpkg.com/"
465
+ }
466
+ };
467
+ var development_dependencies_default = { "development-dependencies": { async content() {
468
+ const { developmentDependencies } = await getReadmeMetadata();
469
+ if (developmentDependencies === void 0) return "";
470
+ const { packageManagers, runtimes } = developmentDependencies;
471
+ const items = [];
472
+ for (const { name, version } of [...runtimes ?? [], ...packageManagers ?? []]) {
473
+ const info = TOOL_INFO[name.toLowerCase()];
474
+ const display = info ? `[${info.display}](${info.url})` : name;
475
+ items.push(version === void 0 || version === "" ? `- ${display}` : `- ${display} ${version}`);
476
+ }
477
+ return [
478
+ "## Development Dependencies",
479
+ "",
480
+ ...items
481
+ ].join("\n");
482
+ } } };
483
+
484
+ //#endregion
485
+ //#region src/lib/readme/rules/dev-dependencies.ts
486
+ /**
487
+ * Simple alias for `development-dependencies`
488
+ */
489
+ var dev_dependencies_default = { "dev-dependencies": getSoleRule(development_dependencies_default) };
490
+
412
491
  //#endregion
413
492
  //#region src/lib/readme/rules/license.ts
414
493
  var license_default = { license: { async content() {
@@ -446,7 +525,7 @@ var title_default = { title: {
446
525
  },
447
526
  order: 2
448
527
  } };
449
- const SPLIT_FOR_TITLE_CASE_REGEX = /[ _-]+/;
528
+ const SPLIT_FOR_TITLE_CASE_REGEX = /[ _\-]+/v;
450
529
  function makeTitleCase(text) {
451
530
  return text.split(SPLIT_FOR_TITLE_CASE_REGEX).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
452
531
  }
@@ -520,7 +599,7 @@ async function createSizeReport(filePath) {
520
599
  original: createSizeInfo(originalSize, originalSize)
521
600
  };
522
601
  } catch (error) {
523
- throw new Error(`Failed to analyze file: ${error instanceof Error ? error.message : "Unknown error"}`);
602
+ throw new Error(`Failed to analyze file: ${error instanceof Error ? error.message : "Unknown error"}`, { cause: error });
524
603
  }
525
604
  }
526
605
 
@@ -543,7 +622,8 @@ function getSizeForCompression(report, compression) {
543
622
  }
544
623
  var size_default = { size: { async content(options) {
545
624
  const validOptions = optionsSchema$1.parse(options);
546
- return getSizeForCompression(await createSizeReport(path.join(process.cwd(), validOptions.file)), validOptions.compression);
625
+ const fullPath = path.join(process.cwd(), validOptions.file);
626
+ return getSizeForCompression(await createSizeReport(fullPath), validOptions.compression);
547
627
  } } };
548
628
 
549
629
  //#endregion
@@ -593,7 +673,8 @@ function formatMarkdownTable(reports, options) {
593
673
  var size_table_default = { "size-table": { async content(options) {
594
674
  const validOptions = optionsSchema.parse(options);
595
675
  return formatMarkdownTable(await Promise.all(validOptions.files.map(async (file) => {
596
- return [file, await createSizeReport(path.join(process.cwd(), file))];
676
+ const fullPath = path.join(process.cwd(), file);
677
+ return [file, await createSizeReport(fullPath)];
597
678
  })), {
598
679
  brotli: validOptions.brotli,
599
680
  gzip: validOptions.gzip,
@@ -606,20 +687,21 @@ var size_table_default = { "size-table": { async content(options) {
606
687
  //#region src/lib/readme/rules/table-of-contents.ts
607
688
  var table_of_contents_default = { "table-of-contents": {
608
689
  async content(options, { tree }) {
690
+ const validOptions = z.object({ depth: z.union([
691
+ z.literal(1),
692
+ z.literal(2),
693
+ z.literal(3),
694
+ z.literal(4),
695
+ z.literal(5),
696
+ z.literal(6)
697
+ ]).optional() }).optional().parse(options);
609
698
  const result = toc(tree, {
610
699
  heading: null,
611
- maxDepth: z.object({ depth: z.union([
612
- z.literal(1),
613
- z.literal(2),
614
- z.literal(3),
615
- z.literal(4),
616
- z.literal(5),
617
- z.literal(6)
618
- ]).optional() }).optional().parse(options)?.depth ?? 3,
700
+ maxDepth: validOptions?.depth ?? 3,
619
701
  tight: true
620
702
  });
621
- const heading = `## Table of contents`;
622
703
  if (result.map === void 0) throw new Error("Could not generate table of contents");
704
+ const heading = `## Table of contents`;
623
705
  const rootWrapper = {
624
706
  children: result.map.children,
625
707
  type: "root"
@@ -645,6 +727,8 @@ var rules_default = {
645
727
  ...contributing_default,
646
728
  ...dependencies_default,
647
729
  ...description_default,
730
+ ...dev_dependencies_default,
731
+ ...development_dependencies_default,
648
732
  ...footer_default,
649
733
  ...header_default,
650
734
  ...install_default,
@@ -680,13 +764,13 @@ function getAdditionalConfigExplorer() {
680
764
  async function loadConfig(options) {
681
765
  const { additionalConfig, defaults = rules_default, searchFrom } = options ?? {};
682
766
  let finalConfig = { mdat: `Powered by the Markdown Autophagic Template system: [mdat](https://github.com/kitschpatrol/mdat).` };
683
- if (defaults) finalConfig = deepMergeDefined(finalConfig, defaults);
767
+ finalConfig = deepMergeDefined(finalConfig, defaults);
684
768
  const results = await getConfigExplorer().search(searchFrom);
685
769
  if (results) {
686
770
  const { config, filepath } = results;
687
771
  let possibleRules = config;
688
772
  log.debug(`Using config from "${filepath}"`);
689
- if (filepath.endsWith("package.json") && typeof config === "string") {
773
+ if (typeof config === "string" && filepath.endsWith("package.json")) {
690
774
  log.debug(`Detected shared config string: "${config}"`);
691
775
  const { default: sharedConfig } = await import(config);
692
776
  possibleRules = sharedConfig;
@@ -699,14 +783,16 @@ async function loadConfig(options) {
699
783
  for (const configOrPath of additionalConfigArray) {
700
784
  let loaded;
701
785
  if (typeof configOrPath === "string") {
702
- let results;
703
- if (path.basename(configOrPath).endsWith("package.json")) results = {
704
- config: mdatJsonLoader(configOrPath, await fs.readFile(configOrPath, "utf8")),
705
- filepath: configOrPath
706
- };
707
- else results = await getAdditionalConfigExplorer().load(configOrPath);
708
- if (results === null || results === void 0) continue;
709
- const { config: loadedConfig, filepath } = results;
786
+ let loadResults;
787
+ if (path.basename(configOrPath).endsWith("package.json")) {
788
+ const packageJson = await fs.readFile(configOrPath, "utf8");
789
+ loadResults = {
790
+ config: mdatJsonLoader(configOrPath, packageJson),
791
+ filepath: configOrPath
792
+ };
793
+ } else loadResults = await getAdditionalConfigExplorer().load(configOrPath);
794
+ if (loadResults === null || loadResults === void 0) continue;
795
+ const { config: loadedConfig, filepath } = loadResults;
710
796
  log.debug(`Loaded additional config from "${filepath}"`);
711
797
  loaded = loadedConfig;
712
798
  } else loaded = configOrPath;
@@ -740,7 +826,7 @@ async function formatWithPrettier(content, filePath) {
740
826
  } catch {
741
827
  throw new Error("The --format flag requires `prettier` to be installed. Run: pnpm add -D prettier");
742
828
  }
743
- const configKey = filePath ? path.dirname(filePath) : process.cwd();
829
+ const configKey = filePath === void 0 || filePath === "" ? process.cwd() : path.dirname(filePath);
744
830
  let config = configCache.get(configKey);
745
831
  if (config === void 0 && !configCache.has(configKey)) {
746
832
  config = await cachedPrettier.resolveConfig(filePath ?? process.cwd());
@@ -763,22 +849,22 @@ function zeroPad(n, nMax) {
763
849
  async function getInputOutputPaths(inputs, output, name, extension) {
764
850
  const paths = [];
765
851
  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)}` : "");
852
+ const inputOutputPath = await getInputOutputPath(file, output, name, extension, name !== void 0 && name !== "" && inputs.length > 1 ? `-${zeroPad(index + 1, inputs.length)}` : "");
767
853
  paths.push(inputOutputPath);
768
854
  }
769
855
  return paths;
770
856
  }
771
857
  async function getInputOutputPath(input, output, name, extension, nameSuffix = "") {
772
858
  const resolvedInput = expandPath(input);
773
- const resolvedOutput = output ? expandPath(output) : void 0;
774
859
  if (!isFileSync(resolvedInput)) throw new Error(`Input file not found: "${resolvedInput}"`);
775
- if (resolvedOutput) {
860
+ const resolvedOutput = output === void 0 || output === "" ? void 0 : expandPath(output);
861
+ if (resolvedOutput !== void 0) {
776
862
  if (isFileSync(resolvedOutput)) throw new Error(`Output path must be a directory, received a file path: "${resolvedOutput}"`);
777
863
  await fs.mkdir(resolvedOutput, { recursive: true });
778
864
  }
779
865
  return {
780
866
  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}`}`,
867
+ 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
868
  output: resolvedOutput ?? path.dirname(resolvedInput)
783
869
  };
784
870
  }
@@ -789,7 +875,7 @@ function ensureArray(value) {
789
875
  if (value === void 0 || value === null) return [];
790
876
  return Array.isArray(value) ? value : [value];
791
877
  }
792
- const README_SEARCH_REGEX = /^readme(?:\.\w+)?$/i;
878
+ const README_SEARCH_REGEX = /^readme(?:\.\w+)?$/iv;
793
879
  /**
794
880
  * Finds a readme file in the current working directory (case-insensitive).
795
881
  */
@@ -866,14 +952,15 @@ function stripLintPlugins(config) {
866
952
  //#endregion
867
953
  //#region src/lib/processors.ts
868
954
  async function processFiles(files, loader, processorGetter, name, output, config) {
869
- const [resolvedConfig, localRemarkConfiguration] = await Promise.all([loader({ additionalConfig: config }), loadAmbientRemarkConfig()]);
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 }) => {
955
+ const [resolvedConfig, localRemarkConfig] = await Promise.all([loader({ additionalConfig: config }), loadAmbientRemarkConfig()]);
956
+ const resolvedFiles = ensureArray(files);
957
+ const inputOutputPaths = await getInputOutputPaths(resolvedFiles, output, name, "md");
958
+ const resolvedProcessor = processorGetter(resolvedConfig, localRemarkConfig);
959
+ return await Promise.all(inputOutputPaths.map(async ({ input, name: outputName, output: outputDirectory }) => {
873
960
  const inputFile = await read(input);
874
961
  const result = await resolvedProcessor.process(inputFile);
875
- result.dirname = output;
876
- result.basename = name;
962
+ result.dirname = outputDirectory;
963
+ result.basename = outputName;
877
964
  return result;
878
965
  }));
879
966
  }
@@ -970,13 +1057,15 @@ async function createReadmeInteractive() {
970
1057
  const readmePath = await findReadme();
971
1058
  intro(`Running ${picocolors.bold("mdat create")} interactively`);
972
1059
  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
- })(),
1060
+ async overwrite() {
1061
+ if (readmePath === void 0) return true;
1062
+ if (await confirm({
1063
+ message: `Found an existing readme at "${picocolors.blue(readmePath)}". Do you want to overwrite it?`,
1064
+ active: "Overwrite",
1065
+ inactive: "Exit"
1066
+ }) === false) throw new Error("`mdat create` was cancelled to avoid an overwrite - no changes were made");
1067
+ return true;
1068
+ },
980
1069
  template: async () => select({
981
1070
  message: "Which template would you like to use?",
982
1071
  options: getTemplateOptions()
@@ -1018,17 +1107,24 @@ async function createReadme(options) {
1018
1107
  const templateString = getTemplateForConfig(resolvedOptions.template, resolvedOptions.compound);
1019
1108
  const readmePath = path.join(resolvedOptions.output, "readme.md");
1020
1109
  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.`);
1110
+ let exists = true;
1111
+ try {
1112
+ await fs.access(readmePath);
1113
+ } catch {
1114
+ exists = false;
1115
+ }
1116
+ if (exists) throw new Error(`Readme already exists at "${readmePath}". Use the overwrite option to replace it.`);
1022
1117
  }
1023
1118
  await fs.writeFile(readmePath, templateString, "utf8");
1024
1119
  if (resolvedOptions.expand) {
1025
1120
  const [result] = await expand(readmePath);
1121
+ if (result === void 0) throw new Error(`Failed to expand readme at "${readmePath}"`);
1026
1122
  await write(result);
1027
1123
  }
1028
1124
  return readmePath;
1029
1125
  }
1030
1126
  function getTemplateForConfig(templateKey, compound) {
1031
- if (!(templateKey in templates_default)) throw new Error(`Unknown template "${templateKey}". Available templates: ${Object.keys(templates_default).join(", ")}`);
1127
+ if (!Object.hasOwn(templates_default, templateKey)) throw new Error(`Unknown template "${templateKey}". Available templates: ${Object.keys(templates_default).join(", ")}`);
1032
1128
  return templates_default[templateKey].content[compound ? "compound" : "explicit"];
1033
1129
  }
1034
1130
  function getTemplateOptions() {
@@ -1093,7 +1189,11 @@ async function check(files, config, options) {
1093
1189
  const resolvedFiles = Array.isArray(files) ? files : [files];
1094
1190
  const [originals, results] = await Promise.all([Promise.all(resolvedFiles.map(async (f) => read(f))), processFiles(files, loadConfig, getExpandProcessor, void 0, void 0, config)]);
1095
1191
  if (options?.format) await formatResults(results);
1096
- return results.map((result, i) => compareWithDiff(originals[i], result, options));
1192
+ return results.map((result, i) => {
1193
+ const original = originals[i];
1194
+ if (original === void 0) throw new Error(`Missing original file for comparison at index ${i}`);
1195
+ return compareWithDiff(original, result, options);
1196
+ });
1097
1197
  }
1098
1198
  function compareWithDiff(original, result, options) {
1099
1199
  const inSync = original.toString().replaceAll("\r\n", "\n") === result.toString();
@@ -1115,7 +1215,7 @@ function compareWithDiff(original, result, options) {
1115
1215
  };
1116
1216
  }
1117
1217
  async function formatResults(results) {
1118
- for (const file of results) file.value = await formatWithPrettier(file.toString(), file.path || void 0);
1218
+ for (const file of results) file.value = await formatWithPrettier(file.toString(), file.path === "" ? void 0 : file.path);
1119
1219
  }
1120
1220
 
1121
1221
  //#endregion
@@ -1195,55 +1295,57 @@ const yargsInstance = yargs(hideBin(process.argv));
1195
1295
  try {
1196
1296
  await yargsInstance.scriptName("mdat").usage("$0 [command] [files..] [options]", "Work with MDAT placeholder comments in Markdown files.").option(verboseOption).middleware((argv) => {
1197
1297
  setLogger$2(createLogger({
1198
- name,
1199
- verbose: argv.verbose ?? false,
1200
1298
  logToConsole: {
1201
1299
  showLevel: false,
1202
1300
  showName: false,
1203
1301
  showTime: false
1204
- }
1302
+ },
1303
+ name,
1304
+ verbose: argv.verbose ?? false
1205
1305
  }));
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 }) => {
1306
+ }).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
1307
  logConflicts({
1208
- name,
1308
+ name: fileName,
1209
1309
  output,
1210
1310
  print
1211
1311
  });
1212
- const results = await expand(files, name, output, collectConfig(config), { format });
1312
+ const mergedConfig = collectConfig(config);
1313
+ const results = await expand(files, fileName, output, mergedConfig, { format });
1213
1314
  for (const file of results) if (print) process.stdout.write(file.toString());
1214
1315
  else await write(file);
1215
1316
  reporterMdat(results);
1216
1317
  log.debug(`Expanded comments in ${prettyMilliseconds(performance.now() - startTime)}.`);
1217
1318
  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 }) => {
1319
+ }).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
1320
  logConflicts({
1220
- name,
1321
+ name: fileName,
1221
1322
  output,
1222
1323
  print
1223
1324
  });
1224
- const results = await collapse(files, name, output, void 0, { format });
1325
+ const results = await collapse(files, fileName, output, void 0, { format });
1225
1326
  for (const file of results) if (print) process.stdout.write(file.toString());
1226
1327
  else await write(file);
1227
1328
  reporterMdat(results);
1228
1329
  log.debug(`Collapsed comments in ${prettyMilliseconds(performance.now() - startTime)}.`);
1229
1330
  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 }) => {
1331
+ }).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
1332
  logConflicts({
1232
- name,
1333
+ name: fileName,
1233
1334
  output,
1234
1335
  print
1235
1336
  });
1236
- const results = await strip(files, name, output, void 0, { format });
1337
+ const results = await strip(files, fileName, output, void 0, { format });
1237
1338
  for (const file of results) if (print) process.stdout.write(file.toString());
1238
1339
  else await write(file);
1239
1340
  reporterMdat(results);
1240
1341
  log.debug(`Stripped comments in ${prettyMilliseconds(performance.now() - startTime)}.`);
1241
1342
  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 }) => {
1243
- const results = await check(files, collectConfig(config), { format });
1343
+ }).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 }) => {
1344
+ const mergedConfig = collectConfig(config);
1345
+ const results = await check(files, mergedConfig, { format });
1244
1346
  let allInSync = true;
1245
1347
  for (const { inSync, result } of results) {
1246
- const filePath = result.path || "unknown";
1348
+ const filePath = result.path === "" ? "unknown" : result.path;
1247
1349
  if (inSync) log.debug(`${picocolors.green("Up to date")}: ${filePath}`);
1248
1350
  else {
1249
1351
  log.warn(`${picocolors.red("Stale content")}: ${filePath}`);
@@ -1253,12 +1355,12 @@ try {
1253
1355
  }
1254
1356
  log.debug(`Checked in ${prettyMilliseconds(performance.now() - startTime)}.`);
1255
1357
  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 }) => {
1358
+ }).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
1359
  if (interactive) await createReadmeInteractive();
1258
1360
  else {
1259
1361
  const readmePath = await createReadme({
1260
1362
  compound,
1261
- expand,
1363
+ expand: expandTemplate,
1262
1364
  output,
1263
1365
  overwrite,
1264
1366
  template
@@ -1272,11 +1374,11 @@ try {
1272
1374
  process.exitCode = 1;
1273
1375
  }
1274
1376
  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`);
1377
+ if (args.print && args.output !== void 0 && args.output !== "") log.warn(`Ignoring --output option because --print is set`);
1378
+ if (args.print && args.name !== void 0 && args.name !== "") log.warn(`Ignoring --name option because --print is set`);
1277
1379
  }
1278
1380
  function collectConfig(config) {
1279
- if (config === void 0) return void 0;
1381
+ if (config === void 0) return;
1280
1382
  return ensureArray(config);
1281
1383
  }
1282
1384
  function getExitCode(results) {