diff --git a/src/next/base.ts b/src/next/base.ts index cbab6de6..11e592f6 100644 --- a/src/next/base.ts +++ b/src/next/base.ts @@ -72,6 +72,10 @@ export const base = createBase({ .describe( "email address to be listed as the point of contact in docs and packages", ), + explainer: z + .array(z.string()) + .optional() + .describe("additional README.md sentence(s) describing the package"), funding: z .string() .optional() diff --git a/src/next/blocks/blockREADME.test.ts b/src/next/blocks/blockREADME.test.ts index 548215b1..452d47ca 100644 --- a/src/next/blocks/blockREADME.test.ts +++ b/src/next/blocks/blockREADME.test.ts @@ -62,7 +62,7 @@ describe("blockREADME", () => { "README.md": "

Test Title

- First sentence. + First sentence. Second sentence.

@@ -89,6 +89,47 @@ describe("blockREADME", () => { `); }); + test("options.explainer", () => { + const creation = testBlock(blockREADME, { + options: { + ...options, + explainer: ["And a one.", "And a two."], + }, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + "README.md": "

Test Title

+ +

Test description

+ +

+ ๐Ÿค Code of Conduct: Kept + ๐Ÿงช Coverage + ๐Ÿ“ License: MIT + ๐Ÿ“ฆ npm version + ๐Ÿ’ช TypeScript: Strict +

+ + And a one. + And a two. + + ## Usage + + Use it. + + ## Development + + See [\`.github/CONTRIBUTING.md\`](./.github/CONTRIBUTING.md), then [\`.github/DEVELOPMENT.md\`](./.github/DEVELOPMENT.md). + Thanks! ๐Ÿ’– + + ", + }, + } + `); + }); + test("options.logo without sizing", () => { const creation = testBlock(blockREADME, { options: { @@ -177,6 +218,55 @@ describe("blockREADME", () => { `); }); + test("options.explainer and options.logo", () => { + const creation = testBlock(blockREADME, { + options: { + ...options, + explainer: ["And a one.", "And a two."], + logo: { + alt: "My logo", + height: 100, + src: "img.jpg", + width: 128, + }, + }, + }); + + expect(creation).toMatchInlineSnapshot(` + { + "files": { + "README.md": "

Test Title

+ +

Test description

+ +

+ ๐Ÿค Code of Conduct: Kept + ๐Ÿงช Coverage + ๐Ÿ“ License: MIT + ๐Ÿ“ฆ npm version + ๐Ÿ’ช TypeScript: Strict +

+ + My logo + + And a one. + And a two. + + ## Usage + + Use it. + + ## Development + + See [\`.github/CONTRIBUTING.md\`](./.github/CONTRIBUTING.md), then [\`.github/DEVELOPMENT.md\`](./.github/DEVELOPMENT.md). + Thanks! ๐Ÿ’– + + ", + }, + } + `); + }); + test("without addons", () => { const creation = testBlock(blockREADME, { options, diff --git a/src/next/blocks/blockREADME.ts b/src/next/blocks/blockREADME.ts index 84873635..70f3c9cf 100644 --- a/src/next/blocks/blockREADME.ts +++ b/src/next/blocks/blockREADME.ts @@ -5,6 +5,7 @@ import { base } from "../base.js"; function printAttributes(attributes: Record) { return Object.entries(attributes) .map(([key, value]) => `${key}="${value}"`) + .sort() .join(" "); } @@ -20,9 +21,12 @@ export const blockREADME = base.createBlock({ produce({ addons, options }) { const { badges, notices, sections } = addons; - const logo = options.logo - ? `\n\n` - : ""; + const logo = + options.logo && + `\n\n`; + + const explainer = + options.explainer && `\n${options.explainer.join("\n")}\n`; return { files: { @@ -37,7 +41,7 @@ export const blockREADME = base.createBlock({ ๐Ÿ“ฆ npm version ๐Ÿ’ช TypeScript: Strict

-${logo} +${[logo, explainer].filter(Boolean).join("")} ## Usage ${options.usage} @@ -58,5 +62,5 @@ function formatDescription(description: string) { return description; } - return "\n\t" + description.replaceAll(". ", ". \n\t") + "\n"; + return "\n\t" + description.replaceAll(". ", ".\n\t") + "\n"; } diff --git a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts index c9559ccc..82098f8e 100644 --- a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts +++ b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts @@ -19,6 +19,50 @@ vi.mock("./getUsageFromReadme.js", () => ({ })); describe("readDefaultsFromReadme", () => { + describe("explainer", () => { + it("defaults to undefined when it cannot be found", async () => { + const explainer = await readDefaultsFromReadme( + () => Promise.resolve(`nothing.`), + () => Promise.resolve(undefined), + ).explainer(); + + expect(explainer).toBeUndefined(); + }); + + it("parses a line after badges", async () => { + const explainer = await readDefaultsFromReadme( + () => + Promise.resolve(` +

+ +This is my project. + +## Usage + .`), + () => Promise.resolve(undefined), + ).explainer(); + + expect(explainer).toEqual(["This is my project."]); + }); + + it("parses multiple line after badges", async () => { + const explainer = await readDefaultsFromReadme( + () => + Promise.resolve(` +

+ +This is my project. +It is good. + +## Usage + .`), + () => Promise.resolve(undefined), + ).explainer(); + + expect(explainer).toEqual(["This is my project.", "It is good."]); + }); + }); + describe("logo", () => { it("defaults to undefined when it cannot be found", async () => { const logo = await readDefaultsFromReadme( @@ -107,7 +151,7 @@ describe("readDefaultsFromReadme", () => { const logo = await readDefaultsFromReadme( () => Promise.resolve(` -Project logo: a fancy circle`), +Project logo: a fancy circle`), () => Promise.resolve(undefined), ).logo(); diff --git a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts index 41fb6ed8..9b8aaf38 100644 --- a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts +++ b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts @@ -13,6 +13,14 @@ export function readDefaultsFromReadme( ); return { + explainer: async () => { + return />\n\n([\s\S]*?)\n\n## Usage/u + .exec(await readme())?.[1] + .split("\n") + .map((line) => line.trim()) + .filter(Boolean); + }, + logo: async () => { const tag = await imageTag(); @@ -22,18 +30,22 @@ export function readDefaultsFromReadme( const src = /src\s*=(.+)['"/]>/ .exec(tag)?.[1] - ?.replaceAll(/^['"]|['"]$/g, ""); + ?.split(/\s*\w+=/)[0] + .replaceAll(/^['"]|['"]$/g, ""); if (!src) { return undefined; } return { - alt: /alt=['"](.+)['"]\s*src=/.exec(tag)?.[1] ?? "Project logo", + alt: + /alt=['"](.+)['"]\s*src=/.exec(tag)?.[1].split(/['"]?\s*\w+=/)[0] ?? + "Project logo", src, ...readLogoSizing(src), }; }, + title: async () => { const text = await readme(); const fromText = (/^(.+)<\/h1>/.exec(text) ??