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

Issue:
🚀 Feature: Add ignoreDeps Addon to blockRenovate
### 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

To support #2177 (https://github.com/JoshuaKGoldberg/create-typescript-app/issues/2177#issuecomment-2787002777), I'm going to pin `all-contributors-cli` to a specific version. That means this repo's own `.github/renovate.json` is going to need an `ignoreDeps` Addon.

### Additional Info

I'll also add an `intake` to read it from disk if available.

Pull request description:
feat: add ignoreDeps Addon to blockRenovate
## PR Checklist

- [x] Addresses an existing open issue: fixes #2179
- [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

Also standardizes `blockTypeScript` to use the newer `intakeFileAsJson`, while I'm in the area.

🎁

Patch:
diff --git a/src/blocks/blockAllContributors.ts b/src/blocks/blockAllContributors.ts
index c496974..6645bbf 100644
--- a/src/blocks/blockAllContributors.ts
+++ b/src/blocks/blockAllContributors.ts
@@ -112,7 +112,7 @@ export const blockAllContributors = base.createBlock({
 			scripts: [
 				{
 					commands: [
-						`pnpx all-contributors-cli add ${options.owner} ${ownerContributions.join(",")}`,
+						`pnpx all-contributors-cli@6.23.0 add ${options.owner} ${ownerContributions.join(",")}`,
 					],
 					phase: CommandPhase.Process,
 				},
diff --git a/src/blocks/blockRenovate.ts b/src/blocks/blockRenovate.ts
index 05e50a2..345ebb1 100644
--- a/src/blocks/blockRenovate.ts
+++ b/src/blocks/blockRenovate.ts
@@ -1,11 +1,28 @@
+import { z } from "zod";
+
 import { base } from "../base.js";
 import { blockGitHubApps } from "./blockGitHubApps.js";
+import { intakeFileAsJson } from "./intake/intakeFileAsJson.js";
+
+const zIgnoreDeps = z.array(z.string()).default([]);
 
 export const blockRenovate = base.createBlock({
 	about: {
 		name: "Renovate",
 	},
-	produce() {
+	addons: {
+		ignoreDeps: zIgnoreDeps,
+	},
+	intake({ files }) {
+		const raw = intakeFileAsJson(files, [".github", "renovate.json"]);
+
+		return {
+			ignoreDeps: zIgnoreDeps.safeParse(raw?.ignoreDeps).data,
+		};
+	},
+	produce({ addons }) {
+		const { ignoreDeps } = addons;
+
 		return {
 			addons: [
 				blockGitHubApps({
@@ -27,7 +44,9 @@ export const blockRenovate = base.createBlock({
 							"config:best-practices",
 							"replacements:all",
 						],
-						ignoreDeps: ["codecov/codecov-action"],
+						ignoreDeps: Array.from(
+							new Set(["codecov/codecov-action", ...ignoreDeps]),
+						).sort(),
 						labels: ["dependencies"],
 						minimumReleaseAge: "7 days",
 						patch: { enabled: false },
diff --git a/src/blocks/blockTypeScript.ts b/src/blocks/blockTypeScript.ts
index 2a646ad..b67aa2c 100644
--- a/src/blocks/blockTypeScript.ts
+++ b/src/blocks/blockTypeScript.ts
@@ -1,4 +1,3 @@
-import JSON5 from "json5";
 import sortKeys from "sort-keys";
 import { CompilerOptionsSchema } from "zod-tsconfig";
 
@@ -15,7 +14,7 @@ import { blockPackageJson } from "./blockPackageJson.js";
 import { blockRemoveWorkflows } from "./blockRemoveWorkflows.js";
 import { blockVitest } from "./blockVitest.js";
 import { blockVSCode } from "./blockVSCode.js";
-import { intakeFile } from "./intake/intakeFile.js";
+import { intakeFileAsJson } from "./intake/intakeFileAsJson.js";
 
 export const blockTypeScript = base.createBlock({
 	about: {
@@ -25,12 +24,7 @@ export const blockTypeScript = base.createBlock({
 		compilerOptions: CompilerOptionsSchema.optional(),
 	},
 	intake({ files }) {
-		const tsconfig = intakeFile(files, ["tsconfig.json"]);
-		if (!tsconfig) {
-			return undefined;
-		}
-
-		const raw = JSON5.parse<Record<string, undefined> | undefined>(tsconfig[0]);
+		const raw = intakeFileAsJson(files, ["tsconfig.json"]);
 		const { data } = CompilerOptionsSchema.safeParse(raw?.compilerOptions);
 		if (!data) {
 			return undefined;


Test patch:
diff --git a/src/blocks/blockAllContributors.test.ts b/src/blocks/blockAllContributors.test.ts
index 617eb5a..4de5269 100644
--- a/src/blocks/blockAllContributors.test.ts
+++ b/src/blocks/blockAllContributors.test.ts
@@ -89,7 +89,7 @@ describe("blockAllContributors", () => {
 			  "scripts": [
 			    {
 			      "commands": [
-			        "pnpx all-contributors-cli add test-owner code,content,doc,ideas,infra,maintenance,projectManagement,tool",
+			        "pnpx all-contributors-cli@6.23.0 add test-owner code,content,doc,ideas,infra,maintenance,projectManagement,tool",
 			      ],
 			      "phase": 3,
 			    },
@@ -231,7 +231,7 @@ describe("blockAllContributors", () => {
 			  "scripts": [
 			    {
 			      "commands": [
-			        "pnpx all-contributors-cli add JoshuaKGoldberg bug,code,design,doc,test,tool,content,ideas,infra,maintenance,projectManagement",
+			        "pnpx all-contributors-cli@6.23.0 add JoshuaKGoldberg bug,code,design,doc,test,tool,content,ideas,infra,maintenance,projectManagement",
 			      ],
 			      "phase": 3,
 			    },
@@ -372,7 +372,7 @@ describe("blockAllContributors", () => {
 			  "scripts": [
 			    {
 			      "commands": [
-			        "pnpx all-contributors-cli add test-owner code,content,doc,ideas,infra,maintenance,projectManagement,tool",
+			        "pnpx all-contributors-cli@6.23.0 add test-owner code,content,doc,ideas,infra,maintenance,projectManagement,tool",
 			      ],
 			      "phase": 3,
 			    },
diff --git a/src/blocks/blockRenovate.test.ts b/src/blocks/blockRenovate.test.ts
new file mode 100644
index 0000000..d76e26d
--- /dev/null
+++ b/src/blocks/blockRenovate.test.ts
@@ -0,0 +1,135 @@
+import { testBlock, testIntake } from "bingo-stratum-testers";
+import { describe, expect, it, test } from "vitest";
+
+import { blockRenovate } from "./blockRenovate.js";
+import { optionsBase } from "./options.fakes.js";
+
+describe("blockRenovate", () => {
+	test("without addons", () => {
+		const creation = testBlock(blockRenovate, {
+			options: optionsBase,
+		});
+
+		expect(creation).toMatchInlineSnapshot(`
+			{
+			  "addons": [
+			    {
+			      "addons": {
+			        "apps": [
+			          {
+			            "name": "Renovate",
+			            "url": "https://github.com/apps/renovate",
+			          },
+			        ],
+			      },
+			      "block": [Function],
+			    },
+			  ],
+			  "files": {
+			    ".github": {
+			      "renovate.json": "{"$schema":"https://docs.renovatebot.com/renovate-schema.json","automerge":true,"extends":[":preserveSemverRanges","config:best-practices","replacements:all"],"ignoreDeps":["codecov/codecov-action"],"labels":["dependencies"],"minimumReleaseAge":"7 days","patch":{"enabled":false},"postUpdateOptions":["pnpmDedupe"]}",
+			    },
+			  },
+			}
+		`);
+	});
+
+	test("with addons", () => {
+		const creation = testBlock(blockRenovate, {
+			addons: {
+				ignoreDeps: ["all-contributors-cli"],
+			},
+			options: optionsBase,
+		});
+
+		expect(creation).toMatchInlineSnapshot(`
+			{
+			  "addons": [
+			    {
+			      "addons": {
+			        "apps": [
+			          {
+			            "name": "Renovate",
+			            "url": "https://github.com/apps/renovate",
+			          },
+			        ],
+			      },
+			      "block": [Function],
+			    },
+			  ],
+			  "files": {
+			    ".github": {
+			      "renovate.json": "{"$schema":"https://docs.renovatebot.com/renovate-schema.json","automerge":true,"extends":[":preserveSemverRanges","config:best-practices","replacements:all"],"ignoreDeps":["all-contributors-cli","codecov/codecov-action"],"labels":["dependencies"],"minimumReleaseAge":"7 days","patch":{"enabled":false},"postUpdateOptions":["pnpmDedupe"]}",
+			    },
+			  },
+			}
+		`);
+	});
+
+	describe("intake", () => {
+		it("returns no ignoreDeps when .github/renovate.json does not exist", () => {
+			const actual = testIntake(blockRenovate, {
+				files: {},
+			});
+
+			expect(actual).toEqual({ ignoreDeps: [] });
+		});
+
+		it("returns no ignoreDeps when .github/renovate.json does not contain truthy data", () => {
+			const actual = testIntake(blockRenovate, {
+				files: {
+					".github": {
+						"renovate.json": [JSON.stringify(null)],
+					},
+				},
+			});
+
+			expect(actual).toEqual({ ignoreDeps: [] });
+		});
+
+		it("returns no ignoreDeps when .github/renovate.json contains only unrelated data", () => {
+			const actual = testIntake(blockRenovate, {
+				files: {
+					".github": {
+						"renovate.json": [JSON.stringify({ other: true })],
+					},
+				},
+			});
+
+			expect(actual).toEqual({ ignoreDeps: [] });
+		});
+
+		it("returns ignoreDeps when .github/renovate.json contains ignoreDeps", () => {
+			const ignoreDeps = ["all-contributors-cli", "codecov/codecov-action"];
+
+			const actual = testIntake(blockRenovate, {
+				files: {
+					".github": {
+						"renovate.json": [JSON.stringify({ ignoreDeps })],
+					},
+				},
+			});
+
+			expect(actual).toEqual({ ignoreDeps });
+		});
+
+		it("returns ignoreDeps when .github/renovate.json contains ignoreDeps and other data", () => {
+			const ignoreDeps = ["all-contributors-cli", "codecov/codecov-action"];
+
+			const actual = testIntake(blockRenovate, {
+				files: {
+					".github": {
+						"renovate.json": [
+							JSON.stringify({
+								ignoreDeps,
+								other: true,
+							}),
+						],
+					},
+				},
+			});
+
+			expect(actual).toEqual({ ignoreDeps });
+		});
+	});
+});
diff --git a/src/blocks/blockTypeScript.test.ts b/src/blocks/blockTypeScript.test.ts
index 405f180..813b808 100644
--- a/src/blocks/blockTypeScript.test.ts
+++ b/src/blocks/blockTypeScript.test.ts
@@ -693,5 +693,22 @@ describe("blockTypeScript", () => {
 
 			expect(actual).toEqual({ compilerOptions });
 		});
+
+		it("returns compilerOptions when tsconfig.json contains compilerOptions and other data", () => {
+			const compilerOptions = { module: "ESNext" };
+
+			const actual = testIntake(blockTypeScript, {
+				files: {
+					"tsconfig.json": [
+						JSON.stringify({
+							compilerOptions,
+							other: true,
+						}),
+					],
+				},
+			});
+
+			expect(actual).toEqual({ compilerOptions });
+		});
 	});
 });