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

Issue:
🛠 Tooling: Use an ESLint object literal keys sort rule
### 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

I prefer sorting things 😄 and now you're all going to suffer with me!

```diff
const value = {
-	def, abc,
+	abc, def,
};
```

But, I think it's only reasonable to sort object literal keys when:
* The rule will autofix them for you
* The rule doesn't change any logic

### Additional Info

I'll take this on to make sure we get a good one.

* https://eslint.org/docs/latest/rules/sort-keys doesn't mention an auto-fixer
* https://www.npmjs.com/package/eslint-plugin-sort-keys hasn't been updated in 2 years
* https://github.com/leo-buneev/eslint-plugin-sort-keys-fix is also a thing?

Pull request description:
feat: use eslint-plugin-perfectionist
## PR Checklist

- [x] Addresses an existing open issue: fixes #396
- [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

I am a perfectionist. It's a match made in heaven!

Swaps from the much more targeted `eslint-plugin-simple-import-sort` and `eslint-plugin-typescript-sort-keys` to `eslint-plugin-perfectionist`.

Patch:
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 0cf295c..0cb9013 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -17,6 +17,7 @@ module.exports = {
 	extends: [
 		"eslint:recommended",
 		"plugin:eslint-comments/recommended",
+		"plugin:perfectionist/recommended-natural",
 		"plugin:regexp/recommended",
 		"prettier",
 	],
@@ -30,7 +31,6 @@ module.exports = {
 			extends: [
 				"plugin:jsdoc/recommended-typescript-error",
 				"plugin:@typescript-eslint/recommended",
-				"plugin:typescript-sort-keys/recommended",
 			],
 			files: ["**/*.ts"],
 			parser: "@typescript-eslint/parser",
@@ -116,9 +116,8 @@ module.exports = {
 		"import",
 		"jsdoc",
 		"no-only-tests",
+		"perfectionist",
 		"regexp",
-		"simple-import-sort",
-		"typescript-sort-keys",
 		"vitest",
 	],
 	root: true,
@@ -127,8 +126,6 @@ module.exports = {
 		"@typescript-eslint/no-unused-vars": ["error", { caughtErrors: "all" }],
 		"import/extensions": ["error", "ignorePackages"],
 		"no-only-tests/no-only-tests": "error",
-		"simple-import-sort/exports": "error",
-		"simple-import-sort/imports": "error",
 
 		// These on-by-default rules don't work well for this repo and we like them off.
 		"no-constant-condition": "off",
diff --git a/.prettierrc b/.prettierrc
index c3bd685..19bc819 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,9 +1,9 @@
 {
 	"$schema": "http://json.schemastore.org/prettierrc",
-	"plugins": ["prettier-plugin-curly", "prettier-plugin-packagejson"],
 	"overrides": [
 		{ "files": ".*rc", "options": { "parser": "json" } },
 		{ "files": ".nvmrc", "options": { "parser": "yaml" } }
 	],
+	"plugins": ["prettier-plugin-curly", "prettier-plugin-packagejson"],
 	"useTabs": true
 }
diff --git a/package.json b/package.json
index 41f191b..e1adbc0 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "template-typescript-node-package",
-	"version": "1.28.11",
+	"version": "1.28.12",
 	"description": "A quickstart-friendly TypeScript template with comprehensive formatting, linting, releases, testing, and other great tooling built-in. ✨",
 	"repository": {
 		"type": "git",
@@ -67,9 +67,8 @@
 		"eslint-plugin-jsonc": "^2.8.0",
 		"eslint-plugin-markdown": "^3.0.0",
 		"eslint-plugin-no-only-tests": "^3.1.0",
+		"eslint-plugin-perfectionist": "^1.1.2",
 		"eslint-plugin-regexp": "^1.15.0",
-		"eslint-plugin-simple-import-sort": "^10.0.0",
-		"eslint-plugin-typescript-sort-keys": "^2.3.0",
 		"eslint-plugin-vitest": "^0.2.2",
 		"eslint-plugin-yml": "^1.7.0",
 		"globby": "^13.1.4",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 205d3a0..705a90c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,4 +1,4 @@
-lockfileVersion: '6.1'
+lockfileVersion: '6.0'
 
 settings:
   autoInstallPeers: true
@@ -79,15 +79,12 @@ devDependencies:
   eslint-plugin-no-only-tests:
     specifier: ^3.1.0
     version: 3.1.0
+  eslint-plugin-perfectionist:
+    specifier: ^1.1.2
+    version: 1.1.2(eslint@8.40.0)(typescript@5.0.4)
   eslint-plugin-regexp:
     specifier: ^1.15.0
     version: 1.15.0(eslint@8.40.0)
-  eslint-plugin-simple-import-sort:
-    specifier: ^10.0.0
-    version: 10.0.0(eslint@8.40.0)
-  eslint-plugin-typescript-sort-keys:
-    specifier: ^2.3.0
-    version: 2.3.0(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@5.0.4)
   eslint-plugin-vitest:
     specifier: ^0.2.2
     version: 0.2.2(eslint@8.40.0)(typescript@5.0.4)
@@ -1031,7 +1028,7 @@ packages:
     dependencies:
       '@npmcli/name-from-folder': 2.0.0
       glob: 10.2.2
-      minimatch: 9.0.0
+      minimatch: 9.0.1
       read-package-json-fast: 3.0.2
     dev: true
 
@@ -1500,19 +1497,6 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/experimental-utils@5.48.0(eslint@8.40.0)(typescript@5.0.4):
-    resolution: {integrity: sha512-ehoJFf67UViwnYuz6JUneZ8qxgDk0qEWKiTLmpE8WpPEr15e2cSLtp0E6Zicx2DaYdwctUA0uLRTbLckxQpurg==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    peerDependencies:
-      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
-    dependencies:
-      '@typescript-eslint/utils': 5.48.0(eslint@8.40.0)(typescript@5.0.4)
-      eslint: 8.40.0
-    transitivePeerDependencies:
-      - supports-color
-      - typescript
-    dev: true
-
   /@typescript-eslint/parser@5.59.5(eslint@8.40.0)(typescript@5.0.4):
     resolution: {integrity: sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -1533,12 +1517,12 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/scope-manager@5.48.0:
-    resolution: {integrity: sha512-0AA4LviDtVtZqlyUQnZMVHydDATpD9SAX/RC5qh6cBd3xmyWvmXYF+WT1oOmxkeMnWDlUVTwdODeucUnjz3gow==}
+  /@typescript-eslint/scope-manager@5.59.11:
+    resolution: {integrity: sha512-dHFOsxoLFtrIcSj5h0QoBT/89hxQONwmn3FOQ0GOQcLOOXm+MIrS8zEAhs4tWl5MraxCY3ZJpaXQQdFMc2Tu+Q==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
-      '@typescript-eslint/types': 5.48.0
-      '@typescript-eslint/visitor-keys': 5.48.0
+      '@typescript-eslint/types': 5.59.11
+      '@typescript-eslint/visitor-keys': 5.59.11
     dev: true
 
   /@typescript-eslint/scope-manager@5.59.5:
@@ -1569,8 +1553,8 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/types@5.48.0:
-    resolution: {integrity: sha512-UTe67B0Ypius0fnEE518NB2N8gGutIlTojeTg4nt0GQvikReVkurqxd2LvYa9q9M5MQ6rtpNyWTBxdscw40Xhw==}
+  /@typescript-eslint/types@5.59.11:
+    resolution: {integrity: sha512-epoN6R6tkvBYSc+cllrz+c2sOFWkbisJZWkOE+y3xHtvYaOE6Wk6B8e114McRJwFRjGvYdJwLXQH5c9osME/AA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
@@ -1579,8 +1563,8 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
-  /@typescript-eslint/typescript-estree@5.48.0(typescript@5.0.4):
-    resolution: {integrity: sha512-7pjd94vvIjI1zTz6aq/5wwE/YrfIyEPLtGJmRfyNR9NYIW+rOvzzUv3Cmq2hRKpvt6e9vpvPUQ7puzX7VSmsEw==}
+  /@typescript-eslint/typescript-estree@5.59.11(typescript@5.0.4):
+    resolution: {integrity: sha512-YupOpot5hJO0maupJXixi6l5ETdrITxeo5eBOeuV7RSKgYdU3G5cxO49/9WRnJq9EMrB7AuTSLH/bqOsXi7wPA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       typescript: '*'
@@ -1588,8 +1572,8 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/types': 5.48.0
-      '@typescript-eslint/visitor-keys': 5.48.0
+      '@typescript-eslint/types': 5.59.11
+      '@typescript-eslint/visitor-keys': 5.59.11
       debug: 4.3.4
       globby: 11.1.0
       is-glob: 4.0.3
@@ -1621,20 +1605,20 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/utils@5.48.0(eslint@8.40.0)(typescript@5.0.4):
-    resolution: {integrity: sha512-x2jrMcPaMfsHRRIkL+x96++xdzvrdBCnYRd5QiW5Wgo1OB4kDYPbC1XjWP/TNqlfK93K/lUL92erq5zPLgFScQ==}
+  /@typescript-eslint/utils@5.59.11(eslint@8.40.0)(typescript@5.0.4):
+    resolution: {integrity: sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
     dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0)
       '@types/json-schema': 7.0.11
       '@types/semver': 7.3.13
-      '@typescript-eslint/scope-manager': 5.48.0
-      '@typescript-eslint/types': 5.48.0
-      '@typescript-eslint/typescript-estree': 5.48.0(typescript@5.0.4)
+      '@typescript-eslint/scope-manager': 5.59.11
+      '@typescript-eslint/types': 5.59.11
+      '@typescript-eslint/typescript-estree': 5.59.11(typescript@5.0.4)
       eslint: 8.40.0
       eslint-scope: 5.1.1
-      eslint-utils: 3.0.0(eslint@8.40.0)
       semver: 7.5.1
     transitivePeerDependencies:
       - supports-color
@@ -1661,11 +1645,11 @@ packages:
       - typescript
     dev: true
 
-  /@typescript-eslint/visitor-keys@5.48.0:
-    resolution: {integrity: sha512-5motVPz5EgxQ0bHjut3chzBkJ3Z3sheYVcSwS5BpHZpLqSptSmELNtGixmgj65+rIfhvtQTz5i9OP2vtzdDH7Q==}
+  /@typescript-eslint/visitor-keys@5.59.11:
+    resolution: {integrity: sha512-KGYniTGG3AMTuKF9QBD7EIrvufkB6O6uX3knP73xbKLMpH+QRPcgnCxjWXSHjMRuOxFLovljqQgQpR0c7GvjoA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
-      '@typescript-eslint/types': 5.48.0
+      '@typescript-eslint/types': 5.59.11
       eslint-visitor-keys: 3.4.1
     dev: true
 
@@ -3114,7 +3098,7 @@ packages:
     resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==}
     dependencies:
       debug: 3.2.7
-      is-core-module: 2.11.0
+      is-core-module: 2.12.1
       resolve: 1.22.1
     transitivePeerDependencies:
       - supports-color
@@ -3155,7 +3139,7 @@ packages:
       eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
       typescript: ^3.7.5 || ^4.0.0 || ^5.0.0
     dependencies:
-      '@typescript-eslint/utils': 5.59.5(eslint@8.40.0)(typescript@5.0.4)
+      '@typescript-eslint/utils': 5.59.11(eslint@8.40.0)(typescript@5.0.4)
       eslint: 8.40.0
       tslib: 2.5.0
       tsutils: 3.21.0(typescript@5.0.4)
@@ -3195,7 +3179,7 @@ packages:
       eslint-import-resolver-node: 0.3.7
       eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.59.5)(eslint-import-resolver-node@0.3.7)(eslint@8.40.0)
       has: 1.0.3
-      is-core-module: 2.11.0
+      is-core-module: 2.12.1
       is-glob: 4.0.3
       minimatch: 3.1.2
       object.values: 1.1.6
@@ -3256,6 +3240,23 @@ packages:
     engines: {node: '>=5.0.0'}
     dev: true
 
+  /eslint-plugin-perfectionist@1.1.2(eslint@8.40.0)(typescript@5.0.4):
+    resolution: {integrity: sha512-JG/rS7VnH6hXjWAGO83xCHTK+tgGRJyaDk5wGv2qTpnS8Iw+TpzSj/gQSJ2WAPaQJ9hEMN4OvrrUO1mzikUNEg==}
+    peerDependencies:
+      eslint: '>=8.0.0'
+    dependencies:
+      '@typescript-eslint/types': 5.59.11
+      '@typescript-eslint/utils': 5.59.11(eslint@8.40.0)(typescript@5.0.4)
+      eslint: 8.40.0
+      is-core-module: 2.12.1
+      json5: 2.2.3
+      minimatch: 9.0.1
+      natural-compare-lite: 1.4.0
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+    dev: true
+
   /eslint-plugin-regexp@1.15.0(eslint@8.40.0):
     resolution: {integrity: sha512-YEtQPfdudafU7RBIFci81R/Q1yErm0mVh3BkGnXD2Dk8DLwTFdc2ITYH1wCnHKim2gnHfPFgrkh+b2ozyyU7ag==}
     engines: {node: ^12 || >=14}
@@ -3273,39 +3274,13 @@ packages:
       scslre: 0.2.0
     dev: true
 
-  /eslint-plugin-simple-import-sort@10.0.0(eslint@8.40.0):
-    resolution: {integrity: sha512-AeTvO9UCMSNzIHRkg8S6c3RPy5YEwKWSQPx3DYghLedo2ZQxowPFLGDN1AZ2evfg6r6mjBSZSLxLFsWSu3acsw==}
-    peerDependencies:
-      eslint: '>=5.0.0'
-    dependencies:
-      eslint: 8.40.0
-    dev: true
-
-  /eslint-plugin-typescript-sort-keys@2.3.0(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@5.0.4):
-    resolution: {integrity: sha512-3LAcYulo5gNYiPWee+TksITfvWeBuBjGgcSLTacPESFVKEoy8laOQuZvJlSCwTBHT2SCGIxr3bJ56zuux+3MCQ==}
-    engines: {node: 12 || >= 13.9}
-    peerDependencies:
-      '@typescript-eslint/parser': ^1 || ^2 || ^3 || ^4 || ^5
-      eslint: ^5 || ^6 || ^7 || ^8
-      typescript: ^3 || ^4 || ^5
-    dependencies:
-      '@typescript-eslint/experimental-utils': 5.48.0(eslint@8.40.0)(typescript@5.0.4)
-      '@typescript-eslint/parser': 5.59.5(eslint@8.40.0)(typescript@5.0.4)
-      eslint: 8.40.0
-      json-schema: 0.4.0
-      natural-compare-lite: 1.4.0
-      typescript: 5.0.4
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
   /eslint-plugin-vitest@0.2.2(eslint@8.40.0)(typescript@5.0.4):
     resolution: {integrity: sha512-f6E90twgtAAuIE8P2YOy4f1RBVbZ0NB9yoyeHSpwYL6xiNxT4fmq6UHZoR4tHN+NMkbtAoZtiTr1EmqSpB2C4A==}
     engines: {node: 14.x || >= 16}
     peerDependencies:
       eslint: '>=8.0.0'
     dependencies:
-      '@typescript-eslint/utils': 5.59.5(eslint@8.40.0)(typescript@5.0.4)
+      '@typescript-eslint/utils': 5.59.11(eslint@8.40.0)(typescript@5.0.4)
       eslint: 8.40.0
     transitivePeerDependencies:
       - supports-color
@@ -3343,21 +3318,6 @@ packages:
       estraverse: 5.3.0
     dev: true
 
-  /eslint-utils@3.0.0(eslint@8.40.0):
-    resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
-    engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
-    peerDependencies:
-      eslint: '>=5'
-    dependencies:
-      eslint: 8.40.0
-      eslint-visitor-keys: 2.1.0
-    dev: true
-
-  /eslint-visitor-keys@2.1.0:
-    resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==}
-    engines: {node: '>=10'}
-    dev: true
-
   /eslint-visitor-keys@3.4.1:
     resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3800,7 +3760,7 @@ packages:
     dependencies:
       foreground-child: 3.1.1
       jackspeak: 2.2.0
-      minimatch: 9.0.0
+      minimatch: 9.0.1
       minipass: 5.0.0
       path-scurry: 1.7.0
     dev: true
@@ -4266,8 +4226,8 @@ packages:
       ci-info: 3.7.1
     dev: true
 
-  /is-core-module@2.11.0:
-    resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==}
+  /is-core-module@2.12.1:
+    resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==}
     dependencies:
       has: 1.0.3
     dev: true
@@ -4671,10 +4631,6 @@ packages:
     resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
     dev: true
 
-  /json-schema@0.4.0:
-    resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
-    dev: true
-
   /json-stable-stringify-without-jsonify@1.0.1:
     resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
     dev: true
@@ -5091,7 +5047,7 @@ packages:
       js-yaml: 4.1.0
       jsonc-parser: 3.2.0
       markdownlint: 0.28.2
-      minimatch: 9.0.0
+      minimatch: 9.0.1
       run-con: 1.2.11
     dev: true
 
@@ -5264,8 +5220,8 @@ packages:
       brace-expansion: 2.0.1
     dev: true
 
-  /minimatch@9.0.0:
-    resolution: {integrity: sha512-0jJj8AvgKqWN05mrwuqi8QYKx1WmYSUoKSxu5Qhs9prezTz10sxAHGNZe9J9cqIJzta8DWsleh2KaVaLl6Ru2w==}
+  /minimatch@9.0.1:
+    resolution: {integrity: sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==}
     engines: {node: '>=16 || 14 >=14.17'}
     dependencies:
       brace-expansion: 2.0.1
@@ -5385,7 +5341,7 @@ packages:
     engines: {node: '>=10'}
     dependencies:
       hosted-git-info: 4.1.0
-      is-core-module: 2.11.0
+      is-core-module: 2.12.1
       semver: 7.5.1
       validate-npm-package-license: 3.0.4
     dev: true
@@ -6188,7 +6144,7 @@ packages:
     resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==}
     hasBin: true
     dependencies:
-      is-core-module: 2.11.0
+      is-core-module: 2.12.1
       path-parse: 1.0.7
       supports-preserve-symlinks-flag: 1.0.0
     dev: true
diff --git a/src/hydrate/hydrate.ts b/src/hydrate/hydrate.ts
index be7e2c6..3d89092 100644
--- a/src/hydrate/hydrate.ts
+++ b/src/hydrate/hydrate.ts
@@ -22,8 +22,8 @@ export async function hydrate(args: string[]) {
 			"skip-install": { type: "boolean" },
 			"skip-setup": { type: "boolean" },
 		},
-		tokens: true,
 		strict: false,
+		tokens: true,
 	});
 
 	return await runOrRestore({
diff --git a/src/hydrate/steps/finalizeDependencies.ts b/src/hydrate/steps/finalizeDependencies.ts
index 9862b9d..925b844 100644
--- a/src/hydrate/steps/finalizeDependencies.ts
+++ b/src/hydrate/steps/finalizeDependencies.ts
@@ -20,9 +20,8 @@ export async function finalizeDependencies({
 		"eslint-plugin-jsdoc",
 		"eslint-plugin-jsonc",
 		"eslint-plugin-markdown",
+		"eslint-plugin-perfectionist",
 		"eslint-plugin-regexp",
-		"eslint-plugin-simple-import-sort",
-		"eslint-plugin-typescript-sort-keys",
 		"eslint-plugin-yml",
 		"husky",
 		"jsonc-eslint-parser",
diff --git a/src/hydrate/steps/writing/creation/dotGitHub/createWorkflowFile.ts b/src/hydrate/steps/writing/creation/dotGitHub/createWorkflowFile.ts
index 408fd87..0aed690 100644
--- a/src/hydrate/steps/writing/creation/dotGitHub/createWorkflowFile.ts
+++ b/src/hydrate/steps/writing/creation/dotGitHub/createWorkflowFile.ts
@@ -7,12 +7,12 @@ interface WorkflowFileConcurrency {
 
 interface WorkflowFileOn {
 	pull_request?:
-		| null
-		| string
 		| {
 				branches?: string | string[];
 				types?: string[];
-		  };
+		  }
+		| null
+		| string;
 	push?: {
 		branches: string[];
 	};
@@ -44,7 +44,7 @@ interface WorkflowFileOptionsBase {
 }
 
 interface WorkflowFileOptionsRuns extends WorkflowFileOptionsBase {
-	runs: (string | WorkflowFileStep)[];
+	runs: (WorkflowFileStep | string)[];
 }
 
 interface WorkflowFileOptionsSteps extends WorkflowFileOptionsBase {
diff --git a/src/hydrate/steps/writing/creation/formatters/formatYaml.ts b/src/hydrate/steps/writing/creation/formatters/formatYaml.ts
index 5285546..cbfd753 100644
--- a/src/hydrate/steps/writing/creation/formatters/formatYaml.ts
+++ b/src/hydrate/steps/writing/creation/formatters/formatYaml.ts
@@ -3,10 +3,6 @@ import jsYaml from "js-yaml";
 const options: jsYaml.DumpOptions = {
 	lineWidth: -1,
 	noCompatMode: true,
-	sortKeys: true,
-	styles: {
-		"!!null": "canonical",
-	},
 	// https://github.com/nodeca/js-yaml/pull/515
 	replacer(_, value: unknown) {
 		if (typeof value !== "string" || !value.includes("\n\t\t")) {
@@ -19,6 +15,10 @@ const options: jsYaml.DumpOptions = {
 
 			.replaceAll(/\n\t\t\t\t\t\t$/g, "");
 	},
+	sortKeys: true,
+	styles: {
+		"!!null": "canonical",
+	},
 };
 
 export function formatYaml(value: unknown) {
diff --git a/src/hydrate/steps/writing/creation/rootFiles.ts b/src/hydrate/steps/writing/creation/rootFiles.ts
index d04b78c..f195cc0 100644
--- a/src/hydrate/steps/writing/creation/rootFiles.ts
+++ b/src/hydrate/steps/writing/creation/rootFiles.ts
@@ -46,6 +46,7 @@ module.exports = {
 	extends: [
 		"eslint:recommended",
 		"plugin:eslint-comments/recommended",
+		"plugin:perfectionist/recommended-natural",
 		"plugin:regexp/recommended",
 		"prettier",
 	],
@@ -59,7 +60,6 @@ module.exports = {
 			extends: [
 				"plugin:jsdoc/recommended-typescript-error",
 				"plugin:@typescript-eslint/recommended",
-				"plugin:typescript-sort-keys/recommended",
 			],
 			files: ["**/*.ts"],
 			parser: "@typescript-eslint/parser",
@@ -139,8 +139,8 @@ module.exports = {
 		"deprecation",
 		"import",
 		"jsdoc",${values.unitTests ? `\n"no-only-tests",` : ""}
+		"perfectionist",
 		"regexp",
-		"simple-import-sort",
 		"typescript-sort-keys",${values.unitTests ? `\n"vitest",` : ""}
 	],
 	root: true,
@@ -150,8 +150,6 @@ module.exports = {
 		"import/extensions": ["error", "ignorePackages"],${
 			values.unitTests ? `\n"no-only-tests/no-only-tests": "error",` : ""
 		}
-		"simple-import-sort/exports": "error",
-		"simple-import-sort/imports": "error",
 
 		// These on-by-default rules don't work well for this repo and we like them off.
 		"no-constant-condition": "off",
@@ -201,7 +199,6 @@ module.exports = {
 		]),
 		".prettierrc": formatJson({
 			$schema: "http://json.schemastore.org/prettierrc",
-			plugins: ["prettier-plugin-curly", "prettier-plugin-packagejson"],
 			overrides: [
 				{
 					files: ".*rc",
@@ -212,6 +209,7 @@ module.exports = {
 					options: { parser: "yaml" },
 				},
 			],
+			plugins: ["prettier-plugin-curly", "prettier-plugin-packagejson"],
 			useTabs: true,
 		}),
 		".release-it.json": formatJson({
diff --git a/src/hydrate/steps/writing/creation/writePackageJson.ts b/src/hydrate/steps/writing/creation/writePackageJson.ts
index 4eba58c..5b3ea3e 100644
--- a/src/hydrate/steps/writing/creation/writePackageJson.ts
+++ b/src/hydrate/steps/writing/creation/writePackageJson.ts
@@ -24,24 +24,32 @@ export async function writePackageJson({
 		// To start, copy over all existing package fields (e.g. "dependencies")
 		...((await readFileAsJson("./package.json")) as object),
 
+		author: { email, name: author },
+		description,
+		engines: {
+			node: ">=18",
+		},
 		// Remove fields we know we don't want, such as old or redundant configs
 		eslintConfig: undefined,
-		husky: undefined,
-		prettierConfig: undefined,
-		types: undefined,
 
+		files: ["lib/", "package.json", "LICENSE.md", "README.md"],
+		husky: undefined,
+		license: "MIT",
+		"lint-staged": {
+			"*": "prettier --ignore-unknown --write",
+		},
+		main: "./lib/index.js",
 		// The rest of the fields are ones we know from our template
 		name: repository,
-		description,
+		packageManager: "pnpm@8.5.0",
+		prettierConfig: undefined,
+		publishConfig: {
+			provenance: true,
+		},
 		repository: {
 			type: "git",
 			url: `https://github.com/${owner}/${repository}`,
 		},
-		license: "MIT",
-		author: { email, name: author },
-		type: "module",
-		main: "./lib/index.js",
-		files: ["lib/", "package.json", "LICENSE.md", "README.md"],
 		scripts: {
 			build: "tsc",
 			format: 'prettier "**/*" --ignore-unknown',
@@ -59,15 +67,7 @@ export async function writePackageJson({
 			}),
 			...(unitTests && { test: "vitest" }),
 		},
-		"lint-staged": {
-			"*": "prettier --ignore-unknown --write",
-		},
-		packageManager: "pnpm@8.5.0",
-		engines: {
-			node: ">=18",
-		},
-		publishConfig: {
-			provenance: true,
-		},
+		type: "module",
+		types: undefined,
 	});
 }
diff --git a/src/hydrate/steps/writing/types.ts b/src/hydrate/steps/writing/types.ts
index 6ec8160..6b5449c 100644
--- a/src/hydrate/steps/writing/types.ts
+++ b/src/hydrate/steps/writing/types.ts
@@ -1,3 +1,3 @@
 export interface Structure {
-	[i: string]: string | Structure;
+	[i: string]: Structure | string;
 }
diff --git a/src/hydrate/values/getHydrationDefaults.ts b/src/hydrate/values/getHydrationDefaults.ts
index 691d30f..1268d28 100644
--- a/src/hydrate/values/getHydrationDefaults.ts
+++ b/src/hydrate/values/getHydrationDefaults.ts
@@ -5,7 +5,7 @@ import { readFileSafe } from "../readFileSafe.js";
 import { readFundingIfExists } from "./readFundingIfExists.js";
 
 interface PartialPackageData {
-	author?: string | { email: string; name: string };
+	author?: { email: string; name: string } | string;
 	description?: string;
 	email?: string;
 	name?: string;
@@ -37,7 +37,7 @@ export async function getHydrationDefaults() {
 				/origin\s+https:\/\/\S+\.\w+\/([^/]+)/
 			)?.[1],
 		releases: true,
-		unitTests: true,
 		title: existingReadme.match(/^(?:# |<h1\s+align="center">)(\S+)/)?.[1],
+		unitTests: true,
 	};
 }
diff --git a/src/hydrate/values/hydrationInputValues.ts b/src/hydrate/values/hydrationInputValues.ts
index 016a233..4a3743a 100644
--- a/src/hydrate/values/hydrationInputValues.ts
+++ b/src/hydrate/values/hydrationInputValues.ts
@@ -1,5 +1,5 @@
-import { InputValues } from "../../shared/inputs.js";
 import { PrefillPrompter } from "../../shared/PrefillPrompter.js";
+import { InputValues } from "../../shared/inputs.js";
 
 export async function augmentWithHydrationValues(values: InputValues) {
 	const prompter = new PrefillPrompter();
diff --git a/src/setup/settings/addOwnerAsAllContributor.ts b/src/setup/settings/addOwnerAsAllContributor.ts
index 05bc73f..ccb902a 100644
--- a/src/setup/settings/addOwnerAsAllContributor.ts
+++ b/src/setup/settings/addOwnerAsAllContributor.ts
@@ -1,7 +1,6 @@
-import fs from "node:fs/promises";
-
 import chalk from "chalk";
 import { $ } from "execa";
+import fs from "node:fs/promises";
 import prettier from "prettier";
 
 import { readFileAsJson } from "../../shared/readFileAsJson.js";
@@ -49,7 +48,7 @@ export async function addOwnerAsAllContributor(owner: string) {
 			JSON.stringify({
 				...existingContributors,
 				contributors: existingContributors.contributors
-					.filter(({ login }) => [user, "JoshuaKGoldberg"].includes(login))
+					.filter(({ login }) => ["JoshuaKGoldberg", user].includes(login))
 					.map((contributor) =>
 						contributor.login === "JoshuaKGoldberg"
 							? { ...contributor, contributions: ["tool"] }
diff --git a/src/setup/steps/clearChangelog.ts b/src/setup/steps/clearChangelog.ts
index a865229..aebeb7d 100644
--- a/src/setup/steps/clearChangelog.ts
+++ b/src/setup/steps/clearChangelog.ts
@@ -1,5 +1,4 @@
 import fs from "node:fs/promises";
-
 import prettier from "prettier";
 
 export async function clearChangelog() {
diff --git a/src/setup/steps/updateAllContributorsFile.ts b/src/setup/steps/updateAllContributorsFile.ts
index 8acbabb..fe31f13 100644
--- a/src/setup/steps/updateAllContributorsFile.ts
+++ b/src/setup/steps/updateAllContributorsFile.ts
@@ -1,5 +1,4 @@
 import fs from "node:fs/promises";
-
 import prettier from "prettier";
 
 import { InputValues } from "../../shared/inputs.js";
diff --git a/src/setup/steps/updateAllContributorsTable.ts b/src/setup/steps/updateAllContributorsTable.ts
index 53dc838..37c4344 100644
--- a/src/setup/steps/updateAllContributorsTable.ts
+++ b/src/setup/steps/updateAllContributorsTable.ts
@@ -1,5 +1,4 @@
 import fs from "node:fs/promises";
-
 import prettier from "prettier";
 
 import { InputValues } from "../../shared/inputs.js";
diff --git a/src/setup/steps/updateLocalFiles.ts b/src/setup/steps/updateLocalFiles.ts
index a95fdca..2fea988 100644
--- a/src/setup/steps/updateLocalFiles.ts
+++ b/src/setup/steps/updateLocalFiles.ts
@@ -26,7 +26,7 @@ export async function updateLocalFiles({
 		"./package.json"
 	)) as ExistingPackageData;
 
-	const replacements: [string | RegExp, string, string?][] = [
+	const replacements: [RegExp | string, string, string?][] = [
 		[/Template TypeScript Node Package/g, title],
 		[/JoshuaKGoldberg/g, owner],
 		[/template-typescript-node-package/g, repository],
diff --git a/src/shared/getNpmAuthor.ts b/src/shared/getNpmAuthor.ts
index 7effaba..ab64a9a 100644
--- a/src/shared/getNpmAuthor.ts
+++ b/src/shared/getNpmAuthor.ts
@@ -39,7 +39,7 @@ export async function getNpmAuthor(
 		return owner;
 	}
 
-	const { name = owner, email } = npmUserInfo;
+	const { email, name = owner } = npmUserInfo;
 	// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
 	return email ? `${name!} <${email}>` : name;
 }
diff --git a/src/shared/inputs.ts b/src/shared/inputs.ts
index 4209955..b031ae4 100644
--- a/src/shared/inputs.ts
+++ b/src/shared/inputs.ts
@@ -1,18 +1,17 @@
 import { parseArgs } from "node:util";
-
 import { Octokit } from "octokit";
 import { titleCase } from "title-case";
 
+import { PrefillPrompter } from "./PrefillPrompter.js";
 import { getDefaultSettings } from "./defaults.js";
 import { ensureRepositoryExists } from "./ensureRepositoryExists.js";
 import { getOctokit } from "./getOctokit.js";
 import { optionalDefault } from "./optionalDefault.js";
-import { PrefillPrompter } from "./PrefillPrompter.js";
 
 export type GetterDefaultInputValues = {
 	[K in keyof DefaultInputValues]:
-		| DefaultInputValues[K]
-		| (() => Promise<DefaultInputValues[K]>);
+		| (() => Promise<DefaultInputValues[K]>)
+		| DefaultInputValues[K];
 };
 
 export interface DefaultInputValues {
@@ -55,15 +54,15 @@ export async function getInputValuesAndOctokit(
 			owner: { type: "string" },
 			releases: { type: "boolean" },
 			repository: { type: "string" },
-			unitTests: { type: "boolean" },
-			title: { type: "string" },
 			"skip-api": { type: "boolean" },
 			"skip-removal": { type: "boolean" },
 			"skip-restore": { type: "boolean" },
 			"skip-uninstall": { type: "boolean" },
+			title: { type: "string" },
+			unitTests: { type: "boolean" },
 		},
-		tokens: true,
 		strict: false,
+		tokens: true,
 	});
 	const prompter = new PrefillPrompter();
 
@@ -110,6 +109,7 @@ export async function getInputValuesAndOctokit(
 				values.author as string | undefined,
 				defaults.author
 			),
+			description,
 			email: await optionalDefault(
 				values.email as string | undefined,
 				defaults.email
@@ -118,22 +118,21 @@ export async function getInputValuesAndOctokit(
 				values.funding as string | undefined,
 				defaults.funding
 			),
+			owner,
 			releases: await optionalDefault(
 				values.releases as boolean | undefined,
 				defaults.releases
 			),
-			unitTests: await optionalDefault(
-				values.unitTests as boolean | undefined,
-				defaults.unitTests
-			),
-			description,
-			owner,
 			repository,
 			skipApi: !!values["skip-api"],
 			skipRemoval: !!values["skip-removal"],
 			skipRestore: !!values["skip-restore"],
 			skipUninstalls: !!values["skip-uninstalls"],
 			title,
+			unitTests: await optionalDefault(
+				values.unitTests as boolean | undefined,
+				defaults.unitTests
+			),
 		},
 	};
 }
diff --git a/src/shared/optionalDefault.ts b/src/shared/optionalDefault.ts
index dc075f5..12ccb4e 100644
--- a/src/shared/optionalDefault.ts
+++ b/src/shared/optionalDefault.ts
@@ -1,6 +1,6 @@
 export async function optionalDefault<T extends boolean | number | string>(
 	value: T | undefined,
-	getDefault: T | (() => Promise<T | undefined>) | undefined
+	getDefault: (() => Promise<T | undefined>) | T | undefined
 ) {
 	if (value !== undefined) {
 		return value;
diff --git a/src/shared/runOrRestore.ts b/src/shared/runOrRestore.ts
index 4fde086..fd610a2 100644
--- a/src/shared/runOrRestore.ts
+++ b/src/shared/runOrRestore.ts
@@ -3,9 +3,9 @@ import chalk from "chalk";
 import { $ } from "execa";
 
 import {
-	getInputValuesAndOctokit,
 	GetterDefaultInputValues,
 	InputValuesAndOctokit,
+	getInputValuesAndOctokit,
 } from "./inputs.js";
 import { handlePromptCancel } from "./prompts.js";
 


Test patch:
diff --git a/script/setup-test-e2e.js b/script/setup-test-e2e.js
index 844e4be..d917686 100644
--- a/script/setup-test-e2e.js
+++ b/script/setup-test-e2e.js
@@ -1,8 +1,7 @@
-import { strict as assert } from "node:assert";
-
 import { $ } from "execa";
 import { promises as fs } from "fs";
 import { globby } from "globby";
+import { strict as assert } from "node:assert";
 
 const description = "New Description Test";
 const owner = "RNR1";
diff --git a/src/hydrate/steps/finalizeDependencies.test.ts b/src/hydrate/steps/finalizeDependencies.test.ts
index 2cf2a32..ab00352 100644
--- a/src/hydrate/steps/finalizeDependencies.test.ts
+++ b/src/hydrate/steps/finalizeDependencies.test.ts
@@ -30,9 +30,8 @@ describe("finalize", () => {
 				"eslint-plugin-jsdoc@latest",
 				"eslint-plugin-jsonc@latest",
 				"eslint-plugin-markdown@latest",
+				"eslint-plugin-perfectionist@latest",
 				"eslint-plugin-regexp@latest",
-				"eslint-plugin-simple-import-sort@latest",
-				"eslint-plugin-typescript-sort-keys@latest",
 				"eslint-plugin-yml@latest",
 				"husky@latest",
 				"jsonc-eslint-parser@latest",
@@ -74,9 +73,8 @@ describe("finalize", () => {
 				"eslint-plugin-jsdoc@latest",
 				"eslint-plugin-jsonc@latest",
 				"eslint-plugin-markdown@latest",
+				"eslint-plugin-perfectionist@latest",
 				"eslint-plugin-regexp@latest",
-				"eslint-plugin-simple-import-sort@latest",
-				"eslint-plugin-typescript-sort-keys@latest",
 				"eslint-plugin-yml@latest",
 				"husky@latest",
 				"jsonc-eslint-parser@latest",
diff --git a/src/hydrate/steps/writing/writeStructureWorker.ts b/src/hydrate/steps/writing/writeStructureWorker.ts
index 4f85fef..562f4da 100644
--- a/src/hydrate/steps/writing/writeStructureWorker.ts
+++ b/src/hydrate/steps/writing/writeStructureWorker.ts
@@ -1,5 +1,4 @@
 import * as fs from "node:fs/promises";
-
 import * as path from "path";
 import prettier from "prettier";
 
@@ -30,8 +29,8 @@ function format(fileName: string, text: string) {
 	}
 
 	return prettier.format(text, {
-		useTabs: true,
 		parser,
+		useTabs: true,
 	});
 }
 
diff --git a/src/shared/ensureRepositoryExists.test.ts b/src/shared/ensureRepositoryExists.test.ts
index 271858d..1d6bbb5 100644
--- a/src/shared/ensureRepositoryExists.test.ts
+++ b/src/shared/ensureRepositoryExists.test.ts
@@ -1,5 +1,5 @@
 import { Octokit } from "octokit";
-import { beforeEach, describe, expect, it, SpyInstance, vi } from "vitest";
+import { SpyInstance, beforeEach, describe, expect, it, vi } from "vitest";
 
 import { ensureRepositoryExists } from "./ensureRepositoryExists.js";
 
@@ -7,8 +7,8 @@ const mockSelect = vi.fn();
 const mockText = vi.fn();
 
 vi.mock("@clack/prompts", () => ({
-	isCancel: vi.fn(),
 	intro: vi.fn(),
+	isCancel: vi.fn(),
 	outro: vi.fn(),
 	get select() {
 		return mockSelect;
@@ -63,11 +63,11 @@ describe("ensureRepositoryExists", () => {
 
 	it("creates the repository when the octokit GET rejects and the prompt resolves 'create'", async () => {
 		const octokit = createMockOctokit({
+			createUsingTemplate: vi.fn().mockResolvedValue({}),
 			get: vi
 				.fn()
 				.mockRejectedValueOnce({ status: 404 })
 				.mockResolvedValueOnce({}),
-			createUsingTemplate: vi.fn().mockResolvedValue({}),
 		});
 
 		mockSelect.mockResolvedValue("create");
diff --git a/src/shared/getNpmAuthor.test.ts b/src/shared/getNpmAuthor.test.ts
index 180bebf..fe8d0f7 100644
--- a/src/shared/getNpmAuthor.test.ts
+++ b/src/shared/getNpmAuthor.test.ts
@@ -1,5 +1,5 @@
 import chalk from "chalk";
-import { beforeEach, describe, expect, it, SpyInstance, vi } from "vitest";
+import { SpyInstance, beforeEach, describe, expect, it, vi } from "vitest";
 
 import { getNpmAuthor } from "./getNpmAuthor.js";
 
diff --git a/src/shared/runOrRestore.test.ts b/src/shared/runOrRestore.test.ts
index 240ff78..5dd2227 100644
--- a/src/shared/runOrRestore.test.ts
+++ b/src/shared/runOrRestore.test.ts
@@ -8,8 +8,8 @@ vi.mock("@clack/prompts", () => ({
 	get confirm() {
 		return mockConfirm;
 	},
-	isCancel: vi.fn(),
 	intro: vi.fn(),
+	isCancel: vi.fn(),
 	outro: vi.fn(),
 }));