From b5d80e3b420a929090d529dd1068e0a6e7a91677 Mon Sep 17 00:00:00 2001
From: Josh Goldberg <git@joshuakgoldberg.com>
Date: Sat, 30 Dec 2023 10:02:44 -0500
Subject: [PATCH 1/3] fix: normalize whether build happens before lint/knip

---
 .github/workflows/lint-knip.yml               |  1 -
 src/create/createWithOptions.ts               | 13 ++++++----
 src/initialize/initializeWithOptions.ts       | 11 +++++---
 src/migrate/migrateWithOptions.ts             | 12 ++++++---
 src/shared/createCleanUpFilesCommands.test.ts | 25 +++++++++++++++++++
 src/shared/createCleanUpFilesCommands.ts      | 18 +++++++++++++
 .../dotGitHub/createWorkflows.test.ts         |  2 +-
 .../creation/dotGitHub/createWorkflows.ts     |  2 +-
 8 files changed, 68 insertions(+), 16 deletions(-)
 create mode 100644 src/shared/createCleanUpFilesCommands.test.ts
 create mode 100644 src/shared/createCleanUpFilesCommands.ts

diff --git a/.github/workflows/lint-knip.yml b/.github/workflows/lint-knip.yml
index df3f955b7..781d52ef1 100644
--- a/.github/workflows/lint-knip.yml
+++ b/.github/workflows/lint-knip.yml
@@ -4,7 +4,6 @@ jobs:
     steps:
       - uses: actions/checkout@v4
       - uses: ./.github/actions/prepare
-      - run: pnpm build || true
       - run: pnpm lint:knip
 
 name: Lint Knip
diff --git a/src/create/createWithOptions.ts b/src/create/createWithOptions.ts
index 8dc5caa80..6067fe65c 100644
--- a/src/create/createWithOptions.ts
+++ b/src/create/createWithOptions.ts
@@ -1,5 +1,6 @@
 import { $ } from "execa";
 
+import { createCleanUpFilesCommands } from "../shared/createCleanUpFilesCommands.js";
 import { withSpinner, withSpinners } from "../shared/cli/spinners.js";
 import { doesRepositoryExist } from "../shared/doesRepositoryExist.js";
 import { GitHubAndOptions } from "../shared/options/readOptions.js";
@@ -37,11 +38,13 @@ export async function createWithOptions({ github, options }: GitHubAndOptions) {
 			finalizeDependencies(options),
 		);
 
-		await runCommands("Cleaning up files", [
-			"pnpm dedupe",
-			"pnpm lint --fix",
-			"pnpm format --write",
-		]);
+		await runCommands(
+			"Cleaning up files",
+			createCleanUpFilesCommands({
+				bin: !!options.bin,
+				dedupe: true,
+			}),
+		);
 	}
 
 	const sendToGitHub =
diff --git a/src/initialize/initializeWithOptions.ts b/src/initialize/initializeWithOptions.ts
index 960c2c329..cfa7767d6 100644
--- a/src/initialize/initializeWithOptions.ts
+++ b/src/initialize/initializeWithOptions.ts
@@ -1,3 +1,4 @@
+import { createCleanUpFilesCommands } from "../shared/createCleanUpFilesCommands.js";
 import { withSpinner, withSpinners } from "../shared/cli/spinners.js";
 import { GitHubAndOptions } from "../shared/options/readOptions.js";
 import { addOwnerAsAllContributor } from "../steps/addOwnerAsAllContributor.js";
@@ -60,8 +61,10 @@ export async function initializeWithOptions({
 		);
 	}
 
-	await runCommands("Cleaning up files", [
-		"pnpm lint --fix",
-		"pnpm format --write",
-	]);
+	await runCommands(
+		"Cleaning up files",
+		createCleanUpFilesCommands({
+			bin: !!options.bin,
+		}),
+	);
 }
diff --git a/src/migrate/migrateWithOptions.ts b/src/migrate/migrateWithOptions.ts
index 01e4a3291..e5b542904 100644
--- a/src/migrate/migrateWithOptions.ts
+++ b/src/migrate/migrateWithOptions.ts
@@ -1,3 +1,4 @@
+import { createCleanUpFilesCommands } from "../shared/createCleanUpFilesCommands.js";
 import { withSpinner, withSpinners } from "../shared/cli/spinners.js";
 import { GitHubAndOptions } from "../shared/options/readOptions.js";
 import { clearUnnecessaryFiles } from "../steps/clearUnnecessaryFiles.js";
@@ -60,8 +61,11 @@ export async function migrateWithOptions({
 		);
 	}
 
-	await runCommands("Cleaning up files", [
-		"pnpm lint --fix",
-		"pnpm format --write",
-	]);
+	await runCommands(
+		"Cleaning up files",
+		createCleanUpFilesCommands({
+			bin: !!options.bin,
+			dedupe: true,
+		}),
+	);
 }
