mdat 3.5.0 → 3.6.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 +1 -1
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.js +1 -1
- package/dist/{lib-D3Djaa1D.js → lib-CqQWGRqt.js} +280 -92
- package/package.json +4 -4
- package/readme.md +64 -42
- package/skills/mdat/SKILL.md +20 -18
- package/skills/mdat-plugin-authoring/SKILL.md +8 -0
package/dist/bin/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { S as version, a as expand, b as setLogger$1, d as templates_default, f as ensureArray, l as createReadme, r as collapse, s as strip, t as check, u as createReadmeInteractive, x as name, y as log } from "../lib-
|
|
2
|
+
import { S as version, a as expand, b as setLogger$1, d as templates_default, f as ensureArray, l as createReadme, r as collapse, s as strip, t as check, u as createReadmeInteractive, x as name, y as log } from "../lib-CqQWGRqt.js";
|
|
3
3
|
import { createLogger } from "lognow";
|
|
4
4
|
import picocolors from "picocolors";
|
|
5
5
|
import prettyMilliseconds from "pretty-ms";
|
package/dist/lib/index.d.ts
CHANGED
|
@@ -179,6 +179,7 @@ declare const readmeMetadataTemplate: import("metascope").Template<{
|
|
|
179
179
|
licenseFilePath: string | undefined;
|
|
180
180
|
licenseUrl: string | undefined;
|
|
181
181
|
name: string | undefined;
|
|
182
|
+
obsidianPluginId: string | undefined;
|
|
182
183
|
operatingSystem: string[] | undefined;
|
|
183
184
|
peerDependencies: {
|
|
184
185
|
name: string;
|
|
@@ -186,9 +187,11 @@ declare const readmeMetadataTemplate: import("metascope").Template<{
|
|
|
186
187
|
version: string;
|
|
187
188
|
}[] | undefined;
|
|
188
189
|
projectDirectory: string | undefined;
|
|
190
|
+
repositoryDirectory: string | undefined;
|
|
189
191
|
repositoryUrl: string | undefined;
|
|
190
192
|
runtimePlatform: string[] | undefined;
|
|
191
193
|
usesGitLfs: boolean;
|
|
194
|
+
vscodeExtensionId: string | undefined;
|
|
192
195
|
}>;
|
|
193
196
|
type ReadmeMetadata = ReturnType<typeof readmeMetadataTemplate>;
|
|
194
197
|
/**
|
|
@@ -225,6 +228,7 @@ export declare function getReadmeMetadata(): Promise<{
|
|
|
225
228
|
licenseFilePath: string | undefined;
|
|
226
229
|
licenseUrl: string | undefined;
|
|
227
230
|
name: string | undefined;
|
|
231
|
+
obsidianPluginId: string | undefined;
|
|
228
232
|
operatingSystem: string[] | undefined;
|
|
229
233
|
peerDependencies: {
|
|
230
234
|
name: string;
|
|
@@ -232,9 +236,11 @@ export declare function getReadmeMetadata(): Promise<{
|
|
|
232
236
|
version: string;
|
|
233
237
|
}[] | undefined;
|
|
234
238
|
projectDirectory: string | undefined;
|
|
239
|
+
repositoryDirectory: string | undefined;
|
|
235
240
|
repositoryUrl: string | undefined;
|
|
236
241
|
runtimePlatform: string[] | undefined;
|
|
237
242
|
usesGitLfs: boolean;
|
|
243
|
+
vscodeExtensionId: string | undefined;
|
|
238
244
|
}>;
|
|
239
245
|
/**
|
|
240
246
|
* Reset all cached metadata. Call between tests or when the underlying project
|
package/dist/lib/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { _ as getReadmeMetadata, a as expand, b as setLogger, c as stripString, g as getContextMetadata, h as mergeConfig, i as collapseString, l as createReadme, m as loadConfig, n as checkString, o as expandString, p as defineConfig, r as collapse, s as strip, t as check, u as createReadmeInteractive, v as resetMetadataCaches } from "../lib-
|
|
1
|
+
import { _ as getReadmeMetadata, a as expand, b as setLogger, c as stripString, g as getContextMetadata, h as mergeConfig, i as collapseString, l as createReadme, m as loadConfig, n as checkString, o as expandString, p as defineConfig, r as collapse, s as strip, t as check, u as createReadmeInteractive, v as resetMetadataCaches } from "../lib-CqQWGRqt.js";
|
|
2
2
|
|
|
3
3
|
export { check, checkString, collapse, collapseString, createReadme as create, createReadmeInteractive as createInteractive, defineConfig, expand, expandString, getContextMetadata, getReadmeMetadata, loadConfig, mergeConfig, resetMetadataCaches, setLogger, strip, stripString };
|
|
@@ -26,14 +26,13 @@ import { confirm, group, intro, note, outro, select } from "@clack/prompts";
|
|
|
26
26
|
|
|
27
27
|
//#region package.json
|
|
28
28
|
var name = "mdat";
|
|
29
|
-
var version = "3.
|
|
29
|
+
var version = "3.6.0";
|
|
30
30
|
|
|
31
31
|
//#endregion
|
|
32
32
|
//#region src/lib/deep-merge-defined.ts
|
|
33
33
|
const deepmerge = deepmergeCustom({ mergeArrays: false });
|
|
34
34
|
function stripUndefinedDeep(object) {
|
|
35
|
-
|
|
36
|
-
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
|
+
return Array.isArray(object) ? object.map((v) => v !== null && typeof v === "object" ? stripUndefinedDeep(v) : v).filter((v) => v !== void 0) : Object.entries(object).map(([k, v]) => [k, v !== null && typeof v === "object" ? stripUndefinedDeep(v) : v]).reduce((acc, [k, v]) => v === void 0 ? acc : {
|
|
37
36
|
...acc,
|
|
38
37
|
[k]: v
|
|
39
38
|
}, {});
|
|
@@ -193,6 +192,29 @@ function getHomebrewFormula(packageName, scripts) {
|
|
|
193
192
|
}
|
|
194
193
|
}
|
|
195
194
|
/**
|
|
195
|
+
* VS Code Marketplace item identifier for packages that are VS Code extensions,
|
|
196
|
+
* identified by an `engines.vscode` field.
|
|
197
|
+
*
|
|
198
|
+
* @returns E.g. `kitschpatrol.tldraw-preview`, or undefined if the package
|
|
199
|
+
* isn't an extension
|
|
200
|
+
*/
|
|
201
|
+
function getVscodeExtensionId(nodePackage) {
|
|
202
|
+
return typeof nodePackage?.publisher === "string" && nodePackage.engines?.vscode !== void 0 ? `${nodePackage.publisher}.${nodePackage.name}` : void 0;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Obsidian community plugin identifier, from the plugin's manifest.json.
|
|
206
|
+
*/
|
|
207
|
+
function getObsidianPluginId(manifests) {
|
|
208
|
+
return helpers.firstOf(manifests)?.data.id;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Package location within a monorepo, from the package.json
|
|
212
|
+
* `repository.directory` field.
|
|
213
|
+
*/
|
|
214
|
+
function getRepositoryDirectory(nodePackage) {
|
|
215
|
+
return typeof nodePackage?.repository === "object" ? nodePackage.repository.directory : void 0;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
196
218
|
* Reset cached context metadata. Call between tests or when the underlying
|
|
197
219
|
* project files may have changed on disk.
|
|
198
220
|
*
|
|
@@ -202,12 +224,12 @@ function resetContextMetadata() {
|
|
|
202
224
|
metascopeMetadata = void 0;
|
|
203
225
|
}
|
|
204
226
|
const readmeMetadataTemplate = defineTemplate((context) => {
|
|
205
|
-
const { githubActions, gitStats, licenseFile, metascope, nodePackageJson } = context;
|
|
227
|
+
const { githubActions, gitStats, licenseFile, metascope, nodePackageJson, obsidianPluginManifestJson } = context;
|
|
206
228
|
const codemeta = templates.codemetaJson(context, {});
|
|
207
229
|
const nodePackage = helpers.firstOf(nodePackageJson)?.data;
|
|
208
230
|
const licenseFileData = helpers.firstOf(licenseFile);
|
|
209
231
|
const ciActionFilePath = helpers.ensureArray(githubActions).find((entry) => entry.data.name.toLowerCase() === "ci")?.source;
|
|
210
|
-
const
|
|
232
|
+
const repositoryUrl = codemeta.codeRepository?.replace(GIT_PREFIX_REGEX, "").replace(GIT_SUFFIX_REGEX, "").replace(TRAILING_SLASH_REGEX, "");
|
|
211
233
|
const bin = (() => {
|
|
212
234
|
if (nodePackage === void 0) return;
|
|
213
235
|
const binField = nodePackage.bin;
|
|
@@ -257,6 +279,9 @@ const readmeMetadataTemplate = defineTemplate((context) => {
|
|
|
257
279
|
};
|
|
258
280
|
})();
|
|
259
281
|
const homebrewFormula = getHomebrewFormula(nodePackage?.name, nodePackage?.scripts);
|
|
282
|
+
const repositoryDirectory = getRepositoryDirectory(nodePackage);
|
|
283
|
+
const vscodeExtensionId = getVscodeExtensionId(nodePackage);
|
|
284
|
+
const obsidianPluginId = getObsidianPluginId(obsidianPluginManifestJson);
|
|
260
285
|
const firstAuthor = helpers.firstOf(helpers.ensureArray(codemeta.author));
|
|
261
286
|
return {
|
|
262
287
|
author: helpers.firstOf(helpers.mixedStringsToArray(helpers.toBasicNames(codemeta.author))),
|
|
@@ -276,12 +301,15 @@ const readmeMetadataTemplate = defineTemplate((context) => {
|
|
|
276
301
|
licenseFilePath: licenseFileData?.source,
|
|
277
302
|
licenseUrl: licenseFileData?.data.match?.spdxUrl,
|
|
278
303
|
name: codemeta.name,
|
|
304
|
+
obsidianPluginId,
|
|
279
305
|
operatingSystem: codemeta.operatingSystem ?? getSupportedOperatingSystems(nodePackage?.os),
|
|
280
306
|
peerDependencies,
|
|
281
307
|
projectDirectory: metascope?.data.options.path === void 0 ? void 0 : `file://${metascope.data.options.path}`,
|
|
282
|
-
|
|
308
|
+
repositoryDirectory,
|
|
309
|
+
repositoryUrl,
|
|
283
310
|
runtimePlatform: codemeta.runtimePlatform,
|
|
284
|
-
usesGitLfs: helpers.firstOf(gitStats)?.data.hasLfs === true
|
|
311
|
+
usesGitLfs: helpers.firstOf(gitStats)?.data.hasLfs === true,
|
|
312
|
+
vscodeExtensionId
|
|
285
313
|
};
|
|
286
314
|
});
|
|
287
315
|
let readmeMetadata;
|
|
@@ -316,24 +344,159 @@ function resetMetadataCaches() {
|
|
|
316
344
|
|
|
317
345
|
//#endregion
|
|
318
346
|
//#region src/lib/readme/rules/badges.ts
|
|
347
|
+
const HOMEBREW_FORMULA_REGEX = /^(?<owner>[^\/]+)\/(?<tap>[^\/]+)\/(?<formula>[^\/]+)$/v;
|
|
348
|
+
const GITHUB_REPOSITORY_REGEX$1 = /^https:\/\/github\.com\/(?<owner>[^\/]+)\/(?<repo>[^\/]+)$/v;
|
|
349
|
+
const NO_NPM_PACKAGE = "no public npm package was found. Remove \"private\": true from package.json if the package is published, or list packages explicitly, e.g. badges({ npm: [\"package-name\"] })";
|
|
350
|
+
const optionsSchema$2 = z.object({
|
|
351
|
+
bundleSize: z.boolean().optional(),
|
|
352
|
+
ci: z.boolean().optional(),
|
|
353
|
+
custom: z.record(z.string(), z.object({
|
|
354
|
+
image: z.string(),
|
|
355
|
+
link: z.string()
|
|
356
|
+
})).default({}),
|
|
357
|
+
githubRelease: z.boolean().optional(),
|
|
358
|
+
gitLfs: z.boolean().optional(),
|
|
359
|
+
homebrew: z.union([z.boolean(), z.string().regex(HOMEBREW_FORMULA_REGEX, "The badges \"homebrew\" option expects a formula in the form \"owner/tap/formula\"")]).optional(),
|
|
360
|
+
license: z.boolean().optional(),
|
|
361
|
+
npm: z.union([z.boolean(), z.array(z.string()).min(1, "Pass npm: false instead of an empty array to hide the NPM version badge")]).optional(),
|
|
362
|
+
npmDownloads: z.boolean().optional(),
|
|
363
|
+
obsidianDownloads: z.boolean().optional(),
|
|
364
|
+
vscode: z.boolean().optional()
|
|
365
|
+
});
|
|
366
|
+
/**
|
|
367
|
+
* Escape text for a path segment of a shields.io static badge, where `-` and
|
|
368
|
+
* `_` are delimiters and must be doubled to appear literally.
|
|
369
|
+
*/
|
|
370
|
+
function escapeStaticBadgeText(text) {
|
|
371
|
+
return encodeURIComponent(text.replaceAll("-", "--").replaceAll("_", "__"));
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* One badge per npm package, for badges that report on npm packages.
|
|
375
|
+
*/
|
|
376
|
+
function mapNpmPackages(npmPackages, toBadge) {
|
|
377
|
+
return npmPackages.length === 0 ? { unavailable: NO_NPM_PACKAGE } : npmPackages.map((packageName) => toBadge(packageName));
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Owner and name of the project's GitHub repository, or undefined if the
|
|
381
|
+
* repository isn't on GitHub.
|
|
382
|
+
*/
|
|
383
|
+
function getGithubRepository(repositoryUrl = "") {
|
|
384
|
+
const { owner, repo } = GITHUB_REPOSITORY_REGEX$1.exec(repositoryUrl)?.groups ?? {};
|
|
385
|
+
return owner === void 0 || repo === void 0 ? void 0 : {
|
|
386
|
+
owner,
|
|
387
|
+
repo
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Reason for badges that need a GitHub repository.
|
|
392
|
+
*/
|
|
393
|
+
function getNotGithubReason(repositoryUrl) {
|
|
394
|
+
return `${repositoryUrl === void 0 ? "no repository URL was found in the project metadata" : `the repository URL "${repositoryUrl}" is not a GitHub repository`}. Set the "repository" field in package.json to the project's GitHub repository.`;
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Explicit package names are useful for monorepos. Otherwise the package itself
|
|
398
|
+
* is used if it's public, even when the NPM version badge is hidden, so the
|
|
399
|
+
* download and size badges can still appear.
|
|
400
|
+
*/
|
|
401
|
+
function getNpmPackages(option, { isPublicNpmPackage, name }) {
|
|
402
|
+
if (Array.isArray(option)) return option;
|
|
403
|
+
return isPublicNpmPackage && name !== void 0 ? [name] : [];
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Badges in display order. Each appears when its metadata is available unless
|
|
407
|
+
* it's `optIn`, and its option can hide it (`false`) or require it (`true`).
|
|
408
|
+
*/
|
|
409
|
+
const badgeDefinitions = [
|
|
410
|
+
{
|
|
411
|
+
key: "npm",
|
|
412
|
+
render: ({ npmPackages }) => mapNpmPackages(npmPackages, (packageName) => `[](https://www.npmjs.com/package/${packageName})`)
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
key: "license",
|
|
416
|
+
render: ({ license, licenseUrl }) => license === void 0 || licenseUrl === void 0 ? { unavailable: "no license file matching a known SPDX license was found" } : [`[}-yellow.svg)](${licenseUrl})`]
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
key: "ci",
|
|
420
|
+
render({ ciActionFileName, repositoryUrl }) {
|
|
421
|
+
if (ciActionFileName === void 0) return { unavailable: "no GitHub Actions workflow named \"CI\" was found in .github/workflows" };
|
|
422
|
+
if (repositoryUrl === void 0) return { unavailable: "no repository URL was found in the project metadata" };
|
|
423
|
+
return [`[](${repositoryUrl}/actions/workflows/${ciActionFileName})`];
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
key: "gitLfs",
|
|
428
|
+
render: ({ usesGitLfs }) => usesGitLfs ? [""] : { unavailable: "the repository does not track any files with Git LFS" }
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
key: "vscode",
|
|
432
|
+
render({ repositoryDirectory, repositoryUrl, vscodeExtensionId }) {
|
|
433
|
+
if (vscodeExtensionId === void 0) return { unavailable: "package.json is missing a \"publisher\" or \"engines.vscode\" field" };
|
|
434
|
+
const repository = getGithubRepository(repositoryUrl);
|
|
435
|
+
if (repository === void 0) return { unavailable: `the extension version is read from package.json on GitHub, but ${getNotGithubReason(repositoryUrl)}` };
|
|
436
|
+
const packageJsonPath = [repositoryDirectory, "package.json"].filter((segment) => segment !== void 0).join("/");
|
|
437
|
+
const packageJsonUrl = `https://raw.githubusercontent.com/${repository.owner}/${repository.repo}/HEAD/${packageJsonPath}`;
|
|
438
|
+
return [`[}&query=version&label=VS%20Code%20Marketplace)](https://marketplace.visualstudio.com/items?itemName=${vscodeExtensionId})`];
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
key: "homebrew",
|
|
443
|
+
render({ homebrewFormula, options }) {
|
|
444
|
+
const formulaPath = typeof options.homebrew === "string" ? options.homebrew : homebrewFormula;
|
|
445
|
+
if (formulaPath === void 0) return { unavailable: "no \"brewpub --tap <owner>/<tap>\" invocation was found in the package.json scripts. Pass the formula explicitly instead, e.g. badges({ homebrew: \"owner/tap/formula\" })" };
|
|
446
|
+
const formulaParts = HOMEBREW_FORMULA_REGEX.exec(formulaPath)?.groups;
|
|
447
|
+
if (formulaParts?.owner === void 0 || formulaParts.tap === void 0 || formulaParts.formula === void 0) return { unavailable: `the detected formula "${formulaPath}" is not in the form "owner/tap/formula"` };
|
|
448
|
+
const { formula, owner, tap } = formulaParts;
|
|
449
|
+
return [`[}-FBB040?logo=homebrew&logoColor=white)](https://github.com/${owner}/homebrew-${tap}/blob/HEAD/Formula/${formula}.rb)`];
|
|
450
|
+
}
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
key: "githubRelease",
|
|
454
|
+
optIn: true,
|
|
455
|
+
render({ repositoryUrl }) {
|
|
456
|
+
const repository = getGithubRepository(repositoryUrl);
|
|
457
|
+
if (repository === void 0) return { unavailable: getNotGithubReason(repositoryUrl) };
|
|
458
|
+
const { owner, repo } = repository;
|
|
459
|
+
return [`[](https://github.com/${owner}/${repo}/releases/latest)`];
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
{
|
|
463
|
+
key: "npmDownloads",
|
|
464
|
+
optIn: true,
|
|
465
|
+
render: ({ npmPackages }) => mapNpmPackages(npmPackages, (packageName) => `[](https://www.npmjs.com/package/${packageName})`)
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
key: "obsidianDownloads",
|
|
469
|
+
optIn: true,
|
|
470
|
+
render({ obsidianPluginId }) {
|
|
471
|
+
if (obsidianPluginId === void 0) return { unavailable: "no Obsidian plugin manifest.json was found" };
|
|
472
|
+
const statsUrl = "https://raw.githubusercontent.com/obsidianmd/obsidian-releases/master/community-plugin-stats.json";
|
|
473
|
+
const query = `$["${obsidianPluginId}"].downloads`;
|
|
474
|
+
return [`[}&url=${encodeURIComponent(statsUrl)})](https://community.obsidian.md/plugins/${obsidianPluginId})`];
|
|
475
|
+
}
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
key: "bundleSize",
|
|
479
|
+
optIn: true,
|
|
480
|
+
render: ({ npmPackages }) => mapNpmPackages(npmPackages, (packageName) => `[](https://bundlephobia.com/package/${packageName})`)
|
|
481
|
+
}
|
|
482
|
+
];
|
|
319
483
|
var badges_default = { badges: { async content(options) {
|
|
320
|
-
const validOptions =
|
|
321
|
-
custom: z.record(z.string(), z.object({
|
|
322
|
-
image: z.string(),
|
|
323
|
-
link: z.string()
|
|
324
|
-
})).optional(),
|
|
325
|
-
npm: z.array(z.string()).optional()
|
|
326
|
-
}).optional().parse(options);
|
|
484
|
+
const validOptions = optionsSchema$2.parse(options ?? {});
|
|
327
485
|
const metadata = await getReadmeMetadata();
|
|
328
|
-
const
|
|
486
|
+
const context = {
|
|
487
|
+
...metadata,
|
|
488
|
+
npmPackages: getNpmPackages(validOptions.npm, metadata),
|
|
489
|
+
options: validOptions
|
|
490
|
+
};
|
|
329
491
|
const badges = [];
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
492
|
+
for (const { key, optIn = false, render } of badgeDefinitions) {
|
|
493
|
+
const option = validOptions[key];
|
|
494
|
+
if (option === false || optIn && option === void 0) continue;
|
|
495
|
+
const result = render(context);
|
|
496
|
+
if (Array.isArray(result)) badges.push(...result);
|
|
497
|
+
else if (option !== void 0) throw new Error(`The "${key}" badge was requested, but ${result.unavailable}`);
|
|
498
|
+
}
|
|
499
|
+
for (const [badgeName, { image, link }] of Object.entries(validOptions.custom)) badges.push(`[](${link})`);
|
|
337
500
|
return badges.join("\n");
|
|
338
501
|
} } };
|
|
339
502
|
|
|
@@ -418,29 +581,51 @@ var code_default = { code: { async content(options) {
|
|
|
418
581
|
|
|
419
582
|
//#endregion
|
|
420
583
|
//#region src/lib/readme/rules/utilities/heading.ts
|
|
584
|
+
const MAX_HEADING_LEVEL = 6;
|
|
421
585
|
/**
|
|
422
586
|
* Option schema for the Markdown heading level (1-6) at which a rule emits its
|
|
423
|
-
* section heading. Sub-headings, if any, are nested one level deeper
|
|
424
|
-
*
|
|
425
|
-
*
|
|
587
|
+
* section heading. Sub-headings, if any, are nested one level deeper, whether
|
|
588
|
+
* or not the section heading itself is shown. Each rule's default matches its
|
|
589
|
+
* placement in the house readme template.
|
|
426
590
|
*/
|
|
427
|
-
const headingLevelSchema = z.number().int().min(1).max(
|
|
591
|
+
const headingLevelSchema = z.number().int().min(1).max(MAX_HEADING_LEVEL).optional();
|
|
592
|
+
/**
|
|
593
|
+
* Option schema for a rule's section heading. `true` (the default) emits the
|
|
594
|
+
* rule's standard heading, `false` leaves the heading out, and a string
|
|
595
|
+
* replaces the heading text.
|
|
596
|
+
*/
|
|
597
|
+
const headingSchema = z.union([z.boolean(), z.string().trim().min(1)]).optional();
|
|
428
598
|
/**
|
|
429
599
|
* The `#` prefix for a Markdown heading, clamped to the maximum level of 6.
|
|
430
600
|
*/
|
|
431
601
|
function getHeadingPrefix(level) {
|
|
432
|
-
return "#".repeat(Math.min(level,
|
|
602
|
+
return "#".repeat(Math.min(level, MAX_HEADING_LEVEL));
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* The lines of a rule's section heading, including the blank line separating it
|
|
606
|
+
* from the section body, or no lines if the heading is suppressed.
|
|
607
|
+
*
|
|
608
|
+
* @param heading - Value of the rule's `heading` option
|
|
609
|
+
* @param level - Markdown heading level
|
|
610
|
+
* @param defaultText - Heading text used unless `heading` overrides it
|
|
611
|
+
*/
|
|
612
|
+
function getHeadingLines(heading, level, defaultText) {
|
|
613
|
+
if (heading === false) return [];
|
|
614
|
+
const text = typeof heading === "string" ? heading : defaultText;
|
|
615
|
+
return [`${getHeadingPrefix(level)} ${text}`, ""];
|
|
433
616
|
}
|
|
434
617
|
|
|
435
618
|
//#endregion
|
|
436
619
|
//#region src/lib/readme/rules/contributing.ts
|
|
437
620
|
var contributing_default = { contributing: { async content(options) {
|
|
438
|
-
const validOptions = z.object({
|
|
621
|
+
const validOptions = z.object({
|
|
622
|
+
heading: headingSchema,
|
|
623
|
+
headingLevel: headingLevelSchema
|
|
624
|
+
}).optional().parse(options);
|
|
439
625
|
const { issuesUrl } = await getReadmeMetadata();
|
|
440
626
|
if (issuesUrl === void 0) throw new Error("Could not find \"bugs.url\" entry in package.json");
|
|
441
627
|
return [
|
|
442
|
-
|
|
443
|
-
"",
|
|
628
|
+
...getHeadingLines(validOptions?.heading, validOptions?.headingLevel ?? 2, "Contributing"),
|
|
444
629
|
`[Issues](${issuesUrl}) are welcome and appreciated.`,
|
|
445
630
|
"",
|
|
446
631
|
"Please open an issue to discuss changes before submitting a pull request. Unsolicited PRs (especially AI-generated ones) are unlikely to be merged.",
|
|
@@ -530,7 +715,10 @@ function getPlatformItem(key, version) {
|
|
|
530
715
|
return version === void 0 || version.trim() === "" ? `- ${display}` : `- ${display} ${describeVersionRange(version)}`;
|
|
531
716
|
}
|
|
532
717
|
var dependencies_default = { dependencies: { async content(options) {
|
|
533
|
-
const validOptions = z.object({
|
|
718
|
+
const validOptions = z.object({
|
|
719
|
+
heading: headingSchema,
|
|
720
|
+
headingLevel: headingLevelSchema
|
|
721
|
+
}).optional().parse(options);
|
|
534
722
|
const { engines, operatingSystem, peerDependencies, runtimePlatform } = await getReadmeMetadata();
|
|
535
723
|
const items = [];
|
|
536
724
|
const engineEntries = Object.entries(engines ?? {});
|
|
@@ -551,11 +739,7 @@ var dependencies_default = { dependencies: { async content(options) {
|
|
|
551
739
|
items.push(`- [${name}](${npmUrl}) \`${version}\` _(peer dependency)_`);
|
|
552
740
|
}
|
|
553
741
|
if (items.length === 0) return "";
|
|
554
|
-
return [
|
|
555
|
-
`${getHeadingPrefix(validOptions?.headingLevel ?? 3)} Dependencies`,
|
|
556
|
-
"",
|
|
557
|
-
...items
|
|
558
|
-
].join("\n");
|
|
742
|
+
return [...getHeadingLines(validOptions?.heading, validOptions?.headingLevel ?? 3, "Dependencies"), ...items].join("\n");
|
|
559
743
|
} } };
|
|
560
744
|
|
|
561
745
|
//#endregion
|
|
@@ -598,7 +782,10 @@ const TOOL_INFO = {
|
|
|
598
782
|
}
|
|
599
783
|
};
|
|
600
784
|
var development_dependencies_default = { "development-dependencies": { async content(options) {
|
|
601
|
-
const validOptions = z.object({
|
|
785
|
+
const validOptions = z.object({
|
|
786
|
+
heading: headingSchema,
|
|
787
|
+
headingLevel: headingLevelSchema
|
|
788
|
+
}).optional().parse(options);
|
|
602
789
|
const { developmentDependencies } = await getReadmeMetadata();
|
|
603
790
|
if (developmentDependencies === void 0) return "";
|
|
604
791
|
const { packageManagers, runtimes } = developmentDependencies;
|
|
@@ -608,11 +795,7 @@ var development_dependencies_default = { "development-dependencies": { async con
|
|
|
608
795
|
const display = info ? `[${info.display}](${info.url})` : name;
|
|
609
796
|
items.push(version === void 0 || version === "" ? `- ${display}` : `- ${display} ${version}`);
|
|
610
797
|
}
|
|
611
|
-
return [
|
|
612
|
-
`${getHeadingPrefix(validOptions?.headingLevel ?? 3)} Development dependencies`,
|
|
613
|
-
"",
|
|
614
|
-
...items
|
|
615
|
-
].join("\n");
|
|
798
|
+
return [...getHeadingLines(validOptions?.heading, validOptions?.headingLevel ?? 3, "Development dependencies"), ...items].join("\n");
|
|
616
799
|
} } };
|
|
617
800
|
|
|
618
801
|
//#endregion
|
|
@@ -625,12 +808,15 @@ var dev_dependencies_default = { "dev-dependencies": getSoleRule(development_dep
|
|
|
625
808
|
//#endregion
|
|
626
809
|
//#region src/lib/readme/rules/license.ts
|
|
627
810
|
var license_default = { license: { async content(options) {
|
|
628
|
-
const validOptions = z.object({
|
|
811
|
+
const validOptions = z.object({
|
|
812
|
+
heading: headingSchema,
|
|
813
|
+
headingLevel: headingLevelSchema
|
|
814
|
+
}).optional().parse(options);
|
|
629
815
|
const { author, authorUrl, license, licenseFilePath } = await getReadmeMetadata();
|
|
630
816
|
if (author === void 0) throw new Error("Could not find author name in project");
|
|
631
817
|
if (license === void 0 || licenseFilePath === void 0) throw new Error("Could not find license for project");
|
|
632
818
|
const authorDisplay = authorUrl === void 0 ? author : `[${author}](${authorUrl})`;
|
|
633
|
-
return
|
|
819
|
+
return [...getHeadingLines(validOptions?.heading, validOptions?.headingLevel ?? 2, "License"), `[${license}](${licenseFilePath}) © ${authorDisplay}`].join("\n");
|
|
634
820
|
} } };
|
|
635
821
|
|
|
636
822
|
//#endregion
|
|
@@ -732,23 +918,23 @@ function getLibraryLines(name, bin, hasTypes, dev) {
|
|
|
732
918
|
var install_default = { install: { async content(options) {
|
|
733
919
|
const validOptions = z.object({
|
|
734
920
|
dev: z.boolean().optional(),
|
|
921
|
+
heading: headingSchema,
|
|
735
922
|
headingLevel: headingLevelSchema,
|
|
736
923
|
homebrew: z.union([z.string(), z.literal(false)]).optional()
|
|
737
|
-
}).
|
|
924
|
+
}).parse(options ?? {});
|
|
738
925
|
const { bin, hasTypes, homebrewFormula, isLibrary, isNodePackage, isPublicNpmPackage, name, runtimePlatform } = await getReadmeMetadata();
|
|
739
926
|
if (name === void 0) throw new Error("Could not find project name");
|
|
740
|
-
const headingLevel = validOptions
|
|
741
|
-
const
|
|
927
|
+
const headingLevel = validOptions.headingLevel ?? 3;
|
|
928
|
+
const headingLines = getHeadingLines(validOptions.heading, headingLevel, "Installation");
|
|
742
929
|
const subheading = getHeadingPrefix(headingLevel + 1);
|
|
743
930
|
if (isNodePackage) {
|
|
744
931
|
if (!isPublicNpmPackage) throw new Error("Install instructions require a publishable package, but package.json sets \"private\": true");
|
|
745
|
-
const homebrew = validOptions
|
|
932
|
+
const homebrew = validOptions.homebrew === void 0 ? homebrewFormula : validOptions.homebrew === false ? void 0 : validOptions.homebrew;
|
|
746
933
|
const hasCli = bin !== void 0 && bin.length > 0;
|
|
747
934
|
const cliLines = getCliLines(name, homebrew);
|
|
748
|
-
const libraryLines = getLibraryLines(name, bin, hasTypes, validOptions
|
|
935
|
+
const libraryLines = getLibraryLines(name, bin, hasTypes, validOptions.dev ?? false);
|
|
749
936
|
if (hasCli && isLibrary) return [
|
|
750
|
-
|
|
751
|
-
"",
|
|
937
|
+
...headingLines,
|
|
752
938
|
`There are several ways to install ${name} depending on how you're planning to use it:`,
|
|
753
939
|
"",
|
|
754
940
|
`${subheading} CLI`,
|
|
@@ -759,20 +945,12 @@ var install_default = { install: { async content(options) {
|
|
|
759
945
|
"",
|
|
760
946
|
...libraryLines
|
|
761
947
|
].join("\n");
|
|
762
|
-
return [
|
|
763
|
-
heading,
|
|
764
|
-
"",
|
|
765
|
-
...hasCli ? cliLines : libraryLines
|
|
766
|
-
].join("\n");
|
|
948
|
+
return [...headingLines, ...hasCli ? cliLines : libraryLines].join("\n");
|
|
767
949
|
}
|
|
768
950
|
const platform = (prefix) => runtimePlatform?.some((p) => p.startsWith(prefix));
|
|
769
951
|
const command = platform("python") ? `pip install ${name}` : platform("rust") ? `cargo install ${name}` : platform("go") ? `go install ${name}@latest` : platform("ruby") ? `gem install ${name}` : void 0;
|
|
770
952
|
if (command === void 0) throw new Error("Could not determine project ecosystem for install instructions");
|
|
771
|
-
return [
|
|
772
|
-
heading,
|
|
773
|
-
"",
|
|
774
|
-
...codeBlock$1(command)
|
|
775
|
-
].join("\n");
|
|
953
|
+
return [...headingLines, ...codeBlock$1(command)].join("\n");
|
|
776
954
|
} } };
|
|
777
955
|
|
|
778
956
|
//#endregion
|
|
@@ -913,6 +1091,7 @@ const SKILLS_DIRECTORY = "skills";
|
|
|
913
1091
|
const SKILL_FILE_NAME = "SKILL.md";
|
|
914
1092
|
const GITHUB_REPOSITORY_REGEX = /^https:\/\/github\.com\/(?<slug>[^\/]+\/[^\/]+)$/v;
|
|
915
1093
|
const WHITESPACE_REGEX = /\s+/gv;
|
|
1094
|
+
const FIRST_SENTENCE_REGEX = /^.*?(?<!\b(?:e\.g|i\.e|vs))[.!?](?=\s+\p{Lu})/v;
|
|
916
1095
|
const skillFrontmatterSchema = z.object({
|
|
917
1096
|
description: z.string().trim().min(1),
|
|
918
1097
|
name: z.string().trim().min(1)
|
|
@@ -964,58 +1143,66 @@ async function findSkills(projectDirectory) {
|
|
|
964
1143
|
* The argument identifying a repository to the `skills add` command, which
|
|
965
1144
|
* accepts an `owner/repo` shorthand for GitHub and full URLs for other hosts.
|
|
966
1145
|
*/
|
|
967
|
-
function getSkillsCliSource(
|
|
968
|
-
return GITHUB_REPOSITORY_REGEX.exec(
|
|
1146
|
+
function getSkillsCliSource(repositoryUrl) {
|
|
1147
|
+
return GITHUB_REPOSITORY_REGEX.exec(repositoryUrl)?.groups?.slug ?? repositoryUrl;
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* The first sentence of a skill description, which by convention says what the
|
|
1151
|
+
* skill does, leaving out the guidance on when to use it that follows.
|
|
1152
|
+
*/
|
|
1153
|
+
function getFirstSentence(description) {
|
|
1154
|
+
const singleLine = description.replaceAll(WHITESPACE_REGEX, " ");
|
|
1155
|
+
return FIRST_SENTENCE_REGEX.exec(singleLine)?.[0] ?? singleLine;
|
|
969
1156
|
}
|
|
970
1157
|
var skills_default = { skills: { async content(options) {
|
|
971
|
-
const validOptions = z.object({
|
|
1158
|
+
const validOptions = z.object({
|
|
1159
|
+
heading: headingSchema,
|
|
1160
|
+
headingLevel: headingLevelSchema
|
|
1161
|
+
}).optional().parse(options);
|
|
972
1162
|
const skills = await findSkills(process.cwd());
|
|
973
1163
|
if (skills.length === 0) throw new Error(`Could not find any skills, expected at least one "${SKILLS_DIRECTORY}/<name>/${SKILL_FILE_NAME}" file in the project`);
|
|
974
1164
|
const { isPublicNpmPackage, name, repositoryUrl } = await getReadmeMetadata();
|
|
975
1165
|
if (name === void 0) throw new Error("Could not find project name");
|
|
976
1166
|
const headingLevel = validOptions?.headingLevel ?? 2;
|
|
977
|
-
const
|
|
978
|
-
const skillNoun =
|
|
979
|
-
const
|
|
980
|
-
const
|
|
981
|
-
|
|
1167
|
+
const skillHeading = getHeadingPrefix(headingLevel + 1);
|
|
1168
|
+
const skillNoun = plur("skill", skills.length);
|
|
1169
|
+
const agentSkillsLink = `${skills.length === 1 ? "an" : skills.length} [${plur("Agent Skill", skills.length)}](https://agentskills.io)`;
|
|
1170
|
+
const skillsCliLink = "Vercel's [skills CLI](https://github.com/vercel-labs/skills)";
|
|
1171
|
+
const headingLines = getHeadingLines(validOptions?.heading, headingLevel, "Agent skills");
|
|
1172
|
+
const skillLines = [`Included ${skillNoun}:`, ...skills.flatMap((skill) => [
|
|
982
1173
|
"",
|
|
983
|
-
|
|
1174
|
+
`${skillHeading} Skill: [\`${skill.name}\`](${skill.filePath})`,
|
|
984
1175
|
"",
|
|
985
|
-
|
|
986
|
-
];
|
|
1176
|
+
getFirstSentence(skill.description)
|
|
1177
|
+
])];
|
|
987
1178
|
if (!isPublicNpmPackage) {
|
|
988
1179
|
if (repositoryUrl === void 0) throw new Error("Skill install instructions require either a publishable npm package or a repository URL in the project metadata");
|
|
989
1180
|
return [
|
|
990
|
-
...
|
|
1181
|
+
...headingLines,
|
|
1182
|
+
`This project includes ${agentSkillsLink} to help coding agents work with ${name}.`,
|
|
1183
|
+
"",
|
|
1184
|
+
`To install the ${skillNoun}, run ${skillsCliLink}:`,
|
|
991
1185
|
"",
|
|
992
|
-
`
|
|
1186
|
+
...codeBlock(`npx skills add ${getSkillsCliSource(repositoryUrl)}`),
|
|
993
1187
|
"",
|
|
994
|
-
...
|
|
1188
|
+
...skillLines
|
|
995
1189
|
].join("\n");
|
|
996
1190
|
}
|
|
997
1191
|
return [
|
|
998
|
-
...
|
|
1192
|
+
...headingLines,
|
|
1193
|
+
`This project bundles ${agentSkillsLink} in its published package to help coding agents work with ${name}.`,
|
|
999
1194
|
"",
|
|
1000
|
-
`
|
|
1001
|
-
"",
|
|
1002
|
-
`${subheading} Sync from the installed package (recommended)`,
|
|
1003
|
-
"",
|
|
1004
|
-
`With \`${name}\` installed as a project dependency, the [\`skills\`](https://github.com/vercel-labs/skills) CLI finds skills bundled in your dependencies and copies them into your project's agent skill directories, so they match the version of \`${name}\` you have installed:`,
|
|
1195
|
+
`To sync the ${skillNoun} into your project, run ${skillsCliLink} from your project root:`,
|
|
1005
1196
|
"",
|
|
1006
1197
|
...codeBlock("npx skills experimental_sync"),
|
|
1007
|
-
"",
|
|
1008
|
-
`Run the command again after upgrading \`${name}\` to refresh the copies. The \`experimental_sync\` command is experimental and its behavior may change.`,
|
|
1009
1198
|
...repositoryUrl === void 0 ? [] : [
|
|
1010
1199
|
"",
|
|
1011
|
-
|
|
1200
|
+
"Or install globally:",
|
|
1012
1201
|
"",
|
|
1013
|
-
`
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
`${skills.length === 1 ? "A skill" : "Skills"} installed this way ${skills.length === 1 ? "follows" : "follow"} the repository's default branch rather than your installed version of \`${name}\`.`
|
|
1018
|
-
]
|
|
1202
|
+
...codeBlock(`npx skills add ${getSkillsCliSource(repositoryUrl)} --global`)
|
|
1203
|
+
],
|
|
1204
|
+
"",
|
|
1205
|
+
...skillLines
|
|
1019
1206
|
].join("\n");
|
|
1020
1207
|
} } };
|
|
1021
1208
|
|
|
@@ -1032,6 +1219,7 @@ var table_of_contents_default = { "table-of-contents": {
|
|
|
1032
1219
|
z.literal(5),
|
|
1033
1220
|
z.literal(6)
|
|
1034
1221
|
]).optional(),
|
|
1222
|
+
heading: headingSchema,
|
|
1035
1223
|
headingLevel: headingLevelSchema
|
|
1036
1224
|
}).optional().parse(options);
|
|
1037
1225
|
const result = toc(tree, {
|
|
@@ -1040,12 +1228,12 @@ var table_of_contents_default = { "table-of-contents": {
|
|
|
1040
1228
|
tight: true
|
|
1041
1229
|
});
|
|
1042
1230
|
if (result.map === void 0) throw new Error("Could not generate table of contents");
|
|
1043
|
-
const heading = `${getHeadingPrefix(validOptions?.headingLevel ?? 2)} Table of contents`;
|
|
1044
1231
|
const rootWrapper = {
|
|
1045
1232
|
children: result.map.children,
|
|
1046
1233
|
type: "root"
|
|
1047
1234
|
};
|
|
1048
|
-
|
|
1235
|
+
const tocString = remark().use(remarkGfm).stringify(rootWrapper).replaceAll("\n\n", "\n");
|
|
1236
|
+
return [...getHeadingLines(validOptions?.heading, validOptions?.headingLevel ?? 2, "Table of contents"), tocString].join("\n");
|
|
1049
1237
|
},
|
|
1050
1238
|
order: 1
|
|
1051
1239
|
} };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mdat",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.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",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"gray-matter-es": "^0.2.1",
|
|
53
53
|
"lognow": "^0.8.4",
|
|
54
54
|
"mdast-util-toc": "^7.1.0",
|
|
55
|
-
"metascope": "^0.12.
|
|
55
|
+
"metascope": "^0.12.1",
|
|
56
56
|
"path-type": "^6.0.0",
|
|
57
57
|
"picocolors": "^1.1.1",
|
|
58
58
|
"plur": "^6.0.0",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@arethetypeswrong/core": "^0.18.5",
|
|
74
|
-
"@kitschpatrol/shared-config": "^8.
|
|
74
|
+
"@kitschpatrol/shared-config": "^8.11.0",
|
|
75
75
|
"@types/mdast": "^4.0.4",
|
|
76
76
|
"@types/node": "~24.13.6",
|
|
77
77
|
"@types/unist": "^3.0.3",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"brewpub": "^0.4.0",
|
|
81
81
|
"bumpp": "^12.3.0",
|
|
82
82
|
"execa": "^10.0.1",
|
|
83
|
-
"mdat-plugin-cli-help": "^3.
|
|
83
|
+
"mdat-plugin-cli-help": "^3.4.0",
|
|
84
84
|
"mdat-plugin-example": "^3.0.0",
|
|
85
85
|
"mdat-plugin-tldraw": "^3.0.0",
|
|
86
86
|
"nanoid": "^6.0.1",
|
package/readme.md
CHANGED
|
@@ -4,22 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
<!-- /title -->
|
|
6
6
|
|
|
7
|
-
<!-- badges
|
|
8
|
-
custom: {
|
|
9
|
-
"Homebrew": {
|
|
10
|
-
image:
|
|
11
|
-
"https://img.shields.io/badge/Homebrew-kitschpatrol%2Ftap%2Fmdat-FBB040?logo=homebrew&logoColor=white",
|
|
12
|
-
link: "https://github.com/kitschpatrol/homebrew-tap/blob/main/Formula/mdat.rb",
|
|
13
|
-
},
|
|
14
|
-
}
|
|
15
|
-
}) -->
|
|
7
|
+
<!-- // badges -->
|
|
16
8
|
|
|
17
9
|
[](https://www.npmjs.com/package/mdat)
|
|
18
10
|
[](https://opensource.org/license/mit)
|
|
19
11
|
[](https://github.com/kitschpatrol/mdat/actions/workflows/ci.yml)
|
|
20
|
-
[](https://github.com/kitschpatrol/homebrew-tap/blob/
|
|
12
|
+
[](https://github.com/kitschpatrol/homebrew-tap/blob/HEAD/Formula/mdat.rb)
|
|
21
13
|
|
|
22
|
-
<!-- /badges -->
|
|
14
|
+
<!-- // /badges -->
|
|
23
15
|
|
|
24
16
|
<!-- description -->
|
|
25
17
|
|
|
@@ -47,8 +39,8 @@
|
|
|
47
39
|
- [Creating a rule plugin](#creating-a-rule-plugin)
|
|
48
40
|
- [Available rule plugins](#available-rule-plugins)
|
|
49
41
|
- [Agent skills](#agent-skills)
|
|
50
|
-
- [
|
|
51
|
-
- [
|
|
42
|
+
- [Skill: `mdat`](#skill-mdat)
|
|
43
|
+
- [Skill: `mdat-plugin-authoring`](#skill-mdat-plugin-authoring)
|
|
52
44
|
- [Migrating from 1.x to 2.x](#migrating-from-1x-to-2x)
|
|
53
45
|
- [Flat CLI commands](#flat-cli-commands)
|
|
54
46
|
- [Polyglot metadata](#polyglot-metadata)
|
|
@@ -250,7 +242,9 @@ npm install mdat
|
|
|
250
242
|
|
|
251
243
|
<!-- cli-help -->
|
|
252
244
|
|
|
253
|
-
####
|
|
245
|
+
#### Commands
|
|
246
|
+
|
|
247
|
+
##### Command: `mdat`
|
|
254
248
|
|
|
255
249
|
Work with MDAT placeholder comments in Markdown files.
|
|
256
250
|
|
|
@@ -274,7 +268,7 @@ mdat [command] [files..] [options]
|
|
|
274
268
|
|
|
275
269
|
_See the sections below for more information on each subcommand._
|
|
276
270
|
|
|
277
|
-
|
|
271
|
+
##### Subcommand: `mdat expand`
|
|
278
272
|
|
|
279
273
|
Expand MDAT placeholder comments. If no files are provided, the closest readme.md is expanded.
|
|
280
274
|
|
|
@@ -299,7 +293,7 @@ mdat expand [files..] [options]
|
|
|
299
293
|
| `--help`<br>`-h` | Show help | `boolean` | |
|
|
300
294
|
| `--version`<br>`-v` | Show version number | `boolean` | |
|
|
301
295
|
|
|
302
|
-
|
|
296
|
+
##### Subcommand: `mdat collapse`
|
|
303
297
|
|
|
304
298
|
Collapse MDAT placeholder comments. If no files are provided, the closest readme.md is collapsed.
|
|
305
299
|
|
|
@@ -323,7 +317,7 @@ mdat collapse [files..] [options]
|
|
|
323
317
|
| `--help`<br>`-h` | Show help | `boolean` | |
|
|
324
318
|
| `--version`<br>`-v` | Show version number | `boolean` | |
|
|
325
319
|
|
|
326
|
-
|
|
320
|
+
##### Subcommand: `mdat strip`
|
|
327
321
|
|
|
328
322
|
Strip MDAT comments while preserving expanded content. If no files are provided, the closest readme.md is stripped.
|
|
329
323
|
|
|
@@ -347,7 +341,7 @@ mdat strip [files..] [options]
|
|
|
347
341
|
| `--help`<br>`-h` | Show help | `boolean` | |
|
|
348
342
|
| `--version`<br>`-v` | Show version number | `boolean` | |
|
|
349
343
|
|
|
350
|
-
|
|
344
|
+
##### Subcommand: `mdat check`
|
|
351
345
|
|
|
352
346
|
Check if MDAT placeholder comments are up to date. Exits with code 1 if any files have stale or unexpanded content.
|
|
353
347
|
|
|
@@ -369,7 +363,7 @@ mdat check [files..] [options]
|
|
|
369
363
|
| `--help`<br>`-h` | Show help | `boolean` |
|
|
370
364
|
| `--version`<br>`-v` | Show version number | `boolean` |
|
|
371
365
|
|
|
372
|
-
|
|
366
|
+
##### Subcommand: `mdat create`
|
|
373
367
|
|
|
374
368
|
Create a new Markdown file from a template.
|
|
375
369
|
|
|
@@ -592,7 +586,17 @@ See the [Examples section](https://github.com/kitschpatrol/remark-mdat#examples)
|
|
|
592
586
|
|
|
593
587
|
### Bundled rules
|
|
594
588
|
|
|
595
|
-
Every rule that
|
|
589
|
+
Every stand-alone rule that generates a section of the readme emits its own heading, and accepts two options to control it:
|
|
590
|
+
|
|
591
|
+
- `heading` is `true` by default, which emits the rule's standard heading. Pass a string to replace the heading text, or `false` to leave the heading out, e.g. when you've written your own heading above the comment, or when the same rule appears several times in one section.
|
|
592
|
+
|
|
593
|
+
- `headingLevel` (`1`-`6`) sets the heading's Markdown level. Any sub-headings are nested one level deeper, whether or not the heading itself is shown. The defaults match each rule's placement in the bundled MDAT Readme template: `2` for top-level sections (`table-of-contents`, `contributing`, `license`, and `skills`), and `3` for the rules that sit under "Getting started" (`dependencies`, `install`, and `development-dependencies`).
|
|
594
|
+
|
|
595
|
+
```md
|
|
596
|
+
<!-- install({ heading: "Setup", headingLevel: 2 }) -->
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
Rules that generate inline content, like `badges`, `code`, and `size`, have no heading. Rule plugins follow the same convention.
|
|
596
600
|
|
|
597
601
|
#### Stand-alone
|
|
598
602
|
|
|
@@ -606,7 +610,28 @@ Every rule that emits a section heading accepts a `headingLevel` option (`1`-`6`
|
|
|
606
610
|
|
|
607
611
|
- ##### `<!-- badges -->`
|
|
608
612
|
|
|
609
|
-
Generates badges based on project metadata.
|
|
613
|
+
Generates badges based on project metadata. Each badge has an option. Leave it out for the default behavior, pass `false` to hide the badge, or pass `true` to require it, which reports an error if the metadata the badge needs is missing. Two options also take a value that replaces the detected metadata.
|
|
614
|
+
|
|
615
|
+
| Option | Badge | Default | Value |
|
|
616
|
+
| ------------------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------- | ----------------------------------------------- |
|
|
617
|
+
| `npm` | NPM version, for each package | Shown for public packages | Package names (`string[]`), useful in monorepos |
|
|
618
|
+
| `license` | License | Shown when a license file matches a known SPDX license | |
|
|
619
|
+
| `ci` | GitHub Actions CI status | Shown when a workflow is named "CI" | |
|
|
620
|
+
| `gitLfs` | Git LFS | Shown when the repository tracks files with Git LFS | |
|
|
621
|
+
| `vscode` | VS Code Marketplace version | Shown for VS Code extensions hosted on GitHub | |
|
|
622
|
+
| `homebrew` | Homebrew tap | Shown when a `brewpub --tap` invocation is found in the package scripts | Formula (`"owner/tap/formula"`) |
|
|
623
|
+
| `githubRelease` | Latest GitHub release | Hidden | |
|
|
624
|
+
| `npmDownloads` | Monthly NPM downloads, for each package | Hidden | |
|
|
625
|
+
| `obsidianDownloads` | Obsidian community plugin downloads | Hidden | |
|
|
626
|
+
| `bundleSize` | [Bundlephobia](https://bundlephobia.com) minified and gzipped size, for each package | Hidden | |
|
|
627
|
+
|
|
628
|
+
Badges appear in the order of the table, followed by `custom` badges, which take a `Record<string, { image: string, link: string }>` keyed by alt text.
|
|
629
|
+
|
|
630
|
+
The download and size badges cover the packages listed in `npm`, or the current package if it's public, even when `npm` is `false`. VS Code extensions are identified by the `publisher` and `engines.vscode` fields in `package.json`. Shields.io retired its Marketplace badge, so the version is read from `package.json` in the GitHub repository instead, respecting `repository.directory` in monorepos. The Homebrew formula is detected the same way as in the `install` rule. The GitHub release badge needs a GitHub repository URL, and the Obsidian downloads badge reads the plugin ID from `manifest.json`.
|
|
631
|
+
|
|
632
|
+
```md
|
|
633
|
+
<!-- badges({ ci: false, npmDownloads: true }) -->
|
|
634
|
+
```
|
|
610
635
|
|
|
611
636
|
- ##### `<!-- description -->`
|
|
612
637
|
|
|
@@ -665,11 +690,11 @@ Every rule that emits a section heading accepts a `headingLevel` option (`1`-`6`
|
|
|
665
690
|
|
|
666
691
|
- ##### `<!-- skills -->`
|
|
667
692
|
|
|
668
|
-
Documents the [Agent Skills](https://agentskills.io) bundled with the project and how to install them. Detects skills from `skills/<name>/SKILL.md` files, and
|
|
693
|
+
Documents the [Agent Skills](https://agentskills.io) bundled with the project and how to install them. Detects skills from `skills/<name>/SKILL.md` files, and gives each one a "Skill:" sub-heading linking to its `SKILL.md` file, followed by the first sentence of the description from its frontmatter. Publishable npm packages get instructions for syncing skills from the installed package with the `experimental_sync` command of Vercel's [skills CLI](https://github.com/vercel-labs/skills), and for installing them from the repository with `skills add`. Private packages and non-Node projects only get the repository instructions. Reports an error if no skills are found. Remember to include the `skills` directory in the `files` field of `package.json`. The optional `headingLevel` sets the Markdown heading level and defaults to `2`.
|
|
669
694
|
|
|
670
695
|
#### Compound
|
|
671
696
|
|
|
672
|
-
Compound rules combine several stand-alone rules under a single keyword. Options are passed as an array with one entry per stand-alone rule, in order, e.g. `<!-- footer([{ headingLevel: 3 }, { headingLevel: 3 }]) -->`.
|
|
697
|
+
Compound rules combine several stand-alone rules under a single keyword. Options are passed as an array with one entry per stand-alone rule, in order, e.g. `<!-- footer([{ headingLevel: 3 }, { heading: "Legal", headingLevel: 3 }]) -->`.
|
|
673
698
|
|
|
674
699
|
- ##### `<!-- header -->`
|
|
675
700
|
|
|
@@ -767,44 +792,41 @@ Transform a CLI command's `--help` output into Markdown tables. Recursively call
|
|
|
767
792
|
|
|
768
793
|
Example: `<!-- cli-help -->`
|
|
769
794
|
|
|
770
|
-
#### [mdat-plugin-api](https://github.com/kitschpatrol/mdat-plugin-api)
|
|
795
|
+
#### [mdat-plugin-api-help](https://github.com/kitschpatrol/mdat-plugin-api-help)
|
|
771
796
|
|
|
772
797
|
Generate API documentation for a TypeScript package's public exports. Runs [TypeDoc](https://typedoc.org) over the package's entry point and embeds signatures, JSDoc descriptions, parameter and property tables, and examples as Markdown. A compact format renders one table row per export for large or namespaced APIs, and exports can be selected by name.
|
|
773
798
|
|
|
774
|
-
Example: `<!-- api({ format: "compact", include: ["sync*"] }) -->`
|
|
799
|
+
Example: `<!-- api-help({ format: "compact", include: ["sync*"] }) -->`
|
|
775
800
|
|
|
776
|
-
<!-- //skills -->
|
|
801
|
+
<!-- // skills -->
|
|
777
802
|
|
|
778
803
|
## Agent skills
|
|
779
804
|
|
|
780
|
-
This project
|
|
781
|
-
|
|
782
|
-
- **[`mdat`](skills/mdat/SKILL.md)**: Use and configure mdat, the Markdown Autophagic Template CLI and library that expands HTML comment placeholders such as `<!-- title -->` in Markdown files. Use when a readme or other Markdown file contains mdat comment placeholders, when editing mdat.config.ts or an "mdat" key in package.json, when generating or updating readme boilerplate from project metadata, when `mdat check` fails, or when calling the mdat TypeScript API.
|
|
783
|
-
- **[`mdat-plugin-authoring`](skills/mdat-plugin-authoring/SKILL.md)**: Write custom mdat expansion rules and publishable mdat rule plugins (mdat-plugin-\* packages). Use when creating or debugging rule functions for mdat comment placeholders, validating comment arguments, reading project metadata or the Markdown tree inside a rule, building compound rules, or packaging and testing rules for reuse across projects.
|
|
805
|
+
This project bundles 2 [Agent Skills](https://agentskills.io) in its published package to help coding agents work with mdat.
|
|
784
806
|
|
|
785
|
-
|
|
807
|
+
To sync the skills into your project, run Vercel's [skills CLI](https://github.com/vercel-labs/skills) from your project root:
|
|
786
808
|
|
|
787
|
-
|
|
809
|
+
```sh
|
|
810
|
+
npx skills experimental_sync
|
|
811
|
+
```
|
|
788
812
|
|
|
789
|
-
|
|
813
|
+
Or install globally:
|
|
790
814
|
|
|
791
815
|
```sh
|
|
792
|
-
npx skills
|
|
816
|
+
npx skills add kitschpatrol/mdat --global
|
|
793
817
|
```
|
|
794
818
|
|
|
795
|
-
|
|
819
|
+
Included skills:
|
|
796
820
|
|
|
797
|
-
###
|
|
821
|
+
### Skill: [`mdat`](skills/mdat/SKILL.md)
|
|
798
822
|
|
|
799
|
-
|
|
823
|
+
Use and configure mdat, the Markdown Autophagic Template CLI and library that expands HTML comment placeholders such as `<!-- title -->` in Markdown files.
|
|
800
824
|
|
|
801
|
-
|
|
802
|
-
npx skills add kitschpatrol/mdat
|
|
803
|
-
```
|
|
825
|
+
### Skill: [`mdat-plugin-authoring`](skills/mdat-plugin-authoring/SKILL.md)
|
|
804
826
|
|
|
805
|
-
|
|
827
|
+
Write custom mdat expansion rules and publishable mdat rule plugins (mdat-plugin-\* packages).
|
|
806
828
|
|
|
807
|
-
<!-- //skills -->
|
|
829
|
+
<!-- // /skills -->
|
|
808
830
|
|
|
809
831
|
## Migrating from 1.x to 2.x
|
|
810
832
|
|
package/skills/mdat/SKILL.md
CHANGED
|
@@ -61,24 +61,26 @@ Only JSON5 values are accepted. No JavaScript is evaluated. Bundled rules valida
|
|
|
61
61
|
|
|
62
62
|
These work without any configuration. They read normalized project metadata, so most also work in Python, Rust, Go, and Ruby projects, not only Node.
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
|
67
|
-
|
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
77
|
-
| `
|
|
78
|
-
| `
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
64
|
+
Rules that generate a section emit their own heading. Their `heading` option is `true` by default, takes a string to replace the heading text, and takes `false` to leave the heading out, e.g. under a hand-written heading or when a rule appears several times in one section. `headingLevel` sets the heading's level, with sub-headings nested one level deeper whether or not the heading is shown.
|
|
65
|
+
|
|
66
|
+
| Keyword | Output | Options |
|
|
67
|
+
| ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
68
|
+
| `title` | Project name as a heading. | |
|
|
69
|
+
| `banner` | A banner image found in the project directory. | |
|
|
70
|
+
| `badges` | npm version, license, CI, Git LFS, VS Code Marketplace, and Homebrew badges when detected, plus opt-in GitHub release, npm downloads, Obsidian downloads, and bundle size badges. | `npm`, `license`, `ci`, `gitLfs`, `vscode`, `homebrew`, `githubRelease`, `npmDownloads`, `obsidianDownloads`, `bundleSize`: `false` hides, `true` requires. Also `npm: string[]`, `homebrew: "owner/tap/formula"`, `custom: { [name]: { image, link } }` |
|
|
71
|
+
| `description` (alias `short-description`) | Project description. | |
|
|
72
|
+
| `table-of-contents` (alias `toc`) | Table of contents from the document headings. | `depth` (default `3`), `heading`, `headingLevel` (default `2`) |
|
|
73
|
+
| `install` (alias `installation`) | Install instructions for consumers of the package. Private packages are rejected. | `heading`, `headingLevel` (default `3`), `dev`, `homebrew` |
|
|
74
|
+
| `dependencies` | Runtime platform requirements and required peer dependencies. | `heading`, `headingLevel` (default `3`) |
|
|
75
|
+
| `development-dependencies` (alias `dev-dependencies`) | Tools needed to work on the project, from `devEngines` and `packageManager`. | `heading`, `headingLevel` (default `3`) |
|
|
76
|
+
| `contributing` | Invitation to open issues and pull requests. | `heading`, `headingLevel` (default `2`) |
|
|
77
|
+
| `license` | License section. | `heading`, `headingLevel` (default `2`) |
|
|
78
|
+
| `code` | A code block embedded from a file. | `file` (required), `language`, `trim` (default `true`) |
|
|
79
|
+
| `size` | A file's size. | `file` (required), `compression`: `none`, `gzip`, `brotli` |
|
|
80
|
+
| `size-table` | A table of file sizes with Gzip and Brotli columns. | `files` (required), `precision` (default `0`) |
|
|
81
|
+
| `skills` | Agent Skills found in `skills/<name>/SKILL.md` and how to install them. | `heading`, `headingLevel` (default `2`) |
|
|
82
|
+
| `header` | Compound: `title`, `banner`, `badges`, `short-description`. | Array, one entry per sub-rule in order. |
|
|
83
|
+
| `footer` | Compound: `contributing`, `license`. | Array, one entry per sub-rule in order. |
|
|
82
84
|
|
|
83
85
|
File paths in `code`, `size`, and `size-table` resolve from the current working directory.
|
|
84
86
|
|
|
@@ -101,6 +101,14 @@ const metadata = await getContextMetadata()
|
|
|
101
101
|
|
|
102
102
|
See the exported `ReadmeMetadata` type for the complete field list. Call `resetMetadataCaches()` between tests that change project files on disk.
|
|
103
103
|
|
|
104
|
+
## Headings
|
|
105
|
+
|
|
106
|
+
Follow the convention of the bundled rules so that readmes stay consistent:
|
|
107
|
+
|
|
108
|
+
- A rule that generates a section emits its own heading. A rule that generates inline content, like a badge or a code block, has no heading.
|
|
109
|
+
- Accept a `heading` option. `true` is the default and emits the rule's standard heading, a string replaces the heading text, and `false` leaves the heading out.
|
|
110
|
+
- Accept a `headingLevel` option (`1`-`6`) for the heading's level. Nest any sub-headings one level deeper, whether or not the heading is shown. Default to the level that suits the section's usual place in a readme.
|
|
111
|
+
|
|
104
112
|
## Order and compound rules
|
|
105
113
|
|
|
106
114
|
- `order` defaults to `0`. Rules with a higher order run later. Use it when a rule reads content other rules generate. The bundled `table-of-contents` rule uses `order: 1` so that it sees headings produced by other rules.
|