Repository: JoshuaKGoldberg/template-typescript-node-package (ts)

Issue:
🚀 Feature: Setup script should rename known existing labels
### 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/template-typescript-node-package/issues?q=is%3Aissue) and found none that matched my issue.

### Overview

This repo's setup script fills in a repository's labels with an assortment of known good labels, with preset names, descriptions, and colors.

Right now, it'll delete any pre-existing label that already exists in the repository:

https://github.com/JoshuaKGoldberg/template-typescript-node-package/blob/d95fdf1e7dc9cd0646d28ae6095c1e463a8d6d11/script/setup.js#L539-L543

...but if the repo has been using labels already, that can be a little annoying. Especially once the hydration and setup scripts share logic (#451). 

How about, for a known set of labels, we check if they already exist in the repo? If they do, we can rename them to this template's preferred label?

A few examples of labels that come with repositories by default or commonly exist in repositories:

| Original        | Template              |
| --------------- | --------------------- |
| `bug`           | `type: bug`           |
| `documentation` | `area: documentation` |
| `duplicate`     | `status: duplicate`   |
| `enhancement`   | `type: feature`       |


### Additional Info

Labels are stored in this `labels.json` file: https://github.com/JoshuaKGoldberg/template-typescript-node-package/blob/d95fdf1e7dc9cd0646d28ae6095c1e463a8d6d11/script/labels.json

Testing the setup script is documented in _`.github/DEVELOPMENT.md`_: https://github.com/JoshuaKGoldberg/template-typescript-node-package/blob/d95fdf1e7dc9cd0646d28ae6095c1e463a8d6d11/.github/DEVELOPMENT.md#the-setup-script

I bet some of these label aliases can be deduced automatically from our labels in `labels.json`. For example, removing the (`area: `, `type: `, etc.) from a template label produces a likely alias/original.

Pull request:
feat: rename existing aliased labels
## PR Checklist

- [x] Addresses an existing open issue: fixes #455
- [x] That issue was marked as [`status: accepting prs`](https://github.com/JoshuaKGoldberg/template-typescript-node-package/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22)
- [x] Steps in [CONTRIBUTING.md](https://github.com/JoshuaKGoldberg/template-typescript-node-package/blob/main/.github/CONTRIBUTING.md) were taken

## Overview

Adds an `getExistingEquivalentLabel` function to check for common label aliases.

See the default labels here: https://docs.github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels

Patch:
diff --git a/src/setup/setupWithInformation.ts b/src/setup/setupWithInformation.ts
index 721a811..3ea7151 100644
--- a/src/setup/setupWithInformation.ts
+++ b/src/setup/setupWithInformation.ts
@@ -8,8 +8,8 @@ import { InputValuesAndOctokit } from "../shared/inputs.js";
 import { addOwnerAsAllContributor } from "./settings/addOwnerAsAllContributor.js";
 import { clearChangelog } from "./steps/clearChangelog.js";
 import { hydrateBranchProtectionSettings } from "./steps/hydrateBranchProtectionSettings.js";
-import { hydrateRepositoryLabels } from "./steps/hydrateRepositoryLabels.js";
 import { hydrateRepositorySettings } from "./steps/hydrateRepositorySettings.js";
+import { hydrateRepositoryLabels } from "./steps/labels/hydrateRepositoryLabels.js";
 import { removeSetupScripts } from "./steps/removeSetupScripts.js";
 import { resetGitTags } from "./steps/resetGitTags.js";
 import { uninstallPackages } from "./steps/uninstallPackages.js";
diff --git a/src/setup/steps/labels/getExistingEquivalentLabel.ts b/src/setup/steps/labels/getExistingEquivalentLabel.ts
new file mode 100644
index 0000000..e22336b
--- /dev/null
+++ b/src/setup/steps/labels/getExistingEquivalentLabel.ts
@@ -0,0 +1,16 @@
+const aliases = new Map([
+	["enhancement", "type: feature"],
+	["help wanted", "status: accepting prs"],
+]);
+
+export function getExistingEquivalentLabel(
+	existingLabels: string[],
+	outcomeLabel: string
+) {
+	const outcomeTrimmed = outcomeLabel.replace(/\w+: (\w+)/, "$1");
+
+	return existingLabels.find(
+		(existingLabel) =>
+			aliases.has(existingLabel) || existingLabel === outcomeTrimmed
+	);
+}
diff --git a/src/setup/steps/hydrateRepositoryLabels.ts b/src/setup/steps/labels/hydrateRepositoryLabels.ts
similarity index 63%
rename from src/setup/steps/hydrateRepositoryLabels.ts
rename to src/setup/steps/labels/hydrateRepositoryLabels.ts
index 7ed4d1d..fa6be25 100644
--- a/src/setup/steps/hydrateRepositoryLabels.ts
+++ b/src/setup/steps/labels/hydrateRepositoryLabels.ts
@@ -1,6 +1,7 @@
 import { $ } from "execa";
 
-import { readFileAsJSON } from "../readFileAsJSON.js";
+import { readFileAsJSON } from "../../readFileAsJSON.js";
+import { getExistingEquivalentLabel } from "./getExistingEquivalentLabel.js";
 
 interface GhLabelData {
 	name: string;
@@ -26,10 +27,15 @@ export async function hydrateRepositoryLabels() {
 	)) as FileLabelData[];
 
 	for (const outcome of outcomeLabels) {
-		const action = existingLabels.some((existing) => existing === outcome.name)
-			? "edit"
-			: "create";
-		await $`gh label ${action} ${outcome.name} --color ${outcome.color} --description ${outcome.description}`;
+		const existingEquivalent = getExistingEquivalentLabel(
+			existingLabels,
+			outcome.name
+		);
+		const [action, label] = existingEquivalent
+			? ["edit", existingEquivalent]
+			: ["create", outcome.name];
+
+		await $`gh label ${action} ${label} --color ${outcome.color} --description ${outcome.description}`;
 	}
 
 	const allowedLabels = new Set(outcomeLabels.map(getLabelName));


Test patch:
diff --git a/src/setup/steps/labels/getExistingEquivalentLabel.test.ts b/src/setup/steps/labels/getExistingEquivalentLabel.test.ts
new file mode 100644
index 0000000..bb63ab0
--- /dev/null
+++ b/src/setup/steps/labels/getExistingEquivalentLabel.test.ts
@@ -0,0 +1,41 @@
+import { describe, expect, it } from "vitest";
+
+import { getExistingEquivalentLabel } from "./getExistingEquivalentLabel.js";
+
+describe("getExistingEquivalentLabel", () => {
+	it("returns undefined when there are no existing labels", () => {
+		const actual = getExistingEquivalentLabel([], "abc");
+
+		expect(actual).toBe(undefined);
+	});
+
+	it("returns undefined when no existing label matches", () => {
+		const actual = getExistingEquivalentLabel(["abc"], "def");
+
+		expect(actual).toBe(undefined);
+	});
+
+	it("returns the existing label when it matches by name", () => {
+		const actual = getExistingEquivalentLabel(["def", "abc", "ghi"], "abc");
+
+		expect(actual).toBe("abc");
+	});
+
+	it("returns the existing label when it matches excluding prefix", () => {
+		const actual = getExistingEquivalentLabel(
+			["abc: def", "abc", "ghi"],
+			"type: abc"
+		);
+
+		expect(actual).toBe("abc");
+	});
+
+	it("returns the existing label when it matches an alias", () => {
+		const actual = getExistingEquivalentLabel(
+			["abc: def", "enhancement", "ghi"],
+			"type: feature"
+		);
+
+		expect(actual).toBe("enhancement");
+	});
+});