Repository: JoshuaKGoldberg/template-typescript-node-package (ts)

Issue:
🚀 Feature: Create template for users in --mode create
### 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/template-typescript-node-package/issues?q=is%3Aissue) and found none that matched my issue.

### Overview

Running `npx template-typescript-node-app` right now gives you a kind of messy flow:

1. It'll create the repo for you, albeit with a couple un-skippable prompts
2. It then tells you to run a bunch of CLI commands to create the repo, including an `initialize`

Bleh! All that should be handled for you. With a flag like `--create-repository` to make it all automatic.

### Additional Info

_No response_

Pull request:
feat: automate repository creation for --mode create
## PR Checklist

- [x] Addresses an existing open issue: fixes #688
- [x] That issue was marked as [`status: accepting prs`](https://github.com/JoshuaKGoldberg/template-typescript-node-package/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22)
- [x] Steps in [CONTRIBUTING.md](https://github.com/JoshuaKGoldberg/template-typescript-node-package/blob/main/.github/CONTRIBUTING.md) were taken

## Overview

Streamlines the heck out of the `--mode create` (`npx template-typescript-node-package`) experience:

* Instead of telling you to run some initialize commands, it offers to run them for you
* Similarly, after creating a new repository, the creation script will offer to update the repo using GitHub's APIs for you
* Adds a `--create-repository` flag to automatically do those things

Patch:
diff --git a/docs/InitializationFromTemplate.md b/docs/InitializationFromTemplate.md
index a8a2d83..ef0c85e 100644
--- a/docs/InitializationFromTemplate.md
+++ b/docs/InitializationFromTemplate.md
@@ -21,7 +21,7 @@ pnpm run initialize
 ```
 
 The initialization script will interactively prompt for values to be used in creating the new repository.
-Each may provided as a string CLI flag as well:
+Each may provided as a CLI flag as well:
 
 - `--description`: Sentence case description of the repository (e.g. `A quickstart-friendly TypeScript package with lots of great repository tooling. ✨`)
 - `--owner`: GitHub organization or user the repository is underneath (e.g. `JoshuaKGoldberg`)
diff --git a/docs/InitializationFromTerminal.md b/docs/InitializationFromTerminal.md
index b849ff6..88de802 100644
--- a/docs/InitializationFromTerminal.md
+++ b/docs/InitializationFromTerminal.md
@@ -13,32 +13,30 @@ Pass `--mode create` to tell it to create a new repository in a child directory:
 npx template-typescript-node-package --mode create
 ```
 
-Upon completion, the prompt will suggest commands to get started working in the repository and create a corresponding GitHub repository.
-Those commands will roughly run the [The Initialization Script](./InitializationFromTemplate.md#the-initialization-script), but with `npm template-typescript-node-package --mode initialize` instead of `pnpm run initialize`:
+Then, go through the following two steps to set up required repository tooling on GitHub:
 
-```shell
-cd repository-name
-gh repo create repository-name --public --source=. --remote=origin
-npx template-typescript-node-package --mode initialize
-git add -A
-git commit -m "chore: initial commit ✨"
-git push -u origin main
-```
+1. Create two tokens in [repository secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets):
+   - `ACCESS_TOKEN`: A [GitHub PAT](https://github.com/settings/tokens/new) with _repo_ and _workflow_ permissions
+   - `NPM_TOKEN`: An [npm access token](https://docs.npmjs.com/creating-and-viewing-access-tokens/) with _Automation_ permissions
+2. Install the [Codecov GitHub App](https://github.com/marketplace/codecov) and [Renovate GitHub App](https://github.com/marketplace/renovate)
+
+At this point, your new repository should be ready for development! 🥳
 
 ## The Creation Script
 
 The creation script will interactively prompt for values to be used in creating the new repository.
-Each may provided as a string CLI flag as well:
+Each may provided as a CLI flag as well:
 
+- `--create-repository`: Whether to automatically create a repository on github.com with the given repository name under the owner
 - `--description`: Sentence case description of the repository (e.g. `A quickstart-friendly TypeScript package with lots of great repository tooling. ✨`)
 - `--owner`: GitHub organization or user the repository is underneath (e.g. `JoshuaKGoldberg`)
 - `--repository`: The kebab-case name of the repository (e.g. `template-typescript-node-package`)
 - `--title`: Title Case title for the repository to be used in documentation (e.g. `Template TypeScript Node Package`)
 
-For example, pre-populating all values:
+For example, pre-populating all values and creating a new repository:
 
 ```shell
-npx template-typescript-node-package --mode create --repository "testing-repository" --title "Testing Title" --owner "TestingOwner" --description "Test Description"
+npx template-typescript-node-package --create-repository --mode create --repository "testing-repository" --title "Testing Title" --owner "TestingOwner" --description "Test Description"
 ```
 
 Then, go through the following two steps to set up required repository tooling on GitHub:
diff --git a/package.json b/package.json
index 33e36fd..12cb7c5 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "template-typescript-node-package",
-	"version": "1.29.1",
+	"version": "1.29.2",
 	"description": "A quickstart-friendly TypeScript template with comprehensive formatting, linting, releases, testing, and other great tooling built-in. ✨",
 	"repository": {
 		"type": "git",
diff --git a/src/bin/index.ts b/src/bin/index.ts
index c28c770..4feb915 100644
--- a/src/bin/index.ts
+++ b/src/bin/index.ts
@@ -13,9 +13,9 @@ export async function bin(args: string[]) {
 
 	prompts.intro(
 		[
-			chalk.greenBright`Welcome to the`,
-			chalk.bgGreenBright.black`template-typescript-node-package`,
-			chalk.greenBright(`package! 🎉`),
+			chalk.greenBright(`Welcome to`),
+			chalk.bgGreenBright.black(`template-typescript-node-package`),
+			chalk.greenBright(`! 🎉`),
 		].join(" "),
 	);
 
diff --git a/src/create/createWithValues.ts b/src/create/createWithValues.ts
index 65577f0..97ef24b 100644
--- a/src/create/createWithValues.ts
+++ b/src/create/createWithValues.ts
@@ -1,7 +1,14 @@
+import * as prompts from "@clack/prompts";
+import { $ } from "execa";
+
 import { runOrSkip } from "../shared/cli/runOrSkip.js";
 import { withSpinner } from "../shared/cli/spinners.js";
+import { doesRepositoryExist } from "../shared/doesRepositoryExist.js";
 import { HelpersAndValues } from "../shared/inputs.js";
 import { finalizeDependencies } from "../steps/finalizeDependencies.js";
+import { initializeBranchProtectionSettings } from "../steps/initializeBranchProtectionSettings.js";
+import { initializeRepositorySettings } from "../steps/initializeRepositorySettings.js";
+import { initializeRepositoryLabels } from "../steps/labels/initializeRepositoryLabels.js";
 import { runCommands } from "../steps/runCommands.js";
 import { writeReadme } from "../steps/writeReadme.js";
 import { writeStructure } from "../steps/writing/writeStructure.js";
@@ -30,4 +37,28 @@ export async function createWithValues(input: HelpersAndValues) {
 		"pnpm format --write",
 		"pnpm lint --fix",
 	]);
+
+	const sendToGitHub =
+		input.octokit &&
+		(await doesRepositoryExist(input.octokit, input.values)) &&
+		(input.values.createRepository ??
+			(await prompts.confirm({
+				message:
+					"Would you like to push the template's tooling up to the repository on GitHub?",
+			})) === true);
+
+	await runOrSkip("Initializing API metadata", !sendToGitHub, async () => {
+		await $`git remote add origin https://github.com/${input.values.owner}/${input.values.repository}`;
+		await $`git add -A`;
+		await $`git commit --message ${"chore: initialized repo ✨"}`;
+		await $`git push -u origin main --force`;
+
+		/* eslint-disable @typescript-eslint/no-non-null-assertion */
+		await initializeBranchProtectionSettings(input.octokit!, input.values);
+		await initializeRepositoryLabels();
+		await initializeRepositorySettings(input.octokit!, input.values);
+		/* eslint-enable @typescript-eslint/no-non-null-assertion */
+	});
+
+	return { sentToGitHub: sendToGitHub };
 }
diff --git a/src/create/index.ts b/src/create/index.ts
index 7f0d91a..d4a7f7f 100644
--- a/src/create/index.ts
+++ b/src/create/index.ts
@@ -34,29 +34,38 @@ export async function create(args: string[]) {
 
 	return await runOrRestore({
 		run: async () => {
-			await createWithValues({
+			const { sentToGitHub } = await createWithValues({
 				...inputs,
 				values: await augmentValuesWithNpmInfo(inputs.values),
 			});
 
-			outro([
-				{
-					label:
-						"Consider creating a GitHub repository from the new directory:",
-					lines: [
-						`cd ${inputs.values.repository}`,
-						`gh repo create ${inputs.values.repository} --public --source=. --remote=origin`,
-						`npx template-typescript-node-package --mode initialize`,
-						`git add -A`,
-						`git commit -m "chore: initial commit ✨"`,
-						`git push -u origin main`,
-					],
-				},
-				{
-					label:
-						"If you do, be sure to populate its ACCESS_TOKEN and NPM_TOKEN secrets.",
-				},
-			]);
+			outro(
+				sentToGitHub
+					? [
+							{
+								label:
+									"Now, all you have to do is populate the repository's ACCESS_TOKEN and NPM_TOKEN secrets.",
+							},
+					  ]
+					: [
+							{
+								label:
+									"Consider creating a GitHub repository from the new directory:",
+								lines: [
+									`cd ${inputs.values.repository}`,
+									`gh repo create ${inputs.values.repository} --public --source=. --remote=origin`,
+									`npx template-typescript-node-package --mode initialize`,
+									`git add -A`,
+									`git commit -m "chore: initial commit ✨"`,
+									`git push -u origin main`,
+								],
+							},
+							{
+								label:
+									"If you do, be sure to populate its ACCESS_TOKEN and NPM_TOKEN secrets.",
+							},
+					  ],
+			);
 		},
 		skipRestore: inputs.values.skipRestore ?? true,
 	});
diff --git a/src/initialize/initializeWithValues.ts b/src/initialize/initializeWithValues.ts
index dec6cd5..91c6392 100644
--- a/src/initialize/initializeWithValues.ts
+++ b/src/initialize/initializeWithValues.ts
@@ -36,9 +36,9 @@ export async function initializeWithValues(input: HelpersAndValues) {
 
 	await runOrSkip("Initializing API metadata", !input.octokit, async () => {
 		/* eslint-disable @typescript-eslint/no-non-null-assertion */
+		await initializeBranchProtectionSettings(input.octokit!, input.values);
 		await initializeRepositoryLabels();
 		await initializeRepositorySettings(input.octokit!, input.values);
-		await initializeBranchProtectionSettings(input.octokit!, input.values);
 		/* eslint-enable @typescript-eslint/no-non-null-assertion */
 	});
 
diff --git a/src/shared/args.ts b/src/shared/args.ts
index 05e41c1..309db72 100644
--- a/src/shared/args.ts
+++ b/src/shared/args.ts
@@ -1,5 +1,6 @@
 export const allArgOptions = {
 	author: { type: "string" },
+	"create-repository": { type: "boolean" },
 	description: { type: "string" },
 	email: { type: "string" },
 	funding: { type: "string" },
diff --git a/src/shared/augmentValuesWithNpmInfo.ts b/src/shared/augmentValuesWithNpmInfo.ts
index d7cca72..9ec1b36 100644
--- a/src/shared/augmentValuesWithNpmInfo.ts
+++ b/src/shared/augmentValuesWithNpmInfo.ts
@@ -14,6 +14,7 @@ export async function augmentValuesWithNpmInfo<Values extends InputValues>(
 		author: await getPrefillOrPromptedValue(
 			values.author,
 			"What author (username) will be used for the npm owner?",
+			values.owner.toLowerCase(),
 		),
 		email: await getPrefillOrPromptedValue(
 			values.email,
diff --git a/src/shared/doesRepositoryExist.ts b/src/shared/doesRepositoryExist.ts
new file mode 100644
index 0000000..8868a2f
--- /dev/null
+++ b/src/shared/doesRepositoryExist.ts
@@ -0,0 +1,27 @@
+import { Octokit, RequestError } from "octokit";
+
+export interface DoesRepositoryExistOptions {
+	owner: string;
+	repository: string;
+}
+
+export async function doesRepositoryExist(
+	octokit: Octokit,
+	options: DoesRepositoryExistOptions,
+) {
+	// Because the Octokit SDK throws on 404s (😡),
+	// we try/catch to check whether the repo exists.
+	try {
+		await octokit.rest.repos.get({
+			owner: options.owner,
+			repo: options.repository,
+		});
+		return true;
+	} catch (error) {
+		if ((error as RequestError).status !== 404) {
+			throw error;
+		}
+
+		return false;
+	}
+}
diff --git a/src/shared/ensureRepositoryExists.ts b/src/shared/ensureRepositoryExists.ts
index 4e7f4ea..e40e87c 100644
--- a/src/shared/ensureRepositoryExists.ts
+++ b/src/shared/ensureRepositoryExists.ts
@@ -1,47 +1,53 @@
 import * as prompts from "@clack/prompts";
-import { RequestError } from "@octokit/request-error";
 import { Octokit } from "octokit";
 
+import { doesRepositoryExist } from "./doesRepositoryExist.js";
+import { InputValues } from "./inputs.js";
 import { handleCancel, handlePromptCancel } from "./prompts.js";
 
+export type EnsureRepositoryValues = Pick<
+	InputValues,
+	"createRepository" | "owner" | "repository"
+>;
+
+export interface RepositoryExistsResult {
+	octokit: Octokit | undefined;
+	repository: string;
+}
+
 export async function ensureRepositoryExists(
 	octokit: Octokit | undefined,
-	owner: string,
-	repository: string,
-) {
-	if (!octokit) {
-		return repository;
-	}
+	values: EnsureRepositoryValues,
+): Promise<RepositoryExistsResult> {
+	// We'll only assume --create-repository should be true once
+	let { createRepository, repository } = values;
 
 	// We'll continuously pester the user for a repository
 	// until they bail, create a new one, or it exists.
-	while (true) {
-		// Because the Octokit SDK throws on 404s (😡),
-		// we try/catch to check whether the repo exists.
-		try {
-			await octokit.rest.repos.get({
-				owner,
-				repo: repository,
-			});
-			return repository;
-		} catch (error) {
-			if ((error as RequestError).status !== 404) {
-				throw error;
-			}
+	while (octokit) {
+		if (await doesRepositoryExist(octokit, values)) {
+			return { octokit, repository };
 		}
 
-		const selection = (await prompts.select({
-			message: `Repository ${repository} doesn't seem to exist under ${owner}. What would you like to do?`,
-			options: [
-				{ label: "Bail out and maybe try again later", value: "bail" },
-				{ label: "Create a new repository", value: "create" },
-				{
-					label: "Try again with a different repository",
-					value: "different",
-				},
-			],
-		})) as "bail" | "create" | "different";
+		const selection = createRepository
+			? "create"
+			: ((await prompts.select({
+					message: `Repository ${values.repository} doesn't seem to exist under ${values.owner}. What would you like to do?`,
+					options: [
+						{ label: "Create a new repository", value: "create" },
+						{
+							label: "Switch to a different repository name",
+							value: "different",
+						},
+						{
+							label: "Keep changes local",
+							value: "local",
+						},
+						{ label: "Bail out and maybe try again later", value: "bail" },
+					],
+			  })) as "bail" | "create" | "different" | "local");
 
+		createRepository = false;
 		handlePromptCancel(selection);
 
 		switch (selection) {
@@ -52,19 +58,26 @@ export async function ensureRepositoryExists(
 			case "create":
 				await octokit.rest.repos.createUsingTemplate({
 					name: repository,
-					owner,
+					owner: values.owner,
 					template_owner: "JoshuaKGoldberg",
 					template_repo: "template-typescript-node-package",
 				});
-				break;
+				return { octokit, repository };
 
 			case "different":
 				const newRepository = await prompts.text({
 					message: `What would you like to call the repository?`,
 				});
+
 				handlePromptCancel(newRepository);
 				repository = newRepository;
 				break;
+
+			case "local":
+				octokit = undefined;
+				break;
 		}
 	}
+
+	return { octokit, repository };
 }
diff --git a/src/shared/inputs.ts b/src/shared/inputs.ts
index 0066e9d..ba39e8d 100644
--- a/src/shared/inputs.ts
+++ b/src/shared/inputs.ts
@@ -17,6 +17,7 @@ export type GetterDefaultInputValues = {
 
 export interface DefaultInputValues {
 	author: string | undefined;
+	createRepository: boolean | undefined;
 	description: string;
 	email: string | undefined;
 	funding: string | undefined;
@@ -72,23 +73,24 @@ export async function readInputs({
 				"What owner or user will the repository be under?",
 			))));
 
-	const octokit = await runOrSkip(
-		"Checking GitHub authentication",
-		!!values["skip-github-api"],
-		getOctokit,
-	);
-
-	const repository =
-		(values.repository as string | undefined) ??
-		(await (overrides.repository?.() ??
-			ensureRepositoryExists(
-				octokit,
-				owner,
-				await getPrefillOrPromptedValue(
-					values.repository as string | undefined,
+	const { octokit, repository } = await ensureRepositoryExists(
+		await runOrSkip(
+			"Checking GitHub authentication",
+			!!values["skip-github-api"],
+			getOctokit,
+		),
+		{
+			createRepository: values["create-repository"] as boolean | undefined,
+			owner,
+			repository:
+				(values.repository as string | undefined) ??
+				(await overrides.repository?.()) ??
+				(await getPrefillOrPromptedValue(
+					undefined,
 					"What will the kebab-case name of the repository be?",
-				),
-			)));
+				)),
+		},
+	);
 
 	const title = await getPrefillOrPromptedValue(
 		values.title as string | undefined,
@@ -109,6 +111,10 @@ export async function readInputs({
 				values.author as string | undefined,
 				defaults.author,
 			),
+			createRepository: await optionalDefault(
+				values["create-repository"] as boolean | undefined,
+				defaults.createRepository,
+			),
 			description,
 			email: await optionalDefault(
 				values.email as string | undefined,
diff --git a/src/steps/initializeRepositorySettings.ts b/src/steps/initializeRepositorySettings.ts
index 37feb55..94085a3 100644
--- a/src/steps/initializeRepositorySettings.ts
+++ b/src/steps/initializeRepositorySettings.ts
@@ -2,14 +2,14 @@ import { Octokit } from "octokit";
 
 import { InputValues } from "../shared/inputs.js";
 
-type MigrateRepositoryValues = Pick<
+type InitializeRepositorySettings = Pick<
 	InputValues,
 	"description" | "owner" | "repository"
 >;
 
 export async function initializeRepositorySettings(
 	octokit: Octokit,
-	{ description, owner, repository }: MigrateRepositoryValues,
+	{ description, owner, repository }: InitializeRepositorySettings,
 ) {
 	await octokit.rest.repos.update({
 		allow_auto_merge: true,


Test patch:
diff --git a/src/shared/doesRepositoryExist.test.ts b/src/shared/doesRepositoryExist.test.ts
new file mode 100644
index 0000000..198ea2f
--- /dev/null
+++ b/src/shared/doesRepositoryExist.test.ts
@@ -0,0 +1,47 @@
+import { Octokit } from "octokit";
+import { SpyInstance, describe, expect, it, vi } from "vitest";
+
+import { doesRepositoryExist } from "./doesRepositoryExist.js";
+
+const createMockOctokit = (
+	repos: Partial<Record<keyof Octokit["rest"]["repos"], SpyInstance>>,
+) =>
+	({
+		rest: {
+			repos,
+		},
+	}) as unknown as Octokit;
+
+const owner = "StubOwner";
+const repository = "stub-repository";
+
+describe("doesRepositoryExist", () => {
+	it("returns true when the octokit GET resolves", async () => {
+		const octokit = createMockOctokit({ get: vi.fn().mockResolvedValue({}) });
+
+		const actual = await doesRepositoryExist(octokit, { owner, repository });
+
+		expect(actual).toEqual(true);
+	});
+
+	it("returns false when the octokit GET rejects with a 404", async () => {
+		const octokit = createMockOctokit({
+			get: vi.fn().mockRejectedValue({ status: 404 }),
+		});
+
+		const actual = await doesRepositoryExist(octokit, { owner, repository });
+
+		expect(actual).toEqual(false);
+	});
+
+	it("throws the error when awaiting the octokit GET throws a non-404 error", async () => {
+		const error = new Error("Oh no!");
+		const octokit = createMockOctokit({
+			get: vi.fn().mockRejectedValue(error),
+		});
+
+		await expect(
+			async () => await doesRepositoryExist(octokit, { owner, repository }),
+		).rejects.toEqual(error);
+	});
+});
diff --git a/src/shared/ensureRepositoryExists.test.ts b/src/shared/ensureRepositoryExists.test.ts
index c356740..764a646 100644
--- a/src/shared/ensureRepositoryExists.test.ts
+++ b/src/shared/ensureRepositoryExists.test.ts
@@ -1,5 +1,5 @@
 import { Octokit } from "octokit";
-import { SpyInstance, beforeEach, describe, expect, it, vi } from "vitest";
+import { describe, expect, it, vi } from "vitest";
 
 import { ensureRepositoryExists } from "./ensureRepositoryExists.js";
 
@@ -18,79 +18,144 @@ vi.mock("@clack/prompts", () => ({
 	},
 }));
 
+const mockDoesRepositoryExist = vi.fn();
+
+vi.mock("./doesRepositoryExist.js", () => ({
+	get doesRepositoryExist() {
+		return mockDoesRepositoryExist;
+	},
+}));
+
 const owner = "StubOwner";
 const repository = "stub-repository";
 
-const createMockOctokit = (
-	repos: Partial<Record<keyof Octokit["rest"]["repos"], SpyInstance>>,
-) =>
-	({
-		rest: {
-			repos,
-		},
-	}) as unknown as Octokit;
+const createUsingTemplate = vi.fn();
 
-describe("ensureRepositoryExists", () => {
-	beforeEach(() => {
-		vi.spyOn(console, "clear").mockImplementation(() => undefined);
-		vi.spyOn(console, "log").mockImplementation(() => undefined);
-	});
+const createMockOctokit = () =>
+	({ rest: { repos: { createUsingTemplate } } }) as unknown as Octokit;
 
+describe("ensureRepositoryExists", () => {
 	it("returns the repository when octokit is undefined", async () => {
-		const actual = await ensureRepositoryExists(undefined, owner, repository);
+		const actual = await ensureRepositoryExists(undefined, {
+			createRepository: false,
+			owner,
+			repository,
+		});
 
-		expect(actual).toEqual(repository);
+		expect(actual).toEqual({ octokit: undefined, repository });
 	});
 
-	it("returns the repository when the octokit GET resolves", async () => {
-		const octokit = createMockOctokit({ get: vi.fn().mockResolvedValue({}) });
-
-		const actual = await ensureRepositoryExists(octokit, owner, repository);
+	it("returns the repository when octokit is defined and the repository exists", async () => {
+		mockDoesRepositoryExist.mockResolvedValue(true);
+		const octokit = createMockOctokit();
+		const actual = await ensureRepositoryExists(octokit, {
+			createRepository: false,
+			owner,
+			repository,
+		});
 
-		expect(actual).toEqual(repository);
+		expect(actual).toEqual({ octokit, repository });
 	});
 
-	it("throws the error when awaiting the octokit GET throws a non-404 error", async () => {
-		const error = new Error("Oh no!");
-		const octokit = createMockOctokit({
-			get: vi.fn().mockRejectedValue(error),
+	it("creates a new repository when createRepository is true and the repository does not exist", async () => {
+		const octokit = createMockOctokit();
+
+		mockDoesRepositoryExist.mockResolvedValue(false);
+
+		const actual = await ensureRepositoryExists(octokit, {
+			createRepository: true,
+			owner,
+			repository,
 		});
 
-		await expect(() =>
-			ensureRepositoryExists(octokit, owner, repository),
-		).rejects.toEqual(error);
+		expect(actual).toEqual({ octokit, repository });
+		expect(octokit.rest.repos.createUsingTemplate).toHaveBeenCalledWith({
+			name: repository,
+			owner,
+			template_owner: "JoshuaKGoldberg",
+			template_repo: "template-typescript-node-package",
+		});
 	});
 
-	it("creates the repository when the octokit GET rejects and the prompt resolves 'create'", async () => {
-		const octokit = createMockOctokit({
-			createUsingTemplate: vi.fn().mockResolvedValue({}),
-			get: vi
-				.fn()
-				.mockRejectedValueOnce({ status: 404 })
-				.mockResolvedValueOnce({}),
-		});
+	it("creates a new repository when the prompt is 'create' and the repository does not exist", async () => {
+		const octokit = createMockOctokit();
 
+		mockDoesRepositoryExist.mockResolvedValue(false);
 		mockSelect.mockResolvedValue("create");
 
-		const actual = await ensureRepositoryExists(octokit, owner, repository);
+		const actual = await ensureRepositoryExists(octokit, {
+			createRepository: false,
+			owner,
+			repository,
+		});
 
-		expect(actual).toEqual(repository);
+		expect(actual).toEqual({ octokit, repository });
+		expect(octokit.rest.repos.createUsingTemplate).toHaveBeenCalledWith({
+			name: repository,
+			owner,
+			template_owner: "JoshuaKGoldberg",
+			template_repo: "template-typescript-node-package",
+		});
 	});
 
-	it("creates a different repository when the octokit GET rejects and the prompt resolves 'different'", async () => {
-		const octokit = createMockOctokit({
-			get: vi
-				.fn()
-				.mockRejectedValueOnce({ status: 404 })
-				.mockResolvedValueOnce({}),
+	it("returns the second repository when the prompt is 'different', the first repository does not exist, and the second repository exists", async () => {
+		const octokit = createMockOctokit();
+		const newRepository = "new-repository";
+
+		mockDoesRepositoryExist
+			.mockResolvedValueOnce(false)
+			.mockResolvedValueOnce(true);
+		mockSelect.mockResolvedValueOnce("different");
+		mockText.mockResolvedValue(newRepository);
+
+		const actual = await ensureRepositoryExists(octokit, {
+			createRepository: false,
+			owner,
+			repository,
 		});
+
+		expect(actual).toEqual({ octokit, repository: newRepository });
+		expect(octokit.rest.repos.createUsingTemplate).not.toHaveBeenCalled();
+	});
+
+	it("creates the second repository when the prompt is 'different', the first repository does not exist, and the second repository does not exist", async () => {
+		const octokit = createMockOctokit();
 		const newRepository = "new-repository";
 
-		mockSelect.mockResolvedValue("different");
+		mockDoesRepositoryExist.mockResolvedValue(false);
+		mockSelect
+			.mockResolvedValueOnce("different")
+			.mockResolvedValueOnce("create");
 		mockText.mockResolvedValue(newRepository);
 
-		const actual = await ensureRepositoryExists(octokit, owner, repository);
+		const actual = await ensureRepositoryExists(octokit, {
+			createRepository: false,
+			owner,
+			repository,
+		});
+
+		expect(actual).toEqual({ octokit, repository: newRepository });
+		expect(octokit.rest.repos.createUsingTemplate).toHaveBeenCalledWith({
+			name: newRepository,
+			owner,
+			template_owner: "JoshuaKGoldberg",
+			template_repo: "template-typescript-node-package",
+		});
+	});
+
+	it("switches octokit to undefined when the prompt is 'local' and the repository does not exist", async () => {
+		const octokit = createMockOctokit();
+
+		mockDoesRepositoryExist.mockResolvedValue(false);
+		mockSelect.mockResolvedValue("local");
+
+		const actual = await ensureRepositoryExists(octokit, {
+			createRepository: false,
+			owner,
+			repository,
+		});
 
-		expect(actual).toEqual(newRepository);
+		expect(actual).toEqual({ octokit: undefined, repository });
+		expect(octokit.rest.repos.createUsingTemplate).not.toHaveBeenCalled();
 	});
 });
diff --git a/src/steps/writeReadme.test.ts b/src/steps/writeReadme.test.ts
index 832404e..23e885b 100644
--- a/src/steps/writeReadme.test.ts
+++ b/src/steps/writeReadme.test.ts
@@ -24,6 +24,7 @@ vi.mock("../shared/readFileSafe.js", () => ({
 
 const stubValues = {
 	author: "Test Author",
+	createRepository: false,
 	description: "Test description.",
 	email: "test@test.test",
 	funding: undefined,