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

Issue:
🚀 Feature: Hydration script should run ESLint autofixer
### 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

#441 added a hydration script that updates an existing repository to use this template's goodness. But the existing repository may have some lint failures at the end of the script, such as using extension-less imports (`... from "./file"` instead of `... from "./file.js"`).

Let's have the hydration script run `pnpm run lint --fix` after it finishes `pnpm run format:write`!

### Additional Info

_No response_

Pull request:
feat: run ESLint and Prettier fixers in hydration
## PR Checklist

- [x] Addresses an existing open issue: fixes #452
- [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

Per the issue, runs `pnpm lint --fix` near the end of hydration. Also runs `pnpm format --write`. For both, if there was an error, the user is warned to run the command themselves.

Rewords from _"Succeeded"_ to _"Passed"_ for spinner blocks to indicate that there might not have been a total success.

Patch:
diff --git a/src/hydrate/hydrate.ts b/src/hydrate/hydrate.ts
index 0268bcb..be7e2c6 100644
--- a/src/hydrate/hydrate.ts
+++ b/src/hydrate/hydrate.ts
@@ -9,6 +9,7 @@ import {
 import { runOrRestore } from "../shared/runOrRestore.js";
 import { clearUnnecessaryFiles } from "./steps/clearUnnecessaryFiles.js";
 import { finalizeDependencies as finalizeDependencies } from "./steps/finalizeDependencies.js";
+import { runCommand } from "./steps/runCommand.js";
 import { writeReadme } from "./steps/writeReadme.js";
 import { writeStructure } from "./steps/writing/writeStructure.js";
 import { getHydrationDefaults } from "./values/getHydrationDefaults.js";
@@ -53,6 +54,9 @@ export async function hydrate(args: string[]) {
 				);
 			}
 
+			await runCommand("pnpm lint --fix", "auto-fixing lint rules");
+			await runCommand("pnpm format --write", "formatting files");
+
 			if (hydrationSkips["skip-setup"]) {
 				skipSpinnerBlock(`Done hydrating, and skipping setup command.`);
 			} else {
diff --git a/src/hydrate/steps/runCommand.ts b/src/hydrate/steps/runCommand.ts
new file mode 100644
index 0000000..639fef2
--- /dev/null
+++ b/src/hydrate/steps/runCommand.ts
@@ -0,0 +1,27 @@
+import chalk from "chalk";
+import { execaCommand } from "execa";
+
+import { logLine } from "../../shared/cli/lines.js";
+import { withSpinner } from "../../shared/cli/spinners.js";
+
+export async function runCommand(command: string, label: string) {
+	const succeeded = await withSpinner(async () => {
+		try {
+			await execaCommand(command);
+			return true;
+		} catch {
+			return false;
+		}
+	}, label);
+
+	if (!succeeded) {
+		logLine();
+		logLine(
+			[
+				chalk.yellow(`⚠️ Running \``),
+				chalk.yellowBright(command),
+				chalk.yellow(`\` failed. You should run it and fix its complaints.`),
+			].join("")
+		);
+	}
+}
diff --git a/src/shared/cli/spinners.ts b/src/shared/cli/spinners.ts
index 1908487..b9a1a3b 100644
--- a/src/shared/cli/spinners.ts
+++ b/src/shared/cli/spinners.ts
@@ -27,7 +27,7 @@ export async function withSpinner<Return>(
 			const extra = warningHint ? `: ${warningHint}` : "";
 			s.stop(chalk.yellow(`⚠️ Error ${label}${extra}.`));
 		} else {
-			s.stop(chalk.green(`✅ Success ${label}.`));
+			s.stop(chalk.green(`✅ Passed ${label}.`));
 		}
 
 		return result;
diff --git a/src/shared/defaults.ts b/src/shared/defaults.ts
index 18d6876..35901e6 100644
--- a/src/shared/defaults.ts
+++ b/src/shared/defaults.ts
@@ -1,6 +1,8 @@
 import chalk from "chalk";
 import { $ } from "execa";
 
+import { logLine } from "./cli/lines.js";
+
 export async function getDefaultSettings() {
 	let gitRemoteFetch;
 	try {
@@ -9,7 +11,8 @@ export async function getDefaultSettings() {
 			.pipeStdout?.($({ stdin: "pipe" })`grep origin`)
 			.pipeStdout?.($({ stdin: "pipe" })`grep fetch`);
 	} catch {
-		console.log(
+		logLine();
+		logLine(
 			chalk.gray(
 				"Could not populate default owner and repository. Did not detect a Git repository with an origin. "
 			)


Test patch:
diff --git a/src/hydrate/steps/runCommand.test.ts b/src/hydrate/steps/runCommand.test.ts
new file mode 100644
index 0000000..ea433d8
--- /dev/null
+++ b/src/hydrate/steps/runCommand.test.ts
@@ -0,0 +1,48 @@
+import chalk from "chalk";
+import { describe, expect, it, vi } from "vitest";
+
+import { runCommand } from "./runCommand.js";
+
+const mockExecaCommand = vi.fn().mockRejectedValue("Oh no!");
+
+vi.mock("execa", () => ({
+	get execaCommand() {
+		return mockExecaCommand;
+	},
+}));
+
+const mockLogLine = vi.fn();
+
+vi.mock("../../shared/cli/lines.js", () => ({
+	get logLine() {
+		return mockLogLine;
+	},
+}));
+
+vi.mock("../../shared/cli/spinners.js", () => ({
+	withSpinner: vi.fn((callback: () => unknown) => callback()),
+}));
+
+describe("runCommand", () => {
+	it("does not log when the command succeeds", async () => {
+		mockExecaCommand.mockResolvedValue(undefined);
+
+		await runCommand("command", "label");
+
+		expect(mockLogLine).not.toHaveBeenCalled();
+	});
+
+	it("logs when the command failures", async () => {
+		mockExecaCommand.mockRejectedValue(new Error("Oh no!"));
+
+		await runCommand("command", "label");
+
+		expect(mockLogLine).toHaveBeenCalledWith(
+			[
+				chalk.yellow(`⚠️ Running \``),
+				chalk.yellowBright("command"),
+				chalk.yellow(`\` failed. You should run it and fix its complaints.`),
+			].join("")
+		);
+	});
+});