Repository: JoshuaKGoldberg/create-typescript-app (ts)

Issue:
🐛 Bug: Default usage receives undefined repository
### Bug Report Checklist

- [X] I have tried restarting my IDE and the issue persists.
- [X] I have pulled the latest `main` branch of the repository.
- [X] I have [searched for related issues](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aissue) and found none that matched my issue.

### Expected

When creating a new repo with `npx create typescript-app` without an explicit `--repository`, the inferred `usage` option should factor in whatever `repository` option is prompted.

### Actual

It's being printed as `undefined`.

### Additional Info

💖 

Pull request description:
fix: lazily evaluate options.repository for options.usage
## PR Checklist

- [x] Addresses an existing open issue: fixes #1826
- [x] That issue was marked as [`status: accepting prs`](https://github.com/JoshuaKGoldberg/create-typescript-app/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22)
- [x] Steps in [CONTRIBUTING.md](https://github.com/JoshuaKGoldberg/create-typescript-app/blob/main/.github/CONTRIBUTING.md) were taken

## Overview

Moves `repository` into its own separate `lazyValue` that's then used for `usage`.

Also allows `repository` to default to `options.directory`.

💖 

Patch:
diff --git a/src/next/base.ts b/src/next/base.ts
index d349318..b85da94 100644
--- a/src/next/base.ts
+++ b/src/next/base.ts
@@ -197,6 +197,14 @@ export const base = createBase({
 
 		const version = lazyValue(async () => (await packageData()).version);
 
+		const repository = lazyValue(
+			async () =>
+				options.repository ??
+				(await gitDefaults())?.name ??
+				(await packageData()).name ??
+				options.directory,
+		);
+
 		return {
 			access: "public" as const,
 			author,
@@ -222,11 +230,8 @@ export const base = createBase({
 					scripts: original.scripts,
 				};
 			},
-			repository: async () =>
-				options.repository ??
-				(await gitDefaults())?.name ??
-				(await packageData()).name,
-			...readDefaultsFromReadme(readme, options.repository),
+			repository,
+			...readDefaultsFromReadme(readme, repository),
 			version,
 		};
 	},
diff --git a/src/shared/options/createOptionDefaults/index.ts b/src/shared/options/createOptionDefaults/index.ts
index 7996c83..3049463 100644
--- a/src/shared/options/createOptionDefaults/index.ts
+++ b/src/shared/options/createOptionDefaults/index.ts
@@ -52,7 +52,10 @@ export function createOptionDefaults(promptedOptions?: PromptedOptions) {
 		repository: async () =>
 			promptedOptions?.repository ??
 			(await gitDefaults())?.name ??
-			(await packageData()).name,
-		...readDefaultsFromReadme(readme, promptedOptions?.repository),
+			(await packageData()).name ??
+			promptedOptions?.directory,
+		...readDefaultsFromReadme(readme, () =>
+			Promise.resolve(promptedOptions?.repository),
+		),
 	};
 }
diff --git a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts
index 39fb4d2..de759b2 100644
--- a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts
+++ b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.ts
@@ -5,7 +5,7 @@ import { getUsageFromReadme } from "./getUsageFromReadme.js";
 
 export function readDefaultsFromReadme(
 	readme: () => Promise<string>,
-	repository: string | undefined,
+	repository: () => Promise<string | undefined>,
 ) {
 	const imageTag = lazyValue(
 		async () => /\n<img.+src=.+>/.exec(await readme())?.[0],
@@ -43,10 +43,10 @@ export function readDefaultsFromReadme(
 			return (
 				getUsageFromReadme(await readme()) ??
 				`\`\`\`shell
-npm i ${repository}
+npm i ${await repository()}
 \`\`\`
 \`\`\`ts
-import { greet } from "${repository}";
+import { greet } from "${await repository()}";
 
 greet("Hello, world! 💖");
 \`\`\``


Test patch:
diff --git a/src/shared/options/createOptionDefaults/index.test.ts b/src/shared/options/createOptionDefaults/index.test.ts
index 519a477..54189be 100644
--- a/src/shared/options/createOptionDefaults/index.test.ts
+++ b/src/shared/options/createOptionDefaults/index.test.ts
@@ -196,7 +196,7 @@ describe("createOptionDefaults", () => {
 			expect(actual).toBe(name);
 		});
 
-		it("returns the package name when it exists and promptedOptions.repository a Git name don't", async () => {
+		it("returns the package name when it exists and promptedOptions.repository and Git name don't", async () => {
 			const name = "test-package-name";
 			mockReadPackageData.mockResolvedValueOnce({ name });
 
@@ -204,5 +204,15 @@ describe("createOptionDefaults", () => {
 
 			expect(actual).toBe(name);
 		});
+
+		it("returns the directory when it exists and promptedOptions.repository, Git name, and package name don't", async () => {
+			mockReadPackageData.mockResolvedValueOnce({});
+			const directory = "test-prompted-directory";
+			const promptedOptions = { directory };
+
+			const actual = await createOptionDefaults(promptedOptions).repository();
+
+			expect(actual).toBe(directory);
+		});
 	});
 });
diff --git a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts
index ae4db2a..c6bd6c9 100644
--- a/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts
+++ b/src/shared/options/createOptionDefaults/readDefaultsFromReadme.test.ts
@@ -10,12 +10,20 @@ vi.mock("../readLogoSizing.js", () => ({
 	},
 }));
 
+const mockGetUsageFromReadme = vi.fn().mockResolvedValue({});
+
+vi.mock("./getUsageFromReadme.js", () => ({
+	get getUsageFromReadme() {
+		return mockGetUsageFromReadme;
+	},
+}));
+
 describe("readDefaultsFromReadme", () => {
 	describe("logo", () => {
 		it("defaults to undefined when it cannot be found", async () => {
 			const logo = await readDefaultsFromReadme(
 				() => Promise.resolve(`nothing.`),
-				undefined,
+				() => Promise.resolve(undefined),
 			).logo();
 
 			expect(logo).toBeUndefined();
@@ -26,7 +34,7 @@ describe("readDefaultsFromReadme", () => {
 				() =>
 					Promise.resolve(`
 <img src=abc/def.jpg/>`),
-				undefined,
+				() => Promise.resolve(undefined),
 			).logo();
 
 			expect(logo).toEqual({
@@ -40,7 +48,7 @@ describe("readDefaultsFromReadme", () => {
 				() =>
 					Promise.resolve(`
 <img src='abc/def.jpg'/>`),
-				undefined,
+				() => Promise.resolve(undefined),
 			).logo();
 
 			expect(logo).toEqual({
@@ -54,7 +62,7 @@ describe("readDefaultsFromReadme", () => {
 				() =>
 					Promise.resolve(`
 <img src="abc/def.jpg"/>`),
-				undefined,
+				() => Promise.resolve(undefined),
 			).logo();
 
 			expect(logo).toEqual({
@@ -68,7 +76,7 @@ describe("readDefaultsFromReadme", () => {
 				() =>
 					Promise.resolve(`
 <img alt="Project logo: a fancy circle" src="abc/def.jpg"/>`),
-				undefined,
+				() => Promise.resolve(undefined),
 			).logo();
 
 			expect(logo).toEqual({
@@ -82,7 +90,7 @@ describe("readDefaultsFromReadme", () => {
 				() =>
 					Promise.resolve(`
 <img alt='Project logo: a fancy circle' src='abc/def.jpg'/>`),
-				undefined,
+				() => Promise.resolve(undefined),
 			).logo();
 
 			expect(logo).toEqual({
@@ -100,7 +108,7 @@ describe("readDefaultsFromReadme", () => {
 				() =>
 					Promise.resolve(`
 <img alt='Project logo: a fancy circle' src='abc/def.jpg'/>`),
-				undefined,
+				() => Promise.resolve(undefined),
 			).logo();
 
 			expect(logo).toEqual({
@@ -117,7 +125,7 @@ describe("readDefaultsFromReadme", () => {
 		<a href="#contributors" target="_blank"><img alt="👪 All Contributors: 48" src="https://img.shields.io/badge/%F0%9F%91%AA_all_contributors-48-21bb42.svg" /></a>
 <img src=abc/def.jpg/>
 `),
-				undefined,
+				() => Promise.resolve(undefined),
 			).logo();
 
 			expect(logo).toEqual({
@@ -149,7 +157,7 @@ describe("readDefaultsFromReadme", () => {
 
 <img align="right" alt="Project logo: the TypeScript blue square with rounded corners, but a plus sign instead of 'TS'" src="./docs/create-typescript-app.png">
 `),
-				undefined,
+				() => Promise.resolve(undefined),
 			).logo();
 
 			expect(logo).toEqual({
@@ -163,7 +171,7 @@ describe("readDefaultsFromReadme", () => {
 		it("defaults to undefined when it cannot be found", async () => {
 			const title = await readDefaultsFromReadme(
 				() => Promise.resolve(`nothing`),
-				undefined,
+				() => Promise.resolve(undefined),
 			).title();
 
 			expect(title).toBeUndefined();
@@ -172,7 +180,7 @@ describe("readDefaultsFromReadme", () => {
 		it('reads title as markdown from "README.md" when it exists', async () => {
 			const title = await readDefaultsFromReadme(
 				() => Promise.resolve(`# My Awesome Package`),
-				undefined,
+				() => Promise.resolve(undefined),
 			).title();
 
 			expect(title).toBe("My Awesome Package");
@@ -181,7 +189,7 @@ describe("readDefaultsFromReadme", () => {
 		it('reads title as HTML from "README.md" when it exists', async () => {
 			const title = await readDefaultsFromReadme(
 				() => Promise.resolve('<h1 align="center">My Awesome Package</h1>'),
-				undefined,
+				() => Promise.resolve(undefined),
 			).title();
 
 			expect(title).toBe("My Awesome Package");
@@ -190,10 +198,43 @@ describe("readDefaultsFromReadme", () => {
 		it("returns undefined when title does not exist", async () => {
 			const title = await readDefaultsFromReadme(
 				() => Promise.resolve(`Other text.`),
-				undefined,
+				() => Promise.resolve(undefined),
 			).title();
 
 			expect(title).toBeUndefined();
 		});
 	});
+
+	describe("usage", () => {
+		it("returns the existing usage when getUsageFromReadme provides one", async () => {
+			const existing = "Use it.";
+
+			mockGetUsageFromReadme.mockReturnValueOnce(existing);
+
+			const usage = await readDefaultsFromReadme(
+				() => Promise.resolve(""),
+				() => Promise.resolve(undefined),
+			).usage();
+
+			expect(usage).toBe(existing);
+		});
+
+		it("returns sample usage when getUsageFromReadme doesn't provide usage", async () => {
+			mockGetUsageFromReadme.mockReturnValueOnce(undefined);
+
+			const usage = await readDefaultsFromReadme(
+				() => Promise.resolve(""),
+				() => Promise.resolve("test-repository"),
+			).usage();
+
+			expect(usage).toBe(`\`\`\`shell
+npm i test-repository
+\`\`\`
+\`\`\`ts
+import { greet } from "test-repository";
+
+greet("Hello, world! 💖");
+\`\`\``);
+		});
+	});
 });