mdat 2.0.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/cli.js +211 -119
- package/dist/lib/index.d.ts +44 -17
- package/dist/lib/index.js +222 -113
- package/package.json +11 -6
- package/readme.md +90 -16
- package/dist/.DS_Store +0 -0
package/dist/lib/index.js
CHANGED
|
@@ -4,10 +4,10 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import picocolors from "picocolors";
|
|
6
6
|
import plur from "plur";
|
|
7
|
-
import { getSoleRule,
|
|
8
|
-
import { defineTemplate, getMetadata, helpers, setLogger as setLogger$2, templates } from "metascope";
|
|
7
|
+
import { getSoleRule, mdatCollapse, mdatExpand, mdatSplit, mdatStrip, rulesSchema, setLogger as setLogger$1 } from "remark-mdat";
|
|
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: {
|
|
@@ -672,17 +748,26 @@ function getExpandProcessor(config, ambientRemarkConfig) {
|
|
|
672
748
|
emphasis: "_"
|
|
673
749
|
} }).use(remarkGfm).use(ambientRemarkConfig).use(() => async function(tree, file) {
|
|
674
750
|
mdatSplit(tree, file);
|
|
675
|
-
|
|
751
|
+
mdatCollapse(tree, file);
|
|
676
752
|
await mdatExpand(tree, file, config);
|
|
677
753
|
});
|
|
678
754
|
}
|
|
679
|
-
function
|
|
755
|
+
function getCollapseProcessor(_config, ambientRemarkConfig) {
|
|
680
756
|
return remark().use({ settings: {
|
|
681
757
|
bullet: "-",
|
|
682
758
|
emphasis: "_"
|
|
683
759
|
} }).use(remarkGfm).use(ambientRemarkConfig).use(() => function(tree, file) {
|
|
684
760
|
mdatSplit(tree, file);
|
|
685
|
-
|
|
761
|
+
mdatCollapse(tree, file);
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
function getStripProcessor(_config, ambientRemarkConfig) {
|
|
765
|
+
return remark().use({ settings: {
|
|
766
|
+
bullet: "-",
|
|
767
|
+
emphasis: "_"
|
|
768
|
+
} }).use(remarkGfm).use(ambientRemarkConfig).use(() => function(tree, file) {
|
|
769
|
+
mdatSplit(tree, file);
|
|
770
|
+
mdatStrip(tree, file);
|
|
686
771
|
});
|
|
687
772
|
}
|
|
688
773
|
//#endregion
|
|
@@ -812,10 +897,11 @@ function getTemplateOptions() {
|
|
|
812
897
|
//#endregion
|
|
813
898
|
//#region src/lib/api.ts
|
|
814
899
|
/**
|
|
815
|
-
* Expand MDAT comments in one or more Markdown files.
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
900
|
+
* Expand MDAT comments in one or more Markdown files. If no files are provided,
|
|
901
|
+
* auto-finds the closest readme.md. Writing is the responsibility of the caller
|
|
902
|
+
* (e.g. via `await write(result)`)
|
|
903
|
+
*
|
|
904
|
+
* @returns An array of VFiles
|
|
819
905
|
*/
|
|
820
906
|
async function expand(files, name, output, config, options) {
|
|
821
907
|
files ??= await findReadmeThrows();
|
|
@@ -832,14 +918,15 @@ async function expandString(markdown, config, options) {
|
|
|
832
918
|
return result;
|
|
833
919
|
}
|
|
834
920
|
/**
|
|
835
|
-
* Collapse MDAT comments in one or more Markdown files.
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
*
|
|
921
|
+
* Collapse MDAT comments in one or more Markdown files. If no files are
|
|
922
|
+
* provided, auto-finds the closest readme.md. Writing is the responsibility of
|
|
923
|
+
* the caller (e.g. via `await write(result)`)
|
|
924
|
+
*
|
|
925
|
+
* @returns An array of VFiles
|
|
839
926
|
*/
|
|
840
927
|
async function collapse(files, name, output, config, options) {
|
|
841
928
|
files ??= await findReadmeThrows();
|
|
842
|
-
const results = await processFiles(files, loadConfig,
|
|
929
|
+
const results = await processFiles(files, loadConfig, getCollapseProcessor, name, output, config);
|
|
843
930
|
if (options?.format) await formatResults(results);
|
|
844
931
|
return results;
|
|
845
932
|
}
|
|
@@ -847,24 +934,46 @@ async function collapse(files, name, output, config, options) {
|
|
|
847
934
|
* Collapse MDAT comments in a Markdown string.
|
|
848
935
|
*/
|
|
849
936
|
async function collapseString(markdown, config, options) {
|
|
850
|
-
const result = await processString(markdown, loadConfig,
|
|
937
|
+
const result = await processString(markdown, loadConfig, getCollapseProcessor, config);
|
|
938
|
+
if (options?.format) await formatResults([result]);
|
|
939
|
+
return result;
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Strips MDAT comments in one or more Markdown files without touching other
|
|
943
|
+
* content. Does _not_ automatically expand Mdat content before stripping the
|
|
944
|
+
* tags. If no files are provided, auto-finds the closest readme.md. Writing is
|
|
945
|
+
* the responsibility of the caller (e.g. via `await write(result)`)
|
|
946
|
+
*
|
|
947
|
+
* @returns An array of VFiles
|
|
948
|
+
*/
|
|
949
|
+
async function strip(files, name, output, config, options) {
|
|
950
|
+
files ??= await findReadmeThrows();
|
|
951
|
+
const results = await processFiles(files, loadConfig, getStripProcessor, name, output, config);
|
|
952
|
+
if (options?.format) await formatResults(results);
|
|
953
|
+
return results;
|
|
954
|
+
}
|
|
955
|
+
/**
|
|
956
|
+
* Strip MDAT comments from a Markdown string.
|
|
957
|
+
*/
|
|
958
|
+
async function stripString(markdown, config, options) {
|
|
959
|
+
const result = await processString(markdown, loadConfig, getStripProcessor, config);
|
|
851
960
|
if (options?.format) await formatResults([result]);
|
|
852
961
|
return result;
|
|
853
962
|
}
|
|
854
963
|
/**
|
|
855
|
-
* Dry-run expand and compare with file on disk.
|
|
856
|
-
*
|
|
857
|
-
*
|
|
964
|
+
* Dry-run expand and compare with file on disk. If no files are provided,
|
|
965
|
+
* auto-finds the closest readme.md.
|
|
966
|
+
*
|
|
967
|
+
* @returns Per-file sync status and expanded VFiles
|
|
858
968
|
*/
|
|
859
969
|
async function check(files, config, options) {
|
|
860
970
|
files ??= await findReadmeThrows();
|
|
861
971
|
const { read } = await import("to-vfile");
|
|
862
972
|
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);
|
|
973
|
+
const [originals, results] = await Promise.all([Promise.all(resolvedFiles.map(async (f) => read(f))), processFiles(files, loadConfig, getExpandProcessor, void 0, void 0, config)]);
|
|
865
974
|
if (options?.format) await formatResults(results);
|
|
866
975
|
return results.map((result, i) => ({
|
|
867
|
-
inSync: originals[i].toString() === result.toString(),
|
|
976
|
+
inSync: originals[i].toString().replaceAll("\r\n", "\n") === result.toString(),
|
|
868
977
|
result
|
|
869
978
|
}));
|
|
870
979
|
}
|
|
@@ -872,4 +981,4 @@ async function formatResults(results) {
|
|
|
872
981
|
for (const file of results) file.value = await formatWithPrettier(file.toString(), file.path || void 0);
|
|
873
982
|
}
|
|
874
983
|
//#endregion
|
|
875
|
-
export { check, collapse, collapseString, createReadme as create, createReadmeInteractive as createInteractive, defineConfig, expand, expandString, getContextMetadata, getReadmeMetadata, loadConfig, mergeConfig, setLogger };
|
|
984
|
+
export { check, collapse, collapseString, createReadme as create, createReadmeInteractive as createInteractive, defineConfig, expand, expandString, getContextMetadata, getReadmeMetadata, loadConfig, mergeConfig, resetMetadataCaches, setLogger, strip, stripString };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mdat",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "CLI tool and TypeScript library implementing the Markdown Autophagic Template (MDAT) system. MDAT lets you use comments as dynamic content templates in Markdown files, making it easy to generate and update readme boilerplate.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"mdat",
|
|
@@ -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.6.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.
|
|
61
|
+
"remark-mdat": "^2.1.0",
|
|
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.1",
|
|
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
|
}
|