Commit 139b3fd0616 for woocommerce

commit 139b3fd061609c8d627a3d3eb35528530a0fbd54
Author: Darren Ethier <darren@roughsmootheng.in>
Date:   Mon Nov 2 07:47:28 2026 -0500

    Rewrite monorepo-merge tool as a dependency-free Node CLI (#70486)

diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b22323663b9..347715598e5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -4054,59 +4054,13 @@ importers:
         version: 10.7.0(jiti@2.6.1)

   tools/monorepo-merge:
-    dependencies:
-      '@inquirer/confirm':
-        specifier: ^6.1.1
-        version: 6.2.0(@types/node@24.12.2)
-      '@inquirer/password':
-        specifier: ^5.1.1
-        version: 5.1.2(@types/node@24.12.2)
-      '@oclif/core':
-        specifier: ^4.13.5
-        version: 4.13.5
-      '@oclif/plugin-help':
-        specifier: ^6.2.58
-        version: 6.2.58
-      '@oclif/plugin-plugins':
-        specifier: ^5.4.87
-        version: 5.5.0
-      '@octokit/graphql':
-        specifier: 4.8.0
-        version: 4.8.0(encoding@0.1.13)
     devDependencies:
-      '@octokit/request-error':
-        specifier: ^3.0.3
-        version: 3.0.3
-      '@types/node':
-        specifier: ^24.1.0
-        version: 24.12.2
       '@woocommerce/eslint-config':
         specifier: workspace:*
         version: link:../eslint-config
-      '@woocommerce/eslint-plugin':
-        specifier: workspace:*
-        version: link:../../packages/js/eslint-plugin
       eslint:
         specifier: ^10.0.0
         version: 10.7.0(jiti@2.6.1)
-      jscodeshift:
-        specifier: ^0.13.1
-        version: 0.13.1(@babel/preset-env@7.25.7(@babel/core@7.29.7))
-      oclif:
-        specifier: ^4.23.30
-        version: 4.23.30(@types/node@24.12.2)
-      shx:
-        specifier: ^0.3.4
-        version: 0.3.4
-      ts-node:
-        specifier: ^10.9.2
-        version: 10.9.2(@swc/core@1.15.24)(@types/node@24.12.2)(typescript@5.7.3)
-      tslib:
-        specifier: ^2.6.2
-        version: 2.8.1
-      typescript:
-        specifier: 5.7.x
-        version: 5.7.3

   tools/monorepo-utils:
     dependencies:
@@ -4784,11 +4738,6 @@ packages:
     resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==}
     engines: {node: '>=6.9.0'}

-  '@babel/parser@7.29.2':
-    resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==}
-    engines: {node: '>=6.0.0'}
-    hasBin: true
-
   '@babel/parser@7.29.8':
     resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==}
     engines: {node: '>=6.0.0'}
@@ -4824,27 +4773,6 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0

-  '@babel/plugin-proposal-class-properties@7.18.6':
-    resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==}
-    engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6':
-    resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==}
-    engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
-  '@babel/plugin-proposal-optional-chaining@7.21.0':
-    resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==}
-    engines: {node: '>=6.9.0'}
-    deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
   '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2':
     resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==}
     engines: {node: '>=6.9.0'}
@@ -4882,12 +4810,6 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0-0

-  '@babel/plugin-syntax-flow@7.28.6':
-    resolution: {integrity: sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
   '@babel/plugin-syntax-import-assertions@7.28.6':
     resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==}
     engines: {node: '>=6.9.0'}
@@ -5078,12 +5000,6 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0-0

-  '@babel/plugin-transform-flow-strip-types@7.27.1':
-    resolution: {integrity: sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
   '@babel/plugin-transform-for-of@7.27.1':
     resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==}
     engines: {node: '>=6.9.0'}
@@ -5342,12 +5258,6 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0-0

-  '@babel/preset-flow@7.27.1':
-    resolution: {integrity: sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
   '@babel/preset-modules@0.1.6-no-external-plugins':
     resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==}
     peerDependencies:
@@ -5365,12 +5275,6 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0-0

-  '@babel/register@7.28.6':
-    resolution: {integrity: sha512-pgcbbEl/dWQYb6L6Yew6F94rdwygfuv+vJ/tXfwIOYAfPB6TNWpXUMEtEq3YuTeHRdvMIhvz13bkT9CNaS+wqA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
   '@babel/runtime@7.25.7':
     resolution: {integrity: sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==}
     engines: {node: '>=6.9.0'}
@@ -6109,10 +6013,6 @@ packages:
     resolution: {integrity: sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==}
     engines: {node: '>=18'}

-  '@inquirer/ansi@2.0.7':
-    resolution: {integrity: sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==}
-    engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
-
   '@inquirer/checkbox@4.3.2':
     resolution: {integrity: sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==}
     engines: {node: '>=18'}
@@ -6135,15 +6035,6 @@ packages:
       '@types/node':
         optional: true

-  '@inquirer/confirm@6.2.0':
-    resolution: {integrity: sha512-SKXarWrYhtpqOEctf9XGCGy29QjsvJAM0Aq9ZR9z4Ns94OmpqudOly+aSEfNqUf9SwsQaUgY9+Z8hyzG0xX8fw==}
-    engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
-    peerDependencies:
-      '@types/node': '>=18'
-    peerDependenciesMeta:
-      '@types/node':
-        optional: true
-
   '@inquirer/core@10.3.2':
     resolution: {integrity: sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==}
     engines: {node: '>=18'}
@@ -6153,15 +6044,6 @@ packages:
       '@types/node':
         optional: true

-  '@inquirer/core@12.0.0':
-    resolution: {integrity: sha512-+nnvFEXIB08CZNVXpvW3B+zHW96QXvGUjNKJ8NJIPqAZi5Kd4WhYt2S3C234ReepG1qw2HOlEUbjYVHBowXObA==}
-    engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
-    peerDependencies:
-      '@types/node': '>=18'
-    peerDependenciesMeta:
-      '@types/node':
-        optional: true
-
   '@inquirer/core@9.2.1':
     resolution: {integrity: sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==}
     engines: {node: '>=18'}
@@ -6197,10 +6079,6 @@ packages:
     resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==}
     engines: {node: '>=18'}

-  '@inquirer/figures@2.0.8':
-    resolution: {integrity: sha512-tApbon79GM9ry56ja/Ud3SY2CL4TQsao9fIwDQbgTeNY55025GdMzQ2+UdegV/lx51VNGUB59M0v0nMpybYY4Q==}
-    engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
-
   '@inquirer/input@2.3.0':
     resolution: {integrity: sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==}
     engines: {node: '>=18'}
@@ -6232,15 +6110,6 @@ packages:
       '@types/node':
         optional: true

-  '@inquirer/password@5.1.2':
-    resolution: {integrity: sha512-nSdufycW8xynEVssFkNQEYIzTySilog0UlfOVRwh3pXzPSk4frXUT2jZWjHnKae6RU9PaoF9wfy1pGwewQuqGw==}
-    engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
-    peerDependencies:
-      '@types/node': '>=18'
-    peerDependenciesMeta:
-      '@types/node':
-        optional: true
-
   '@inquirer/prompts@7.10.1':
     resolution: {integrity: sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==}
     engines: {node: '>=18'}
@@ -6298,15 +6167,6 @@ packages:
       '@types/node':
         optional: true

-  '@inquirer/type@4.0.7':
-    resolution: {integrity: sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==}
-    engines: {node: '>=23.5.0 || ^22.13.0 || ^20.17.0'}
-    peerDependencies:
-      '@types/node': '>=18'
-    peerDependenciesMeta:
-      '@types/node':
-        optional: true
-
   '@isaacs/cliui@8.0.2':
     resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
     engines: {node: '>=12'}
@@ -11139,14 +10999,6 @@ packages:
     resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
     engines: {node: '>= 0.4'}

-  arr-diff@4.0.0:
-    resolution: {integrity: sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==}
-    engines: {node: '>=0.10.0'}
-
-  arr-flatten@1.1.0:
-    resolution: {integrity: sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==}
-    engines: {node: '>=0.10.0'}
-
   arr-union@3.1.0:
     resolution: {integrity: sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==}
     engines: {node: '>=0.10.0'}
@@ -11190,10 +11042,6 @@ packages:
     resolution: {integrity: sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==}
     engines: {node: '>=0.10.0'}

-  array-unique@0.3.2:
-    resolution: {integrity: sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==}
-    engines: {node: '>=0.10.0'}
-
   array.prototype.find@2.2.3:
     resolution: {integrity: sha512-fO/ORdOELvjbbeIfZfzrXFMhYHGofRGqd+am9zm3tZ4GlJINj/pA2eITyfd65Vg6+ZbHd/Cys7stpoRSWtQFdA==}
     engines: {node: '>= 0.4'}
@@ -11233,10 +11081,6 @@ packages:
     resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
     engines: {node: '>=12'}

-  assign-symbols@1.0.0:
-    resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==}
-    engines: {node: '>=0.10.0'}
-
   ast-types-flow@0.0.8:
     resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==}

@@ -11281,11 +11125,6 @@ packages:
   asynckit@0.4.0:
     resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}

-  atob@2.1.2:
-    resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
-    engines: {node: '>= 4.5.0'}
-    hasBin: true
-
   atomically@2.1.1:
     resolution: {integrity: sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==}

@@ -11339,11 +11178,6 @@ packages:
       react-native-b4a:
         optional: true

-  babel-core@7.0.0-bridge.0:
-    resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-
   babel-jest@29.5.0:
     resolution: {integrity: sha512-mA4eCDh5mSo2EcA9xQjVTpmbbNk32Zb3Q3QFQsNhaK56Q+yoXowzFodLux30HRgyOho5rsQ6B0P9QpMkvvnJ0Q==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -11469,10 +11303,6 @@ packages:
   base64-js@1.5.1:
     resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}

-  base@0.11.2:
-    resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==}
-    engines: {node: '>=0.10.0'}
-
   baseline-browser-mapping@2.11.27:
     resolution: {integrity: sha512-ElY12DaROGuan+lMmZ8Cvo/ZUbXPe7Enc/9VU/b1T3Kp4dwytRcNdR8DoSJN5SNJT/CuvcCA0DHDVmMOCePdRQ==}
     engines: {node: '>=6.0.0'}
@@ -11531,10 +11361,6 @@ packages:
     resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==}
     engines: {node: 20 || >=22}

-  braces@2.3.2:
-    resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==}
-    engines: {node: '>=0.10.0'}
-
   braces@3.0.3:
     resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
     engines: {node: '>=8'}
@@ -11622,10 +11448,6 @@ packages:
     engines: {node: '>=10.12.0'}
     hasBin: true

-  cache-base@1.0.1:
-    resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==}
-    engines: {node: '>=0.10.0'}
-
   cacheable-lookup@5.0.4:
     resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==}
     engines: {node: '>=10.6.0'}
@@ -11824,10 +11646,6 @@ packages:
   cjs-module-lexer@1.4.3:
     resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==}

-  class-utils@0.3.6:
-    resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==}
-    engines: {node: '>=0.10.0'}
-
   classnames@2.5.1:
     resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}

@@ -11939,10 +11757,6 @@ packages:
   collect-v8-coverage@1.0.3:
     resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==}

-  collection-visit@1.0.0:
-    resolution: {integrity: sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==}
-    engines: {node: '>=0.10.0'}
-
   color-convert@1.9.3:
     resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}

@@ -12151,10 +11965,6 @@ packages:
     resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
     engines: {node: '>=12.13'}

-  copy-descriptor@0.1.1:
-    resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==}
-    engines: {node: '>=0.10.0'}
-
   copy-dir@1.3.0:
     resolution: {integrity: sha512-Q4+qBFnN4bwGwvtXXzbp4P/4iNk0MaiGAzvQ8OiMtlLjkIKjmNN689uVzShSM0908q7GoFHXIPx4zi75ocoaHw==}

@@ -12521,10 +12331,6 @@ packages:
   decimal.js@10.6.0:
     resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}

-  decode-uri-component@0.2.2:
-    resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
-    engines: {node: '>=0.10'}
-
   decompress-response@6.0.0:
     resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
     engines: {node: '>=10'}
@@ -12635,18 +12441,6 @@ packages:
     resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
     engines: {node: '>= 0.4'}

-  define-property@0.2.5:
-    resolution: {integrity: sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==}
-    engines: {node: '>=0.10.0'}
-
-  define-property@1.0.0:
-    resolution: {integrity: sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==}
-    engines: {node: '>=0.10.0'}
-
-  define-property@2.0.2:
-    resolution: {integrity: sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==}
-    engines: {node: '>=0.10.0'}
-
   degenerator@5.0.1:
     resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
     engines: {node: '>= 14'}
@@ -13333,10 +13127,6 @@ packages:
     resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==}
     engines: {node: '>= 0.8.0'}

-  expand-brackets@2.1.4:
-    resolution: {integrity: sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==}
-    engines: {node: '>=0.10.0'}
-
   expand-tilde@1.2.2:
     resolution: {integrity: sha512-rtmc+cjLZqnu9dSYosX9EWmSJhTwpACgJQTfj4hgg2JjOD/6SIQalZrt4a3aQeh++oNxkazcaxrhPUj6+g5G/Q==}
     engines: {node: '>=0.10.0'}
@@ -13360,10 +13150,6 @@ packages:
     resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==}
     engines: {node: '>=0.10.0'}

-  extend-shallow@3.0.2:
-    resolution: {integrity: sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==}
-    engines: {node: '>=0.10.0'}
-
   extend@3.0.2:
     resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}

@@ -13371,10 +13157,6 @@ packages:
     resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==}
     engines: {node: '>=4'}

-  extglob@2.0.4:
-    resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==}
-    engines: {node: '>=0.10.0'}
-
   extract-zip@1.7.0:
     resolution: {integrity: sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==}
     hasBin: true
@@ -13429,18 +13211,9 @@ packages:
   fast-sort@3.4.1:
     resolution: {integrity: sha512-76uvGPsF6So53sZAqenP9UVT3p5l7cyTHkLWVCMinh41Y8NDrK1IYXJgaBMfc1gk7nJiSRZp676kddFG2Aa5+A==}

-  fast-string-truncated-width@3.0.3:
-    resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==}
-
-  fast-string-width@3.0.2:
-    resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==}
-
   fast-uri@3.1.5:
     resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==}

-  fast-wrap-ansi@0.2.2:
-    resolution: {integrity: sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==}
-
   fast-xml-builder@1.3.1:
     resolution: {integrity: sha512-pIM/1n3ntFXKYrUZwW7QCK0gAW7XY+wzj1YMIV3tLDvPj/V+zTGJK5e3/4WJfwj0qWw2ElNXiTixda/R+3YSug==}

@@ -13517,10 +13290,6 @@ packages:
     resolution: {integrity: sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==}
     engines: {node: '>=8'}

-  fill-range@4.0.0:
-    resolution: {integrity: sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==}
-    engines: {node: '>=0.10.0'}
-
   fill-range@7.1.1:
     resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
     engines: {node: '>=8'}
@@ -13529,10 +13298,6 @@ packages:
     resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==}
     engines: {node: '>= 0.8'}

-  find-cache-dir@2.1.0:
-    resolution: {integrity: sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==}
-    engines: {node: '>=6'}
-
   find-cache-dir@3.3.2:
     resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==}
     engines: {node: '>=8'}
@@ -13616,10 +13381,6 @@ packages:
   flatted@3.4.2:
     resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}

-  flow-parser@0.309.0:
-    resolution: {integrity: sha512-poYRskeIXiHsE19Fb9sRE/CV7PYOq21j3lS5vKr27ujFBvSAhmCbbilAonJ0/u0Uai+Xgyq30/twHQeQc2Ngiw==}
-    engines: {node: '>=0.4.0'}
-
   follow-redirects@1.16.0:
     resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
     engines: {node: '>=4.0'}
@@ -13697,10 +13458,6 @@ packages:
   fraction.js@5.3.4:
     resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}

-  fragment-cache@0.2.1:
-    resolution: {integrity: sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==}
-    engines: {node: '>=0.10.0'}
-
   framer-motion@10.18.0:
     resolution: {integrity: sha512-oGlDh1Q1XqYPksuTD/usb0I70hq95OUzmL9+6Zd+Hs4XV0oaISBa/UUMSjYiq6m8EUF32132mOJ8xVZS+I0S6w==}
     peerDependencies:
@@ -13847,10 +13604,6 @@ packages:
     resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==}
     engines: {node: '>= 14'}

-  get-value@2.0.6:
-    resolution: {integrity: sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==}
-    engines: {node: '>=0.10.0'}
-
   getobject@1.0.2:
     resolution: {integrity: sha512-2zblDBaFcb3rB4rF77XVnuINOE2h2k/OnqXAiy0IrTxUfV1iFp3la33oAQVY9pCpWU268WFYVt2t71hlMuLsOg==}
     engines: {node: '>=10'}
@@ -14141,22 +13894,6 @@ packages:
     resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
     engines: {node: '>= 0.4'}

-  has-value@0.3.1:
-    resolution: {integrity: sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==}
-    engines: {node: '>=0.10.0'}
-
-  has-value@1.0.0:
-    resolution: {integrity: sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==}
-    engines: {node: '>=0.10.0'}
-
-  has-values@0.1.4:
-    resolution: {integrity: sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==}
-    engines: {node: '>=0.10.0'}
-
-  has-values@1.0.0:
-    resolution: {integrity: sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==}
-    engines: {node: '>=0.10.0'}
-
   hash.js@1.1.7:
     resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==}

@@ -14513,10 +14250,6 @@ packages:
     resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==}
     engines: {node: '>=0.10.0'}

-  is-accessor-descriptor@1.0.1:
-    resolution: {integrity: sha512-YBUanLI8Yoihw923YeFUS5fs0fF2f5TSFTNiYAAzhhDscDa3lEqYuz1pDOEP5KvX94I9ey3vsqjJcLVFVU+3QA==}
-    engines: {node: '>= 0.10'}
-
   is-arguments@1.2.0:
     resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==}
     engines: {node: '>= 0.4'}
@@ -14562,10 +14295,6 @@ packages:
     resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
     engines: {node: '>= 0.4'}

-  is-data-descriptor@1.0.1:
-    resolution: {integrity: sha512-bc4NlCDiCr28U4aEsQ3Qs2491gVq4V8G7MQyws968ImqjKuYtTJXrl7Vq7jsN7Ly/C3xj5KWFrY7sHNeDkAzXw==}
-    engines: {node: '>= 0.4'}
-
   is-data-view@1.0.2:
     resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
     engines: {node: '>= 0.4'}
@@ -14574,14 +14303,6 @@ packages:
     resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
     engines: {node: '>= 0.4'}

-  is-descriptor@0.1.7:
-    resolution: {integrity: sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==}
-    engines: {node: '>= 0.4'}
-
-  is-descriptor@1.0.3:
-    resolution: {integrity: sha512-JCNNGbwWZEVaSPtS45mdtrneRWJFp07LLmykxeFV5F6oBvNF8vHSfJuJgoT472pSfk+Mf8VnlrspaFBHWM8JAw==}
-    engines: {node: '>= 0.4'}
-
   is-docker@2.2.1:
     resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==}
     engines: {node: '>=8'}
@@ -14599,10 +14320,6 @@ packages:
     resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==}
     engines: {node: '>=0.10.0'}

-  is-extendable@1.0.1:
-    resolution: {integrity: sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==}
-    engines: {node: '>=0.10.0'}
-
   is-extglob@2.1.1:
     resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
     engines: {node: '>=0.10.0'}
@@ -14671,10 +14388,6 @@ packages:
     resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
     engines: {node: '>= 0.4'}

-  is-number@3.0.0:
-    resolution: {integrity: sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==}
-    engines: {node: '>=0.10.0'}
-
   is-number@7.0.0:
     resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
     engines: {node: '>=0.12.0'}
@@ -14835,10 +14548,6 @@ packages:
     resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==}
     engines: {node: '>=18'}

-  isobject@2.1.0:
-    resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==}
-    engines: {node: '>=0.10.0'}
-
   isobject@3.0.1:
     resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
     engines: {node: '>=0.10.0'}
@@ -15154,12 +14863,6 @@ packages:
     resolution: {integrity: sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==}
     hasBin: true

-  jscodeshift@0.13.1:
-    resolution: {integrity: sha512-lGyiEbGOvmMRKgWk4vf+lUrCWO/8YR8sUR3FKF1Cq5fovjZDlIcw3Hu5ppLHAnEXshVffvaM0eyuY/AbOeYpnQ==}
-    hasBin: true
-    peerDependencies:
-      '@babel/preset-env': ^7.1.6
-
   jsdoc-type-pratt-parser@4.1.0:
     resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==}
     engines: {node: '>=12.0.0'}
@@ -15277,10 +14980,6 @@ packages:
     resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==}
     engines: {node: '>=0.10.0'}

