From e7fdfcbdb4541555801539fa59ea1dce2975c9de Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 15 Jan 2025 09:21:26 -0500 Subject: [PATCH 1/3] feat: add options.explainer for blockREADME --- src/next/base.ts | 4 ++ src/next/blocks/blockREADME.test.ts | 92 ++++++++++++++++++++++++++++- src/next/blocks/blockREADME.ts | 14 +++-- 3 files changed, 104 insertions(+), 6 deletions(-) diff --git a/src/next/base.ts b/src/next/base.ts index cbab6de62..11e592f6d 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 548215b1e..452d47ca9 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 848736359..70f3c9cf4 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"; } From 8efcd9636deafd736263c179f9e5a4e25360d839 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 15 Jan 2025 09:30:45 -0500 Subject: [PATCH 2/3] Add option parsing too --- .../readDefaultsFromReadme.test.ts | 44 +++++++++++++++++++ .../readDefaultsFromReadme.ts | 16 ++++++- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts index c9559ccc0..f388168f6 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( diff --git a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts index 41fb6ed82..9b8aaf388 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) ?? From f0f9b1611220ed3870cd7d0eb1fd38ba4ac1716b Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 15 Jan 2025 09:31:39 -0500 Subject: [PATCH 3/3] And touchup to readLogoSizing --- .../options/createOptionDefaults/readDefaultsFromReadme.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts index f388168f6..82098f8ec 100644 --- a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts +++ b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts @@ -151,7 +151,7 @@ It is good. const logo = await readDefaultsFromReadme( () => Promise.resolve(` -Project logo: a fancy circle`), +Project logo: a fancy circle`), () => Promise.resolve(undefined), ).logo();