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

Issue:
🛠 Tooling: Add instrumentation to repo end-to-end tests
### 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 (`src/setup`) and #469 (`src/hydrate`) both added a bunch of code under `src/`, without fully unit testing that code. They have followup issues for adding more unit tests: #447 and #471, respectively.

But! The `src/hydrate/index.ts` and `src/setup/index.ts` scripts, despite not being unit tested, are _end-to-end_ tested: `pnpm run hydrate:test` and `pnpm run setup:test`, respectively. So I'd consider those lines as covered... even though Codecov doesn't think so.

![Screenshot of "coverage: 31%" readme badge with an orange/red flair background](https://github.com/JoshuaKGoldberg/template-typescript-node-package/assets/3335181/37280ae2-e7bd-4eaf-8e38-a86a8fc787e8)

So in theory this repository could:

1. Augment the `pnpm run hydrate:test` and `pnpm run setup:test` commands to generate code coverage reports
2. Use [Codecov coverage report merging](https://docs.codecov.com/docs/merging-reports) to merge reports for calculation

It'd be really nice to get that code coverage report on the README.md to the high number it deserves to be! 💚

### Additional Info

_No response_

Pull request:
build: also track e2e test coverage
<!-- 👋 Hi, thanks for sending a PR to template-typescript-node-package! 💖.
Please fill out all fields below and make sure each item is true and [x] checked.
Otherwise we may not be able to review your PR. -->

## PR Checklist

- [x] Addresses an existing open issue: fixes #472
- [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

Again uses the [codecov/codecov-action](https://github.com/codecov/codecov-action) to store coverage reports, this time from `coverage-hydrate` and `coverage-setup`.

For convenience, all coverage reports are stored in GitHub Actions (https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts).


Patch:
diff --git a/.eslintignore b/.eslintignore
index 9425b10..06a1bea 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,5 +1,5 @@
 !.*
-coverage
+coverage*
 lib
 node_modules
 pnpm-lock.yaml
diff --git a/.github/workflows/hydrate.yml b/.github/workflows/hydrate.yml
index be4c342..8defb82 100644
--- a/.github/workflows/hydrate.yml
+++ b/.github/workflows/hydrate.yml
@@ -5,7 +5,15 @@ jobs:
       - uses: actions/checkout@v3
       - uses: ./.github/actions/prepare
       - run: pnpm run build
-      - run: pnpm run setup:test
+      - run: pnpm run hydrate:test
+      - name: Codecov
+        uses: codecov/codecov-action@v3
+        with:
+          files: coverage-hydrate/lcov.info
+      - name: Archive code coverage results
+        uses: actions/upload-artifact@v3
+        with:
+          path: coverage-hydrate
 
 name: Test Hydrate Script
 
diff --git a/.github/workflows/setup.yml b/.github/workflows/setup.yml
index a6e97ab..9377766 100644
--- a/.github/workflows/setup.yml
+++ b/.github/workflows/setup.yml
@@ -4,6 +4,7 @@ jobs:
     steps:
       - uses: actions/checkout@v3
       - uses: ./.github/actions/prepare
+      - run: pnpm run build
       - run: pnpm run setup:test
 
 name: Test Setup Script
diff --git a/.gitignore b/.gitignore
index a8bff3b..32ffbca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
-coverage/
+coverage*/
 lib/
 node_modules/
diff --git a/.prettierignore b/.prettierignore
index ec0950c..fb77bd6 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,4 +1,4 @@
-coverage/
+coverage*/
 lib/
 pnpm-lock.yaml
 
diff --git a/knip.jsonc b/knip.jsonc
index 0100426..34dfd57 100644
--- a/knip.jsonc
+++ b/knip.jsonc
@@ -7,5 +7,6 @@
 		"script/*e2e.js"
 	],
 	"ignoreBinaries": ["dedupe", "gh"],
+	"ignoreDependencies": ["c8"],
 	"project": ["src/**/*.ts!", "script/**/*.js"]
 }
diff --git a/package.json b/package.json
index 652f458..37ad95c 100644
--- a/package.json
+++ b/package.json
@@ -52,6 +52,7 @@
 		"@typescript-eslint/parser": "^5.59.5",
 		"@vitest/coverage-istanbul": "^0.31.0",
 		"all-contributors-cli": "^6.25.1",
+		"c8": "^7.13.0",
 		"console-fail-test": "^0.2.3",
 		"cspell": "^6.31.1",
 		"eslint": "^8.40.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8f0cbb3..b442ea8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -39,6 +39,9 @@ devDependencies:
   all-contributors-cli:
     specifier: ^6.25.1
     version: 6.25.1
+  c8:
+    specifier: ^7.13.0
+    version: 7.13.0
   console-fail-test:
     specifier: ^0.2.3
     version: 0.2.3
@@ -365,6 +368,10 @@ packages:
       to-fast-properties: 2.0.0
     dev: true
 
+  /@bcoe/v8-coverage@0.2.3:
+    resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
+    dev: true
+
   /@clack/core@0.3.2:
     resolution: {integrity: sha512-FZnsNynwGDIDktx6PEZK1EuCkFpY4ldEX6VYvfl0dqeoLPb9Jpw1xoUXaVcGR8ExmYNm1w2vdGdJkEUYD/2pqg==}
     dependencies:
@@ -1401,6 +1408,10 @@ packages:
     resolution: {integrity: sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==}
     dev: true
 
+  /@types/istanbul-lib-coverage@2.0.4:
+    resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==}
+    dev: true
+
   /@types/js-yaml@4.0.5:
     resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==}
     dev: true
