mdat 2.0.0 → 2.0.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.
- package/dist/.DS_Store +0 -0
- package/dist/bin/cli.js +157 -103
- package/dist/lib/index.d.ts +13 -6
- package/dist/lib/index.js +171 -96
- package/package.json +11 -6
- package/readme.md +48 -11
package/dist/.DS_Store
CHANGED
|
Binary file
|
package/dist/bin/cli.js
CHANGED
|
@@ -7,9 +7,9 @@ import { TypeScriptLoader } from "cosmiconfig-typescript-loader";
|
|
|
7
7
|
import fs from "node:fs/promises";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import plur from "plur";
|
|
10
|
-
import { defineTemplate, getMetadata, helpers, setLogger as setLogger$1, templates } from "metascope";
|
|
11
10
|
import { deepmerge } from "deepmerge-ts";
|
|
12
11
|
import { createLogger, getChildLogger, injectionHelper } from "lognow";
|
|
12
|
+
import { defineTemplate, getMetadata, helpers, setLogger as setLogger$1, templates } from "metascope";
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { globby } from "globby";
|
|
15
15
|
import { isFile, isFileSync } from "path-type";
|
|
@@ -27,65 +27,6 @@ import { confirm, group, intro, note, outro, select } from "@clack/prompts";
|
|
|
27
27
|
import yargs from "yargs";
|
|
28
28
|
import { hideBin } from "yargs/helpers";
|
|
29
29
|
|
|
30
|
-
//#region src/lib/context.ts
|
|
31
|
-
let metascopeMetadata;
|
|
32
|
-
/**
|
|
33
|
-
* Get a bunch of platform-agnostic local metadata via metascope, exposed
|
|
34
|
-
* primarily for plugin developers.
|
|
35
|
-
* Result is memoized the result.
|
|
36
|
-
* @throws {Error} If no package.json is found
|
|
37
|
-
*/
|
|
38
|
-
async function getContextMetadata() {
|
|
39
|
-
if (metascopeMetadata !== void 0) return metascopeMetadata;
|
|
40
|
-
metascopeMetadata = await getMetadata({
|
|
41
|
-
absolute: false,
|
|
42
|
-
offline: true
|
|
43
|
-
});
|
|
44
|
-
return metascopeMetadata;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Reset
|
|
48
|
-
*/
|
|
49
|
-
function resetContextMetadata() {
|
|
50
|
-
metascopeMetadata = void 0;
|
|
51
|
-
}
|
|
52
|
-
const readmeMetadataTemplate = defineTemplate((context) => {
|
|
53
|
-
const { githubActions, licenseFile, metascope, nodePackageJson } = context;
|
|
54
|
-
const codemeta = templates.codemetaJson(context, {});
|
|
55
|
-
const nodePackage = helpers.firstOf(nodePackageJson)?.data;
|
|
56
|
-
const licenseFileData = helpers.firstOf(licenseFile);
|
|
57
|
-
const ciActionFilePath = helpers.ensureArray(githubActions).find((entry) => entry.data.name.toLowerCase() === "ci")?.source;
|
|
58
|
-
const repositoryOwner = codemeta.codeRepository ? new URL(codemeta.codeRepository).pathname.split("/")[1] : void 0;
|
|
59
|
-
return {
|
|
60
|
-
author: helpers.firstOf(helpers.mixedStringsToArray(helpers.toBasicNames(codemeta.author))),
|
|
61
|
-
ciActionFileName: ciActionFilePath ? path.basename(ciActionFilePath) : void 0,
|
|
62
|
-
description: codemeta.description,
|
|
63
|
-
isPublicNpmPackage: !nodePackage?.name.startsWith("@") || nodePackage.publishConfig?.access === "public",
|
|
64
|
-
issuesUrl: codemeta.issueTracker,
|
|
65
|
-
license: helpers.toBasicLicense(helpers.firstOf(helpers.ensureArray(codemeta.license))),
|
|
66
|
-
licenseFilePath: licenseFileData?.source,
|
|
67
|
-
name: codemeta.name,
|
|
68
|
-
projectDirectory: metascope?.data.options.path === void 0 ? void 0 : `file://${metascope.data.options.path}`,
|
|
69
|
-
repositoryOwner
|
|
70
|
-
};
|
|
71
|
-
});
|
|
72
|
-
let readmeMetadata;
|
|
73
|
-
/**
|
|
74
|
-
* Nice data for readme rules
|
|
75
|
-
*/
|
|
76
|
-
async function getReadmeMetadata() {
|
|
77
|
-
if (readmeMetadata !== void 0) return readmeMetadata;
|
|
78
|
-
readmeMetadata = readmeMetadataTemplate(await getContextMetadata(), {});
|
|
79
|
-
return readmeMetadata;
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Reset
|
|
83
|
-
*/
|
|
84
|
-
function resetReadmeMetadata() {
|
|
85
|
-
readmeMetadata = void 0;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
//#endregion
|
|
89
30
|
//#region src/lib/deep-merge-defined.ts
|
|
90
31
|
function stripUndefinedDeep(object) {
|
|
91
32
|
if (Array.isArray(object)) return object.map((v) => v && typeof v === "object" ? stripUndefinedDeep(v) : v).filter((v) => v !== void 0);
|
|
@@ -101,7 +42,7 @@ function deepMergeDefined(...objects) {
|
|
|
101
42
|
//#endregion
|
|
102
43
|
//#region package.json
|
|
103
44
|
var name = "mdat";
|
|
104
|
-
var version = "2.0.
|
|
45
|
+
var version = "2.0.1";
|
|
105
46
|
|
|
106
47
|
//#endregion
|
|
107
48
|
//#region src/lib/log.ts
|
|
@@ -146,6 +87,87 @@ function flattenJson(jsonObject, parentKey = "", result = {}) {
|
|
|
146
87
|
return result;
|
|
147
88
|
}
|
|
148
89
|
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/lib/context.ts
|
|
92
|
+
let metascopeMetadata;
|
|
93
|
+
/**
|
|
94
|
+
* Get a bunch of platform-agnostic local metadata via metascope, exposed
|
|
95
|
+
* primarily for plugin developers. Result is memoized the result.
|
|
96
|
+
*
|
|
97
|
+
* @throws {Error} If no package.json is found
|
|
98
|
+
*/
|
|
99
|
+
async function getContextMetadata() {
|
|
100
|
+
if (metascopeMetadata !== void 0) return metascopeMetadata;
|
|
101
|
+
metascopeMetadata = await getMetadata({
|
|
102
|
+
absolute: false,
|
|
103
|
+
offline: true,
|
|
104
|
+
sources: [
|
|
105
|
+
"arduinoLibraryProperties",
|
|
106
|
+
"cinderCinderblockXml",
|
|
107
|
+
"codemetaJson",
|
|
108
|
+
"gitConfig",
|
|
109
|
+
"githubActions",
|
|
110
|
+
"goGoMod",
|
|
111
|
+
"goGoreleaserYaml",
|
|
112
|
+
"javaPomXml",
|
|
113
|
+
"licenseFile",
|
|
114
|
+
"metadataFile",
|
|
115
|
+
"metascope",
|
|
116
|
+
"nodePackageJson",
|
|
117
|
+
"obsidianPluginManifestJson",
|
|
118
|
+
"openframeworksAddonConfigMk",
|
|
119
|
+
"openframeworksInstallXml",
|
|
120
|
+
"processingLibraryProperties",
|
|
121
|
+
"processingSketchProperties",
|
|
122
|
+
"publiccodeYaml",
|
|
123
|
+
"pythonPkgInfo",
|
|
124
|
+
"pythonPyprojectToml",
|
|
125
|
+
"pythonSetupCfg",
|
|
126
|
+
"pythonSetupPy",
|
|
127
|
+
"readmeFile",
|
|
128
|
+
"rubyGemspec",
|
|
129
|
+
"rustCargoToml",
|
|
130
|
+
"xcodeInfoPlist",
|
|
131
|
+
"xcodeProjectPbxproj"
|
|
132
|
+
]
|
|
133
|
+
});
|
|
134
|
+
return metascopeMetadata;
|
|
135
|
+
}
|
|
136
|
+
const GIT_PREFIX_REGEX = /^git\+/;
|
|
137
|
+
const GIT_SUFFIX_REGEX = /\.git$/;
|
|
138
|
+
const TRAILING_SLASH_REGEX = /\/$/;
|
|
139
|
+
const readmeMetadataTemplate = defineTemplate((context) => {
|
|
140
|
+
const { githubActions, licenseFile, metascope, nodePackageJson } = context;
|
|
141
|
+
const codemeta = templates.codemetaJson(context, {});
|
|
142
|
+
const nodePackage = helpers.firstOf(nodePackageJson)?.data;
|
|
143
|
+
const licenseFileData = helpers.firstOf(licenseFile);
|
|
144
|
+
const ciActionFilePath = helpers.ensureArray(githubActions).find((entry) => entry.data.name.toLowerCase() === "ci")?.source;
|
|
145
|
+
const repositoryUrl = codemeta.codeRepository?.replace(GIT_PREFIX_REGEX, "").replace(GIT_SUFFIX_REGEX, "").replace(TRAILING_SLASH_REGEX, "");
|
|
146
|
+
return {
|
|
147
|
+
author: helpers.firstOf(helpers.mixedStringsToArray(helpers.toBasicNames(codemeta.author))),
|
|
148
|
+
ciActionFileName: ciActionFilePath ? path.basename(ciActionFilePath) : void 0,
|
|
149
|
+
description: codemeta.description,
|
|
150
|
+
isPublicNpmPackage: !nodePackage?.name.startsWith("@") || nodePackage.publishConfig?.access === "public",
|
|
151
|
+
issuesUrl: codemeta.issueTracker,
|
|
152
|
+
license: helpers.toBasicLicense(helpers.firstOf(helpers.ensureArray(codemeta.license))),
|
|
153
|
+
licenseFilePath: licenseFileData?.source,
|
|
154
|
+
name: codemeta.name,
|
|
155
|
+
projectDirectory: metascope?.data.options.path === void 0 ? void 0 : `file://${metascope.data.options.path}`,
|
|
156
|
+
repositoryUrl
|
|
157
|
+
};
|
|
158
|
+
});
|
|
159
|
+
let readmeMetadata;
|
|
160
|
+
/**
|
|
161
|
+
* Nice data for readme rules
|
|
162
|
+
*
|
|
163
|
+
* @public
|
|
164
|
+
*/
|
|
165
|
+
async function getReadmeMetadata() {
|
|
166
|
+
if (readmeMetadata !== void 0) return readmeMetadata;
|
|
167
|
+
readmeMetadata = readmeMetadataTemplate(await getContextMetadata(), {});
|
|
168
|
+
return readmeMetadata;
|
|
169
|
+
}
|
|
170
|
+
|
|
149
171
|
//#endregion
|
|
150
172
|
//#region src/lib/readme/rules/badges.ts
|
|
151
173
|
var badges_default = { badges: { async content(options) {
|
|
@@ -157,13 +179,13 @@ var badges_default = { badges: { async content(options) {
|
|
|
157
179
|
npm: z.array(z.string()).optional()
|
|
158
180
|
}).optional().parse(options);
|
|
159
181
|
const metadata = await getReadmeMetadata();
|
|
160
|
-
const { ciActionFileName, license, name,
|
|
182
|
+
const { ciActionFileName, license, name, repositoryUrl } = metadata;
|
|
161
183
|
const badges = [];
|
|
162
184
|
if (validOptions?.npm === void 0) {
|
|
163
185
|
if (metadata.isPublicNpmPackage) badges.push(`[](https://npmjs.com/package/${name})`);
|
|
164
186
|
} else for (const name of validOptions.npm) badges.push(`[](https://npmjs.com/package/${name})`);
|
|
165
187
|
if (license !== void 0) badges.push(`[}-yellow.svg)](https://opensource.org/licenses/${license})`);
|
|
166
|
-
if (ciActionFileName !== void 0 &&
|
|
188
|
+
if (ciActionFileName !== void 0 && repositoryUrl !== void 0) badges.push(`[](${repositoryUrl}/actions/workflows/${ciActionFileName})`);
|
|
167
189
|
if (validOptions?.custom !== void 0) for (const [name, { image, link }] of Object.entries(validOptions.custom)) badges.push(`[](${link})`);
|
|
168
190
|
return badges.join("\n");
|
|
169
191
|
} } };
|
|
@@ -231,24 +253,21 @@ function isUrl(text, lenient = true) {
|
|
|
231
253
|
if (typeof text !== "string") throw new TypeError("Expected a string");
|
|
232
254
|
text = text.trim();
|
|
233
255
|
if (text.includes(" ")) return false;
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
} catch {
|
|
238
|
-
if (lenient) return isUrl(`https://${text}`, false);
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
256
|
+
if (URL.canParse(text)) return true;
|
|
257
|
+
if (lenient) return isUrl(`https://${text}`, false);
|
|
258
|
+
return false;
|
|
241
259
|
}
|
|
242
260
|
|
|
243
261
|
//#endregion
|
|
244
262
|
//#region src/lib/readme/rules/code.ts
|
|
263
|
+
const LEADING_DOT_REGEX = /^\./;
|
|
245
264
|
var code_default = { code: { async content(options) {
|
|
246
265
|
const validOptions = z.object({
|
|
247
266
|
file: z.string(),
|
|
248
267
|
language: z.string().optional(),
|
|
249
268
|
trim: z.boolean().default(true)
|
|
250
269
|
}).parse(options);
|
|
251
|
-
const lang = (path.extname(validOptions.file) ?? "").replace(
|
|
270
|
+
const lang = (path.extname(validOptions.file) ?? "").replace(LEADING_DOT_REGEX, "");
|
|
252
271
|
const exampleCode = await fs.readFile(path.join(process.cwd(), validOptions.file), "utf8");
|
|
253
272
|
return `\`\`\`${lang}\n${validOptions.trim ? exampleCode.trim() : exampleCode}\n\`\`\``;
|
|
254
273
|
} } };
|
|
@@ -309,8 +328,9 @@ var title_default = { title: {
|
|
|
309
328
|
},
|
|
310
329
|
order: 2
|
|
311
330
|
} };
|
|
331
|
+
const SPLIT_FOR_TITLE_CASE_REGEX = /[ _-]+/;
|
|
312
332
|
function makeTitleCase(text) {
|
|
313
|
-
return text.split(
|
|
333
|
+
return text.split(SPLIT_FOR_TITLE_CASE_REGEX).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
314
334
|
}
|
|
315
335
|
|
|
316
336
|
//#endregion
|
|
@@ -497,6 +517,19 @@ var rules_default = {
|
|
|
497
517
|
|
|
498
518
|
//#endregion
|
|
499
519
|
//#region src/lib/config.ts
|
|
520
|
+
let _configExplorer;
|
|
521
|
+
let _additionalConfigExplorer;
|
|
522
|
+
function getConfigExplorer() {
|
|
523
|
+
_configExplorer ??= cosmiconfig("mdat", { loaders: { ".ts": TypeScriptLoader() } });
|
|
524
|
+
return _configExplorer;
|
|
525
|
+
}
|
|
526
|
+
function getAdditionalConfigExplorer() {
|
|
527
|
+
_additionalConfigExplorer ??= cosmiconfig("mdat", { loaders: {
|
|
528
|
+
".json": mdatJsonLoader,
|
|
529
|
+
".ts": TypeScriptLoader()
|
|
530
|
+
} });
|
|
531
|
+
return _additionalConfigExplorer;
|
|
532
|
+
}
|
|
500
533
|
/**
|
|
501
534
|
* Load and validate mdat configuration.
|
|
502
535
|
* Uses cosmiconfig to search in the usual places.
|
|
@@ -504,11 +537,9 @@ var rules_default = {
|
|
|
504
537
|
*/
|
|
505
538
|
async function loadConfig(options) {
|
|
506
539
|
const { additionalConfig, defaults = rules_default, searchFrom } = options ?? {};
|
|
507
|
-
resetReadmeMetadata();
|
|
508
|
-
resetContextMetadata();
|
|
509
540
|
let finalConfig = { mdat: `Powered by the Markdown Autophagic Template system: [mdat](https://github.com/kitschpatrol/mdat).` };
|
|
510
541
|
if (defaults) finalConfig = deepMergeDefined(finalConfig, defaults);
|
|
511
|
-
const results = await
|
|
542
|
+
const results = await getConfigExplorer().search(searchFrom);
|
|
512
543
|
if (results) {
|
|
513
544
|
const { config, filepath } = results;
|
|
514
545
|
let possibleRules = config;
|
|
@@ -523,10 +554,6 @@ async function loadConfig(options) {
|
|
|
523
554
|
}
|
|
524
555
|
if (additionalConfig !== void 0) {
|
|
525
556
|
const additionalConfigArray = Array.isArray(additionalConfig) ? additionalConfig : [additionalConfig];
|
|
526
|
-
const configExplorer2 = cosmiconfig("mdat", { loaders: {
|
|
527
|
-
".json": mdatJsonLoader,
|
|
528
|
-
".ts": TypeScriptLoader()
|
|
529
|
-
} });
|
|
530
557
|
for (const configOrPath of additionalConfigArray) {
|
|
531
558
|
let loaded;
|
|
532
559
|
if (typeof configOrPath === "string") {
|
|
@@ -535,7 +562,7 @@ async function loadConfig(options) {
|
|
|
535
562
|
config: mdatJsonLoader(configOrPath, await fs.readFile(configOrPath, "utf8")),
|
|
536
563
|
filepath: configOrPath
|
|
537
564
|
};
|
|
538
|
-
else results = await
|
|
565
|
+
else results = await getAdditionalConfigExplorer().load(configOrPath);
|
|
539
566
|
if (results === null || results === void 0) continue;
|
|
540
567
|
const { config: loadedConfig, filepath } = results;
|
|
541
568
|
log.debug(`Loaded additional config from "${filepath}"`);
|
|
@@ -559,20 +586,26 @@ function validateConfig(value) {
|
|
|
559
586
|
|
|
560
587
|
//#endregion
|
|
561
588
|
//#region src/lib/format.ts
|
|
589
|
+
let cachedPrettier;
|
|
590
|
+
const configCache = /* @__PURE__ */ new Map();
|
|
562
591
|
/**
|
|
563
592
|
* Format a markdown string with Prettier, using config discovered from the file path.
|
|
564
593
|
* Requires `prettier` to be installed as a peer dependency.
|
|
565
594
|
*/
|
|
566
595
|
async function formatWithPrettier(content, filePath) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
prettier = await import("prettier");
|
|
596
|
+
if (cachedPrettier === void 0) try {
|
|
597
|
+
cachedPrettier = await import("prettier");
|
|
570
598
|
} catch {
|
|
571
599
|
throw new Error("The --format flag requires `prettier` to be installed. Run: pnpm add -D prettier");
|
|
572
600
|
}
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
601
|
+
const configKey = filePath ? path.dirname(filePath) : process.cwd();
|
|
602
|
+
let config = configCache.get(configKey);
|
|
603
|
+
if (config === void 0 && !configCache.has(configKey)) {
|
|
604
|
+
config = await cachedPrettier.resolveConfig(filePath ?? process.cwd());
|
|
605
|
+
configCache.set(configKey, config);
|
|
606
|
+
if (config) log.debug(`Using Prettier config from "${config.filepath}" for "${filePath ?? process.cwd()}"`);
|
|
607
|
+
}
|
|
608
|
+
return cachedPrettier.format(content, {
|
|
576
609
|
...config,
|
|
577
610
|
filepath: filePath,
|
|
578
611
|
parser: "markdown"
|
|
@@ -614,12 +647,13 @@ function ensureArray(value) {
|
|
|
614
647
|
if (value === void 0 || value === null) return [];
|
|
615
648
|
return Array.isArray(value) ? value : [value];
|
|
616
649
|
}
|
|
650
|
+
const README_SEARCH_REGEX = /^readme(?:\.\w+)?$/i;
|
|
617
651
|
/**
|
|
618
652
|
* Finds a readme file in the current working directory (case-insensitive).
|
|
619
653
|
*/
|
|
620
654
|
async function findReadme() {
|
|
621
655
|
log.debug("Searching for readme in current directory...");
|
|
622
|
-
const readme = (await fs.readdir(process.cwd())).find((entry) =>
|
|
656
|
+
const readme = (await fs.readdir(process.cwd())).find((entry) => README_SEARCH_REGEX.test(entry));
|
|
623
657
|
if (readme !== void 0) {
|
|
624
658
|
const absolutePath = path.resolve(readme);
|
|
625
659
|
log.debug(`Found readme at "${absolutePath}"`);
|
|
@@ -635,7 +669,9 @@ async function findReadmeThrows() {
|
|
|
635
669
|
if (readme === void 0) throw new Error("No readme found");
|
|
636
670
|
return readme;
|
|
637
671
|
}
|
|
672
|
+
let cachedAmbientRemarkConfig;
|
|
638
673
|
async function loadAmbientRemarkConfig() {
|
|
674
|
+
if (cachedAmbientRemarkConfig !== void 0) return cachedAmbientRemarkConfig;
|
|
639
675
|
const ambientConfig = new Configuration({
|
|
640
676
|
cwd: process.cwd(),
|
|
641
677
|
detectConfig: true,
|
|
@@ -656,32 +692,47 @@ async function loadAmbientRemarkConfig() {
|
|
|
656
692
|
const { filePath } = configResult;
|
|
657
693
|
if (filePath === void 0) log.debug("No ambient Remark configuration file found");
|
|
658
694
|
else log.debug(`Found and loaded ambient Remark configuration from "${filePath}"`);
|
|
659
|
-
|
|
695
|
+
cachedAmbientRemarkConfig = stripLintPlugins(configResult);
|
|
696
|
+
return cachedAmbientRemarkConfig;
|
|
660
697
|
}
|
|
661
698
|
log.debug("No ambient Remark configuration found");
|
|
662
|
-
|
|
699
|
+
cachedAmbientRemarkConfig = {
|
|
663
700
|
filePath: void 0,
|
|
664
701
|
plugins: [],
|
|
665
702
|
settings: {}
|
|
666
703
|
};
|
|
704
|
+
return cachedAmbientRemarkConfig;
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Strip remark-lint plugins from an ambient config. Lint plugins only produce
|
|
708
|
+
* VFile warnings and never modify the AST or output — running them during
|
|
709
|
+
* expansion is pure overhead.
|
|
710
|
+
*/
|
|
711
|
+
function stripLintPlugins(config) {
|
|
712
|
+
return {
|
|
713
|
+
...config,
|
|
714
|
+
plugins: config.plugins.filter((entry) => {
|
|
715
|
+
const plugin = Array.isArray(entry) ? entry[0] : entry;
|
|
716
|
+
if (typeof plugin !== "function") return true;
|
|
717
|
+
const { name } = plugin;
|
|
718
|
+
return name !== "remarkLint" && !name.startsWith("remark-lint:") && name !== "remarkValidateLinks";
|
|
719
|
+
})
|
|
720
|
+
};
|
|
667
721
|
}
|
|
668
722
|
|
|
669
723
|
//#endregion
|
|
670
724
|
//#region src/lib/processors.ts
|
|
671
725
|
async function processFiles(files, loader, processorGetter, name, output, config) {
|
|
672
|
-
const resolvedConfig = await loader({ additionalConfig: config });
|
|
673
|
-
const localRemarkConfiguration = await loadAmbientRemarkConfig();
|
|
726
|
+
const [resolvedConfig, localRemarkConfiguration] = await Promise.all([loader({ additionalConfig: config }), loadAmbientRemarkConfig()]);
|
|
674
727
|
const inputOutputPaths = await getInputOutputPaths(ensureArray(files), output, name, "md");
|
|
675
|
-
const results = [];
|
|
676
728
|
const resolvedProcessor = processorGetter(resolvedConfig, localRemarkConfiguration);
|
|
677
|
-
|
|
729
|
+
return await Promise.all(inputOutputPaths.map(async ({ input, name, output }) => {
|
|
678
730
|
const inputFile = await read(input);
|
|
679
731
|
const result = await resolvedProcessor.process(inputFile);
|
|
680
732
|
result.dirname = output;
|
|
681
733
|
result.basename = name;
|
|
682
|
-
|
|
683
|
-
}
|
|
684
|
-
return results;
|
|
734
|
+
return result;
|
|
735
|
+
}));
|
|
685
736
|
}
|
|
686
737
|
function getExpandProcessor(config, ambientRemarkConfig) {
|
|
687
738
|
return remark().use({ settings: {
|
|
@@ -870,11 +921,10 @@ async function check(files, config, options) {
|
|
|
870
921
|
files ??= await findReadmeThrows();
|
|
871
922
|
const { read } = await import("to-vfile");
|
|
872
923
|
const resolvedFiles = Array.isArray(files) ? files : [files];
|
|
873
|
-
const originals = await Promise.all(resolvedFiles.map(async (f) => read(f)));
|
|
874
|
-
const results = await processFiles(files, loadConfig, getExpandProcessor, void 0, void 0, config);
|
|
924
|
+
const [originals, results] = await Promise.all([Promise.all(resolvedFiles.map(async (f) => read(f))), processFiles(files, loadConfig, getExpandProcessor, void 0, void 0, config)]);
|
|
875
925
|
if (options?.format) await formatResults(results);
|
|
876
926
|
return results.map((result, i) => ({
|
|
877
|
-
inSync: originals[i].toString() === result.toString(),
|
|
927
|
+
inSync: originals[i].toString().replaceAll("\r\n", "\n") === result.toString(),
|
|
878
928
|
result
|
|
879
929
|
}));
|
|
880
930
|
}
|
|
@@ -961,7 +1011,11 @@ try {
|
|
|
961
1011
|
setLogger$2(createLogger({
|
|
962
1012
|
name,
|
|
963
1013
|
verbose: argv.verbose ?? false,
|
|
964
|
-
logToConsole: {
|
|
1014
|
+
logToConsole: {
|
|
1015
|
+
showLevel: false,
|
|
1016
|
+
showName: false,
|
|
1017
|
+
showTime: false
|
|
1018
|
+
}
|
|
965
1019
|
}));
|
|
966
1020
|
}).command(["$0 [files..] [options]", "expand [files..] [options]"], "Expand MDAT placeholder comments. If no files are provided, the closest readme.md is expanded.", (yargs) => yargs.positional(...filesPositional).option(configOption).option(outputOption).option(nameOption).option(printOption).option(formatOption), async ({ config, files, format, name, output, print }) => {
|
|
967
1021
|
logConflicts({
|
|
@@ -987,14 +1041,14 @@ try {
|
|
|
987
1041
|
reporterMdat(results);
|
|
988
1042
|
log.debug(`Collapsed comments in ${prettyMilliseconds(performance.now() - startTime)}.`);
|
|
989
1043
|
process.exitCode = getExitCode(results);
|
|
990
|
-
}).command("check [files..] [options]", "Check if MDAT placeholder comments are up to date. Exits with code 1 if any files
|
|
1044
|
+
}).command("check [files..] [options]", "Check if MDAT placeholder comments are up to date. Exits with code 1 if any files have stale or unexpanded content.", (yargs) => yargs.positional(...filesPositional).option(configOption).option(formatOption), async ({ config, files, format }) => {
|
|
991
1045
|
const results = await check(files, collectConfig(config), { format });
|
|
992
1046
|
let allInSync = true;
|
|
993
1047
|
for (const { inSync, result } of results) {
|
|
994
1048
|
const filePath = result.path || "unknown";
|
|
995
|
-
if (inSync) log.
|
|
1049
|
+
if (inSync) log.debug(`${picocolors.green("Up to date")}: ${filePath}`);
|
|
996
1050
|
else {
|
|
997
|
-
log.
|
|
1051
|
+
log.warn(`${picocolors.red("Stale content")}: ${filePath}`);
|
|
998
1052
|
allInSync = false;
|
|
999
1053
|
}
|
|
1000
1054
|
}
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Rule, Rule as Rule$1 } from "remark-mdat";
|
|
2
|
+
import { ILogBasic, ILogLayer } from "lognow";
|
|
2
3
|
import * as _$metascope from "metascope";
|
|
3
4
|
import { MetadataContext } from "metascope";
|
|
4
|
-
import { ILogBasic, ILogLayer } from "lognow";
|
|
5
5
|
import { VFile } from "vfile";
|
|
6
6
|
|
|
7
7
|
//#region src/lib/config.d.ts
|
|
@@ -111,8 +111,8 @@ declare function check(files?: string | string[], config?: ConfigToLoad, options
|
|
|
111
111
|
//#region src/lib/context.d.ts
|
|
112
112
|
/**
|
|
113
113
|
* Get a bunch of platform-agnostic local metadata via metascope, exposed
|
|
114
|
-
* primarily for plugin developers.
|
|
115
|
-
*
|
|
114
|
+
* primarily for plugin developers. Result is memoized the result.
|
|
115
|
+
*
|
|
116
116
|
* @throws {Error} If no package.json is found
|
|
117
117
|
*/
|
|
118
118
|
declare function getContextMetadata(): Promise<MetadataContext>;
|
|
@@ -126,11 +126,13 @@ declare const readmeMetadataTemplate: _$metascope.Template<{
|
|
|
126
126
|
licenseFilePath: string | undefined;
|
|
127
127
|
name: string | undefined;
|
|
128
128
|
projectDirectory: string | undefined;
|
|
129
|
-
|
|
129
|
+
repositoryUrl: string | undefined;
|
|
130
130
|
}>;
|
|
131
131
|
type ReadmeMetadata = ReturnType<typeof readmeMetadataTemplate>;
|
|
132
132
|
/**
|
|
133
133
|
* Nice data for readme rules
|
|
134
|
+
*
|
|
135
|
+
* @public
|
|
134
136
|
*/
|
|
135
137
|
declare function getReadmeMetadata(): Promise<{
|
|
136
138
|
author: string | undefined;
|
|
@@ -142,8 +144,13 @@ declare function getReadmeMetadata(): Promise<{
|
|
|
142
144
|
licenseFilePath: string | undefined;
|
|
143
145
|
name: string | undefined;
|
|
144
146
|
projectDirectory: string | undefined;
|
|
145
|
-
|
|
147
|
+
repositoryUrl: string | undefined;
|
|
146
148
|
}>;
|
|
149
|
+
/**
|
|
150
|
+
* Reset all cached metadata. Call between tests or when the underlying project
|
|
151
|
+
* files may have changed on disk.
|
|
152
|
+
*/
|
|
153
|
+
declare function resetMetadataCaches(): void;
|
|
147
154
|
//#endregion
|
|
148
155
|
//#region src/lib/log.d.ts
|
|
149
156
|
/**
|
|
@@ -153,4 +160,4 @@ declare function getReadmeMetadata(): Promise<{
|
|
|
153
160
|
*/
|
|
154
161
|
declare function setLogger(logger?: ILogBasic | ILogLayer): void;
|
|
155
162
|
//#endregion
|
|
156
|
-
export { type Config, type ReadmeMetadata, type Rule, check, collapse, collapseString, createReadme as create, createReadmeInteractive as createInteractive, defineConfig, expand, expandString, getContextMetadata, getReadmeMetadata, loadConfig, mergeConfig, setLogger };
|
|
163
|
+
export { type Config, type ReadmeMetadata, type Rule, check, collapse, collapseString, createReadme as create, createReadmeInteractive as createInteractive, defineConfig, expand, expandString, getContextMetadata, getReadmeMetadata, loadConfig, mergeConfig, resetMetadataCaches, setLogger };
|
package/dist/lib/index.js
CHANGED
|
@@ -5,9 +5,9 @@ import path from "node:path";
|
|
|
5
5
|
import picocolors from "picocolors";
|
|
6
6
|
import plur from "plur";
|
|
7
7
|
import { getSoleRule, mdatClean, mdatExpand, mdatSplit, rulesSchema, setLogger as setLogger$1 } from "remark-mdat";
|
|
8
|
-
import { defineTemplate, getMetadata, helpers, setLogger as setLogger$2, templates } from "metascope";
|
|
9
8
|
import { deepmerge } from "deepmerge-ts";
|
|
10
9
|
import { createLogger, getChildLogger, injectionHelper } from "lognow";
|
|
10
|
+
import { defineTemplate, getMetadata, helpers, setLogger as setLogger$2, templates } from "metascope";
|
|
11
11
|
import { z } from "zod";
|
|
12
12
|
import { globby } from "globby";
|
|
13
13
|
import { isFile, isFileSync } from "path-type";
|
|
@@ -22,24 +22,111 @@ import { VFile } from "vfile";
|
|
|
22
22
|
import { Configuration } from "unified-engine";
|
|
23
23
|
import untildify from "untildify";
|
|
24
24
|
import { confirm, group, intro, note, outro, select } from "@clack/prompts";
|
|
25
|
+
//#region src/lib/deep-merge-defined.ts
|
|
26
|
+
function stripUndefinedDeep(object) {
|
|
27
|
+
if (Array.isArray(object)) return object.map((v) => v && typeof v === "object" ? stripUndefinedDeep(v) : v).filter((v) => v !== void 0);
|
|
28
|
+
return Object.entries(object).map(([k, v]) => [k, v && typeof v === "object" ? stripUndefinedDeep(v) : v]).reduce((acc, [k, v]) => v === void 0 ? acc : {
|
|
29
|
+
...acc,
|
|
30
|
+
[k]: v
|
|
31
|
+
}, {});
|
|
32
|
+
}
|
|
33
|
+
function deepMergeDefined(...objects) {
|
|
34
|
+
return deepmerge(...objects.map((v, i) => i === 0 ? v : stripUndefinedDeep(v)));
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/lib/log.ts
|
|
38
|
+
/**
|
|
39
|
+
* The default logger instance for the library.
|
|
40
|
+
*/
|
|
41
|
+
let log = createLogger({
|
|
42
|
+
logToConsole: { showTime: false },
|
|
43
|
+
name: "mdat"
|
|
44
|
+
});
|
|
45
|
+
setLogger$1(getChildLogger(log, "remark-mdat"));
|
|
46
|
+
setLogger$2(getChildLogger(log, "metascope"));
|
|
47
|
+
/**
|
|
48
|
+
* Set the logger instance for the module.
|
|
49
|
+
* Export this for library consumers to inject their own logger.
|
|
50
|
+
* @param logger - Accepts either a LogLayer instance or a Console- or Stream-like log target
|
|
51
|
+
*/
|
|
52
|
+
function setLogger(logger) {
|
|
53
|
+
log = injectionHelper(logger);
|
|
54
|
+
setLogger$1(getChildLogger(log, "remark-mdat"));
|
|
55
|
+
setLogger$2(getChildLogger(log, "metascope"));
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/lib/mdat-json-loader.ts
|
|
59
|
+
/**
|
|
60
|
+
* Lets arbitrary JSON objects (like from package.json) become reasonably good mdat rule sets
|
|
61
|
+
* HOWEVER cosmiconfig treats package.json as a special case and will always load only specific keys from it
|
|
62
|
+
* So we have to intercept and load them manually in config.ts
|
|
63
|
+
*/
|
|
64
|
+
function mdatJsonLoader(filePath, content) {
|
|
65
|
+
const defaultJsonLoader = defaultLoaders[".json"];
|
|
66
|
+
return flattenJson(defaultJsonLoader(filePath, content));
|
|
67
|
+
}
|
|
68
|
+
function flattenJson(jsonObject, parentKey = "", result = {}) {
|
|
69
|
+
for (const [key, value] of Object.entries(jsonObject)) {
|
|
70
|
+
const fullPath = parentKey ? `${parentKey}.${key}` : key;
|
|
71
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) flattenJson(value, fullPath, result);
|
|
72
|
+
else if (value === null) result[fullPath] = "null";
|
|
73
|
+
else result[fullPath] = value.toString();
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
//#endregion
|
|
25
78
|
//#region src/lib/context.ts
|
|
26
79
|
let metascopeMetadata;
|
|
27
80
|
/**
|
|
28
81
|
* Get a bunch of platform-agnostic local metadata via metascope, exposed
|
|
29
|
-
* primarily for plugin developers.
|
|
30
|
-
*
|
|
82
|
+
* primarily for plugin developers. Result is memoized the result.
|
|
83
|
+
*
|
|
31
84
|
* @throws {Error} If no package.json is found
|
|
32
85
|
*/
|
|
33
86
|
async function getContextMetadata() {
|
|
34
87
|
if (metascopeMetadata !== void 0) return metascopeMetadata;
|
|
35
88
|
metascopeMetadata = await getMetadata({
|
|
36
89
|
absolute: false,
|
|
37
|
-
offline: true
|
|
90
|
+
offline: true,
|
|
91
|
+
sources: [
|
|
92
|
+
"arduinoLibraryProperties",
|
|
93
|
+
"cinderCinderblockXml",
|
|
94
|
+
"codemetaJson",
|
|
95
|
+
"gitConfig",
|
|
96
|
+
"githubActions",
|
|
97
|
+
"goGoMod",
|
|
98
|
+
"goGoreleaserYaml",
|
|
99
|
+
"javaPomXml",
|
|
100
|
+
"licenseFile",
|
|
101
|
+
"metadataFile",
|
|
102
|
+
"metascope",
|
|
103
|
+
"nodePackageJson",
|
|
104
|
+
"obsidianPluginManifestJson",
|
|
105
|
+
"openframeworksAddonConfigMk",
|
|
106
|
+
"openframeworksInstallXml",
|
|
107
|
+
"processingLibraryProperties",
|
|
108
|
+
"processingSketchProperties",
|
|
109
|
+
"publiccodeYaml",
|
|
110
|
+
"pythonPkgInfo",
|
|
111
|
+
"pythonPyprojectToml",
|
|
112
|
+
"pythonSetupCfg",
|
|
113
|
+
"pythonSetupPy",
|
|
114
|
+
"readmeFile",
|
|
115
|
+
"rubyGemspec",
|
|
116
|
+
"rustCargoToml",
|
|
117
|
+
"xcodeInfoPlist",
|
|
118
|
+
"xcodeProjectPbxproj"
|
|
119
|
+
]
|
|
38
120
|
});
|
|
39
121
|
return metascopeMetadata;
|
|
40
122
|
}
|
|
123
|
+
const GIT_PREFIX_REGEX = /^git\+/;
|
|
124
|
+
const GIT_SUFFIX_REGEX = /\.git$/;
|
|
125
|
+
const TRAILING_SLASH_REGEX = /\/$/;
|
|
41
126
|
/**
|
|
42
127
|
* Reset
|
|
128
|
+
*
|
|
129
|
+
* @public
|
|
43
130
|
*/
|
|
44
131
|
function resetContextMetadata() {
|
|
45
132
|
metascopeMetadata = void 0;
|
|
@@ -50,7 +137,7 @@ const readmeMetadataTemplate = defineTemplate((context) => {
|
|
|
50
137
|
const nodePackage = helpers.firstOf(nodePackageJson)?.data;
|
|
51
138
|
const licenseFileData = helpers.firstOf(licenseFile);
|
|
52
139
|
const ciActionFilePath = helpers.ensureArray(githubActions).find((entry) => entry.data.name.toLowerCase() === "ci")?.source;
|
|
53
|
-
const
|
|
140
|
+
const repositoryUrl = codemeta.codeRepository?.replace(GIT_PREFIX_REGEX, "").replace(GIT_SUFFIX_REGEX, "").replace(TRAILING_SLASH_REGEX, "");
|
|
54
141
|
return {
|
|
55
142
|
author: helpers.firstOf(helpers.mixedStringsToArray(helpers.toBasicNames(codemeta.author))),
|
|
56
143
|
ciActionFileName: ciActionFilePath ? path.basename(ciActionFilePath) : void 0,
|
|
@@ -61,12 +148,14 @@ const readmeMetadataTemplate = defineTemplate((context) => {
|
|
|
61
148
|
licenseFilePath: licenseFileData?.source,
|
|
62
149
|
name: codemeta.name,
|
|
63
150
|
projectDirectory: metascope?.data.options.path === void 0 ? void 0 : `file://${metascope.data.options.path}`,
|
|
64
|
-
|
|
151
|
+
repositoryUrl
|
|
65
152
|
};
|
|
66
153
|
});
|
|
67
154
|
let readmeMetadata;
|
|
68
155
|
/**
|
|
69
156
|
* Nice data for readme rules
|
|
157
|
+
*
|
|
158
|
+
* @public
|
|
70
159
|
*/
|
|
71
160
|
async function getReadmeMetadata() {
|
|
72
161
|
if (readmeMetadata !== void 0) return readmeMetadata;
|
|
@@ -75,62 +164,19 @@ async function getReadmeMetadata() {
|
|
|
75
164
|
}
|
|
76
165
|
/**
|
|
77
166
|
* Reset
|
|
167
|
+
*
|
|
168
|
+
* @public
|
|
78
169
|
*/
|
|
79
170
|
function resetReadmeMetadata() {
|
|
80
171
|
readmeMetadata = void 0;
|
|
81
172
|
}
|
|
82
|
-
//#endregion
|
|
83
|
-
//#region src/lib/deep-merge-defined.ts
|
|
84
|
-
function stripUndefinedDeep(object) {
|
|
85
|
-
if (Array.isArray(object)) return object.map((v) => v && typeof v === "object" ? stripUndefinedDeep(v) : v).filter((v) => v !== void 0);
|
|
86
|
-
return Object.entries(object).map(([k, v]) => [k, v && typeof v === "object" ? stripUndefinedDeep(v) : v]).reduce((acc, [k, v]) => v === void 0 ? acc : {
|
|
87
|
-
...acc,
|
|
88
|
-
[k]: v
|
|
89
|
-
}, {});
|
|
90
|
-
}
|
|
91
|
-
function deepMergeDefined(...objects) {
|
|
92
|
-
return deepmerge(...objects.map((v, i) => i === 0 ? v : stripUndefinedDeep(v)));
|
|
93
|
-
}
|
|
94
|
-
//#endregion
|
|
95
|
-
//#region src/lib/log.ts
|
|
96
|
-
/**
|
|
97
|
-
* The default logger instance for the library.
|
|
98
|
-
*/
|
|
99
|
-
let log = createLogger({
|
|
100
|
-
logToConsole: { showTime: false },
|
|
101
|
-
name: "mdat"
|
|
102
|
-
});
|
|
103
|
-
setLogger$1(getChildLogger(log, "remark-mdat"));
|
|
104
|
-
setLogger$2(getChildLogger(log, "metascope"));
|
|
105
|
-
/**
|
|
106
|
-
* Set the logger instance for the module.
|
|
107
|
-
* Export this for library consumers to inject their own logger.
|
|
108
|
-
* @param logger - Accepts either a LogLayer instance or a Console- or Stream-like log target
|
|
109
|
-
*/
|
|
110
|
-
function setLogger(logger) {
|
|
111
|
-
log = injectionHelper(logger);
|
|
112
|
-
setLogger$1(getChildLogger(log, "remark-mdat"));
|
|
113
|
-
setLogger$2(getChildLogger(log, "metascope"));
|
|
114
|
-
}
|
|
115
|
-
//#endregion
|
|
116
|
-
//#region src/lib/mdat-json-loader.ts
|
|
117
173
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* So we have to intercept and load them manually in config.ts
|
|
174
|
+
* Reset all cached metadata. Call between tests or when the underlying project
|
|
175
|
+
* files may have changed on disk.
|
|
121
176
|
*/
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
function flattenJson(jsonObject, parentKey = "", result = {}) {
|
|
127
|
-
for (const [key, value] of Object.entries(jsonObject)) {
|
|
128
|
-
const fullPath = parentKey ? `${parentKey}.${key}` : key;
|
|
129
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) flattenJson(value, fullPath, result);
|
|
130
|
-
else if (value === null) result[fullPath] = "null";
|
|
131
|
-
else result[fullPath] = value.toString();
|
|
132
|
-
}
|
|
133
|
-
return result;
|
|
177
|
+
function resetMetadataCaches() {
|
|
178
|
+
resetContextMetadata();
|
|
179
|
+
resetReadmeMetadata();
|
|
134
180
|
}
|
|
135
181
|
//#endregion
|
|
136
182
|
//#region src/lib/readme/rules/badges.ts
|
|
@@ -143,13 +189,13 @@ var badges_default = { badges: { async content(options) {
|
|
|
143
189
|
npm: z.array(z.string()).optional()
|
|
144
190
|
}).optional().parse(options);
|
|
145
191
|
const metadata = await getReadmeMetadata();
|
|
146
|
-
const { ciActionFileName, license, name,
|
|
192
|
+
const { ciActionFileName, license, name, repositoryUrl } = metadata;
|
|
147
193
|
const badges = [];
|
|
148
194
|
if (validOptions?.npm === void 0) {
|
|
149
195
|
if (metadata.isPublicNpmPackage) badges.push(`[](https://npmjs.com/package/${name})`);
|
|
150
196
|
} else for (const name of validOptions.npm) badges.push(`[](https://npmjs.com/package/${name})`);
|
|
151
197
|
if (license !== void 0) badges.push(`[}-yellow.svg)](https://opensource.org/licenses/${license})`);
|
|
152
|
-
if (ciActionFileName !== void 0 &&
|
|
198
|
+
if (ciActionFileName !== void 0 && repositoryUrl !== void 0) badges.push(`[](${repositoryUrl}/actions/workflows/${ciActionFileName})`);
|
|
153
199
|
if (validOptions?.custom !== void 0) for (const [name, { image, link }] of Object.entries(validOptions.custom)) badges.push(`[](${link})`);
|
|
154
200
|
return badges.join("\n");
|
|
155
201
|
} } };
|
|
@@ -216,23 +262,20 @@ function isUrl(text, lenient = true) {
|
|
|
216
262
|
if (typeof text !== "string") throw new TypeError("Expected a string");
|
|
217
263
|
text = text.trim();
|
|
218
264
|
if (text.includes(" ")) return false;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
} catch {
|
|
223
|
-
if (lenient) return isUrl(`https://${text}`, false);
|
|
224
|
-
return false;
|
|
225
|
-
}
|
|
265
|
+
if (URL.canParse(text)) return true;
|
|
266
|
+
if (lenient) return isUrl(`https://${text}`, false);
|
|
267
|
+
return false;
|
|
226
268
|
}
|
|
227
269
|
//#endregion
|
|
228
270
|
//#region src/lib/readme/rules/code.ts
|
|
271
|
+
const LEADING_DOT_REGEX = /^\./;
|
|
229
272
|
var code_default = { code: { async content(options) {
|
|
230
273
|
const validOptions = z.object({
|
|
231
274
|
file: z.string(),
|
|
232
275
|
language: z.string().optional(),
|
|
233
276
|
trim: z.boolean().default(true)
|
|
234
277
|
}).parse(options);
|
|
235
|
-
const lang = (path.extname(validOptions.file) ?? "").replace(
|
|
278
|
+
const lang = (path.extname(validOptions.file) ?? "").replace(LEADING_DOT_REGEX, "");
|
|
236
279
|
const exampleCode = await fs.readFile(path.join(process.cwd(), validOptions.file), "utf8");
|
|
237
280
|
return `\`\`\`${lang}\n${validOptions.trim ? exampleCode.trim() : exampleCode}\n\`\`\``;
|
|
238
281
|
} } };
|
|
@@ -287,8 +330,9 @@ var title_default = { title: {
|
|
|
287
330
|
},
|
|
288
331
|
order: 2
|
|
289
332
|
} };
|
|
333
|
+
const SPLIT_FOR_TITLE_CASE_REGEX = /[ _-]+/;
|
|
290
334
|
function makeTitleCase(text) {
|
|
291
|
-
return text.split(
|
|
335
|
+
return text.split(SPLIT_FOR_TITLE_CASE_REGEX).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
292
336
|
}
|
|
293
337
|
//#endregion
|
|
294
338
|
//#region src/lib/readme/rules/header.ts
|
|
@@ -467,6 +511,19 @@ var rules_default = {
|
|
|
467
511
|
};
|
|
468
512
|
//#endregion
|
|
469
513
|
//#region src/lib/config.ts
|
|
514
|
+
let _configExplorer;
|
|
515
|
+
let _additionalConfigExplorer;
|
|
516
|
+
function getConfigExplorer() {
|
|
517
|
+
_configExplorer ??= cosmiconfig("mdat", { loaders: { ".ts": TypeScriptLoader() } });
|
|
518
|
+
return _configExplorer;
|
|
519
|
+
}
|
|
520
|
+
function getAdditionalConfigExplorer() {
|
|
521
|
+
_additionalConfigExplorer ??= cosmiconfig("mdat", { loaders: {
|
|
522
|
+
".json": mdatJsonLoader,
|
|
523
|
+
".ts": TypeScriptLoader()
|
|
524
|
+
} });
|
|
525
|
+
return _additionalConfigExplorer;
|
|
526
|
+
}
|
|
470
527
|
/**
|
|
471
528
|
* Load and validate mdat configuration.
|
|
472
529
|
* Uses cosmiconfig to search in the usual places.
|
|
@@ -474,11 +531,9 @@ var rules_default = {
|
|
|
474
531
|
*/
|
|
475
532
|
async function loadConfig(options) {
|
|
476
533
|
const { additionalConfig, defaults = rules_default, searchFrom } = options ?? {};
|
|
477
|
-
resetReadmeMetadata();
|
|
478
|
-
resetContextMetadata();
|
|
479
534
|
let finalConfig = { mdat: `Powered by the Markdown Autophagic Template system: [mdat](https://github.com/kitschpatrol/mdat).` };
|
|
480
535
|
if (defaults) finalConfig = deepMergeDefined(finalConfig, defaults);
|
|
481
|
-
const results = await
|
|
536
|
+
const results = await getConfigExplorer().search(searchFrom);
|
|
482
537
|
if (results) {
|
|
483
538
|
const { config, filepath } = results;
|
|
484
539
|
let possibleRules = config;
|
|
@@ -493,10 +548,6 @@ async function loadConfig(options) {
|
|
|
493
548
|
}
|
|
494
549
|
if (additionalConfig !== void 0) {
|
|
495
550
|
const additionalConfigArray = Array.isArray(additionalConfig) ? additionalConfig : [additionalConfig];
|
|
496
|
-
const configExplorer2 = cosmiconfig("mdat", { loaders: {
|
|
497
|
-
".json": mdatJsonLoader,
|
|
498
|
-
".ts": TypeScriptLoader()
|
|
499
|
-
} });
|
|
500
551
|
for (const configOrPath of additionalConfigArray) {
|
|
501
552
|
let loaded;
|
|
502
553
|
if (typeof configOrPath === "string") {
|
|
@@ -505,7 +556,7 @@ async function loadConfig(options) {
|
|
|
505
556
|
config: mdatJsonLoader(configOrPath, await fs.readFile(configOrPath, "utf8")),
|
|
506
557
|
filepath: configOrPath
|
|
507
558
|
};
|
|
508
|
-
else results = await
|
|
559
|
+
else results = await getAdditionalConfigExplorer().load(configOrPath);
|
|
509
560
|
if (results === null || results === void 0) continue;
|
|
510
561
|
const { config: loadedConfig, filepath } = results;
|
|
511
562
|
log.debug(`Loaded additional config from "${filepath}"`);
|
|
@@ -541,20 +592,26 @@ function defineConfig(config) {
|
|
|
541
592
|
}
|
|
542
593
|
//#endregion
|
|
543
594
|
//#region src/lib/format.ts
|
|
595
|
+
let cachedPrettier;
|
|
596
|
+
const configCache = /* @__PURE__ */ new Map();
|
|
544
597
|
/**
|
|
545
598
|
* Format a markdown string with Prettier, using config discovered from the file path.
|
|
546
599
|
* Requires `prettier` to be installed as a peer dependency.
|
|
547
600
|
*/
|
|
548
601
|
async function formatWithPrettier(content, filePath) {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
prettier = await import("prettier");
|
|
602
|
+
if (cachedPrettier === void 0) try {
|
|
603
|
+
cachedPrettier = await import("prettier");
|
|
552
604
|
} catch {
|
|
553
605
|
throw new Error("The --format flag requires `prettier` to be installed. Run: pnpm add -D prettier");
|
|
554
606
|
}
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
607
|
+
const configKey = filePath ? path.dirname(filePath) : process.cwd();
|
|
608
|
+
let config = configCache.get(configKey);
|
|
609
|
+
if (config === void 0 && !configCache.has(configKey)) {
|
|
610
|
+
config = await cachedPrettier.resolveConfig(filePath ?? process.cwd());
|
|
611
|
+
configCache.set(configKey, config);
|
|
612
|
+
if (config) log.debug(`Using Prettier config from "${config.filepath}" for "${filePath ?? process.cwd()}"`);
|
|
613
|
+
}
|
|
614
|
+
return cachedPrettier.format(content, {
|
|
558
615
|
...config,
|
|
559
616
|
filepath: filePath,
|
|
560
617
|
parser: "markdown"
|
|
@@ -595,12 +652,13 @@ function ensureArray(value) {
|
|
|
595
652
|
if (value === void 0 || value === null) return [];
|
|
596
653
|
return Array.isArray(value) ? value : [value];
|
|
597
654
|
}
|
|
655
|
+
const README_SEARCH_REGEX = /^readme(?:\.\w+)?$/i;
|
|
598
656
|
/**
|
|
599
657
|
* Finds a readme file in the current working directory (case-insensitive).
|
|
600
658
|
*/
|
|
601
659
|
async function findReadme() {
|
|
602
660
|
log.debug("Searching for readme in current directory...");
|
|
603
|
-
const readme = (await fs.readdir(process.cwd())).find((entry) =>
|
|
661
|
+
const readme = (await fs.readdir(process.cwd())).find((entry) => README_SEARCH_REGEX.test(entry));
|
|
604
662
|
if (readme !== void 0) {
|
|
605
663
|
const absolutePath = path.resolve(readme);
|
|
606
664
|
log.debug(`Found readme at "${absolutePath}"`);
|
|
@@ -616,7 +674,9 @@ async function findReadmeThrows() {
|
|
|
616
674
|
if (readme === void 0) throw new Error("No readme found");
|
|
617
675
|
return readme;
|
|
618
676
|
}
|
|
677
|
+
let cachedAmbientRemarkConfig;
|
|
619
678
|
async function loadAmbientRemarkConfig() {
|
|
679
|
+
if (cachedAmbientRemarkConfig !== void 0) return cachedAmbientRemarkConfig;
|
|
620
680
|
const ambientConfig = new Configuration({
|
|
621
681
|
cwd: process.cwd(),
|
|
622
682
|
detectConfig: true,
|
|
@@ -637,34 +697,50 @@ async function loadAmbientRemarkConfig() {
|
|
|
637
697
|
const { filePath } = configResult;
|
|
638
698
|
if (filePath === void 0) log.debug("No ambient Remark configuration file found");
|
|
639
699
|
else log.debug(`Found and loaded ambient Remark configuration from "${filePath}"`);
|
|
640
|
-
|
|
700
|
+
cachedAmbientRemarkConfig = stripLintPlugins(configResult);
|
|
701
|
+
return cachedAmbientRemarkConfig;
|
|
641
702
|
}
|
|
642
703
|
log.debug("No ambient Remark configuration found");
|
|
643
|
-
|
|
704
|
+
cachedAmbientRemarkConfig = {
|
|
644
705
|
filePath: void 0,
|
|
645
706
|
plugins: [],
|
|
646
707
|
settings: {}
|
|
647
708
|
};
|
|
709
|
+
return cachedAmbientRemarkConfig;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Strip remark-lint plugins from an ambient config. Lint plugins only produce
|
|
713
|
+
* VFile warnings and never modify the AST or output — running them during
|
|
714
|
+
* expansion is pure overhead.
|
|
715
|
+
*/
|
|
716
|
+
function stripLintPlugins(config) {
|
|
717
|
+
return {
|
|
718
|
+
...config,
|
|
719
|
+
plugins: config.plugins.filter((entry) => {
|
|
720
|
+
const plugin = Array.isArray(entry) ? entry[0] : entry;
|
|
721
|
+
if (typeof plugin !== "function") return true;
|
|
722
|
+
const { name } = plugin;
|
|
723
|
+
return name !== "remarkLint" && !name.startsWith("remark-lint:") && name !== "remarkValidateLinks";
|
|
724
|
+
})
|
|
725
|
+
};
|
|
648
726
|
}
|
|
649
727
|
//#endregion
|
|
650
728
|
//#region src/lib/processors.ts
|
|
651
729
|
async function processFiles(files, loader, processorGetter, name, output, config) {
|
|
652
|
-
const resolvedConfig = await loader({ additionalConfig: config });
|
|
653
|
-
const localRemarkConfiguration = await loadAmbientRemarkConfig();
|
|
730
|
+
const [resolvedConfig, localRemarkConfiguration] = await Promise.all([loader({ additionalConfig: config }), loadAmbientRemarkConfig()]);
|
|
654
731
|
const inputOutputPaths = await getInputOutputPaths(ensureArray(files), output, name, "md");
|
|
655
|
-
const results = [];
|
|
656
732
|
const resolvedProcessor = processorGetter(resolvedConfig, localRemarkConfiguration);
|
|
657
|
-
|
|
733
|
+
return await Promise.all(inputOutputPaths.map(async ({ input, name, output }) => {
|
|
658
734
|
const inputFile = await read(input);
|
|
659
735
|
const result = await resolvedProcessor.process(inputFile);
|
|
660
736
|
result.dirname = output;
|
|
661
737
|
result.basename = name;
|
|
662
|
-
|
|
663
|
-
}
|
|
664
|
-
return results;
|
|
738
|
+
return result;
|
|
739
|
+
}));
|
|
665
740
|
}
|
|
666
741
|
async function processString(markdown, loader, processorGetter, config) {
|
|
667
|
-
|
|
742
|
+
const [resolvedConfig, localRemarkConfiguration] = await Promise.all([loader({ additionalConfig: config }), loadAmbientRemarkConfig()]);
|
|
743
|
+
return processorGetter(resolvedConfig, localRemarkConfiguration).process(new VFile(markdown));
|
|
668
744
|
}
|
|
669
745
|
function getExpandProcessor(config, ambientRemarkConfig) {
|
|
670
746
|
return remark().use({ settings: {
|
|
@@ -860,11 +936,10 @@ async function check(files, config, options) {
|
|
|
860
936
|
files ??= await findReadmeThrows();
|
|
861
937
|
const { read } = await import("to-vfile");
|
|
862
938
|
const resolvedFiles = Array.isArray(files) ? files : [files];
|
|
863
|
-
const originals = await Promise.all(resolvedFiles.map(async (f) => read(f)));
|
|
864
|
-
const results = await processFiles(files, loadConfig, getExpandProcessor, void 0, void 0, config);
|
|
939
|
+
const [originals, results] = await Promise.all([Promise.all(resolvedFiles.map(async (f) => read(f))), processFiles(files, loadConfig, getExpandProcessor, void 0, void 0, config)]);
|
|
865
940
|
if (options?.format) await formatResults(results);
|
|
866
941
|
return results.map((result, i) => ({
|
|
867
|
-
inSync: originals[i].toString() === result.toString(),
|
|
942
|
+
inSync: originals[i].toString().replaceAll("\r\n", "\n") === result.toString(),
|
|
868
943
|
result
|
|
869
944
|
}));
|
|
870
945
|
}
|
|
@@ -872,4 +947,4 @@ async function formatResults(results) {
|
|
|
872
947
|
for (const file of results) file.value = await formatWithPrettier(file.toString(), file.path || void 0);
|
|
873
948
|
}
|
|
874
949
|
//#endregion
|
|
875
|
-
export { check, collapse, collapseString, createReadme as create, createReadmeInteractive as createInteractive, defineConfig, expand, expandString, getContextMetadata, getReadmeMetadata, loadConfig, mergeConfig, setLogger };
|
|
950
|
+
export { check, collapse, collapseString, createReadme as create, createReadmeInteractive as createInteractive, defineConfig, expand, expandString, getContextMetadata, getReadmeMetadata, loadConfig, mergeConfig, resetMetadataCaches, setLogger };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mdat",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
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",
|
|
@@ -48,9 +48,9 @@
|
|
|
48
48
|
"cosmiconfig-typescript-loader": "^6.2.0",
|
|
49
49
|
"deepmerge-ts": "^7.1.5",
|
|
50
50
|
"globby": "^16.2.0",
|
|
51
|
-
"lognow": "^0.
|
|
51
|
+
"lognow": "^0.6.0",
|
|
52
52
|
"mdast-util-toc": "^7.1.0",
|
|
53
|
-
"metascope": "^0.
|
|
53
|
+
"metascope": "^0.5.0",
|
|
54
54
|
"path-type": "^6.0.0",
|
|
55
55
|
"picocolors": "^1.1.1",
|
|
56
56
|
"plur": "^6.0.0",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"pretty-ms": "^9.3.0",
|
|
59
59
|
"remark": "^15.0.1",
|
|
60
60
|
"remark-gfm": "^4.0.1",
|
|
61
|
-
"remark-mdat": "^2.0.
|
|
61
|
+
"remark-mdat": "^2.0.2",
|
|
62
62
|
"to-vfile": "^8.0.0",
|
|
63
63
|
"type-fest": "^5.5.0",
|
|
64
64
|
"unified-engine": "^11.2.2",
|
|
@@ -69,13 +69,16 @@
|
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@arethetypeswrong/core": "^0.18.2",
|
|
72
|
-
"@kitschpatrol/shared-config": "^
|
|
72
|
+
"@kitschpatrol/shared-config": "^7.0.0",
|
|
73
73
|
"@types/mdast": "^4.0.4",
|
|
74
74
|
"@types/node": "~22.17.2",
|
|
75
75
|
"@types/unist": "^3.0.3",
|
|
76
76
|
"@types/yargs": "^17.0.35",
|
|
77
77
|
"bumpp": "^11.0.1",
|
|
78
78
|
"execa": "^9.6.1",
|
|
79
|
+
"mdat-plugin-cli-help": "^2.0.2",
|
|
80
|
+
"mdat-plugin-example": "^2.0.0",
|
|
81
|
+
"mdat-plugin-tldraw": "^2.0.0",
|
|
79
82
|
"nanoid": "^5.1.7",
|
|
80
83
|
"prettier": "^3.8.1",
|
|
81
84
|
"publint": "^0.3.18",
|
|
@@ -102,11 +105,13 @@
|
|
|
102
105
|
}
|
|
103
106
|
},
|
|
104
107
|
"scripts": {
|
|
108
|
+
"bench": "vitest bench --run --compare test/benchmarks/baseline.json",
|
|
109
|
+
"bench:baseline": "vitest bench --run --outputJson test/benchmarks/baseline.json",
|
|
105
110
|
"build": "tsdown",
|
|
106
111
|
"clean": "git rm -f pnpm-lock.yaml ; git clean -fdX",
|
|
107
112
|
"fix": "ksc fix",
|
|
108
113
|
"lint": "ksc lint",
|
|
109
114
|
"release": "bumpp --commit 'Release: %s' && pnpm run build && NPM_AUTH_TOKEN=$(op read 'op://Personal/npm/token') && pnpm publish",
|
|
110
|
-
"test": "vitest run
|
|
115
|
+
"test": "vitest run"
|
|
111
116
|
}
|
|
112
117
|
}
|
package/readme.md
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
<!--+ Warning: Content inside HTML comment blocks was generated by mdat and may be overwritten. +-->
|
|
2
|
-
|
|
3
1
|
<!-- title -->
|
|
4
2
|
|
|
5
3
|
# mdat
|
|
@@ -10,6 +8,7 @@
|
|
|
10
8
|
|
|
11
9
|
[](https://npmjs.com/package/mdat)
|
|
12
10
|
[](https://opensource.org/licenses/MIT)
|
|
11
|
+
[](https://github.com/kitschpatrol/mdat/actions/workflows/ci.yml)
|
|
13
12
|
|
|
14
13
|
<!-- /badges -->
|
|
15
14
|
|
|
@@ -229,7 +228,7 @@ mdat [command] [files..] [options]
|
|
|
229
228
|
| ---------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
230
229
|
| `expand` | `[files..]` `[options]` | Expand MDAT placeholder comments. If no files are provided, the closest readme.md is expanded. _(Default command.)_ |
|
|
231
230
|
| `collapse` | `[files..]` `[options]` | Collapse MDAT placeholder comments. If no files are provided, the closest readme.md is collapsed. |
|
|
232
|
-
| `check` | `[files..]` `[options]` | Check if MDAT placeholder comments are up to date. Exits with code 1 if any files
|
|
231
|
+
| `check` | `[files..]` `[options]` | Check if MDAT placeholder comments are up to date. Exits with code 1 if any files have stale or unexpanded content. |
|
|
233
232
|
| `create` | `[options]` | Create a new Markdown file from a template. |
|
|
234
233
|
|
|
235
234
|
_See the sections below for more information on each subcommand._
|
|
@@ -285,7 +284,7 @@ mdat collapse [files..] [options]
|
|
|
285
284
|
|
|
286
285
|
#### Subcommand: `mdat check`
|
|
287
286
|
|
|
288
|
-
Check if MDAT placeholder comments are up to date. Exits with code 1 if any files
|
|
287
|
+
Check if MDAT placeholder comments are up to date. Exits with code 1 if any files have stale or unexpanded content.
|
|
289
288
|
|
|
290
289
|
Usage:
|
|
291
290
|
|
|
@@ -587,7 +586,7 @@ See the [Examples section](https://github.com/kitschpatrol/remark-mdat#examples)
|
|
|
587
586
|
| File | Original | Gzip | Brotli |
|
|
588
587
|
| ---------- | -------- | ------ | ------ |
|
|
589
588
|
| .gitignore | 305 B | 245 B | 216 B |
|
|
590
|
-
| readme.md |
|
|
589
|
+
| readme.md | 34.4 kB | 8.3 kB | 6.9 kB |
|
|
591
590
|
|
|
592
591
|
<!-- /size-table -->
|
|
593
592
|
|
|
@@ -738,9 +737,9 @@ In 2.x, arguments **must** use function-call syntax with parentheses
|
|
|
738
737
|
|
|
739
738
|
### New functionality
|
|
740
739
|
|
|
741
|
-
- The new
|
|
740
|
+
- The new `--format` flag runs expanded output through Prettier with local configuration before writing.
|
|
742
741
|
- The badges rule now detects GitHub Actions CI workflows and includes a CI status badge automatically.
|
|
743
|
-
- Check command
|
|
742
|
+
- Check command reimplemented as a simplified dry-run expand and diff and reporting if content is unexpanded or out of date. Respects the `--format` flag.
|
|
744
743
|
|
|
745
744
|
### Rule shape changes
|
|
746
745
|
|
|
@@ -756,14 +755,52 @@ MDAT solves this by turning HTML comments in Markdown into placeholders for dyna
|
|
|
756
755
|
|
|
757
756
|
### Similar projects
|
|
758
757
|
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
-
|
|
758
|
+
There's quite a bit of prior art and similar explorations of this problem space:
|
|
759
|
+
|
|
760
|
+
- Benjamin Lupton's [projectz](https://github.com/bevry/projectz)\
|
|
761
|
+
Goes way back.
|
|
762
|
+
|
|
763
|
+
- David Wells' [Markdown Magic](https://github.com/DavidWells/markdown-magic)\
|
|
764
|
+
I somehow missed the existence of this one until after building out MDAT. It's very similar conceptually, and has a nice ecosystem of plugins.
|
|
765
|
+
|
|
766
|
+
- Titus Wormer's [mdast-zone](https://github.com/syntax-tree/mdast-zone)\
|
|
767
|
+
Allows comments to be used as ranges or markers in Markdown files. Similar tree parsing and walking strategy to MDAT. Mdast-zone uses different syntax for arguments, and requires both opening and closing tags to be present for expansion to occur.
|
|
768
|
+
|
|
762
769
|
- Jason Dent's [inject-markdown](https://github.com/streetsidesoftware/inject-markdown)
|
|
770
|
+
|
|
763
771
|
- lillallol's [md-in-place](https://www.npmjs.com/package/md-in-place)
|
|
764
|
-
|
|
772
|
+
|
|
773
|
+
- [AutoMD](https://automd.unjs.io/)\
|
|
774
|
+
Extremely similar functionality to mdat. The project was initiated around the same time as MDAT, but I didn't find the project until a few years later. Ships in the night.
|
|
775
|
+
|
|
776
|
+
- Franck Abgrall's [readme-md-generator](https://github.com/kefranabg/readme-md-generator)
|
|
777
|
+
|
|
765
778
|
- Anders Pitman's [tuplates](https://github.com/anderspitman/tuplates-py)
|
|
779
|
+
|
|
780
|
+
- VitePress' [Markdown file inclusion](https://vitepress.dev/guide/markdown#markdown-file-inclusion)
|
|
781
|
+
|
|
782
|
+
There's quite a bit of prior art and similar explorations of this problem space:
|
|
783
|
+
|
|
784
|
+
- Benjamin Lupton's [projectz](https://github.com/bevry/projectz)\
|
|
785
|
+
Goes way back.
|
|
786
|
+
|
|
787
|
+
- David Wells' [Markdown Magic](https://github.com/DavidWells/markdown-magic)\
|
|
788
|
+
I somehow missed the existence of this one until after building out MDAT. It's very similar conceptually, and has a nice ecosystem of plugins.
|
|
789
|
+
|
|
790
|
+
- Titus Wormer's [mdast-zone](https://github.com/syntax-tree/mdast-zone)\
|
|
791
|
+
Allows comments to be used as ranges or markers in Markdown files. Similar tree parsing and walking strategy to MDAT. Mdast-zone uses different syntax for arguments, and requires both opening and closing tags to be present for expansion to occur.
|
|
792
|
+
|
|
793
|
+
- Jason Dent's [inject-markdown](https://github.com/streetsidesoftware/inject-markdown)
|
|
794
|
+
|
|
795
|
+
- lillallol's [md-in-place](https://www.npmjs.com/package/md-in-place)
|
|
796
|
+
|
|
797
|
+
- [AutoMD](https://automd.unjs.io/)\
|
|
798
|
+
Extremely similar functionality to mdat. The project was initiated around the same time as MDAT, but I didn't find the project until a few years later. Ships in the night.
|
|
799
|
+
|
|
766
800
|
- Franck Abgrall's [readme-md-generator](https://github.com/kefranabg/readme-md-generator)
|
|
801
|
+
|
|
802
|
+
- Anders Pitman's [tuplates](https://github.com/anderspitman/tuplates-py)
|
|
803
|
+
|
|
767
804
|
- VitePress' [Markdown file inclusion](https://vitepress.dev/guide/markdown#markdown-file-inclusion)
|
|
768
805
|
|
|
769
806
|
### Implementation notes
|