diff --git a/package-lock.json b/package-lock.json index f75c960bb11..2db3f038c4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "license": "MIT", "dependencies": { "@fastify/static": "^10.0.0", - "@inquirer/search": "^4.3.3", + "@inquirer/prompts": "^8.7.2", "@netlify/ai": "^1.0.1", "@netlify/api": "^15.1.2", "@netlify/blobs": "^11.1.3", @@ -66,7 +66,6 @@ "http-proxy": "^1.18.1", "http-proxy-middleware": "^3.0.5", "https-proxy-agent": "^8.0.0", - "inquirer": "^8.2.7", "is-docker": "^4.0.0", "is-stream": "^4.0.1", "is-wsl": "^3.1.0", @@ -129,7 +128,6 @@ "@types/express": "^5.0.6", "@types/folder-walker": "^3.2.5", "@types/gitconfiglocal": "^2.0.3", - "@types/inquirer": "^9.0.9", "@types/jsonwebtoken": "^9.0.10", "@types/lodash.shuffle": "^4.2.9", "@types/multiparty": "^4.2.1", @@ -2173,6 +2171,50 @@ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" } }, + "node_modules/@inquirer/checkbox": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.2.5.tgz", + "integrity": "sha512-bRt8J8m+Fot9CXv+zNQGXUq2ET0MggR1fPz7v6edN6MFYmsbfGnMmkmWZJEegMKqrAC8ej/o1sqisHZXZJMAfQ==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.8", + "@inquirer/core": "^12.0.3", + "@inquirer/figures": "^2.0.9", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.3.2.tgz", + "integrity": "sha512-Xvr/0HggjddPtGppuqVmxhTw+Hr8PvsZ/k0HmOEaAqQEt80OITNkFWnsdNmyT0/eM4Ab+iJLx2R8rctlEyfSVg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.3", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@inquirer/core": { "version": "12.0.3", "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-12.0.3.tgz", @@ -2229,15 +2271,39 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@inquirer/external-editor": { - "version": "1.0.2", + "node_modules/@inquirer/editor": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.3.3.tgz", + "integrity": "sha512-YsKkS2q63IiLtaDK/9nqzdComN97SDQrmKiyNggN+ceP4ty+Z6VwyTz3FpjeUWeW1Efss2xHFKCC9sx7hnrsxg==", "license": "MIT", "dependencies": { - "chardet": "^2.1.0", - "iconv-lite": "^0.7.0" + "@inquirer/core": "^12.0.3", + "@inquirer/external-editor": "^3.0.5", + "@inquirer/type": "4.1.1" }, "engines": { - "node": ">=18" + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor/node_modules/@inquirer/external-editor": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.5.tgz", + "integrity": "sha512-f3QQJRIX5ZEneBHNUIuPjmbdzHnmRFJA8r2dkcb8q+OM5Uv5KtnuAttQumnrjcBVBM3mcTX1CkmtAkU58VRZxg==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.2" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" }, "peerDependencies": { "@types/node": ">=18" @@ -2248,7 +2314,7 @@ } } }, - "node_modules/@inquirer/external-editor/node_modules/iconv-lite": { + "node_modules/@inquirer/editor/node_modules/iconv-lite": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", @@ -2264,6 +2330,27 @@ "url": "https://opencollective.com/express" } }, + "node_modules/@inquirer/expand": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.1.5.tgz", + "integrity": "sha512-uHuXLmXW+TtIfT/9vSBotypAkqn1n34Ul+CLGPos/xANyO4Ff5xZzkYhbKR4NEcfVK4a9mHQOpwVZzluSHFRGw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.3", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@inquirer/figures": { "version": "2.0.9", "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.9.tgz", @@ -2273,6 +2360,120 @@ "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" } }, + "node_modules/@inquirer/input": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.1.6.tgz", + "integrity": "sha512-HtcJhB2QFVXbLuJ5S3syhNbTUVxYvwqV4VRBDkQceBloC9bmTViUoRFP5PbSaDZb3HzfPmpuU/gG4ybVBz4FHA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.3", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.2.3.tgz", + "integrity": "sha512-6Yuwh1NGSbu1Lo4N1EWjXs1jKRntLg/ZCwhmeorEHde90v1XxAozdbd4Iu30eOQLW+6h1hp2O9ujNfLSbTPJnA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.3", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.2.2.tgz", + "integrity": "sha512-W9zYdyzogK+6110mqwaSJWCBu2yA5Q/OfnGSjjZB1bNpHlmUozXxTl0+QOZBNeVd6Qo81/qT75gW05gLAtITxw==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.8", + "@inquirer/core": "^12.0.3", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "8.7.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.7.2.tgz", + "integrity": "sha512-QoRB4wFIjgH5iOhSjoIKMkTvSHDuV+O3OITlIqAYO0oK5x364GJILXiMBvlPiE+klg7Xx9tq5XVqQHcGUDYYPA==", + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^5.2.5", + "@inquirer/confirm": "^6.3.2", + "@inquirer/editor": "^5.3.3", + "@inquirer/expand": "^5.1.5", + "@inquirer/input": "^5.1.6", + "@inquirer/number": "^4.2.3", + "@inquirer/password": "^5.2.2", + "@inquirer/rawlist": "^5.3.5", + "@inquirer/search": "^4.3.3", + "@inquirer/select": "^5.2.5" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.3.5.tgz", + "integrity": "sha512-1oHky1ONfCOwNrnkQGDE1oaSij/3fI6HFMSf2H/WsGO2lEyDX9My82iggITSy9ddSZ8yk8j9v41OI0fVoSIoaA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^12.0.3", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@inquirer/search": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.3.3.tgz", @@ -2295,6 +2496,29 @@ } } }, + "node_modules/@inquirer/select": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.2.5.tgz", + "integrity": "sha512-9kc15hr8r/kI+3DO/xLog5nOzTz1jqsHXa6JBFzmQKhkoJ8Slda1I1L/uD8ZSZ9tF1yp79wwXe7mclvX1rqR2Q==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.8", + "@inquirer/core": "^12.0.3", + "@inquirer/figures": "^2.0.9", + "@inquirer/type": "4.1.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "node_modules/@inquirer/type": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.1.1.tgz", @@ -6841,15 +7065,6 @@ "@types/node": "*" } }, - "node_modules/@types/inquirer": { - "version": "9.0.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/through": "*", - "rxjs": "^7.2.0" - } - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "dev": true, @@ -7004,14 +7219,6 @@ "source-map": "^0.6.0" } }, - "node_modules/@types/through": { - "version": "0.0.33", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/triple-beam": { "version": "1.3.5", "license": "MIT" @@ -9661,27 +9868,6 @@ "file-uri-to-path": "1.0.0" } }, - "node_modules/bl": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/body-parser": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", @@ -9880,28 +10066,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer": { - "version": "5.7.1", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/buffer-crc32": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", @@ -10240,7 +10404,9 @@ } }, "node_modules/chardet": { - "version": "2.1.0", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", "license": "MIT" }, "node_modules/check-error": { @@ -10378,33 +10544,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "license": "MIT", - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "3.0.0", - "license": "ISC", - "engines": { - "node": ">= 10" - } - }, "node_modules/clipanion": { "version": "4.0.0-rc.4", "dev": true, @@ -10509,13 +10648,6 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/clone": { - "version": "1.0.4", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, "node_modules/color": { "version": "3.2.1", "license": "MIT", @@ -11142,16 +11274,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/defaults": { - "version": "1.0.4", - "license": "MIT", - "dependencies": { - "clone": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -14195,172 +14317,6 @@ "version": "1.3.8", "license": "ISC" }, - "node_modules/inquirer": { - "version": "8.2.7", - "license": "MIT", - "dependencies": { - "@inquirer/external-editor": "^1.0.0", - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/inquirer/node_modules/ansi-escapes": { - "version": "4.3.2", - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inquirer/node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/inquirer/node_modules/emoji-regex": { - "version": "8.0.0", - "license": "MIT" - }, - "node_modules/inquirer/node_modules/escape-string-regexp": { - "version": "1.0.5", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/inquirer/node_modules/figures": { - "version": "3.2.0", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inquirer/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/string-width": { - "version": "4.2.3", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/strip-ansi": { - "version": "6.0.1", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/supports-color": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/inquirer/node_modules/type-fest": { - "version": "0.21.3", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/inquirer/node_modules/wrap-ansi": { - "version": "6.2.0", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -14823,13 +14779,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -15727,6 +15676,7 @@ "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, "license": "MIT" }, "node_modules/lodash.includes": { @@ -15788,77 +15738,6 @@ "node": ">=16.17.0" } }, - "node_modules/log-symbols": { - "version": "4.1.0", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/node_modules/is-unicode-supported": { - "version": "0.1.0", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/log-update": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-8.0.0.tgz", @@ -16430,10 +16309,6 @@ "node": ">= 0.10" } }, - "node_modules/mute-stream": { - "version": "0.0.8", - "license": "ISC" - }, "node_modules/nan": { "version": "2.23.0", "license": "MIT", @@ -16934,101 +16809,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "5.4.1", - "license": "MIT", - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ora/node_modules/is-unicode-supported": { - "version": "0.1.0", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "6.0.1", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/os-name": { "version": "6.1.0", "license": "MIT", @@ -18632,17 +18412,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "license": "MIT", - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ret": { "version": "0.5.0", "license": "MIT", @@ -18752,13 +18521,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/run-async": { - "version": "2.4.1", - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "funding": [ @@ -18780,13 +18542,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.8.2", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-array-concat": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", @@ -19909,6 +19664,7 @@ }, "node_modules/through": { "version": "2.3.8", + "dev": true, "license": "MIT" }, "node_modules/through2": { @@ -22052,13 +21808,6 @@ } } }, - "node_modules/wcwidth": { - "version": "1.0.1", - "license": "MIT", - "dependencies": { - "defaults": "^1.0.3" - } - }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "license": "MIT", diff --git a/package.json b/package.json index 730b0ef5d95..bfeba884207 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ }, "dependencies": { "@fastify/static": "^10.0.0", - "@inquirer/search": "^4.3.3", + "@inquirer/prompts": "^8.7.2", "@netlify/ai": "^1.0.1", "@netlify/api": "^15.1.2", "@netlify/blobs": "^11.1.3", @@ -108,7 +108,6 @@ "http-proxy": "^1.18.1", "http-proxy-middleware": "^3.0.5", "https-proxy-agent": "^8.0.0", - "inquirer": "^8.2.7", "is-docker": "^4.0.0", "is-stream": "^4.0.1", "is-wsl": "^3.1.0", @@ -167,7 +166,6 @@ "@types/express": "^5.0.6", "@types/folder-walker": "^3.2.5", "@types/gitconfiglocal": "^2.0.3", - "@types/inquirer": "^9.0.9", "@types/jsonwebtoken": "^9.0.10", "@types/lodash.shuffle": "^4.2.9", "@types/multiparty": "^4.2.1", diff --git a/src/commands/agents/agents-create.ts b/src/commands/agents/agents-create.ts index 53fcd870193..cdd98aba19c 100644 --- a/src/commands/agents/agents-create.ts +++ b/src/commands/agents/agents-create.ts @@ -1,5 +1,5 @@ import type { OptionValues } from 'commander' -import inquirer from 'inquirer' +import { input, select } from '@inquirer/prompts' import { chalk, logAndThrowError, log, logJson } from '../../utils/command-helpers.js' import { startSpinner, stopSpinner } from '../../lib/spinner.js' @@ -30,17 +30,10 @@ export const agentsCreate = async (promptArg: string, options: AgentCreateOption // Interactive prompt if not provided if (!prompt && !promptArg) { - const { promptInput } = await inquirer.prompt<{ - promptInput: string - }>([ - { - type: 'input', - name: 'promptInput', - message: 'What would you like the agent to do?', - validate: validatePrompt, - }, - ]) - finalPrompt = promptInput + finalPrompt = await input({ + message: 'What would you like the agent to do?', + validate: validatePrompt, + }) } else { finalPrompt = (promptArg || prompt) ?? '' } @@ -52,18 +45,11 @@ export const agentsCreate = async (promptArg: string, options: AgentCreateOption // Agent selection if not provided if (!agent) { - const { agentInput } = await inquirer.prompt<{ - agentInput: string - }>([ - { - type: 'list', - name: 'agentInput', - message: 'Which agent would you like to use?', - choices: AVAILABLE_AGENTS, - default: 'claude', - }, - ]) - agent = agentInput + agent = await select({ + message: 'Which agent would you like to use?', + choices: AVAILABLE_AGENTS, + default: 'claude', + }) } else { const agentIsValid = validateAgent(agent) if (agentIsValid !== true) { @@ -75,22 +61,16 @@ export const agentsCreate = async (promptArg: string, options: AgentCreateOption if (!branch) { const defaultBranch = siteInfo.build_settings?.repo_branch - const { branchInput } = await inquirer.prompt<{ - branchInput: string - }>([ - { - type: 'input', - name: 'branchInput', - message: 'Which branch would you like to work on?', - default: defaultBranch, - validate: (input: string) => { - if (!input || input.trim().length === 0) { - return 'Branch name is required' - } - return true - }, + const branchInput = await input({ + message: 'Which branch would you like to work on?', + default: defaultBranch, + validate: (value: string) => { + if (!value || value.trim().length === 0) { + return 'Branch name is required' + } + return true }, - ]) + }) branch = branchInput.trim() } diff --git a/src/commands/base-command.ts b/src/commands/base-command.ts index 963c1e0cd78..2477b20be5a 100644 --- a/src/commands/base-command.ts +++ b/src/commands/base-command.ts @@ -9,7 +9,7 @@ import { NodeFS, NoopLogger } from '@netlify/build-info/node' import { resolveConfig } from '@netlify/config' import { getGlobalConfigStore, LocalState } from '@netlify/dev-utils' import { isCI } from 'ci-info' -import search from '@inquirer/search' +import { search } from '@inquirer/prompts' import { Command, CommanderError, Help, Option, type OptionValues } from 'commander' import debug from 'debug' import { findUp } from 'find-up' diff --git a/src/commands/clone/clone.ts b/src/commands/clone/clone.ts index c75b2fbbcc7..6a235f12a32 100644 --- a/src/commands/clone/clone.ts +++ b/src/commands/clone/clone.ts @@ -1,7 +1,7 @@ import { resolve } from 'path' import { LocalState } from '@netlify/dev-utils' -import inquirer from 'inquirer' +import { input } from '@inquirer/prompts' import { normalizeRepoUrl } from '../../utils/normalize-repo-url.js' import { chalk, logAndThrowError, log, getToken, netlifyCommand, type APIError } from '../../utils/command-helpers.js' @@ -26,16 +26,10 @@ const isNetlifyGitServiceUrl = (repoUrl: string): boolean => { } const getTargetDir = async (defaultDir: string): Promise => { - const { selectedDir } = await inquirer.prompt<{ selectedDir: string }>([ - { - type: 'input', - name: 'selectedDir', - message: 'Where should we clone the repository?', - default: defaultDir, - }, - ]) - - return selectedDir + return await input({ + message: 'Where should we clone the repository?', + default: defaultDir, + }) } const cloneRepo = async (repoUrl: string, targetDir: string, debug: boolean): Promise => { diff --git a/src/commands/completion/completion.ts b/src/commands/completion/completion.ts index e90b5550baf..00201fd2c9e 100644 --- a/src/commands/completion/completion.ts +++ b/src/commands/completion/completion.ts @@ -2,7 +2,7 @@ import fs from 'fs' import { homedir } from 'os' import { dirname, join } from 'path' import { fileURLToPath } from 'url' -import inquirer from 'inquirer' +import { confirm } from '@inquirer/prompts' import type { OptionValues } from 'commander' import { install, uninstall } from '@pnpm/tabtab' @@ -56,14 +56,10 @@ export const completionGenerate = async (_options: OptionValues, command: BaseCo ) { log(`To enable Tabtab autocompletion with zsh, the following line may need to be added to your ~/.zshrc:`) log(chalk.bold.cyan(`\n${AUTOLOAD_COMPINIT}\n`)) - const { compinitAdded } = await inquirer.prompt([ - { - type: 'confirm', - name: 'compinitAdded', - message: `Would you like to add it?`, - default: true, - }, - ]) + const compinitAdded = await confirm({ + message: `Would you like to add it?`, + default: true, + }) if (compinitAdded) { fs.readFile(zshConfigFilepath, 'utf8', (_err, data) => { const updatedZshFile = AUTOLOAD_COMPINIT + '\n' + data diff --git a/src/commands/create/create-action.ts b/src/commands/create/create-action.ts index 1033c4eff31..7a103d767e6 100644 --- a/src/commands/create/create-action.ts +++ b/src/commands/create/create-action.ts @@ -8,9 +8,9 @@ import { pipeline } from 'stream/promises' import { promisify } from 'util' import type { OptionValues } from 'commander' -import inquirer from 'inquirer' import fetch from 'node-fetch' +import { select } from '@inquirer/prompts' import type { NetlifyAPI } from '@netlify/api' import { LocalState } from '@netlify/dev-utils' @@ -155,16 +155,10 @@ const selectRepoOwner = async (ghToken: string, repoOwnerFlag?: string): Promise ...orgs.map((org) => ({ name: org.login, value: org.login })), ] - const { owner } = await inquirer.prompt<{ owner: string }>([ - { - type: 'list', - name: 'owner', - message: 'Where should the GitHub repo be created?', - choices, - }, - ]) - - return owner + return await select({ + message: 'Where should the GitHub repo be created?', + choices, + }) } // TODO: Replace with api client call once the site repo endpoint is added to @netlify/open-api @@ -283,18 +277,13 @@ export const createAction = async (promptArg: string, options: CreateOptions, co if (accountSlugFlag) { accountSlug = accountSlugFlag } else if (accounts.length > 1) { - const { accountSlug: selected } = await inquirer.prompt<{ accountSlug: string }>([ - { - type: 'list', - name: 'accountSlug', - message: 'Team:', - choices: accounts.map((account) => ({ - value: account.slug, - name: account.name, - })), - }, - ]) - accountSlug = selected + accountSlug = await select({ + message: 'Team:', + choices: accounts.map((account) => ({ + value: account.slug, + name: account.name, + })), + }) } else { accountSlug = accounts[0]?.slug } diff --git a/src/commands/database/db-init.ts b/src/commands/database/db-init.ts index 6c97e07a816..de2107fc16a 100644 --- a/src/commands/database/db-init.ts +++ b/src/commands/database/db-init.ts @@ -3,7 +3,7 @@ import { mkdir, readdir, writeFile } from 'fs/promises' import { join } from 'path' import { applyMigrations } from '@netlify/dev' -import inquirer from 'inquirer' +import { confirm, select } from '@inquirer/prompts' import { chalk, log, netlifyCommand } from '../../utils/command-helpers.js' import { startSpinner, stopSpinner } from '../../lib/spinner.js' @@ -54,17 +54,10 @@ const success = (text: string): void => { const carefullyWriteFile = async (filePath: string, data: string, projectRoot: string) => { if (existsSync(filePath)) { - type Answers = { - overwrite: boolean - } - const answers = await inquirer.prompt([ - { - type: 'confirm', - name: 'overwrite', - message: `Overwrite existing file .${filePath.replace(projectRoot, '')}?`, - }, - ]) - if (answers.overwrite) { + const overwrite = await confirm({ + message: `Overwrite existing file .${filePath.replace(projectRoot, '')}?`, + }) + if (overwrite) { await writeFile(filePath, data) } } else { @@ -87,20 +80,14 @@ const promptForQueryStyle = async (interactive: boolean): Promise => } log('') - const { queryStyle } = await inquirer.prompt<{ queryStyle: QueryStyle }>([ - { - type: 'list', - name: 'queryStyle', - message: 'What is your preferred style?', - default: 'drizzle', - choices: [ - { name: 'Drizzle ORM (recommended)', value: 'drizzle' }, - { name: 'Direct SQL', value: 'raw' }, - ], - }, - ]) - - return queryStyle + return await select({ + message: 'What is your preferred style?', + default: 'drizzle', + choices: [ + { name: 'Drizzle ORM (recommended)', value: 'drizzle' }, + { name: 'Direct SQL', value: 'raw' }, + ], + }) } const promptForStarter = async (interactive: boolean): Promise => { @@ -113,15 +100,10 @@ const promptForStarter = async (interactive: boolean): Promise => { log('and then query it. Alternatively, you can do this yourself at any time.') log('') - const { answer } = await inquirer.prompt<{ answer: boolean }>([ - { - type: 'confirm', - name: 'answer', - message: 'Do you want to create sample data?', - default: true, - }, - ]) - return answer + return await confirm({ + message: 'Do you want to create sample data?', + default: true, + }) } const installDependencies = async ( diff --git a/src/commands/database/db-migration-new.ts b/src/commands/database/db-migration-new.ts index 1a3ef73b0b6..4bb752e0d2e 100644 --- a/src/commands/database/db-migration-new.ts +++ b/src/commands/database/db-migration-new.ts @@ -1,7 +1,7 @@ import { readdir, mkdir, writeFile } from 'fs/promises' import { join } from 'path' -import inquirer from 'inquirer' +import { input, select } from '@inquirer/prompts' import { log, logJson } from '../../utils/command-helpers.js' import BaseCommand from '../base-command.js' @@ -91,15 +91,10 @@ export const migrationNew = async (options: MigrationNewOptions, command: BaseCo if (!description) { if (isInteractive()) { - const answers = await inquirer.prompt<{ description: string }>([ - { - type: 'input', - name: 'description', - message: 'What is the purpose of this migration?', - validate: (input: string) => (input.trim().length > 0 ? true : 'Description cannot be empty'), - }, - ]) - description = answers.description + description = await input({ + message: 'What is the purpose of this migration?', + validate: (value: string) => (value.trim().length > 0 ? true : 'Description cannot be empty'), + }) } else { throw new Error( `--description argument is required when not running interactively. Provide a description of the migration (e.g. --description "add users table").`, @@ -111,19 +106,14 @@ export const migrationNew = async (options: MigrationNewOptions, command: BaseCo const defaultScheme = detectedScheme ?? 'timestamp' if (isInteractive()) { - const answers = await inquirer.prompt<{ scheme: NumberingScheme }>([ - { - type: 'list', - name: 'scheme', - message: 'Numbering scheme:', - choices: [ - { name: 'Timestamp (e.g. 20260312143000) [Recommended]', value: 'timestamp' }, - { name: 'Sequential (e.g. 0001, 0002, ...)', value: 'sequential' }, - ], - default: defaultScheme, - }, - ]) - scheme = answers.scheme + scheme = await select({ + message: 'Numbering scheme:', + choices: [ + { name: 'Timestamp (e.g. 20260312143000) [Recommended]', value: 'timestamp' }, + { name: 'Sequential (e.g. 0001, 0002, ...)', value: 'sequential' }, + ], + default: defaultScheme, + }) } else { scheme = defaultScheme } diff --git a/src/commands/database/db-migration-pull.ts b/src/commands/database/db-migration-pull.ts index d0f904bc8f2..39ee31ca220 100644 --- a/src/commands/database/db-migration-pull.ts +++ b/src/commands/database/db-migration-pull.ts @@ -1,7 +1,7 @@ import { rm, mkdir, writeFile } from 'fs/promises' import { dirname, resolve, isAbsolute } from 'path' -import inquirer from 'inquirer' +import { confirm } from '@inquirer/prompts' import { log, logJson } from '../../utils/command-helpers.js' import execa from '../../utils/execa.js' @@ -148,16 +148,12 @@ export const migrationPull = async (options: MigrationPullOptions, command: Base }) if (!force) { - const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([ - { - type: 'confirm', - name: 'confirmed', - message: `This will overwrite all local migrations in ${migrationsDirectory} with ${String( - migrations.length, - )} migration${migrations.length === 1 ? '' : 's'} from ${branch}. Continue?`, - default: false, - }, - ]) + const confirmed = await confirm({ + message: `This will overwrite all local migrations in ${migrationsDirectory} with ${String( + migrations.length, + )} migration${migrations.length === 1 ? '' : 's'} from ${branch}. Continue?`, + default: false, + }) if (!confirmed) { log('Pull cancelled.') diff --git a/src/commands/database/db-reset.ts b/src/commands/database/db-reset.ts index d0ffdfa5bc9..48fdab60972 100644 --- a/src/commands/database/db-reset.ts +++ b/src/commands/database/db-reset.ts @@ -1,6 +1,6 @@ import { rm } from 'fs/promises' -import inquirer from 'inquirer' +import { confirm } from '@inquirer/prompts' import { resetDatabase } from '@netlify/dev' @@ -25,14 +25,10 @@ const discardLocalDatabase = async (error: LocalDatabaseStartError, options: Res log(error.summary) - const { confirmed } = await inquirer.prompt<{ confirmed: boolean }>([ - { - type: 'confirm', - name: 'confirmed', - message: `Delete ${directory} and start from an empty database?`, - default: false, - }, - ]) + const confirmed = await confirm({ + message: `Delete ${directory} and start from an empty database?`, + default: false, + }) if (!confirmed) { log('Reset cancelled.') diff --git a/src/commands/deploy/deploy.ts b/src/commands/deploy/deploy.ts index 22c173cac8e..5287bd12e75 100644 --- a/src/commands/deploy/deploy.ts +++ b/src/commands/deploy/deploy.ts @@ -4,9 +4,9 @@ import { stat } from 'fs/promises' import { basename, resolve } from 'path' import { stdin, stdout } from 'process' +import { confirm, input, select } from '@inquirer/prompts' import type { NetlifyAPI } from '@netlify/api' import { type NetlifyConfig, type OnPostBuild, runCoreSteps } from '@netlify/build' -import inquirer from 'inquirer' import { parseAllHeaders } from '@netlify/headers-parser' import { parseAllRedirects } from '@netlify/redirect-parser' import prettyjson from 'prettyjson' @@ -154,16 +154,11 @@ const getDeployFolder = async ({ log(`\nTo specify directory non-interactively, use: ${copyableCommand}\n`) - const { promptPath } = await inquirer.prompt([ - { - type: 'input', - name: 'promptPath', - message: 'Publish directory', - default: '.', - filter: (input) => resolve(command.workingDir, input), - }, - ]) - deployFolder = promptPath as string + const promptPath = await input({ + message: 'Publish directory', + default: '.', + }) + deployFolder = resolve(command.workingDir, promptPath) } return deployFolder @@ -395,14 +390,10 @@ const prepareProductionDeploy = async ({ api, siteData, options, command }) => { log(` ${overrideCommand}`) log('\nWarning: Only use --prod-if-unlocked if you are absolutely sure you want to override the deployment lock.\n') - const { unlockChoice } = await inquirer.prompt([ - { - type: 'confirm', - name: 'unlockChoice', - message: 'Would you like to "unlock" deployments for production context to proceed?', - default: false, - }, - ]) + const unlockChoice = await confirm({ + message: 'Would you like to "unlock" deployments for production context to proceed?', + default: false, + }) if (!unlockChoice) exit(0) await api.unlockDeploy({ deploy_id: siteData.published_deploy.id }) log(`\n${NETLIFYDEVLOG} "Auto publishing" has been enabled for production context\n`) @@ -1110,23 +1101,19 @@ const promptForSiteAction = async (options: DeployOptionValues, command: BaseCom log(`\nYou must pick a --team: ${availableTeams.map((team) => team.slug).join(', ')}`) } - const { initChoice } = await inquirer.prompt([ - { - type: 'list', - name: 'initChoice', - message: 'What would you like to do?', - choices: [ - { - name: '⇄ Link this directory to an existing project', - value: 'link', - }, - { - name: '+ Create & configure a new project', - value: 'create', - }, - ], - }, - ]) + const initChoice = await select({ + message: 'What would you like to do?', + choices: [ + { + name: '⇄ Link this directory to an existing project', + value: 'link', + }, + { + name: '+ Create & configure a new project', + value: 'create', + }, + ], + }) const siteData = initChoice === 'create' ? await sitesCreate({}, command) : await link({}, command) diff --git a/src/commands/env/env-list.ts b/src/commands/env/env-list.ts index b96412b36f7..58ea9a83520 100644 --- a/src/commands/env/env-list.ts +++ b/src/commands/env/env-list.ts @@ -2,7 +2,7 @@ import ansiEscapes from 'ansi-escapes' import AsciiTable from 'ascii-table' import { isCI } from 'ci-info' import type { OptionValues } from 'commander' -import inquirer from 'inquirer' +import { confirm } from '@inquirer/prompts' import logUpdate from 'log-update' import { chalk, log, logJson } from '../../utils/command-helpers.js' @@ -101,14 +101,10 @@ export const envList = async (options: OptionValues, command: BaseCommand) => { } logUpdate(getTable({ environment, hideValues: true, scopesColumn: true })) - const { showValues } = await inquirer.prompt([ - { - type: 'confirm', - name: 'showValues', - message: 'Show values?', - default: false, - }, - ]) + const showValues = await confirm({ + message: 'Show values?', + default: false, + }) if (showValues) { // since inquirer adds a prompt, we need to account for it when printing the table again diff --git a/src/commands/functions/functions-create.ts b/src/commands/functions/functions-create.ts index cc0a713e129..a3ea6c6d2ca 100644 --- a/src/commands/functions/functions-create.ts +++ b/src/commands/functions/functions-create.ts @@ -6,11 +6,10 @@ import path, { dirname, join, relative } from 'path' import process from 'process' import { fileURLToPath, pathToFileURL } from 'url' -import search, { Separator } from '@inquirer/search' +import { confirm, input, search, select, Separator } from '@inquirer/prompts' import { OptionValues } from 'commander' import { findUp } from 'find-up' import fuzzy from 'fuzzy' -import inquirer from 'inquirer' import fetch from 'node-fetch' import { createSpinner } from 'nanospinner' @@ -83,18 +82,12 @@ const getNameFromArgs = async function (argumentName, options, defaultName) { return argumentName } - const { name } = await inquirer.prompt([ - { - name: 'name', - message: 'Name your function:', - default: defaultName, - type: 'input', - validate: (val) => isValidFunctionName(val), - // make sure it is not undefined and is a valid filename. - // this has some nuance i have ignored, eg crossenv and i18n concerns - }, - ]) - return name + return await input({ + message: 'Name your function:', + default: defaultName, + // this has some nuance i have ignored, eg crossenv and i18n concerns + validate: (val) => isValidFunctionName(val), + }) } // @ts-expect-error TS(7006) FIXME: Parameter 'registry' implicitly has an 'any' type. @@ -201,14 +194,10 @@ const pickTemplate = async function ({ language: languageFromFlag, template: tem ? languages.filter((lang) => lang.value === 'javascript' || lang.value === 'typescript') : languages.filter(Boolean) - const { language: languageFromPrompt } = await inquirer.prompt({ + language = await select({ choices: langs, message: 'Select the language of your function', - name: 'language', - type: 'list', }) - - language = languageFromPrompt } let templatesForLanguage @@ -257,19 +246,14 @@ const DEFAULT_PRIORITY = 999 const selectTypeOfFunc = async (): Promise<'edge' | 'serverless'> => { const functionTypes = [ - { name: 'Edge function (Deno)', value: 'edge' }, - { name: 'Serverless function (Node)', value: 'serverless' }, + { name: 'Edge function (Deno)', value: 'edge' as const }, + { name: 'Serverless function (Node)', value: 'serverless' as const }, ] - const { functionType } = await inquirer.prompt([ - { - name: 'functionType', - message: "Select the type of function you'd like to create", - type: 'list', - choices: functionTypes, - }, - ]) - return functionType + return await select({ + message: "Select the type of function you'd like to create", + choices: functionTypes, + }) } /** @@ -320,14 +304,10 @@ const promptFunctionsDirectory = async (command) => { ) } - const { functionsDir } = await inquirer.prompt([ - { - type: 'input', - name: 'functionsDir', - message: 'Enter the path, relative to your project, where your functions should live:', - default: 'netlify/functions', - }, - ]) + const functionsDir = await input({ + message: 'Enter the path, relative to your project, where your functions should live:', + default: 'netlify/functions', + }) try { log(`${NETLIFYDEVLOG} updating project settings with ${chalk.magenta.inverse(functionsDir)}`) @@ -511,16 +491,10 @@ const scaffoldFromTemplate = async function (command, options, argumentName, fun // pull the rest of the metadata from the template const chosenTemplate = await pickTemplate(options, funcType) if (chosenTemplate === 'url') { - const { chosenUrl } = await inquirer.prompt([ - { - name: 'chosenUrl', - message: 'URL to clone: ', - type: 'input', - validate: (/** @type {string} */ val) => Boolean(validateRepoURL(val)), - // make sure it is not undefined and is a valid filename. - // this has some nuance i have ignored, eg crossenv and i18n concerns - }, - ]) + const chosenUrl = await input({ + message: 'URL to clone: ', + validate: (val) => Boolean(validateRepoURL(val)), + }) options.url = chosenUrl.trim() try { await downloadFromURL(command, options, argumentName, functionsDir) @@ -643,14 +617,10 @@ const handleAddonDidInstall = async ({ addonCreated, addonDidInstall, command, f return } - const { confirmPostInstall } = await inquirer.prompt([ - { - type: 'confirm', - name: 'confirmPostInstall', - message: `This template has an optional setup script that runs after addon install. This can be helpful for first time users to try out templates. Run the script?`, - default: false, - }, - ]) + const confirmPostInstall = await confirm({ + message: `This template has an optional setup script that runs after addon install. This can be helpful for first time users to try out templates. Run the script?`, + default: false, + }) if (!confirmPostInstall) { return @@ -719,20 +689,15 @@ const registerEFInToml = async (funcName, options) => { log(`${NETLIFYDEVLOG} \`${relConfigFilePath}\` file does not exist yet. Creating it...`) } - let { funcPath } = await inquirer.prompt([ - { - type: 'input', - name: 'funcPath', - message: `What route do you want your edge function to be invoked on?`, - default: '/test', - validate: (val) => Boolean(val), - // Make sure route isn't undefined and is valid - // Todo: add more validation? - }, - ]) + let funcPath = await input({ + message: `What route do you want your edge function to be invoked on?`, + default: '/test', + // Todo: add more validation? + validate: (val) => Boolean(val), + }) // Make sure path begins with a '/' - if (funcPath[0] !== '/') { + if (!funcPath.startsWith('/')) { funcPath = `/${funcPath}` } diff --git a/src/commands/functions/functions-invoke.ts b/src/commands/functions/functions-invoke.ts index 04a4788a9b4..1449d0c88d0 100644 --- a/src/commands/functions/functions-invoke.ts +++ b/src/commands/functions/functions-invoke.ts @@ -2,8 +2,8 @@ import fs from 'fs' import { createRequire } from 'module' import path from 'path' +import { select } from '@inquirer/prompts' import { OptionValues } from 'commander' -import inquirer from 'inquirer' import fetch from 'node-fetch' import { APIError, NETLIFYDEVWARN, chalk, logAndThrowError, exit } from '../../utils/command-helpers.js' @@ -113,15 +113,10 @@ const getNameFromArgs = async function (functions, options, argumentName) { ) } - const { trigger } = await inquirer.prompt([ - { - type: 'list', - message: 'Pick a function to trigger', - name: 'trigger', - choices: functionNames, - }, - ]) - return trigger + return await select({ + message: 'Pick a function to trigger', + choices: functionNames, + }) } /** diff --git a/src/commands/init/init.ts b/src/commands/init/init.ts index 76f53310b0e..3c7829afff2 100644 --- a/src/commands/init/init.ts +++ b/src/commands/init/init.ts @@ -1,5 +1,5 @@ import { OptionValues } from 'commander' -import inquirer from 'inquirer' +import { confirm, select } from '@inquirer/prompts' import { isEmpty } from '../../utils/object-utilities.js' import { chalk, exit, log, netlifyCommand } from '../../utils/command-helpers.js' @@ -59,13 +59,9 @@ const createNewSiteAndExit = async ({ persistState({ state, siteInfo }) if (!disableLinking) { - const { shouldConfigureBuild } = await inquirer.prompt<{ shouldConfigureBuild: boolean }>([ - { - type: 'confirm', - name: 'shouldConfigureBuild', - message: `Do you want to configure build settings? We'll suggest settings for your project automatically`, - }, - ]) + const shouldConfigureBuild = await confirm({ + message: `Do you want to configure build settings? We'll suggest settings for your project automatically`, + }) if (shouldConfigureBuild) { const { cachedConfig: { configPath }, @@ -154,14 +150,10 @@ git remote add origin https://github.com/YourUserName/RepoName.git const NEW_SITE_NO_GIT = 'Yes, create and deploy project manually' const NO_ABORT = 'No, I will connect this directory with GitHub first' - const { noGitRemoteChoice } = await inquirer.prompt<{ noGitRemoteChoice: typeof NEW_SITE_NO_GIT | typeof NO_ABORT }>([ - { - type: 'list', - name: 'noGitRemoteChoice', - message: 'Do you want to create a Netlify project without a git repository?', - choices: [NEW_SITE_NO_GIT, NO_ABORT], - }, - ]) + const noGitRemoteChoice = await select({ + message: 'Do you want to create a Netlify project without a git repository?', + choices: [NEW_SITE_NO_GIT, NO_ABORT], + }) if (noGitRemoteChoice === NEW_SITE_NO_GIT) { // TODO(ndhoule): Shove a custom error message in here @@ -179,15 +171,10 @@ const createOrLinkSiteToRepo = async (command: BaseCommand) => { const initializeOpts = [EXISTING_SITE, NEW_SITE] as const - // TODO(serhalp): inquirer should infer the choice type here, but doesn't. Fix. - const { initChoice } = await inquirer.prompt<{ initChoice: (typeof initializeOpts)[number] }>([ - { - type: 'list', - name: 'initChoice', - message: 'What would you like to do?', - choices: initializeOpts, - }, - ]) + const initChoice = await select({ + message: 'What would you like to do?', + choices: initializeOpts, + }) // create site or search for one if (initChoice === NEW_SITE) { diff --git a/src/commands/link/link.ts b/src/commands/link/link.ts index 647b42b26fe..10e488c9c3a 100644 --- a/src/commands/link/link.ts +++ b/src/commands/link/link.ts @@ -1,6 +1,6 @@ import assert from 'node:assert' -import inquirer from 'inquirer' +import { input, select } from '@inquirer/prompts' import { isEmpty } from '../../utils/object-utilities.js' import type { NetlifyAPI } from '@netlify/api' @@ -65,19 +65,13 @@ To search for projects: spinner.warn({ text: `Found ${matchingSites.length} projects connected to ${repoUrl}` }) - const { selectedSite } = await inquirer.prompt<{ - selectedSite: SiteInfo | undefined - }>([ - { - type: 'list', - name: 'selectedSite', - message: 'Which project do you want to link?', - choices: matchingSites.map((matchingSite) => ({ - name: `${matchingSite.name} - ${matchingSite.ssl_url}`, - value: matchingSite, - })), - }, - ]) + const selectedSite = await select({ + message: 'Which project do you want to link?', + choices: matchingSites.map((matchingSite) => ({ + name: `${matchingSite.name} - ${matchingSite.ssl_url}`, + value: matchingSite, + })), + }) if (!selectedSite) { return logAndThrowError('No project selected') @@ -109,14 +103,10 @@ const linkPrompt = async (command: BaseCommand, options: LinkOptionValues): Prom log() log(`${chalk.cyanBright(`${netlifyCommand()} link`)} will connect this folder to a project on Netlify`) log() - const { linkType } = await inquirer.prompt<{ linkType: string | undefined }>([ - { - type: 'list', - name: 'linkType', - message: 'How do you want to link this folder to a project?', - choices: linkChoices, - }, - ]) + const linkType = await select({ + message: 'How do you want to link this folder to a project?', + choices: linkChoices, + }) let kind switch (linkType) { @@ -130,13 +120,9 @@ const linkPrompt = async (command: BaseCommand, options: LinkOptionValues): Prom } case SITE_NAME_PROMPT: { kind = 'byName' - const { searchTerm } = await inquirer.prompt<{ searchTerm: string }>([ - { - type: 'input', - name: 'searchTerm', - message: 'Enter the project name (or just part of it):', - }, - ]) + const searchTerm = await input({ + message: 'Enter the project name (or just part of it):', + }) log(`Looking for projects with names containing '${searchTerm}'...`) log() @@ -169,17 +155,10 @@ To create a new project: if (matchingSites.length > 1) { log(`Found ${matchingSites.length} matching projects!`) - const { selectedSite } = await inquirer.prompt<{ - selectedSite: SiteInfo | undefined - }>([ - { - type: 'list', - name: 'selectedSite', - message: 'Which project do you want to link?', - paginated: true, - choices: matchingSites.map((matchingSite) => ({ name: matchingSite.name, value: matchingSite })), - }, - ]) + const selectedSite = await select({ + message: 'Which project do you want to link?', + choices: matchingSites.map((matchingSite) => ({ name: matchingSite.name, value: matchingSite })), + }) if (!selectedSite) { return logAndThrowError('No project selected') } @@ -210,15 +189,10 @@ To create a new project: ) } - const { selectedSite } = await inquirer.prompt<{ selectedSite: SiteInfo | undefined }>([ - { - type: 'list', - name: 'selectedSite', - message: 'Which project do you want to link?', - paginated: true, - choices: sites.map((matchingSite) => ({ name: matchingSite.name, value: matchingSite })), - }, - ]) + const selectedSite = await select({ + message: 'Which project do you want to link?', + choices: sites.map((matchingSite) => ({ name: matchingSite.name, value: matchingSite })), + }) if (!selectedSite) { return logAndThrowError('No project selected') } @@ -227,13 +201,9 @@ To create a new project: } case SITE_ID_PROMPT: { kind = 'bySiteId' - const { siteId } = await inquirer.prompt<{ siteId: string }>([ - { - type: 'input', - name: 'siteId', - message: 'What is the project ID?', - }, - ]) + const siteId = await input({ + message: 'What is the project ID?', + }) try { site = await api.getSite({ siteId }) diff --git a/src/commands/logs/build.ts b/src/commands/logs/build.ts index 376f41a6148..4bcc085bba8 100644 --- a/src/commands/logs/build.ts +++ b/src/commands/logs/build.ts @@ -1,5 +1,5 @@ import type { OptionValues } from 'commander' -import inquirer from 'inquirer' +import { select } from '@inquirer/prompts' import { log, chalk } from '../../utils/command-helpers.js' import { getWebSocket } from '../../utils/websockets/index.js' @@ -53,9 +53,7 @@ export const logsBuild = async (_options: OptionValues, command: BaseCommand) => let [deploy] = deploys if (deploys.length > 1) { - const { result } = await inquirer.prompt({ - name: 'result', - type: 'list', + const result = await select({ message: `Select a deploy\n\n${chalk.yellow('*')} indicates a deploy created by you`, choices: deploys.map((dep: any) => ({ name: getName({ deploy: dep, userId }), diff --git a/src/commands/main.ts b/src/commands/main.ts index 0eef5a1433e..2dee95c0396 100644 --- a/src/commands/main.ts +++ b/src/commands/main.ts @@ -3,7 +3,7 @@ import process from 'process' import { Option, CommanderError } from 'commander' import envinfo from 'envinfo' import { closest } from 'fastest-levenshtein' -import inquirer from 'inquirer' +import { confirm } from '@inquirer/prompts' import { getGlobalConfigStore } from '@netlify/dev-utils' @@ -217,24 +217,17 @@ const mainCommand = async function (options, command) { exit(EXIT_CODES.USAGE_ERROR) } - const applySuggestion = await new Promise((resolve) => { - const prompt = inquirer.prompt({ - type: 'confirm', - name: 'suggestion', - message: `Did you mean ${chalk.blue(suggestion)}`, - default: false, - }) - - setTimeout(() => { - // @ts-expect-error TS(2445) FIXME: Property 'close' is protected and only accessible ... Remove this comment to see the full error message - prompt.ui.close() - resolve(false) - }, SUGGESTION_TIMEOUT) - - prompt.then((value) => { - resolve(value.suggestion) - }) - }) + let applySuggestion = false + try { + applySuggestion = await confirm( + { message: `Did you mean ${chalk.blue(suggestion)}`, default: false }, + { signal: AbortSignal.timeout(SUGGESTION_TIMEOUT) }, + ) + } catch (error) { + if (!(error instanceof Error && error.name === 'AbortPromptError')) { + throw error + } + } // create new log line log() diff --git a/src/commands/recipes/recipes.ts b/src/commands/recipes/recipes.ts index 5c98aef8a33..56e7d6784b8 100644 --- a/src/commands/recipes/recipes.ts +++ b/src/commands/recipes/recipes.ts @@ -2,7 +2,7 @@ import { basename } from 'path' import { OptionValues } from 'commander' import { closest } from 'fastest-levenshtein' -import inquirer from 'inquirer' +import { confirm } from '@inquirer/prompts' import { NETLIFYDEVERR, chalk, log } from '../../utils/command-helpers.js' import BaseCommand from '../base-command.js' @@ -51,23 +51,14 @@ export const recipesCommand = async (recipeName: string, options: OptionValues, const recipes = await listRecipes() const recipeNames = recipes.map(({ name }) => name) const suggestion = closest(recipeName, recipeNames) - const applySuggestion = await new Promise((resolve) => { - const prompt = inquirer.prompt({ - type: 'confirm', - name: 'suggestion', - message: `Did you mean ${chalk.blue(suggestion)}`, - default: false, - }) - - setTimeout(() => { - // @ts-expect-error TS(2445) FIXME: Property 'close' is protected and only accessible ... Remove this comment to see the full error message - prompt.ui.close() - resolve(false) - }, SUGGESTION_TIMEOUT) - - prompt.then((value) => { - resolve(value.suggestion) - }) + const applySuggestion = await confirm( + { message: `Did you mean ${chalk.blue(suggestion)}`, default: false }, + { signal: AbortSignal.timeout(SUGGESTION_TIMEOUT) }, + ).catch((error: unknown) => { + if (error instanceof Error && error.name === 'AbortPromptError') { + return false + } + throw error }) if (applySuggestion) { diff --git a/src/commands/sites/sites-create.ts b/src/commands/sites/sites-create.ts index 6f65fea1ebf..b595a6af04d 100644 --- a/src/commands/sites/sites-create.ts +++ b/src/commands/sites/sites-create.ts @@ -1,5 +1,5 @@ import type { OptionValues } from 'commander' -import inquirer from 'inquirer' +import { input, select } from '@inquirer/prompts' import { pick } from '../../utils/object-utilities.js' import prettyjson from 'prettyjson' @@ -16,16 +16,11 @@ import { link } from '../link/link.js' export const getSiteNameInput = async (name: string | undefined): Promise<{ name: string }> => { if (!name) { - const { name: nameInput } = await inquirer.prompt([ - { - type: 'input', - name: 'name', - message: 'Project name (leave blank for a random name; you can change it later):', - validate: (input) => - /^[a-zA-Z\d-]+$/.test(input || undefined) || 'Only alphanumeric characters and hyphens are allowed', - }, - ]) - name = typeof nameInput === 'string' ? nameInput : '' + name = await input({ + message: 'Project name (leave blank for a random name; you can change it later):', + validate: (value) => + !value || /^[a-zA-Z\d-]+$/.test(value) || 'Only alphanumeric characters and hyphens are allowed', + }) } return { name } @@ -46,20 +41,13 @@ export const sitesCreate = async (options: OptionValues, command: BaseCommand) = accountSlug = team.slug log(`Using team: ${team.name}`) } else { - const { accountSlug: accountSlugInput }: { accountSlug: string } = await inquirer.prompt< - Promise<{ accountSlug: string }> - >([ - { - type: 'list', - name: 'accountSlug', - message: 'Team:', - choices: accounts.map((account) => ({ - value: account.slug, - name: account.name, - })), - }, - ]) - accountSlug = accountSlugInput + accountSlug = await select({ + message: 'Team:', + choices: accounts.map((account) => ({ + value: account.slug, + name: account.name, + })), + }) } } diff --git a/src/commands/sites/sites-delete.ts b/src/commands/sites/sites-delete.ts index 6002b8a32b2..a98b36fc761 100644 --- a/src/commands/sites/sites-delete.ts +++ b/src/commands/sites/sites-delete.ts @@ -1,5 +1,5 @@ import type { OptionValues } from 'commander' -import inquirer from 'inquirer' +import { confirm } from '@inquirer/prompts' import { chalk, logAndThrowError, exit, log, type APIError } from '../../utils/command-helpers.js' import type BaseCommand from '../base-command.js' @@ -34,9 +34,7 @@ export const sitesDelete = async (siteId: string, options: OptionValues, command log() log(chalk.bold('Be careful here. There is no undo!')) log() - const { wantsToDelete } = await inquirer.prompt({ - type: 'confirm', - name: 'wantsToDelete', + const wantsToDelete = await confirm({ message: `WARNING: Are you sure you want to delete the "${siteData.name}" project?`, default: false, }) @@ -57,9 +55,7 @@ export const sitesDelete = async (siteId: string, options: OptionValues, command log() log(`Verify this project ID "${siteId}" supplied is correct and proceed.`) log('To skip this prompt, pass a --force flag to the delete command') - const { wantsToDelete } = await inquirer.prompt({ - type: 'confirm', - name: 'wantsToDelete', + const wantsToDelete = await confirm({ message: `Verify & Proceed with deletion of project "${siteId}"?`, default: false, }) diff --git a/src/commands/switch/switch.ts b/src/commands/switch/switch.ts index 3683980d9ff..563bddb0f33 100644 --- a/src/commands/switch/switch.ts +++ b/src/commands/switch/switch.ts @@ -1,5 +1,5 @@ import { OptionValues } from 'commander' -import inquirer from 'inquirer' +import { select } from '@inquirer/prompts' import { chalk, log } from '../../utils/command-helpers.js' import BaseCommand from '../base-command.js' @@ -30,14 +30,10 @@ export const switchCommand = async (options: OptionValues, command: BaseCommand) log('') } - const { accountSwitchChoice } = await inquirer.prompt([ - { - type: 'list', - name: 'accountSwitchChoice', - message: 'Please select the account you want to use:', - choices: [...Object.entries(availableUsersChoices).map(([, val]) => val), LOGIN_NEW], - }, - ]) + const accountSwitchChoice = await select({ + message: 'Please select the account you want to use:', + choices: [...Object.entries(availableUsersChoices).map(([, val]) => val), LOGIN_NEW], + }) if (accountSwitchChoice === LOGIN_NEW) { await login({ new: true }, command) diff --git a/src/lib/edge-functions/editor-helper.ts b/src/lib/edge-functions/editor-helper.ts index eac4c7810c8..45c676965b4 100644 --- a/src/lib/edge-functions/editor-helper.ts +++ b/src/lib/edge-functions/editor-helper.ts @@ -1,6 +1,6 @@ import { env, stdin, stdout } from 'process' -import inquirer from 'inquirer' +import { confirm } from '@inquirer/prompts' import { runRecipe } from '../../commands/recipes/recipes.js' @@ -25,14 +25,12 @@ export const promptEditorHelper = async ({ NETLIFYDEVLOG, chalk, config, log, re state.set(STATE_PROMPT_PROPERTY, true) const message = 'Would you like to configure VS Code to use Edge Functions?' - const { confirm } = await inquirer.prompt({ - type: 'confirm', - name: 'confirm', + const confirmed = await confirm({ message, default: true, }) - if (!confirm) { + if (!confirmed) { log( `${NETLIFYDEVLOG} You can start this configuration manually by running ${chalk.magenta.bold( 'netlify recipes vscode', diff --git a/src/recipes/ai-context/index.ts b/src/recipes/ai-context/index.ts index ebfa437bf82..7580d3932bc 100644 --- a/src/recipes/ai-context/index.ts +++ b/src/recipes/ai-context/index.ts @@ -1,6 +1,6 @@ import { resolve } from 'node:path' -import inquirer from 'inquirer' +import { input, select } from '@inquirer/prompts' import execa from 'execa' import type { RunRecipeOptions } from '../../commands/recipes/recipes.js' @@ -42,28 +42,20 @@ const presets = cliContextConsumers.map((consumer) => ({ presets.push({ name: 'Custom location', value: rulesForDefaultConsumer.key }) const promptForContextConsumerSelection = async (): Promise => { - const { consumerKey } = await inquirer.prompt([ - { - name: 'consumerKey', - message: 'Where should we put the context files?', - type: 'list', - choices: presets, - }, - ]) + const consumerKey = await select({ + message: 'Where should we put the context files?', + choices: presets, + }) const contextConsumer = consumerKey ? cliContextConsumers.find((consumer) => consumer.key === consumerKey) : null if (contextConsumer) { return contextConsumer } - const { customPath } = await inquirer.prompt([ - { - type: 'input', - name: 'customPath', - message: 'Enter the path, relative to the project root, where the context files should be placed', - default: './ai-context', - }, - ]) + const customPath = await input({ + message: 'Enter the path, relative to the project root, where the context files should be placed', + default: './ai-context', + }) if (customPath) { return { ...rulesForDefaultConsumer, path: customPath || rulesForDefaultConsumer.path } diff --git a/src/recipes/blobs-migrate/index.ts b/src/recipes/blobs-migrate/index.ts index 0b7c6243bc5..8490ad3de2a 100644 --- a/src/recipes/blobs-migrate/index.ts +++ b/src/recipes/blobs-migrate/index.ts @@ -1,5 +1,5 @@ import { getStore, listStores } from '@netlify/blobs' -import inquirer from 'inquirer' +import { confirm } from '@inquirer/prompts' import pMap from 'p-map' import BaseCommand from '../../commands/base-command.js' @@ -48,9 +48,7 @@ export const run = async ({ args, command }: Options) => { const { stores } = await listStores(clientOptions) if (stores.includes(storeName)) { - const { confirmExistingStore } = await inquirer.prompt({ - type: 'confirm', - name: 'confirmExistingStore', + const confirmExistingStore = await confirm({ message: `The store '${storeName}' already exists in the new format, which means it has already been migrated or it has been used with a newer version of the Netlify Blobs client. If you continue with the migration, any blobs from the legacy store will overwrite newer entries that have the same key. Do you want to proceed?`, default: false, }) @@ -60,9 +58,7 @@ export const run = async ({ args, command }: Options) => { } } - const { confirmMigration } = await inquirer.prompt({ - type: 'confirm', - name: 'confirmMigration', + const confirmMigration = await confirm({ message: `You're about to migrate the store '${storeName}' with ${blobs.length} blobs. Do you want to proceed?`, default: true, }) diff --git a/src/recipes/vscode/index.ts b/src/recipes/vscode/index.ts index ef0e14ad6eb..abe30ab8452 100644 --- a/src/recipes/vscode/index.ts +++ b/src/recipes/vscode/index.ts @@ -2,7 +2,7 @@ import { join } from 'path' import { DenoBridge } from '@netlify/edge-bundler' import execa from 'execa' -import inquirer from 'inquirer' +import { confirm } from '@inquirer/prompts' import { NETLIFYDEVLOG, NETLIFYDEVWARN, chalk, logAndThrowError, log } from '../../utils/command-helpers.js' @@ -17,9 +17,7 @@ const getPrompt = ({ fileExists, path }) => { ? `There is a VS Code settings file at ${formattedPath}. Can we update it?` : `A new VS Code settings file will be created at ${formattedPath}` - return inquirer.prompt({ - type: 'confirm', - name: 'confirm', + return confirm({ message, default: true, }) @@ -55,9 +53,7 @@ const getDenoVSCodeExt = async (repositoryRoot) => { const getDenoExtPrompt = () => { const message = 'The Deno VS Code extension is recommended. Would you like to install it now?' - return inquirer.prompt({ - type: 'confirm', - name: 'confirm', + return confirm({ message, default: true, }) @@ -81,15 +77,15 @@ export const run = async ({ config, repositoryRoot }) => { const edgeFunctionsPath = getEdgeFunctionsPath({ config, repositoryRoot }) const { fileExists, settings: existingSettings } = await getSettings(settingsPath) const settings = applySettings(existingSettings, { denoBinary, edgeFunctionsPath, repositoryRoot }) - const { confirm } = await getPrompt({ fileExists, path: settingsPath }) + const confirmed = await getPrompt({ fileExists, path: settingsPath }) - if (!confirm) { + if (!confirmed) { return } try { if (!(await hasDenoVSCodeExt(repositoryRoot))) { - const { confirm: denoExtConfirm } = await getDenoExtPrompt() + const denoExtConfirm = await getDenoExtPrompt() if (denoExtConfirm) { getDenoVSCodeExt(repositoryRoot) } diff --git a/src/utils/build-info.ts b/src/utils/build-info.ts index 018090a0da3..ac10355b940 100644 --- a/src/utils/build-info.ts +++ b/src/utils/build-info.ts @@ -1,5 +1,5 @@ import type { Settings } from '@netlify/build-info' -import search from '@inquirer/search' +import { search } from '@inquirer/prompts' import { isCI } from 'ci-info' import fuzzy from 'fuzzy' diff --git a/src/utils/gh-auth.ts b/src/utils/gh-auth.ts index a0100608a49..03ceee127dc 100644 --- a/src/utils/gh-auth.ts +++ b/src/utils/gh-auth.ts @@ -2,8 +2,8 @@ import http from 'http' import process from 'process' +import { password, select } from '@inquirer/prompts' import getPort from 'get-port' -import inquirer from 'inquirer' import { log } from './command-helpers.js' import createDeferred from './create-deferred.js' @@ -23,16 +23,12 @@ const promptForAuthMethod = async () => { const authChoiceToken = 'Authorize with a GitHub personal access token' const authChoices = [authChoiceNetlify, authChoiceToken] as const - const { authMethod } = await inquirer.prompt<{ authMethod: (typeof authChoices)[number] }>([ - { - type: 'list', - name: 'authMethod', - message: - 'Netlify CLI needs access to your GitHub account to configure Webhooks and Deploy Keys. ' + - 'What would you like to do?', - choices: authChoices, - }, - ]) + const authMethod = await select({ + message: + 'Netlify CLI needs access to your GitHub account to configure Webhooks and Deploy Keys. ' + + 'What would you like to do?', + choices: authChoices, + }) return authMethod === authChoiceNetlify } @@ -83,16 +79,9 @@ export const authWithNetlify = async (): Promise => { } const getPersonalAccessToken = async (): Promise<{ token: string }> => { - const { token } = await inquirer.prompt<{ token: string }>([ - { - type: 'password', - name: 'token', - message: 'Your GitHub personal access token:', - filter: (input: string) => input.trim(), - }, - ]) - - return { token } + const token = await password({ message: 'Your GitHub personal access token:' }) + + return { token: token.trim() } } /** diff --git a/src/utils/init/config-manual.ts b/src/utils/init/config-manual.ts index 40a026e9cee..c6432b01726 100644 --- a/src/utils/init/config-manual.ts +++ b/src/utils/init/config-manual.ts @@ -1,4 +1,4 @@ -import inquirer from 'inquirer' +import { confirm, input } from '@inquirer/prompts' import { exit, log } from '../command-helpers.js' import type BaseCommand from '../../commands/base-command.js' @@ -15,14 +15,7 @@ const addDeployKey = async (deployKey: DeployKey) => { // eslint-disable-next-line @typescript-eslint/restrict-template-expressions log(`\n${deployKey.public_key}\n\n`) - const { sshKeyAdded } = (await inquirer.prompt([ - { - type: 'confirm', - name: 'sshKeyAdded', - message: 'Continue?', - default: true, - }, - ])) as { sshKeyAdded: boolean } + const sshKeyAdded = await confirm({ message: 'Continue?', default: true }) if (!sshKeyAdded) { return exit() @@ -30,17 +23,11 @@ const addDeployKey = async (deployKey: DeployKey) => { } const getRepoPath = async ({ repoData }: { repoData: RepoData }): Promise => { - const { repoPath } = await inquirer.prompt<{ repoPath: string }>([ - { - type: 'input', - name: 'repoPath', - message: 'The SSH URL of the remote git repo:', - default: repoData.url, - validate: (url: string) => (SSH_URL_REGEXP.test(url) ? true : 'The URL provided does not use the SSH protocol'), - }, - ]) - - return repoPath + return await input({ + message: 'The SSH URL of the remote git repo:', + default: repoData.url, + validate: (url: string) => (SSH_URL_REGEXP.test(url) ? true : 'The URL provided does not use the SSH protocol'), + }) } const addDeployHook = async (deployHook: string | undefined): Promise => { @@ -48,16 +35,7 @@ const addDeployHook = async (deployHook: string | undefined): Promise = // FIXME(serhalp): Handle nullish `deployHook` by throwing user-facing error or fixing upstream type. // eslint-disable-next-line @typescript-eslint/restrict-template-expressions log(`\n${deployHook}\n\n`) - const { deployHookAdded } = await inquirer.prompt<{ deployHookAdded: boolean }>([ - { - type: 'confirm', - name: 'deployHookAdded', - message: 'Continue?', - default: true, - }, - ]) - - return deployHookAdded + return await confirm({ message: 'Continue?', default: true }) } const isSupportedProvider = (provider: string | null): provider is 'github' | 'gitlab' => diff --git a/src/utils/init/utils.ts b/src/utils/init/utils.ts index d2a67faf9e4..077451c8dad 100644 --- a/src/utils/init/utils.ts +++ b/src/utils/init/utils.ts @@ -4,7 +4,7 @@ import path from 'path' import type { NetlifyAPI } from '@netlify/api' import type { Settings } from '@netlify/build-info' import cleanDeep from 'clean-deep' -import inquirer from 'inquirer' +import { confirm, input } from '@inquirer/prompts' import type BaseCommand from '../../commands/base-command.js' import { fileExistsAsync } from '../../lib/fs.js' @@ -62,40 +62,6 @@ const normalizeSettings = (settings: Partial, config: NormalizedCached } } -const getPromptInputs = ({ - defaultBaseDir, - defaultBuildCmd, - defaultBuildDir, -}: { - defaultBaseDir: string - defaultBuildCmd?: string | undefined - defaultBuildDir?: string | undefined -}) => { - const inputs = [ - defaultBaseDir !== '' && { - type: 'input', - name: 'baseDir', - message: 'Base directory `(blank for current dir):', - default: defaultBaseDir, - }, - { - type: 'input', - name: 'buildCmd', - message: 'Your build command (hugo build/yarn run build/etc):', - filter: (val: string) => (val === '' ? '# no build command' : val), - default: defaultBuildCmd, - }, - { - type: 'input', - name: 'buildDir', - message: 'Directory to deploy (blank for current dir):', - default: defaultBuildDir, - }, - ].filter(Boolean) - - return inputs.filter(Boolean) -} - export const getBuildSettings = async ({ command, config, @@ -120,17 +86,16 @@ export const getBuildSettings = async ({ log() } - const { baseDir, buildCmd, buildDir } = await inquirer.prompt<{ - baseDir?: string | undefined - buildCmd: string - buildDir: string - }>( - getPromptInputs({ - defaultBaseDir, - defaultBuildCmd, - defaultBuildDir, - }), - ) + const baseDir = + defaultBaseDir === '' + ? undefined + : await input({ message: 'Base directory `(blank for current dir):', default: defaultBaseDir }) + const buildCmdInput = await input({ + message: 'Your build command (hugo build/yarn run build/etc):', + default: defaultBuildCmd, + }) + const buildCmd = buildCmdInput === '' ? '# no build command' : buildCmdInput + const buildDir = await input({ message: 'Directory to deploy (blank for current dir):', default: defaultBuildDir }) const pluginsToInstall = recommendedPlugins.map((plugin) => ({ package: plugin })) const normalizedBaseDir = baseDir ? normalizeBackslash(baseDir) : undefined @@ -198,14 +163,10 @@ export const saveNetlifyToml = async ({ return } - const { makeNetlifyTOML } = await inquirer.prompt([ - { - type: 'confirm', - name: 'makeNetlifyTOML', - message: 'No netlify.toml detected. Would you like to create one with these build settings?', - default: true, - }, - ]) + const makeNetlifyTOML = await confirm({ + message: 'No netlify.toml detected. Would you like to create one with these build settings?', + default: true, + }) if (makeNetlifyTOML) { try { await writeFile( diff --git a/src/utils/prompts/confirm-prompt.ts b/src/utils/prompts/confirm-prompt.ts index fa6ea6dc89f..88ffcb96e3f 100644 --- a/src/utils/prompts/confirm-prompt.ts +++ b/src/utils/prompts/confirm-prompt.ts @@ -1,21 +1,11 @@ -import inquirer from 'inquirer' +import { confirm } from '@inquirer/prompts' import { log, exit } from '../command-helpers.js' export const confirmPrompt = async (message: string): Promise => { - try { - const { confirm } = await inquirer.prompt({ - type: 'confirm', - name: 'confirm', - message, - default: false, - }) - log() - if (!confirm) { - exit() - } - } catch (error) { - console.error(error) + const confirmed = await confirm({ message, default: false }) + log() + if (!confirmed) { exit() } } diff --git a/tests/integration/commands/blobs/blobs-delete.test.ts b/tests/integration/commands/blobs/blobs-delete.test.ts index e87f1d345ac..ecfbb47ef7f 100644 --- a/tests/integration/commands/blobs/blobs-delete.test.ts +++ b/tests/integration/commands/blobs/blobs-delete.test.ts @@ -10,7 +10,9 @@ import { reportError } from '../../../../src/utils/telemetry/report-error.js' import { Route } from '../../utils/mock-api-vitest.js' import { getEnvironmentVariables, withMockApi, setTTYMode, setCI, setTestingPrompts } from '../../utils/mock-api.js' import { runMockProgram } from '../../utils/mock-program.js' -import { mockPrompt, spyOnMockPrompt } from '../../utils/inquirer-mock-prompt.js' +import { mockConfirm, spyOnConfirm } from '../../utils/inquirer-mock-prompt.js' + +vi.mock('@inquirer/prompts') const siteInfo = { account_slug: 'test-account', @@ -93,13 +95,11 @@ describe('blobs:delete command', () => { delete: mockDelete, }) - const promptSpy = mockPrompt({ confirm: true }) + const promptSpy = mockConfirm(true) await runMockProgram(['', '', 'blobs:delete', storeName, key]) expect(promptSpy).toHaveBeenCalledWith({ - type: 'confirm', - name: 'confirm', message: expect.stringContaining(overwriteConfirmation), default: false, }) @@ -120,7 +120,7 @@ describe('blobs:delete command', () => { delete: mockDelete, }) - const promptSpy = mockPrompt({ confirm: false }) + const promptSpy = mockConfirm(false) try { await runMockProgram(['', '', 'blobs:delete', storeName, key]) @@ -131,8 +131,6 @@ describe('blobs:delete command', () => { } expect(promptSpy).toHaveBeenCalledWith({ - type: 'confirm', - name: 'confirm', message: expect.stringContaining(overwriteConfirmation), default: false, }) @@ -153,7 +151,7 @@ describe('blobs:delete command', () => { delete: mockDelete, }) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'blobs:delete', storeName, key, '--force']) @@ -178,7 +176,7 @@ describe('blobs:delete command', () => { delete: mockDelete, }) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() try { await runMockProgram(['', '', 'blobs:delete', storeName, key, '--force']) @@ -214,7 +212,7 @@ describe('blobs:delete command', () => { delete: mockDelete, }) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'blobs:delete', storeName, key]) expect(promptSpy).not.toHaveBeenCalled() @@ -236,7 +234,7 @@ describe('blobs:delete command', () => { delete: mockDelete, }) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'blobs:delete', storeName, key]) diff --git a/tests/integration/commands/blobs/blobs-set.test.ts b/tests/integration/commands/blobs/blobs-set.test.ts index 031fc0fdc9b..85bdb085fa6 100644 --- a/tests/integration/commands/blobs/blobs-set.test.ts +++ b/tests/integration/commands/blobs/blobs-set.test.ts @@ -2,7 +2,6 @@ import process from 'process' import { getStore } from '@netlify/blobs' import chalk from 'chalk' -import inquirer from 'inquirer' import { describe, expect, test, vi, beforeEach, afterAll } from 'vitest' import { log } from '../../../../src/utils/command-helpers.js' @@ -11,7 +10,9 @@ import { reportError } from '../../../../src/utils/telemetry/report-error.js' import { Route } from '../../utils/mock-api-vitest.js' import { getEnvironmentVariables, withMockApi, setTTYMode, setCI, setTestingPrompts } from '../../utils/mock-api.js' import { runMockProgram } from '../../utils/mock-program.js' -import { mockPrompt, spyOnMockPrompt } from '../../utils/inquirer-mock-prompt.js' +import { mockConfirm, spyOnConfirm } from '../../utils/inquirer-mock-prompt.js' + +vi.mock('@inquirer/prompts') const siteInfo = { account_slug: 'test-account', @@ -98,7 +99,7 @@ describe('blobs:set command', () => { set: mockSet, }) - const promptSpy = vi.spyOn(inquirer, 'prompt').mockResolvedValue({ wantsToSet: true }) + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'blobs:set', storeName, key, value]) @@ -122,13 +123,11 @@ describe('blobs:set command', () => { set: mockSet, }) - const promptSpy = mockPrompt({ confirm: true }) + const promptSpy = mockConfirm(true) await runMockProgram(['', '', 'blobs:set', storeName, key, newValue]) expect(promptSpy).toHaveBeenCalledWith({ - type: 'confirm', - name: 'confirm', message: expect.stringContaining(overwriteConfirmation), default: false, }) @@ -152,7 +151,7 @@ describe('blobs:set command', () => { set: mockSet, }) - const promptSpy = mockPrompt({ confirm: false }) + const promptSpy = mockConfirm(false) try { await runMockProgram(['', '', 'blobs:set', storeName, key, newValue]) @@ -163,8 +162,6 @@ describe('blobs:set command', () => { } expect(promptSpy).toHaveBeenCalledWith({ - type: 'confirm', - name: 'confirm', message: expect.stringContaining(overwriteConfirmation), default: false, }) @@ -188,7 +185,7 @@ describe('blobs:set command', () => { set: mockSet, }) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'blobs:set', storeName, key, newValue, '--force']) @@ -210,7 +207,7 @@ describe('blobs:set command', () => { set: mockSet, }) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() try { await runMockProgram(['', '', 'blobs:set', storeName, key, newValue, '--force']) @@ -246,7 +243,7 @@ describe('blobs:set command', () => { set: mockSet, }) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'blobs:set', storeName, key, newValue]) @@ -272,7 +269,7 @@ describe('blobs:set command', () => { set: mockSet, }) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'blobs:set', storeName, key, newValue]) expect(promptSpy).not.toHaveBeenCalled() diff --git a/tests/integration/commands/env/__snapshots__/env.test.ts.snap b/tests/integration/commands/env/__snapshots__/env.test.ts.snap index 41231145f9b..c7da4d2da30 100644 --- a/tests/integration/commands/env/__snapshots__/env.test.ts.snap +++ b/tests/integration/commands/env/__snapshots__/env.test.ts.snap @@ -27,7 +27,8 @@ nctions | | '------------------------------------------------------------------------------- --------' -? Show values? (y/N) ? Show values? No" +? Show values? (y/N)✔ Show values? No +[?25h" `; exports[`commands/env > env:list > should hide variables values and show on confirm 1`] = ` @@ -48,8 +49,8 @@ nctions | | '------------------------------------------------------------------------------- --------' -? Show values? (y/N) ? Show values? (y/N) y? Show values? Yes - +? Show values? (y/N)? Show values? (y/N) y✔ Show values? Yes +[?25h .----------------------------------------------. | Environment variables | |----------------------------------------------| diff --git a/tests/integration/commands/env/env-clone.test.ts b/tests/integration/commands/env/env-clone.test.ts index 714a9b101e8..7af30c7a6f5 100644 --- a/tests/integration/commands/env/env-clone.test.ts +++ b/tests/integration/commands/env/env-clone.test.ts @@ -9,7 +9,9 @@ import { getEnvironmentVariables, withMockApi, setTTYMode, setCI, setTestingProm import { existingVar, routes, secondSiteInfo } from './api-routes.js' import { runMockProgram } from '../../utils/mock-program.js' -import { mockPrompt, spyOnMockPrompt } from '../../utils/inquirer-mock-prompt.js' +import { mockConfirm, spyOnConfirm } from '../../utils/inquirer-mock-prompt.js' + +vi.mock('@inquirer/prompts') vi.mock('../../../../src/utils/command-helpers.js', async () => ({ ...(await vi.importActual('../../../../src/utils/command-helpers.js')), @@ -58,13 +60,11 @@ describe('env:clone command', () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = mockPrompt({ confirm: true }) + const promptSpy = mockConfirm(true) await runMockProgram(['', '', 'env:clone', '-t', siteIdTwo]) expect(promptSpy).toHaveBeenCalledWith({ - type: 'confirm', - name: 'confirm', message: expect.stringContaining(overwriteConfirmation), default: false, }) @@ -83,7 +83,7 @@ describe('env:clone command', () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'env:clone', '--force', '-t', siteIdTwo]) @@ -103,7 +103,7 @@ describe('env:clone command', () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = mockPrompt({ confirm: false }) + const promptSpy = mockConfirm(false) try { await runMockProgram(['', '', 'env:clone', '-t', siteIdTwo]) @@ -132,7 +132,7 @@ describe('env:clone command', () => { 'site-name', )} to ${chalk.green('site-name-3')}` - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'env:clone', '-t', 'site_id_3']) @@ -156,7 +156,7 @@ describe('env:clone command', () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'env:clone', '-t', siteIdTwo]) @@ -174,7 +174,7 @@ describe('env:clone command', () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'env:clone', '-t', siteIdTwo]) diff --git a/tests/integration/commands/env/env-set.test.ts b/tests/integration/commands/env/env-set.test.ts index c24a2d3ea0d..4a647836724 100644 --- a/tests/integration/commands/env/env-set.test.ts +++ b/tests/integration/commands/env/env-set.test.ts @@ -9,7 +9,9 @@ import { FixtureTestContext, setupFixtureTests } from '../../utils/fixture.js' import { getEnvironmentVariables, withMockApi, setTTYMode, setCI, setTestingPrompts } from '../../utils/mock-api.js' import { runMockProgram } from '../../utils/mock-program.js' import { routes } from './api-routes.js' -import { mockPrompt, spyOnMockPrompt } from '../../utils/inquirer-mock-prompt.js' +import { mockConfirm, spyOnConfirm } from '../../utils/inquirer-mock-prompt.js' + +vi.mock('@inquirer/prompts') vi.mock('../../../../src/utils/command-helpers.js', async () => ({ ...(await vi.importActual('../../../../src/utils/command-helpers.js')), @@ -306,13 +308,11 @@ describe('env:set command', async () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = mockPrompt({ confirm: true }) + const promptSpy = mockConfirm(true) await runMockProgram(['', '', 'env:set', existingVar, newEnvValue]) expect(promptSpy).toHaveBeenCalledWith({ - type: 'confirm', - name: 'confirm', message: expect.stringContaining(overwriteConfirmation), default: false, }) @@ -327,7 +327,7 @@ describe('env:set command', async () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'env:set', 'NEW_ENV_VAR', 'NEW_VALUE']) @@ -345,7 +345,7 @@ describe('env:set command', async () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'env:set', existingVar, newEnvValue, '--force']) @@ -361,7 +361,7 @@ describe('env:set command', async () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = mockPrompt({ confirm: false }) + const promptSpy = mockConfirm(false) try { await runMockProgram(['', '', 'env:set', existingVar, newEnvValue]) @@ -387,7 +387,7 @@ describe('env:set command', async () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'env:set', existingVar, newEnvValue]) @@ -405,7 +405,7 @@ describe('env:set command', async () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'env:set', existingVar, newEnvValue]) expect(promptSpy).not.toHaveBeenCalled() diff --git a/tests/integration/commands/env/env-unset.test.ts b/tests/integration/commands/env/env-unset.test.ts index 41a0ae700cc..845351a8a4b 100644 --- a/tests/integration/commands/env/env-unset.test.ts +++ b/tests/integration/commands/env/env-unset.test.ts @@ -9,7 +9,9 @@ import { getEnvironmentVariables, withMockApi, setTTYMode, setCI, setTestingProm import { routes } from './api-routes.js' import { runMockProgram } from '../../utils/mock-program.js' -import { mockPrompt, spyOnMockPrompt } from '../../utils/inquirer-mock-prompt.js' +import { mockConfirm, spyOnConfirm } from '../../utils/inquirer-mock-prompt.js' + +vi.mock('@inquirer/prompts') vi.mock('../../../../src/utils/command-helpers.js', async () => ({ ...(await vi.importActual('../../../../src/utils/command-helpers.js')), @@ -112,13 +114,11 @@ describe('env:unset command', async () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = mockPrompt({ confirm: true }) + const promptSpy = mockConfirm(true) await runMockProgram(['', '', 'env:unset', existingVar]) expect(promptSpy).toHaveBeenCalledWith({ - type: 'confirm', - name: 'confirm', message: expect.stringContaining(overwriteConfirmation), default: false, }) @@ -133,7 +133,7 @@ describe('env:unset command', async () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'env:unset', existingVar, '--force']) @@ -149,7 +149,7 @@ describe('env:unset command', async () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = mockPrompt({ confirm: false }) + const promptSpy = mockConfirm(false) try { await runMockProgram(['', '', 'env:unset', existingVar]) @@ -171,7 +171,7 @@ describe('env:unset command', async () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'env:unset', 'NEW_ENV_VAR']) @@ -191,7 +191,7 @@ describe('env:unset command', async () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'env:unset', existingVar]) expect(promptSpy).not.toHaveBeenCalled() @@ -208,7 +208,7 @@ describe('env:unset command', async () => { await withMockApi(routes, async ({ apiUrl }) => { Object.assign(process.env, getEnvironmentVariables({ apiUrl })) - const promptSpy = spyOnMockPrompt() + const promptSpy = spyOnConfirm() await runMockProgram(['', '', 'env:unset', existingVar]) expect(promptSpy).not.toHaveBeenCalled() diff --git a/tests/integration/commands/init/init.test.ts b/tests/integration/commands/init/init.test.ts index dc1c3a1ce48..d49e3975a53 100644 --- a/tests/integration/commands/init/init.test.ts +++ b/tests/integration/commands/init/init.test.ts @@ -124,7 +124,7 @@ describe.concurrent('commands/init', () => { question: 'Create & configure a new project', answer: answerWithValue(DOWN), }, - { question: 'Team: (Use arrow keys)', answer: CONFIRM }, + { question: 'Team:', answer: CONFIRM }, { question: 'Project name (leave blank for a random name; you can change it later)', answer: answerWithValue('test-site-name'), @@ -220,7 +220,7 @@ describe.concurrent('commands/init', () => { question: 'Yes, create and deploy project manually', answer: CONFIRM, // List selection only needs one CONFIRM, not answerWithValue }, - { question: 'Team: (Use arrow keys)', answer: CONFIRM }, + { question: 'Team:', answer: CONFIRM }, { question: 'Project name (leave blank for a random name; you can change it later)', answer: answerWithValue('test-site-name'), @@ -303,7 +303,7 @@ describe.concurrent('commands/init', () => { question: 'Create & configure a new project', answer: answerWithValue(DOWN), }, - { question: 'Team: (Use arrow keys)', answer: CONFIRM }, + { question: 'Team:', answer: CONFIRM }, { question: 'Project name (leave blank for a random name; you can change it later)', answer: answerWithValue('test-site-name'), @@ -408,7 +408,7 @@ describe.concurrent('commands/init', () => { question: 'Create & configure a new project', answer: answerWithValue(DOWN), }, - { question: 'Team: (Use arrow keys)', answer: CONFIRM }, + { question: 'Team:', answer: CONFIRM }, { question: 'Project name (leave blank for a random name; you can change it later)', answer: answerWithValue('test-site-name'), @@ -601,7 +601,7 @@ describe.concurrent('commands/init', () => { question: 'Create & configure a new project', answer: answerWithValue(DOWN), }, - { question: 'Team: (Use arrow keys)', answer: CONFIRM }, + { question: 'Team:', answer: CONFIRM }, { question: 'Project name (leave blank for a random name; you can change it later)', answer: answerWithValue('test-site-name'), diff --git a/tests/integration/commands/recipes/__snapshots__/recipes.test.ts.snap b/tests/integration/commands/recipes/__snapshots__/recipes.test.ts.snap index cde45b7c2d0..e8d4d91dcfa 100644 --- a/tests/integration/commands/recipes/__snapshots__/recipes.test.ts.snap +++ b/tests/integration/commands/recipes/__snapshots__/recipes.test.ts.snap @@ -14,5 +14,6 @@ exports[`commands/recipes > Shows a list of all the available recipes 1`] = ` exports[`commands/recipes > Suggests closest matching recipe on typo 1`] = ` "⬥ vsc is not a valid recipe name. -? Did you mean vscode (y/N) " +? Did you mean vscode (y/N) +[?25h" `; diff --git a/tests/integration/commands/sites/sites.test.ts b/tests/integration/commands/sites/sites.test.ts index 968db952451..5ffb78c8e43 100644 --- a/tests/integration/commands/sites/sites.test.ts +++ b/tests/integration/commands/sites/sites.test.ts @@ -1,6 +1,5 @@ import process from 'process' -import inquirer from 'inquirer' import { afterAll, beforeEach, describe, expect, test, vi } from 'vitest' import BaseCommand from '../../../../src/commands/base-command.js' @@ -12,7 +11,10 @@ vi.mock('../../../../src/utils/command-helpers.js', async () => ({ log: () => {}, })) -vi.spyOn(inquirer, 'prompt').mockImplementation(() => Promise.resolve({ accountSlug: 'test-account' })) +vi.mock('@inquirer/prompts', () => ({ + input: vi.fn().mockResolvedValue(''), + select: vi.fn().mockResolvedValue('test-account'), +})) const siteInfo = { admin_url: 'https://app.netlify.com/projects/site-name/overview', diff --git a/tests/integration/utils/inquirer-mock-prompt.ts b/tests/integration/utils/inquirer-mock-prompt.ts index e4121f886fc..ed475493a7d 100644 --- a/tests/integration/utils/inquirer-mock-prompt.ts +++ b/tests/integration/utils/inquirer-mock-prompt.ts @@ -1,23 +1,8 @@ -// tests/utils/inquirer-mock.ts -import inquirer from 'inquirer' +import { confirm } from '@inquirer/prompts' import { vi } from 'vitest' -export const mockPrompt = (response = { confirm: true }) => { - // Create the mock function - const mockFn = vi.fn().mockResolvedValue(response) +// Callers must mock the module in their own test file with `vi.mock('@inquirer/prompts')`, +// because `vi.mock` is hoisted per file. +export const mockConfirm = (answer: boolean) => vi.mocked(confirm).mockReset().mockResolvedValue(answer) - // Preserve the original properties of inquirer.prompt - Object.assign(mockFn, inquirer.prompt) - - // Create the spy with our prepared mock - const spy = vi.spyOn(inquirer, 'prompt').mockImplementation(mockFn) - - inquirer.registerPrompt = vi.fn() - inquirer.prompt.registerPrompt = vi.fn() - - return spy -} - -export const spyOnMockPrompt = () => { - return vi.spyOn(inquirer, 'prompt') -} +export const spyOnConfirm = () => vi.mocked(confirm).mockReset() diff --git a/tests/unit/commands/database/db-init.test.ts b/tests/unit/commands/database/db-init.test.ts index 89fce2d2969..4e1ea4b1129 100644 --- a/tests/unit/commands/database/db-init.test.ts +++ b/tests/unit/commands/database/db-init.test.ts @@ -15,7 +15,6 @@ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest' const { mockSpawnAsync, mockConnectRawClient, - mockInquirerPrompt, mockIsInteractive, mockFormatQueryResult, mockApplyMigrations, @@ -25,7 +24,6 @@ const { } = vi.hoisted(() => ({ mockSpawnAsync: vi.fn(), mockConnectRawClient: vi.fn(), - mockInquirerPrompt: vi.fn(), mockIsInteractive: vi.fn().mockReturnValue(true), mockFormatQueryResult: vi.fn(), mockApplyMigrations: vi.fn(), @@ -34,12 +32,7 @@ const { logMessages: [] as string[], })) -vi.mock('inquirer', () => ({ - default: { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - prompt: (...args: unknown[]) => mockInquirerPrompt(...args), - }, -})) +vi.mock('@inquirer/prompts') vi.mock('../../../../src/commands/database/util/spawn-async.js', async (importOriginal) => ({ ...(await importOriginal()), @@ -79,6 +72,8 @@ vi.mock('../../../../src/utils/command-helpers.js', async () => ({ }, })) +import { confirm, select } from '@inquirer/prompts' + import { initDatabase } from '../../../../src/commands/database/db-init.js' import { utcTimestampPrefix } from '../../../../src/commands/database/util/timestamp.js' @@ -99,13 +94,9 @@ function createCommand(projectRoot: string) { } as unknown as Parameters[1] } -const setPrompts = (...responses: Record[]) => { - const queue = [...responses] - mockInquirerPrompt.mockImplementation(() => { - const next = queue.shift() - if (!next) throw new Error('Unexpected inquirer.prompt call — no response queued') - return Promise.resolve(next) - }) +const answerPrompts = ({ queryStyle, createSampleData }: { queryStyle: string; createSampleData: boolean }) => { + vi.mocked(select).mockResolvedValueOnce(queryStyle) + vi.mocked(confirm).mockResolvedValueOnce(createSampleData) } const exists = async (path: string): Promise => { @@ -131,6 +122,8 @@ beforeEach(async () => { logMessages.length = 0 vi.clearAllMocks() + vi.mocked(select).mockReset().mockRejectedValue(new Error('Unexpected select prompt — no answer queued')) + vi.mocked(confirm).mockReset().mockRejectedValue(new Error('Unexpected confirm prompt — no answer queued')) // Simulate `drizzle-kit generate` by writing a timestamp-prefixed directory // with a migration.sql under the configured out dir. Anything else (package @@ -175,7 +168,7 @@ afterEach(async () => { describe('initDatabase (integration)', () => { test('raw SQL + starter writes a timestamp-prefixed migration with a CREATE TABLE and seed data', async () => { - setPrompts({ queryStyle: 'raw' }, { answer: true }) + answerPrompts({ queryStyle: 'raw', createSampleData: true }) await initDatabase({}, createCommand(projectRoot())) @@ -199,7 +192,7 @@ describe('initDatabase (integration)', () => { }) test('Drizzle + starter writes schema/config, runs drizzle-kit generate, and seeds after it', async () => { - setPrompts({ queryStyle: 'drizzle' }, { answer: true }) + answerPrompts({ queryStyle: 'drizzle', createSampleData: true }) await initDatabase({}, createCommand(projectRoot())) @@ -242,7 +235,7 @@ describe('initDatabase (integration)', () => { }) test('Drizzle without starter scaffolds drizzle.config.ts only (no schema, no migration, no generate)', async () => { - setPrompts({ queryStyle: 'drizzle' }, { answer: false }) + answerPrompts({ queryStyle: 'drizzle', createSampleData: false }) await initDatabase({}, createCommand(projectRoot())) @@ -260,7 +253,7 @@ describe('initDatabase (integration)', () => { }) test('raw without starter writes nothing extra; next steps point at `database migrations new`', async () => { - setPrompts({ queryStyle: 'raw' }, { answer: false }) + answerPrompts({ queryStyle: 'raw', createSampleData: false }) await initDatabase({}, createCommand(projectRoot())) @@ -282,7 +275,8 @@ describe('initDatabase (integration)', () => { await initDatabase({}, createCommand(projectRoot())) expect(await readMigrations(projectRoot())).toEqual(['0001_existing.sql']) - expect(mockInquirerPrompt).not.toHaveBeenCalled() + expect(select).not.toHaveBeenCalled() + expect(confirm).not.toHaveBeenCalled() expect(mockSpawnAsync).not.toHaveBeenCalled() expect(mockConnectRawClient).not.toHaveBeenCalled() expect(logMessages.join('\n')).toContain('you already have migrations set up') @@ -293,7 +287,8 @@ describe('initDatabase (integration)', () => { await initDatabase({}, createCommand(projectRoot())) - expect(mockInquirerPrompt).not.toHaveBeenCalled() + expect(select).not.toHaveBeenCalled() + expect(confirm).not.toHaveBeenCalled() expect(await exists(join(projectRoot(), 'drizzle.config.ts'))).toBe(true) expect(await exists(join(projectRoot(), 'db', 'schema.ts'))).toBe(true) expect((await readMigrations(projectRoot())).some((name) => name.includes('seed_planets'))).toBe(true) diff --git a/tests/unit/commands/database/db-migration-new.test.ts b/tests/unit/commands/database/db-migration-new.test.ts index aeed6876d57..f3a37dc4bf5 100644 --- a/tests/unit/commands/database/db-migration-new.test.ts +++ b/tests/unit/commands/database/db-migration-new.test.ts @@ -22,9 +22,7 @@ vi.mock('fs/promises', async (importOriginal) => { } }) -vi.mock('inquirer', () => ({ - default: { prompt: vi.fn() }, -})) +vi.mock('@inquirer/prompts') vi.mock('../../../../src/utils/scripted-commands.js', () => ({ isInteractive: vi.fn(), @@ -42,7 +40,7 @@ vi.mock('../../../../src/utils/command-helpers.js', async () => ({ import { join } from 'path' -import inquirer from 'inquirer' +import { input, select } from '@inquirer/prompts' import { migrationNew, generateSlug, @@ -201,13 +199,13 @@ describe('migrationNew', () => { test('prompts for description when not provided', async () => { vi.mocked(isInteractive).mockReturnValue(true) - vi.mocked(inquirer.prompt) - .mockResolvedValueOnce({ description: 'create users table' }) - .mockResolvedValueOnce({ scheme: 'sequential' }) + vi.mocked(input).mockResolvedValueOnce('create users table') + vi.mocked(select).mockResolvedValueOnce('sequential') await migrationNew({}, createMockCommand()) - expect(inquirer.prompt).toHaveBeenCalledTimes(2) + expect(input).toHaveBeenCalledOnce() + expect(select).toHaveBeenCalledOnce() expect(mockMkdir).toHaveBeenCalledWith(expect.stringContaining('create-users-table'), expect.any(Object)) }) @@ -222,12 +220,11 @@ describe('migrationNew', () => { test('prompts for scheme with detected default when not provided', async () => { vi.mocked(isInteractive).mockReturnValue(true) mockReaddir.mockResolvedValue([dirEntry('0001_create-users'), dirEntry('0002_add-posts')]) - vi.mocked(inquirer.prompt).mockResolvedValueOnce({ scheme: 'sequential' }) + vi.mocked(select).mockResolvedValueOnce('sequential') await migrationNew({ description: 'add comments' }, createMockCommand()) - const promptCall = vi.mocked(inquirer.prompt).mock.calls[0][0] as { default?: string }[] - expect(promptCall[0].default).toBe('sequential') + expect(select).toHaveBeenCalledWith(expect.objectContaining({ default: 'sequential' })) }) test('defaults to timestamp scheme in non-interactive mode when no migrations exist', async () => { @@ -235,7 +232,8 @@ describe('migrationNew', () => { await migrationNew({ description: 'add posts table' }, createMockCommand()) - expect(inquirer.prompt).not.toHaveBeenCalled() + expect(input).not.toHaveBeenCalled() + expect(select).not.toHaveBeenCalled() const mkdirCall = mockMkdir.mock.calls[0][0] as string const folderName = mkdirCall.split(/[/\\]/).pop() ?? '' expect(folderName).toMatch(/^\d{14}_add-posts-table$/) @@ -247,7 +245,8 @@ describe('migrationNew', () => { await migrationNew({ description: 'add comments' }, createMockCommand()) - expect(inquirer.prompt).not.toHaveBeenCalled() + expect(input).not.toHaveBeenCalled() + expect(select).not.toHaveBeenCalled() expect(mockMkdir).toHaveBeenCalledWith(join('/project/netlify/database/migrations', '0003_add-comments'), { recursive: true, }) @@ -259,7 +258,8 @@ describe('migrationNew', () => { await migrationNew({ description: 'add comments' }, createMockCommand()) - expect(inquirer.prompt).not.toHaveBeenCalled() + expect(input).not.toHaveBeenCalled() + expect(select).not.toHaveBeenCalled() const mkdirCall = mockMkdir.mock.calls[0][0] as string const folderName = mkdirCall.split(/[/\\]/).pop() ?? '' expect(folderName).toMatch(/^\d{14}_add-comments$/) diff --git a/tests/unit/commands/database/db-migration-pull.test.ts b/tests/unit/commands/database/db-migration-pull.test.ts index 2966b2f4f77..8febc268a6c 100644 --- a/tests/unit/commands/database/db-migration-pull.test.ts +++ b/tests/unit/commands/database/db-migration-pull.test.ts @@ -24,9 +24,7 @@ vi.mock('fs/promises', async (importOriginal) => { } }) -vi.mock('inquirer', () => ({ - default: { prompt: vi.fn() }, -})) +vi.mock('@inquirer/prompts') vi.mock('../../../../src/utils/command-helpers.js', async () => ({ ...(await vi.importActual('../../../../src/utils/command-helpers.js')), @@ -47,7 +45,7 @@ vi.stubGlobal('fetch', mockFetch) import { resolve } from 'path' -import inquirer from 'inquirer' +import { confirm } from '@inquirer/prompts' import { migrationPull } from '../../../../src/commands/database/db-migration-pull.js' interface SampleMigration { @@ -148,7 +146,7 @@ describe('migrationPull', () => { test('fetches migrations from the correct API endpoint', async () => { mockFetchResponse(sampleMigrations) - vi.mocked(inquirer.prompt).mockResolvedValueOnce({ confirmed: true }) + vi.mocked(confirm).mockResolvedValueOnce(true) await migrationPull({}, createMockCommand()) @@ -250,11 +248,11 @@ describe('migrationPull', () => { test('prompts for confirmation before overwriting', async () => { mockFetchResponse(sampleMigrations) - vi.mocked(inquirer.prompt).mockResolvedValueOnce({ confirmed: false }) + vi.mocked(confirm).mockResolvedValueOnce(false) await migrationPull({}, createMockCommand()) - expect(inquirer.prompt).toHaveBeenCalledTimes(1) + expect(confirm).toHaveBeenCalledOnce() expect(mockRm).not.toHaveBeenCalled() expect(logMessages).toContain('Pull cancelled.') }) @@ -264,13 +262,13 @@ describe('migrationPull', () => { await migrationPull({ force: true }, createMockCommand()) - expect(inquirer.prompt).not.toHaveBeenCalled() + expect(confirm).not.toHaveBeenCalled() expect(mockRm).toHaveBeenCalled() }) test('removes existing migrations directory and writes fetched migrations', async () => { mockFetchResponse(sampleMigrations) - vi.mocked(inquirer.prompt).mockResolvedValueOnce({ confirmed: true }) + vi.mocked(confirm).mockResolvedValueOnce(true) const migrationsPath = '/project/netlify/database/migrations' const resolved = resolve(migrationsPath) @@ -295,7 +293,7 @@ describe('migrationPull', () => { test('logs pulled migration names', async () => { mockFetchResponse(sampleMigrations) - vi.mocked(inquirer.prompt).mockResolvedValueOnce({ confirmed: true }) + vi.mocked(confirm).mockResolvedValueOnce(true) await migrationPull({}, createMockCommand()) diff --git a/tests/unit/commands/database/db-reset.test.ts b/tests/unit/commands/database/db-reset.test.ts index 2c3da7281c4..d8942a7e3b1 100644 --- a/tests/unit/commands/database/db-reset.test.ts +++ b/tests/unit/commands/database/db-reset.test.ts @@ -6,7 +6,6 @@ const { mockConnectToDatabase, mockExecutor, mockRm, - mockPrompt, mockIsInteractive, logMessages, jsonMessages, @@ -16,7 +15,6 @@ const { const mockExecutor = {} const mockConnectToDatabase = vi.fn() const mockRm = vi.fn().mockResolvedValue(undefined) - const mockPrompt = vi.fn() const mockIsInteractive = vi.fn().mockReturnValue(true) const logMessages: string[] = [] const jsonMessages: unknown[] = [] @@ -26,7 +24,6 @@ const { mockConnectToDatabase, mockExecutor, mockRm, - mockPrompt, mockIsInteractive, logMessages, jsonMessages, @@ -50,10 +47,7 @@ vi.mock('fs/promises', async () => ({ rm: (...args: unknown[]) => mockRm(...args), })) -vi.mock('inquirer', () => ({ - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - default: { prompt: (...args: unknown[]) => mockPrompt(...args) }, -})) +vi.mock('@inquirer/prompts') vi.mock('../../../../src/utils/scripted-commands.js', () => ({ // eslint-disable-next-line @typescript-eslint/no-unsafe-return @@ -70,6 +64,8 @@ vi.mock('../../../../src/utils/command-helpers.js', async () => ({ }, })) +import { confirm } from '@inquirer/prompts' + import { reset } from '../../../../src/commands/database/db-reset.js' import { LocalDatabaseStartError } from '../../../../src/commands/database/util/db-connection.js' @@ -142,7 +138,7 @@ describe('reset', () => { }) test('discards the data directory once the user confirms', async () => { - mockPrompt.mockResolvedValue({ confirmed: true }) + vi.mocked(confirm).mockResolvedValue(true) await reset({}, createMockCommand()) @@ -150,7 +146,7 @@ describe('reset', () => { }) test('explains the underlying startup failure before prompting', async () => { - mockPrompt.mockResolvedValue({ confirmed: true }) + vi.mocked(confirm).mockResolvedValue(true) await reset({}, createMockCommand()) @@ -158,7 +154,7 @@ describe('reset', () => { }) test('keeps the data directory when the user declines', async () => { - mockPrompt.mockResolvedValue({ confirmed: false }) + vi.mocked(confirm).mockResolvedValue(false) await reset({}, createMockCommand()) @@ -169,7 +165,7 @@ describe('reset', () => { test('discards without prompting when --force is set', async () => { await reset({ force: true }, createMockCommand()) - expect(mockPrompt).not.toHaveBeenCalled() + expect(confirm).not.toHaveBeenCalled() expect(mockRm).toHaveBeenCalledWith(DB_DIRECTORY, { recursive: true, force: true }) }) @@ -190,7 +186,7 @@ describe('reset', () => { test('refuses to prompt when --json is set without --force', async () => { await expect(reset({ json: true }, createMockCommand())).rejects.toThrow('--force') - expect(mockPrompt).not.toHaveBeenCalled() + expect(confirm).not.toHaveBeenCalled() expect(mockRm).not.toHaveBeenCalled() }) diff --git a/tests/unit/commands/switch/switch.test.ts b/tests/unit/commands/switch/switch.test.ts index 9a0d14cf371..4a67d023544 100644 --- a/tests/unit/commands/switch/switch.test.ts +++ b/tests/unit/commands/switch/switch.test.ts @@ -1,13 +1,13 @@ import { beforeEach, describe, expect, test, vi } from 'vitest' -const { logMessages, mockPrompt, mockLogin } = vi.hoisted(() => ({ +const { logMessages, mockSelect, mockLogin } = vi.hoisted(() => ({ logMessages: [] as string[], - mockPrompt: vi.fn(), + mockSelect: vi.fn(), mockLogin: vi.fn(), })) -vi.mock('inquirer', () => ({ - default: { prompt: mockPrompt }, +vi.mock('@inquirer/prompts', () => ({ + select: mockSelect, })) vi.mock('../../../../src/utils/command-helpers.js', async () => ({ @@ -54,42 +54,42 @@ describe('switchCommand', () => { expect(mockSet).toHaveBeenCalledWith('userId', 'user-1') expect(logMessages.some((m) => m.includes('Alice'))).toBe(true) - expect(mockPrompt).not.toHaveBeenCalled() + expect(mockSelect).not.toHaveBeenCalled() }) test('--email falls through to prompt when no match is found', async () => { const { command } = createCommand() - mockPrompt.mockResolvedValueOnce({ accountSwitchChoice: 'Bob (bob@corp.com)' }) + mockSelect.mockResolvedValueOnce('Bob (bob@corp.com)') await switchCommand({ email: 'nobody@example.com' }, command) expect(logMessages.some((m) => m.includes('No account found matching'))).toBe(true) - expect(mockPrompt).toHaveBeenCalled() + expect(mockSelect).toHaveBeenCalled() }) test('--email does not match partial email strings', async () => { const { command } = createCommand() - mockPrompt.mockResolvedValueOnce({ accountSwitchChoice: 'Bob (bob@corp.com)' }) + mockSelect.mockResolvedValueOnce('Bob (bob@corp.com)') await switchCommand({ email: 'bob@corp' }, command) expect(logMessages.some((m) => m.includes('No account found matching'))).toBe(true) - expect(mockPrompt).toHaveBeenCalled() + expect(mockSelect).toHaveBeenCalled() }) test('without --email shows interactive prompt', async () => { const { command, mockSet } = createCommand() - mockPrompt.mockResolvedValueOnce({ accountSwitchChoice: 'Alice (alice@example.com)' }) + mockSelect.mockResolvedValueOnce('Alice (alice@example.com)') await switchCommand({}, command) - expect(mockPrompt).toHaveBeenCalled() + expect(mockSelect).toHaveBeenCalled() expect(mockSet).toHaveBeenCalledWith('userId', 'user-1') }) test('selecting login new triggers login flow', async () => { const { command } = createCommand() - mockPrompt.mockResolvedValueOnce({ accountSwitchChoice: 'I would like to login to a new account' }) + mockSelect.mockResolvedValueOnce('I would like to login to a new account') await switchCommand({}, command) diff --git a/tests/unit/recipes/ai-context/index.test.ts b/tests/unit/recipes/ai-context/index.test.ts index 2c629978b12..5638add1733 100644 --- a/tests/unit/recipes/ai-context/index.test.ts +++ b/tests/unit/recipes/ai-context/index.test.ts @@ -33,8 +33,9 @@ vi.mock('../../../../src/utils/telemetry/index.js', () => ({ track: vi.fn(), })) -vi.mock('inquirer', () => ({ - default: { prompt: vi.fn().mockResolvedValue({ consumerKey: 'cursor' }) }, +vi.mock('@inquirer/prompts', () => ({ + input: vi.fn(), + select: vi.fn().mockResolvedValue('cursor'), })) import { downloadAndWriteContextFiles } from '../../../../src/recipes/ai-context/context.js' diff --git a/tests/unit/utils/init/config-manual.test.ts b/tests/unit/utils/init/config-manual.test.ts index c720c5d67a6..e757aa89be2 100644 --- a/tests/unit/utils/init/config-manual.test.ts +++ b/tests/unit/utils/init/config-manual.test.ts @@ -3,7 +3,8 @@ import type { RepoData } from '../../../../src/utils/get-repo-data.js' import type { NetlifyAPI } from '@netlify/api' import type BaseCommand from '../../../../src/commands/base-command.js' -const mockPrompt = vi.fn() +const mockConfirm = vi.fn() +const mockInput = vi.fn() const mockLog = vi.fn() const mockExit = vi.fn() const mockCreateDeployKey = vi.fn() @@ -11,10 +12,9 @@ const mockGetBuildSettings = vi.fn() const mockSaveNetlifyToml = vi.fn() const mockSetupSite = vi.fn() -vi.mock('inquirer', () => ({ - default: { - prompt: mockPrompt, - }, +vi.mock('@inquirer/prompts', () => ({ + confirm: mockConfirm, + input: mockInput, })) vi.mock('../../../../src/utils/command-helpers.js', () => ({ @@ -46,11 +46,8 @@ describe('config-manual', () => { } as BaseCommand['netlify'], } - mockPrompt.mockResolvedValue({ - sshKeyAdded: true, - repoPath: 'git@gitlab.com:test/repo.git', - deployHookAdded: true, - }) + mockConfirm.mockResolvedValue(true) + mockInput.mockResolvedValue('git@gitlab.com:test/repo.git') mockCreateDeployKey.mockResolvedValue({ id: 'key-123', public_key: 'ssh-rsa test' }) mockGetBuildSettings.mockResolvedValue({