Repository: JoshuaKGoldberg/bingo (ts)

Issue:
🚀 Feature: Have Stratum Templates infer refinement Block additions from disk in transition mode
### 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/bingo/issues?q=is%3Aissue) and found none that matched my issue.

### Overview

Over in https://github.com/JoshuaKGoldberg/create-typescript-app/issues/1966, I've been rolling out CTA 2 with a `blockCTATransitions` with Bingo's transition mode to a few dozen of my repositories. All of the repositories have specified `refinements` in a `create-typescript-app.config.js` file. That's an unfortunate increase in the number of root-level files in the repo.

#327 -> #328 allows for `refinements.addons` to be inferred from disk (in lieu of the more automatic #128). But what about `refinements.blocks`?

Transition mode already infers which Blocks _seem_ to exist based on existing files: #237 -> #245. Let's use that information to also infer `refinements.blocks.add` based on any Block whose >=1 created files all exist on disk.

### Additional Info

After this is implemented, the `blocks.add: [blockCTATransitions]` in https://github.com/JoshuaKGoldberg/create-typescript-app/issues/1966 should be inferable. If a repo was set up with `npx create-typescript-app --add-cta-transitions` then running `npx create-typescript-app` later on should infer the Block.

💝 

Pull request description:
feat(bingo-stratum): infer --add-* options from files on disk
## PR Checklist