-  kind-of@4.0.0:
-    resolution: {integrity: sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==}
-    engines: {node: '>=0.10.0'}
-
   kind-of@6.0.3:
     resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==}
     engines: {node: '>=0.10.0'}
@@ -15663,10 +15362,6 @@ packages:
   map-values@1.0.1:
     resolution: {integrity: sha512-BbShUnr5OartXJe1GeccAWtfro11hhgNJg6G9/UtWKjVGvV5U4C09cg5nk8JUevhXODaXY+hQ3xxMUKSs62ONQ==}

-  map-visit@1.0.0:
-    resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==}
-    engines: {node: '>=0.10.0'}
-
   markdown-it@12.3.2:
     resolution: {integrity: sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==}
     hasBin: true
@@ -15786,10 +15481,6 @@ packages:
     resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==}
     engines: {node: '>= 0.6'}

-  micromatch@3.1.10:
-    resolution: {integrity: sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==}
-    engines: {node: '>=0.10.0'}
-
   micromatch@4.0.8:
     resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
     engines: {node: '>=8.6'}
@@ -15886,10 +15577,6 @@ packages:
   mitt@3.0.1:
     resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}

-  mixin-deep@1.3.2:
-    resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==}
-    engines: {node: '>=0.10.0'}
-
   mixin-object@2.0.1:
     resolution: {integrity: sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==}
     engines: {node: '>=0.10.0'}
@@ -15956,10 +15643,6 @@ packages:
     resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==}
     engines: {node: ^18.17.0 || >=20.5.0}

-  mute-stream@3.0.0:
-    resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==}
-    engines: {node: ^20.17.0 || >=22.9.0}
-
   nan@2.26.2:
     resolution: {integrity: sha512-0tTvBTYkt3tdGw22nrAy50x7gpbGCCFH3AFcyS5WiUu7Eu4vWlri1woE6qHBSfy11vksDqkiwjOnlR7WV8G1Hw==}

@@ -15968,10 +15651,6 @@ packages:
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true

-  nanomatch@1.2.13:
-    resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
-    engines: {node: '>=0.10.0'}
-
   napi-postinstall@0.3.4:
     resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==}
     engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
@@ -16213,10 +15892,6 @@ packages:
     resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
     engines: {node: '>=0.10.0'}

-  object-copy@0.1.0:
-    resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==}
-    engines: {node: '>=0.10.0'}
-
   object-filter@1.0.2:
     resolution: {integrity: sha512-NahvP2vZcy1ZiiYah30CEPw0FpDcSkSePJBMpzl5EQgCmISijiGuJm3SPYp7U+Lf2TljyaIw3E5EgkEx/TNEVA==}

@@ -16240,10 +15915,6 @@ packages:
     resolution: {integrity: sha512-Y6tg5rHfsefSkfKujv2SwHulInROy/rCL5F4w0QOWxut8AnxYxf0YmNhTh95Zfyxpsudo66uqkux0ACFnyMSgQ==}
     engines: {node: '>= 16'}

-  object-visit@1.0.1:
-    resolution: {integrity: sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==}
-    engines: {node: '>=0.10.0'}
-
   object.assign@4.1.7:
     resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
     engines: {node: '>= 0.4'}
@@ -16500,10 +16171,6 @@ packages:
   pascal-case@3.1.2:
     resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}

-  pascalcase@0.1.1:
-    resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==}
-    engines: {node: '>=0.10.0'}
-
   password-prompt@1.1.3:
     resolution: {integrity: sha512-HkrjG2aJlvF0t2BMH0e2LB/EHf3Lcq3fNMzy4GYHcQblAvOl+QQji1Lx7WRBMqpVK8p+KR7bCg7oqAMXtdgqyw==}

@@ -16661,10 +16328,6 @@ packages:
     resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
     engines: {node: '>= 6'}

-  pkg-dir@3.0.0:
-    resolution: {integrity: sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==}
-    engines: {node: '>=6'}
-
   pkg-dir@4.2.0:
     resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==}
     engines: {node: '>=8'}
@@ -16690,10 +16353,6 @@ packages:
     resolution: {integrity: sha512-4UGewrYgqDFw9vV6zNV+ADmPAUAfJPKtGvb/VdpQAx25X5f3xXdGdyOEVFwkl8Hl/tl7+xbeHqSEM+D5/TirUg==}
     engines: {node: '>=10'}

-  posix-character-classes@0.1.1:
-    resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==}
-    engines: {node: '>=0.10.0'}
-
   possible-typed-array-names@1.1.0:
     resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
     engines: {node: '>= 0.4'}
@@ -17724,10 +17383,6 @@ packages:
       react: ^16.8.0 || ^17.0.0
       react-dom: ^16.8.0 || ^17.0.0

-  recast@0.20.5:
-    resolution: {integrity: sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ==}
-    engines: {node: '>= 4'}
-
   recast@0.23.11:
     resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
     engines: {node: '>= 4'}
@@ -17778,10 +17433,6 @@ packages:
   regenerator-runtime@0.14.1:
     resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}

-  regex-not@1.0.2:
-    resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==}
-    engines: {node: '>=0.10.0'}
-
   regexp.prototype.flags@1.5.4:
     resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
     engines: {node: '>= 0.4'}
@@ -17827,14 +17478,6 @@ packages:
   renderkid@3.0.0:
     resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==}

-  repeat-element@1.1.4:
-    resolution: {integrity: sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==}
-    engines: {node: '>=0.10.0'}
-
-  repeat-string@1.6.1:
-    resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
-    engines: {node: '>=0.10'}
-
   repeating@2.0.1:
     resolution: {integrity: sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==}
     engines: {node: '>=0.10.0'}
@@ -17908,10 +17551,6 @@ packages:
   resolve-pkg-maps@1.0.0:
     resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}

-  resolve-url@0.2.1:
-    resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==}
-    deprecated: https://github.com/lydell/resolve-url#deprecated
-
   resolve.exports@2.0.3:
     resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==}
     engines: {node: '>=10'}
@@ -17937,10 +17576,6 @@ packages:
     resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
     engines: {node: '>=8'}

-  ret@0.1.15:
-    resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==}
-    engines: {node: '>=0.12'}
-
   retry@0.13.1:
     resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
     engines: {node: '>= 4'}
@@ -17956,11 +17591,6 @@ packages:
     resolution: {integrity: sha512-F49dXX73a92N09uQkfCp2QjwXpmJcn9/i9PvjmwsSIXUGqRLCf/yx5Q9gRxuLQTq248kakqQuc8GX/U/CxSqlA==}
     hasBin: true

-  rimraf@2.6.3:
-    resolution: {integrity: sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==}
-    deprecated: Rimraf versions prior to v4 are no longer supported
-    hasBin: true
-
   rimraf@2.7.1:
     resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
     deprecated: Rimraf versions prior to v4 are no longer supported
@@ -18042,9 +17672,6 @@ packages:
     resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
     engines: {node: '>= 0.4'}

-  safe-regex@1.1.0:
-    resolution: {integrity: sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==}
-
   safe-stable-stringify@2.5.0:
     resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
     engines: {node: '>=10'}
@@ -18315,10 +17942,6 @@ packages:
     resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
     engines: {node: '>= 0.4'}

-  set-value@2.0.1:
-    resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==}
-    engines: {node: '>=0.10.0'}
-
   setprototypeof@1.2.0:
     resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}

@@ -18459,18 +18082,6 @@ packages:
   snake-case@3.0.4:
     resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==}

-  snapdragon-node@2.1.1:
-    resolution: {integrity: sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==}
-    engines: {node: '>=0.10.0'}
-
-  snapdragon-util@3.0.1:
-    resolution: {integrity: sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==}
-    engines: {node: '>=0.10.0'}
-
-  snapdragon@0.8.2:
-    resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==}
-    engines: {node: '>=0.10.0'}
-
   sockjs@0.3.24:
     resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==}

@@ -18496,20 +18107,12 @@ packages:
     peerDependencies:
       webpack: ^5.0.0

-  source-map-resolve@0.5.3:
-    resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==}
-    deprecated: See https://github.com/lydell/source-map-resolve#deprecated
-
   source-map-support@0.5.13:
     resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==}

   source-map-support@0.5.21:
     resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}

-  source-map-url@0.4.1:
-    resolution: {integrity: sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==}
-    deprecated: See https://github.com/lydell/source-map-url#deprecated
-
   source-map@0.5.7:
     resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
     engines: {node: '>=0.10.0'}
@@ -18556,10 +18159,6 @@ packages:
     resolution: {integrity: sha512-DI7/OuAUD+GMpR6dmu8lliO2Wg5zfeh+/xsdyJZCzd8o5JgFUjCeLsBDuZjIQJdwXS3J0L/uZYrELKYqx+PXog==}
     engines: {node: '>=8.0'}

-  split-string@3.1.0:
-    resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==}
-    engines: {node: '>=0.10.0'}
-
   sprintf-js@1.0.3:
     resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}

@@ -18581,10 +18180,6 @@ packages:
   stackframe@1.3.4:
     resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==}

-  static-extend@0.1.2:
-    resolution: {integrity: sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==}
-    engines: {node: '>=0.10.0'}
-
   statuses@1.5.0:
     resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==}
     engines: {node: '>= 0.6'}
@@ -18974,10 +18569,6 @@ packages:
     resolution: {integrity: sha512-f3b5CaPwPvMviA+CtHy0qoIGWvzpRrNpXmGRc/Y1jc9gAYy+xOlndJFyn7Vfcz7cBcS8QRvv8z0EEH59sHCQxg==}
     engines: {node: '>=18.13.0'}

-  temp@0.8.4:
-    resolution: {integrity: sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==}
-    engines: {node: '>=6.0.0'}
-
   temporal-polyfill@1.0.4:
     resolution: {integrity: sha512-MLEU0qOD2uXlz24oINNtdLZQl8RgmMxSnRtKEGZGGetTJjlRwQJjk+VsJ4EREaUoiaTb8NJOcUiKtoAiWn9EBg==}

@@ -19097,22 +18688,10 @@ packages:
     resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==}
     engines: {node: '>= 0.4'}

-  to-object-path@0.3.0:
-    resolution: {integrity: sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==}
-    engines: {node: '>=0.10.0'}
-
-  to-regex-range@2.1.1:
-    resolution: {integrity: sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==}
-    engines: {node: '>=0.10.0'}
-
   to-regex-range@5.0.1:
     resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
     engines: {node: '>=8.0'}

-  to-regex@3.0.2:
-    resolution: {integrity: sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==}
-    engines: {node: '>=0.10.0'}
-
   toidentifier@1.0.1:
     resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
     engines: {node: '>=0.6'}
@@ -19368,10 +18947,6 @@ packages:
     resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==}
     engines: {node: '>=4'}

-  union-value@1.0.1:
-    resolution: {integrity: sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==}
-    engines: {node: '>=0.10.0'}
-
   unique-string@1.0.0:
     resolution: {integrity: sha512-ODgiYu03y5g76A1I9Gt0/chLCzQjvzDy7DsZGsLOE/1MrF6wriEskSncj1+/C58Xk/kPZDppSctDybCwOSaGAg==}
     engines: {node: '>=4'}
@@ -19405,10 +18980,6 @@ packages:
   unrs-resolver@1.11.1:
     resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==}

-  unset-value@1.0.0:
-    resolution: {integrity: sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==}
-    engines: {node: '>=0.10.0'}
-
   unzip-response@2.0.1:
     resolution: {integrity: sha512-N0XH6lqDtFH84JxptQoZYmloF4nzrQqqrAymNj+/gW60AO2AZgOcf4O/nUXJcYfyQkqvMo9lSupBZmmgvuVXlw==}
     engines: {node: '>=4'}
@@ -19436,10 +19007,6 @@ packages:
     resolution: {integrity: sha512-8pMuAn4KacYdGMkFaoQARicp4HSw24/DHOVKWqVRJ8LhhAwPPFpdGvdL9184JVmUwe7vz7Z9n6IqI6t5n2ELdg==}
     engines: {node: '>= 0.10'}

-  urix@0.1.0:
-    resolution: {integrity: sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==}
-    deprecated: Please see https://github.com/lydell/urix#deprecated
-
   url-loader@4.1.1:
     resolution: {integrity: sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==}
     engines: {node: '>= 10.13.0'}
@@ -19518,10 +19085,6 @@ packages:
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0

-  use@3.1.1:
-    resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
-    engines: {node: '>=0.10.0'}
-
   usehooks-ts@2.16.0:
     resolution: {integrity: sha512-bez95WqYujxp6hFdM/CpRDiVPirZPxlMzOH2QB8yopoKQMXpscyZoxOjpEdaxvV+CAWUDSM62cWnqHE0E/MZ7w==}
     engines: {node: '>=16.15.0'}
@@ -20907,10 +20470,6 @@ snapshots:
       '@babel/template': 7.29.7
       '@babel/types': 7.29.8

-  '@babel/parser@7.29.2':
-    dependencies:
-      '@babel/types': 7.29.8
-
   '@babel/parser@7.29.8':
     dependencies:
       '@babel/types': 7.29.8
@@ -20985,29 +20544,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color

-  '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.29.7)':
-    dependencies:
-      '@babel/core': 7.29.7
-      '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.7)
-      '@babel/helper-plugin-utils': 7.28.6
-    transitivePeerDependencies:
-      - supports-color
-
-  '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.29.7)':
-    dependencies:
-      '@babel/core': 7.29.7
-      '@babel/helper-plugin-utils': 7.28.6
-      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7)
-
-  '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.29.7)':
-    dependencies:
-      '@babel/core': 7.29.7
-      '@babel/helper-plugin-utils': 7.28.6
-      '@babel/helper-skip-transparent-expression-wrappers': 7.27.1
-      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7)
-    transitivePeerDependencies:
-      - supports-color
-
   '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.25.7)':
     dependencies:
       '@babel/core': 7.25.7
@@ -21076,11 +20612,6 @@ snapshots:
       '@babel/core': 7.29.7
       '@babel/helper-plugin-utils': 7.28.6

-  '@babel/plugin-syntax-flow@7.28.6(@babel/core@7.29.7)':
-    dependencies:
-      '@babel/core': 7.29.7
-      '@babel/helper-plugin-utils': 7.28.6
-
   '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.25.7)':
     dependencies:
       '@babel/core': 7.25.7
@@ -21457,12 +20988,6 @@ snapshots:
       '@babel/core': 7.29.7
       '@babel/helper-plugin-utils': 7.28.6

-  '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.29.7)':
-    dependencies:
-      '@babel/core': 7.29.7
-      '@babel/helper-plugin-utils': 7.28.6
-      '@babel/plugin-syntax-flow': 7.28.6(@babel/core@7.29.7)
-
   '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.25.7)':
     dependencies:
       '@babel/core': 7.25.7
@@ -22185,13 +21710,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color

-  '@babel/preset-flow@7.27.1(@babel/core@7.29.7)':
-    dependencies:
-      '@babel/core': 7.29.7
-      '@babel/helper-plugin-utils': 7.28.6
-      '@babel/helper-validator-option': 7.29.7
-      '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.29.7)
-
   '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.25.7)':
     dependencies:
       '@babel/core': 7.25.7
@@ -22252,15 +21770,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color

-  '@babel/register@7.28.6(@babel/core@7.29.7)':
-    dependencies:
-      '@babel/core': 7.29.7
-      clone-deep: 4.0.1
-      find-cache-dir: 2.1.0
-      make-dir: 2.1.0
-      pirates: 4.0.7
-      source-map-support: 0.5.21
-
   '@babel/runtime@7.25.7':
     dependencies:
       regenerator-runtime: 0.14.1
@@ -22930,8 +22439,6 @@ snapshots:

   '@inquirer/ansi@1.0.2': {}

-  '@inquirer/ansi@2.0.7': {}
-
   '@inquirer/checkbox@4.3.2(@types/node@24.12.2)':
     dependencies:
       '@inquirer/ansi': 1.0.2
@@ -22954,13 +22461,6 @@ snapshots:
     optionalDependencies:
       '@types/node': 24.12.2

-  '@inquirer/confirm@6.2.0(@types/node@24.12.2)':
-    dependencies:
-      '@inquirer/core': 12.0.0(@types/node@24.12.2)
-      '@inquirer/type': 4.0.7(@types/node@24.12.2)
-    optionalDependencies:
-      '@types/node': 24.12.2
-
   '@inquirer/core@10.3.2(@types/node@24.12.2)':
     dependencies:
       '@inquirer/ansi': 1.0.2
@@ -22974,18 +22474,6 @@ snapshots:
     optionalDependencies:
       '@types/node': 24.12.2

-  '@inquirer/core@12.0.0(@types/node@24.12.2)':
-    dependencies:
-      '@inquirer/ansi': 2.0.7
-      '@inquirer/figures': 2.0.8
-      '@inquirer/type': 4.0.7(@types/node@24.12.2)
-      cli-width: 4.1.0
-      fast-wrap-ansi: 0.2.2
-      mute-stream: 3.0.0
-      signal-exit: 4.1.0
-    optionalDependencies:
-      '@types/node': 24.12.2
-
   '@inquirer/core@9.2.1':
     dependencies:
       '@inquirer/figures': 1.0.15
@@ -23026,8 +22514,6 @@ snapshots:

   '@inquirer/figures@1.0.15': {}

-  '@inquirer/figures@2.0.8': {}
-
   '@inquirer/input@2.3.0':
     dependencies:
       '@inquirer/core': 9.2.1
@@ -23055,14 +22541,6 @@ snapshots:
     optionalDependencies:
       '@types/node': 24.12.2

-  '@inquirer/password@5.1.2(@types/node@24.12.2)':
-    dependencies:
-      '@inquirer/ansi': 2.0.7
-      '@inquirer/core': 12.0.0(@types/node@24.12.2)
-      '@inquirer/type': 4.0.7(@types/node@24.12.2)
-    optionalDependencies:
-      '@types/node': 24.12.2
-
   '@inquirer/prompts@7.10.1(@types/node@24.12.2)':
     dependencies:
       '@inquirer/checkbox': 4.3.2(@types/node@24.12.2)
@@ -23125,10 +22603,6 @@ snapshots:
     optionalDependencies:
       '@types/node': 24.12.2

-  '@inquirer/type@4.0.7(@types/node@24.12.2)':
-    optionalDependencies:
-      '@types/node': 24.12.2
-
   '@isaacs/cliui@8.0.2':
     dependencies:
       string-width: 5.1.2
@@ -32784,10 +32258,6 @@ snapshots:

   aria-query@5.3.2: {}

-  arr-diff@4.0.0: {}
-
-  arr-flatten@1.1.0: {}
-
   arr-union@3.1.0: {}

   array-buffer-byte-length@1.0.2:
@@ -32824,8 +32294,6 @@ snapshots:

   array-uniq@1.0.3: {}

-  array-unique@0.3.2: {}
-
   array.prototype.find@2.2.3:
     dependencies:
       call-bind: 1.0.9
@@ -32891,8 +32359,6 @@ snapshots:

   assertion-error@2.0.1: {}

-  assign-symbols@1.0.0: {}
-
   ast-types-flow@0.0.8: {}

   ast-types@0.13.4:
@@ -32928,8 +32394,6 @@ snapshots:

   asynckit@0.4.0: {}

-  atob@2.1.2: {}
-
   atomically@2.1.1:
     dependencies:
       stubborn-fs: 2.0.0
@@ -32996,10 +32460,6 @@ snapshots:

   b4a@1.8.0: {}

-  babel-core@7.0.0-bridge.0(@babel/core@7.29.7):
-    dependencies:
-      '@babel/core': 7.29.7
-
   babel-jest@29.5.0(@babel/core@7.25.7):
     dependencies:
       '@babel/core': 7.25.7
@@ -33234,16 +32694,6 @@ snapshots:

   base64-js@1.5.1: {}

-  base@0.11.2:
-    dependencies:
-      cache-base: 1.0.1
-      class-utils: 0.3.6
-      component-emitter: 1.3.1
-      define-property: 1.0.0
-      isobject: 3.0.1
-      mixin-deep: 1.3.2
-      pascalcase: 0.1.1
-
   baseline-browser-mapping@2.11.27: {}

   basic-ftp@5.3.1: {}
@@ -33316,21 +32766,6 @@ snapshots:
     dependencies:
       balanced-match: 4.0.4

-  braces@2.3.2:
-    dependencies:
-      arr-flatten: 1.1.0
-      array-unique: 0.3.2
-      extend-shallow: 2.0.1
-      fill-range: 4.0.0
-      isobject: 3.0.1
-      repeat-element: 1.1.4
-      snapdragon: 0.8.2
-      snapdragon-node: 2.1.1
-      split-string: 3.1.0
-      to-regex: 3.0.2
-    transitivePeerDependencies:
-      - supports-color
-
   braces@3.0.3:
     dependencies:
       fill-range: 7.1.1
