diff --git a/README.md b/README.md index bc2f0e1..09b97a5 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,12 @@ Upgrade your dependencies interactively. Works with npm, yarn, pnpm, and bun. npx inup ``` +Scan deeper package layouts: + +```bash +npx inup --max-depth 15 +``` + Or install globally: ```bash @@ -50,7 +56,10 @@ inup [options] -d, --dir Run in specific directory -e, --exclude Skip directories (comma-separated regex) +-i, --ignore Ignore packages (comma-separated, glob supported) +--max-depth Maximum scan depth for package discovery (default: 10) --package-manager Force package manager (npm, yarn, pnpm, bun) +--debug Write verbose debug logs ``` ## 🔒 Privacy diff --git a/package.json b/package.json index 5fb47da..06ba736 100644 --- a/package.json +++ b/package.json @@ -55,12 +55,12 @@ "devDependencies": { "@types/inquirer": "^9.0.9", "@types/keypress.js": "^2.1.3", - "@types/node": "^24.10.1", + "@types/node": "^24.12.0", "@types/semver": "^7.7.1", - "@vitest/coverage-v8": "^3.2.4", + "@vitest/coverage-v8": "^4.1.0", "prettier": "^3.8.1", "typescript": "^5.9.3", - "vitest": "^3.2.4" + "vitest": "^4.1.0" }, "dependencies": { "chalk": "^5.6.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6a4f7bd..5cfa91e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,7 +19,7 @@ importers: version: 4.0.0 inquirer: specifier: ^13.3.2 - version: 13.3.2(@types/node@24.10.1) + version: 13.3.2(@types/node@24.12.0) keypress: specifier: ^0.2.1 version: 0.2.1 @@ -40,14 +40,14 @@ importers: specifier: ^2.1.3 version: 2.1.3 '@types/node': - specifier: ^24.10.1 - version: 24.10.1 + specifier: ^24.12.0 + version: 24.12.0 '@types/semver': specifier: ^7.7.1 version: 7.7.1 '@vitest/coverage-v8': - specifier: ^3.2.4 - version: 3.2.4(vitest@3.2.4(@types/node@24.10.1)) + specifier: ^4.1.0 + version: 4.1.0(vitest@4.1.0(@types/node@24.12.0)(vite@7.3.1(@types/node@24.12.0))) prettier: specifier: ^3.8.1 version: 3.8.1 @@ -55,15 +55,11 @@ importers: specifier: ^5.9.3 version: 5.9.3 vitest: - specifier: ^3.2.4 - version: 3.2.4(@types/node@24.10.1) + specifier: ^4.1.0 + version: 4.1.0(@types/node@24.12.0)(vite@7.3.1(@types/node@24.12.0)) packages: - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -72,13 +68,13 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.6': - resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==} + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/types@7.28.6': - resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@1.0.2': @@ -375,17 +371,6 @@ packages: '@types/node': optional: true - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} @@ -396,10 +381,6 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - '@rollup/rollup-android-arm-eabi@4.57.0': resolution: {integrity: sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==} cpu: [arm] @@ -538,6 +519,9 @@ packages: cpu: [x64] os: [win32] + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -553,8 +537,8 @@ packages: '@types/keypress.js@2.1.3': resolution: {integrity: sha512-jv+1ObzMLgR6Jl5MB29mHn8HfY251/IEz3EFm6du4gSyIe6zSEHC5Tjay/UoGCYfvYmWDsDZtsItwiSdFnLzWQ==} - '@types/node@24.10.1': - resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} + '@types/node@24.12.0': + resolution: {integrity: sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==} '@types/semver@7.7.1': resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} @@ -562,79 +546,53 @@ packages: '@types/through@0.0.33': resolution: {integrity: sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==} - '@vitest/coverage-v8@3.2.4': - resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==} + '@vitest/coverage-v8@4.1.0': + resolution: {integrity: sha512-nDWulKeik2bL2Va/Wl4x7DLuTKAXa906iRFooIRPR+huHkcvp9QDkPQ2RJdmjOFrqOqvNfoSQLF68deE3xC3CQ==} peerDependencies: - '@vitest/browser': 3.2.4 - vitest: 3.2.4 + '@vitest/browser': 4.1.0 + vitest: 4.1.0 peerDependenciesMeta: '@vitest/browser': optional: true - '@vitest/expect@3.2.4': - resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + '@vitest/expect@4.1.0': + resolution: {integrity: sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==} - '@vitest/mocker@3.2.4': - resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + '@vitest/mocker@4.1.0': + resolution: {integrity: sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==} peerDependencies: msw: ^2.4.9 - vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@3.2.4': - resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} - - '@vitest/runner@3.2.4': - resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + '@vitest/pretty-format@4.1.0': + resolution: {integrity: sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==} - '@vitest/snapshot@3.2.4': - resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + '@vitest/runner@4.1.0': + resolution: {integrity: sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==} - '@vitest/spy@3.2.4': - resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + '@vitest/snapshot@4.1.0': + resolution: {integrity: sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==} - '@vitest/utils@3.2.4': - resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + '@vitest/spy@4.1.0': + resolution: {integrity: sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==} - 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'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} + '@vitest/utils@4.1.0': + resolution: {integrity: sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==} assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - ast-v8-to-istanbul@0.3.10: - resolution: {integrity: sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} + ast-v8-to-istanbul@1.0.0: + resolution: {integrity: sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==} - chai@5.3.3: - resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} chalk@5.6.2: @@ -644,57 +602,23 @@ packages: chardet@2.1.1: resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} - check-error@2.1.3: - resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} - engines: {node: '>= 16'} - cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - commander@14.0.3: resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} engines: {node: '>=20'} - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - deep-eql@5.0.2: - resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} - engines: {node: '>=6'} - - 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==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} env-paths@4.0.0: resolution: {integrity: sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw==} engines: {node: '>=20'} - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} esbuild@0.27.2: resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} @@ -726,20 +650,11 @@ packages: picomatch: optional: true - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -760,17 +675,10 @@ packages: '@types/node': optional: true - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - is-safe-filename@0.1.1: resolution: {integrity: sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g==} engines: {node: '>=20'} - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - istanbul-lib-coverage@3.2.2: resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} engines: {node: '>=8'} @@ -779,50 +687,26 @@ packages: resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} engines: {node: '>=10'} - istanbul-lib-source-maps@5.0.6: - resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} - engines: {node: '>=10'} - istanbul-reports@3.2.0: resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} engines: {node: '>=8'} - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - js-tokens@9.0.1: - resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-tokens@10.0.0: + resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==} keypress@0.2.1: resolution: {integrity: sha512-HjorDJFNhnM4SicvaUXac0X77NiskggxJdesG72+O5zBKpSqKFCrqmndKVqpu3pFqkla0St6uGk8Ju0sCurrmg==} - loupe@3.2.1: - resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magicast@0.3.5: - resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==} + magicast@0.5.2: + resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} - engines: {node: '>=16 || 14 >=14.17'} - - 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==} - mute-stream@3.0.0: resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} engines: {node: ^20.17.0 || >=22.9.0} @@ -835,24 +719,12 @@ packages: nanospinner@1.2.2: resolution: {integrity: sha512-Zt/AmG6qRU3e+WnzGGLuMCEAO/dAu45stNbHY223tUxldaDAeE+FxSPsd9Q+j+paejmm0ZbrNVs5Sraqy3dRxA==} - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - 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'} + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - pathval@2.0.1: - resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} - engines: {node: '>= 14.16'} - picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -889,14 +761,6 @@ packages: engines: {node: '>=10'} hasBin: true - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} @@ -911,56 +775,26 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@3.10.0: - resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} - engines: {node: '>=12'} - - strip-literal@3.1.0: - resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + std-env@4.0.0: + resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - test-exclude@7.0.1: - resolution: {integrity: sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==} - engines: {node: '>=18'} - tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} - tinyexec@0.3.2: - resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + tinyexec@1.0.4: + resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} + engines: {node: '>=18'} tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} - tinypool@1.1.1: - resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} - engines: {node: ^18.0.0 || >=20.0.0} - - tinyrainbow@2.0.0: - resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} - engines: {node: '>=14.0.0'} - - tinyspy@4.0.4: - resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} tslib@2.8.1: @@ -978,11 +812,6 @@ packages: resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==} engines: {node: '>=20.18.1'} - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - vite@7.3.1: resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1023,26 +852,33 @@ packages: yaml: optional: true - vitest@3.2.4: - resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + vitest@4.1.0: + resolution: {integrity: sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' - '@types/debug': ^4.1.12 - '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 - '@vitest/browser': 3.2.4 - '@vitest/ui': 3.2.4 + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.0 + '@vitest/browser-preview': 4.1.0 + '@vitest/browser-webdriverio': 4.1.0 + '@vitest/ui': 4.1.0 happy-dom: '*' jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 peerDependenciesMeta: '@edge-runtime/vm': optional: true - '@types/debug': + '@opentelemetry/api': optional: true '@types/node': optional: true - '@vitest/browser': + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': optional: true '@vitest/ui': optional: true @@ -1051,40 +887,22 @@ packages: jsdom: optional: true - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - snapshots: - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} - '@babel/parser@7.28.6': + '@babel/parser@7.29.2': dependencies: - '@babel/types': 7.28.6 + '@babel/types': 7.29.0 - '@babel/types@7.28.6': + '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 @@ -1171,138 +989,122 @@ snapshots: '@inquirer/ansi@2.0.4': {} - '@inquirer/checkbox@5.1.2(@types/node@24.10.1)': + '@inquirer/checkbox@5.1.2(@types/node@24.12.0)': dependencies: '@inquirer/ansi': 2.0.4 - '@inquirer/core': 11.1.7(@types/node@24.10.1) + '@inquirer/core': 11.1.7(@types/node@24.12.0) '@inquirer/figures': 2.0.4 - '@inquirer/type': 4.0.4(@types/node@24.10.1) + '@inquirer/type': 4.0.4(@types/node@24.12.0) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 - '@inquirer/confirm@6.0.10(@types/node@24.10.1)': + '@inquirer/confirm@6.0.10(@types/node@24.12.0)': dependencies: - '@inquirer/core': 11.1.7(@types/node@24.10.1) - '@inquirer/type': 4.0.4(@types/node@24.10.1) + '@inquirer/core': 11.1.7(@types/node@24.12.0) + '@inquirer/type': 4.0.4(@types/node@24.12.0) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 - '@inquirer/core@11.1.7(@types/node@24.10.1)': + '@inquirer/core@11.1.7(@types/node@24.12.0)': dependencies: '@inquirer/ansi': 2.0.4 '@inquirer/figures': 2.0.4 - '@inquirer/type': 4.0.4(@types/node@24.10.1) + '@inquirer/type': 4.0.4(@types/node@24.12.0) cli-width: 4.1.0 fast-wrap-ansi: 0.2.0 mute-stream: 3.0.0 signal-exit: 4.1.0 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 - '@inquirer/editor@5.0.10(@types/node@24.10.1)': + '@inquirer/editor@5.0.10(@types/node@24.12.0)': dependencies: - '@inquirer/core': 11.1.7(@types/node@24.10.1) - '@inquirer/external-editor': 2.0.4(@types/node@24.10.1) - '@inquirer/type': 4.0.4(@types/node@24.10.1) + '@inquirer/core': 11.1.7(@types/node@24.12.0) + '@inquirer/external-editor': 2.0.4(@types/node@24.12.0) + '@inquirer/type': 4.0.4(@types/node@24.12.0) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 - '@inquirer/expand@5.0.10(@types/node@24.10.1)': + '@inquirer/expand@5.0.10(@types/node@24.12.0)': dependencies: - '@inquirer/core': 11.1.7(@types/node@24.10.1) - '@inquirer/type': 4.0.4(@types/node@24.10.1) + '@inquirer/core': 11.1.7(@types/node@24.12.0) + '@inquirer/type': 4.0.4(@types/node@24.12.0) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 - '@inquirer/external-editor@2.0.4(@types/node@24.10.1)': + '@inquirer/external-editor@2.0.4(@types/node@24.12.0)': dependencies: chardet: 2.1.1 iconv-lite: 0.7.2 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 '@inquirer/figures@2.0.4': {} - '@inquirer/input@5.0.10(@types/node@24.10.1)': + '@inquirer/input@5.0.10(@types/node@24.12.0)': dependencies: - '@inquirer/core': 11.1.7(@types/node@24.10.1) - '@inquirer/type': 4.0.4(@types/node@24.10.1) + '@inquirer/core': 11.1.7(@types/node@24.12.0) + '@inquirer/type': 4.0.4(@types/node@24.12.0) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 - '@inquirer/number@4.0.10(@types/node@24.10.1)': + '@inquirer/number@4.0.10(@types/node@24.12.0)': dependencies: - '@inquirer/core': 11.1.7(@types/node@24.10.1) - '@inquirer/type': 4.0.4(@types/node@24.10.1) + '@inquirer/core': 11.1.7(@types/node@24.12.0) + '@inquirer/type': 4.0.4(@types/node@24.12.0) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 - '@inquirer/password@5.0.10(@types/node@24.10.1)': + '@inquirer/password@5.0.10(@types/node@24.12.0)': dependencies: '@inquirer/ansi': 2.0.4 - '@inquirer/core': 11.1.7(@types/node@24.10.1) - '@inquirer/type': 4.0.4(@types/node@24.10.1) + '@inquirer/core': 11.1.7(@types/node@24.12.0) + '@inquirer/type': 4.0.4(@types/node@24.12.0) optionalDependencies: - '@types/node': 24.10.1 - - '@inquirer/prompts@8.3.2(@types/node@24.10.1)': - dependencies: - '@inquirer/checkbox': 5.1.2(@types/node@24.10.1) - '@inquirer/confirm': 6.0.10(@types/node@24.10.1) - '@inquirer/editor': 5.0.10(@types/node@24.10.1) - '@inquirer/expand': 5.0.10(@types/node@24.10.1) - '@inquirer/input': 5.0.10(@types/node@24.10.1) - '@inquirer/number': 4.0.10(@types/node@24.10.1) - '@inquirer/password': 5.0.10(@types/node@24.10.1) - '@inquirer/rawlist': 5.2.6(@types/node@24.10.1) - '@inquirer/search': 4.1.6(@types/node@24.10.1) - '@inquirer/select': 5.1.2(@types/node@24.10.1) + '@types/node': 24.12.0 + + '@inquirer/prompts@8.3.2(@types/node@24.12.0)': + dependencies: + '@inquirer/checkbox': 5.1.2(@types/node@24.12.0) + '@inquirer/confirm': 6.0.10(@types/node@24.12.0) + '@inquirer/editor': 5.0.10(@types/node@24.12.0) + '@inquirer/expand': 5.0.10(@types/node@24.12.0) + '@inquirer/input': 5.0.10(@types/node@24.12.0) + '@inquirer/number': 4.0.10(@types/node@24.12.0) + '@inquirer/password': 5.0.10(@types/node@24.12.0) + '@inquirer/rawlist': 5.2.6(@types/node@24.12.0) + '@inquirer/search': 4.1.6(@types/node@24.12.0) + '@inquirer/select': 5.1.2(@types/node@24.12.0) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 - '@inquirer/rawlist@5.2.6(@types/node@24.10.1)': + '@inquirer/rawlist@5.2.6(@types/node@24.12.0)': dependencies: - '@inquirer/core': 11.1.7(@types/node@24.10.1) - '@inquirer/type': 4.0.4(@types/node@24.10.1) + '@inquirer/core': 11.1.7(@types/node@24.12.0) + '@inquirer/type': 4.0.4(@types/node@24.12.0) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 - '@inquirer/search@4.1.6(@types/node@24.10.1)': + '@inquirer/search@4.1.6(@types/node@24.12.0)': dependencies: - '@inquirer/core': 11.1.7(@types/node@24.10.1) + '@inquirer/core': 11.1.7(@types/node@24.12.0) '@inquirer/figures': 2.0.4 - '@inquirer/type': 4.0.4(@types/node@24.10.1) + '@inquirer/type': 4.0.4(@types/node@24.12.0) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 - '@inquirer/select@5.1.2(@types/node@24.10.1)': + '@inquirer/select@5.1.2(@types/node@24.12.0)': dependencies: '@inquirer/ansi': 2.0.4 - '@inquirer/core': 11.1.7(@types/node@24.10.1) + '@inquirer/core': 11.1.7(@types/node@24.12.0) '@inquirer/figures': 2.0.4 - '@inquirer/type': 4.0.4(@types/node@24.10.1) + '@inquirer/type': 4.0.4(@types/node@24.12.0) optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 - '@inquirer/type@4.0.4(@types/node@24.10.1)': + '@inquirer/type@4.0.4(@types/node@24.12.0)': optionalDependencies: - '@types/node': 24.10.1 - - '@isaacs/cliui@8.0.2': - 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 - - '@istanbuljs/schema@0.1.3': {} - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 + '@types/node': 24.12.0 '@jridgewell/resolve-uri@3.1.2': {} @@ -1313,9 +1115,6 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@pkgjs/parseargs@0.11.0': - optional: true - '@rollup/rollup-android-arm-eabi@4.57.0': optional: true @@ -1391,6 +1190,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.57.0': optional: true + '@standard-schema/spec@1.1.0': {} + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -1407,7 +1208,7 @@ snapshots: '@types/keypress.js@2.1.3': {} - '@types/node@24.10.1': + '@types/node@24.12.0': dependencies: undici-types: 7.16.0 @@ -1415,142 +1216,88 @@ snapshots: '@types/through@0.0.33': dependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 - '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/node@24.10.1))': + '@vitest/coverage-v8@4.1.0(vitest@4.1.0(@types/node@24.12.0)(vite@7.3.1(@types/node@24.12.0)))': dependencies: - '@ampproject/remapping': 2.3.0 '@bcoe/v8-coverage': 1.0.2 - ast-v8-to-istanbul: 0.3.10 - debug: 4.4.3 + '@vitest/utils': 4.1.0 + ast-v8-to-istanbul: 1.0.0 istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 - istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.2.0 - magic-string: 0.30.21 - magicast: 0.3.5 - std-env: 3.10.0 - test-exclude: 7.0.1 - tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/node@24.10.1) - transitivePeerDependencies: - - supports-color + magicast: 0.5.2 + obug: 2.1.1 + std-env: 4.0.0 + tinyrainbow: 3.1.0 + vitest: 4.1.0(@types/node@24.12.0)(vite@7.3.1(@types/node@24.12.0)) - '@vitest/expect@3.2.4': + '@vitest/expect@4.1.0': dependencies: + '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - tinyrainbow: 2.0.0 + '@vitest/spy': 4.1.0 + '@vitest/utils': 4.1.0 + chai: 6.2.2 + tinyrainbow: 3.1.0 - '@vitest/mocker@3.2.4(vite@7.3.1(@types/node@24.10.1))': + '@vitest/mocker@4.1.0(vite@7.3.1(@types/node@24.12.0))': dependencies: - '@vitest/spy': 3.2.4 + '@vitest/spy': 4.1.0 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.1(@types/node@24.10.1) + vite: 7.3.1(@types/node@24.12.0) - '@vitest/pretty-format@3.2.4': + '@vitest/pretty-format@4.1.0': dependencies: - tinyrainbow: 2.0.0 + tinyrainbow: 3.1.0 - '@vitest/runner@3.2.4': + '@vitest/runner@4.1.0': dependencies: - '@vitest/utils': 3.2.4 + '@vitest/utils': 4.1.0 pathe: 2.0.3 - strip-literal: 3.1.0 - '@vitest/snapshot@3.2.4': + '@vitest/snapshot@4.1.0': dependencies: - '@vitest/pretty-format': 3.2.4 + '@vitest/pretty-format': 4.1.0 + '@vitest/utils': 4.1.0 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@3.2.4': - dependencies: - tinyspy: 4.0.4 - - '@vitest/utils@3.2.4': - dependencies: - '@vitest/pretty-format': 3.2.4 - loupe: 3.2.1 - tinyrainbow: 2.0.0 - - ansi-regex@5.0.1: {} - - ansi-regex@6.2.2: {} + '@vitest/spy@4.1.0': {} - ansi-styles@4.3.0: + '@vitest/utils@4.1.0': dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.3: {} + '@vitest/pretty-format': 4.1.0 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 assertion-error@2.0.1: {} - ast-v8-to-istanbul@0.3.10: + ast-v8-to-istanbul@1.0.0: dependencies: '@jridgewell/trace-mapping': 0.3.31 estree-walker: 3.0.3 - js-tokens: 9.0.1 - - balanced-match@1.0.2: {} + js-tokens: 10.0.0 - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - - cac@6.7.14: {} - - chai@5.3.3: - dependencies: - assertion-error: 2.0.1 - check-error: 2.1.3 - deep-eql: 5.0.2 - loupe: 3.2.1 - pathval: 2.0.1 + chai@6.2.2: {} chalk@5.6.2: {} chardet@2.1.1: {} - check-error@2.1.3: {} - cli-width@4.1.0: {} - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - commander@14.0.3: {} - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - deep-eql@5.0.2: {} - - eastasianwidth@0.2.0: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} + convert-source-map@2.0.0: {} env-paths@4.0.0: dependencies: is-safe-filename: 0.1.1 - es-module-lexer@1.7.0: {} + es-module-lexer@2.0.0: {} esbuild@0.27.2: optionalDependencies: @@ -1601,23 +1348,9 @@ snapshots: optionalDependencies: picomatch: 4.0.3 - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - fsevents@2.3.3: optional: true - glob@10.5.0: - 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 - has-flag@4.0.0: {} html-escaper@2.0.2: {} @@ -1626,24 +1359,20 @@ snapshots: dependencies: safer-buffer: 2.1.2 - inquirer@13.3.2(@types/node@24.10.1): + inquirer@13.3.2(@types/node@24.12.0): dependencies: '@inquirer/ansi': 2.0.4 - '@inquirer/core': 11.1.7(@types/node@24.10.1) - '@inquirer/prompts': 8.3.2(@types/node@24.10.1) - '@inquirer/type': 4.0.4(@types/node@24.10.1) + '@inquirer/core': 11.1.7(@types/node@24.12.0) + '@inquirer/prompts': 8.3.2(@types/node@24.12.0) + '@inquirer/type': 4.0.4(@types/node@24.12.0) mute-stream: 3.0.0 run-async: 4.0.6 rxjs: 7.8.2 optionalDependencies: - '@types/node': 24.10.1 - - is-fullwidth-code-point@3.0.0: {} + '@types/node': 24.12.0 is-safe-filename@0.1.1: {} - isexe@2.0.0: {} - istanbul-lib-coverage@3.2.2: {} istanbul-lib-report@3.0.1: @@ -1652,55 +1381,29 @@ snapshots: make-dir: 4.0.0 supports-color: 7.2.0 - istanbul-lib-source-maps@5.0.6: - dependencies: - '@jridgewell/trace-mapping': 0.3.31 - debug: 4.4.3 - istanbul-lib-coverage: 3.2.2 - transitivePeerDependencies: - - supports-color - istanbul-reports@3.2.0: dependencies: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - js-tokens@9.0.1: {} + js-tokens@10.0.0: {} keypress@0.2.1: {} - loupe@3.2.1: {} - - lru-cache@10.4.3: {} - magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magicast@0.3.5: + magicast@0.5.2: dependencies: - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 source-map-js: 1.2.1 make-dir@4.0.0: dependencies: semver: 7.7.4 - minimatch@9.0.5: - dependencies: - brace-expansion: 2.0.2 - - minipass@7.1.2: {} - - ms@2.1.3: {} - mute-stream@3.0.0: {} nanoid@3.3.11: {} @@ -1709,19 +1412,10 @@ snapshots: dependencies: picocolors: 1.1.1 - package-json-from-dist@1.0.1: {} - - path-key@3.1.1: {} - - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.2 + obug@2.1.1: {} pathe@2.0.3: {} - pathval@2.0.1: {} - picocolors@1.1.1: {} picomatch@4.0.3: {} @@ -1775,12 +1469,6 @@ snapshots: semver@7.7.4: {} - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - siginfo@2.0.0: {} signal-exit@4.1.0: {} @@ -1789,56 +1477,22 @@ snapshots: stackback@0.0.2: {} - std-env@3.10.0: {} - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.1.2 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.1.2: - dependencies: - ansi-regex: 6.2.2 - - strip-literal@3.1.0: - dependencies: - js-tokens: 9.0.1 + std-env@4.0.0: {} supports-color@7.2.0: dependencies: has-flag: 4.0.0 - test-exclude@7.0.1: - dependencies: - '@istanbuljs/schema': 0.1.3 - glob: 10.5.0 - minimatch: 9.0.5 - tinybench@2.9.0: {} - tinyexec@0.3.2: {} + tinyexec@1.0.4: {} tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 - tinypool@1.1.1: {} - - tinyrainbow@2.0.0: {} - - tinyspy@4.0.4: {} + tinyrainbow@3.1.0: {} tslib@2.8.1: {} @@ -1848,28 +1502,7 @@ snapshots: undici@7.24.4: {} - vite-node@3.2.4(@types/node@24.10.1): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 7.3.1(@types/node@24.10.1) - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - vite@7.3.1(@types/node@24.10.1): + vite@7.3.1(@types/node@24.12.0): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -1878,67 +1511,37 @@ snapshots: rollup: 4.57.0 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 fsevents: 2.3.3 - vitest@3.2.4(@types/node@24.10.1): + vitest@4.1.0(@types/node@24.12.0)(vite@7.3.1(@types/node@24.12.0)): dependencies: - '@types/chai': 5.2.3 - '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.1(@types/node@24.10.1)) - '@vitest/pretty-format': 3.2.4 - '@vitest/runner': 3.2.4 - '@vitest/snapshot': 3.2.4 - '@vitest/spy': 3.2.4 - '@vitest/utils': 3.2.4 - chai: 5.3.3 - debug: 4.4.3 + '@vitest/expect': 4.1.0 + '@vitest/mocker': 4.1.0(vite@7.3.1(@types/node@24.12.0)) + '@vitest/pretty-format': 4.1.0 + '@vitest/runner': 4.1.0 + '@vitest/snapshot': 4.1.0 + '@vitest/spy': 4.1.0 + '@vitest/utils': 4.1.0 + es-module-lexer: 2.0.0 expect-type: 1.3.0 magic-string: 0.30.21 + obug: 2.1.1 pathe: 2.0.3 picomatch: 4.0.3 - std-env: 3.10.0 + std-env: 4.0.0 tinybench: 2.9.0 - tinyexec: 0.3.2 + tinyexec: 1.0.4 tinyglobby: 0.2.15 - tinypool: 1.1.1 - tinyrainbow: 2.0.0 - vite: 7.3.1(@types/node@24.10.1) - vite-node: 3.2.4(@types/node@24.10.1) + tinyrainbow: 3.1.0 + vite: 7.3.1(@types/node@24.12.0) why-is-node-running: 2.3.0 optionalDependencies: - '@types/node': 24.10.1 + '@types/node': 24.12.0 transitivePeerDependencies: - - jiti - - less - - lightningcss - msw - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - yaml - - which@2.0.2: - dependencies: - isexe: 2.0.0 why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.1.2 diff --git a/src/cli.ts b/src/cli.ts index 2084b98..ccd11eb 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -21,14 +21,12 @@ program .option('-d, --dir ', 'specify directory to run in', process.cwd()) .option('-e, --exclude ', 'exclude paths matching regex patterns (comma-separated)', '') .option('-i, --ignore ', 'ignore packages (comma-separated, supports glob patterns like @babel/*)') + .option('--max-depth ', 'maximum directory depth for package.json discovery', '10') .option('--package-manager ', 'manually specify package manager (npm, yarn, pnpm, bun)') .option('--debug', 'write verbose debug log to /tmp/inup-debug-YYYY-MM-DD.log') .action(async (options) => { console.log(chalk.bold.blue(`🚀 `) + chalk.bold.red(`i`) + chalk.bold.yellow(`n`) + chalk.bold.blue(`u`) + chalk.bold.magenta(`p`) + `\n`) - // Check for updates in the background (non-blocking) - const updateCheckPromise = checkForUpdateAsync('inup', packageJson.version) - const cwd = resolve(options.dir) if (options.debug || process.env.INUP_DEBUG === '1') { @@ -56,6 +54,16 @@ program : [] const ignorePackages = [...new Set([...cliIgnorePatterns, ...(projectConfig.ignore || [])])] + const maxDepth = Number.parseInt(options.maxDepth, 10) + if (!Number.isInteger(maxDepth) || maxDepth < 0) { + console.error(chalk.red(`Invalid max depth: ${options.maxDepth}`)) + console.error(chalk.yellow('Expected a non-negative integer, for example: --max-depth 10')) + process.exit(1) + } + + // Check for updates in the background (non-blocking) + const updateCheckPromise = checkForUpdateAsync('inup', packageJson.version) + // Validate package manager if provided let packageManager: PackageManager | undefined if (options.packageManager) { @@ -71,6 +79,7 @@ program const upgrader = new UpgradeRunner({ cwd, excludePatterns, + maxDepth, ignorePackages, packageManager, debug: options.debug || process.env.INUP_DEBUG === '1', diff --git a/src/core/package-detector.ts b/src/core/package-detector.ts index dce063d..aa8004e 100644 --- a/src/core/package-detector.ts +++ b/src/core/package-detector.ts @@ -3,7 +3,7 @@ import { PackageInfo, PackageJson, UpgradeOptions } from '../types' import { findPackageJson, readPackageJson, - findAllPackageJsonFiles, + findAllPackageJsonFilesAsync, collectAllDependenciesAsync, findClosestMinorVersion, } from '../utils' @@ -18,11 +18,13 @@ export class PackageDetector { private cwd: string private excludePatterns: string[] private ignorePackages: string[] + private maxDepth: number constructor(options?: UpgradeOptions) { this.cwd = options?.cwd || process.cwd() this.excludePatterns = options?.excludePatterns || [] this.ignorePackages = options?.ignorePackages || [] + this.maxDepth = options?.maxDepth ?? 10 this.packageJsonPath = findPackageJson(this.cwd) if (this.packageJsonPath) { this.packageJson = readPackageJson(this.packageJsonPath) @@ -45,7 +47,7 @@ export class PackageDetector { // Always check all package.json files recursively with timeout protection this.showProgress('🔍 Scanning repository for package.json files...') const tScan = Date.now() - const allPackageJsonFiles = this.findPackageJsonFilesWithTimeout(30000) // 30 second timeout + const allPackageJsonFiles = await this.findPackageJsonFilesWithTimeout(30000) // 30 second timeout debugLog.perf('PackageDetector', `file scan (${allPackageJsonFiles.length} files)`, tScan, { files: allPackageJsonFiles, }) @@ -222,20 +224,35 @@ export class PackageDetector { } } - private findPackageJsonFilesWithTimeout(timeoutMs: number): string[] { - // Synchronous file search with depth limiting and symlink protection - // The timeout parameter is kept for future async implementation + private async findPackageJsonFilesWithTimeout(timeoutMs: number): Promise { try { - return findAllPackageJsonFiles( - this.cwd, - this.excludePatterns, - 10, - (currentDir: string, foundCount: number) => { - // Show scanning progress with current directory and count - const truncatedDir = currentDir.length > 50 ? '...' + currentDir.slice(-47) : currentDir - this.showProgress(`🔍 Scanning ${truncatedDir} (found ${foundCount})`) + let timeoutId: NodeJS.Timeout | undefined + + try { + return await Promise.race([ + findAllPackageJsonFilesAsync( + this.cwd, + this.excludePatterns, + this.maxDepth, + (currentDir: string, foundCount: number) => { + // Show scanning progress with current directory and count + const truncatedDir = + currentDir.length > 50 ? '...' + currentDir.slice(-47) : currentDir + this.showProgress(`🔍 Scanning ${truncatedDir} (found ${foundCount})`) + } + ), + new Promise((_, reject) => { + timeoutId = setTimeout(() => { + reject(new Error(`Scan timed out after ${timeoutMs}ms`)) + }, timeoutMs) + timeoutId.unref?.() + }), + ]) + } finally { + if (timeoutId) { + clearTimeout(timeoutId) } - ) + } } catch (err) { throw new Error( `Failed to scan for package.json files: ${err}. Try using --exclude patterns to skip problematic directories.` diff --git a/src/types.ts b/src/types.ts index 7f8a228..2894824 100644 --- a/src/types.ts +++ b/src/types.ts @@ -70,6 +70,7 @@ export interface PackageManagerInfo { export interface UpgradeOptions { cwd?: string excludePatterns?: string[] + maxDepth?: number // Maximum package.json scan depth, defaults to 10 packageManager?: PackageManager // Manual override for package manager ignorePackages?: string[] // Package names/patterns to ignore (from .inuprc or --ignore flag) debug?: boolean // Write verbose debug log to /tmp/inup-debug-YYYY-MM-DD.log diff --git a/src/utils/filesystem.ts b/src/utils/filesystem.ts index 7f2d4cc..7ff62c1 100644 --- a/src/utils/filesystem.ts +++ b/src/utils/filesystem.ts @@ -59,6 +59,26 @@ export interface CollectDependenciesOptions { includeOptionalDeps?: boolean } +export interface PackageJsonScanOptions { + concurrency?: number +} + +const SKIP_DIRS = new Set([ + 'node_modules', + 'dist', + 'build', + 'coverage', + 'out', + 'lib', + 'es', + 'esm', + 'cjs', +]) + +function shouldSkipDirectory(name: string): boolean { + return name.startsWith('.') || SKIP_DIRS.has(name) +} + /** * Collects all dependencies from multiple package.json files. * Always includes regular dependencies and devDependencies. @@ -164,6 +184,8 @@ export function findAllPackageJsonFiles( const packageJsonFiles: string[] = [] const visitedPaths = new Set() let directoriesScanned = 0 + let lastProgressAt = 0 + const progressIntervalMs = 250 // Compile regex patterns for exclude filtering const excludeRegexes = excludePatterns.map((pattern) => new RegExp(pattern, 'i')) @@ -172,6 +194,19 @@ export function findAllPackageJsonFiles( return excludeRegexes.some((regex) => regex.test(relativePath)) } + function reportProgress(currentDir: string, force: boolean = false): void { + if (!onProgress) return + + const now = Date.now() + if (!force && now - lastProgressAt < progressIntervalMs) { + return + } + + lastProgressAt = now + const relativePath = relative(rootDir, currentDir) || '.' + onProgress(relativePath, packageJsonFiles.length) + } + function traverseDirectory(dir: string, depth: number = 0): void { // Prevent infinite recursion with depth limit if (depth > maxDepth) { @@ -190,13 +225,13 @@ export function findAllPackageJsonFiles( // Report progress every 10 directories or on first scan if (onProgress && (directoriesScanned % 10 === 0 || directoriesScanned === 1)) { - const relativePath = relative(rootDir, dir) || '.' - onProgress(relativePath, packageJsonFiles.length) + reportProgress(dir, true) } const files = readdirSync(dir) for (const file of files) { + reportProgress(dir) const fullPath = join(dir, file) const relativePath = relative(rootDir, fullPath) @@ -213,26 +248,7 @@ export function findAllPackageJsonFiles( continue } - // Skip common build and dependency directories - const skipDirs = [ - 'node_modules', - '.git', - 'dist', - 'build', - '.next', - 'coverage', - '.cache', - 'out', - '.output', - '.nuxt', - '.vercel', - '.netlify', - 'lib', - 'es', - 'esm', - 'cjs', - ] - if (stat.isDirectory() && !file.startsWith('.') && !skipDirs.includes(file)) { + if (stat.isDirectory() && !shouldSkipDirectory(file)) { traverseDirectory(fullPath, depth + 1) } else if (file === 'package.json' && stat.isFile()) { packageJsonFiles.push(fullPath) @@ -246,3 +262,145 @@ export function findAllPackageJsonFiles( traverseDirectory(rootDir) return packageJsonFiles } + +/** + * Find all package.json files recursively with bounded parallel directory traversal. + */ +export async function findAllPackageJsonFilesAsync( + rootDir: string = process.cwd(), + excludePatterns: string[] = [], + maxDepth: number = 10, + onProgress?: (current: string, found: number) => void, + options: PackageJsonScanOptions = {} +): Promise { + const packageJsonFiles: string[] = [] + const visitedPaths = new Set() + let directoriesScanned = 0 + let lastProgressAt = 0 + const progressIntervalMs = 250 + const concurrency = Math.max(1, Math.min(options.concurrency ?? 16, 64)) + + const excludeRegexes = excludePatterns.map((pattern) => new RegExp(pattern, 'i')) + + function shouldExcludePath(relativePath: string): boolean { + return excludeRegexes.some((regex) => regex.test(relativePath)) + } + + function reportProgress(currentDir: string, force: boolean = false): void { + if (!onProgress) return + + const now = Date.now() + if (!force && now - lastProgressAt < progressIntervalMs) { + return + } + + lastProgressAt = now + const relativePath = relative(rootDir, currentDir) || '.' + onProgress(relativePath, packageJsonFiles.length) + } + + const pending: Array<{ dir: string; depth: number }> = [] + let activeTasks = 0 + let failedError: unknown = null + let resolveDone: (() => void) | null = null + let rejectDone: ((error: unknown) => void) | null = null + + const done = new Promise((resolve, reject) => { + resolveDone = resolve + rejectDone = reject + }) + + function finishIfIdle(): void { + if (pending.length === 0 && activeTasks === 0) { + resolveDone?.() + } + } + + function schedule(dir: string, depth: number): void { + pending.push({ dir, depth }) + pump() + } + + async function processDirectory(dir: string, depth: number): Promise { + if (depth > maxDepth) { + return + } + + let realPath: string + try { + realPath = await fsPromises.realpath(dir) + } catch { + return + } + + if (visitedPaths.has(realPath)) { + return + } + visitedPaths.add(realPath) + + directoriesScanned++ + if (directoriesScanned % 10 === 0 || directoriesScanned === 1) { + reportProgress(dir, true) + } + + let files: string[] + try { + files = await fsPromises.readdir(dir) + } catch { + return + } + + for (const file of files) { + reportProgress(dir) + + const fullPath = join(dir, file) + const relativePath = relative(rootDir, fullPath) + + if (shouldExcludePath(relativePath)) { + continue + } + + let stat + try { + stat = await fsPromises.stat(fullPath) + } catch { + continue + } + + if (stat.isDirectory() && !shouldSkipDirectory(file)) { + schedule(fullPath, depth + 1) + } else if (file === 'package.json' && stat.isFile()) { + packageJsonFiles.push(fullPath) + } + } + } + + function pump(): void { + while (activeTasks < concurrency && pending.length > 0 && !failedError) { + const next = pending.shift() + if (!next) break + + activeTasks++ + void processDirectory(next.dir, next.depth) + .catch((error) => { + if (!failedError) { + failedError = error + rejectDone?.(error) + } + }) + .finally(() => { + activeTasks-- + if (failedError) { + return + } + pump() + finishIfIdle() + }) + } + } + + schedule(rootDir, 0) + await done + + return packageJsonFiles +} diff --git a/test/unit/services/jsdelivr-registry.retries.test.ts b/test/unit/services/jsdelivr-registry.retries.test.ts index 41dbf04..850f3e0 100644 --- a/test/unit/services/jsdelivr-registry.retries.test.ts +++ b/test/unit/services/jsdelivr-registry.retries.test.ts @@ -3,11 +3,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' const requestMock = vi.fn() const closeMock = vi.fn() const getAllPackageDataMock = vi.fn() +const PoolMock = vi.fn( + class MockPool { + close = closeMock + } +) vi.mock('undici', () => ({ - Pool: vi.fn().mockImplementation(() => ({ - close: closeMock, - })), + Pool: PoolMock, request: requestMock, })) diff --git a/test/unit/utils/filesystem.test.ts b/test/unit/utils/filesystem.test.ts index 8684ad9..57f6bdb 100644 --- a/test/unit/utils/filesystem.test.ts +++ b/test/unit/utils/filesystem.test.ts @@ -1,4 +1,4 @@ -import { describe, it, expect, beforeEach, afterEach } from 'vitest' +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import { mkdirSync, writeFileSync, rmSync, existsSync, mkdtempSync } from 'fs' import { join } from 'path' import { tmpdir } from 'os' @@ -9,6 +9,7 @@ import { collectAllDependencies, collectAllDependenciesAsync, findAllPackageJsonFiles, + findAllPackageJsonFilesAsync, findWorkspaceRoot, } from '../../../src/utils/filesystem' @@ -265,6 +266,18 @@ describe('filesystem utils', () => { expect(result[0]).toBe(join(testDir, 'package.json')) }) + it('should skip hidden directories', () => { + writeFileSync(join(testDir, 'package.json'), '{}') + + const hiddenDir = join(testDir, '.turbo', 'nested-package') + mkdirSync(hiddenDir, { recursive: true }) + writeFileSync(join(hiddenDir, 'package.json'), '{}') + + const result = findAllPackageJsonFiles(testDir) + + expect(result).toEqual([join(testDir, 'package.json')]) + }) + it('should skip directories matching exclude patterns', () => { writeFileSync(join(testDir, 'package.json'), '{}') @@ -295,6 +308,34 @@ describe('filesystem utils', () => { expect(progressCalls.length).toBeGreaterThan(0) }) + it('should keep reporting progress while scanning a large directory', () => { + writeFileSync(join(testDir, 'package.json'), '{}') + + const largeDir = join(testDir, 'large-dir') + mkdirSync(largeDir, { recursive: true }) + for (let i = 0; i < 20; i++) { + writeFileSync(join(largeDir, `file-${i}.txt`), 'content') + } + + const progressCalls: Array<{ current: string; found: number }> = [] + let now = 0 + const dateNowSpy = vi.spyOn(Date, 'now').mockImplementation(() => { + now += 100 + return now + }) + + try { + findAllPackageJsonFiles(testDir, [], 10, (current, found) => { + progressCalls.push({ current, found }) + }) + } finally { + dateNowSpy.mockRestore() + } + + expect(progressCalls.length).toBeGreaterThan(1) + expect(progressCalls.some((call) => call.current === 'large-dir')).toBe(true) + }) + it('should respect max depth limit', () => { // Create deeply nested structure let currentDir = testDir @@ -311,6 +352,95 @@ describe('filesystem utils', () => { }) }) + describe('findAllPackageJsonFilesAsync()', () => { + it('should find package.json files recursively', async () => { + writeFileSync(join(testDir, 'package.json'), '{}') + + const packagesDir = join(testDir, 'packages') + mkdirSync(join(packagesDir, 'pkg-a'), { recursive: true }) + mkdirSync(join(packagesDir, 'pkg-b'), { recursive: true }) + + writeFileSync(join(packagesDir, 'pkg-a', 'package.json'), '{}') + writeFileSync(join(packagesDir, 'pkg-b', 'package.json'), '{}') + + const result = await findAllPackageJsonFilesAsync(testDir) + + expect(result).toHaveLength(3) + expect(result).toContain(join(testDir, 'package.json')) + expect(result).toContain(join(packagesDir, 'pkg-a', 'package.json')) + expect(result).toContain(join(packagesDir, 'pkg-b', 'package.json')) + }) + + it('should skip node_modules directories and exclude patterns', async () => { + writeFileSync(join(testDir, 'package.json'), '{}') + + const nodeModulesDir = join(testDir, 'node_modules', 'some-package') + const excludedDir = join(testDir, 'skip-me') + mkdirSync(nodeModulesDir, { recursive: true }) + mkdirSync(excludedDir, { recursive: true }) + + writeFileSync(join(nodeModulesDir, 'package.json'), '{}') + writeFileSync(join(excludedDir, 'package.json'), '{}') + + const result = await findAllPackageJsonFilesAsync(testDir, ['^skip-me']) + + expect(result).toEqual([join(testDir, 'package.json')]) + }) + + it('should skip hidden directories', async () => { + writeFileSync(join(testDir, 'package.json'), '{}') + + const hiddenDir = join(testDir, '.turbo', 'nested-package') + mkdirSync(hiddenDir, { recursive: true }) + writeFileSync(join(hiddenDir, 'package.json'), '{}') + + const result = await findAllPackageJsonFilesAsync(testDir) + + expect(result).toEqual([join(testDir, 'package.json')]) + }) + + it('should call progress callback while scanning large directories', async () => { + writeFileSync(join(testDir, 'package.json'), '{}') + + const largeDir = join(testDir, 'large-dir') + mkdirSync(largeDir, { recursive: true }) + for (let i = 0; i < 20; i++) { + writeFileSync(join(largeDir, `file-${i}.txt`), 'content') + } + + const progressCalls: Array<{ current: string; found: number }> = [] + let now = 0 + const dateNowSpy = vi.spyOn(Date, 'now').mockImplementation(() => { + now += 100 + return now + }) + + try { + await findAllPackageJsonFilesAsync(testDir, [], 10, (current, found) => { + progressCalls.push({ current, found }) + }) + } finally { + dateNowSpy.mockRestore() + } + + expect(progressCalls.length).toBeGreaterThan(1) + expect(progressCalls.some((call) => call.current === 'large-dir')).toBe(true) + }) + + it('should respect max depth limit', async () => { + let currentDir = testDir + for (let i = 0; i < 15; i++) { + currentDir = join(currentDir, `level-${i}`) + mkdirSync(currentDir, { recursive: true }) + writeFileSync(join(currentDir, 'package.json'), '{}') + } + + const result = await findAllPackageJsonFilesAsync(testDir, [], 5) + + expect(result.length).toBeLessThan(15) + }) + }) + describe('findWorkspaceRoot()', () => { it('should find workspace root with pnpm', () => { writeFileSync(