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

Issue:
🐛 Bug: Don't offer to restore repository with --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/create-typescript-app/issues?q=is%3Aissue) and found none that matched my issue.

### Expected

When running `npx create-typescript-app --mode create`, if the CLI crashes for some reason, you should get the standard error message + call stack but shouldn't be offered to _"restore the repository to how it was"_. That offer doesn't make sense - the repository never existed to begin with!

### Actual

I had a bug in #1000 and got:

```plaintext
│
◇  ❌ Error initializing GitHub repository.
│
Error: Failed initializing GitHub repository
    at withSpinner (file:///Users/josh/repos/create-typescript-app/lib/shared/cli/spinners.js:16:11)
    at async createWithOptions (file:///Users/josh/repos/create-typescript-app/lib/create/createWithOptions.js:43:5)
    at async run (file:///Users/josh/repos/create-typescript-app/lib/create/index.js:31:34)
    at async runOrRestore (file:///Users/josh/repos/create-typescript-app/lib/shared/runOrRestore.js:7:5)
    at async Object.create (file:///Users/josh/repos/create-typescript-app/lib/create/index.js:29:11)
    at async bin (file:///Users/josh/repos/create-typescript-app/lib/bin/index.js:67:36)
    at async file:///Users/josh/repos/create-typescript-app/bin/index.js:4:20 {
  [cause]: RequestError [HttpError]: Not Found
      at /Users/josh/repos/create-typescript-app/node_modules/.pnpm/@octokit+request@8.1.0/node_modules/@octokit/request/dist-node/index.js:112:21
      at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
      at async requestWithGraphqlErrorHandling (/Users/josh/repos/create-typescript-app/node_modules/.pnpm/@octokit+plugin-retry@6.0.0_@octokit+core@5.0.0/node_modules/@octokit/plugin-retry/dist-node/index.js:71:20)
      at async Job.doExecute (/Users/josh/repos/create-typescript-app/node_modules/.pnpm/bottleneck@2.19.5/node_modules/bottleneck/light.js:405:18) {
    status: 404,
    response: {
      url: 'https://api.github.com/repos/OWNER/REPO/labels',
      status: 404,
      headers: [Object],
      data: [Object]
    },
    request: {
      method: 'GET',
      url: 'https://api.github.com/repos/OWNER/REPO/labels',
      headers: [Object],
      request: [Object]
    }
  }
}
│
◆  Do you want to restore the repository to how it was?
│  ● Yes / ○ No
└
```

### Additional Info

_No response_

Pull request:
fix: default skipRestore to true when mode is create
## PR Checklist