- [x] Addresses an existing open issue: fixes #332
- [x] That issue was marked as [`status: accepting prs`](https://github.com/JoshuaKGoldberg/bingo/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22)
- [x] Steps in [CONTRIBUTING.md](https://github.com/JoshuaKGoldberg/bingo/blob/main/.github/CONTRIBUTING.md) were taken

## Overview

Augments `inferPreset` to now be `inferExistingBlocks`, also seeing which Blocks have all their `files` creations represented on disk. This allows the Stratum `prepareOptions` to infer `--add-*` options.

💝 

Patch:
diff --git a/packages/bingo-stratum/src/creators/inferPreset.ts b/packages/bingo-stratum/src/creators/inferExistingBlocks.ts
similarity index 60%
rename from packages/bingo-stratum/src/creators/inferPreset.ts
rename to packages/bingo-stratum/src/creators/inferExistingBlocks.ts
index 25eb9c7..078e68b 100644
--- a/packages/bingo-stratum/src/creators/inferPreset.ts
+++ b/packages/bingo-stratum/src/creators/inferExistingBlocks.ts
@@ -13,14 +13,15 @@ import {
 } from "../producers/produceBlock.js";
 import { Block } from "../types/blocks.js";
 import { Preset } from "../types/presets.js";
+import { StratumTemplate } from "../types/templates.js";
 import { slugifyName } from "../utils/slugifyName.js";
 
-export function inferPreset<OptionsShape extends AnyShape, Refinements>(
+export function inferExistingBlocks<OptionsShape extends AnyShape, Refinements>(
 	context: Pick<
 		TemplatePrepareContext<Partial<InferredObject<OptionsShape>>, Refinements>,
 		"files" | "options"
 	>,
-	presets: Preset<OptionsShape>[],
+	template: StratumTemplate<OptionsShape>,
 ) {
 	const blockSettings: ProduceBlockSettings<undefined, Options> = {
 		...context,
@@ -32,17 +33,28 @@ export function inferPreset<OptionsShape extends AnyShape, Refinements>(
 
 	let record: undefined | { percentage: number; preset: Preset<OptionsShape> };
 
-	for (const preset of presets) {
-		const allProduced = preset.blocks.map((block) => {
+	const existingProductions = new Map(
+		template.blocks.map((block) => {
 			try {
-				return produceBlock(block as Block<undefined, Options>, blockSettings);
+				return [
+					block,
+					produceBlock(block as Block<undefined, Options>, blockSettings),
+				];
 			} catch {
-				return {};
+				return [block, {}];
 			}
-		});
+		}),
+	);
 
-		const produced = allProduced.reduce(mergeCreations);
-		const counted = countMatchedFilePaths(context.files, produced.files);
+	for (const preset of template.presets) {
+		const existingPresetProduction = preset.blocks
+			.map((block) => existingProductions.get(block))
+			.filter((x) => !!x)
+			.reduce(mergeCreations, {});
+		const counted = countMatchedFilePaths(
+			context.files,
+			existingPresetProduction.files,
+		);
 		const percentage = counted.matched / counted.created;
 
 		if (record) {
@@ -54,13 +66,27 @@ export function inferPreset<OptionsShape extends AnyShape, Refinements>(
 		}
 	}
 
-	return (
-		// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-		record!.percentage >= 0.35
-			? // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
-				slugifyName(record!.preset.about.name)
-			: undefined
-	);
+	const existingPreset =
+		record && record.percentage >= 0.35 ? record.preset : undefined;
+
+	if (!existingPreset) {
+		return {};
+	}
+
+	const existingBlocks = Array.from(existingProductions)
+		.filter(([, production]) => {
+			const counted = countMatchedFilePaths(context.files, production.files);
+
+			return !!counted.matched && !counted.missed;
+		})
+		.map(([block]) => block);
+
+	const blocksInPreset = new Set(existingPreset.blocks);
+
+	return {
+		blocks: existingBlocks.filter((block) => !blocksInPreset.has(block)),
+		preset: slugifyName(existingPreset.about.name),
+	};
 }
 
 function countMatchedFilePaths(
@@ -70,6 +96,7 @@ function countMatchedFilePaths(
 	const found = {
 		created: 0,
 		matched: 0,
+		missed: 0,
 	};
 
 	const queue: [CreatedEntry | undefined, CreatedEntry | undefined][] = [
@@ -88,13 +115,18 @@ function countMatchedFilePaths(
 			}
 
 			continue;
+		} else if (isCreatedFile(currentProduced)) {
+			found.missed += 1;
 		}
 
 		if (
 			isCreatedDirectory(currentCreated) &&
 			isCreatedDirectory(currentProduced)
 		) {
-			for (const key of Object.keys(currentCreated)) {
+			for (const key of new Set([
+				...Object.keys(currentCreated),
+				...Object.keys(currentProduced),
+			])) {
 				queue.push([currentCreated[key], currentProduced[key]]);
 			}
 		}
diff --git a/packages/bingo-stratum/src/creators/utils.ts b/packages/bingo-stratum/src/creators/utils.ts
index f2c7962..92f4319 100644
--- a/packages/bingo-stratum/src/creators/utils.ts
+++ b/packages/bingo-stratum/src/creators/utils.ts
@@ -1,6 +1,8 @@
 import { AnyOptionalShape, InferredObject } from "bingo";
 import { z } from "zod";
 
+import { Block } from "../types/blocks.js";
+
 export function applyZodDefaults<Shape extends AnyOptionalShape>(
 	shape: Shape,
 	value: InferredObject<Shape> | undefined,
@@ -8,6 +10,15 @@ export function applyZodDefaults<Shape extends AnyOptionalShape>(
 	return z.object(shape).parse(value ?? {}) as InferredObject<Shape>;
 }
 
+export function isBlockWithName<
+	Addons extends object | undefined,
+	Options extends object,
+>(
+	block: Block<Addons, Options>,
+): block is Block<Addons, Options> & { about: { name: string } } {
+	return !!block.about?.name;
+}
+
 export function isDefinitionWithAddons(definition: object) {
 	return "addons" in definition;
 }
diff --git a/packages/bingo-stratum/src/producers/applyBlockRefinements.ts b/packages/bingo-stratum/src/producers/applyBlockRefinements.ts
index 05ee546..9a6c0f3 100644
--- a/packages/bingo-stratum/src/producers/applyBlockRefinements.ts
+++ b/packages/bingo-stratum/src/producers/applyBlockRefinements.ts
@@ -1,5 +1,6 @@
 import { CachedFactory } from "cached-factory";
 
+import { isBlockWithName } from "../creators/utils.js";
 import { Block } from "../types/blocks.js";
 import { BlockRefinements } from "../types/refinements.js";
 import { slugifyName } from "../utils/slugifyName.js";
@@ -9,8 +10,6 @@ interface BlockRefinement {
 	exclude?: boolean;
 }
 
-type BlockWithName = Block & { about: { name: string } };
-
 export function applyBlockRefinements<Options extends object>(
 	blocksAvailable: Block<object | undefined, Options>[],
 	blocksInitial: Block<object | undefined, Options>[],
@@ -24,7 +23,7 @@ export function applyBlockRefinements<Options extends object>(
 	);
 	const allBlocksByName = new Map(
 		blocksAvailable
-			.filter((block): block is BlockWithName => !!block.about?.name)
+			.filter(isBlockWithName)
 			.map((block) => [slugifyName(block.about.name), block]),
 	);
 	const refinementsByBlock = new CachedFactory<string, BlockRefinement>(
diff --git a/packages/bingo/package.json b/packages/bingo/package.json
index 244bb7e..9926a45 100644
--- a/packages/bingo/package.json
+++ b/packages/bingo/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "bingo",
-	"version": "0.5.12",
+	"version": "0.5.13",
 	"description": "Delightful templates for web repositories. 💝",
 	"homepage": "https://create.bingo",
 	"repository": {


Test patch:
diff --git a/packages/bingo-stratum/src/creators/createStratumTemplate.test.ts b/packages/bingo-stratum/src/creators/createStratumTemplate.test.ts
index 0820efe..315242d 100644
--- a/packages/bingo-stratum/src/creators/createStratumTemplate.test.ts
+++ b/packages/bingo-stratum/src/creators/createStratumTemplate.test.ts
@@ -3,14 +3,13 @@ import chalk from "chalk";
 import { describe, expect, it, vi } from "vitest";
 import { z } from "zod";
 
-import { BaseOptionsFor } from "../types/bases.js";
 import { createBase } from "./createBase.js";
 
-const mockInferPreset = vi.fn();
+const mockInferExistingBlocks = vi.fn();
 
-vi.mock("./inferPreset.js", () => ({
-	get inferPreset() {
-		return mockInferPreset;
+vi.mock("./inferExistingBlocks.js", () => ({
+	get inferExistingBlocks() {
+		return mockInferExistingBlocks;
 	},
 }));
 
@@ -22,6 +21,7 @@ const preset = base.createPreset({
 	about: { name: "Example" },
 	blocks: [],
 });
+
 const mockLog = vi.fn();
 
 const mockOptions = { name: "Test Name" };
@@ -89,12 +89,12 @@ describe("createStratumTemplate", () => {
 			]);
 		});
 
-		describe("--preset", () => {
+		describe("inference", () => {
 			const templateWithPreset = base.createStratumTemplate({
 				presets: [preset],
 			});
 
-			it("does not call inferPreset when context.files is not provided", async () => {
+			it("does not call inferExistingBlocks when context.files is not provided", async () => {
 				const lazyOptions = templateWithPreset.prepare({
 					log: mockLog,
 					options: mockOptions,
@@ -103,35 +103,46 @@ describe("createStratumTemplate", () => {
 
 				const options = await allPropertiesLazy(lazyOptions);
 
-				expect(mockInferPreset).not.toHaveBeenCalled();
+				expect(mockInferExistingBlocks).not.toHaveBeenCalled();
 				expect(options).toEqual({});
 			});
 
-			it("does not call inferPreset when a preset option is manually provided", async () => {
-				const preset = "example";
+			it("does not display a log when inferExistingBlocks returns no blocks or preset", async () => {
+				mockInferExistingBlocks.mockReturnValueOnce({
+					blocks: [],
+					preset: undefined,
+				});
+
 				const lazyOptions = templateWithPreset.prepare({
+					files: {
+						"README.md": "...",
+					},
 					log: mockLog,
-					// TODO: why is this type assertion necessary?
-					// https://github.com/JoshuaKGoldberg/bingo/issues/287
-					options: { ...mockOptions, preset } as BaseOptionsFor<typeof base>,
+					options: mockOptions,
 					take: vi.fn(),
 				});
 
 				const options = await allPropertiesLazy(lazyOptions);
 
-				expect(mockInferPreset).not.toHaveBeenCalled();
-				expect(options).toEqual({ preset });
+				expect(mockLog).not.toHaveBeenCalled();
+				expect(options).toEqual({});
 			});
 
-			it("does not display a log when inferPreset returns undefined", async () => {
-				mockInferPreset.mockReturnValueOnce(undefined);
+			it("does not display a log when inferExistingBlocks infers a block that was explicitly excluded", async () => {
+				mockInferExistingBlocks.mockReturnValueOnce({
+					blocks: [],
+					preset: undefined,
+				});
 
 				const lazyOptions = templateWithPreset.prepare({
 					files: {
 						"README.md": "...",
 					},
 					log: mockLog,
-					options: mockOptions,
+					options: {
+						...mockOptions,
+						"exclude-a": true,
+					} as typeof mockOptions,
 					take: vi.fn(),
 				});
 
@@ -141,9 +152,73 @@ describe("createStratumTemplate", () => {
 				expect(options).toEqual({});
 			});
 
-			it("displays a log when inferPreset returns a preset", async () => {
+			it("displays a log when inferExistingBlocks infers one block", async () => {
+				const blockA = base.createBlock({
+					about: { name: "A" },
+					produce: vi.fn(),
+				});
+				mockInferExistingBlocks.mockReturnValueOnce({
+					blocks: [blockA],
+					preset: undefined,
+				});
+
+				const lazyOptions = templateWithPreset.prepare({
+					files: {
+						"README.md": "...",
+					},
+					log: mockLog,
+					options: mockOptions,
+					take: vi.fn(),
+				});
+
+				const options = await allPropertiesLazy(lazyOptions);
+
+				expect(mockLog).toHaveBeenCalledWith(
+					`Detected ${chalk.blue(`--add-a`)} from existing files on disk.`,
+				);
+				expect(options).toEqual({ "add-a": true });
+			});
+
+			it("displays a log when inferExistingBlocks infers two blocks", async () => {
+				const blockA = base.createBlock({
+					about: { name: "A" },
+					produce: vi.fn(),
+				});
+				const blockB = base.createBlock({
+					about: { name: "B" },
+					produce: vi.fn(),
+				});
+				mockInferExistingBlocks.mockReturnValueOnce({
+					blocks: [blockA, blockB],
+					preset: undefined,
+				});
+
+				const lazyOptions = templateWithPreset.prepare({
+					files: {
+						"README.md": "...",
+					},
+					log: mockLog,
+					options: mockOptions,
+					take: vi.fn(),
+				});
+
+				const options = await allPropertiesLazy(lazyOptions);
+
+				expect(mockLog).toHaveBeenCalledWith(
+					`Detected ${chalk.blue(`--add-a`)} ${chalk.blue(`--add-b`)} from existing files on disk.`,
+				);
+				expect(options).toEqual({
+					"add-a": true,
+					"add-b": true,
+				});
+			});
+
+			it("displays a log when inferExistingBlocks infers a preset", async () => {
 				const presetName = "example";
-				mockInferPreset.mockReturnValueOnce(presetName);
+				mockInferExistingBlocks.mockReturnValueOnce({
+					blocks: [],
+					preset: presetName,
+				});
 
 				const lazyOptions = templateWithPreset.prepare({
 					files: {
diff --git a/packages/bingo-stratum/src/creators/createStratumTemplate.ts b/packages/bingo-stratum/src/creators/createStratumTemplate.ts
index 76d15b9..f866d72 100644
--- a/packages/bingo-stratum/src/creators/createStratumTemplate.ts
+++ b/packages/bingo-stratum/src/creators/createStratumTemplate.ts
@@ -14,7 +14,8 @@ import {
 } from "../types/templates.js";
 import { createBlockRefinementOption } from "../utils/createBlockRefinementOption.js";
 import { slugifyName } from "../utils/slugifyName.js";
-import { inferPreset } from "./inferPreset.js";
+import { inferExistingBlocks } from "./inferExistingBlocks.js";
+import { isBlockWithName } from "./utils.js";
 
 export function createStratumTemplate<OptionsShape extends AnyShape>(
 	base: Base<OptionsShape>,
@@ -92,29 +93,50 @@ export function createStratumTemplate<OptionsShape extends AnyShape>(
 			),
 		},
 		prepare(context) {
-			// TODO: Why are these type assertions necessary?
-			// https://github.com/JoshuaKGoldberg/bingo/issues/287
-			return {
-				preset: () => {
-					if (context.options.preset) {
-						return context.options.preset as string;
-					}
+			const fromBase =
+				base.prepare?.(context) ??
+				// TODO: Why is this type assertion necessary?
+				// https://github.com/JoshuaKGoldberg/bingo/issues/287
+				({} as LazyOptionalOptions<Partial<Options>>);
 
-					// TODO: It would be better to run the base.prepare first to generate option defaults.
-					// https://github.com/JoshuaKGoldberg/bingo/issues/289
-					const preset =
-						context.files && inferPreset(context, templateDefinition.presets);
+			// TODO: It would be better to run the base.prepare first to generate option defaults.
+			// https://github.com/JoshuaKGoldberg/bingo/issues/289
+			const existing = context.files && inferExistingBlocks(context, template);
 
-					if (preset) {
+			if (!existing) {
+				return fromBase;
+			}
+
+			// Enable --add-* options for any inferred existing Block not matched with an explicit --exclude-*
+			const blockAdds = (existing.blocks ?? [])
+				.filter(isBlockWithName)
+				.filter(
+					(block) =>
+						!context.options[
+							createBlockRefinementOption("exclude", block.about.name)
+						],
+				)
+				.map((block) => createBlockRefinementOption("add", block.about.name));
+
+			if (blockAdds.length) {
+				context.log(
+					`Detected ${blockAdds.map((add) => `--${chalk.blue(add)}`).join(" ")} from existing files on disk.`,
+				);
+			}
+
+			return {
+				...fromBase,
+				...Object.fromEntries(blockAdds.map((add) => [add, true])),
+				preset: () => {
+					if (existing.preset) {
 						context.log(
-							`Detected ${chalk.blue(`--preset ${preset}`)} from existing files on disk.`,
+							`Detected ${chalk.blue(`--preset ${existing.preset}`)} from existing files on disk.`,
 						);
 					}
 
-					return preset;
+					return existing.preset;
 				},
-				...(base.prepare?.(context) ?? {}),
-			} as LazyOptionalOptions<Partial<Options>>;
+			};
 		},
 		produce(context) {
 			return produceStratumTemplate(
diff --git a/packages/bingo-stratum/src/creators/inferExistingBlocks.test.ts b/packages/bingo-stratum/src/creators/inferExistingBlocks.test.ts
new file mode 100644
index 0000000..9a558ae
--- /dev/null
+++ b/packages/bingo-stratum/src/creators/inferExistingBlocks.test.ts
@@ -0,0 +1,222 @@
+import { describe, expect, it } from "vitest";
+import { z } from "zod";
+
+import { createBase } from "./createBase.js";
+import { inferExistingBlocks } from "./inferExistingBlocks.js";
+
+const base = createBase({
+	options: { name: z.string() },
+});
+
+const blockA = base.createBlock({
+	about: { name: "A" },
+	produce() {
+		return {
+			files: {
+				a: "...",
+			},
+		};
+	},
+});
+
+const blockB = base.createBlock({
+	about: { name: "B" },
+	produce() {
+		return {
+			files: {
+				b: "...",
+			},
+		};
+	},
+});
+
+const blockAB = base.createBlock({
+	produce() {
+		return {
+			files: {
+				a: "...",
+				b: "...",
+			},
+		};
+	},
+});
+
+describe("inferPreset", () => {
+	describe("blocks", () => {
+		it("returns no blocks when no blocks match and a preset is inferred", () => {
+			const presetAB = base.createPreset({
+				about: { name: "AB" },
+				blocks: [blockA, blockB],
+			});
+
+			const context = {
+				files: {
+					b: "...",
+				},
+				options: {
+					name: "...",
+				},
+			};
+
+			const template = base.createStratumTemplate({
+				presets: [presetAB],
+			});
+
+			const actual = inferExistingBlocks(context, template);
+
+			expect(actual.blocks).toEqual([]);
+		});
+
+		it("returns no blocks when a block only partially matches and a preset is inferred", () => {
+			const presetAB = base.createPreset({
+				about: { name: "AB" },
+				blocks: [blockAB],
+			});
+
+			const context = {
+				files: {
+					a: "...",
+				},
+				options: {
+					name: "...",
+				},
+			};
+
+			const template = base.createStratumTemplate({
+				presets: [presetAB],
+			});
+
+			const actual = inferExistingBlocks(context, template);
+
+			expect(actual.blocks).toEqual([]);
+		});
+
+		it("returns no blocks when one block in the inferred preset matches", () => {
+			const presetA = base.createPreset({
+				about: { name: "A" },
+				blocks: [blockA],
+			});
+
+			const context = {
+				files: {
+					a: "...",
+				},
+				options: {
+					name: "...",
+				},
+			};
+
+			const template = base.createStratumTemplate({
+				blocks: [blockB],
+				presets: [presetA],
+			});
+
+			const actual = inferExistingBlocks(context, template);
+
+			expect(actual.blocks).toEqual([]);
+		});
+
+		it("returns only inferred blocks not in the preset when blocks in and out of the inferred preset match", () => {
+			const presetA = base.createPreset({
+				about: { name: "A" },
+				blocks: [blockA],
+			});
+
+			const context = {
+				files: {
+					a: "...",
+					b: "...",
+				},
+				options: {
+					name: "...",
+				},
+			};
+
+			const template = base.createStratumTemplate({
+				blocks: [blockB],
+				presets: [presetA],
+			});
+
+			const actual = inferExistingBlocks(context, template);
+
+			expect(actual.blocks).toEqual([blockB]);
+		});
+	});
+
+	describe("preset", () => {
+		const files = {
+			a: "...",
+			b: "...",
+			c: "...",
+			d: "...",
+			e: "...",
+		};
+
+		const context = {
+			files,
+			options: {
+				name: "...",
+			},
+		};
+
+		const presetErrors = base.createPreset({
+			about: { name: "Errors" },
+			blocks: [
+				base.createBlock({
+					produce() {
+						throw new Error("Oh no!");
+					},
+				}),
+			],
+		});
+
+		const presetLowPercentageA = base.createPreset({
+			about: { name: "Low Percentage A" },
+			blocks: [blockA],
+		});
+
+		const presetLowPercentageB = base.createPreset({
+			about: { name: "Low Percentage B" },
+			blocks: [blockB],
+		});
+
+		const presetHighPercentage = base.createPreset({
+			about: { name: "High Percentage" },
+			blocks: [blockAB],
+		});
+
+		it("infers no preset without erroring when a block throws an error", () => {
+			const template = base.createStratumTemplate({
+				presets: [presetErrors],
+			});
+
+			const actual = inferExistingBlocks(context, template);
+
+			expect(actual.preset).toBeUndefined();
+		});
+
+		it("infers no preset when no preset has a 35% match", () => {
+			const template = base.createStratumTemplate({
+				presets: [presetLowPercentageA, presetLowPercentageB],
+			});
+
+			const actual = inferExistingBlocks(context, template);
+
+			expect(actual.preset).toBeUndefined();
+		});
+
+		it("infers the matching preset when a preset has a 35% match", () => {
+			const template = base.createStratumTemplate({
+				presets: [
+					presetLowPercentageA,
+					presetLowPercentageB,
+					presetHighPercentage,
+				],
+			});
+
+			const actual = inferExistingBlocks(context, template);
+
+			expect(actual.preset).toBe("high-percentage");
+		});
+	});
+});
diff --git a/packages/bingo-stratum/src/creators/inferPreset.test.ts b/packages/bingo-stratum/src/creators/inferPreset.test.ts
deleted file mode 100644
index 7723d72..0000000
--- a/packages/bingo-stratum/src/creators/inferPreset.test.ts
+++ /dev/null
@@ -1,108 +0,0 @@
-import { describe, expect, it } from "vitest";
-import { z } from "zod";
-
-import { createBase } from "./createBase.js";
-import { inferPreset } from "./inferPreset.js";
-
-const base = createBase({
-	options: { name: z.string() },
-});
-
-const files = {
-	a: "...",
-	b: "...",
-	c: "...",
-	d: "...",
-	e: "...",
-};
-
-const presetErrors = base.createPreset({
-	about: { name: "Errors" },
-	blocks: [
-		base.createBlock({
-			produce() {
-				throw new Error("Oh no!");
-			},
-		}),
-	],
-});
-
-const presetLowPercentageA = base.createPreset({
-	about: { name: "Low Percentage A" },
-	blocks: [
-		base.createBlock({
-			produce() {
-				return {
-					files: {
-						a: "...",
-					},
-				};
-			},
-		}),
-	],
-});
-
-const presetLowPercentageB = base.createPreset({
-	about: { name: "Low Percentage B" },
-	blocks: [
-		base.createBlock({
-			produce() {
-				return {
-					files: {
-						b: "...",
-					},
-				};
-			},
-		}),
-	],
-});
-
-const presetHighPercentage = base.createPreset({
-	about: { name: "High Percentage" },
-	blocks: [
-		base.createBlock({
-			produce() {
-				return {
-					files: {
-						a: "...",
-						b: "...",
-					},
-				};
-			},
-		}),
-	],
-});
-
-const context = {
-	files,
-	options: {
-		name: "...",
-	},
-};
-
-describe("inferPreset", () => {
-	it("returns undefined without erroring when a block throws an error", () => {
-		const actual = inferPreset(context, [presetErrors]);
-
-		expect(actual).toBe(undefined);
-	});
-
-	it("returns undefined when no preset has a 35% match", () => {
-		const actual = inferPreset(context, [
-			presetLowPercentageA,
-			presetLowPercentageB,
-		]);
-
-		expect(actual).toBe(undefined);
-	});
-
-	it("returns the matching preset when a preset has a 35% match", () => {
-		const actual = inferPreset(context, [
-			presetLowPercentageA,
-			presetLowPercentageB,
-			presetHighPercentage,
-		]);
-
-		expect(actual).toBe("high-percentage");
-	});
-});