diff --git a/common/autoinstallers/rush-commands/.gitignore b/common/autoinstallers/rush-commands/.gitignore new file mode 100644 index 0000000000..5f37ca5d16 --- /dev/null +++ b/common/autoinstallers/rush-commands/.gitignore @@ -0,0 +1,2 @@ +lib +dist diff --git a/common/autoinstallers/rush-commands/package.json b/common/autoinstallers/rush-commands/package.json index 86056a747e..2e04059894 100644 --- a/common/autoinstallers/rush-commands/package.json +++ b/common/autoinstallers/rush-commands/package.json @@ -1,20 +1,29 @@ { "name": "rush-commands", "version": "1.0.0", - "description": "", + "private": true, + "description": "Rush command tools and utilities", "keywords": [], "license": "Apache-2.0", "author": "", "main": "index.js", - "scripts": {}, + "scripts": { + "build": "tsc", + "clean": "rimraf lib/", + "revert-useless-changes": "node lib/revert-useless-changes/cli.js" + }, "dependencies": { - "commander": "^11.0.0", + "@typescript-eslint/parser": "^8.0.0", + "chalk": "^4.1.2", + "commander": "^12.0.0", + "glob": "^10.3.10", "simple-git": "^3.20.0" }, "devDependencies": { "@types/node": "^20.0.0", + "rimraf": "^5.0.0", + "sucrase": "^3.32.0", "tsx": "^4.19.2", "typescript": "^5.0.0" } } - diff --git a/common/autoinstallers/rush-commands/pnpm-lock.yaml b/common/autoinstallers/rush-commands/pnpm-lock.yaml index 7f136701a2..54e9f594f9 100644 --- a/common/autoinstallers/rush-commands/pnpm-lock.yaml +++ b/common/autoinstallers/rush-commands/pnpm-lock.yaml @@ -5,9 +5,18 @@ settings: excludeLinksFromLockfile: false dependencies: + '@typescript-eslint/parser': + specifier: ^8.0.0 + version: 8.44.1(eslint@9.36.0)(typescript@5.9.2) + chalk: + specifier: ^4.1.2 + version: 4.1.2 commander: - specifier: ^11.0.0 - version: 11.1.0 + specifier: ^12.0.0 + version: 12.1.0 + glob: + specifier: ^10.3.10 + version: 10.4.5 simple-git: specifier: ^3.20.0 version: 3.28.0 @@ -15,18 +24,24 @@ dependencies: devDependencies: '@types/node': specifier: ^20.0.0 - version: 20.19.9 + version: 20.19.17 + rimraf: + specifier: ^5.0.0 + version: 5.0.10 + sucrase: + specifier: ^3.32.0 + version: 3.35.0 tsx: specifier: ^4.19.2 - version: 4.20.3 + version: 4.20.5 typescript: specifier: ^5.0.0 - version: 5.8.3 + version: 5.9.2 packages: - /@esbuild/aix-ppc64@0.25.8: - resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} + /@esbuild/aix-ppc64@0.25.10: + resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -34,8 +49,8 @@ packages: dev: true optional: true - /@esbuild/android-arm64@0.25.8: - resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} + /@esbuild/android-arm64@0.25.10: + resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -43,8 +58,8 @@ packages: dev: true optional: true - /@esbuild/android-arm@0.25.8: - resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} + /@esbuild/android-arm@0.25.10: + resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -52,8 +67,8 @@ packages: dev: true optional: true - /@esbuild/android-x64@0.25.8: - resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} + /@esbuild/android-x64@0.25.10: + resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -61,8 +76,8 @@ packages: dev: true optional: true - /@esbuild/darwin-arm64@0.25.8: - resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} + /@esbuild/darwin-arm64@0.25.10: + resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -70,8 +85,8 @@ packages: dev: true optional: true - /@esbuild/darwin-x64@0.25.8: - resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} + /@esbuild/darwin-x64@0.25.10: + resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -79,8 +94,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-arm64@0.25.8: - resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} + /@esbuild/freebsd-arm64@0.25.10: + resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -88,8 +103,8 @@ packages: dev: true optional: true - /@esbuild/freebsd-x64@0.25.8: - resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} + /@esbuild/freebsd-x64@0.25.10: + resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -97,8 +112,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm64@0.25.8: - resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} + /@esbuild/linux-arm64@0.25.10: + resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -106,8 +121,8 @@ packages: dev: true optional: true - /@esbuild/linux-arm@0.25.8: - resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} + /@esbuild/linux-arm@0.25.10: + resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -115,8 +130,8 @@ packages: dev: true optional: true - /@esbuild/linux-ia32@0.25.8: - resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} + /@esbuild/linux-ia32@0.25.10: + resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -124,8 +139,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64@0.25.8: - resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} + /@esbuild/linux-loong64@0.25.10: + resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -133,8 +148,8 @@ packages: dev: true optional: true - /@esbuild/linux-mips64el@0.25.8: - resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} + /@esbuild/linux-mips64el@0.25.10: + resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -142,8 +157,8 @@ packages: dev: true optional: true - /@esbuild/linux-ppc64@0.25.8: - resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} + /@esbuild/linux-ppc64@0.25.10: + resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -151,8 +166,8 @@ packages: dev: true optional: true - /@esbuild/linux-riscv64@0.25.8: - resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} + /@esbuild/linux-riscv64@0.25.10: + resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -160,8 +175,8 @@ packages: dev: true optional: true - /@esbuild/linux-s390x@0.25.8: - resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} + /@esbuild/linux-s390x@0.25.10: + resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -169,8 +184,8 @@ packages: dev: true optional: true - /@esbuild/linux-x64@0.25.8: - resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} + /@esbuild/linux-x64@0.25.10: + resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -178,8 +193,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-arm64@0.25.8: - resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} + /@esbuild/netbsd-arm64@0.25.10: + resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] @@ -187,8 +202,8 @@ packages: dev: true optional: true - /@esbuild/netbsd-x64@0.25.8: - resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} + /@esbuild/netbsd-x64@0.25.10: + resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] @@ -196,8 +211,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-arm64@0.25.8: - resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} + /@esbuild/openbsd-arm64@0.25.10: + resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -205,8 +220,8 @@ packages: dev: true optional: true - /@esbuild/openbsd-x64@0.25.8: - resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} + /@esbuild/openbsd-x64@0.25.10: + resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -214,8 +229,8 @@ packages: dev: true optional: true - /@esbuild/openharmony-arm64@0.25.8: - resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} + /@esbuild/openharmony-arm64@0.25.10: + resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] @@ -223,8 +238,8 @@ packages: dev: true optional: true - /@esbuild/sunos-x64@0.25.8: - resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} + /@esbuild/sunos-x64@0.25.10: + resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -232,8 +247,8 @@ packages: dev: true optional: true - /@esbuild/win32-arm64@0.25.8: - resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} + /@esbuild/win32-arm64@0.25.10: + resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -241,8 +256,8 @@ packages: dev: true optional: true - /@esbuild/win32-ia32@0.25.8: - resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} + /@esbuild/win32-ia32@0.25.10: + resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -250,8 +265,8 @@ packages: dev: true optional: true - /@esbuild/win32-x64@0.25.8: - resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} + /@esbuild/win32-x64@0.25.10: + resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -259,10 +274,140 @@ packages: dev: true optional: true + /@eslint-community/eslint-utils@4.9.0(eslint@9.36.0): + resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + dependencies: + eslint: 9.36.0 + eslint-visitor-keys: 3.4.3 + dev: false + + /@eslint-community/regexpp@4.12.1: + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + dev: false + + /@eslint/config-array@0.21.0: + resolution: {integrity: sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + '@eslint/object-schema': 2.1.6 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + dev: false + + /@eslint/config-helpers@0.3.1: + resolution: {integrity: sha512-xR93k9WhrDYpXHORXpxVL5oHj3Era7wo6k/Wd8/IsQNnZUTzkGS29lyn3nAT05v6ltUuTFVCCYDEGfy2Or/sPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: false + + /@eslint/core@0.15.2: + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + '@types/json-schema': 7.0.15 + dev: false + + /@eslint/eslintrc@3.3.1: + resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + dev: false + + /@eslint/js@9.36.0: + resolution: {integrity: sha512-uhCbYtYynH30iZErszX78U+nR3pJU3RHGQ57NXy5QupD4SBVwDeU8TNBy+MjMngc1UyIW9noKqsRqfjQTBU2dw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: false + + /@eslint/object-schema@2.1.6: + resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: false + + /@eslint/plugin-kit@0.3.5: + resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + '@eslint/core': 0.15.2 + levn: 0.4.1 + dev: false + + /@humanfs/core@0.19.1: + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + dev: false + + /@humanfs/node@0.16.7: + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + dev: false + + /@humanwhocodes/module-importer@1.0.1: + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + dev: false + + /@humanwhocodes/retry@0.4.3: + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + dev: false + + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.2 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + + /@jridgewell/gen-mapping@0.3.13: + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + dev: true + + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + dev: true + + /@jridgewell/sourcemap-codec@1.5.5: + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + dev: true + + /@jridgewell/trace-mapping@0.3.31: + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + dev: true + /@kwsites/file-exists@1.1.1: resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} dependencies: - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color dev: false @@ -271,19 +416,247 @@ packages: resolution: {integrity: sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==} dev: false - /@types/node@20.19.9: - resolution: {integrity: sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==} + /@nodelib/fs.scandir@2.1.5: + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + dev: false + + /@nodelib/fs.stat@2.0.5: + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + dev: false + + /@nodelib/fs.walk@1.2.8: + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.19.1 + dev: false + + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + optional: true + + /@types/estree@1.0.8: + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + dev: false + + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + dev: false + + /@types/node@20.19.17: + resolution: {integrity: sha512-gfehUI8N1z92kygssiuWvLiwcbOB3IRktR6hTDgJlXMYh5OvkPSRmgfoBUmfZt+vhwJtX7v1Yw4KvvAf7c5QKQ==} dependencies: undici-types: 6.21.0 dev: true - /commander@11.1.0: - resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} - engines: {node: '>=16'} + /@typescript-eslint/parser@8.44.1(eslint@9.36.0)(typescript@5.9.2): + resolution: {integrity: sha512-EHrrEsyhOhxYt8MTg4zTF+DJMuNBzWwgvvOYNj/zm1vnaD/IC5zCXFehZv94Piqa2cRFfXrTFxIvO95L7Qc/cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + dependencies: + '@typescript-eslint/scope-manager': 8.44.1 + '@typescript-eslint/types': 8.44.1 + '@typescript-eslint/typescript-estree': 8.44.1(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 8.44.1 + debug: 4.4.3 + eslint: 9.36.0 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + dev: false + + /@typescript-eslint/project-service@8.44.1(typescript@5.9.2): + resolution: {integrity: sha512-ycSa60eGg8GWAkVsKV4E6Nz33h+HjTXbsDT4FILyL8Obk5/mx4tbvCNsLf9zret3ipSumAOG89UcCs/KRaKYrA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + dependencies: + '@typescript-eslint/tsconfig-utils': 8.44.1(typescript@5.9.2) + '@typescript-eslint/types': 8.44.1 + debug: 4.4.3 + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + dev: false + + /@typescript-eslint/scope-manager@8.44.1: + resolution: {integrity: sha512-NdhWHgmynpSvyhchGLXh+w12OMT308Gm25JoRIyTZqEbApiBiQHD/8xgb6LqCWCFcxFtWwaVdFsLPQI3jvhywg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + '@typescript-eslint/types': 8.44.1 + '@typescript-eslint/visitor-keys': 8.44.1 + dev: false + + /@typescript-eslint/tsconfig-utils@8.44.1(typescript@5.9.2): + resolution: {integrity: sha512-B5OyACouEjuIvof3o86lRMvyDsFwZm+4fBOqFHccIctYgBjqR3qT39FBYGN87khcgf0ExpdCBeGKpKRhSFTjKQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + dependencies: + typescript: 5.9.2 + dev: false + + /@typescript-eslint/types@8.44.1: + resolution: {integrity: sha512-Lk7uj7y9uQUOEguiDIDLYLJOrYHQa7oBiURYVFqIpGxclAFQ78f6VUOM8lI2XEuNOKNB7XuvM2+2cMXAoq4ALQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: false + + /@typescript-eslint/typescript-estree@8.44.1(typescript@5.9.2): + resolution: {integrity: sha512-qnQJ+mVa7szevdEyvfItbO5Vo+GfZ4/GZWWDRRLjrxYPkhM+6zYB2vRYwCsoJLzqFCdZT4mEqyJoyzkunsZ96A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + dependencies: + '@typescript-eslint/project-service': 8.44.1(typescript@5.9.2) + '@typescript-eslint/tsconfig-utils': 8.44.1(typescript@5.9.2) + '@typescript-eslint/types': 8.44.1 + '@typescript-eslint/visitor-keys': 8.44.1 + debug: 4.4.3 + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.9.2) + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + dev: false + + /@typescript-eslint/visitor-keys@8.44.1: + resolution: {integrity: sha512-576+u0QD+Jp3tZzvfRfxon0EA2lzcDt3lhUbsC6Lgzy9x2VR4E+JUiNyGHi5T8vk0TV+fpJ5GLG1JsJuWCaKhw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + '@typescript-eslint/types': 8.44.1 + eslint-visitor-keys: 4.2.1 dev: false - /debug@4.4.1: - resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + /acorn-jsx@5.3.2(acorn@8.15.0): + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + dependencies: + acorn: 8.15.0 + dev: false + + /acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: false + + /ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + dev: false + + /ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + /ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + /ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + dependencies: + color-convert: 2.0.1 + + /ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: true + + /argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + dev: false + + /balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + /brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + dev: false + + /brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + dependencies: + balanced-match: 1.0.2 + + /braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + dependencies: + fill-range: 7.1.1 + dev: false + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: false + + /chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + dev: false + + /color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + dependencies: + color-name: 1.1.4 + + /color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + /commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + dev: false + + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: true + + /concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + dev: false + + /cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + /debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -294,40 +667,228 @@ packages: ms: 2.1.3 dev: false - /esbuild@0.25.8: - resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} + /deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + dev: false + + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + /esbuild@0.25.10: + resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} engines: {node: '>=18'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/aix-ppc64': 0.25.8 - '@esbuild/android-arm': 0.25.8 - '@esbuild/android-arm64': 0.25.8 - '@esbuild/android-x64': 0.25.8 - '@esbuild/darwin-arm64': 0.25.8 - '@esbuild/darwin-x64': 0.25.8 - '@esbuild/freebsd-arm64': 0.25.8 - '@esbuild/freebsd-x64': 0.25.8 - '@esbuild/linux-arm': 0.25.8 - '@esbuild/linux-arm64': 0.25.8 - '@esbuild/linux-ia32': 0.25.8 - '@esbuild/linux-loong64': 0.25.8 - '@esbuild/linux-mips64el': 0.25.8 - '@esbuild/linux-ppc64': 0.25.8 - '@esbuild/linux-riscv64': 0.25.8 - '@esbuild/linux-s390x': 0.25.8 - '@esbuild/linux-x64': 0.25.8 - '@esbuild/netbsd-arm64': 0.25.8 - '@esbuild/netbsd-x64': 0.25.8 - '@esbuild/openbsd-arm64': 0.25.8 - '@esbuild/openbsd-x64': 0.25.8 - '@esbuild/openharmony-arm64': 0.25.8 - '@esbuild/sunos-x64': 0.25.8 - '@esbuild/win32-arm64': 0.25.8 - '@esbuild/win32-ia32': 0.25.8 - '@esbuild/win32-x64': 0.25.8 + '@esbuild/aix-ppc64': 0.25.10 + '@esbuild/android-arm': 0.25.10 + '@esbuild/android-arm64': 0.25.10 + '@esbuild/android-x64': 0.25.10 + '@esbuild/darwin-arm64': 0.25.10 + '@esbuild/darwin-x64': 0.25.10 + '@esbuild/freebsd-arm64': 0.25.10 + '@esbuild/freebsd-x64': 0.25.10 + '@esbuild/linux-arm': 0.25.10 + '@esbuild/linux-arm64': 0.25.10 + '@esbuild/linux-ia32': 0.25.10 + '@esbuild/linux-loong64': 0.25.10 + '@esbuild/linux-mips64el': 0.25.10 + '@esbuild/linux-ppc64': 0.25.10 + '@esbuild/linux-riscv64': 0.25.10 + '@esbuild/linux-s390x': 0.25.10 + '@esbuild/linux-x64': 0.25.10 + '@esbuild/netbsd-arm64': 0.25.10 + '@esbuild/netbsd-x64': 0.25.10 + '@esbuild/openbsd-arm64': 0.25.10 + '@esbuild/openbsd-x64': 0.25.10 + '@esbuild/openharmony-arm64': 0.25.10 + '@esbuild/sunos-x64': 0.25.10 + '@esbuild/win32-arm64': 0.25.10 + '@esbuild/win32-ia32': 0.25.10 + '@esbuild/win32-x64': 0.25.10 dev: true + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: false + + /eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + dev: false + + /eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dev: false + + /eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dev: false + + /eslint@9.36.0: + resolution: {integrity: sha512-hB4FIzXovouYzwzECDcUkJ4OcfOEkXTv2zRY6B9bkwjx/cprAq0uvm1nl7zvQ0/TsUk0zQiN4uPfJpB9m+rPMQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + dependencies: + '@eslint-community/eslint-utils': 4.9.0(eslint@9.36.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.1 + '@eslint/core': 0.15.2 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.36.0 + '@eslint/plugin-kit': 0.3.5 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + dev: false + + /espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + dev: false + + /esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + engines: {node: '>=0.10'} + dependencies: + estraverse: 5.3.0 + dev: false + + /esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + dependencies: + estraverse: 5.3.0 + dev: false + + /estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + dev: false + + /esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + dev: false + + /fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + dev: false + + /fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + dev: false + + /fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + dev: false + + /fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + dev: false + + /fastq@1.19.1: + resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} + dependencies: + reusify: 1.1.0 + dev: false + + /file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + dependencies: + flat-cache: 4.0.1 + dev: false + + /fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + dependencies: + to-regex-range: 5.0.1 + dev: false + + /find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + dev: false + + /flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + dev: false + + /flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + dev: false + + /foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + /fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -342,41 +903,473 @@ packages: resolve-pkg-maps: 1.0.0 dev: true + /glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + dependencies: + is-glob: 4.0.3 + dev: false + + /glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + dependencies: + is-glob: 4.0.3 + dev: false + + /glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + hasBin: true + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.5 + minipass: 7.1.2 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + /globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + dev: false + + /has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + dev: false + + /ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + dev: false + + /import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: false + + /imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + dev: false + + /is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + dev: false + + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + /is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + dependencies: + is-extglob: 2.1.1 + dev: false + + /is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + dev: false + + /isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + /jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + /js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + dependencies: + argparse: 2.0.1 + dev: false + + /json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + dev: false + + /json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + dev: false + + /json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + dev: false + + /keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + dependencies: + json-buffer: 3.0.1 + dev: false + + /levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + dev: false + + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true + + /locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + dependencies: + p-locate: 5.0.0 + dev: false + + /lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + dev: false + + /lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + /merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + dev: false + + /micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + dev: false + + /minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + dependencies: + brace-expansion: 1.1.12 + dev: false + + /minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.2 + + /minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + /ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: false + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + dev: true + + /natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + dev: false + + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: true + + /optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + dev: false + + /p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + dependencies: + yocto-queue: 0.1.0 + dev: false + + /p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + dependencies: + p-limit: 3.1.0 + dev: false + + /package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: false + + /path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + dev: false + + /path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + /path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.2 + + /picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + dev: false + + /pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + dev: true + + /prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + dev: false + + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + dev: false + + /queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: false + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: false + /resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} dev: true + /reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: false + + /rimraf@5.0.10: + resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} + hasBin: true + dependencies: + glob: 10.4.5 + dev: true + + /run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + dependencies: + queue-microtask: 1.2.3 + dev: false + + /semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + dev: false + + /shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + dependencies: + shebang-regex: 3.0.0 + + /shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + /simple-git@3.28.0: resolution: {integrity: sha512-Rs/vQRwsn1ILH1oBUy8NucJlXmnnLeLCfcvbSehkPzbv3wwoFWIdtfd6Ndo6ZPhlPsCZ60CPI4rxurnwAa+a2w==} dependencies: '@kwsites/file-exists': 1.1.1 '@kwsites/promise-deferred': 1.1.1 - debug: 4.4.1 + debug: 4.4.3 transitivePeerDependencies: - supports-color dev: false - /tsx@4.20.3: - resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.2 + + /strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + dependencies: + ansi-regex: 5.0.1 + + /strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.2.2 + + /strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + dev: false + + /sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + ts-interface-checker: 0.1.13 + dev: true + + /supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + dev: false + + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + dev: true + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + dev: true + + /to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + dependencies: + is-number: 7.0.0 + dev: false + + /ts-api-utils@2.1.0(typescript@5.9.2): + resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + dependencies: + typescript: 5.9.2 + dev: false + + /ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: true + + /tsx@4.20.5: + resolution: {integrity: sha512-+wKjMNU9w/EaQayHXb7WA7ZaHY6hN8WgfvHNQ3t1PnU91/7O8TcTnIhCDYTZwnt8JsO9IBqZ30Ln1r7pPF52Aw==} engines: {node: '>=18.0.0'} hasBin: true dependencies: - esbuild: 0.25.8 + esbuild: 0.25.10 get-tsconfig: 4.10.1 optionalDependencies: fsevents: 2.3.3 dev: true - /typescript@5.8.3: - resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + /type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + dependencies: + prelude-ls: 1.2.1 + dev: false + + /typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} engines: {node: '>=14.17'} hasBin: true - dev: true /undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} dev: true + + /uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + dependencies: + punycode: 2.3.1 + dev: false + + /which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + dependencies: + isexe: 2.0.0 + + /word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + dev: false + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.1.2 + + /yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + dev: false diff --git a/common/autoinstallers/rush-commands/src/revert-useless-changes.js b/common/autoinstallers/rush-commands/src/revert-useless-changes.js new file mode 100644 index 0000000000..2ccf7fea7d --- /dev/null +++ b/common/autoinstallers/rush-commands/src/revert-useless-changes.js @@ -0,0 +1,5 @@ +require('sucrase/register'); + +const { main } = require('./revert-useless-changes/cli.ts'); + +main(); diff --git a/common/autoinstallers/rush-commands/src/revert-useless-changes/README.md b/common/autoinstallers/rush-commands/src/revert-useless-changes/README.md new file mode 100644 index 0000000000..fcb8d1974a --- /dev/null +++ b/common/autoinstallers/rush-commands/src/revert-useless-changes/README.md @@ -0,0 +1,244 @@ +# Revert Useless Changes + +A functional programming-based tool to automatically detect and revert files with only cosmetic changes (whitespace or comments) in Git repositories. + +## Architecture Overview + +### ๐Ÿ—๏ธ Core Design Principles + +- **Functional Programming**: Pure functions with minimal side effects +- **Rule-Based Analysis**: Extensible rule system for different change types +- **Immutable Data Flow**: Data flows through pure transformation functions +- **Type Safety**: Full TypeScript coverage with strict typing + +### ๐Ÿ“ฆ Module Structure + +``` +src/revert-useless-changes/ +โ”œโ”€โ”€ types.ts # Core type definitions +โ”œโ”€โ”€ config.ts # Constants and configuration +โ”œโ”€โ”€ utils/ # Pure utility functions +โ”‚ โ”œโ”€โ”€ git.ts # Git operations (getChangedFiles, revertFile, etc.) +โ”‚ โ””โ”€โ”€ file.ts # File system operations (exists, readFile, etc.) +โ”œโ”€โ”€ rules/ # Analysis rules +โ”‚ โ”œโ”€โ”€ index.ts # Rule registry and matching +โ”‚ โ”œโ”€โ”€ whitespace-rule.ts # Detects whitespace-only changes +โ”‚ โ””โ”€โ”€ ast-comment-rule.ts # Detects comment-only changes via AST +โ”œโ”€โ”€ reporter.ts # Output formatting (console, JSON) +โ”œโ”€โ”€ orchestrator.ts # Main workflow coordination +โ”œโ”€โ”€ cli.ts # Command-line interface (Commander.js) +โ””โ”€โ”€ index.ts # Public API exports +``` + +## ๐Ÿ”„ Core Workflow + +```mermaid +graph TD + A[CLI Input] --> B[Parse Arguments] + B --> C[Get Changed Files from Git] + C --> D[Filter Files by Include/Exclude] + D --> E[Analyze Each File] + E --> F[Apply Applicable Rules] + F --> G[Generate Analysis Report] + G --> H{Dry Run?} + H -->|Yes| I[Display Results] + H -->|No| J[Revert Matching Files] + J --> I[Display Results] +``` + +## ๐Ÿงฉ Key Modules + +### 1. **Orchestrator** (`orchestrator.ts`) +**Purpose**: Coordinates the entire analysis workflow + +**Core Function**: `execute(config: Config) -> Promise` + +**Flow**: +```typescript +1. getChangedFiles() -> string[] // Get Git changed files +2. filterFiles() -> string[] // Apply include/exclude patterns +3. analyzeFiles() -> FileAnalysis[] // Analyze each file with rules +4. generateReport() -> AnalysisReport // Create summary statistics +5. performReverts() -> AnalysisReport // Revert files (if not dry run) +``` + +### 2. **Rule System** (`rules/`) +**Purpose**: Pluggable analysis rules for different change types + +**Rule Interface**: +```typescript +interface Rule { + name: string; // Unique identifier + description: string; // Human-readable description + filePatterns: readonly string[]; // Glob patterns for applicable files +} + +type RuleAnalyzer = (filePath: string, config: Config) => Promise +``` + +**Built-in Rules**: +- **WhitespaceRule**: Uses `git diff -w` to detect whitespace-only changes +- **AstCommentRule**: Parses JS/TS with AST, removes comments, compares structure + +**Rule Registry**: +```typescript +getRulesForFile(filePath) -> RuleDefinition[] // Get applicable rules +getAllRules() -> RuleDefinition[] // Get all available rules +``` + +### 3. **Git Utilities** (`utils/git.ts`) +**Purpose**: Pure functions for Git operations + +**Key Functions**: +```typescript +getChangedFiles(options) -> string[] // List modified files +hasOnlyWhitespaceChanges(file, options) -> boolean +getFileContentAtRef(file, options) -> string // Get file at Git ref +revertFile(file, options) -> void // Revert single file +findGitRepositoryRoot(startDir) -> string | null // Find Git repo root +validateGitRepository(cwd) -> void // Validate Git repo or throw +``` + +### 4. **File Utilities** (`utils/file.ts`) +**Purpose**: Pure functions for file system operations + +**Key Functions**: +```typescript +exists(path) -> boolean // Check file existence +readFile(path) -> string // Read file content +matchesPattern(path, patterns) -> boolean // Pattern matching +toAbsolutePath(path, base) -> string // Path resolution +``` + +### 5. **Reporter** (`reporter.ts`) +**Purpose**: Output formatting and user feedback + +**Key Functions**: +```typescript +generateReport(report, config) -> void // Main report output +logProgress(message, config) -> void // Progress updates +logVerbose(message, config) -> void // Detailed logging +logError/logWarning/logSuccess(...) // Status messages +``` + +## ๐Ÿ” Analysis Process + +### File Analysis Flow +```typescript +1. Check file existence +2. Find applicable rules based on file patterns +3. Execute each rule analyzer function +4. Combine results: shouldRevert = any(rule.shouldRevert) +5. Create FileAnalysis with results and matched rule +``` + +### Rule Execution +```typescript +// Whitespace Rule +hasOnlyWhitespaceChanges(file) -> RuleResult { + shouldRevert: git diff -w shows no changes +} + +// AST Comment Rule +analyzeAstCommentRule(file) -> RuleResult { + 1. Parse current and previous versions with TypeScript parser + 2. Remove comment nodes from both ASTs + 3. Deep compare cleaned ASTs + 4. shouldRevert: ASTs are structurally identical +} +``` + +## ๐Ÿ“Š Data Flow + +### Configuration Flow +``` +CLI Arguments -> Config Object -> All Functions +``` + +### Analysis Flow +``` +Git Files -> File Filters -> Rule Analysis -> Report Generation -> Output +``` + +### Types Flow +```typescript +Config -> FileAnalysis[] -> AnalysisReport -> Console/JSON Output +``` + +## ๐ŸŽฏ Extension Points + +### Adding New Rules +```typescript +// 1. Define rule constant +export const NEW_RULE: Rule = { + name: 'new-rule', + description: 'Detects new type of changes', + filePatterns: ['**/*.ext'] +}; + +// 2. Implement analyzer function +export const analyzeNewRule = async (filePath: string, config: Config): Promise => { + // Analysis logic here + return { filePath, ruleName: NEW_RULE.name, shouldRevert: true/false }; +}; + +// 3. Register in rules/index.ts +export const AVAILABLE_RULES = [ + { rule: WHITESPACE_RULE, analyzer: analyzeWhitespaceRule }, + { rule: AST_COMMENT_RULE, analyzer: analyzeAstCommentRule }, + { rule: NEW_RULE, analyzer: analyzeNewRule } +]; +``` + +### Adding New Output Formats +```typescript +// Extend reporter.ts +export const generateXmlReport = (report: AnalysisReport) => { + // XML formatting logic +}; +``` + +## ๐Ÿงช Testing Strategy + +### Unit Testing +- Test each pure function in isolation +- Mock Git operations for deterministic tests +- Test rule analyzers with sample files + +### Integration Testing +- Test full workflow with temporary Git repos +- Test CLI argument parsing +- Test error handling scenarios + +## ๐Ÿš€ Usage + +### Programmatic API +```typescript +import { analyzeAndRevert } from './index'; + +const report = await analyzeAndRevert({ + cwd: '/path/to/project', + dryRun: true, + verbose: false +}); +``` + +### CLI Usage +```bash +# Analyze and show what would be reverted +revert-useless-changes --dry-run --verbose + +# Actually revert files with only whitespace/comment changes +revert-useless-changes + +# Analyze specific file types only +revert-useless-changes --include "**/*.ts" --include "**/*.js" +``` + +## ๐Ÿ’ก Key Benefits + +1. **Safe Operations**: Dry-run mode prevents accidental changes +2. **Intelligent Analysis**: AST-based comment detection, Git-based whitespace detection +3. **Extensible**: Easy to add new rule types +4. **Fast**: Functional approach enables efficient processing +5. **Reliable**: Pure functions are predictable and testable \ No newline at end of file diff --git a/common/autoinstallers/rush-commands/src/revert-useless-changes/cli.ts b/common/autoinstallers/rush-commands/src/revert-useless-changes/cli.ts new file mode 100644 index 0000000000..cb94f4ff21 --- /dev/null +++ b/common/autoinstallers/rush-commands/src/revert-useless-changes/cli.ts @@ -0,0 +1,120 @@ +#!/usr/bin/env node + +import { Command } from 'commander'; +import { Config } from './types'; +import { resolve } from 'path'; +import { existsSync } from 'fs'; +import { validateGitRepository } from './utils/git'; +import { execute } from './orchestrator'; + +/** + * Command line interface for the revert-useless-changes tool + */ +export async function main(): Promise { + const program = new Command(); + + program + .name('revert-useless-changes') + .description( + 'Analyze and revert files with only whitespace or comment changes', + ) + .version('1.0.0') + .option('--cwd ', 'Working directory to analyze', process.cwd()) + .option( + '-d, --dry-run', + 'Show what would be reverted without actually reverting', + false, + ) + .option('-v, --verbose', 'Show verbose output during analysis', false) + .option('-j, --json', 'Output results in JSON format', false) + .option( + '--include ', + 'File patterns to include (glob patterns)', + [], + ) + .option( + '--exclude ', + 'File patterns to exclude (glob patterns)', + ['**/node_modules/**', '**/tmp/**', '**/.git/**'], + ) + .addHelpText( + 'after', + ` +Examples: + $ revert-useless-changes Analyze current directory and revert files + $ revert-useless-changes --dry-run Show what would be reverted without reverting + $ revert-useless-changes --cwd /path/to/project Analyze a specific directory + $ revert-useless-changes --verbose Show detailed analysis information + $ revert-useless-changes --json Output results in JSON format + $ revert-useless-changes --include "**/*.ts" --include "**/*.js" Only analyze TS/JS files + $ revert-useless-changes --exclude "**/test/**" Exclude test directories from analysis +`, + ); + + program.parse(); + const options = program.opts(); + + // Create configuration from command line arguments + const config: Config = { + cwd: resolve(options.cwd), + dryRun: options.dryRun, + verbose: options.verbose, + json: options.json, + include: + options.include && options.include.length > 0 + ? options.include + : undefined, + exclude: + options.exclude && options.exclude.length > 0 + ? options.exclude + : undefined, + }; + + try { + // Validate working directory exists + if (!existsSync(config.cwd)) { + console.error(`Error: Directory does not exist: ${config.cwd}`); + process.exit(1); + } + + // Check if we're in a git repository + validateGitRepository(config.cwd); + + // Run the analysis workflow + const report = await execute(config); + + // Set exit code based on results + if (report.revertErrors.length > 0) { + process.exit(1); + } + } catch (error) { + console.error( + 'Fatal error:', + error instanceof Error ? error.message : String(error), + ); + if (config.verbose && error instanceof Error && error.stack) { + console.error('Stack trace:', error.stack); + } + process.exit(1); + } +} + +// Handle unhandled promise rejections +process.on('unhandledRejection', (reason, promise) => { + console.error('Unhandled Rejection at:', promise, 'reason:', reason); + process.exit(1); +}); + +// Handle uncaught exceptions +process.on('uncaughtException', error => { + console.error('Uncaught Exception:', error); + process.exit(1); +}); + +// Run the CLI +if (require.main === module) { + main().catch(error => { + console.error('CLI execution failed:', error); + process.exit(1); + }); +} diff --git a/common/autoinstallers/rush-commands/src/revert-useless-changes/config.ts b/common/autoinstallers/rush-commands/src/revert-useless-changes/config.ts new file mode 100644 index 0000000000..368e01def4 --- /dev/null +++ b/common/autoinstallers/rush-commands/src/revert-useless-changes/config.ts @@ -0,0 +1,37 @@ +import { Config } from './types'; + +/** + * Default configuration values + */ +export const DEFAULT_CONFIG: Omit = { + dryRun: false, + verbose: false, + json: false, + exclude: [ + 'node_modules/**', + '.git/**', + 'tmp/**', + '**/*.log', + '**/.DS_Store', + '**/dist/**', + '**/build/**', + '**/lib/**', + ] +}; + +/** + * File patterns for different rule types + */ +export const FILE_PATTERNS = { + TYPESCRIPT_JAVASCRIPT: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], + ALL_FILES: ['**/*'], +} as const; + +/** + * Constants for the tool + */ +export const CONSTANTS = { + TOOL_NAME: 'revert-useless-changes', + VERSION: '1.0.0', + DEFAULT_GIT_REF: 'HEAD', +} as const; \ No newline at end of file diff --git a/common/autoinstallers/rush-commands/src/revert-useless-changes/index.ts b/common/autoinstallers/rush-commands/src/revert-useless-changes/index.ts new file mode 100644 index 0000000000..b4327f28a9 --- /dev/null +++ b/common/autoinstallers/rush-commands/src/revert-useless-changes/index.ts @@ -0,0 +1,46 @@ +/** + * Main entry point for revert-useless-changes tool + */ + +// Export main types +export type { + Config, + Rule, + RuleResult, + FileAnalysis, + AnalysisReport, + RevertError, + ChangeType +} from './types'; + +// Export main orchestrator functions +export { execute } from './orchestrator'; + +// Export CLI +export { main as runCLI } from './cli'; + +// Export reporter functions +export * as reporter from './reporter'; + +// Export rules and rule registry +export * from './rules'; + +// Export utilities +export * as gitUtils from './utils/git'; +export * as fileUtils from './utils/file'; + +// Export configuration constants +export { FILE_PATTERNS } from './config'; + +/** + * Programmatic API for analyzing and reverting files + */ +export async function analyzeAndRevert(config: import('./types').Config): Promise { + const { execute } = await import('./orchestrator'); + return execute(config); +} + +/** + * Default export for CLI usage + */ +export default { analyzeAndRevert }; \ No newline at end of file diff --git a/common/autoinstallers/rush-commands/src/revert-useless-changes/orchestrator.ts b/common/autoinstallers/rush-commands/src/revert-useless-changes/orchestrator.ts new file mode 100644 index 0000000000..7bee82713c --- /dev/null +++ b/common/autoinstallers/rush-commands/src/revert-useless-changes/orchestrator.ts @@ -0,0 +1,297 @@ +import { Config, AnalysisReport, FileAnalysis, RevertError } from './types'; +import { getRulesForFile } from './rules'; +import { getChangedFiles, revertFile } from './utils/git'; +import { toAbsolutePath, toRelativePath, exists, matchesPattern } from './utils/file'; +import * as reporter from './reporter'; + +/** + * Execute the full analysis and revert workflow + */ +export const execute = async (config: Config): Promise => { + reporter.logProgress('Starting analysis...', config); + + // Get list of changed files from Git + const changedFiles = getChangedFilesFiltered(config); + reporter.logProgress(`Found ${changedFiles.length} changed files`, config); + + if (changedFiles.length === 0) { + reporter.logWarning('No changed files found. Nothing to analyze.', config); + return createEmptyReport(config); + } + + // Analyze each file + const fileAnalyses = await analyzeFiles(changedFiles, config); + + // Generate report + const report = generateReport(fileAnalyses, config); + + // Perform reverts if not dry run + const updatedReport = !config.dryRun + ? await performReverts(report, config) + : report; + + // Display results + reporter.generateReport(updatedReport, config); + + return updatedReport; +}; + +/** + * Get list of changed files from Git with filtering applied + */ +const getChangedFilesFiltered = (config: Config): string[] => { + try { + const files = getChangedFiles({ cwd: config.cwd }); + + // Filter out files based on patterns if specified + let filteredFiles = files; + + if (config.include && config.include.length > 0) { + filteredFiles = filteredFiles.filter(file => + matchesPattern(file, config.include!) + ); + } + + if (config.exclude && config.exclude.length > 0) { + filteredFiles = filteredFiles.filter(file => + !matchesPattern(file, config.exclude!) + ); + } + + return filteredFiles; + } catch (error) { + reporter.logError(`Failed to get changed files: ${error instanceof Error ? error.message : String(error)}`, config); + return []; + } +}; + +/** + * Analyze all files using applicable rules + */ +const analyzeFiles = async (filePaths: string[], config: Config): Promise => { + const analyses: FileAnalysis[] = []; + + for (let i = 0; i < filePaths.length; i++) { + const filePath = filePaths[i]; + reporter.logProgress(`Analyzing file ${i + 1}/${filePaths.length}: ${toRelativePath(filePath, config.cwd)}`, config); + + try { + const analysis = await analyzeFile(filePath, config); + analyses.push(analysis); + } catch (error) { + reporter.logError(`Failed to analyze ${filePath}: ${error instanceof Error ? error.message : String(error)}`, config); + + // Create error analysis + analyses.push(createErrorAnalysis(filePath, error)); + } + } + + return analyses; +}; + +/** + * Analyze a single file using applicable rules + */ +const analyzeFile = async (filePath: string, config: Config): Promise => { + const absolutePath = toAbsolutePath(filePath, config.cwd); + const fileExists = exists(absolutePath); + + reporter.logVerbose(`Checking if file exists: ${absolutePath} = ${fileExists}`, config); + + if (!fileExists) { + return createDeletedFileAnalysis(absolutePath); + } + + // Get rules that apply to this file + const applicableRules = getRulesForFile(absolutePath); + reporter.logVerbose(`Found ${applicableRules.length} applicable rules for ${absolutePath}`, config); + + if (applicableRules.length === 0) { + return createNoRulesAnalysis(absolutePath); + } + + // Apply each rule + const ruleResults = []; + let shouldRevert = false; + let matchedRule: string | undefined = undefined; + + for (const { rule, analyzer } of applicableRules) { + reporter.logVerbose(`Applying rule: ${rule.name}`, config); + + try { + const result = await analyzer(absolutePath, config); + ruleResults.push(result); + + // If any rule says we should revert, we should revert + if (result.shouldRevert && !shouldRevert) { + shouldRevert = true; + matchedRule = rule.name; + } + } catch (error) { + reporter.logVerbose(`Rule ${rule.name} failed: ${error}`, config); + ruleResults.push(createRuleErrorResult(absolutePath, rule.name, error)); + } + } + + return { + filePath: absolutePath, + exists: true, + shouldRevert, + matchedRule, + ruleResults + }; +}; + +/** + * Create analysis for a deleted file + */ +const createDeletedFileAnalysis = (filePath: string): FileAnalysis => ({ + filePath, + exists: false, + shouldRevert: false, + matchedRule: undefined, + ruleResults: [{ + filePath, + ruleName: 'file-deleted', + shouldRevert: false, + reason: 'File was deleted' + }] +}); + +/** + * Create analysis for a file with no applicable rules + */ +const createNoRulesAnalysis = (filePath: string): FileAnalysis => ({ + filePath, + exists: true, + shouldRevert: false, + matchedRule: undefined, + ruleResults: [{ + filePath, + ruleName: 'no-rules', + shouldRevert: false, + reason: 'No applicable rules found for this file type' + }] +}); + +/** + * Create error analysis for a file that failed to analyze + */ +const createErrorAnalysis = (filePath: string, error: unknown): FileAnalysis => ({ + filePath, + exists: exists(filePath), + shouldRevert: false, + matchedRule: undefined, + ruleResults: [{ + filePath, + ruleName: 'analysis-error', + shouldRevert: false, + reason: 'Analysis failed', + error: error instanceof Error ? error.message : String(error) + }] +}); + +/** + * Create error result for a rule that failed + */ +const createRuleErrorResult = (filePath: string, ruleName: string, error: unknown) => ({ + filePath, + ruleName, + shouldRevert: false, + reason: 'Rule execution failed', + error: error instanceof Error ? error.message : String(error) +}); + +/** + * Generate analysis report from file analyses + */ +const generateReport = (fileAnalyses: FileAnalysis[], config: Config): AnalysisReport => { + const summary = { + totalFiles: fileAnalyses.length, + revertableFiles: fileAnalyses.filter(f => f.shouldRevert).length, + whitespaceOnlyFiles: fileAnalyses.filter(f => f.matchedRule === 'whitespace-only').length, + commentOnlyFiles: fileAnalyses.filter(f => f.matchedRule === 'ast-comment-only').length, + unchangedFiles: fileAnalyses.filter(f => !f.shouldRevert && f.exists).length, + deletedFiles: fileAnalyses.filter(f => !f.exists).length, + errorFiles: fileAnalyses.filter(f => f.ruleResults.some(r => r.error)).length + }; + + return { + timestamp: new Date().toISOString(), + config, + summary, + fileAnalyses, + revertedFiles: [], + revertErrors: [] + }; +}; + +/** + * Perform file reverts based on analysis + */ +const performReverts = async (report: AnalysisReport, config: Config): Promise => { + const filesToRevert = report.fileAnalyses + .filter(analysis => analysis.shouldRevert) + .map(analysis => analysis.filePath); + + if (filesToRevert.length === 0) { + reporter.logProgress('No files to revert.', config); + return report; + } + + reporter.logProgress(`Reverting ${filesToRevert.length} files...`, config); + + const revertedFiles: string[] = []; + const revertErrors: RevertError[] = []; + + for (const filePath of filesToRevert) { + try { + reporter.logVerbose(`Reverting: ${toRelativePath(filePath, config.cwd)}`, config); + + revertFile(filePath, { cwd: config.cwd }); + revertedFiles.push(filePath); + + reporter.logVerbose(`Successfully reverted: ${filePath}`, config); + } catch (error) { + const errorMessage = error instanceof Error ? error.message : String(error); + revertErrors.push({ file: filePath, error: errorMessage }); + + reporter.logError(`Failed to revert ${filePath}: ${errorMessage}`, config); + } + } + + if (revertedFiles.length > 0) { + reporter.logSuccess(`Successfully reverted ${revertedFiles.length} files`, config); + } + + if (revertErrors.length > 0) { + reporter.logWarning(`Failed to revert ${revertErrors.length} files`, config); + } + + // Return updated report + return { + ...report, + revertedFiles, + revertErrors + }; +}; + +/** + * Create an empty report for when no files are found + */ +const createEmptyReport = (config: Config): AnalysisReport => ({ + timestamp: new Date().toISOString(), + config, + summary: { + totalFiles: 0, + revertableFiles: 0, + whitespaceOnlyFiles: 0, + commentOnlyFiles: 0, + unchangedFiles: 0, + deletedFiles: 0, + errorFiles: 0 + }, + fileAnalyses: [], + revertedFiles: [], + revertErrors: [] +}); \ No newline at end of file diff --git a/common/autoinstallers/rush-commands/src/revert-useless-changes/reporter.ts b/common/autoinstallers/rush-commands/src/revert-useless-changes/reporter.ts new file mode 100644 index 0000000000..da5f790b1a --- /dev/null +++ b/common/autoinstallers/rush-commands/src/revert-useless-changes/reporter.ts @@ -0,0 +1,216 @@ +import { AnalysisReport, Config } from './types'; +import { toRelativePath } from './utils/file'; +import chalk from 'chalk'; + +/** + * Generate and display the analysis report + */ +export const generateReport = (report: AnalysisReport, config: Config): void => { + if (config.json) { + outputJson(report); + } else { + outputConsole(report, config); + } +}; + +/** + * Log progress during analysis + */ +export const logProgress = (message: string, config: Config): void => { + if (!config.json) { + console.log(chalk.blue('๐Ÿ”'), message); + } +}; + +/** + * Log verbose messages + */ +export const logVerbose = (message: string, config: Config): void => { + if (config.verbose && !config.json) { + console.log(chalk.gray(`[VERBOSE] ${message}`)); + } +}; + +/** + * Log error messages + */ +export const logError = (message: string, config: Config): void => { + if (!config.json) { + console.error(chalk.red('โŒ'), message); + } +}; + +/** + * Log warning messages + */ +export const logWarning = (message: string, config: Config): void => { + if (!config.json) { + console.warn(chalk.yellow('โš ๏ธ'), message); + } +}; + +/** + * Log success messages + */ +export const logSuccess = (message: string, config: Config): void => { + if (!config.json) { + console.log(chalk.green('โœ…'), message); + } +}; + +/** + * Output results as JSON + */ +const outputJson = (report: AnalysisReport): void => { + console.log(JSON.stringify(report, null, 2)); +}; + +/** + * Output results to console with formatting + */ +const outputConsole = (report: AnalysisReport, config: Config): void => { + console.log(); + console.log(chalk.bold('๐Ÿ“Š ANALYSIS REPORT')); + console.log('='.repeat(60)); + + // Summary + outputSummary(report); + + // File categorization + outputFileCategorization(report, config); + + // Revert results (if not dry run) + if (!config.dryRun) { + outputRevertResults(report, config); + } + + // Footer + outputFooter(report, config); +}; + +/** + * Output summary statistics + */ +const outputSummary = (report: AnalysisReport): void => { + console.log(chalk.bold('\n๐Ÿ“ˆ Summary:')); + console.log(`${chalk.blue('๐Ÿ“ Total files analyzed:')} ${report.summary.totalFiles}`); + console.log(`${chalk.green('๐Ÿ”„ Revertable files:')} ${report.summary.revertableFiles}`); + console.log(` ${chalk.cyan('โ”œโ”€ Whitespace-only:')} ${report.summary.whitespaceOnlyFiles}`); + console.log(` ${chalk.cyan('โ””โ”€ Comment-only:')} ${report.summary.commentOnlyFiles}`); + console.log(`${chalk.yellow('๐Ÿ“ Files with changes:')} ${report.summary.unchangedFiles}`); + console.log(`${chalk.red('๐Ÿ—‘๏ธ Deleted files:')} ${report.summary.deletedFiles}`); + console.log(`${chalk.red('โŒ Error files:')} ${report.summary.errorFiles}`); +}; + +/** + * Output file categorization + */ +const outputFileCategorization = (report: AnalysisReport, config: Config): void => { + const revertableFiles = report.fileAnalyses.filter(f => f.shouldRevert); + const codeChangeFiles = report.fileAnalyses.filter(f => !f.shouldRevert && f.exists); + + if (revertableFiles.length > 0) { + console.log(chalk.bold('\n๐Ÿ”„ REVERTABLE FILES:')); + console.log('-'.repeat(60)); + revertableFiles.slice(0, 15).forEach((analysis, index) => { + const relativePath = toRelativePath(analysis.filePath, config.cwd); + const ruleType = analysis.matchedRule === 'whitespace-only' ? '๐Ÿ”ค Whitespace' : '๐Ÿ’ฌ Comments'; + console.log(`${(index + 1).toString().padStart(3)}. ${ruleType} - ${relativePath}`); + }); + + if (revertableFiles.length > 15) { + console.log(`... and ${revertableFiles.length - 15} more files`); + } + } + + if (codeChangeFiles.length > 0) { + console.log(chalk.bold('\n๐Ÿ“ FILES WITH CODE CHANGES (keeping):')); + console.log('-'.repeat(60)); + codeChangeFiles.slice(0, 10).forEach((analysis, index) => { + const relativePath = toRelativePath(analysis.filePath, config.cwd); + console.log(`${(index + 1).toString().padStart(3)}. ${relativePath}`); + }); + + if (codeChangeFiles.length > 10) { + console.log(`... and ${codeChangeFiles.length - 10} more files`); + } + } + + const deletedFiles = report.fileAnalyses.filter(f => !f.exists); + if (deletedFiles.length > 0) { + console.log(chalk.bold('\n๐Ÿ—‘๏ธ DELETED FILES:')); + console.log('-'.repeat(60)); + deletedFiles.forEach((analysis, index) => { + const relativePath = toRelativePath(analysis.filePath, config.cwd); + console.log(`${(index + 1).toString().padStart(3)}. ${relativePath}`); + }); + } + + const errorFiles = report.fileAnalyses.filter(f => + f.ruleResults.some(r => r.error) + ); + if (errorFiles.length > 0) { + console.log(chalk.bold('\nโŒ ERROR FILES:')); + console.log('-'.repeat(60)); + errorFiles.forEach((analysis, index) => { + const relativePath = toRelativePath(analysis.filePath, config.cwd); + const errors = analysis.ruleResults.filter(r => r.error).map(r => r.error).join('; '); + console.log(`${(index + 1).toString().padStart(3)}. ${relativePath}`); + console.log(` Error: ${errors}`); + }); + } +}; + +/** + * Output revert operation results + */ +const outputRevertResults = (report: AnalysisReport, config: Config): void => { + if (report.revertedFiles.length > 0 || report.revertErrors.length > 0) { + console.log(chalk.bold('\n๐Ÿ”„ REVERT RESULTS:')); + console.log('='.repeat(60)); + + if (report.revertedFiles.length > 0) { + console.log(`${chalk.green('โœ… Successfully reverted:')} ${report.revertedFiles.length} files`); + if (config.verbose) { + report.revertedFiles.forEach(file => { + const relativePath = toRelativePath(file, config.cwd); + console.log(` - ${relativePath}`); + }); + } + } + + if (report.revertErrors.length > 0) { + console.log(`${chalk.red('โŒ Failed to revert:')} ${report.revertErrors.length} files`); + report.revertErrors.forEach(({ file, error }) => { + const relativePath = toRelativePath(file, config.cwd); + console.log(` - ${relativePath}: ${error}`); + }); + } + } +}; + +/** + * Output footer with recommendations + */ +const outputFooter = (report: AnalysisReport, config: Config): void => { + console.log(chalk.bold('\n๐ŸŽฏ RECOMMENDATIONS:')); + console.log('='.repeat(60)); + + if (config.dryRun && report.summary.revertableFiles > 0) { + console.log(chalk.green(`โœ… Found ${report.summary.revertableFiles} files that can be safely reverted`)); + console.log(chalk.cyan('๐Ÿ’ก Run without --dry-run to actually revert these files')); + } else if (!config.dryRun && report.revertedFiles.length > 0) { + console.log(chalk.green(`โœ… Successfully reverted ${report.revertedFiles.length} files`)); + console.log(chalk.cyan("๐Ÿ’ก Run 'git status' to see remaining changes")); + } + + if (report.summary.unchangedFiles > 0) { + console.log(chalk.yellow(`โš ๏ธ ${report.summary.unchangedFiles} files have actual code/content changes and were kept`)); + } + + if (report.summary.errorFiles > 0) { + console.log(chalk.red(`โŒ ${report.summary.errorFiles} files had analysis errors`)); + } + + console.log(); +}; \ No newline at end of file diff --git a/common/autoinstallers/rush-commands/src/revert-useless-changes/rules/ast-comment-rule.ts b/common/autoinstallers/rush-commands/src/revert-useless-changes/rules/ast-comment-rule.ts new file mode 100644 index 0000000000..3a17a73df8 --- /dev/null +++ b/common/autoinstallers/rush-commands/src/revert-useless-changes/rules/ast-comment-rule.ts @@ -0,0 +1,185 @@ +import { RuleResult, Config, Rule } from '../types'; +import { FILE_PATTERNS } from '../config'; +import { readFile, isJavaScriptOrTypeScript, exists } from '../utils/file'; +import { getFileContentAtRef, isNewFile } from '../utils/git'; +import { parse } from '@typescript-eslint/parser'; + +/** + * Rule definition for detecting comment-only changes using AST + */ +export const AST_COMMENT_RULE: Rule = { + name: 'ast-comment-only', + description: 'Detects JS/TS files that have only comment changes using AST comparison', + filePatterns: FILE_PATTERNS.TYPESCRIPT_JAVASCRIPT +} as const; + +/** + * Create a result object for the AST comment rule + */ +const createResult = ( + filePath: string, + shouldRevert: boolean, + reason?: string, + error?: string +): RuleResult => ({ + filePath, + ruleName: AST_COMMENT_RULE.name, + shouldRevert, + reason, + error +}); + +/** + * Log verbose messages if enabled + */ +const log = (message: string, config: Config): void => { + if (config.verbose) { + console.log(`[${AST_COMMENT_RULE.name}] ${message}`); + } +}; + +/** + * Parse file content and return AST with comments removed + */ +const parseWithoutComments = (filePath: string, content: string): any => { + const isTypeScript = filePath.match(/\.(ts|tsx)$/); + const isJSX = filePath.match(/\.(tsx|jsx)$/); + + const ast = parse(content, { + loc: true, + range: true, + comment: true, + ecmaVersion: 'latest' as any, + sourceType: 'module', + ecmaFeatures: { + jsx: !!isJSX, + }, + ...(isTypeScript && { + filePath, + }), + }); + + return removeComments(ast); +}; + +/** + * Recursively remove comment-related properties from AST nodes + */ +const removeComments = (node: any): any => { + if (!node || typeof node !== 'object') { + return node; + } + + if (Array.isArray(node)) { + return node.map(item => removeComments(item)); + } + + const cleaned: any = {}; + for (const [key, value] of Object.entries(node)) { + // Skip comment-related properties + if (key === 'comments' || key === 'leadingComments' || key === 'trailingComments') { + continue; + } + // Skip location information that might differ due to comments + if (key === 'range' || key === 'loc' || key === 'start' || key === 'end') { + continue; + } + + cleaned[key] = removeComments(value); + } + + return cleaned; +}; + +/** + * Deep equality comparison of two objects + */ +const deepEqual = (obj1: any, obj2: any): boolean => { + if (obj1 === obj2) return true; + + if (obj1 == null || obj2 == null) return obj1 === obj2; + + if (typeof obj1 !== typeof obj2) return false; + + if (typeof obj1 !== 'object') return obj1 === obj2; + + if (Array.isArray(obj1) !== Array.isArray(obj2)) return false; + + if (Array.isArray(obj1)) { + if (obj1.length !== obj2.length) return false; + for (let i = 0; i < obj1.length; i++) { + if (!deepEqual(obj1[i], obj2[i])) return false; + } + return true; + } + + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + + if (keys1.length !== keys2.length) return false; + + for (const key of keys1) { + if (!keys2.includes(key)) return false; + if (!deepEqual(obj1[key], obj2[key])) return false; + } + + return true; +}; + +/** + * Analyze a file for comment-only changes using AST comparison + */ +export const analyzeAstCommentRule = async (filePath: string, config: Config): Promise => { + log(`Analyzing file for comment-only changes: ${filePath}`, config); + + try { + // Check if file exists + if (!exists(filePath)) { + return createResult(filePath, false, 'File does not exist'); + } + + // Check if it's a JS/TS file + if (!isJavaScriptOrTypeScript(filePath)) { + return createResult(filePath, false, 'Not a JavaScript/TypeScript file'); + } + + // Skip new files - they don't have a previous version to compare + if (isNewFile(filePath, { cwd: config.cwd })) { + return createResult(filePath, false, 'File is newly added, skipping analysis'); + } + + // Get current file content + const currentContent = readFile(filePath); + + // Get previous file content from git + const previousContent = getFileContentAtRef(filePath, { cwd: config.cwd }); + + // Parse both versions and compare ASTs without comments + const currentAst = parseWithoutComments(filePath, currentContent); + const previousAst = parseWithoutComments(filePath, previousContent); + + // Compare ASTs + const astEqual = deepEqual(currentAst, previousAst); + + if (astEqual) { + return createResult( + filePath, + true, + 'File has only comment changes based on AST comparison' + ); + } else { + return createResult( + filePath, + false, + 'File has code changes beyond comments' + ); + } + } catch (error) { + return createResult( + filePath, + false, + 'Failed to perform AST comparison', + error instanceof Error ? error.message : String(error) + ); + } +}; diff --git a/common/autoinstallers/rush-commands/src/revert-useless-changes/rules/index.ts b/common/autoinstallers/rush-commands/src/revert-useless-changes/rules/index.ts new file mode 100644 index 0000000000..f1a85706b2 --- /dev/null +++ b/common/autoinstallers/rush-commands/src/revert-useless-changes/rules/index.ts @@ -0,0 +1,62 @@ +import { Rule, RuleAnalyzer } from '../types'; +import { WHITESPACE_RULE, analyzeWhitespaceRule } from './whitespace-rule'; +import { AST_COMMENT_RULE, analyzeAstCommentRule } from './ast-comment-rule'; + +/** + * Registry of all available rules with their analyzers + */ +export interface RuleDefinition { + rule: Rule; + analyzer: RuleAnalyzer; +} + +/** + * All available rules with their analysis functions + */ +export const AVAILABLE_RULES: readonly RuleDefinition[] = [ + { rule: WHITESPACE_RULE, analyzer: analyzeWhitespaceRule }, + { rule: AST_COMMENT_RULE, analyzer: analyzeAstCommentRule } +] as const; + +/** + * Get all available rules + */ +export const getAllRules = (): readonly RuleDefinition[] => { + return AVAILABLE_RULES; +}; + +/** + * Get rules that apply to a specific file + */ +export const getRulesForFile = (filePath: string): readonly RuleDefinition[] => { + return AVAILABLE_RULES.filter(({ rule }) => + rule.filePatterns.some(pattern => matchesPattern(filePath, pattern)) + ); +}; + +/** + * Get a rule by name + */ +export const getRuleByName = (name: string): RuleDefinition | undefined => { + return AVAILABLE_RULES.find(({ rule }) => rule.name === name); +}; + +/** + * Simple pattern matching logic + */ +const matchesPattern = (filePath: string, pattern: string): boolean => { + if (pattern === '**/*') return true; + + // Simple extension matching for patterns like '**/*.ts' + const extensionMatch = pattern.match(/\*\*\/\*\.(\w+)$/); + if (extensionMatch && extensionMatch[1]) { + return filePath.endsWith(`.${extensionMatch[1]}`); + } + + // More complex pattern matching could be implemented here + return false; +}; + +// Re-export rule constants and analyzers for convenience +export { WHITESPACE_RULE, analyzeWhitespaceRule } from './whitespace-rule'; +export { AST_COMMENT_RULE, analyzeAstCommentRule } from './ast-comment-rule'; \ No newline at end of file diff --git a/common/autoinstallers/rush-commands/src/revert-useless-changes/rules/whitespace-rule.ts b/common/autoinstallers/rush-commands/src/revert-useless-changes/rules/whitespace-rule.ts new file mode 100644 index 0000000000..45cc52fca3 --- /dev/null +++ b/common/autoinstallers/rush-commands/src/revert-useless-changes/rules/whitespace-rule.ts @@ -0,0 +1,75 @@ +import { RuleResult, Config, Rule } from '../types'; +import { FILE_PATTERNS } from '../config'; +import { hasOnlyWhitespaceChanges } from '../utils/git'; +import { exists } from '../utils/file'; + +/** + * Rule definition for detecting whitespace-only changes + */ +export const WHITESPACE_RULE: Rule = { + name: 'whitespace-only', + description: 'Detects files that have only whitespace changes (spaces, tabs, newlines, blank lines)', + filePatterns: FILE_PATTERNS.ALL_FILES +} as const; + +/** + * Create a result object for the whitespace rule + */ +const createResult = ( + filePath: string, + shouldRevert: boolean, + reason?: string, + error?: string +): RuleResult => ({ + filePath, + ruleName: WHITESPACE_RULE.name, + shouldRevert, + reason, + error +}); + +/** + * Log verbose messages if enabled + */ +const log = (message: string, config: Config): void => { + if (config.verbose) { + console.log(`[${WHITESPACE_RULE.name}] ${message}`); + } +}; + +/** + * Analyze a file for whitespace-only changes + */ +export const analyzeWhitespaceRule = async (filePath: string, config: Config): Promise => { + log(`Analyzing file for whitespace changes: ${filePath}`, config); + + try { + // Check if file exists + if (!exists(filePath)) { + return createResult(filePath, false, 'File does not exist'); + } + + const hasOnlyWhitespace = hasOnlyWhitespaceChanges(filePath, { cwd: config.cwd }); + + if (hasOnlyWhitespace) { + return createResult( + filePath, + true, + 'File has only whitespace changes (spaces, tabs, newlines, blank lines)' + ); + } else { + return createResult( + filePath, + false, + 'File has non-whitespace changes' + ); + } + } catch (error) { + return createResult( + filePath, + false, + 'Failed to check whitespace changes', + error instanceof Error ? error.message : String(error) + ); + } +}; \ No newline at end of file diff --git a/common/autoinstallers/rush-commands/src/revert-useless-changes/types.ts b/common/autoinstallers/rush-commands/src/revert-useless-changes/types.ts new file mode 100644 index 0000000000..dd5491a01f --- /dev/null +++ b/common/autoinstallers/rush-commands/src/revert-useless-changes/types.ts @@ -0,0 +1,124 @@ +/** + * Configuration options for the revert-useless-changes tool + */ +export interface Config { + /** Working directory to analyze */ + cwd: string; + /** Only analyze, don't actually revert files */ + dryRun: boolean; + /** Enable verbose output */ + verbose: boolean; + /** Output results as JSON */ + json: boolean; + /** File patterns to include in analysis */ + include?: string[]; + /** File patterns to exclude from analysis */ + exclude?: string[]; +} + +/** + * Result of analyzing a single file with a rule + */ +export interface RuleResult { + /** The file that was analyzed */ + filePath: string; + /** Name of the rule that was applied */ + ruleName: string; + /** Whether this rule matched (file should be reverted) */ + shouldRevert: boolean; + /** Optional reason why the rule matched or didn't match */ + reason?: string; + /** Any error that occurred during analysis */ + error?: string; +} + +/** + * Analysis results for a single file across all applicable rules + */ +export interface FileAnalysis { + /** The file that was analyzed */ + filePath: string; + /** Results from each rule that was applied */ + ruleResults: RuleResult[]; + /** Whether any rule matched (file should be reverted) */ + shouldRevert: boolean; + /** The rule that matched (if any) */ + matchedRule?: string; + /** Whether the file exists (not deleted) */ + exists: boolean; +} + +/** + * Overall analysis report + */ +export interface AnalysisReport { + /** Timestamp when analysis was performed */ + timestamp: string; + /** Configuration used for analysis */ + config: Config; + /** Summary statistics */ + summary: { + totalFiles: number; + revertableFiles: number; + whitespaceOnlyFiles: number; + commentOnlyFiles: number; + deletedFiles: number; + errorFiles: number; + unchangedFiles: number; + }; + /** Detailed analysis for each file */ + fileAnalyses: FileAnalysis[]; + /** Files that were successfully reverted (if not dry run) */ + revertedFiles: string[]; + /** Files that failed to revert */ + revertErrors: RevertError[]; +} + +/** + * Error information for files that failed to revert + */ +export interface RevertError { + /** File that failed to revert */ + file: string; + /** Error message */ + error: string; +} + +/** + * File change type classification + */ +export enum ChangeType { + WHITESPACE_ONLY = 'whitespace-only', + COMMENT_ONLY = 'comment-only', + CODE_CHANGES = 'code-changes', + DELETED = 'deleted', + ERROR = 'error', + UNCHANGED = 'unchanged' +} + +/** + * Rule definition for analyzing files + */ +export interface Rule { + /** Unique name of the rule */ + readonly name: string; + /** Description of what the rule detects */ + readonly description: string; + /** File patterns this rule applies to (glob patterns) */ + readonly filePatterns: readonly string[]; +} + +/** + * Function type for analyzing files with a rule + */ +export type RuleAnalyzer = (filePath: string, config: Config) => Promise; + +/** + * Options for git operations + */ +export interface GitOptions { + /** Working directory for git operations */ + cwd: string; + /** Git reference to compare against (default: HEAD) */ + ref?: string; +} \ No newline at end of file diff --git a/common/autoinstallers/rush-commands/src/revert-useless-changes/utils/file.ts b/common/autoinstallers/rush-commands/src/revert-useless-changes/utils/file.ts new file mode 100644 index 0000000000..a4b34526fe --- /dev/null +++ b/common/autoinstallers/rush-commands/src/revert-useless-changes/utils/file.ts @@ -0,0 +1,110 @@ +import { readFileSync, existsSync, statSync } from 'fs'; +import { join, relative, isAbsolute } from 'path'; +import { glob } from 'glob'; + +/** + * Read file content safely + */ +export const readFile = (filePath: string): string => { + try { + return readFileSync(filePath, 'utf8'); + } catch (error) { + throw new Error(`Failed to read file ${filePath}: ${error instanceof Error ? error.message : String(error)}`); + } +}; + +/** + * Check if file exists + */ +export const exists = (filePath: string): boolean => { + return existsSync(filePath); +}; + +/** + * Convert relative path to absolute path + */ +export const toAbsolutePath = (filePath: string, basePath: string): string => { + if (isAbsolute(filePath)) { + return filePath; + } + return join(basePath, filePath); +}; + +/** + * Convert absolute path to relative path + */ +export const toRelativePath = (filePath: string, basePath: string): string => { + return relative(basePath, filePath); +}; + +/** + * Match files against glob patterns + */ +export const matchFiles = (patterns: string[], options: { cwd: string; exclude?: string[] }): string[] => { + const allMatches: string[] = []; + + for (const pattern of patterns) { + try { + const matches = glob.sync(pattern, { + cwd: options.cwd, + absolute: true, + ignore: options.exclude || [], + nodir: true + }); + allMatches.push(...matches); + } catch (error) { + console.warn(`Warning: Failed to match pattern ${pattern}:`, error); + } + } + + // Remove duplicates + return Array.from(new Set(allMatches)); +}; + +/** + * Check if a file matches any of the given patterns + */ +export const matchesPattern = (filePath: string, patterns: string[]): boolean => { + return patterns.some(pattern => { + if (pattern === '**/*') return true; + + // Simple extension matching for patterns like '**/*.ts' + const extensionMatch = pattern.match(/\*\*\/\*\.(\w+)$/); + if (extensionMatch && extensionMatch[1]) { + return filePath.endsWith(`.${extensionMatch[1]}`); + } + + // More complex pattern matching could be implemented here + // For now, we'll use simple string matching + return filePath.includes(pattern.replace(/\*\*/g, '').replace(/\*/g, '')); + }); +}; + +/** + * Get file extension + */ +export const getExtension = (filePath: string): string => { + const lastDot = filePath.lastIndexOf('.'); + if (lastDot === -1 || lastDot === 0) return ''; + return filePath.substring(lastDot + 1); +}; + +/** + * Check if file is a JavaScript/TypeScript file + */ +export const isJavaScriptOrTypeScript = (filePath: string): boolean => { + const ext = getExtension(filePath).toLowerCase(); + return ['js', 'jsx', 'ts', 'tsx'].includes(ext); +}; + +/** + * Get file size in bytes + */ +export const getFileSize = (filePath: string): number => { + try { + const stats = statSync(filePath); + return stats.size; + } catch { + return 0; + } +}; \ No newline at end of file diff --git a/common/autoinstallers/rush-commands/src/revert-useless-changes/utils/git.ts b/common/autoinstallers/rush-commands/src/revert-useless-changes/utils/git.ts new file mode 100644 index 0000000000..6d4645df0d --- /dev/null +++ b/common/autoinstallers/rush-commands/src/revert-useless-changes/utils/git.ts @@ -0,0 +1,220 @@ +import { execSync } from 'child_process'; +import { resolve, join, dirname } from 'path'; +import { existsSync } from 'fs'; +import { GitOptions } from '../types'; + +/** + * Get the project root directory (git repository root) + */ +export const getProjectRoot = (): string => { + try { + return execSync('git rev-parse --show-toplevel', { + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'ignore'] + }).trim(); + } catch (error) { + throw new Error('Not in a git repository or git is not available'); + } +}; + +/** + * Get list of changed files in git working directory (includes both modified and added files) + */ +export const getChangedFiles = (options: GitOptions = { cwd: process.cwd() }): string[] => { + try { + // Get both modified files and added files + const modifiedOutput = execSync('git diff --name-only', { + cwd: options.cwd, + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'ignore'] + }); + + const addedOutput = execSync('git diff --cached --name-only', { + cwd: options.cwd, + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'ignore'] + }); + + const modifiedFiles = modifiedOutput + .split('\n') + .map(file => file.trim()) + .filter(file => file.length > 0); + + const addedFiles = addedOutput + .split('\n') + .map(file => file.trim()) + .filter(file => file.length > 0); + + // Combine and deduplicate + const allFiles = Array.from(new Set([...modifiedFiles, ...addedFiles])); + + return allFiles.map(file => resolve(options.cwd, file)); + } catch (error) { + throw new Error(`Failed to get changed files: ${error instanceof Error ? error.message : String(error)}`); + } +}; + +/** + * Get list of modified files relative to a git reference + */ +export const getModifiedFiles = (options: GitOptions = { cwd: process.cwd() }): string[] => { + try { + const output = execSync(`git diff --name-only ${options.ref || 'HEAD'}`, { + cwd: options.cwd, + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'ignore'] + }); + + return output + .split('\n') + .map(file => file.trim()) + .filter(file => file.length > 0); + } catch (error) { + throw new Error(`Failed to get modified files: ${error instanceof Error ? error.message : String(error)}`); + } +}; + +/** + * Get file content from a specific git reference + */ +export const getFileContentAtRef = (filePath: string, options: GitOptions): string => { + try { + const projectRoot = getProjectRoot(); + const relativePath = filePath.startsWith(projectRoot) + ? filePath.substring(projectRoot.length + 1) + : filePath; + + return execSync(`git show ${options.ref || 'HEAD'}:${relativePath}`, { + cwd: options.cwd, + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'ignore'] + }); + } catch (error) { + throw new Error(`Failed to get file content at ref: ${error instanceof Error ? error.message : String(error)}`); + } +}; + +/** + * Check if a file is newly added (not in HEAD) + */ +export const isNewFile = (filePath: string, options: GitOptions): boolean => { + try { + const projectRoot = getProjectRoot(); + const relativePath = filePath.startsWith(projectRoot) + ? filePath.substring(projectRoot.length + 1) + : filePath; + + execSync(`git show ${options.ref || 'HEAD'}:${relativePath}`, { + cwd: options.cwd, + stdio: ['pipe', 'pipe', 'ignore'] + }); + + // If git show succeeds, the file exists in HEAD, so it's not new + return false; + } catch (error) { + // If git show fails, the file doesn't exist in HEAD, so it's new + return true; + } +}; + +/** + * Check if a file has only whitespace changes (including blank lines) + */ +export const hasOnlyWhitespaceChanges = (filePath: string, options: GitOptions): boolean => { + try { + // Skip analysis for new files + if (isNewFile(filePath, options)) { + return false; + } + + const projectRoot = getProjectRoot(); + const relativePath = filePath.startsWith(projectRoot) + ? filePath.substring(projectRoot.length + 1) + : filePath; + + // Use -w to ignore whitespace changes and -b to ignore blank line changes + // --ignore-space-at-eol ignores changes in whitespace at EOL + // --ignore-blank-lines ignores changes whose lines are all blank + const output = execSync(`git diff -w -b --ignore-space-at-eol --ignore-blank-lines ${options.ref || 'HEAD'} -- "${relativePath}"`, { + cwd: options.cwd, + encoding: 'utf8', + stdio: ['pipe', 'pipe', 'ignore'] + }); + + return output.trim() === ''; + } catch (error) { + // If git diff fails, assume the file has changes + return false; + } +}; + +/** + * Revert a file to its state in the git reference + */ +export const revertFile = (filePath: string, options: GitOptions): void => { + try { + const projectRoot = getProjectRoot(); + const relativePath = filePath.startsWith(projectRoot) + ? filePath.substring(projectRoot.length + 1) + : filePath; + + execSync(`git checkout ${options.ref || 'HEAD'} -- "${relativePath}"`, { + cwd: options.cwd, + stdio: ['pipe', 'pipe', 'pipe'] + }); + } catch (error) { + throw new Error(`Failed to revert file: ${error instanceof Error ? error.message : String(error)}`); + } +}; + +/** + * Check if we're in a git repository + */ +export const isGitRepository = (cwd: string = process.cwd()): boolean => { + try { + execSync('git rev-parse --git-dir', { + cwd, + stdio: ['pipe', 'pipe', 'ignore'] + }); + return true; + } catch { + return false; + } +}; + +/** + * Find git repository root by recursively searching for .git directory + * @param startDir Directory to start searching from + * @returns Git repository root path or null if not found + */ +export const findGitRepositoryRoot = (startDir: string): string | null => { + // Check if .git exists in current directory + const gitDir = join(startDir, '.git'); + if (existsSync(gitDir)) { + return startDir; + } + + // Recursively check parent directories + let currentDir = startDir; + while (currentDir !== dirname(currentDir)) { + const parentGitDir = join(currentDir, '.git'); + if (existsSync(parentGitDir)) { + return currentDir; + } + currentDir = dirname(currentDir); + } + + return null; +}; + +/** + * Validate that a directory is within a git repository + * @param cwd Directory to validate + * @throws Error if not in a git repository + */ +export const validateGitRepository = (cwd: string): void => { + const gitRoot = findGitRepositoryRoot(cwd); + if (!gitRoot) { + throw new Error(`Not a git repository (or any parent directory): ${cwd}`); + } +};