@@ -33439,18 +32874,6 @@ snapshots:
       yargs: 16.2.0
       yargs-parser: 20.2.9

-  cache-base@1.0.1:
-    dependencies:
-      collection-visit: 1.0.0
-      component-emitter: 1.3.1
-      get-value: 2.0.6
-      has-value: 1.0.0
-      isobject: 3.0.1
-      set-value: 2.0.1
-      to-object-path: 0.3.0
-      union-value: 1.0.1
-      unset-value: 1.0.0
-
   cacheable-lookup@5.0.4: {}

   cacheable-lookup@7.0.0: {}
@@ -33694,13 +33117,6 @@ snapshots:

   cjs-module-lexer@1.4.3: {}

-  class-utils@0.3.6:
-    dependencies:
-      arr-union: 3.1.0
-      define-property: 0.2.5
-      isobject: 3.0.1
-      static-extend: 0.1.2
-
   classnames@2.5.1: {}

   clean-css@5.3.3:
@@ -33824,11 +33240,6 @@ snapshots:

   collect-v8-coverage@1.0.3: {}

-  collection-visit@1.0.0:
-    dependencies:
-      map-visit: 1.0.0
-      object-visit: 1.0.1
-
   color-convert@1.9.3:
     dependencies:
       color-name: 1.1.3
@@ -34007,8 +33418,6 @@ snapshots:
     dependencies:
       is-what: 4.1.16

-  copy-descriptor@0.1.1: {}
-
   copy-dir@1.3.0: {}

   copy-webpack-plugin@10.2.4(webpack@5.97.1):
@@ -34458,8 +33867,6 @@ snapshots:

   decimal.js@10.6.0: {}

-  decode-uri-component@0.2.2: {}
-
   decompress-response@6.0.0:
     dependencies:
       mimic-response: 3.1.0
@@ -34548,19 +33955,6 @@ snapshots:
       has-property-descriptors: 1.0.2
       object-keys: 1.1.1

-  define-property@0.2.5:
-    dependencies:
-      is-descriptor: 0.1.7
-
-  define-property@1.0.0:
-    dependencies:
-      is-descriptor: 1.0.3
-
-  define-property@2.0.2:
-    dependencies:
-      is-descriptor: 1.0.3
-      isobject: 3.0.1
-
   degenerator@5.0.1:
     dependencies:
       ast-types: 0.13.4
@@ -35467,18 +34861,6 @@ snapshots:

   exit@0.1.2: {}

-  expand-brackets@2.1.4:
-    dependencies:
-      debug: 2.6.9
-      define-property: 0.2.5
-      extend-shallow: 2.0.1
-      posix-character-classes: 0.1.1
-      regex-not: 1.0.2
-      snapdragon: 0.8.2
-      to-regex: 3.0.2
-    transitivePeerDependencies:
-      - supports-color
-
   expand-tilde@1.2.2:
     dependencies:
       os-homedir: 1.0.2
@@ -35537,11 +34919,6 @@ snapshots:
     dependencies:
       is-extendable: 0.1.1

-  extend-shallow@3.0.2:
-    dependencies:
-      assign-symbols: 1.0.0
-      is-extendable: 1.0.1
-
   extend@3.0.2: {}

   external-editor@3.1.0:
@@ -35550,19 +34927,6 @@ snapshots:
       iconv-lite: 0.4.24
       tmp: 0.0.33

-  extglob@2.0.4:
-    dependencies:
-      array-unique: 0.3.2
-      define-property: 1.0.0
-      expand-brackets: 2.1.4
-      extend-shallow: 2.0.1
-      fragment-cache: 0.2.1
-      regex-not: 1.0.2
-      snapdragon: 0.8.2
-      to-regex: 3.0.2
-    transitivePeerDependencies:
-      - supports-color
-
   extract-zip@1.7.0:
     dependencies:
       concat-stream: 1.6.2
@@ -35621,18 +34985,8 @@ snapshots:

   fast-sort@3.4.1: {}

-  fast-string-truncated-width@3.0.3: {}
-
-  fast-string-width@3.0.2:
-    dependencies:
-      fast-string-truncated-width: 3.0.3
-
   fast-uri@3.1.5: {}

-  fast-wrap-ansi@0.2.2:
-    dependencies:
-      fast-string-width: 3.0.2
-
   fast-xml-builder@1.3.1:
     dependencies:
       path-expression-matcher: 1.6.2
@@ -35715,13 +35069,6 @@ snapshots:
       strip-outer: 1.0.1
       trim-repeated: 1.0.0

-  fill-range@4.0.0:
-    dependencies:
-      extend-shallow: 2.0.1
-      is-number: 3.0.0
-      repeat-string: 1.6.1
-      to-regex-range: 2.1.1
-
   fill-range@7.1.1:
     dependencies:
       to-regex-range: 5.0.1
@@ -35738,12 +35085,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color

-  find-cache-dir@2.1.0:
-    dependencies:
-      commondir: 1.0.1
-      make-dir: 2.1.0
-      pkg-dir: 3.0.0
-
   find-cache-dir@3.3.2:
     dependencies:
       commondir: 1.0.1
@@ -35847,8 +35188,6 @@ snapshots:

   flatted@3.4.2: {}

-  flow-parser@0.309.0: {}
-
   follow-redirects@1.16.0(debug@4.4.3):
     optionalDependencies:
       debug: 4.4.3(supports-color@9.4.0)
@@ -35934,10 +35273,6 @@ snapshots:

   fraction.js@5.3.4: {}

-  fragment-cache@0.2.1:
-    dependencies:
-      map-cache: 0.2.2
-
   framer-motion@10.18.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
     dependencies:
       tslib: 2.8.1
@@ -36073,8 +35408,6 @@ snapshots:
     transitivePeerDependencies:
       - supports-color

-  get-value@2.0.6: {}
-
   getobject@1.0.2: {}

   gettext-parser@1.4.0:
@@ -36450,25 +35783,6 @@ snapshots:
     dependencies:
       has-symbols: 1.1.0

-  has-value@0.3.1:
-    dependencies:
-      get-value: 2.0.6
-      has-values: 0.1.4
-      isobject: 2.1.0
-
-  has-value@1.0.0:
-    dependencies:
-      get-value: 2.0.6
-      has-values: 1.0.0
-      isobject: 3.0.1
-
-  has-values@0.1.4: {}
-
-  has-values@1.0.0:
-    dependencies:
-      is-number: 3.0.0
-      kind-of: 4.0.0
-
   hash.js@1.1.7:
     dependencies:
       inherits: 2.0.4
@@ -36861,10 +36175,6 @@ snapshots:
       is-relative: 1.0.0
       is-windows: 1.0.2

-  is-accessor-descriptor@1.0.1:
-    dependencies:
-      hasown: 2.0.2
-
   is-arguments@1.2.0:
     dependencies:
       call-bound: 1.0.4
@@ -36916,10 +36226,6 @@ snapshots:
     dependencies:
       hasown: 2.0.2

-  is-data-descriptor@1.0.1:
-    dependencies:
-      hasown: 2.0.2
-
   is-data-view@1.0.2:
     dependencies:
       call-bound: 1.0.4
@@ -36931,16 +36237,6 @@ snapshots:
       call-bound: 1.0.4
       has-tostringtag: 1.0.2

-  is-descriptor@0.1.7:
-    dependencies:
-      is-accessor-descriptor: 1.0.1
-      is-data-descriptor: 1.0.1
-
-  is-descriptor@1.0.3:
-    dependencies:
-      is-accessor-descriptor: 1.0.1
-      is-data-descriptor: 1.0.1
-
   is-docker@2.2.1: {}

   is-docker@3.0.0: {}
@@ -36949,10 +36245,6 @@ snapshots:

   is-extendable@0.1.1: {}

-  is-extendable@1.0.1:
-    dependencies:
-      is-plain-object: 2.0.4
-
   is-extglob@2.1.1: {}

   is-finalizationregistry@1.1.1:
@@ -37007,10 +36299,6 @@ snapshots:
       call-bound: 1.0.4
       has-tostringtag: 1.0.2

-  is-number@3.0.0:
-    dependencies:
-      kind-of: 3.2.2
-
   is-number@7.0.0: {}

   is-obj@1.0.1:
@@ -37135,10 +36423,6 @@ snapshots:

   isexe@3.1.5: {}

-  isobject@2.1.0:
-    dependencies:
-      isarray: 1.0.0
-
   isobject@3.0.1: {}

   isomorphic-git@1.37.6:
@@ -37746,31 +37030,6 @@ snapshots:
     dependencies:
       argparse: 2.0.1

-  jscodeshift@0.13.1(@babel/preset-env@7.25.7(@babel/core@7.29.7)):
-    dependencies:
-      '@babel/core': 7.29.7
-      '@babel/parser': 7.29.2
-      '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.29.7)
-      '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.29.7)
-      '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.29.7)
-      '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.7)
-      '@babel/preset-env': 7.25.7(@babel/core@7.29.7)
-      '@babel/preset-flow': 7.27.1(@babel/core@7.29.7)
-      '@babel/preset-typescript': 7.25.7(@babel/core@7.29.7)
-      '@babel/register': 7.28.6(@babel/core@7.29.7)
-      babel-core: 7.0.0-bridge.0(@babel/core@7.29.7)
-      chalk: 4.1.2
-      flow-parser: 0.309.0
-      graceful-fs: 4.2.11
-      micromatch: 3.1.10
-      neo-async: 2.6.2
-      node-dir: 0.1.17
-      recast: 0.20.5
-      temp: 0.8.4
-      write-file-atomic: 2.4.3
-    transitivePeerDependencies:
-      - supports-color
-
   jsdoc-type-pratt-parser@4.1.0: {}

   jsdom@20.0.3:
@@ -37943,10 +37202,6 @@ snapshots:
     dependencies:
       is-buffer: 1.1.6

-  kind-of@4.0.0:
-    dependencies:
-      is-buffer: 1.1.6
-
   kind-of@6.0.3: {}

   kleur@3.0.3: {}
@@ -38385,10 +37640,6 @@ snapshots:

   map-values@1.0.1: {}

-  map-visit@1.0.0:
-    dependencies:
-      object-visit: 1.0.1
-
   markdown-it@12.3.2:
     dependencies:
       argparse: 2.0.1
@@ -38523,24 +37774,6 @@ snapshots:

   methods@1.1.2: {}

-  micromatch@3.1.10:
-    dependencies:
-      arr-diff: 4.0.0
-      array-unique: 0.3.2
-      braces: 2.3.2
-      define-property: 2.0.2
-      extend-shallow: 3.0.2
-      extglob: 2.0.4
-      fragment-cache: 0.2.1
-      kind-of: 6.0.3
-      nanomatch: 1.2.13
-      object.pick: 1.3.0
-      regex-not: 1.0.2
-      snapdragon: 0.8.2
-      to-regex: 3.0.2
-    transitivePeerDependencies:
-      - supports-color
-
   micromatch@4.0.8:
     dependencies:
       braces: 3.0.3
@@ -38618,11 +37851,6 @@ snapshots:

   mitt@3.0.1: {}

-  mixin-deep@1.3.2:
-    dependencies:
-      for-in: 1.0.2
-      is-extendable: 1.0.1
-
   mixin-object@2.0.1:
     dependencies:
       for-in: 0.1.8
@@ -38692,28 +37920,10 @@ snapshots:

   mute-stream@2.0.0: {}

-  mute-stream@3.0.0: {}
-
   nan@2.26.2: {}

   nanoid@3.3.18: {}

-  nanomatch@1.2.13:
-    dependencies:
-      arr-diff: 4.0.0
-      array-unique: 0.3.2
-      define-property: 2.0.2
-      extend-shallow: 3.0.2
-      fragment-cache: 0.2.1
-      is-windows: 1.0.2
-      kind-of: 6.0.3
-      object.pick: 1.3.0
-      regex-not: 1.0.2
-      snapdragon: 0.8.2
-      to-regex: 3.0.2
-    transitivePeerDependencies:
-      - supports-color
-
   napi-postinstall@0.3.4: {}

   native-promise-only@0.8.1: {}
@@ -38907,12 +38117,6 @@ snapshots:

   object-assign@4.1.1: {}

-  object-copy@0.1.0:
-    dependencies:
-      copy-descriptor: 0.1.1
-      define-property: 0.2.5
-      kind-of: 3.2.2
-
   object-filter@1.0.2: {}

   object-inspect@1.13.4: {}
@@ -38928,10 +38132,6 @@ snapshots:

   object-treeify@4.0.1: {}

-  object-visit@1.0.1:
-    dependencies:
-      isobject: 3.0.1
-
   object.assign@4.1.7:
     dependencies:
       call-bind: 1.0.9
@@ -39343,8 +38543,6 @@ snapshots:
       no-case: 3.0.4
       tslib: 2.8.1

-  pascalcase@0.1.1: {}
-
   password-prompt@1.1.3:
     dependencies:
       ansi-escapes: 4.3.2
@@ -39473,10 +38671,6 @@ snapshots:

   pirates@4.0.7: {}

-  pkg-dir@3.0.0:
-    dependencies:
-      find-up: 3.0.0
-
   pkg-dir@4.2.0:
     dependencies:
       find-up: 4.1.0
@@ -39497,8 +38691,6 @@ snapshots:
     dependencies:
       irregular-plurals: 3.5.0

-  posix-character-classes@0.1.1: {}
-
   possible-typed-array-names@1.1.0: {}

   postcode-validator@3.9.2: {}
@@ -40645,13 +39837,6 @@ snapshots:
       reakit-utils: 0.15.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
       reakit-warning: 0.6.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)

-  recast@0.20.5:
-    dependencies:
-      ast-types: 0.14.2
-      esprima: 4.0.1
-      source-map: 0.6.1
-      tslib: 2.8.1
-
   recast@0.23.11:
     dependencies:
       ast-types: 0.16.1
@@ -40713,11 +39898,6 @@ snapshots:

   regenerator-runtime@0.14.1: {}

-  regex-not@1.0.2:
-    dependencies:
-      extend-shallow: 3.0.2
-      safe-regex: 1.1.0
-
   regexp.prototype.flags@1.5.4:
     dependencies:
       call-bind: 1.0.9
@@ -40775,10 +39955,6 @@ snapshots:
       lodash: 4.18.1
       strip-ansi: 6.0.1

-  repeat-element@1.1.4: {}
-
-  repeat-string@1.6.1: {}
-
   repeating@2.0.1:
     dependencies:
       is-finite: 1.1.0
@@ -40841,8 +40017,6 @@ snapshots:

   resolve-pkg-maps@1.0.0: {}

-  resolve-url@0.2.1: {}
-
   resolve.exports@2.0.3: {}

   resolve@1.22.12:
@@ -40874,8 +40048,6 @@ snapshots:
       onetime: 5.1.2
       signal-exit: 3.0.7

-  ret@0.1.15: {}
-
   retry@0.13.1: {}

   reusify@1.1.0: {}
@@ -40884,10 +40056,6 @@ snapshots:

   rgb@0.1.0: {}

-  rimraf@2.6.3:
-    dependencies:
-      glob: 7.2.3
-
   rimraf@2.7.1:
     dependencies:
       glob: 7.2.3
@@ -40973,10 +40141,6 @@ snapshots:
       es-errors: 1.3.0
       is-regex: 1.2.1

-  safe-regex@1.1.0:
-    dependencies:
-      ret: 0.1.15
-
   safe-stable-stringify@2.5.0: {}

   safer-buffer@2.1.2: {}
@@ -41240,13 +40404,6 @@ snapshots:
       es-errors: 1.3.0
       es-object-atoms: 1.1.1

-  set-value@2.0.1:
-    dependencies:
-      extend-shallow: 2.0.1
-      is-extendable: 0.1.1
-      is-plain-object: 2.0.4
-      split-string: 3.1.0
-
   setprototypeof@1.2.0: {}

   sha.js@2.4.12:
@@ -41414,29 +40571,6 @@ snapshots:
       dot-case: 3.0.4
       tslib: 2.8.1

-  snapdragon-node@2.1.1:
-    dependencies:
-      define-property: 1.0.0
-      isobject: 3.0.1
-      snapdragon-util: 3.0.1
-
-  snapdragon-util@3.0.1:
-    dependencies:
-      kind-of: 3.2.2
-
-  snapdragon@0.8.2:
-    dependencies:
-      base: 0.11.2
-      debug: 2.6.9
-      define-property: 0.2.5
-      extend-shallow: 2.0.1
-      map-cache: 0.2.2
-      source-map: 0.5.7
-      source-map-resolve: 0.5.3
-      use: 3.1.1
-    transitivePeerDependencies:
-      - supports-color
-
   sockjs@0.3.24:
     dependencies:
       faye-websocket: 0.11.4
@@ -41467,14 +40601,6 @@ snapshots:
       source-map-js: 1.2.1
       webpack: 5.97.1(@swc/core@1.15.24)(esbuild@0.27.7)(uglify-js@3.19.3)(webpack-cli@5.1.4)

-  source-map-resolve@0.5.3:
-    dependencies:
-      atob: 2.1.2
-      decode-uri-component: 0.2.2
-      resolve-url: 0.2.1
-      source-map-url: 0.4.1
-      urix: 0.1.0
-
   source-map-support@0.5.13:
     dependencies:
       buffer-from: 1.1.2
@@ -41485,8 +40611,6 @@ snapshots:
       buffer-from: 1.1.2
       source-map: 0.6.1

-  source-map-url@0.4.1: {}
-
   source-map@0.5.7: {}

   source-map@0.6.1: {}
@@ -41546,10 +40670,6 @@ snapshots:
       image-ssim: 0.2.0
       jpeg-js: 0.4.4

-  split-string@3.1.0:
-    dependencies:
-      extend-shallow: 3.0.2
-
   sprintf-js@1.0.3: {}

   sprintf-js@1.1.3: {}
@@ -41564,11 +40684,6 @@ snapshots:

   stackframe@1.3.4: {}

-  static-extend@0.1.2:
-    dependencies:
-      define-property: 0.2.5
-      object-copy: 0.1.0
-
   statuses@1.5.0: {}

   statuses@2.0.2: {}
@@ -42075,10 +41190,6 @@ snapshots:

   temml@0.10.34: {}

-  temp@0.8.4:
-    dependencies:
-      rimraf: 2.6.3
-
   temporal-polyfill@1.0.4:
     dependencies:
       temporal-spec: 1.0.1
@@ -42211,26 +41322,10 @@ snapshots:
       safe-buffer: 5.2.1
       typed-array-buffer: 1.0.3

-  to-object-path@0.3.0:
-    dependencies:
-      kind-of: 3.2.2
-
-  to-regex-range@2.1.1:
-    dependencies:
-      is-number: 3.0.0
-      repeat-string: 1.6.1
-
   to-regex-range@5.0.1:
     dependencies:
       is-number: 7.0.0

-  to-regex@3.0.2:
-    dependencies:
-      define-property: 2.0.2
-      extend-shallow: 3.0.2
-      regex-not: 1.0.2
-      safe-regex: 1.1.0
-
   toidentifier@1.0.1: {}

   totalist@3.0.1: {}
@@ -42508,13 +41603,6 @@ snapshots:

   unicode-property-aliases-ecmascript@2.2.0: {}

-  union-value@1.0.1:
-    dependencies:
-      arr-union: 3.1.0
-      get-value: 2.0.6
-      is-extendable: 0.1.1
-      set-value: 2.0.1
-
   unique-string@1.0.0:
     dependencies:
       crypto-random-string: 1.0.0
@@ -42566,11 +41654,6 @@ snapshots:
       '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1
       '@unrs/resolver-binding-win32-x64-msvc': 1.11.1

-  unset-value@1.0.0:
-    dependencies:
-      has-value: 0.3.1
-      isobject: 3.0.1
-
   unzip-response@2.0.1:
     optional: true

@@ -42620,8 +41703,6 @@ snapshots:

   uri-path@1.0.0: {}

-  urix@0.1.0: {}
-
   url-loader@4.1.1(file-loader@6.2.0(webpack@5.97.1))(webpack@5.97.1):
     dependencies:
       loader-utils: 2.0.4
@@ -42688,8 +41769,6 @@ snapshots:
     dependencies:
       react: 18.3.1

-  use@3.1.1: {}
-
   usehooks-ts@2.16.0(react@18.3.1):
     dependencies:
       lodash.debounce: 4.0.8
@@ -43231,6 +42310,7 @@ snapshots:
       graceful-fs: 4.2.11
       imurmurhash: 0.1.4
       signal-exit: 3.0.7
+    optional: true

   write-file-atomic@4.0.2:
     dependencies:
