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

Issue:
🐛 Bug: package.json files should deduplicate clear directory subsets
### Bug Report Checklist

- [x] I have tried restarting my IDE and the issue persists.
- [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.

### Expected

If `blockPackageJson` is being told to print:

```json
"files": [
	"LICENSE.md",
	"README.md",
	"lib/",
	"lib/bin/pjv.mjs"
]
```

...then it should know to remove `"lib/bin/pjv.mjs"`. It's clearly matched by the earlier `"lib/"`.

### Actual

https://github.com/JoshuaKGoldberg/package-json-validator/pull/178/files#r2024966892 shows that this is not the case right now.


### Additional Info


🎁 

Pull request description:
fix: handle subset file matches in blockPackageJson
<!-- 👋 Hi, thanks for sending a PR to create-typescript-app! 🎁
Please fill out all fields below and make sure each item is true and [x] checked.
Otherwise we may not be able to review your PR. -->

## PR Checklist

- [x] Addresses an existing open issue: fixes #2106
- [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/src/blocks/blockPackageJson.ts b/src/blocks/blockPackageJson.ts
index dd9b5e8..24458de 100644
--- a/src/blocks/blockPackageJson.ts
+++ b/src/blocks/blockPackageJson.ts
@@ -59,16 +59,13 @@ export const blockPackageJson = base.createBlock({
 							...(options.pnpm && {
 								packageManager: `pnpm@${options.pnpm}`,
 							}),
-							files: [
+							files: processFiles([
 								...collectBinFiles(options.bin),
 								...(addons.properties.files ?? []),
 								"package.json",
 								"README.md",
-							]
-								.filter(Boolean)
-								.sort(),
+							]),
 							keywords: options.keywords,
-							license: "MIT",
 							name: options.repository,
 							repository: {
 								type: "git",
@@ -112,6 +109,23 @@ function collectBinFiles(bin: Record<string, string> | string | undefined) {
 	return files.map((file) => file.replace(/^\.\//, ""));
 }
 
+function processFiles(files: string[]) {
+	// First sort so that shorter entries are first (e.g. "lib/")...
+	const sortedByLength = files
+		.filter(Boolean)
+		.sort((a, b) => a.length - b.length);
+
+	// ...then remove entries captured by earlier directories (e.g. "lib/index.js")
+	return sortedByLength
+		.filter(
+			(file, i) =>
+				!sortedByLength
+					.slice(0, i)
+					.some((earlier) => earlier.endsWith("/") && file.startsWith(earlier)),
+		)
+		.sort();
+}
+
 function removeRangePrefix(version: string) {
 	const raw = version.replaceAll(/[\^~><=]/gu, "").split(" ")[0];
 


Test patch:
diff --git a/src/blocks/blockPackageJson.test.ts b/src/blocks/blockPackageJson.test.ts
index 81c7cf1..3d8c5cb 100644
--- a/src/blocks/blockPackageJson.test.ts
+++ b/src/blocks/blockPackageJson.test.ts
@@ -17,7 +17,7 @@ describe("blockPackageJson", () => {
 		expect(creation).toMatchInlineSnapshot(`
 			{
 			  "files": {
-			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"license":"MIT","author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"engines":{"node":">=20.12.0"}}",
+			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"engines":{"node":">=20.12.0"}}",
 			  },
 			  "scripts": [
 			    {
@@ -50,7 +50,7 @@ describe("blockPackageJson", () => {
 			    },
 			  ],
 			  "files": {
-			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"license":"MIT","author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"engines":{"node":">=20.12.0"}}",
+			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"engines":{"node":">=20.12.0"}}",
 			  },
 			  "scripts": [
 			    {
@@ -81,7 +81,7 @@ describe("blockPackageJson", () => {
 		expect(creation).toMatchInlineSnapshot(`
 			{
 			  "files": {
-			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"license":"MIT","author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"dependencies":{"is-odd":"1.2.3"},"engines":{"node":">=20.12.0"},"other":true}",
+			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"dependencies":{"is-odd":"1.2.3"},"engines":{"node":">=20.12.0"},"other":true}",
 			  },
 			  "scripts": [
 			    {
@@ -116,7 +116,7 @@ describe("blockPackageJson", () => {
 		expect(creation).toMatchInlineSnapshot(`
 			{
 			  "files": {
-			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"license":"MIT","author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"dependencies":{"is-odd":"1.2.3"},"devDependencies":{"is-even":"4.5.6"},"engines":{"node":">=20.12.0"},"other":true}",
+			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"dependencies":{"is-odd":"1.2.3"},"devDependencies":{"is-even":"4.5.6"},"engines":{"node":">=20.12.0"},"other":true}",
 			  },
 			  "scripts": [
 			    {
@@ -131,6 +131,33 @@ describe("blockPackageJson", () => {
 		`);
 	});
 
+	test("with addons adding overlapping files", () => {
+		const creation = testBlock(blockPackageJson, {
+			addons: {
+				properties: {
+					files: ["README.md", "LICENSE.md", "lib/", "lib/bin/file.js"],
+				},
+			},
+			options,
+		});
+
+		expect(creation).toMatchInlineSnapshot(`
+			{
+			  "files": {
+			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"author":{"email":"npm@email.com"},"type":"module","files":["LICENSE.md","README.md","lib/","package.json"],"engines":{"node":">=20.12.0"}}",
+			  },
+			  "scripts": [
+			    {
+			      "commands": [
+			        "pnpm install --no-frozen-lockfile",
+			      ],
+			      "phase": 1,
+			    },
+			  ],
+			}
+		`);
+	});
+
 	test("with keywords", () => {
 		const creation = testBlock(blockPackageJson, {
 			options: {
@@ -142,7 +169,7 @@ describe("blockPackageJson", () => {
 		expect(creation).toMatchInlineSnapshot(`
 			{
 			  "files": {
-			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","keywords":["abc","def ghi"],"repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"license":"MIT","author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"engines":{"node":">=20.12.0"}}",
+			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","keywords":["abc","def ghi"],"repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"engines":{"node":">=20.12.0"}}",
 			  },
 			  "scripts": [
 			    {
@@ -170,7 +197,7 @@ describe("blockPackageJson", () => {
 		expect(creation).toMatchInlineSnapshot(`
 			{
 			  "files": {
-			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"license":"MIT","author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"packageManager":"pnpm@10.4.0","engines":{"node":">=22.0.0"}}",
+			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"packageManager":"pnpm@10.4.0","engines":{"node":">=22.0.0"}}",
 			  },
 			  "scripts": [
 			    {
@@ -195,7 +222,7 @@ describe("blockPackageJson", () => {
 		expect(creation).toMatchInlineSnapshot(`
 			{
 			  "files": {
-			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"license":"MIT","author":{"email":"npm@email.com"},"type":"module","bin":"bin/index.js","files":["README.md","bin/index.js","package.json"],"engines":{"node":">=20.12.0"}}",
+			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"author":{"email":"npm@email.com"},"type":"module","bin":"bin/index.js","files":["README.md","bin/index.js","package.json"],"engines":{"node":">=20.12.0"}}",
 			  },
 			  "scripts": [
 			    {
@@ -223,7 +250,7 @@ describe("blockPackageJson", () => {
 		expect(creation).toMatchInlineSnapshot(`
 			{
 			  "files": {
-			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"license":"MIT","author":{"email":"npm@email.com"},"type":"module","bin":{"absolute":"bin/absolute.js","relative":"./bin/relative.js"},"files":["README.md","bin/absolute.js","bin/relative.js","package.json"],"engines":{"node":">=20.12.0"}}",
+			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"author":{"email":"npm@email.com"},"type":"module","bin":{"absolute":"bin/absolute.js","relative":"./bin/relative.js"},"files":["README.md","bin/absolute.js","bin/relative.js","package.json"],"engines":{"node":">=20.12.0"}}",
 			  },
 			  "scripts": [
 			    {
@@ -258,7 +285,7 @@ describe("blockPackageJson", () => {
 		expect(creation).toMatchInlineSnapshot(`
 			{
 			  "files": {
-			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"license":"MIT","author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"peerDependencies":{"@types/estree":">=1","eslint":">=8"},"peerDependenciesMeta":{"@types/estree":{"optional":true}},"engines":{"node":">=20.12.0"}}",
+			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"peerDependencies":{"@types/estree":">=1","eslint":">=8"},"peerDependenciesMeta":{"@types/estree":{"optional":true}},"engines":{"node":">=20.12.0"}}",
 			  },
 			  "scripts": [
 			    {
@@ -281,7 +308,7 @@ describe("blockPackageJson", () => {
 		expect(creation).toMatchInlineSnapshot(`
 			{
 			  "files": {
-			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"license":"MIT","author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"engines":{"node":">=20.12.0"}}",
+			    "package.json": "{"name":"test-repository","version":"0.0.0","description":"A very very very very very very very very very very very very very very very very long HTML-ish description ending with an emoji. 🧵","repository":{"type":"git","url":"git+https://github.com/test-owner/test-repository.git"},"author":{"email":"npm@email.com"},"type":"module","files":["README.md","package.json"],"engines":{"node":">=20.12.0"}}",
 			  },
 			  "scripts": [
 			    {