- [x] Addresses an existing open issue: fixes #1085
- [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

When `mode` is `"create"`, `options.skipRestore` now has an additional `??` to set it to `true`.

Most of the time spent here is adjusting mocks, since `readOptions` previously always ran in that mode. Looking forward to the new `create` engine to make this much more automatic and inferred. 😎 

💖 

Patch:
diff --git a/src/shared/options/readOptions.ts b/src/shared/options/readOptions.ts
index 7e08efd..5389c1e 100644
--- a/src/shared/options/readOptions.ts
+++ b/src/shared/options/readOptions.ts
@@ -99,7 +99,7 @@ export async function readOptions(
 		skipGitHubApi: values["skip-github-api"] ?? values.offline,
 		skipInstall: values["skip-install"],
 		skipRemoval: values["skip-removal"],
-		skipRestore: values["skip-restore"],
+		skipRestore: values["skip-restore"] ?? mode === "create",
 		skipUninstall: values["skip-uninstall"],
 		title: values.title,
 	};
diff --git a/src/shared/packages.ts b/src/shared/packages.ts
index ae67839..21e991f 100644
--- a/src/shared/packages.ts
+++ b/src/shared/packages.ts
@@ -4,9 +4,11 @@ import { readFileSafe } from "./readFileSafe.js";
 import { PartialPackageData } from "./types.js";
 
 export async function readPackageData() {
-	return JSON.parse(
-		await readFileSafe("./package.json", "{}"),
-	) as PartialPackageData;
+	return (
+		(JSON.parse(await readFileSafe("./package.json", "{}")) as
+			| PartialPackageData
+			| undefined) ?? {}
+	);
 }
 
 export async function removeDependencies(


Test patch:
diff --git a/src/shared/options/readOptions.test.ts b/src/shared/options/readOptions.test.ts
index f219eaa..306d6cb 100644
--- a/src/shared/options/readOptions.test.ts
+++ b/src/shared/options/readOptions.test.ts
@@ -46,7 +46,7 @@ const emptyOptions = {
 	skipGitHubApi: undefined,
 	skipInstall: undefined,
 	skipRemoval: undefined,
-	skipRestore: undefined,
+	skipRestore: false,
 	skipUninstall: undefined,
 	title: undefined,
 };
@@ -63,7 +63,7 @@ vi.mock("../cli/spinners.ts", () => ({
 	},
 }));
 
-const mockReadPackageData = vi.fn();
+const mockReadPackageData = vi.fn().mockResolvedValue({});
 
 vi.mock("../packages.js", () => ({
 	get readPackageData() {
@@ -140,7 +140,7 @@ describe("readOptions", () => {
 			.object({ base: optionsSchemaShape.base })
 			.safeParse({ base: "b" });
 
-		const actual = await readOptions(["--base", "b"], "create");
+		const actual = await readOptions(["--base", "b"], "migrate");
 
 		expect(actual).toStrictEqual({
 			cancelled: true,
@@ -156,11 +156,12 @@ describe("readOptions", () => {
 			value: undefined,
 		}));
 
-		expect(await readOptions([], "create")).toStrictEqual({
+		expect(await readOptions([], "migrate")).toStrictEqual({
 			cancelled: true,
 			error,
 			options: {
 				...emptyOptions,
+				base: "minimal",
 			},
 		});
 	});
@@ -171,11 +172,12 @@ describe("readOptions", () => {
 			value: undefined,
 		}));
 
-		expect(await readOptions([], "create")).toStrictEqual({
+		expect(await readOptions([], "migrate")).toStrictEqual({
 			cancelled: true,
 			error: undefined,
 			options: {
 				...emptyOptions,
+				base: "minimal",
 			},
 		});
 	});
@@ -186,11 +188,12 @@ describe("readOptions", () => {
 			.mockImplementationOnce(() => ({ value: "MockOwner" }))
 			.mockImplementation(() => ({ value: undefined }));
 
-		expect(await readOptions([], "create")).toStrictEqual({
+		expect(await readOptions([], "migrate")).toStrictEqual({
 			cancelled: true,
 			error: undefined,
 			options: {
 				...emptyOptions,
+				base: "minimal",
 				owner: "MockOwner",
 			},
 		});
@@ -204,11 +207,12 @@ describe("readOptions", () => {
 			.mockImplementation(() => ({ value: undefined }));
 		mockEnsureRepositoryExists.mockResolvedValue({});
 
-		expect(await readOptions([], "create")).toStrictEqual({
+		expect(await readOptions([], "migrate")).toStrictEqual({
 			cancelled: true,
 			error: undefined,
 			options: {
 				...emptyOptions,
+				base: "minimal",
 				owner: "MockOwner",
 				repository: "MockRepository",
 			},
@@ -226,11 +230,12 @@ describe("readOptions", () => {
 			repository: mockOptions.repository,
 		});
 
-		expect(await readOptions([], "create")).toStrictEqual({
+		expect(await readOptions([], "migrate")).toStrictEqual({
 			cancelled: true,
 			error: undefined,
 			options: {
 				...emptyOptions,
+				base: "minimal",
 				owner: "MockOwner",
 				repository: "MockRepository",
 			},
@@ -249,11 +254,12 @@ describe("readOptions", () => {
 			repository: mockOptions.repository,
 		});
 
-		expect(await readOptions([], "create")).toStrictEqual({
+		expect(await readOptions([], "migrate")).toStrictEqual({
 			cancelled: true,
 			error: undefined,
 			options: {
 				...emptyOptions,
+				base: "minimal",
 				description: "Mock description.",
 				owner: "MockOwner",
 				repository: "MockRepository",
@@ -275,12 +281,13 @@ describe("readOptions", () => {
 		});
 
 		expect(
-			await readOptions(["--guide", "https://example.com"], "create"),
+			await readOptions(["--guide", "https://example.com"], "migrate"),
 		).toStrictEqual({
 			cancelled: true,
 			error: undefined,
 			options: {
 				...emptyOptions,
+				base: "minimal",
 				description: "Mock description.",
 				guide: "https://example.com",
 				owner: "MockOwner",
@@ -304,12 +311,13 @@ describe("readOptions", () => {
 		});
 
 		expect(
-			await readOptions(["--guide", "https://example.com"], "create"),
+			await readOptions(["--guide", "https://example.com"], "migrate"),
 		).toStrictEqual({
 			cancelled: true,
 			error: undefined,
 			options: {
 				...emptyOptions,
+				base: "minimal",
 				description: "Mock description.",
 				guide: "https://example.com",
 				owner: "MockOwner",
@@ -331,11 +339,12 @@ describe("readOptions", () => {
 			repository: mockOptions.repository,
 		});
 
-		expect(await readOptions(["--logo", "logo.svg"], "create")).toStrictEqual({
+		expect(await readOptions(["--logo", "logo.svg"], "migrate")).toStrictEqual({
 			cancelled: true,
 			error: undefined,
 			options: {
 				...emptyOptions,
+				base: "minimal",
 				description: "Mock description.",
 				logo: "logo.svg",
 				owner: "MockOwner",
@@ -357,11 +366,12 @@ describe("readOptions", () => {
 			repository: mockOptions.repository,
 		});
 
-		expect(await readOptions([], "create")).toStrictEqual({
+		expect(await readOptions([], "migrate")).toStrictEqual({
 			cancelled: true,
 			error: undefined,
 			options: {
 				...emptyOptions,
+				base: "minimal",
 				description: "Mock description.",
 				owner: "MockOwner",
 				repository: "MockRepository",
@@ -384,11 +394,12 @@ describe("readOptions", () => {
 		});
 		mockAugmentOptionsWithExcludes.mockResolvedValue(undefined);
 
-		expect(await readOptions([], "create")).toStrictEqual({
+		expect(await readOptions([], "migrate")).toStrictEqual({
 			cancelled: true,
 			error: undefined,
 			options: {
 				...emptyOptions,
+				base: "minimal",
 				description: "Mock description.",
 				owner: "MockOwner",
 				repository: "MockRepository",
@@ -411,7 +422,7 @@ describe("readOptions", () => {
 		});
 
 		expect(
-			await readOptions(["--base", mockOptions.base], "create"),
+			await readOptions(["--base", mockOptions.base], "migrate"),
 		).toStrictEqual({
 			cancelled: false,
 			octokit: undefined,
@@ -445,7 +456,7 @@ describe("readOptions", () => {
 					"--logo",
 					"logo.svg",
 				],
-				"create",
+				"migrate",
 			),
 		).toStrictEqual({
 			cancelled: false,
@@ -464,7 +475,7 @@ describe("readOptions", () => {
 		}));
 
 		expect(
-			await readOptions(["--base", mockOptions.base], "create"),
+			await readOptions(["--base", mockOptions.base], "migrate"),
 		).toStrictEqual({
 			cancelled: true,
 			options: {
@@ -494,7 +505,7 @@ describe("readOptions", () => {
 		}));
 
 		expect(
-			await readOptions(["--base", mockOptions.base], "create"),
+			await readOptions(["--base", mockOptions.base], "migrate"),
 		).toStrictEqual({
 			cancelled: false,
 			octokit: undefined,
@@ -522,7 +533,7 @@ describe("readOptions", () => {
 		expect(
 			await readOptions(
 				["--base", mockOptions.base, "--owner", "JoshuaKGoldberg"],
-				"create",
+				"migrate",
 			),
 		).toStrictEqual({
 			cancelled: false,
@@ -533,6 +544,58 @@ describe("readOptions", () => {
 		});
 	});
 
+	it("defaults skipRestore to false when the mode is not create", async () => {
+		mockAugmentOptionsWithExcludes.mockImplementationOnce(
+			(options: Partial<Options>) => ({
+				...options,
+				...mockOptions,
+			}),
+		);
+		mockEnsureRepositoryExists.mockResolvedValue({
+			octokit: undefined,
+			repository: mockOptions.repository,
+		});
+		mockGetPrefillOrPromptedOption.mockImplementation(() => ({
+			value: "mock",
+		}));
+
+		expect(
+			await readOptions(["--base", mockOptions.base], "migrate"),
+		).toStrictEqual({
+			cancelled: false,
+			octokit: undefined,
+			options: expect.objectContaining({
+				skipRestore: false,
+			}),
+		});
+	});
+
+	it("defaults skipRestore to true when the mode is create", async () => {
+		mockAugmentOptionsWithExcludes.mockImplementationOnce(
+			(options: Partial<Options>) => ({
+				...options,
+				...mockOptions,
+			}),
+		);
+		mockEnsureRepositoryExists.mockResolvedValue({
+			octokit: undefined,
+			repository: mockOptions.repository,
+		});
+		mockGetPrefillOrPromptedOption.mockImplementation(() => ({
+			value: "mock",
+		}));
+
+		expect(
+			await readOptions(["--base", mockOptions.base], "create"),
+		).toStrictEqual({
+			cancelled: false,
+			octokit: undefined,
+			options: expect.objectContaining({
+				skipRestore: true,
+			}),
+		});
+	});
+
 	it("skips API calls when --offline is true", async () => {
 		mockAugmentOptionsWithExcludes.mockImplementation((options: Options) => ({
 			...emptyOptions,
@@ -548,7 +611,7 @@ describe("readOptions", () => {
 		});
 
 		expect(
-			await readOptions(["--base", mockOptions.base, "--offline"], "create"),
+			await readOptions(["--base", mockOptions.base, "--offline"], "migrate"),
 		).toStrictEqual({
 			cancelled: false,
 			octokit: undefined,
@@ -564,7 +627,7 @@ describe("readOptions", () => {
 				},
 				guide: undefined,
 				logo: undefined,
-				mode: "create",
+				mode: "migrate",
 				offline: true,
 				owner: "mock",
 				skipAllContributorsApi: true,
@@ -769,7 +832,7 @@ describe("readOptions", () => {
 			      "skipGitHubApi": true,
 			      "skipInstall": undefined,
 			      "skipRemoval": undefined,
-			      "skipRestore": undefined,
+			      "skipRestore": false,
 			      "skipUninstall": undefined,
 			      "title": "Test Title",
 			    },