diff --git a/src/shared/createCleanUpFilesCommands.test.ts b/src/shared/createCleanUpFilesCommands.test.ts
new file mode 100644
index 000000000..1bcf01850
--- /dev/null
+++ b/src/shared/createCleanUpFilesCommands.test.ts
@@ -0,0 +1,25 @@
+import { describe, expect, it, vi } from "vitest";
+
+import { createCleanUpFilesCommands } from "./createCleanUpFilesCommands.js";
+
+describe("createCleanUpFilesCommands", () => {
+	it("only lints and formats when no options are specified", () => {
+		const actual = createCleanUpFilesCommands({});
+
+		expect(actual).toEqual(["pnpm lint --fix", "pnpm format --write"]);
+	});
+
+	it("runs dedupe and build before it lints and formats when both options are specified", () => {
+		const actual = createCleanUpFilesCommands({
+			bin: true,
+			dedupe: true,
+		});
+
+		expect(actual).toEqual([
+			"pnpm dedupe",
+			"pnpm build || exit 0",
+			"pnpm lint --fix",
+			"pnpm format --write",
+		]);
+	});
+});
diff --git a/src/shared/createCleanUpFilesCommands.ts b/src/shared/createCleanUpFilesCommands.ts
new file mode 100644
index 000000000..b13033b48
--- /dev/null
+++ b/src/shared/createCleanUpFilesCommands.ts
@@ -0,0 +1,18 @@
+export interface CleanUpFilesOptions {
+	bin?: boolean;
+	dedupe?: boolean;
+}
+
+export function createCleanUpFilesCommands({
+	bin,
+	dedupe,
+}: CleanUpFilesOptions) {
+	return [
+		// There's no need to dedupe when initializing from the fixed template
+		...(dedupe ? ["pnpm dedupe"] : []),
+		// n/no-missing-import rightfully reports on a missing the bin .js file
+		...(bin ? ["pnpm build || exit 0"] : []),
+		"pnpm lint --fix",
+		"pnpm format --write",
+	];
+}
diff --git a/src/steps/writing/creation/dotGitHub/createWorkflows.test.ts b/src/steps/writing/creation/dotGitHub/createWorkflows.test.ts
index 4ee3242d7..da37f2444 100644
--- a/src/steps/writing/creation/dotGitHub/createWorkflows.test.ts
+++ b/src/steps/writing/creation/dotGitHub/createWorkflows.test.ts
@@ -7,6 +7,7 @@ const createOptions = (exclude: boolean) =>
 	({
 		access: "public",
 		base: "everything",
+		bin: exclude ? undefined : "./bin/index.js",
 		description: "Test description.",
 		directory: ".",
 		email: {
@@ -413,7 +414,6 @@ describe("createWorkflows", () => {
 			    steps:
 			      - uses: actions/checkout@v4
 			      - uses: ./.github/actions/prepare
-			      - run: pnpm build || true
 			      - run: pnpm lint
 
 			name: Lint
diff --git a/src/steps/writing/creation/dotGitHub/createWorkflows.ts b/src/steps/writing/creation/dotGitHub/createWorkflows.ts
index 150604898..d97027d00 100644
--- a/src/steps/writing/creation/dotGitHub/createWorkflows.ts
+++ b/src/steps/writing/creation/dotGitHub/createWorkflows.ts
@@ -114,7 +114,7 @@ export function createWorkflows(options: Options) {
 		}),
 		"lint.yml": createWorkflowFile({
 			name: "Lint",
-			runs: ["pnpm build || true", "pnpm lint"],
+			runs: [...(options.bin ? ["pnpm build || true"] : []), "pnpm lint"],
 		}),
 		...(!options.excludeLintKnip && {
 			"lint-knip.yml": createWorkflowFile({

From 274ff6bc24b9ea51e607181e4da8f54de5a466e6 Mon Sep 17 00:00:00 2001
From: Josh Goldberg <git@joshuakgoldberg.com>
Date: Sat, 30 Dec 2023 10:09:27 -0500
Subject: [PATCH 2/3] Fix linting

---
 script/migrate-test-e2e.js                    | 1 -
 src/create/createWithOptions.ts               | 2 +-
 src/initialize/initializeWithOptions.ts       | 2 +-
 src/migrate/migrateWithOptions.ts             | 2 +-
 src/shared/createCleanUpFilesCommands.test.ts | 2 +-
 5 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/script/migrate-test-e2e.js b/script/migrate-test-e2e.js
index 82921de21..832bb482e 100644
--- a/script/migrate-test-e2e.js
+++ b/script/migrate-test-e2e.js
@@ -12,7 +12,6 @@ const filesExpectedToBeChanged = [
 	".eslintignore",
 	".eslintrc.cjs",
 	".github/DEVELOPMENT.md",
-	".github/workflows/lint-knip.yml",
 	".github/workflows/test.yml",
 	".gitignore",
 	".prettierignore",
diff --git a/src/create/createWithOptions.ts b/src/create/createWithOptions.ts
index 6067fe65c..8c03d0c8d 100644
--- a/src/create/createWithOptions.ts
+++ b/src/create/createWithOptions.ts
@@ -1,7 +1,7 @@
 import { $ } from "execa";
 
-import { createCleanUpFilesCommands } from "../shared/createCleanUpFilesCommands.js";
 import { withSpinner, withSpinners } from "../shared/cli/spinners.js";
+import { createCleanUpFilesCommands } from "../shared/createCleanUpFilesCommands.js";
 import { doesRepositoryExist } from "../shared/doesRepositoryExist.js";
 import { GitHubAndOptions } from "../shared/options/readOptions.js";
 import { addToolAllContributors } from "../steps/addToolAllContributors.js";
diff --git a/src/initialize/initializeWithOptions.ts b/src/initialize/initializeWithOptions.ts
index cfa7767d6..127fb6b14 100644
--- a/src/initialize/initializeWithOptions.ts
+++ b/src/initialize/initializeWithOptions.ts
@@ -1,5 +1,5 @@
-import { createCleanUpFilesCommands } from "../shared/createCleanUpFilesCommands.js";
 import { withSpinner, withSpinners } from "../shared/cli/spinners.js";
+import { createCleanUpFilesCommands } from "../shared/createCleanUpFilesCommands.js";
 import { GitHubAndOptions } from "../shared/options/readOptions.js";
 import { addOwnerAsAllContributor } from "../steps/addOwnerAsAllContributor.js";
 import { clearChangelog } from "../steps/clearChangelog.js";
diff --git a/src/migrate/migrateWithOptions.ts b/src/migrate/migrateWithOptions.ts
index e5b542904..04b769565 100644
--- a/src/migrate/migrateWithOptions.ts
+++ b/src/migrate/migrateWithOptions.ts
@@ -1,5 +1,5 @@
-import { createCleanUpFilesCommands } from "../shared/createCleanUpFilesCommands.js";
 import { withSpinner, withSpinners } from "../shared/cli/spinners.js";
+import { createCleanUpFilesCommands } from "../shared/createCleanUpFilesCommands.js";
 import { GitHubAndOptions } from "../shared/options/readOptions.js";
 import { clearUnnecessaryFiles } from "../steps/clearUnnecessaryFiles.js";
 import { detectExistingContributors } from "../steps/detectExistingContributors.js";
diff --git a/src/shared/createCleanUpFilesCommands.test.ts b/src/shared/createCleanUpFilesCommands.test.ts
index 1bcf01850..6dafc83a8 100644
--- a/src/shared/createCleanUpFilesCommands.test.ts
+++ b/src/shared/createCleanUpFilesCommands.test.ts
@@ -1,4 +1,4 @@
-import { describe, expect, it, vi } from "vitest";
+import { describe, expect, it } from "vitest";
 
 import { createCleanUpFilesCommands } from "./createCleanUpFilesCommands.js";
 

From d39ebaab88897556bb0821de4225f6e00a51e71f Mon Sep 17 00:00:00 2001
From: Josh Goldberg <git@joshuakgoldberg.com>
Date: Sat, 30 Dec 2023 10:10:48 -0500
Subject: [PATCH 3/3] Update snapshot

---
 script/__snapshots__/migrate-test-e2e.js.snap | 13 -------------
 1 file changed, 13 deletions(-)

diff --git a/script/__snapshots__/migrate-test-e2e.js.snap b/script/__snapshots__/migrate-test-e2e.js.snap
index 954ca1ded..2e609b8e6 100644
--- a/script/__snapshots__/migrate-test-e2e.js.snap
+++ b/script/__snapshots__/migrate-test-e2e.js.snap
@@ -63,19 +63,6 @@ exports[`expected file changes > .github/DEVELOPMENT.md 1`] = `
  As described in the \`README.md\` file and \`docs/\`, this template repository comes with three scripts that can set up an existing or new repository."
 `;
 
-exports[`expected file changes > .github/workflows/lint-knip.yml 1`] = `
-"--- a/.github/workflows/lint-knip.yml
-+++ b/.github/workflows/lint-knip.yml
-@@ ... @@ jobs:
-     steps:
-       - uses: actions/checkout@v4
-       - uses: ./.github/actions/prepare
--      - run: pnpm build || true
-       - run: pnpm lint:knip
- 
- name: Lint Knip"
-`;
-
 exports[`expected file changes > .github/workflows/test.yml 1`] = `
 "--- a/.github/workflows/test.yml
 +++ b/.github/workflows/test.yml