@@ -2117,6 +2128,25 @@ packages:
     engines: {node: '>= 0.8'}
     dev: true
 
+  /c8@7.13.0:
+    resolution: {integrity: sha512-/NL4hQTv1gBL6J6ei80zu3IiTrmePDKXKXOTLpHvcIWZTVYQlDhVWjjWvkhICylE8EwwnMVzDZugCvdx0/DIIA==}
+    engines: {node: '>=10.12.0'}
+    hasBin: true
+    dependencies:
+      '@bcoe/v8-coverage': 0.2.3
+      '@istanbuljs/schema': 0.1.3
+      find-up: 5.0.0
+      foreground-child: 2.0.0
+      istanbul-lib-coverage: 3.2.0
+      istanbul-lib-report: 3.0.0
+      istanbul-reports: 3.1.5
+      rimraf: 3.0.2
+      test-exclude: 6.0.0
+      v8-to-istanbul: 9.1.0
+      yargs: 16.2.0
+      yargs-parser: 20.2.9
+    dev: true
+
   /cac@6.7.14:
     resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
     engines: {node: '>=8'}
@@ -2327,6 +2357,14 @@ packages:
       wrap-ansi: 6.2.0
     dev: true
 
+  /cliui@7.0.4:
+    resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
+    dependencies:
+      string-width: 4.2.3
+      strip-ansi: 6.0.1
+      wrap-ansi: 7.0.0
+    dev: true
+
   /cliui@8.0.1:
     resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
     engines: {node: '>=12'}
@@ -3570,6 +3608,14 @@ packages:
       is-callable: 1.2.7
     dev: true
 
+  /foreground-child@2.0.0:
+    resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==}
+    engines: {node: '>=8.0.0'}
+    dependencies:
+      cross-spawn: 7.0.3
+      signal-exit: 3.0.7
+    dev: true
+
   /foreground-child@3.1.1:
     resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==}
     engines: {node: '>=14'}
@@ -6956,6 +7002,15 @@ packages:
     resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
     dev: true
 
+  /v8-to-istanbul@9.1.0:
+    resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==}
+    engines: {node: '>=10.12.0'}
+    dependencies:
+      '@jridgewell/trace-mapping': 0.3.17
+      '@types/istanbul-lib-coverage': 2.0.4
+      convert-source-map: 1.9.0
+    dev: true
+
   /validate-npm-package-license@3.0.4:
     resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==}
     dependencies:
@@ -7322,6 +7377,19 @@ packages:
       yargs-parser: 18.1.3
     dev: true
 
