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

Issue:
🚀 Feature: Remove npmpackagejsonlintrc cleanups/migrations
### Bug Report Checklist

- [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.

### Overview

`npmpackagejsonlintrc` used to be a part of this template. It's referenced in cleanup:

https://github.com/JoshuaKGoldberg/create-typescript-app/blob/9f14f4758f171ffd6fec463efc34e5caac138811/src/steps/clearUnnecessaryFiles.ts#L20

I think it's been long enough that it can be removed.

### Additional Info

I'll do this as part of #1734 to make the cspell work end simpler.

💖 
🚀 Feature: cspell.json should start with inferable words
### Bug Report Checklist

- [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.

### Overview

There are some words we know will always go in `cspell.json` if included by options:

* `"codecov"`
* `"joshuakgoldberg"`
* `"markdownlint"`
* `"vitest"`

...and maybe others.

The template will automatically do a CSpell pass to find any that need to be added. That's helpful for the _migration_ use case of an existing repository. But for _creation_ and _initialization_, we already know the full list of words! No need to do a full CSpell run.

💡 this will probably help with #860 (performance)!

### Additional Info

Split out of #1670.

💖 

Pull request description:
feat: pre-fill cspell.json words
## PR Checklist

- [x] Addresses an existing open issue: fixes #1716; fixes #1737
- [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

Removes the `populateCSpellDictionary` call from _creation_. It was already not in _initialization_.

💖 

Patch:
diff --git a/cspell.json b/cspell.json
index 99ddf58..407cbdf 100644
--- a/cspell.json
+++ b/cspell.json
@@ -1,35 +1,28 @@
 {
-	"dictionaries": ["typescript"],
+	"dictionaries": ["npm", "node", "typescript"],
 	"ignorePaths": [
-		"./coverage*",
-		"./script/__snapshots__",
 		".all-contributorsrc",
 		".github",
 		"CHANGELOG.md",
+		"coverage*",
 		"lib",
 		"node_modules",
-		"pnpm-lock.yaml"
+		"pnpm-lock.yaml",
+		"script/__snapshots__"
 	],
 	"words": [
 		"allcontributors",
 		"apexskier",
 		"arethetypeswrong",
 		"automerge",
-		"codecov",
 		"codespace",
-		"contributorsrc",
 		"execa",
 		"infile",
-		"knip",
+		"joshuakgoldberg",
 		"markdownlintignore",
 		"mtfoley",
-		"npmignore",
-		"npmpackagejsonlintrc",
 		"outro",
-		"packagejson",
 		"tada",
-		"tseslint",
-		"tsup",
-		"vitest"
+		"tseslint"
 	]
 }
diff --git a/src/create/createWithOptions.ts b/src/create/createWithOptions.ts
index 6f60340..2a0407a 100644
--- a/src/create/createWithOptions.ts
+++ b/src/create/createWithOptions.ts
@@ -8,7 +8,6 @@ import { addToolAllContributors } from "../steps/addToolAllContributors.js";
 import { clearLocalGitTags } from "../steps/clearLocalGitTags.js";
 import { finalizeDependencies } from "../steps/finalizeDependencies.js";
 import { initializeGitHubRepository } from "../steps/initializeGitHubRepository/index.js";
-import { populateCSpellDictionary } from "../steps/populateCSpellDictionary.js";
 import { runCleanup } from "../steps/runCleanup.js";
 import { writeReadme } from "../steps/writeReadme/index.js";
 import { writeStructure } from "../steps/writing/writeStructure.js";
@@ -40,13 +39,6 @@ export async function createWithOptions({ github, options }: GitHubAndOptions) {
 			finalizeDependencies(options),
 		);
 
-		if (!options.excludeLintSpelling) {
-			await withSpinner(
-				"Populating CSpell dictionary",
-				populateCSpellDictionary,
-			);
-		}
-
 		await runCleanup(createCleanupCommands(options.bin), options.mode);
 	}
 
diff --git a/src/steps/clearUnnecessaryFiles.ts b/src/steps/clearUnnecessaryFiles.ts
index f1aa338..85f0e4b 100644
--- a/src/steps/clearUnnecessaryFiles.ts
+++ b/src/steps/clearUnnecessaryFiles.ts
@@ -17,7 +17,6 @@ const globPaths = [
 	".eslintrc",
 	"CODE_OF_CONDUCT.md",
 	"CONTRIBUTING.md",
-	".npmpackagejsonlintrc.json",
 	"codecov.yml",
 	"DEVELOPMENT.md",
 	"dist",
diff --git a/src/steps/writing/creation/createCSpellConfig.ts b/src/steps/writing/creation/createCSpellConfig.ts
new file mode 100644
index 0000000..4ebae5b
--- /dev/null
+++ b/src/steps/writing/creation/createCSpellConfig.ts
@@ -0,0 +1,38 @@
+import { Options } from "../../../shared/types.js";
+import { formatJson } from "./formatters/formatJson.js";
+
+export async function createCSpellConfig(
+	options: Pick<
+		Options,
+		| "excludeAllContributors"
+		| "excludeLintMd"
+		| "excludeReleases"
+		| "excludeRenovate"
+		| "excludeTemplatedBy"
+		| "excludeTests"
+	>,
+) {
+	const words = [
+		"tseslint",
+		!options.excludeReleases && "apexskier",
+		!options.excludeRenovate && "automerge",
+		!options.excludeLintMd && "markdownlintignore",
+		!options.excludeTemplatedBy && "joshuakgoldberg",
+	]
+		.filter(Boolean)
+		.sort();
+
+	return await formatJson({
+		dictionaries: ["npm", "node", "typescript"],
+		ignorePaths: [
+			...(options.excludeAllContributors ? [] : [".all-contributorsrc"]),
+			".github",
+			"CHANGELOG.md",
+			...(options.excludeTests ? [] : ["coverage"]),
+			"lib",
+			"node_modules",
+			"pnpm-lock.yaml",
+		],
+		words,
+	});
+}
diff --git a/src/steps/writing/creation/rootFiles.ts b/src/steps/writing/creation/rootFiles.ts
index 8f2f071..1e8bacb 100644
--- a/src/steps/writing/creation/rootFiles.ts
+++ b/src/steps/writing/creation/rootFiles.ts
@@ -1,4 +1,5 @@
 import { Options } from "../../../shared/types.js";
+import { createCSpellConfig } from "./createCSpellConfig.js";
 import { createDotGitignore } from "./createDotGitignore.js";
 import { createESLintConfig } from "./createESLintConfig.js";
 import { createTsupConfig } from "./createTsupConfig.js";
@@ -92,18 +93,7 @@ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 `,
 		...(!options.excludeLintSpelling && {
-			"cspell.json": await formatJson({
-				dictionaries: ["typescript"],
-				ignorePaths: [
-					...(options.excludeAllContributors ? [] : [".all-contributorsrc"]),
-					".github",
-					"CHANGELOG.md",
-					...(options.excludeTests ? [] : ["coverage"]),
-					"lib",
-					"node_modules",
-					"pnpm-lock.yaml",
-				],
-			}),
+			"cspell.json": await createCSpellConfig(options),
 		}),
 		...(!options.excludeLintKnip && {
 			"knip.json": await formatJson({


Test patch:
diff --git a/script/__snapshots__/migrate-test-e2e.ts.snap b/script/__snapshots__/migrate-test-e2e.ts.snap
index 3428595..dd81cbb 100644
--- a/script/__snapshots__/migrate-test-e2e.ts.snap
+++ b/script/__snapshots__/migrate-test-e2e.ts.snap
@@ -105,39 +105,33 @@ exports[`expected file changes > cspell.json 1`] = `
 "--- a/cspell.json
 +++ b/cspell.json
 @@ ... @@
- {
- 	"dictionaries": ["typescript"],
- 	"ignorePaths": [
--		"./coverage*",
--		"./script/__snapshots__",
  		".all-contributorsrc",
  		".github",
  		"CHANGELOG.md",
+-		"coverage*",
 +		"coverage",
  		"lib",
  		"node_modules",
- 		"pnpm-lock.yaml"
+-		"pnpm-lock.yaml",
+-		"script/__snapshots__"
++		"pnpm-lock.yaml"
+ 	],
+ 	"words": [
+ 		"allcontributors",
 @@ ... @@
- 		"apexskier",
  		"arethetypeswrong",
  		"automerge",
--		"codecov",
  		"codespace",
- 		"contributorsrc",
- 		"execa",
+-		"execa",
++		"contributorsrc",
  		"infile",
-+		"joshuakgoldberg",
- 		"knip",
-+		"markdownlint",
+ 		"joshuakgoldberg",
  		"markdownlintignore",
  		"mtfoley",
- 		"npmignore",
-@@ ... @@
+ 		"outro",
  		"tada",
- 		"tseslint",
- 		"tsup",
--		"vitest"
-+		"vitest",
+-		"tseslint"
++		"tseslint",
 +		"wontfix"
  	]
  }"
diff --git a/src/create/createWithOptions.test.ts b/src/create/createWithOptions.test.ts
index 7df6058..d7895f4 100644
--- a/src/create/createWithOptions.test.ts
+++ b/src/create/createWithOptions.test.ts
@@ -7,7 +7,6 @@ import { Options } from "../shared/types.js";
 import { addToolAllContributors } from "../steps/addToolAllContributors.js";
 import { finalizeDependencies } from "../steps/finalizeDependencies.js";
 import { initializeGitHubRepository } from "../steps/initializeGitHubRepository/index.js";
-import { populateCSpellDictionary } from "../steps/populateCSpellDictionary.js";
 import { runCleanup } from "../steps/runCleanup.js";
 import { createWithOptions } from "./createWithOptions.js";
 
@@ -61,8 +60,6 @@ vi.mock("../steps/writeReadme/index.js");
 
 vi.mock("../steps/finalizeDependencies.js");
 
-vi.mock("../steps/populateCSpellDictionary.js");
-
 vi.mock("../steps/clearLocalGitTags.js");
 
 vi.mock("../steps/runCleanup.js");
@@ -119,7 +116,7 @@ describe("createWithOptions", () => {
 		expect(addToolAllContributors).not.toHaveBeenCalled();
 	});
 
-	it("does not call finalizeDependencies, populateCSpellDictionary, or runCleanup when skipInstall is true", async () => {
+	it("does not call finalizeDependencies or runCleanup when skipInstall is true", async () => {
 		const options = {
 			...optionsBase,
 			skipInstall: true,
@@ -127,11 +124,10 @@ describe("createWithOptions", () => {
 
 		await createWithOptions({ github, options });
 		expect(finalizeDependencies).not.toHaveBeenCalled();
-		expect(populateCSpellDictionary).not.toHaveBeenCalled();
 		expect(runCleanup).not.toHaveBeenCalled();
 	});
 
-	it("calls finalizeDependencies, populateCSpellDictionary, and runCleanup when skipInstall is false", async () => {
+	it("calls finalizeDependencies and runCleanup when skipInstall is false", async () => {
 		const options = {
 			...optionsBase,
 			skipInstall: false,
@@ -140,7 +136,6 @@ describe("createWithOptions", () => {
 		await createWithOptions({ github, options });
 
 		expect(finalizeDependencies).toHaveBeenCalledWith(options);
-		expect(populateCSpellDictionary).toHaveBeenCalled();
 		expect(runCleanup).toHaveBeenCalled();
 	});
 
diff --git a/src/steps/writing/creation/createCSpellConfig.test.ts b/src/steps/writing/creation/createCSpellConfig.test.ts
new file mode 100644
index 0000000..b52ecff
--- /dev/null
+++ b/src/steps/writing/creation/createCSpellConfig.test.ts
@@ -0,0 +1,58 @@
+import { describe, expect, it } from "vitest";
+
+import { createCSpellConfig } from "./createCSpellConfig.js";
+
+describe("createCSpellConfig", () => {
+	it("creates an ignore file with all words when exclusions are disabled", async () => {
+		const actual = await createCSpellConfig({});
+
+		expect(actual).toMatchInlineSnapshot(`
+			"{
+				"dictionaries": ["npm", "node", "typescript"],
+				"ignorePaths": [
+					".all-contributorsrc",
+					".github",
+					"CHANGELOG.md",
+					"coverage",
+					"lib",
+					"node_modules",
+					"pnpm-lock.yaml"
+				],
+				"words": [
+					"apexskier",
+					"automerge",
+					"joshuakgoldberg",
+					"markdownlintignore",
+					"tseslint"
+				]
+			}
+			"
+		`);
+	});
+
+	it("creates an ignore file with minimal words when exclusions are enabled", async () => {
+		const actual = await createCSpellConfig({
+			excludeAllContributors: true,
+			excludeLintMd: true,
+			excludeReleases: true,
+			excludeRenovate: true,
+			excludeTemplatedBy: true,
+			excludeTests: true,
+		});
+
+		expect(actual).toMatchInlineSnapshot(`
+			"{
+				"dictionaries": ["npm", "node", "typescript"],
+				"ignorePaths": [
+					".github",
+					"CHANGELOG.md",
+					"lib",
+					"node_modules",
+					"pnpm-lock.yaml"
+				],
+				"words": ["tseslint"]
+			}
+			"
+		`);
+	});
+});