From 95df083a92960d53e2485af74f52a32cb8e21f5b Mon Sep 17 00:00:00 2001
From: Josh Goldberg <git@joshuakgoldberg.com>
Date: Fri, 4 Apr 2025 08:50:21 -0400
Subject: [PATCH] fix: correct README.md parsing without an existing Usage h2

---
 src/options/readReadmeExplainer.test.ts | 70 ++++++++++++++++++++++---
 src/options/readReadmeExplainer.ts      | 26 ++++-----
 2 files changed, 76 insertions(+), 20 deletions(-)

diff --git a/src/options/readReadmeExplainer.test.ts b/src/options/readReadmeExplainer.test.ts
index d545d13c3..0beed9327 100644
--- a/src/options/readReadmeExplainer.test.ts
+++ b/src/options/readReadmeExplainer.test.ts
@@ -37,7 +37,8 @@ describe(readReadmeExplainer, () => {
 This is my project.
 
 ## Usage
-					.`),
+
+...`),
 		);
 
 		expect(actual).toEqual("This is my project.");
@@ -52,7 +53,8 @@ This is my project.
 It is good.
 
 ## Usage
-					.`),
+
+...`),
 		);
 
 		expect(actual).toEqual("This is my project.\nIt is good.");
@@ -80,7 +82,8 @@ This is my project.
 It is good.
 
 ## Usage
-					.`),
+
+...`),
 		);
 
 		expect(actual).toEqual("This is my project.\nIt is good.");
@@ -108,7 +111,8 @@ This is my project.
 It is good.
 
 ## Usage
-					.`),
+
+...`),
 		);
 
 		expect(actual).toEqual("## What?\n\nThis is my project.\nIt is good.");
@@ -129,7 +133,8 @@ It is good.
 > See here.
 
 ## Usage
-					.`),
+
+...`),
 		);
 
 		expect(actual).toEqual(
@@ -152,7 +157,8 @@ This is my project.
 It is good.
 
 ## Usage
-					.`),
+
+...`),
 		);
 
 		expect(actual).toEqual("## What?\n\nThis is my project.\nIt is good.");
@@ -175,7 +181,57 @@ It is good.
 > See here.
 
 ## Usage
-					.`),
+
+...`),
+		);
+
+		expect(actual).toEqual(
+			"## What?\n\nThis is my project.\nIt is good.\n\n> See here.",
+		);
+	});
+
+	it("returns existing content before a non-Usage h2 when the Usage h2 does not exist", async () => {
+		const actual = await readReadmeExplainer(() =>
+			Promise.resolve(`
+	<a href="http://npmjs.com/package/create-typescript-app"><img alt="📦 npm version" src="https://img.shields.io/npm/v/create-typescript-app?color=21bb42&label=%F0%9F%93%A6%20npm" /></a>
+	<img alt="💪 TypeScript: Strict" src="https://img.shields.io/badge/%F0%9F%92%AA_typescript-strict-21bb42.svg" />
+</p>
+
+<img align="right" alt="Project logo: the TypeScript blue square with rounded corners, but a plus sign instead of 'TS'" height="128" src="./docs/create-typescript-app.png" width="128">
+
+## What?
+
+This is my project.
+It is good.
+
+> See here.
+
+## Contributing
+
+...`),
+		);
+
+		expect(actual).toEqual(
+			"## What?\n\nThis is my project.\nIt is good.\n\n> See here.",
+		);
+	});
+
+	it("returns existing content until the end of the file when no subsequent h2 exists", async () => {
+		const actual = await readReadmeExplainer(() =>
+			Promise.resolve(`
+	<a href="http://npmjs.com/package/create-typescript-app"><img alt="📦 npm version" src="https://img.shields.io/npm/v/create-typescript-app?color=21bb42&label=%F0%9F%93%A6%20npm" /></a>
+	<img alt="💪 TypeScript: Strict" src="https://img.shields.io/badge/%F0%9F%92%AA_typescript-strict-21bb42.svg" />
+</p>
+
+<img align="right" alt="Project logo: the TypeScript blue square with rounded corners, but a plus sign instead of 'TS'" height="128" src="./docs/create-typescript-app.png" width="128">
+
+## What?
+
+This is my project.
+It is good.
+
+> See here.
+`),
 		);
 
 		expect(actual).toEqual(
diff --git a/src/options/readReadmeExplainer.ts b/src/options/readReadmeExplainer.ts
index aff6a33da..5f36fe0d5 100644
--- a/src/options/readReadmeExplainer.ts
+++ b/src/options/readReadmeExplainer.ts
@@ -1,24 +1,24 @@
+const lastTagMatchers = [`">`, "/p>", "/>"];
+
 export async function readReadmeExplainer(getReadme: () => Promise<string>) {
 	const readme = await getReadme();
 
-	const indexOfUsageH2 = /## Usage/.exec(readme)?.index ?? readme.indexOf("##");
-	if (indexOfUsageH2 === -1) {
-		return undefined;
-	}
-
-	const beforeUsageH2 = readme.slice(0, indexOfUsageH2);
-
-	const [indexOfLastTag, lastTagMatcher] = lastLastIndexOf(beforeUsageH2, [
-		`">`,
-		"/p>",
-		"/>",
-	]);
+	const indexOfFirstH2 = readme.indexOf("##");
+	const indexOfUsageH2 = /## Usage/.exec(readme)?.index;
+	const beforeH2s = readme.slice(0, indexOfUsageH2 ?? indexOfFirstH2);
+	const [indexOfLastTag, lastTagMatcher] = lastLastIndexOf(
+		beforeH2s,
+		lastTagMatchers,
+	);
 	if (!lastTagMatcher) {
 		return undefined;
 	}
 
+	const endingIndex =
+		indexOfUsageH2 ?? /## (?:Contrib|Develop)/.exec(readme)?.index;
+
 	return readme
-		.slice(indexOfLastTag + lastTagMatcher.length, indexOfUsageH2)
+		.slice(indexOfLastTag + lastTagMatcher.length, endingIndex)
 		.trim();
 }