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

Issue:
🚀 Feature: Allow overriding compilerOptions in tsconfig.json and tsup.config.ts
### Feature Request 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

Some repos customize the TypeScript build settings for `tsc` and `tsup. Example: https://github.com/JoshuaKGoldberg/eslint-fix-utils/pull/13

They'll need hooks to be able to keep those customizations.

### Additional Info

🎁 

Pull request:
feat: allow customizing tsc, tsup builds
## PR Checklist

- [x] Addresses an existing open issue: fixes #2057
- [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

🎁 

Patch:
diff --git a/package.json b/package.json
index 0058ae9..849ac23 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,8 @@
 		"title-case": "^4.3.2",
 		"trash-cli": "^6.0.0",
 		"zod": "^3.24.2",
-		"zod-package-json": "^1.1.0"
+		"zod-package-json": "^1.1.0",
+		"zod-tsconfig": "^0.1.1"
 	},
 	"devDependencies": {
 		"@eslint-community/eslint-plugin-eslint-comments": "4.4.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 17141b5..970ab3c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -98,6 +98,9 @@ importers:
       zod-package-json:
         specifier: ^1.1.0
         version: 1.1.0
+      zod-tsconfig:
+        specifier: ^0.1.1
+        version: 0.1.1
     devDependencies:
       '@eslint-community/eslint-plugin-eslint-comments':
         specifier: 4.4.1
@@ -4444,6 +4447,10 @@ packages:
     resolution: {integrity: sha512-RvEsa3W/NCqEBMtnoE09GRVelA3IqRcKaijEiM6CEGsD19qLurf0HjrYMHwOqImOszlLL0ja63DPJeeU4pm7oQ==}
     engines: {node: '>=20'}
 
+  zod-tsconfig@0.1.1:
+    resolution: {integrity: sha512-vb24ELvJOTwhKCg876cDBo7CGwdAHu8u/MRlJkNHVS8UBAUd4h5nLH8NcnceT/f5EL78jp+2/wVo+2gqVfM8Ew==}
+    engines: {node: '>=18.3.0'}
+
   zod-validation-error@3.4.0:
     resolution: {integrity: sha512-ZOPR9SVY6Pb2qqO5XHt+MkkTRxGXb4EVtnjc9JpXUOtUB1T9Ru7mZOT361AN3MsetVe7R0a1KZshJDZdgp9miQ==}
     engines: {node: '>=18.0.0'}
@@ -8850,6 +8857,10 @@ snapshots:
     dependencies:
       zod: 3.24.2
 
+  zod-tsconfig@0.1.1:
+    dependencies:
+      zod: 3.24.2
+
   zod-validation-error@3.4.0(zod@3.24.2):
     dependencies:
       zod: 3.24.2
diff --git a/src/blocks/blockTSup.ts b/src/blocks/blockTSup.ts
index 793ee9f..a67a885 100644
--- a/src/blocks/blockTSup.ts
+++ b/src/blocks/blockTSup.ts
@@ -18,10 +18,11 @@ export const blockTSup = base.createBlock({
 	},
 	addons: {
 		entry: z.array(z.string()).default([]),
+		properties: z.record(z.unknown()).default({}),
 		runInCI: z.array(z.string()).default([]),
 	},
 	produce({ addons, options }) {
-		const { entry } = addons;
+		const { entry, properties, runInCI } = addons;
 
 		return {
 			addons: [
@@ -53,7 +54,7 @@ pnpm build --watch
 							name: "Build",
 							steps: [
 								{ run: "pnpm build" },
-								...addons.runInCI.map((run) => ({ run })),
+								...runInCI.map((run) => ({ run })),
 							],
 						},
 					],
@@ -78,14 +79,15 @@ pnpm build --watch
 			files: {
 				"tsup.config.ts": `import { defineConfig } from "tsup";
 
