diff --git a/src/next/base.ts b/src/next/base.ts index e0edf286..7aff74e0 100644 --- a/src/next/base.ts +++ b/src/next/base.ts @@ -126,6 +126,10 @@ export const base = createBase({ .string() .describe("'Sentence case.' description of the repository"), title: z.string().describe("'Title Case' title for the repository"), + usage: z + .string() + .optional() + .describe("markdown docs to put in README.md under the ## Usage heading"), version: z .string() .optional() @@ -223,7 +227,7 @@ export const base = createBase({ options.repository ?? (await gitDefaults())?.name ?? (await packageData()).name, - ...readDefaultsFromReadme(readme), + ...readDefaultsFromReadme(readme, options.repository), version, }; }, diff --git a/src/next/blocks/blockREADME.test.ts b/src/next/blocks/blockREADME.test.ts index 825121a5..28c24464 100644 --- a/src/next/blocks/blockREADME.test.ts +++ b/src/next/blocks/blockREADME.test.ts @@ -4,11 +4,16 @@ import { describe, expect, test } from "vitest"; import { blockREADME } from "./blockREADME.js"; import { optionsBase } from "./options.fakes.js"; +const options = { + ...optionsBase, + usage: "Use it.", +}; + describe("blockREADME", () => { test("options.logo without sizing", () => { const creation = testBlock(blockREADME, { options: { - ...optionsBase, + ...options, logo: { alt: "My logo", src: "img.jpg", @@ -39,14 +44,7 @@ describe("blockREADME", () => { ## Usage - \`\`\`shell - npm i test-repository - \`\`\` - \`\`\`ts - import { greet } from "test-repository"; - - greet("Hello, world! 💖"); - \`\`\` + Use it. ## Development @@ -68,7 +66,7 @@ describe("blockREADME", () => { test("options.logo with sizing", () => { const creation = testBlock(blockREADME, { options: { - ...optionsBase, + ...options, logo: { alt: "My logo", height: 100, @@ -101,14 +99,7 @@ describe("blockREADME", () => { ## Usage - \`\`\`shell - npm i test-repository - \`\`\` - \`\`\`ts - import { greet } from "test-repository"; - - greet("Hello, world! 💖"); - \`\`\` + Use it. ## Development @@ -129,7 +120,7 @@ describe("blockREADME", () => { test("without addons", () => { const creation = testBlock(blockREADME, { - options: optionsBase, + options, }); expect(creation).toMatchInlineSnapshot(` @@ -155,14 +146,7 @@ describe("blockREADME", () => { ## Usage - \`\`\`shell - npm i test-repository - \`\`\` - \`\`\`ts - import { greet } from "test-repository"; - - greet("Hello, world! 💖"); - \`\`\` + Use it. ## Development @@ -186,7 +170,7 @@ describe("blockREADME", () => { addons: { notices: ["> Hello, world! 💖"], }, - options: optionsBase, + options, }); expect(creation).toMatchInlineSnapshot(` @@ -212,14 +196,7 @@ describe("blockREADME", () => { ## Usage - \`\`\`shell - npm i test-repository - \`\`\` - \`\`\`ts - import { greet } from "test-repository"; - - greet("Hello, world! 💖"); - \`\`\` + Use it. ## Development diff --git a/src/next/blocks/blockREADME.ts b/src/next/blocks/blockREADME.ts index 5691e3fd..71f52988 100644 --- a/src/next/blocks/blockREADME.ts +++ b/src/next/blocks/blockREADME.ts @@ -44,14 +44,7 @@ ${logo} ## Usage -\`\`\`shell -npm i ${options.repository} -\`\`\` -\`\`\`ts -import { greet } from "${options.repository}"; - -greet("Hello, world! 💖"); -\`\`\` +${options.usage} ## Development diff --git a/src/shared/options/createOptionDefaults/getUsageFromReadme.test.ts b/src/shared/options/createOptionDefaults/getUsageFromReadme.test.ts new file mode 100644 index 00000000..113495fb --- /dev/null +++ b/src/shared/options/createOptionDefaults/getUsageFromReadme.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; + +import { getUsageFromReadme } from "./getUsageFromReadme.js"; + +describe("getUsageFromReadme", () => { + it("returns undefined when ## Usage is not found", () => { + const usage = getUsageFromReadme("## Other"); + + expect(usage).toBeUndefined(); + }); + + it("returns undefined when ## Usage found and ## Development is not found", () => { + const usage = getUsageFromReadme("## Usage\n\nContents."); + + expect(usage).toBeUndefined(); + }); + + it("returns undefined when there is no content between ## Usage and ## Development", () => { + const usage = getUsageFromReadme("## Usage\n\n \n## Development"); + + expect(usage).toBeUndefined(); + }); + + it("returns the content when content exists between ## Usage and ## Development", () => { + const usage = getUsageFromReadme("## Usage\n\n Content.\n## Development"); + + expect(usage).toBe("Content."); + }); +}); diff --git a/src/shared/options/createOptionDefaults/getUsageFromReadme.ts b/src/shared/options/createOptionDefaults/getUsageFromReadme.ts new file mode 100644 index 00000000..124b1eb9 --- /dev/null +++ b/src/shared/options/createOptionDefaults/getUsageFromReadme.ts @@ -0,0 +1,23 @@ +const startDevelopment = "## Development"; +const startUsage = "## Usage"; + +export function getUsageFromReadme(readme: string) { + const indexOfUsage = readme.indexOf(startUsage); + if (indexOfUsage === -1) { + return undefined; + } + + const indexOfDevelopment = readme.indexOf( + startDevelopment, + indexOfUsage + startUsage.length, + ); + if (indexOfDevelopment === -1) { + return undefined; + } + + const usage = readme + .slice(indexOfUsage + startUsage.length, indexOfDevelopment) + .trim(); + + return usage ? usage : undefined; +} diff --git a/src/shared/options/createOptionDefaults/index.ts b/src/shared/options/createOptionDefaults/index.ts index 24a0c825..7996c839 100644 --- a/src/shared/options/createOptionDefaults/index.ts +++ b/src/shared/options/createOptionDefaults/index.ts @@ -53,6 +53,6 @@ export function createOptionDefaults(promptedOptions?: PromptedOptions) { promptedOptions?.repository ?? (await gitDefaults())?.name ?? (await packageData()).name, - ...readDefaultsFromReadme(readme), + ...readDefaultsFromReadme(readme, promptedOptions?.repository), }; } diff --git a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts index 13c226ea..ae4db2ac 100644 --- a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts +++ b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts @@ -13,17 +13,20 @@ vi.mock("../readLogoSizing.js", () => ({ describe("readDefaultsFromReadme", () => { describe("logo", () => { it("defaults to undefined when it cannot be found", async () => { - const logo = await readDefaultsFromReadme(() => - Promise.resolve(`nothing.`), + const logo = await readDefaultsFromReadme( + () => Promise.resolve(`nothing.`), + undefined, ).logo(); expect(logo).toBeUndefined(); }); it("parses when found in an unquoted string", async () => { - const logo = await readDefaultsFromReadme(() => - Promise.resolve(` + const logo = await readDefaultsFromReadme( + () => + Promise.resolve(` `), + undefined, ).logo(); expect(logo).toEqual({ @@ -33,9 +36,11 @@ describe("readDefaultsFromReadme", () => { }); it("parses when found in a single quoted string", async () => { - const logo = await readDefaultsFromReadme(() => - Promise.resolve(` + const logo = await readDefaultsFromReadme( + () => + Promise.resolve(` `), + undefined, ).logo(); expect(logo).toEqual({ @@ -45,9 +50,11 @@ describe("readDefaultsFromReadme", () => { }); it("parses when found in a double quoted string", async () => { - const logo = await readDefaultsFromReadme(() => - Promise.resolve(` + const logo = await readDefaultsFromReadme( + () => + Promise.resolve(` `), + undefined, ).logo(); expect(logo).toEqual({ @@ -57,9 +64,11 @@ describe("readDefaultsFromReadme", () => { }); it("includes alt text when it exists in double quotes", async () => { - const logo = await readDefaultsFromReadme(() => - Promise.resolve(` + const logo = await readDefaultsFromReadme( + () => + Promise.resolve(` Project logo: a fancy circle`), + undefined, ).logo(); expect(logo).toEqual({ @@ -69,9 +78,11 @@ describe("readDefaultsFromReadme", () => { }); it("includes alt text when it exists in single quotes", async () => { - const logo = await readDefaultsFromReadme(() => - Promise.resolve(` + const logo = await readDefaultsFromReadme( + () => + Promise.resolve(` Project logo: a fancy circle`), + undefined, ).logo(); expect(logo).toEqual({ @@ -85,9 +96,11 @@ describe("readDefaultsFromReadme", () => { mockReadLogoSizing.mockReturnValueOnce(sizing); - const logo = await readDefaultsFromReadme(() => - Promise.resolve(` + const logo = await readDefaultsFromReadme( + () => + Promise.resolve(` Project logo: a fancy circle`), + undefined, ).logo(); expect(logo).toEqual({ @@ -98,11 +111,13 @@ describe("readDefaultsFromReadme", () => { }); it("parses when found after a badge image", async () => { - const logo = await readDefaultsFromReadme(() => - Promise.resolve(` + const logo = await readDefaultsFromReadme( + () => + Promise.resolve(` 👪 All Contributors: 48 `), + undefined, ).logo(); expect(logo).toEqual({ @@ -112,8 +127,9 @@ describe("readDefaultsFromReadme", () => { }); it("parses when found after an h1 and many badge images", async () => { - const logo = await readDefaultsFromReadme(() => - Promise.resolve(` + const logo = await readDefaultsFromReadme( + () => + Promise.resolve(`

Create TypeScript App

Quickstart-friendly TypeScript template with comprehensive, configurable, opinionated tooling. ❤️‍🔥

@@ -133,6 +149,7 @@ describe("readDefaultsFromReadme", () => { Project logo: the TypeScript blue square with rounded corners, but a plus sign instead of 'TS' `), + undefined, ).logo(); expect(logo).toEqual({ @@ -144,32 +161,36 @@ describe("readDefaultsFromReadme", () => { describe("title", () => { it("defaults to undefined when it cannot be found", async () => { - const title = await readDefaultsFromReadme(() => - Promise.resolve(`nothing`), + const title = await readDefaultsFromReadme( + () => Promise.resolve(`nothing`), + undefined, ).title(); expect(title).toBeUndefined(); }); it('reads title as markdown from "README.md" when it exists', async () => { - const title = await readDefaultsFromReadme(() => - Promise.resolve(`# My Awesome Package`), + const title = await readDefaultsFromReadme( + () => Promise.resolve(`# My Awesome Package`), + undefined, ).title(); expect(title).toBe("My Awesome Package"); }); it('reads title as HTML from "README.md" when it exists', async () => { - const title = await readDefaultsFromReadme(() => - Promise.resolve('

My Awesome Package

'), + const title = await readDefaultsFromReadme( + () => Promise.resolve('

My Awesome Package

'), + undefined, ).title(); expect(title).toBe("My Awesome Package"); }); it("returns undefined when title does not exist", async () => { - const title = await readDefaultsFromReadme(() => - Promise.resolve(`Other text.`), + const title = await readDefaultsFromReadme( + () => Promise.resolve(`Other text.`), + undefined, ).title(); expect(title).toBeUndefined(); diff --git a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts index 5f5da1dd..39fb4d25 100644 --- a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts +++ b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts @@ -1,8 +1,12 @@ import lazyValue from "lazy-value"; import { readLogoSizing } from "../readLogoSizing.js"; +import { getUsageFromReadme } from "./getUsageFromReadme.js"; -export function readDefaultsFromReadme(readme: () => Promise) { +export function readDefaultsFromReadme( + readme: () => Promise, + repository: string | undefined, +) { const imageTag = lazyValue( async () => /\n/.exec(await readme())?.[0], ); @@ -34,5 +38,19 @@ export function readDefaultsFromReadme(readme: () => Promise) { return (/^(.+)<\/h1>/.exec(text) ?? /^# (.+)/.exec(text))?.[1]; }, + + usage: async () => { + return ( + getUsageFromReadme(await readme()) ?? + `\`\`\`shell +npm i ${repository} +\`\`\` +\`\`\`ts +import { greet } from "${repository}"; + +greet("Hello, world! 💖"); +\`\`\`` + ); + }, }; }