diff --git a/tools/monorepo-merge/README.md b/tools/monorepo-merge/README.md
new file mode 100644
index 00000000000..d3017e6ed4f
--- /dev/null
+++ b/tools/monorepo-merge/README.md
@@ -0,0 +1,50 @@
+# monorepo-merge
+
+Brings another GitHub repository into the WooCommerce monorepo: `merge` imports its code with full history, and `transfer-issues` moves its issues across. It is rarely needed, so read this before running it.
+
+Because it can go years between runs, have an LLM review `bin/` and `src/` before you use it. Ask it to check for changes to the GitHub GraphQL API, `git`, or `git-filter-repo` that affect the import or the issue transfer, and for improvements worth making first. Then run `transfer-issues` with `--dry-run` before the real transfer.
+
+## Requirements
+
+- macOS or Linux with Node 24. The tool has no runtime dependencies; `pnpm install` is only needed to run its tests.
+- For `merge`: `git` and [`git-filter-repo`](https://github.com/newren/git-filter-repo) (for example, `brew install git-filter-repo`).
+- For `transfer-issues`: a GitHub token with the `repo` and `project` scopes, read from `GH_TOKEN`, `GITHUB_TOKEN`, or `gh auth token`. Add the `project` scope to a `gh` login with `gh auth refresh -s project`.
+
+Run `tools/monorepo-merge/bin/monorepo-merge.mjs --help` for the full usage.
+
+## Merging a repository
+
+Start from an up-to-date branch off `trunk`, then run:
+
+```bash
+tools/monorepo-merge/bin/monorepo-merge.mjs merge woocommerce/example-repo packages/js/example --branch trunk
+```
+
+The command clones the repository into a temporary directory, rewrites its history so every file lives under the destination path, and merges it with `git merge --allow-unrelated-histories` into the current branch of the checkout that contains this tool, wherever you run it from. It stops unless that checkout's root is two directories above `tools/monorepo-merge`. To try it without touching your monorepo checkout, copy the tool to `tools/monorepo-merge` inside a scratch git repository, so the scratch repository becomes the target, and run it from there. In commit messages, `(#123)` becomes a link to the source repository's pull request and a standalone `#123` becomes `woocommerce/example-repo#123`, so old references keep pointing at the right place. References that are already qualified, such as `other/repo#12`, are left alone. The source repository's tags are not imported. `--branch` is the source branch to import and defaults to `main`.
+
+Merge the resulting pull request with a merge commit. Squashing it discards the imported history.
+
+### After the merge
+
+The imported code still uses its own text domain for translations. Change it to `woocommerce` in a follow-up commit on the same branch:
+
+- JavaScript: once the imported package uses the monorepo's ESLint config, run ESLint with `--fix`. The `@wordpress/i18n-text-domain` rule in `@woocommerce/eslint-plugin`'s recommended config replaces other text domains with `woocommerce` and adds missing ones.
+- PHP: add the WPCS `WordPress.Utils.I18nTextDomainFixer` sniff to the package's `phpcs.xml`, with `old_text_domain` set to the imported text domain and `new_text_domain` set to `woocommerce`, then run `phpcbf`. `packages/php/woocommerce-analytics/phpcs.xml` shows the syntax. The root `phpcs.xml` reports other text domains but does not fix them.
+
+## Transferring issues
+
+```bash
+tools/monorepo-merge/bin/monorepo-merge.mjs transfer-issues woocommerce/example-repo --labels "focus: example"
+```
+
+Transfers the issues matching `--searchFilter` (default `is:open`) into `--destination` (default `woocommerce/woocommerce`). Both repositories must have the same owner. After the transfer it restores any GitHub Projects field values that did not carry over and adds the `--labels` you list, which must already exist in the destination. If any transfer or post-transfer step fails, the command lists the failures and exits with a non-zero status.
+
+Transfers cannot be undone, and the command asks for confirmation twice. Add `--dry-run` first to run every lookup and list the issues it would transfer, without prompting or changing anything. GitHub search returns at most 1,000 issues per query, so run the command again to move the rest. Transferred issues no longer match the search. A `--searchFilter` value that starts with `-` must use the `--searchFilter=-label:example` form.
+
+## Development
+
+```bash
+pnpm --filter=monorepo-merge test
+```
+
+Runs the unit tests, then ESLint. The commit message tests need `python3`.
diff --git a/tools/monorepo-merge/bin/dev b/tools/monorepo-merge/bin/dev
deleted file mode 100755
index 414f2932634..00000000000
--- a/tools/monorepo-merge/bin/dev
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/usr/bin/env node
-
-const path = require('path')
-const project = path.join(__dirname, '..', 'tsconfig.json')
-
-require('ts-node').register({project})
-
-;(async () => {
-  const oclif = await import('@oclif/core')
-  await oclif.execute({development: true, dir: __dirname})
-})()
diff --git a/tools/monorepo-merge/bin/dev.cmd b/tools/monorepo-merge/bin/dev.cmd
deleted file mode 100644
index 8ae2b12c192..00000000000
--- a/tools/monorepo-merge/bin/dev.cmd
+++ /dev/null
@@ -1,3 +0,0 @@
-@echo off
-
-node "%~dp0\dev" %*
diff --git a/tools/monorepo-merge/bin/monorepo-merge.mjs b/tools/monorepo-merge/bin/monorepo-merge.mjs
new file mode 100755
index 00000000000..248fb9fe091
--- /dev/null
+++ b/tools/monorepo-merge/bin/monorepo-merge.mjs
@@ -0,0 +1,82 @@
+#!/usr/bin/env node
+/**
+ * External dependencies
+ */
+import { parseArgs } from 'node:util';
+
+/**
+ * Internal dependencies
+ */
+import { closePrompts } from '../src/confirm.mjs';
+import { merge } from '../src/merge.mjs';
+import { transferIssues } from '../src/transfer-issues.mjs';
+
+const USAGE = `Usage:
+  monorepo-merge merge <org/repo> <destination> [--branch <branch>]
+    Merges a GitHub repository and its history into <destination> in this monorepo.
+    Requires git and git-filter-repo. --branch defaults to "main".
+
+  monorepo-merge transfer-issues <org/repo> [--destination <org/repo>] [--searchFilter <filter>] [--labels <a,b>] [--dry-run]
+    Transfers issues into --destination (default "woocommerce/woocommerce").
+    --searchFilter defaults to "is:open". --labels are added after the transfer.
+    --dry-run runs every lookup and lists what would be transferred, without prompting or changing anything.
+    The token comes from GH_TOKEN, GITHUB_TOKEN, or "gh auth token" and needs the repo and project scopes.`;
+
+const COMMANDS = {
+	merge: {
+		arguments: 2,
+		options: { branch: { type: 'string', default: 'main' } },
+		run: ( [ source, destination ], options ) =>
+			merge( source, destination, options ),
+	},
+	'transfer-issues': {
+		arguments: 1,
+		options: {
+			destination: { type: 'string', default: 'woocommerce/woocommerce' },
+			searchFilter: { type: 'string', default: 'is:open' },
+			labels: { type: 'string' },
+			'dry-run': { type: 'boolean', default: false },
+		},
+		run: ( [ source ], options ) =>
+			transferIssues( source, {
+				...options,
+				dryRun: options[ 'dry-run' ],
+			} ),
+	},
+};
+
+const [ name, ...args ] = process.argv.slice( 2 );
+const command = Object.hasOwn( COMMANDS, name ) ? COMMANDS[ name ] : undefined;
+
+try {
+	if ( ! command ) {
+		if ( name && ! [ '--help', '-h', 'help' ].includes( name ) ) {
+			throw new Error( `Unknown command "${ name }"\n\n${ USAGE }` );
+		}
+		console.log( USAGE );
+	} else {
+		const { values, positionals } = parseArgs( {
+			args,
+			options: {
+				...command.options,
+				help: { type: 'boolean', short: 'h' },
+			},
+			allowPositionals: true,
+		} );
+
+		if ( values.help ) {
+			console.log( USAGE );
+		} else if ( positionals.length !== command.arguments ) {
+			throw new Error(
+				`"${ name }" expects ${ command.arguments } argument(s)\n\n${ USAGE }`
+			);
+		} else {
+			await command.run( positionals, values );
+		}
+	}
+} catch ( error ) {
+	console.error( `Error: ${ error.message }` );
+	process.exitCode = error.exitCode ?? 1;
+} finally {
+	closePrompts();
+}
diff --git a/tools/monorepo-merge/bin/run b/tools/monorepo-merge/bin/run
deleted file mode 100755
index 200d8d82153..00000000000
--- a/tools/monorepo-merge/bin/run
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env node
-
-;(async () => {
-  const oclif = await import('@oclif/core')
-  await oclif.execute({dir: __dirname})
-})()
diff --git a/tools/monorepo-merge/bin/run.cmd b/tools/monorepo-merge/bin/run.cmd
deleted file mode 100644
index 968fc30758e..00000000000
--- a/tools/monorepo-merge/bin/run.cmd
+++ /dev/null
@@ -1,3 +0,0 @@
-@echo off
-
-node "%~dp0\run" %*
diff --git a/tools/monorepo-merge/codemods/rename-intl-domain.js b/tools/monorepo-merge/codemods/rename-intl-domain.js
deleted file mode 100644
index fca9818f092..00000000000
--- a/tools/monorepo-merge/codemods/rename-intl-domain.js
+++ /dev/null
@@ -1,21 +0,0 @@
-export const parser = 'tsx';
-
-const i18nFunctionsToRename = [ '__', '_n', '_x' ];
-
-const isFunctionNamed = ( path, names ) => {
-	return names.includes( path.value.callee.name );
-};
-
-export default function transformer( file, api ) {
-	const j = api.jscodeshift;
-
-	return j( file.source )
-		.find( j.CallExpression )
-		.filter( ( p ) => isFunctionNamed( p, i18nFunctionsToRename ) )
-		.forEach( ( path ) => {
-			j( path ) // Descend into each call expression to find any strings literal 'woocommerce-admin'
-				.find( j.StringLiteral, { value: 'woocommerce-admin' } )
-				.replaceWith( j.stringLiteral( 'woocommerce' ) );
-		} )
-		.toSource();
-}
diff --git a/tools/monorepo-merge/eslint.config.mjs b/tools/monorepo-merge/eslint.config.mjs
index 38330600dfa..a853487d3a3 100644
--- a/tools/monorepo-merge/eslint.config.mjs
+++ b/tools/monorepo-merge/eslint.config.mjs
@@ -1,11 +1,10 @@
-/**
- * External dependencies
- */
-import { globalIgnores } from 'eslint/config';
-
 /**
  * Internal dependencies
  */
 import woocommerce from '@woocommerce/eslint-config';

-export default [ ...woocommerce, globalIgnores( [ 'dist/**' ] ) ];
+export default [
+	...woocommerce,
+	// A command-line tool reports progress on the terminal.
+	{ rules: { 'no-console': 'off' } },
+];
diff --git a/tools/monorepo-merge/package.json b/tools/monorepo-merge/package.json
index b95b374ec5b..25eea312170 100644
--- a/tools/monorepo-merge/package.json
+++ b/tools/monorepo-merge/package.json
@@ -1,77 +1,38 @@
 {
 	"name": "monorepo-merge",
 	"version": "0.0.0",
+	"private": true,
 	"description": "A tool for merging repositories into the WooCommerce Monorepo.",
 	"author": "Automattic",
 	"bin": {
-		"monorepo-merge": "./bin/run"
+		"monorepo-merge": "./bin/monorepo-merge.mjs"
 	},
 	"homepage": "https://github.com/woocommerce/woocommerce",
 	"license": "GPL-2.0-or-later",
-	"main": "dist/index.js",
 	"repository": "woocommerce/woocommerce",
-	"files": [
-		"/bin",
-		"/dist",
-		"/npm-shrinkwrap.json",
-		"/oclif.manifest.json"
-	],
 	"scripts": {
-		"build": "shx rm -rf dist && tsc --project tsconfig.json",
 		"lint": "eslint .",
-		"prepack": "pnpm build && oclif manifest",
-		"postpack": "shx rm -f oclif.manifest.json",
 		"posttest": "pnpm lint",
-		"test": "pnpm build && node --test test/merge.test.mjs"
-	},
-	"dependencies": {
-		"@inquirer/confirm": "^6.1.1",
-		"@inquirer/password": "^5.1.1",
-		"@oclif/core": "^4.13.5",
-		"@oclif/plugin-help": "^6.2.58",
-		"@oclif/plugin-plugins": "^5.4.87",
-		"@octokit/graphql": "4.8.0"
+		"test": "node --test"
 	},
 	"devDependencies": {
-		"@octokit/request-error": "^3.0.3",
-		"@types/node": "^24.1.0",
 		"@woocommerce/eslint-config": "workspace:*",
-		"@woocommerce/eslint-plugin": "workspace:*",
-		"eslint": "^10.0.0",
-		"jscodeshift": "^0.13.1",
-		"oclif": "^4.23.30",
-		"shx": "^0.3.4",
-		"ts-node": "^10.9.2",
-		"tslib": "^2.6.2",
-		"typescript": "5.7.x"
-	},
-	"oclif": {
-		"bin": "monorepo-merge",
-		"dirname": "monorepo-merge",
-		"commands": "./dist/commands",
-		"plugins": [
-			"@oclif/plugin-help",
-			"@oclif/plugin-plugins"
-		],
-		"topicSeparator": " ",
-		"topics": {
-			"merge": {
-				"description": "Merges other repositories into the monorepo."
-			},
-			"transfer-issues": {
-				"description": "Transfers issues from other repositories into the monorepo."
-			}
-		}
+		"eslint": "^10.0.0"
 	},
 	"engines": {
 		"node": "^24.15.0"
 	},
-	"types": "dist/index.d.ts",
 	"config": {
 		"ci": {
 			"lint": {
 				"command": "lint",
-				"changes": "src/**/*.ts"
+				"changes": [
+					"bin/**",
+					"src/**",
+					"test/**",
+					"eslint.config.mjs",
+					"package.json"
+				]
 			},
 			"tests": [
 				{
@@ -79,9 +40,9 @@
 					"command": "test",
 					"changes": [
 						"bin/**",
-						"src/**/*.ts",
-						"test/**/*.mjs",
-						"tsconfig.json"
+						"src/**",
+						"test/**",
+						"package.json"
 					],
 					"events": [
 						"pull_request",
diff --git a/tools/monorepo-merge/src/commands/merge/index.ts b/tools/monorepo-merge/src/commands/merge/index.ts
deleted file mode 100644
index df6ac7de202..00000000000
--- a/tools/monorepo-merge/src/commands/merge/index.ts
+++ /dev/null
@@ -1,292 +0,0 @@
-/**
- * External dependencies
- */
-import { Args, Command, Flags, ux } from '@oclif/core';
-import { execFile } from 'child_process';
-import { rm } from 'fs/promises';
-import { isAbsolute, join, relative, resolve, sep } from 'path';
-import { tmpdir } from 'os';
-import { promisify } from 'util';
-
-/**
- * Internal dependencies
- */
-import { MONOREPO_ROOT } from '../../const';
-import { access, exec } from '../../node-async';
-
-const execFilePromisified = promisify( execFile );
-
-export default class Merge extends Command {
-	static description =
-		'Merges another repository into this one with history.';
-
-	static args = {
-		source: Args.string( {
-			description: 'The GitHub repository we are merging from.',
-			required: true,
-		} ),
-		destination: Args.string( {
-			description:
-				'The monorepo path for the repository to be merged at.',
-			required: true,
-		} ),
-	};
-
-	static flags = {
-		branch: Flags.string( {
-			description:
-				'The destination branch we want to merge into the monorepo.',
-			default: 'main',
-		} ),
-	};
-
-	/**
-	 * This method is called to execute the command.
-	 */
-	async run(): Promise< void > {
-		const { args, flags } = await this.parse( Merge );
-
-		await this.checkDependencies();
-		await this.validateArgs( args.source, args.destination );
-		const { default: confirm } = await import( '@inquirer/confirm' );
-
-		let confirmation = await confirm( {
-			message:
-				'WARNING: This command will DESTROY the history of your current branch. Are you sure you want to proceed?',
-			default: false,
-		} );
-		if ( ! confirmation ) {
-			this.exit( 0 );
-		}
-
-		const repositoryPath = await this.cloneRepository( args.source );
-		await this.alterRepositoryHistory(
-			args.source,
-			repositoryPath,
-			args.destination
-		);
-
-		confirmation = await confirm( {
-			message:
-				'Are you ready to merge ' +
-				args.source +
-				' from ' +
-				repositoryPath +
-				'?',
-			default: false,
-		} );
-		if ( ! confirmation ) {
-			// Remove the repository we've cloned.
-			try {
-				await rm( repositoryPath, { recursive: true, force: true } );
-			} catch {}
-
-			this.exit( 0 );
-		}
-
-		await this.mergeRepository( args.source, repositoryPath, flags.branch );
-
-		this.log(
-			'Successfully merged ' + args.source + ' into ' + args.destination
-		);
-	}
-
-	/**
-	 * Checks to make sure that all of the necessary dependencies to run the script are installed.
-	 */
-	private async checkDependencies(): Promise< void > {
-		try {
-			await exec( 'git --version' );
-		} catch {
-			this.error( '"git" must be installed' );
-		}
-
-		try {
-			await exec( 'git-filter-repo --version' );
-		} catch {
-			this.error( '"git-filter-repo" must be installed' );
-		}
-	}
-
-	/**
-	 * Validates all of the arguments to make sure they're compatible with the command.
-	 *
-	 * @param {string} source      The GitHub repository we are merging.
-	 * @param {string} destination The local path we're merging into.
-	 */
-	private async validateArgs(
-		source: string,
-		destination: string
-	): Promise< void > {
-		// We only support pulling from GitHub so the format needs to match that.
-		if ( ! source.match( /^[a-zA-Z0-9\-_]+\/[a-zA-Z0-9\-_]+$/ ) ) {
-			this.error(
-				'The "source" argument must be in "organization/repository" format'
-			);
-		}
-
-		const destinationPath = resolve( MONOREPO_ROOT, destination );
-		const relativeDestination = relative( MONOREPO_ROOT, destinationPath );
-		if (
-			isAbsolute( destination ) ||
-			relativeDestination === '' ||
-			relativeDestination === '..' ||
-			relativeDestination.startsWith( '..' + sep ) ||
-			isAbsolute( relativeDestination )
-		) {
-			this.error(
-				'The "destination" argument must point to a path inside the monorepo'
-			);
-		}
-
-		// We can't merge into a directory that already exists.
-		let exists = false;
-		try {
-			await access( destinationPath );
-			exists = true;
-		} catch {
-			exists = false;
-		}
-
-		if ( exists ) {
-			this.error(
-				'The "destination" argument points to a directory that already exists'
-			);
-		}
-	}
-
-	/**
-	 * Builds the git-filter-repo callback that updates imported references.
-	 *
-	 * Parenthesized references such as `(#123)` become pull request links.
-	 * Bare references such as `#123` are qualified with the source repository.
-	 *
-	 * @param {string} source The GitHub repository we are merging.
-	 */
-	private static createMessageCallback( source: string ): string {
-		const pullRequestPattern = String.raw`rb"\(#(?P<pull_request_number>\d+)\)"`;
-		const pullRequestReplacement = String.raw`rb"(https://github.com/${ source }/pull/\g<pull_request_number>)"`;
-		const issuePattern = String.raw`rb"(?<!\()#(?P<issue_number>\d+)(?!\))"`;
-		const issueReplacement = String.raw`rb"${ source }#\g<issue_number>"`;
-		const issueCallback = `re.sub(${ issuePattern }, ${ issueReplacement }, message)`;
-
-		return `return re.sub(${ pullRequestPattern }, ${ pullRequestReplacement }, ${ issueCallback })`;
-	}
-
-	/**
-	 * Clones a repository from GitHub into a temporary directory and returns the path.
-	 *
-	 * @param {string} source The GitHub repository we want to clone.
-	 */
-	private async cloneRepository( source: string ): Promise< string > {
-		// Show progress for the cloning.
-		const gitPath = 'https://github.com/' + source;
-		ux.action.start( 'Cloning from ' + gitPath );
-
-		// We need a fresh directory to clone the source into.
-		const cloneDir = join( tmpdir(), 'monorepo-merge', source );
-		try {
-			await rm( cloneDir, { recursive: true, force: true } );
-		} catch {}
-
-		await execFilePromisified( 'git', [ 'clone', gitPath, cloneDir ] );
-
-		ux.action.stop();
-		return cloneDir;
-	}
-
-	/**
-	 * Alters the commit history so that it appears as if it always existed within the monorepo.
-	 *
-	 * @param {string} source      The GitHub repository we are merging.
-	 * @param {string} cloneDir    The directory we've cloned the repository into.
-	 * @param {string} destination The monorepo directory we want to move the files into.
-	 */
-	private async alterRepositoryHistory(
-		source: string,
-		cloneDir: string,
-		destination: string
-	): Promise< void > {
-		const messageCallback = Merge.createMessageCallback( source );
-
-		ux.action.start( 'Altering repository history' );
-
-		try {
-			await execFilePromisified(
-				'git-filter-repo',
-				[
-					'--to-subdirectory-filter',
-					destination,
-					'--message-callback=' + messageCallback,
-				],
-				{ cwd: cloneDir }
-			);
-		} catch {
-			this.error( 'Failed to alter the repository history' );
-		} finally {
-			ux.action.stop();
-		}
-	}
-
-	/**
-	 * Merges the cloned repository into the current one.
-	 *
-	 * @param {string} source        The GitHub repository we are merging.
-	 * @param {string} cloneDir      The directory we've cloned the repository into.
-	 * @param {string} branchToMerge The branch we want to merge from.
-	 */
-	private async mergeRepository(
-		source: string,
-		cloneDir: string,
-		branchToMerge: string
-	): Promise< void > {
-		ux.action.start( 'Merging repositories' );
-
-		// We need the cloned repository as a remote in order to merge it.
-		try {
-			await execFilePromisified( 'git', [
-				'remote',
-				'add',
-				source,
-				cloneDir,
-			] );
-		} catch {
-			ux.action.stop();
-
-			this.error( 'Failed to add clone repository as remote' );
-		}
-
-		try {
-			await execFilePromisified( 'git', [ 'fetch', source ] );
-		} catch {
-			ux.action.stop();
-
-			this.error( 'Failed to fetch clone repository' );
-		}
-
-		try {
-			await execFilePromisified( 'git', [
-				'merge',
-				'--allow-unrelated-histories',
-				'--',
-				source + '/' + branchToMerge,
-			] );
-		} catch {
-			ux.action.stop();
-
-			this.error( 'Failed to merge the repositories' );
-		}
-
-		// We don't need the remote anymore.
-		try {
-			await execFilePromisified( 'git', [ 'remote', 'remove', source ] );
-			await rm( cloneDir, { recursive: true, force: true } );
-		} catch {
-			ux.action.stop();
-
-			this.error( 'Failed to remove clone repository remote' );
-		}
-
-		ux.action.stop();
-	}
-}
diff --git a/tools/monorepo-merge/src/commands/transfer-issues/index.ts b/tools/monorepo-merge/src/commands/transfer-issues/index.ts
deleted file mode 100644
index 2a1e53a9c1d..00000000000
--- a/tools/monorepo-merge/src/commands/transfer-issues/index.ts
+++ /dev/null
@@ -1,895 +0,0 @@
-/**
- * External dependencies
- */
-import { Args, Command, Flags, ux } from '@oclif/core';
-import { graphql, GraphqlResponseError } from '@octokit/graphql';
-import { RequestError } from '@octokit/request-error';
-
-/**
- * Described a label object.
- */
-interface GitHubLabel {
-	id: string;
-	name: string;
-}
-
-// These properties should match the UpdateProjectV2ItemFieldValueInput mutation input.
-interface GitHubProjectField {
-	projectId: string;
-	fieldId: string;
-	fieldName: string;
-	itemId: string;
-	dataType: string;
-	value: {
-		text?: string;
-		number?: number;
-		date?: string;
-		singleSelectOptionId?: string;
-		iterationId?: string;
-	};
-}
-
-/**
- * Describes an issue object.
- */
-interface GitHubIssue {
-	id: string;
-	newID?: string;
-	title: string;
-	projectFields: GitHubProjectField[];
-}
-
-export default class TransferIssues extends Command {
-	static description =
-		'Transfers issues from another repository into the destination repository.';
-
-	static args = {
-		source: Args.string( {
-			description: 'The GitHub repository we are transferring from.',
-			required: true,
-		} ),
-	};
-
-	static flags = {
-		destination: Flags.string( {
-			description: 'The destination repository to transfer into.',
-			default: 'woocommerce/woocommerce',
-		} ),
-		searchFilter: Flags.string( {
-			description:
-				'The search filter to apply when searching for issues to transfer.',
-			default: 'is:open',
-		} ),
-		labels: Flags.string( {
-			description:
-				'A comma-delimited list of labels that should be added to the issue post-migration.',
-		} ),
-	};
-
-	/**
-	 * This method is called to execute the command.
-	 */
-	async run(): Promise< void > {
-		const { args, flags } = await this.parse( TransferIssues );
-
-		const matches = flags.destination.match( /^([^/]+)\/([^/]+)$/ );
-		if ( ! matches ) {
-			this.error(
-				'The destination repository must be in the format "owner/repo"!'
-			);
-		}
-
-		const destinationOwner = matches[ 1 ];
-		const destinationRepo = matches[ 2 ];
-
-		if ( ! args.source.startsWith( destinationOwner + '/' ) ) {
-			this.error(
-				'The source and target repository must have the same owner!'
-			);
-		}
-		const { default: confirm } = await import( '@inquirer/confirm' );
-
-		let confirmation = await confirm( {
-			message:
-				'Are you sure you want to transfer issues from ' +
-				args.source +
-				' into the monorepo?',
-			default: false,
-		} );
-		if ( ! confirmation ) {
-			this.exit( 0 );
-		}
-
-		const authenticatedGraphQL = await this.authenticateGraphQL();
-
-		const numberOfIssues = await this.getNumberOfIssues(
-			authenticatedGraphQL,
-			args.source,
-			flags.searchFilter
-		);
-
-		if ( numberOfIssues === 0 ) {
-			this.log(
-				'There are no issues to transfer that match this query!'
-			);
-			this.exit( 0 );
-		}
-
-		confirmation = await confirm( {
-			message:
-				'This will transfer ' +
-				numberOfIssues +
-				' issue(s). There is no command to reverse this, are you sure?',
-			default: false,
-		} );
-		if ( ! confirmation ) {
-			this.exit( 0 );
-		}
-
-		const monorepoNodeID = await this.getMonorepoNodeID(
-			authenticatedGraphQL,
-			destinationOwner,
-			destinationRepo
-		);
-		const labelsToAdd = await this.getLabelsToAdd(
-			authenticatedGraphQL,
-			destinationOwner,
-			destinationRepo,
-			flags.labels
-		);
-		const issuesToTransfer = await this.getIssues(
-			authenticatedGraphQL,
-			args.source,
-			flags.searchFilter
-		);
-
-		let transferredIssues = 0;
-		for ( const issue of issuesToTransfer ) {
-			const newIssueID = await this.transferIssue(
-				authenticatedGraphQL,
-				issue,
-				monorepoNodeID
-			);
-
-			// Track the transferred issue.
-			if ( newIssueID ) {
-				issue.newID = newIssueID;
-				transferredIssues++;
-			}
-		}
-
-		// Wait for five seconds to label the issues. This is necessary so that GitHub
-		// can fully transfer the issue with the existing labels, since otherwise,
-		// we would replace the labels that would have been transferred.
-		ux.action.start( 'Waiting for GitHub to process transfers' );
-		await new Promise( ( resolve ) => setTimeout( resolve, 25000 ) );
-		ux.action.stop();
-
-		ux.action.start( 'Running post-transfer tasks' );
-		const postTransferFailures: string[] = [];
-		for ( const issue of issuesToTransfer ) {
-			if ( ! issue.newID ) {
-				continue;
-			}
-
-			/*
-			 * These are awaited so failures surface, but one issue failing must
-			 * not leave the remaining transferred issues unprocessed.
-			 */
-			try {
-				await this.resetProjectFields( authenticatedGraphQL, issue );
-
-				await this.addLabelsToIssue(
-					authenticatedGraphQL,
-					issue.newID,
-					labelsToAdd
-				);
-			} catch ( error ) {
-				postTransferFailures.push(
-					`${ issue.title }: ${ ( error as Error ).message }`
-				);
-			}
-		}
-		ux.action.stop();
-
-		if ( postTransferFailures.length > 0 ) {
-			this.log(
-				'Post-transfer tasks failed for:\n' +
-					postTransferFailures.join( '\n' )
-			);
-		}
-
-		this.log(
-			'Successfully transferred ' +
-				transferredIssues +
-				'/' +
-				numberOfIssues +
-				' issues.'
-		);
-	}
-
-	/**
-	 * Requests a token and verifies that it can be used to query the API.
-	 */
-	private async authenticateGraphQL(): Promise< typeof graphql > {
-		// Prompt them for a token, rather than storing one. This reduces the likelihood that the command can be accidentally executed.
-		const { default: password } = await import( '@inquirer/password' );
-		const token = await password( {
-			message: 'Please supply a GitHub API token',
-			validate: ( value ) => value !== '',
-		} );
-		if ( token === '' ) {
-			this.error( 'You must enter a valid GitHub API token' );
-		}
-
-		ux.action.start( 'Validating GitHub API token' );
-
-		const authenticatedGraphQL = graphql.defaults( {
-			headers: {
-				authorization: 'token ' + token,
-			},
-		} );
-
-		try {
-			await authenticatedGraphQL( '{ viewer { id } }' );
-		} catch ( err: unknown ) {
-			if ( err instanceof RequestError ) {
-				if ( err?.status === 401 ) {
-					this.error( 'The given token is invalid' );
-				}
-			}
-
-			throw err;
-		} finally {
-			ux.action.stop();
-		}
-
-		return authenticatedGraphQL;
-	}
-
-	/**
-	 * Fetches the node ID of the monorepo from GitHub.
-	 *
-	 * @param {graphql} authenticatedGraphQL The graphql object for making requests.
-	 * @param {string}  destinationOwner     The owner of the repository to transfer issues into.
-	 * @param {string}  destinationRepo      The repository to transfer issues into.
-	 */
-	private async getMonorepoNodeID(
-		authenticatedGraphQL: typeof graphql,
-		destinationOwner: string,
-		destinationRepo: string
-	): Promise< string > {
-		ux.action.start( 'Finding Monorepo' );
-
-		try {
-			const { repository } = await authenticatedGraphQL< {
-				repository: {
-					id: string;
-					nameWithOwner: string;
-				};
-			} >(
-				`
-                query ( $monorepoOwner: String!, $monorepoName: String! ) {
-                    repository ( owner: $monorepoOwner, name: $monorepoName ) {
-                        id,
-                        nameWithOwner
-                    }
-                }
-                `,
-				{
-					monorepoOwner: destinationOwner,
-					monorepoName: destinationRepo,
-				}
-			);
-
-			ux.action.stop();
-
-			return repository.id;
-		} catch ( err ) {
-			ux.action.stop();
-
-			if ( err instanceof GraphqlResponseError ) {
-				this.error(
-					'Could not find the repository "' +
-						destinationOwner +
-						'/' +
-						destinationRepo +
-						'"'
-				);
-			}
-
-			throw err;
-		} finally {
-			ux.action.stop();
-		}
-	}
-
-	/**
-	 * Gets all of the labels we want to add from GitHub.
-	 *
-	 * @param {graphql}             authenticatedGraphQL The graphql object for making requests.
-	 * @param {string}              destinationOwner     The owner of the repository to transfer issues into.
-	 * @param {string}              destinationRepo      The repository to transfer issues into.
-	 * @param {Array.<GitHubLabel>} labels               The labels we want to add after the transfer.
-	 */
-	private async getLabelsToAdd(
-		authenticatedGraphQL: typeof graphql,
-		destinationOwner: string,
-		destinationRepo: string,
-		labels?: string
-	): Promise< GitHubLabel[] > {
-		if ( ! labels ) {
-			return [];
-		}
-		const addLabels = labels.split( ',' );
-
-		ux.action.start( 'Getting labels to add' );
-
-		// Gather all of the labels from the monorepo so that
-		// we can validate the labels we want to add and
-		// get the IDs of them to assign on transfer.
-		const allLabels: { [ name: string ]: string } = {};
-		let cursor: string | null = null;
-		do {
-			const { repository } = await authenticatedGraphQL< {
-				repository: {
-					labels: {
-						nodes: GitHubLabel[];
-						pageInfo: { hasNextPage: boolean; endCursor: string };
-					};
-				};
-			} >(
-				`
-                query ( $monorepoOwner: String!, $monorepoName: String!, $cursor: String ) {
-                    repository ( owner: $monorepoOwner, name: $monorepoName ) {
-                        labels ( first: 100, after: $cursor ) {
-                            nodes {
-                                id,
-                                name
-                            },
-                            pageInfo {
-                                hasNextPage,
-                                endCursor
-                            }
-                        }
-                    }
-                }
-                `,
-				{
-					monorepoOwner: destinationOwner,
-					monorepoName: destinationRepo,
-					cursor,
-				}
-			);
-
-			// Record all of the labels so that we can scan for the ones we are adding.
-			for ( const label of repository.labels.nodes ) {
-				allLabels[ label.name ] = label.id;
-			}
-
-			// Continue following the cursor until we have no labels left to get.
-			if ( repository.labels.pageInfo.hasNextPage ) {
-				cursor = repository.labels.pageInfo.endCursor as string;
-			} else {
-				cursor = null;
-			}
-		} while ( cursor !== null );
-
-		// Find all of the labels we are going to add to the issues after the transfer.
-		const gitHubLabels: GitHubLabel[] = [];
-		for ( const label of addLabels ) {
-			if ( ! allLabels[ label ] ) {
-				this.error(
-					'The target repository does not have the label ' +
-						label +
-						'.'
-				);
-			}
-
-			gitHubLabels.push( {
-				id: allLabels[ label ],
-				name: label,
-			} );
-		}
-
-		ux.action.stop();
-
-		return gitHubLabels;
-	}
-
-	/**
-	 * Gets the number of issues that this command is going to migrate.
-	 *
-	 * @param {graphql} authenticatedGraphQL The graphql object for making requests.
-	 * @param {string}  source               The repository to transfer issues from.
-	 * @param {string}  searchFilter         The GitHub search filters for the issues to transfer.
-	 */
-	private async getNumberOfIssues(
-		authenticatedGraphQL: typeof graphql,
-		source: string,
-		searchFilter: string
-	) {
-		ux.action.start( 'Counting issues' );
-
-		const searchQuery = 'repo:' + source + ' is:issue ' + searchFilter;
-
-		const { search } = await authenticatedGraphQL< {
-			search: { issueCount: number };
-		} >(
-			`
-            query ( $searchQuery: String! ) {
-                search (
-                    type: ISSUE,
-                    query: $searchQuery,
-                    first: 0
-                ) {
-                    issueCount,
-                }
-            }
-            `,
-			{ searchQuery }
-		);
-
-		ux.action.stop();
-
-		return search.issueCount;
-	}
-
-	/**
-	 * Gets all of the issues that we are going to transfer into the monorepo.
-	 *
-	 * @param {graphql} authenticatedGraphQL The graphql object for making requests.
-	 * @param {string}  source               The repository to transfer issues from.
-	 * @param {string}  searchFilter         The GitHub search filters for the issues to transfer.
-	 */
-	private async getIssues(
-		authenticatedGraphQL: typeof graphql,
-		source: string,
-		searchFilter: string
-	) {
-		const searchQuery = 'repo:' + source + ' is:issue ' + searchFilter;
-
-		ux.action.start( 'Getting issues' );
-
-		const issues: GitHubIssue[] = [];
-		let cursor: string | null = null;
-		do {
-			const { search } = await authenticatedGraphQL< {
-				search: {
-					nodes: GitHubIssue[];
-					pageInfo: { hasNextPage: boolean; endCursor: string };
-				};
-			} >(
-				`
-				query ($searchQuery: String!, $cursor: String) {
-					search(type: ISSUE, query: $searchQuery, first: 100, after: $cursor) {
-					  nodes {
-							... on Issue {
-								id
-								title
-								projectItems(first: 50) {
-									nodes {
-										id
-										project {
-											id
-										}
-										fieldValues(first: 50) {
-											nodes {
-												... on ProjectV2ItemFieldTextValue {
-													field {
-														... on ProjectV2FieldCommon {
-															id
-															name
-															dataType
-														}
-													}
-													text
-												}
-												... on ProjectV2ItemFieldNumberValue {
-													field {
-														... on ProjectV2FieldCommon {
-															id
-															name
-															dataType
-														}
-													}
-													number
-												}
-												... on ProjectV2ItemFieldDateValue {
-													field {
-														... on ProjectV2FieldCommon {
-															id
-															name
-															dataType
-														}
-													}
-													date
-												}
-												... on ProjectV2ItemFieldSingleSelectValue {
-													field {
-														... on ProjectV2FieldCommon {
-															id
-															name
-															dataType
-														}
-													}
-													optionId
-												}
-												... on ProjectV2ItemFieldIterationValue {
-													field {
-														... on ProjectV2FieldCommon {
-															id
-															name
-															dataType
-														}
-													}
-													iterationId
-												}
-											}
-										}
-									}
-								}
-						  }
-					  }
-					  pageInfo {
-						  hasNextPage
-						  endCursor
-					  }
-					}
-				  }
-				`,
-				{
-					searchQuery,
-					cursor,
-				}
-			);
-
-			// Record all of the issues that we've found
-			for ( const issue of search.nodes ) {
-				const projectFields: GitHubProjectField[] = [];
-
-				// @ts-expect-error The types do not cover the projectItems field.
-				for ( const projectItem of issue.projectItems.nodes ) {
-					for ( const fieldValue of projectItem.fieldValues.nodes ) {
-						// We only care if this is a field we can process.
-						if ( ! fieldValue.field ) {
-							continue;
-						}
-
-						const projectField: GitHubProjectField = {
-							projectId: projectItem.project.id,
-							fieldId: fieldValue.field.id,
-							fieldName: fieldValue.field.name,
-							itemId: projectItem.id,
-							dataType: fieldValue.field.dataType,
-							value: {},
-						};
-
-						// Map the field value to the correct property.
-						switch ( fieldValue.field.dataType ) {
-							case 'TEXT':
-								projectField.value = { text: fieldValue.text };
-								break;
-
-							case 'NUMBER':
-								projectField.value = {
-									number: fieldValue.number,
-								};
-								break;
-
-							case 'DATE':
-								projectField.value = { date: fieldValue.date };
-								break;
-
-							case 'SINGLE_SELECT':
-								projectField.value = {
-									singleSelectOptionId: fieldValue.optionId,
-								};
-								break;
-
-							case 'ITERATION':
-								projectField.value = {
-									iterationId: fieldValue.iterationId,
-								};
-								break;
-
-							default:
-								continue;
-						}
-
-						projectFields.push( projectField );
-					}
-				}
-
-				issues.push( {
-					id: issue.id,
-					title: issue.title,
-					projectFields,
-				} );
-			}
-
-			// Continue following the cursor until we have no issues left to get.
-			if ( search.pageInfo.hasNextPage ) {
-				cursor = search.pageInfo.endCursor as string;
-			} else {
-				cursor = null;
-			}
-		} while ( cursor !== null );
-
-		ux.action.stop();
-
-		return issues;
-	}
-
-	/**
-	 * Transfers an issue into the monorepo.
-	 *
-	 * @param {graphql}     authenticatedGraphQL The graphql object for making requests.
-	 * @param {GitHubIssue} issue                The issue that we are going to transfer.
-	 * @param {string}      monorepoNodeID       The global node ID of the monorepo.
-	 */
-	private async transferIssue(
-		authenticatedGraphQL: typeof graphql,
-		issue: GitHubIssue,
-		monorepoNodeID: string
-	) {
-		ux.action.start( 'Transferring "' + issue.title + '"' );
-
-		try {
-			const input = {
-				clientMutationId: 'monorepo-merge',
-				issueId: issue.id,
-				repositoryId: monorepoNodeID,
-			};
-
-			const { transferIssue } = await authenticatedGraphQL< {
-				transferIssue: { issue: { id: string } };
-			} >(
-				`
-                mutation ( $input: TransferIssueInput! ) {
-                    transferIssue (
-                        input: $input
-                    ) {
-                        issue {
-                            id
-                        }
-                    }
-                }
-                `,
-				{ input }
-			);
-
-			return transferIssue.issue.id;
-		} catch ( err ) {
-			if ( err instanceof GraphqlResponseError && err.errors ) {
-				ux.action.stop( err.errors[ 0 ].message );
-			} else {
-				ux.action.stop( 'Failed to migrate issue' );
-			}
-
-			return null;
-		} finally {
-			ux.action.stop();
-		}
-	}
-
-	/**
-	 * Resets the project fields for the issue if necessary.
-	 *
-	 * @param {graphql}     authenticatedGraphQL The graphql object for making requests.
-	 * @param {GitHubIssue} issue                The GitHub issue to update the project fields for.
-	 */
-	private async resetProjectFields(
-		authenticatedGraphQL: typeof graphql,
-		issue: GitHubIssue
-	) {
-		// Pull all of the project fields from the issue post-transfer so that we can make sure that they've been transferred correctly.
-		// If they haven't, we need to to try and set them and log the ones that required this step.
-		// eslint-disable-next-line @typescript-eslint/no-explicit-any
-		const { node } = await authenticatedGraphQL< { node: any } >(
-			`
-			query ($nodeID: ID!) {
-				node(id: $nodeID) {
-					... on Issue {
-						id
-						title
-						projectItems(first: 50) {
-							nodes {
-								id
-								project {
-									id
-									title
-								}
-								fieldValues(first: 50) {
-									nodes {
-										... on ProjectV2ItemFieldTextValue {
-											field {
-												... on ProjectV2FieldCommon {
-													id
-													name
-													dataType
-												}
-											}
-											text
-										}
-										... on ProjectV2ItemFieldNumberValue {
-											field {
-												... on ProjectV2FieldCommon {
-													id
-													name
-													dataType
-												}
-											}
-											number
-										}
-										... on ProjectV2ItemFieldDateValue {
-											field {
-												... on ProjectV2FieldCommon {
-													id
-													name
-													dataType
-												}
-											}
-											date
-										}
-										... on ProjectV2ItemFieldSingleSelectValue {
-											field {
-												... on ProjectV2FieldCommon {
-													id
-													name
-													dataType
-												}
-											}
-											optionId
-										}
-										... on ProjectV2ItemFieldIterationValue {
-											field {
-												... on ProjectV2FieldCommon {
-													id
-													name
-													dataType
-												}
-											}
-											iterationId
-										}
-									}
-								}
-							}
-						}
-					}
-				}
-			}
-`,
-			{
-				nodeID: issue.newID,
-			}
-		);
-
-		for ( const projectField of issue.projectFields ) {
-			// Check for existing project fields and error if one was not give an error and exit.
-			for ( const project of node.projectItems.nodes ) {
-				if ( project.project.id !== projectField.projectId ) {
-					this.error(
-						'Issue "' +
-							issue.title +
-							'" does not have an entry for project "' +
-							project.project.title +
-							'"!'
-					);
-				}
-
-				let foundField = false;
-				for ( const field of project.fieldValues.nodes ) {
-					if ( ! field.field ) {
-						continue;
-					}
-
-					if ( projectField.fieldId === field.field.id ) {
-						foundField = true;
-					}
-				}
-
-				if ( ! foundField ) {
-					this.error(
-						'Issue "' +
-							issue.title +
-							'" - Project "' +
-							project.project.title +
-							'" is missing "' +
-							projectField.fieldId +
-							'" value "' +
-							JSON.stringify( projectField.value ) +
-							'"!'
-					);
-				}
-			}
-
-			const input = {
-				clientMutationId: 'monorepo-merge',
-				projectId: projectField.projectId,
-				fieldId: projectField.fieldId,
-				itemId: projectField.itemId,
-				value: projectField.value,
-			};
-
-			await authenticatedGraphQL(
-				`
-				mutation ( $input: UpdateProjectV2ItemFieldValueInput! ) {
-					updateProjectV2ItemFieldValue (
-						input: $input
-					) {
-						projectV2Item {
-						id
-					  }
-					}
-				}
-				`,
-				{ input }
-			);
-		}
-	}
-
-	/**
-	 * Adds labels to an issue.
-	 *
-	 * @param {graphql}             authenticatedGraphQL The graphql object for making requests.
-	 * @param {string}              issueID              The ID of the issue to label.
-	 * @param {Array.<GitHubLabel>} labelsToAdd          The labels to add to the issue.
-	 */
-	private async addLabelsToIssue(
-		authenticatedGraphQL: typeof graphql,
-		issueID: string,
-		labelsToAdd: GitHubLabel[]
-	) {
-		const { node } = await authenticatedGraphQL< {
-			node: { labels: { nodes: { id: string }[] } };
-		} >(
-			`
-            query ( $issueID: ID! ) {
-                node ( id: $issueID ) {
-                    ... on Issue {
-                        labels ( first: 100 ) {
-                            nodes {
-                                id
-                            }
-                        }
-                    }
-                }
-            }
-            `,
-			{
-				issueID,
-			}
-		);
-
-		// Combine the labels to add with the existing ones.
-		const labelIDs: string[] = [];
-		for ( const label of node.labels.nodes ) {
-			labelIDs.push( label.id );
-		}
-		for ( const label of labelsToAdd ) {
-			labelIDs.push( label.id );
-		}
-
-		const input = {
-			clientMutationId: 'monorepo-merge',
-			id: issueID,
-			labelIds: labelIDs,
-		};
-
-		await authenticatedGraphQL(
-			`
-            mutation ( $input: UpdateIssueInput! ) {
-                updateIssue (
-                    input: $input
-                ) {
-                    issue {
-                        id
-                    }
-                }
-            }
-            `,
-			{ input }
-		);
-	}
-}
diff --git a/tools/monorepo-merge/src/confirm.mjs b/tools/monorepo-merge/src/confirm.mjs
new file mode 100644
index 00000000000..0aae651b4a3
--- /dev/null
+++ b/tools/monorepo-merge/src/confirm.mjs
@@ -0,0 +1,51 @@
+/**
+ * External dependencies
+ */
+import { stdin as input, stdout as output } from 'node:process';
+import { createInterface } from 'node:readline';
+
+// One reader for every prompt, so answers piped in ahead of time are not dropped between questions.
+// `terminal: false` leaves the terminal in its normal mode, so Ctrl+C still stops the tool and its git commands.
+let readline;
+let lines;
+let closed = false;
+
+/**
+ * Asks a yes/no question on the terminal. Anything other than "y" or "yes" is a no, as is end of input.
+ *
+ * @param {string} message The question to ask.
+ * @return {Promise<boolean>} Whether the user answered yes.
+ */
+export async function confirm( message ) {
+	if ( ! readline ) {
+		readline = createInterface( { input, terminal: false } );
+		readline.on( 'close', () => ( closed = true ) );
+		lines = readline[ Symbol.asyncIterator ]();
+	}
+
+	output.write( `${ message } (y/N) ` );
+	// The reader closes at end of input, and a closed reader cannot be resumed or paused.
+	if ( ! closed ) {
+		readline.resume();
+	}
+	const { value, done } = await lines.next();
+	if ( done ) {
+		output.write( '\n' );
+		return false;
+	}
+	// Stop reading between prompts so input meant for git, such as a credential prompt, reaches git.
+	if ( ! closed ) {
+		readline.pause();
+	}
+
+	return /^y(es)?$/i.test( value.trim() );
+}
+
+/**
+ * Releases the terminal so the process can exit.
+ */
+export function closePrompts() {
+	if ( readline && ! closed ) {
+		readline.close();
+	}
+}
diff --git a/tools/monorepo-merge/src/const.ts b/tools/monorepo-merge/src/const.ts
deleted file mode 100644
index 2475bfc9b95..00000000000
--- a/tools/monorepo-merge/src/const.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * External dependencies
- */
-import { dirname } from 'path';
-
-// Escape from ./tools/monorepo-merge/src
-export const MONOREPO_ROOT = dirname( dirname( dirname( __dirname ) ) );
diff --git a/tools/monorepo-merge/src/github.mjs b/tools/monorepo-merge/src/github.mjs
new file mode 100644
index 00000000000..df4ea9e0c0f
--- /dev/null
+++ b/tools/monorepo-merge/src/github.mjs
@@ -0,0 +1,110 @@
+/**
+ * External dependencies
+ */
+import { execFile } from 'node:child_process';
+import { promisify } from 'node:util';
+
+// "owner/repo", where a repository name may contain dots but cannot be only dots.
+export const REPOSITORY_PATTERN = /^[a-zA-Z0-9\-_]+\/(?!\.+$)[a-zA-Z0-9\-_.]+$/;
+
+/**
+ * Gets a GitHub API token from GH_TOKEN, GITHUB_TOKEN, or the GitHub CLI.
+ *
+ * @return {Promise<string>} The token.
+ */
+export async function getToken() {
+	const token = ( process.env.GH_TOKEN || process.env.GITHUB_TOKEN )?.trim();
+	if ( token ) {
+		return token;
+	}
+
+	try {
+		const { stdout } = await promisify( execFile )( 'gh', [
+			'auth',
+			'token',
+			'--hostname',
+			'github.com',
+		] );
+		if ( stdout.trim() ) {
+			return stdout.trim();
+		}
+	} catch {}
+
+	throw new Error(
+		'Set GH_TOKEN or sign in with "gh auth login" to supply a GitHub API token'
+	);
+}
+
+/**
+ * Creates a function that sends GraphQL requests to the GitHub API.
+ *
+ * @param {string} rawToken The GitHub API token; surrounding whitespace is ignored.
+ * @return {(query: string, variables?: Object) => Promise<Object>} Resolves with the response data and rejects on HTTP or GraphQL errors. Network and HTTP failures have `requestFailed: true`.
+ */
+export function createGraphQL( rawToken ) {
+	const token = rawToken.trim();
+	// Node's header validation error would print the token, so reject bad characters with a fixed message.
+	if ( ! /^[\x21-\x7E]+$/.test( token ) ) {
+		throw new Error(
+			'The GitHub API token contains characters a token cannot have'
+		);
+	}
+
+	// Failures of the request itself, as opposed to GraphQL errors about one query, will fail every later request too.
+	const requestFailure = ( message ) =>
+		Object.assign( new Error( message ), { requestFailed: true } );
+
+	return async ( query, variables = {} ) => {
+		let response;
+		try {
+			response = await fetch( 'https://api.github.com/graphql', {
+				method: 'POST',
+				headers: {
+					authorization: `bearer ${ token }`,
+					'content-type': 'application/json',
+				},
+				body: JSON.stringify( { query, variables } ),
+			} );
+		} catch ( error ) {
+			throw requestFailure(
+				`Could not reach GitHub: ${ error.message }`
+			);
+		}
+
+		if ( response.status === 401 ) {
+			throw requestFailure( 'The given token is invalid' );
+		}
+		if ( ! response.ok ) {
+			// GitHub explains failures such as SAML enforcement or rate limits in the body's message.
+			const { message } =
+				( await response.json().catch( () => null ) ) ?? {};
+			throw requestFailure(
+				`GitHub API request failed with HTTP ${ response.status }${
+					message ? `: ${ message }` : ''
+				}`
+			);
+		}
+
+		let body;
+		try {
+			body = await response.json();
+		} catch ( error ) {
+			throw requestFailure(
+				`Could not read GitHub's response: ${ error.message }`
+			);
+		}
+
+		const { data, errors } = body ?? {};
+		if ( errors?.length ) {
+			const message = errors
+				.map( ( error ) => error.message )
+				.join( '\n' );
+			// GitHub reports GraphQL rate limits with HTTP 200 and a RATE_LIMITED error.
+			throw errors.some( ( error ) => error.type === 'RATE_LIMITED' )
+				? requestFailure( message )
+				: new Error( message );
+		}
+
+		return data;
+	};
+}
diff --git a/tools/monorepo-merge/src/index.ts b/tools/monorepo-merge/src/index.ts
deleted file mode 100644
index d620e709ab9..00000000000
--- a/tools/monorepo-merge/src/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export { run } from '@oclif/core';
diff --git a/tools/monorepo-merge/src/merge.mjs b/tools/monorepo-merge/src/merge.mjs
new file mode 100644
index 00000000000..ac841992d80
--- /dev/null
+++ b/tools/monorepo-merge/src/merge.mjs
@@ -0,0 +1,343 @@
+/**
+ * External dependencies
+ */
+import { execFile } from 'node:child_process';
+import { access, mkdtemp, realpath, rm } from 'node:fs/promises';
+import { tmpdir } from 'node:os';
+import { isAbsolute, join, relative, resolve, sep } from 'node:path';
+import { promisify } from 'node:util';
+
+/**
+ * Internal dependencies
+ */
+import { closePrompts, confirm } from './confirm.mjs';
+import { REPOSITORY_PATTERN } from './github.mjs';
+
+const execFileAsync = promisify( execFile );
+
+export const MONOREPO_ROOT = resolve( import.meta.dirname, '../../..' );
+
+const REMOTE = 'monorepo-merge-import';
+
+// Signals that stop the merge but still let it remove the temporary clone and remote.
+const SIGNALS = [ 'SIGINT', 'SIGTERM', 'SIGHUP' ];
+
+// The command `run` is waiting on, so a signal sent to this process alone can be passed on to it.
+let activeChild;
+
+/**
+ * Runs a command and replaces any failure with a readable error.
+ *
+ * @param {string}   command        The executable to run.
+ * @param {string[]} args           The command arguments.
+ * @param {string}   failureMessage The error message to show when the command fails.
+ * @param {Object}   [options]      Options for `child_process.execFile`.
+ * @return {Promise<string>} The command's trimmed standard output.
+ */
+async function run( command, args, failureMessage, options ) {
+	// git merge prints a line per imported file, which can exceed the default 1 MiB buffer.
+	const pending = execFileAsync( command, args, {
+		maxBuffer: Infinity,
+		...options,
+	} );
+	activeChild = pending.child;
+	try {
+		const { stdout } = await pending;
+
+		return stdout.trim();
+	} catch ( error ) {
+		// Merge conflicts are listed on stdout, so include it alongside stderr.
+		const detail =
+			[ error.stdout?.trim(), error.stderr?.trim() ]
+				.filter( Boolean )
+				.join( '\n' ) || error.message;
+		throw new Error( `${ failureMessage }: ${ detail }` );
+	} finally {
+		activeChild = undefined;
+	}
+}
+
+/**
+ * Validates the source repository and the destination path.
+ *
+ * @param {string} source      The GitHub repository we are merging, in "organization/repository" format.
+ * @param {string} destination The monorepo path to merge the repository into.
+ * @return {Promise<string>} The destination relative to the monorepo root, without `./` or a trailing slash.
+ */
+export async function validateArgs( source, destination ) {
+	// We only support pulling from GitHub so the format needs to match that.
+	if ( ! REPOSITORY_PATTERN.test( source ) ) {
+		throw new Error(
+			'The "source" argument must be in "organization/repository" format'
+		);
+	}
+
+	const destinationPath = resolve( MONOREPO_ROOT, destination );
+	const relativeDestination = relative( MONOREPO_ROOT, destinationPath );
+	if (
+		isAbsolute( destination ) ||
+		relativeDestination === '' ||
+		relativeDestination === '..' ||
+		relativeDestination.startsWith( '..' + sep ) ||
+		isAbsolute( relativeDestination )
+	) {
+		throw new Error(
+			'The "destination" argument must point to a path inside the monorepo'
+		);
+	}
+
+	const exists = await access( destinationPath ).then(
+		() => true,
+		() => false
+	);
+	if ( exists ) {
+		throw new Error(
+			'The "destination" argument points to a file or directory that already exists'
+		);
+	}
+
+	return relativeDestination;
+}
+
+/**
+ * Builds the git-filter-repo callback that updates imported references.
+ *
+ * A reference alone in parentheses, such as `(#123)`, becomes a pull request link first.
+ * Every other standalone reference, such as `#123` or the ones in `(#12, #34)`, is then qualified with the
+ * source repository. A `#` that follows a word character or `/`, as in `other/repo#12` or a URL fragment,
+ * is not a reference and is left alone.
+ *
+ * @param {string} source The GitHub repository we are merging.
+ * @return {string} The Python body of the message callback.
+ */
+export function createMessageCallback( source ) {
+	const pullRequestPattern = String.raw`rb"\(#(?P<pull_request_number>\d+)\)"`;
+	const pullRequestReplacement = String.raw`rb"(https://github.com/${ source }/pull/\g<pull_request_number>)"`;
+	const issuePattern = String.raw`rb"(?<![\w/])#(?P<issue_number>\d+)(?!\w)"`;
+	const issueReplacement = String.raw`rb"${ source }#\g<issue_number>"`;
+	const pullRequestCallback = `re.sub(${ pullRequestPattern }, ${ pullRequestReplacement }, message)`;
+
+	return `return re.sub(${ issuePattern }, ${ issueReplacement }, ${ pullRequestCallback })`;
+}
+
+/**
+ * Merges a GitHub repository into the current branch with its history, moved under `destination`.
+ *
+ * @param {string} source               The GitHub repository to merge, in "organization/repository" format.
+ * @param {string} requestedDestination The monorepo path to merge the repository into.
+ * @param {Object} options
+ * @param {string} options.branch       The source branch to merge.
+ */
+export async function merge( source, requestedDestination, { branch } ) {
+	for ( const command of [ 'git', 'git-filter-repo' ] ) {
+		await run(
+			command,
+			[ '--version' ],
+			`"${ command }" must be installed`
+		);
+	}
+	const destination = await validateArgs( source, requestedDestination );
+
+	// Ask git from the tool's own directory, so a copy outside tools/monorepo-merge cannot target an enclosing repository.
+	const inMonorepo = { cwd: MONOREPO_ROOT };
+	const notARepository = `The checkout to merge into must be two directories above this tool, at ${ MONOREPO_ROOT }, but that is not the root of the git checkout containing it`;
+	const topLevel = await run(
+		'git',
+		[ 'rev-parse', '--show-toplevel' ],
+		notARepository,
+		{ cwd: import.meta.dirname }
+	);
+	if (
+		( await realpath( topLevel ) ) !== ( await realpath( MONOREPO_ROOT ) )
+	) {
+		throw new Error( notARepository );
+	}
+	const currentBranch = await run(
+		'git',
+		[ 'branch', '--show-current' ],
+		'Failed to read the current branch',
+		inMonorepo
+	);
+
+	const clonePrefix = join( tmpdir(), 'monorepo-merge-' );
+	// The remote may belong to another run in progress or to one that was killed, which this run cannot tell
+	// apart, so never remove it automatically.
+	const existing = await run(
+		'git',
+		[ 'remote', 'get-url', REMOTE ],
+		'',
+		inMonorepo
+	).catch( () => null );
+	if ( existing ) {
+		throw new Error(
+			`A "${ REMOTE }" remote already exists and points at ${ existing }. Make sure no other merge is running, remove it with "git remote remove ${ REMOTE }", then run the command again.`
+		);
+	}
+
+	if (
+		! ( await confirm(
+			`This merges ${ source } and its history into ${ destination } on ${
+				currentBranch
+					? `branch "${ currentBranch }"`
+					: 'a detached HEAD'
+			} of ${ MONOREPO_ROOT }. Continue?`
+		) )
+	) {
+		throw new Error( 'Cancelled.' );
+	}
+
+	const cloneDir = await mkdtemp( clonePrefix );
+
+	// Catching these signals lets the cleanup below still run. Ctrl+C from a terminal already reaches git,
+	// but a signal sent to this process alone has to be passed on to the running command.
+	let interrupted = false;
+	let mergeStarted = false;
+	let cleaningUp = false;
+	const onInterrupt = ( signal ) => {
+		interrupted = true;
+		closePrompts();
+		if ( ! cleaningUp ) {
+			activeChild?.kill( signal );
+		}
+	};
+	for ( const signal of SIGNALS ) {
+		process.on( signal, onInterrupt );
+	}
+	const step = ( ...args ) => {
+		if ( interrupted ) {
+			throw new Error( 'Interrupted' );
+		}
+		return run( ...args );
+	};
+
+	let failure;
+	try {
+		const gitPath = 'https://github.com/' + source;
+		console.log( `Cloning ${ gitPath }` );
+		await step(
+			'git',
+			[ 'clone', gitPath, cloneDir ],
+			'Failed to clone the repository'
+		);
+
+		console.log( 'Rewriting the repository history' );
+		await step(
+			'git-filter-repo',
+			[
+				'--to-subdirectory-filter=' + destination,
+				'--message-callback=' + createMessageCallback( source ),
+			],
+			'Failed to alter the repository history',
+			{ cwd: cloneDir }
+		);
+
+		if (
+			! ( await confirm(
+				`Are you ready to merge ${ source } from ${ cloneDir }?`
+			) )
+		) {
+			throw new Error( 'Cancelled. Nothing was merged.' );
+		}
+
+		console.log( 'Merging repositories' );
+		// A fixed remote name, because names such as "org/.github" are valid repositories but invalid remotes.
+		await step(
+			'git',
+			[ 'remote', 'add', REMOTE, cloneDir ],
+			'Failed to add the cloned repository as a remote',
+			inMonorepo
+		);
+		// Without --no-tags the source repository's tags would stay in the monorepo after the remote is removed.
+		await step(
+			'git',
+			[ 'fetch', '--no-tags', REMOTE ],
+			'Failed to fetch the cloned repository',
+			inMonorepo
+		);
+		mergeStarted = true;
+		await step(
+			'git',
+			[
+				'merge',
+				'--allow-unrelated-histories',
+				'-m',
+				`Merge ${ source } into ${ destination }`,
+				'--',
+				`${ REMOTE }/${ branch }`,
+			],
+			'Failed to merge the repositories',
+			inMonorepo
+		);
+	} catch ( error ) {
+		failure = error;
+	}
+	// Read before cleanup: a Ctrl+C during cleanup did not stop any step.
+	const stopped = Boolean( failure ) && interrupted;
+
+	// The merge only needs the fetched objects, so the clone and remote can go even after a conflict.
+	// Signals are no longer passed on once cleanup starts. A terminal Ctrl+C can still hit these quick git commands,
+	// and if that strands the remote, the next run stops and explains how to remove it.
+	cleaningUp = true;
+	const problems = [];
+	try {
+		// Checked by URL rather than a flag, because an interrupted `git remote add` can still have written the remote.
+		try {
+			const remotes = await run(
+				'git',
+				[ 'remote' ],
+				'Failed to list the remotes',
+				inMonorepo
+			);
+			if (
+				remotes.split( '\n' ).includes( REMOTE ) &&
+				( await run(
+					'git',
+					[ 'remote', 'get-url', REMOTE ],
+					`Failed to read the temporary remote "${ REMOTE }"`,
+					inMonorepo
+				) ) === cloneDir
+			) {
+				await run(
+					'git',
+					[ 'remote', 'remove', REMOTE ],
+					`Failed to remove the temporary remote "${ REMOTE }"`,
+					inMonorepo
+				);
+			}
+		} catch ( error ) {
+			problems.push( error.message );
+		}
+		await rm( cloneDir, { recursive: true, force: true } ).catch(
+			( error ) =>
+				problems.push(
+					`Failed to remove the temporary clone ${ cloneDir }: ${ error.message }`
+				)
+		);
+	} finally {
+		for ( const signal of SIGNALS ) {
+			process.off( signal, onInterrupt );
+		}
+	}
+
+	if ( ! failure ) {
+		console.log( `Successfully merged ${ source } into ${ destination }` );
+		if ( problems.length > 0 ) {
+			throw new Error(
+				`The merge succeeded, but cleanup failed:\n${ problems.join(
+					'\n'
+				) }`
+			);
+		}
+		return;
+	}
+
+	let reason = failure.message;
+	if ( stopped ) {
+		reason = mergeStarted
+			? 'Interrupted during git merge. The checkout may be mid-merge; run "git merge --abort" to undo it.'
+			: 'Interrupted';
+	}
+	throw Object.assign( new Error( [ reason, ...problems ].join( '\n' ) ), {
+		exitCode: stopped ? 130 : 1,
+	} );
+}
diff --git a/tools/monorepo-merge/src/node-async.ts b/tools/monorepo-merge/src/node-async.ts
deleted file mode 100644
index 7d2fb9c1c72..00000000000
--- a/tools/monorepo-merge/src/node-async.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-/**
- * External dependencies
- */
-import { access as nodeAccess, PathLike } from 'fs';
-import { exec as nodeExec, ExecOptions } from 'child_process';
-
-/**
- * A promise wrapper for Node's `fs.access` function.
- *
- * @param {string} path   The path to access.
- * @param {number} [mode] The access mode.
- */
-export async function access( path: PathLike, mode?: number ): Promise< void > {
-	return new Promise( ( resolve, reject ) => {
-		nodeAccess( path, mode, ( err ) => {
-			if ( err ) {
-				reject( err );
-				return;
-			}
-
-			resolve();
-		} );
-	} );
-}
-
-/**
- * A promise wrapper for Node's `child_process.exec` function.
- *
- * @param {string}      command   The command to execute.
- * @param {ExecOptions} [options] The options for the command.
- */
-export async function exec(
-	command: string,
-	options?: ExecOptions
-): Promise< string | Buffer > {
-	return new Promise( ( resolve, reject ) => {
-		nodeExec( command, options, ( err, stdout, stderr ) => {
-			if ( err ) {
-				reject( stderr );
-				return;
-			}
-
-			resolve( stdout );
-		} );
-	} );
-}
diff --git a/tools/monorepo-merge/src/transfer-issues.mjs b/tools/monorepo-merge/src/transfer-issues.mjs
new file mode 100644
index 00000000000..8b3ba5c317d
--- /dev/null
+++ b/tools/monorepo-merge/src/transfer-issues.mjs
@@ -0,0 +1,495 @@
+/**
+ * Internal dependencies
+ */
+import { confirm } from './confirm.mjs';
+import { REPOSITORY_PATTERN, createGraphQL, getToken } from './github.mjs';
+
+// GitHub search never returns more than this many results for one query.
+export const SEARCH_LIMIT = 1000;
+
+const FIELD = `field { ... on ProjectV2FieldCommon { id name dataType } }`;
+const PROJECT_ITEMS = `
+	projectItems( first: 50 ) {
+		nodes {
+			id
+			project { id title }
+			fieldValues( first: 50 ) {
+				nodes {
+					... on ProjectV2ItemFieldTextValue { ${ FIELD } text }
+					... on ProjectV2ItemFieldNumberValue { ${ FIELD } number }
+					... on ProjectV2ItemFieldDateValue { ${ FIELD } date }
+					... on ProjectV2ItemFieldSingleSelectValue { ${ FIELD } optionId }
+					... on ProjectV2ItemFieldIterationValue { ${ FIELD } iterationId }
+				}
+			}
+		}
+	}
+`;
+
+// Maps each project field data type to the value property in the API response and in UpdateProjectV2ItemFieldValueInput.
+const FIELD_VALUE_KEYS = {
+	TEXT: [ 'text', 'text' ],
+	NUMBER: [ 'number', 'number' ],
+	DATE: [ 'date', 'date' ],
+	SINGLE_SELECT: [ 'optionId', 'singleSelectOptionId' ],
+	ITERATION: [ 'iterationId', 'iterationId' ],
+};
+
+/**
+ * Reads the project field values from an issue's `projectItems` nodes.
+ *
+ * @param {Object[]} projectItems The `projectItems.nodes` of an issue.
+ * @return {Object[]} The field values, shaped for UpdateProjectV2ItemFieldValueInput.
+ */
+export function readProjectFields( projectItems ) {
+	const fields = [];
+	for ( const item of projectItems ) {
+		for ( const fieldValue of item.fieldValues.nodes ) {
+			const keys = FIELD_VALUE_KEYS[ fieldValue.field?.dataType ];
+			if ( ! keys ) {
+				continue;
+			}
+
+			fields.push( {
+				projectId: item.project.id,
+				projectTitle: item.project.title,
+				fieldId: fieldValue.field.id,
+				fieldName: fieldValue.field.name,
+				value: { [ keys[ 1 ] ]: fieldValue[ keys[ 0 ] ] },
+			} );
+		}
+	}
+
+	return fields;
+}
+
+/**
+ * Compares the field values an issue had before the transfer with its project items after it.
+ *
+ * @param {Object[]} expectedFields   The fields read from the issue before the transfer.
+ * @param {Object[]} projectItems     The `projectItems.nodes` of the issue after the transfer.
+ * @param {Object[]} expectedProjects The `{ id, title }` of every project the issue was in, including ones with no field values to restore.
+ * @return {{missingProjects: string[], fieldsToRestore: Object[]}} Titles of projects the issue is no longer in, and the fields to set again with the new item IDs.
+ */
+export function findFieldsToRestore(
+	expectedFields,
+	projectItems,
+	expectedProjects = []
+) {
+	const itemIds = new Map(
+		projectItems.map( ( item ) => [ item.project.id, item.id ] )
+	);
+	const missingProjects = new Set(
+		expectedProjects
+			.filter( ( project ) => ! itemIds.has( project.id ) )
+			.map( ( project ) => project.title )
+	);
+	const actualValues = new Map(
+		readProjectFields( projectItems ).map( ( field ) => [
+			`${ field.projectId }:${ field.fieldId }`,
+			JSON.stringify( field.value ),
+		] )
+	);
+
+	const fieldsToRestore = [];
+	for ( const field of expectedFields ) {
+		const itemId = itemIds.get( field.projectId );
+		if ( ! itemId ) {
+			missingProjects.add( field.projectTitle );
+		} else if (
+			actualValues.get( `${ field.projectId }:${ field.fieldId }` ) !==
+			JSON.stringify( field.value )
+		) {
+			fieldsToRestore.push( { ...field, itemId } );
+		}
+	}
+
+	return { missingProjects: [ ...missingProjects ], fieldsToRestore };
+}
+
+/**
+ * Gets the IDs of the labels to add after the transfer, failing if the destination lacks any of them.
+ *
+ * @param {Function} graphql The GraphQL request function.
+ * @param {string}   owner   The destination repository owner.
+ * @param {string}   name    The destination repository name.
+ * @param {string[]} names   The label names.
+ * @return {Promise<string[]>} The label IDs.
+ */
+async function getLabelIds( graphql, owner, name, names ) {
+	if ( names.length === 0 ) {
+		return [];
+	}
+
+	const allLabels = new Map();
+	let cursor = null;
+	do {
+		const { repository } = await graphql(
+			`
+				query ($owner: String!, $name: String!, $cursor: String) {
+					repository(owner: $owner, name: $name) {
+						labels(first: 100, after: $cursor) {
+							nodes {
+								id
+								name
+							}
+							pageInfo {
+								hasNextPage
+								endCursor
+							}
+						}
+					}
+				}
+			`,
+			{ owner, name, cursor }
+		);
+
+		for ( const label of repository.labels.nodes ) {
+			allLabels.set( label.name, label.id );
+		}
+
+		const { hasNextPage, endCursor } = repository.labels.pageInfo;
+		cursor = hasNextPage ? endCursor : null;
+	} while ( cursor !== null );
+
+	return names.map( ( label ) => {
+		if ( ! allLabels.has( label ) ) {
+			throw new Error(
+				`The target repository does not have the label ${ label }.`
+			);
+		}
+
+		return allLabels.get( label );
+	} );
+}
+
+/**
+ * Gets the issues matching the search query, with their project field values.
+ *
+ * @param {Function} graphql     The GraphQL request function.
+ * @param {string}   searchQuery The GitHub search query.
+ * @return {Promise<Object[]>} The issues.
+ */
+async function getIssues( graphql, searchQuery ) {
+	const issues = [];
+	let cursor = null;
+	do {
+		const { search } = await graphql(
+			`query ( $searchQuery: String!, $cursor: String ) {
+				search( type: ISSUE, query: $searchQuery, first: 100, after: $cursor ) {
+					nodes { ... on Issue { id title ${ PROJECT_ITEMS } } }
+					pageInfo { hasNextPage endCursor }
+				}
+			}`,
+			{ searchQuery, cursor }
+		);
+
+		for ( const issue of search.nodes ) {
+			issues.push( {
+				id: issue.id,
+				title: issue.title,
+				projects: issue.projectItems.nodes.map(
+					( item ) => item.project
+				),
+				projectFields: readProjectFields( issue.projectItems.nodes ),
+			} );
+		}
+
+		const { hasNextPage, endCursor } = search.pageInfo;
+		cursor = hasNextPage ? endCursor : null;
+	} while ( cursor !== null );
+
+	return issues;
+}
+
+/**
+ * Sets project field values that did not survive the transfer.
+ *
+ * @param {Function} graphql The GraphQL request function.
+ * @param {Object}   issue   The transferred issue.
+ * @return {Promise<string[]>} Problems to report: projects the issue is no longer in, and fields that could not be set. Each field is tried even if another fails, unless a request-level failure stops the rest; that error is rethrown with these problems in `problems`.
+ */
+async function restoreProjectFields( graphql, issue ) {
+	if ( issue.projects.length === 0 ) {
+		return [];
+	}
+
+	const { node } = await graphql(
+		`query ( $id: ID! ) { node( id: $id ) { ... on Issue { ${ PROJECT_ITEMS } } } }`,
+		{ id: issue.newId }
+	);
+	if ( ! node ) {
+		throw new Error( 'Could not read the transferred issue' );
+	}
+	const { missingProjects, fieldsToRestore } = findFieldsToRestore(
+		issue.projectFields,
+		node.projectItems.nodes,
+		issue.projects
+	);
+
+	const problems = missingProjects.map(
+		( title ) => `no longer in project "${ title }"`
+	);
+	for ( const field of fieldsToRestore ) {
+		const name = `"${ field.fieldName }" in project "${ field.projectTitle }"`;
+		try {
+			await graphql(
+				`
+					mutation ($input: UpdateProjectV2ItemFieldValueInput!) {
+						updateProjectV2ItemFieldValue(input: $input) {
+							projectV2Item {
+								id
+							}
+						}
+					}
+				`,
+				{
+					input: {
+						projectId: field.projectId,
+						fieldId: field.fieldId,
+						itemId: field.itemId,
+						value: field.value,
+					},
+				}
+			);
+			console.log( `Restored ${ name } for "${ issue.title }"` );
+		} catch ( error ) {
+			problems.push( `restoring ${ name } failed: ${ error.message }` );
+			// Every later request would fail the same way, so hand the problems so far to the caller and stop.
+			if ( error.requestFailed ) {
+				throw Object.assign( error, { problems } );
+			}
+		}
+	}
+
+	return problems;
+}
+
+/**
+ * Transfers issues from another repository into the destination repository.
+ *
+ * @param {string}  source               The repository to transfer from, in "owner/repo" format.
+ * @param {Object}  options
+ * @param {string}  options.destination  The repository to transfer into, in "owner/repo" format.
+ * @param {string}  options.searchFilter GitHub search filters for the issues to transfer.
+ * @param {string}  [options.labels]     A comma-separated list of labels to add after the transfer.
+ * @param {boolean} [options.dryRun]     Run every lookup and print the plan without prompting or changing anything.
+ */
+export async function transferIssues(
+	source,
+	{ destination, searchFilter, labels, dryRun = false }
+) {
+	const [ , owner, name ] = destination.match( /^([^/]+)\/([^/]+)$/ ) ?? [];
+	if ( ! owner ) {
+		throw new Error(
+			'The destination repository must be in the format "owner/repo"!'
+		);
+	}
+	if ( ! REPOSITORY_PATTERN.test( source ) ) {
+		throw new Error(
+			'The source repository must be in the format "owner/repo"!'
+		);
+	}
+	// GitHub owner names are case-insensitive.
+	if ( ! source.toLowerCase().startsWith( owner.toLowerCase() + '/' ) ) {
+		throw new Error(
+			'The source and target repository must have the same owner!'
+		);
+	}
+
+	if (
+		! dryRun &&
+		! ( await confirm(
+			`Are you sure you want to transfer issues from ${ source } into ${ destination }?`
+		) )
+	) {
+		throw new Error( 'Cancelled. Nothing was transferred.' );
+	}
+
+	const graphql = createGraphQL( await getToken() );
+	await graphql( '{ viewer { id } }' );
+
+	const searchQuery = `repo:${ source } is:issue ${ searchFilter }`;
+	const { search } = await graphql(
+		`
+			query ($searchQuery: String!) {
+				search(type: ISSUE, query: $searchQuery, first: 0) {
+					issueCount
+				}
+			}
+		`,
+		{ searchQuery }
+	);
+	if ( search.issueCount === 0 ) {
+		console.log( 'There are no issues to transfer that match this query!' );
+		return;
+	}
+
+	const issueCount = Math.min( search.issueCount, SEARCH_LIMIT );
+	if ( search.issueCount > SEARCH_LIMIT ) {
+		console.log(
+			`${ search.issueCount } issues match, but GitHub search returns at most ${ SEARCH_LIMIT }. Run the command again afterward to transfer the rest.`
+		);
+	}
+	// Look up the destination and labels before the final prompt, so a typo fails before the scary question.
+	const {
+		repository: { id: repositoryId },
+	} = await graphql(
+		`
+			query ($owner: String!, $name: String!) {
+				repository(owner: $owner, name: $name) {
+					id
+				}
+			}
+		`,
+		{ owner, name }
+	);
+	const labelNames = ( labels ?? '' )
+		.split( ',' )
+		.map( ( label ) => label.trim() )
+		.filter( Boolean );
+	const labelIds = await getLabelIds( graphql, owner, name, labelNames );
+
+	if (
+		! dryRun &&
+		! ( await confirm(
+			`This will transfer ${ issueCount } issue(s). There is no command to reverse this, are you sure?`
+		) )
+	) {
+		throw new Error( 'Cancelled. Nothing was transferred.' );
+	}
+
+	const issues = await getIssues( graphql, searchQuery );
+
+	if ( dryRun ) {
+		console.log(
+			`Dry run: would transfer ${ issues.length } issue(s) into ${ destination }:`
+		);
+		for ( const { title, projectFields } of issues ) {
+			console.log(
+				`- "${ title }" (${ projectFields.length } project field value(s) to check)`
+			);
+		}
+		if ( labelNames.length > 0 ) {
+			console.log(
+				`Would then add the labels: ${ labelNames.join( ', ' ) }`
+			);
+		}
+		console.log( 'No changes were made.' );
+		return;
+	}
+
+	let transferred = 0;
+	for ( const issue of issues ) {
+		console.log( `Transferring "${ issue.title }"` );
+		try {
+			const { transferIssue } = await graphql(
+				`
+					mutation ($input: TransferIssueInput!) {
+						transferIssue(input: $input) {
+							issue {
+								id
+							}
+						}
+					}
+				`,
+				{ input: { issueId: issue.id, repositoryId } }
+			);
+			issue.newId = transferIssue.issue.id;
+			transferred++;
+		} catch ( error ) {
+			console.log(
+				`Failed to transfer "${ issue.title }": ${ error.message }`
+			);
+			// A rejected token, a rate limit, or a lost connection would fail every remaining transfer too.
+			if ( error.requestFailed ) {
+				console.log(
+					'Stopping the transfer; running post-transfer tasks for the issues already moved.'
+				);
+				break;
+			}
+		}
+	}
+
+	const failures = [];
+	if ( transferred > 0 ) {
+		// Give GitHub time to finish moving labels and project items before we touch the new issues.
+		console.log( 'Waiting for GitHub to process transfers' );
+		await new Promise( ( resolve ) => setTimeout( resolve, 25000 ) );
+
+		console.log( 'Running post-transfer tasks' );
+	}
+	// Each step is tried on its own, so one failure does not skip the other or the remaining issues,
+	// unless the failure is one that every later request would hit too.
+	const moved = issues.filter( ( { newId } ) => newId );
+	for ( const [ index, issue ] of moved.entries() ) {
+		let requestFailure;
+		try {
+			for ( const problem of await restoreProjectFields(
+				graphql,
+				issue
+			) ) {
+				failures.push( `${ issue.title }: ${ problem }` );
+			}
+		} catch ( error ) {
+			const problems = error.problems ?? [
+				`restoring project fields failed: ${ error.message }`,
+			];
+			for ( const problem of problems ) {
+				failures.push( `${ issue.title }: ${ problem }` );
+			}
+			requestFailure = error.requestFailed ? error : undefined;
+		}
+		// When the project restore is what stopped, this issue's remaining fields and labels were never tried.
+		const currentIssueUnfinished = Boolean( requestFailure );
+
+		if ( labelIds.length > 0 && ! requestFailure ) {
+			try {
+				await graphql(
+					`
+						mutation ($input: AddLabelsToLabelableInput!) {
+							addLabelsToLabelable(input: $input) {
+								clientMutationId
+							}
+						}
+					`,
+					{ input: { labelableId: issue.newId, labelIds } }
+				);
+			} catch ( error ) {
+				failures.push(
+					`${ issue.title }: adding labels failed: ${ error.message }`
+				);
+				requestFailure = error.requestFailed ? error : undefined;
+			}
+		}
+
+		if ( requestFailure ) {
+			const skipped = moved.slice(
+				currentIssueUnfinished ? index : index + 1
+			);
+			const tasks =
+				labelIds.length > 0
+					? 'project fields and labels'
+					: 'project fields';
+			if ( skipped.length > 0 ) {
+				failures.push(
+					`Stopped post-transfer tasks, so these transferred issues still need their ${ tasks } checked: ${ skipped
+						.map( ( { title } ) => `"${ title }"` )
+						.join( ', ' ) }`
+				);
+			}
+			break;
+		}
+	}
+
+	if ( failures.length > 0 ) {
+		console.log(
+			'Post-transfer tasks failed for:\n' + failures.join( '\n' )
+		);
+	}
+	const count = `${ transferred }/${ issues.length } issues`;
+	if ( transferred < issues.length || failures.length > 0 ) {
+		throw new Error( `Transferred ${ count }. See the failures above.` );
+	}
+	console.log( `Successfully transferred ${ count }.` );
+}
diff --git a/tools/monorepo-merge/test/confirm.test.mjs b/tools/monorepo-merge/test/confirm.test.mjs
new file mode 100644
index 00000000000..946238b2d5f
--- /dev/null
+++ b/tools/monorepo-merge/test/confirm.test.mjs
@@ -0,0 +1,51 @@
+import assert from 'node:assert/strict';
+import { spawnSync } from 'node:child_process';
+import { join } from 'node:path';
+import { describe, it } from 'node:test';
+
+const BIN = join( import.meta.dirname, '../bin/monorepo-merge.mjs' );
+const CONFIRM = join( import.meta.dirname, '../src/confirm.mjs' );
+
+function askTwice( input ) {
+	const script = `import { confirm } from ${ JSON.stringify( CONFIRM ) };
+		console.log( JSON.stringify( [ await confirm( 'One?' ), await confirm( 'Two?' ) ] ) );`;
+	const { stdout, status } = spawnSync(
+		process.execPath,
+		[ '--input-type=module', '-e', script ],
+		// A prompt that never resolves should fail the test rather than hang the suite.
+		{ input, encoding: 'utf8', timeout: 10000 }
+	);
+	assert.equal( status, 0 );
+
+	// Answered prompts print no newline, so the result shares the last line with them.
+	return JSON.parse( stdout.match( /\[[^\]]*\]\s*$/ )[ 0 ] );
+}
+
+describe( 'confirm', () => {
+	it( 'reads piped answers for consecutive prompts', () => {
+		assert.deepEqual( askTwice( 'y\nn\n' ), [ true, false ] );
+	} );
+
+	it( 'reads a last answer without a trailing newline', () => {
+		assert.deepEqual( askTwice( 'y\ny' ), [ true, true ] );
+	} );
+
+	it( 'treats the end of input as no', () => {
+		assert.deepEqual( askTwice( 'y\n' ), [ true, false ] );
+		assert.deepEqual( askTwice( '' ), [ false, false ] );
+	} );
+
+	it( 'cancels a command cleanly when input ends at the first prompt', () => {
+		const { stderr, status } = spawnSync(
+			process.execPath,
+			[ BIN, 'transfer-issues', 'woocommerce/example' ],
+			{ input: '', encoding: 'utf8', timeout: 10000 }
+		);
+
+		assert.equal( status, 1 );
+		assert.equal(
+			stderr.trim(),
+			'Error: Cancelled. Nothing was transferred.'
+		);
+	} );
+} );
diff --git a/tools/monorepo-merge/test/fixtures/rate-limited-github.mjs b/tools/monorepo-merge/test/fixtures/rate-limited-github.mjs
new file mode 100644
index 00000000000..6bffa52c39f
--- /dev/null
+++ b/tools/monorepo-merge/test/fixtures/rate-limited-github.mjs
@@ -0,0 +1,78 @@
+// Preloaded with `node --import` to stand in for GitHub: three transferable issues whose project
+// values are lost, and a rate limit on the first attempt to restore one. The 25-second wait is skipped.
+const realSetTimeout = globalThis.setTimeout;
+globalThis.setTimeout = ( callback, ms, ...args ) =>
+	realSetTimeout( callback, Math.min( ms, 10 ), ...args );
+
+const field = { id: 'F', name: 'Status', dataType: 'SINGLE_SELECT' };
+
+globalThis.fetch = async ( url, { body } ) => {
+	const { query, variables } = JSON.parse( body );
+
+	if ( query.includes( 'updateProjectV2ItemFieldValue' ) ) {
+		process.stderr.write( 'MOCK field update\n' );
+		return Response.json( {
+			errors: [
+				{ type: 'RATE_LIMITED', message: 'API rate limit exceeded' },
+			],
+		} );
+	}
+	if ( query.includes( 'transferIssue' ) ) {
+		return Response.json( {
+			data: {
+				transferIssue: {
+					issue: { id: `${ variables.input.issueId }-new` },
+				},
+			},
+		} );
+	}
+	if ( query.includes( 'node(' ) ) {
+		return Response.json( {
+			data: {
+				node: {
+					projectItems: {
+						nodes: [
+							{
+								id: `${ variables.id }-item`,
+								project: { id: 'P', title: 'Board' },
+								fieldValues: { nodes: [] },
+							},
+						],
+					},
+				},
+			},
+		} );
+	}
+	if ( query.includes( 'issueCount' ) ) {
+		return Response.json( { data: { search: { issueCount: 3 } } } );
+	}
+	if ( query.includes( 'search(' ) ) {
+		return Response.json( {
+			data: {
+				search: {
+					nodes: [ 1, 2, 3 ].map( ( number ) => ( {
+						id: `issue-${ number }`,
+						title: `Issue ${ number }`,
+						projectItems: {
+							nodes: [
+								{
+									id: `old-${ number }`,
+									project: { id: 'P', title: 'Board' },
+									fieldValues: {
+										nodes: [ { field, optionId: 'todo' } ],
+									},
+								},
+							],
+						},
+					} ) ),
+					pageInfo: { hasNextPage: false },
+				},
+			},
+		} );
+	}
+	if ( query.includes( 'repository(' ) ) {
+		return Response.json( { data: { repository: { id: 'R' } } } );
+	}
+
+	return Response.json( { data: { viewer: { id: 'viewer' } } } );
+};
diff --git a/tools/monorepo-merge/test/github.test.mjs b/tools/monorepo-merge/test/github.test.mjs
new file mode 100644
index 00000000000..731d8206435
--- /dev/null
+++ b/tools/monorepo-merge/test/github.test.mjs
@@ -0,0 +1,72 @@
+import assert from 'node:assert/strict';
+import { describe, it } from 'node:test';
+
+import { createGraphQL } from '../src/github.mjs';
+
+describe( 'createGraphQL', () => {
+	it( 'reports an invalid token on HTTP 401', async ( t ) => {
+		t.mock.method( globalThis, 'fetch', async () =>
+			Response.json( { message: 'Bad credentials' }, { status: 401 } )
+		);
+
+		await assert.rejects( createGraphQL( 'bad' )( '{ viewer { id } }' ), {
+			message: 'The given token is invalid',
+		} );
+	} );
+
+	it( 'keeps a malformed token out of error messages', async () => {
+		await assert.rejects(
+			async () =>
+				createGraphQL( 'ghp_secret\nvalue' )( '{ viewer { id } }' ),
+			( error ) => ! error.message.includes( 'ghp_secret' )
+		);
+	} );
+
+	it( "includes GitHub's explanation for other HTTP failures", async ( t ) => {
+		t.mock.method( globalThis, 'fetch', async () =>
+			Response.json(
+				{
+					message:
+						'Resource protected by organization SAML enforcement',
+				},
+				{ status: 403 }
+			)
+		);
+
+		await assert.rejects( createGraphQL( 'token' )( '{ viewer { id } }' ), {
+			message:
+				'GitHub API request failed with HTTP 403: Resource protected by organization SAML enforcement',
+		} );
+	} );
+
+	it( 'marks a GraphQL rate limit as a failure of every request', async ( t ) => {
+		t.mock.method( globalThis, 'fetch', async () =>
+			Response.json( {
+				errors: [
+					{
+						type: 'RATE_LIMITED',
+						message: 'API rate limit exceeded',
+					},
+				],
+			} )
+		);
+
+		await assert.rejects( createGraphQL( 'token' )( '{ viewer { id } }' ), {
+			message: 'API rate limit exceeded',
+			requestFailed: true,
+		} );
+	} );
+
+	it( 'rejects with the GraphQL error messages', async ( t ) => {
+		t.mock.method( globalThis, 'fetch', async () =>
+			Response.json( {
+				data: { repository: null },
+				errors: [ { message: 'Could not resolve to a Repository' } ],
+			} )
+		);
+
+		await assert.rejects( createGraphQL( 'token' )( '{ viewer { id } }' ), {
+			message: 'Could not resolve to a Repository',
+		} );
+	} );
+} );
diff --git a/tools/monorepo-merge/test/merge.test.mjs b/tools/monorepo-merge/test/merge.test.mjs
index b61321c49b8..a1e03c1782c 100644
--- a/tools/monorepo-merge/test/merge.test.mjs
+++ b/tools/monorepo-merge/test/merge.test.mjs
@@ -3,11 +3,11 @@ import { execFileSync } from 'node:child_process';
 import { join } from 'node:path';
 import { describe, it } from 'node:test';

-import mergeModule from '../dist/commands/merge/index.js';
-import constants from '../dist/const.js';
-
-const { default: Merge } = mergeModule;
-const { MONOREPO_ROOT } = constants;
+import {
+	MONOREPO_ROOT,
+	createMessageCallback,
+	validateArgs,
+} from '../src/merge.mjs';

 const SOURCE = 'woocommerce/example';
 const DESTINATION_ERROR =
@@ -18,7 +18,7 @@ function rewriteMessage( message ) {
 		'import re',
 		'import sys',
 		'def rewrite(message):',
-		`    ${ Merge.createMessageCallback( SOURCE ) }`,
+		`    ${ createMessageCallback( SOURCE ) }`,
 		'sys.stdout.buffer.write(rewrite(sys.stdin.buffer.read()))',
 	].join( '\n' );

@@ -28,53 +28,58 @@ function rewriteMessage( message ) {
 	} );
 }

-function createCommand() {
-	const command = Object.create( Merge.prototype );
-	command.error = ( message ) => {
-		throw new Error( message );
-	};
-
-	return command;
-}
-
-describe( 'destination validation', () => {
-	it( 'accepts a nested path inside the monorepo', async () => {
+describe( 'source validation', () => {
+	it( 'accepts a repository name with dots', async () => {
 		await assert.doesNotReject( () =>
-			createCommand().validateArgs(
-				SOURCE,
+			validateArgs(
+				'woocommerce/example.com',
 				'tools/__monorepo_merge_test__'
 			)
 		);
 	} );

+	it( 'rejects a repository name made only of dots', async () => {
+		await assert.rejects(
+			validateArgs( 'woocommerce/..', 'tools/__monorepo_merge_test__' ),
+			{
+				message:
+					'The "source" argument must be in "organization/repository" format',
+			}
+		);
+	} );
+} );
+
+describe( 'destination validation', () => {
+	it( 'accepts a nested path and returns it relative to the monorepo', async () => {
+		assert.equal(
+			await validateArgs( SOURCE, './tools/__monorepo_merge_test__/' ),
+			'tools/__monorepo_merge_test__'
+		);
+	} );
+
 	it( 'rejects an absolute path', async () => {
 		await assert.rejects(
-			createCommand().validateArgs(
-				SOURCE,
-				join( MONOREPO_ROOT, 'tools/example' )
-			),
+			validateArgs( SOURCE, join( MONOREPO_ROOT, 'tools/example' ) ),
 			{ message: DESTINATION_ERROR }
 		);
 	} );

 	it( 'rejects the monorepo root', async () => {
-		await assert.rejects( createCommand().validateArgs( SOURCE, '.' ), {
+		await assert.rejects( validateArgs( SOURCE, '.' ), {
 			message: DESTINATION_ERROR,
 		} );
 	} );

 	it( 'rejects a path that traverses outside the monorepo', async () => {
-		await assert.rejects(
-			createCommand().validateArgs( SOURCE, '../outside' ),
-			{ message: DESTINATION_ERROR }
-		);
+		await assert.rejects( validateArgs( SOURCE, '../outside' ), {
+			message: DESTINATION_ERROR,
+		} );
 	} );

 	it( 'rejects a sibling path with the monorepo name as its prefix', async () => {
-		await assert.rejects(
-			createCommand().validateArgs( SOURCE, '../woocommerce-copy' ),
-			{ message: DESTINATION_ERROR }
-		);
+		await assert.rejects( validateArgs( SOURCE, '../woocommerce-copy' ), {
+			message: DESTINATION_ERROR,
+		} );
 	} );
 } );

@@ -95,15 +100,36 @@ describe( 'commit message rewriting', () => {

 	it( 'rewrites adjacent valid references', () => {
 		assert.equal(
-			rewriteMessage( '(#12)(#34) #56#78' ),
+			rewriteMessage( '(#12)(#34) #56 #78' ),
 			'(https://github.com/woocommerce/example/pull/12)' +
 				'(https://github.com/woocommerce/example/pull/34) ' +
-				'woocommerce/example#56woocommerce/example#78'
+				'woocommerce/example#56 woocommerce/example#78'
+		);
+	} );
+
+	it( 'qualifies every reference in a parenthesized list', () => {
+		assert.equal(
+			rewriteMessage( 'Fix widget (#12, #34)' ),
+			'Fix widget (woocommerce/example#12, woocommerce/example#34)'
+		);
+	} );
+
+	it( 'qualifies references inside parenthesized text', () => {
+		assert.equal(
+			rewriteMessage( 'Tidy up (see #12).' ),
+			'Tidy up (see woocommerce/example#12).'
 		);
 	} );

+	it( 'leaves qualified references and URL fragments unchanged', () => {
+		const message =
+			'See other/repo#12, https://example.com/page#5 and abc#7.';
+
+		assert.equal( rewriteMessage( message ), message );
+	} );
+
 	it( 'leaves malformed references unchanged', () => {
-		const message = 'Malformed: (#), (#abc), (#12, and #.';
+		const message = 'Malformed: (#), (#abc), # 12, and #.';

 		assert.equal( rewriteMessage( message ), message );
 	} );
diff --git a/tools/monorepo-merge/test/transfer-issues.test.mjs b/tools/monorepo-merge/test/transfer-issues.test.mjs
new file mode 100644
index 00000000000..49c04d304e8
--- /dev/null
+++ b/tools/monorepo-merge/test/transfer-issues.test.mjs
@@ -0,0 +1,224 @@
+import assert from 'node:assert/strict';
+import { spawnSync } from 'node:child_process';
+import { join } from 'node:path';
+import { describe, it } from 'node:test';
+
+import { closePrompts } from '../src/confirm.mjs';
+import {
+	findFieldsToRestore,
+	readProjectFields,
+	transferIssues,
+} from '../src/transfer-issues.mjs';
+
+describe( 'transferIssues after a request-level failure', () => {
+	it( 'stops post-transfer tasks and lists the issues still to check', () => {
+		const { stdout, stderr, status } = spawnSync(
+			process.execPath,
+			[
+				'--import',
+				join( import.meta.dirname, 'fixtures/rate-limited-github.mjs' ),
+				join( import.meta.dirname, '../bin/monorepo-merge.mjs' ),
+				'transfer-issues',
+				'woocommerce/example',
+			],
+			{
+				input: 'y\ny\n',
+				encoding: 'utf8',
+				env: { ...process.env, GH_TOKEN: 'token' },
+				timeout: 20000,
+			}
+		);
+
+		assert.equal( status, 1 );
+		assert.equal( stderr.match( /MOCK field update/g )?.length, 1 );
+		assert.ok(
+			stdout.includes(
+				'Stopped post-transfer tasks, so these transferred issues still need their project fields checked: "Issue 1", "Issue 2", "Issue 3"'
+			),
+			stdout
+		);
+	} );
+} );
+
+describe( 'transferIssues dry run', () => {
+	// A prompt writes "(y/N)" and, with stdin held open, waits forever, so the spy and the timeout each catch one.
+	it(
+		'lists the issues without prompting or sending mutations',
+		{
+			timeout: 5000,
+		},
+		async ( t ) => {
+			const previousToken = process.env.GH_TOKEN;
+			process.env.GH_TOKEN = 'token';
+			t.after( () => {
+				closePrompts();
+				if ( previousToken === undefined ) {
+					delete process.env.GH_TOKEN;
+				} else {
+					process.env.GH_TOKEN = previousToken;
+				}
+			} );
+			const writes = t.mock.method( process.stdout, 'write' );
+			const queries = [];
+			t.mock.method( globalThis, 'fetch', async ( url, { body } ) => {
+				const { query } = JSON.parse( body );
+				queries.push( query );
+
+				let data = { viewer: { id: 'viewer' } };
+				if ( query.includes( 'issueCount' ) ) {
+					data = { search: { issueCount: 1 } };
+				} else if ( query.includes( 'labels(' ) ) {
+					data = {
+						repository: {
+							labels: {
+								nodes: [ { id: 'label-id', name: 'migrated' } ],
+								pageInfo: { hasNextPage: false },
+							},
+						},
+					};
+				} else if ( query.includes( 'search(' ) ) {
+					data = {
+						search: {
+							nodes: [
+								{
+									id: 'issue-id',
+									title: 'An issue',
+									projectItems: { nodes: [] },
+								},
+							],
+							pageInfo: { hasNextPage: false },
+						},
+					};
+				} else if ( query.includes( 'repository(' ) ) {
+					data = { repository: { id: 'repo-id' } };
+				}
+
+				return Response.json( { data } );
+			} );
+			const log = t.mock.method( console, 'log', () => {} );
+
+			await transferIssues( 'woocommerce/example', {
+				destination: 'woocommerce/woocommerce',
+				searchFilter: 'is:open',
+				labels: 'migrated',
+				dryRun: true,
+			} );
+
+			assert.equal(
+				queries.filter( ( query ) => /^\s*mutation/.test( query ) )
+					.length,
+				0
+			);
+			assert.ok(
+				! writes.mock.calls.some( ( call ) =>
+					String( call.arguments[ 0 ] ).includes( '(y/N)' )
+				),
+				'A dry run must not prompt'
+			);
+			const output = log.mock.calls.map(
+				( call ) => call.arguments[ 0 ]
+			);
+			for ( const line of [
+				'- "An issue" (0 project field value(s) to check)',
+				'No changes were made.',
+			] ) {
+				assert.ok( output.includes( line ), `Missing "${ line }"` );
+			}
+		}
+	);
+} );
+
+// Mirrors the live API: value types without a fragment come back as `{}`, and Title has its own data type.
+function projectItem( itemId, projectId, optionId ) {
+	const fieldValues = [
+		{},
+		{
+			field: {
+				id: `${ projectId }-title`,
+				name: 'Title',
+				dataType: 'TITLE',
+			},
+			text: 'Issue title',
+		},
+	];
+	if ( optionId ) {
+		fieldValues.push( {
+			field: {
+				id: `${ projectId }-status`,
+				name: 'Status',
+				dataType: 'SINGLE_SELECT',
+			},
+			optionId,
+		} );
+	}
+
+	return {
+		id: itemId,
+		project: { id: projectId, title: `Project ${ projectId }` },
+		fieldValues: { nodes: fieldValues },
+	};
+}
+
+describe( 'findFieldsToRestore', () => {
+	const before = readProjectFields( [
+		projectItem( 'old-a', 'A', 'todo' ),
+		projectItem( 'old-b', 'B', 'done' ),
+	] );
+
+	it( 'restores nothing when every project kept its values', () => {
+		assert.deepEqual(
+			findFieldsToRestore( before, [
+				projectItem( 'new-a', 'A', 'todo' ),
+				projectItem( 'new-b', 'B', 'done' ),
+			] ),
+			{ missingProjects: [], fieldsToRestore: [] }
+		);
+	} );
+
+	it( 'restores lost values on the new project item', () => {
+		const { missingProjects, fieldsToRestore } = findFieldsToRestore(
+			before,
+			[ projectItem( 'new-a', 'A', 'todo' ), projectItem( 'new-b', 'B' ) ]
+		);
+
+		assert.deepEqual( missingProjects, [] );
+		assert.deepEqual(
+			fieldsToRestore.map( ( { itemId, value } ) => ( {
+				itemId,
+				value,
+			} ) ),
+			[ { itemId: 'new-b', value: { singleSelectOptionId: 'done' } } ]
+		);
+	} );
+
+	it( 'restores values that changed during the transfer', () => {
+		const { fieldsToRestore } = findFieldsToRestore( before, [
+			projectItem( 'new-a', 'A', 'in-progress' ),
+			projectItem( 'new-b', 'B', 'done' ),
+		] );
+
+		assert.deepEqual(
+			fieldsToRestore.map( ( { itemId, value } ) => ( {
+				itemId,
+				value,
+			} ) ),
+			[ { itemId: 'new-a', value: { singleSelectOptionId: 'todo' } } ]
+		);
+	} );
+
+	it( 'reports projects the issue is no longer in', () => {
+		assert.deepEqual(
+			findFieldsToRestore( before, [
+				projectItem( 'new-a', 'A', 'todo' ),
+			] ),
+			{ missingProjects: [ 'Project B' ], fieldsToRestore: [] }
+		);
+	} );
+
+	it( 'reports a lost project that had no values to restore', () => {
+		assert.deepEqual(
+			findFieldsToRestore( [], [], [ { id: 'C', title: 'Project C' } ] ),
+			{ missingProjects: [ 'Project C' ], fieldsToRestore: [] }
+		);
+	} );
+} );
diff --git a/tools/monorepo-merge/tsconfig.json b/tools/monorepo-merge/tsconfig.json
deleted file mode 100644
index a66b59451bb..00000000000
--- a/tools/monorepo-merge/tsconfig.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-	"compilerOptions": {
-		"declaration": true,
-		"importHelpers": true,
-		"target": "esnext",
-		"module": "nodenext",
-		"moduleResolution": "nodenext",
-		"outDir": "dist",
-		"rootDir": "src",
-		"strict": true,
-		"typeRoots": [
-			"./node_modules/@types"
-		],
-		"skipLibCheck": true
-	},
-	"ts-node": {
-		"esm": false
-	},
-	"include": [
-		"src/**/*"
-	]
-}