-export default defineConfig({
-	bundle: false,
-	clean: true,
-	dts: true,
-	entry: ${JSON.stringify(["src/**/*.ts", ...entry])},
-	format: "esm",
-	outDir: "lib",
-});
+export default defineConfig(${JSON.stringify({
+					bundle: false,
+					clean: true,
+					dts: true,
+					entry: ["src/**/*.ts", ...entry],
+					format: "esm",
+					outDir: "lib",
+					...properties,
+				})});
 `,
 			},
 			scripts: options.bin
diff --git a/src/blocks/blockTypeScript.ts b/src/blocks/blockTypeScript.ts
index 17a4447..73b08f9 100644
--- a/src/blocks/blockTypeScript.ts
+++ b/src/blocks/blockTypeScript.ts
@@ -1,3 +1,5 @@
+import { CompilerOptionsSchema } from "zod-tsconfig";
+
 import { base } from "../base.js";
 import { getPackageDependencies } from "../data/packageData.js";
 import { getPrimaryBin } from "./bin/getPrimaryBin.js";
@@ -15,7 +17,11 @@ export const blockTypeScript = base.createBlock({
 	about: {
 		name: "TypeScript",
 	},
-	produce({ options }) {
+	addons: {
+		compilerOptions: CompilerOptionsSchema.optional(),
+	},
+	produce({ addons, options }) {
+		const { compilerOptions } = addons;
 		const primaryBin = getPrimaryBin(options.bin, options.repository);
 
 		return {
@@ -126,6 +132,7 @@ export * from "./types.js";
 						skipLibCheck: true,
 						strict: true,
 						target: "ES2022",
+						...compilerOptions,
 					},
 					include: ["src"],
 				}),


Test patch:
diff --git a/src/blocks/blockTSup.test.ts b/src/blocks/blockTSup.test.ts
index cdb4482..2952b28 100644
--- a/src/blocks/blockTSup.test.ts
+++ b/src/blocks/blockTSup.test.ts
@@ -88,14 +88,7 @@ describe("blockTSup", () => {
 			  "files": {
 			    "tsup.config.ts": "import { defineConfig } from "tsup";
 
-			export default defineConfig({
-				bundle: false,
-				clean: true,
-				dts: true,
-				entry: ["src/**/*.ts"],
-				format: "esm",
-				outDir: "lib",
-			});
+			export default defineConfig({"bundle":false,"clean":true,"dts":true,"entry":["src/**/*.ts"],"format":"esm","outDir":"lib"});
 			",
 			  },
 			  "scripts": undefined,
@@ -214,14 +207,7 @@ describe("blockTSup", () => {
 			  "files": {
 			    "tsup.config.ts": "import { defineConfig } from "tsup";
 
-			export default defineConfig({
-				bundle: false,
-				clean: true,
-				dts: true,
-				entry: ["src/**/*.ts"],
-				format: "esm",
-				outDir: "lib",
-			});
+			export default defineConfig({"bundle":false,"clean":true,"dts":true,"entry":["src/**/*.ts"],"format":"esm","outDir":"lib"});
 			",
 			  },
 			}
@@ -310,14 +296,7 @@ describe("blockTSup", () => {
 			  "files": {
 			    "tsup.config.ts": "import { defineConfig } from "tsup";
 
-			export default defineConfig({
-				bundle: false,
-				clean: true,
-				dts: true,
-				entry: ["src/**/*.ts","!src/**/*.test.ts"],
-				format: "esm",
-				outDir: "lib",
-			});
+			export default defineConfig({"bundle":false,"clean":true,"dts":true,"entry":["src/**/*.ts","!src/**/*.test.ts"],"format":"esm","outDir":"lib"});
 			",
 			  },
 			  "scripts": undefined,
diff --git a/src/blocks/blockTypeScript.test.ts b/src/blocks/blockTypeScript.test.ts
index 0af3ff9..d2949f7 100644
--- a/src/blocks/blockTypeScript.test.ts
+++ b/src/blocks/blockTypeScript.test.ts
@@ -5,7 +5,7 @@ import { blockTypeScript } from "./blockTypeScript.js";
 import { optionsBase } from "./options.fakes.js";
 
 describe("blockTypeScript", () => {
-	test("without options.bin", () => {
+	test("without addons or options", () => {
 		const creation = testBlock(blockTypeScript, {
 			options: optionsBase,
 		});
@@ -152,6 +152,158 @@ describe("blockTypeScript", () => {
 		`);
 	});
 
