mdat 2.2.1 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/cli.js CHANGED
@@ -42,7 +42,7 @@ function deepMergeDefined(...objects) {
42
42
  //#endregion
43
43
  //#region package.json
44
44
  var name = "mdat";
45
- var version = "2.2.1";
45
+ var version = "2.3.0";
46
46
 
47
47
  //#endregion
48
48
  //#region src/lib/log.ts
@@ -144,17 +144,52 @@ const readmeMetadataTemplate = defineTemplate((context) => {
144
144
  const licenseFileData = helpers.firstOf(licenseFile);
145
145
  const ciActionFilePath = helpers.ensureArray(githubActions).find((entry) => entry.data.name.toLowerCase() === "ci")?.source;
146
146
  const repositoryUrl = codemeta.codeRepository?.replace(GIT_PREFIX_REGEX, "").replace(GIT_SUFFIX_REGEX, "").replace(TRAILING_SLASH_REGEX, "");
147
+ const bin = (() => {
148
+ if (nodePackage === void 0) return;
149
+ const binField = nodePackage.bin;
150
+ if (binField === void 0) return;
151
+ if (typeof binField === "string") return [nodePackage.name];
152
+ const names = Object.keys(binField);
153
+ return names.length > 0 ? names : void 0;
154
+ })();
155
+ const engines = (() => {
156
+ const raw = nodePackage?.engines;
157
+ if (raw === void 0) return;
158
+ const entries = Object.entries(raw).filter((entry) => entry[1] !== void 0);
159
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
160
+ })();
161
+ const peerDependencies = (() => {
162
+ if (nodePackage === void 0) return;
163
+ const peers = nodePackage.peerDependencies;
164
+ if (peers === void 0) return;
165
+ const entries = Object.entries(peers).filter((entry) => entry[1] !== void 0);
166
+ if (entries.length === 0) return;
167
+ const meta = nodePackage.peerDependenciesMeta;
168
+ return entries.map(([name, version]) => ({
169
+ name,
170
+ optional: meta?.[name]?.optional === true,
171
+ version
172
+ }));
173
+ })();
174
+ const firstAuthor = helpers.firstOf(helpers.ensureArray(codemeta.author));
147
175
  return {
148
176
  author: helpers.firstOf(helpers.mixedStringsToArray(helpers.toBasicNames(codemeta.author))),
177
+ authorUrl: firstAuthor?.url,
178
+ bin,
149
179
  ciActionFileName: ciActionFilePath ? path.basename(ciActionFilePath) : void 0,
150
180
  description: codemeta.description,
151
- isPublicNpmPackage: !nodePackage?.name.startsWith("@") || nodePackage.publishConfig?.access === "public",
181
+ engines,
182
+ isPublicNpmPackage: nodePackage?.private !== true && (nodePackage?.name.startsWith("@") && nodePackage.publishConfig?.access === "public" || true),
152
183
  issuesUrl: codemeta.issueTracker,
153
184
  license: helpers.toBasicLicense(helpers.firstOf(helpers.ensureArray(codemeta.license))),
154
185
  licenseFilePath: licenseFileData?.source,
155
186
  name: codemeta.name,
187
+ operatingSystem: codemeta.operatingSystem,
188
+ peerDependencies,
156
189
  projectDirectory: metascope?.data.options.path === void 0 ? void 0 : `file://${metascope.data.options.path}`,
157
- repositoryUrl
190
+ repositoryUrl,
191
+ runtimePlatform: codemeta.runtimePlatform,
192
+ usesPnpm: helpers.usesPnpm(nodePackageJson)
158
193
  };
159
194
  });
160
195
  let readmeMetadata;
@@ -278,7 +313,85 @@ var code_default = { code: { async content(options) {
278
313
  var contributing_default = { contributing: { async content() {
279
314
  const { issuesUrl } = await getReadmeMetadata();
280
315
  if (issuesUrl === void 0) throw new Error("Could not find \"bugs.url\" entry in package.json");
281
- return `## Contributing\n[Issues](${issuesUrl}) and pull requests are welcome.`;
316
+ return [
317
+ "## Contributing",
318
+ "",
319
+ `[Issues](${issuesUrl}) are welcome and appreciated.`,
320
+ "",
321
+ "Please open an issue to discuss changes before submitting a pull request. Unsolicited PRs (especially AI-generated ones) are unlikely to be merged.",
322
+ "",
323
+ "This repository uses [@kitschpatrol/shared-config](https://github.com/kitschpatrol/shared-config) (via its `ksc` CLI) for linting and formatting, plus [MDAT](https://github.com/kitschpatrol/mdat) for readme placeholder expansion."
324
+ ].join("\n");
325
+ } } };
326
+
327
+ //#endregion
328
+ //#region src/lib/readme/rules/dependencies.ts
329
+ const PLATFORM_INFO = {
330
+ bun: {
331
+ display: "Bun",
332
+ url: "https://bun.sh/"
333
+ },
334
+ deno: {
335
+ display: "Deno",
336
+ url: "https://deno.land/"
337
+ },
338
+ go: {
339
+ display: "Go",
340
+ url: "https://go.dev/"
341
+ },
342
+ java: {
343
+ display: "Java",
344
+ url: "https://www.java.com/"
345
+ },
346
+ node: {
347
+ display: "Node.js",
348
+ url: "https://nodejs.org/"
349
+ },
350
+ python: {
351
+ display: "Python",
352
+ url: "https://www.python.org/"
353
+ },
354
+ ruby: {
355
+ display: "Ruby",
356
+ url: "https://www.ruby-lang.org/"
357
+ },
358
+ rust: {
359
+ display: "Rust",
360
+ url: "https://www.rust-lang.org/"
361
+ }
362
+ };
363
+ var dependencies_default = { dependencies: { async content() {
364
+ const { engines, operatingSystem, peerDependencies, runtimePlatform } = await getReadmeMetadata();
365
+ const platformItems = [];
366
+ if (engines !== void 0) for (const [name, version] of Object.entries(engines)) {
367
+ const info = PLATFORM_INFO[name.toLowerCase()];
368
+ const display = info ? `[${info.display}](${info.url})` : name;
369
+ platformItems.push(`- ${display} ${version}`);
370
+ }
371
+ if (runtimePlatform !== void 0) for (const entry of runtimePlatform) {
372
+ const spaceIndex = entry.indexOf(" ");
373
+ const platformKey = spaceIndex > 0 ? entry.slice(0, spaceIndex) : entry;
374
+ const version = spaceIndex > 0 ? entry.slice(spaceIndex + 1) : void 0;
375
+ if (engines?.[platformKey] !== void 0) continue;
376
+ const info = PLATFORM_INFO[platformKey.toLowerCase()];
377
+ const display = info ? `[${info.display}](${info.url})` : platformKey;
378
+ platformItems.push(version ? `- ${display} ${version}` : `- ${display}`);
379
+ }
380
+ if (operatingSystem !== void 0 && operatingSystem.length > 0) platformItems.push(`- Supported platforms: ${operatingSystem.join(", ")}`);
381
+ const peerItems = [];
382
+ if (peerDependencies !== void 0) for (const { name, optional, version } of peerDependencies) {
383
+ const npmUrl = `https://www.npmjs.com/package/${name}`;
384
+ const optionalSuffix = optional ? " _(optional)_" : "";
385
+ peerItems.push(`- [${name}](${npmUrl}) ${version}${optionalSuffix}`);
386
+ }
387
+ const hasPlatform = platformItems.length > 0;
388
+ const hasPeers = peerItems.length > 0;
389
+ if (!hasPlatform && !hasPeers) return "";
390
+ const sections = ["## Dependencies"];
391
+ if (hasPlatform && hasPeers) sections.push("", "### Platform", "", ...platformItems, "", "### Peer Dependencies", "", ...peerItems);
392
+ else if (hasPlatform) sections.push("", ...platformItems);
393
+ else sections.push("", ...peerItems);
394
+ return sections.join("\n");
282
395
  } } };
283
396
 
284
397
  //#endregion
@@ -295,10 +408,10 @@ var description_default = { description: { async content() {
295
408
  //#endregion
296
409
  //#region src/lib/readme/rules/license.ts
297
410
  var license_default = { license: { async content() {
298
- const { author, license, licenseFilePath } = await getReadmeMetadata();
411
+ const { author, authorUrl, license, licenseFilePath } = await getReadmeMetadata();
299
412
  if (author === void 0) throw new Error("Could not find author name in project");
300
413
  if (license === void 0 || licenseFilePath === void 0) throw new Error("Could not find license for project");
301
- return `## License\n[${license}](${licenseFilePath}) © ${author}`;
414
+ return `## License\n[${license}](${licenseFilePath}) © ${authorUrl === void 0 ? author : `[${author}](${authorUrl})`}`;
302
415
  } } };
303
416
 
304
417
  //#endregion
@@ -346,6 +459,25 @@ var header_default = { header: {
346
459
  order: 2
347
460
  } };
348
461
 
462
+ //#endregion
463
+ //#region src/lib/readme/rules/install.ts
464
+ var install_default = { install: { async content() {
465
+ const { bin, engines, isPublicNpmPackage, name, runtimePlatform, usesPnpm } = await getReadmeMetadata();
466
+ if (name === void 0) throw new Error("Could not find project name");
467
+ const lines = ["## Install"];
468
+ if (isPublicNpmPackage || engines?.node !== void 0 || runtimePlatform?.includes("node")) {
469
+ const pmAdd = usesPnpm ? "pnpm add" : "npm install";
470
+ const pmx = usesPnpm ? "pnpx" : "npx";
471
+ lines.push("", "```sh", `${pmAdd} ${name}`, "```");
472
+ if (bin !== void 0 && bin.length > 0) lines.push("", "Or run it directly:", "", "```sh", `${pmx} ${name}`, "```");
473
+ } else if (runtimePlatform?.some((p) => p.startsWith("python"))) lines.push("", "```sh", `pip install ${name}`, "```");
474
+ else if (runtimePlatform?.some((p) => p.startsWith("rust"))) lines.push("", "```sh", `cargo install ${name}`, "```");
475
+ else if (runtimePlatform?.some((p) => p.startsWith("go"))) lines.push("", "```sh", `go install ${name}@latest`, "```");
476
+ else if (runtimePlatform?.some((p) => p.startsWith("ruby"))) lines.push("", "```sh", `gem install ${name}`, "```");
477
+ else throw new Error("Could not determine project ecosystem for install instructions");
478
+ return lines.join("\n");
479
+ } } };
480
+
349
481
  //#endregion
350
482
  //#region src/lib/readme/rules/utilities/size/size-report.ts
351
483
  const brotliCompressAsync = promisify(brotliCompress);
@@ -504,9 +636,11 @@ var rules_default = {
504
636
  ...banner_default,
505
637
  ...code_default,
506
638
  ...contributing_default,
639
+ ...dependencies_default,
507
640
  ...description_default,
508
641
  ...footer_default,
509
642
  ...header_default,
643
+ ...install_default,
510
644
  ...license_default,
511
645
  ...short_description_default,
512
646
  ...size_default,
@@ -532,9 +666,9 @@ function getAdditionalConfigExplorer() {
532
666
  return _additionalConfigExplorer;
533
667
  }
534
668
  /**
535
- * Load and validate mdat configuration.
536
- * Uses cosmiconfig to search in the usual places.
537
- * Merge precedence: Base Defaults < Defaults < Searched Config < Additional Config
669
+ * Load and validate mdat configuration. Uses cosmiconfig to search in the usual
670
+ * places. Merge precedence: Base Defaults < Defaults < Searched Config <
671
+ * Additional Config
538
672
  */
539
673
  async function loadConfig(options) {
540
674
  const { additionalConfig, defaults = rules_default, searchFrom } = options ?? {};
@@ -6,39 +6,43 @@ import { VFile } from "vfile";
6
6
 
7
7
  //#region src/lib/config.d.ts
8
8
  /**
9
- * An mdat configuration is a record of expansion rules.
10
- * Keys become comment keywords, values define the expansion content.
9
+ * An mdat configuration is a record of expansion rules. Keys become comment
10
+ * keywords, values define the expansion content.
11
11
  */
12
12
  type Config = Record<string, Rule$1>;
13
13
  /**
14
- * Generously accept either string paths to .ts, .js, or .json config files,
15
- * or inline Config objects.
14
+ * Generously accept either string paths to .ts, .js, or .json config files, or
15
+ * inline Config objects.
16
16
  */
17
17
  type ConfigToLoad = Array<Config | string> | Config | string;
18
18
  /**
19
- * Load and validate mdat configuration.
20
- * Uses cosmiconfig to search in the usual places.
21
- * Merge precedence: Base Defaults < Defaults < Searched Config < Additional Config
19
+ * Load and validate mdat configuration. Uses cosmiconfig to search in the usual
20
+ * places. Merge precedence: Base Defaults < Defaults < Searched Config <
21
+ * Additional Config
22
22
  */
23
23
  declare function loadConfig(options?: {
24
24
  /**
25
25
  * Additional Config objects to merge.
26
26
  *
27
- * Strings are treated as paths to `ts`, `js`, or `json` config files.
28
- * These will be dynamically loaded by Cosmiconfig.
29
- * Accepts an individual item, or an array. Objects in the array will be merged right to left.
27
+ * Strings are treated as paths to `ts`, `js`, or `json` config files. These
28
+ * will be dynamically loaded by Cosmiconfig. Accepts an individual item, or
29
+ * an array. Objects in the array will be merged right to left.
30
30
  */
31
31
  additionalConfig?: ConfigToLoad;
32
32
  /**
33
- * Default rules that have higher priority than base defaults but lower than searched config.
34
- * Defaults to the built-in readme rules. Pass `{}` to disable.
33
+ * Default rules that have higher priority than base defaults but lower than
34
+ * searched config. Defaults to the built-in readme rules. Pass `{}` to
35
+ * disable.
36
+ */
37
+ defaults?: Config;
38
+ /**
39
+ * Search for config in specific directories, mainly useful for testing.
40
+ * Cosmiconfig default search paths used if unset.
35
41
  */
36
- defaults?: Config; /** Search for config in specific directories, mainly useful for testing. Cosmiconfig default search paths used if unset. */
37
42
  searchFrom?: string;
38
43
  }): Promise<Config>;
39
44
  /**
40
- * Convenience function for merging configs.
41
- * Rightmost config takes precedence.
45
+ * Convenience function for merging configs. Rightmost config takes precedence.
42
46
  */
43
47
  declare function mergeConfig(a: Config, b: Config): Config;
44
48
  /**
@@ -148,15 +152,28 @@ declare function checkString(markdown: string, config?: ConfigToLoad, options?:
148
152
  declare function getContextMetadata(): Promise<MetadataContext>;
149
153
  declare const readmeMetadataTemplate: _$metascope.Template<{
150
154
  author: string | undefined;
155
+ authorUrl: string | undefined;
156
+ bin: string[] | undefined;
151
157
  ciActionFileName: string | undefined;
152
158
  description: string | undefined;
159
+ engines: {
160
+ [k: string]: string;
161
+ } | undefined;
153
162
  isPublicNpmPackage: boolean;
154
163
  issuesUrl: string | undefined;
155
164
  license: string | undefined;
156
165
  licenseFilePath: string | undefined;
157
166
  name: string | undefined;
167
+ operatingSystem: string[] | undefined;
168
+ peerDependencies: {
169
+ name: string;
170
+ optional: boolean;
171
+ version: string;
172
+ }[] | undefined;
158
173
  projectDirectory: string | undefined;
159
174
  repositoryUrl: string | undefined;
175
+ runtimePlatform: string[] | undefined;
176
+ usesPnpm: boolean;
160
177
  }>;
161
178
  type ReadmeMetadata = ReturnType<typeof readmeMetadataTemplate>;
162
179
  /**
@@ -166,15 +183,28 @@ type ReadmeMetadata = ReturnType<typeof readmeMetadataTemplate>;
166
183
  */
167
184
  declare function getReadmeMetadata(): Promise<{
168
185
  author: string | undefined;
186
+ authorUrl: string | undefined;
187
+ bin: string[] | undefined;
169
188
  ciActionFileName: string | undefined;
170
189
  description: string | undefined;
190
+ engines: {
191
+ [k: string]: string;
192
+ } | undefined;
171
193
  isPublicNpmPackage: boolean;
172
194
  issuesUrl: string | undefined;
173
195
  license: string | undefined;
174
196
  licenseFilePath: string | undefined;
175
197
  name: string | undefined;
198
+ operatingSystem: string[] | undefined;
199
+ peerDependencies: {
200
+ name: string;
201
+ optional: boolean;
202
+ version: string;
203
+ }[] | undefined;
176
204
  projectDirectory: string | undefined;
177
205
  repositoryUrl: string | undefined;
206
+ runtimePlatform: string[] | undefined;
207
+ usesPnpm: boolean;
178
208
  }>;
179
209
  /**
180
210
  * Reset all cached metadata. Call between tests or when the underlying project
@@ -188,6 +218,6 @@ declare function resetMetadataCaches(): void;
188
218
  * Export this for library consumers to inject their own logger.
189
219
  * @param logger - Accepts either a LogLayer instance or a Console- or Stream-like log target
190
220
  */
191
- declare function setLogger(logger?: ILogBasic | ILogLayer): void;
221
+ declare function setLogger(logger?: ILogBasic | ILogLayer<unknown>): void;
192
222
  //#endregion
193
223
  export { type Config, type ReadmeMetadata, type Rule, check, checkString, collapse, collapseString, createReadme as create, createReadmeInteractive as createInteractive, defineConfig, expand, expandString, getContextMetadata, getReadmeMetadata, loadConfig, mergeConfig, resetMetadataCaches, setLogger, strip, stripString };
package/dist/lib/index.js CHANGED
@@ -140,17 +140,52 @@ const readmeMetadataTemplate = defineTemplate((context) => {
140
140
  const licenseFileData = helpers.firstOf(licenseFile);
141
141
  const ciActionFilePath = helpers.ensureArray(githubActions).find((entry) => entry.data.name.toLowerCase() === "ci")?.source;
142
142
  const repositoryUrl = codemeta.codeRepository?.replace(GIT_PREFIX_REGEX, "").replace(GIT_SUFFIX_REGEX, "").replace(TRAILING_SLASH_REGEX, "");
143
+ const bin = (() => {
144
+ if (nodePackage === void 0) return;
145
+ const binField = nodePackage.bin;
146
+ if (binField === void 0) return;
147
+ if (typeof binField === "string") return [nodePackage.name];
148
+ const names = Object.keys(binField);
149
+ return names.length > 0 ? names : void 0;
150
+ })();
151
+ const engines = (() => {
152
+ const raw = nodePackage?.engines;
153
+ if (raw === void 0) return;
154
+ const entries = Object.entries(raw).filter((entry) => entry[1] !== void 0);
155
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
156
+ })();
157
+ const peerDependencies = (() => {
158
+ if (nodePackage === void 0) return;
159
+ const peers = nodePackage.peerDependencies;
160
+ if (peers === void 0) return;
161
+ const entries = Object.entries(peers).filter((entry) => entry[1] !== void 0);
162
+ if (entries.length === 0) return;
163
+ const meta = nodePackage.peerDependenciesMeta;
164
+ return entries.map(([name, version]) => ({
165
+ name,
166
+ optional: meta?.[name]?.optional === true,
167
+ version
168
+ }));
169
+ })();
170
+ const firstAuthor = helpers.firstOf(helpers.ensureArray(codemeta.author));
143
171
  return {
144
172
  author: helpers.firstOf(helpers.mixedStringsToArray(helpers.toBasicNames(codemeta.author))),
173
+ authorUrl: firstAuthor?.url,
174
+ bin,
145
175
  ciActionFileName: ciActionFilePath ? path.basename(ciActionFilePath) : void 0,
146
176
  description: codemeta.description,
147
- isPublicNpmPackage: !nodePackage?.name.startsWith("@") || nodePackage.publishConfig?.access === "public",
177
+ engines,
178
+ isPublicNpmPackage: nodePackage?.private !== true && (nodePackage?.name.startsWith("@") && nodePackage.publishConfig?.access === "public" || true),
148
179
  issuesUrl: codemeta.issueTracker,
149
180
  license: helpers.toBasicLicense(helpers.firstOf(helpers.ensureArray(codemeta.license))),
150
181
  licenseFilePath: licenseFileData?.source,
151
182
  name: codemeta.name,
183
+ operatingSystem: codemeta.operatingSystem,
184
+ peerDependencies,
152
185
  projectDirectory: metascope?.data.options.path === void 0 ? void 0 : `file://${metascope.data.options.path}`,
153
- repositoryUrl
186
+ repositoryUrl,
187
+ runtimePlatform: codemeta.runtimePlatform,
188
+ usesPnpm: helpers.usesPnpm(nodePackageJson)
154
189
  };
155
190
  });
156
191
  let readmeMetadata;
@@ -287,7 +322,84 @@ var code_default = { code: { async content(options) {
287
322
  var contributing_default = { contributing: { async content() {
288
323
  const { issuesUrl } = await getReadmeMetadata();
289
324
  if (issuesUrl === void 0) throw new Error("Could not find \"bugs.url\" entry in package.json");
290
- return `## Contributing\n[Issues](${issuesUrl}) and pull requests are welcome.`;
325
+ return [
326
+ "## Contributing",
327
+ "",
328
+ `[Issues](${issuesUrl}) are welcome and appreciated.`,
329
+ "",
330
+ "Please open an issue to discuss changes before submitting a pull request. Unsolicited PRs (especially AI-generated ones) are unlikely to be merged.",
331
+ "",
332
+ "This repository uses [@kitschpatrol/shared-config](https://github.com/kitschpatrol/shared-config) (via its `ksc` CLI) for linting and formatting, plus [MDAT](https://github.com/kitschpatrol/mdat) for readme placeholder expansion."
333
+ ].join("\n");
334
+ } } };
335
+ //#endregion
336
+ //#region src/lib/readme/rules/dependencies.ts
337
+ const PLATFORM_INFO = {
338
+ bun: {
339
+ display: "Bun",
340
+ url: "https://bun.sh/"
341
+ },
342
+ deno: {
343
+ display: "Deno",
344
+ url: "https://deno.land/"
345
+ },
346
+ go: {
347
+ display: "Go",
348
+ url: "https://go.dev/"
349
+ },
350
+ java: {
351
+ display: "Java",
352
+ url: "https://www.java.com/"
353
+ },
354
+ node: {
355
+ display: "Node.js",
356
+ url: "https://nodejs.org/"
357
+ },
358
+ python: {
359
+ display: "Python",
360
+ url: "https://www.python.org/"
361
+ },
362
+ ruby: {
363
+ display: "Ruby",
364
+ url: "https://www.ruby-lang.org/"
365
+ },
366
+ rust: {
367
+ display: "Rust",
368
+ url: "https://www.rust-lang.org/"
369
+ }
370
+ };
371
+ var dependencies_default = { dependencies: { async content() {
372
+ const { engines, operatingSystem, peerDependencies, runtimePlatform } = await getReadmeMetadata();
373
+ const platformItems = [];
374
+ if (engines !== void 0) for (const [name, version] of Object.entries(engines)) {
375
+ const info = PLATFORM_INFO[name.toLowerCase()];
376
+ const display = info ? `[${info.display}](${info.url})` : name;
377
+ platformItems.push(`- ${display} ${version}`);
378
+ }
379
+ if (runtimePlatform !== void 0) for (const entry of runtimePlatform) {
380
+ const spaceIndex = entry.indexOf(" ");
381
+ const platformKey = spaceIndex > 0 ? entry.slice(0, spaceIndex) : entry;
382
+ const version = spaceIndex > 0 ? entry.slice(spaceIndex + 1) : void 0;
383
+ if (engines?.[platformKey] !== void 0) continue;
384
+ const info = PLATFORM_INFO[platformKey.toLowerCase()];
385
+ const display = info ? `[${info.display}](${info.url})` : platformKey;
386
+ platformItems.push(version ? `- ${display} ${version}` : `- ${display}`);
387
+ }
388
+ if (operatingSystem !== void 0 && operatingSystem.length > 0) platformItems.push(`- Supported platforms: ${operatingSystem.join(", ")}`);
389
+ const peerItems = [];
390
+ if (peerDependencies !== void 0) for (const { name, optional, version } of peerDependencies) {
391
+ const npmUrl = `https://www.npmjs.com/package/${name}`;
392
+ const optionalSuffix = optional ? " _(optional)_" : "";
393
+ peerItems.push(`- [${name}](${npmUrl}) ${version}${optionalSuffix}`);
394
+ }
395
+ const hasPlatform = platformItems.length > 0;
396
+ const hasPeers = peerItems.length > 0;
397
+ if (!hasPlatform && !hasPeers) return "";
398
+ const sections = ["## Dependencies"];
399
+ if (hasPlatform && hasPeers) sections.push("", "### Platform", "", ...platformItems, "", "### Peer Dependencies", "", ...peerItems);
400
+ else if (hasPlatform) sections.push("", ...platformItems);
401
+ else sections.push("", ...peerItems);
402
+ return sections.join("\n");
291
403
  } } };
292
404
  //#endregion
293
405
  //#region src/lib/readme/rules/description.ts
@@ -302,10 +414,10 @@ var description_default = { description: { async content() {
302
414
  //#endregion
303
415
  //#region src/lib/readme/rules/license.ts
304
416
  var license_default = { license: { async content() {
305
- const { author, license, licenseFilePath } = await getReadmeMetadata();
417
+ const { author, authorUrl, license, licenseFilePath } = await getReadmeMetadata();
306
418
  if (author === void 0) throw new Error("Could not find author name in project");
307
419
  if (license === void 0 || licenseFilePath === void 0) throw new Error("Could not find license for project");
308
- return `## License\n[${license}](${licenseFilePath}) © ${author}`;
420
+ return `## License\n[${license}](${licenseFilePath}) © ${authorUrl === void 0 ? author : `[${author}](${authorUrl})`}`;
309
421
  } } };
310
422
  //#endregion
311
423
  //#region src/lib/readme/rules/footer.ts
@@ -349,6 +461,24 @@ var header_default = { header: {
349
461
  order: 2
350
462
  } };
351
463
  //#endregion
464
+ //#region src/lib/readme/rules/install.ts
465
+ var install_default = { install: { async content() {
466
+ const { bin, engines, isPublicNpmPackage, name, runtimePlatform, usesPnpm } = await getReadmeMetadata();
467
+ if (name === void 0) throw new Error("Could not find project name");
468
+ const lines = ["## Install"];
469
+ if (isPublicNpmPackage || engines?.node !== void 0 || runtimePlatform?.includes("node")) {
470
+ const pmAdd = usesPnpm ? "pnpm add" : "npm install";
471
+ const pmx = usesPnpm ? "pnpx" : "npx";
472
+ lines.push("", "```sh", `${pmAdd} ${name}`, "```");
473
+ if (bin !== void 0 && bin.length > 0) lines.push("", "Or run it directly:", "", "```sh", `${pmx} ${name}`, "```");
474
+ } else if (runtimePlatform?.some((p) => p.startsWith("python"))) lines.push("", "```sh", `pip install ${name}`, "```");
475
+ else if (runtimePlatform?.some((p) => p.startsWith("rust"))) lines.push("", "```sh", `cargo install ${name}`, "```");
476
+ else if (runtimePlatform?.some((p) => p.startsWith("go"))) lines.push("", "```sh", `go install ${name}@latest`, "```");
477
+ else if (runtimePlatform?.some((p) => p.startsWith("ruby"))) lines.push("", "```sh", `gem install ${name}`, "```");
478
+ else throw new Error("Could not determine project ecosystem for install instructions");
479
+ return lines.join("\n");
480
+ } } };
481
+ //#endregion
352
482
  //#region src/lib/readme/rules/utilities/size/size-report.ts
353
483
  const brotliCompressAsync = promisify(brotliCompress);
354
484
  const gzipCompressAsync = promisify(gzip);
@@ -501,9 +631,11 @@ var rules_default = {
501
631
  ...banner_default,
502
632
  ...code_default,
503
633
  ...contributing_default,
634
+ ...dependencies_default,
504
635
  ...description_default,
505
636
  ...footer_default,
506
637
  ...header_default,
638
+ ...install_default,
507
639
  ...license_default,
508
640
  ...short_description_default,
509
641
  ...size_default,
@@ -528,9 +660,9 @@ function getAdditionalConfigExplorer() {
528
660
  return _additionalConfigExplorer;
529
661
  }
530
662
  /**
531
- * Load and validate mdat configuration.
532
- * Uses cosmiconfig to search in the usual places.
533
- * Merge precedence: Base Defaults < Defaults < Searched Config < Additional Config
663
+ * Load and validate mdat configuration. Uses cosmiconfig to search in the usual
664
+ * places. Merge precedence: Base Defaults < Defaults < Searched Config <
665
+ * Additional Config
534
666
  */
535
667
  async function loadConfig(options) {
536
668
  const { additionalConfig, defaults = rules_default, searchFrom } = options ?? {};
@@ -581,8 +713,7 @@ function validateConfig(value) {
581
713
  log.warn(`Config has the wrong shape. Ignoring and using default configuration:\n${JSON.stringify(value, void 0, 2)}`);
582
714
  }
583
715
  /**
584
- * Convenience function for merging configs.
585
- * Rightmost config takes precedence.
716
+ * Convenience function for merging configs. Rightmost config takes precedence.
586
717
  */
587
718
  function mergeConfig(a, b) {
588
719
  return deepMergeDefined(a, b);
@@ -775,46 +906,28 @@ function getStripProcessor(_config, ambientRemarkConfig) {
775
906
  });
776
907
  }
777
908
  //#endregion
778
- //#region src/lib/readme/templates/mdat-readme-compound.md?raw
779
- var mdat_readme_compound_default = "<!-- header -->\n\n<!-- table-of-contents -->\n\n## Overview\n\n## Getting started\n\n### Dependencies\n\n### Installation\n\n## Usage\n\n### Library\n\n#### API\n\n#### Examples\n\n### CLI\n\n#### Commands\n\n#### Examples\n\n## Background\n\n### Motivation\n\n### Implementation notes\n\n### Similar projects\n\n## The future\n\n## Maintainers\n\n_List maintainer(s) for a repository, along with one way of contacting them (e.g. GitHub link or email)._\n\n## Acknowledgments\n\n_State anyone or anything that significantly helped with the development of your project. State public contact hyper-links if applicable._\n\n<!-- footer -->\n";
780
- //#endregion
781
- //#region src/lib/readme/templates/mdat-readme.md?raw
782
- var mdat_readme_default = "<!-- title -->\n\n<!-- banner -->\n\n<!-- badges -->\n\n<!-- short-description -->\n\n<!-- table-of-contents -->\n\n## Overview\n\n## Getting started\n\n### Dependencies\n\n### Installation\n\n## Usage\n\n### Library\n\n#### API\n\n#### Examples\n\n### CLI\n\n#### Commands\n\n#### Examples\n\n## Background\n\n### Motivation\n\n### Implementation notes\n\n### Similar projects\n\n## The future\n\n## Maintainers\n\n_List maintainer(s) for a repository, along with one way of contacting them (e.g. GitHub link or email)._\n\n## Acknowledgments\n\n_State anyone or anything that significantly helped with the development of your project. State public contact hyper-links if applicable._\n\n<!-- contributing -->\n\n<!-- license -->\n";
783
- //#endregion
784
- //#region src/lib/readme/templates/standard-readme-basic-compound.md?raw
785
- var standard_readme_basic_compound_default = "<!-- header -->\n\n## Install\n\n```sh\n# Code block illustrating how to install.\n```\n\n## Usage\n\n```ts\n// Code block illustrating common usage.\n// Consider using the <!-- code({ src: \"path/to/example.ts\" }) --> comment as well.\n```\n\n<!-- footer -->\n";
786
- //#endregion
787
- //#region src/lib/readme/templates/standard-readme-basic.md?raw
788
- var standard_readme_basic_default = "<!-- title -->\n\n<!-- short-description -->\n\n<!-- table-of-contents -->\n\n## Install\n\n```sh\n# Code block illustrating how to install.\n```\n\n## Usage\n\n```ts\n// Code block illustrating common usage.\n// Consider using the <!-- code({ src: \"path/to/example.ts\" })--> comment as well.\n```\n\n<!-- contributing -->\n\n<!-- license -->\n";
789
- //#endregion
790
- //#region src/lib/readme/templates/standard-readme-full-compound.md?raw
791
- var standard_readme_full_compound_default = "<!-- header -->\n\n_Long description goes here._\n\n_No heading. Cover the main reasons for building the repository. This should describe your module in broad terms, generally in just a few paragraphs; more detail of the module's routines or methods, lengthy code examples, or other in-depth material should be given in subsequent sections._\n\n<!-- table-of-contents -->\n\n## Background\n\n_Cover motivation. Cover abstract dependencies. Cover intellectual provenance: A See Also section is also fitting._\n\n### See also\n\n## Install\n\n```sh\n# Code block illustrating how to install.\n```\n\n### Dependencies\n\n_Required if there are unusual dependencies or dependencies that must be manually installed._\n\n## Usage\n\n```ts\n// Code block illustrating common usage.\n// Consider using the <!-- code({ src: \"path/to/example.ts\" })--> comment as well.\n```\n\n```ts\n// If importable, code block indicating both import functionality and usage.\n```\n\n### CLI\n\n```ts\n// If CLI compatible, code block indicating common usage.\n```\n\n## _Extra Sections (Rename)_\n\n## Security\n\n## API\n\n_Describe exported functions and objects._\n\n- _Describe signatures, return types, callbacks, and events._\n- _Cover types covered where not obvious._\n- _Describe caveats._\n- _If using an external API generator (like go-doc, js-doc, or so on), point to an external API.md file. This can be the only item in the section, if present._\n\n## Maintainers\n\n_List maintainer(s) for a repository, along with one way of contacting them (e.g. GitHub link or email)._\n\n## Thanks\n\n_State anyone or anything that significantly helped with the development of your project. State public contact hyper-links if applicable._\n\n<!-- footer -->\n";
792
- //#endregion
793
- //#region src/lib/readme/templates/standard-readme-full.md?raw
794
- var standard_readme_full_default = "<!-- title -->\n\n<!-- banner -->\n\n<!-- badges -->\n\n<!-- short-description -->\n\n_Long description goes here._\n\n_No heading. Cover the main reasons for building the repository. This should describe your module in broad terms, generally in just a few paragraphs; more detail of the module's routines or methods, lengthy code examples, or other in-depth material should be given in subsequent sections._\n\n<!-- table-of-contents -->\n\n## Background\n\n_Cover motivation. Cover abstract dependencies. Cover intellectual provenance: A See Also section is also fitting._\n\n### See also\n\n## Install\n\n```sh\n# Code block illustrating how to install.\n```\n\n### Dependencies\n\n_Required if there are unusual dependencies or dependencies that must be manually installed._\n\n## Usage\n\n```ts\n// Code block illustrating common usage.\n// Consider using the <!-- code({ src: \"path/to/example.ts\" })--> comment as well.\n```\n\n```ts\n// If importable, code block indicating both import functionality and usage.\n```\n\n### CLI\n\n```ts\n// If CLI compatible, code block indicating common usage.\n```\n\n## _Extra Sections (Rename)_\n\n## Security\n\n## API\n\n_Describe exported functions and objects._\n\n- _Describe signatures, return types, callbacks, and events._\n- _Cover types covered where not obvious._\n- _Describe caveats._\n- _If using an external API generator (like go-doc, js-doc, or so on), point to an external API.md file. This can be the only item in the section, if present._\n\n## Maintainers\n\n_List maintainer(s) for a repository, along with one way of contacting them (e.g. GitHub link or email)._\n\n## Thanks\n\n_State anyone or anything that significantly helped with the development of your project. State public contact hyper-links if applicable._\n\n<!-- contributing -->\n\n<!-- license -->\n";
795
- //#endregion
796
909
  //#region src/lib/readme/templates/index.ts
797
910
  var templates_default = {
798
911
  "MDAT Readme": {
799
912
  content: {
800
- compound: mdat_readme_compound_default,
801
- explicit: mdat_readme_default
913
+ compound: "<!-- header -->\n\n<!-- table-of-contents -->\n\n## Overview\n\n## Getting started\n\n### Dependencies\n\n### Installation\n\n## Usage\n\n### Library\n\n#### API\n\n#### Examples\n\n### CLI\n\n#### Commands\n\n#### Examples\n\n## Background\n\n### Motivation\n\n### Implementation notes\n\n### Similar projects\n\n## The future\n\n## Maintainers\n\n_List maintainer(s) for a repository, along with one way of contacting them (e.g. GitHub link or email)._\n\n## Acknowledgments\n\n_State anyone or anything that significantly helped with the development of your project. State public contact hyper-links if applicable._\n\n<!-- footer -->\n",
914
+ explicit: "<!-- title -->\n\n<!-- banner -->\n\n<!-- badges -->\n\n<!-- short-description -->\n\n<!-- table-of-contents -->\n\n## Overview\n\n## Getting started\n\n### Dependencies\n\n### Installation\n\n## Usage\n\n### Library\n\n#### API\n\n#### Examples\n\n### CLI\n\n#### Commands\n\n#### Examples\n\n## Background\n\n### Motivation\n\n### Implementation notes\n\n### Similar projects\n\n## The future\n\n## Maintainers\n\n_List maintainer(s) for a repository, along with one way of contacting them (e.g. GitHub link or email)._\n\n## Acknowledgments\n\n_State anyone or anything that significantly helped with the development of your project. State public contact hyper-links if applicable._\n\n<!-- contributing -->\n\n<!-- license -->\n"
802
915
  },
803
916
  description: "The house style. An expansive starting point. Prune to your context and taste.",
804
917
  exampleLink: "https://github.com/kitschpatrol/mdat/blob/main/readme.md"
805
918
  },
806
919
  "Standard Readme Basic": {
807
920
  content: {
808
- compound: standard_readme_basic_compound_default,
809
- explicit: standard_readme_basic_default
921
+ compound: "<!-- header -->\n\n## Install\n\n```sh\n# Code block illustrating how to install.\n```\n\n## Usage\n\n```ts\n// Code block illustrating common usage.\n// Consider using the <!-- code({ src: \"path/to/example.ts\" }) --> comment as well.\n```\n\n<!-- footer -->\n",
922
+ explicit: "<!-- title -->\n\n<!-- short-description -->\n\n<!-- table-of-contents -->\n\n## Install\n\n```sh\n# Code block illustrating how to install.\n```\n\n## Usage\n\n```ts\n// Code block illustrating common usage.\n// Consider using the <!-- code({ src: \"path/to/example.ts\" })--> comment as well.\n```\n\n<!-- contributing -->\n\n<!-- license -->\n"
810
923
  },
811
924
  description: "Includes only the \"required\" sections from the Standard Readme specification.",
812
925
  exampleLink: "https://github.com/RichardLitt/standard-readme/blob/main/example-readmes/minimal-readme.md"
813
926
  },
814
927
  "Standard Readme Full": {
815
928
  content: {
816
- compound: standard_readme_full_compound_default,
817
- explicit: standard_readme_full_default
929
+ compound: "<!-- header -->\n\n_Long description goes here._\n\n_No heading. Cover the main reasons for building the repository. This should describe your module in broad terms, generally in just a few paragraphs; more detail of the module's routines or methods, lengthy code examples, or other in-depth material should be given in subsequent sections._\n\n<!-- table-of-contents -->\n\n## Background\n\n_Cover motivation. Cover abstract dependencies. Cover intellectual provenance: A See Also section is also fitting._\n\n### See also\n\n## Install\n\n```sh\n# Code block illustrating how to install.\n```\n\n### Dependencies\n\n_Required if there are unusual dependencies or dependencies that must be manually installed._\n\n## Usage\n\n```ts\n// Code block illustrating common usage.\n// Consider using the <!-- code({ src: \"path/to/example.ts\" })--> comment as well.\n```\n\n```ts\n// If importable, code block indicating both import functionality and usage.\n```\n\n### CLI\n\n```ts\n// If CLI compatible, code block indicating common usage.\n```\n\n## _Extra Sections (Rename)_\n\n## Security\n\n## API\n\n_Describe exported functions and objects._\n\n- _Describe signatures, return types, callbacks, and events._\n- _Cover types covered where not obvious._\n- _Describe caveats._\n- _If using an external API generator (like go-doc, js-doc, or so on), point to an external API.md file. This can be the only item in the section, if present._\n\n## Maintainers\n\n_List maintainer(s) for a repository, along with one way of contacting them (e.g. GitHub link or email)._\n\n## Thanks\n\n_State anyone or anything that significantly helped with the development of your project. State public contact hyper-links if applicable._\n\n<!-- footer -->\n",
930
+ explicit: "<!-- title -->\n\n<!-- banner -->\n\n<!-- badges -->\n\n<!-- short-description -->\n\n_Long description goes here._\n\n_No heading. Cover the main reasons for building the repository. This should describe your module in broad terms, generally in just a few paragraphs; more detail of the module's routines or methods, lengthy code examples, or other in-depth material should be given in subsequent sections._\n\n<!-- table-of-contents -->\n\n## Background\n\n_Cover motivation. Cover abstract dependencies. Cover intellectual provenance: A See Also section is also fitting._\n\n### See also\n\n## Install\n\n```sh\n# Code block illustrating how to install.\n```\n\n### Dependencies\n\n_Required if there are unusual dependencies or dependencies that must be manually installed._\n\n## Usage\n\n```ts\n// Code block illustrating common usage.\n// Consider using the <!-- code({ src: \"path/to/example.ts\" })--> comment as well.\n```\n\n```ts\n// If importable, code block indicating both import functionality and usage.\n```\n\n### CLI\n\n```ts\n// If CLI compatible, code block indicating common usage.\n```\n\n## _Extra Sections (Rename)_\n\n## Security\n\n## API\n\n_Describe exported functions and objects._\n\n- _Describe signatures, return types, callbacks, and events._\n- _Cover types covered where not obvious._\n- _Describe caveats._\n- _If using an external API generator (like go-doc, js-doc, or so on), point to an external API.md file. This can be the only item in the section, if present._\n\n## Maintainers\n\n_List maintainer(s) for a repository, along with one way of contacting them (e.g. GitHub link or email)._\n\n## Thanks\n\n_State anyone or anything that significantly helped with the development of your project. State public contact hyper-links if applicable._\n\n<!-- contributing -->\n\n<!-- license -->\n"
818
931
  },
819
932
  description: "Includes all sections from the Standard Readme specification.",
820
933
  exampleLink: "https://github.com/RichardLitt/standard-readme/blob/main/example-readmes/maximal-readme.md"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mdat",
3
- "version": "2.2.1",
3
+ "version": "2.3.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",
@@ -45,12 +45,12 @@
45
45
  "dependencies": {
46
46
  "@clack/prompts": "^1.2.0",
47
47
  "cosmiconfig": "^9.0.1",
48
- "cosmiconfig-typescript-loader": "^6.2.0",
48
+ "cosmiconfig-typescript-loader": "^6.3.0",
49
49
  "deepmerge-ts": "^7.1.5",
50
50
  "globby": "^16.2.0",
51
51
  "lognow": "^0.6.1",
52
52
  "mdast-util-toc": "^7.1.0",
53
- "metascope": "^0.6.3",
53
+ "metascope": "^0.7.0",
54
54
  "path-type": "^6.0.0",
55
55
  "picocolors": "^1.1.1",
56
56
  "plur": "^6.0.0",
@@ -58,9 +58,9 @@
58
58
  "pretty-ms": "^9.3.0",
59
59
  "remark": "^15.0.1",
60
60
  "remark-gfm": "^4.0.1",
61
- "remark-mdat": "^2.2.0",
61
+ "remark-mdat": "^2.2.1",
62
62
  "to-vfile": "^8.0.0",
63
- "type-fest": "^5.5.0",
63
+ "type-fest": "^5.6.0",
64
64
  "unified-engine": "^11.2.2",
65
65
  "untildify": "^6.0.0",
66
66
  "vfile": "^6.0.3",
@@ -69,7 +69,7 @@
69
69
  },
70
70
  "devDependencies": {
71
71
  "@arethetypeswrong/core": "^0.18.2",
72
- "@kitschpatrol/shared-config": "^7.1.0",
72
+ "@kitschpatrol/shared-config": "^7.4.1",
73
73
  "@types/mdast": "^4.0.4",
74
74
  "@types/node": "~22.17.2",
75
75
  "@types/unist": "^3.0.3",
@@ -77,16 +77,17 @@
77
77
  "@vitest/coverage-v8": "4.1.2",
78
78
  "bumpp": "^11.0.1",
79
79
  "execa": "^9.6.1",
80
- "mdat-plugin-cli-help": "^2.1.1",
80
+ "mdat-plugin-cli-help": "^2.1.2",
81
81
  "mdat-plugin-example": "^2.0.0",
82
82
  "mdat-plugin-tldraw": "^2.0.1",
83
- "nanoid": "^5.1.7",
84
- "prettier": "^3.8.1",
83
+ "nanoid": "^5.1.9",
84
+ "prettier": "^3.8.3",
85
85
  "publint": "^0.3.18",
86
- "tsdown": "^0.21.7",
87
- "typescript": "~5.9.3",
86
+ "shx": "^0.4.0",
87
+ "tsdown": "^0.21.9",
88
+ "typescript": "~6.0.3",
88
89
  "unplugin-raw": "^0.7.0",
89
- "vitest": "^4.1.3"
90
+ "vitest": "^4.1.5"
90
91
  },
91
92
  "peerDependencies": {
92
93
  "prettier": "^3.0.0"
@@ -109,11 +110,11 @@
109
110
  "bench": "vitest bench --run --compare test/benchmarks/baseline.json",
110
111
  "bench:baseline": "vitest bench --run --outputJson test/benchmarks/baseline.json",
111
112
  "build": "tsdown",
112
- "clean": "git rm -f pnpm-lock.yaml ; git clean -fdX",
113
+ "clean": "shx rm -f pnpm-lock.yaml && git clean -fdX -e !.claude/",
113
114
  "coverage": "vitest --coverage --run",
114
115
  "fix": "ksc fix",
115
116
  "lint": "ksc lint",
116
- "release": "bumpp --commit 'Release: %s' && pnpm run build && NPM_AUTH_TOKEN=$(op read 'op://Personal/npm/token') && pnpm publish",
117
+ "release": "bumpp --commit 'Release: %s' && pnpm build && NPM_AUTH_TOKEN=$(op read 'op://Personal/npm/token') && pnpm publish",
117
118
  "test": "vitest run"
118
119
  }
119
120
  }
package/readme.md CHANGED
@@ -572,6 +572,14 @@ See the [Examples section](https://github.com/kitschpatrol/remark-mdat#examples)
572
572
 
573
573
  The project description. Also aliased as `<!-- short-description -->` for [standard-readme](https://github.com/RichardLitt/standard-readme/blob/main/spec.md) compatibility.
574
574
 
575
+ - ##### `<!-- install -->`
576
+
577
+ Ecosystem-aware install instructions derived from project metadata. Emits `pnpm add` / `npm install` for Node packages (plus a `pnpx` / `npx` hint when the project exposes a binary), and falls back to `pip`, `cargo`, `gem`, or `go install` for Python, Rust, Ruby, and Go projects.
578
+
579
+ - ##### `<!-- dependencies -->`
580
+
581
+ Documents platform requirements and peer dependencies. Lists runtime platforms (Node, Python, Rust, Go, Ruby, etc.) with version constraints from `engines` or equivalent metadata, supported operating systems, and peer dependencies with links to npm.
582
+
575
583
  - ##### `<!-- table-of-contents -->`
576
584
 
577
585
  Auto-generated via [mdast-util-toc](https://github.com/syntax-tree/mdast-util-toc). Also aliased as `<!-- toc -->`.
@@ -600,7 +608,7 @@ See the [Examples section](https://github.com/kitschpatrol/remark-mdat#examples)
600
608
 
601
609
  | File | Original | Gzip | Brotli |
602
610
  | ----------- | -------- | ----- | ------ |
603
- | .gitignore | 305 B | 245 B | 216 B |
611
+ | .gitignore | 318 B | 252 B | 237 B |
604
612
  | license.txt | 1 kB | 659 B | 468 B |
605
613
 
606
614
  <!-- /size-table -->
@@ -794,13 +802,17 @@ There's quite a bit of prior art and similar explorations of this problem space:
794
802
 
795
803
  - VitePress' [Markdown file inclusion](https://vitepress.dev/guide/markdown#markdown-file-inclusion)
796
804
 
805
+ - Hiroki Osame's [comment-mark](https://github.com/privatenumber/comment-mark)
806
+
807
+ - Hiroki Osame's [mdeval](https://github.com/privatenumber/mdeval)
808
+
797
809
  ### Implementation notes
798
810
 
799
811
  This project was split from a monorepo containing both `mdat` and `remark-mdat` into separate repos in July 2024.
800
812
 
801
813
  ## Maintainers
802
814
 
803
- [@kitschpatrol](https://github.com/kitschpatrol)
815
+ [kitschpatrol](https://github.com/kitschpatrol)
804
816
 
805
817
  ## Acknowledgments
806
818
 
package/dist/.DS_Store DELETED
Binary file