+  /yargs@16.2.0:
+    resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==}
+    engines: {node: '>=10'}
+    dependencies:
+      cliui: 7.0.4
+      escalade: 3.1.1
+      get-caller-file: 2.0.5
+      require-directory: 2.1.1
+      string-width: 4.2.3
+      y18n: 5.0.8
+      yargs-parser: 20.2.9
+    dev: true
+
   /yargs@17.7.1:
     resolution: {integrity: sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==}
     engines: {node: '>=12'}
diff --git a/src/hydrate/creation/rootFiles.ts b/src/hydrate/creation/rootFiles.ts
index ba1fb73..ea556e1 100644
--- a/src/hydrate/creation/rootFiles.ts
+++ b/src/hydrate/creation/rootFiles.ts
@@ -359,7 +359,7 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 				skipLibCheck: true,
 				sourceMap: true,
 				strict: true,
-				target: "ES2021",
+				target: "ES2022",
 			},
 			include: ["src"],
 		}),
diff --git a/src/hydrate/finalize.ts b/src/hydrate/finalize.ts
index d991217..a50096e 100644
--- a/src/hydrate/finalize.ts
+++ b/src/hydrate/finalize.ts
@@ -11,6 +11,7 @@ export async function finalize({ releases, unitTests }: FinalizeOptions) {
 		"@types/eslint",
 		"@typescript-eslint/eslint-plugin",
 		"@typescript-eslint/parser",
+		"all-contributors-cli",
 		"cspell",
 		"eslint",
 		"eslint-config-prettier",
@@ -56,7 +57,8 @@ export async function finalize({ releases, unitTests }: FinalizeOptions) {
 
 	for (const command of [
 		`pnpm add ${devDependencies.join(" ")} -D`,
-		`all-contributors generate`,
+		`npx all-contributors generate`,
+		`pnpm uninstall all-contributors-cli -D`,
 		"pnpm run format:write",
 	]) {
 		console.log(chalk.gray(`$ ${command}`));
diff --git a/src/setup/index.ts b/src/setup/index.ts
index 7a33e48..caec91a 100644
--- a/src/setup/index.ts
+++ b/src/setup/index.ts
@@ -1,204 +1,3 @@
-import * as prompts from "@clack/prompts";
-import chalk from "chalk";
-import { $ } from "execa";
+import { setup } from "./setup.js";
 
-import { handlePromptCancel } from "./cli/prompts.js";
-import {
-	skipSpinnerBlock,
-	successSpinnerBlock,
-	withSpinner,
-} from "./cli/spinners.js";
-import { getNpmAuthor } from "./settings/getNpmAuthor.js";
-import { getUser } from "./settings/getUser.js";
-import { getInputValuesAndOctokit } from "./settings/inputs.js";
-import { clearChangelog } from "./steps/clearChangelog.js";
-import { hydrateBranchProtectionSettings } from "./steps/hydrateBranchProtectionSettings.js";
-import { hydrateRepositoryLabels } from "./steps/hydrateRepositoryLabels.js";
-import { hydrateRepositorySettings } from "./steps/hydrateRepositorySettings.js";
-import { removeSetupScripts } from "./steps/removeSetupScripts.js";
-import { resetGitTags } from "./steps/resetGitTags.js";
-import { uninstallPackages } from "./steps/uninstallPackages.js";
-import { updateAllContributorsFile } from "./steps/updateAllContributorsFile.js";
-import { updateAllContributorsTable } from "./steps/updateAllContributorsTable.js";
-import { updateLocalFiles } from "./steps/updateLocalFiles.js";
-import { updateReadme } from "./steps/updateReadme.js";
-
-let exitCode = 0;
-let skipRestore = true;
-
-try {
-	console.clear();
-	console.log(
-		chalk.greenBright`Welcome to the`,
-		chalk.bgGreenBright.black`template-typescript-node-package`,
-		chalk.greenBright`package setup! 🎉`
-	);
-	console.log();
-
-	const args = process.argv.slice(2);
-	const { octokit, values } = await getInputValuesAndOctokit(args);
-
-	skipRestore = !!values.skipRestore;
-
-	prompts.intro(
-		chalk.blue(
-			"Let's collect some information to fill out repository details..."
-		)
-	);
-	console.log(chalk.gray("│"));
-
-	successSpinnerBlock("Started hydrating package metadata locally.");
-
-	await withSpinner(() => getUser(values.owner), {
-		startText: "Updating existing contributors details...",
-		successText: `Updated existing contributors details.`,
-		stopText: `Error updating existing contributors details.`,
-		errorText:
-			"Failed to remove existing contributors & update '.all-contributorsrc' with new changes. ",
-	});
-
-	const npmAuthor = await getNpmAuthor(values.owner);
-
-	await withSpinner(() => updateLocalFiles({ ...values, npmAuthor }), {
-		startText: "Updating all the files with provided details...",
-		successText: `Updated all the files with provided details.`,
-		stopText: `Error updating all the files with provided details.`,
-		errorText: "Failed to update all the files with provided details. ",
-	});
-
-	await withSpinner(() => updateAllContributorsFile(values), {
-		startText: "Updating '.all-contributorsrc' with new repository details...",
-		successText: `Updated '.all-contributorsrc' with new repository details.`,
-		stopText: `Error updating '.all-contributorsrc' with new repository details.`,
-		errorText:
-			"Failed to update '.all-contributorsrc' with new repository details. ",
-	});
-
-	await withSpinner(updateReadme, {
-		startText:
-			"Appending template-typescript-node-package notice to 'README.md'...",
-		successText: `Appended template-typescript-node-package notice to 'README.md'.`,
-		stopText: `Error appending template-typescript-node-package notice to 'README.md'.`,
-		errorText:
-			"Failed to append template-typescript-node-package notice to 'README.md'. ",
-	});
-
-	successSpinnerBlock(`Finished hydrating package metadata locally.`);
-
-	await withSpinner(clearChangelog, {
-		startText: `Clearing CHANGELOG.md...`,
-		successText: `Cleared CHANGELOG.md.`,
-		stopText: `Error clearing CHANGELOG.md.`,
-		errorText: `Could not empty 'CHANGELOG.md'. `,
-	});
-
-	await withSpinner(() => updateAllContributorsTable(values), {
-		startText: `Generating all-contributors table in README.md...`,
-		successText: `Generated all-contributors table in README.md.`,
-		stopText: `Error generating all-contributors table in README.md.`,
-		errorText: `Could not empty 'CHANGELOG.md'. `,
-	});
-
-	await withSpinner(resetGitTags, {
-		startText: `Deleting local git tags...`,
-		successText: `Deleted local git tags.`,
-		stopText: `Error deleting local git tags.`,
-		errorText: `Could not delete local git tags. `,
-	});
-
-	if (!octokit) {
-		skipSpinnerBlock(`Skipping API hydration.`);
-	} else {
-		successSpinnerBlock(`Starting API hydration.`);
-
-		await withSpinner(hydrateRepositoryLabels, {
-			startText: `Hydrating repository labels...`,
-			successText: `Hydrated repository labels.`,
-			stopText: `Error hydrating repository labels.`,
-			errorText: `Could not hydrate repository labels. `,
-		});
-
-		await withSpinner(() => hydrateRepositorySettings(octokit, values), {
-			startText: `Hydrating initial repository settings...`,
-			successText: `Hydrated initial repository settings.`,
-			stopText: `Error hydrating initial repository settings.`,
-			errorText: `Could not hydrate initial repository settings. `,
-		});
-
-		await withSpinner(() => hydrateBranchProtectionSettings(octokit, values), {
-			startText: `Hydrating branch protection settings...`,
-			successText: `Hydrated branch protection settings.`,
-			stopText: `Error hydrating branch protection settings.`,
-			errorText: `Could not hydrate branch protection settings. `,
-			warnText: `Could not hydrate branch protection settings: private repositories require GitHub Pro for that API.`,
-		});
-
-		successSpinnerBlock(`Finished API hydration.`);
-	}
-
-	await withSpinner(removeSetupScripts, {
-		startText: `Removing setup scripts...`,
-		successText: `Removed setup scripts.`,
-		stopText: `Error removing setup scripts.`,
-		errorText: `Could not remove setup scripts. `,
-	});
-
-	if (values.skipUninstalls) {
-		skipSpinnerBlock(`Skipping uninstall of packages only used for setup.`);
-	} else {
-		await withSpinner(uninstallPackages, {
-			startText: `Removing packages only used for setup...`,
-			successText: `Removed packages only used for setup.`,
-			stopText: `Error removing packages only used for setup.`,
-			errorText: `Could not remove packages only used for setup. `,
-		});
-	}
-
-	prompts.outro(chalk.blue`Great, looks like everything worked! 🎉`);
-
-	console.log(chalk.blue`You may consider committing these changes:`);
-	console.log();
-	console.log(chalk.gray`git add -A`);
-	console.log(chalk.gray`git commit -m "chore: hydrated repo"`);
-	console.log(chalk.gray`git push`);
-	console.log();
-	console.log(chalk.greenBright`See ya! 👋`);
-	console.log();
-
-	exitCode = 0;
-} catch (error) {
-	prompts.outro(
-		chalk.red`Looks like there was a problem. Correct it and try again? 😕`
-	);
-
-	console.log();
-	console.log(error);
-
-	if (skipRestore) {
-		console.log();
-		console.log(chalk.gray`Skipping restoring local repository, as requested.`);
-		console.log();
-	} else {
-		const shouldRestore = await prompts.confirm({
-			message:
-				"Do you want to restore the repository to how it was before running setup?",
-		});
-
-		handlePromptCancel(shouldRestore);
-
-		if (shouldRestore) {
-			console.log();
-			console.log(
-				chalk.gray`Resetting repository using`,
-				chalk.reset`git restore .`
-			);
-			await $`git restore .`;
-			console.log("Repository is reset. Ready to try again?");
-			console.log();
-		}
-	}
-
-	exitCode = 1;
-} finally {
-	process.exit(exitCode);
-}
+process.exitCode = await setup(process.argv.slice(2));
diff --git a/src/setup/setup.ts b/src/setup/setup.ts
new file mode 100644
index 0000000..d642f48
--- /dev/null
+++ b/src/setup/setup.ts
@@ -0,0 +1,211 @@
+import * as prompts from "@clack/prompts";
+import chalk from "chalk";
+import { $ } from "execa";
+
+import { handlePromptCancel } from "./cli/prompts.js";
+import {
+	skipSpinnerBlock,
+	successSpinnerBlock,
+	withSpinner,
+} from "./cli/spinners.js";
+import { getNpmAuthor } from "./settings/getNpmAuthor.js";
+import { getUser } from "./settings/getUser.js";
+import { getInputValuesAndOctokit } from "./settings/inputs.js";
+import { clearChangelog } from "./steps/clearChangelog.js";
+import { hydrateBranchProtectionSettings } from "./steps/hydrateBranchProtectionSettings.js";
+import { hydrateRepositoryLabels } from "./steps/hydrateRepositoryLabels.js";
+import { hydrateRepositorySettings } from "./steps/hydrateRepositorySettings.js";
+import { removeSetupScripts } from "./steps/removeSetupScripts.js";
+import { resetGitTags } from "./steps/resetGitTags.js";
+import { uninstallPackages } from "./steps/uninstallPackages.js";
+import { updateAllContributorsFile } from "./steps/updateAllContributorsFile.js";
+import { updateAllContributorsTable } from "./steps/updateAllContributorsTable.js";
+import { updateLocalFiles } from "./steps/updateLocalFiles.js";
+import { updateReadme } from "./steps/updateReadme.js";
+
+export async function setup(args: string[]) {
+	let exitCode = 0;
+	let skipRestore = true;
+
+	try {
+		console.clear();
+		console.log(
+			chalk.greenBright`Welcome to the`,
+			chalk.bgGreenBright.black`template-typescript-node-package`,
+			chalk.greenBright`package setup! 🎉`
+		);
+		console.log();
+
+		const { octokit, values } = await getInputValuesAndOctokit(args);
+
+		skipRestore = !!values.skipRestore;
+
+		prompts.intro(
+			chalk.blue(
+				"Let's collect some information to fill out repository details..."
+			)
+		);
+		console.log(chalk.gray("│"));
+
+		successSpinnerBlock("Started hydrating package metadata locally.");
+
+		await withSpinner(() => getUser(values.owner), {
+			startText: "Updating existing contributors details...",
+			successText: `Updated existing contributors details.`,
+			stopText: `Error updating existing contributors details.`,
+			errorText:
+				"Failed to remove existing contributors & update '.all-contributorsrc' with new changes. ",
+		});
+
+		const npmAuthor = await getNpmAuthor(values.owner);
+
+		await withSpinner(() => updateLocalFiles({ ...values, npmAuthor }), {
+			startText: "Updating all the files with provided details...",
+			successText: `Updated all the files with provided details.`,
+			stopText: `Error updating all the files with provided details.`,
+			errorText: "Failed to update all the files with provided details. ",
+		});
+
+		await withSpinner(() => updateAllContributorsFile(values), {
+			startText:
+				"Updating '.all-contributorsrc' with new repository details...",
+			successText: `Updated '.all-contributorsrc' with new repository details.`,
+			stopText: `Error updating '.all-contributorsrc' with new repository details.`,
+			errorText:
+				"Failed to update '.all-contributorsrc' with new repository details. ",
+		});
+
+		await withSpinner(updateReadme, {
+			startText:
+				"Appending template-typescript-node-package notice to 'README.md'...",
+			successText: `Appended template-typescript-node-package notice to 'README.md'.`,
+			stopText: `Error appending template-typescript-node-package notice to 'README.md'.`,
+			errorText:
+				"Failed to append template-typescript-node-package notice to 'README.md'. ",
+		});
+
+		successSpinnerBlock(`Finished hydrating package metadata locally.`);
+
+		await withSpinner(clearChangelog, {
+			startText: `Clearing CHANGELOG.md...`,
+			successText: `Cleared CHANGELOG.md.`,
+			stopText: `Error clearing CHANGELOG.md.`,
+			errorText: `Could not empty 'CHANGELOG.md'. `,
+		});
+
+		await withSpinner(() => updateAllContributorsTable(values), {
+			startText: `Generating all-contributors table in README.md...`,
+			successText: `Generated all-contributors table in README.md.`,
+			stopText: `Error generating all-contributors table in README.md.`,
+			errorText: `Could not empty 'CHANGELOG.md'. `,
+		});
+
+		await withSpinner(resetGitTags, {
+			startText: `Deleting local git tags...`,
+			successText: `Deleted local git tags.`,
+			stopText: `Error deleting local git tags.`,
+			errorText: `Could not delete local git tags. `,
+		});
+
+		if (!octokit) {
+			skipSpinnerBlock(`Skipping API hydration.`);
+		} else {
+			successSpinnerBlock(`Starting API hydration.`);
+
+			await withSpinner(hydrateRepositoryLabels, {
+				startText: `Hydrating repository labels...`,
+				successText: `Hydrated repository labels.`,
+				stopText: `Error hydrating repository labels.`,
+				errorText: `Could not hydrate repository labels. `,
+			});
+
+			await withSpinner(() => hydrateRepositorySettings(octokit, values), {
+				startText: `Hydrating initial repository settings...`,
+				successText: `Hydrated initial repository settings.`,
+				stopText: `Error hydrating initial repository settings.`,
+				errorText: `Could not hydrate initial repository settings. `,
+			});
+
+			await withSpinner(
+				() => hydrateBranchProtectionSettings(octokit, values),
+				{
+					startText: `Hydrating branch protection settings...`,
+					successText: `Hydrated branch protection settings.`,
+					stopText: `Error hydrating branch protection settings.`,
+					errorText: `Could not hydrate branch protection settings. `,
+					warnText: `Could not hydrate branch protection settings: private repositories require GitHub Pro for that API.`,
+				}
+			);
+
+			successSpinnerBlock(`Finished API hydration.`);
+		}
+
+		await withSpinner(removeSetupScripts, {
+			startText: `Removing setup scripts...`,
+			successText: `Removed setup scripts.`,
+			stopText: `Error removing setup scripts.`,
+			errorText: `Could not remove setup scripts. `,
+		});
+
+		if (values.skipUninstalls) {
+			skipSpinnerBlock(`Skipping uninstall of packages only used for setup.`);
+		} else {
+			await withSpinner(uninstallPackages, {
+				startText: `Removing packages only used for setup...`,
+				successText: `Removed packages only used for setup.`,
+				stopText: `Error removing packages only used for setup.`,
+				errorText: `Could not remove packages only used for setup. `,
+			});
+		}
+
+		prompts.outro(chalk.blue`Great, looks like everything worked! 🎉`);
+
+		console.log(chalk.blue`You may consider committing these changes:`);
+		console.log();
+		console.log(chalk.gray`git add -A`);
+		console.log(chalk.gray`git commit -m "chore: hydrated repo"`);
+		console.log(chalk.gray`git push`);
+		console.log();
+		console.log(chalk.greenBright`See ya! 👋`);
+		console.log();
+
+		exitCode = 0;
+	} catch (error) {
+		prompts.outro(
+			chalk.red`Looks like there was a problem. Correct it and try again? 😕`
+		);
+
+		console.log();
+		console.log(error);
+
+		if (skipRestore) {
+			console.log();
+			console.log(
+				chalk.gray`Skipping restoring local repository, as requested.`
+			);
+			console.log();
+		} else {
+			const shouldRestore = await prompts.confirm({
+				message:
+					"Do you want to restore the repository to how it was before running setup?",
+			});
+
+			handlePromptCancel(shouldRestore);
+
+			if (shouldRestore) {
+				console.log();
+				console.log(
+					chalk.gray`Resetting repository using`,
+					chalk.reset`git restore .`
+				);
+				await $`git restore .`;
+				console.log("Repository is reset. Ready to try again?");
+				console.log();
+			}
+		}
+
+		exitCode = 1;
+	}
+
+	return exitCode;
+}
diff --git a/src/setup/steps/uninstallPackages.ts b/src/setup/steps/uninstallPackages.ts
index 72a19b9..ec827d7 100644
--- a/src/setup/steps/uninstallPackages.ts
+++ b/src/setup/steps/uninstallPackages.ts
@@ -2,5 +2,5 @@ import { $ } from "execa";
 
 export async function uninstallPackages() {
 	await $`pnpm remove chalk execa js-yaml`;
-	await $`pnpm remove @clack/prompts @octokit/request-error @types/js-yaml @types/prettier all-contributors-cli globby octokit npm-user replace-in-file title-case tsx -D`;
+	await $`pnpm remove @clack/prompts @octokit/request-error @types/js-yaml @types/prettier all-contributors-cli c8 globby octokit npm-user replace-in-file title-case tsx -D`;
 }
diff --git a/src/setup/steps/updateLocalFiles.ts b/src/setup/steps/updateLocalFiles.ts
index ff30fae..eaa8d51 100644
--- a/src/setup/steps/updateLocalFiles.ts
+++ b/src/setup/steps/updateLocalFiles.ts
@@ -40,6 +40,7 @@ export async function updateLocalFiles({
 		[`,\n\t\t["src/setup/*.json"`, ``, "./cspell.json"],
 		[`\t\t"src/hydrate/index.ts",\n`, ``, "./knip.jsonc"],
 		[`\t\t"src/setup/index.ts",\n`, ``, "./knip.jsonc"],
+		[`\t\t"ignoreDependencies": ["c8"],\n`, ``, "./knip.jsonc"],
 		[
 			`["src/index.ts!", "script/setup*.js"]`,
 			`"src/index.ts!"`,


Test patch:
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 52e0032..d9e0315 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -7,8 +7,11 @@ jobs:
       - run: pnpm run test --coverage
       - name: Codecov
         uses: codecov/codecov-action@v3
+      - name: Archive code coverage results
+        uses: actions/upload-artifact@v3
         with:
-          github-token: ${{ secrets.GITHUB_TOKEN }}
+          name: code-coverage-report
+          path: coverage
 
 name: Test
 
diff --git a/script/hydrate-test-e2e.js b/script/hydrate-test-e2e.js
index 2168320..a136a9c 100644
--- a/script/hydrate-test-e2e.js
+++ b/script/hydrate-test-e2e.js
@@ -1,6 +1,10 @@
 import chalk from "chalk";
 import { $ } from "execa";
 
+await $({
+	stdio: "inherit",
+})`c8 -o ./coverage-hydrate -r html -r lcov node ./lib/hydrate/index.js`;
+
 const { stdout: gitStatus } = await $`git status`;
 console.log(`Stdout from running \`git status\`:\n${gitStatus}`);
 
@@ -19,6 +23,15 @@ const filesExpectedToBeChanged = new Set([
 	"knip.jsonc",
 	"package.json",
 	"pnpm-lock.yaml",
+	".eslintignore",
+	".eslintrc.cjs",
+	".github/DEVELOPMENT.md",
+	".github/workflows/release.yml",
+	".github/workflows/test.yml",
+	".gitignore",
+	".prettierignore",
+	".release-it.json",
+	"cspell.json",
 ]);
 
 const unstagedModifiedFiles = gitStatus
@@ -27,24 +40,29 @@ const unstagedModifiedFiles = gitStatus
 	.map((match) => match.split(/\s+/g)[1])
 	.filter((filePath) => !filesExpectedToBeChanged.has(filePath));
 
+console.log("Unexpected modified files are:", unstagedModifiedFiles);
+
 if (unstagedModifiedFiles.length) {
+	const gitDiffCommand = `git diff HEAD -- ${unstagedModifiedFiles.join(" ")}`;
 	console.log(
-		`Stdout from running \`git diff\`:${(await $`git diff HEAD`).stdout}`
+		`Stdout from running \`${gitDiffCommand}\`:\n${
+			(await $(gitDiffCommand)).stdout
+		}`
 	);
 	console.error(
-		chalk.red(
-			[
-				"",
-				"Oh no! Running the hydrate script modified some files:",
-				...unstagedModifiedFiles.map((filePath) => ` - ${filePath}`),
-				"",
-				"That likely indicates changes made to the repository without",
-				"corresponding updates to templates in src/hydrate/creation.",
-				"",
-				"Please search for those file(s)' name(s) under src/hydrate for",
-				"the corresponding template and update those as well.",
-			].join("\n")
-		)
+		[
+			"",
+			"Oh no! Running the hydrate script modified some files:",
+			...unstagedModifiedFiles.map((filePath) => ` - ${filePath}`),
+			"",
+			"That likely indicates changes made to the repository without",
+			"corresponding updates to templates in src/hydrate/creation.",
+			"",
+			"Please search for those file(s)' name(s) under src/hydrate for",
+			"the corresponding template and update those as well.",
+		]
+			.map((line) => chalk.red(line))
+			.join("\n")
 	);
 	process.exitCode = 1;
 }
diff --git a/script/setup-test-e2e.js b/script/setup-test-e2e.js
index 44ed23c..3a4c39c 100644
--- a/script/setup-test-e2e.js
+++ b/script/setup-test-e2e.js
@@ -9,9 +9,9 @@ const owner = "RNR1";
 const title = "New Title Test";
 const repository = "new-repository-test";
 
-const result =
-	await $`pnpm run setup --description ${description} --owner ${owner} --title ${title} --repository ${repository} --skip-api --skip-restore`;
-console.log("Result from pnpm run setup:", result);
+await $({
+	stdio: "inherit",
+})`node ./lib/setup/index.js --description ${description} --owner ${owner} --title ${title} --repository ${repository} --skip-api --skip-restore`;
 
 const newPackageJson = JSON.parse(
 	(await fs.readFile("./package.json")).toString()
diff --git a/src/hydrate/finalize.test.ts b/src/hydrate/finalize.test.ts
index 85e30d0..98a427f 100644
--- a/src/hydrate/finalize.test.ts
+++ b/src/hydrate/finalize.test.ts
@@ -24,6 +24,7 @@ describe("finalize", () => {
 				"@types/eslint",
 				"@typescript-eslint/eslint-plugin",
 				"@typescript-eslint/parser",
+				"all-contributors-cli",
 				"cspell",
 				"eslint",
 				"eslint-config-prettier",
@@ -66,6 +67,7 @@ describe("finalize", () => {
 				"@types/eslint",
 				"@typescript-eslint/eslint-plugin",
 				"@typescript-eslint/parser",
+				"all-contributors-cli",
 				"cspell",
 				"eslint",
 				"eslint-config-prettier",