+	test("with addons", () => {
+		const creation = testBlock(blockTypeScript, {
+			addons: {
+				compilerOptions: {
+					strictBindCallApply: false,
+				},
+			},
+			options: optionsBase,
+		});
+
+		expect(creation).toMatchInlineSnapshot(`
+			{
+			  "addons": [
+			    {
+			      "addons": {
+			        "sections": {
+			          "Type Checking": {
+			            "contents": "
+			You should be able to see suggestions from [TypeScript](https://typescriptlang.org) in your editor for all open files.
+
+			However, it can be useful to run the TypeScript command-line (\`tsc\`) to type check all files in \`src/\`:
+
+			\`\`\`shell
+			pnpm tsc
+			\`\`\`
+
+			Add \`--watch\` to keep the type checker running in a watch mode that updates the display as you save files:
+
+			\`\`\`shell
+			pnpm tsc --watch
+			\`\`\`
+			",
+			          },
+			        },
+			      },
+			      "block": [Function],
+			    },
+			    {
+			      "addons": {
+			        "files": {
+			          "greet.ts": "import { GreetOptions } from "./types.js";
+				
+				export function greet(options: GreetOptions | string) {
+					const {
+						logger = console.log.bind(console),
+						message,
+						times = 1,
+					} = typeof options === "string" ? { message: options } : options;
+				
+					for (let i = 0; i < times; i += 1) {
+						logger(message);
+					}
+				}
+				",
+			          "index.ts": "export * from "./greet.js";
+			export * from "./types.js";
+			",
+			          "types.ts": "export interface GreetOptions {
+					logger?: (message: string) => void;
+					message: string;
+					times?: number;
+				}
+				",
+			        },
+			      },
+			      "block": [Function],
+			    },
+			    {
+			      "addons": {
+			        "ignores": [
+			          "/lib",
+			        ],
+			      },
+			      "block": [Function],
+			    },
+			    {
+			      "addons": {
+			        "jobs": [
+			          {
+			            "name": "Type Check",
+			            "steps": [
+			              {
+			                "run": "pnpm tsc",
+			              },
+			            ],
+			          },
+			        ],
+			      },
+			      "block": [Function],
+			    },
+			    {
+			      "addons": {
+			        "ignores": [
+			          "lib/",
+			        ],
+			      },
+			      "block": [Function],
+			    },
+			    {
+			      "addons": {
+			        "properties": {
+			          "devDependencies": {
+			            "typescript": "5.8.2",
+			          },
+			          "files": [
+			            "lib/",
+			          ],
+			          "scripts": {
+			            "tsc": "tsc",
+			          },
+			        },
+			      },
+			      "block": [Function],
+			    },
+			    {
+			      "addons": {
+			        "coverage": {
+			          "include": [
+			            "src",
+			          ],
+			        },
+			        "exclude": [
+			          "lib",
+			        ],
+			      },
+			      "block": [Function],
+			    },
+			    {
+			      "addons": {
+			        "debuggers": [],
+			        "settings": {
+			          "typescript.tsdk": "node_modules/typescript/lib",
+			        },
+			        "tasks": [
+			          {
+			            "detail": "Build the project",
+			            "label": "build",
+			            "script": "build",
+			            "type": "npm",
+			          },
+			        ],
+			      },
+			      "block": [Function],
+			    },
+			  ],
+			  "files": {
+			    "tsconfig.json": "{"compilerOptions":{"declaration":true,"declarationMap":true,"esModuleInterop":true,"module":"NodeNext","moduleResolution":"NodeNext","noEmit":true,"resolveJsonModule":true,"skipLibCheck":true,"strict":true,"target":"ES2022","strictBindCallApply":false},"include":["src"]}",
+			  },
+			}
+		`);
+	});
+
 	test("with options.bin", () => {
 		const creation = testBlock(blockTypeScript, {
 			options: {