mdat 3.2.2 → 3.4.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,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { S as version, a as expand, b as setLogger$1, d as templates_default, f as ensureArray, l as createReadme, r as collapse, s as strip, t as check, u as createReadmeInteractive, x as name, y as log } from "../lib-BEZJR3k_.js";
2
+ import { S as version, a as expand, b as setLogger$1, d as templates_default, f as ensureArray, l as createReadme, r as collapse, s as strip, t as check, u as createReadmeInteractive, x as name, y as log } from "../lib-D8QaJZ-l.js";
3
3
  import { createLogger } from "lognow";
4
4
  import picocolors from "picocolors";
5
5
  import prettyMilliseconds from "pretty-ms";
@@ -169,6 +169,10 @@ declare const readmeMetadataTemplate: import("metascope").Template<{
169
169
  engines: {
170
170
  [k: string]: string;
171
171
  } | undefined;
172
+ hasTypes: boolean;
173
+ homebrewFormula: string | undefined;
174
+ isLibrary: boolean;
175
+ isNodePackage: boolean;
172
176
  isPublicNpmPackage: boolean;
173
177
  issuesUrl: string | undefined;
174
178
  license: string | undefined;
@@ -185,7 +189,6 @@ declare const readmeMetadataTemplate: import("metascope").Template<{
185
189
  repositoryUrl: string | undefined;
186
190
  runtimePlatform: string[] | undefined;
187
191
  usesGitLfs: boolean;
188
- usesPnpm: boolean;
189
192
  }>;
190
193
  type ReadmeMetadata = ReturnType<typeof readmeMetadataTemplate>;
191
194
  /**
@@ -212,6 +215,10 @@ export declare function getReadmeMetadata(): Promise<{
212
215
  engines: {
213
216
  [k: string]: string;
214
217
  } | undefined;
218
+ hasTypes: boolean;
219
+ homebrewFormula: string | undefined;
220
+ isLibrary: boolean;
221
+ isNodePackage: boolean;
215
222
  isPublicNpmPackage: boolean;
216
223
  issuesUrl: string | undefined;
217
224
  license: string | undefined;
@@ -228,7 +235,6 @@ export declare function getReadmeMetadata(): Promise<{
228
235
  repositoryUrl: string | undefined;
229
236
  runtimePlatform: string[] | undefined;
230
237
  usesGitLfs: boolean;
231
- usesPnpm: boolean;
232
238
  }>;
233
239
  /**
234
240
  * Reset all cached metadata. Call between tests or when the underlying project
package/dist/lib/index.js CHANGED
@@ -1,3 +1,3 @@
1
- import { _ as getReadmeMetadata, a as expand, b as setLogger, c as stripString, g as getContextMetadata, h as mergeConfig, i as collapseString, l as createReadme, m as loadConfig, n as checkString, o as expandString, p as defineConfig, r as collapse, s as strip, t as check, u as createReadmeInteractive, v as resetMetadataCaches } from "../lib-BEZJR3k_.js";
1
+ import { _ as getReadmeMetadata, a as expand, b as setLogger, c as stripString, g as getContextMetadata, h as mergeConfig, i as collapseString, l as createReadme, m as loadConfig, n as checkString, o as expandString, p as defineConfig, r as collapse, s as strip, t as check, u as createReadmeInteractive, v as resetMetadataCaches } from "../lib-D8QaJZ-l.js";
2
2
 
3
3
  export { check, checkString, collapse, collapseString, createReadme as create, createReadmeInteractive as createInteractive, defineConfig, expand, expandString, getContextMetadata, getReadmeMetadata, loadConfig, mergeConfig, resetMetadataCaches, setLogger, strip, stripString };
@@ -9,7 +9,7 @@ import { TypeScriptLoader } from "cosmiconfig-typescript-loader";
9
9
  import fs from "node:fs/promises";
10
10
  import path from "node:path";
11
11
  import plur from "plur";
12
- import { deepmerge } from "deepmerge-ts";
12
+ import { deepmergeCustom } from "deepmerge-ts";
13
13
  import { defineTemplate, getMetadata, helpers, setLogger as setLogger$1, templates } from "metascope";
14
14
  import { z } from "zod";
15
15
  import { globby } from "globby";
@@ -25,10 +25,11 @@ import { confirm, group, intro, note, outro, select } from "@clack/prompts";
25
25
 
26
26
  //#region package.json
27
27
  var name = "mdat";
28
- var version = "3.2.2";
28
+ var version = "3.4.0";
29
29
 
30
30
  //#endregion
31
31
  //#region src/lib/deep-merge-defined.ts
32
+ const deepmerge = deepmergeCustom({ mergeArrays: false });
32
33
  function stripUndefinedDeep(object) {
33
34
  if (Array.isArray(object)) return object.map((v) => v !== null && typeof v === "object" ? stripUndefinedDeep(v) : v).filter((v) => v !== void 0);
34
35
  return Object.entries(object).map(([k, v]) => [k, v !== null && typeof v === "object" ? stripUndefinedDeep(v) : v]).reduce((acc, [k, v]) => v === void 0 ? acc : {
@@ -139,6 +140,57 @@ const GIT_PREFIX_REGEX = /^git\+/v;
139
140
  const GIT_SUFFIX_REGEX = /\.git$/v;
140
141
  const TRAILING_SLASH_REGEX = /\/$/v;
141
142
  const PACKAGE_MANAGER_REGEX = /^(?<name>[^@]+)@(?<version>[^+]+)/v;
143
+ const BREWPUB_TAP_REGEX = /\bbrewpub\b[^&;]*?--tap[ =](?<tap>[\w.\-\/]+)/v;
144
+ const BREWPUB_NAME_REGEX = /\bbrewpub\b[^&;]*?--name[ =](?<name>[\w.\-\/@+]+)/v;
145
+ const HOMEBREW_TAP_PREFIX_REGEX = /^homebrew-/v;
146
+ /**
147
+ * True if the package.json `exports` field declares a `types` condition
148
+ * anywhere in its (possibly nested) structure.
149
+ */
150
+ function containsTypesKey(exports) {
151
+ return typeof exports === "object" && exports !== null && Object.entries(exports).some(([key, value]) => key === "types" || containsTypesKey(value));
152
+ }
153
+ /**
154
+ * True if the package is importable as a library, as opposed to a CLI-only
155
+ * package that only exposes a `bin`.
156
+ */
157
+ function isLibraryPackage(nodePackage) {
158
+ return nodePackage !== void 0 && [
159
+ nodePackage.exports,
160
+ nodePackage.main,
161
+ nodePackage.module,
162
+ nodePackage.types
163
+ ].some((entry) => entry !== void 0);
164
+ }
165
+ /**
166
+ * Supported operating systems from the package.json `os` field, which codemeta
167
+ * doesn't carry. Exclusions like `!win32` are ignored since they don't name a
168
+ * supported platform.
169
+ */
170
+ function getSupportedOperatingSystems(os) {
171
+ const supported = helpers.ensureArray(os).filter((entry) => !entry.startsWith("!"));
172
+ return supported.length > 0 ? supported : void 0;
173
+ }
174
+ /**
175
+ * Derive the `brew install` path for a package published to a Homebrew tap with
176
+ * [brewpub](https://github.com/kitschpatrol/brewpub), by finding the `brewpub
177
+ * --tap <owner>/<tap>` invocation in the package.json scripts. Mirrors
178
+ * brewpub's formula naming: the `--name` option if passed, otherwise the
179
+ * package name without its scope.
180
+ *
181
+ * @returns E.g. `kitschpatrol/tap/mdat`, or undefined if brewpub isn't used
182
+ */
183
+ function getHomebrewFormula(packageName, scripts) {
184
+ if (packageName === void 0 || scripts === void 0) return;
185
+ for (const script of Object.values(scripts)) {
186
+ const tap = BREWPUB_TAP_REGEX.exec(script ?? "")?.groups?.tap;
187
+ if (tap === void 0) continue;
188
+ const [owner, tapName] = tap.split("/", 2);
189
+ if (owner === void 0 || tapName === void 0) continue;
190
+ const formula = BREWPUB_NAME_REGEX.exec(script ?? "")?.groups?.name ?? packageName.slice(packageName.indexOf("/") + 1).toLowerCase();
191
+ return `${owner}/${tapName.replace(HOMEBREW_TAP_PREFIX_REGEX, "")}/${formula}`;
192
+ }
193
+ }
142
194
  /**
143
195
  * Reset cached context metadata. Call between tests or when the underlying
144
196
  * project files may have changed on disk.
@@ -203,6 +255,7 @@ const readmeMetadataTemplate = defineTemplate((context) => {
203
255
  runtimes: runtimes.length > 0 ? runtimes : void 0
204
256
  };
205
257
  })();
258
+ const homebrewFormula = getHomebrewFormula(nodePackage?.name, nodePackage?.scripts);
206
259
  const firstAuthor = helpers.firstOf(helpers.ensureArray(codemeta.author));
207
260
  return {
208
261
  author: helpers.firstOf(helpers.mixedStringsToArray(helpers.toBasicNames(codemeta.author))),
@@ -212,19 +265,22 @@ const readmeMetadataTemplate = defineTemplate((context) => {
212
265
  description: codemeta.description,
213
266
  developmentDependencies,
214
267
  engines,
215
- isPublicNpmPackage: nodePackage?.private !== true && (nodePackage?.name.startsWith("@") && nodePackage.publishConfig?.access === "public" || true),
268
+ hasTypes: nodePackage?.types !== void 0 || containsTypesKey(nodePackage?.exports),
269
+ homebrewFormula,
270
+ isLibrary: isLibraryPackage(nodePackage),
271
+ isNodePackage: nodePackage !== void 0,
272
+ isPublicNpmPackage: nodePackage !== void 0 && nodePackage.private !== true,
216
273
  issuesUrl: codemeta.issueTracker,
217
274
  license: helpers.toBasicLicense(helpers.firstOf(helpers.ensureArray(codemeta.license))),
218
275
  licenseFilePath: licenseFileData?.source,
219
276
  licenseUrl: licenseFileData?.data.match?.spdxUrl,
220
277
  name: codemeta.name,
221
- operatingSystem: codemeta.operatingSystem,
278
+ operatingSystem: codemeta.operatingSystem ?? getSupportedOperatingSystems(nodePackage?.os),
222
279
  peerDependencies,
223
280
  projectDirectory: metascope?.data.options.path === void 0 ? void 0 : `file://${metascope.data.options.path}`,
224
281
  repositoryUrl: repoUrl,
225
282
  runtimePlatform: codemeta.runtimePlatform,
226
- usesGitLfs: helpers.firstOf(gitStats)?.data.hasLfs === true,
227
- usesPnpm: helpers.usesPnpm(nodePackageJson)
283
+ usesGitLfs: helpers.firstOf(gitStats)?.data.hasLfs === true
228
284
  };
229
285
  });
230
286
  let readmeMetadata;
@@ -359,13 +415,30 @@ var code_default = { code: { async content(options) {
359
415
  return `\`\`\`${lang}\n${validOptions.trim ? exampleCode.trim() : exampleCode}\n\`\`\``;
360
416
  } } };
361
417
 
418
+ //#endregion
419
+ //#region src/lib/readme/rules/utilities/heading.ts
420
+ /**
421
+ * Option schema for the Markdown heading level (1-6) at which a rule emits its
422
+ * section heading. Sub-headings, if any, are nested one level deeper. Rules
423
+ * default to level 3, matching their placement under a level 2 section like
424
+ * "Getting started" in the house readme template.
425
+ */
426
+ const headingLevelSchema = z.number().int().min(1).max(6).optional();
427
+ /**
428
+ * The `#` prefix for a Markdown heading, clamped to the maximum level of 6.
429
+ */
430
+ function getHeadingPrefix(level) {
431
+ return "#".repeat(Math.min(level, 6));
432
+ }
433
+
362
434
  //#endregion
363
435
  //#region src/lib/readme/rules/contributing.ts
364
- var contributing_default = { contributing: { async content() {
436
+ var contributing_default = { contributing: { async content(options) {
437
+ const validOptions = z.object({ headingLevel: headingLevelSchema }).optional().parse(options);
365
438
  const { issuesUrl } = await getReadmeMetadata();
366
439
  if (issuesUrl === void 0) throw new Error("Could not find \"bugs.url\" entry in package.json");
367
440
  return [
368
- "## Contributing",
441
+ `${getHeadingPrefix(validOptions?.headingLevel ?? 2)} Contributing`,
369
442
  "",
370
443
  `[Issues](${issuesUrl}) are welcome and appreciated.`,
371
444
  "",
@@ -375,6 +448,37 @@ var contributing_default = { contributing: { async content() {
375
448
  ].join("\n");
376
449
  } } };
377
450
 
451
+ //#endregion
452
+ //#region src/lib/readme/rules/utilities/version-range.ts
453
+ const BARE_VERSION_REGEX = /^v?\d+(?:\.\d+){0,2}$/v;
454
+ const MINIMUM_ONLY_REGEX = /^>=\s*v?(?<version>\d+(?:\.\d+){0,2})$/v;
455
+ const LOWER_BOUND_REGEX = /(?:^|[\s\|\(^~]|>=)\s*v?(?<version>\d+(?:\.\d+){0,2})/gv;
456
+ function compareVersions(a, b) {
457
+ const partsA = a.split(".").map(Number);
458
+ const partsB = b.split(".").map(Number);
459
+ for (let index = 0; index < Math.max(partsA.length, partsB.length); index++) {
460
+ const difference = (partsA[index] ?? 0) - (partsB[index] ?? 0);
461
+ if (difference !== 0) return difference;
462
+ }
463
+ return 0;
464
+ }
465
+ /**
466
+ * Describe a semver range in plain language for readme prose.
467
+ *
468
+ * @example
469
+ * `>=24.16.0` → `24.16.0 or newer`
470
+ * `^24.16.0 || >=26.3.0` → `24.16.0 or newer (specifically \`^24.16.0 || >=26.3.0\`)`
471
+ * `3.13` → `3.13`
472
+ */
473
+ function describeVersionRange(range) {
474
+ const trimmed = range.trim();
475
+ if (BARE_VERSION_REGEX.test(trimmed)) return trimmed;
476
+ const minimumOnly = MINIMUM_ONLY_REGEX.exec(trimmed)?.groups?.version;
477
+ if (minimumOnly !== void 0) return `${minimumOnly} or newer`;
478
+ const floor = trimmed.matchAll(LOWER_BOUND_REGEX).map((match) => match.groups?.version).filter((version) => version !== void 0).toArray().toSorted(compareVersions)[0];
479
+ return floor === void 0 ? `\`${trimmed}\`` : `${floor} or newer (specifically \`${trimmed}\`)`;
480
+ }
481
+
378
482
  //#endregion
379
483
  //#region src/lib/readme/rules/dependencies.ts
380
484
  const PLATFORM_INFO = {
@@ -396,7 +500,7 @@ const PLATFORM_INFO = {
396
500
  },
397
501
  node: {
398
502
  display: "Node.js",
399
- url: "https://nodejs.org/en"
503
+ url: "https://nodejs.org/"
400
504
  },
401
505
  python: {
402
506
  display: "Python",
@@ -411,38 +515,46 @@ const PLATFORM_INFO = {
411
515
  url: "https://www.rust-lang.org/"
412
516
  }
413
517
  };
414
- var dependencies_default = { dependencies: { async content() {
518
+ const OPERATING_SYSTEM_NAMES = {
519
+ darwin: "macOS",
520
+ linux: "Linux",
521
+ win32: "Windows"
522
+ };
523
+ function getPlatformDisplay(key) {
524
+ const info = PLATFORM_INFO[key.toLowerCase()];
525
+ return info ? `[${info.display}](${info.url})` : key;
526
+ }
527
+ function getPlatformItem(key, version) {
528
+ const display = getPlatformDisplay(key);
529
+ return version === void 0 || version.trim() === "" ? `- ${display}` : `- ${display} ${describeVersionRange(version)}`;
530
+ }
531
+ var dependencies_default = { dependencies: { async content(options) {
532
+ const validOptions = z.object({ headingLevel: headingLevelSchema }).optional().parse(options);
415
533
  const { engines, operatingSystem, peerDependencies, runtimePlatform } = await getReadmeMetadata();
416
- const platformItems = [];
417
- if (engines !== void 0) for (const [name, version] of Object.entries(engines)) {
418
- const info = PLATFORM_INFO[name.toLowerCase()];
419
- const display = info ? `[${info.display}](${info.url})` : name;
420
- platformItems.push(`- ${display} ${version}`);
421
- }
422
- if (runtimePlatform !== void 0) for (const entry of runtimePlatform) {
423
- const spaceIndex = entry.indexOf(" ");
424
- const platformKey = spaceIndex > 0 ? entry.slice(0, spaceIndex) : entry;
425
- const version = spaceIndex > 0 ? entry.slice(spaceIndex + 1) : void 0;
534
+ const items = [];
535
+ const engineEntries = Object.entries(engines ?? {});
536
+ const runtimePlatformEntries = runtimePlatform ?? [];
537
+ const peerDependencyEntries = (peerDependencies ?? []).filter(({ optional }) => !optional);
538
+ for (const [name, version] of engineEntries) items.push(getPlatformItem(name, version));
539
+ for (const entry of runtimePlatformEntries) {
540
+ const [platformKey = entry, ...versionParts] = entry.split(" ");
426
541
  if (engines?.[platformKey] !== void 0) continue;
427
- const info = PLATFORM_INFO[platformKey.toLowerCase()];
428
- const display = info ? `[${info.display}](${info.url})` : platformKey;
429
- platformItems.push(version === void 0 || version === "" ? `- ${display}` : `- ${display} ${version}`);
542
+ items.push(getPlatformItem(platformKey, versionParts.join(" ")));
430
543
  }
431
- if (operatingSystem !== void 0 && operatingSystem.length > 0) platformItems.push(`- Supported platforms: ${operatingSystem.join(", ")}`);
432
- const peerItems = [];
433
- if (peerDependencies !== void 0) for (const { name, optional, version } of peerDependencies) {
544
+ if (operatingSystem !== void 0 && operatingSystem.length > 0) {
545
+ const names = operatingSystem.map((os) => OPERATING_SYSTEM_NAMES[os] ?? os);
546
+ items.push(`- Supported operating systems: ${names.join(", ")}`);
547
+ }
548
+ for (const { name, version } of peerDependencyEntries) {
434
549
  const npmUrl = `https://www.npmjs.com/package/${name}`;
435
- const optionalSuffix = optional ? " _(optional)_" : "";
436
- peerItems.push(`- [${name}](${npmUrl}) ${version}${optionalSuffix}`);
550
+ items.push(`- [${name}](${npmUrl}) \`${version}\` _(peer dependency)_`);
437
551
  }
438
- const hasPlatform = platformItems.length > 0;
439
- const hasPeers = peerItems.length > 0;
440
- if (!hasPlatform && !hasPeers) return "";
441
- const sections = ["## Dependencies"];
442
- if (hasPlatform && hasPeers) sections.push("", "### Platform", "", ...platformItems, "", "### Peer Dependencies", "", ...peerItems);
443
- else if (hasPlatform) sections.push("", ...platformItems);
444
- else sections.push("", ...peerItems);
445
- return sections.join("\n");
552
+ if (items.length === 0) return "";
553
+ return [
554
+ `${getHeadingPrefix(validOptions?.headingLevel ?? 3)} Dependencies`,
555
+ "",
556
+ ...items
557
+ ].join("\n");
446
558
  } } };
447
559
 
448
560
  //#endregion
@@ -469,7 +581,7 @@ const TOOL_INFO = {
469
581
  },
470
582
  node: {
471
583
  display: "Node.js",
472
- url: "https://nodejs.org/en"
584
+ url: "https://nodejs.org/"
473
585
  },
474
586
  npm: {
475
587
  display: "npm",
@@ -484,7 +596,8 @@ const TOOL_INFO = {
484
596
  url: "https://yarnpkg.com/"
485
597
  }
486
598
  };
487
- var development_dependencies_default = { "development-dependencies": { async content() {
599
+ var development_dependencies_default = { "development-dependencies": { async content(options) {
600
+ const validOptions = z.object({ headingLevel: headingLevelSchema }).optional().parse(options);
488
601
  const { developmentDependencies } = await getReadmeMetadata();
489
602
  if (developmentDependencies === void 0) return "";
490
603
  const { packageManagers, runtimes } = developmentDependencies;
@@ -495,7 +608,7 @@ var development_dependencies_default = { "development-dependencies": { async con
495
608
  items.push(version === void 0 || version === "" ? `- ${display}` : `- ${display} ${version}`);
496
609
  }
497
610
  return [
498
- "## Development Dependencies",
611
+ `${getHeadingPrefix(validOptions?.headingLevel ?? 3)} Development dependencies`,
499
612
  "",
500
613
  ...items
501
614
  ].join("\n");
@@ -510,11 +623,13 @@ var dev_dependencies_default = { "dev-dependencies": getSoleRule(development_dep
510
623
 
511
624
  //#endregion
512
625
  //#region src/lib/readme/rules/license.ts
513
- var license_default = { license: { async content() {
626
+ var license_default = { license: { async content(options) {
627
+ const validOptions = z.object({ headingLevel: headingLevelSchema }).optional().parse(options);
514
628
  const { author, authorUrl, license, licenseFilePath } = await getReadmeMetadata();
515
629
  if (author === void 0) throw new Error("Could not find author name in project");
516
630
  if (license === void 0 || licenseFilePath === void 0) throw new Error("Could not find license for project");
517
- return `## License\n[${license}](${licenseFilePath}) © ${authorUrl === void 0 ? author : `[${author}](${authorUrl})`}`;
631
+ const authorDisplay = authorUrl === void 0 ? author : `[${author}](${authorUrl})`;
632
+ return `${`${getHeadingPrefix(validOptions?.headingLevel ?? 2)} License`}\n[${license}](${licenseFilePath}) © ${authorDisplay}`;
518
633
  } } };
519
634
 
520
635
  //#endregion
@@ -564,21 +679,99 @@ var header_default = { header: {
564
679
 
565
680
  //#endregion
566
681
  //#region src/lib/readme/rules/install.ts
567
- var install_default = { install: { async content() {
568
- const { bin, engines, isPublicNpmPackage, name, runtimePlatform, usesPnpm } = await getReadmeMetadata();
682
+ const listFormatter = new Intl.ListFormat("en", {
683
+ style: "long",
684
+ type: "conjunction"
685
+ });
686
+ function codeBlock(command) {
687
+ return [
688
+ "```sh",
689
+ command,
690
+ "```"
691
+ ];
692
+ }
693
+ /**
694
+ * Install instructions for command-line use: run once with `npx`, or install
695
+ * globally with Homebrew (if published to a tap) or npm.
696
+ */
697
+ function getCliLines(name, homebrewFormula) {
698
+ return [
699
+ "Run it once without installing:",
700
+ "",
701
+ ...codeBlock(`npx ${name}`),
702
+ ...homebrewFormula === void 0 ? [] : [
703
+ "",
704
+ "Or install it globally with Homebrew:",
705
+ "",
706
+ ...codeBlock(`brew install ${homebrewFormula}`)
707
+ ],
708
+ "",
709
+ "Or install it globally with npm:",
710
+ "",
711
+ ...codeBlock(`npm install --global ${name}`)
712
+ ];
713
+ }
714
+ /**
715
+ * Install instructions for use as a project dependency.
716
+ */
717
+ function getLibraryLines(name, bin, hasTypes, dev) {
718
+ const lead = dev ? "Add it to your project as a development dependency" : "Add it to your project";
719
+ const command = `npm install ${dev ? "--save-dev " : ""}${name}`;
720
+ if (bin === void 0 || bin.length === 0) return [
721
+ `${lead}:`,
722
+ "",
723
+ ...codeBlock(command)
724
+ ];
725
+ return [
726
+ `${lead} to import the ${hasTypes ? "TypeScript API" : "API"}. This also puts the ${listFormatter.format(bin.map((binName) => `\`${binName}\``))} ${bin.length === 1 ? "CLI" : "CLIs"} on your project's path:`,
727
+ "",
728
+ ...codeBlock(command)
729
+ ];
730
+ }
731
+ var install_default = { install: { async content(options) {
732
+ const validOptions = z.object({
733
+ dev: z.boolean().optional(),
734
+ headingLevel: headingLevelSchema,
735
+ homebrew: z.union([z.string(), z.literal(false)]).optional()
736
+ }).optional().parse(options);
737
+ const { bin, hasTypes, homebrewFormula, isLibrary, isNodePackage, isPublicNpmPackage, name, runtimePlatform } = await getReadmeMetadata();
569
738
  if (name === void 0) throw new Error("Could not find project name");
570
- const lines = ["## Install"];
571
- if (isPublicNpmPackage || engines?.node !== void 0 || runtimePlatform?.includes("node")) {
572
- const pmAdd = usesPnpm ? "pnpm add" : "npm install";
573
- const pmx = usesPnpm ? "pnpx" : "npx";
574
- lines.push("", "```sh", `${pmAdd} ${name}`, "```");
575
- if (bin !== void 0 && bin.length > 0) lines.push("", "Or run it directly:", "", "```sh", `${pmx} ${name}`, "```");
576
- } else if (runtimePlatform?.some((p) => p.startsWith("python"))) lines.push("", "```sh", `pip install ${name}`, "```");
577
- else if (runtimePlatform?.some((p) => p.startsWith("rust"))) lines.push("", "```sh", `cargo install ${name}`, "```");
578
- else if (runtimePlatform?.some((p) => p.startsWith("go"))) lines.push("", "```sh", `go install ${name}@latest`, "```");
579
- else if (runtimePlatform?.some((p) => p.startsWith("ruby"))) lines.push("", "```sh", `gem install ${name}`, "```");
580
- else throw new Error("Could not determine project ecosystem for install instructions");
581
- return lines.join("\n");
739
+ const headingLevel = validOptions?.headingLevel ?? 3;
740
+ const heading = `${getHeadingPrefix(headingLevel)} Installation`;
741
+ const subheading = getHeadingPrefix(headingLevel + 1);
742
+ if (isNodePackage) {
743
+ if (!isPublicNpmPackage) throw new Error("Install instructions require a publishable package, but package.json sets \"private\": true");
744
+ const homebrew = validOptions?.homebrew === void 0 ? homebrewFormula : validOptions.homebrew === false ? void 0 : validOptions.homebrew;
745
+ const hasCli = bin !== void 0 && bin.length > 0;
746
+ const cliLines = getCliLines(name, homebrew);
747
+ const libraryLines = getLibraryLines(name, bin, hasTypes, validOptions?.dev ?? false);
748
+ if (hasCli && isLibrary) return [
749
+ heading,
750
+ "",
751
+ "Pick the option that matches how you plan to use it.",
752
+ "",
753
+ `${subheading} CLI`,
754
+ "",
755
+ ...cliLines,
756
+ "",
757
+ `${subheading} Library`,
758
+ "",
759
+ ...libraryLines
760
+ ].join("\n");
761
+ return [
762
+ heading,
763
+ "",
764
+ ...hasCli ? cliLines : libraryLines
765
+ ].join("\n");
766
+ }
767
+ const platform = (prefix) => runtimePlatform?.some((p) => p.startsWith(prefix));
768
+ const command = platform("python") ? `pip install ${name}` : platform("rust") ? `cargo install ${name}` : platform("go") ? `go install ${name}@latest` : platform("ruby") ? `gem install ${name}` : void 0;
769
+ if (command === void 0) throw new Error("Could not determine project ecosystem for install instructions");
770
+ return [
771
+ heading,
772
+ "",
773
+ ...codeBlock(command)
774
+ ].join("\n");
582
775
  } } };
583
776
 
584
777
  //#endregion
@@ -710,21 +903,24 @@ var size_table_default = { "size-table": { async content(options) {
710
903
  //#region src/lib/readme/rules/table-of-contents.ts
711
904
  var table_of_contents_default = { "table-of-contents": {
712
905
  async content(options, { tree }) {
713
- const validOptions = z.object({ depth: z.union([
714
- z.literal(1),
715
- z.literal(2),
716
- z.literal(3),
717
- z.literal(4),
718
- z.literal(5),
719
- z.literal(6)
720
- ]).optional() }).optional().parse(options);
906
+ const validOptions = z.object({
907
+ depth: z.union([
908
+ z.literal(1),
909
+ z.literal(2),
910
+ z.literal(3),
911
+ z.literal(4),
912
+ z.literal(5),
913
+ z.literal(6)
914
+ ]).optional(),
915
+ headingLevel: headingLevelSchema
916
+ }).optional().parse(options);
721
917
  const result = toc(tree, {
722
918
  heading: null,
723
919
  maxDepth: validOptions?.depth ?? 3,
724
920
  tight: true
725
921
  });
726
922
  if (result.map === void 0) throw new Error("Could not generate table of contents");
727
- const heading = `## Table of contents`;
923
+ const heading = `${getHeadingPrefix(validOptions?.headingLevel ?? 2)} Table of contents`;
728
924
  const rootWrapper = {
729
925
  children: result.map.children,
730
926
  type: "root"
@@ -1034,11 +1230,11 @@ function getStripProcessor(_config, ambientRemarkConfig) {
1034
1230
 
1035
1231
  //#endregion
1036
1232
  //#region src/lib/readme/templates/mdat-readme-compound.md?raw
1037
- var mdat_readme_compound_default = "<!-- header -->\n\n<!-- table-of-contents -->\n\n## Overview\n\n## Getting started\n\n### Dependencies\n\n### Installation\n\n## Usage\n\n### Library\n\n#### API\n\n#### Examples\n\n### CLI\n\n#### Commands\n\n#### Examples\n\n## Background\n\n### Motivation\n\n### Implementation notes\n\n### Similar projects\n\n## The future\n\n## Maintainers\n\n_List maintainer(s) for a repository, along with one way of contacting them (e.g. GitHub link or email)._\n\n## Acknowledgments\n\n_State anyone or anything that significantly helped with the development of your project. State public contact hyper-links if applicable._\n\n<!-- footer -->\n";
1233
+ var mdat_readme_compound_default = "<!-- header -->\n\n<!-- table-of-contents -->\n\n## Overview\n\n## Getting started\n\n<!-- dependencies -->\n\n<!-- install -->\n\n## Usage\n\n### Library\n\n#### API\n\n#### Examples\n\n### CLI\n\n#### Commands\n\n#### Examples\n\n## Background\n\n### Motivation\n\n### Implementation notes\n\n### Similar projects\n\n## The future\n\n## Maintainers\n\n_List maintainer(s) for a repository, along with one way of contacting them (e.g. GitHub link or email)._\n\n## Acknowledgments\n\n_State anyone or anything that significantly helped with the development of your project. State public contact hyper-links if applicable._\n\n<!-- footer -->\n";
1038
1234
 
1039
1235
  //#endregion
1040
1236
  //#region src/lib/readme/templates/mdat-readme.md?raw
1041
- var mdat_readme_default = "<!-- title -->\n\n<!-- banner -->\n\n<!-- badges -->\n\n<!-- short-description -->\n\n<!-- table-of-contents -->\n\n## Overview\n\n## Getting started\n\n### Dependencies\n\n### Installation\n\n## Usage\n\n### Library\n\n#### API\n\n#### Examples\n\n### CLI\n\n#### Commands\n\n#### Examples\n\n## Background\n\n### Motivation\n\n### Implementation notes\n\n### Similar projects\n\n## The future\n\n## Maintainers\n\n_List maintainer(s) for a repository, along with one way of contacting them (e.g. GitHub link or email)._\n\n## Acknowledgments\n\n_State anyone or anything that significantly helped with the development of your project. State public contact hyper-links if applicable._\n\n<!-- contributing -->\n\n<!-- license -->\n";
1237
+ var mdat_readme_default = "<!-- title -->\n\n<!-- banner -->\n\n<!-- badges -->\n\n<!-- short-description -->\n\n<!-- table-of-contents -->\n\n## Overview\n\n## Getting started\n\n<!-- dependencies -->\n\n<!-- install -->\n\n## Usage\n\n### Library\n\n#### API\n\n#### Examples\n\n### CLI\n\n#### Commands\n\n#### Examples\n\n## Background\n\n### Motivation\n\n### Implementation notes\n\n### Similar projects\n\n## The future\n\n## Maintainers\n\n_List maintainer(s) for a repository, along with one way of contacting them (e.g. GitHub link or email)._\n\n## Acknowledgments\n\n_State anyone or anything that significantly helped with the development of your project. State public contact hyper-links if applicable._\n\n<!-- contributing -->\n\n<!-- license -->\n";
1042
1238
 
1043
1239
  //#endregion
1044
1240
  //#region src/lib/readme/templates/standard-readme-basic-compound.md?raw
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdat",
3
- "version": "3.2.2",
3
+ "version": "3.4.0",
4
4
  "description": "CLI tool and TypeScript library implementing the Markdown Autophagic Template (MDAT) system. MDAT lets you use comments as dynamic content templates in Markdown files, making it easy to generate and update readme boilerplate.",
5
5
  "keywords": [
6
6
  "mdat",
@@ -58,7 +58,7 @@
58
58
  "pretty-ms": "^9.3.1",
59
59
  "remark": "^15.0.1",
60
60
  "remark-gfm": "^4.0.1",
61
- "remark-mdat": "^3.0.2",
61
+ "remark-mdat": "^3.1.0",
62
62
  "to-vfile": "^8.0.0",
63
63
  "type-fest": "^5.10.0",
64
64
  "unified-engine": "^11.2.2",
@@ -71,7 +71,7 @@
71
71
  "@arethetypeswrong/core": "^0.18.5",
72
72
  "@kitschpatrol/shared-config": "^8.10.0",
73
73
  "@types/mdast": "^4.0.4",
74
- "@types/node": "~24.13.4",
74
+ "@types/node": "~24.13.6",
75
75
  "@types/unist": "^3.0.3",
76
76
  "@types/yargs": "^17.0.35",
77
77
  "@vitest/coverage-v8": "^5.0.1",
@@ -99,7 +99,7 @@
99
99
  }
100
100
  },
101
101
  "engines": {
102
- "node": ">=24.16.0"
102
+ "node": "^24.16.0 || >=26.3.0"
103
103
  },
104
104
  "scripts": {
105
105
  "bench": "vitest bench --run --reporter=default",
package/readme.md CHANGED
@@ -6,9 +6,10 @@
6
6
 
7
7
  <!-- badges({
8
8
  custom: {
9
- "Bundle Size": {
10
- image: "https://img.shields.io/bundlephobia/minzip/mdat?label=Size",
11
- link: "https://bundlephobia.com/package/mdat",
9
+ "Homebrew": {
10
+ image:
11
+ "https://img.shields.io/badge/Homebrew-kitschpatrol%2Ftap%2Fmdat-FBB040?logo=homebrew&logoColor=white",
12
+ link: "https://github.com/kitschpatrol/homebrew-tap/blob/main/Formula/mdat.rb",
12
13
  },
13
14
  }
14
15
  }) -->
@@ -16,7 +17,7 @@
16
17
  [![NPM Package mdat](https://img.shields.io/npm/v/mdat.svg)](https://www.npmjs.com/package/mdat)
17
18
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/license/mit)
18
19
  [![CI](https://github.com/kitschpatrol/mdat/actions/workflows/ci.yml/badge.svg)](https://github.com/kitschpatrol/mdat/actions/workflows/ci.yml)
19
- [![Bundle Size](https://img.shields.io/bundlephobia/minzip/mdat?label=Size)](https://bundlephobia.com/package/mdat)
20
+ [![Homebrew](https://img.shields.io/badge/Homebrew-kitschpatrol%2Ftap%2Fmdat-FBB040?logo=homebrew&logoColor=white)](https://github.com/kitschpatrol/homebrew-tap/blob/main/Formula/mdat.rb)
20
21
 
21
22
  <!-- /badges -->
22
23
 
@@ -36,8 +37,8 @@
36
37
  - [Installation](#installation)
37
38
  - [Features](#features)
38
39
  - [Usage](#usage)
39
- - [CLI](#cli)
40
- - [API](#api)
40
+ - [CLI](#cli-1)
41
+ - [Library](#library-1)
41
42
  - [Configuration](#configuration)
42
43
  - [Creating custom rules](#creating-custom-rules)
43
44
  - [Bundled rules](#bundled-rules)
@@ -109,30 +110,52 @@ The `<!-- title -->` comment was expanded with content derived from your project
109
110
 
110
111
  ## Getting started
111
112
 
113
+ <!-- dependencies -->
114
+
112
115
  ### Dependencies
113
116
 
114
- Node 24+ (specifically `>=24.16.0`). Written in TypeScript with bundled type definitions.
117
+ - [Node.js](https://nodejs.org/) 24.16.0 or newer (specifically `^24.16.0 || >=26.3.0`)
118
+
119
+ <!-- /dependencies -->
120
+
121
+ Written in TypeScript with bundled type definitions.
122
+
123
+ <!-- install -->
115
124
 
116
125
  ### Installation
117
126
 
118
- Install locally to access the CLI and API in a single project:
127
+ Pick the option that matches how you plan to use it.
128
+
129
+ #### CLI
130
+
131
+ Run it once without installing:
119
132
 
120
133
  ```sh
121
- pnpm install mdat
134
+ npx mdat
122
135
  ```
123
136
 
124
- Or install globally:
137
+ Or install it globally with Homebrew:
125
138
 
126
139
  ```sh
127
- pnpm install --global mdat
140
+ brew install kitschpatrol/tap/mdat
128
141
  ```
129
142
 
130
- The CLI tool is also available on Homebrew:
143
+ Or install it globally with npm:
131
144
 
132
145
  ```sh
133
- brew install kitschpatrol/tap/mdat
146
+ npm install --global mdat
147
+ ```
148
+
149
+ #### Library
150
+
151
+ Add it to your project to import the TypeScript API. This also puts the `mdat` CLI on your project's path:
152
+
153
+ ```sh
154
+ npm install mdat
134
155
  ```
135
156
 
157
+ <!-- /install -->
158
+
136
159
  ## Features
137
160
 
138
161
  1. **Minimalist syntax**
@@ -423,7 +446,7 @@ mdat --format
423
446
  mdat create
424
447
  ```
425
448
 
426
- ### API
449
+ ### Library
427
450
 
428
451
  `mdat` exports functions for expanding, collapsing, checking, and creating Markdown files programmatically.
429
452
 
@@ -566,6 +589,8 @@ See the [Examples section](https://github.com/kitschpatrol/remark-mdat#examples)
566
589
 
567
590
  ### Bundled rules
568
591
 
592
+ Every rule that emits a section heading accepts a `headingLevel` option (`1`-`6`) to control the heading's Markdown level, with any sub-headings nested one level deeper. The defaults match each rule's placement in the bundled MDAT Readme template: `2` for top-level sections (`table-of-contents`, `contributing`, and `license`), and `3` for the rules that sit under "Getting started" (`dependencies`, `install`, and `development-dependencies`).
593
+
569
594
  #### Stand-alone
570
595
 
571
596
  - ##### `<!-- title -->`
@@ -586,19 +611,23 @@ See the [Examples section](https://github.com/kitschpatrol/remark-mdat#examples)
586
611
 
587
612
  - ##### `<!-- install -->`
588
613
 
589
- Ecosystem-aware install instructions derived from project metadata. Emits `pnpm add` / `npm install` for Node packages (plus a `pnpx` / `npx` hint when the project exposes a binary), and falls back to `pip`, `cargo`, `gem`, or `go install` for Python, Rust, Ruby, and Go projects.
614
+ Install instructions derived from `package.json`, written for people consuming the package rather than developing it, so they always use `npm`. Packages with a `bin` get CLI instructions (`npx`, Homebrew, and `npm install --global`), packages with an entry point (`exports`, `main`, `module`, or `types`) get library instructions (`npm install`), and packages with both get a section for each. The Homebrew option appears automatically when a `brewpub --tap` invocation is found in the package scripts, and can be set explicitly with `homebrew: "owner/tap/formula"` or suppressed with `homebrew: false`. Pass `dev: true` to recommend `npm install --save-dev` for tooling packages. Private packages are rejected. Falls back to `pip`, `cargo`, `gem`, or `go install` for Python, Rust, Ruby, and Go projects. The optional `headingLevel` sets the Markdown heading level and defaults to `3`, matching the template's placement under a "Getting started" section:
615
+
616
+ ```md
617
+ <!-- install({ headingLevel: 2, dev: true, homebrew: false }) -->
618
+ ```
590
619
 
591
620
  - ##### `<!-- dependencies -->`
592
621
 
593
- Documents platform requirements and peer dependencies. Lists runtime platforms (Node, Python, Rust, Go, Ruby, etc.) with version constraints from `engines` or equivalent metadata, supported operating systems, and peer dependencies with links to npm.
622
+ Documents platform requirements and peer dependencies. Lists runtime platforms (Node, Python, Rust, Go, Ruby, etc.) with version constraints from `engines` or equivalent metadata, supported operating systems, and required peer dependencies with links to npm. Optional peer dependencies are left out. Version ranges are described in plain language (`>=24.16.0` becomes "24.16.0 or newer"), with the exact range included when it can't be summarized that simply. The optional `headingLevel` sets the Markdown heading level and defaults to `3`.
594
623
 
595
624
  - ##### `<!-- development-dependencies -->`
596
625
 
597
- Documents the tools needed to work on the project itself, derived from the `devEngines` and `packageManager` fields in `package.json`. Lists required runtimes and package managers with links and version constraints. Also aliased as `<!-- dev-dependencies -->`.
626
+ Documents the tools needed to work on the project itself, derived from the `devEngines` and `packageManager` fields in `package.json`. Lists required runtimes and package managers with links and version constraints. The optional `headingLevel` sets the Markdown heading level and defaults to `3`. Also aliased as `<!-- dev-dependencies -->`.
598
627
 
599
628
  - ##### `<!-- table-of-contents -->`
600
629
 
601
- Auto-generated via [mdast-util-toc](https://github.com/syntax-tree/mdast-util-toc). Also aliased as `<!-- toc -->`.
630
+ Auto-generated via [mdast-util-toc](https://github.com/syntax-tree/mdast-util-toc). The optional `depth` limits how deeply nested headings are listed and defaults to `3`. Also aliased as `<!-- toc -->`.
602
631
 
603
632
  - ##### `<!-- contributing -->`
604
633
 
@@ -631,7 +660,7 @@ See the [Examples section](https://github.com/kitschpatrol/remark-mdat#examples)
631
660
 
632
661
  #### Compound
633
662
 
634
- Compound rules combine several stand-alone rules under a single keyword.
663
+ Compound rules combine several stand-alone rules under a single keyword. Options are passed as an array with one entry per stand-alone rule, in order, e.g. `<!-- footer([{ headingLevel: 3 }, { headingLevel: 3 }]) -->`.
635
664
 
636
665
  - ##### `<!-- header -->`
637
666
 
@@ -668,7 +697,7 @@ Rule plugins are packages for sharing mdat expansion rules across projects.
668
697
  ### Installing a rule plugin
669
698
 
670
699
  ```sh
671
- pnpm install mdat-plugin-example
700
+ npm install mdat-plugin-example
672
701
  ```
673
702
 
674
703
  Spread the plugin into your configuration: