mdat 3.2.0 → 3.2.1

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.
@@ -0,0 +1,1298 @@
1
+ import { createLogger, getChildLogger, injectionHelper } from "lognow";
2
+ import picocolors from "picocolors";
3
+ import { getSoleRule, mdatCollapse, mdatDiff, mdatExpand, mdatSplit, mdatStrip, rulesSchema, setLogger } from "remark-mdat";
4
+ import { read, write } from "to-vfile";
5
+ import { remark } from "remark";
6
+ import remarkGfm from "remark-gfm";
7
+ import { cosmiconfig, defaultLoaders } from "cosmiconfig";
8
+ import { TypeScriptLoader } from "cosmiconfig-typescript-loader";
9
+ import fs from "node:fs/promises";
10
+ import path from "node:path";
11
+ import plur from "plur";
12
+ import { deepmerge } from "deepmerge-ts";
13
+ import { defineTemplate, getMetadata, helpers, setLogger as setLogger$1, templates } from "metascope";
14
+ import { z } from "zod";
15
+ import { globby } from "globby";
16
+ import { isFile, isFileSync } from "path-type";
17
+ import { promisify } from "node:util";
18
+ import { brotliCompress, gzip } from "node:zlib";
19
+ import prettyBytes from "pretty-bytes";
20
+ import { toc } from "mdast-util-toc";
21
+ import { VFile } from "vfile";
22
+ import { Configuration } from "unified-engine";
23
+ import untildify from "untildify";
24
+ import { confirm, group, intro, note, outro, select } from "@clack/prompts";
25
+
26
+ //#region package.json
27
+ var name = "mdat";
28
+ var version = "3.2.1";
29
+
30
+ //#endregion
31
+ //#region src/lib/deep-merge-defined.ts
32
+ function stripUndefinedDeep(object) {
33
+ if (Array.isArray(object)) return object.map((v) => v !== null && typeof v === "object" ? stripUndefinedDeep(v) : v).filter((v) => v !== void 0);
34
+ return Object.entries(object).map(([k, v]) => [k, v !== null && typeof v === "object" ? stripUndefinedDeep(v) : v]).reduce((acc, [k, v]) => v === void 0 ? acc : {
35
+ ...acc,
36
+ [k]: v
37
+ }, {});
38
+ }
39
+ function deepMergeDefined(...objects) {
40
+ const stripped = objects.map((v, i) => i === 0 ? v : stripUndefinedDeep(v));
41
+ return deepmerge(...stripped);
42
+ }
43
+
44
+ //#endregion
45
+ //#region src/lib/log.ts
46
+ /**
47
+ * The default logger instance for the library.
48
+ */
49
+ let log = createLogger({
50
+ logToConsole: { showTime: false },
51
+ name
52
+ });
53
+ setLogger(getChildLogger(log, "remark-mdat"));
54
+ setLogger$1(getChildLogger(log, "metascope"));
55
+ /**
56
+ * Set the logger instance for the module. Export this for library consumers to
57
+ * inject their own logger.
58
+ *
59
+ * @param logger - Accepts either a LogLayer instance or a Console- or
60
+ * Stream-like log target
61
+ */
62
+ function setLogger$2(logger) {
63
+ log = injectionHelper(logger);
64
+ setLogger(getChildLogger(log, "remark-mdat"));
65
+ setLogger$1(getChildLogger(log, "metascope"));
66
+ }
67
+
68
+ //#endregion
69
+ //#region src/lib/mdat-json-loader.ts
70
+ /**
71
+ * Lets arbitrary JSON objects (like from package.json) become reasonably good
72
+ * mdat rule sets HOWEVER cosmiconfig treats package.json as a special case and
73
+ * will always load only specific keys from it So we have to intercept and load
74
+ * them manually in config.ts
75
+ */
76
+ function mdatJsonLoader(filePath, content) {
77
+ const defaultJsonLoader = defaultLoaders[".json"];
78
+ return flattenJson(defaultJsonLoader(filePath, content));
79
+ }
80
+ function flattenJson(jsonObject, parentKey = "", result = {}) {
81
+ for (const [key, value] of Object.entries(jsonObject)) {
82
+ const fullPath = parentKey === "" ? key : `${parentKey}.${key}`;
83
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) flattenJson(value, fullPath, result);
84
+ else if (value === null) result[fullPath] = "null";
85
+ else result[fullPath] = value.toString();
86
+ }
87
+ return result;
88
+ }
89
+
90
+ //#endregion
91
+ //#region src/lib/context.ts
92
+ let metascopeMetadata;
93
+ /**
94
+ * Get a bunch of platform-agnostic local metadata via metascope, exposed
95
+ * primarily for plugin developers. Result is memoized the result.
96
+ *
97
+ * @throws {Error} If no package.json is found
98
+ */
99
+ async function getContextMetadata() {
100
+ if (metascopeMetadata !== void 0) return metascopeMetadata;
101
+ metascopeMetadata = await getMetadata({
102
+ absolute: false,
103
+ offline: true,
104
+ recursive: false,
105
+ sources: [
106
+ "arduinoLibraryProperties",
107
+ "cinderCinderblockXml",
108
+ "codemetaJson",
109
+ "gitConfig",
110
+ "githubActions",
111
+ "gitStats",
112
+ "goGoMod",
113
+ "goGoreleaserYaml",
114
+ "javaPomXml",
115
+ "licenseFile",
116
+ "metadataFile",
117
+ "metascope",
118
+ "nodePackageJson",
119
+ "obsidianPluginManifestJson",
120
+ "openframeworksAddonConfigMk",
121
+ "openframeworksInstallXml",
122
+ "processingLibraryProperties",
123
+ "processingSketchProperties",
124
+ "publiccodeYaml",
125
+ "pythonPkgInfo",
126
+ "pythonPyprojectToml",
127
+ "pythonSetupCfg",
128
+ "pythonSetupPy",
129
+ "readmeFile",
130
+ "rubyGemspec",
131
+ "rustCargoToml",
132
+ "xcodeInfoPlist",
133
+ "xcodeProjectPbxproj"
134
+ ]
135
+ });
136
+ return metascopeMetadata;
137
+ }
138
+ const GIT_PREFIX_REGEX = /^git\+/v;
139
+ const GIT_SUFFIX_REGEX = /\.git$/v;
140
+ const TRAILING_SLASH_REGEX = /\/$/v;
141
+ const PACKAGE_MANAGER_REGEX = /^(?<name>[^@]+)@(?<version>[^+]+)/v;
142
+ /**
143
+ * Reset cached context metadata. Call between tests or when the underlying
144
+ * project files may have changed on disk.
145
+ *
146
+ * @public
147
+ */
148
+ function resetContextMetadata() {
149
+ metascopeMetadata = void 0;
150
+ }
151
+ const readmeMetadataTemplate = defineTemplate((context) => {
152
+ const { githubActions, gitStats, licenseFile, metascope, nodePackageJson } = context;
153
+ const codemeta = templates.codemetaJson(context, {});
154
+ const nodePackage = helpers.firstOf(nodePackageJson)?.data;
155
+ const licenseFileData = helpers.firstOf(licenseFile);
156
+ const ciActionFilePath = helpers.ensureArray(githubActions).find((entry) => entry.data.name.toLowerCase() === "ci")?.source;
157
+ const repoUrl = codemeta.codeRepository?.replace(GIT_PREFIX_REGEX, "").replace(GIT_SUFFIX_REGEX, "").replace(TRAILING_SLASH_REGEX, "");
158
+ const bin = (() => {
159
+ if (nodePackage === void 0) return;
160
+ const binField = nodePackage.bin;
161
+ if (binField === void 0) return;
162
+ if (typeof binField === "string") return [nodePackage.name];
163
+ const names = Object.keys(binField);
164
+ return names.length > 0 ? names : void 0;
165
+ })();
166
+ const engines = (() => {
167
+ const raw = nodePackage?.engines;
168
+ if (raw === void 0) return;
169
+ const entries = Object.entries(raw).filter((entry) => entry[1] !== void 0);
170
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
171
+ })();
172
+ const peerDependencies = (() => {
173
+ if (nodePackage === void 0) return;
174
+ const peers = nodePackage.peerDependencies;
175
+ if (peers === void 0) return;
176
+ const entries = Object.entries(peers).filter((entry) => entry[1] !== void 0);
177
+ if (entries.length === 0) return;
178
+ const meta = nodePackage.peerDependenciesMeta;
179
+ return entries.map(([name, version]) => ({
180
+ name,
181
+ optional: meta?.[name]?.optional === true,
182
+ version
183
+ }));
184
+ })();
185
+ const developmentDependencies = (() => {
186
+ if (nodePackage === void 0) return;
187
+ const runtimes = helpers.ensureArray(nodePackage.devEngines?.runtime).map(({ name, version }) => ({
188
+ name,
189
+ version
190
+ }));
191
+ const packageManagers = helpers.ensureArray(nodePackage.devEngines?.packageManager).map(({ name, version }) => ({
192
+ name,
193
+ version
194
+ }));
195
+ const pinnedPackageManager = nodePackage.packageManager === void 0 ? void 0 : PACKAGE_MANAGER_REGEX.exec(nodePackage.packageManager)?.groups;
196
+ if (pinnedPackageManager?.name !== void 0 && pinnedPackageManager.version !== void 0 && packageManagers.every((entry) => entry.name !== pinnedPackageManager.name)) packageManagers.push({
197
+ name: pinnedPackageManager.name,
198
+ version: pinnedPackageManager.version
199
+ });
200
+ if (runtimes.length === 0 && packageManagers.length === 0) return;
201
+ return {
202
+ packageManagers: packageManagers.length > 0 ? packageManagers : void 0,
203
+ runtimes: runtimes.length > 0 ? runtimes : void 0
204
+ };
205
+ })();
206
+ const firstAuthor = helpers.firstOf(helpers.ensureArray(codemeta.author));
207
+ return {
208
+ author: helpers.firstOf(helpers.mixedStringsToArray(helpers.toBasicNames(codemeta.author))),
209
+ authorUrl: firstAuthor?.url,
210
+ bin,
211
+ ciActionFileName: ciActionFilePath === void 0 ? void 0 : path.basename(ciActionFilePath),
212
+ description: codemeta.description,
213
+ developmentDependencies,
214
+ engines,
215
+ isPublicNpmPackage: nodePackage?.private !== true && (nodePackage?.name.startsWith("@") && nodePackage.publishConfig?.access === "public" || true),
216
+ issuesUrl: codemeta.issueTracker,
217
+ license: helpers.toBasicLicense(helpers.firstOf(helpers.ensureArray(codemeta.license))),
218
+ licenseFilePath: licenseFileData?.source,
219
+ licenseUrl: licenseFileData?.data.match?.spdxUrl,
220
+ name: codemeta.name,
221
+ operatingSystem: codemeta.operatingSystem,
222
+ peerDependencies,
223
+ projectDirectory: metascope?.data.options.path === void 0 ? void 0 : `file://${metascope.data.options.path}`,
224
+ repositoryUrl: repoUrl,
225
+ runtimePlatform: codemeta.runtimePlatform,
226
+ usesGitLfs: helpers.firstOf(gitStats)?.data.hasLfs === true,
227
+ usesPnpm: helpers.usesPnpm(nodePackageJson)
228
+ };
229
+ });
230
+ let readmeMetadata;
231
+ /**
232
+ * Nice data for readme rules
233
+ *
234
+ * @public
235
+ */
236
+ async function getReadmeMetadata() {
237
+ if (readmeMetadata !== void 0) return readmeMetadata;
238
+ const contextMetadata = await getContextMetadata();
239
+ readmeMetadata = readmeMetadataTemplate(contextMetadata, {});
240
+ return readmeMetadata;
241
+ }
242
+ /**
243
+ * Reset cached readme metadata. Call between tests or when the underlying
244
+ * project files may have changed on disk.
245
+ *
246
+ * @public
247
+ */
248
+ function resetReadmeMetadata() {
249
+ readmeMetadata = void 0;
250
+ }
251
+ /**
252
+ * Reset all cached metadata. Call between tests or when the underlying project
253
+ * files may have changed on disk.
254
+ */
255
+ function resetMetadataCaches() {
256
+ resetContextMetadata();
257
+ resetReadmeMetadata();
258
+ }
259
+
260
+ //#endregion
261
+ //#region src/lib/readme/rules/badges.ts
262
+ var badges_default = { badges: { async content(options) {
263
+ const validOptions = z.object({
264
+ custom: z.record(z.string(), z.object({
265
+ image: z.string(),
266
+ link: z.string()
267
+ })).optional(),
268
+ npm: z.array(z.string()).optional()
269
+ }).optional().parse(options);
270
+ const metadata = await getReadmeMetadata();
271
+ const { ciActionFileName, license, licenseUrl, name, repositoryUrl, usesGitLfs } = metadata;
272
+ const badges = [];
273
+ if (validOptions?.npm === void 0) {
274
+ if (metadata.isPublicNpmPackage) badges.push(`[![NPM Package ${name}](https://img.shields.io/npm/v/${name}.svg)](https://www.npmjs.com/package/${name})`);
275
+ } 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})`);
276
+ if (license !== void 0 && licenseUrl !== void 0) badges.push(`[![License: ${license}](https://img.shields.io/badge/License-${license.replaceAll("-", "--")}-yellow.svg)](${licenseUrl})`);
277
+ if (ciActionFileName !== void 0 && repositoryUrl !== void 0) badges.push(`[![CI](${repositoryUrl}/actions/workflows/${ciActionFileName}/badge.svg)](${repositoryUrl}/actions/workflows/${ciActionFileName})`);
278
+ if (usesGitLfs) badges.push("![Git LFS](https://img.shields.io/badge/Git%20LFS-enabled-F64935?logo=gitlfs&logoColor=white)");
279
+ if (validOptions?.custom !== void 0) for (const [badgeName, { image, link }] of Object.entries(validOptions.custom)) badges.push(`[![${badgeName}](${image})](${link})`);
280
+ return badges.join("\n");
281
+ } } };
282
+
283
+ //#endregion
284
+ //#region src/lib/readme/rules/banner.ts
285
+ var banner_default = { banner: { async content(options) {
286
+ const validOptions = z.object({
287
+ alt: z.string().optional(),
288
+ src: z.string().optional()
289
+ }).optional().parse(options);
290
+ const src = validOptions?.src ?? await getBannerSrc();
291
+ if (src === void 0) throw new Error("Banner image not found at any typical location, consider adding something at ./assets/banner.webp");
292
+ if (!isUrl(src) && !await isFile(src)) throw new Error(`Banner image not found at "${src}"`);
293
+ let alt = validOptions?.alt;
294
+ if (alt === void 0) {
295
+ const { name: packageName } = await getReadmeMetadata();
296
+ if (packageName === void 0) throw new Error("Banner image alt text not available");
297
+ alt = `${packageName} banner`;
298
+ }
299
+ return `![${alt}](${src})`;
300
+ } } };
301
+ async function getBannerSrc() {
302
+ const { projectDirectory } = await getReadmeMetadata();
303
+ if (projectDirectory === void 0) throw new Error("No project directory found");
304
+ const [firstPath] = await globby([
305
+ ".",
306
+ "assets",
307
+ "media",
308
+ "readme-assets",
309
+ "readme-media",
310
+ "readme",
311
+ "images",
312
+ ".github/assets"
313
+ ].map((location) => path.join(projectDirectory, location)), {
314
+ deep: 1,
315
+ expandDirectories: {
316
+ extensions: [
317
+ "png",
318
+ "gif",
319
+ "jpg",
320
+ "jpeg",
321
+ "svg",
322
+ "webp"
323
+ ],
324
+ files: [
325
+ "banner",
326
+ "cover",
327
+ "demo",
328
+ "header",
329
+ "hero",
330
+ "image",
331
+ "logo",
332
+ "overview",
333
+ "readme",
334
+ "screenshot",
335
+ "screenshots",
336
+ "splash"
337
+ ]
338
+ }
339
+ });
340
+ return firstPath === void 0 ? void 0 : path.relative(process.cwd(), firstPath);
341
+ }
342
+ function isUrl(text, lenient = true) {
343
+ if (typeof text !== "string") throw new TypeError("Expected a string");
344
+ text = text.trim();
345
+ if (text.includes(" ")) return false;
346
+ if (URL.canParse(text)) return true;
347
+ return lenient && URL.canParse(`https://${text}`);
348
+ }
349
+
350
+ //#endregion
351
+ //#region src/lib/readme/rules/code.ts
352
+ const LEADING_DOT_REGEX = /^\./v;
353
+ var code_default = { code: { async content(options) {
354
+ const validOptions = z.object({
355
+ file: z.string(),
356
+ language: z.string().optional(),
357
+ trim: z.boolean().default(true)
358
+ }).parse(options);
359
+ const lang = (path.extname(validOptions.file) ?? "").replace(LEADING_DOT_REGEX, "");
360
+ const exampleCode = await fs.readFile(path.join(process.cwd(), validOptions.file), "utf8");
361
+ return `\`\`\`${lang}\n${validOptions.trim ? exampleCode.trim() : exampleCode}\n\`\`\``;
362
+ } } };
363
+
364
+ //#endregion
365
+ //#region src/lib/readme/rules/contributing.ts
366
+ var contributing_default = { contributing: { async content() {
367
+ const { issuesUrl } = await getReadmeMetadata();
368
+ if (issuesUrl === void 0) throw new Error("Could not find \"bugs.url\" entry in package.json");
369
+ return [
370
+ "## Contributing",
371
+ "",
372
+ `[Issues](${issuesUrl}) are welcome and appreciated.`,
373
+ "",
374
+ "Please open an issue to discuss changes before submitting a pull request. Unsolicited PRs (especially AI-generated ones) are unlikely to be merged.",
375
+ "",
376
+ "This repository uses [@kitschpatrol/shared-config](https://github.com/kitschpatrol/shared-config) (via its `ksc` CLI) for linting and formatting, plus [MDAT](https://github.com/kitschpatrol/mdat) for readme placeholder expansion."
377
+ ].join("\n");
378
+ } } };
379
+
380
+ //#endregion
381
+ //#region src/lib/readme/rules/dependencies.ts
382
+ const PLATFORM_INFO = {
383
+ bun: {
384
+ display: "Bun",
385
+ url: "https://bun.sh/"
386
+ },
387
+ deno: {
388
+ display: "Deno",
389
+ url: "https://deno.land/"
390
+ },
391
+ go: {
392
+ display: "Go",
393
+ url: "https://go.dev/"
394
+ },
395
+ java: {
396
+ display: "Java",
397
+ url: "https://www.java.com/"
398
+ },
399
+ node: {
400
+ display: "Node.js",
401
+ url: "https://nodejs.org/en"
402
+ },
403
+ python: {
404
+ display: "Python",
405
+ url: "https://www.python.org/"
406
+ },
407
+ ruby: {
408
+ display: "Ruby",
409
+ url: "https://www.ruby-lang.org/"
410
+ },
411
+ rust: {
412
+ display: "Rust",
413
+ url: "https://www.rust-lang.org/"
414
+ }
415
+ };
416
+ var dependencies_default = { dependencies: { async content() {
417
+ const { engines, operatingSystem, peerDependencies, runtimePlatform } = await getReadmeMetadata();
418
+ const platformItems = [];
419
+ if (engines !== void 0) for (const [name, version] of Object.entries(engines)) {
420
+ const info = PLATFORM_INFO[name.toLowerCase()];
421
+ const display = info ? `[${info.display}](${info.url})` : name;
422
+ platformItems.push(`- ${display} ${version}`);
423
+ }
424
+ if (runtimePlatform !== void 0) for (const entry of runtimePlatform) {
425
+ const spaceIndex = entry.indexOf(" ");
426
+ const platformKey = spaceIndex > 0 ? entry.slice(0, spaceIndex) : entry;
427
+ const version = spaceIndex > 0 ? entry.slice(spaceIndex + 1) : void 0;
428
+ if (engines?.[platformKey] !== void 0) continue;
429
+ const info = PLATFORM_INFO[platformKey.toLowerCase()];
430
+ const display = info ? `[${info.display}](${info.url})` : platformKey;
431
+ platformItems.push(version === void 0 || version === "" ? `- ${display}` : `- ${display} ${version}`);
432
+ }
433
+ if (operatingSystem !== void 0 && operatingSystem.length > 0) platformItems.push(`- Supported platforms: ${operatingSystem.join(", ")}`);
434
+ const peerItems = [];
435
+ if (peerDependencies !== void 0) for (const { name, optional, version } of peerDependencies) {
436
+ const npmUrl = `https://www.npmjs.com/package/${name}`;
437
+ const optionalSuffix = optional ? " _(optional)_" : "";
438
+ peerItems.push(`- [${name}](${npmUrl}) ${version}${optionalSuffix}`);
439
+ }
440
+ const hasPlatform = platformItems.length > 0;
441
+ const hasPeers = peerItems.length > 0;
442
+ if (!hasPlatform && !hasPeers) return "";
443
+ const sections = ["## Dependencies"];
444
+ if (hasPlatform && hasPeers) sections.push("", "### Platform", "", ...platformItems, "", "### Peer Dependencies", "", ...peerItems);
445
+ else if (hasPlatform) sections.push("", ...platformItems);
446
+ else sections.push("", ...peerItems);
447
+ return sections.join("\n");
448
+ } } };
449
+
450
+ //#endregion
451
+ //#region src/lib/readme/rules/description.ts
452
+ /**
453
+ * Simple alias for short-description
454
+ */
455
+ var description_default = { description: { async content() {
456
+ const { description } = await getReadmeMetadata();
457
+ if (description === void 0) throw new Error("Could not find \"description\" entry in package.json");
458
+ return `**${description}**`;
459
+ } } };
460
+
461
+ //#endregion
462
+ //#region src/lib/readme/rules/development-dependencies.ts
463
+ const TOOL_INFO = {
464
+ bun: {
465
+ display: "Bun",
466
+ url: "https://bun.sh/"
467
+ },
468
+ deno: {
469
+ display: "Deno",
470
+ url: "https://deno.land/"
471
+ },
472
+ node: {
473
+ display: "Node.js",
474
+ url: "https://nodejs.org/en"
475
+ },
476
+ npm: {
477
+ display: "npm",
478
+ url: "https://www.npmjs.com/"
479
+ },
480
+ pnpm: {
481
+ display: "pnpm",
482
+ url: "https://pnpm.io/"
483
+ },
484
+ yarn: {
485
+ display: "Yarn",
486
+ url: "https://yarnpkg.com/"
487
+ }
488
+ };
489
+ var development_dependencies_default = { "development-dependencies": { async content() {
490
+ const { developmentDependencies } = await getReadmeMetadata();
491
+ if (developmentDependencies === void 0) return "";
492
+ const { packageManagers, runtimes } = developmentDependencies;
493
+ const items = [];
494
+ for (const { name, version } of [...runtimes ?? [], ...packageManagers ?? []]) {
495
+ const info = TOOL_INFO[name.toLowerCase()];
496
+ const display = info ? `[${info.display}](${info.url})` : name;
497
+ items.push(version === void 0 || version === "" ? `- ${display}` : `- ${display} ${version}`);
498
+ }
499
+ return [
500
+ "## Development Dependencies",
501
+ "",
502
+ ...items
503
+ ].join("\n");
504
+ } } };
505
+
506
+ //#endregion
507
+ //#region src/lib/readme/rules/dev-dependencies.ts
508
+ /**
509
+ * Simple alias for `development-dependencies`
510
+ */
511
+ var dev_dependencies_default = { "dev-dependencies": getSoleRule(development_dependencies_default) };
512
+
513
+ //#endregion
514
+ //#region src/lib/readme/rules/license.ts
515
+ var license_default = { license: { async content() {
516
+ const { author, authorUrl, license, licenseFilePath } = await getReadmeMetadata();
517
+ if (author === void 0) throw new Error("Could not find author name in project");
518
+ if (license === void 0 || licenseFilePath === void 0) throw new Error("Could not find license for project");
519
+ return `## License\n[${license}](${licenseFilePath}) © ${authorUrl === void 0 ? author : `[${author}](${authorUrl})`}`;
520
+ } } };
521
+
522
+ //#endregion
523
+ //#region src/lib/readme/rules/footer.ts
524
+ var footer_default = { footer: { content: [getSoleRule(contributing_default), getSoleRule(license_default)] } };
525
+
526
+ //#endregion
527
+ //#region src/lib/readme/rules/short-description.ts
528
+ /**
529
+ * Simple alias for `description` rule, to match nomenclature in
530
+ * [standard-readme](https://github.com/RichardLitt/standard-readme/blob/main/spec.md#short-description)
531
+ * spec.
532
+ */
533
+ var short_description_default = { "short-description": getSoleRule(description_default) };
534
+
535
+ //#endregion
536
+ //#region src/lib/readme/rules/title.ts
537
+ var title_default = { title: {
538
+ async content(options) {
539
+ const { postfix, prefix, titleCase } = z.object({
540
+ postfix: z.string().optional().default(""),
541
+ prefix: z.string().optional().default(""),
542
+ titleCase: z.boolean().optional().default(false)
543
+ }).parse(options ?? {});
544
+ const { name: packageName } = await getReadmeMetadata();
545
+ if (packageName === void 0) throw new Error("Could not find project name");
546
+ return `# ${prefix}${titleCase ? makeTitleCase(packageName) : packageName}${postfix}`;
547
+ },
548
+ order: 2
549
+ } };
550
+ const SPLIT_FOR_TITLE_CASE_REGEX = /[ _\-]+/v;
551
+ function makeTitleCase(text) {
552
+ return text.split(SPLIT_FOR_TITLE_CASE_REGEX).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
553
+ }
554
+
555
+ //#endregion
556
+ //#region src/lib/readme/rules/header.ts
557
+ var header_default = { header: {
558
+ content: [
559
+ getSoleRule(title_default),
560
+ getSoleRule(banner_default),
561
+ getSoleRule(badges_default),
562
+ getSoleRule(short_description_default)
563
+ ],
564
+ order: 2
565
+ } };
566
+
567
+ //#endregion
568
+ //#region src/lib/readme/rules/install.ts
569
+ var install_default = { install: { async content() {
570
+ const { bin, engines, isPublicNpmPackage, name, runtimePlatform, usesPnpm } = await getReadmeMetadata();
571
+ if (name === void 0) throw new Error("Could not find project name");
572
+ const lines = ["## Install"];
573
+ if (isPublicNpmPackage || engines?.node !== void 0 || runtimePlatform?.includes("node")) {
574
+ const pmAdd = usesPnpm ? "pnpm add" : "npm install";
575
+ const pmx = usesPnpm ? "pnpx" : "npx";
576
+ lines.push("", "```sh", `${pmAdd} ${name}`, "```");
577
+ if (bin !== void 0 && bin.length > 0) lines.push("", "Or run it directly:", "", "```sh", `${pmx} ${name}`, "```");
578
+ } else if (runtimePlatform?.some((p) => p.startsWith("python"))) lines.push("", "```sh", `pip install ${name}`, "```");
579
+ else if (runtimePlatform?.some((p) => p.startsWith("rust"))) lines.push("", "```sh", `cargo install ${name}`, "```");
580
+ else if (runtimePlatform?.some((p) => p.startsWith("go"))) lines.push("", "```sh", `go install ${name}@latest`, "```");
581
+ else if (runtimePlatform?.some((p) => p.startsWith("ruby"))) lines.push("", "```sh", `gem install ${name}`, "```");
582
+ else throw new Error("Could not determine project ecosystem for install instructions");
583
+ return lines.join("\n");
584
+ } } };
585
+
586
+ //#endregion
587
+ //#region src/lib/readme/rules/utilities/size/size-report.ts
588
+ const brotliCompressAsync = promisify(brotliCompress);
589
+ const gzipCompressAsync = promisify(gzip);
590
+ /**
591
+ * Creates a SizeInfo object with formatted values
592
+ *
593
+ * @param bytes - Size in bytes
594
+ * @param originalSize - Original file size for percentage calculation
595
+ */
596
+ function createSizeInfo(bytes, originalSize, precision) {
597
+ const percent = originalSize === 0 ? 0 : (originalSize - bytes) / originalSize * 100;
598
+ return {
599
+ bytes,
600
+ bytesPretty: prettyBytes(bytes, { maximumFractionDigits: precision }),
601
+ percent,
602
+ percentPretty: `${Math.round(percent)}%`
603
+ };
604
+ }
605
+ /**
606
+ * Analyzes a file's size and its compressed sizes using Brotli and Gzip
607
+ *
608
+ * @param filePath - Path to the file to analyze
609
+ * @param precision - Maximum number of decimal places in formatted byte sizes
610
+ *
611
+ * @returns Promise containing detailed size report
612
+ * @throws {Error} If file cannot be read or compressed
613
+ */
614
+ async function createSizeReport(filePath, precision = 1) {
615
+ try {
616
+ const fileContent = await fs.readFile(filePath);
617
+ const originalSize = fileContent.length;
618
+ const [brotliCompressed, gzipCompressed] = await Promise.all([brotliCompressAsync(fileContent), gzipCompressAsync(fileContent)]);
619
+ return {
620
+ brotli: createSizeInfo(brotliCompressed.length, originalSize, precision),
621
+ gzip: createSizeInfo(gzipCompressed.length, originalSize, precision),
622
+ original: createSizeInfo(originalSize, originalSize, precision)
623
+ };
624
+ } catch (error) {
625
+ throw new Error(`Failed to analyze file: ${error instanceof Error ? error.message : "Unknown error"}`, { cause: error });
626
+ }
627
+ }
628
+
629
+ //#endregion
630
+ //#region src/lib/readme/rules/size.ts
631
+ const optionsSchema$1 = z.object({
632
+ compression: z.enum([
633
+ "none",
634
+ "brotli",
635
+ "gzip"
636
+ ]).optional().default("none"),
637
+ file: z.string()
638
+ });
639
+ function getSizeForCompression(report, compression) {
640
+ switch (compression) {
641
+ case "brotli": return report.brotli.bytesPretty;
642
+ case "gzip": return report.gzip.bytesPretty;
643
+ case "none": return report.original.bytesPretty;
644
+ }
645
+ }
646
+ var size_default = { size: { async content(options) {
647
+ const validOptions = optionsSchema$1.parse(options);
648
+ const fullPath = path.join(process.cwd(), validOptions.file);
649
+ return getSizeForCompression(await createSizeReport(fullPath), validOptions.compression);
650
+ } } };
651
+
652
+ //#endregion
653
+ //#region src/lib/readme/rules/size-table.ts
654
+ const fileSchema = z.union([z.string(), z.array(z.string())]).transform((files) => Array.isArray(files) ? files : [files]);
655
+ const optionsSchema = z.union([z.object({
656
+ brotli: z.boolean().optional().default(true),
657
+ file: fileSchema,
658
+ gzip: z.boolean().optional().default(true),
659
+ original: z.boolean().optional().default(true),
660
+ precision: z.number().int().min(0).max(100).optional().default(0),
661
+ showPercentage: z.boolean().optional().default(false)
662
+ }), z.object({
663
+ brotli: z.boolean().optional().default(true),
664
+ files: fileSchema,
665
+ gzip: z.boolean().optional().default(true),
666
+ original: z.boolean().optional().default(true),
667
+ precision: z.number().int().min(0).max(100).optional().default(0),
668
+ showPercentage: z.boolean().optional().default(false)
669
+ })]).transform((options) => {
670
+ if ("file" in options) {
671
+ const { file, ...rest } = options;
672
+ return {
673
+ files: file,
674
+ ...rest
675
+ };
676
+ }
677
+ return options;
678
+ });
679
+ function formatMarkdownTable(reports, options) {
680
+ const headers = ["File"];
681
+ if (options.original) headers.push("Original");
682
+ if (options.gzip) headers.push("Gzip");
683
+ if (options.brotli) headers.push("Brotli");
684
+ const separators = headers.map(() => "---");
685
+ const rows = reports.map(([file, report]) => {
686
+ const row = [path.basename(file)];
687
+ if (options.original) row.push(report.original.bytesPretty);
688
+ if (options.gzip) row.push(options.showPercentage ? `${report.gzip.bytesPretty} (${report.gzip.percentPretty})` : report.gzip.bytesPretty);
689
+ if (options.brotli) row.push(options.showPercentage ? `${report.brotli.bytesPretty} (${report.brotli.percentPretty})` : report.brotli.bytesPretty);
690
+ return row;
691
+ });
692
+ return [
693
+ `| ${headers.join(" | ")} |`,
694
+ `| ${separators.join(" | ")} |`,
695
+ ...rows.map((row) => `| ${row.join(" | ")} |`)
696
+ ].join("\n");
697
+ }
698
+ var size_table_default = { "size-table": { async content(options) {
699
+ const validOptions = optionsSchema.parse(options);
700
+ return formatMarkdownTable(await Promise.all(validOptions.files.map(async (file) => {
701
+ const fullPath = path.join(process.cwd(), file);
702
+ return [file, await createSizeReport(fullPath, validOptions.precision)];
703
+ })), {
704
+ brotli: validOptions.brotli,
705
+ gzip: validOptions.gzip,
706
+ original: validOptions.original,
707
+ showPercentage: validOptions.showPercentage
708
+ });
709
+ } } };
710
+
711
+ //#endregion
712
+ //#region src/lib/readme/rules/table-of-contents.ts
713
+ var table_of_contents_default = { "table-of-contents": {
714
+ async content(options, { tree }) {
715
+ const validOptions = z.object({ depth: z.union([
716
+ z.literal(1),
717
+ z.literal(2),
718
+ z.literal(3),
719
+ z.literal(4),
720
+ z.literal(5),
721
+ z.literal(6)
722
+ ]).optional() }).optional().parse(options);
723
+ const result = toc(tree, {
724
+ heading: null,
725
+ maxDepth: validOptions?.depth ?? 3,
726
+ tight: true
727
+ });
728
+ if (result.map === void 0) throw new Error("Could not generate table of contents");
729
+ const heading = `## Table of contents`;
730
+ const rootWrapper = {
731
+ children: result.map.children,
732
+ type: "root"
733
+ };
734
+ return [heading, remark().use(remarkGfm).stringify(rootWrapper).replaceAll("\n\n", "\n")].join("\n");
735
+ },
736
+ order: 1
737
+ } };
738
+
739
+ //#endregion
740
+ //#region src/lib/readme/rules/toc.ts
741
+ /**
742
+ * Simple alias for table-of-contents
743
+ */
744
+ var toc_default = { toc: getSoleRule(table_of_contents_default) };
745
+
746
+ //#endregion
747
+ //#region src/lib/readme/rules/index.ts
748
+ var rules_default = {
749
+ ...badges_default,
750
+ ...banner_default,
751
+ ...code_default,
752
+ ...contributing_default,
753
+ ...dependencies_default,
754
+ ...description_default,
755
+ ...dev_dependencies_default,
756
+ ...development_dependencies_default,
757
+ ...footer_default,
758
+ ...header_default,
759
+ ...install_default,
760
+ ...license_default,
761
+ ...short_description_default,
762
+ ...size_default,
763
+ ...size_table_default,
764
+ ...table_of_contents_default,
765
+ ...title_default,
766
+ ...toc_default
767
+ };
768
+
769
+ //#endregion
770
+ //#region src/lib/config.ts
771
+ let _configExplorer;
772
+ let _additionalConfigExplorer;
773
+ function getConfigExplorer() {
774
+ _configExplorer ??= cosmiconfig("mdat", { loaders: { ".ts": TypeScriptLoader() } });
775
+ return _configExplorer;
776
+ }
777
+ function getAdditionalConfigExplorer() {
778
+ _additionalConfigExplorer ??= cosmiconfig("mdat", { loaders: {
779
+ ".json": mdatJsonLoader,
780
+ ".ts": TypeScriptLoader()
781
+ } });
782
+ return _additionalConfigExplorer;
783
+ }
784
+ /**
785
+ * Load and validate mdat configuration. Uses cosmiconfig to search in the usual
786
+ * places. Merge precedence: Base Defaults < Defaults < Searched Config <
787
+ * Additional Config
788
+ */
789
+ async function loadConfig(options) {
790
+ const { additionalConfig, defaults = rules_default, searchFrom } = options ?? {};
791
+ let finalConfig = { mdat: `Powered by the Markdown Autophagic Template system: [mdat](https://github.com/kitschpatrol/mdat).` };
792
+ finalConfig = deepMergeDefined(finalConfig, defaults);
793
+ const results = await getConfigExplorer().search(searchFrom);
794
+ if (results) {
795
+ const { config, filepath } = results;
796
+ let possibleRules = config;
797
+ log.debug(`Using config from "${filepath}"`);
798
+ if (typeof config === "string" && filepath.endsWith("package.json")) {
799
+ log.debug(`Detected shared config string: "${config}"`);
800
+ const { default: sharedConfig } = await import(config);
801
+ possibleRules = sharedConfig;
802
+ }
803
+ const validated = validateConfig(possibleRules);
804
+ if (validated) finalConfig = deepMergeDefined(finalConfig, validated);
805
+ }
806
+ if (additionalConfig !== void 0) {
807
+ const additionalConfigArray = Array.isArray(additionalConfig) ? additionalConfig : [additionalConfig];
808
+ for (const configOrPath of additionalConfigArray) {
809
+ let loaded;
810
+ if (typeof configOrPath === "string") {
811
+ let loadResults;
812
+ if (path.basename(configOrPath).endsWith("package.json")) {
813
+ const packageJson = await fs.readFile(configOrPath, "utf8");
814
+ loadResults = {
815
+ config: mdatJsonLoader(configOrPath, packageJson),
816
+ filepath: configOrPath
817
+ };
818
+ } else loadResults = await getAdditionalConfigExplorer().load(configOrPath);
819
+ if (loadResults === null || loadResults === void 0) continue;
820
+ const { config: loadedConfig, filepath } = loadResults;
821
+ log.debug(`Loaded additional config from "${filepath}"`);
822
+ loaded = loadedConfig;
823
+ } else loaded = configOrPath;
824
+ if (loaded === void 0) continue;
825
+ log.debug("Merging config");
826
+ const validated = validateConfig(loaded);
827
+ if (validated) finalConfig = deepMergeDefined(finalConfig, validated);
828
+ }
829
+ }
830
+ const prettyRules = Object.keys(finalConfig).toSorted().map((rule) => `"${picocolors.green(picocolors.bold(rule))}"`);
831
+ log.debug(`Loaded ${picocolors.bold(prettyRules.length)} mdat comment expansion ${plur("rule", prettyRules.length)}:`);
832
+ for (const rule of prettyRules) log.debug(`\t${rule}`);
833
+ return finalConfig;
834
+ }
835
+ function validateConfig(value) {
836
+ if (rulesSchema.safeParse(value).success) return value;
837
+ log.warn(`Config has the wrong shape. Ignoring and using default configuration:\n${JSON.stringify(value, void 0, 2)}`);
838
+ }
839
+ /**
840
+ * Convenience function for merging configs. Rightmost config takes precedence.
841
+ */
842
+ function mergeConfig(a, b) {
843
+ return deepMergeDefined(a, b);
844
+ }
845
+ /**
846
+ * Identity function providing type inference for mdat configuration files.
847
+ */
848
+ function defineConfig(config) {
849
+ return config;
850
+ }
851
+
852
+ //#endregion
853
+ //#region src/lib/format.ts
854
+ let cachedPrettier;
855
+ const configCache = /* @__PURE__ */ new Map();
856
+ /**
857
+ * Format a markdown string with Prettier, using config discovered from the file
858
+ * path. Requires `prettier` to be installed as a peer dependency.
859
+ */
860
+ async function formatWithPrettier(content, filePath) {
861
+ if (cachedPrettier === void 0) try {
862
+ cachedPrettier = await import("prettier");
863
+ } catch {
864
+ throw new Error("The --format flag requires `prettier` to be installed. Run: pnpm add -D prettier");
865
+ }
866
+ const configKey = filePath === void 0 || filePath === "" ? process.cwd() : path.dirname(filePath);
867
+ let config = configCache.get(configKey);
868
+ if (config === void 0 && !configCache.has(configKey)) {
869
+ config = await cachedPrettier.resolveConfig(filePath ?? process.cwd());
870
+ configCache.set(configKey, config);
871
+ if (config) log.debug(`Resolved Prettier config for "${configKey}"`);
872
+ }
873
+ return cachedPrettier.format(content, {
874
+ ...config,
875
+ filepath: filePath,
876
+ parser: "markdown"
877
+ });
878
+ }
879
+
880
+ //#endregion
881
+ //#region src/lib/utilities.ts
882
+ function zeroPad(n, nMax) {
883
+ const places = nMax === 0 ? 1 : Math.floor(Math.log10(Math.abs(nMax)) + 1);
884
+ return n.toString().padStart(places, "0");
885
+ }
886
+ async function getInputOutputPaths(inputs, output, name, extension) {
887
+ const paths = [];
888
+ for (const [index, file] of inputs.entries()) {
889
+ const inputOutputPath = await getInputOutputPath(file, output, name, extension, name !== void 0 && name !== "" && inputs.length > 1 ? `-${zeroPad(index + 1, inputs.length)}` : "");
890
+ paths.push(inputOutputPath);
891
+ }
892
+ return paths;
893
+ }
894
+ async function getInputOutputPath(input, output, name, extension, nameSuffix = "") {
895
+ const resolvedInput = expandPath(input);
896
+ if (!isFileSync(resolvedInput)) throw new Error(`Input file not found: "${resolvedInput}"`);
897
+ const resolvedOutput = output === void 0 || output === "" ? void 0 : expandPath(output);
898
+ if (resolvedOutput !== void 0) {
899
+ if (isFileSync(resolvedOutput)) throw new Error(`Output path must be a directory, received a file path: "${resolvedOutput}"`);
900
+ await fs.mkdir(resolvedOutput, { recursive: true });
901
+ }
902
+ return {
903
+ input: resolvedInput,
904
+ 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}`}`,
905
+ output: resolvedOutput ?? path.dirname(resolvedInput)
906
+ };
907
+ }
908
+ function expandPath(file) {
909
+ return untildify(file);
910
+ }
911
+ function ensureArray(value) {
912
+ if (value === void 0 || value === null) return [];
913
+ return Array.isArray(value) ? value : [value];
914
+ }
915
+ const README_SEARCH_REGEX = /^readme(?:\.\w+)?$/iv;
916
+ /**
917
+ * Finds a readme file in the current working directory (case-insensitive).
918
+ */
919
+ async function findReadme() {
920
+ log.debug("Searching for readme in current directory...");
921
+ const readme = (await fs.readdir(process.cwd())).find((entry) => README_SEARCH_REGEX.test(entry));
922
+ if (readme !== void 0) {
923
+ const absolutePath = path.resolve(readme);
924
+ log.debug(`Found readme at "${absolutePath}"`);
925
+ return absolutePath;
926
+ }
927
+ }
928
+ /**
929
+ * Searches up for a readme.md file.
930
+ *
931
+ * @throws {Error} If no readme is found
932
+ */
933
+ async function findReadmeThrows() {
934
+ const readme = await findReadme();
935
+ if (readme === void 0) throw new Error("No readme found");
936
+ return readme;
937
+ }
938
+ let cachedAmbientRemarkConfig;
939
+ async function loadAmbientRemarkConfig() {
940
+ if (cachedAmbientRemarkConfig !== void 0) return cachedAmbientRemarkConfig;
941
+ const ambientConfig = new Configuration({
942
+ cwd: process.cwd(),
943
+ detectConfig: true,
944
+ packageField: "remarkConfig",
945
+ rcName: ".remarkrc"
946
+ });
947
+ const configResult = await new Promise((resolve) => {
948
+ ambientConfig.load("", (error, result) => {
949
+ if (error) {
950
+ log.error(String(error));
951
+ resolve(void 0);
952
+ return;
953
+ }
954
+ resolve(result);
955
+ });
956
+ });
957
+ if (configResult) {
958
+ const { filePath } = configResult;
959
+ if (filePath === void 0) log.debug("No ambient Remark configuration file found");
960
+ else log.debug(`Found and loaded ambient Remark configuration from "${filePath}"`);
961
+ cachedAmbientRemarkConfig = stripLintPlugins(configResult);
962
+ return cachedAmbientRemarkConfig;
963
+ }
964
+ log.debug("No ambient Remark configuration found");
965
+ cachedAmbientRemarkConfig = {
966
+ filePath: void 0,
967
+ plugins: [],
968
+ settings: {}
969
+ };
970
+ return cachedAmbientRemarkConfig;
971
+ }
972
+ /**
973
+ * Strip remark-lint plugins from an ambient config. Lint plugins only produce
974
+ * VFile warnings and never modify the AST or output — running them during
975
+ * expansion is pure overhead.
976
+ */
977
+ function stripLintPlugins(config) {
978
+ return {
979
+ ...config,
980
+ plugins: config.plugins.filter((entry) => {
981
+ const plugin = Array.isArray(entry) ? entry[0] : entry;
982
+ if (typeof plugin !== "function") return true;
983
+ const { name } = plugin;
984
+ return name !== "remarkLint" && !name.startsWith("remark-lint:") && name !== "remarkValidateLinks";
985
+ })
986
+ };
987
+ }
988
+
989
+ //#endregion
990
+ //#region src/lib/processors.ts
991
+ async function processFiles(files, loader, processorGetter, name, output, config) {
992
+ const [resolvedConfig, localRemarkConfig] = await Promise.all([loader({ additionalConfig: config }), loadAmbientRemarkConfig()]);
993
+ const resolvedFiles = ensureArray(files);
994
+ const inputOutputPaths = await getInputOutputPaths(resolvedFiles, output, name, "md");
995
+ const resolvedProcessor = processorGetter(resolvedConfig, localRemarkConfig);
996
+ return await Promise.all(inputOutputPaths.map(async ({ input, name: outputName, output: outputDirectory }) => {
997
+ const inputFile = await read(input);
998
+ const result = await resolvedProcessor.process(inputFile);
999
+ result.dirname = outputDirectory;
1000
+ result.basename = outputName;
1001
+ return result;
1002
+ }));
1003
+ }
1004
+ async function processString(markdown, loader, processorGetter, config) {
1005
+ const [resolvedConfig, localRemarkConfig] = await Promise.all([loader({ additionalConfig: config }), loadAmbientRemarkConfig()]);
1006
+ return processorGetter(resolvedConfig, localRemarkConfig).process(new VFile(markdown));
1007
+ }
1008
+ function getExpandProcessor(config, ambientRemarkConfig) {
1009
+ return remark().use({ settings: {
1010
+ bullet: "-",
1011
+ emphasis: "_"
1012
+ } }).use(remarkGfm).use(ambientRemarkConfig).use(() => async function(tree, file) {
1013
+ mdatSplit(tree, file);
1014
+ mdatCollapse(tree, file);
1015
+ await mdatExpand(tree, file, config);
1016
+ });
1017
+ }
1018
+ function getCollapseProcessor(_config, ambientRemarkConfig) {
1019
+ return remark().use({ settings: {
1020
+ bullet: "-",
1021
+ emphasis: "_"
1022
+ } }).use(remarkGfm).use(ambientRemarkConfig).use(() => function(tree, file) {
1023
+ mdatSplit(tree, file);
1024
+ mdatCollapse(tree, file);
1025
+ });
1026
+ }
1027
+ function getStripProcessor(_config, ambientRemarkConfig) {
1028
+ return remark().use({ settings: {
1029
+ bullet: "-",
1030
+ emphasis: "_"
1031
+ } }).use(remarkGfm).use(ambientRemarkConfig).use(() => function(tree, file) {
1032
+ mdatSplit(tree, file);
1033
+ mdatStrip(tree, file);
1034
+ });
1035
+ }
1036
+
1037
+ //#endregion
1038
+ //#region src/lib/readme/templates/mdat-readme-compound.md?raw
1039
+ 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";
1040
+
1041
+ //#endregion
1042
+ //#region src/lib/readme/templates/mdat-readme.md?raw
1043
+ 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";
1044
+
1045
+ //#endregion
1046
+ //#region src/lib/readme/templates/standard-readme-basic-compound.md?raw
1047
+ var standard_readme_basic_compound_default = "<!-- header -->\n\n## Install\n\n```sh\n# Code block illustrating how to install.\n```\n\n## Usage\n\n```ts\n// Code block illustrating common usage.\n// Consider using the <!-- code({ src: \"path/to/example.ts\" }) --> comment as well.\n```\n\n<!-- footer -->\n";
1048
+
1049
+ //#endregion
1050
+ //#region src/lib/readme/templates/standard-readme-basic.md?raw
1051
+ var standard_readme_basic_default = "<!-- title -->\n\n<!-- short-description -->\n\n<!-- table-of-contents -->\n\n## Install\n\n```sh\n# Code block illustrating how to install.\n```\n\n## Usage\n\n```ts\n// Code block illustrating common usage.\n// Consider using the <!-- code({ src: \"path/to/example.ts\" })--> comment as well.\n```\n\n<!-- contributing -->\n\n<!-- license -->\n";
1052
+
1053
+ //#endregion
1054
+ //#region src/lib/readme/templates/standard-readme-full-compound.md?raw
1055
+ var standard_readme_full_compound_default = "<!-- header -->\n\n_Long description goes here._\n\n_No heading. Cover the main reasons for building the repository. This should describe your module in broad terms, generally in just a few paragraphs; more detail of the module's routines or methods, lengthy code examples, or other in-depth material should be given in subsequent sections._\n\n<!-- table-of-contents -->\n\n## Background\n\n_Cover motivation. Cover abstract dependencies. Cover intellectual provenance: A See Also section is also fitting._\n\n### See also\n\n## Install\n\n```sh\n# Code block illustrating how to install.\n```\n\n### Dependencies\n\n_Required if there are unusual dependencies or dependencies that must be manually installed._\n\n## Usage\n\n```ts\n// Code block illustrating common usage.\n// Consider using the <!-- code({ src: \"path/to/example.ts\" })--> comment as well.\n```\n\n```ts\n// If importable, code block indicating both import functionality and usage.\n```\n\n### CLI\n\n```ts\n// If CLI compatible, code block indicating common usage.\n```\n\n## _Extra Sections (Rename)_\n\n## Security\n\n## API\n\n_Describe exported functions and objects._\n\n- _Describe signatures, return types, callbacks, and events._\n- _Cover types covered where not obvious._\n- _Describe caveats._\n- _If using an external API generator (like go-doc, js-doc, or so on), point to an external API.md file. This can be the only item in the section, if present._\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## Thanks\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";
1056
+
1057
+ //#endregion
1058
+ //#region src/lib/readme/templates/standard-readme-full.md?raw
1059
+ var standard_readme_full_default = "<!-- title -->\n\n<!-- banner -->\n\n<!-- badges -->\n\n<!-- short-description -->\n\n_Long description goes here._\n\n_No heading. Cover the main reasons for building the repository. This should describe your module in broad terms, generally in just a few paragraphs; more detail of the module's routines or methods, lengthy code examples, or other in-depth material should be given in subsequent sections._\n\n<!-- table-of-contents -->\n\n## Background\n\n_Cover motivation. Cover abstract dependencies. Cover intellectual provenance: A See Also section is also fitting._\n\n### See also\n\n## Install\n\n```sh\n# Code block illustrating how to install.\n```\n\n### Dependencies\n\n_Required if there are unusual dependencies or dependencies that must be manually installed._\n\n## Usage\n\n```ts\n// Code block illustrating common usage.\n// Consider using the <!-- code({ src: \"path/to/example.ts\" })--> comment as well.\n```\n\n```ts\n// If importable, code block indicating both import functionality and usage.\n```\n\n### CLI\n\n```ts\n// If CLI compatible, code block indicating common usage.\n```\n\n## _Extra Sections (Rename)_\n\n## Security\n\n## API\n\n_Describe exported functions and objects._\n\n- _Describe signatures, return types, callbacks, and events._\n- _Cover types covered where not obvious._\n- _Describe caveats._\n- _If using an external API generator (like go-doc, js-doc, or so on), point to an external API.md file. This can be the only item in the section, if present._\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## Thanks\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";
1060
+
1061
+ //#endregion
1062
+ //#region src/lib/readme/templates/index.ts
1063
+ var templates_default = {
1064
+ "MDAT Readme": {
1065
+ content: {
1066
+ compound: mdat_readme_compound_default,
1067
+ explicit: mdat_readme_default
1068
+ },
1069
+ description: "The house style. An expansive starting point. Prune to your context and taste.",
1070
+ exampleLink: "https://github.com/kitschpatrol/mdat/blob/main/readme.md"
1071
+ },
1072
+ "Standard Readme Basic": {
1073
+ content: {
1074
+ compound: standard_readme_basic_compound_default,
1075
+ explicit: standard_readme_basic_default
1076
+ },
1077
+ description: "Includes only the \"required\" sections from the Standard Readme specification.",
1078
+ exampleLink: "https://github.com/RichardLitt/standard-readme/blob/main/example-readmes/minimal-readme.md"
1079
+ },
1080
+ "Standard Readme Full": {
1081
+ content: {
1082
+ compound: standard_readme_full_compound_default,
1083
+ explicit: standard_readme_full_default
1084
+ },
1085
+ description: "Includes all sections from the Standard Readme specification.",
1086
+ exampleLink: "https://github.com/RichardLitt/standard-readme/blob/main/example-readmes/maximal-readme.md"
1087
+ }
1088
+ };
1089
+
1090
+ //#endregion
1091
+ //#region src/lib/readme/create.ts
1092
+ /**
1093
+ * Creates a new readme file interactively.
1094
+ *
1095
+ * @returns Path to the created readme file.
1096
+ */
1097
+ async function createReadmeInteractive() {
1098
+ const readmePath = await findReadme();
1099
+ intro(`Running ${picocolors.bold("mdat create")} interactively`);
1100
+ const newReadmePath = await createReadme(await group({
1101
+ async overwrite() {
1102
+ if (readmePath === void 0) return true;
1103
+ if (await confirm({
1104
+ message: `Found an existing readme at "${picocolors.blue(readmePath)}". Do you want to overwrite it?`,
1105
+ active: "Overwrite",
1106
+ inactive: "Exit"
1107
+ }) === false) throw new Error("`mdat create` was cancelled to avoid an overwrite - no changes were made");
1108
+ return true;
1109
+ },
1110
+ template: async () => select({
1111
+ message: "Which template would you like to use?",
1112
+ options: getTemplateOptions()
1113
+ }),
1114
+ compound: async () => select({
1115
+ message: "Do you want to use \"compound comments\" where possible, which combine several expansions into a single comment block?",
1116
+ options: [{
1117
+ label: `Yes: Combine things like ${picocolors.green("<!-- title -->")} and ${picocolors.green("<!-- badges -->")} in a single ${picocolors.green("<!-- header -->")} comment.`,
1118
+ value: true
1119
+ }, {
1120
+ label: "No: Use individual MDAT expansion comments for each section.",
1121
+ value: false
1122
+ }]
1123
+ }),
1124
+ expand: async () => confirm({
1125
+ initialValue: true,
1126
+ message: "Do you want to run `mdat expand` now to expand the template with content from your project metadata?"
1127
+ })
1128
+ }, { onCancel() {
1129
+ throw new Error("`mdat create` was cancelled - no changes were made");
1130
+ } }));
1131
+ note(`Readme created: "${picocolors.blue(picocolors.bold(newReadmePath))}"`);
1132
+ outro("Done!");
1133
+ return newReadmePath;
1134
+ }
1135
+ /**
1136
+ * Creates a new readme file with the given options.
1137
+ *
1138
+ * @returns Path to the created readme file.
1139
+ */
1140
+ async function createReadme(options) {
1141
+ const resolvedOptions = deepMergeDefined({
1142
+ compound: true,
1143
+ output: process.cwd(),
1144
+ overwrite: true,
1145
+ expand: true,
1146
+ template: Object.keys(templates_default)[0]
1147
+ }, options ?? {});
1148
+ const templateString = getTemplateForConfig(resolvedOptions.template, resolvedOptions.compound);
1149
+ const readmePath = path.join(resolvedOptions.output, "readme.md");
1150
+ if (!resolvedOptions.overwrite) {
1151
+ let exists = true;
1152
+ try {
1153
+ await fs.access(readmePath);
1154
+ } catch {
1155
+ exists = false;
1156
+ }
1157
+ if (exists) throw new Error(`Readme already exists at "${readmePath}". Use the overwrite option to replace it.`);
1158
+ }
1159
+ await fs.writeFile(readmePath, templateString, "utf8");
1160
+ if (resolvedOptions.expand) {
1161
+ const [result] = await expand(readmePath);
1162
+ if (result === void 0) throw new Error(`Failed to expand readme at "${readmePath}"`);
1163
+ await write(result);
1164
+ }
1165
+ return readmePath;
1166
+ }
1167
+ function getTemplateForConfig(templateKey, compound) {
1168
+ if (!Object.hasOwn(templates_default, templateKey)) throw new Error(`Unknown template "${templateKey}". Available templates: ${Object.keys(templates_default).join(", ")}`);
1169
+ return templates_default[templateKey].content[compound ? "compound" : "explicit"];
1170
+ }
1171
+ function getTemplateOptions() {
1172
+ return Object.entries(templates_default).map(([key, value]) => ({
1173
+ label: key,
1174
+ hint: value.description,
1175
+ value: key
1176
+ }));
1177
+ }
1178
+
1179
+ //#endregion
1180
+ //#region src/lib/api.ts
1181
+ /**
1182
+ * Expand MDAT comments in one or more Markdown files. If no files are provided,
1183
+ * auto-finds the closest readme.md. Writing is the responsibility of the caller
1184
+ * (e.g. via `await write(result)`)
1185
+ *
1186
+ * @returns An array of VFiles
1187
+ */
1188
+ async function expand(files, name, output, config, options) {
1189
+ files ??= await findReadmeThrows();
1190
+ const results = await processFiles(files, loadConfig, getExpandProcessor, name, output, config);
1191
+ if (options?.format) await formatResults(results);
1192
+ return results;
1193
+ }
1194
+ /**
1195
+ * Expand MDAT comments in a Markdown string.
1196
+ */
1197
+ async function expandString(markdown, config, options) {
1198
+ const result = await processString(markdown, loadConfig, getExpandProcessor, config);
1199
+ if (options?.format) await formatResults([result]);
1200
+ return result;
1201
+ }
1202
+ /**
1203
+ * Collapse MDAT comments in one or more Markdown files. If no files are
1204
+ * provided, auto-finds the closest readme.md. Writing is the responsibility of
1205
+ * the caller (e.g. via `await write(result)`)
1206
+ *
1207
+ * @returns An array of VFiles
1208
+ */
1209
+ async function collapse(files, name, output, config, options) {
1210
+ files ??= await findReadmeThrows();
1211
+ const results = await processFiles(files, loadConfig, getCollapseProcessor, name, output, config);
1212
+ if (options?.format) await formatResults(results);
1213
+ return results;
1214
+ }
1215
+ /**
1216
+ * Collapse MDAT comments in a Markdown string.
1217
+ */
1218
+ async function collapseString(markdown, config, options) {
1219
+ const result = await processString(markdown, loadConfig, getCollapseProcessor, config);
1220
+ if (options?.format) await formatResults([result]);
1221
+ return result;
1222
+ }
1223
+ /**
1224
+ * Strips MDAT comments in one or more Markdown files without touching other
1225
+ * content. Does _not_ automatically expand Mdat content before stripping the
1226
+ * tags. If no files are provided, auto-finds the closest readme.md. Writing is
1227
+ * the responsibility of the caller (e.g. via `await write(result)`)
1228
+ *
1229
+ * @returns An array of VFiles
1230
+ */
1231
+ async function strip(files, name, output, config, options) {
1232
+ files ??= await findReadmeThrows();
1233
+ const results = await processFiles(files, loadConfig, getStripProcessor, name, output, config);
1234
+ if (options?.format) await formatResults(results);
1235
+ return results;
1236
+ }
1237
+ /**
1238
+ * Strip MDAT comments from a Markdown string.
1239
+ */
1240
+ async function stripString(markdown, config, options) {
1241
+ const result = await processString(markdown, loadConfig, getStripProcessor, config);
1242
+ if (options?.format) await formatResults([result]);
1243
+ return result;
1244
+ }
1245
+ /**
1246
+ * Dry-run expand and compare with file on disk. If no files are provided,
1247
+ * auto-finds the closest readme.md.
1248
+ *
1249
+ * @returns Per-file sync status and expanded VFiles
1250
+ */
1251
+ async function check(files, config, options) {
1252
+ files ??= await findReadmeThrows();
1253
+ const { read } = await import("to-vfile");
1254
+ const resolvedFiles = Array.isArray(files) ? files : [files];
1255
+ const [originals, results] = await Promise.all([Promise.all(resolvedFiles.map(async (f) => read(f))), processFiles(files, loadConfig, getExpandProcessor, void 0, void 0, config)]);
1256
+ if (options?.format) await formatResults(results);
1257
+ return results.map((result, i) => {
1258
+ const original = originals[i];
1259
+ if (original === void 0) throw new Error(`Missing original file for comparison at index ${i}`);
1260
+ return compareWithDiff(original, result, options);
1261
+ });
1262
+ }
1263
+ /**
1264
+ * Check if MDAT comments in a Markdown string are up to date by expanding and
1265
+ * comparing per-tag.
1266
+ */
1267
+ async function checkString(markdown, config, options) {
1268
+ const { VFile: VF } = await import("vfile");
1269
+ const original = new VF(markdown);
1270
+ const result = await processString(markdown, loadConfig, getExpandProcessor, config);
1271
+ if (options?.format) await formatResults([result]);
1272
+ return compareWithDiff(original, result, options);
1273
+ }
1274
+ function compareWithDiff(original, result, options) {
1275
+ const inSync = original.toString().replaceAll("\r\n", "\n") === result.toString();
1276
+ if (!inSync) {
1277
+ const parser = remark().use(remarkGfm);
1278
+ const originalTree = parser.parse(original.toString());
1279
+ mdatSplit(originalTree, original);
1280
+ const expandedTree = parser.parse(result.toString());
1281
+ mdatSplit(expandedTree, result);
1282
+ const diffResults = mdatDiff(originalTree, original, expandedTree, result);
1283
+ if (options?.format && diffResults.every((r) => r.status === "ok")) {
1284
+ const message = result.message("Formatting differences outside mdat tags", { source: "diff" });
1285
+ message.fatal = false;
1286
+ }
1287
+ }
1288
+ return {
1289
+ inSync,
1290
+ result
1291
+ };
1292
+ }
1293
+ async function formatResults(results) {
1294
+ for (const file of results) file.value = await formatWithPrettier(file.toString(), file.path === "" ? void 0 : file.path);
1295
+ }
1296
+
1297
+ //#endregion
1298
+ export { version as S, getReadmeMetadata as _, expand as a, setLogger$2 as b, stripString as c, templates_default as d, ensureArray as f, getContextMetadata as g, mergeConfig as h, collapseString as i, createReadme as l, loadConfig as m, checkString as n, expandString as o, defineConfig as p, collapse as r, strip as s, check as t, createReadmeInteractive as u, resetMetadataCaches as v, name as x, log as y };